mirror of https://gogs.blitter.com/RLabs/xs
				
				
				
			Merge branch 'hkexcp-proto'
This commit is contained in:
		
						commit
						45d270b03e
					
				| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
## Template for copying files from local to remote site, destdir DEST:
 | 
			
		||||
tar -cz -f - testdir/sub1/bar.txt | \
 | 
			
		||||
  tar -xzv -C DEST --xform="s#.*/\(.*\)#\1#"
 | 
			
		||||
 | 
			
		||||
# Note the --xform= option will strip leading path components from the file
 | 
			
		||||
# on extraction (ie., throw away dirtree info when copying into remote DEST)
 | 
			
		||||
#
 | 
			
		||||
# Probably need to have a '-r' option ala 'scp -r' to control --xform=
 | 
			
		||||
# (in the absence of --xform=.. above, files and dirs will all be extracted
 | 
			
		||||
# to remote DEST preserving tree structure.)
 | 
			
		||||
 | 
			
		||||
tar cf /dev/stdout ../*.txt | tar xf -
 | 
			
		||||
| 
						 | 
				
			
			@ -202,7 +202,6 @@ 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
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -439,7 +438,12 @@ func (hc Conn) Read(b []byte) (n int, err error) {
 | 
			
		|||
			log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols)
 | 
			
		||||
			hc.WinCh <- WinSize{hc.Rows, hc.Cols}
 | 
			
		||||
		} else if ctrlStatOp == CSOExitStatus {
 | 
			
		||||
			if len(payloadBytes) > 0 {
 | 
			
		||||
				*hc.closeStat = uint8(payloadBytes[0])
 | 
			
		||||
			} else {
 | 
			
		||||
				log.Println("[truncated payload, cannot determine CSOExitStatus]")
 | 
			
		||||
				*hc.closeStat = 98
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			hc.dBuf.Write(payloadBytes)
 | 
			
		||||
			//log.Printf("hc.dBuf: %s\n", hex.Dump(hc.dBuf.Bytes()))
 | 
			
		||||
| 
						 | 
				
			
			@ -450,12 +454,16 @@ 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 {
 | 
			
		||||
			log.Println("[cannot verify HMAC]")
 | 
			
		||||
		} else {
 | 
			
		||||
			// Log alert if hmac didn't match, corrupted channel
 | 
			
		||||
			if !bytes.Equal(hTmp, []byte(hmacIn[0:])) /*|| hmacIn[0] > 0xf8*/ {
 | 
			
		||||
				fmt.Println("** ALERT - detected HMAC mismatch, possible channel tampering **")
 | 
			
		||||
				_, _ = hc.c.Write([]byte{CSOHmacInvalid})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	retN := hc.dBuf.Len()
 | 
			
		||||
	if retN > len(b) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
hkexsh
 | 
			
		||||
| 
						 | 
				
			
			@ -16,9 +16,11 @@ import (
 | 
			
		|||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	hkexsh "blitter.com/go/hkexsh"
 | 
			
		||||
	"blitter.com/go/hkexsh/hkexnet"
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +32,7 @@ type cmdSpec struct {
 | 
			
		|||
	who        []byte
 | 
			
		||||
	cmd        []byte
 | 
			
		||||
	authCookie []byte
 | 
			
		||||
	status     int // though UNIX shell exit status is uint8, os.Exit() wants int
 | 
			
		||||
	status     int // UNIX exit status is uint8, but os.Exit() wants int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
| 
						 | 
				
			
			@ -52,8 +54,237 @@ func GetSize() (cols, rows int, err error) {
 | 
			
		|||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Demo of a simple client that dials up to a simple test server to
 | 
			
		||||
// send data.
 | 
			
		||||
func parseNonSwitchArgs(a []string) (user, host, path string, isDest bool, otherArgs []string) {
 | 
			
		||||
	// Whether fancyArg is src or dst file depends on flag.Args() index;
 | 
			
		||||
	//  fancyArg as last flag.Args() element denotes dstFile
 | 
			
		||||
	//  fancyArg as not-last flag.Args() element denotes srcFile
 | 
			
		||||
	var fancyUser, fancyHost, fancyPath string
 | 
			
		||||
	for i, arg := range a {
 | 
			
		||||
		if strings.Contains(arg, ":") || strings.Contains(arg, "@") {
 | 
			
		||||
			fancyArg := strings.Split(flag.Arg(i), "@")
 | 
			
		||||
			var fancyHostPath []string
 | 
			
		||||
			if len(fancyArg) < 2 {
 | 
			
		||||
				//TODO: no user specified, use current
 | 
			
		||||
				fancyUser = "[default:getUser]"
 | 
			
		||||
				fancyHostPath = strings.Split(fancyArg[0], ":")
 | 
			
		||||
			} else {
 | 
			
		||||
				// user@....
 | 
			
		||||
				fancyUser = fancyArg[0]
 | 
			
		||||
				fancyHostPath = strings.Split(fancyArg[1], ":")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// [...@]host[:path]
 | 
			
		||||
			if len(fancyHostPath) > 1 {
 | 
			
		||||
				fancyPath = fancyHostPath[1]
 | 
			
		||||
			}
 | 
			
		||||
			fancyHost = fancyHostPath[0]
 | 
			
		||||
 | 
			
		||||
			//if fancyPath == "" {
 | 
			
		||||
			//	fancyPath = "."
 | 
			
		||||
			//}
 | 
			
		||||
 | 
			
		||||
			if i == len(a)-1 {
 | 
			
		||||
				isDest = true
 | 
			
		||||
				fmt.Println("remote path isDest")
 | 
			
		||||
			}
 | 
			
		||||
			fmt.Println("fancyArgs: user:", fancyUser, "host:", fancyHost, "path:", fancyPath)
 | 
			
		||||
		} else {
 | 
			
		||||
			otherArgs = append(otherArgs, a[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fancyUser, fancyHost, fancyPath, isDest, otherArgs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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) {
 | 
			
		||||
	if remoteDest {
 | 
			
		||||
		fmt.Println("local files:", files, "remote filepath:", string(rec.cmd))
 | 
			
		||||
 | 
			
		||||
		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 = strings.TrimSpace(files)
 | 
			
		||||
		// Awesome fact: tar actually can take multiple -C args, and
 | 
			
		||||
		// changes to the dest dir *as it sees each one*. This enables
 | 
			
		||||
		// its use below, where clients can send scattered sets of source
 | 
			
		||||
		// files and dirs to be extraced to a single dest dir server-side,
 | 
			
		||||
		// whilst preserving the subtrees of dirs on the other side. :)
 | 
			
		||||
		// Eg., tar -c -f /dev/stdout -C /dirA fileInA -C /some/where/dirB fileInB /foo/dirC
 | 
			
		||||
		// packages fileInA, fileInB, and dirC at a single toplevel in the tar.
 | 
			
		||||
		// The tar authors are/were real smarties :)
 | 
			
		||||
		for _, v := range strings.Split(files, " ") {
 | 
			
		||||
			dirTmp, fileTmp := path.Split(v)
 | 
			
		||||
			cmdArgs = append(cmdArgs, "-C", dirTmp, fileTmp)
 | 
			
		||||
			//cmdArgs = append(cmdArgs, v)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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.Dir, _ = os.Getwd()
 | 
			
		||||
		fmt.Println("[wd:", c.Dir, "]")
 | 
			
		||||
		c.Stdout = conn
 | 
			
		||||
		// Stderr sinkholing is important. Any extraneous output to tarpipe
 | 
			
		||||
		// messes up remote side as it's expecting pure tar data.
 | 
			
		||||
		// (For example, if user specifies abs paths, tar outputs
 | 
			
		||||
		// "Removing leading '/' from path names")
 | 
			
		||||
		c.Stderr = nil
 | 
			
		||||
 | 
			
		||||
		// 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)
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
		cmdArgs := []string{"-xz", "-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).
 | 
			
		||||
func doShellMode(isInteractive bool, conn *hkexnet.Conn, oldState *hkexsh.State, rec *cmdSpec) {
 | 
			
		||||
	//client reader (from server) goroutine
 | 
			
		||||
	//Read remote end's stdout
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		defer wg.Done()
 | 
			
		||||
		// By deferring a call to wg.Done(),
 | 
			
		||||
		// each goroutine guarantees that it marks
 | 
			
		||||
		// its direction's stream as finished.
 | 
			
		||||
 | 
			
		||||
		// io.Copy() expects EOF so normally this will
 | 
			
		||||
		// exit with inerr == nil
 | 
			
		||||
		_, inerr := io.Copy(os.Stdout, conn)
 | 
			
		||||
		if inerr != nil {
 | 
			
		||||
			fmt.Println(inerr)
 | 
			
		||||
			_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
 | 
			
		||||
			os.Exit(1)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		rec.status = int(conn.GetStatus())
 | 
			
		||||
		log.Println("rec.status:", rec.status)
 | 
			
		||||
 | 
			
		||||
		if isInteractive {
 | 
			
		||||
			log.Println("[* Got EOF *]")
 | 
			
		||||
			_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// Only look for data from stdin to send to remote end
 | 
			
		||||
	// for interactive sessions.
 | 
			
		||||
	if isInteractive {
 | 
			
		||||
		handleTermResizes(conn)
 | 
			
		||||
 | 
			
		||||
		// client writer (to server) goroutine
 | 
			
		||||
		// Write local stdin to remote end
 | 
			
		||||
		wg.Add(1)
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer wg.Done()
 | 
			
		||||
			//!defer wg.Done()
 | 
			
		||||
			// Copy() expects EOF so this will
 | 
			
		||||
			// exit with outerr == nil
 | 
			
		||||
			//!_, outerr := io.Copy(conn, os.Stdin)
 | 
			
		||||
			_, outerr := func(conn *hkexnet.Conn, r io.Reader) (w int64, e error) {
 | 
			
		||||
				w, e = io.Copy(conn, r)
 | 
			
		||||
				return w, e
 | 
			
		||||
			}(conn, os.Stdin)
 | 
			
		||||
 | 
			
		||||
			if outerr != nil {
 | 
			
		||||
				log.Println(outerr)
 | 
			
		||||
				fmt.Println(outerr)
 | 
			
		||||
				_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
 | 
			
		||||
				os.Exit(255)
 | 
			
		||||
			}
 | 
			
		||||
			log.Println("[Sent EOF]")
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Wait until both stdin and stdout goroutines finish before returning
 | 
			
		||||
	// (ensure client gets all data from server before closing)
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UsageShell() {
 | 
			
		||||
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
 | 
			
		||||
	fmt.Fprintf(os.Stderr, "%s [opts] [user]@server\n", os.Args[0])
 | 
			
		||||
	flag.PrintDefaults()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UsageCp() {
 | 
			
		||||
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
 | 
			
		||||
	fmt.Fprintf(os.Stderr, "%s [opts] srcFileOrDir [...] [user]@server[:dstpath]\n", os.Args[0])
 | 
			
		||||
	fmt.Fprintf(os.Stderr, "%s [opts] [user]@server[:srcFileOrDir] dstPath\n", os.Args[0])
 | 
			
		||||
	flag.PrintDefaults()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// hkexsh - a client for secure shell and file copy operations.
 | 
			
		||||
//
 | 
			
		||||
// While conforming to the basic net.Conn interface HKex.Conn has extra
 | 
			
		||||
// capabilities designed to allow apps to define connection options,
 | 
			
		||||
| 
						 | 
				
			
			@ -68,44 +299,163 @@ func main() {
 | 
			
		|||
	version := "0.1pre (NO WARRANTY)"
 | 
			
		||||
	var vopt bool
 | 
			
		||||
	var dbg bool
 | 
			
		||||
	var shellMode bool // if true act as shell, else file copier
 | 
			
		||||
	var cAlg string
 | 
			
		||||
	var hAlg string
 | 
			
		||||
	var server string
 | 
			
		||||
	var port uint
 | 
			
		||||
	var cmdStr string
 | 
			
		||||
	var altUser string
 | 
			
		||||
 | 
			
		||||
	var copySrc []byte
 | 
			
		||||
	var copyDst string
 | 
			
		||||
 | 
			
		||||
	var authCookie string
 | 
			
		||||
	var chaffEnabled bool
 | 
			
		||||
	var chaffFreqMin uint
 | 
			
		||||
	var chaffFreqMax uint
 | 
			
		||||
	var chaffBytesMax uint
 | 
			
		||||
 | 
			
		||||
	var op []byte
 | 
			
		||||
	isInteractive := false
 | 
			
		||||
 | 
			
		||||
	flag.BoolVar(&vopt, "v", false, "show version")
 | 
			
		||||
	flag.StringVar(&cAlg, "c", "C_AES_256", "cipher [\"C_AES_256\" | \"C_TWOFISH_128\" | \"C_BLOWFISH_64\"]")
 | 
			
		||||
	flag.StringVar(&hAlg, "h", "H_SHA256", "hmac [\"H_SHA256\"]")
 | 
			
		||||
	flag.StringVar(&server, "s", "localhost:2000", "server hostname/address[:port]")
 | 
			
		||||
	flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)")
 | 
			
		||||
	flag.StringVar(&altUser, "u", "", "specify alternate user")
 | 
			
		||||
	flag.StringVar(&authCookie, "a", "", "auth cookie")
 | 
			
		||||
	flag.BoolVar(&chaffEnabled, "cE", true, "enabled chaff pkts (default true)")
 | 
			
		||||
	flag.UintVar(&chaffFreqMin, "cfm", 100, "chaff pkt freq min (msecs)")
 | 
			
		||||
	flag.UintVar(&chaffFreqMax, "cfM", 5000, "chaff pkt freq max (msecs)")
 | 
			
		||||
	flag.UintVar(&chaffBytesMax, "cbM", 64, "chaff pkt size max (bytes)")
 | 
			
		||||
	flag.BoolVar(&dbg, "d", false, "debug logging")
 | 
			
		||||
	flag.StringVar(&cAlg, "c", "C_AES_256", "`cipher` [\"C_AES_256\" | \"C_TWOFISH_128\" | \"C_BLOWFISH_64\"]")
 | 
			
		||||
	flag.StringVar(&hAlg, "m", "H_SHA256", "`hmac` [\"H_SHA256\"]")
 | 
			
		||||
	flag.UintVar(&port, "p", 2000, "`port`")
 | 
			
		||||
	flag.StringVar(&authCookie, "a", "", "auth cookie")
 | 
			
		||||
	flag.BoolVar(&chaffEnabled, "e", true, "enabled chaff pkts (default true)")
 | 
			
		||||
	flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt `freq` min (msecs)")
 | 
			
		||||
	flag.UintVar(&chaffFreqMax, "F", 5000, "chaff pkt `freq` max (msecs)")
 | 
			
		||||
	flag.UintVar(&chaffBytesMax, "B", 64, "chaff pkt `size` max (bytes)")
 | 
			
		||||
 | 
			
		||||
	// Find out what program we are (shell or copier)
 | 
			
		||||
	myPath := strings.Split(os.Args[0], string(os.PathSeparator))
 | 
			
		||||
	if myPath[len(myPath)-1] != "hkexcp" && myPath[len(myPath)-1] != "hkexcp.exe" {
 | 
			
		||||
		// hkexsh accepts a command (-x) but not
 | 
			
		||||
		// a srcpath (-r) or dstpath (-t)
 | 
			
		||||
		flag.StringVar(&cmdStr, "x", "", "`command` to run (if not specified run interactive shell)")
 | 
			
		||||
		shellMode = true
 | 
			
		||||
		flag.Usage = UsageShell
 | 
			
		||||
	} else {
 | 
			
		||||
		flag.Usage = UsageCp
 | 
			
		||||
	}
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	remoteUser, tmpHost, tmpPath, pathIsDest, otherArgs :=
 | 
			
		||||
		parseNonSwitchArgs(flag.Args())
 | 
			
		||||
	fmt.Println("otherArgs:", otherArgs)
 | 
			
		||||
 | 
			
		||||
	// Set defaults if user doesn't specify user, path or port
 | 
			
		||||
	var uname string
 | 
			
		||||
	if remoteUser == "" {
 | 
			
		||||
		u, _ := user.Current()
 | 
			
		||||
		uname = u.Username
 | 
			
		||||
	} else {
 | 
			
		||||
		uname = remoteUser
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if tmpHost != "" {
 | 
			
		||||
		server = tmpHost + ":" + fmt.Sprintf("%d", port)
 | 
			
		||||
	}
 | 
			
		||||
	if tmpPath == "" {
 | 
			
		||||
		tmpPath = "."
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var fileArgs string
 | 
			
		||||
	if !shellMode /*&& tmpPath != ""*/ {
 | 
			
		||||
		// -if pathIsSrc && len(otherArgs) > 1 ERROR
 | 
			
		||||
		// -else flatten otherArgs into space-delim list => copySrc
 | 
			
		||||
		if pathIsDest {
 | 
			
		||||
			if len(otherArgs) == 0 {
 | 
			
		||||
				log.Fatal("ERROR: Must specify at least one dest path for copy")
 | 
			
		||||
			} else {
 | 
			
		||||
				for _, v := range otherArgs {
 | 
			
		||||
					copySrc = append(copySrc, ' ')
 | 
			
		||||
					copySrc = append(copySrc, v...)
 | 
			
		||||
				}
 | 
			
		||||
				copyDst = tmpPath
 | 
			
		||||
				fileArgs = string(copySrc)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if len(otherArgs) == 0 {
 | 
			
		||||
				log.Fatal("ERROR: Must specify src path for copy")
 | 
			
		||||
			} else if len(otherArgs) == 1 {
 | 
			
		||||
				copyDst = otherArgs[0]
 | 
			
		||||
				if strings.Contains(copyDst, "*") || strings.Contains(copyDst, "?") {
 | 
			
		||||
					log.Fatal("ERROR: wildcards not allowed in dest path for copy")
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				log.Fatal("ERROR: cannot specify more than one dest path for copy")
 | 
			
		||||
			}
 | 
			
		||||
			copySrc = []byte(tmpPath)
 | 
			
		||||
			fileArgs = copyDst
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Do some more option consistency checks
 | 
			
		||||
 | 
			
		||||
	//fmt.Println("server finally is:", server)
 | 
			
		||||
	if flag.NFlag() == 0 && server == "" {
 | 
			
		||||
		flag.Usage()
 | 
			
		||||
		os.Exit(0)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if vopt {
 | 
			
		||||
		fmt.Printf("version v%s\n", version)
 | 
			
		||||
		os.Exit(0)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(cmdStr) != 0 && (len(copySrc) != 0 || len(copyDst) != 0) {
 | 
			
		||||
		log.Fatal("incompatible options -- either cmd (-x) or copy ops but not both")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//-------------------------------------------------------------------
 | 
			
		||||
	// Here we have parsed all options and can now carry out
 | 
			
		||||
	// either the shell session or copy operation.
 | 
			
		||||
	_ = shellMode
 | 
			
		||||
 | 
			
		||||
	if dbg {
 | 
			
		||||
		log.SetOutput(os.Stdout)
 | 
			
		||||
	} else {
 | 
			
		||||
		log.SetOutput(ioutil.Discard)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if shellMode {
 | 
			
		||||
		// We must make the decision about interactivity before Dial()
 | 
			
		||||
		// as it affects chaffing behaviour. 20180805
 | 
			
		||||
		if len(cmdStr) == 0 {
 | 
			
		||||
			op = []byte{'s'}
 | 
			
		||||
			isInteractive = true
 | 
			
		||||
		} else {
 | 
			
		||||
			op = []byte{'c'}
 | 
			
		||||
			// non-interactive cmds may complete quickly, so chaff earlier/faster
 | 
			
		||||
			// to help ensure there's some cover to the brief traffic.
 | 
			
		||||
			// (ignoring cmdline values)
 | 
			
		||||
			chaffFreqMin = 2
 | 
			
		||||
			chaffFreqMax = 10
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// as copy mode is also non-interactive, set up chaffing
 | 
			
		||||
		// just like the 'c' mode above
 | 
			
		||||
		chaffFreqMin = 2
 | 
			
		||||
		chaffFreqMax = 10
 | 
			
		||||
 | 
			
		||||
		if pathIsDest {
 | 
			
		||||
			// client->server file copy
 | 
			
		||||
			// src file list is in copySrc
 | 
			
		||||
			op = []byte{'D'}
 | 
			
		||||
			fmt.Println("client->server copy:", string(copySrc), "->", copyDst)
 | 
			
		||||
			cmdStr = copyDst
 | 
			
		||||
		} else {
 | 
			
		||||
			// server->client file copy
 | 
			
		||||
			// remote src file(s) in copyDsr
 | 
			
		||||
			op = []byte{'S'}
 | 
			
		||||
			fmt.Println("server->client copy:", string(copySrc), "->", copyDst)
 | 
			
		||||
			cmdStr = string(copySrc)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conn, err := hkexnet.Dial("tcp", server, cAlg, hAlg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("Err!")
 | 
			
		||||
| 
						 | 
				
			
			@ -118,6 +468,7 @@ func main() {
 | 
			
		|||
	// TODO: send flag to server side indicating this
 | 
			
		||||
	//  affects shell command used
 | 
			
		||||
	var oldState *hkexsh.State
 | 
			
		||||
	if shellMode {
 | 
			
		||||
		if isatty.IsTerminal(os.Stdin.Fd()) {
 | 
			
		||||
			oldState, err = hkexsh.MakeRaw(int(os.Stdin.Fd()))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -127,33 +478,6 @@ func main() {
 | 
			
		|||
		} else {
 | 
			
		||||
			log.Println("NOT A TTY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	var uname string
 | 
			
		||||
	if len(altUser) == 0 {
 | 
			
		||||
		u, _ := user.Current()
 | 
			
		||||
		uname = u.Username
 | 
			
		||||
	} else {
 | 
			
		||||
		uname = altUser
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var op []byte
 | 
			
		||||
	if len(cmdStr) == 0 {
 | 
			
		||||
		op = []byte{'s'}
 | 
			
		||||
		isInteractive = true
 | 
			
		||||
	} else if cmdStr == "-" {
 | 
			
		||||
		op = []byte{'c'}
 | 
			
		||||
		cmdStdin, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
		cmdStr = strings.Trim(string(cmdStdin), "\r\n")
 | 
			
		||||
	} else {
 | 
			
		||||
		op = []byte{'c'}
 | 
			
		||||
		// non-interactive cmds may complete quickly, so chaff earlier/faster
 | 
			
		||||
		// to help ensure there's some cover to the brief traffic.
 | 
			
		||||
		// (ignoring cmdline values)
 | 
			
		||||
		chaffFreqMin = 2
 | 
			
		||||
		chaffFreqMax = 10
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(authCookie) == 0 {
 | 
			
		||||
| 
						 | 
				
			
			@ -188,76 +512,19 @@ func main() {
 | 
			
		|||
	conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing
 | 
			
		||||
	if chaffEnabled {
 | 
			
		||||
		conn.EnableChaff()
 | 
			
		||||
	}
 | 
			
		||||
		defer conn.DisableChaff()
 | 
			
		||||
		defer conn.ShutdownChaff()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//client reader (from server) goroutine
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		// By deferring a call to wg.Done(),
 | 
			
		||||
		// each goroutine guarantees that it marks
 | 
			
		||||
		// its direction's stream as finished.
 | 
			
		||||
		//
 | 
			
		||||
		// Whichever direction's goroutine finishes first
 | 
			
		||||
		// will call wg.Done() once more, explicitly, to
 | 
			
		||||
		// hang up on the other side, so that this client
 | 
			
		||||
		// exits immediately on an EOF from either side.
 | 
			
		||||
		defer wg.Done()
 | 
			
		||||
	if shellMode {
 | 
			
		||||
		doShellMode(isInteractive, conn, oldState, rec)
 | 
			
		||||
	} else {
 | 
			
		||||
		doCopyMode(conn, pathIsDest, fileArgs, rec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		// io.Copy() expects EOF so this will
 | 
			
		||||
		// exit with inerr == nil
 | 
			
		||||
		_, inerr := io.Copy(os.Stdout, conn)
 | 
			
		||||
		if inerr != nil {
 | 
			
		||||
			if inerr.Error() != "EOF" {
 | 
			
		||||
				fmt.Println(inerr)
 | 
			
		||||
	if oldState != nil {
 | 
			
		||||
		_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
 | 
			
		||||
				os.Exit(1)
 | 
			
		||||
			}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		rec.status = int(conn.GetStatus())
 | 
			
		||||
		log.Println("rec.status:", rec.status)
 | 
			
		||||
 | 
			
		||||
		if isInteractive {
 | 
			
		||||
			log.Println("[* Got EOF *]")
 | 
			
		||||
			_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
 | 
			
		||||
			wg.Done()
 | 
			
		||||
			//os.Exit(rec.status)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	if isInteractive {
 | 
			
		||||
		handleTermResizes(conn)
 | 
			
		||||
 | 
			
		||||
		// client writer (to server) goroutine
 | 
			
		||||
		wg.Add(1)
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer wg.Done()
 | 
			
		||||
 | 
			
		||||
			// Copy() expects EOF so this will
 | 
			
		||||
			// exit with outerr == nil
 | 
			
		||||
			//!_, outerr := io.Copy(conn, os.Stdin)
 | 
			
		||||
			_, outerr := func(conn *hkexnet.Conn, r io.Reader) (w int64, e error) {
 | 
			
		||||
				return io.Copy(conn, r)
 | 
			
		||||
			}(conn, os.Stdin)
 | 
			
		||||
 | 
			
		||||
			if outerr != nil {
 | 
			
		||||
				log.Println(outerr)
 | 
			
		||||
				if outerr.Error() != "EOF" {
 | 
			
		||||
					fmt.Println(outerr)
 | 
			
		||||
					_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
 | 
			
		||||
					os.Exit(255)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			log.Println("[Sent EOF]")
 | 
			
		||||
			wg.Done() // client hung up, close WaitGroup to exit client
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Wait until both stdin and stdout goroutines finish
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
 | 
			
		||||
	_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
 | 
			
		||||
	os.Exit(rec.status)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,8 +16,10 @@ import (
 | 
			
		|||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"blitter.com/go/goutmp"
 | 
			
		||||
| 
						 | 
				
			
			@ -36,53 +38,170 @@ type cmdSpec struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/* -------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 // Run a command (via os.exec) as a specific user
 | 
			
		||||
//
 | 
			
		||||
// Uses ptys to support commands which expect a terminal.
 | 
			
		||||
func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) {
 | 
			
		||||
// Perform a client->server copy
 | 
			
		||||
func runClientToServerCopyAs(who string, conn hkexnet.Conn, fpath string, chaffing bool) (err error, exitStatus int) {
 | 
			
		||||
	u, _ := user.Lookup(who)
 | 
			
		||||
	var uid, gid uint32
 | 
			
		||||
	fmt.Sscanf(u.Uid, "%d", &uid)
 | 
			
		||||
	fmt.Sscanf(u.Gid, "%d", &gid)
 | 
			
		||||
	fmt.Println("uid:", uid, "gid:", gid)
 | 
			
		||||
	log.Println("uid:", uid, "gid:", gid)
 | 
			
		||||
 | 
			
		||||
	args := strings.Split(cmd, " ")
 | 
			
		||||
	arg0 := args[0]
 | 
			
		||||
	args = args[1:]
 | 
			
		||||
	c := exec.Command(arg0, args...)
 | 
			
		||||
	// Need to clear server's env and set key vars of the
 | 
			
		||||
	// target user. This isn't perfect (TERM doesn't seem to
 | 
			
		||||
	// work 100%; ANSI/xterm colour isn't working even
 | 
			
		||||
	// if we set "xterm" or "ansi" here; and line count
 | 
			
		||||
	// reported by 'stty -a' defaults to 24 regardless
 | 
			
		||||
	// of client shell window used to run client.
 | 
			
		||||
	// Investigate -- rlm 2018-01-26)
 | 
			
		||||
	os.Clearenv()
 | 
			
		||||
	os.Setenv("HOME", u.HomeDir)
 | 
			
		||||
	os.Setenv("TERM", "vt102") // TODO: server or client option?
 | 
			
		||||
 | 
			
		||||
	var c *exec.Cmd
 | 
			
		||||
	cmdName := "/bin/tar"
 | 
			
		||||
 | 
			
		||||
	var destDir string
 | 
			
		||||
	if path.IsAbs(fpath) {
 | 
			
		||||
		destDir = fpath
 | 
			
		||||
	} else {
 | 
			
		||||
		destDir = path.Join(u.HomeDir, fpath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmdArgs := []string{"-xz", "-C", destDir}
 | 
			
		||||
 | 
			
		||||
	// 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{"-x", "-C", destDir, `--xform=s#.*/\(.*\)#\1#`}
 | 
			
		||||
	c = exec.Command(cmdName, cmdArgs...)
 | 
			
		||||
 | 
			
		||||
	c.Dir = destDir
 | 
			
		||||
 | 
			
		||||
	//If os.Clearenv() isn't called by server above these will be seen in the
 | 
			
		||||
	//client's session env.
 | 
			
		||||
	//c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
 | 
			
		||||
	//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
 | 
			
		||||
	c.Stdout = os.Stdout
 | 
			
		||||
	c.Stderr = os.Stderr
 | 
			
		||||
 | 
			
		||||
	// Start the command with a pty.
 | 
			
		||||
	ptmx, err := pty.Start(c) // returns immediately with ptmx file
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	if chaffing {
 | 
			
		||||
		conn.EnableChaff()
 | 
			
		||||
	}
 | 
			
		||||
	// Make sure to close the pty at the end.
 | 
			
		||||
	defer func() { _ = ptmx.Close() }() // Best effort.
 | 
			
		||||
	// Copy stdin to the pty and the pty to stdout.
 | 
			
		||||
	go func() { _, _ = io.Copy(ptmx, conn) }()
 | 
			
		||||
	_, _ = io.Copy(conn, ptmx)
 | 
			
		||||
 | 
			
		||||
	//err = c.Run()  // returns when c finishes.
 | 
			
		||||
	defer conn.DisableChaff()
 | 
			
		||||
	defer conn.ShutdownChaff()
 | 
			
		||||
 | 
			
		||||
	// Start the command (no pty)
 | 
			
		||||
	log.Printf("[%v %v]\n", cmdName, cmdArgs)
 | 
			
		||||
	err = c.Start() // returns immediately
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("Command finished with error: %v", err)
 | 
			
		||||
		log.Printf("[%s]\n", cmd)
 | 
			
		||||
		return err, 253 // !?
 | 
			
		||||
	} else {
 | 
			
		||||
		if err := c.Wait(); err != nil {
 | 
			
		||||
			fmt.Println("*** c.Wait() done ***")
 | 
			
		||||
			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 ***")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Perform a server->client copy
 | 
			
		||||
func runServerToClientCopyAs(who string, conn hkexnet.Conn, srcPath string, chaffing bool) (err error, exitStatus int) {
 | 
			
		||||
	u, _ := user.Lookup(who)
 | 
			
		||||
	var uid, gid uint32
 | 
			
		||||
	fmt.Sscanf(u.Uid, "%d", &uid)
 | 
			
		||||
	fmt.Sscanf(u.Gid, "%d", &gid)
 | 
			
		||||
	log.Println("uid:", uid, "gid:", gid)
 | 
			
		||||
 | 
			
		||||
	// Need to clear server's env and set key vars of the
 | 
			
		||||
	// target user. This isn't perfect (TERM doesn't seem to
 | 
			
		||||
	// work 100%; ANSI/xterm colour isn't working even
 | 
			
		||||
	// if we set "xterm" or "ansi" here; and line count
 | 
			
		||||
	// reported by 'stty -a' defaults to 24 regardless
 | 
			
		||||
	// of client shell window used to run client.
 | 
			
		||||
	// Investigate -- rlm 2018-01-26)
 | 
			
		||||
	os.Clearenv()
 | 
			
		||||
	os.Setenv("HOME", u.HomeDir)
 | 
			
		||||
	os.Setenv("TERM", "vt102") // TODO: server or client option?
 | 
			
		||||
 | 
			
		||||
	var c *exec.Cmd
 | 
			
		||||
	cmdName := "/bin/tar"
 | 
			
		||||
	if !path.IsAbs(srcPath) {
 | 
			
		||||
		srcPath = fmt.Sprintf("%s%c%s", u.HomeDir, os.PathSeparator, srcPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	srcDir, srcBase := path.Split(srcPath)
 | 
			
		||||
	cmdArgs := []string{"-cz", "-C", srcDir, "-f", "-", srcBase}
 | 
			
		||||
 | 
			
		||||
	c = exec.Command(cmdName, cmdArgs...)
 | 
			
		||||
 | 
			
		||||
	//If os.Clearenv() isn't called by server above these will be seen in the
 | 
			
		||||
	//client's session env.
 | 
			
		||||
	//c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
 | 
			
		||||
	c.Dir = u.HomeDir
 | 
			
		||||
	c.SysProcAttr = &syscall.SysProcAttr{}
 | 
			
		||||
	c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
 | 
			
		||||
	c.Stdout = conn
 | 
			
		||||
	// Stderr sinkholing is important. Any extraneous output to tarpipe
 | 
			
		||||
	// messes up remote side as it's expecting pure tar data.
 | 
			
		||||
	// (For example, if user specifies abs paths, tar outputs
 | 
			
		||||
	// "Removing leading '/' from path names")
 | 
			
		||||
	c.Stderr = nil
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("Command finished with error: %v", err)
 | 
			
		||||
		return err, 253 // !?
 | 
			
		||||
	} else {
 | 
			
		||||
		if err := c.Wait(); err != nil {
 | 
			
		||||
			fmt.Println("*** c.Wait() done ***")
 | 
			
		||||
			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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// 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) {
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	u, _ := user.Lookup(who)
 | 
			
		||||
	var uid, gid uint32
 | 
			
		||||
	fmt.Sscanf(u.Uid, "%d", &uid)
 | 
			
		||||
| 
						 | 
				
			
			@ -135,15 +254,16 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
 | 
			
		|||
				log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
 | 
			
		||||
				pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols})
 | 
			
		||||
			}
 | 
			
		||||
			fmt.Println("*** WinCh goroutine done ***")
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		// Copy stdin to the pty.. (bgnd goroutine)
 | 
			
		||||
		go func() {
 | 
			
		||||
			_, e := io.Copy(ptmx, conn)
 | 
			
		||||
			if e != nil {
 | 
			
		||||
				log.Printf("** std->pty ended **\n")
 | 
			
		||||
				return
 | 
			
		||||
				log.Println("** stdin->pty ended **:", e.Error())
 | 
			
		||||
			}
 | 
			
		||||
			fmt.Println("*** stdin->pty goroutine done ***")
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		if chaffing {
 | 
			
		||||
| 
						 | 
				
			
			@ -153,17 +273,25 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
 | 
			
		|||
		defer conn.ShutdownChaff()
 | 
			
		||||
 | 
			
		||||
		// ..and the pty to stdout.
 | 
			
		||||
		// This may take some time exceeding that of the
 | 
			
		||||
		// actual command's lifetime, so the c.Wait() below
 | 
			
		||||
		// must synchronize with the completion of this goroutine
 | 
			
		||||
		// to ensure all stdout data gets to the client before
 | 
			
		||||
		// connection is closed.
 | 
			
		||||
		wg.Add(1)
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer wg.Done()
 | 
			
		||||
			_, e := io.Copy(conn, ptmx)
 | 
			
		||||
			if e != nil {
 | 
			
		||||
				log.Printf("** pty->stdout ended **\n")
 | 
			
		||||
				return
 | 
			
		||||
				log.Println("** pty->stdout ended **:", e.Error())
 | 
			
		||||
			}
 | 
			
		||||
			// The above io.Copy() will exit when the command attached
 | 
			
		||||
			// to the pty exits
 | 
			
		||||
			fmt.Println("*** pty->stdout goroutine done ***")
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		if err := c.Wait(); err != nil {
 | 
			
		||||
			fmt.Println("*** c.Wait() done ***")
 | 
			
		||||
			if exiterr, ok := err.(*exec.ExitError); ok {
 | 
			
		||||
				// The program has exited with an exit code != 0
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -177,6 +305,7 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexnet.Conn, cha
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		wg.Wait() // Wait on pty->stdout completion to client
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -202,10 +331,10 @@ func main() {
 | 
			
		|||
 | 
			
		||||
	flag.BoolVar(&vopt, "v", false, "show version")
 | 
			
		||||
	flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen")
 | 
			
		||||
	flag.BoolVar(&chaffEnabled, "cE", true, "enabled chaff pkts")
 | 
			
		||||
	flag.UintVar(&chaffFreqMin, "cfm", 100, "chaff pkt freq min (msecs)")
 | 
			
		||||
	flag.UintVar(&chaffFreqMax, "cfM", 5000, "chaff pkt freq max (msecs)")
 | 
			
		||||
	flag.UintVar(&chaffBytesMax, "cbM", 64, "chaff pkt size max (bytes)")
 | 
			
		||||
	flag.BoolVar(&chaffEnabled, "e", true, "enabled chaff pkts")
 | 
			
		||||
	flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min (msecs)")
 | 
			
		||||
	flag.UintVar(&chaffFreqMax, "F", 5000, "chaff pkt freq max (msecs)")
 | 
			
		||||
	flag.UintVar(&chaffBytesMax, "B", 64, "chaff pkt size max (bytes)")
 | 
			
		||||
	flag.BoolVar(&dbg, "d", false, "debug logging")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -354,6 +483,42 @@ func main() {
 | 
			
		|||
						log.Printf("[Shell completed for %s@%s, status %d]\n", rec.who, hname, cmdStatus)
 | 
			
		||||
						hc.SetStatus(uint8(cmdStatus))
 | 
			
		||||
					}
 | 
			
		||||
				} else if rec.op[0] == 'D' {
 | 
			
		||||
					// File copy (destination) operation - client copy to server
 | 
			
		||||
					log.Printf("[Client->Server copy]\n")
 | 
			
		||||
					// TODO: call function with hc, rec.cmd, chaffEnabled etc.
 | 
			
		||||
					// func hooks tar cmd right-half of pipe to hc Reader
 | 
			
		||||
					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)
 | 
			
		||||
					// 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 cp 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))
 | 
			
		||||
					}
 | 
			
		||||
				} else if rec.op[0] == 'S' {
 | 
			
		||||
					// File copy (src) operation - server copy to client
 | 
			
		||||
					log.Printf("[Server->Client copy]\n")
 | 
			
		||||
					// TODO: call function to copy rec.cmd (file list) to
 | 
			
		||||
					// tar cmd left-half of pipeline to hc.Writer ?
 | 
			
		||||
					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)
 | 
			
		||||
					// 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 cp 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))
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					log.Println("[Bad cmdSpec]")
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue