mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-30 23:01:30 +01:00
sanitize packets to be 7-bit ascii without control chars. dont send data/dataend when no rundata present. use os.Executable to locate mshell if running locally. more work on detached mode
This commit is contained in:
parent
4d8841a459
commit
b6711e7428
@ -10,10 +10,8 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||
@ -24,19 +22,6 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// in single run mode, we don't want mshell to die from signals
|
||||
// since we want the single mshell to persist even if session / main mshell
|
||||
// is terminated.
|
||||
func setupSingleSignals(cmd *shexec.ShExecType) {
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
go func() {
|
||||
for range sigCh {
|
||||
// do nothing
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func doSingle(ck base.CommandKey) {
|
||||
packetParser := packet.MakePacketParser(os.Stdin)
|
||||
sender := packet.MakePacketSender(os.Stdout)
|
||||
@ -60,12 +45,12 @@ func doSingle(ck base.CommandKey) {
|
||||
sender.SendErrorPacket(fmt.Sprintf("run packet cmdid[%s] did not match arg[%s]", runPacket.CK, ck))
|
||||
return
|
||||
}
|
||||
cmd, err := shexec.RunCommand(runPacket, sender)
|
||||
cmd, err := shexec.RunCommandDetached(runPacket, sender)
|
||||
if err != nil {
|
||||
sender.SendErrorPacket(fmt.Sprintf("error running command: %v", err))
|
||||
return
|
||||
}
|
||||
setupSingleSignals(cmd)
|
||||
shexec.SetupSignalsForDetach()
|
||||
startPacket := cmd.MakeCmdStartPacket()
|
||||
sender.SendPacket(startPacket)
|
||||
donePacket := cmd.WaitForCommand()
|
||||
@ -245,15 +230,30 @@ func handleSingle() {
|
||||
sender.SendCKErrorPacket(ck, err.Error())
|
||||
return
|
||||
}
|
||||
cmd, err := shexec.RunCommand(runPacket, sender)
|
||||
err = shexec.ValidateRunPacket(runPacket)
|
||||
if err != nil {
|
||||
sender.SendCKErrorPacket(runPacket.CK, fmt.Sprintf("error running command: %v", err))
|
||||
sender.SendCKErrorPacket(runPacket.CK, err.Error())
|
||||
return
|
||||
}
|
||||
if runPacket.Detached {
|
||||
cmd, err := shexec.RunCommandDetached(runPacket, sender)
|
||||
if err != nil {
|
||||
sender.SendCKErrorPacket(runPacket.CK, err.Error())
|
||||
return
|
||||
}
|
||||
cmd.WaitForCommand()
|
||||
} else {
|
||||
cmd, err := shexec.RunCommandSimple(runPacket, sender)
|
||||
if err != nil {
|
||||
sender.SendCKErrorPacket(runPacket.CK, fmt.Sprintf("error running command: %v", err))
|
||||
return
|
||||
}
|
||||
defer cmd.Close()
|
||||
startPacket := cmd.MakeCmdStartPacket()
|
||||
sender.SendPacket(startPacket)
|
||||
cmd.RunRemoteIOAndWait(packetParser, sender)
|
||||
return
|
||||
}
|
||||
defer cmd.Close()
|
||||
startPacket := cmd.MakeCmdStartPacket()
|
||||
sender.SendPacket(startPacket)
|
||||
cmd.RunRemoteIOAndWait(packetParser, sender)
|
||||
}
|
||||
|
||||
func detectOpenFds() ([]packet.RemoteFd, error) {
|
||||
@ -494,7 +494,10 @@ Sudo Options:
|
||||
|
||||
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
|
||||
securely through a high numbered fd to "sudo -S". See full documentation for more details.
|
||||
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.
|
||||
|
||||
Examples:
|
||||
# execute a python script remotely, with stdin still hooked up correctly
|
||||
|
@ -548,6 +548,14 @@ func ParseJsonPacket(jsonBuf []byte) (PacketType, error) {
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func sanitizeBytes(buf []byte) {
|
||||
for idx, b := range buf {
|
||||
if b >= 127 || (b < 32 && b != 10 && b != 13) {
|
||||
buf[idx] = '?'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendPacket(w io.Writer, packet PacketType) error {
|
||||
if packet == nil {
|
||||
return nil
|
||||
@ -564,7 +572,9 @@ func SendPacket(w io.Writer, packet PacketType) error {
|
||||
if GlobalDebug {
|
||||
fmt.Printf("SEND> %s\n", AsString(packet))
|
||||
}
|
||||
_, err = w.Write(outBuf.Bytes())
|
||||
outBytes := outBuf.Bytes()
|
||||
sanitizeBytes(outBytes)
|
||||
_, err = w.Write(outBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -687,6 +697,9 @@ func MakeRunPacketBuilder() *RunPacketBuilder {
|
||||
func (b *RunPacketBuilder) ProcessPacket(pk PacketType) (bool, *RunPacketType) {
|
||||
if pk.GetType() == RunPacketStr {
|
||||
runPacket := pk.(*RunPacketType)
|
||||
if len(runPacket.RunData) == 0 {
|
||||
return true, runPacket
|
||||
}
|
||||
b.RunMap[runPacket.CK] = runPacket
|
||||
return true, nil
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ func (c *serverFdContext) processDataPacket(pk *packet.DataPacketType) {
|
||||
}
|
||||
|
||||
func (m *MServer) MakeServerFdContext(ck base.CommandKey) *serverFdContext {
|
||||
m.Lock.Lock()
|
||||
defer m.Lock.Unlock()
|
||||
rtn := &serverFdContext{
|
||||
M: m,
|
||||
Lock: &sync.Mutex{},
|
||||
@ -62,6 +64,7 @@ func (m *MServer) MakeServerFdContext(ck base.CommandKey) *serverFdContext {
|
||||
CK: ck,
|
||||
Readers: make(map[int]*mpio.PacketReader),
|
||||
}
|
||||
m.FdContextMap[ck] = rtn
|
||||
return rtn
|
||||
}
|
||||
|
||||
@ -103,16 +106,20 @@ func (c *serverFdContext) GetReader(fdNum int) io.ReadCloser {
|
||||
return reader
|
||||
}
|
||||
|
||||
func (m *MServer) RemoveFdContext(ck base.CommandKey) {
|
||||
m.Lock.Lock()
|
||||
defer m.Lock.Unlock()
|
||||
delete(m.FdContextMap, ck)
|
||||
}
|
||||
|
||||
func (m *MServer) runCommand(runPacket *packet.RunPacketType) {
|
||||
if err := runPacket.CK.Validate("packet"); err != nil {
|
||||
m.Sender.SendErrorPacket(fmt.Sprintf("server run packets require valid ck: %s", err))
|
||||
return
|
||||
}
|
||||
fdContext := m.MakeServerFdContext(runPacket.CK)
|
||||
m.Lock.Lock()
|
||||
m.FdContextMap[runPacket.CK] = fdContext
|
||||
m.Lock.Unlock()
|
||||
go func() {
|
||||
defer m.RemoveFdContext(runPacket.CK)
|
||||
donePk, err := shexec.RunClientSSHCommandAndWait(runPacket, fdContext, shexec.SSHOpts{}, m, m.Debug)
|
||||
if donePk != nil {
|
||||
m.Sender.SendPacket(donePk)
|
||||
@ -143,7 +150,6 @@ func RunServer() (int, error) {
|
||||
server.MainInput = packet.MakePacketParser(os.Stdin)
|
||||
server.Sender = packet.MakePacketSender(os.Stdout)
|
||||
defer server.Close()
|
||||
defer fmt.Printf("runserver done\n")
|
||||
initPacket := packet.MakeInitPacket()
|
||||
initPacket.Version = base.MShellVersion
|
||||
server.Sender.SendPacket(initPacket)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
@ -164,20 +165,59 @@ func UpdateCmdEnv(cmd *exec.Cmd, envVars map[string]string) {
|
||||
cmd.Env = newEnv
|
||||
}
|
||||
|
||||
func MakeExecCmd(pk *packet.RunPacketType, cmdTty *os.File) *exec.Cmd {
|
||||
// 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) {
|
||||
ecmd := exec.Command("bash", "-c", pk.Command)
|
||||
UpdateCmdEnv(ecmd, pk.Env)
|
||||
if pk.Cwd != "" {
|
||||
ecmd.Dir = base.ExpandHomeDir(pk.Cwd)
|
||||
}
|
||||
ecmd.Stdin = cmdTty
|
||||
if !HasDupStdin(pk.Fds) {
|
||||
ecmd.Stdin = cmdTty
|
||||
}
|
||||
ecmd.Stdout = cmdTty
|
||||
ecmd.Stderr = cmdTty
|
||||
ecmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setsid: true,
|
||||
Setctty: true,
|
||||
}
|
||||
return ecmd
|
||||
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 && rfd.DupStdin {
|
||||
extraFiles[rfd.FdNum] = cmdTty
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("invalid fd %d passed to detached command", rfd.FdNum)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func MakeRunnerExec(ck base.CommandKey) (*exec.Cmd, error) {
|
||||
@ -269,19 +309,6 @@ func GetWinsize(p *packet.RunPacketType) *pty.Winsize {
|
||||
return &pty.Winsize{Rows: uint16(rows), Cols: uint16(cols)}
|
||||
}
|
||||
|
||||
// when err is nil, the command will have already been started
|
||||
func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
||||
err := ValidateRunPacket(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !pk.Detached {
|
||||
return runCommandSimple(pk, sender)
|
||||
} else {
|
||||
return runCommandDetached(pk, sender)
|
||||
}
|
||||
}
|
||||
|
||||
type SSHOpts struct {
|
||||
SSHHost string
|
||||
SSHOptsStr string
|
||||
@ -308,6 +335,25 @@ type ClientOpts struct {
|
||||
Detach bool
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
return opts.MakeSSHExecCmd(InstallCommand), nil
|
||||
}
|
||||
|
||||
func (opts SSHOpts) MakeMShellSingleCmd() (*exec.Cmd, error) {
|
||||
if opts.SSHHost == "" {
|
||||
execFile, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find local mshell executable: %w", err)
|
||||
}
|
||||
ecmd := exec.Command(execFile, "--single")
|
||||
return ecmd, nil
|
||||
}
|
||||
return opts.MakeSSHExecCmd(ClientCommand), nil
|
||||
}
|
||||
|
||||
func (opts SSHOpts) MakeSSHExecCmd(remoteCommand string) *exec.Cmd {
|
||||
remoteCommand = strings.TrimSpace(remoteCommand)
|
||||
if opts.SSHHost == "" {
|
||||
@ -474,7 +520,10 @@ func sendOptFile(input io.WriteCloser, optName string) error {
|
||||
|
||||
func RunInstallSSHCommand(opts *InstallOpts) error {
|
||||
tryDetect := opts.Detect
|
||||
ecmd := opts.SSHOpts.MakeSSHExecCmd(InstallCommand)
|
||||
ecmd, err := opts.SSHOpts.MakeSSHInstallCmd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inputWriter, err := ecmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating stdin pipe: %v", err)
|
||||
@ -548,7 +597,10 @@ func HasDupStdin(fds []packet.RemoteFd) bool {
|
||||
|
||||
func RunClientSSHCommandAndWait(runPacket *packet.RunPacketType, fdContext FdContext, sshOpts SSHOpts, upr packet.UnknownPacketReporter, debug bool) (*packet.CmdDonePacketType, error) {
|
||||
cmd := MakeShExec(runPacket.CK, upr)
|
||||
ecmd := sshOpts.MakeSSHExecCmd(ClientCommand)
|
||||
ecmd, err := sshOpts.MakeMShellSingleCmd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.Cmd = ecmd
|
||||
inputWriter, err := ecmd.StdinPipe()
|
||||
if err != nil {
|
||||
@ -646,6 +698,9 @@ func min(v1 int, v2 int) int {
|
||||
|
||||
func SendRunPacketAndRunData(sender *packet.PacketSender, runPacket *packet.RunPacketType) {
|
||||
sender.SendPacket(runPacket)
|
||||
if len(runPacket.RunData) == 0 {
|
||||
return
|
||||
}
|
||||
for _, runData := range runPacket.RunData {
|
||||
sendBuf := runData.Data
|
||||
for len(sendBuf) > 0 {
|
||||
@ -696,7 +751,7 @@ func (cmd *ShExecType) RunRemoteIOAndWait(packetParser *packet.PacketParser, sen
|
||||
sender.SendPacket(donePacket)
|
||||
}
|
||||
|
||||
func runCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
||||
func RunCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
||||
cmd := MakeShExec(pk.CK, nil)
|
||||
cmd.Cmd = exec.Command("bash", "-c", pk.Command)
|
||||
UpdateCmdEnv(cmd.Cmd, pk.Env)
|
||||
@ -767,7 +822,19 @@ func runCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender) (*S
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func runCommandDetached(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
||||
// 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)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
go func() {
|
||||
for range sigCh {
|
||||
// do nothing
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func RunCommandDetached(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
||||
fileNames, err := base.GetCommandFileNames(pk.CK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -788,11 +855,20 @@ func runCommandDetached(pk *packet.RunPacketType, sender *packet.PacketSender) (
|
||||
cmdTty.Close()
|
||||
}()
|
||||
rtn := MakeShExec(pk.CK, nil)
|
||||
ecmd := MakeExecCmd(pk, cmdTty)
|
||||
ecmd, err := MakeDetachedExecCmd(pk, cmdTty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
SetupSignalsForDetach()
|
||||
err = ecmd.Start()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("starting command: %w", err)
|
||||
}
|
||||
for _, fd := range ecmd.ExtraFiles {
|
||||
if fd != cmdTty {
|
||||
fd.Close()
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user