diff --git a/session_test.go b/session_test.go index 786a661..7e8e129 100644 --- a/session_test.go +++ b/session_test.go @@ -199,6 +199,9 @@ func TestPty(t *testing.T) { term := "xterm" winWidth := 40 winHeight := 80 + TerminalModes := make(gossh.TerminalModes) + ttyOPOSPEED := uint32(38400) + TerminalModes[gossh.TTY_OP_OSPEED] = ttyOPOSPEED done := make(chan bool) session, _, cleanup := newTestSession(t, &Server{ Handler: func(s Session) { @@ -215,11 +218,15 @@ func TestPty(t *testing.T) { if ptyReq.Window.Height != winHeight { t.Fatalf("expected window height %#v but got %#v", winHeight, ptyReq.Window.Height) } + mode := ptyReq.TerminalModes[gossh.TTY_OP_OSPEED] + if ptyReq.TerminalModes[gossh.TTY_OP_OSPEED] != ttyOPOSPEED { + t.Fatalf("expected mode %#v but got %#v", ttyOPOSPEED, mode) + } close(done) }, }, nil) defer cleanup() - if err := session.RequestPty(term, winHeight, winWidth, gossh.TerminalModes{}); err != nil { + if err := session.RequestPty(term, winHeight, winWidth, TerminalModes); err != nil { t.Fatalf("expected nil but got %v", err) } if err := session.Shell(); err != nil { diff --git a/ssh.go b/ssh.go index 9673ac3..ffe3e94 100644 --- a/ssh.go +++ b/ssh.go @@ -72,9 +72,9 @@ type Window struct { // Pty represents a PTY request and configuration. type Pty struct { - Term string - Window Window - // HELP WANTED: terminal modes! + Term string + Window Window + TerminalModes gossh.TerminalModes } // Serve accepts incoming SSH connections on the listener l, creating a new diff --git a/util.go b/util.go index 015a44e..a41e7cd 100644 --- a/util.go +++ b/util.go @@ -8,6 +8,19 @@ import ( "golang.org/x/crypto/ssh" ) +type ptyRequestMsg struct { + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string +} + +const ( + ttyOPEND = 0 +) + func generateSigner() (ssh.Signer, error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { @@ -17,28 +30,91 @@ func generateSigner() (ssh.Signer, error) { } func parsePtyRequest(s []byte) (pty Pty, ok bool) { - term, s, ok := parseString(s) - if !ok { - return - } - width32, s, ok := parseUint32(s) - if !ok { + reqMsg := &ptyRequestMsg{} + err := ssh.Unmarshal(s, reqMsg) + if err != nil { return } - height32, _, ok := parseUint32(s) + + modes := []byte(reqMsg.Modelist) + terminalModes, ok := parseTerminalModes(modes) if !ok { return } + pty = Pty{ - Term: term, + Term: reqMsg.Term, Window: Window{ - Width: int(width32), - Height: int(height32), + Width: int(reqMsg.Columns), + Height: int(reqMsg.Rows), }, + TerminalModes: terminalModes, } return } +func makeTerminalModes(terminalModes ssh.TerminalModes) string { + var tm []byte + for k, v := range terminalModes { + kv := struct { + Key byte + Val uint32 + }{k, v} + + tm = append(tm, ssh.Marshal(&kv)...) + } + tm = append(tm, ttyOPEND) + return string(tm) +} + +func parseTerminalModes(s []byte) (terminalModes ssh.TerminalModes, ok bool) { + mode := struct { + Key uint8 + Val uint32 + }{} + + terminalModes = make(ssh.TerminalModes, 0) + for { + if len(s) < 1 { + ok = true + return + } + + opcode := s[0] + switch opcode { + case ttyOPEND: + ok = true + return + default: + /* + * SSH2: + * Opcodes 1 to 159 are defined to have a uint32 + * argument. + * Opcodes 160 to 255 are undefined and cause parsing + * to stop. + */ + if opcode > 0 && opcode < 160 { + if len(s) < 5 { + // parse failed + return + } + + b := s[:5] + if err := ssh.Unmarshal(b, &mode); err != nil { + return + } + + terminalModes[mode.Key] = mode.Val + s = s[6:] + + } else { + ok = true + return + } + } + } +} + func parseWinchRequest(s []byte) (win Window, ok bool) { width32, s, ok := parseUint32(s) if width32 < 1 {