-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"
)
// 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 (
CSONone = iota // No error, normal packet
CSOHmacInvalid // HMAC mismatch detected on remote end
@ -77,7 +87,7 @@ type (
chaff ChaffConfig
closeStat *uint8 // close status
closeStat *uint32 // close status (CSOExitStatus)
r cipher.Stream //read cipherStream
rm hash.Hash
w cipher.Stream //write cipherStream
@ -86,11 +96,11 @@ type (
}
)
func (hc Conn) GetStatus() uint8 {
func (hc Conn) GetStatus() uint32 {
return *hc.closeStat
}
func (hc *Conn) SetStatus(stat uint8) {
func (hc *Conn) SetStatus(stat uint32) {
*hc.closeStat = stat
//fmt.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
}
// 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...)
// 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.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
}
// Close a hkex.Conn
func (hc *Conn) Close() (err error) {
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()
log.Println("[Conn Closing]")
return
@ -308,13 +320,13 @@ func (hl *HKExListener) Accept() (hc Conn, err error) {
// Open raw Conn c
c, err := hl.l.Accept()
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}
return hc, err
}
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)}
// 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}
} else if ctrlStatOp == CSOExitStatus {
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 hc.GetStatus() != 0 {
//! log.Print("CSOExitStatus:", hc.GetStatus())
@ -446,7 +458,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
//!}
} else {
log.Println("[truncated payload, cannot determine CSOExitStatus]")
*hc.closeStat = 98
*hc.closeStat = CSETruncCSO
}
} else {
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]
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]")
} else {
// Log alert if hmac didn't match, corrupted channel

View File

@ -9,6 +9,7 @@ package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"io"
@ -34,7 +35,7 @@ type cmdSpec struct {
who []byte
cmd []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 (
@ -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.
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 {
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
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
exitStatus = uint32(status.ExitStatus())
log.Printf("Exit Status: %d", exitStatus)
fmt.Print(stdErrBuffer)
}
@ -169,7 +170,9 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec)
}
//fmt.Println("*** client->server cp finished ***")
// 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*/)
}
} 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
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
exitStatus = uint32(status.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;
// otherwise, return remote status if nonzero
if exitStatus == 0 {
exitStatus = int(conn.GetStatus())
exitStatus = uint32(conn.GetStatus())
}
//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)
if isInteractive {
@ -282,7 +285,7 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State,
log.Println(outerr)
fmt.Println(outerr)
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
os.Exit(255)
os.Exit(254)
}
log.Println("[Sent EOF]")
}()
@ -549,7 +552,15 @@ func main() {
}
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 (
"bytes"
"encoding/binary"
"flag"
"fmt"
"io"
@ -40,7 +41,7 @@ type cmdSpec struct {
/* -------------------------------------------------------------- */
// 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)
var uid, gid uint32
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
if err != nil {
log.Printf("Command finished with error: %v", err)
return err, 253 // !?
return err, hkexnet.CSEExecFail // !?
} else {
if err := c.Wait(); err != nil {
//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
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
exitStatus = uint32(status.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
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)
var uid, gid uint32
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
if err != nil {
log.Printf("Command finished with error: %v", err)
return err, 253 // !?
return err, hkexnet.CSEExecFail // !?
} else {
if err := c.Wait(); err != nil {
//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
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
exitStatus = uint32(status.ExitStatus())
log.Printf("Exit Status: %d", exitStatus)
// TODO: send stdErrBuffer to client via specific packet
// 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
//
// 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
u, _ := user.Lookup(who)
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.
ptmx, err := pty.Start(c) // returns immediately with ptmx file
if err != nil {
return err, 0
return err, hkexnet.CSEPtyExecFail
}
// Make sure to close the pty at the end.
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
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
exitStatus = uint32(status.ExitStatus())
log.Printf("Exit Status: %d", exitStatus)
}
}
conn.SetStatus(uint8(exitStatus))
conn.SetStatus(exitStatus)
}
wg.Wait() // Wait on pty->stdout completion to client
}
@ -446,6 +447,7 @@ func main() {
rec.op[0], string(rec.who), string(rec.cmd))
valid, allowedCmds := hkexsh.AuthUser(string(rec.who), string(rec.authCookie), "/etc/hkexsh.passwd")
// Security scrub
for i := range rec.authCookie {
rec.authCookie[i] = 0
@ -454,6 +456,14 @@ func main() {
if !valid {
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()))
return
}
@ -474,7 +484,7 @@ func main() {
log.Printf("[Error spawning cmd for %s@%s]\n", rec.who, hname)
} else {
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' {
// Interactive session
@ -494,7 +504,7 @@ func main() {
log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname)
} else {
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' {
// File copy (destination) operation - client copy to server
@ -511,7 +521,7 @@ func main() {
} else {
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' {
// File copy (src) operation - server copy to client
log.Printf("[Server->Client copy]\n")
@ -528,9 +538,11 @@ func main() {
} else {
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
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.")
_, _ = hc.Read(nil /*ackByte*/)
//fmt.Println("Got remote end ack.")