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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
// Package net implements the server transports: telnet, telnet-over-TLS,
// and the WebSocket bridge for the web client, plus the shared Session type.
// It must not be confused with the standard library net package.
package net
import (
"bufio"
"bytes"
"io"
"net"
"strings"
"sync/atomic"
"time"
)
type Conn interface {
ReadMessage() (string, error)
io.Writer
io.Closer
// SetWriteDeadline is optional for transports: sessions that support it
// enforce per-write deadlines so a stalled peer surfaces as a timeout
// instead of a hung writer goroutine. Implementations without deadline
// support return nil (deadlines are best-effort).
SetWriteDeadline(t time.Time) error
SetEcho(bool) error
}
type tcpConn struct {
conn net.Conn
reader *bufio.Reader
// echoOff is written by the owner goroutine (SetEcho/SetEchoState) and
// read by this connection's reader goroutine, so it must be atomic.
echoOff atomic.Bool
}
func newTCPConn(conn net.Conn) *tcpConn {
return &tcpConn{
conn: conn,
reader: bufio.NewReaderSize(conn, maxLineLength*4),
}
}
// maxLineLength bounds how much of an incoming line is accepted. Longer
// lines are drained from the socket and truncated so a client cannot grow
// the read buffer without limit.
const maxLineLength = 1024
func (t *tcpConn) ReadMessage() (string, error) {
line, err := t.reader.ReadSlice('\n')
if err == bufio.ErrBufferFull {
// Copy the bounded prefix out of the reusable read buffer BEFORE
// draining the rest of the oversized line; further reads overwrite the
// buffer the slice points into.
prefixLen := len(line)
if prefixLen > maxLineLength {
prefixLen = maxLineLength
}
prefix := make([]byte, prefixLen)
copy(prefix, line[:prefixLen])
for {
_, err = t.reader.ReadSlice('\n')
if err == nil {
break
}
if err != bufio.ErrBufferFull {
return "", err
}
}
line = prefix
} else if err != nil {
return "", err
}
text := strings.TrimRight(string(line), "\r\n")
text = stripIAC(text)
if t.echoOff.Load() {
t.conn.Write([]byte("\r\n"))
}
return text, nil
}
func (t *tcpConn) Write(b []byte) (int, error) {
return t.conn.Write(b)
}
func (t *tcpConn) SetWriteDeadline(ts time.Time) error {
return t.conn.SetWriteDeadline(ts)
}
func (t *tcpConn) Close() error {
return t.conn.Close()
}
func (t *tcpConn) SetEcho(on bool) error {
t.echoOff.Store(!on)
if on {
return t.writeTelnetCmd(wont, echo)
}
return t.writeTelnetCmd(will, echo)
}
// EchoSequence returns the telnet negotiation bytes that switch the client's
// echo state. Queued sessions transmit these through the session writer so a
// stalled client cannot block the caller (see Session.SetEcho); the local
// echo state is applied separately via SetEchoState.
func (t *tcpConn) EchoSequence(on bool) []byte {
if on {
return []byte{iac, wont, echo}
}
return []byte{iac, will, echo}
}
// SetEchoState records the echo state without writing to the network; used
// when the negotiation bytes are queued instead of written inline.
func (t *tcpConn) SetEchoState(on bool) {
t.echoOff.Store(!on)
}
const (
iac = 255
will = 251
wont = 252
do = 253
dont = 254
sb = 250
se = 240
echo = 1
)
func (t *tcpConn) writeTelnetCmd(cmd, opt byte) error {
_, err := t.conn.Write([]byte{iac, cmd, opt})
return err
}
func stripIAC(s string) string {
b := []byte(s)
var out []byte
i := 0
iacSE := []byte{iac, se}
for i < len(b) {
if b[i] == iac && i+2 < len(b) {
if b[i+1] == sb {
end := bytes.Index(b[i+2:], iacSE)
if end >= 0 {
i += end + 4
continue
}
}
i += 3
continue
}
if b[i] == iac {
i++
continue
}
out = append(out, b[i])
i++
}
return string(out)
}
|