Basic server-side recording of exitStatus of pty(cmd).

TODO: sending of exitStatus to client and client handling of said packet via
  a WritePacket() with unique existStatus op.
This commit is contained in:
Russ Magee 2018-06-29 16:54:20 -07:00
parent aa48314ee9
commit c64797f2d9

View file

@ -80,7 +80,7 @@ func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) {
// 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 hkexsh.Conn, chaffing bool) (err error) { func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn, chaffing bool) (err error, exitStatus int) {
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)
@ -117,46 +117,65 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn, chaf
// 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 return err, 0
} }
// 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.
// Watch for term resizes
go func() {
for sz := range conn.WinCh {
log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols})
}
}()
// Copy stdin to the pty.. (bgnd goroutine)
go func() {
_, e := io.Copy(ptmx, conn)
if e != nil {
log.Printf("** std->pty ended **\n")
return
}
}()
if chaffing {
conn.EnableChaff()
}
defer conn.DisableChaff()
defer conn.ShutdownChaff()
// ..and the pty to stdout.
_, e := io.Copy(conn, ptmx)
if e != nil {
log.Printf("** pty->stdout ended **\n")
return
}
//err = c.Run() // returns when c finishes.
log.Printf("[%s]\n", cmd) log.Printf("[%s]\n", cmd)
if err != nil { if err != nil {
log.Printf("Command finished with error: %v", err) log.Printf("Command finished with error: %v", err)
} else {
// Watch for term resizes
go func() {
for sz := range conn.WinCh {
log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols})
}
}()
// Copy stdin to the pty.. (bgnd goroutine)
go func() {
_, e := io.Copy(ptmx, conn)
if e != nil {
log.Printf("** std->pty ended **\n")
return
}
}()
if chaffing {
conn.EnableChaff()
}
defer conn.DisableChaff()
defer conn.ShutdownChaff()
// ..and the pty to stdout.
go func() {
_, e := io.Copy(conn, ptmx)
if e != nil {
log.Printf("** pty->stdout ended **\n")
return
}
}()
// The above io.Copy() will exit when the command attached
// to the pty exits
if err := c.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// 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()
log.Printf("Exit Status: %d", exitStatus)
}
}
}
} }
return return
} }
@ -303,11 +322,15 @@ func main() {
hname := goutmp.GetHost(addr.String()) hname := goutmp.GetHost(addr.String())
log.Printf("[Running command for [%s@%s]]\n", rec.who, hname) log.Printf("[Running command for [%s@%s]]\n", rec.who, hname)
runShellAs(string(rec.who), string(rec.cmd), false, conn, chaffEnabled) runErr, cmdStatus := runShellAs(string(rec.who), string(rec.cmd), false, conn, 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.op[0] = 0
log.Printf("[Command completed for [%s@%s]\n", rec.who, hname) if runErr != nil {
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)
}
} else if rec.op[0] == 's' { } else if rec.op[0] == 's' {
// Interactive session // Interactive session
addr := c.RemoteAddr() addr := c.RemoteAddr()
@ -317,11 +340,15 @@ func main() {
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)
runShellAs(string(rec.who), string(rec.cmd), true, conn, chaffEnabled) runErr, cmdStatus := runShellAs(string(rec.who), string(rec.cmd), true, conn, 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.op[0] = 0
log.Printf("[Exiting shell for [%s@%s]]\n", rec.who, hname) if runErr != nil {
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)
}
} else { } else {
log.Println("[Bad cmdSpec]") log.Println("[Bad cmdSpec]")
} }