2018-04-07 20:04:10 +00:00
// hkexshd server
//
// Copyright (c) 2017-2018 Russell Magee
// 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-14 06:51:49 +00:00
"crypto/rand"
2018-09-06 20:50:56 +00:00
"encoding/binary"
2018-09-14 06:51:49 +00:00
"encoding/hex"
2018-09-14 18:58:10 +00:00
"errors"
2018-01-13 06:24:40 +00:00
"flag"
2018-01-06 15:30:56 +00:00
"fmt"
2018-01-19 02:57:37 +00:00
"io"
2018-01-21 23:46:40 +00:00
"io/ioutil"
2018-01-06 15:30:56 +00:00
"log"
2018-01-21 04:37:27 +00:00
"os"
2018-01-18 00:39:01 +00:00
"os/exec"
2018-11-16 06:57:21 +00:00
"os/signal"
2018-01-18 00:39:01 +00:00
"os/user"
2018-08-25 06:22:07 +00:00
"path"
2018-08-06 04:43:21 +00:00
"sync"
2018-01-18 00:39:01 +00:00
"syscall"
2019-07-05 03:27:49 +00:00
"unsafe"
2018-01-09 02:27:01 +00:00
2018-06-27 21:58:57 +00:00
"blitter.com/go/goutmp"
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-11-14 07:59:34 +00:00
"blitter.com/go/hkexsh/logger"
2018-01-19 02:57:37 +00:00
"github.com/kr/pty"
2018-01-06 15:30:56 +00:00
)
2018-10-26 05:14:18 +00:00
var (
2019-08-09 04:36:37 +00:00
version string
gitCommit string // set in -ldflags by build
useSysLogin bool
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-09 04:36:37 +00:00
2018-11-25 18:24:10 +00:00
// Log - syslog output (with no -d)
Log * logger . Writer
2018-10-26 05:14:18 +00:00
)
2019-07-05 03:27:49 +00:00
func ioctl ( fd , request , argp uintptr ) error {
if _ , _ , e := syscall . Syscall6 ( syscall . SYS_IOCTL , fd , request , argp , 0 , 0 , 0 ) ; e != 0 {
return e
}
return nil
}
func ptsName ( fd uintptr ) ( string , error ) {
var n uintptr
err := ioctl ( fd , syscall . TIOCGPTN , uintptr ( unsafe . Pointer ( & n ) ) )
if err != nil {
return "" , err
}
return fmt . Sprintf ( "/dev/pts/%d" , n ) , nil
}
2018-01-19 02:57:37 +00:00
/* -------------------------------------------------------------- */
2018-08-23 18:03:19 +00:00
// Perform a client->server copy
2018-11-25 18:24:10 +00:00
func runClientToServerCopyAs ( who , ttype string , conn * hkexnet . Conn , fpath string , chaffing bool ) ( exitStatus uint32 , err error ) {
u , _ := user . Lookup ( who ) // nolint: gosec
2018-01-18 00:39:01 +00:00
var uid , gid uint32
2018-11-25 18:24:10 +00:00
fmt . Sscanf ( u . Uid , "%d" , & uid ) // nolint: gosec,errcheck
fmt . Sscanf ( u . Gid , "%d" , & gid ) // nolint: gosec,errcheck
2018-08-23 18:03:19 +00:00
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 ( )
2018-11-25 18:24:10 +00:00
os . Setenv ( "HOME" , u . HomeDir ) // nolint: gosec,errcheck
2018-11-29 05:03:20 +00:00
os . Setenv ( "TERM" , ttype ) // nolint: gosec,errcheck
os . Setenv ( "HKEXSH" , "1" ) // nolint: gosec,errcheck
2018-01-18 00:39:01 +00:00
2018-08-23 18:03:19 +00:00
var c * exec . Cmd
cmdName := "/bin/tar"
2018-08-25 06:22:07 +00:00
var destDir string
if path . IsAbs ( fpath ) {
destDir = fpath
} else {
destDir = path . Join ( u . HomeDir , fpath )
}
2018-08-31 03:06:42 +00:00
2018-09-06 23:37:17 +00:00
cmdArgs := [ ] string { "-xz" , "-C" , destDir }
2018-08-25 06:22:07 +00:00
2018-08-23 18:03:19 +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-08-31 03:06:42 +00:00
//cmdArgs := []string{"-x", "-C", destDir, `--xform=s#.*/\(.*\)#\1#`}
2018-11-25 18:24:10 +00:00
c = exec . Command ( cmdName , cmdArgs ... ) // nolint: gosec
2018-08-23 18:03:19 +00:00
2018-08-25 06:22:07 +00:00
c . Dir = destDir
2018-08-23 18:03:19 +00:00
//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}
2018-08-25 06:22:07 +00:00
//c.Dir = u.HomeDir
2018-01-18 00:39:01 +00:00
c . SysProcAttr = & syscall . SysProcAttr { }
c . SysProcAttr . Credential = & syscall . Credential { Uid : uid , Gid : gid }
2018-01-18 05:27:00 +00:00
c . Stdin = conn
2018-08-25 06:22:07 +00:00
c . Stdout = os . Stdout
c . Stderr = os . Stderr
2018-08-25 01:50:45 +00:00
2018-08-25 06:22:07 +00:00
if chaffing {
conn . EnableChaff ( )
}
defer conn . DisableChaff ( )
defer conn . ShutdownChaff ( )
2018-01-18 05:27:00 +00:00
2018-08-23 18:03:19 +00:00
// Start the command (no pty)
log . Printf ( "[%v %v]\n" , cmdName , cmdArgs )
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-01-19 02:57:37 +00:00
if err != nil {
2018-09-17 00:14:50 +00:00
log . Println ( "cmd exited immediately. Cannot get cmd.Wait().ExitStatus()" )
err = errors . New ( "cmd exited prematurely" )
//exitStatus = uint32(254)
exitStatus = hkexnet . CSEExecFail
2018-08-23 18:03:19 +00:00
} else {
if err := c . Wait ( ) ; err != nil {
2018-09-06 03:36:32 +00:00
//fmt.Println("*** c.Wait() done ***")
2018-08-23 18:03:19 +00:00
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-11-25 18:24:10 +00:00
//err = errors.New("cmd returned nonzero status")
2018-09-17 00:14:50 +00:00
log . Printf ( "Exit Status: %d\n" , exitStatus )
2018-08-23 18:03:19 +00:00
}
}
}
2018-09-17 00:14:50 +00:00
log . Println ( "*** client->server cp finished ***" )
2018-01-19 02:57:37 +00:00
}
2018-09-17 00:14:50 +00:00
return
2018-08-23 18:03:19 +00:00
}
// Perform a server->client copy
2018-11-25 18:24:10 +00:00
func runServerToClientCopyAs ( who , ttype string , conn * hkexnet . Conn , srcPath string , chaffing bool ) ( exitStatus uint32 , err error ) {
u , err := user . Lookup ( who )
if err != nil {
exitStatus = 1
return
}
2018-08-23 18:03:19 +00:00
var uid , gid uint32
2018-11-25 18:24:10 +00:00
_ , _ = fmt . Sscanf ( u . Uid , "%d" , & uid ) // nolint: gosec
_ , _ = fmt . Sscanf ( u . Gid , "%d" , & gid ) // nolint: gosec
2018-08-23 18:03:19 +00:00
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 ( )
2018-11-25 18:24:10 +00:00
_ = os . Setenv ( "HOME" , u . HomeDir ) // nolint: gosec
_ = os . Setenv ( "TERM" , ttype ) // nolint: gosec
_ = os . Setenv ( "HKEXSH" , "1" ) // nolint: gosec
2018-01-19 02:57:37 +00:00
2018-08-23 18:03:19 +00:00
var c * exec . Cmd
cmdName := "/bin/tar"
2018-08-31 03:16:55 +00:00
if ! path . IsAbs ( srcPath ) {
srcPath = fmt . Sprintf ( "%s%c%s" , u . HomeDir , os . PathSeparator , srcPath )
}
2018-08-26 06:38:58 +00:00
srcDir , srcBase := path . Split ( srcPath )
2018-09-06 23:37:17 +00:00
cmdArgs := [ ] string { "-cz" , "-C" , srcDir , "-f" , "-" , srcBase }
2018-08-26 06:38:58 +00:00
2018-11-25 18:24:10 +00:00
c = exec . Command ( cmdName , cmdArgs ... ) // nolint: gosec
2018-01-19 02:57:37 +00:00
2018-08-23 18:03:19 +00:00
//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
2018-09-06 04:58:55 +00:00
// Stderr sinkholing (or buffering to something other than stdout)
// is important. Any extraneous output to tarpipe messes up remote
// side as it's expecting pure tar data.
2018-08-26 06:38:58 +00:00
// (For example, if user specifies abs paths, tar outputs
// "Removing leading '/' from path names")
2018-09-06 04:58:55 +00:00
stdErrBuffer := new ( bytes . Buffer )
c . Stderr = stdErrBuffer
//c.Stderr = nil
2018-08-23 18:03:19 +00:00
2018-08-25 01:50:45 +00:00
if chaffing {
conn . EnableChaff ( )
}
//defer conn.Close()
defer conn . DisableChaff ( )
defer conn . ShutdownChaff ( )
2018-08-23 18:03:19 +00:00
// Start the command (no pty)
log . Printf ( "[%v %v]\n" , cmdName , cmdArgs )
err = c . Start ( ) // returns immediately
2018-01-18 00:39:01 +00:00
if err != nil {
log . Printf ( "Command finished with error: %v" , err )
2018-11-25 18:24:10 +00:00
return hkexnet . CSEExecFail , err // !?
}
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 = uint32 ( status . ExitStatus ( ) )
if len ( stdErrBuffer . Bytes ( ) ) > 0 {
log . Print ( stdErrBuffer )
2018-08-23 18:03:19 +00:00
}
2018-11-25 18:24:10 +00:00
log . Printf ( "Exit Status: %d" , exitStatus )
2018-08-23 18:03:19 +00:00
}
}
2018-01-18 00:39:01 +00:00
}
2018-11-25 18:24:10 +00:00
//fmt.Println("*** server->client cp finished ***")
return
2018-01-18 00:39:01 +00:00
}
2018-01-22 01:31:54 +00:00
// Run a command (via default shell) as a specific user
//
// Uses ptys to support commands which expect a terminal.
2018-11-25 18:24:10 +00:00
// nolint: gocyclo
2019-07-05 03:27:49 +00:00
func runShellAs ( who , hname , ttype , cmd string , interactive bool , conn * hkexnet . Conn , chaffing bool ) ( exitStatus uint32 , err error ) {
2018-08-06 04:43:21 +00:00
var wg sync . WaitGroup
2018-11-25 18:24:10 +00:00
u , err := user . Lookup ( who )
if err != nil {
exitStatus = 1
return
}
2018-01-22 01:31:54 +00:00
var uid , gid uint32
2018-11-25 18:24:10 +00:00
_ , _ = fmt . Sscanf ( u . Uid , "%d" , & uid ) // nolint: gosec
_ , _ = fmt . Sscanf ( u . Gid , "%d" , & gid ) // nolint: gosec
2018-02-17 02:46:29 +00:00
log . Println ( "uid:" , uid , "gid:" , gid )
2018-01-22 01:31:54 +00:00
2018-01-27 00:15:39 +00:00
// 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 ( )
2018-11-25 18:24:10 +00:00
_ = os . Setenv ( "HOME" , u . HomeDir ) // nolint: gosec
_ = os . Setenv ( "TERM" , ttype ) // nolint: gosec
_ = os . Setenv ( "HKEXSH" , "1" ) // nolint: gosec
2018-09-14 08:13:14 +00:00
2018-01-22 01:31:54 +00:00
var c * exec . Cmd
if interactive {
2019-08-09 04:36:37 +00:00
if useSysLogin {
// Use the server's login binary (post-auth
// which is still done via our own bcrypt file)
// Things UNIX login does, like print the 'motd',
// and use the shell specified by /etc/passwd, will be done
// automagically, at the cost of another external tool
// dependency.
//
c = exec . Command ( "/bin/login" , "-f" , "-p" , who ) // nolint: gosec
} else {
c = exec . Command ( "/bin/bash" , "-i" , "-l" ) // nolint: gosec
}
2018-01-22 01:31:54 +00:00
} else {
2018-11-25 18:24:10 +00:00
c = exec . Command ( "/bin/bash" , "-c" , cmd ) // nolint: gosec
2018-01-22 01:31:54 +00:00
}
2018-01-27 00:15:39 +00:00
//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
2018-01-22 01:31:54 +00:00
c . SysProcAttr = & syscall . SysProcAttr { }
2019-08-09 04:36:37 +00:00
if useSysLogin {
// If using server's login binary, drop to user creds
// is taken care of by it.
c . SysProcAttr . Credential = & syscall . Credential { }
} else {
c . SysProcAttr . Credential = & syscall . Credential { Uid : uid , Gid : gid }
}
2018-01-22 01:31:54 +00:00
c . Stdin = conn
c . Stdout = conn
c . Stderr = conn
// Start the command with a pty.
ptmx , err := pty . Start ( c ) // returns immediately with ptmx file
if err != nil {
2019-08-08 07:21:34 +00:00
log . Println ( err )
2018-11-25 18:24:10 +00:00
return hkexnet . CSEPtyExecFail , err
2018-01-22 01:31:54 +00:00
}
// Make sure to close the pty at the end.
2018-11-26 02:43:53 +00:00
// #gv:s/label=\"runShellAs\$1\"/label=\"deferPtmxClose\"/
2019-08-08 07:21:34 +00:00
defer func ( ) {
//logger.LogDebug(fmt.Sprintf("[Exited process was %d]", c.Process.Pid))
_ = ptmx . Close ( )
} ( ) // nolint: gosec
2018-04-29 02:28:37 +00:00
2019-07-05 03:27:49 +00:00
// get pty info for system accounting (who, lastlog)
pts , pe := ptsName ( ptmx . Fd ( ) )
if pe != nil {
return hkexnet . CSEPtyGetNameFail , err
}
utmpx := goutmp . Put_utmp ( who , pts , hname )
defer func ( ) { goutmp . Unput_utmp ( utmpx ) } ( )
goutmp . Put_lastlog_entry ( "hkexsh" , who , pts , hname )
2018-03-27 04:58:42 +00:00
log . Printf ( "[%s]\n" , cmd )
2018-01-22 01:31:54 +00:00
if err != nil {
log . Printf ( "Command finished with error: %v" , err )
2018-06-29 23:54:20 +00:00
} else {
// Watch for term resizes
2018-11-26 02:43:53 +00:00
// #gv:s/label=\"runShellAs\$2\"/label=\"termResizeWatcher\"/
2018-06-29 23:54:20 +00:00
go func ( ) {
for sz := range conn . WinCh {
log . Printf ( "[Setting term size to: %v %v]\n" , sz . Rows , sz . Cols )
2018-11-25 18:24:10 +00:00
pty . Setsize ( ptmx , & pty . Winsize { Rows : sz . Rows , Cols : sz . Cols } ) // nolint: gosec,errcheck
2018-06-29 23:54:20 +00:00
}
2018-09-17 00:14:50 +00:00
log . Println ( "*** WinCh goroutine done ***" )
2018-06-29 23:54:20 +00:00
} ( )
// Copy stdin to the pty.. (bgnd goroutine)
2018-11-26 02:43:53 +00:00
// #gv:s/label=\"runShellAs\$3\"/label=\"stdinToPtyWorker\"/
2018-06-29 23:54:20 +00:00
go func ( ) {
_ , e := io . Copy ( ptmx , conn )
if e != nil {
2018-08-06 04:43:21 +00:00
log . Println ( "** stdin->pty ended **:" , e . Error ( ) )
2018-09-06 04:58:55 +00:00
} else {
log . Println ( "*** stdin->pty goroutine done ***" )
2018-06-29 23:54:20 +00:00
}
} ( )
if chaffing {
conn . EnableChaff ( )
}
2018-11-26 02:43:53 +00:00
// #gv:s/label=\"runShellAs\$4\"/label=\"deferChaffShutdown\"/
defer func ( ) {
2018-11-29 05:03:20 +00:00
conn . DisableChaff ( )
conn . ShutdownChaff ( )
2018-11-26 02:43:53 +00:00
} ( )
2018-06-29 23:54:20 +00:00
// ..and the pty to stdout.
2018-08-06 04:43:21 +00:00
// 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 )
2018-11-26 02:55:07 +00:00
// #gv:s/label=\"runShellAs\$5\"/label=\"ptyToStdoutWorker\"/
2018-06-29 23:54:20 +00:00
go func ( ) {
2018-08-06 04:43:21 +00:00
defer wg . Done ( )
2018-06-29 23:54:20 +00:00
_ , e := io . Copy ( conn , ptmx )
if e != nil {
2018-08-06 04:43:21 +00:00
log . Println ( "** pty->stdout ended **:" , e . Error ( ) )
2018-09-06 04:58:55 +00:00
} else {
// The above io.Copy() will exit when the command attached
// to the pty exits
log . Println ( "*** pty->stdout goroutine done ***" )
2018-06-29 23:54:20 +00:00
}
} ( )
if err := c . Wait ( ) ; err != nil {
2018-09-06 03:36:32 +00:00
//fmt.Println("*** c.Wait() done ***")
2018-06-29 23:54:20 +00:00
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-06-29 23:54:20 +00:00
log . Printf ( "Exit Status: %d" , exitStatus )
}
2018-09-06 18:40:13 +00:00
}
2018-09-18 06:07:04 +00:00
conn . SetStatus ( hkexnet . CSOType ( exitStatus ) )
2019-08-08 07:21:34 +00:00
} else {
logger . LogDebug ( "*** Main proc has exited. ***" )
// Background jobs still may be running; close the
// pty anyway, so the client can return before
// wg.Wait() below completes (Issue #18)
2019-08-23 06:18:20 +00:00
if interactive {
_ = ptmx . Close ( )
}
2018-06-29 23:54:20 +00:00
}
2018-08-06 04:43:21 +00:00
wg . Wait ( ) // Wait on pty->stdout completion to client
2018-01-22 01:31:54 +00:00
}
return
}
2018-11-25 18:24:10 +00:00
// GenAuthToken generates a pseudorandom auth token for a specific
// user from a specific host to allow non-interactive logins.
2018-09-14 08:13:14 +00:00
func GenAuthToken ( who string , connhost string ) string {
//tokenA, e := os.Hostname()
//if e != nil {
// tokenA = "badhost"
//}
tokenA := connhost
2018-09-14 06:51:49 +00:00
tokenB := make ( [ ] byte , 64 )
2018-11-25 18:24:10 +00:00
_ , _ = rand . Read ( tokenB ) // nolint: gosec
2018-09-14 06:51:49 +00:00
return fmt . Sprintf ( "%s:%s" , tokenA , hex . EncodeToString ( tokenB ) )
}
2018-01-09 03:16:55 +00:00
// Demo of a simple server that listens and spawns goroutines for each
// connecting client. Note this code is identical to standard tcp
// server code, save for declaring 'hkex' rather than 'net'
// Listener and Conns. The KEx and encrypt/decrypt is done within the type.
// Compare to 'serverp.go' in this directory to see the equivalence.
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-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-01-21 23:46:40 +00:00
var dbg bool
2018-01-13 06:24:40 +00:00
var laddr string
2018-05-13 01:41:39 +00:00
flag . BoolVar ( & vopt , "v" , false , "show version" )
2018-01-13 06:24:40 +00:00
flag . StringVar ( & laddr , "l" , ":2000" , "interface[:port] to listen" )
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 ` )
2019-08-09 04:36:37 +00:00
flag . BoolVar ( & useSysLogin , "L" , false , "use system login" )
2019-04-09 04:58:33 +00:00
flag . BoolVar ( & chaffEnabled , "e" , true , "enable chaff pkts" )
2018-07-19 05:32:49 +00:00
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)" )
2018-01-21 23:46:40 +00:00
flag . BoolVar ( & dbg , "d" , false , "debug logging" )
2018-01-13 06:24:40 +00:00
flag . Parse ( )
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 )
2018-05-13 01:41:39 +00:00
os . Exit ( 0 )
}
2018-06-28 02:28:03 +00:00
{
me , e := user . Current ( )
if e != nil || me . Uid != "0" {
log . Fatal ( "Must run as root." )
}
}
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-11-25 18:24:10 +00:00
Log , _ = logger . New ( logger . LOG_DAEMON | logger . LOG_DEBUG | logger . LOG_NOTICE | logger . LOG_ERR , "hkexshd" ) // nolint: gosec
2018-11-02 01:52:01 +00:00
hkexnet . Init ( dbg , "hkexshd" , logger . LOG_DAEMON | logger . LOG_DEBUG | logger . LOG_NOTICE | logger . LOG_ERR )
2018-07-14 03:26:48 +00:00
if dbg {
2018-10-26 05:14:18 +00:00
log . SetOutput ( Log )
2018-07-14 03:26:48 +00:00
} else {
log . SetOutput ( ioutil . Discard )
}
2018-11-16 06:57:21 +00:00
// Set up handler for daemon signalling
exitCh := make ( chan os . Signal , 1 )
signal . Notify ( exitCh , os . Signal ( syscall . SIGTERM ) , os . Signal ( syscall . SIGINT ) , os . Signal ( syscall . SIGHUP ) , os . Signal ( syscall . SIGUSR1 ) , os . Signal ( syscall . SIGUSR2 ) )
go func ( ) {
for {
sig := <- exitCh
switch sig . String ( ) {
case "terminated" :
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Got signal: %s]" , sig ) ) // nolint: gosec,errcheck
2018-11-16 06:57:21 +00:00
signal . Reset ( )
2018-11-25 18:24:10 +00:00
syscall . Kill ( 0 , syscall . SIGTERM ) // nolint: gosec,errcheck
2018-11-16 06:57:21 +00:00
case "interrupt" :
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Got signal: %s]" , sig ) ) // nolint: gosec,errcheck
2018-11-16 06:57:21 +00:00
signal . Reset ( )
2018-11-25 18:24:10 +00:00
syscall . Kill ( 0 , syscall . SIGINT ) // nolint: gosec,errcheck
2018-11-16 06:57:21 +00:00
case "hangup" :
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Got signal: %s - nop]" , sig ) ) // nolint:gosec,errcheck
2018-11-16 06:57:21 +00:00
default :
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Got signal: %s - ignored]" , sig ) ) // nolint: gosec,errcheck
2018-11-16 06:57:21 +00:00
}
}
} ( )
2019-08-14 03:54:58 +00:00
proto := "tcp"
2019-08-17 06:16:40 +00:00
if kcpMode != "unused" {
2019-08-14 03:54:58 +00:00
proto = "kcp"
}
2019-08-17 06:16:40 +00:00
l , err := hkexnet . Listen ( proto , laddr , kcpMode )
2018-01-06 15:30:56 +00:00
if err != nil {
log . Fatal ( err )
}
2018-11-25 18:24:10 +00:00
defer l . Close ( ) // nolint: errcheck
2018-01-06 15:30:56 +00:00
2018-02-17 02:46:29 +00:00
log . Println ( "Serving on" , laddr )
2018-01-06 15:30:56 +00:00
for {
// Wait for a connection.
conn , err := l . Accept ( )
if err != nil {
2018-04-29 02:28:37 +00:00
log . Printf ( "Accept() got error(%v), hanging up.\n" , err )
2018-04-28 23:05:33 +00:00
} else {
log . Println ( "Accepted client" )
2018-01-06 15:30:56 +00:00
2018-05-07 00:41:09 +00:00
// Set up chaffing to client
2018-05-26 20:43:09 +00:00
// Will only start when runShellAs() is called
// after stdin/stdout are hooked up
2018-06-27 03:14:43 +00:00
conn . SetupChaff ( chaffFreqMin , chaffFreqMax , chaffBytesMax ) // configure server->client chaffing
2018-05-07 00:41:09 +00:00
2018-04-28 23:05:33 +00:00
// Handle the connection in a new goroutine.
// The loop then returns to accepting, so that
// multiple connections may be served concurrently.
2018-09-27 05:57:36 +00:00
go func ( hc * hkexnet . Conn ) ( e error ) {
2018-11-25 18:24:10 +00:00
defer hc . Close ( ) // nolint: errcheck
2018-01-21 04:37:27 +00:00
2018-04-28 23:05:33 +00:00
//We use io.ReadFull() here to guarantee we consume
2018-09-07 22:35:33 +00:00
//just the data we want for the hkexsh.Session, and no more.
2018-04-28 23:05:33 +00:00
//Otherwise data will be sitting in the channel that isn't
//passed down to the command handlers.
2018-09-07 22:35:33 +00:00
var rec hkexsh . Session
2018-09-14 08:13:14 +00:00
var len1 , len2 , len3 , len4 , len5 , len6 uint32
2018-03-26 04:47:38 +00:00
2018-09-14 08:13:14 +00:00
n , err := fmt . Fscanf ( hc , "%d %d %d %d %d %d\n" , & len1 , & len2 , & len3 , & len4 , & len5 , & len6 )
log . Printf ( "hkexsh.Session read:%d %d %d %d %d %d\n" , len1 , len2 , len3 , len4 , len5 , len6 )
2018-03-26 04:47:38 +00:00
2018-09-14 08:13:14 +00:00
if err != nil || n < 6 {
2018-09-07 22:35:33 +00:00
log . Println ( "[Bad hkexsh.Session fmt]" )
2018-04-28 23:05:33 +00:00
return err
}
2018-01-21 04:37:27 +00:00
2018-11-25 18:24:10 +00:00
tmp := make ( [ ] byte , len1 )
2018-09-07 22:35:33 +00:00
_ , err = io . ReadFull ( hc , tmp )
2018-04-28 23:05:33 +00:00
if err != nil {
2018-09-07 22:35:33 +00:00
log . Println ( "[Bad hkexsh.Session.Op]" )
2018-04-28 23:05:33 +00:00
return err
}
2018-09-07 22:35:33 +00:00
rec . SetOp ( tmp )
2018-11-25 18:24:10 +00:00
tmp = make ( [ ] byte , len2 )
2018-09-07 22:35:33 +00:00
_ , err = io . ReadFull ( hc , tmp )
2018-04-28 23:05:33 +00:00
if err != nil {
2018-09-07 22:35:33 +00:00
log . Println ( "[Bad hkexsh.Session.Who]" )
2018-04-28 23:05:33 +00:00
return err
}
2018-09-07 22:35:33 +00:00
rec . SetWho ( tmp )
2018-01-21 04:37:27 +00:00
2018-11-25 18:24:10 +00:00
tmp = make ( [ ] byte , len3 )
2018-09-07 22:35:33 +00:00
_ , err = io . ReadFull ( hc , tmp )
2018-09-14 08:13:14 +00:00
if err != nil {
log . Println ( "[Bad hkexsh.Session.ConnHost]" )
return err
}
rec . SetConnHost ( tmp )
2018-11-25 18:24:10 +00:00
tmp = make ( [ ] byte , len4 )
2018-09-14 08:13:14 +00:00
_ , err = io . ReadFull ( hc , tmp )
2018-09-08 03:37:47 +00:00
if err != nil {
log . Println ( "[Bad hkexsh.Session.TermType]" )
return err
}
rec . SetTermType ( tmp )
2018-11-25 18:24:10 +00:00
tmp = make ( [ ] byte , len5 )
2018-09-08 03:37:47 +00:00
_ , err = io . ReadFull ( hc , tmp )
2018-04-28 23:05:33 +00:00
if err != nil {
2018-09-07 22:35:33 +00:00
log . Println ( "[Bad hkexsh.Session.Cmd]" )
2018-04-28 23:05:33 +00:00
return err
}
2018-09-07 22:35:33 +00:00
rec . SetCmd ( tmp )
2018-01-21 04:37:27 +00:00
2018-11-25 18:24:10 +00:00
tmp = make ( [ ] byte , len6 )
2018-09-07 22:35:33 +00:00
_ , err = io . ReadFull ( hc , tmp )
2018-04-28 23:05:33 +00:00
if err != nil {
2018-09-07 22:35:33 +00:00
log . Println ( "[Bad hkexsh.Session.AuthCookie]" )
2018-04-28 23:05:33 +00:00
return err
}
2018-09-07 22:35:33 +00:00
rec . SetAuthCookie ( tmp )
2018-03-26 02:58:04 +00:00
2018-09-14 08:13:14 +00:00
log . Printf ( "[hkexsh.Session: op:%c who:%s connhost:%s cmd:%s auth:****]\n" ,
rec . Op ( ) [ 0 ] , string ( rec . Who ( ) ) , string ( rec . ConnHost ( ) ) , string ( rec . Cmd ( ) ) )
2018-01-21 04:37:27 +00:00
2018-09-14 06:51:49 +00:00
var valid bool
var allowedCmds string // Currently unused
2018-09-14 08:13:14 +00:00
if hkexsh . AuthUserByToken ( string ( rec . Who ( ) ) , string ( rec . ConnHost ( ) ) , string ( rec . AuthCookie ( true ) ) ) {
2018-09-14 06:51:49 +00:00
valid = true
} else {
valid , allowedCmds = hkexsh . AuthUserByPasswd ( string ( rec . Who ( ) ) , string ( rec . AuthCookie ( true ) ) , "/etc/hkexsh.passwd" )
}
2018-09-06 20:50:56 +00:00
2018-05-05 06:25:26 +00:00
// Security scrub
2018-09-07 22:35:33 +00:00
rec . ClearAuthCookie ( )
2018-05-05 06:25:26 +00:00
2018-09-06 23:23:57 +00:00
// Tell client if auth was valid
if valid {
2018-11-25 18:24:10 +00:00
hc . Write ( [ ] byte { 1 } ) // nolint: gosec,errcheck
2018-09-06 23:23:57 +00:00
} else {
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintln ( "Invalid user" , string ( rec . Who ( ) ) ) ) // nolint: errcheck,gosec
2018-11-29 05:03:20 +00:00
hc . Write ( [ ] byte { 0 } ) // nolint: gosec,errcheck
2018-04-28 23:05:33 +00:00
return
}
2018-09-06 23:23:57 +00:00
2018-04-28 23:05:33 +00:00
log . Printf ( "[allowedCmds:%s]\n" , allowedCmds )
2018-01-22 06:02:08 +00:00
2018-09-14 06:51:49 +00:00
if rec . Op ( ) [ 0 ] == 'A' {
// Generate automated login token
addr := hc . RemoteAddr ( )
2018-10-02 18:03:10 +00:00
hname := goutmp . GetHost ( addr . String ( ) )
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Generating autologin token for [%s@%s]]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
2018-09-14 08:13:14 +00:00
token := GenAuthToken ( string ( rec . Who ( ) ) , string ( rec . ConnHost ( ) ) )
2018-09-14 18:58:10 +00:00
tokenCmd := fmt . Sprintf ( "echo \"%s\" | tee -a ~/.hkexsh_id" , token )
2019-07-05 03:27:49 +00:00
cmdStatus , runErr := runShellAs ( string ( rec . Who ( ) ) , hname , string ( rec . TermType ( ) ) , tokenCmd , false , hc , chaffEnabled )
2018-09-14 06:51:49 +00:00
// Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF
rec . SetOp ( [ ] byte { 0 } )
if runErr != nil {
2018-11-25 18:24:10 +00:00
logger . LogErr ( fmt . Sprintf ( "[Error generating autologin token for %s@%s]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
2018-09-14 06:51:49 +00:00
} else {
log . Printf ( "[Autologin token generation completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus )
2018-09-18 06:07:04 +00:00
hc . SetStatus ( hkexnet . CSOType ( cmdStatus ) )
2018-09-14 06:51:49 +00:00
}
} else if rec . Op ( ) [ 0 ] == 'c' {
2018-04-28 23:05:33 +00:00
// Non-interactive command
2018-06-30 02:23:11 +00:00
addr := hc . RemoteAddr ( )
2018-10-02 18:03:10 +00:00
hname := goutmp . GetHost ( addr . String ( ) )
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Running command for [%s@%s]]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
2019-07-05 03:27:49 +00:00
cmdStatus , runErr := runShellAs ( string ( rec . Who ( ) ) , hname , string ( rec . TermType ( ) ) , string ( rec . Cmd ( ) ) , false , hc , chaffEnabled )
2018-04-28 23:05:33 +00:00
// Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF
2018-09-07 22:35:33 +00:00
rec . SetOp ( [ ] byte { 0 } )
2018-06-29 23:54:20 +00:00
if runErr != nil {
2018-11-25 18:24:10 +00:00
logger . LogErr ( fmt . Sprintf ( "[Error spawning cmd for %s@%s]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
2018-06-29 23:54:20 +00:00
} else {
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Command completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus ) ) // nolint: gosec,errcheck
2018-09-18 06:07:04 +00:00
hc . SetStatus ( hkexnet . CSOType ( cmdStatus ) )
2018-06-29 23:54:20 +00:00
}
2018-09-07 22:35:33 +00:00
} else if rec . Op ( ) [ 0 ] == 's' {
2018-06-28 02:09:35 +00:00
// Interactive session
2018-06-30 02:23:11 +00:00
addr := hc . RemoteAddr ( )
2018-10-02 18:03:10 +00:00
hname := goutmp . GetHost ( addr . String ( ) )
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Running shell for [%s@%s]]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
2018-06-28 00:32:26 +00:00
2019-07-05 03:27:49 +00:00
cmdStatus , runErr := runShellAs ( string ( rec . Who ( ) ) , hname , string ( rec . TermType ( ) ) , string ( rec . Cmd ( ) ) , true , hc , chaffEnabled )
2018-04-28 23:05:33 +00:00
// Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF
2018-09-07 22:35:33 +00:00
rec . SetOp ( [ ] byte { 0 } )
2018-06-29 23:54:20 +00:00
if runErr != nil {
2018-11-25 18:24:10 +00:00
Log . Err ( fmt . Sprintf ( "[Error spawning shell for %s@%s]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
2018-06-29 23:54:20 +00:00
} else {
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Shell completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus ) ) // nolint: gosec,errcheck
2018-09-18 06:07:04 +00:00
hc . SetStatus ( hkexnet . CSOType ( cmdStatus ) )
2018-06-29 23:54:20 +00:00
}
2018-09-07 22:35:33 +00:00
} else if rec . Op ( ) [ 0 ] == 'D' {
2018-08-07 05:29:51 +00:00
// File copy (destination) operation - client copy to server
log . Printf ( "[Client->Server copy]\n" )
2018-08-23 18:03:19 +00:00
addr := hc . RemoteAddr ( )
2018-10-02 18:03:10 +00:00
hname := goutmp . GetHost ( addr . String ( ) )
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Running copy for [%s@%s]]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
cmdStatus , runErr := runClientToServerCopyAs ( string ( rec . Who ( ) ) , string ( rec . TermType ( ) ) , hc , string ( rec . Cmd ( ) ) , chaffEnabled )
2018-08-25 01:50:45 +00:00
// Returned hopefully via an EOF or exit/logout;
// Clear current op so user can enter next, or EOF
2018-09-07 22:35:33 +00:00
rec . SetOp ( [ ] byte { 0 } )
2018-08-23 18:03:19 +00:00
if runErr != nil {
2018-11-25 18:24:10 +00:00
logger . LogErr ( fmt . Sprintf ( "[Error running cp for %s@%s]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
2018-08-23 18:03:19 +00:00
} else {
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Command completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus ) ) // nolint: gosec,errcheck
2018-08-23 18:03:19 +00:00
}
2018-09-18 06:07:04 +00:00
hc . SetStatus ( hkexnet . CSOType ( cmdStatus ) )
2018-09-17 00:14:50 +00:00
// Send CSOExitStatus *before* client closes channel
s := make ( [ ] byte , 4 )
binary . BigEndian . PutUint32 ( s , cmdStatus )
log . Printf ( "** cp writing closeStat %d at Close()\n" , cmdStatus )
2018-11-25 18:24:10 +00:00
hc . WritePacket ( s , hkexnet . CSOExitStatus ) // nolint: gosec,errcheck
2018-09-07 22:35:33 +00:00
} else if rec . Op ( ) [ 0 ] == 'S' {
2018-08-07 05:29:51 +00:00
// File copy (src) operation - server copy to client
log . Printf ( "[Server->Client copy]\n" )
2018-08-23 18:03:19 +00:00
addr := hc . RemoteAddr ( )
2018-10-02 18:03:10 +00:00
hname := goutmp . GetHost ( addr . String ( ) )
2018-11-25 18:24:10 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Running copy for [%s@%s]]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
cmdStatus , runErr := runServerToClientCopyAs ( string ( rec . Who ( ) ) , string ( rec . TermType ( ) ) , hc , string ( rec . Cmd ( ) ) , chaffEnabled )
2018-08-23 18:03:19 +00:00
if runErr != nil {
2018-11-25 18:24:10 +00:00
logger . LogErr ( fmt . Sprintf ( "[Error spawning cp for %s@%s]\n" , rec . Who ( ) , hname ) ) // nolint: gosec,errcheck
2018-08-23 18:03:19 +00:00
} else {
2018-11-25 18:24:10 +00:00
// Returned hopefully via an EOF or exit/logout;
logger . LogNotice ( fmt . Sprintf ( "[Command completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus ) ) // nolint: gosec,errcheck
2018-08-23 18:03:19 +00:00
}
2018-11-25 18:24:10 +00:00
// Clear current op so user can enter next, or EOF
rec . SetOp ( [ ] byte { 0 } )
2018-09-18 06:07:04 +00:00
hc . SetStatus ( hkexnet . CSOType ( cmdStatus ) )
2018-09-05 05:24:16 +00:00
//fmt.Println("Waiting for EOF from other end.")
2018-09-17 00:14:50 +00:00
//_, _ = hc.Read(nil /*ackByte*/)
2018-09-05 05:24:16 +00:00
//fmt.Println("Got remote end ack.")
2018-04-28 23:05:33 +00:00
} else {
2018-11-25 18:24:10 +00:00
logger . LogErr ( fmt . Sprintln ( "[Bad hkexsh.Session]" ) ) // nolint: gosec,errcheck
2018-04-28 23:05:33 +00:00
}
return
2018-11-25 18:24:10 +00:00
} ( & conn ) // nolint: errcheck
2018-04-28 23:05:33 +00:00
} // Accept() success
2018-01-21 04:37:27 +00:00
} //endfor
2018-11-25 18:24:10 +00:00
//logger.LogNotice(fmt.Sprintln("[Exiting]")) // nolint: gosec,errcheck
2018-01-06 15:30:56 +00:00
}