2018-04-07 20:04:10 +00:00
// hkexsh client
//
2019-05-10 05:46:08 +00:00
// Copyright (c) 2017-2019 Russell Magee
2018-04-07 20:04:10 +00:00
// Licensed under the terms of the MIT license (see LICENSE.mit in this
// distribution)
//
// golang implementation by Russ Magee (rmagee_at_gmail.com)
2018-01-06 15:30:56 +00:00
package main
import (
2018-09-06 04:58:55 +00:00
"bytes"
2018-09-06 20:50:56 +00:00
"encoding/binary"
2018-09-17 00:14:50 +00:00
"errors"
2018-01-12 07:01:39 +00:00
"flag"
2018-01-06 15:30:56 +00:00
"fmt"
2018-01-13 06:47:57 +00:00
"io"
2018-01-18 05:27:00 +00:00
"io/ioutil"
"log"
2019-07-10 08:11:23 +00:00
"math/rand"
2018-10-27 08:51:40 +00:00
"net"
2018-01-13 06:47:57 +00:00
"os"
2018-04-29 02:28:37 +00:00
"os/exec"
2018-01-21 23:46:40 +00:00
"os/user"
2018-08-31 03:06:42 +00:00
"path"
2018-09-01 17:20:33 +00:00
"path/filepath"
2018-05-05 06:25:26 +00:00
"runtime"
2019-06-20 04:42:34 +00:00
"runtime/pprof"
2018-01-21 23:46:40 +00:00
"strings"
2018-01-18 04:36:53 +00:00
"sync"
2018-08-25 01:50:45 +00:00
"syscall"
2018-11-12 06:46:39 +00:00
"time"
2018-01-06 20:26:08 +00:00
2019-06-20 04:42:34 +00:00
"net/http"
_ "net/http/pprof"
2018-04-28 23:05:33 +00:00
hkexsh "blitter.com/go/hkexsh"
2018-07-05 05:06:07 +00:00
"blitter.com/go/hkexsh/hkexnet"
2018-10-26 23:05:01 +00:00
"blitter.com/go/hkexsh/logger"
2018-10-27 08:51:40 +00:00
"blitter.com/go/hkexsh/spinsult"
2018-01-19 05:17:57 +00:00
isatty "github.com/mattn/go-isatty"
2018-01-06 15:30:56 +00:00
)
2018-05-26 20:43:09 +00:00
var (
2019-07-11 17:12:38 +00:00
version string
gitCommit string // set in -ldflags by build
2019-05-10 05:46:08 +00:00
// wg controls when the goroutines handling client I/O complete
2018-11-23 00:49:09 +00:00
wg sync . WaitGroup
2019-08-14 03:54:58 +00:00
2019-08-17 06:16:40 +00:00
kcpMode string // set to a valid KCP BlockCrypt alg tag to use rather than TCP
2019-08-14 03:54:58 +00:00
2018-11-23 00:49:09 +00:00
// Log defaults to regular syslog output (no -d)
Log * logger . Writer
2019-06-20 04:42:34 +00:00
cpuprofile string
memprofile string
2018-05-26 20:43:09 +00:00
)
2018-12-09 05:44:06 +00:00
////////////////////////////////////////////////////
2019-05-10 05:46:08 +00:00
// Praise Bob. Do not remove, lest ye lose Slack.
2018-12-12 08:34:23 +00:00
const bob = string ( "\r\n\r\n" +
2018-12-09 05:44:06 +00:00
"@@@@@@@^^~~~~~~~~~~~~~~~~~~~~^@@@@@@@@@\r\n" +
"@@@@@@^ ~^ @ @@ @ @ @ I ~^@@@@@@\r\n" +
"@@@@@ ~ ~~ ~I @@@@@\r\n" +
"@@@@' ' _,w@< @@@@\r\n" +
"@@@@ @@@@@@@@w___,w@@@@@@@@ @ @@@\r\n" +
"@@@@ @@@@@@@@@@@@@@@@@@@@@@ I @@@\r\n" +
"@@@@ @@@@@@@@@@@@@@@@@@@@*@[ i @@@\r\n" +
"@@@@ @@@@@@@@@@@@@@@@@@@@[][ | ]@@@\r\n" +
"@@@@ ~_,,_ ~@@@@@@@~ ____~ @ @@@\r\n" +
"@@@@ _~ , , `@@@~ _ _`@ ]L J@@@\r\n" +
"@@@@ , @@w@ww+ @@@ww``,,@w@ ][ @@@@\r\n" +
"@@@@, @@@@www@@@ @@@@@@@ww@@@@@[ @@@@\r\n" +
"@@@@@_|| @@@@@@P' @@P@@@@@@@@@@@[|c@@@@\r\n" +
"@@@@@@w| '@@P~ P]@@@-~, ~Y@@^'],@@@@@@\r\n" +
"@@@@@@@[ _ _J@@Tk ]]@@@@@@\r\n" +
"@@@@@@@@,@ @@, c,,,,,,,y ,w@@[ ,@@@@@@@\r\n" +
"@@@@@@@@@ i @w ====--_@@@@@ @@@@@@@@\r\n" +
"@@@@@@@@@@`,P~ _ ~^^^^Y@@@@@ @@@@@@@@@\r\n" +
"@@@@^^=^@@^ ^' ,ww,w@@@@@ _@@@@@@@@@@\r\n" +
"@@@_xJ~ ~ , @@@@@@@P~_@@@@@@@@@@@@\r\n" +
"@@ @, ,@@@,_____ _,J@@@@@@@@@@@@@\r\n" +
"@@L `' ,@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\r\n" +
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\r\n" +
"\r\n" )
type (
2019-05-10 05:46:08 +00:00
// Handler for special functions invoked by escSeqs
2018-12-12 08:34:23 +00:00
escHandler func ( io . Writer )
2019-05-10 05:46:08 +00:00
// escSeqs is a map of special keystroke sequences to trigger escHandlers
escSeqs map [ byte ] escHandler
2018-12-09 05:44:06 +00:00
)
// Copy copies from src to dst until either EOF is reached
// on src or an error occurs. It returns the number of bytes
// copied and the first error encountered while copying, if any.
//
// A successful Copy returns err == nil, not err == EOF.
// Because Copy is defined to read from src until EOF, it does
// not treat an EOF from Read as an error to be reported.
//
// If src implements the WriterTo interface,
// the copy is implemented by calling src.WriteTo(dst).
// Otherwise, if dst implements the ReaderFrom interface,
// the copy is implemented by calling dst.ReadFrom(src).
2018-12-09 05:53:35 +00:00
//
// This is identical to stdlib pkg/io.Copy save that it
// calls a client-custom version of copyBuffer(), which allows
// some client escape sequences to trigger special actions during
// interactive sessions.
2019-05-10 05:46:08 +00:00
//
// (See go doc hkexsh/hkexsh.{escSeqs,escHandler})
2018-12-09 05:44:06 +00:00
func Copy ( dst io . Writer , src io . Reader ) ( written int64 , err error ) {
written , err = copyBuffer ( dst , src , nil )
return
}
// copyBuffer is the actual implementation of Copy and CopyBuffer.
// if buf is nil, one is allocated.
2018-12-09 05:53:35 +00:00
//
// This private version of copyBuffer is derived from the
// go stdlib pkg/io, with escape sequence interpretation to trigger
// some special client-side actions.
2019-05-10 05:46:08 +00:00
//
// (See go doc hkexsh/hkexsh.{escSeqs,escHandler})
2018-12-09 05:44:06 +00:00
func copyBuffer ( dst io . Writer , src io . Reader , buf [ ] byte ) ( written int64 , err error ) {
// NOTE: using dst.Write() in these esc funcs will cause the output
2019-05-10 05:46:08 +00:00
// to function as a 'macro', outputting as if user typed the sequence
// (that is, the client 'sees' the user type it, and the server 'sees'
// it as well).
2018-12-09 05:44:06 +00:00
//
// Using os.Stdout outputs to the client's term w/o it or the server
// 'seeing' the output.
//
// TODO: Devise a way to signal to main client thread that
// a goroutine should be spawned to do long-lived tasks for
// some esc sequences (eg., a time ticker in the corner of terminal,
// or tunnel traffic indicator - note we cannot just spawn a goroutine
// here, as copyBuffer() returns after each burst of data. Scope must
// outlive individual copyBuffer calls).
2018-12-12 08:34:23 +00:00
escs := escSeqs {
2018-12-09 05:44:06 +00:00
'i' : func ( io . Writer ) { os . Stdout . Write ( [ ] byte ( "\x1b[s\x1b[2;1H\x1b[1;31m[HKEXSH]\x1b[39;49m\x1b[u" ) ) } ,
't' : func ( io . Writer ) { os . Stdout . Write ( [ ] byte ( "\x1b[1;32m[HKEXSH]\x1b[39;49m" ) ) } ,
2018-12-12 08:34:23 +00:00
'B' : func ( io . Writer ) { os . Stdout . Write ( [ ] byte ( "\x1b[1;32m" + bob + "\x1b[39;49m" ) ) } ,
2018-12-09 05:44:06 +00:00
}
/ *
// If the reader has a WriteTo method, use it to do the copy.
// Avoids an allocation and a copy.
if wt , ok := src . ( io . WriterTo ) ; ok {
return wt . WriteTo ( dst )
}
// Similarly, if the writer has a ReadFrom method, use it to do the copy.
if rt , ok := dst . ( io . ReaderFrom ) ; ok {
return rt . ReadFrom ( src )
}
* /
if buf == nil {
size := 32 * 1024
if l , ok := src . ( * io . LimitedReader ) ; ok && int64 ( size ) > l . N {
if l . N < 1 {
size = 1
} else {
size = int ( l . N )
}
}
buf = make ( [ ] byte , size )
}
var seqPos int
for {
nr , er := src . Read ( buf )
if nr > 0 {
// Look for sequences to trigger client-side diags
// A repeat of 4 keys (conveniently 'dead' chars for most
// interactive shells; here CTRL-]) shall introduce
// some special responses or actions on the client side.
if seqPos < 4 {
if buf [ 0 ] == 0x1d {
seqPos ++
}
2019-05-10 05:46:08 +00:00
} else {
2018-12-09 05:44:06 +00:00
if v , ok := escs [ buf [ 0 ] ] ; ok {
v ( dst )
nr --
buf = buf [ 1 : ]
}
seqPos = 0
}
nw , ew := dst . Write ( buf [ 0 : nr ] )
if nw > 0 {
written += int64 ( nw )
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io . ErrShortWrite
break
}
}
if er != nil {
if er != io . EOF {
err = er
}
break
}
}
return written , err
}
////////////////////////////////////////////////////
2018-11-23 00:49:09 +00:00
// GetSize gets the terminal size using 'stty' command
2019-05-10 05:46:08 +00:00
//
// TODO: do in code someday instead of using external 'stty'
2018-05-26 20:43:09 +00:00
func GetSize ( ) ( cols , rows int , err error ) {
2018-11-23 00:49:09 +00:00
cmd := exec . Command ( "stty" , "size" ) // #nosec
2018-04-29 02:28:37 +00:00
cmd . Stdin = os . Stdin
out , err := cmd . Output ( )
if err != nil {
2018-05-26 20:43:09 +00:00
log . Println ( err )
2018-06-30 02:23:11 +00:00
cols , rows = 80 , 24 //failsafe
2018-05-26 20:43:09 +00:00
} else {
2018-11-23 00:49:09 +00:00
n , err := fmt . Sscanf ( string ( out ) , "%d %d\n" , & rows , & cols )
if n < 2 ||
rows < 0 ||
cols < 0 ||
rows > 9000 ||
cols > 9000 ||
err != nil {
log . Printf ( "GetSize error: rows:%d cols:%d; %v\n" ,
rows , cols , err )
}
2018-04-29 02:28:37 +00:00
}
return
}
2018-08-07 05:29:51 +00:00
// doCopyMode begins a secure hkexsh local<->remote file copy operation.
2019-05-10 05:46:08 +00:00
//
2018-11-25 18:24:10 +00:00
// TODO: reduce gocyclo
2018-11-23 00:49:09 +00:00
func doCopyMode ( conn * hkexnet . Conn , remoteDest bool , files string , rec * hkexsh . Session ) ( exitStatus uint32 , err error ) {
2018-08-07 05:29:51 +00:00
if remoteDest {
2018-09-07 22:35:33 +00:00
log . Println ( "local files:" , files , "remote filepath:" , string ( rec . Cmd ( ) ) )
2018-08-25 01:50:45 +00:00
var c * exec . Cmd
//os.Clearenv()
//os.Setenv("HOME", u.HomeDir)
//os.Setenv("TERM", "vt102") // TODO: server or client option?
cmdName := "/bin/tar"
2018-09-06 23:37:17 +00:00
cmdArgs := [ ] string { "-cz" , "-f" , "/dev/stdout" }
2018-08-26 06:38:58 +00:00
files = strings . TrimSpace ( files )
2018-08-31 03:06:42 +00:00
// 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
2018-09-01 17:20:33 +00:00
// files and dirs to be extracted to a single dest dir server-side,
2018-09-03 02:58:13 +00:00
// whilst preserving the subtrees of dirs on the other side.
2018-08-31 03:06:42 +00:00
// 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 :)
2018-09-01 17:20:33 +00:00
//
// This is the 'scatter/gather' logic to allow specification of
// files and dirs in different trees to be deposited in a single
// remote destDir.
2018-08-26 06:38:58 +00:00
for _ , v := range strings . Split ( files , " " ) {
2018-11-23 00:49:09 +00:00
v , _ = filepath . Abs ( v ) // #nosec
2018-08-31 03:06:42 +00:00
dirTmp , fileTmp := path . Split ( v )
2018-09-01 17:20:33 +00:00
if dirTmp == "" {
cmdArgs = append ( cmdArgs , fileTmp )
} else {
cmdArgs = append ( cmdArgs , "-C" , dirTmp , fileTmp )
}
2018-08-26 06:38:58 +00:00
}
2018-09-06 23:37:17 +00:00
log . Printf ( "[%v %v]\n" , cmdName , cmdArgs )
2018-08-25 01:50:45 +00:00
// 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)
2018-11-23 00:49:09 +00:00
c = exec . Command ( cmdName , cmdArgs ... ) // #nosec
c . Dir , _ = os . Getwd ( ) // #nosec
2018-09-06 23:37:17 +00:00
log . Println ( "[wd:" , c . Dir , "]" )
2018-08-25 01:50:45 +00:00
c . Stdout = conn
2018-09-06 04:58:55 +00:00
stdErrBuffer := new ( bytes . Buffer )
c . Stderr = stdErrBuffer
2018-08-25 06:22:07 +00:00
2018-08-25 01:50:45 +00:00
// Start the command (no pty)
err = c . Start ( ) // returns immediately
2018-09-17 00:14:50 +00:00
/////////////
// NOTE: There is, apparently, a bug in Go stdlib here. Start()
// can actually return immediately, on a command which *does*
// start but exits quickly, with c.Wait() error
// "c.Wait status: exec: not started".
// As in this example, attempting a client->server copy to
// a nonexistent remote dir (it's tar exiting right away, exitStatus
// 2, stderr
// /bin/tar -xz -C /home/someuser/nosuchdir
// stderr: fork/exec /bin/tar: no such file or directory
//
// In this case, c.Wait() won't give us the real
// exit status (is it lost?).
/////////////
2018-08-25 01:50:45 +00:00
if err != nil {
2018-09-17 00:14:50 +00:00
fmt . Println ( "cmd exited immediately. Cannot get cmd.Wait().ExitStatus()" )
err = errors . New ( "cmd exited prematurely" )
exitStatus = uint32 ( 2 )
2018-08-25 01:50:45 +00:00
} 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 {
2018-09-06 20:50:56 +00:00
exitStatus = uint32 ( status . ExitStatus ( ) )
2018-09-06 04:58:55 +00:00
fmt . Print ( stdErrBuffer )
2018-08-25 01:50:45 +00:00
}
}
}
2018-09-17 00:14:50 +00:00
// send CSOExitStatus to inform remote (server) end cp is done
log . Println ( "Sending local exitStatus:" , exitStatus )
r := make ( [ ] byte , 4 )
binary . BigEndian . PutUint32 ( r , exitStatus )
2018-11-23 00:49:09 +00:00
_ , we := conn . WritePacket ( r , hkexnet . CSOExitStatus )
if we != nil {
fmt . Println ( "Error:" , we )
}
2018-09-17 00:14:50 +00:00
// Do a final read for remote's exit status
2018-09-06 20:50:56 +00:00
s := make ( [ ] byte , 4 )
2018-09-17 00:14:50 +00:00
_ , remErr := conn . Read ( s )
2018-11-21 02:50:09 +00:00
if remErr != io . EOF &&
! strings . Contains ( remErr . Error ( ) , "use of closed network" ) &&
! strings . Contains ( remErr . Error ( ) , "connection reset by peer" ) {
2018-09-17 00:14:50 +00:00
fmt . Printf ( "*** remote status Read() failed: %v\n" , remErr )
2018-11-21 02:50:09 +00:00
} else {
conn . SetStatus ( 0 ) // cp finished OK
2018-09-17 00:14:50 +00:00
}
// If local side status was OK, use remote side's status
if exitStatus == 0 {
2018-09-18 06:07:04 +00:00
exitStatus = uint32 ( conn . GetStatus ( ) )
2018-09-17 00:14:50 +00:00
log . Println ( "Received remote exitStatus:" , exitStatus )
}
log . Printf ( "*** client->server cp finished , status %d ***\n" , conn . GetStatus ( ) )
2018-08-25 01:50:45 +00:00
}
2018-08-07 05:29:51 +00:00
} else {
2018-09-07 22:35:33 +00:00
log . Println ( "remote filepath:" , string ( rec . Cmd ( ) ) , "local files:" , files )
2018-08-25 01:50:45 +00:00
var c * exec . Cmd
cmdName := "/bin/tar"
destPath := files
2018-09-06 23:37:17 +00:00
cmdArgs := [ ] string { "-xz" , "-C" , destPath }
log . Printf ( "[%v %v]\n" , cmdName , cmdArgs )
2018-08-25 01:50:45 +00:00
// 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#`}
2018-11-23 00:49:09 +00:00
c = exec . Command ( cmdName , cmdArgs ... ) // #nosec
2018-08-25 01:50:45 +00:00
c . Stdin = conn
c . Stdout = os . Stdout
c . Stderr = os . Stderr
2018-08-25 06:22:07 +00:00
2018-08-25 01:50:45 +00:00
// Start the command (no pty)
err = c . Start ( ) // returns immediately
if err != nil {
fmt . Println ( 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 {
2018-09-06 20:50:56 +00:00
exitStatus = uint32 ( status . ExitStatus ( ) )
2018-08-25 01:50:45 +00:00
}
}
}
2018-09-06 07:16:44 +00:00
// return local status, if nonzero;
// otherwise, return remote status if nonzero
if exitStatus == 0 {
2018-09-06 20:50:56 +00:00
exitStatus = uint32 ( conn . GetStatus ( ) )
2018-09-06 07:16:44 +00:00
}
2018-11-21 02:50:09 +00:00
log . Printf ( "*** server->client cp finished, status %d ***\n" , conn . GetStatus ( ) )
2018-08-25 01:50:45 +00:00
}
2018-08-07 05:29:51 +00:00
}
2018-08-25 01:50:45 +00:00
return
2018-08-07 05:29:51 +00:00
}
2019-05-10 05:46:08 +00:00
// doShellMode begins an hkexsh shell session (one-shot command or
// interactive).
2018-09-07 22:35:33 +00:00
func doShellMode ( isInteractive bool , conn * hkexnet . Conn , oldState * hkexsh . State , rec * hkexsh . Session ) {
2018-08-07 05:29:51 +00:00
//client reader (from server) goroutine
//Read remote end's stdout
2018-11-12 05:05:25 +00:00
2018-08-07 05:29:51 +00:00
wg . Add ( 1 )
2018-11-23 07:09:22 +00:00
// #gv:s/label=\"doShellMode\$1\"/label=\"shellRemoteToStdin\"/
2018-11-23 07:43:03 +00:00
// TODO:.gv:doShellMode:1:shellRemoteToStdin
2018-11-26 05:08:37 +00:00
shellRemoteToStdin := func ( ) {
2018-11-30 02:05:22 +00:00
defer func ( ) {
wg . Done ( )
} ( )
2018-08-07 05:29:51 +00:00
// By deferring a call to wg.Done(),
// each goroutine guarantees that it marks
// its direction's stream as finished.
2018-11-30 02:05:22 +00:00
// pkg io/Copy expects EOF so normally this will
2018-08-07 05:29:51 +00:00
// exit with inerr == nil
2018-12-09 05:37:26 +00:00
_ , inerr := io . Copy ( os . Stdout , conn )
2018-08-07 05:29:51 +00:00
if inerr != nil {
2018-11-23 00:49:09 +00:00
_ = hkexsh . Restore ( int ( os . Stdin . Fd ( ) ) , oldState ) // #nosec
2018-09-06 18:40:13 +00:00
// Copy operations and user logging off will cause
// a "use of closed network connection" so handle that
// gracefully here
2018-09-06 07:16:44 +00:00
if ! strings . HasSuffix ( inerr . Error ( ) , "use of closed network connection" ) {
log . Println ( inerr )
2019-06-20 04:42:34 +00:00
exitWithStatus ( 1 )
2018-09-06 07:16:44 +00:00
}
2018-08-07 05:29:51 +00:00
}
2018-09-07 22:35:33 +00:00
rec . SetStatus ( uint32 ( conn . GetStatus ( ) ) )
2018-11-23 00:49:09 +00:00
log . Println ( "rec.status:" , rec . Status ( ) )
2018-08-07 05:29:51 +00:00
if isInteractive {
log . Println ( "[* Got EOF *]" )
2018-11-23 00:49:09 +00:00
_ = hkexsh . Restore ( int ( os . Stdin . Fd ( ) ) , oldState ) // #nosec
2019-06-20 04:42:34 +00:00
exitWithStatus ( int ( rec . Status ( ) ) )
2018-08-07 05:29:51 +00:00
}
2018-11-23 07:43:03 +00:00
}
2018-11-26 05:08:37 +00:00
go shellRemoteToStdin ( )
2018-08-07 05:29:51 +00:00
// 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 )
2018-11-23 07:09:22 +00:00
// #gv:s/label=\"doShellMode\$2\"/label=\"shellStdinToRemote\"/
2018-11-23 07:43:03 +00:00
// TODO:.gv:doShellMode:2:shellStdinToRemote
shellStdinToRemote := func ( ) {
2018-08-07 05:29:51 +00:00
defer wg . Done ( )
_ , outerr := func ( conn * hkexnet . Conn , r io . Reader ) ( w int64 , e error ) {
2018-11-30 02:05:22 +00:00
// Copy() expects EOF so this will
// exit with outerr == nil
2018-12-09 05:44:06 +00:00
w , e = Copy ( conn , r )
2018-08-07 05:29:51 +00:00
return w , e
} ( conn , os . Stdin )
if outerr != nil {
log . Println ( outerr )
fmt . Println ( outerr )
2018-11-23 00:49:09 +00:00
_ = hkexsh . Restore ( int ( os . Stdin . Fd ( ) ) , oldState ) // #nosec
2018-09-06 23:23:57 +00:00
log . Println ( "[Hanging up]" )
2019-06-20 04:42:34 +00:00
exitWithStatus ( 0 )
2018-08-07 05:29:51 +00:00
}
2018-11-23 07:43:03 +00:00
}
go shellStdinToRemote ( )
2018-08-07 05:29:51 +00:00
}
// Wait until both stdin and stdout goroutines finish before returning
// (ensure client gets all data from server before closing)
wg . Wait ( )
}
2018-11-23 00:49:09 +00:00
func usageShell ( ) {
2018-11-25 18:24:10 +00:00
fmt . Fprintf ( os . Stderr , "Usage of %s:\n" , os . Args [ 0 ] ) // nolint: errcheck
fmt . Fprintf ( os . Stderr , "%s [opts] [user]@server\n" , os . Args [ 0 ] ) // nolint: errcheck
2018-08-26 07:12:42 +00:00
flag . PrintDefaults ( )
}
2018-11-23 00:49:09 +00:00
func usageCp ( ) {
2018-11-25 18:24:10 +00:00
fmt . Fprintf ( os . Stderr , "Usage of %s:\n" , os . Args [ 0 ] ) // nolint: errcheck
fmt . Fprintf ( os . Stderr , "%s [opts] srcFileOrDir [...] [user]@server[:dstpath]\n" , os . Args [ 0 ] ) // nolint: errcheck
fmt . Fprintf ( os . Stderr , "%s [opts] [user]@server[:srcFileOrDir] dstPath\n" , os . Args [ 0 ] ) // nolint: errcheck
2018-08-26 07:12:42 +00:00
flag . PrintDefaults ( )
}
2019-05-10 05:46:08 +00:00
// rejectUserMsg snarkily rebukes users giving incorrect
// credentials.
//
// TODO: do this from the server side and have client just emit that
2018-09-06 23:23:57 +00:00
func rejectUserMsg ( ) string {
return "Begone, " + spinsult . GetSentence ( ) + "\r\n"
}
2018-10-29 02:17:47 +00:00
// Transmit request to server for it to set up the remote end of a tunnel
//
// Server responds with [CSOTunAck:rport] or [CSOTunRefused:rport]
2018-11-01 03:11:00 +00:00
// (handled in hkexnet.Read())
func reqTunnel ( hc * hkexnet . Conn , lp uint16 , p string /*net.Addr*/ , rp uint16 ) {
// Write request to server so it can attempt to set up its end
2018-10-27 08:51:40 +00:00
var bTmp bytes . Buffer
2018-11-23 00:49:09 +00:00
if e := binary . Write ( & bTmp , binary . BigEndian , lp ) ; e != nil {
2018-11-25 18:24:10 +00:00
fmt . Fprintln ( os . Stderr , "reqTunnel:" , e ) // nolint: errcheck
2018-11-23 00:49:09 +00:00
}
if e := binary . Write ( & bTmp , binary . BigEndian , rp ) ; e != nil {
2018-11-25 18:24:10 +00:00
fmt . Fprintln ( os . Stderr , "reqTunnel:" , e ) // nolint: errcheck
2018-11-23 00:49:09 +00:00
}
2018-11-25 18:24:10 +00:00
_ = logger . LogDebug ( fmt . Sprintln ( "[Client sending CSOTunSetup]" ) ) // nolint: gosec
2018-11-23 00:49:09 +00:00
if n , e := hc . WritePacket ( bTmp . Bytes ( ) , hkexnet . CSOTunSetup ) ; e != nil || n != len ( bTmp . Bytes ( ) ) {
2018-11-25 18:24:10 +00:00
fmt . Fprintln ( os . Stderr , "reqTunnel:" , e ) // nolint: errcheck
2018-11-23 00:49:09 +00:00
}
2018-10-27 08:51:40 +00:00
}
2018-11-12 07:26:22 +00:00
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 i == len ( a ) - 1 {
isDest = true
}
} else {
otherArgs = append ( otherArgs , a [ i ] )
}
}
return fancyUser , fancyHost , fancyPath , isDest , otherArgs
}
2018-11-12 08:44:16 +00:00
func launchTuns ( conn * hkexnet . Conn , remoteHost string , tuns string ) {
2018-11-25 18:24:10 +00:00
remAddrs , _ := net . LookupHost ( remoteHost ) // nolint: gosec
2018-11-12 08:44:16 +00:00
2018-11-14 07:59:34 +00:00
if tuns == "" {
return
}
2018-11-12 08:44:16 +00:00
tunSpecs := strings . Split ( tuns , "," )
2018-11-14 07:59:34 +00:00
for _ , tunItem := range tunSpecs {
2018-11-12 08:44:16 +00:00
var lPort , rPort uint16
2018-11-25 18:24:10 +00:00
_ , _ = fmt . Sscanf ( tunItem , "%d:%d" , & lPort , & rPort ) // nolint: gosec
2018-11-12 08:44:16 +00:00
reqTunnel ( conn , lPort , remAddrs [ 0 ] , rPort )
}
2018-11-12 07:26:22 +00:00
}
2018-11-23 00:49:09 +00:00
func sendSessionParams ( conn io . Writer /* *hkexnet.Conn*/ , rec * hkexsh . Session ) ( e error ) {
_ , e = fmt . Fprintf ( conn , "%d %d %d %d %d %d\n" ,
len ( rec . Op ( ) ) , len ( rec . Who ( ) ) , len ( rec . ConnHost ( ) ) , len ( rec . TermType ( ) ) , len ( rec . Cmd ( ) ) , len ( rec . AuthCookie ( true ) ) )
2018-11-25 18:24:10 +00:00
if e != nil {
return
}
2018-11-23 00:49:09 +00:00
_ , e = conn . Write ( rec . Op ( ) )
2018-11-25 18:24:10 +00:00
if e != nil {
return
}
2018-11-23 00:49:09 +00:00
_ , e = conn . Write ( rec . Who ( ) )
2018-11-25 18:24:10 +00:00
if e != nil {
return
}
2018-11-23 00:49:09 +00:00
_ , e = conn . Write ( rec . ConnHost ( ) )
2018-11-25 18:24:10 +00:00
if e != nil {
return
}
2018-11-23 00:49:09 +00:00
_ , e = conn . Write ( rec . TermType ( ) )
2018-11-25 18:24:10 +00:00
if e != nil {
return
}
2018-11-23 00:49:09 +00:00
_ , e = conn . Write ( rec . Cmd ( ) )
2018-11-25 18:24:10 +00:00
if e != nil {
return
}
2018-11-23 00:49:09 +00:00
_ , e = conn . Write ( rec . AuthCookie ( true ) )
2018-11-25 18:24:10 +00:00
return e
2018-11-23 00:49:09 +00:00
}
2018-11-25 18:24:10 +00:00
// TODO: reduce gocyclo
2018-01-06 15:30:56 +00:00
func main ( ) {
2018-05-13 01:41:39 +00:00
var vopt bool
2018-09-14 18:58:10 +00:00
var gopt bool //login via password, asking server to generate authToken
2018-01-21 23:46:40 +00:00
var dbg bool
2018-11-23 00:49:09 +00:00
var shellMode bool // if true act as shell, else file copier
var cipherAlg string //cipher alg
var hmacAlg string //hmac alg
var kexAlg string //KEX/KEM alg
2018-01-13 06:24:40 +00:00
var server string
2018-08-26 06:38:58 +00:00
var port uint
2018-01-21 23:46:40 +00:00
var cmdStr string
2018-10-27 08:51:40 +00:00
var tunSpecStr string // lport1:rport1[,lport2:rport2,...]
2018-07-19 05:32:49 +00:00
2018-07-29 20:22:35 +00:00
var copySrc [ ] byte
2018-07-19 05:32:49 +00:00
var copyDst string
2018-01-22 06:02:08 +00:00
var authCookie string
2018-05-26 20:43:09 +00:00
var chaffEnabled bool
2018-05-07 00:41:09 +00:00
var chaffFreqMin uint
var chaffFreqMax uint
var chaffBytesMax uint
2018-05-07 01:20:12 +00:00
2018-07-19 05:32:49 +00:00
var op [ ] byte
2018-01-19 05:17:57 +00:00
isInteractive := false
2018-01-13 06:47:57 +00:00
2018-05-13 01:41:39 +00:00
flag . BoolVar ( & vopt , "v" , false , "show version" )
2018-07-19 05:32:49 +00:00
flag . BoolVar ( & dbg , "d" , false , "debug logging" )
2018-11-23 00:49:09 +00:00
flag . StringVar ( & cipherAlg , "c" , "C_AES_256" , "`cipher` [\"C_AES_256\" | \"C_TWOFISH_128\" | \"C_BLOWFISH_64\" | \"C_CRYPTMT1\"]" )
2019-04-09 04:58:33 +00:00
flag . StringVar ( & hmacAlg , "m" , "H_SHA256" , "`hmac` [\"H_SHA256\" | \"H_SHA512\"]" )
2019-07-03 16:50:37 +00:00
flag . StringVar ( & kexAlg , "k" , "KEX_HERRADURA512" , "`kex` [\"KEX_HERRADURA{256/512/1024/2048}\" | \"KEX_KYBER{512/768/1024}\" | \"KEX_NEWHOPE\" | \"KEX_NEWHOPE_SIMPLE\"]" )
2019-08-17 06:16:40 +00:00
flag . StringVar ( & kcpMode , "K" , "unused" , ` set to one of ["KCP_NONE","KCP_AES", "KCP_BLOWFISH", "KCP_CAST5", "KCP_SM4", "KCP_SALSA20", "KCP_SIMPLEXOR", "KCP_TEA", "KCP_3DES", "KCP_TWOFISH", "KCP_XTEA"] to use KCP (github.com/xtaci/kcp-go) reliable UDP instead of TCP ` )
2018-08-26 06:51:11 +00:00
flag . UintVar ( & port , "p" , 2000 , "`port`" )
2018-09-14 06:51:49 +00:00
//flag.StringVar(&authCookie, "a", "", "auth cookie")
2019-04-09 04:58:33 +00:00
flag . BoolVar ( & chaffEnabled , "e" , true , "enable chaff pkts" )
2018-10-27 08:51:40 +00:00
flag . UintVar ( & chaffFreqMin , "f" , 100 , "`msecs-min` chaff pkt freq min (msecs)" )
flag . UintVar ( & chaffFreqMax , "F" , 5000 , "`msecs-max` chaff pkt freq max (msecs)" )
flag . UintVar ( & chaffBytesMax , "B" , 64 , "chaff pkt size max (bytes)" )
2018-07-29 07:48:42 +00:00
2019-06-20 04:42:34 +00:00
flag . StringVar ( & cpuprofile , "cpuprofile" , "" , "write cpu profile to `file`" )
flag . StringVar ( & memprofile , "memprofile" , "" , "write memory profile to `file`" )
2018-07-19 05:32:49 +00:00
// Find out what program we are (shell or copier)
myPath := strings . Split ( os . Args [ 0 ] , string ( os . PathSeparator ) )
2018-07-29 07:48:42 +00:00
if myPath [ len ( myPath ) - 1 ] != "hkexcp" && myPath [ len ( myPath ) - 1 ] != "hkexcp.exe" {
2018-07-19 05:32:49 +00:00
// hkexsh accepts a command (-x) but not
// a srcpath (-r) or dstpath (-t)
2018-08-26 06:51:11 +00:00
flag . StringVar ( & cmdStr , "x" , "" , "`command` to run (if not specified run interactive shell)" )
2018-11-12 07:26:22 +00:00
flag . StringVar ( & tunSpecStr , "T" , "" , "`tunnelspec` localPort:remotePort[,localPort:remotePort,...]" )
2018-09-14 18:58:10 +00:00
flag . BoolVar ( & gopt , "g" , false , "ask server to generate authtoken" )
2018-08-06 04:43:21 +00:00
shellMode = true
2018-11-23 00:49:09 +00:00
flag . Usage = usageShell
2018-08-07 05:29:51 +00:00
} else {
2018-11-23 00:49:09 +00:00
flag . Usage = usageCp
2018-08-07 05:29:51 +00:00
}
2018-07-29 07:48:42 +00:00
flag . Parse ( )
2019-06-20 04:42:34 +00:00
if cpuprofile != "" {
f , err := os . Create ( cpuprofile )
if err != nil {
log . Fatal ( "could not create CPU profile: " , err )
}
defer f . Close ( )
fmt . Println ( "StartCPUProfile()" )
if err := pprof . StartCPUProfile ( f ) ; err != nil {
log . Fatal ( "could not start CPU profile: " , err )
} else {
defer pprof . StopCPUProfile ( )
}
go func ( ) { http . ListenAndServe ( "localhost:6060" , nil ) } ( )
}
2018-09-14 08:13:14 +00:00
remoteUser , remoteHost , tmpPath , pathIsDest , otherArgs :=
2018-08-26 06:38:58 +00:00
parseNonSwitchArgs ( flag . Args ( ) )
2018-09-08 03:37:47 +00:00
//fmt.Println("otherArgs:", otherArgs)
2018-08-26 06:38:58 +00:00
// Set defaults if user doesn't specify user, path or port
var uname string
if remoteUser == "" {
2018-11-25 18:24:10 +00:00
u , _ := user . Current ( ) // nolint: gosec
2018-08-26 06:38:58 +00:00
uname = u . Username
} else {
uname = remoteUser
2018-07-29 07:48:42 +00:00
}
2018-08-26 06:38:58 +00:00
2018-09-14 08:13:14 +00:00
if remoteHost != "" {
server = remoteHost + ":" + fmt . Sprintf ( "%d" , port )
2018-08-26 06:38:58 +00:00
}
if tmpPath == "" {
tmpPath = "."
2018-07-29 07:48:42 +00:00
}
2018-08-07 05:29:51 +00:00
var fileArgs string
2018-08-26 06:38:58 +00:00
if ! shellMode /*&& tmpPath != ""*/ {
2018-07-29 20:22:35 +00:00
// -if pathIsSrc && len(otherArgs) > 1 ERROR
// -else flatten otherArgs into space-delim list => copySrc
2018-07-29 19:47:44 +00:00
if pathIsDest {
2018-08-07 05:29:51 +00:00
if len ( otherArgs ) == 0 {
2018-08-25 01:50:45 +00:00
log . Fatal ( "ERROR: Must specify at least one dest path for copy" )
2018-08-07 05:29:51 +00:00
} else {
for _ , v := range otherArgs {
copySrc = append ( copySrc , ' ' )
copySrc = append ( copySrc , v ... )
}
copyDst = tmpPath
fileArgs = string ( copySrc )
2018-07-29 20:22:35 +00:00
}
2018-08-23 18:03:19 +00:00
} else {
2018-08-07 05:29:51 +00:00
if len ( otherArgs ) == 0 {
2018-08-25 01:50:45 +00:00
log . Fatal ( "ERROR: Must specify src path for copy" )
2018-08-07 05:29:51 +00:00
} 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 {
2018-07-29 20:22:35 +00:00
log . Fatal ( "ERROR: cannot specify more than one dest path for copy" )
}
2018-08-06 04:43:21 +00:00
copySrc = [ ] byte ( tmpPath )
2018-08-07 05:29:51 +00:00
fileArgs = copyDst
2018-07-29 19:47:44 +00:00
}
2018-07-29 07:48:42 +00:00
}
2018-08-06 04:43:21 +00:00
// Do some more option consistency checks
2018-07-19 05:32:49 +00:00
2018-08-06 04:43:21 +00:00
//fmt.Println("server finally is:", server)
2018-07-29 07:48:42 +00:00
if flag . NFlag ( ) == 0 && server == "" {
2018-07-19 05:32:49 +00:00
flag . Usage ( )
2019-06-20 04:42:34 +00:00
exitWithStatus ( 0 )
2018-07-19 05:32:49 +00:00
}
2018-01-12 07:01:39 +00:00
2018-05-13 01:41:39 +00:00
if vopt {
2019-07-11 17:12:38 +00:00
fmt . Printf ( "version %s (%s)\n" , version , gitCommit )
2019-06-20 04:42:34 +00:00
exitWithStatus ( 0 )
2018-05-13 01:41:39 +00:00
}
2018-07-19 05:32:49 +00:00
if len ( cmdStr ) != 0 && ( len ( copySrc ) != 0 || len ( copyDst ) != 0 ) {
2018-07-29 20:22:35 +00:00
log . Fatal ( "incompatible options -- either cmd (-x) or copy ops but not both" )
2018-07-19 05:32:49 +00:00
}
2018-08-06 04:43:21 +00:00
//-------------------------------------------------------------------
// Here we have parsed all options and can now carry out
// either the shell session or copy operation.
_ = shellMode
2018-11-25 18:24:10 +00:00
Log , _ = logger . New ( logger . LOG_USER | logger . LOG_DEBUG | logger . LOG_NOTICE | logger . LOG_ERR , "hkexsh" ) // nolint: errcheck,gosec
2018-11-02 01:52:01 +00:00
hkexnet . Init ( dbg , "hkexsh" , logger . LOG_USER | logger . LOG_DEBUG | logger . LOG_NOTICE | logger . LOG_ERR )
2018-01-21 23:46:40 +00:00
if dbg {
2018-10-26 05:14:18 +00:00
log . SetOutput ( Log )
2018-01-21 23:46:40 +00:00
} else {
log . SetOutput ( ioutil . Discard )
}
2018-10-27 08:51:40 +00:00
2018-09-14 18:58:10 +00:00
if ! gopt {
// See if we can log in via an auth token
2018-11-25 18:24:10 +00:00
u , _ := user . Current ( ) // nolint: gosec
2018-09-14 18:58:10 +00:00
ab , aerr := ioutil . ReadFile ( fmt . Sprintf ( "%s/.hkexsh_id" , u . HomeDir ) )
if aerr == nil {
idx := strings . Index ( string ( ab ) , remoteHost )
if idx >= 0 {
ab = ab [ idx : ]
2018-09-17 00:30:02 +00:00
entries := strings . SplitN ( string ( ab ) , "\n" , - 1 )
authCookie = strings . TrimSpace ( entries [ 0 ] )
// Security scrub
ab = nil
runtime . GC ( )
2018-09-14 18:58:10 +00:00
} else {
2018-11-23 00:49:09 +00:00
_ , _ = fmt . Fprintln ( os . Stderr , "[no authtoken, use -g to request one from server]" )
2018-09-14 18:58:10 +00:00
}
2018-09-17 00:56:17 +00:00
} else {
log . Printf ( "[cannot read %s/.hkexsh_id]\n" , u . HomeDir )
2018-09-14 18:58:10 +00:00
}
2018-09-14 06:51:49 +00:00
}
2019-07-03 16:50:37 +00:00
// Enforce some sane min/max vals on chaff flags
if chaffFreqMin < 2 {
chaffFreqMin = 2
}
if chaffFreqMax == 0 {
chaffFreqMax = chaffFreqMin + 1
}
if chaffBytesMax == 0 || chaffBytesMax > 4096 {
chaffBytesMax = 64
}
2018-08-07 05:29:51 +00:00
if shellMode {
// We must make the decision about interactivity before Dial()
// as it affects chaffing behaviour. 20180805
2018-09-14 18:58:10 +00:00
if gopt {
2018-11-25 18:24:10 +00:00
fmt . Fprintln ( os . Stderr , "[requesting authtoken from server]" ) // nolint: errcheck
2018-09-14 06:51:49 +00:00
op = [ ] byte { 'A' }
chaffFreqMin = 2
chaffFreqMax = 10
} else if len ( cmdStr ) == 0 {
2018-08-07 05:29:51 +00:00
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
}
2018-08-06 04:43:21 +00:00
} else {
2018-08-07 05:29:51 +00:00
// as copy mode is also non-interactive, set up chaffing
// just like the 'c' mode above
2018-08-06 04:43:21 +00:00
chaffFreqMin = 2
chaffFreqMax = 10
2018-08-07 05:29:51 +00:00
if pathIsDest {
// client->server file copy
// src file list is in copySrc
op = [ ] byte { 'D' }
2018-09-14 06:51:49 +00:00
//fmt.Println("client->server copy:", string(copySrc), "->", copyDst)
2018-08-07 05:29:51 +00:00
cmdStr = copyDst
} else {
// server->client file copy
// remote src file(s) in copyDsr
op = [ ] byte { 'S' }
2018-09-14 06:51:49 +00:00
//fmt.Println("server->client copy:", string(copySrc), "->", copyDst)
2018-08-07 05:29:51 +00:00
cmdStr = string ( copySrc )
}
2018-08-06 04:43:21 +00:00
}
2019-08-17 06:16:40 +00:00
2019-08-14 03:54:58 +00:00
proto := "tcp"
2019-08-17 06:16:40 +00:00
if kcpMode != "unused" {
proto = "kcp"
2019-08-14 03:54:58 +00:00
}
2019-08-17 06:16:40 +00:00
conn , err := hkexnet . Dial ( proto , server , cipherAlg , hmacAlg , kexAlg , kcpMode )
2018-01-06 15:30:56 +00:00
if err != nil {
2018-09-14 06:51:49 +00:00
fmt . Println ( err )
2019-06-20 04:42:34 +00:00
exitWithStatus ( 3 )
2018-01-13 06:47:57 +00:00
}
2018-11-25 18:24:10 +00:00
defer conn . Close ( ) // nolint: errcheck
2018-01-23 21:53:05 +00:00
// From this point on, conn is a secure encrypted channel
2018-01-18 04:36:53 +00:00
2018-01-19 05:17:57 +00:00
// Set stdin in raw mode if it's an interactive session
2018-01-21 23:46:40 +00:00
// TODO: send flag to server side indicating this
// affects shell command used
2018-04-15 19:58:24 +00:00
var oldState * hkexsh . State
2018-08-06 04:43:21 +00:00
if shellMode {
if isatty . IsTerminal ( os . Stdin . Fd ( ) ) {
oldState , err = hkexsh . MakeRaw ( int ( os . Stdin . Fd ( ) ) )
if err != nil {
panic ( err )
}
2018-11-23 07:09:22 +00:00
// #gv:s/label=\"main\$1\"/label=\"deferRestore\"/
2018-11-23 07:43:03 +00:00
// TODO:.gv:main:1:deferRestore
2018-11-25 18:24:10 +00:00
defer func ( ) { _ = hkexsh . Restore ( int ( os . Stdin . Fd ( ) ) , oldState ) } ( ) // nolint: errcheck,gosec
2018-08-06 04:43:21 +00:00
} else {
log . Println ( "NOT A TTY" )
2018-01-19 05:17:57 +00:00
}
2018-01-19 02:57:37 +00:00
}
2018-01-22 06:02:08 +00:00
if len ( authCookie ) == 0 {
2018-09-14 07:40:20 +00:00
//No auth token, prompt for password
2018-01-22 06:02:08 +00:00
fmt . Printf ( "Gimme cookie:" )
2018-11-23 00:49:09 +00:00
ab , e := hkexsh . ReadPassword ( int ( os . Stdin . Fd ( ) ) )
2018-01-23 21:53:05 +00:00
fmt . Printf ( "\r\n" )
2018-11-23 00:49:09 +00:00
if e != nil {
panic ( e )
2018-01-22 06:02:08 +00:00
}
authCookie = string ( ab )
}
2018-11-23 00:49:09 +00:00
// Security scrub
runtime . GC ( )
2018-09-14 06:51:49 +00:00
2018-09-08 03:37:47 +00:00
// Set up session params and send over to server
2018-09-14 08:13:14 +00:00
rec := hkexsh . NewSession ( op , [ ] byte ( uname ) , [ ] byte ( remoteHost ) , [ ] byte ( os . Getenv ( "TERM" ) ) , [ ] byte ( cmdStr ) , [ ] byte ( authCookie ) , 0 )
2018-11-25 18:24:10 +00:00
sendErr := sendSessionParams ( & conn , rec )
if sendErr != nil {
log . Fatal ( sendErr )
}
2018-03-26 02:58:04 +00:00
2018-09-14 07:40:20 +00:00
//Security scrub
2018-11-25 18:24:10 +00:00
authCookie = "" // nolint: ineffassign
2018-09-14 07:40:20 +00:00
runtime . GC ( )
2018-09-06 23:23:57 +00:00
// Read auth reply from server
authReply := make ( [ ] byte , 1 ) // bool: 0 = fail, 1 = pass
_ , err = conn . Read ( authReply )
2018-11-23 00:49:09 +00:00
if err != nil {
2018-11-25 18:24:10 +00:00
fmt . Fprintln ( os . Stderr , "Error reading auth reply" ) // nolint: errcheck
2018-11-23 00:49:09 +00:00
rec . SetStatus ( 255 )
} else if authReply [ 0 ] == 0 {
2018-11-25 18:24:10 +00:00
fmt . Fprintln ( os . Stderr , rejectUserMsg ( ) ) // nolint: errcheck
2018-09-07 22:35:33 +00:00
rec . SetStatus ( 255 )
2018-08-07 05:29:51 +00:00
} else {
2018-09-06 23:23:57 +00:00
// Set up chaffing to server
conn . SetupChaff ( chaffFreqMin , chaffFreqMax , chaffBytesMax ) // enable client->server chaffing
if chaffEnabled {
2018-11-23 07:09:22 +00:00
// #gv:s/label=\"main\$2\"/label=\"deferCloseChaff\"/
2018-11-23 07:43:03 +00:00
// TODO:.gv:main:2:deferCloseChaff
2018-10-27 08:51:40 +00:00
conn . EnableChaff ( ) // goroutine, returns immediately
2018-09-06 23:23:57 +00:00
defer conn . DisableChaff ( )
defer conn . ShutdownChaff ( )
}
2018-08-06 07:06:09 +00:00
2018-11-12 06:46:39 +00:00
// Keepalive for any tunnels that may exist
2018-11-23 07:09:22 +00:00
// #gv:s/label=\"main\$1\"/label=\"tunKeepAlive\"/
2018-11-23 07:43:03 +00:00
// TODO:.gv:main:1:tunKeepAlive
2019-07-10 08:11:23 +00:00
//[1]: better to always send tunnel keepAlives even if client didn't specify
// any, to prevent listeners from knowing this.
//[1] if tunSpecStr != "" {
2018-11-23 07:43:03 +00:00
keepAliveWorker := func ( ) {
2018-11-12 06:46:39 +00:00
for {
2019-07-10 08:11:23 +00:00
// Add a bit of jitter to keepAlive so it doesn't stand out quite as much
time . Sleep ( time . Duration ( 2000 - rand . Intn ( 200 ) ) * time . Millisecond )
// FIXME: keepAlives should probably have small random packet len/data as well
// to further obscure them vs. interactive or tunnel data
2019-07-11 03:44:02 +00:00
// keepAlives must be >=2 bytes, due to processing elsewhere
2018-11-25 18:24:10 +00:00
conn . WritePacket ( [ ] byte { 0 , 0 } , hkexnet . CSOTunKeepAlive ) // nolint: errcheck,gosec
2018-11-12 06:46:39 +00:00
}
2018-11-23 07:43:03 +00:00
}
go keepAliveWorker ( )
2019-07-10 08:11:23 +00:00
//[1]}
2018-11-12 06:46:39 +00:00
2018-09-06 23:23:57 +00:00
if shellMode {
2018-11-12 07:26:22 +00:00
launchTuns ( & conn , remoteHost , tunSpecStr )
2018-09-29 19:15:53 +00:00
doShellMode ( isInteractive , & conn , oldState , rec )
2018-09-06 23:23:57 +00:00
} else { // copyMode
2018-11-25 18:24:10 +00:00
s , _ := doCopyMode ( & conn , pathIsDest , fileArgs , rec ) // nolint: errcheck,gosec
2018-09-14 06:51:49 +00:00
rec . SetStatus ( s )
2018-09-06 20:50:56 +00:00
}
2018-09-07 22:35:33 +00:00
if rec . Status ( ) != 0 {
2018-12-09 05:44:06 +00:00
_ = hkexsh . Restore ( int ( os . Stdin . Fd ( ) ) , oldState ) // nolint: errcheck,gosec
2018-11-25 18:24:10 +00:00
fmt . Fprintln ( os . Stderr , "Session exited with status:" , rec . Status ( ) ) // nolint: errcheck
2018-09-06 23:23:57 +00:00
}
}
2018-09-06 20:50:56 +00:00
2018-09-06 23:23:57 +00:00
if oldState != nil {
2018-11-25 18:24:10 +00:00
_ = hkexsh . Restore ( int ( os . Stdin . Fd ( ) ) , oldState ) // nolint: gosec
2018-09-06 18:40:13 +00:00
}
2019-06-20 04:42:34 +00:00
exitWithStatus ( int ( rec . Status ( ) ) )
}
// exitWithStatus wraps os.Exit() plus does any required pprof housekeeping
func exitWithStatus ( status int ) {
if cpuprofile != "" {
pprof . StopCPUProfile ( )
}
if memprofile != "" {
f , err := os . Create ( memprofile )
if err != nil {
log . Fatal ( "could not create memory profile: " , err )
}
defer f . Close ( )
runtime . GC ( ) // get up-to-date statistics
if err := pprof . WriteHeapProfile ( f ) ; err != nil {
log . Fatal ( "could not write memory profile: " , err )
}
}
os . Exit ( status )
2018-01-06 15:30:56 +00:00
}