-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.go
219 lines (179 loc) · 5.79 KB
/
server.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package telnet
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net"
"runtime/debug"
"sync"
"time"
)
// ListenAndServe listens on the TCP network address 'addr' and then spawns a call to ServeTELNET
// method on 'handler' to serve each incoming connection.
func ListenAndServe(addr string, handler HandlerFunc) error {
server := &Server{Addr: addr, Handler: handler, logger: slog.Default()}
return server.ListenAndServe()
}
// Serve accepts an incoming TELNET or TELNETS client connection on the net.Listener 'listener'.
func Serve(listener net.Listener, handler HandlerFunc) error {
server := &Server{Handler: handler, logger: slog.Default()}
return server.Serve(listener)
}
type (
// Server defines parameters of a running TELNET server.
Server struct {
listener net.Listener
ConnCallback func(ctx context.Context, conn net.Conn) net.Conn // optional callback for wrapping net.Conn before handling
Handler HandlerFunc // handler to invoke; default is telnet.EchoHandler if nil
TLSConfig *tls.Config // optional TLS configuration; used by ListenAndServeTLS
logger *slog.Logger // optional logger
handles map[string]context.CancelFunc
Addr string // TCP address to listen on; ":23" or ":992" if empty (used with ListenAndServe or ListenAndServeTLS respectively).
Timeout time.Duration
handlesMu sync.Mutex
}
// serverConn is used to wrap a handle with context.
serverConn struct {
net.Conn
ctx context.Context
cancel context.CancelFunc
}
)
// ListenAndServe listens on the TCP network address 'server.Addr' and then spawns a call to Serve
// method on 'server.Handler' to serve each incoming connection.
func (server *Server) ListenAndServe() error {
addr := server.Addr
if addr == "" {
addr = ":23"
}
listener, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return server.Serve(listener)
}
// Serve accepts an incoming TELNET client connection on the net.Listener 'listener'.
func (server *Server) Serve(listener net.Listener) error {
if server.listener != nil {
return errors.New("server already listening")
}
defer listener.Close()
server.listener = listener
server.handles = make(map[string]context.CancelFunc)
handler := server.Handler
if handler == nil {
server.logger.Debug("no handler set, using EchoHandler")
handler = EchoHandler
}
for {
rawConn, err := listener.Accept()
if err != nil {
return err
}
var ctx context.Context
var cancel context.CancelFunc
if server.Timeout > 0 {
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(server.Timeout))
} else {
ctx, cancel = context.WithCancel(context.Background())
}
if server.ConnCallback != nil {
rawConn = server.ConnCallback(ctx, rawConn)
}
conn := serverConn{
Conn: rawConn,
cancel: cancel,
ctx: ctx,
}
server.logger.Debug("received new connection", "FROM", conn.RemoteAddr().String())
// Spawn a new goroutine to handle the new client connection.
go server.handle(conn, handler)
}
}
func (server *Server) SetLogger(logger *slog.Logger) {
server.logger = logger
}
func (server *Server) Shutdown() error {
if server.listener != nil {
if err := server.listener.Close(); err != nil {
return fmt.Errorf("failed to close listener: %w", err)
}
}
wg := sync.WaitGroup{}
wg.Add(len(server.handles))
for _, cancel := range server.handles {
go func() {
defer wg.Done()
cancel()
}()
}
wg.Wait()
return nil
}
// handle manages the lifecycle of a TELNET client connection.
func (server *Server) handle(conn serverConn, handler HandlerFunc) {
defer conn.Close()
// Leave a slight delay to close the context (needed to allow the connection to gracefully close).
defer func() {
if recovery := recover(); recovery != nil {
server.logger.Error("recovered from handle panic", "recovered", recovery, "stack", string(debug.Stack()))
}
}()
// Close the handle if context is cancelled.
go func() {
server.handlesMu.Lock()
server.handles[conn.RemoteAddr().String()] = conn.cancel
server.handlesMu.Unlock()
<-conn.ctx.Done()
server.logger.Debug("received context completion, closing telnet connection", "from", conn.RemoteAddr().String())
if err := conn.Conn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
server.logger.Error("failed to close telnet connection", "from", conn.RemoteAddr().String(), "err", err)
}
server.handlesMu.Lock()
delete(server.handles, conn.RemoteAddr().String())
server.handlesMu.Unlock()
}()
defer func() {
conn.cancel()
}()
r := newReader(conn)
w := newWriter(conn)
// TODO: handle real protocol negotiation
// Disable SGA by default. Clients connecting without defining a host port negotiate SGA, which causes ENTER to be
// handled incorrectly if the server enables and disables echoing (e.g. to mask the user's password during auth).
if _, err := WriteCommand(w, IAC, WONT, SGA); err != nil {
return
}
handler.ServeTELNET(&Session{
ctx: conn.ctx,
Conn: conn,
reader: r,
writer: w,
})
}
// The HandlerFunc type is an adapter to allow the use of ordinary functions as TELNET handlers.
type HandlerFunc func(server *Session)
// ServeTELNET calls f(ctx, w, r).
func (f HandlerFunc) ServeTELNET(session *Session) {
f(session)
}
// EchoHandler is a simple TELNET server which "echos" back to the client any (non-command)
// data back to the TELNET client, it received from the TELNET client.
var EchoHandler HandlerFunc = func(session *Session) {
// Buffer needs to be small to avoid waiting for it to fill up.
var buffer [1]byte
p := buffer[:]
for {
n, err := session.Read(p)
if n > 0 {
if _, err := session.Write(p[:n]); err != nil {
return
}
}
if err != nil {
break
}
}
}