From 7867f84b87bd98bc31c93044eb08b9677c9e29a4 Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Fri, 24 Aug 2018 18:50:45 -0700 Subject: [PATCH] WIP: server->client copy primitively functional; TODO client->server copy --- hkexsh/hkexsh.go | 101 +++++++++++++++++++++++++++++++++++++++++---- hkexshd/hkexshd.go | 53 ++++++++++++++---------- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/hkexsh/hkexsh.go b/hkexsh/hkexsh.go index c69b6a2..7167849 100755 --- a/hkexsh/hkexsh.go +++ b/hkexsh/hkexsh.go @@ -19,6 +19,7 @@ import ( "runtime" "strings" "sync" + "syscall" hkexsh "blitter.com/go/hkexsh" "blitter.com/go/hkexsh/hkexnet" @@ -101,17 +102,101 @@ func parseNonSwitchArgs(a []string, dp string) (user, host, port, path string, i } // doCopyMode begins a secure hkexsh local<->remote file copy operation. -func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, recurs bool, rec *cmdSpec) { - // TODO: Bring in runShellAs(), stripped down, from hkexshd - // and build either side of tar pipeline: names? - // runTarSrc(), runTarSink() ? +func doCopyMode(conn *hkexnet.Conn, remoteDest bool, files string, recurs bool, rec *cmdSpec) (err error, exitStatus int) { if remoteDest { fmt.Println("local files:", files, "remote filepath:", string(rec.cmd)) - fmt.Fprintf(conn, "copyMode remoteDest TODO\n") + fmt.Fprintf(conn, "copyMode remoteDest ...\n") + + var c *exec.Cmd + + //os.Clearenv() + //os.Setenv("HOME", u.HomeDir) + //os.Setenv("TERM", "vt102") // TODO: server or client option? + + cmdName := "/bin/tar" + cmdArgs := []string{"-cz", "-f", "/dev/stdout", files} + fmt.Printf("[%v %v]\n", cmdName, cmdArgs) + // NOTE the lack of quotes around --xform option's sed expression. + // When args are passed in exec() format, no quoting is required + // (as this isn't input from a shell) (right? -rlm 20180823) + //cmdArgs := []string{"-xvz", "-C", files, `--xform=s#.*/\(.*\)#\1#`} + c = exec.Command(cmdName, cmdArgs...) + c.Stdout = conn + + // Start the command (no pty) + err = c.Start() // returns immediately + if err != nil { + fmt.Println(err) + //log.Fatal(err) + } else { + 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) + } + } + } + fmt.Println("*** client->server cp finished ***") + } } else { fmt.Println("remote filepath:", string(rec.cmd), "local files:", files) - fmt.Fprintf(conn, "copyMode localDest TODO\n") + fmt.Fprintf(conn, "copyMode localDest ...\n") + var c *exec.Cmd + + //os.Clearenv() + //os.Setenv("HOME", u.HomeDir) + //os.Setenv("TERM", "vt102") // TODO: server or client option? + + cmdName := "/bin/tar" + destPath := files + //if path.IsAbs(files) { + // destPath := files + //} else { + // destPath := strings.Join({os.Getenv("PWD"),files}, os.PathSeparator) + //} + + cmdArgs := []string{"-xvz", "-C", destPath} + fmt.Printf("[%v %v]\n", cmdName, cmdArgs) + // NOTE the lack of quotes around --xform option's sed expression. + // When args are passed in exec() format, no quoting is required + // (as this isn't input from a shell) (right? -rlm 20180823) + //cmdArgs := []string{"-xvz", "-C", destPath, `--xform=s#.*/\(.*\)#\1#`} + c = exec.Command(cmdName, cmdArgs...) + c.Stdin = conn + c.Stdout = os.Stdout + c.Stderr = os.Stderr + + // Start the command (no pty) + err = c.Start() // returns immediately + if err != nil { + fmt.Println(err) + //log.Fatal(err) + } else { + 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) + } + } + } + fmt.Println("*** server->client cp finished ***") + } } + return } // doShellMode begins an hkexsh shell session (one-shot command or interactive). @@ -256,7 +341,7 @@ func main() { // -else flatten otherArgs into space-delim list => copySrc if pathIsDest { if len(otherArgs) == 0 { - log.Fatal("ERROR: Must specify at least one src path for copy") + log.Fatal("ERROR: Must specify at least one dest path for copy") } else { for _, v := range otherArgs { copySrc = append(copySrc, ' ') @@ -267,7 +352,7 @@ func main() { } } else { if len(otherArgs) == 0 { - log.Fatal("ERROR: Must specify dest path for copy") + log.Fatal("ERROR: Must specify src path for copy") } else if len(otherArgs) == 1 { copyDst = otherArgs[0] if strings.Contains(copyDst, "*") || strings.Contains(copyDst, "?") { diff --git a/hkexshd/hkexshd.go b/hkexshd/hkexshd.go index 98d6649..2ce9319 100755 --- a/hkexshd/hkexshd.go +++ b/hkexshd/hkexshd.go @@ -61,7 +61,8 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, destPath string, cha // NOTE the lack of quotes around --xform option's sed expression. // When args are passed in exec() format, no quoting is required // (as this isn't input from a shell) (right? -rlm 20180823) - cmdArgs := []string{"-xzv", "-C", destPath, `--xform=s#.*/\(.*\)#\1#`} + cmdArgs := []string{"-xvz", "-C", destPath} + //cmdArgs := []string{"-xvz", "-C", destPath, `--xform=s#.*/\(.*\)#\1#`} c = exec.Command(cmdName, cmdArgs...) //If os.Clearenv() isn't called by server above these will be seen in the @@ -71,8 +72,14 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, destPath string, cha c.SysProcAttr = &syscall.SysProcAttr{} c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid} c.Stdin = conn - c.Stdout = conn - c.Stderr = conn + //c.Stdout = conn + //c.Stderr = conn + + if chaffing { + conn.EnableChaff() + } + defer conn.DisableChaff() + defer conn.ShutdownChaff() // Start the command (no pty) log.Printf("[%v %v]\n", cmdName, cmdArgs) @@ -81,12 +88,6 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, destPath string, cha log.Printf("Command finished with error: %v", err) return err, 253 // !? } else { - if chaffing { - conn.EnableChaff() - } - defer conn.DisableChaff() - defer conn.ShutdownChaff() - if err := c.Wait(); err != nil { fmt.Println("*** c.Wait() done ***") if exiterr, ok := err.(*exec.ExitError); ok { @@ -102,8 +103,9 @@ func runClientToServerCopyAs(who string, conn hkexnet.Conn, destPath string, cha } } } + fmt.Println("*** client->server cp finished ***") + return } - return } // Perform a server->client copy @@ -136,10 +138,16 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf c.Dir = u.HomeDir c.SysProcAttr = &syscall.SysProcAttr{} c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid} - c.Stdin = conn c.Stdout = conn c.Stderr = conn + if chaffing { + conn.EnableChaff() + } + //defer conn.Close() + defer conn.DisableChaff() + defer conn.ShutdownChaff() + // Start the command (no pty) log.Printf("[%v %v]\n", cmdName, cmdArgs) err = c.Start() // returns immediately @@ -147,12 +155,6 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf log.Printf("Command finished with error: %v", err) return err, 253 // !? } else { - if chaffing { - conn.EnableChaff() - } - defer conn.DisableChaff() - defer conn.ShutdownChaff() - if err := c.Wait(); err != nil { fmt.Println("*** c.Wait() done ***") if exiterr, ok := err.(*exec.ExitError); ok { @@ -168,8 +170,9 @@ func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaf } } } + fmt.Println("*** server->client cp finished ***") + return } - return } // Run a command (via default shell) as a specific user @@ -467,10 +470,13 @@ func main() { 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) + // Returned hopefully via an EOF or exit/logout; + // Clear current op so user can enter next, or EOF + rec.op[0] = 0 if runErr != nil { - log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname) + log.Printf("[Error spawning cp for %s@%s]\n", rec.who, hname) } else { - log.Printf("[Shell 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)) } } else if rec.op[0] == 'S' { @@ -482,10 +488,13 @@ func main() { 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) + // Returned hopefully via an EOF or exit/logout; + // Clear current op so user can enter next, or EOF + rec.op[0] = 0 if runErr != nil { - log.Printf("[Error spawning shell for %s@%s]\n", rec.who, hname) + log.Printf("[Error spawning cp for %s@%s]\n", rec.who, hname) } else { - log.Printf("[Shell 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)) } } else {