Continuing groundwork for cp mode - refactor main client code into shell/copy subroutines; -r option

This commit is contained in:
Russ Magee 2018-08-06 22:29:51 -07:00
parent c3f3bcb13f
commit 5859131678
4 changed files with 156 additions and 95 deletions

1
cp.cmd
View File

@ -9,3 +9,4 @@ tar -cz -f - testdir/sub1/bar.txt | \
# (in the absence of --xform=.. above, files and dirs will all be extracted # (in the absence of --xform=.. above, files and dirs will all be extracted
# to remote DEST preserving tree structure.) # to remote DEST preserving tree structure.)
tar cf /dev/stdout ../*.txt | tar xf -

View File

@ -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.r, hc.rm, err = hc.getStream(hc.h.FA())
hc.w, hc.wm, err = hc.getStream(hc.h.FA()) hc.w, hc.wm, err = hc.getStream(hc.h.FA())
*hc.closeStat = 99 // open or prematurely-closed status *hc.closeStat = 99 // open or prematurely-closed status
return return
} }
@ -455,7 +454,7 @@ func (hc Conn) Read(b []byte) (n int, err error) {
hTmp := hc.rm.Sum(nil)[0:4] hTmp := hc.rm.Sum(nil)[0:4]
log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp) log.Printf("<%04x) HMAC:(i)%s (c)%02x\r\n", decryptN, hex.EncodeToString([]byte(hmacIn[0:])), hTmp)
if *hc.closeStat >90 { if *hc.closeStat > 90 {
log.Println("[cannot verify HMAC]") log.Println("[cannot verify HMAC]")
} else { } else {
// Log alert if hmac didn't match, corrupted channel // Log alert if hmac didn't match, corrupted channel

View File

@ -54,11 +54,9 @@ func GetSize() (cols, rows int, err error) {
} }
func parseNonSwitchArgs(a []string, dp string) (user, host, port, path string, isDest bool, otherArgs []string) { func parseNonSwitchArgs(a []string, dp string) (user, host, port, path string, isDest bool, otherArgs []string) {
//TODO: Look for non-option fancyArg of syntax user@host:filespec to set -r,-t and -u // Whether fancyArg is src or dst file depends on flag.Args() index;
// Consider: whether fancyArg is src or dst file depends on flag.Args() index;
// fancyArg as last flag.Args() element denotes dstFile // fancyArg as last flag.Args() element denotes dstFile
// fancyArg as not-last flag.Args() element denotes srcFile // fancyArg as not-last flag.Args() element denotes srcFile
// * throw error if >1 fancyArgs are found in flags.Args()
var fancyUser, fancyHost, fancyPort, fancyPath string var fancyUser, fancyHost, fancyPort, fancyPath string
for i, arg := range a { for i, arg := range a {
if strings.Contains(arg, ":") || strings.Contains(arg, "@") { if strings.Contains(arg, ":") || strings.Contains(arg, "@") {
@ -102,8 +100,82 @@ func parseNonSwitchArgs(a []string, dp string) (user, host, port, path string, i
return fancyUser, fancyHost, fancyPort, fancyPath, isDest, otherArgs return fancyUser, fancyHost, fancyPort, fancyPath, isDest, otherArgs
} }
// Demo of a simple client that dials up to a simple test server to // doCopyMode begins a secure hkexsh local<->remote file copy operation.
// send data. 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() ?
if remoteDest {
fmt.Println("local files:", files, "remote filepath:", string(rec.cmd))
} else {
fmt.Println("remote filepath:", string(rec.cmd), "local files:", files)
}
}
// 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()
}
// hkexsh - a client for secure shell and file copy operations.
// //
// While conforming to the basic net.Conn interface HKex.Conn has extra // While conforming to the basic net.Conn interface HKex.Conn has extra
// capabilities designed to allow apps to define connection options, // capabilities designed to allow apps to define connection options,
@ -124,6 +196,7 @@ func main() {
var server string var server string
var cmdStr string var cmdStr string
var recursiveCopy bool
var copySrc []byte var copySrc []byte
var copyDst string var copyDst string
@ -156,12 +229,10 @@ func main() {
// a srcpath (-r) or dstpath (-t) // a srcpath (-r) or dstpath (-t)
flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)") flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)")
shellMode = true shellMode = true
} // else { } else {
//// hkexcp accepts srcpath (-r) and dstpath (-t), but not // Note: only makes sense for client->server copies
//// a command (-x) flag.BoolVar(&recursiveCopy, "r", false, "recursive copy/preserve tree copy")
//flag.StringVar(&copySrc, "r", "", "copy srcpath") }
//flag.StringVar(&copyDst, "t", "", "copy dstpath")
//}
flag.Parse() flag.Parse()
tmpUser, tmpHost, tmpPort, tmpPath, pathIsDest, otherArgs := tmpUser, tmpHost, tmpPort, tmpPath, pathIsDest, otherArgs :=
@ -176,21 +247,35 @@ func main() {
server = tmpHost + ":" + tmpPort server = tmpHost + ":" + tmpPort
//fmt.Println("tmpHost sets server to", server) //fmt.Println("tmpHost sets server to", server)
} }
if tmpPath != "" {
var fileArgs string
if !shellMode && tmpPath != "" {
// -if pathIsSrc && len(otherArgs) > 1 ERROR // -if pathIsSrc && len(otherArgs) > 1 ERROR
// -else flatten otherArgs into space-delim list => copySrc // -else flatten otherArgs into space-delim list => copySrc
if pathIsDest { if pathIsDest {
if len(otherArgs) == 0 {
log.Fatal("ERROR: Must specify at least one src path for copy")
} else {
for _, v := range otherArgs { for _, v := range otherArgs {
copySrc = append(copySrc, ' ') copySrc = append(copySrc, ' ')
copySrc = append(copySrc, v...) copySrc = append(copySrc, v...)
} }
fmt.Println(">> copySrc:", string(copySrc))
copyDst = tmpPath copyDst = tmpPath
fileArgs = string(copySrc)
}
} else {
if len(otherArgs) == 0 {
log.Fatal("ERROR: Must specify dest 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 { } else {
if len(otherArgs) > 1 {
log.Fatal("ERROR: cannot specify more than one dest path for copy") log.Fatal("ERROR: cannot specify more than one dest path for copy")
} }
copySrc = []byte(tmpPath) copySrc = []byte(tmpPath)
fileArgs = copyDst
} }
} }
@ -222,6 +307,7 @@ func main() {
log.SetOutput(ioutil.Discard) log.SetOutput(ioutil.Discard)
} }
if shellMode {
// We must make the decision about interactivity before Dial() // We must make the decision about interactivity before Dial()
// as it affects chaffing behaviour. 20180805 // as it affects chaffing behaviour. 20180805
if len(cmdStr) == 0 { if len(cmdStr) == 0 {
@ -232,11 +318,29 @@ func main() {
// non-interactive cmds may complete quickly, so chaff earlier/faster // non-interactive cmds may complete quickly, so chaff earlier/faster
// to help ensure there's some cover to the brief traffic. // to help ensure there's some cover to the brief traffic.
// (ignoring cmdline values) // (ignoring cmdline values)
//!DEBUG
//chaffEnabled = false
chaffFreqMin = 2 chaffFreqMin = 2
chaffFreqMax = 10 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) conn, err := hkexnet.Dial("tcp", server, cAlg, hAlg)
if err != nil { if err != nil {
@ -306,68 +410,15 @@ func main() {
defer conn.ShutdownChaff() defer conn.ShutdownChaff()
} }
//client reader (from server) goroutine if shellMode {
//Read remote end's stdout doShellMode(isInteractive, conn, oldState, rec)
wg.Add(1) } else {
go func() { doCopyMode(conn, pathIsDest, fileArgs, recursiveCopy, rec)
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()) if oldState != nil {
log.Println("rec.status:", rec.status)
if isInteractive {
log.Println("[* Got EOF *]")
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort. _ = 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
// ** IMPORTANT! This must come before the Restore() tty call below
// in order to maintain raw mode for interactive sessions. -rlm 20180805
wg.Wait()
_ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) // Best effort.
os.Exit(rec.status) os.Exit(rec.status)
} }

View File

@ -369,6 +369,16 @@ func main() {
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(uint8(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
} 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 ?
} else { } else {
log.Println("[Bad cmdSpec]") log.Println("[Bad cmdSpec]")
} }