implement sudo dance allowing passing the sudo password on stdin with sudo -S, and passing a different stdin fd to the command

This commit is contained in:
sawka 2022-06-26 01:41:58 -07:00
parent e8ae01efae
commit 222deff0db
4 changed files with 149 additions and 18 deletions

View File

@ -306,6 +306,33 @@ func parseClientOpts() (*shexec.ClientOpts, error) {
opts.Debug = true
continue
}
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
opts.SudoWithPass = true
opts.SudoPw = iter.Next()
continue
}
if argStr == "--sudo-with-passfile" {
if !iter.HasNext() {
return nil, fmt.Errorf("'--sudo-with-passfile [file]', missing file")
}
opts.Sudo = true
opts.SudoWithPass = true
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)
}
opts.SudoPw = string(contents)
continue
}
}
if opts.IsSSH {
// parse SSH opts
@ -339,6 +366,9 @@ func handleClient() (int, error) {
if err != nil {
return 1, fmt.Errorf("parsing opts: %w", err)
}
if opts.Debug {
packet.GlobalDebug = true
}
if !opts.IsSSH {
return 1, fmt.Errorf("when running in client mode '--ssh' option must be present")
}

View File

@ -100,6 +100,18 @@ func (m *Multiplexer) MakeWriterPipe(fdNum int) (*os.File, error) {
return pr, nil
}
func (m *Multiplexer) MakeStringFdReader(fdNum int, contents string) error {
pw, err := m.MakeReaderPipe(fdNum)
if err != nil {
return err
}
go func() {
pw.Write([]byte(contents))
pw.Close()
}()
return nil
}
func (m *Multiplexer) MakeRawFdReader(fdNum int, fd *os.File, shouldClose bool) {
m.Lock.Lock()
defer m.Lock.Unlock()

View File

@ -385,9 +385,11 @@ type TermSize struct {
}
type RemoteFd struct {
FdNum int `json:"fdnum"`
Read bool `json:"read"`
Write bool `json:"write"`
FdNum int `json:"fdnum"`
Read bool `json:"read"`
Write bool `json:"write"`
Content string `json:"-"`
DupStdin bool `json:"-"`
}
type RunPacketType struct {

View File

@ -30,6 +30,12 @@ const MaxCols = 1024
const MaxFdNum = 1023
const FirstExtraFilesFdNum = 3
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"`
type ShExecType struct {
Lock *sync.Mutex
StartTs time.Time
@ -213,21 +219,87 @@ func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecT
}
type ClientOpts struct {
IsSSH bool
SSHOptsTerm bool
SSHOpts []string
Command string
Fds []packet.RemoteFd
Cwd string
Debug bool
IsSSH bool
SSHOptsTerm bool
SSHOpts []string
Command string
Fds []packet.RemoteFd
Cwd string
Debug bool
Sudo bool
SudoWithPass bool
SudoPw string
CommandStdinFdNum int
}
func (opts *ClientOpts) MakeRunPacket() *packet.RunPacketType {
func (opts *ClientOpts) MakeRunPacket() (*packet.RunPacketType, error) {
runPacket := packet.MakeRunPacket()
runPacket.Command = opts.Command
runPacket.Cwd = opts.Cwd
runPacket.Fds = opts.Fds
return runPacket
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
}
func ValidateRemoteFds(rfds []packet.RemoteFd) error {
@ -261,11 +333,14 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
if err != nil {
return nil, err
}
runPacket, err := opts.MakeRunPacket() // modifies opts
if err != nil {
return nil, err
}
cmd := MakeShExec("", "")
sshRemoteCommand := `PATH=$PATH:~/.mshell; mshell --remote`
var fullSshOpts []string
fullSshOpts = append(fullSshOpts, opts.SSHOpts...)
fullSshOpts = append(fullSshOpts, sshRemoteCommand)
fullSshOpts = append(fullSshOpts, SSHRemoteCommand)
ecmd := exec.Command("ssh", fullSshOpts...)
cmd.Cmd = ecmd
inputWriter, err := ecmd.StdinPipe()
@ -280,10 +355,23 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
if err != nil {
return nil, fmt.Errorf("creating stderr pipe: %v", err)
}
cmd.Multiplexer.MakeRawFdReader(0, os.Stdin, false)
if !opts.SudoWithPass {
cmd.Multiplexer.MakeRawFdReader(0, os.Stdin, false)
}
cmd.Multiplexer.MakeRawFdWriter(1, os.Stdout, false)
cmd.Multiplexer.MakeRawFdWriter(2, os.Stderr, false)
for _, rfd := range opts.Fds {
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
}
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)
@ -322,7 +410,6 @@ func RunClientSSHCommandAndWait(opts *ClientOpts) (*packet.CmdDonePacketType, er
if !versionOk {
return nil, fmt.Errorf("did not receive version from remote mshell")
}
runPacket := opts.MakeRunPacket()
sender.SendPacket(runPacket)
if opts.Debug {
cmd.Multiplexer.Debug = true