-Moved recCmd out of hkexsh and hkexshd into hkexsession.go (now abstract Session type)

This commit is contained in:
Russ Magee 2018-09-07 15:35:33 -07:00
parent bff56a2c61
commit 9e803ffc19
3 changed files with 163 additions and 94 deletions

88
hkexsession.go Normal file
View file

@ -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}
}

View file

@ -31,14 +31,6 @@ import (
isatty "github.com/mattn/go-isatty" 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 ( var (
wg sync.WaitGroup 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. // 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 { 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 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 ***") //fmt.Println("*** client->server cp finished ***")
// Signal other end transfer is complete // Signal other end transfer is complete
s := make([]byte, 4) s := make([]byte, 4)
binary.BigEndian.PutUint32(s, rec.status) binary.BigEndian.PutUint32(s, rec.Status())
conn.WritePacket(s, hkexnet.CSOExitStatus) conn.WritePacket(s, hkexnet.CSOExitStatus)
_, _ = conn.Read(nil /*ackByte*/) _, _ = conn.Read(nil /*ackByte*/)
} }
} else { } 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 var c *exec.Cmd
//os.Clearenv() //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). // 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 //client reader (from server) goroutine
//Read remote end's stdout //Read remote end's stdout
wg.Add(1) wg.Add(1)
@ -254,8 +246,8 @@ func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State,
} }
} }
rec.status = uint32(conn.GetStatus()) rec.SetStatus(uint32(conn.GetStatus()))
log.Println("rec.status:", rec.status) log.Println("rec.status:", rec.Status)
if isInteractive { if isInteractive {
log.Println("[* Got EOF *]") log.Println("[* Got EOF *]")
@ -523,27 +515,22 @@ func main() {
runtime.GC() runtime.GC()
} }
rec := &cmdSpec{ rec := hkexsh.NewSession(op, []byte(uname), []byte(cmdStr), []byte(authCookie),0)
op: op,
who: []byte(uname),
cmd: []byte(cmdStr),
authCookie: []byte(authCookie),
status: 0}
_, err = fmt.Fprintf(conn, "%d %d %d %d\n", _, 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.Op())
_, err = conn.Write(rec.who) _, err = conn.Write(rec.Who())
_, err = conn.Write(rec.cmd) _, err = conn.Write(rec.Cmd())
_, err = conn.Write(rec.authCookie) _, err = conn.Write(rec.AuthCookie(true))
// Read auth reply from server // Read auth reply from server
authReply := make([]byte, 1) // bool: 0 = fail, 1 = pass authReply := make([]byte, 1) // bool: 0 = fail, 1 = pass
_, err = conn.Read(authReply) _, err = conn.Read(authReply)
if authReply[0] == 0 { if authReply[0] == 0 {
fmt.Fprintln(os.Stderr, rejectUserMsg()) fmt.Fprintln(os.Stderr, rejectUserMsg())
rec.status = 255 rec.SetStatus(255)
} else { } else {
// Set up chaffing to server // Set up chaffing to server
@ -557,16 +544,17 @@ func main() {
if shellMode { if shellMode {
doShellMode(isInteractive, conn, oldState, rec) doShellMode(isInteractive, conn, oldState, rec)
} else { // copyMode } else { // copyMode
_, rec.status = doCopyMode(conn, pathIsDest, fileArgs, rec) _, s := doCopyMode(conn, pathIsDest, fileArgs, rec)
rec.SetStatus(s)
} }
if rec.status != 0 { if rec.Status() != 0 {
fmt.Fprintln(os.Stderr, "Remote end exited with status:", rec.status) fmt.Fprintln(os.Stderr, "Remote end exited with status:", rec.Status())
} }
} }
if oldState != nil { if oldState != nil {
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
} }
os.Exit(int(rec.status)) os.Exit(int(rec.Status()))
} }

View file

@ -19,7 +19,6 @@ import (
"os/exec" "os/exec"
"os/user" "os/user"
"path" "path"
"runtime"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
@ -30,14 +29,6 @@ import (
"github.com/kr/pty" "github.com/kr/pty"
) )
type cmdSpec struct {
op []byte
who []byte
cmd []byte
authCookie []byte
status int
}
/* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */
// Perform a client->server copy // Perform a client->server copy
func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffing bool) (err error, exitStatus uint32) { func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffing bool) (err error, exitStatus uint32) {
@ -394,138 +385,140 @@ func main() {
defer hc.Close() defer hc.Close()
//We use io.ReadFull() here to guarantee we consume //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 //Otherwise data will be sitting in the channel that isn't
//passed down to the command handlers. //passed down to the command handlers.
var rec cmdSpec var rec hkexsh.Session
var len1, len2, len3, len4 uint32 var len1, len2, len3, len4 uint32
n, err := fmt.Fscanf(hc, "%d %d %d %d\n", &len1, &len2, &len3, &len4) 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 { if err != nil || n < 4 {
log.Println("[Bad cmdSpec fmt]") log.Println("[Bad hkexsh.Session fmt]")
return err return err
} }
//fmt.Printf(" lens:%d %d %d %d\n", len1, len2, len3, len4) //fmt.Printf(" lens:%d %d %d %d\n", len1, len2, len3, len4)
rec.op = make([]byte, len1, len1) tmp := make([]byte, len1, len1)
_, err = io.ReadFull(hc, rec.op) _, err = io.ReadFull(hc, tmp)
if err != nil { if err != nil {
log.Println("[Bad cmdSpec.op]") log.Println("[Bad hkexsh.Session.Op]")
return err return err
} }
rec.who = make([]byte, len2, len2) rec.SetOp(tmp)
_, err = io.ReadFull(hc, rec.who)
tmp = make([]byte, len2, len2)
_, err = io.ReadFull(hc, tmp)
if err != nil { if err != nil {
log.Println("[Bad cmdSpec.who]") log.Println("[Bad hkexsh.Session.Who]")
return err return err
} }
rec.SetWho(tmp)
rec.cmd = make([]byte, len3, len3) tmp = make([]byte, len3, len3)
_, err = io.ReadFull(hc, rec.cmd) _, err = io.ReadFull(hc, tmp)
if err != nil { if err != nil {
log.Println("[Bad cmdSpec.cmd]") log.Println("[Bad hkexsh.Session.Cmd]")
return err return err
} }
rec.SetCmd(tmp)
rec.authCookie = make([]byte, len4, len4) tmp = make([]byte, len4, len4)
_, err = io.ReadFull(hc, rec.authCookie) _, err = io.ReadFull(hc, tmp)
if err != nil { if err != nil {
log.Println("[Bad cmdSpec.authCookie]") log.Println("[Bad hkexsh.Session.AuthCookie]")
return err return err
} }
rec.SetAuthCookie(tmp)
log.Printf("[cmdSpec: op:%c who:%s cmd:%s auth:****]\n", log.Printf("[hkexsh.Session: op:%c who:%s cmd:%s auth:****]\n",
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(true)), "/etc/hkexsh.passwd")
// Security scrub // Security scrub
for i := range rec.authCookie { rec.ClearAuthCookie()
rec.authCookie[i] = 0
}
runtime.GC()
// Tell client if auth was valid // Tell client if auth was valid
if valid { if valid {
hc.Write([]byte{1}) hc.Write([]byte{1})
} else { } else {
log.Println("Invalid user", string(rec.who)) log.Println("Invalid user", string(rec.Who()))
hc.Write([]byte{0}) hc.Write([]byte{0}) // ? required?
return return
} }
log.Printf("[allowedCmds:%s]\n", allowedCmds) log.Printf("[allowedCmds:%s]\n", allowedCmds)
if rec.op[0] == 'c' { if rec.Op()[0] == 'c' {
// Non-interactive command // Non-interactive command
addr := hc.RemoteAddr() addr := hc.RemoteAddr()
//hname := goutmp.GetHost(addr.String()) //hname := goutmp.GetHost(addr.String())
hname := strings.Split(addr.String(), ":")[0] hname := strings.Split(addr.String(), ":")[0]
log.Printf("[Running command for [%s@%s]]\n", rec.who, hname) log.Printf("[Running command for [%s@%s]]\n", rec.Who(), hname)
runErr, cmdStatus := runShellAs(string(rec.who), string(rec.cmd), false, hc, chaffEnabled) runErr, cmdStatus := runShellAs(string(rec.Who()), string(rec.Cmd()), false, hc, chaffEnabled)
// Returned hopefully via an EOF or exit/logout; // Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF // Clear current op so user can enter next, or EOF
rec.op[0] = 0 rec.SetOp([]byte{0})
if runErr != nil { 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 { } 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) hc.SetStatus(cmdStatus)
} }
} else if rec.op[0] == 's' { } else if rec.Op()[0] == 's' {
// Interactive session // Interactive session
addr := hc.RemoteAddr() addr := hc.RemoteAddr()
//hname := goutmp.GetHost(addr.String()) //hname := goutmp.GetHost(addr.String())
hname := strings.Split(addr.String(), ":")[0] 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) }() defer func() { goutmp.Unput_utmp(utmpx) }()
goutmp.Put_lastlog_entry("hkexsh", string(rec.who), hname) goutmp.Put_lastlog_entry("hkexsh", string(rec.Who()), hname)
runErr, cmdStatus := runShellAs(string(rec.who), string(rec.cmd), true, hc, chaffEnabled) runErr, cmdStatus := runShellAs(string(rec.Who()), string(rec.Cmd()), true, hc, chaffEnabled)
// Returned hopefully via an EOF or exit/logout; // Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF // Clear current op so user can enter next, or EOF
rec.op[0] = 0 rec.SetOp([]byte{0})
if runErr != nil { 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 { } 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) 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
log.Printf("[Client->Server copy]\n") log.Printf("[Client->Server copy]\n")
addr := hc.RemoteAddr() addr := hc.RemoteAddr()
hname := strings.Split(addr.String(), ":")[0] hname := strings.Split(addr.String(), ":")[0]
log.Printf("[Running copy for [%s@%s]]\n", rec.who, hname) log.Printf("[Running copy for [%s@%s]]\n", rec.Who(), hname)
runErr, cmdStatus := runClientToServerCopyAs(string(rec.who), hc, string(rec.cmd), chaffEnabled) runErr, cmdStatus := runClientToServerCopyAs(string(rec.Who()), hc, string(rec.Cmd()), chaffEnabled)
// Returned hopefully via an EOF or exit/logout; // Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF // Clear current op so user can enter next, or EOF
rec.op[0] = 0 rec.SetOp([]byte{0})
if runErr != nil { 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 { } 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) 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")
addr := hc.RemoteAddr() addr := hc.RemoteAddr()
hname := strings.Split(addr.String(), ":")[0] hname := strings.Split(addr.String(), ":")[0]
log.Printf("[Running copy for [%s@%s]]\n", rec.who, hname) log.Printf("[Running copy for [%s@%s]]\n", rec.Who(), hname)
runErr, cmdStatus := runServerToClientCopyAs(string(rec.who), hc, string(rec.cmd), chaffEnabled) runErr, cmdStatus := runServerToClientCopyAs(string(rec.Who()), hc, string(rec.Cmd()), chaffEnabled)
//fmt.Print("ServerToClient cmdStatus:", cmdStatus) //fmt.Print("ServerToClient cmdStatus:", cmdStatus)
// Returned hopefully via an EOF or exit/logout; // Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF // Clear current op so user can enter next, or EOF
rec.op[0] = 0 rec.SetOp([]byte{0})
if runErr != nil { 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 { } 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) hc.SetStatus(cmdStatus)
// Signal other end transfer is complete // Signal other end transfer is complete
@ -536,7 +529,7 @@ func main() {
_, _ = hc.Read(nil /*ackByte*/) _, _ = hc.Read(nil /*ackByte*/)
//fmt.Println("Got remote end ack.") //fmt.Println("Got remote end ack.")
} else { } else {
log.Println("[Bad cmdSpec]") log.Println("[Bad hkexsh.Session]")
} }
return return
}(conn) }(conn)