2022-06-10 09:35:24 +02:00
|
|
|
// Copyright 2022 Dashborg Inc
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-06-28 00:59:14 +02:00
|
|
|
"bytes"
|
2022-06-10 09:35:24 +02:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2022-10-01 02:22:57 +02:00
|
|
|
"strconv"
|
2022-06-23 21:48:45 +02:00
|
|
|
"strings"
|
2022-12-06 07:26:13 +01:00
|
|
|
"time"
|
2022-06-10 09:35:24 +02:00
|
|
|
|
2022-06-23 19:16:54 +02:00
|
|
|
"github.com/scripthaus-dev/mshell/pkg/base"
|
|
|
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
2022-06-29 00:04:08 +02:00
|
|
|
"github.com/scripthaus-dev/mshell/pkg/server"
|
2022-06-23 19:16:54 +02:00
|
|
|
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
2022-06-25 08:42:00 +02:00
|
|
|
"golang.org/x/sys/unix"
|
2022-06-10 09:35:24 +02:00
|
|
|
)
|
|
|
|
|
2022-07-06 02:45:46 +02:00
|
|
|
// func doMainRun(pk *packet.RunPacketType, sender *packet.PacketSender) {
|
|
|
|
// err := shexec.ValidateRunPacket(pk)
|
|
|
|
// if err != nil {
|
|
|
|
// sender.SendCKErrorPacket(pk.CK, fmt.Sprintf("invalid run packet: %v", err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// fileNames, err := base.GetCommandFileNames(pk.CK)
|
|
|
|
// if err != nil {
|
|
|
|
// sender.SendCKErrorPacket(pk.CK, fmt.Sprintf("cannot get command file names: %v", err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// cmd, err := shexec.MakeRunnerExec(pk.CK)
|
|
|
|
// if err != nil {
|
|
|
|
// sender.SendCKErrorPacket(pk.CK, fmt.Sprintf("cannot make mshell command: %v", err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// cmdStdin, err := cmd.StdinPipe()
|
|
|
|
// if err != nil {
|
|
|
|
// sender.SendCKErrorPacket(pk.CK, fmt.Sprintf("cannot pipe stdin to command: %v", err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// // touch ptyout file (should exist for tailer to work correctly)
|
|
|
|
// ptyOutFd, err := os.OpenFile(fileNames.PtyOutFile, os.O_CREATE|os.O_TRUNC|os.O_APPEND|os.O_WRONLY, 0600)
|
|
|
|
// if err != nil {
|
|
|
|
// sender.SendCKErrorPacket(pk.CK, fmt.Sprintf("cannot open pty out file '%s': %v", fileNames.PtyOutFile, err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// ptyOutFd.Close() // just opened to create the file, can close right after
|
|
|
|
// runnerOutFd, err := os.OpenFile(fileNames.RunnerOutFile, os.O_CREATE|os.O_TRUNC|os.O_APPEND|os.O_WRONLY, 0600)
|
|
|
|
// if err != nil {
|
|
|
|
// sender.SendCKErrorPacket(pk.CK, fmt.Sprintf("cannot open runner out file '%s': %v", fileNames.RunnerOutFile, err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// defer runnerOutFd.Close()
|
|
|
|
// cmd.Stdout = runnerOutFd
|
|
|
|
// cmd.Stderr = runnerOutFd
|
|
|
|
// err = cmd.Start()
|
|
|
|
// if err != nil {
|
|
|
|
// sender.SendCKErrorPacket(pk.CK, fmt.Sprintf("error starting command: %v", err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// go func() {
|
|
|
|
// err = packet.SendPacket(cmdStdin, pk)
|
|
|
|
// if err != nil {
|
|
|
|
// sender.SendCKErrorPacket(pk.CK, fmt.Sprintf("error sending forked runner command: %v", err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// cmdStdin.Close()
|
2022-06-11 06:37:21 +02:00
|
|
|
|
2022-07-06 02:45:46 +02:00
|
|
|
// // clean up zombies
|
|
|
|
// cmd.Wait()
|
|
|
|
// }()
|
|
|
|
// }
|
2022-06-11 06:37:21 +02:00
|
|
|
|
2022-07-08 06:38:05 +02:00
|
|
|
// func doGetCmd(tailer *cmdtail.Tailer, pk *packet.GetCmdPacketType, sender *packet.PacketSender) error {
|
|
|
|
// err := tailer.AddWatch(pk)
|
|
|
|
// if err != nil {
|
|
|
|
// return err
|
|
|
|
// }
|
|
|
|
// return nil
|
|
|
|
// }
|
2022-06-15 07:16:58 +02:00
|
|
|
|
2022-07-06 02:45:46 +02:00
|
|
|
// func doMain() {
|
|
|
|
// homeDir := base.GetHomeDir()
|
|
|
|
// err := os.Chdir(homeDir)
|
|
|
|
// if err != nil {
|
|
|
|
// packet.SendErrorPacket(os.Stdout, fmt.Sprintf("cannot change directory to $HOME '%s': %v", homeDir, err))
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// _, err = base.GetMShellPath()
|
|
|
|
// if err != nil {
|
|
|
|
// packet.SendErrorPacket(os.Stdout, err.Error())
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// packetParser := packet.MakePacketParser(os.Stdin)
|
|
|
|
// sender := packet.MakePacketSender(os.Stdout)
|
|
|
|
// tailer, err := cmdtail.MakeTailer(sender)
|
|
|
|
// if err != nil {
|
|
|
|
// packet.SendErrorPacket(os.Stdout, err.Error())
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// go tailer.Run()
|
|
|
|
// initPacket := shexec.MakeInitPacket()
|
|
|
|
// sender.SendPacket(initPacket)
|
|
|
|
// for pk := range packetParser.MainCh {
|
|
|
|
// if pk.GetType() == packet.RunPacketStr {
|
|
|
|
// doMainRun(pk.(*packet.RunPacketType), sender)
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
// if pk.GetType() == packet.GetCmdPacketStr {
|
|
|
|
// err = doGetCmd(tailer, pk.(*packet.GetCmdPacketType), sender)
|
|
|
|
// if err != nil {
|
|
|
|
// errPk := packet.MakeErrorPacket(err.Error())
|
|
|
|
// sender.SendPacket(errPk)
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
// if pk.GetType() == packet.CdPacketStr {
|
|
|
|
// cdPacket := pk.(*packet.CdPacketType)
|
|
|
|
// err := os.Chdir(cdPacket.Dir)
|
|
|
|
// resp := packet.MakeResponsePacket(cdPacket.ReqId)
|
|
|
|
// if err != nil {
|
|
|
|
// resp.Error = err.Error()
|
|
|
|
// } else {
|
|
|
|
// resp.Success = true
|
|
|
|
// }
|
|
|
|
// sender.SendPacket(resp)
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
// if pk.GetType() == packet.ErrorPacketStr {
|
|
|
|
// errPk := pk.(*packet.ErrorPacketType)
|
|
|
|
// errPk.Error = "invalid packet sent to mshell: " + errPk.Error
|
|
|
|
// sender.SendPacket(errPk)
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
// sender.SendErrorPacket(fmt.Sprintf("invalid packet '%s' sent to mshell", pk.GetType()))
|
|
|
|
// }
|
|
|
|
// }
|
2022-06-11 06:37:21 +02:00
|
|
|
|
2022-06-29 06:57:30 +02:00
|
|
|
func readFullRunPacket(packetParser *packet.PacketParser) (*packet.RunPacketType, error) {
|
|
|
|
rpb := packet.MakeRunPacketBuilder()
|
|
|
|
for pk := range packetParser.MainCh {
|
|
|
|
ok, runPacket := rpb.ProcessPacket(pk)
|
|
|
|
if runPacket != nil {
|
|
|
|
return runPacket, nil
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("invalid packet '%s' sent to mshell", pk.GetType())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("no run packet received")
|
|
|
|
}
|
|
|
|
|
2022-09-06 21:57:54 +02:00
|
|
|
func handleSingle(fromServer bool) {
|
2022-06-27 21:14:07 +02:00
|
|
|
packetParser := packet.MakePacketParser(os.Stdin)
|
2022-11-27 22:47:18 +01:00
|
|
|
sender := packet.MakePacketSender(os.Stdout, nil)
|
2022-06-23 21:48:45 +02:00
|
|
|
defer func() {
|
2022-06-29 00:04:08 +02:00
|
|
|
sender.Close()
|
|
|
|
sender.WaitForDone()
|
2022-06-23 21:48:45 +02:00
|
|
|
}()
|
2022-07-02 02:37:37 +02:00
|
|
|
initPacket := shexec.MakeInitPacket()
|
|
|
|
sender.SendPacket(initPacket)
|
2022-06-28 07:39:16 +02:00
|
|
|
if len(os.Args) >= 3 && os.Args[2] == "--version" {
|
|
|
|
return
|
|
|
|
}
|
2022-06-29 06:57:30 +02:00
|
|
|
runPacket, err := readFullRunPacket(packetParser)
|
|
|
|
if err != nil {
|
2022-07-06 02:45:46 +02:00
|
|
|
sender.SendErrorResponse(runPacket.ReqId, err)
|
2022-06-25 09:30:41 +02:00
|
|
|
return
|
|
|
|
}
|
2022-06-29 23:29:38 +02:00
|
|
|
err = shexec.ValidateRunPacket(runPacket)
|
2022-06-23 21:48:45 +02:00
|
|
|
if err != nil {
|
2022-07-06 02:45:46 +02:00
|
|
|
sender.SendErrorResponse(runPacket.ReqId, err)
|
2022-06-29 23:29:38 +02:00
|
|
|
return
|
|
|
|
}
|
2022-09-06 21:57:54 +02:00
|
|
|
if fromServer {
|
|
|
|
err = runPacket.CK.Validate("run packet")
|
|
|
|
if err != nil {
|
|
|
|
sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("run packets from server must have a CK: %v", err))
|
|
|
|
}
|
|
|
|
}
|
2022-06-29 23:29:38 +02:00
|
|
|
if runPacket.Detached {
|
2022-07-06 08:14:14 +02:00
|
|
|
cmd, startPk, err := shexec.RunCommandDetached(runPacket, sender)
|
2022-06-29 23:29:38 +02:00
|
|
|
if err != nil {
|
2022-07-06 02:45:46 +02:00
|
|
|
sender.SendErrorResponse(runPacket.ReqId, err)
|
2022-06-29 23:29:38 +02:00
|
|
|
return
|
|
|
|
}
|
2022-07-06 08:14:14 +02:00
|
|
|
sender.SendPacket(startPk)
|
|
|
|
sender.Close()
|
|
|
|
sender.WaitForDone()
|
|
|
|
cmd.DetachedWait(startPk)
|
|
|
|
return
|
2022-06-29 23:29:38 +02:00
|
|
|
} else {
|
2022-12-06 07:26:13 +01:00
|
|
|
shexec.IgnoreSigPipe()
|
|
|
|
ticker := time.NewTicker(1 * time.Minute)
|
|
|
|
go func() {
|
|
|
|
for range ticker.C {
|
|
|
|
// this will let the command detect when the server has gone away
|
|
|
|
// that will then trigger cmd.SendHup() to send SIGHUP to the exec'ed process
|
|
|
|
sender.SendPacket(packet.MakePingPacket())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
defer ticker.Stop()
|
2022-09-06 21:57:54 +02:00
|
|
|
cmd, err := shexec.RunCommandSimple(runPacket, sender, true)
|
2022-06-29 23:29:38 +02:00
|
|
|
if err != nil {
|
2022-07-06 02:45:46 +02:00
|
|
|
sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("error running command: %w", err))
|
2022-06-29 23:29:38 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
defer cmd.Close()
|
2022-07-06 08:14:14 +02:00
|
|
|
startPacket := cmd.MakeCmdStartPacket(runPacket.ReqId)
|
2022-06-29 23:29:38 +02:00
|
|
|
sender.SendPacket(startPacket)
|
2022-12-06 07:26:13 +01:00
|
|
|
go func() {
|
|
|
|
exitErr := sender.WaitForDone()
|
|
|
|
if exitErr != nil {
|
|
|
|
base.Logf("I/O error talking to server, sending SIGHUP to children\n")
|
|
|
|
cmd.SendHup()
|
|
|
|
}
|
|
|
|
}()
|
2022-06-29 23:29:38 +02:00
|
|
|
cmd.RunRemoteIOAndWait(packetParser, sender)
|
2022-06-23 21:48:45 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-25 08:42:00 +02:00
|
|
|
func detectOpenFds() ([]packet.RemoteFd, error) {
|
|
|
|
var fds []packet.RemoteFd
|
|
|
|
for fdNum := 3; fdNum <= 64; fdNum++ {
|
|
|
|
flags, err := unix.FcntlInt(uintptr(fdNum), unix.F_GETFL, 0)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
flags = flags & 3
|
|
|
|
rfd := packet.RemoteFd{FdNum: fdNum}
|
|
|
|
if flags&2 == 2 {
|
|
|
|
return nil, fmt.Errorf("invalid fd=%d, mshell does not support fds open for reading and writing", fdNum)
|
|
|
|
}
|
|
|
|
if flags&1 == 1 {
|
|
|
|
rfd.Write = true
|
|
|
|
} else {
|
|
|
|
rfd.Read = true
|
|
|
|
}
|
|
|
|
fds = append(fds, rfd)
|
|
|
|
}
|
|
|
|
return fds, nil
|
2022-06-24 09:02:18 +02:00
|
|
|
}
|
|
|
|
|
2022-06-28 07:39:16 +02:00
|
|
|
func parseInstallOpts() (*shexec.InstallOpts, error) {
|
|
|
|
opts := &shexec.InstallOpts{}
|
|
|
|
iter := base.MakeOptsIter(os.Args[2:]) // first arg is --install
|
2022-06-24 09:02:18 +02:00
|
|
|
for iter.HasNext() {
|
|
|
|
argStr := iter.Next()
|
2022-06-28 07:39:16 +02:00
|
|
|
found, err := tryParseSSHOpt(iter, &opts.SSHOpts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-06-24 09:02:18 +02:00
|
|
|
}
|
2022-06-28 07:39:16 +02:00
|
|
|
if found {
|
2022-06-27 23:57:01 +02:00
|
|
|
continue
|
|
|
|
}
|
2022-06-28 08:14:53 +02:00
|
|
|
if argStr == "--detect" {
|
|
|
|
opts.Detect = true
|
|
|
|
continue
|
|
|
|
}
|
2022-06-28 07:39:16 +02:00
|
|
|
if base.IsOption(argStr) {
|
|
|
|
return nil, fmt.Errorf("invalid option '%s' passed to mshell --install", argStr)
|
2022-06-27 23:57:01 +02:00
|
|
|
}
|
2022-06-28 07:39:16 +02:00
|
|
|
opts.ArchStr = argStr
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return opts, nil
|
|
|
|
}
|
|
|
|
|
2022-06-29 00:04:08 +02:00
|
|
|
func tryParseSSHOpt(iter *base.OptsIter, sshOpts *shexec.SSHOpts) (bool, error) {
|
2022-06-28 07:39:16 +02:00
|
|
|
argStr := iter.Current()
|
|
|
|
if argStr == "--ssh" {
|
|
|
|
if !iter.IsNextPlain() {
|
|
|
|
return false, fmt.Errorf("'--ssh [user@host]' missing host")
|
|
|
|
}
|
|
|
|
sshOpts.SSHHost = iter.Next()
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
if argStr == "--ssh-opts" {
|
|
|
|
if !iter.HasNext() {
|
|
|
|
return false, fmt.Errorf("'--ssh-opts [options]' missing options")
|
|
|
|
}
|
|
|
|
sshOpts.SSHOptsStr = iter.Next()
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
if argStr == "-i" {
|
|
|
|
if !iter.IsNextPlain() {
|
|
|
|
return false, fmt.Errorf("-i [identity-file]' missing file")
|
|
|
|
}
|
|
|
|
sshOpts.SSHIdentity = iter.Next()
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
if argStr == "-l" {
|
|
|
|
if !iter.IsNextPlain() {
|
|
|
|
return false, fmt.Errorf("-l [user]' missing user")
|
|
|
|
}
|
|
|
|
sshOpts.SSHUser = iter.Next()
|
|
|
|
return true, nil
|
|
|
|
}
|
2022-10-01 02:22:57 +02:00
|
|
|
if argStr == "-p" {
|
|
|
|
if !iter.IsNextPlain() {
|
|
|
|
return false, fmt.Errorf("-p [port]' missing port")
|
|
|
|
}
|
|
|
|
nextArgStr := iter.Next()
|
|
|
|
portVal, err := strconv.Atoi(nextArgStr)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("-p [port]' invalid port: %v", err)
|
|
|
|
}
|
|
|
|
if portVal <= 0 {
|
|
|
|
return false, fmt.Errorf("-p [port]' invalid port: %d", portVal)
|
|
|
|
}
|
|
|
|
sshOpts.SSHPort = portVal
|
|
|
|
return true, nil
|
|
|
|
}
|
2022-06-28 07:39:16 +02:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseClientOpts() (*shexec.ClientOpts, error) {
|
|
|
|
opts := &shexec.ClientOpts{}
|
|
|
|
iter := base.MakeOptsIter(os.Args[1:])
|
|
|
|
for iter.HasNext() {
|
|
|
|
argStr := iter.Next()
|
|
|
|
found, err := tryParseSSHOpt(iter, &opts.SSHOpts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if found {
|
2022-06-27 23:57:01 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if argStr == "--cwd" {
|
|
|
|
if !iter.IsNextPlain() {
|
2022-06-25 08:42:00 +02:00
|
|
|
return nil, fmt.Errorf("'--cwd [dir]' missing directory")
|
|
|
|
}
|
|
|
|
opts.Cwd = iter.Next()
|
|
|
|
continue
|
|
|
|
}
|
2022-06-28 03:42:56 +02:00
|
|
|
if argStr == "--detach" {
|
|
|
|
opts.Detach = true
|
|
|
|
continue
|
|
|
|
}
|
2022-07-06 20:21:15 +02:00
|
|
|
if argStr == "--pty" {
|
|
|
|
opts.UsePty = true
|
|
|
|
continue
|
|
|
|
}
|
2022-06-25 09:22:03 +02:00
|
|
|
if argStr == "--debug" {
|
|
|
|
opts.Debug = true
|
|
|
|
continue
|
|
|
|
}
|
2022-06-26 10:41:58 +02:00
|
|
|
if argStr == "--sudo" {
|
|
|
|
opts.Sudo = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if argStr == "--sudo-with-password" {
|
|
|
|
if !iter.HasNext() {
|
|
|
|
return nil, fmt.Errorf("'--sudo-with-password [pw]', missing password")
|
|
|
|
}
|
|
|
|
opts.Sudo = true
|
2022-06-29 02:20:01 +02:00
|
|
|
opts.SudoWithPass = true
|
2022-06-26 10:41:58 +02:00
|
|
|
opts.SudoPw = iter.Next()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if argStr == "--sudo-with-passfile" {
|
2022-06-27 23:57:01 +02:00
|
|
|
if !iter.IsNextPlain() {
|
2022-06-26 10:41:58 +02:00
|
|
|
return nil, fmt.Errorf("'--sudo-with-passfile [file]', missing file")
|
|
|
|
}
|
|
|
|
opts.Sudo = true
|
2022-06-29 02:20:01 +02:00
|
|
|
opts.SudoWithPass = true
|
2022-06-26 10:41:58 +02:00
|
|
|
fileName := iter.Next()
|
|
|
|
contents, err := os.ReadFile(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot read --sudo-with-passfile file '%s': %w", fileName, err)
|
|
|
|
}
|
2022-06-28 00:59:14 +02:00
|
|
|
if newlineIdx := bytes.Index(contents, []byte{'\n'}); newlineIdx != -1 {
|
|
|
|
contents = contents[0:newlineIdx]
|
|
|
|
}
|
|
|
|
opts.SudoPw = string(contents) + "\n"
|
2022-06-26 10:41:58 +02:00
|
|
|
continue
|
|
|
|
}
|
2022-06-27 23:57:01 +02:00
|
|
|
if argStr == "--" {
|
|
|
|
if !iter.HasNext() {
|
|
|
|
return nil, fmt.Errorf("'--' should be followed by command")
|
2022-06-25 09:30:41 +02:00
|
|
|
}
|
2022-06-27 23:57:01 +02:00
|
|
|
opts.Command = strings.Join(iter.Rest(), " ")
|
|
|
|
break
|
2022-06-24 09:02:18 +02:00
|
|
|
}
|
2022-06-28 08:14:53 +02:00
|
|
|
return nil, fmt.Errorf("invalid option '%s' passed to mshell", argStr)
|
2022-06-24 09:02:18 +02:00
|
|
|
}
|
|
|
|
return opts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleClient() (int, error) {
|
|
|
|
opts, err := parseClientOpts()
|
|
|
|
if err != nil {
|
|
|
|
return 1, fmt.Errorf("parsing opts: %w", err)
|
|
|
|
}
|
2022-06-26 10:41:58 +02:00
|
|
|
if opts.Debug {
|
|
|
|
packet.GlobalDebug = true
|
|
|
|
}
|
2022-06-27 23:57:01 +02:00
|
|
|
if opts.Command == "" {
|
|
|
|
return 1, fmt.Errorf("no [command] specified. [command] follows '--' option (see usage)")
|
|
|
|
}
|
2022-06-25 08:42:00 +02:00
|
|
|
fds, err := detectOpenFds()
|
|
|
|
if err != nil {
|
|
|
|
return 1, err
|
|
|
|
}
|
|
|
|
opts.Fds = fds
|
2022-06-29 00:04:08 +02:00
|
|
|
err = shexec.ValidateRemoteFds(opts.Fds)
|
|
|
|
if err != nil {
|
|
|
|
return 1, err
|
|
|
|
}
|
|
|
|
runPacket, err := opts.MakeRunPacket() // modifies opts
|
|
|
|
if err != nil {
|
|
|
|
return 1, err
|
|
|
|
}
|
2022-08-20 00:28:32 +02:00
|
|
|
if runPacket.Detached {
|
|
|
|
return 1, fmt.Errorf("cannot run detached command from command line client")
|
|
|
|
}
|
2022-06-29 04:01:33 +02:00
|
|
|
donePacket, err := shexec.RunClientSSHCommandAndWait(runPacket, shexec.StdContext{}, opts.SSHOpts, nil, opts.Debug)
|
2022-06-24 09:02:18 +02:00
|
|
|
if err != nil {
|
2022-06-24 22:25:09 +02:00
|
|
|
return 1, err
|
2022-06-24 09:02:18 +02:00
|
|
|
}
|
2022-06-24 22:25:09 +02:00
|
|
|
return donePacket.ExitCode, nil
|
2022-06-23 21:48:45 +02:00
|
|
|
}
|
|
|
|
|
2022-06-28 07:39:16 +02:00
|
|
|
func handleInstall() (int, error) {
|
|
|
|
opts, err := parseInstallOpts()
|
|
|
|
if err != nil {
|
|
|
|
return 1, fmt.Errorf("parsing opts: %w", err)
|
|
|
|
}
|
|
|
|
if opts.SSHOpts.SSHHost == "" {
|
|
|
|
return 1, fmt.Errorf("cannot install without '--ssh user@host' option")
|
|
|
|
}
|
2022-06-28 08:14:53 +02:00
|
|
|
if opts.Detect && opts.ArchStr != "" {
|
|
|
|
return 1, fmt.Errorf("cannot supply both --detect and arch '%s'", opts.ArchStr)
|
2022-06-28 07:39:16 +02:00
|
|
|
}
|
2022-06-28 08:14:53 +02:00
|
|
|
if opts.ArchStr == "" && !opts.Detect {
|
|
|
|
return 1, fmt.Errorf("must supply an arch string or '--detect' to auto detect")
|
2022-06-28 07:39:16 +02:00
|
|
|
}
|
2022-06-28 08:14:53 +02:00
|
|
|
if opts.ArchStr != "" {
|
|
|
|
fullArch := opts.ArchStr
|
|
|
|
fields := strings.SplitN(fullArch, ".", 2)
|
|
|
|
if len(fields) != 2 {
|
|
|
|
return 1, fmt.Errorf("invalid arch format '%s' passed to mshell --install", fullArch)
|
|
|
|
}
|
|
|
|
goos, goarch := fields[0], fields[1]
|
|
|
|
if !base.ValidGoArch(goos, goarch) {
|
|
|
|
return 1, fmt.Errorf("invalid arch '%s' passed to mshell --install", fullArch)
|
|
|
|
}
|
2022-09-26 22:02:34 +02:00
|
|
|
optName := base.GoArchOptFile(base.MShellVersion, goos, goarch)
|
2022-06-28 08:14:53 +02:00
|
|
|
_, err = os.Stat(optName)
|
|
|
|
if err != nil {
|
|
|
|
return 1, fmt.Errorf("cannot install mshell to remote host, cannot read '%s': %w", optName, err)
|
|
|
|
}
|
|
|
|
opts.OptName = optName
|
2022-06-28 07:39:16 +02:00
|
|
|
}
|
2022-09-26 22:02:34 +02:00
|
|
|
err = shexec.RunInstallFromOpts(opts)
|
2022-06-28 07:39:16 +02:00
|
|
|
if err != nil {
|
|
|
|
return 1, err
|
|
|
|
}
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2022-08-23 00:59:03 +02:00
|
|
|
func handleEnv() (int, error) {
|
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return 1, err
|
|
|
|
}
|
2022-10-15 22:45:52 +02:00
|
|
|
fmt.Printf("%s\x00\x00", cwd)
|
2022-08-23 00:59:03 +02:00
|
|
|
fullEnv := os.Environ()
|
2022-10-15 22:45:52 +02:00
|
|
|
var linePrinted bool
|
2022-08-23 00:59:03 +02:00
|
|
|
for _, envLine := range fullEnv {
|
2022-10-15 22:45:52 +02:00
|
|
|
if envLine != "" {
|
|
|
|
fmt.Printf("%s\x00", envLine)
|
|
|
|
linePrinted = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if linePrinted {
|
|
|
|
fmt.Printf("\x00")
|
|
|
|
} else {
|
|
|
|
fmt.Printf("\x00\x00")
|
2022-08-23 00:59:03 +02:00
|
|
|
}
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2022-06-24 09:02:18 +02:00
|
|
|
func handleUsage() {
|
2022-06-23 21:48:45 +02:00
|
|
|
usage := `
|
2022-06-27 23:57:01 +02:00
|
|
|
Client Usage: mshell [opts] --ssh user@host -- [command]
|
2022-06-23 21:48:45 +02:00
|
|
|
|
|
|
|
mshell multiplexes input and output streams to a remote command over ssh.
|
|
|
|
|
|
|
|
Options:
|
2022-06-27 23:57:01 +02:00
|
|
|
-i [identity-file] - used to set '-i' option for ssh command
|
|
|
|
-l [user] - used to set '-l' option for ssh command
|
|
|
|
--cwd [dir] - execute remote command in [dir]
|
|
|
|
--ssh-opts [opts] - addition options to pass to ssh command
|
|
|
|
[command] - the remote command to execute
|
2022-06-24 09:02:18 +02:00
|
|
|
|
2022-06-26 10:53:07 +02:00
|
|
|
Sudo Options:
|
2022-06-27 23:57:01 +02:00
|
|
|
--sudo - use only if sudo never requires a password
|
|
|
|
--sudo-with-password [pw] - not recommended, use --sudo-with-passfile if possible
|
2022-06-26 10:53:07 +02:00
|
|
|
--sudo-with-passfile [file]
|
|
|
|
|
|
|
|
Sudo options allow you to run the given command using "sudo". The first
|
|
|
|
option only works when you can sudo without a password. Your password will be passed
|
2022-06-29 23:29:38 +02:00
|
|
|
securely through a high numbered fd to "sudo -S". Note that to use high numbered
|
|
|
|
file descriptors with sudo, you will need to add this line to your /etc/sudoers file:
|
|
|
|
Defaults closefrom_override
|
|
|
|
See full documentation for more details.
|
2022-06-26 10:53:07 +02:00
|
|
|
|
2022-06-24 09:02:18 +02:00
|
|
|
Examples:
|
|
|
|
# execute a python script remotely, with stdin still hooked up correctly
|
2022-06-27 23:57:01 +02:00
|
|
|
mshell --cwd "~/work" -i key.pem --ssh ubuntu@somehost -- "python3 /dev/fd/4" 4< myscript.py
|
2022-06-24 09:02:18 +02:00
|
|
|
|
|
|
|
# capture multiple outputs
|
|
|
|
mshell --ssh ubuntu@test -- "cat file1.txt > /dev/fd/3; cat file2.txt > /dev/fd/4" 3> file1.txt 4> file2.txt
|
|
|
|
|
|
|
|
# execute a script, catpure stdout/stderr in fd-3 and fd-4
|
|
|
|
# useful if you need to see stdout for interacting with ssh (password or host auth)
|
|
|
|
mshell --ssh user@host -- "test.sh > /dev/fd/3 2> /dev/fd/4" 3> test.stdout 4> test.stderr
|
2022-06-23 21:48:45 +02:00
|
|
|
|
2022-06-26 10:53:07 +02:00
|
|
|
# run a script as root (via sudo), capture output
|
|
|
|
mshell --sudo-with-passfile pw.txt --ssh ubuntu@somehost -- "python3 /dev/fd/3 > /dev/fd/4" 3< myscript.py 4> script-output.txt < script-input.txt
|
|
|
|
|
2022-06-23 21:48:45 +02:00
|
|
|
mshell is licensed under the MPLv2
|
|
|
|
Please see https://github.com/scripthaus-dev/mshell for extended usage modes, source code, bugs, and feature requests
|
|
|
|
`
|
|
|
|
fmt.Printf("%s\n\n", strings.TrimSpace(usage))
|
|
|
|
}
|
|
|
|
|
2022-06-11 06:37:21 +02:00
|
|
|
func main() {
|
2022-06-23 21:48:45 +02:00
|
|
|
if len(os.Args) == 1 {
|
2022-06-24 09:02:18 +02:00
|
|
|
handleUsage()
|
2022-06-23 21:48:45 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
firstArg := os.Args[1]
|
|
|
|
if firstArg == "--help" {
|
2022-06-24 09:02:18 +02:00
|
|
|
handleUsage()
|
2022-06-23 21:48:45 +02:00
|
|
|
return
|
|
|
|
} else if firstArg == "--version" {
|
2022-09-24 22:53:19 +02:00
|
|
|
fmt.Printf("mshell %s\n", base.MShellVersion)
|
2022-06-23 21:48:45 +02:00
|
|
|
return
|
2022-10-25 06:26:39 +02:00
|
|
|
} else if firstArg == "--test-env" {
|
|
|
|
state, err := shexec.GetShellState()
|
|
|
|
if state != nil {
|
|
|
|
|
|
|
|
}
|
2022-08-23 00:59:03 +02:00
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "[error] %v\n", err)
|
2022-10-25 06:26:39 +02:00
|
|
|
os.Exit(1)
|
2022-08-23 00:59:03 +02:00
|
|
|
}
|
2022-06-27 23:57:01 +02:00
|
|
|
} else if firstArg == "--single" {
|
2022-11-29 03:05:54 +01:00
|
|
|
base.InitDebugLog("single")
|
2022-09-06 21:57:54 +02:00
|
|
|
handleSingle(false)
|
|
|
|
return
|
|
|
|
} else if firstArg == "--single-from-server" {
|
2022-11-29 03:05:54 +01:00
|
|
|
base.InitDebugLog("single")
|
2022-09-06 21:57:54 +02:00
|
|
|
handleSingle(true)
|
2022-06-23 21:48:45 +02:00
|
|
|
return
|
|
|
|
} else if firstArg == "--server" {
|
2022-11-29 03:05:54 +01:00
|
|
|
base.InitDebugLog("server")
|
2022-06-29 00:04:08 +02:00
|
|
|
rtnCode, err := server.RunServer()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "[error] %v\n", err)
|
|
|
|
}
|
|
|
|
if rtnCode != 0 {
|
|
|
|
os.Exit(rtnCode)
|
|
|
|
}
|
2022-06-23 21:48:45 +02:00
|
|
|
return
|
2022-06-28 07:39:16 +02:00
|
|
|
} else if firstArg == "--install" {
|
|
|
|
rtnCode, err := handleInstall()
|
|
|
|
if err != nil {
|
2022-06-29 00:04:08 +02:00
|
|
|
fmt.Fprintf(os.Stderr, "[error] %v\n", err)
|
2022-06-28 07:39:16 +02:00
|
|
|
}
|
|
|
|
os.Exit(rtnCode)
|
|
|
|
return
|
2022-06-23 21:48:45 +02:00
|
|
|
} else {
|
2022-06-24 09:02:18 +02:00
|
|
|
rtnCode, err := handleClient()
|
|
|
|
if err != nil {
|
2022-06-29 00:04:08 +02:00
|
|
|
fmt.Fprintf(os.Stderr, "[error] %v\n", err)
|
|
|
|
}
|
|
|
|
if rtnCode != 0 {
|
|
|
|
os.Exit(rtnCode)
|
2022-06-24 09:02:18 +02:00
|
|
|
}
|
2022-06-23 21:48:45 +02:00
|
|
|
return
|
|
|
|
}
|
2022-06-10 09:35:24 +02:00
|
|
|
}
|