From 9e803ffc19461287cbd4dab59f86ebdc613cdc7a Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Fri, 7 Sep 2018 15:35:33 -0700 Subject: [PATCH] -Moved recCmd out of hkexsh and hkexshd into hkexsession.go (now abstract Session type) --- hkexsession.go | 88 ++++++++++++++++++++++++++++++++++ hkexsh/hkexsh.go | 54 +++++++++------------ hkexshd/hkexshd.go | 115 +++++++++++++++++++++------------------------ 3 files changed, 163 insertions(+), 94 deletions(-) create mode 100644 hkexsession.go diff --git a/hkexsession.go b/hkexsession.go new file mode 100644 index 0000000..50b21ef --- /dev/null +++ b/hkexsession.go @@ -0,0 +1,88 @@ +// Session info/routines for the HKExSh +// +// Copyright (c) 2017-2018 Russell Magee +// Licensed under the terms of the MIT license (see LICENSE.mit in this +// distribution) +// +// golang implementation by Russ Magee (rmagee_at_gmail.com) + +package hkexsh + +import ( + "fmt" + "runtime" +) + +type Session struct { + op []byte + who []byte + cmd []byte + authCookie []byte + status uint32 // exit status (0-255 is std UNIX status) +} + +// Output Session record as a string. Implements Stringer interface. +func (h *Session) String() string { + return fmt.Sprintf("hkexsh.Session:\nOp:%v\nWho:%v\nCmd:%v\nAuthCookie:%v\nStatus:%v", + h.op, h.who, h.cmd, h.AuthCookie(false), h.status) +} + +func (h Session) Op() []byte { + return h.op +} + +func (h *Session) SetOp(o []byte) { + h.op = o +} + +func (h Session) Who() []byte { + return h.who +} + +func (h *Session) SetWho(w []byte) { + h.who = w +} + +func (h Session) Cmd() []byte { + return h.cmd +} + +func (h *Session) SetCmd(c []byte) { + h.cmd = c +} + +func (h Session) AuthCookie(reallyShow bool) []byte { + if reallyShow { + return h.authCookie + } else { + return []byte("**REDACTED**") + } +} + +func (h *Session) SetAuthCookie(a []byte) { + h.authCookie = a +} + +func (h *Session) ClearAuthCookie() { + for i := range h.authCookie { + h.authCookie[i] = 0 + } + runtime.GC() +} + +func (h Session) Status() uint32 { + return h.status +} + +func (h *Session) SetStatus(s uint32) { + h.status = s +} + +func NewSession(op, who, cmd, authcookie []byte, status uint32) *Session { + return &Session{ + op: op, + who: who, + cmd: cmd, + authCookie: authcookie, + status: status} +} diff --git a/hkexsh/hkexsh.go b/hkexsh/hkexsh.go index 6513402..128c040 100755 --- a/hkexsh/hkexsh.go +++ b/hkexsh/hkexsh.go @@ -31,14 +31,6 @@ import ( isatty "github.com/mattn/go-isatty" ) -type cmdSpec struct { - op []byte - who []byte - cmd []byte - authCookie []byte - status uint32 // exit status (0-255 is std UNIX status) -} - var ( wg sync.WaitGroup ) @@ -100,9 +92,9 @@ 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 uint32) { +func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *hkexsh.Session) (err error, exitStatus uint32) { if remoteDest { - log.Println("local files:", files, "remote filepath:", string(rec.cmd)) + log.Println("local files:", files, "remote filepath:", string(rec.Cmd())) var c *exec.Cmd @@ -172,12 +164,12 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) //fmt.Println("*** client->server cp finished ***") // Signal other end transfer is complete s := make([]byte, 4) - binary.BigEndian.PutUint32(s, rec.status) + binary.BigEndian.PutUint32(s, rec.Status()) conn.WritePacket(s, hkexnet.CSOExitStatus) _, _ = conn.Read(nil /*ackByte*/) } } else { - log.Println("remote filepath:", string(rec.cmd), "local files:", files) + log.Println("remote filepath:", string(rec.Cmd()), "local files:", files) var c *exec.Cmd //os.Clearenv() @@ -230,7 +222,7 @@ func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, rec *cmdSpec) } // doShellMode begins an hkexsh shell session (one-shot command or interactive). -func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State, rec *cmdSpec) { +func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State, rec *hkexsh.Session) { //client reader (from server) goroutine //Read remote end's stdout wg.Add(1) @@ -254,8 +246,8 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State, } } - rec.status = uint32(conn.GetStatus()) - log.Println("rec.status:", rec.status) + rec.SetStatus(uint32(conn.GetStatus())) + log.Println("rec.status:", rec.Status) if isInteractive { log.Println("[* Got EOF *]") @@ -522,28 +514,23 @@ func main() { ab = nil runtime.GC() } - - rec := &cmdSpec{ - op: op, - who: []byte(uname), - cmd: []byte(cmdStr), - authCookie: []byte(authCookie), - status: 0} + + rec := hkexsh.NewSession(op, []byte(uname), []byte(cmdStr), []byte(authCookie),0) _, err = fmt.Fprintf(conn, "%d %d %d %d\n", - len(rec.op), len(rec.who), len(rec.cmd), len(rec.authCookie)) + len(rec.Op()), len(rec.Who()), len(rec.Cmd()), len(rec.AuthCookie(true))) - _, err = conn.Write(rec.op) - _, err = conn.Write(rec.who) - _, err = conn.Write(rec.cmd) - _, err = conn.Write(rec.authCookie) + _, err = conn.Write(rec.Op()) + _, err = conn.Write(rec.Who()) + _, err = conn.Write(rec.Cmd()) + _, err = conn.Write(rec.AuthCookie(true)) // Read auth reply from server authReply := make([]byte, 1) // bool: 0 = fail, 1 = pass _, err = conn.Read(authReply) if authReply[0] == 0 { fmt.Fprintln(os.Stderr, rejectUserMsg()) - rec.status = 255 + rec.SetStatus(255) } else { // Set up chaffing to server @@ -557,16 +544,17 @@ func main() { if shellMode { doShellMode(isInteractive, conn, oldState, rec) } else { // copyMode - _, rec.status = doCopyMode(conn, pathIsDest, fileArgs, rec) + _, s := doCopyMode(conn, pathIsDest, fileArgs, rec) + rec.SetStatus(s) } - - if rec.status != 0 { - fmt.Fprintln(os.Stderr, "Remote end exited with status:", rec.status) + + if rec.Status() != 0 { + fmt.Fprintln(os.Stderr, "Remote end exited with status:", rec.Status()) } } if oldState != nil { _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. } - os.Exit(int(rec.status)) + os.Exit(int(rec.Status())) } diff --git a/hkexshd/hkexshd.go b/hkexshd/hkexshd.go index da419d0..3cbeb51 100755 --- a/hkexshd/hkexshd.go +++ b/hkexshd/hkexshd.go @@ -19,7 +19,6 @@ import ( "os/exec" "os/user" "path" - "runtime" "strings" "sync" "syscall" @@ -30,14 +29,6 @@ import ( "github.com/kr/pty" ) -type cmdSpec struct { - op []byte - who []byte - cmd []byte - authCookie []byte - status int -} - /* -------------------------------------------------------------- */ // Perform a client->server copy func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffing bool) (err error, exitStatus uint32) { @@ -394,138 +385,140 @@ func main() { defer hc.Close() //We use io.ReadFull() here to guarantee we consume - //just the data we want for the cmdSpec, and no more. + //just the data we want for the hkexsh.Session, and no more. //Otherwise data will be sitting in the channel that isn't //passed down to the command handlers. - var rec cmdSpec + var rec hkexsh.Session var len1, len2, len3, len4 uint32 n, err := fmt.Fscanf(hc, "%d %d %d %d\n", &len1, &len2, &len3, &len4) - log.Printf("cmdSpec read:%d %d %d %d\n", len1, len2, len3, len4) + log.Printf("hkexsh.Session read:%d %d %d %d\n", len1, len2, len3, len4) if err != nil || n < 4 { - log.Println("[Bad cmdSpec fmt]") + log.Println("[Bad hkexsh.Session fmt]") return err } //fmt.Printf(" lens:%d %d %d %d\n", len1, len2, len3, len4) - rec.op = make([]byte, len1, len1) - _, err = io.ReadFull(hc, rec.op) + tmp := make([]byte, len1, len1) + _, err = io.ReadFull(hc, tmp) if err != nil { - log.Println("[Bad cmdSpec.op]") + log.Println("[Bad hkexsh.Session.Op]") return err } - rec.who = make([]byte, len2, len2) - _, err = io.ReadFull(hc, rec.who) + rec.SetOp(tmp) + + tmp = make([]byte, len2, len2) + _, err = io.ReadFull(hc, tmp) if err != nil { - log.Println("[Bad cmdSpec.who]") + log.Println("[Bad hkexsh.Session.Who]") return err } + rec.SetWho(tmp) - rec.cmd = make([]byte, len3, len3) - _, err = io.ReadFull(hc, rec.cmd) + tmp = make([]byte, len3, len3) + _, err = io.ReadFull(hc, tmp) if err != nil { - log.Println("[Bad cmdSpec.cmd]") + log.Println("[Bad hkexsh.Session.Cmd]") return err } + rec.SetCmd(tmp) - rec.authCookie = make([]byte, len4, len4) - _, err = io.ReadFull(hc, rec.authCookie) + tmp = make([]byte, len4, len4) + _, err = io.ReadFull(hc, tmp) if err != nil { - log.Println("[Bad cmdSpec.authCookie]") + log.Println("[Bad hkexsh.Session.AuthCookie]") return err } + rec.SetAuthCookie(tmp) - log.Printf("[cmdSpec: op:%c who:%s cmd:%s auth:****]\n", - rec.op[0], string(rec.who), string(rec.cmd)) + log.Printf("[hkexsh.Session: op:%c who:%s cmd:%s auth:****]\n", + 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(true)), "/etc/hkexsh.passwd") // Security scrub - for i := range rec.authCookie { - rec.authCookie[i] = 0 - } - runtime.GC() + rec.ClearAuthCookie() // Tell client if auth was valid if valid { hc.Write([]byte{1}) } else { - log.Println("Invalid user", string(rec.who)) - hc.Write([]byte{0}) + log.Println("Invalid user", string(rec.Who())) + hc.Write([]byte{0}) // ? required? return } log.Printf("[allowedCmds:%s]\n", allowedCmds) - if rec.op[0] == 'c' { + if rec.Op()[0] == 'c' { // Non-interactive command addr := hc.RemoteAddr() //hname := goutmp.GetHost(addr.String()) hname := strings.Split(addr.String(), ":")[0] - log.Printf("[Running command for [%s@%s]]\n", rec.who, hname) - runErr, cmdStatus := runShellAs(string(rec.who), string(rec.cmd), false, hc, chaffEnabled) + log.Printf("[Running command for [%s@%s]]\n", rec.Who(), hname) + runErr, cmdStatus := runShellAs(string(rec.Who()), string(rec.Cmd()), false, hc, chaffEnabled) // Returned hopefully via an EOF or exit/logout; // Clear current op so user can enter next, or EOF - rec.op[0] = 0 + rec.SetOp([]byte{0}) if runErr != nil { - 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 { - 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(cmdStatus) } - } else if rec.op[0] == 's' { + } else if rec.Op()[0] == 's' { // Interactive session addr := hc.RemoteAddr() //hname := goutmp.GetHost(addr.String()) hname := strings.Split(addr.String(), ":")[0] - log.Printf("[Running shell for [%s@%s]]\n", rec.who, hname) + log.Printf("[Running shell for [%s@%s]]\n", rec.Who(), hname) - utmpx := goutmp.Put_utmp(string(rec.who), hname) + utmpx := goutmp.Put_utmp(string(rec.Who()), hname) defer func() { goutmp.Unput_utmp(utmpx) }() - goutmp.Put_lastlog_entry("hkexsh", string(rec.who), hname) - runErr, cmdStatus := runShellAs(string(rec.who), string(rec.cmd), true, hc, chaffEnabled) + goutmp.Put_lastlog_entry("hkexsh", string(rec.Who()), hname) + runErr, cmdStatus := runShellAs(string(rec.Who()), string(rec.Cmd()), true, hc, chaffEnabled) // Returned hopefully via an EOF or exit/logout; // Clear current op so user can enter next, or EOF - rec.op[0] = 0 + rec.SetOp([]byte{0}) if runErr != nil { - 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 { - 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(cmdStatus) } - } else if rec.op[0] == 'D' { + } else if rec.Op()[0] == 'D' { // File copy (destination) operation - client copy to server log.Printf("[Client->Server copy]\n") addr := hc.RemoteAddr() hname := strings.Split(addr.String(), ":")[0] - log.Printf("[Running copy for [%s@%s]]\n", rec.who, hname) - runErr, cmdStatus := runClientToServerCopyAs(string(rec.who), hc, string(rec.cmd), chaffEnabled) + log.Printf("[Running copy for [%s@%s]]\n", rec.Who(), hname) + runErr, cmdStatus := runClientToServerCopyAs(string(rec.Who()), hc, string(rec.Cmd()), chaffEnabled) // Returned hopefully via an EOF or exit/logout; // Clear current op so user can enter next, or EOF - rec.op[0] = 0 + rec.SetOp([]byte{0}) if runErr != nil { - log.Printf("[Error spawning cp for %s@%s]\n", rec.who, hname) + log.Printf("[Error spawning cp for %s@%s]\n", rec.Who(), hname) } 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(cmdStatus) - } else if rec.op[0] == 'S' { + } else if rec.Op()[0] == 'S' { // File copy (src) operation - server copy to client log.Printf("[Server->Client copy]\n") addr := hc.RemoteAddr() hname := strings.Split(addr.String(), ":")[0] - log.Printf("[Running copy for [%s@%s]]\n", rec.who, hname) - runErr, cmdStatus := runServerToClientCopyAs(string(rec.who), hc, string(rec.cmd), chaffEnabled) + log.Printf("[Running copy for [%s@%s]]\n", rec.Who(), hname) + runErr, cmdStatus := runServerToClientCopyAs(string(rec.Who()), hc, string(rec.Cmd()), chaffEnabled) //fmt.Print("ServerToClient cmdStatus:", cmdStatus) // Returned hopefully via an EOF or exit/logout; // Clear current op so user can enter next, or EOF - rec.op[0] = 0 + rec.SetOp([]byte{0}) if runErr != nil { - log.Printf("[Error spawning cp for %s@%s]\n", rec.who, hname) + log.Printf("[Error spawning cp for %s@%s]\n", rec.Who(), hname) } 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(cmdStatus) // Signal other end transfer is complete @@ -536,7 +529,7 @@ func main() { _, _ = hc.Read(nil /*ackByte*/) //fmt.Println("Got remote end ack.") } else { - log.Println("[Bad cmdSpec]") + log.Println("[Bad hkexsh.Session]") } return }(conn)