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 shexec
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
2022-06-23 21:48:45 +02:00
|
|
|
"sync"
|
2022-06-10 09:35:24 +02:00
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/creack/pty"
|
2022-06-23 19:16:54 +02:00
|
|
|
"github.com/scripthaus-dev/mshell/pkg/base"
|
2022-06-24 19:24:02 +02:00
|
|
|
"github.com/scripthaus-dev/mshell/pkg/mpio"
|
2022-06-23 19:16:54 +02:00
|
|
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
2022-06-10 09:35:24 +02:00
|
|
|
)
|
|
|
|
|
2022-06-21 02:51:28 +02:00
|
|
|
const DefaultRows = 25
|
|
|
|
const DefaultCols = 80
|
|
|
|
const MaxRows = 1024
|
|
|
|
const MaxCols = 1024
|
2022-06-24 03:23:30 +02:00
|
|
|
const MaxFdNum = 1023
|
|
|
|
const FirstExtraFilesFdNum = 3
|
2022-06-21 02:51:28 +02:00
|
|
|
|
2022-06-26 10:41:58 +02:00
|
|
|
const SSHRemoteCommand = `PATH=$PATH:~/.mshell; mshell --remote`
|
|
|
|
|
|
|
|
const RemoteCommandFmt = `%s`
|
|
|
|
const RemoteSudoCommandFmt = `sudo -C %d bash /dev/fd/%d`
|
|
|
|
const RemoteSudoPasswordCommandFmt = `cat /dev/fd/%d | sudo -S -C %d bash -c "echo '[from-mshell]'; bash /dev/fd/%d < /dev/fd/%d"`
|
|
|
|
|
2022-06-10 09:35:24 +02:00
|
|
|
type ShExecType struct {
|
2022-06-24 19:24:02 +02:00
|
|
|
Lock *sync.Mutex
|
|
|
|
StartTs time.Time
|
2022-06-27 21:03:47 +02:00
|
|
|
CK base.CommandKey
|
2022-06-24 19:24:02 +02:00
|
|
|
FileNames *base.CommandFileNames
|
|
|
|
Cmd *exec.Cmd
|
|
|
|
CmdPty *os.File
|
|
|
|
Multiplexer *mpio.Multiplexer
|
2022-06-23 21:48:45 +02:00
|
|
|
}
|
|
|
|
|
2022-06-27 21:03:47 +02:00
|
|
|
func MakeShExec(ck base.CommandKey) *ShExecType {
|
2022-06-23 21:48:45 +02:00
|
|
|
return &ShExecType{
|
2022-06-24 19:24:02 +02:00
|
|
|
Lock: &sync.Mutex{},
|
|
|
|
StartTs: time.Now(),
|
2022-06-27 21:03:47 +02:00
|
|
|
CK: ck,
|
|
|
|
Multiplexer: mpio.MakeMultiplexer(ck),
|
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-06-23 21:48:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ShExecType) MakeCmdStartPacket() *packet.CmdStartPacketType {
|
|
|
|
startPacket := packet.MakeCmdStartPacket()
|
|
|
|
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
|
|
|
|
}
|
|
|
|
if cmd.Env != nil {
|
|
|
|
cmd.Env = os.Environ()
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func MakeExecCmd(pk *packet.RunPacketType, cmdTty *os.File) *exec.Cmd {
|
|
|
|
ecmd := exec.Command("bash", "-c", pk.Command)
|
|
|
|
UpdateCmdEnv(ecmd, pk.Env)
|
2022-06-23 19:16:54 +02:00
|
|
|
if pk.Cwd != "" {
|
2022-06-25 08:42:00 +02:00
|
|
|
ecmd.Dir = base.ExpandHomeDir(pk.Cwd)
|
2022-06-10 09:35:24 +02:00
|
|
|
}
|
|
|
|
ecmd.Stdin = cmdTty
|
|
|
|
ecmd.Stdout = cmdTty
|
|
|
|
ecmd.Stderr = cmdTty
|
|
|
|
ecmd.SysProcAttr = &syscall.SysProcAttr{
|
|
|
|
Setsid: true,
|
|
|
|
Setctty: true,
|
|
|
|
}
|
|
|
|
return ecmd
|
|
|
|
}
|
|
|
|
|
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-10 09:35:24 +02:00
|
|
|
}
|
2022-06-23 19:16:54 +02:00
|
|
|
if pk.Cwd != "" {
|
2022-06-25 08:42:00 +02:00
|
|
|
realCwd := base.ExpandHomeDir(pk.Cwd)
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-21 02:51:28 +02:00
|
|
|
func GetWinsize(p *packet.RunPacketType) *pty.Winsize {
|
|
|
|
rows := DefaultRows
|
|
|
|
cols := DefaultCols
|
2022-06-23 19:16:54 +02:00
|
|
|
if p.TermSize.Rows > 0 && p.TermSize.Rows <= MaxRows {
|
|
|
|
rows = p.TermSize.Rows
|
2022-06-21 02:51:28 +02:00
|
|
|
}
|
2022-06-23 19:16:54 +02:00
|
|
|
if p.TermSize.Cols > 0 && p.TermSize.Cols <= MaxCols {
|
|
|
|
cols = p.TermSize.Cols
|
2022-06-21 02:51:28 +02:00
|
|
|
}
|
|
|
|
return &pty.Winsize{Rows: uint16(rows), Cols: uint16(cols)}
|
|
|
|
}
|
|
|
|
|
2022-06-11 06:37:21 +02:00
|
|
|
// when err is nil, the command will have already been started
|
|
|
|
func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
2022-06-10 09:35:24 +02:00
|
|
|
err := ValidateRunPacket(pk)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-23 21:48:45 +02:00
|
|
|
if !pk.Detached {
|
|
|
|
return runCommandSimple(pk, sender)
|
|
|
|
} else {
|
|
|
|
return runCommandDetached(pk, sender)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-24 22:25:09 +02:00
|
|
|
type ClientOpts struct {
|
2022-06-26 10:41:58 +02:00
|
|
|
IsSSH bool
|
|
|
|
SSHOptsTerm bool
|
|
|
|
SSHOpts []string
|
|
|
|
Command string
|
|
|
|
Fds []packet.RemoteFd
|
|
|
|
Cwd string
|
|
|
|
Debug bool
|
|
|
|
Sudo bool
|
|
|
|
SudoWithPass bool
|
|
|
|
SudoPw string
|
|
|
|
CommandStdinFdNum int
|
2022-06-24 22:25:09 +02:00
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
runPacket.Cwd = opts.Cwd
|
|
|
|
runPacket.Fds = opts.Fds
|
2022-06-26 10:41:58 +02:00
|
|
|
if !opts.Sudo {
|
|
|
|
// normal, non-sudo command
|
|
|
|
runPacket.Command = opts.Command
|
|
|
|
return runPacket, nil
|
|
|
|
}
|
|
|
|
if opts.SudoWithPass {
|
|
|
|
pwFdNum, err := opts.NextFreeFdNum()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pwRfd := packet.RemoteFd{FdNum: pwFdNum, Read: true, Content: opts.SudoPw}
|
|
|
|
opts.Fds = append(opts.Fds, pwRfd)
|
|
|
|
commandFdNum, err := opts.NextFreeFdNum()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
commandRfd := packet.RemoteFd{FdNum: commandFdNum, Read: true, Content: opts.Command}
|
|
|
|
opts.Fds = append(opts.Fds, commandRfd)
|
|
|
|
commandStdinFdNum, err := opts.NextFreeFdNum()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
commandStdinRfd := packet.RemoteFd{FdNum: commandStdinFdNum, Read: true, DupStdin: true}
|
|
|
|
opts.Fds = append(opts.Fds, commandStdinRfd)
|
|
|
|
opts.CommandStdinFdNum = commandStdinFdNum
|
|
|
|
maxFdNum := opts.MaxFdNum()
|
|
|
|
runPacket.Command = fmt.Sprintf(RemoteSudoPasswordCommandFmt, pwFdNum, maxFdNum+1, commandFdNum, commandStdinFdNum)
|
|
|
|
runPacket.Fds = opts.Fds
|
|
|
|
return runPacket, nil
|
|
|
|
} else {
|
|
|
|
commandFdNum, err := opts.NextFreeFdNum()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rfd := packet.RemoteFd{FdNum: commandFdNum, Read: true, Content: opts.Command}
|
|
|
|
opts.Fds = append(opts.Fds, rfd)
|
|
|
|
maxFdNum := opts.MaxFdNum()
|
|
|
|
runPacket.Command = fmt.Sprintf(RemoteSudoCommandFmt, maxFdNum+1, commandFdNum)
|
|
|
|
runPacket.Fds = opts.Fds
|
|
|
|
return runPacket, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (opts *ClientOpts) NextFreeFdNum() (int, error) {
|
|
|
|
fdMap := make(map[int]bool)
|
|
|
|
for _, fd := range opts.Fds {
|
|
|
|
fdMap[fd.FdNum] = true
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (opts *ClientOpts) MaxFdNum() int {
|
|
|
|
maxFdNum := 3
|
|
|
|
for _, fd := range opts.Fds {
|
|
|
|
if fd.FdNum > maxFdNum {
|
|
|
|
maxFdNum = fd.FdNum
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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-06-24 22:25:09 +02:00
|
|
|
func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, error) {
|
2022-06-25 08:42:00 +02:00
|
|
|
err := ValidateRemoteFds(opts.Fds)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-26 10:41:58 +02:00
|
|
|
runPacket, err := opts.MakeRunPacket() // modifies opts
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-27 21:03:47 +02:00
|
|
|
cmd := MakeShExec("")
|
2022-06-24 22:25:09 +02:00
|
|
|
var fullSshOpts []string
|
|
|
|
fullSshOpts = append(fullSshOpts, opts.SSHOpts...)
|
2022-06-26 10:41:58 +02:00
|
|
|
fullSshOpts = append(fullSshOpts, SSHRemoteCommand)
|
2022-06-24 22:25:09 +02:00
|
|
|
ecmd := exec.Command("ssh", fullSshOpts...)
|
|
|
|
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-26 10:41:58 +02:00
|
|
|
if !opts.SudoWithPass {
|
|
|
|
cmd.Multiplexer.MakeRawFdReader(0, os.Stdin, false)
|
|
|
|
}
|
2022-06-25 08:42:00 +02:00
|
|
|
cmd.Multiplexer.MakeRawFdWriter(1, os.Stdout, false)
|
|
|
|
cmd.Multiplexer.MakeRawFdWriter(2, os.Stderr, false)
|
2022-06-26 10:41:58 +02:00
|
|
|
for _, rfd := range runPacket.Fds {
|
|
|
|
if rfd.Read && rfd.Content != "" {
|
|
|
|
err = cmd.Multiplexer.MakeStringFdReader(rfd.FdNum, rfd.Content)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("creating content fd %d", rfd.FdNum)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if rfd.Read && rfd.DupStdin {
|
|
|
|
cmd.Multiplexer.MakeRawFdReader(rfd.FdNum, os.Stdin, false)
|
|
|
|
continue
|
|
|
|
}
|
2022-06-25 08:42:00 +02:00
|
|
|
fd := os.NewFile(uintptr(rfd.FdNum), fmt.Sprintf("/dev/fd/%d", rfd.FdNum))
|
|
|
|
if fd == nil {
|
|
|
|
return nil, fmt.Errorf("cannot open fd %d", rfd.FdNum)
|
|
|
|
}
|
|
|
|
if rfd.Read {
|
|
|
|
cmd.Multiplexer.MakeRawFdReader(rfd.FdNum, fd, true)
|
|
|
|
} else if rfd.Write {
|
|
|
|
cmd.Multiplexer.MakeRawFdWriter(rfd.FdNum, fd, true)
|
|
|
|
}
|
|
|
|
}
|
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()
|
2022-06-25 09:05:37 +02:00
|
|
|
stdoutPacketCh := packet.PacketParser(stdoutReader)
|
|
|
|
stderrPacketCh := packet.PacketParser(stderrReader)
|
|
|
|
packetCh := packet.CombinePacketParsers(stdoutPacketCh, stderrPacketCh)
|
2022-06-24 22:25:09 +02:00
|
|
|
sender := packet.MakePacketSender(inputWriter)
|
2022-06-25 09:33:18 +02:00
|
|
|
versionOk := false
|
2022-06-24 22:25:09 +02:00
|
|
|
for pk := range packetCh {
|
|
|
|
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)
|
|
|
|
if initPk.Version != "0.1.0" {
|
|
|
|
return nil, fmt.Errorf("invalid remote mshell version 'v%s', must be v0.1.0", initPk.Version)
|
|
|
|
}
|
2022-06-25 09:33:18 +02:00
|
|
|
versionOk = true
|
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-06-24 22:25:09 +02:00
|
|
|
sender.SendPacket(runPacket)
|
2022-06-25 09:22:03 +02:00
|
|
|
if opts.Debug {
|
|
|
|
cmd.Multiplexer.Debug = true
|
|
|
|
}
|
2022-06-24 22:25:09 +02:00
|
|
|
remoteDonePacket := cmd.Multiplexer.RunIOAndWait(packetCh, sender, false, true, true)
|
|
|
|
donePacket := cmd.WaitForCommand()
|
|
|
|
if remoteDonePacket != nil {
|
|
|
|
donePacket = remoteDonePacket
|
|
|
|
}
|
|
|
|
return donePacket, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cmd *ShExecType) RunRemoteIOAndWait(packetCh chan packet.PacketType, sender *packet.PacketSender) {
|
2022-06-25 08:42:00 +02:00
|
|
|
defer cmd.Close()
|
2022-06-24 22:25:09 +02:00
|
|
|
cmd.Multiplexer.RunIOAndWait(packetCh, sender, true, false, false)
|
2022-06-23 21:48:45 +02:00
|
|
|
donePacket := cmd.WaitForCommand()
|
|
|
|
sender.SendPacket(donePacket)
|
|
|
|
}
|
|
|
|
|
|
|
|
func runCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
2022-06-27 21:03:47 +02:00
|
|
|
cmd := MakeShExec(pk.CK)
|
2022-06-23 21:48:45 +02:00
|
|
|
cmd.Cmd = exec.Command("bash", "-c", pk.Command)
|
|
|
|
UpdateCmdEnv(cmd.Cmd, pk.Env)
|
|
|
|
if pk.Cwd != "" {
|
2022-06-25 08:42:00 +02:00
|
|
|
cmd.Cmd.Dir = base.ExpandHomeDir(pk.Cwd)
|
|
|
|
}
|
|
|
|
err := ValidateRemoteFds(pk.Fds)
|
|
|
|
if err != nil {
|
|
|
|
cmd.Close()
|
|
|
|
return nil, err
|
2022-06-23 21:48:45 +02:00
|
|
|
}
|
2022-06-24 19:24:02 +02:00
|
|
|
cmd.Cmd.Stdin, err = cmd.Multiplexer.MakeWriterPipe(0)
|
2022-06-23 21:48:45 +02:00
|
|
|
if err != nil {
|
|
|
|
cmd.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-24 19:24:02 +02:00
|
|
|
cmd.Cmd.Stdout, err = cmd.Multiplexer.MakeReaderPipe(1)
|
2022-06-23 21:48:45 +02:00
|
|
|
if err != nil {
|
|
|
|
cmd.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-24 19:24:02 +02:00
|
|
|
cmd.Cmd.Stderr, err = cmd.Multiplexer.MakeReaderPipe(2)
|
2022-06-23 21:48:45 +02:00
|
|
|
if err != nil {
|
|
|
|
cmd.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-24 03:23:30 +02:00
|
|
|
extraFiles := make([]*os.File, 0, MaxFdNum+1)
|
|
|
|
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
|
2022-06-24 19:24:02 +02:00
|
|
|
extraFiles[rfd.FdNum], err = cmd.Multiplexer.MakeWriterPipe(rfd.FdNum)
|
2022-06-24 03:23:30 +02:00
|
|
|
if err != nil {
|
|
|
|
cmd.Close()
|
|
|
|
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 {
|
|
|
|
cmd.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(extraFiles) > FirstExtraFilesFdNum {
|
|
|
|
cmd.Cmd.ExtraFiles = extraFiles[FirstExtraFilesFdNum:]
|
|
|
|
}
|
|
|
|
|
2022-06-23 21:48:45 +02:00
|
|
|
err = cmd.Cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
cmd.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return cmd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func runCommandDetached(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, 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 {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-06-17 21:27:29 +02:00
|
|
|
ptyOutInfo, err := os.Stat(fileNames.PtyOutFile)
|
|
|
|
if err == nil { // non-nil error will be caught by regular OpenFile below
|
|
|
|
// must have size 0
|
|
|
|
if ptyOutInfo.Size() != 0 {
|
2022-06-27 21:03:47 +02:00
|
|
|
return nil, fmt.Errorf("cmdkey '%s' was already used (ptyout len=%d)", pk.CK, ptyOutInfo.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 {
|
|
|
|
return nil, fmt.Errorf("opening new pty: %w", err)
|
|
|
|
}
|
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-06-27 21:03:47 +02:00
|
|
|
rtn := MakeShExec(pk.CK)
|
2022-06-10 09:35:24 +02:00
|
|
|
ecmd := MakeExecCmd(pk, cmdTty)
|
|
|
|
err = ecmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("starting command: %w", err)
|
|
|
|
}
|
|
|
|
ptyOutFd, err := os.OpenFile(fileNames.PtyOutFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot open ptyout file '%s': %w", fileNames.PtyOutFile, err)
|
|
|
|
}
|
|
|
|
go func() {
|
|
|
|
// copy pty output to .ptyout file
|
|
|
|
_, copyErr := io.Copy(ptyOutFd, cmdPty)
|
|
|
|
if copyErr != nil {
|
2022-06-11 06:37:21 +02:00
|
|
|
sender.SendErrorPacket(fmt.Sprintf("copying pty output to ptyout file: %v", copyErr))
|
2022-06-10 09:35:24 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
// copy .stdin fifo contents to pty input
|
|
|
|
copyFifoErr := MakeAndCopyStdinFifo(cmdPty, fileNames.StdinFifo)
|
|
|
|
if copyFifoErr != nil {
|
2022-06-11 06:37:21 +02:00
|
|
|
sender.SendErrorPacket(fmt.Sprintf("reading from stdin fifo: %v", copyFifoErr))
|
2022-06-10 09:35:24 +02:00
|
|
|
}
|
|
|
|
}()
|
2022-06-23 21:48:45 +02:00
|
|
|
rtn.FileNames = fileNames
|
|
|
|
rtn.Cmd = ecmd
|
|
|
|
rtn.CmdPty = cmdPty
|
|
|
|
return rtn, 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-06-23 21:48:45 +02:00
|
|
|
func (c *ShExecType) WaitForCommand() *packet.CmdDonePacketType {
|
2022-06-16 01:29:39 +02:00
|
|
|
exitErr := c.Cmd.Wait()
|
2022-06-11 06:37:21 +02:00
|
|
|
endTs := time.Now()
|
|
|
|
cmdDuration := endTs.Sub(c.StartTs)
|
2022-06-16 01:29:39 +02:00
|
|
|
exitCode := GetExitCode(exitErr)
|
2022-06-11 06:37:21 +02:00
|
|
|
donePacket := packet.MakeCmdDonePacket()
|
|
|
|
donePacket.Ts = endTs.UnixMilli()
|
2022-06-27 21:03:47 +02:00
|
|
|
donePacket.CK = c.CK
|
2022-06-11 06:37:21 +02:00
|
|
|
donePacket.ExitCode = exitCode
|
|
|
|
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
|
|
|
}
|