From 26479f59c0d6e0dd0cf03d77d58fc71f4da94b1b Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 27 Jun 2022 18:42:56 -0700 Subject: [PATCH] pass uname back when mshell isn't found, parse, and give install command --- main-mshell.go | 4 +++ pkg/mpio/mpio.go | 4 +-- pkg/packet/packet.go | 1 + pkg/packet/parser.go | 11 ++++--- pkg/shexec/shexec.go | 74 ++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 85 insertions(+), 9 deletions(-) diff --git a/main-mshell.go b/main-mshell.go index 6137ab721..d83aee3cc 100644 --- a/main-mshell.go +++ b/main-mshell.go @@ -320,6 +320,10 @@ func parseClientOpts() (*shexec.ClientOpts, error) { opts.Cwd = iter.Next() continue } + if argStr == "--detach" { + opts.Detach = true + continue + } if argStr == "--debug" { opts.Debug = true continue diff --git a/pkg/mpio/mpio.go b/pkg/mpio/mpio.go index 227625a18..6fce20616 100644 --- a/pkg/mpio/mpio.go +++ b/pkg/mpio/mpio.go @@ -16,8 +16,8 @@ import ( "github.com/scripthaus-dev/mshell/pkg/packet" ) -const ReadBufSize = 32 * 1024 -const WriteBufSize = 32 * 1024 +const ReadBufSize = 128 * 1024 +const WriteBufSize = 128 * 1024 const MaxSingleWriteSize = 4 * 1024 type Multiplexer struct { diff --git a/pkg/packet/packet.go b/pkg/packet/packet.go index 3638a04f0..01191ed39 100644 --- a/pkg/packet/packet.go +++ b/pkg/packet/packet.go @@ -341,6 +341,7 @@ type InitPacketType struct { Env []string `json:"env,omitempty"` User string `json:"user,omitempty"` NotFound bool `json:"notfound,omitempty"` + UName string `json:"uname,omitempty"` } func (*InitPacketType) GetType() string { diff --git a/pkg/packet/parser.go b/pkg/packet/parser.go index 907ae83fe..40e59b24b 100644 --- a/pkg/packet/parser.go +++ b/pkg/packet/parser.go @@ -76,10 +76,13 @@ func MakePacketParser(input io.Reader) *PacketParser { parser.MainCh <- MakeRawPacket(line[:len(line)-1]) continue } - packetLen, err := strconv.Atoi(line[2:bracePos]) - if err != nil || packetLen != len(line)-bracePos-1 { - parser.MainCh <- MakeRawPacket(line[:len(line)-1]) - continue + packetLen := -1 + if line[2:bracePos] != "N" { + packetLen, err = strconv.Atoi(line[2:bracePos]) + if err != nil || packetLen != len(line)-bracePos-1 { + parser.MainCh <- MakeRawPacket(line[:len(line)-1]) + continue + } } pk, err := ParseJsonPacket([]byte(line[bracePos:])) if err != nil { diff --git a/pkg/shexec/shexec.go b/pkg/shexec/shexec.go index bd6d79b83..78316a2d5 100644 --- a/pkg/shexec/shexec.go +++ b/pkg/shexec/shexec.go @@ -32,10 +32,10 @@ const FirstExtraFilesFdNum = 3 const ClientCommand = ` PATH=$PATH:~/.mshell; -which mshell > /dev/null; +which mshell2 > /dev/null; if [[ "$?" -ne 0 ]] then - printf "\n##34{\"type\": \"init\", \"notfound\": true}\n" + printf "\n##N{\"type\": \"init\", \"notfound\": true, \"uname\": \"%s | %s\"}\n" "$(uname -s)" "$(uname -m)" else mshell --single fi @@ -175,6 +175,19 @@ func ValidateRunPacket(pk *packet.RunPacketType) error { if err != nil { return err } + for _, rfd := range pk.Fds { + if rfd.Write { + return fmt.Errorf("cannot detach command with writable remote files fd=%d", rfd.FdNum) + } + if rfd.Read { + if rfd.Content == "" { + return fmt.Errorf("cannot detach command with readable remote files fd=%d", rfd.FdNum) + } + if len(rfd.Content) > mpio.ReadBufSize { + return fmt.Errorf("cannot detach command, constant readable input too large fd=%d, len=%d, max=%d", rfd.FdNum, len(rfd.Content), mpio.ReadBufSize) + } + } + } } if pk.Cwd != "" { realCwd := base.ExpandHomeDir(pk.Cwd) @@ -227,6 +240,7 @@ type ClientOpts struct { SudoWithPass bool SudoPw string CommandStdinFdNum int + Detach bool } func (opts *ClientOpts) MakeExecCmd() *exec.Cmd { @@ -251,8 +265,30 @@ func (opts *ClientOpts) MakeExecCmd() *exec.Cmd { } } +func (opts *ClientOpts) MakeInstallCommandString(goos string, goarch string) string { + var moreSSHOpts []string + if opts.SSHIdentity != "" { + identityOpt := fmt.Sprintf("-i %s", shellescape.Quote(opts.SSHIdentity)) + moreSSHOpts = append(moreSSHOpts, identityOpt) + } + if opts.SSHUser != "" { + userOpt := fmt.Sprintf("-l %s", shellescape.Quote(opts.SSHUser)) + moreSSHOpts = append(moreSSHOpts, userOpt) + } + if opts.SSHOptsStr != "" { + optsOpt := fmt.Sprintf("--ssh-opts %s", shellescape.Quote(opts.SSHOptsStr)) + moreSSHOpts = append(moreSSHOpts, optsOpt) + } + if opts.SSHHost != "" { + sshArg := fmt.Sprintf("--ssh %s", shellescape.Quote(opts.SSHHost)) + moreSSHOpts = append(moreSSHOpts, sshArg) + } + return fmt.Sprintf("mshell --install %s %s_%s", strings.Join(moreSSHOpts, " "), goos, goarch) +} + func (opts *ClientOpts) MakeRunPacket() (*packet.RunPacketType, error) { runPacket := packet.MakeRunPacket() + runPacket.Detached = opts.Detach runPacket.Cwd = opts.Cwd runPacket.Fds = opts.Fds if !opts.Sudo { @@ -417,7 +453,16 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er if pk.GetType() == packet.InitPacketStr { initPk := pk.(*packet.InitPacketType) if initPk.NotFound { - return nil, fmt.Errorf("mshell command not found on remote server, can install with 'mshell --install'") + fmt.Printf("UNAME> %s\n", initPk.UName) + if initPk.UName == "" { + return nil, fmt.Errorf("mshell command not found on remote server, no uname detected") + } + goos, goarch, err := UNameStringToGoArch(initPk.UName) + if err != nil { + return nil, fmt.Errorf("mshell command not found on remote server, architecture cannot be detected (might be incompatible with mshell): %w", err) + } + installCmd := opts.MakeInstallCommandString(goos, goarch) + return nil, fmt.Errorf("mshell command not found on remote server, can install with '%s'", installCmd) } if initPk.Version != "0.1.0" { return nil, fmt.Errorf("invalid remote mshell version 'v%s', must be v0.1.0", initPk.Version) @@ -444,6 +489,29 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er return donePacket, nil } +func UNameStringToGoArch(uname string) (string, string, error) { + 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" + } else if archVal == "aarch64" || archVal == "amd64" { + goarch = "arm64" + } + if goarch == "" { + return "", "", fmt.Errorf("invalid uname machine type '%s', mshell only supports aarch64 (amd64) and x86_64 (amd64)", archVal) + } + return goos, goarch, nil +} + func (cmd *ShExecType) RunRemoteIOAndWait(packetParser *packet.PacketParser, sender *packet.PacketSender) { defer cmd.Close() cmd.Multiplexer.RunIOAndWait(packetParser, sender, true, false, false)