From 2fccde5d00389df8edd4f660470ac8f105c6280e Mon Sep 17 00:00:00 2001 From: Willem van der Schyff Date: Sun, 21 Oct 2012 16:16:55 +1100 Subject: [PATCH] go.crypto/ssh: add terminal modes to ssh.RequestPty() R=dave, agl CC=golang-dev https://golang.org/cl/6655046 --- ssh/example_test.go | 36 +++++++++++++++++++ ssh/session.go | 75 +++++++++++++++++++++++++++++++++++++--- ssh/test/session_test.go | 74 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 5 deletions(-) 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 @@ func ExampleClientConn_Listen() { 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 @@ var signals = map[Signal]int{ 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 @@ func (s *Session) Setenv(name, value string) error { 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 @@ type ptyRequestMsg struct { } // 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 @@ func (s *Session) RequestPty(term string, h, w int) error { 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 @@ package test import ( "bytes" + "code.google.com/p/go.crypto/ssh" "io" + "strings" "testing" + "time" ) func TestRunCommandSuccess(t *testing.T) { @@ -99,3 +102,74 @@ func TestFuncLargeRead(t *testing.T) { 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") + } +}