ssh/sudo.go

71 lines
1.8 KiB
Go

package ssh
import (
"bytes"
"io"
"sync"
)
// This is the phrase that tells us sudo is looking for a password via stdin
const sudoPwPrompt = "sudo_password"
// sudoWriter is used to both combine stdout and stderr as well as
// look for a password request from sudo.
type sudoWriter struct {
b bytes.Buffer
pw string // The password to pass to sudo (if requested)
stdin io.Writer // The writer from the ssh session
m sync.Mutex
}
func (w *sudoWriter) Write(p []byte) (int, error) {
// If we get the sudo password prompt phrase send the password via stdin
// and don't write it to the buffer.
if string(p) == sudoPwPrompt {
w.stdin.Write([]byte(w.pw + "\n"))
w.pw = "" // We don't need the password anymore so reset the string
return len(p), nil
}
w.m.Lock()
defer w.m.Unlock()
return w.b.Write(p)
}
// ExecSu Execute cmd via sudo. Do not include the sudo command in
// the cmd string. For example: Client.ExecSudo("uptime", "password").
// If you are using passwordless sudo you can use the regular Exec()
// function.
func (c *Client) ExecSu(cmd, passwd string) ([]byte, error) {
session, err := c.SSHClient.NewSession()
if err != nil {
return nil, err
}
defer session.Close()
// -n run non interactively
// -p specify the prompt. We do this to know that sudo is asking for a passwd
// -S Writes the prompt to StdErr and reads the password from StdIn
cmd = "sudo -p " + sudoPwPrompt + " -S " + cmd
// Use the sudoRW struct to handle the interaction with sudo and capture the
// output of the command
w := &sudoWriter{
pw: passwd,
}
w.stdin, err = session.StdinPipe()
if err != nil {
return nil, err
}
// Combine stdout, stderr to the same writer which also looks for the sudo
// password prompt
session.Stdout = w
session.Stderr = w
err = session.Run(cmd)
return w.b.Bytes(), err
}