@@ -16,61 +16,134 @@ func generateSigner() (ssh.Signer, error) {
1616 return ssh .NewSignerFromKey (key )
1717}
1818
19- func parsePtyRequest (s []byte ) (pty Pty , ok bool ) {
20- term , s , ok := parseString (s )
19+ func parsePtyRequest (payload []byte ) (pty Pty , ok bool ) {
20+ // From https://datatracker.ietf.org/doc/html/rfc4254
21+ // 6.2. Requesting a Pseudo-Terminal
22+ // A pseudo-terminal can be allocated for the session by sending the
23+ // following message.
24+ // byte SSH_MSG_CHANNEL_REQUEST
25+ // uint32 recipient channel
26+ // string "pty-req"
27+ // boolean want_reply
28+ // string TERM environment variable value (e.g., vt100)
29+ // uint32 terminal width, characters (e.g., 80)
30+ // uint32 terminal height, rows (e.g., 24)
31+ // uint32 terminal width, pixels (e.g., 640)
32+ // uint32 terminal height, pixels (e.g., 480)
33+ // string encoded terminal modes
34+
35+ // The payload starts from the TERM variable.
36+ term , rem , ok := parseString (payload )
2137 if ! ok {
2238 return
2339 }
24- width32 , s , ok := parseUint32 ( s )
40+ win , rem , ok := parseWindow ( rem )
2541 if ! ok {
2642 return
2743 }
28- height32 , _ , ok := parseUint32 ( s )
44+ modes , ok := parseTerminalModes ( rem )
2945 if ! ok {
3046 return
3147 }
3248 pty = Pty {
33- Term : term ,
34- Window : Window {
35- Width : int (width32 ),
36- Height : int (height32 ),
37- },
49+ Term : term ,
50+ Window : win ,
51+ Modes : modes ,
3852 }
3953 return
4054}
4155
42- func parseWinchRequest (s []byte ) (win Window , ok bool ) {
43- width32 , s , ok := parseUint32 (s )
44- if width32 < 1 {
45- ok = false
56+ func parseTerminalModes (in []byte ) (modes ssh.TerminalModes , ok bool ) {
57+ // From https://datatracker.ietf.org/doc/html/rfc4254
58+ // 8. Encoding of Terminal Modes
59+ //
60+ // All 'encoded terminal modes' (as passed in a pty request) are encoded
61+ // into a byte stream. It is intended that the coding be portable
62+ // across different environments. The stream consists of opcode-
63+ // argument pairs wherein the opcode is a byte value. Opcodes 1 to 159
64+ // have a single uint32 argument. Opcodes 160 to 255 are not yet
65+ // defined, and cause parsing to stop (they should only be used after
66+ // any other data). The stream is terminated by opcode TTY_OP_END
67+ // (0x00).
68+ //
69+ // The client SHOULD put any modes it knows about in the stream, and the
70+ // server MAY ignore any modes it does not know about. This allows some
71+ // degree of machine-independence, at least between systems that use a
72+ // POSIX-like tty interface. The protocol can support other systems as
73+ // well, but the client may need to fill reasonable values for a number
74+ // of parameters so the server pty gets set to a reasonable mode (the
75+ // server leaves all unspecified mode bits in their default values, and
76+ // only some combinations make sense).
77+ _ , rem , ok := parseUint32 (in )
78+ if ! ok {
79+ return
80+ }
81+ const ttyOpEnd = 0
82+ for len (rem ) > 0 {
83+ if modes == nil {
84+ modes = make (ssh.TerminalModes )
85+ }
86+ code := uint8 (rem [0 ])
87+ rem = rem [1 :]
88+ if code == ttyOpEnd || code > 160 {
89+ break
90+ }
91+ var val uint32
92+ val , rem , ok = parseUint32 (rem )
93+ if ! ok {
94+ return
95+ }
96+ modes [code ] = val
97+ }
98+ ok = true
99+ return
100+ }
101+
102+ func parseWindow (s []byte ) (win Window , rem []byte , ok bool ) {
103+ // 6.7. Window Dimension Change Message
104+ // When the window (terminal) size changes on the client side, it MAY
105+ // send a message to the other side to inform it of the new dimensions.
106+
107+ // byte SSH_MSG_CHANNEL_REQUEST
108+ // uint32 recipient channel
109+ // string "window-change"
110+ // boolean FALSE
111+ // uint32 terminal width, columns
112+ // uint32 terminal height, rows
113+ // uint32 terminal width, pixels
114+ // uint32 terminal height, pixels
115+ wCols , rem , ok := parseUint32 (s )
116+ if ! ok {
117+ return
46118 }
119+ hRows , rem , ok := parseUint32 (rem )
47120 if ! ok {
48121 return
49122 }
50- height32 , _ , ok := parseUint32 (s )
51- if height32 < 1 {
52- ok = false
123+ wPixels , rem , ok := parseUint32 (rem )
124+ if ! ok {
125+ return
53126 }
127+ hPixels , rem , ok := parseUint32 (rem )
54128 if ! ok {
55129 return
56130 }
57131 win = Window {
58- Width : int (width32 ),
59- Height : int (height32 ),
132+ Width : int (wCols ),
133+ Height : int (hRows ),
134+ WidthPixels : int (wPixels ),
135+ HeightPixels : int (hPixels ),
60136 }
61137 return
62138}
63139
64- func parseString (in []byte ) (out string , rest []byte , ok bool ) {
65- if len (in ) < 4 {
66- return
67- }
68- length := binary .BigEndian .Uint32 (in )
69- if uint32 (len (in )) < 4 + length {
140+ func parseString (in []byte ) (out string , rem []byte , ok bool ) {
141+ length , rem , ok := parseUint32 (in )
142+ if uint32 (len (rem )) < length || ! ok {
143+ ok = false
70144 return
71145 }
72- out = string (in [4 : 4 + length ])
73- rest = in [4 + length :]
146+ out , rem = string (rem [:length ]), rem [length :]
74147 ok = true
75148 return
76149}
0 commit comments