2022-06-10 09:35:24 +02:00
package shexec
import (
2022-08-23 00:59:03 +02:00
"bytes"
2022-07-07 03:59:46 +02:00
"context"
2022-06-29 06:57:30 +02:00
"encoding/base64"
2022-06-10 09:35:24 +02:00
"fmt"
"io"
"os"
"os/exec"
2022-06-29 23:29:38 +02:00
"os/signal"
2022-07-02 02:37:37 +02:00
"os/user"
2022-09-22 08:26:53 +02:00
"runtime"
2022-12-21 06:58:24 +01:00
"strconv"
2022-06-10 09:35:24 +02:00
"strings"
2022-10-22 23:45:31 +02:00
"sync"
2022-06-10 09:35:24 +02:00
"syscall"
"time"
2022-06-27 23:57:01 +02:00
"github.com/alessio/shellescape"
2023-10-16 22:25:53 +02:00
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
"github.com/wavetermdev/waveterm/waveshell/pkg/cirfile"
"github.com/wavetermdev/waveterm/waveshell/pkg/mpio"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
2023-08-14 21:23:33 +02:00
"github.com/creack/pty"
2022-09-26 22:02:34 +02:00
"golang.org/x/mod/semver"
2022-07-02 02:37:37 +02:00
"golang.org/x/sys/unix"
2022-06-10 09:35:24 +02:00
)
2022-09-04 08:26:57 +02:00
const DefaultTermRows = 24
const DefaultTermCols = 80
const MinTermRows = 2
const MinTermCols = 10
const MaxTermRows = 1024
const MaxTermCols = 1024
2022-06-24 03:23:30 +02:00
const MaxFdNum = 1023
const FirstExtraFilesFdNum = 3
2022-07-06 23:06:58 +02:00
const DefaultTermType = "xterm-256color"
2022-08-20 00:28:32 +02:00
const DefaultMaxPtySize = 1024 * 1024
2022-09-04 08:38:35 +02:00
const MinMaxPtySize = 16 * 1024
const MaxMaxPtySize = 100 * 1024 * 1024
2023-01-31 21:14:41 +01:00
const MaxRunDataSize = 1024 * 1024
const MaxTotalRunDataSize = 10 * MaxRunDataSize
2023-10-05 18:50:35 +02:00
const ShellVarName = "SHELL"
2022-06-21 02:51:28 +02:00
2022-08-23 00:59:03 +02:00
const GetStateTimeout = 5 * time . Second
2022-10-27 09:34:16 +02:00
const BaseBashOpts = ` set +m; set +H; shopt -s extglob `
2023-04-12 08:52:58 +02:00
var GetShellStateCmds = [ ] string {
` echo bash v$ { BASH_VERSINFO[0]}.$ { BASH_VERSINFO[1]}.$ { BASH_VERSINFO[2]}; ` ,
` pwd; ` ,
` declare -p $(compgen -A variable); ` ,
` alias -p; ` ,
` declare -f; ` ,
` printf "GITBRANCH %s\x00" "$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" ` ,
}
2022-10-25 06:26:39 +02:00
2022-09-26 22:02:34 +02:00
const ClientCommandFmt = `
2022-06-27 23:57:01 +02:00
PATH = $ PATH : ~ / . mshell ;
2022-06-28 07:39:16 +02:00
which mshell > / dev / null ;
2022-06-27 23:57:01 +02:00
if [ [ "$?" - ne 0 ] ]
then
2022-09-22 08:26:53 +02:00
printf "\n##N{\"type\": \"init\", \"notfound\": true, \"uname\": \"%s|%s\"}\n" "$(uname -s)" "$(uname -m)"
2022-06-27 23:57:01 +02:00
else
2022-09-26 22:02:34 +02:00
mshell - [ % VERSION % ] -- single
2022-06-27 23:57:01 +02:00
fi
`
2022-06-26 10:41:58 +02:00
2022-09-26 22:02:34 +02:00
func MakeClientCommandStr ( ) string {
return strings . ReplaceAll ( ClientCommandFmt , "[%VERSION%]" , semver . MajorMinor ( base . MShellVersion ) )
}
const InstallCommandFmt = `
2022-09-22 08:26:53 +02:00
printf "\n##N{\"type\": \"init\", \"notfound\": true, \"uname\": \"%s|%s\"}\n" "$(uname -s)" "$(uname -m)" ;
2022-06-28 07:39:16 +02:00
mkdir - p ~ / . mshell / ;
2022-09-22 08:51:16 +02:00
cat > ~ / . mshell / mshell . temp ;
if [ [ - s ~ / . mshell / mshell . temp ] ]
then
2022-09-26 22:02:34 +02:00
mv ~ / . mshell / mshell . temp ~ / . mshell / mshell - [ % VERSION % ] ;
chmod a + x ~ / . mshell / mshell - [ % VERSION % ] ;
~ / . mshell / mshell - [ % VERSION % ] -- single -- version
2022-09-22 08:51:16 +02:00
fi
2022-06-28 07:39:16 +02:00
`
2022-09-26 22:02:34 +02:00
func MakeInstallCommandStr ( ) string {
return strings . ReplaceAll ( InstallCommandFmt , "[%VERSION%]" , semver . MajorMinor ( base . MShellVersion ) )
}
2022-06-27 23:57:01 +02:00
const RunCommandFmt = ` %s `
2022-06-28 00:59:14 +02:00
const RunSudoCommandFmt = ` sudo -n -C %d bash /dev/fd/%d `
const RunSudoPasswordCommandFmt = ` cat /dev/fd/%d | sudo -k -S -C %d bash -c "echo '[from-mshell]'; exec %d>&-; bash /dev/fd/%d < /dev/fd/%d" `
2022-06-26 10:41:58 +02:00
2022-11-02 05:19:42 +01:00
type MShellBinaryReaderFn func ( version string , goos string , goarch string ) ( io . ReadCloser , error )
2022-10-22 23:45:31 +02:00
type ReturnStateBuf struct {
Lock * sync . Mutex
Buf [ ] byte
Done bool
Err error
Reader * os . File
FdNum int
DoneCh chan bool
}
func MakeReturnStateBuf ( ) * ReturnStateBuf {
return & ReturnStateBuf { Lock : & sync . Mutex { } , DoneCh : make ( chan bool ) }
}
2022-06-10 09:35:24 +02:00
type ShExecType struct {
2022-12-06 07:26:13 +01:00
Lock * sync . Mutex // only locks "Exited" field
2022-07-02 02:37:37 +02:00
StartTs time . Time
CK base . CommandKey
FileNames * base . CommandFileNames
Cmd * exec . Cmd
CmdPty * os . File
2022-08-20 00:28:32 +02:00
MaxPtySize int64
2022-07-02 02:37:37 +02:00
Multiplexer * mpio . Multiplexer
Detached bool
DetachedOutput * packet . PacketSender
RunnerOutFd * os . File
2022-09-07 01:40:41 +02:00
MsgSender * packet . PacketSender // where to send out-of-band messages back to calling proceess
2022-10-22 23:45:31 +02:00
ReturnState * ReturnStateBuf
2022-12-06 07:26:13 +01:00
Exited bool // locked via Lock
2022-06-23 21:48:45 +02:00
}
2022-06-29 02:20:01 +02:00
type StdContext struct { }
func ( StdContext ) GetWriter ( fdNum int ) io . WriteCloser {
if fdNum == 0 {
return os . Stdin
}
if fdNum == 1 {
return os . Stdout
}
if fdNum == 2 {
return os . Stderr
}
fd := os . NewFile ( uintptr ( fdNum ) , fmt . Sprintf ( "/dev/fd/%d" , fdNum ) )
return fd
}
func ( StdContext ) GetReader ( fdNum int ) io . ReadCloser {
if fdNum == 0 {
return os . Stdin
}
if fdNum == 1 {
return os . Stdout
}
if fdNum == 2 {
return os . Stdout
}
fd := os . NewFile ( uintptr ( fdNum ) , fmt . Sprintf ( "/dev/fd/%d" , fdNum ) )
return fd
}
type FdContext interface {
GetWriter ( fdNum int ) io . WriteCloser
GetReader ( fdNum int ) io . ReadCloser
}
2022-09-07 01:40:41 +02:00
type ShExecUPR struct {
ShExec * ShExecType
UPR packet . UnknownPacketReporter
}
2023-04-12 08:52:58 +02:00
func GetShellStateCmd ( ) string {
return strings . Join ( GetShellStateCmds , ` printf "\x00\x00"; ` )
}
2022-09-07 01:40:41 +02:00
func ( s * ShExecType ) processSpecialInputPacket ( pk * packet . SpecialInputPacketType ) error {
2022-12-21 06:58:24 +01:00
base . Logf ( "processSpecialInputPacket: %#v\n" , pk )
2022-09-07 01:40:41 +02:00
if pk . WinSize != nil {
if s . CmdPty == nil {
return fmt . Errorf ( "cannot change winsize, cmd was not started with a pty" )
}
winSize := & pty . Winsize {
Rows : uint16 ( base . BoundInt ( pk . WinSize . Rows , MinTermRows , MaxTermRows ) ) ,
Cols : uint16 ( base . BoundInt ( pk . WinSize . Cols , MinTermCols , MaxTermCols ) ) ,
}
pty . Setsize ( s . CmdPty , winSize )
s . Cmd . Process . Signal ( syscall . SIGWINCH )
}
if pk . SigName != "" {
2022-12-21 06:58:24 +01:00
var signal syscall . Signal
sigNumInt , err := strconv . Atoi ( pk . SigName )
if err == nil {
signal = syscall . Signal ( sigNumInt )
} else {
signal = unix . SignalNum ( pk . SigName )
}
if signal == 0 {
2022-09-07 01:40:41 +02:00
return fmt . Errorf ( "error signal %q not found, cannot send" , pk . SigName )
}
2022-12-21 06:58:24 +01:00
s . SendSignal ( syscall . Signal ( signal ) )
2022-09-07 01:40:41 +02:00
}
return nil
}
func ( s ShExecUPR ) UnknownPacket ( pk packet . PacketType ) {
if pk . GetType ( ) == packet . SpecialInputPacketStr {
inputPacket := pk . ( * packet . SpecialInputPacketType )
err := s . ShExec . processSpecialInputPacket ( inputPacket )
if err != nil && s . ShExec . MsgSender != nil {
msg := packet . MakeMessagePacket ( err . Error ( ) )
msg . CK = s . ShExec . CK
s . ShExec . MsgSender . SendPacket ( msg )
}
return
}
if s . UPR != nil {
s . UPR . UnknownPacket ( pk )
}
}
2022-06-29 04:01:33 +02:00
func MakeShExec ( ck base . CommandKey , upr packet . UnknownPacketReporter ) * ShExecType {
2022-06-23 21:48:45 +02:00
return & ShExecType {
2022-12-06 07:26:13 +01:00
Lock : & sync . Mutex { } ,
2022-06-24 19:24:02 +02:00
StartTs : time . Now ( ) ,
2022-06-27 21:03:47 +02:00
CK : ck ,
2022-06-29 04:01:33 +02:00
Multiplexer : mpio . MakeMultiplexer ( ck , upr ) ,
2022-06-23 21:48:45 +02:00
}
2022-06-10 09:35:24 +02:00
}
func ( c * ShExecType ) Close ( ) {
2022-06-23 21:48:45 +02:00
if c . CmdPty != nil {
c . CmdPty . Close ( )
}
2022-06-24 19:24:02 +02:00
c . Multiplexer . Close ( )
2022-07-02 02:37:37 +02:00
if c . DetachedOutput != nil {
c . DetachedOutput . Close ( )
c . DetachedOutput . WaitForDone ( )
}
if c . RunnerOutFd != nil {
c . RunnerOutFd . Close ( )
}
2022-10-22 23:45:31 +02:00
if c . ReturnState != nil {
c . ReturnState . Reader . Close ( )
}
2022-06-23 21:48:45 +02:00
}
2022-07-06 08:14:14 +02:00
func ( c * ShExecType ) MakeCmdStartPacket ( reqId string ) * packet . CmdStartPacketType {
startPacket := packet . MakeCmdStartPacket ( reqId )
2022-06-23 21:48:45 +02:00
startPacket . Ts = time . Now ( ) . UnixMilli ( )
2022-06-27 21:03:47 +02:00
startPacket . CK = c . CK
2022-06-23 21:48:45 +02:00
startPacket . Pid = c . Cmd . Process . Pid
startPacket . MShellPid = os . Getpid ( )
return startPacket
2022-06-10 09:35:24 +02:00
}
func getEnvStrKey ( envStr string ) string {
eqIdx := strings . Index ( envStr , "=" )
if eqIdx == - 1 {
return envStr
}
return envStr [ 0 : eqIdx ]
}
func UpdateCmdEnv ( cmd * exec . Cmd , envVars map [ string ] string ) {
if len ( envVars ) == 0 {
return
}
found := make ( map [ string ] bool )
var newEnv [ ] string
for _ , envStr := range cmd . Env {
envKey := getEnvStrKey ( envStr )
newEnvVal , ok := envVars [ envKey ]
if ok {
if newEnvVal == "" {
continue
}
newEnv = append ( newEnv , envKey + "=" + newEnvVal )
found [ envKey ] = true
} else {
newEnv = append ( newEnv , envStr )
}
}
for envKey , envVal := range envVars {
if found [ envKey ] {
continue
}
newEnv = append ( newEnv , envKey + "=" + envVal )
}
cmd . Env = newEnv
}
2022-06-29 23:29:38 +02:00
// returns (pr, err)
func MakeSimpleStaticWriterPipe ( data [ ] byte ) ( * os . File , error ) {
pr , pw , err := os . Pipe ( )
if err != nil {
return nil , err
}
go func ( ) {
defer pw . Close ( )
pw . Write ( data )
} ( )
return pr , err
}
func MakeDetachedExecCmd ( pk * packet . RunPacketType , cmdTty * os . File ) ( * exec . Cmd , error ) {
2022-10-17 08:46:59 +02:00
state := pk . State
if state == nil {
state = & packet . ShellState { }
}
2022-06-10 09:35:24 +02:00
ecmd := exec . Command ( "bash" , "-c" , pk . Command )
2022-10-17 08:46:59 +02:00
if ! pk . StateComplete {
2022-08-23 01:24:53 +02:00
ecmd . Env = os . Environ ( )
}
2022-10-25 21:31:07 +02:00
UpdateCmdEnv ( ecmd , EnvMapFromState ( state ) )
2022-11-02 05:19:42 +01:00
UpdateCmdEnv ( ecmd , MShellEnvVars ( getTermType ( pk ) ) )
2022-10-17 08:46:59 +02:00
if state . Cwd != "" {
ecmd . Dir = base . ExpandHomeDir ( state . Cwd )
2022-06-10 09:35:24 +02:00
}
2022-07-06 20:21:15 +02:00
if HasDupStdin ( pk . Fds ) {
return nil , fmt . Errorf ( "cannot detach command with dup stdin" )
2022-06-29 23:29:38 +02:00
}
2022-07-06 20:21:15 +02:00
ecmd . Stdin = cmdTty
2022-06-10 09:35:24 +02:00
ecmd . Stdout = cmdTty
ecmd . Stderr = cmdTty
ecmd . SysProcAttr = & syscall . SysProcAttr {
Setsid : true ,
Setctty : true ,
}
2022-06-29 23:29:38 +02:00
extraFiles := make ( [ ] * os . File , 0 , MaxFdNum + 1 )
2022-07-06 20:21:15 +02:00
if len ( pk . Fds ) > 0 {
return nil , fmt . Errorf ( "invalid fd %d passed to detached command" , pk . Fds [ 0 ] . FdNum )
2022-06-29 23:29:38 +02:00
}
for _ , runData := range pk . RunData {
if runData . FdNum >= len ( extraFiles ) {
extraFiles = extraFiles [ : runData . FdNum + 1 ]
}
var err error
extraFiles [ runData . FdNum ] , err = MakeSimpleStaticWriterPipe ( runData . Data )
if err != nil {
return nil , err
}
}
if len ( extraFiles ) > FirstExtraFilesFdNum {
ecmd . ExtraFiles = extraFiles [ FirstExtraFilesFdNum : ]
}
return ecmd , nil
2022-06-10 09:35:24 +02:00
}
2022-06-27 21:03:47 +02:00
func MakeRunnerExec ( ck base . CommandKey ) ( * exec . Cmd , error ) {
2022-06-23 21:48:45 +02:00
msPath , err := base . GetMShellPath ( )
if err != nil {
return nil , err
}
2022-06-27 21:03:47 +02:00
ecmd := exec . Command ( msPath , string ( ck ) )
2022-06-11 06:37:21 +02:00
return ecmd , nil
}
2022-06-10 09:35:24 +02:00
// this will never return (unless there is an error creating/opening the file), as fifoFile will never EOF
func MakeAndCopyStdinFifo ( dst * os . File , fifoName string ) error {
os . Remove ( fifoName )
err := syscall . Mkfifo ( fifoName , 0600 ) // only read/write from user for security
if err != nil {
return fmt . Errorf ( "cannot make stdin-fifo '%s': %v" , fifoName , err )
}
// rw is non-blocking, will keep the fifo "open" for the blocking reader
rwfd , err := os . OpenFile ( fifoName , os . O_RDWR , 0600 )
if err != nil {
return fmt . Errorf ( "cannot open stdin-fifo(1) '%s': %v" , fifoName , err )
}
defer rwfd . Close ( )
fifoReader , err := os . Open ( fifoName ) // blocking open/reads (open won't block because of rwfd)
if err != nil {
return fmt . Errorf ( "cannot open stdin-fifo(2) '%s': %w" , fifoName , err )
}
defer fifoReader . Close ( )
io . Copy ( dst , fifoReader )
return nil
}
func ValidateRunPacket ( pk * packet . RunPacketType ) error {
if pk . Type != packet . RunPacketStr {
return fmt . Errorf ( "run packet has wrong type: %s" , pk . Type )
}
2022-06-23 21:48:45 +02:00
if pk . Detached {
2022-06-27 21:03:47 +02:00
err := pk . CK . Validate ( "run packet" )
2022-06-23 21:48:45 +02:00
if err != nil {
2022-06-27 21:03:47 +02:00
return err
2022-06-23 21:48:45 +02:00
}
2022-06-28 03:42:56 +02:00
for _ , rfd := range pk . Fds {
if rfd . Write {
return fmt . Errorf ( "cannot detach command with writable remote files fd=%d" , rfd . FdNum )
}
2022-07-06 20:21:15 +02:00
if rfd . Read && rfd . DupStdin {
return fmt . Errorf ( "cannot detach command with dup stdin fd=%d" , rfd . FdNum )
}
if rfd . Read {
2022-06-29 06:57:30 +02:00
return fmt . Errorf ( "cannot detach command with readable remote files fd=%d" , rfd . FdNum )
}
}
totalRunData := 0
for _ , rd := range pk . RunData {
2023-01-31 21:14:41 +01:00
if rd . DataLen > MaxRunDataSize {
2022-06-29 06:57:30 +02:00
return fmt . Errorf ( "cannot detach command, constant rundata input too large fd=%d, len=%d, max=%d" , rd . FdNum , rd . DataLen , mpio . ReadBufSize )
2022-06-28 03:42:56 +02:00
}
2022-06-29 06:57:30 +02:00
totalRunData += rd . DataLen
}
2023-01-31 21:14:41 +01:00
if totalRunData > MaxTotalRunDataSize {
2022-06-29 06:57:30 +02:00
return fmt . Errorf ( "cannot detach command, constant rundata input too large len=%d, max=%d" , totalRunData , mpio . MaxTotalRunDataSize )
2022-06-28 03:42:56 +02:00
}
2022-06-10 09:35:24 +02:00
}
2022-10-17 08:46:59 +02:00
if pk . State != nil && pk . State . Cwd != "" {
realCwd := base . ExpandHomeDir ( pk . State . Cwd )
2022-06-25 08:42:00 +02:00
dirInfo , err := os . Stat ( realCwd )
2022-06-10 09:35:24 +02:00
if err != nil {
2022-06-25 08:42:00 +02:00
return fmt . Errorf ( "invalid cwd '%s' for command: %v" , realCwd , err )
2022-06-10 09:35:24 +02:00
}
if ! dirInfo . IsDir ( ) {
2022-06-25 08:42:00 +02:00
return fmt . Errorf ( "invalid cwd '%s' for command, not a directory" , realCwd )
2022-06-10 09:35:24 +02:00
}
}
2022-06-29 06:57:30 +02:00
for _ , runData := range pk . RunData {
if runData . DataLen != len ( runData . Data ) {
return fmt . Errorf ( "rundata length mismatch, fd=%d, datalen=%d, expected=%d" , runData . FdNum , len ( runData . Data ) , runData . DataLen )
}
}
2022-07-06 20:21:15 +02:00
if pk . UsePty && HasDupStdin ( pk . Fds ) {
return fmt . Errorf ( "cannot use pty with command that has dup stdin" )
}
2022-06-10 09:35:24 +02:00
return nil
}
2022-06-21 02:51:28 +02:00
func GetWinsize ( p * packet . RunPacketType ) * pty . Winsize {
2022-09-04 08:26:57 +02:00
rows := DefaultTermRows
cols := DefaultTermCols
2022-07-06 23:06:58 +02:00
if p . TermOpts != nil {
2022-09-04 08:26:57 +02:00
rows = base . BoundInt ( p . TermOpts . Rows , MinTermRows , MaxTermRows )
cols = base . BoundInt ( p . TermOpts . Cols , MinTermCols , MaxTermCols )
2022-06-21 02:51:28 +02:00
}
return & pty . Winsize { Rows : uint16 ( rows ) , Cols : uint16 ( cols ) }
}
2022-06-29 00:04:08 +02:00
type SSHOpts struct {
2022-09-14 02:10:18 +02:00
SSHHost string
SSHOptsStr string
SSHIdentity string
SSHUser string
2022-10-01 02:22:57 +02:00
SSHPort int
2022-09-14 02:10:18 +02:00
SSHErrorsToTty bool
2022-10-01 02:22:57 +02:00
BatchMode bool
2022-06-28 07:39:16 +02:00
}
type InstallOpts struct {
2022-06-29 00:04:08 +02:00
SSHOpts SSHOpts
2022-06-28 07:39:16 +02:00
ArchStr string
OptName string
2022-06-28 08:14:53 +02:00
Detect bool
2022-09-26 22:02:34 +02:00
CmdPty * os . File
2022-06-28 07:39:16 +02:00
}
2022-06-24 22:25:09 +02:00
type ClientOpts struct {
2022-06-29 06:57:30 +02:00
SSHOpts SSHOpts
Command string
Fds [ ] packet . RemoteFd
Cwd string
Debug bool
Sudo bool
SudoWithPass bool
SudoPw string
Detach bool
2022-07-06 20:21:15 +02:00
UsePty bool
2022-06-24 22:25:09 +02:00
}
2022-06-29 23:29:38 +02:00
func ( opts SSHOpts ) MakeSSHInstallCmd ( ) ( * exec . Cmd , error ) {
if opts . SSHHost == "" {
return nil , fmt . Errorf ( "no ssh host provided, can only install to a remote host" )
}
2022-09-26 22:02:34 +02:00
cmdStr := MakeInstallCommandStr ( )
return opts . MakeSSHExecCmd ( cmdStr ) , nil
2022-06-29 23:29:38 +02:00
}
2022-07-07 03:59:46 +02:00
func ( opts SSHOpts ) MakeMShellServerCmd ( ) ( * exec . Cmd , error ) {
msPath , err := base . GetMShellPath ( )
if err != nil {
return nil , err
}
ecmd := exec . Command ( msPath , "--server" )
return ecmd , nil
}
2022-09-06 21:57:54 +02:00
func ( opts SSHOpts ) MakeMShellSingleCmd ( fromServer bool ) ( * exec . Cmd , error ) {
2022-06-29 23:29:38 +02:00
if opts . SSHHost == "" {
execFile , err := os . Executable ( )
if err != nil {
return nil , fmt . Errorf ( "cannot find local mshell executable: %w" , err )
}
2022-09-06 21:57:54 +02:00
var ecmd * exec . Cmd
if fromServer {
ecmd = exec . Command ( execFile , "--single-from-server" )
} else {
ecmd = exec . Command ( execFile , "--single" )
}
2022-06-29 23:29:38 +02:00
return ecmd , nil
}
2022-09-26 22:02:34 +02:00
cmdStr := MakeClientCommandStr ( )
return opts . MakeSSHExecCmd ( cmdStr ) , nil
2022-06-29 23:29:38 +02:00
}
2022-06-29 00:04:08 +02:00
func ( opts SSHOpts ) MakeSSHExecCmd ( remoteCommand string ) * exec . Cmd {
remoteCommand = strings . TrimSpace ( remoteCommand )
if opts . SSHHost == "" {
2022-08-24 11:11:49 +02:00
homeDir , _ := os . UserHomeDir ( ) // ignore error
if homeDir == "" {
homeDir = "/"
}
2022-06-29 00:04:08 +02:00
ecmd := exec . Command ( "bash" , "-c" , remoteCommand )
2022-08-24 11:11:49 +02:00
ecmd . Dir = homeDir
2022-06-28 00:10:17 +02:00
return ecmd
} else {
var moreSSHOpts [ ] string
2022-06-29 00:04:08 +02:00
if opts . SSHIdentity != "" {
identityOpt := fmt . Sprintf ( "-i %s" , shellescape . Quote ( opts . SSHIdentity ) )
2022-06-28 00:10:17 +02:00
moreSSHOpts = append ( moreSSHOpts , identityOpt )
}
2022-06-29 00:04:08 +02:00
if opts . SSHUser != "" {
userOpt := fmt . Sprintf ( "-l %s" , shellescape . Quote ( opts . SSHUser ) )
2022-06-28 00:10:17 +02:00
moreSSHOpts = append ( moreSSHOpts , userOpt )
}
2022-10-01 02:22:57 +02:00
if opts . SSHPort != 0 {
portOpt := fmt . Sprintf ( "-p %d" , opts . SSHPort )
moreSSHOpts = append ( moreSSHOpts , portOpt )
}
2022-09-14 02:10:18 +02:00
if opts . SSHErrorsToTty {
2022-10-01 02:22:57 +02:00
errFdStr := "-E /dev/tty"
moreSSHOpts = append ( moreSSHOpts , errFdStr )
}
if opts . BatchMode {
batchOpt := "-o 'BatchMode=yes'"
moreSSHOpts = append ( moreSSHOpts , batchOpt )
2022-09-14 02:10:18 +02:00
}
2022-10-01 02:22:57 +02:00
// note that SSHOptsStr is *not* escaped
sshCmd := fmt . Sprintf ( "ssh %s %s %s %s" , strings . Join ( moreSSHOpts , " " ) , opts . SSHOptsStr , shellescape . Quote ( opts . SSHHost ) , shellescape . Quote ( remoteCommand ) )
2022-06-28 00:10:17 +02:00
ecmd := exec . Command ( "bash" , "-c" , sshCmd )
return ecmd
}
2022-06-27 23:57:01 +02:00
}
2022-06-29 00:04:08 +02:00
func ( opts SSHOpts ) MakeMShellSSHOpts ( ) string {
2022-06-28 07:39:16 +02:00
var moreSSHOpts [ ] string
2022-06-29 00:04:08 +02:00
if opts . SSHIdentity != "" {
identityOpt := fmt . Sprintf ( "-i %s" , shellescape . Quote ( opts . SSHIdentity ) )
2022-06-28 07:39:16 +02:00
moreSSHOpts = append ( moreSSHOpts , identityOpt )
}
2022-06-29 00:04:08 +02:00
if opts . SSHUser != "" {
userOpt := fmt . Sprintf ( "-l %s" , shellescape . Quote ( opts . SSHUser ) )
2022-06-28 07:39:16 +02:00
moreSSHOpts = append ( moreSSHOpts , userOpt )
}
2022-10-01 02:22:57 +02:00
if opts . SSHPort != 0 {
portOpt := fmt . Sprintf ( "-p %d" , opts . SSHPort )
moreSSHOpts = append ( moreSSHOpts , portOpt )
}
2022-06-29 00:04:08 +02:00
if opts . SSHOptsStr != "" {
optsOpt := fmt . Sprintf ( "--ssh-opts %s" , shellescape . Quote ( opts . SSHOptsStr ) )
2022-06-28 03:42:56 +02:00
moreSSHOpts = append ( moreSSHOpts , optsOpt )
}
2022-06-29 00:04:08 +02:00
if opts . SSHHost != "" {
sshArg := fmt . Sprintf ( "--ssh %s" , shellescape . Quote ( opts . SSHHost ) )
2022-06-28 03:42:56 +02:00
moreSSHOpts = append ( moreSSHOpts , sshArg )
}
2022-06-29 00:04:08 +02:00
return strings . Join ( moreSSHOpts , " " )
2022-06-28 03:42:56 +02:00
}
2022-07-06 23:06:58 +02:00
func GetTerminalSize ( ) ( int , int , error ) {
fd , err := os . Open ( "/dev/tty" )
if err != nil {
return 0 , 0 , err
}
defer fd . Close ( )
return pty . Getsize ( fd )
}
2022-06-26 10:41:58 +02:00
func ( opts * ClientOpts ) MakeRunPacket ( ) ( * packet . RunPacketType , error ) {
2022-06-24 22:25:09 +02:00
runPacket := packet . MakeRunPacket ( )
2022-06-28 03:42:56 +02:00
runPacket . Detached = opts . Detach
2022-10-17 08:46:59 +02:00
runPacket . State = & packet . ShellState { }
runPacket . State . Cwd = opts . Cwd
2022-06-24 22:25:09 +02:00
runPacket . Fds = opts . Fds
2022-07-06 23:06:58 +02:00
if opts . UsePty {
runPacket . UsePty = true
runPacket . TermOpts = & packet . TermOpts { }
rows , cols , err := GetTerminalSize ( )
if err == nil {
runPacket . TermOpts . Rows = rows
runPacket . TermOpts . Cols = cols
}
term := os . Getenv ( "TERM" )
if term != "" {
runPacket . TermOpts . Term = term
}
}
2022-06-26 10:41:58 +02:00
if ! opts . Sudo {
// normal, non-sudo command
2022-06-27 23:57:01 +02:00
runPacket . Command = fmt . Sprintf ( RunCommandFmt , opts . Command )
2022-06-26 10:41:58 +02:00
return runPacket , nil
}
2022-06-29 02:20:01 +02:00
if opts . SudoWithPass {
2022-06-29 06:57:30 +02:00
pwFdNum , err := AddRunData ( runPacket , opts . SudoPw , "sudo pw" )
2022-06-26 10:41:58 +02:00
if err != nil {
return nil , err
}
2022-06-29 06:57:30 +02:00
commandFdNum , err := AddRunData ( runPacket , opts . Command , "command" )
2022-06-26 10:41:58 +02:00
if err != nil {
return nil , err
}
2022-06-29 06:57:30 +02:00
commandStdinFdNum , err := NextFreeFdNum ( runPacket )
2022-06-26 10:41:58 +02:00
if err != nil {
return nil , err
}
commandStdinRfd := packet . RemoteFd { FdNum : commandStdinFdNum , Read : true , DupStdin : true }
2022-06-29 06:57:30 +02:00
runPacket . Fds = append ( runPacket . Fds , commandStdinRfd )
maxFdNum := MaxFdNumInPacket ( runPacket )
2022-06-28 00:59:14 +02:00
runPacket . Command = fmt . Sprintf ( RunSudoPasswordCommandFmt , pwFdNum , maxFdNum + 1 , pwFdNum , commandFdNum , commandStdinFdNum )
2022-06-26 10:41:58 +02:00
return runPacket , nil
} else {
2022-06-29 06:57:30 +02:00
commandFdNum , err := AddRunData ( runPacket , opts . Command , "command" )
2022-06-26 10:41:58 +02:00
if err != nil {
return nil , err
}
2022-06-29 06:57:30 +02:00
maxFdNum := MaxFdNumInPacket ( runPacket )
2022-06-27 23:57:01 +02:00
runPacket . Command = fmt . Sprintf ( RunSudoCommandFmt , maxFdNum + 1 , commandFdNum )
2022-06-26 10:41:58 +02:00
return runPacket , nil
}
}
2022-06-29 06:57:30 +02:00
func AddRunData ( pk * packet . RunPacketType , data string , dataType string ) ( int , error ) {
2023-01-31 21:14:41 +01:00
if len ( data ) > MaxRunDataSize {
return 0 , fmt . Errorf ( "%s too large, exceeds read buffer size size:%d" , dataType , len ( data ) )
2022-06-29 06:57:30 +02:00
}
fdNum , err := NextFreeFdNum ( pk )
if err != nil {
return 0 , err
}
runData := packet . RunDataType { FdNum : fdNum , DataLen : len ( data ) , Data : [ ] byte ( data ) }
pk . RunData = append ( pk . RunData , runData )
return fdNum , nil
}
func NextFreeFdNum ( pk * packet . RunPacketType ) ( int , error ) {
2022-06-26 10:41:58 +02:00
fdMap := make ( map [ int ] bool )
2022-06-29 06:57:30 +02:00
for _ , fd := range pk . Fds {
2022-06-26 10:41:58 +02:00
fdMap [ fd . FdNum ] = true
}
2022-06-29 06:57:30 +02:00
for _ , rd := range pk . RunData {
fdMap [ rd . FdNum ] = true
}
2022-06-26 10:41:58 +02:00
for i := 3 ; i <= MaxFdNum ; i ++ {
if ! fdMap [ i ] {
return i , nil
}
}
return 0 , fmt . Errorf ( "reached maximum number of fds, all fds between 3-%d are in use" , MaxFdNum )
}
2022-06-29 06:57:30 +02:00
func MaxFdNumInPacket ( pk * packet . RunPacketType ) int {
2022-06-26 10:41:58 +02:00
maxFdNum := 3
2022-06-29 06:57:30 +02:00
for _ , fd := range pk . Fds {
2022-06-26 10:41:58 +02:00
if fd . FdNum > maxFdNum {
maxFdNum = fd . FdNum
}
}
2022-06-29 06:57:30 +02:00
for _ , rd := range pk . RunData {
if rd . FdNum > maxFdNum {
maxFdNum = rd . FdNum
}
}
2022-06-26 10:41:58 +02:00
return maxFdNum
2022-06-24 22:25:09 +02:00
}
2022-06-25 08:42:00 +02:00
func ValidateRemoteFds ( rfds [ ] packet . RemoteFd ) error {
dupMap := make ( map [ int ] bool )
for _ , rfd := range rfds {
if rfd . FdNum < 0 {
return fmt . Errorf ( "mshell negative fd numbers fd=%d" , rfd . FdNum )
}
if rfd . FdNum < FirstExtraFilesFdNum {
return fmt . Errorf ( "mshell does not support re-opening fd=%d (0, 1, and 2, are always open)" , rfd . FdNum )
}
if rfd . FdNum > MaxFdNum {
return fmt . Errorf ( "mshell does not support opening fd numbers above %d" , MaxFdNum )
}
if dupMap [ rfd . FdNum ] {
return fmt . Errorf ( "mshell got duplicate entries for fd=%d" , rfd . FdNum )
}
if rfd . Read && rfd . Write {
return fmt . Errorf ( "mshell does not support opening fd numbers for reading and writing, fd=%d" , rfd . FdNum )
}
if ! rfd . Read && ! rfd . Write {
return fmt . Errorf ( "invalid fd=%d, neither reading or writing mode specified" , rfd . FdNum )
}
dupMap [ rfd . FdNum ] = true
}
return nil
}
2022-11-02 05:19:42 +01:00
func sendMShellBinary ( input io . WriteCloser , mshellStream io . Reader ) {
2022-06-28 08:14:53 +02:00
go func ( ) {
defer input . Close ( )
2022-11-02 05:19:42 +01:00
io . Copy ( input , mshellStream )
2022-06-28 08:14:53 +02:00
} ( )
}
2022-11-02 05:19:42 +01:00
func RunInstallFromCmd ( ctx context . Context , ecmd * exec . Cmd , tryDetect bool , mshellStream io . Reader , mshellReaderFn MShellBinaryReaderFn , msgFn func ( string ) ) error {
2022-06-28 07:39:16 +02:00
inputWriter , err := ecmd . StdinPipe ( )
if err != nil {
return fmt . Errorf ( "creating stdin pipe: %v" , err )
}
stdoutReader , err := ecmd . StdoutPipe ( )
if err != nil {
return fmt . Errorf ( "creating stdout pipe: %v" , err )
}
stderrReader , err := ecmd . StderrPipe ( )
if err != nil {
return fmt . Errorf ( "creating stderr pipe: %v" , err )
}
go func ( ) {
io . Copy ( os . Stderr , stderrReader )
} ( )
2022-11-02 05:19:42 +01:00
if mshellStream != nil {
sendMShellBinary ( inputWriter , mshellStream )
2022-06-28 07:39:16 +02:00
}
2023-09-01 07:03:38 +02:00
packetParser := packet . MakePacketParser ( stdoutReader , false )
2022-06-28 07:39:16 +02:00
err = ecmd . Start ( )
if err != nil {
return fmt . Errorf ( "running ssh command: %w" , err )
}
2022-06-28 08:14:53 +02:00
firstInit := true
2022-09-27 08:23:32 +02:00
for {
var pk packet . PacketType
select {
case pk = <- packetParser . MainCh :
case <- ctx . Done ( ) :
return ctx . Err ( )
}
2023-02-01 09:44:31 +01:00
if pk == nil {
return fmt . Errorf ( "no response packet received from client" )
}
2022-06-28 08:14:53 +02:00
if pk . GetType ( ) == packet . InitPacketStr && firstInit {
firstInit = false
initPacket := pk . ( * packet . InitPacketType )
if ! tryDetect {
continue // ignore
}
tryDetect = false
if initPacket . UName == "" {
return fmt . Errorf ( "cannot detect arch, no uname received from remote server" )
}
goos , goarch , err := DetectGoArch ( initPacket . UName )
if err != nil {
return fmt . Errorf ( "arch cannot be detected (might be incompatible with mshell): %w" , err )
}
2022-09-26 22:02:34 +02:00
msgStr := fmt . Sprintf ( "mshell detected remote architecture as '%s.%s'\n" , goos , goarch )
msgFn ( msgStr )
2022-11-02 05:19:42 +01:00
detectedMSS , err := mshellReaderFn ( base . MShellVersion , goos , goarch )
2022-09-26 22:02:34 +02:00
if err != nil {
2022-11-02 05:19:42 +01:00
return err
2022-09-26 22:02:34 +02:00
}
2022-11-02 05:19:42 +01:00
defer detectedMSS . Close ( )
sendMShellBinary ( inputWriter , detectedMSS )
2022-06-28 08:14:53 +02:00
continue
}
if pk . GetType ( ) == packet . InitPacketStr && ! firstInit {
2022-06-28 07:39:16 +02:00
initPacket := pk . ( * packet . InitPacketType )
if initPacket . Version == base . MShellVersion {
return nil
}
return fmt . Errorf ( "invalid version '%s' received from client, expecting '%s'" , initPacket . Version , base . MShellVersion )
}
if pk . GetType ( ) == packet . RawPacketStr {
rawPk := pk . ( * packet . RawPacketType )
2022-09-27 08:23:32 +02:00
msgFn ( fmt . Sprintf ( "%s\n" , rawPk . Data ) )
2022-06-28 07:39:16 +02:00
continue
}
return fmt . Errorf ( "invalid response packet '%s' received from client" , pk . GetType ( ) )
}
return fmt . Errorf ( "did not receive version string from client, install not successful" )
}
2022-09-26 22:02:34 +02:00
func RunInstallFromOpts ( opts * InstallOpts ) error {
ecmd , err := opts . SSHOpts . MakeSSHInstallCmd ( )
if err != nil {
return err
}
msgFn := func ( str string ) {
fmt . Printf ( "%s" , str )
}
2022-11-02 05:19:42 +01:00
var mshellStream * os . File
if opts . OptName != "" {
mshellStream , err = os . Open ( opts . OptName )
if err != nil {
return fmt . Errorf ( "cannot open mshell binary %q: %v" , opts . OptName , err )
}
defer mshellStream . Close ( )
}
err = RunInstallFromCmd ( context . Background ( ) , ecmd , opts . Detect , mshellStream , base . MShellBinaryFromOptDir , msgFn )
2022-09-26 22:02:34 +02:00
if err != nil {
return err
}
mmVersion := semver . MajorMinor ( base . MShellVersion )
fmt . Printf ( "mshell installed successfully at %s:~/.mshell/mshell%s\n" , opts . SSHOpts . SSHHost , mmVersion )
return nil
}
2022-06-29 02:20:01 +02:00
func HasDupStdin ( fds [ ] packet . RemoteFd ) bool {
for _ , rfd := range fds {
if rfd . Read && rfd . DupStdin {
return true
}
}
return false
}
2022-06-29 04:01:33 +02:00
func RunClientSSHCommandAndWait ( runPacket * packet . RunPacketType , fdContext FdContext , sshOpts SSHOpts , upr packet . UnknownPacketReporter , debug bool ) ( * packet . CmdDonePacketType , error ) {
cmd := MakeShExec ( runPacket . CK , upr )
2022-09-06 21:57:54 +02:00
ecmd , err := sshOpts . MakeMShellSingleCmd ( false )
2022-06-29 23:29:38 +02:00
if err != nil {
return nil , err
}
2022-06-24 22:25:09 +02:00
cmd . Cmd = ecmd
inputWriter , err := ecmd . StdinPipe ( )
if err != nil {
return nil , fmt . Errorf ( "creating stdin pipe: %v" , err )
}
stdoutReader , err := ecmd . StdoutPipe ( )
if err != nil {
return nil , fmt . Errorf ( "creating stdout pipe: %v" , err )
}
stderrReader , err := ecmd . StderrPipe ( )
if err != nil {
return nil , fmt . Errorf ( "creating stderr pipe: %v" , err )
}
2022-06-29 02:20:01 +02:00
if ! HasDupStdin ( runPacket . Fds ) {
2022-07-06 21:16:37 +02:00
cmd . Multiplexer . MakeRawFdReader ( 0 , fdContext . GetReader ( 0 ) , false , false )
2022-06-26 10:41:58 +02:00
}
2023-04-18 00:13:03 +02:00
cmd . Multiplexer . MakeRawFdWriter ( 1 , fdContext . GetWriter ( 1 ) , false , "client" )
cmd . Multiplexer . MakeRawFdWriter ( 2 , fdContext . GetWriter ( 2 ) , false , "client" )
2022-06-26 10:41:58 +02:00
for _ , rfd := range runPacket . Fds {
if rfd . Read && rfd . DupStdin {
2022-07-06 21:16:37 +02:00
cmd . Multiplexer . MakeRawFdReader ( rfd . FdNum , fdContext . GetReader ( 0 ) , false , false )
2022-06-26 10:41:58 +02:00
continue
}
2022-06-25 08:42:00 +02:00
if rfd . Read {
2022-06-29 02:20:01 +02:00
fd := fdContext . GetReader ( rfd . FdNum )
2022-07-06 21:16:37 +02:00
cmd . Multiplexer . MakeRawFdReader ( rfd . FdNum , fd , false , false )
2022-06-25 08:42:00 +02:00
} else if rfd . Write {
2022-06-29 02:20:01 +02:00
fd := fdContext . GetWriter ( rfd . FdNum )
2023-04-18 00:13:03 +02:00
cmd . Multiplexer . MakeRawFdWriter ( rfd . FdNum , fd , true , "client" )
2022-06-25 08:42:00 +02:00
}
}
2022-06-24 22:25:09 +02:00
err = ecmd . Start ( )
if err != nil {
return nil , fmt . Errorf ( "running ssh command: %w" , err )
}
defer cmd . Close ( )
2023-09-01 07:03:38 +02:00
stdoutPacketParser := packet . MakePacketParser ( stdoutReader , false )
stderrPacketParser := packet . MakePacketParser ( stderrReader , false )
packetParser := packet . CombinePacketParsers ( stdoutPacketParser , stderrPacketParser , false )
2022-11-27 22:47:18 +01:00
sender := packet . MakePacketSender ( inputWriter , nil )
2022-06-25 09:33:18 +02:00
versionOk := false
2022-06-27 21:14:07 +02:00
for pk := range packetParser . MainCh {
2022-06-24 22:25:09 +02:00
if pk . GetType ( ) == packet . RawPacketStr {
rawPk := pk . ( * packet . RawPacketType )
fmt . Printf ( "%s\n" , rawPk . Data )
continue
}
if pk . GetType ( ) == packet . InitPacketStr {
initPk := pk . ( * packet . InitPacketType )
2022-09-26 22:02:34 +02:00
mmVersion := semver . MajorMinor ( base . MShellVersion )
2022-06-27 23:57:01 +02:00
if initPk . NotFound {
2022-06-29 00:04:08 +02:00
if sshOpts . SSHHost == "" {
2022-09-26 22:02:34 +02:00
return nil , fmt . Errorf ( "mshell-%s command not found on local server" , mmVersion )
2022-06-29 00:04:08 +02:00
}
2022-06-28 03:42:56 +02:00
if initPk . UName == "" {
2022-09-26 22:02:34 +02:00
return nil , fmt . Errorf ( "mshell-%s command not found on remote server, no uname detected" , mmVersion )
2022-06-28 03:42:56 +02:00
}
2022-06-28 08:14:53 +02:00
goos , goarch , err := DetectGoArch ( initPk . UName )
2022-06-28 03:42:56 +02:00
if err != nil {
2022-09-26 22:02:34 +02:00
return nil , fmt . Errorf ( "mshell-%s command not found on remote server, architecture cannot be detected (might be incompatible with mshell): %w" , mmVersion , err )
2022-06-28 03:42:56 +02:00
}
2022-06-29 00:04:08 +02:00
sshOptsStr := sshOpts . MakeMShellSSHOpts ( )
2022-09-26 22:02:34 +02:00
return nil , fmt . Errorf ( "mshell-%s command not found on remote server, can install with 'mshell --install %s %s.%s'" , mmVersion , sshOptsStr , goos , goarch )
2022-06-27 23:57:01 +02:00
}
2022-09-26 22:02:34 +02:00
if semver . MajorMinor ( initPk . Version ) != semver . MajorMinor ( base . MShellVersion ) {
return nil , fmt . Errorf ( "invalid remote mshell version '%s', must be '=%s'" , initPk . Version , semver . MajorMinor ( base . MShellVersion ) )
2022-06-24 22:25:09 +02:00
}
2022-06-25 09:33:18 +02:00
versionOk = true
2022-06-29 00:04:08 +02:00
if debug {
2022-06-28 00:59:14 +02:00
fmt . Printf ( "VERSION> %s\n" , initPk . Version )
}
2022-06-24 22:25:09 +02:00
break
}
}
2022-06-25 09:33:18 +02:00
if ! versionOk {
return nil , fmt . Errorf ( "did not receive version from remote mshell" )
}
2022-07-07 03:59:46 +02:00
SendRunPacketAndRunData ( context . Background ( ) , sender , runPacket )
2022-06-29 00:04:08 +02:00
if debug {
2022-06-25 09:22:03 +02:00
cmd . Multiplexer . Debug = true
}
2022-06-27 21:14:07 +02:00
remoteDonePacket := cmd . Multiplexer . RunIOAndWait ( packetParser , sender , false , true , true )
2022-06-24 22:25:09 +02:00
donePacket := cmd . WaitForCommand ( )
if remoteDonePacket != nil {
donePacket = remoteDonePacket
}
return donePacket , nil
}
2022-06-29 06:57:30 +02:00
func min ( v1 int , v2 int ) int {
if v1 <= v2 {
return v1
}
return v2
}
2022-07-07 03:59:46 +02:00
func SendRunPacketAndRunData ( ctx context . Context , sender * packet . PacketSender , runPacket * packet . RunPacketType ) error {
err := sender . SendPacketCtx ( ctx , runPacket )
if err != nil {
return err
}
2022-06-29 23:29:38 +02:00
if len ( runPacket . RunData ) == 0 {
2022-07-07 03:59:46 +02:00
return nil
2022-06-29 23:29:38 +02:00
}
2022-06-29 06:57:30 +02:00
for _ , runData := range runPacket . RunData {
sendBuf := runData . Data
for len ( sendBuf ) > 0 {
chunkSize := min ( len ( sendBuf ) , mpio . MaxSingleWriteSize )
chunk := sendBuf [ 0 : chunkSize ]
dataPk := packet . MakeDataPacket ( )
dataPk . CK = runPacket . CK
dataPk . FdNum = runData . FdNum
dataPk . Data64 = base64 . StdEncoding . EncodeToString ( chunk )
dataPk . Eof = ( len ( chunk ) == len ( sendBuf ) )
sendBuf = sendBuf [ chunkSize : ]
2022-07-07 03:59:46 +02:00
err = sender . SendPacketCtx ( ctx , dataPk )
if err != nil {
return err
}
2022-06-29 06:57:30 +02:00
}
}
2022-07-07 03:59:46 +02:00
err = sender . SendPacketCtx ( ctx , packet . MakeDataEndPacket ( runPacket . CK ) )
if err != nil {
return err
}
return nil
2022-06-29 06:57:30 +02:00
}
2022-06-28 08:14:53 +02:00
func DetectGoArch ( uname string ) ( string , string , error ) {
2022-06-28 03:42:56 +02:00
fields := strings . SplitN ( uname , "|" , 2 )
if len ( fields ) != 2 {
return "" , "" , fmt . Errorf ( "invalid uname string returned" )
}
osVal := strings . TrimSpace ( strings . ToLower ( fields [ 0 ] ) )
archVal := strings . TrimSpace ( strings . ToLower ( fields [ 1 ] ) )
if osVal != "darwin" && osVal != "linux" {
return "" , "" , fmt . Errorf ( "invalid uname OS '%s', mshell only supports OS X (darwin) and linux" , osVal )
}
goos := osVal
goarch := ""
if archVal == "x86_64" || archVal == "i686" || archVal == "amd64" {
goarch = "amd64"
2022-09-22 08:26:53 +02:00
} else if archVal == "aarch64" || archVal == "arm64" {
2022-06-28 03:42:56 +02:00
goarch = "arm64"
}
if goarch == "" {
return "" , "" , fmt . Errorf ( "invalid uname machine type '%s', mshell only supports aarch64 (amd64) and x86_64 (amd64)" , archVal )
}
2022-06-28 07:39:16 +02:00
if ! base . ValidGoArch ( goos , goarch ) {
return "" , "" , fmt . Errorf ( "invalid arch detected %s.%s" , goos , goarch )
}
2022-06-28 03:42:56 +02:00
return goos , goarch , nil
}
2022-06-27 21:14:07 +02:00
func ( cmd * ShExecType ) RunRemoteIOAndWait ( packetParser * packet . PacketParser , sender * packet . PacketSender ) {
2022-06-25 08:42:00 +02:00
defer cmd . Close ( )
2022-10-22 23:45:31 +02:00
if cmd . ReturnState != nil {
go cmd . ReturnState . Run ( )
}
2022-06-27 21:14:07 +02:00
cmd . Multiplexer . RunIOAndWait ( packetParser , sender , true , false , false )
2022-06-23 21:48:45 +02:00
donePacket := cmd . WaitForCommand ( )
sender . SendPacket ( donePacket )
}
2022-07-06 23:06:58 +02:00
func getTermType ( pk * packet . RunPacketType ) string {
termType := DefaultTermType
if pk . TermOpts != nil && pk . TermOpts . Term != "" {
termType = pk . TermOpts . Term
}
return termType
}
2022-10-22 23:45:31 +02:00
func makeRcFileStr ( pk * packet . RunPacketType ) string {
2022-10-25 21:31:07 +02:00
var rcBuf bytes . Buffer
2022-10-27 09:34:16 +02:00
rcBuf . WriteString ( BaseBashOpts + "\n" )
2022-10-25 21:31:07 +02:00
varDecls := VarDeclsFromState ( pk . State )
for _ , varDecl := range varDecls {
if varDecl . IsExport ( ) || varDecl . IsReadOnly ( ) {
continue
}
rcBuf . WriteString ( varDecl . DeclareStmt ( ) )
rcBuf . WriteString ( "\n" )
}
2022-10-22 23:45:31 +02:00
if pk . State != nil && pk . State . Funcs != "" {
2022-10-25 21:31:07 +02:00
rcBuf . WriteString ( pk . State . Funcs )
rcBuf . WriteString ( "\n" )
2022-10-17 08:46:59 +02:00
}
2022-10-22 23:45:31 +02:00
if pk . State != nil && pk . State . Aliases != "" {
2022-10-25 21:31:07 +02:00
rcBuf . WriteString ( pk . State . Aliases )
rcBuf . WriteString ( "\n" )
2022-10-22 23:45:31 +02:00
}
2022-10-25 21:31:07 +02:00
return rcBuf . String ( )
2022-10-17 08:46:59 +02:00
}
2022-10-25 06:26:39 +02:00
func makeExitTrap ( fdNum int ) string {
stateCmd := GetShellStateRedirectCommandStr ( fdNum )
2022-10-22 23:45:31 +02:00
fmtStr := `
2022-12-20 02:38:24 +01:00
_mshell_exittrap ( ) {
2022-10-22 23:45:31 +02:00
% s
}
2022-12-20 02:38:24 +01:00
trap _mshell_exittrap EXIT
2022-10-22 23:45:31 +02:00
`
2022-10-25 06:26:39 +02:00
return fmt . Sprintf ( fmtStr , stateCmd )
2022-10-22 23:45:31 +02:00
}
2022-12-21 06:58:24 +01:00
func ( s * ShExecType ) SendSignal ( sig syscall . Signal ) {
base . Logf ( "signal start\n" )
2022-12-06 07:26:13 +01:00
if s . Cmd == nil || s . Cmd . Process == nil || s . IsExited ( ) {
return
}
pgroup := false
if s . Cmd . SysProcAttr != nil && ( s . Cmd . SysProcAttr . Setsid || s . Cmd . SysProcAttr . Setpgid ) {
pgroup = true
}
pid := s . Cmd . Process . Pid
if pgroup {
2022-12-21 06:58:24 +01:00
base . Logf ( "send signal %s to %d (pgroup)\n" , sig , - pid )
syscall . Kill ( - pid , sig )
2022-12-06 07:26:13 +01:00
} else {
2022-12-21 06:58:24 +01:00
base . Logf ( "send signal %s to %d (normal)\n" , sig , pid )
syscall . Kill ( pid , sig )
2022-12-06 07:26:13 +01:00
}
}
2022-10-22 23:45:31 +02:00
func RunCommandSimple ( pk * packet . RunPacketType , sender * packet . PacketSender , fromServer bool ) ( rtnShExec * ShExecType , rtnErr error ) {
2022-10-17 08:46:59 +02:00
state := pk . State
if state == nil {
state = & packet . ShellState { }
}
2022-09-07 01:40:41 +02:00
cmd := MakeShExec ( pk . CK , nil )
2022-10-22 23:45:31 +02:00
defer func ( ) {
// on error, call cmd.Close()
if rtnErr != nil {
cmd . Close ( )
}
} ( )
2022-09-06 21:57:54 +02:00
if fromServer {
2022-09-07 01:40:41 +02:00
msgUpr := packet . MessageUPR { CK : pk . CK , Sender : sender }
upr := ShExecUPR { ShExec : cmd , UPR : msgUpr }
cmd . Multiplexer . UPR = upr
cmd . MsgSender = sender
2022-09-06 21:57:54 +02:00
}
2022-10-22 23:45:31 +02:00
var rtnStateWriter * os . File
rcFileStr := makeRcFileStr ( pk )
if pk . ReturnState {
pr , pw , err := os . Pipe ( )
if err != nil {
return nil , fmt . Errorf ( "cannot create returnstate pipe: %v" , err )
}
cmd . ReturnState = MakeReturnStateBuf ( )
cmd . ReturnState . Reader = pr
cmd . ReturnState . FdNum = 20
rtnStateWriter = pw
defer pw . Close ( )
2022-10-25 06:26:39 +02:00
trapCmdStr := makeExitTrap ( cmd . ReturnState . FdNum )
2022-10-22 23:45:31 +02:00
rcFileStr += trapCmdStr
}
2023-08-14 21:23:33 +02:00
shellVarMap := ShellVarMapFromState ( state )
if base . HasDebugFlag ( shellVarMap , base . DebugFlag_LogRcFile ) {
debugRcFileName := base . GetDebugRcFileName ( )
err := os . WriteFile ( debugRcFileName , [ ] byte ( rcFileStr ) , 0600 )
if err != nil {
base . Logf ( "error writing %s: %v\n" , debugRcFileName , err )
}
}
2022-10-22 23:45:31 +02:00
rcFileFdNum , err := AddRunData ( pk , rcFileStr , "rcfile" )
2022-10-17 08:46:59 +02:00
if err != nil {
return nil , err
}
2022-08-30 09:23:03 +02:00
if pk . UsePty {
2022-10-22 23:45:31 +02:00
cmd . Cmd = exec . Command ( "bash" , "--rcfile" , fmt . Sprintf ( "/dev/fd/%d" , rcFileFdNum ) , "-i" , "-c" , pk . Command )
2022-08-30 09:23:03 +02:00
} else {
2022-10-22 23:45:31 +02:00
cmd . Cmd = exec . Command ( "bash" , "--rcfile" , fmt . Sprintf ( "/dev/fd/%d" , rcFileFdNum ) , "-c" , pk . Command )
2022-08-30 09:23:03 +02:00
}
2022-10-17 08:46:59 +02:00
if ! pk . StateComplete {
2022-08-23 01:24:53 +02:00
cmd . Cmd . Env = os . Environ ( )
}
2022-10-25 21:31:07 +02:00
UpdateCmdEnv ( cmd . Cmd , EnvMapFromState ( state ) )
2022-10-17 08:46:59 +02:00
if state . Cwd != "" {
cmd . Cmd . Dir = base . ExpandHomeDir ( state . Cwd )
2022-06-25 08:42:00 +02:00
}
2022-10-17 08:46:59 +02:00
err = ValidateRemoteFds ( pk . Fds )
2022-06-25 08:42:00 +02:00
if err != nil {
return nil , err
2022-06-23 21:48:45 +02:00
}
2022-07-06 20:21:15 +02:00
var cmdPty * os . File
var cmdTty * os . File
if pk . UsePty {
cmdPty , cmdTty , err = pty . Open ( )
if err != nil {
return nil , fmt . Errorf ( "opening new pty: %w" , err )
}
pty . Setsize ( cmdPty , GetWinsize ( pk ) )
defer func ( ) {
cmdTty . Close ( )
} ( )
cmd . CmdPty = cmdPty
2022-11-02 05:19:42 +01:00
UpdateCmdEnv ( cmd . Cmd , MShellEnvVars ( getTermType ( pk ) ) )
2022-07-06 20:21:15 +02:00
}
if cmdTty != nil {
cmd . Cmd . Stdin = cmdTty
cmd . Cmd . Stdout = cmdTty
cmd . Cmd . Stderr = cmdTty
cmd . Cmd . SysProcAttr = & syscall . SysProcAttr {
Setsid : true ,
Setctty : true ,
}
2023-04-18 00:13:03 +02:00
cmd . Multiplexer . MakeRawFdWriter ( 0 , cmdPty , false , "simple" )
2022-07-06 21:16:37 +02:00
cmd . Multiplexer . MakeRawFdReader ( 1 , cmdPty , false , true )
2022-07-06 20:21:15 +02:00
nullFd , err := os . Open ( "/dev/null" )
if err != nil {
return nil , fmt . Errorf ( "cannot open /dev/null: %w" , err )
}
2022-07-06 21:16:37 +02:00
cmd . Multiplexer . MakeRawFdReader ( 2 , nullFd , true , false )
2022-07-06 20:21:15 +02:00
} else {
2023-04-18 00:13:03 +02:00
cmd . Cmd . Stdin , err = cmd . Multiplexer . MakeWriterPipe ( 0 , "simple" )
2022-07-06 20:21:15 +02:00
if err != nil {
return nil , err
}
cmd . Cmd . Stdout , err = cmd . Multiplexer . MakeReaderPipe ( 1 )
if err != nil {
return nil , err
}
cmd . Cmd . Stderr , err = cmd . Multiplexer . MakeReaderPipe ( 2 )
if err != nil {
return nil , err
}
2022-06-23 21:48:45 +02:00
}
2022-06-24 03:23:30 +02:00
extraFiles := make ( [ ] * os . File , 0 , MaxFdNum + 1 )
2022-06-29 06:57:30 +02:00
for _ , runData := range pk . RunData {
if runData . FdNum >= len ( extraFiles ) {
extraFiles = extraFiles [ : runData . FdNum + 1 ]
}
2023-04-18 00:13:03 +02:00
extraFiles [ runData . FdNum ] , err = cmd . Multiplexer . MakeStaticWriterPipe ( runData . FdNum , runData . Data , MaxRunDataSize , "simple-rundata" )
2022-06-29 06:57:30 +02:00
if err != nil {
return nil , err
}
}
2022-06-24 03:23:30 +02:00
for _ , rfd := range pk . Fds {
if rfd . FdNum >= len ( extraFiles ) {
extraFiles = extraFiles [ : rfd . FdNum + 1 ]
}
if rfd . Read {
// client file is open for reading, so we make a writer pipe
2023-04-18 00:13:03 +02:00
extraFiles [ rfd . FdNum ] , err = cmd . Multiplexer . MakeWriterPipe ( rfd . FdNum , "simple" )
2022-06-24 03:23:30 +02:00
if err != nil {
return nil , err
}
}
if rfd . Write {
// client file is open for writing, so we make a reader pipe
2022-06-24 19:24:02 +02:00
extraFiles [ rfd . FdNum ] , err = cmd . Multiplexer . MakeReaderPipe ( rfd . FdNum )
2022-06-24 03:23:30 +02:00
if err != nil {
return nil , err
}
}
}
2022-10-22 23:45:31 +02:00
if cmd . ReturnState != nil {
if cmd . ReturnState . FdNum >= len ( extraFiles ) {
extraFiles = extraFiles [ : cmd . ReturnState . FdNum + 1 ]
}
extraFiles [ cmd . ReturnState . FdNum ] = rtnStateWriter
}
2022-06-24 03:23:30 +02:00
if len ( extraFiles ) > FirstExtraFilesFdNum {
cmd . Cmd . ExtraFiles = extraFiles [ FirstExtraFilesFdNum : ]
}
2022-06-23 21:48:45 +02:00
err = cmd . Cmd . Start ( )
if err != nil {
return nil , err
}
return cmd , nil
}
2022-10-22 23:45:31 +02:00
// TODO limit size of read state buffer
func ( rs * ReturnStateBuf ) Run ( ) {
buf := make ( [ ] byte , 1024 )
defer func ( ) {
rs . Lock . Lock ( )
defer rs . Lock . Unlock ( )
rs . Reader . Close ( )
rs . Done = true
close ( rs . DoneCh )
} ( )
for {
n , readErr := rs . Reader . Read ( buf )
if readErr == io . EOF {
break
}
if readErr != nil {
rs . Lock . Lock ( )
rs . Err = readErr
rs . Lock . Unlock ( )
break
}
rs . Lock . Lock ( )
rs . Buf = append ( rs . Buf , buf [ 0 : n ] ... )
rs . Lock . Unlock ( )
}
}
2022-06-29 23:29:38 +02:00
// in detached run mode, we don't want mshell to die from signals
// since we want mshell to persist even if the mshell --server is terminated
func SetupSignalsForDetach ( ) {
sigCh := make ( chan os . Signal , 1 )
2022-12-06 07:26:13 +01:00
signal . Notify ( sigCh , syscall . SIGINT , syscall . SIGTERM , syscall . SIGHUP , syscall . SIGPIPE )
2022-06-29 23:29:38 +02:00
go func ( ) {
for range sigCh {
// do nothing
}
} ( )
}
2022-12-06 07:26:13 +01:00
// in detached run mode, we don't want mshell to die from signals
// since we want mshell to persist even if the mshell --server is terminated
func IgnoreSigPipe ( ) {
sigCh := make ( chan os . Signal , 1 )
signal . Notify ( sigCh , syscall . SIGPIPE )
go func ( ) {
for sig := range sigCh {
base . Logf ( "ignoring signal %v\n" , sig )
}
} ( )
}
2022-08-20 00:28:32 +02:00
func copyToCirFile ( dest * cirfile . File , src io . Reader ) error {
buf := make ( [ ] byte , 64 * 1024 )
for {
var appendErr error
nr , readErr := src . Read ( buf )
if nr > 0 {
appendErr = dest . AppendData ( context . Background ( ) , buf [ 0 : nr ] )
}
if readErr != nil && readErr != io . EOF {
return readErr
}
if appendErr != nil {
return appendErr
}
if readErr == io . EOF {
return nil
}
}
}
2022-07-06 08:14:14 +02:00
func ( cmd * ShExecType ) DetachedWait ( startPacket * packet . CmdStartPacketType ) {
// after Start(), any output/errors must go to DetachedOutput
2022-07-06 09:21:44 +02:00
// close stdin, redirect stdout/stderr to /dev/null, but wait for cmdstart packet to get sent
cmd . DetachedOutput . SendPacket ( startPacket )
err := os . Stdin . Close ( )
2022-07-06 08:14:14 +02:00
if err != nil {
2022-07-06 09:21:44 +02:00
cmd . DetachedOutput . SendCmdError ( cmd . CK , fmt . Errorf ( "cannot close stdin: %w" , err ) )
2022-07-06 08:14:14 +02:00
}
2022-07-06 09:21:44 +02:00
err = unix . Dup2 ( int ( cmd . RunnerOutFd . Fd ( ) ) , int ( os . Stdout . Fd ( ) ) )
if err != nil {
cmd . DetachedOutput . SendCmdError ( cmd . CK , fmt . Errorf ( "cannot dup2 stdin to runout: %w" , err ) )
}
err = unix . Dup2 ( int ( cmd . RunnerOutFd . Fd ( ) ) , int ( os . Stderr . Fd ( ) ) )
if err != nil {
cmd . DetachedOutput . SendCmdError ( cmd . CK , fmt . Errorf ( "cannot dup2 stdin to runout: %w" , err ) )
2022-07-06 08:14:14 +02:00
}
2022-08-20 00:28:32 +02:00
ptyOutFile , err := cirfile . CreateCirFile ( cmd . FileNames . PtyOutFile , cmd . MaxPtySize )
2022-07-06 08:14:14 +02:00
if err != nil {
cmd . DetachedOutput . SendCmdError ( cmd . CK , fmt . Errorf ( "cannot open ptyout file '%s': %w" , cmd . FileNames . PtyOutFile , err ) )
// don't return (command is already running)
}
2022-07-06 09:21:44 +02:00
ptyCopyDone := make ( chan bool )
2022-07-06 08:14:14 +02:00
go func ( ) {
// copy pty output to .ptyout file
2022-07-06 09:21:44 +02:00
defer close ( ptyCopyDone )
2022-08-20 00:28:32 +02:00
defer ptyOutFile . Close ( )
copyErr := copyToCirFile ( ptyOutFile , cmd . CmdPty )
2022-07-06 08:14:14 +02:00
if copyErr != nil {
cmd . DetachedOutput . SendCmdError ( cmd . CK , fmt . Errorf ( "copying pty output to ptyout file: %w" , copyErr ) )
}
} ( )
go func ( ) {
// copy .stdin fifo contents to pty input
copyFifoErr := MakeAndCopyStdinFifo ( cmd . CmdPty , cmd . FileNames . StdinFifo )
if copyFifoErr != nil {
cmd . DetachedOutput . SendCmdError ( cmd . CK , fmt . Errorf ( "reading from stdin fifo: %w" , copyFifoErr ) )
}
} ( )
donePacket := cmd . WaitForCommand ( )
cmd . DetachedOutput . SendPacket ( donePacket )
2022-07-06 09:21:44 +02:00
<- ptyCopyDone
cmd . Close ( )
2022-07-06 08:14:14 +02:00
return
}
func RunCommandDetached ( pk * packet . RunPacketType , sender * packet . PacketSender ) ( * ShExecType , * packet . CmdStartPacketType , error ) {
2022-06-27 21:03:47 +02:00
fileNames , err := base . GetCommandFileNames ( pk . CK )
2022-06-10 09:35:24 +02:00
if err != nil {
2022-07-06 08:14:14 +02:00
return nil , nil , err
2022-06-10 09:35:24 +02:00
}
2022-07-06 20:21:15 +02:00
runOutInfo , err := os . Stat ( fileNames . RunnerOutFile )
2022-06-17 21:27:29 +02:00
if err == nil { // non-nil error will be caught by regular OpenFile below
// must have size 0
2022-07-06 20:21:15 +02:00
if runOutInfo . Size ( ) != 0 {
return nil , nil , fmt . Errorf ( "cmdkey '%s' was already used (runout len=%d)" , pk . CK , runOutInfo . Size ( ) )
2022-06-17 21:27:29 +02:00
}
2022-06-10 09:35:24 +02:00
}
cmdPty , cmdTty , err := pty . Open ( )
if err != nil {
2022-07-06 08:14:14 +02:00
return nil , nil , fmt . Errorf ( "opening new pty: %w" , err )
2022-06-10 09:35:24 +02:00
}
2022-06-21 02:51:28 +02:00
pty . Setsize ( cmdPty , GetWinsize ( pk ) )
2022-06-10 09:35:24 +02:00
defer func ( ) {
cmdTty . Close ( )
} ( )
2022-07-02 02:37:37 +02:00
cmd := MakeShExec ( pk . CK , nil )
cmd . FileNames = fileNames
cmd . CmdPty = cmdPty
cmd . Detached = true
2022-09-04 08:38:35 +02:00
cmd . MaxPtySize = DefaultMaxPtySize
if pk . TermOpts != nil && pk . TermOpts . MaxPtySize > 0 {
cmd . MaxPtySize = base . BoundInt64 ( pk . TermOpts . MaxPtySize , MinMaxPtySize , MaxMaxPtySize )
2022-08-20 00:28:32 +02:00
}
2022-07-02 02:37:37 +02:00
cmd . RunnerOutFd , err = os . OpenFile ( fileNames . RunnerOutFile , os . O_TRUNC | os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0600 )
if err != nil {
2022-07-06 08:14:14 +02:00
return nil , nil , fmt . Errorf ( "cannot open runout file '%s': %w" , fileNames . RunnerOutFile , err )
2022-07-02 02:37:37 +02:00
}
2022-11-27 22:47:18 +01:00
cmd . DetachedOutput = packet . MakePacketSender ( cmd . RunnerOutFd , nil )
2022-06-29 23:29:38 +02:00
ecmd , err := MakeDetachedExecCmd ( pk , cmdTty )
if err != nil {
2022-07-06 08:14:14 +02:00
return nil , nil , err
2022-06-29 23:29:38 +02:00
}
2022-07-02 02:37:37 +02:00
cmd . Cmd = ecmd
2022-06-29 23:29:38 +02:00
SetupSignalsForDetach ( )
2022-06-10 09:35:24 +02:00
err = ecmd . Start ( )
if err != nil {
2022-07-06 08:14:14 +02:00
return nil , nil , fmt . Errorf ( "starting command: %w" , err )
2022-06-10 09:35:24 +02:00
}
2022-06-29 23:29:38 +02:00
for _ , fd := range ecmd . ExtraFiles {
if fd != cmdTty {
fd . Close ( )
}
}
2022-07-06 08:14:14 +02:00
startPacket := cmd . MakeCmdStartPacket ( pk . ReqId )
return cmd , startPacket , nil
2022-06-10 09:35:24 +02:00
}
2022-06-16 01:29:39 +02:00
func GetExitCode ( err error ) int {
if err == nil {
return 0
}
if exitErr , ok := err . ( * exec . ExitError ) ; ok {
return exitErr . ExitCode ( )
} else {
return - 1
}
}
2022-12-06 07:26:13 +01:00
func ( c * ShExecType ) ProcWait ( ) error {
exitErr := c . Cmd . Wait ( )
2022-12-21 06:58:24 +01:00
base . Logf ( "procwait: %v\n" , exitErr )
2022-12-06 07:26:13 +01:00
c . Lock . Lock ( )
c . Exited = true
c . Lock . Unlock ( )
return exitErr
}
func ( c * ShExecType ) IsExited ( ) bool {
c . Lock . Lock ( )
defer c . Lock . Unlock ( )
return c . Exited
}
2022-06-23 21:48:45 +02:00
func ( c * ShExecType ) WaitForCommand ( ) * packet . CmdDonePacketType {
2022-10-22 23:45:31 +02:00
donePacket := packet . MakeCmdDonePacket ( c . CK )
2022-12-06 07:26:13 +01:00
exitErr := c . ProcWait ( )
2022-10-22 23:45:31 +02:00
if c . ReturnState != nil {
<- c . ReturnState . DoneCh
state , _ := ParseShellStateOutput ( c . ReturnState . Buf ) // TODO what to do with error?
donePacket . FinalState = state
}
2022-06-11 06:37:21 +02:00
endTs := time . Now ( )
cmdDuration := endTs . Sub ( c . StartTs )
donePacket . Ts = endTs . UnixMilli ( )
2022-12-06 07:26:13 +01:00
donePacket . ExitCode = GetExitCode ( exitErr )
2022-06-11 06:37:21 +02:00
donePacket . DurationMs = int64 ( cmdDuration / time . Millisecond )
2022-06-23 21:48:45 +02:00
if c . FileNames != nil {
os . Remove ( c . FileNames . StdinFifo ) // best effort (no need to check error)
}
2022-06-11 06:37:21 +02:00
return donePacket
2022-06-10 09:35:24 +02:00
}
2022-07-02 02:37:37 +02:00
func MakeInitPacket ( ) * packet . InitPacketType {
initPacket := packet . MakeInitPacket ( )
initPacket . Version = base . MShellVersion
2023-04-13 06:45:45 +02:00
initPacket . BuildTime = base . BuildTime
2022-07-02 02:37:37 +02:00
initPacket . HomeDir = base . GetHomeDir ( )
initPacket . MShellHomeDir = base . GetMShellHomeDir ( )
if user , _ := user . Current ( ) ; user != nil {
initPacket . User = user . Username
}
2022-07-07 22:25:42 +02:00
initPacket . HostName , _ = os . Hostname ( )
2022-09-22 08:26:53 +02:00
initPacket . UName = fmt . Sprintf ( "%s|%s" , runtime . GOOS , runtime . GOARCH )
2022-07-02 02:37:37 +02:00
return initPacket
}
func MakeServerInitPacket ( ) ( * packet . InitPacketType , error ) {
var err error
initPacket := MakeInitPacket ( )
2022-10-17 08:46:59 +02:00
shellState , err := GetShellState ( )
2022-08-23 00:59:03 +02:00
if err != nil {
return nil , err
}
2022-10-17 08:46:59 +02:00
initPacket . State = shellState
2023-10-05 18:50:35 +02:00
initPacket . Shell = os . Getenv ( ShellVarName )
2022-07-02 02:37:37 +02:00
initPacket . RemoteId , err = base . GetRemoteId ( )
if err != nil {
return nil , err
}
return initPacket , nil
}
2022-08-23 00:59:03 +02:00
2022-08-23 02:27:55 +02:00
func ParseEnv0 ( env [ ] byte ) map [ string ] string {
2022-08-23 00:59:03 +02:00
envLines := bytes . Split ( env , [ ] byte { 0 } )
rtn := make ( map [ string ] string )
for _ , envLine := range envLines {
if len ( envLine ) == 0 {
continue
}
eqIdx := bytes . Index ( envLine , [ ] byte { '=' } )
if eqIdx == - 1 {
continue
}
varName := string ( envLine [ 0 : eqIdx ] )
varVal := string ( envLine [ eqIdx + 1 : ] )
rtn [ varName ] = varVal
}
return rtn
}
2022-08-23 02:27:55 +02:00
func MakeEnv0 ( envMap map [ string ] string ) [ ] byte {
var buf bytes . Buffer
for envName , envVal := range envMap {
buf . WriteString ( envName )
buf . WriteByte ( '=' )
buf . WriteString ( envVal )
buf . WriteByte ( 0 )
}
return buf . Bytes ( )
}
2022-08-23 00:59:03 +02:00
func getStderr ( err error ) string {
exitErr , ok := err . ( * exec . ExitError )
if ! ok {
return ""
}
if len ( exitErr . Stderr ) == 0 {
return ""
}
lines := strings . SplitN ( string ( exitErr . Stderr ) , "\n" , 2 )
if len ( lines [ 0 ] ) > 100 {
return lines [ 0 ] [ 0 : 100 ]
}
return lines [ 0 ]
}
2022-08-30 09:23:03 +02:00
func runSimpleCmdInPty ( ecmd * exec . Cmd ) ( [ ] byte , error ) {
ecmd . Env = os . Environ ( )
2022-11-02 05:19:42 +01:00
UpdateCmdEnv ( ecmd , MShellEnvVars ( DefaultTermType ) )
2022-08-30 09:23:03 +02:00
cmdPty , cmdTty , err := pty . Open ( )
if err != nil {
return nil , fmt . Errorf ( "opening new pty: %w" , err )
}
2022-09-04 08:26:57 +02:00
pty . Setsize ( cmdPty , & pty . Winsize { Rows : DefaultTermRows , Cols : DefaultTermCols } )
2022-08-30 09:23:03 +02:00
ecmd . Stdin = cmdTty
ecmd . Stdout = cmdTty
ecmd . Stderr = cmdTty
ecmd . SysProcAttr = & syscall . SysProcAttr { }
ecmd . SysProcAttr . Setsid = true
ecmd . SysProcAttr . Setctty = true
err = ecmd . Start ( )
if err != nil {
cmdTty . Close ( )
cmdPty . Close ( )
return nil , err
}
cmdTty . Close ( )
defer cmdPty . Close ( )
ioDone := make ( chan bool )
var outputBuf bytes . Buffer
go func ( ) {
// ignore error (/dev/ptmx has read error when process is done)
io . Copy ( & outputBuf , cmdPty )
close ( ioDone )
} ( )
exitErr := ecmd . Wait ( )
if exitErr != nil {
return nil , exitErr
}
<- ioDone
return outputBuf . Bytes ( ) , nil
}
2022-10-25 06:26:39 +02:00
func GetShellStateRedirectCommandStr ( outputFdNum int ) string {
2023-04-12 08:52:58 +02:00
return fmt . Sprintf ( "cat <(%s) > /dev/fd/%d" , GetShellStateCmd ( ) , outputFdNum )
2022-08-23 00:59:03 +02:00
}
2022-10-22 23:45:31 +02:00
func GetShellState ( ) ( * packet . ShellState , error ) {
ctx , _ := context . WithTimeout ( context . Background ( ) , GetStateTimeout )
2023-04-12 08:52:58 +02:00
cmdStr := BaseBashOpts + "; " + GetShellStateCmd ( )
2022-10-27 09:34:16 +02:00
ecmd := exec . CommandContext ( ctx , "bash" , "-l" , "-i" , "-c" , cmdStr )
2022-10-22 23:45:31 +02:00
outputBytes , err := runSimpleCmdInPty ( ecmd )
if err != nil {
return nil , err
}
return ParseShellStateOutput ( outputBytes )
}
2022-11-02 05:19:42 +01:00
func MShellEnvVars ( termType string ) map [ string ] string {
rtn := make ( map [ string ] string )
if termType != "" {
rtn [ "TERM" ] = termType
}
rtn [ "MSHELL" ] , _ = os . Executable ( )
rtn [ "MSHELL_VERSION" ] = base . MShellVersion
return rtn
}