Преглед изворни кода

go.crypto/ssh: add terminal modes to ssh.RequestPty()

R=dave, agl
CC=golang-dev
https://golang.org/cl/6655046
Willem van der Schyff пре 13 година
родитељ
комит
2fccde5d00
3 измењених фајлова са 180 додато и 5 уклоњено
  1. 36 0
      ssh/example_test.go
  2. 70 5
      ssh/session.go
  3. 74 0
      ssh/test/session_test.go

+ 36 - 0
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)
+	}
+}

+ 70 - 5
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

+ 74 - 0
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")
+	}
+}