blob: 5cedeeb75e733aaefad76adb143cb2ae8eea660b [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// Package telnet provides simple interface for interacting with Telnet
2// connection.
3package telnet
4
5import (
6 "bufio"
7 "bytes"
8 "fmt"
9 "net"
10 "time"
11 "unicode"
12)
13
14const (
15 CR = byte('\r')
16 LF = byte('\n')
17)
18
19const (
20 cmdSE = 240
21 cmdNOP = 241
22 cmdData = 242
23
24 cmdBreak = 243
25 cmdGA = 249
26 cmdSB = 250
27
28 cmdWill = 251
29 cmdWont = 252
30 cmdDo = 253
31 cmdDont = 254
32
33 cmdIAC = 255
34)
35
36const (
37 optEcho = 1
38 optSuppressGoAhead = 3
39 // optTerminalType = 24
40 optNAWS = 31
41)
42
43// Conn implements net.Conn interface for Telnet protocol plus some set of
44// Telnet specific methods.
45type Conn struct {
46 net.Conn
47 r *bufio.Reader
48
49 unixWriteMode bool
50
51 cliSuppressGoAhead bool
52 cliEcho bool
53}
54
55func NewConn(conn net.Conn) (*Conn, error) {
56 c := Conn{
57 Conn: conn,
58 r: bufio.NewReaderSize(conn, 256),
59 }
60 return &c, nil
61}
62
63func Dial(network, addr string) (*Conn, error) {
64 conn, err := net.Dial(network, addr)
65 if err != nil {
66 return nil, err
67 }
68 return NewConn(conn)
69}
70
71func DialTimeout(network, addr string, timeout time.Duration) (*Conn, error) {
72 conn, err := net.DialTimeout(network, addr, timeout)
73 if err != nil {
74 return nil, err
75 }
76 return NewConn(conn)
77}
78
79// SetUnixWriteMode sets flag that applies only to the Write method.
80// If set, Write converts any '\n' (LF) to '\r\n' (CR LF).
81func (c *Conn) SetUnixWriteMode(uwm bool) {
82 c.unixWriteMode = uwm
83}
84
85func (c *Conn) do(option byte) error {
86 //log.Println("do:", option)
87 _, err := c.Conn.Write([]byte{cmdIAC, cmdDo, option})
88 return err
89}
90
91func (c *Conn) dont(option byte) error {
92 //log.Println("dont:", option)
93 _, err := c.Conn.Write([]byte{cmdIAC, cmdDont, option})
94 return err
95}
96
97func (c *Conn) will(option byte) error {
98 //log.Println("will:", option)
99 _, err := c.Conn.Write([]byte{cmdIAC, cmdWill, option})
100 return err
101}
102
103func (c *Conn) wont(option byte) error {
104 //log.Println("wont:", option)
105 _, err := c.Conn.Write([]byte{cmdIAC, cmdWont, option})
106 return err
107}
108
109func (c *Conn) sub(opt byte, data ...byte) error {
110 if _, err := c.Conn.Write([]byte{cmdIAC, cmdSB, opt}); err != nil {
111 return err
112 }
113 if _, err := c.Conn.Write(data); err != nil {
114 return err
115 }
116 _, err := c.Conn.Write([]byte{cmdIAC, cmdSE})
117 return err
118}
119
120func (c *Conn) deny(cmd, opt byte) (err error) {
121 switch cmd {
122 case cmdDo:
123 err = c.wont(opt)
124 case cmdDont:
125 // nop
126 case cmdWill, cmdWont:
127 err = c.dont(opt)
128 }
129 return
130}
131
132func (c *Conn) skipSubneg() error {
133 for {
134 if b, err := c.r.ReadByte(); err != nil {
135 return err
136 } else if b == cmdIAC {
137 if b, err = c.r.ReadByte(); err != nil {
138 return err
139 } else if b == cmdSE {
140 return nil
141 }
142 }
143 }
144}
145
146func (c *Conn) cmd(cmd byte) error {
147 switch cmd {
148 case cmdGA:
149 return nil
150 case cmdDo, cmdDont, cmdWill, cmdWont:
151 // Process cmd after this switch.
152 case cmdSB:
153 return c.skipSubneg()
154 default:
155 return fmt.Errorf("unknown command: %d", cmd)
156 }
157 // Read an option
158 o, err := c.r.ReadByte()
159 if err != nil {
160 return err
161 }
162 //log.Println("received cmd:", cmd, o)
163 switch o {
164 case optEcho:
165 // Accept any echo configuration.
166 switch cmd {
167 case cmdDo:
168 if !c.cliEcho {
169 c.cliEcho = true
170 err = c.will(o)
171 }
172 case cmdDont:
173 if c.cliEcho {
174 c.cliEcho = false
175 err = c.wont(o)
176 }
177 case cmdWill:
178 if !c.cliEcho {
179 c.cliEcho = true
180 err = c.do(o)
181 }
182 case cmdWont:
183 if c.cliEcho {
184 c.cliEcho = false
185 err = c.dont(o)
186 }
187 }
188 case optSuppressGoAhead:
189 // We don't use GA so can allways accept every configuration
190 switch cmd {
191 case cmdDo:
192 if !c.cliSuppressGoAhead {
193 c.cliSuppressGoAhead = true
194 err = c.will(o)
195 }
196 case cmdDont:
197 if c.cliSuppressGoAhead {
198 c.cliSuppressGoAhead = false
199 err = c.wont(o)
200 }
201 case cmdWill:
202 if !c.cliSuppressGoAhead {
203 c.cliSuppressGoAhead = true
204 err = c.do(o)
205 }
206 case cmdWont:
207 if c.cliSuppressGoAhead {
208 c.cliSuppressGoAhead = false
209 err = c.dont(o)
210 }
211 }
212 case optNAWS:
213 if cmd != cmdDo {
214 err = c.deny(cmd, o)
215 break
216 }
217 if err = c.will(o); err != nil {
218 break
219 }
220 // Reply with max window size: 65535x65535
221 err = c.sub(o, 255, 255, 255, 255)
222 default:
223 // Deny any other option
224 err = c.deny(cmd, o)
225 }
226 return err
227}
228
229func (c *Conn) tryReadByte() (b byte, retry bool, err error) {
230 b, err = c.r.ReadByte()
231 if err != nil || b != cmdIAC {
232 return
233 }
234 b, err = c.r.ReadByte()
235 if err != nil {
236 return
237 }
238 if b != cmdIAC {
239 err = c.cmd(b)
240 if err != nil {
241 return
242 }
243 retry = true
244 }
245 return
246}
247
248// SetEcho tries to enable/disable echo on server side. Typically telnet
249// servers doesn't support this.
250func (c *Conn) SetEcho(echo bool) error {
251 if echo {
252 return c.do(optEcho)
253 }
254 return c.dont(optEcho)
255}
256
257// ReadByte works like bufio.ReadByte
258func (c *Conn) ReadByte() (b byte, err error) {
259 retry := true
260 for retry && err == nil {
261 b, retry, err = c.tryReadByte()
262 }
263 return
264}
265
266// ReadRune works like bufio.ReadRune
267func (c *Conn) ReadRune() (r rune, size int, err error) {
268loop:
269 r, size, err = c.r.ReadRune()
270 if err != nil {
271 return
272 }
273 if r != unicode.ReplacementChar || size != 1 {
274 // Properly readed rune
275 return
276 }
277 // Bad rune
278 err = c.r.UnreadRune()
279 if err != nil {
280 return
281 }
282 // Read telnet command or escaped IAC
283 _, retry, err := c.tryReadByte()
284 if err != nil {
285 return
286 }
287 if retry {
288 // This bad rune was a begining of telnet command. Try read next rune.
289 goto loop
290 }
291 // Return escaped IAC as unicode.ReplacementChar
292 return
293}
294
295// Read is for implement an io.Reader interface
296func (c *Conn) Read(buf []byte) (int, error) {
297 var n int
298 for n < len(buf) {
299 b, retry, err := c.tryReadByte()
300 if err != nil {
301 return n, err
302 }
303 if !retry {
304 buf[n] = b
305 n++
306 }
307 if n > 0 && c.r.Buffered() == 0 {
308 // Don't block if can't return more data.
309 return n, err
310 }
311 }
312 return n, nil
313}
314
315// ReadBytes works like bufio.ReadBytes
316func (c *Conn) ReadBytes(delim byte) ([]byte, error) {
317 var line []byte
318 for {
319 b, err := c.ReadByte()
320 if err != nil {
321 return nil, err
322 }
323 line = append(line, b)
324 if b == delim {
325 break
326 }
327 }
328 return line, nil
329}
330
331// SkipBytes works like ReadBytes but skips all read data.
332func (c *Conn) SkipBytes(delim byte) error {
333 for {
334 b, err := c.ReadByte()
335 if err != nil {
336 return err
337 }
338 if b == delim {
339 break
340 }
341 }
342 return nil
343}
344
345// ReadString works like bufio.ReadString
346func (c *Conn) ReadString(delim byte) (string, error) {
347 bytes, err := c.ReadBytes(delim)
348 return string(bytes), err
349}
350
351func (c *Conn) readUntil(read bool, delims ...string) ([]byte, int, error) {
352 if len(delims) == 0 {
353 return nil, 0, nil
354 }
355 p := make([]string, len(delims))
356 for i, s := range delims {
357 if len(s) == 0 {
358 return nil, 0, nil
359 }
360 p[i] = s
361 }
362 var line []byte
363 for {
364 b, err := c.ReadByte()
365 if err != nil {
366 return nil, 0, err
367 }
368 if read {
369 line = append(line, b)
370 }
371 for i, s := range p {
372 if s[0] == b {
373 if len(s) == 1 {
374 return line, i, nil
375 }
376 p[i] = s[1:]
377 } else {
378 p[i] = delims[i]
379 }
380 }
381 }
382 panic(nil)
383}
384
385// ReadUntilIndex reads from connection until one of delimiters occurs. Returns
386// read data and an index of delimiter or error.
387func (c *Conn) ReadUntilIndex(delims ...string) ([]byte, int, error) {
388 return c.readUntil(true, delims...)
389}
390
391// ReadUntil works like ReadUntilIndex but don't return a delimiter index.
392func (c *Conn) ReadUntil(delims ...string) ([]byte, error) {
393 d, _, err := c.readUntil(true, delims...)
394 return d, err
395}
396
397// SkipUntilIndex works like ReadUntilIndex but skips all read data.
398func (c *Conn) SkipUntilIndex(delims ...string) (int, error) {
399 _, i, err := c.readUntil(false, delims...)
400 return i, err
401}
402
403// SkipUntil works like ReadUntil but skips all read data.
404func (c *Conn) SkipUntil(delims ...string) error {
405 _, _, err := c.readUntil(false, delims...)
406 return err
407}
408
409// Write is for implement an io.Writer interface
410func (c *Conn) Write(buf []byte) (int, error) {
411 search := "\xff"
412 if c.unixWriteMode {
413 search = "\xff\n"
414 }
415 var (
416 n int
417 err error
418 )
419 for len(buf) > 0 {
420 var k int
421 i := bytes.IndexAny(buf, search)
422 if i == -1 {
423 k, err = c.Conn.Write(buf)
424 n += k
425 break
426 }
427 k, err = c.Conn.Write(buf[:i])
428 n += k
429 if err != nil {
430 break
431 }
432 switch buf[i] {
433 case LF:
434 k, err = c.Conn.Write([]byte{CR, LF})
435 case cmdIAC:
436 k, err = c.Conn.Write([]byte{cmdIAC, cmdIAC})
437 }
438 n += k
439 if err != nil {
440 break
441 }
442 buf = buf[i+1:]
443 }
444 return n, err
445}