2019-10-30 03:34:09 +00:00
// xsd server
2018-04-07 20:04:10 +00:00
//
2020-08-08 08:54:46 +00:00
// Copyright (c) 2017-2020 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-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-06 15:30:56 +00:00
"log"
2023-11-05 22:58:24 +00:00
"net/http"
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"
2023-11-05 22:58:24 +00:00
"runtime"
"runtime/pprof"
2019-12-15 19:38:04 +00:00
"strings"
2018-08-06 04:43:21 +00:00
"sync"
2018-01-18 00:39:01 +00:00
"syscall"
2020-01-31 07:32:36 +00:00
"time"
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"
2019-10-30 03:34:09 +00:00
xs "blitter.com/go/xs"
"blitter.com/go/xs/logger"
2019-12-15 19:38:04 +00:00
"blitter.com/go/xs/xsnet"
2021-01-11 06:04:52 +00:00
"github.com/creack/pty"
2018-01-06 15:30:56 +00:00
)
2018-10-26 05:14:18 +00:00
var (
2019-09-27 16:44:57 +00:00
version string
gitCommit string // set in -ldflags by build
2019-08-09 04:36:37 +00:00
useSysLogin bool
2019-09-27 16:44:57 +00:00
kcpMode string // set to a valid KCP BlockCrypt alg tag to use rather than TCP
2018-11-25 18:24:10 +00:00
// Log - syslog output (with no -d)
Log * logger . Writer
2023-11-05 22:58:24 +00:00
cpuprofile string
memprofile string
2018-10-26 05:14:18 +00:00
)
2022-09-27 05:09:54 +00:00
const (
AuthTokenLen = 64
LoginTimeoutSecs = 30
)
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
2019-10-30 03:34:09 +00:00
func runClientToServerCopyAs ( who , ttype string , conn * xsnet . Conn , fpath string , chaffing bool ) ( exitStatus uint32 , err error ) {
2022-09-27 05:09:54 +00:00
u , _ := user . Lookup ( who )
2018-01-18 00:39:01 +00:00
var uid , gid uint32
2022-09-27 05:09:54 +00:00
fmt . Sscanf ( u . Uid , "%d" , & uid )
fmt . Sscanf ( u . Gid , "%d" , & gid )
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
2023-11-04 06:57:55 +00:00
// target user.
2018-08-23 18:03:19 +00:00
os . Clearenv ( )
2022-09-27 05:09:54 +00:00
os . Setenv ( "HOME" , u . HomeDir )
os . Setenv ( "TERM" , ttype )
os . Setenv ( "XS_SESSION" , "1" )
2018-01-18 00:39:01 +00:00
2018-08-23 18:03:19 +00:00
var c * exec . Cmd
2020-04-26 01:03:29 +00:00
cmdName := xs . GetTool ( "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#`}
2020-04-26 01:03:29 +00:00
fmt . Println ( cmdName , cmdArgs )
2022-09-27 05:09:54 +00:00
c = exec . Command ( cmdName , cmdArgs ... )
2018-08-23 18:03:19 +00:00
2018-08-25 06:22:07 +00:00
c . Dir = destDir
2023-11-04 06:57:55 +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
2023-11-05 22:58:24 +00:00
// === Set up connection keepalive to client
conn . StartupKeepAlive ( ) // goroutine, returns immediately
defer conn . ShutdownKeepAlive ( )
2018-08-25 06:22:07 +00:00
if chaffing {
2023-11-05 22:58:24 +00:00
conn . StartupChaff ( )
2018-08-25 06:22:07 +00:00
}
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)
2019-10-30 03:34:09 +00:00
exitStatus = xsnet . 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
2019-10-30 03:34:09 +00:00
func runServerToClientCopyAs ( who , ttype string , conn * xsnet . Conn , srcPath string , chaffing bool ) ( exitStatus uint32 , err error ) {
2018-11-25 18:24:10 +00:00
u , err := user . Lookup ( who )
if err != nil {
exitStatus = 1
return
}
2018-08-23 18:03:19 +00:00
var uid , gid uint32
2022-09-27 05:09:54 +00:00
_ , _ = fmt . Sscanf ( u . Uid , "%d" , & uid )
_ , _ = fmt . Sscanf ( u . Gid , "%d" , & gid )
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
2023-11-04 06:57:55 +00:00
// target user.
2018-08-23 18:03:19 +00:00
os . Clearenv ( )
2022-09-27 05:09:54 +00:00
_ = os . Setenv ( "HOME" , u . HomeDir )
_ = os . Setenv ( "TERM" , ttype )
_ = os . Setenv ( "XS_SESSION" , "1" )
2018-01-19 02:57:37 +00:00
2018-08-23 18:03:19 +00:00
var c * exec . Cmd
2020-04-26 01:03:29 +00:00
cmdName := xs . GetTool ( "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
2022-09-27 05:09:54 +00:00
c = exec . Command ( cmdName , cmdArgs ... )
2018-01-19 02:57:37 +00:00
2023-11-04 06:57:55 +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-23 18:03:19 +00:00
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
2023-11-05 22:58:24 +00:00
// === Set up connection keepalive to client
conn . StartupKeepAlive ( ) // goroutine, returns immediately
defer conn . ShutdownKeepAlive ( )
2018-08-25 01:50:45 +00:00
if chaffing {
2023-11-05 22:58:24 +00:00
conn . StartupChaff ( )
2018-08-25 01:50:45 +00:00
}
//defer conn.Close()
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 )
2019-10-30 03:34:09 +00:00
return xsnet . CSEExecFail , err // !?
2018-11-25 18:24:10 +00:00
}
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
}
2022-09-27 05:09:54 +00:00
// Run a command (via default shell) as a specific user. Uses
// ptys to support commands which expect a terminal. //nolint:gofmt
func runShellAs ( who , hname , ttype , cmd string , interactive bool , //nolint:funlen
conn * xsnet . 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
2022-09-27 05:09:54 +00:00
_ , _ = fmt . Sscanf ( u . Uid , "%d" , & uid )
_ , _ = fmt . Sscanf ( u . Gid , "%d" , & gid )
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
2023-11-04 06:57:55 +00:00
// target user.
2018-01-27 00:15:39 +00:00
os . Clearenv ( )
2022-09-27 05:09:54 +00:00
_ = os . Setenv ( "HOME" , u . HomeDir )
_ = os . Setenv ( "TERM" , ttype )
_ = os . Setenv ( "XS_SESSION" , "1" )
2018-09-14 08:13:14 +00:00
2018-01-22 01:31:54 +00:00
var c * exec . Cmd
2021-02-01 04:18:48 +00:00
2018-01-22 01:31:54 +00:00
if interactive {
2019-08-09 04:36:37 +00:00
if useSysLogin {
2023-11-07 05:57:19 +00:00
// Use the server's login binary (post-auth)
2021-02-01 04:18:48 +00:00
//
2019-08-09 04:36:37 +00:00
// 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.
//
2023-11-07 05:57:19 +00:00
// One drawback of using 'login' is that the remote side
// cannot give us back the shell's exit code, since it
// exits back to 'login', which usually returns its own
// 0 status back to us.
//
// Note login will drop privs to the intended user for us.
//
2022-09-27 05:09:54 +00:00
c = exec . Command ( xs . GetTool ( "login" ) , "-f" , "-p" , who ) //nolint:gosec
2019-08-09 04:36:37 +00:00
} else {
2023-11-07 05:57:19 +00:00
// Run shell directly (which allows nonzero exit codes back to
// the local system upon shell exit, whereas 'login' does not.)
2021-02-01 04:18:48 +00:00
//
2023-11-07 05:57:19 +00:00
// Note we must drop privs ourselves for the user shell since
// we aren't using 'login' on the remote end which would do it
// for us.
2021-02-01 04:18:48 +00:00
//
2022-09-27 05:09:54 +00:00
c = exec . Command ( xs . GetTool ( "bash" ) , "-i" , "-l" ) //nolint:gosec
2021-02-01 04:18:48 +00:00
c . SysProcAttr = & syscall . SysProcAttr { }
c . SysProcAttr . Credential = & syscall . Credential { Uid : uid , Gid : gid }
2019-08-09 04:36:37 +00:00
}
2018-01-22 01:31:54 +00:00
} else {
2022-09-27 05:09:54 +00:00
c = exec . Command ( xs . GetTool ( "bash" ) , "-c" , cmd ) //nolint:gosec
2021-02-01 04:18:48 +00:00
c . SysProcAttr = & syscall . SysProcAttr { }
c . SysProcAttr . Credential = & syscall . Credential { Uid : uid , Gid : gid }
2018-01-22 01:31:54 +00:00
}
2023-11-04 06:57:55 +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-01-27 00:15:39 +00:00
c . Dir = u . HomeDir
2018-01-22 01:31:54 +00:00
// 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 )
2019-10-30 03:34:09 +00:00
return xsnet . 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 ( )
2022-09-27 05:09:54 +00:00
} ( )
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 {
2019-10-30 03:34:09 +00:00
return xsnet . CSEPtyGetNameFail , err
2019-07-05 03:27:49 +00:00
}
utmpx := goutmp . Put_utmp ( who , pts , hname )
defer func ( ) { goutmp . Unput_utmp ( utmpx ) } ( )
2019-10-30 03:34:09 +00:00
goutmp . Put_lastlog_entry ( "xs" , who , pts , hname )
2019-07-05 03:27:49 +00:00
2023-11-07 07:17:13 +00:00
conn . Pproc = c . Process . Pid
//fmt.Printf("[process %d started]\n", c.Process.Pid)
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 )
2022-09-27 05:09:54 +00:00
pty . Setsize ( ptmx , & pty . Winsize { Rows : sz . Rows , Cols : sz . Cols } ) //nolint: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
}
} ( )
2023-11-05 22:58:24 +00:00
// === Set up connection keepalive to client
conn . StartupKeepAlive ( ) // goroutine, returns immediately
defer conn . ShutdownKeepAlive ( )
2018-06-29 23:54:20 +00:00
if chaffing {
2023-11-05 22:58:24 +00:00
conn . StartupChaff ( )
2018-06-29 23:54:20 +00:00
}
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 . 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
}
2019-10-30 03:34:09 +00:00
conn . SetStatus ( xsnet . CSOType ( exitStatus ) )
2019-08-08 07:21:34 +00:00
} else {
2023-11-07 05:57:19 +00:00
logger . LogDebug ( fmt . Sprintf ( "*** Main proc has exited (%d) ***" , c . ProcessState . ExitCode ( ) ) ) //nolint:errcheck
2019-08-08 07:21:34 +00:00
// 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 {
2021-02-01 04:18:48 +00:00
//hname, e := os.Hostname()
2018-09-14 08:13:14 +00:00
//if e != nil {
2021-02-01 04:18:48 +00:00
// hname = "#badhost#"
2018-09-14 08:13:14 +00:00
//}
2021-02-01 04:18:48 +00:00
hname := connhost
2018-09-14 06:51:49 +00:00
2022-09-27 05:09:54 +00:00
token := make ( [ ] byte , AuthTokenLen )
_ , _ = rand . Read ( token )
2021-02-01 04:18:48 +00:00
return fmt . Sprintf ( "%s:%s:%s" , hname , who , hex . EncodeToString ( token ) )
2018-09-14 06:51:49 +00:00
}
2019-12-15 19:38:04 +00:00
var (
aKEXAlgs allowedKEXAlgs
aCipherAlgs allowedCipherAlgs
aHMACAlgs allowedHMACAlgs
)
2021-11-15 05:17:56 +00:00
type allowedKEXAlgs [ ] string
type allowedCipherAlgs [ ] string
type allowedHMACAlgs [ ] string
2019-12-15 19:38:04 +00:00
func ( a allowedKEXAlgs ) allowed ( k xsnet . KEXAlg ) bool {
for i := 0 ; i < len ( a ) ; i ++ {
if a [ i ] == "KEX_all" || a [ i ] == k . String ( ) {
return true
}
}
return false
}
func ( a * allowedKEXAlgs ) String ( ) string {
return fmt . Sprintf ( "allowedKEXAlgs: %v" , * a )
}
func ( a * allowedKEXAlgs ) Set ( value string ) error {
* a = append ( * a , strings . TrimSpace ( value ) )
return nil
}
func ( a allowedCipherAlgs ) allowed ( c xsnet . CSCipherAlg ) bool {
for i := 0 ; i < len ( a ) ; i ++ {
if a [ i ] == "C_all" || a [ i ] == c . String ( ) {
return true
}
}
return false
}
func ( a * allowedCipherAlgs ) String ( ) string {
return fmt . Sprintf ( "allowedCipherAlgs: %v" , * a )
}
func ( a * allowedCipherAlgs ) Set ( value string ) error {
* a = append ( * a , strings . TrimSpace ( value ) )
return nil
}
func ( a allowedHMACAlgs ) allowed ( h xsnet . CSHmacAlg ) bool {
for i := 0 ; i < len ( a ) ; i ++ {
if a [ i ] == "H_all" || a [ i ] == h . String ( ) {
return true
}
}
return false
}
func ( a * allowedHMACAlgs ) String ( ) string {
return fmt . Sprintf ( "allowedHMACAlgs: %v" , * a )
}
func ( a * allowedHMACAlgs ) Set ( value string ) error {
* a = append ( * a , strings . TrimSpace ( value ) )
return nil
}
// Main server that listens and spawns goroutines for each
2019-12-20 04:34:05 +00:00
// connecting client to serve interactive or file copy sessions
// and any requested tunnels.
// Note that this server does not do UNIX forks of itself to give
// each client its own separate manager process, so if the main
// daemon dies, all clients will be rudely disconnected.
// Consider this when planning to restart or upgrade in-place an installation.
2018-11-25 18:24:10 +00:00
// TODO: reduce gocyclo
2022-09-27 05:09:54 +00:00
func main ( ) { //nolint:funlen,gocyclo
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
2023-11-15 08:32:50 +00:00
var rekeySecs uint
2018-01-13 06:24:40 +00:00
2019-12-20 04:01:39 +00:00
var useSystemPasswd bool
2018-05-13 01:41:39 +00:00
flag . BoolVar ( & vopt , "v" , false , "show version" )
2023-11-15 08:32:50 +00:00
flag . UintVar ( & rekeySecs , "r" , 300 , "rekey interval in `secs`" )
flag . StringVar ( & laddr , "l" , ":2000" , "interface[:port] to listen" ) //nolint:gomnd,lll
2022-09-27 05:09:54 +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 ` ) //nolint:lll
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" )
2022-09-27 05:09:54 +00:00
flag . UintVar ( & chaffFreqMin , "f" , 100 , "chaff pkt freq min (msecs)" ) //nolint:gomnd
flag . UintVar ( & chaffFreqMax , "F" , 5000 , "chaff pkt freq max (msecs)" ) //nolint:gomnd
flag . UintVar ( & chaffBytesMax , "B" , 64 , "chaff pkt size max (bytes)" ) //nolint:gomnd
2019-12-20 04:01:39 +00:00
flag . BoolVar ( & useSystemPasswd , "s" , true , "use system shadow passwds" )
2018-01-21 23:46:40 +00:00
flag . BoolVar ( & dbg , "d" , false , "debug logging" )
2022-09-27 05:09:54 +00:00
flag . Var ( & aKEXAlgs , "aK" , "Allowed KEX `alg`s (eg. '-aK KEXAlgA -aK KEXAlgB ...')" + `
2021-11-15 05:17:56 +00:00
KEX_all
KEX_HERRADURA256
KEX_HERRADURA512
KEX_HERRADURA1024
KEX_HERRADURA2048
KEX_KYBER512
KEX_KYBER768
KEX_KYBER1024
KEX_NEWHOPE
KEX_NEWHOPE_SIMPLE
KEX_FRODOKEM_1344AES
KEX_FRODOKEM_1344SHAKE
KEX_FRODOKEM_976AES
KEX_FRODOKEM_976SHAKE ` )
2022-09-27 05:09:54 +00:00
flag . Var ( & aCipherAlgs , "aC" , "Allowed `cipher`s (eg. '-aC CAlgA -aC CAlgB ...')" + `
2021-11-15 05:17:56 +00:00
C_all
C_AES_256
C_TWOFISH_128
C_BLOWFISH_64
C_CRYPTMT1
C_HOPSCOTCH
C_CHACHA20_12 ` )
2022-09-27 05:09:54 +00:00
flag . Var ( & aHMACAlgs , "aH" , "Allowed `HMAC`s (eg. '-aH HMACAlgA -aH HMACAlgB ...')" + `
2021-11-15 05:17:56 +00:00
H_all
H_SHA256
H_SHA512 ` )
2019-12-15 19:38:04 +00:00
2023-11-05 22:58:24 +00:00
flag . StringVar ( & cpuprofile , "cpuprofile" , "" , "write cpu profile to <`file`>" )
flag . StringVar ( & memprofile , "memprofile" , "" , "write memory profile to <`file`>" )
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." )
}
}
2023-11-05 22:58:24 +00:00
// === Profiling instrumentation
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 ) //nolint:gocritic
} else {
defer pprof . StopCPUProfile ( )
}
go func ( ) { http . ListenAndServe ( "localhost:6060" , nil ) } ( ) //nolint:errcheck,gosec
}
2019-07-03 16:50:37 +00:00
// Enforce some sane min/max vals on chaff flags
2022-09-27 05:09:54 +00:00
if chaffFreqMin < 2 { //nolint:gomnd
2019-07-03 16:50:37 +00:00
chaffFreqMin = 2
}
if chaffFreqMax == 0 {
chaffFreqMax = chaffFreqMin + 1
}
if chaffBytesMax == 0 || chaffBytesMax > 4096 {
chaffBytesMax = 64
}
2022-09-27 05:09:54 +00:00
Log , _ = logger . New ( logger . LOG_DAEMON | logger . LOG_DEBUG | logger . LOG_NOTICE | logger . LOG_ERR , "xsd" )
2019-10-30 03:34:09 +00:00
xsnet . Init ( dbg , "xsd" , 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 {
2022-09-27 05:09:54 +00:00
log . SetOutput ( io . Discard )
2018-07-14 03:26:48 +00:00
}
2019-12-15 19:38:04 +00:00
// Set up allowed algs, if specified (default allow all)
if len ( aKEXAlgs ) == 0 {
2021-11-15 05:17:56 +00:00
aKEXAlgs = [ ] string { "none" }
2019-12-15 19:38:04 +00:00
}
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "Allowed KEXAlgs: %v\n" , aKEXAlgs ) ) //nolint:errcheck
2019-12-15 19:38:04 +00:00
if len ( aCipherAlgs ) == 0 {
2021-11-15 05:17:56 +00:00
aCipherAlgs = [ ] string { "none" }
2019-12-15 19:38:04 +00:00
}
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "Allowed CipherAlgs: %v\n" , aCipherAlgs ) ) //nolint:errcheck
2019-12-15 19:38:04 +00:00
if len ( aHMACAlgs ) == 0 {
2021-11-15 05:17:56 +00:00
aHMACAlgs = [ ] string { "none" }
2019-12-15 19:38:04 +00:00
}
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "Allowed HMACAlgs: %v\n" , aHMACAlgs ) ) //nolint:errcheck
2019-12-15 19:38:04 +00:00
2018-11-16 06:57:21 +00:00
// Set up handler for daemon signalling
exitCh := make ( chan os . Signal , 1 )
2022-09-27 05:09:54 +00:00
signal . Notify ( exitCh , os . Signal ( syscall . SIGTERM ) , os . Signal ( syscall . SIGINT ) , os . Signal ( syscall . SIGHUP ) , os . Signal ( syscall . SIGUSR1 ) , os . Signal ( syscall . SIGUSR2 ) ) //nolint:lll
2018-11-16 06:57:21 +00:00
go func ( ) {
for {
sig := <- exitCh
2023-11-15 08:32:50 +00:00
switch sig {
case syscall . SIGTERM : //"terminated":
logger . LogNotice ( fmt . Sprintf ( "[Got signal: %s]" , sig . String ( ) ) ) //nolint:errcheck
2018-11-16 06:57:21 +00:00
signal . Reset ( )
2022-09-27 05:09:54 +00:00
syscall . Kill ( 0 , syscall . SIGTERM ) //nolint:errcheck
2023-11-15 08:32:50 +00:00
case syscall . SIGINT : //"interrupt":
logger . LogNotice ( fmt . Sprintf ( "[Got signal: %s]" , sig . String ( ) ) ) //nolint:errcheck
2018-11-16 06:57:21 +00:00
signal . Reset ( )
2022-09-27 05:09:54 +00:00
syscall . Kill ( 0 , syscall . SIGINT ) //nolint:errcheck
2023-11-15 08:32:50 +00:00
case syscall . SIGHUP : //"hangup":
logger . LogNotice ( fmt . Sprintf ( "[Got signal: %s - nop]" , sig . String ( ) ) ) //nolint:errcheck
2023-11-05 22:58:24 +00:00
if cpuprofile != "" || memprofile != "" {
dumpProf ( )
}
2018-11-16 06:57:21 +00:00
default :
2023-11-15 08:32:50 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Got signal: %s - ignored]" , sig . String ( ) ) ) //nolint: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-10-30 03:34:09 +00:00
l , err := xsnet . Listen ( proto , laddr , kcpMode )
2018-01-06 15:30:56 +00:00
if err != nil {
log . Fatal ( err )
}
2022-09-27 05:09:54 +00:00
defer l . Close ( )
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.
2019-12-15 19:38:04 +00:00
// Then check if client-proposed algs are allowed
2018-01-06 15:30:56 +00:00
conn , err := l . Accept ( )
2023-11-05 22:58:24 +00:00
//logger.LogDebug(fmt.Sprintf("l.Accept()\n"))
2018-01-06 15:30:56 +00:00
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 {
2021-11-15 05:17:56 +00:00
if ! aKEXAlgs . allowed ( conn . KEX ( ) ) {
log . Printf ( "Accept() rejected for banned KEX alg %d, hanging up.\n" , conn . KEX ( ) )
conn . SetStatus ( xsnet . CSEKEXAlgDenied )
conn . Close ( )
} else if ! aCipherAlgs . allowed ( conn . CAlg ( ) ) {
log . Printf ( "Accept() rejected for banned Cipher alg %d, hanging up.\n" , conn . CAlg ( ) )
conn . SetStatus ( xsnet . CSECipherAlgDenied )
conn . Close ( )
} else if ! aHMACAlgs . allowed ( conn . HAlg ( ) ) {
log . Printf ( "Accept() rejected for banned HMAC alg %d, hanging up.\n" , conn . HAlg ( ) )
conn . SetStatus ( xsnet . CSEHMACAlgDenied )
conn . Close ( )
} else {
log . Println ( "Accepted client" )
2023-11-15 08:32:50 +00:00
conn . RekeyHelper ( rekeySecs )
2021-11-15 05:17:56 +00:00
// Set up chaffing to client
// Will only start when runShellAs() is called
// after stdin/stdout are hooked up
conn . SetupChaff ( chaffFreqMin , chaffFreqMax , chaffBytesMax ) // configure server->client chaffing
// Handle the connection in a new goroutine.
// The loop then returns to accepting, so that
// multiple connections may be served concurrently.
go func ( hc * xsnet . Conn ) ( e error ) {
2023-11-15 08:32:50 +00:00
defer hc . ShutdownRekey ( )
2022-09-27 05:09:54 +00:00
defer hc . Close ( )
2021-11-15 05:17:56 +00:00
// Start login timeout here and disconnect if user/pass phase stalls
2022-09-27 05:09:54 +00:00
loginTimeout := time . AfterFunc ( LoginTimeoutSecs * time . Second , func ( ) {
logger . LogNotice ( fmt . Sprintln ( "Login timed out" ) ) //nolint:errcheck
hc . Write ( [ ] byte { 0 } ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
hc . Close ( )
} )
//We use io.ReadFull() here to guarantee we consume
//just the data we want for the xs.Session, and no more.
//Otherwise data will be sitting in the channel that isn't
//passed down to the command handlers.
var rec xs . Session
var len1 , len2 , len3 , len4 , len5 , len6 uint32
n , err := fmt . Fscanf ( hc , "%d %d %d %d %d %d\n" , & len1 , & len2 , & len3 , & len4 , & len5 , & len6 )
log . Printf ( "xs.Session read:%d %d %d %d %d %d\n" , len1 , len2 , len3 , len4 , len5 , len6 )
if err != nil || n < 6 {
log . Println ( "[Bad xs.Session fmt]" )
return err
}
2018-09-07 22:35:33 +00:00
2021-11-15 05:17:56 +00:00
tmp := make ( [ ] byte , len1 )
_ , err = io . ReadFull ( hc , tmp )
if err != nil {
log . Println ( "[Bad xs.Session.Op]" )
return err
}
rec . SetOp ( tmp )
2018-01-21 04:37:27 +00:00
2021-11-15 05:17:56 +00:00
tmp = make ( [ ] byte , len2 )
_ , err = io . ReadFull ( hc , tmp )
if err != nil {
log . Println ( "[Bad xs.Session.Who]" )
return err
}
rec . SetWho ( tmp )
2018-09-14 08:13:14 +00:00
2021-11-15 05:17:56 +00:00
tmp = make ( [ ] byte , len3 )
_ , err = io . ReadFull ( hc , tmp )
if err != nil {
log . Println ( "[Bad xs.Session.ConnHost]" )
return err
}
rec . SetConnHost ( tmp )
2018-09-08 03:37:47 +00:00
2021-11-15 05:17:56 +00:00
tmp = make ( [ ] byte , len4 )
_ , err = io . ReadFull ( hc , tmp )
if err != nil {
log . Println ( "[Bad xs.Session.TermType]" )
return err
}
rec . SetTermType ( tmp )
2018-01-21 04:37:27 +00:00
2021-11-15 05:17:56 +00:00
tmp = make ( [ ] byte , len5 )
_ , err = io . ReadFull ( hc , tmp )
if err != nil {
log . Println ( "[Bad xs.Session.Cmd]" )
return err
2019-12-20 04:01:39 +00:00
}
2021-11-15 05:17:56 +00:00
rec . SetCmd ( tmp )
2018-09-06 20:50:56 +00:00
2021-11-15 05:17:56 +00:00
tmp = make ( [ ] byte , len6 )
_ , err = io . ReadFull ( hc , tmp )
if err != nil {
log . Println ( "[Bad xs.Session.AuthCookie]" )
return err
}
rec . SetAuthCookie ( tmp )
2018-05-05 06:25:26 +00:00
2021-11-15 05:17:56 +00:00
log . Printf ( "[xs.Session: op:%c who:%s connhost:%s cmd:%s auth:****]\n" ,
rec . Op ( ) [ 0 ] , string ( rec . Who ( ) ) , string ( rec . ConnHost ( ) ) , string ( rec . Cmd ( ) ) )
2018-09-06 23:23:57 +00:00
2021-11-15 05:17:56 +00:00
var valid bool
var allowedCmds string // Currently unused
if xs . AuthUserByToken ( xs . NewAuthCtx ( ) , string ( rec . Who ( ) ) , string ( rec . ConnHost ( ) ) , string ( rec . AuthCookie ( true ) ) ) {
valid = true
2018-09-14 06:51:49 +00:00
} else {
2021-11-15 05:17:56 +00:00
if useSystemPasswd {
//var passErr error
valid , _ /*passErr*/ = xs . VerifyPass ( xs . NewAuthCtx ( ) , string ( rec . Who ( ) ) , string ( rec . AuthCookie ( true ) ) )
} else {
valid , allowedCmds = xs . AuthUserByPasswd ( xs . NewAuthCtx ( ) , string ( rec . Who ( ) ) , string ( rec . AuthCookie ( true ) ) , "/etc/xs.passwd" )
}
2018-09-14 06:51:49 +00:00
}
2021-11-15 05:17:56 +00:00
_ = loginTimeout . Stop ( )
// Security scrub
rec . ClearAuthCookie ( )
// Tell client if auth was valid
if valid {
2022-09-27 05:09:54 +00:00
hc . Write ( [ ] byte { 1 } ) //nolint:errcheck
2018-06-29 23:54:20 +00:00
} else {
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintln ( "Invalid user" , string ( rec . Who ( ) ) ) ) //nolint:errcheck
hc . Write ( [ ] byte { 0 } ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
return
2018-06-29 23:54:20 +00:00
}
2021-11-15 05:17:56 +00:00
log . Printf ( "[allowedCmds:%s]\n" , allowedCmds )
if rec . Op ( ) [ 0 ] == 'A' {
// Generate automated login token
addr := hc . RemoteAddr ( )
hname := goutmp . GetHost ( addr . String ( ) )
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Generating autologin token for [%s@%s]]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
token := GenAuthToken ( string ( rec . Who ( ) ) , string ( rec . ConnHost ( ) ) )
2022-09-27 05:09:54 +00:00
tokenCmd := fmt . Sprintf ( "echo %q | tee -a ~/.xs_id" , token )
2021-11-15 05:17:56 +00:00
cmdStatus , runErr := runShellAs ( string ( rec . Who ( ) ) , hname , string ( rec . TermType ( ) ) , tokenCmd , false , hc , chaffEnabled )
// 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 {
2022-09-27 05:09:54 +00:00
logger . LogErr ( fmt . Sprintf ( "[Error generating autologin token for %s@%s]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
} else {
log . Printf ( "[Autologin token generation completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus )
hc . SetStatus ( xsnet . CSOType ( cmdStatus ) )
}
} else if rec . Op ( ) [ 0 ] == 'c' {
// Non-interactive command
addr := hc . RemoteAddr ( )
hname := goutmp . GetHost ( addr . String ( ) )
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Running command for [%s@%s]]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
cmdStatus , runErr := runShellAs ( string ( rec . Who ( ) ) , hname , string ( rec . TermType ( ) ) , string ( rec . Cmd ( ) ) , false , hc , chaffEnabled )
// 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 {
2022-09-27 05:09:54 +00:00
logger . LogErr ( fmt . Sprintf ( "[Error spawning cmd for %s@%s]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
} else {
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Command completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
hc . SetStatus ( xsnet . CSOType ( cmdStatus ) )
}
} else if rec . Op ( ) [ 0 ] == 's' {
// Interactive session
addr := hc . RemoteAddr ( )
hname := goutmp . GetHost ( addr . String ( ) )
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Running shell for [%s@%s]]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
cmdStatus , runErr := runShellAs ( string ( rec . Who ( ) ) , hname , string ( rec . TermType ( ) ) , string ( rec . Cmd ( ) ) , true , hc , chaffEnabled )
// 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 {
2022-09-27 05:09:54 +00:00
Log . Err ( fmt . Sprintf ( "[Error spawning shell for %s@%s]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
} else {
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[Shell completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
hc . SetStatus ( xsnet . CSOType ( cmdStatus ) )
}
} else if rec . Op ( ) [ 0 ] == 'D' {
// File copy (destination) operation - client copy to server
log . Printf ( "[Client->Server copy]\n" )
addr := hc . RemoteAddr ( )
hname := goutmp . GetHost ( addr . String ( ) )
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[c->s copy for %s@%s]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
cmdStatus , runErr := runClientToServerCopyAs ( string ( rec . Who ( ) ) , string ( rec . TermType ( ) ) , hc , string ( rec . Cmd ( ) ) , chaffEnabled )
// 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 {
2022-09-27 05:09:54 +00:00
logger . LogErr ( fmt . Sprintf ( "[c->s copy error for %s@%s]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
} else {
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[c->s copy completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
}
// TODO: Test this with huge files.. see Bug #22 - do we need to
// sync w/sender (client) that we've gotten all data?
2019-10-30 03:34:09 +00:00
hc . SetStatus ( xsnet . CSOType ( cmdStatus ) )
2021-11-15 05:17:56 +00:00
// Send CSOExitStatus *before* client closes channel
2022-09-27 05:09:54 +00:00
s := make ( [ ] byte , 4 ) //nolint:gomnd
2021-11-15 05:17:56 +00:00
binary . BigEndian . PutUint32 ( s , cmdStatus )
log . Printf ( "** cp writing closeStat %d at Close()\n" , cmdStatus )
2022-09-27 05:09:54 +00:00
hc . WritePacket ( s , xsnet . CSOExitStatus ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
} else if rec . Op ( ) [ 0 ] == 'S' {
// File copy (src) operation - server copy to client
log . Printf ( "[Server->Client copy]\n" )
addr := hc . RemoteAddr ( )
hname := goutmp . GetHost ( addr . String ( ) )
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[s->c copy for %s@%s]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
cmdStatus , runErr := runServerToClientCopyAs ( string ( rec . Who ( ) ) , string ( rec . TermType ( ) ) , hc , string ( rec . Cmd ( ) ) , chaffEnabled )
if runErr != nil {
2022-09-27 05:09:54 +00:00
logger . LogErr ( fmt . Sprintf ( "[s->c copy error for %s@%s]\n" , rec . Who ( ) , hname ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
} else {
// Returned hopefully via an EOF or exit/logout;
2022-09-27 05:09:54 +00:00
logger . LogNotice ( fmt . Sprintf ( "[s->c copy completed for %s@%s, status %d]\n" , rec . Who ( ) , hname , cmdStatus ) ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
}
// Clear current op so user can enter next, or EOF
rec . SetOp ( [ ] byte { 0 } )
hc . SetStatus ( xsnet . CSOType ( cmdStatus ) )
//fmt.Println("Waiting for EOF from other end.")
//_, _ = hc.Read(nil /*ackByte*/)
//fmt.Println("Got remote end ack.")
2018-08-23 18:03:19 +00:00
} else {
2022-09-27 05:09:54 +00:00
logger . LogErr ( fmt . Sprintln ( "[Bad xs.Session]" ) ) //nolint:errcheck
2018-08-23 18:03:19 +00:00
}
2021-11-15 05:17:56 +00:00
return
2022-09-27 05:09:54 +00:00
} ( & conn ) //nolint:errcheck
2021-11-15 05:17:56 +00:00
} // algs valid and not blacklisted
2018-04-28 23:05:33 +00:00
} // Accept() success
2018-01-21 04:37:27 +00:00
} //endfor
2022-09-27 05:09:54 +00:00
//logger.LogNotice(fmt.Sprintln("[Exiting]")) //nolint:errcheck
2018-01-06 15:30:56 +00:00
}
2023-11-05 22:58:24 +00:00
func dumpProf ( ) {
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 ) //nolint:gocritic
}
}
//os.Exit(status)
}