mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-02-24 03:01:58 +01:00
simplify argument parsing, hard code common ssh options
This commit is contained in:
parent
6574402691
commit
dafe2b5a57
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module github.com/scripthaus-dev/mshell
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/creack/pty v1.1.18 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -1,3 +1,5 @@
|
||||
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
|
||||
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
|
@ -211,7 +211,7 @@ func doMain() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleRemote() {
|
||||
func handleSingle() {
|
||||
packetParser := packet.MakePacketParser(os.Stdin)
|
||||
sender := packet.MakePacketSender(os.Stdout)
|
||||
defer func() {
|
||||
@ -285,14 +285,35 @@ func parseClientOpts() (*shexec.ClientOpts, error) {
|
||||
for iter.HasNext() {
|
||||
argStr := iter.Next()
|
||||
if argStr == "--ssh" {
|
||||
if opts.IsSSH {
|
||||
return nil, fmt.Errorf("duplicate '--ssh' option")
|
||||
if !iter.IsNextPlain() {
|
||||
return nil, fmt.Errorf("'--ssh [user@host]' missing host")
|
||||
}
|
||||
opts.IsSSH = true
|
||||
break
|
||||
opts.SSHHost = iter.Next()
|
||||
continue
|
||||
}
|
||||
if argStr == "--ssh-opts" {
|
||||
if !iter.HasNext() {
|
||||
return nil, fmt.Errorf("'--ssh-opts [options]' missing options")
|
||||
}
|
||||
opts.SSHOptsStr = iter.Next()
|
||||
continue
|
||||
}
|
||||
if argStr == "-i" {
|
||||
if !iter.IsNextPlain() {
|
||||
return nil, fmt.Errorf("-i [identity-file]' missing file")
|
||||
}
|
||||
opts.SSHIdentity = iter.Next()
|
||||
continue
|
||||
}
|
||||
if argStr == "-l" {
|
||||
if !iter.IsNextPlain() {
|
||||
return nil, fmt.Errorf("-l [user]' missing user")
|
||||
}
|
||||
opts.SSHUser = iter.Next()
|
||||
continue
|
||||
}
|
||||
if argStr == "--cwd" {
|
||||
if !iter.HasNext() {
|
||||
if !iter.IsNextPlain() {
|
||||
return nil, fmt.Errorf("'--cwd [dir]' missing directory")
|
||||
}
|
||||
opts.Cwd = iter.Next()
|
||||
@ -316,7 +337,7 @@ func parseClientOpts() (*shexec.ClientOpts, error) {
|
||||
continue
|
||||
}
|
||||
if argStr == "--sudo-with-passfile" {
|
||||
if !iter.HasNext() {
|
||||
if !iter.IsNextPlain() {
|
||||
return nil, fmt.Errorf("'--sudo-with-passfile [file]', missing file")
|
||||
}
|
||||
opts.Sudo = true
|
||||
@ -329,29 +350,12 @@ func parseClientOpts() (*shexec.ClientOpts, error) {
|
||||
opts.SudoPw = string(contents)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if opts.IsSSH {
|
||||
// parse SSH opts
|
||||
for iter.HasNext() {
|
||||
argStr := iter.Next()
|
||||
if argStr == "--" {
|
||||
opts.SSHOptsTerm = true
|
||||
break
|
||||
if argStr == "--" {
|
||||
if !iter.HasNext() {
|
||||
return nil, fmt.Errorf("'--' should be followed by command")
|
||||
}
|
||||
if argStr == "-t" || argStr == "-tt" {
|
||||
return nil, fmt.Errorf("mshell cannot run over ssh -t")
|
||||
}
|
||||
opts.SSHOpts = append(opts.SSHOpts, argStr)
|
||||
}
|
||||
if !opts.SSHOptsTerm {
|
||||
return nil, fmt.Errorf("ssh options must be terminated with '--' followed by [command]")
|
||||
}
|
||||
if !iter.HasNext() {
|
||||
return nil, fmt.Errorf("no command specified")
|
||||
}
|
||||
opts.Command = strings.Join(iter.Rest(), " ")
|
||||
if strings.TrimSpace(opts.Command) == "" {
|
||||
return nil, fmt.Errorf("no command or empty command specified")
|
||||
opts.Command = strings.Join(iter.Rest(), " ")
|
||||
break
|
||||
}
|
||||
}
|
||||
return opts, nil
|
||||
@ -365,9 +369,12 @@ func handleClient() (int, error) {
|
||||
if opts.Debug {
|
||||
packet.GlobalDebug = true
|
||||
}
|
||||
if !opts.IsSSH {
|
||||
if opts.SSHHost == "" {
|
||||
return 1, fmt.Errorf("when running in client mode '--ssh' option must be present")
|
||||
}
|
||||
if opts.Command == "" {
|
||||
return 1, fmt.Errorf("no [command] specified. [command] follows '--' option (see usage)")
|
||||
}
|
||||
fds, err := detectOpenFds()
|
||||
if err != nil {
|
||||
return 1, err
|
||||
@ -382,17 +389,20 @@ func handleClient() (int, error) {
|
||||
|
||||
func handleUsage() {
|
||||
usage := `
|
||||
Client Usage: mshell [mshell-opts] --ssh [ssh-opts] user@host -- [command]
|
||||
Client Usage: mshell [opts] --ssh user@host -- [command]
|
||||
|
||||
mshell multiplexes input and output streams to a remote command over ssh.
|
||||
|
||||
Options:
|
||||
--cwd [dir] - execute remote command in [dir]
|
||||
[command] - a single argument (should be quoted)
|
||||
-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
|
||||
|
||||
Sudo Options:
|
||||
--sudo
|
||||
--sudo-with-password [pw] (not recommended, use --sudo-with-passfile if possible)
|
||||
--sudo - use only if sudo never requires a password
|
||||
--sudo-with-password [pw] - not recommended, use --sudo-with-passfile if possible
|
||||
--sudo-with-passfile [file]
|
||||
|
||||
Sudo options allow you to run the given command using "sudo". The first
|
||||
@ -401,7 +411,7 @@ securely through a high numbered fd to "sudo -S". See full documentation for mo
|
||||
|
||||
Examples:
|
||||
# execute a python script remotely, with stdin still hooked up correctly
|
||||
mshell --cwd "~/work" --ssh -i key.pem ubuntu@somehost -- "python3 /dev/fd/4" 4< myscript.py
|
||||
mshell --cwd "~/work" -i key.pem --ssh ubuntu@somehost -- "python3 /dev/fd/4" 4< myscript.py
|
||||
|
||||
# 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
|
||||
@ -431,8 +441,8 @@ func main() {
|
||||
} else if firstArg == "--version" {
|
||||
fmt.Printf("mshell v%s\n", MShellVersion)
|
||||
return
|
||||
} else if firstArg == "--remote" {
|
||||
handleRemote()
|
||||
} else if firstArg == "--single" {
|
||||
handleSingle()
|
||||
return
|
||||
} else if firstArg == "--server" {
|
||||
handleServer()
|
||||
|
@ -25,6 +25,13 @@ func (iter *OptsIter) HasNext() bool {
|
||||
return iter.Pos <= len(iter.Opts)-1
|
||||
}
|
||||
|
||||
func (iter *OptsIter) IsNextPlain() bool {
|
||||
if !iter.HasNext() {
|
||||
return false
|
||||
}
|
||||
return !IsOption(iter.Opts[iter.Pos])
|
||||
}
|
||||
|
||||
func (iter *OptsIter) Next() string {
|
||||
if iter.Pos >= len(iter.Opts) {
|
||||
return ""
|
||||
|
@ -340,6 +340,7 @@ type InitPacketType struct {
|
||||
HomeDir string `json:"homedir,omitempty"`
|
||||
Env []string `json:"env,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
NotFound bool `json:"notfound,omitempty"`
|
||||
}
|
||||
|
||||
func (*InitPacketType) GetType() string {
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"github.com/creack/pty"
|
||||
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||
"github.com/scripthaus-dev/mshell/pkg/mpio"
|
||||
@ -29,11 +30,20 @@ const MaxCols = 1024
|
||||
const MaxFdNum = 1023
|
||||
const FirstExtraFilesFdNum = 3
|
||||
|
||||
const SSHRemoteCommand = `PATH=$PATH:~/.mshell; mshell --remote`
|
||||
const SSHRemoteCommand = `
|
||||
PATH=$PATH:~/.mshell;
|
||||
which mshell > /dev/null;
|
||||
if [[ "$?" -ne 0 ]]
|
||||
then
|
||||
printf "\n##34{\"type\": \"init\", \"notfound\": true}\n"
|
||||
else
|
||||
mshell --single
|
||||
fi
|
||||
`
|
||||
|
||||
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"`
|
||||
const RunCommandFmt = `%s`
|
||||
const RunSudoCommandFmt = `sudo -C %d bash /dev/fd/%d`
|
||||
const RunSudoPasswordCommandFmt = `cat /dev/fd/%d | sudo -S -C %d bash -c "echo '[from-mshell]'; bash /dev/fd/%d < /dev/fd/%d"`
|
||||
|
||||
type ShExecType struct {
|
||||
Lock *sync.Mutex
|
||||
@ -205,9 +215,10 @@ func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecT
|
||||
}
|
||||
|
||||
type ClientOpts struct {
|
||||
IsSSH bool
|
||||
SSHOptsTerm bool
|
||||
SSHOpts []string
|
||||
SSHHost string
|
||||
SSHOptsStr string
|
||||
SSHIdentity string
|
||||
SSHUser string
|
||||
Command string
|
||||
Fds []packet.RemoteFd
|
||||
Cwd string
|
||||
@ -218,13 +229,29 @@ type ClientOpts struct {
|
||||
CommandStdinFdNum int
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) MakeSSHCommandString() 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)
|
||||
}
|
||||
remoteCommand := strings.TrimSpace(SSHRemoteCommand)
|
||||
// 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))
|
||||
return sshCmd
|
||||
}
|
||||
|
||||
func (opts *ClientOpts) MakeRunPacket() (*packet.RunPacketType, error) {
|
||||
runPacket := packet.MakeRunPacket()
|
||||
runPacket.Cwd = opts.Cwd
|
||||
runPacket.Fds = opts.Fds
|
||||
if !opts.Sudo {
|
||||
// normal, non-sudo command
|
||||
runPacket.Command = opts.Command
|
||||
runPacket.Command = fmt.Sprintf(RunCommandFmt, opts.Command)
|
||||
return runPacket, nil
|
||||
}
|
||||
if opts.SudoWithPass {
|
||||
@ -248,7 +275,7 @@ func (opts *ClientOpts) MakeRunPacket() (*packet.RunPacketType, error) {
|
||||
opts.Fds = append(opts.Fds, commandStdinRfd)
|
||||
opts.CommandStdinFdNum = commandStdinFdNum
|
||||
maxFdNum := opts.MaxFdNum()
|
||||
runPacket.Command = fmt.Sprintf(RemoteSudoPasswordCommandFmt, pwFdNum, maxFdNum+1, commandFdNum, commandStdinFdNum)
|
||||
runPacket.Command = fmt.Sprintf(RunSudoPasswordCommandFmt, pwFdNum, maxFdNum+1, commandFdNum, commandStdinFdNum)
|
||||
runPacket.Fds = opts.Fds
|
||||
return runPacket, nil
|
||||
} else {
|
||||
@ -259,7 +286,7 @@ func (opts *ClientOpts) MakeRunPacket() (*packet.RunPacketType, error) {
|
||||
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.Command = fmt.Sprintf(RunSudoCommandFmt, maxFdNum+1, commandFdNum)
|
||||
runPacket.Fds = opts.Fds
|
||||
return runPacket, nil
|
||||
}
|
||||
@ -324,10 +351,8 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
|
||||
return nil, err
|
||||
}
|
||||
cmd := MakeShExec("")
|
||||
var fullSshOpts []string
|
||||
fullSshOpts = append(fullSshOpts, opts.SSHOpts...)
|
||||
fullSshOpts = append(fullSshOpts, SSHRemoteCommand)
|
||||
ecmd := exec.Command("ssh", fullSshOpts...)
|
||||
sshCmdStr := opts.MakeSSHCommandString()
|
||||
ecmd := exec.Command("bash", "-c", sshCmdStr)
|
||||
cmd.Cmd = ecmd
|
||||
inputWriter, err := ecmd.StdinPipe()
|
||||
if err != nil {
|
||||
@ -386,6 +411,9 @@ 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'")
|
||||
}
|
||||
if initPk.Version != "0.1.0" {
|
||||
return nil, fmt.Errorf("invalid remote mshell version 'v%s', must be v0.1.0", initPk.Version)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user