-Converted exit status to uint32 (0-255: UNIX exit codes), above for OOB (out-of-band) status

-Failed auth for shell logins now returns extended code CSEBadAuth to client
This commit is contained in:
Russ Magee 2018-09-06 13:50:56 -07:00
parent 8a24fb113f
commit 9ff35a69fe
3 changed files with 71 additions and 36 deletions

View File

@ -34,6 +34,16 @@ import (
"blitter.com/go/hkexsh/herradurakex" "blitter.com/go/hkexsh/herradurakex"
) )
// const CSExtendedCode - extended (>255 UNIX exit status) codes
// This indicate channel-related or internal errors
const (
CSEBadAuth = 1024 // failed login
CSETruncCSO // No CSOExitStatus in payload
CSEStillOpen // Channel closed unexpectedly
CSEExecFail // cmd.Start() (exec) failed
CSEPtyExecFail // pty.Start() (exec w/pty) failed
)
const ( const (
CSONone = iota // No error, normal packet CSONone = iota // No error, normal packet
CSOHmacInvalid // HMAC mismatch detected on remote end CSOHmacInvalid // HMAC mismatch detected on remote end
@ -77,7 +87,7 @@ type (
chaff ChaffConfig chaff ChaffConfig
closeStat *uint8 // close status closeStat *uint32 // close status (CSOExitStatus)
r cipher.Stream //read cipherStream r cipher.Stream //read cipherStream
rm hash.Hash rm hash.Hash
w cipher.Stream //write cipherStream w cipher.Stream //write cipherStream
@ -86,11 +96,11 @@ type (
} }
) )
func (hc Conn) GetStatus() uint8 { func (hc Conn) GetStatus() uint32 {
return *hc.closeStat return *hc.closeStat
} }
func (hc *Conn) SetStatus(stat uint8) { func (hc *Conn) SetStatus(stat uint32) {
*hc.closeStat = stat *hc.closeStat = stat
//fmt.Println("closeStat:", *hc.closeStat) //fmt.Println("closeStat:", *hc.closeStat)
log.Println("closeStat:", *hc.closeStat) log.Println("closeStat:", *hc.closeStat)
@ -177,7 +187,7 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e
return nil, err return nil, err
} }
// Init hkexnet.Conn hc over net.Conn c // Init hkexnet.Conn hc over net.Conn c
hc = &Conn{m: &sync.Mutex{}, c: c, closeStat: new(uint8), h: hkex.New(0, 0), dBuf: new(bytes.Buffer)} hc = &Conn{m: &sync.Mutex{}, c: c, closeStat: new(uint32), h: hkex.New(0, 0), dBuf: new(bytes.Buffer)}
hc.applyConnExtensions(extensions...) hc.applyConnExtensions(extensions...)
// Send hkexnet.Conn parameters to remote side // Send hkexnet.Conn parameters to remote side
@ -205,14 +215,16 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e
hc.r, hc.rm, err = hc.getStream(hc.h.FA()) hc.r, hc.rm, err = hc.getStream(hc.h.FA())
hc.w, hc.wm, err = hc.getStream(hc.h.FA()) hc.w, hc.wm, err = hc.getStream(hc.h.FA())
*hc.closeStat = 99 // open or prematurely-closed status *hc.closeStat = CSEStillOpen // open or prematurely-closed status
return return
} }
// Close a hkex.Conn // Close a hkex.Conn
func (hc *Conn) Close() (err error) { func (hc *Conn) Close() (err error) {
hc.DisableChaff() hc.DisableChaff()
hc.WritePacket([]byte{byte(*hc.closeStat)}, CSOExitStatus) s := make([]byte, 4)
binary.BigEndian.PutUint32(s, *hc.closeStat)
hc.WritePacket(s, CSOExitStatus)
err = hc.c.Close() err = hc.c.Close()
log.Println("[Conn Closing]") log.Println("[Conn Closing]")
return return
@ -308,13 +320,13 @@ func (hl *HKExListener) Accept() (hc Conn, err error) {
// Open raw Conn c // Open raw Conn c
c, err := hl.l.Accept() c, err := hl.l.Accept()
if err != nil { if err != nil {
hc := Conn{m: &sync.Mutex{}, c: nil, h: nil, closeStat: new(uint8), cipheropts: 0, opts: 0, hc := Conn{m: &sync.Mutex{}, c: nil, h: nil, closeStat: new(uint32), cipheropts: 0, opts: 0,
r: nil, w: nil} r: nil, w: nil}
return hc, err return hc, err
} }
log.Println("[Accepted]") log.Println("[Accepted]")
hc = Conn{m: &sync.Mutex{}, c: c, h: hkex.New(0, 0), closeStat: new(uint8), WinCh: make(chan WinSize, 1), hc = Conn{m: &sync.Mutex{}, c: c, h: hkex.New(0, 0), closeStat: new(uint32), WinCh: make(chan WinSize, 1),
dBuf: new(bytes.Buffer)} dBuf: new(bytes.Buffer)}
// Read in hkexnet.Conn parameters over raw Conn c // Read in hkexnet.Conn parameters over raw Conn c
@ -438,7 +450,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
hc.WinCh <- WinSize{hc.Rows, hc.Cols} hc.WinCh <- WinSize{hc.Rows, hc.Cols}
} else if ctrlStatOp == CSOExitStatus { } else if ctrlStatOp == CSOExitStatus {
if len(payloadBytes) > 0 { if len(payloadBytes) > 0 {
hc.SetStatus(payloadBytes[0]) hc.SetStatus(binary.BigEndian.Uint32(payloadBytes))
//!// If remote end is closing with an error, reply we're closing ours //!// If remote end is closing with an error, reply we're closing ours
//!if hc.GetStatus() != 0 { //!if hc.GetStatus() != 0 {
//! log.Print("CSOExitStatus:", hc.GetStatus()) //! log.Print("CSOExitStatus:", hc.GetStatus())
@ -446,7 +458,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
//!} //!}
} else { } else {
log.Println("[truncated payload, cannot determine CSOExitStatus]") log.Println("[truncated payload, cannot determine CSOExitStatus]")
*hc.closeStat = 98 *hc.closeStat = CSETruncCSO
} }
} else { } else {
hc.dBuf.Write(payloadBytes) hc.dBuf.Write(payloadBytes)
@ -458,7 +470,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
hTmp := hc.rm.Sum(nil)[0:4] hTmp := hc.rm.Sum(nil)[0:4]
log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp) log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp)
if *hc.closeStat > 90 { if *hc.closeStat == CSETruncCSO {
log.Println("[cannot verify HMAC]") log.Println("[cannot verify HMAC]")
} else { } else {
// Log alert if hmac didn't match, corrupted channel // Log alert if hmac didn't match, corrupted channel

View File

@ -9,6 +9,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/binary"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -34,7 +35,7 @@ type cmdSpec struct {
who []byte who []byte
cmd []byte cmd []byte
authCookie []byte authCookie []byte
status int // UNIX exit status is uint8, but os.Exit() wants int status uint32 // exit status (0-255 is std UNIX status)
} }
var ( var (
@ -98,7 +99,7 @@ func parseNonSwitchArgs(a []string) (user, host, path string, isDest bool, other
} }
// doCopyMode begins a secure hkexsh local<->remote file copy operation. // doCopyMode begins a secure hkexsh local<->remote file copy operation.
func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) (err error, exitStatus int) { func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) (err error, exitStatus uint32) {
if remoteDest { if remoteDest {
fmt.Println("local files:", files, "remote filepath:", string(rec.cmd)) fmt.Println("local files:", files, "remote filepath:", string(rec.cmd))
@ -161,7 +162,7 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec)
// defined for both Unix and Windows and in both cases has // defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature. // an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus() exitStatus = uint32(status.ExitStatus())
log.Printf("Exit Status: %d", exitStatus) log.Printf("Exit Status: %d", exitStatus)
fmt.Print(stdErrBuffer) fmt.Print(stdErrBuffer)
} }
@ -169,7 +170,9 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec)
} }
//fmt.Println("*** client->server cp finished ***") //fmt.Println("*** client->server cp finished ***")
// Signal other end transfer is complete // Signal other end transfer is complete
conn.WritePacket([]byte{byte( /*255*/ rec.status)}, hkexnet.CSOExitStatus) s := make([]byte, 4)
binary.BigEndian.PutUint32(s, rec.status)
conn.WritePacket(s, hkexnet.CSOExitStatus)
_, _ = conn.Read(nil /*ackByte*/) _, _ = conn.Read(nil /*ackByte*/)
} }
} else { } else {
@ -209,7 +212,7 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec)
// defined for both Unix and Windows and in both cases has // defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature. // an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus() exitStatus = uint32(status.ExitStatus())
log.Printf("Exit Status: %d", exitStatus) log.Printf("Exit Status: %d", exitStatus)
} }
} }
@ -217,7 +220,7 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec)
// return local status, if nonzero; // return local status, if nonzero;
// otherwise, return remote status if nonzero // otherwise, return remote status if nonzero
if exitStatus == 0 { if exitStatus == 0 {
exitStatus = int(conn.GetStatus()) exitStatus = uint32(conn.GetStatus())
} }
//fmt.Println("*** server->client cp finished ***") //fmt.Println("*** server->client cp finished ***")
} }
@ -250,7 +253,7 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State,
} }
} }
rec.status = int(conn.GetStatus()) rec.status = uint32(conn.GetStatus())
log.Println("rec.status:", rec.status) log.Println("rec.status:", rec.status)
if isInteractive { if isInteractive {
@ -282,7 +285,7 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State,
log.Println(outerr) log.Println(outerr)
fmt.Println(outerr) fmt.Println(outerr)
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
os.Exit(255) os.Exit(254)
} }
log.Println("[Sent EOF]") log.Println("[Sent EOF]")
}() }()
@ -549,7 +552,15 @@ func main() {
} }
if rec.status != 0 { if rec.status != 0 {
fmt.Fprintln(os.Stderr, "Remote end exited with status:", rec.status) fmt.Fprint(os.Stderr, "Remote end ")
if rec.status == hkexnet.CSEBadAuth {
// shell exit status can't hold CSEBadAuth (uint32)
rec.status = 255
fmt.Fprintln(os.Stderr, "replied: bad auth")
} else {
fmt.Fprintln(os.Stderr, "exited with status:", rec.status)
}
} }
os.Exit(rec.status) os.Exit(int(rec.status))
} }

View File

@ -9,6 +9,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/binary"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -40,7 +41,7 @@ type cmdSpec struct {
/* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */
// Perform a client->server copy // Perform a client->server copy
func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffing bool) (err error, exitStatus int) { func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffing bool) (err error, exitStatus uint32) {
u, _ := user.Lookup(who) u, _ := user.Lookup(who)
var uid, gid uint32 var uid, gid uint32
fmt.Sscanf(u.Uid, "%d", &uid) fmt.Sscanf(u.Uid, "%d", &uid)
@ -99,7 +100,7 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffi
err = c.Start() // returns immediately err = c.Start() // returns immediately
if err != nil { if err != nil {
log.Printf("Command finished with error: %v", err) log.Printf("Command finished with error: %v", err)
return err, 253 // !? return err, hkexnet.CSEExecFail // !?
} else { } else {
if err := c.Wait(); err != nil { if err := c.Wait(); err != nil {
//fmt.Println("*** c.Wait() done ***") //fmt.Println("*** c.Wait() done ***")
@ -111,7 +112,7 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffi
// defined for both Unix and Windows and in both cases has // defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature. // an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus() exitStatus = uint32(status.ExitStatus())
log.Printf("Exit Status: %d", exitStatus) log.Printf("Exit Status: %d", exitStatus)
} }
} }
@ -122,7 +123,7 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffi
} }
// Perform a server->client copy // Perform a server->client copy
func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaffing bool) (err error, exitStatus int) { func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaffing bool) (err error, exitStatus uint32) {
u, _ := user.Lookup(who) u, _ := user.Lookup(who)
var uid, gid uint32 var uid, gid uint32
fmt.Sscanf(u.Uid, "%d", &uid) fmt.Sscanf(u.Uid, "%d", &uid)
@ -179,7 +180,7 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf
err = c.Start() // returns immediately err = c.Start() // returns immediately
if err != nil { if err != nil {
log.Printf("Command finished with error: %v", err) log.Printf("Command finished with error: %v", err)
return err, 253 // !? return err, hkexnet.CSEExecFail // !?
} else { } else {
if err := c.Wait(); err != nil { if err := c.Wait(); err != nil {
//fmt.Println("*** c.Wait() done ***") //fmt.Println("*** c.Wait() done ***")
@ -191,7 +192,7 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf
// defined for both Unix and Windows and in both cases has // defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature. // an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus() exitStatus = uint32(status.ExitStatus())
log.Printf("Exit Status: %d", exitStatus) log.Printf("Exit Status: %d", exitStatus)
// TODO: send stdErrBuffer to client via specific packet // TODO: send stdErrBuffer to client via specific packet
// type so it can inform user // type so it can inform user
@ -209,7 +210,7 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf
// Run a command (via default shell) as a specific user // Run a command (via default shell) as a specific user
// //
// Uses ptys to support commands which expect a terminal. // Uses ptys to support commands which expect a terminal.
func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, chaffing bool) (err error, exitStatus int) { func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, chaffing bool) (err error, exitStatus uint32) {
var wg sync.WaitGroup var wg sync.WaitGroup
u, _ := user.Lookup(who) u, _ := user.Lookup(who)
var uid, gid uint32 var uid, gid uint32
@ -248,7 +249,7 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
// Start the command with a pty. // Start the command with a pty.
ptmx, err := pty.Start(c) // returns immediately with ptmx file ptmx, err := pty.Start(c) // returns immediately with ptmx file
if err != nil { if err != nil {
return err, 0 return err, hkexnet.CSEPtyExecFail
} }
// Make sure to close the pty at the end. // Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort. defer func() { _ = ptmx.Close() }() // Best effort.
@ -312,11 +313,11 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
// defined for both Unix and Windows and in both cases has // defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature. // an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus() exitStatus = uint32(status.ExitStatus())
log.Printf("Exit Status: %d", exitStatus) log.Printf("Exit Status: %d", exitStatus)
} }
} }
conn.SetStatus(uint8(exitStatus)) conn.SetStatus(exitStatus)
} }
wg.Wait() // Wait on pty->stdout completion to client wg.Wait() // Wait on pty->stdout completion to client
} }
@ -446,6 +447,7 @@ func main() {
rec.op[0], string(rec.who), string(rec.cmd)) rec.op[0], string(rec.who), string(rec.cmd))
valid, allowedCmds := hkexsh.AuthUser(string(rec.who), string(rec.authCookie), "/etc/hkexsh.passwd") valid, allowedCmds := hkexsh.AuthUser(string(rec.who), string(rec.authCookie), "/etc/hkexsh.passwd")
// Security scrub // Security scrub
for i := range rec.authCookie { for i := range rec.authCookie {
rec.authCookie[i] = 0 rec.authCookie[i] = 0
@ -454,6 +456,14 @@ func main() {
if !valid { if !valid {
log.Println("Invalid user", string(rec.who)) log.Println("Invalid user", string(rec.who))
// Signal other end auth failed
rec.status = hkexnet.CSEBadAuth
hc.SetStatus(hkexnet.CSEBadAuth)
s := make([]byte, 4)
binary.BigEndian.PutUint32(s, hkexnet.CSEBadAuth)
hc.WritePacket(s, hkexnet.CSOExitStatus)
hc.Write([]byte(rejectUserMsg())) hc.Write([]byte(rejectUserMsg()))
return return
} }
@ -474,7 +484,7 @@ func main() {
log.Printf("[Error spawning cmd for %s@%s]\n", rec.who, hname) log.Printf("[Error spawning cmd for %s@%s]\n", rec.who, hname)
} else { } else {
log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus) log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
hc.SetStatus(uint8(cmdStatus)) hc.SetStatus(cmdStatus)
} }
} else if rec.op[0] == 's' { } else if rec.op[0] == 's' {
// Interactive session // Interactive session
@ -494,7 +504,7 @@ func main() {
log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname) log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname)
} else { } else {
log.Printf("[Shell completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus) log.Printf("[Shell completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
hc.SetStatus(uint8(cmdStatus)) hc.SetStatus(cmdStatus)
} }
} else if rec.op[0] == 'D' { } else if rec.op[0] == 'D' {
// File copy (destination) operation - client copy to server // File copy (destination) operation - client copy to server
@ -511,7 +521,7 @@ func main() {
} else { } else {
log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus) log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
} }
hc.SetStatus(uint8(cmdStatus)) hc.SetStatus(cmdStatus)
} else if rec.op[0] == 'S' { } else if rec.op[0] == 'S' {
// File copy (src) operation - server copy to client // File copy (src) operation - server copy to client
log.Printf("[Server->Client copy]\n") log.Printf("[Server->Client copy]\n")
@ -528,9 +538,11 @@ func main() {
} else { } else {
log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus) log.Printf("[Command completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
} }
hc.SetStatus(uint8(cmdStatus)) hc.SetStatus(cmdStatus)
// Signal other end transfer is complete // Signal other end transfer is complete
hc.WritePacket([]byte{byte( /*255*/ cmdStatus)}, hkexnet.CSOExitStatus) s := make([]byte, 4)
binary.BigEndian.PutUint32(s, cmdStatus)
hc.WritePacket(s, hkexnet.CSOExitStatus)
//fmt.Println("Waiting for EOF from other end.") //fmt.Println("Waiting for EOF from other end.")
_, _ = hc.Read(nil /*ackByte*/) _, _ = hc.Read(nil /*ackByte*/)
//fmt.Println("Got remote end ack.") //fmt.Println("Got remote end ack.")