go.crypto/ssh: add terminal modes to ssh.RequestPty()
R=dave, agl
CC=golang-dev
https://golang.org/cl/6655046
diff --git a/ssh/example_test.go b/ssh/example_test.go
index c8a2de8..54f3610 100644
--- a/ssh/example_test.go
+++ b/ssh/example_test.go
@@ -149,3 +149,39 @@
fmt.Fprintf(resp, "Hello world!\n")
}))
}
+
+func ExampleSession_RequestPty() {
+ // Create client config
+ config := &ClientConfig{
+ User: "username",
+ Auth: []ClientAuth{
+ ClientAuthPassword(password("password")),
+ },
+ }
+ // Connect to ssh server
+ conn, err := Dial("tcp", "localhost:22", config)
+ if err != nil {
+ log.Fatalf("unable to connect: %s", err)
+ }
+ defer conn.Close()
+ // Create a session
+ session, err := conn.NewSession()
+ if err != nil {
+ log.Fatalf("unable to create session: %s", err)
+ }
+ defer session.Close()
+ // Set up terminal modes
+ modes := TerminalModes{
+ ECHO: 0, // disable echoing
+ TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
+ TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
+ }
+ // Request pseudo terminal
+ if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
+ log.Fatalf("request for pseudo terminal failed: %s", err)
+ }
+ // Start remote shell
+ if err := session.Shell(); err != nil {
+ log.Fatalf("failed to start shell: %s", err)
+ }
+}
diff --git a/ssh/session.go b/ssh/session.go
index 5d6487c..5eb3cc3 100644
--- a/ssh/session.go
+++ b/ssh/session.go
@@ -48,6 +48,68 @@
SIGTERM: 15,
}
+type TerminalModes map[uint8]uint32
+
+// POSIX terminal mode flags as listed in RFC 4254 Section 8.
+const (
+ tty_OP_END = 0
+ VINTR = 1
+ VQUIT = 2
+ VERASE = 3
+ VKILL = 4
+ VEOF = 5
+ VEOL = 6
+ VEOL2 = 7
+ VSTART = 8
+ VSTOP = 9
+ VSUSP = 10
+ VDSUSP = 11
+ VREPRINT = 12
+ VWERASE = 13
+ VLNEXT = 14
+ VFLUSH = 15
+ VSWTCH = 16
+ VSTATUS = 17
+ VDISCARD = 18
+ IGNPAR = 30
+ PARMRK = 31
+ INPCK = 32
+ ISTRIP = 33
+ INLCR = 34
+ IGNCR = 35
+ ICRNL = 36
+ IUCLC = 37
+ IXON = 38
+ IXANY = 39
+ IXOFF = 40
+ IMAXBEL = 41
+ ISIG = 50
+ ICANON = 51
+ XCASE = 52
+ ECHO = 53
+ ECHOE = 54
+ ECHOK = 55
+ ECHONL = 56
+ NOFLSH = 57
+ TOSTOP = 58
+ IEXTEN = 59
+ ECHOCTL = 60
+ ECHOKE = 61
+ PENDIN = 62
+ OPOST = 70
+ OLCUC = 71
+ ONLCR = 72
+ OCRNL = 73
+ ONOCR = 74
+ ONLRET = 75
+ CS7 = 90
+ CS8 = 91
+ PARENB = 92
+ PARODD = 93
+ TTY_OP_ISPEED = 128
+ TTY_OP_OSPEED = 129
+)
+
// A Session represents a connection to a remote command or shell.
type Session struct {
// Stdin specifies the remote process's standard input.
@@ -109,9 +171,6 @@
return s.waitForResponse()
}
-// An empty mode list, see RFC 4254 Section 8.
-var emptyModelist = "\x00"
-
// RFC 4254 Section 6.2.
type ptyRequestMsg struct {
PeersId uint32
@@ -126,7 +185,13 @@
}
// RequestPty requests the association of a pty with the session on the remote host.
-func (s *Session) RequestPty(term string, h, w int) error {
+func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
+ var tm []byte
+ for k, v := range termmodes {
+ tm = append(tm, k)
+ tm = appendU32(tm, v)
+ }
+ tm = append(tm, tty_OP_END)
req := ptyRequestMsg{
PeersId: s.remoteId,
Request: "pty-req",
@@ -136,7 +201,7 @@
Rows: uint32(h),
Width: uint32(w * 8),
Height: uint32(h * 8),
- Modelist: emptyModelist,
+ Modelist: string(tm),
}
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
return err
diff --git a/ssh/test/session_test.go b/ssh/test/session_test.go
index b326cab..91dea2a 100644
--- a/ssh/test/session_test.go
+++ b/ssh/test/session_test.go
@@ -10,8 +10,11 @@
import (
"bytes"
+ "code.google.com/p/go.crypto/ssh"
"io"
+ "strings"
"testing"
+ "time"
)
func TestRunCommandSuccess(t *testing.T) {
@@ -99,3 +102,74 @@
t.Fatalf("Expected %d bytes but read only %d from remote command", 2048, n)
}
}
+
+func TestInvalidTerminalMode(t *testing.T) {
+ server := newServer(t)
+ defer server.Shutdown()
+ conn := server.Dial()
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ if err = session.RequestPty("vt100", 80, 40, ssh.TerminalModes{255: 1984}); err == nil {
+ t.Fatalf("req-pty failed: successful request with invalid mode")
+ }
+}
+
+func TestValidTerminalMode(t *testing.T) {
+ server := newServer(t)
+ defer server.Shutdown()
+ conn := server.Dial()
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdout pipe: %s", err)
+ }
+
+ stdin, err := session.StdinPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdin pipe: %s", err)
+ }
+
+ tm := ssh.TerminalModes{ssh.ECHO: 0}
+ if err = session.RequestPty("xterm", 80, 40, tm); err != nil {
+ t.Fatalf("req-pty failed: %s", err)
+ }
+
+ err = session.Shell()
+ if err != nil {
+ t.Fatalf("session failed: %s", err)
+ }
+
+ stdin.Write([]byte("stty -a && exit\n"))
+
+ rc := make(chan string)
+ go func() {
+ var buf bytes.Buffer
+ if _, err := io.Copy(&buf, stdout); err != nil {
+ t.Fatalf("reading failed: %s", err)
+ }
+ rc <- buf.String()
+ }()
+
+ result := ""
+ select {
+ case result = <-rc:
+ case <-time.After(1 * time.Second):
+ }
+
+ if !strings.Contains(result, "-echo") {
+ t.Fatalf("terminal mode failure: expected '%s'", "-echo")
+ }
+}