-
Notifications
You must be signed in to change notification settings - Fork 3
/
telnet.go
140 lines (128 loc) · 4.18 KB
/
telnet.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// This file is part of https://github.com/racingmars/go3270/
// Copyright 2020 by Matthew R. Wilson, licensed under the MIT license. See
// LICENSE in the project root for license information.
package go3270
import (
"net"
"time"
)
const (
binary = 0
send = 1
se = 240 // f0
sb = 250 // fa
will = 251 // fb
wont = 252 // fc
do = 253 // fd
dont = 254 // fe
iac = 255 // ff
terminalType = 24 // 18
eoroption = 25 // 19
eor = 239 // f1
)
// NegotiateTelnet will naively (e.g. not checking client responses) negotiate
// the options necessary for tn3270 on a new telnet connection, conn.
func NegotiateTelnet(conn net.Conn) error {
conn.Write([]byte{iac, do, terminalType})
conn.Write([]byte{iac, sb, terminalType, send, iac, se})
conn.Write([]byte{iac, do, eoroption})
conn.Write([]byte{iac, do, binary})
conn.Write([]byte{iac, will, eoroption, iac, will, binary})
flushConnection(conn, time.Second*5)
return nil
}
// UnNegotiateTelnet will naively (e.g. not checking client responses) attempt
// to restore the telnet options state to what it was before NegotiateTelnet()
// was called.
func UnNegotiateTelnet(conn net.Conn, timeout time.Duration) error {
conn.Write([]byte{iac, wont, eoroption, iac, wont, binary})
conn.Write([]byte{iac, dont, binary})
conn.Write([]byte{iac, dont, eoroption})
conn.Write([]byte{iac, dont, terminalType})
flushConnection(conn, timeout)
return nil
}
// flushConnection discards all bytes that it can read from conn, allowing up
// to the duration timeout for the first byte to be read.
func flushConnection(conn net.Conn, timeout time.Duration) error {
defer conn.SetReadDeadline(time.Time{})
buffer := make([]byte, 1024)
for {
conn.SetReadDeadline(time.Now().Add(timeout))
n, err := conn.Read(buffer)
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
debugf("nothing to flush\n")
return nil
}
if err != nil {
debugf("error while flushing: %v\n", err)
return err
}
debugf("%d bytes read while flushing connection\n", n)
// for follow-up reads, reduce the timeout
timeout = time.Second / 2
}
}
// telnetRead returns the next byte of data from the connection c, but
// filters out all telnet commands. If passEOR is true, then telnetRead will
// return upon encountering the telnet End of Record command, setting isEor to
// true. When isEor is true, the value of b is meaningless and must be ignored
// (valid will be false). When valid is true, the value in byte b is a real
// value read from the connection; when value is false, do not use the value
// in b. (For example, a valid byte AND error can be returned in the same
// call.)
func telnetRead(c net.Conn, passEOR bool) (b byte, valid, isEor bool, err error) {
const (
normal = iota
command
subneg
)
buf := make([]byte, 1)
state := normal
for {
bn, berr := c.Read(buf)
// When there are no bytes to process and we received an error, we
// are done no matter what state we're in. Any non-command bytes will
// already be in p, so we return.
if bn == 0 && berr != nil {
return 0, false, false, berr
}
// If we received 0 bytes but no error, we'll just read again.
if bn == 0 {
continue
}
// We got a byte! Let's progress through our state machine.
switch state {
case normal:
if buf[0] == iac {
state = command
debugf("entering telnet command state\n")
} else {
return buf[0], true, false, berr
}
case command:
if buf[0] == 0xff {
debugf("leaving telnet command state; was an escaped 0xff\n")
return 0xff, true, false, nil
} else if buf[0] == sb {
state = subneg
debugf("entering telnet command subnegotiation state\n")
} else if passEOR && buf[0] == eor {
debugf("leaving telnet command state; returning EOR\n")
return 0, false, true, nil
} else {
state = normal
debugf("leaving telnet command state; command was %02x\n",
buf[0])
}
case subneg:
if buf[0] == se {
state = normal
debugf("leaving telnet command subnegotiation state\n")
} else {
// remain in subnegotiation consuming bytes until we get se
debugf("consumed telnet subnegotiation byte: %02x\n", buf[0])
}
}
}
}