blob: 94589479e2f79285d35a7819809793d97794e814 [file] [log] [blame]
package impl
import (
"github.com/kr/pty"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"syscall"
"veyron/examples/tunnel"
"veyron/examples/tunnel/lib"
"veyron2/ipc"
"veyron2/vlog"
)
// T implements tunnel.TunnelService
type T struct {
}
func (t *T) Forward(ctx ipc.ServerContext, network, address string, stream tunnel.TunnelServiceForwardStream) error {
conn, err := net.Dial(network, address)
if err != nil {
return err
}
name := fmt.Sprintf("RemoteID:%v LocalAddr:%v RemoteAddr:%v", ctx.RemoteID(), conn.LocalAddr(), conn.RemoteAddr())
vlog.Infof("TUNNEL START: %v", name)
err = lib.Forward(conn, stream)
vlog.Infof("TUNNEL END : %v (%v)", name, err)
return err
}
func (t *T) Shell(ctx ipc.ServerContext, command string, shellOpts tunnel.ShellOpts, stream tunnel.TunnelServiceShellStream) (int32, error) {
vlog.Infof("SHELL START for %v: %q", ctx.RemoteID(), command)
const nonShellErrorCode = 255
shell, err := findShell()
if err != nil {
return nonShellErrorCode, err
}
var c *exec.Cmd
// An empty command means that we need an interactive shell.
if len(command) == 0 {
c = exec.Command(shell, "-i")
sendMotd(stream)
} else {
c = exec.Command(shell, "-c", command)
}
c.Env = []string{
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
fmt.Sprintf("VEYRON_LOCAL_IDENTITY=%s", ctx.LocalID()),
fmt.Sprintf("VEYRON_REMOTE_IDENTITY=%s", ctx.RemoteID()),
}
c.Env = append(c.Env, shellOpts.Environment...)
vlog.Infof("Shell environment: %v", c.Env)
c.Dir = os.Getenv("HOME")
vlog.Infof("Shell CWD: %v", c.Dir)
var (
stdin io.WriteCloser // We write to stdin.
stdout, stderr io.ReadCloser // We read from stdout and stderr.
ptyFd uintptr // File descriptor for pty.
)
if shellOpts.UsePty {
f, err := pty.Start(c)
if err != nil {
return nonShellErrorCode, err
}
stdin = f
stdout = f
stderr = nil
ptyFd = f.Fd()
defer f.Close()
setWindowSize(ptyFd, shellOpts.Rows, shellOpts.Cols)
} else {
var err error
if stdin, err = c.StdinPipe(); err != nil {
return nonShellErrorCode, err
}
defer stdin.Close()
if stdout, err = c.StdoutPipe(); err != nil {
return nonShellErrorCode, err
}
defer stdout.Close()
if stderr, err = c.StderrPipe(); err != nil {
return nonShellErrorCode, err
}
defer stderr.Close()
if err = c.Start(); err != nil {
vlog.Infof("Cmd.Start failed: %v", err)
return nonShellErrorCode, err
}
}
defer c.Process.Kill()
ferr := runIOManager(stdin, stdout, stderr, ptyFd, stream)
vlog.Infof("SHELL END for %v: %q (%v)", ctx.RemoteID(), command, ferr)
// Check the exit status.
var status syscall.WaitStatus
if _, err := syscall.Wait4(c.Process.Pid, &status, syscall.WNOHANG, nil); err != nil {
return nonShellErrorCode, err
}
if status.Signaled() {
return int32(status), fmt.Errorf("process killed by signal %d (%v)", int(status.Signal()), status.Signal())
}
if status.Exited() {
if status.ExitStatus() == 0 {
return 0, nil
}
return int32(status.ExitStatus()), fmt.Errorf("process exited with exit status %d", status.ExitStatus())
}
// The process has not exited. Use the error from ForwardStdIO.
return nonShellErrorCode, ferr
}
// findShell returns the path to the first usable shell binary.
func findShell() (string, error) {
shells := []string{"/bin/bash", "/bin/sh"}
for _, s := range shells {
if _, err := os.Stat(s); err == nil {
return s, nil
}
}
return "", errors.New("could not find any shell binary")
}
// sendMotd sends the content of the MOTD file to the stream, if it exists.
func sendMotd(s tunnel.TunnelServiceShellStream) {
data, err := ioutil.ReadFile("/etc/motd")
if err != nil {
// No MOTD. That's OK.
return
}
packet := tunnel.ServerShellPacket{Stdout: []byte(data)}
if err = s.Send(packet); err != nil {
vlog.Infof("Send failed: %v", err)
}
}
func setWindowSize(fd uintptr, row, col uint32) {
ws := lib.Winsize{Row: uint16(row), Col: uint16(col)}
if err := lib.SetWindowSize(fd, ws); err != nil {
vlog.Infof("Failed to set window size: %v", err)
}
}