//go:build !windows package sshd import ( "errors" "fmt" "io" "os" "os/exec" "github.com/creack/pty" "golang.org/x/crypto/ssh" ) const ( ptySupported = true runSupported = true ) // run starts the user's shell (or, for exec, runs cmd through it) and returns its exit code. // With a pty request it gets a terminal, otherwise plain pipes. func run(ch ssh.Channel, cmd string, p *ptyReq, win <-chan [2]uint32) int { shell := os.Getenv("SHELL") if shell == "" { shell = "/bin/sh" } var c *exec.Cmd if cmd == "" { c = exec.Command(shell, "-l") } else { c = exec.Command(shell, "-c", cmd) } c.Env = os.Environ() if p != nil { term := p.term if term == "" { term = "xterm" } c.Env = append(c.Env, "TERM="+term) return runPty(ch, c, p, win) } in, err := c.StdinPipe() if err != nil { fmt.Fprintf(ch.Stderr(), "bamboo: %v\r\n", err) return 255 } go func() { io.Copy(in, ch); in.Close() }() c.Stdout = ch c.Stderr = ch.Stderr() return exitCode(c.Run(), c) } func runPty(ch ssh.Channel, c *exec.Cmd, p *ptyReq, win <-chan [2]uint32) int { f, err := pty.StartWithSize(c, &pty.Winsize{Cols: uint16(p.cols), Rows: uint16(p.rows)}) if err != nil { fmt.Fprintf(ch.Stderr(), "bamboo: %v\r\n", err) return 255 } defer f.Close() done := make(chan struct{}) defer close(done) go func() { for { select { case w := <-win: pty.Setsize(f, &pty.Winsize{Cols: uint16(w[0]), Rows: uint16(w[1])}) case <-done: return } } }() go func() { io.Copy(f, ch) }() io.Copy(ch, f) // ends with an error when the shell exits and the terminal closes return exitCode(c.Wait(), c) } func exitCode(err error, c *exec.Cmd) int { if err == nil { return 0 } var ee *exec.ExitError if errors.As(err, &ee) && ee.ExitCode() >= 0 { return ee.ExitCode() } return 255 }