Download
jcs
/subtext
/telnet.c
(View History)
jcs telnet: Speed up initial IAC negotiation to avoid delay after connect | Latest amendment: 573 on 2023-12-02 |
1 | /* |
2 | * Copyright (c) 2021 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdio.h> |
18 | #include <string.h> |
19 | |
20 | #include "logger.h" |
21 | #include "session.h" |
22 | #include "subtext.h" |
23 | #include "telnet.h" |
24 | #include "tcp.h" |
25 | #include "util.h" |
26 | |
27 | #define SE 240 /* end of sub-negotiation options */ |
28 | #define NOP 241 /* no operation */ |
29 | #define SB 250 /* start of sub-negotiation options */ |
30 | #define WILL 251 /* confirm willingness to negotiate */ |
31 | #define WONT 252 /* confirm unwillingness to negotiate */ |
32 | #define DO 253 /* indicate willingness to negotiate */ |
33 | #define DONT 254 /* indicate unwillingness to negotiate */ |
34 | #define IAC 255 /* start of a negotiation sequence */ |
35 | |
36 | #define IS 0 /* sub-negotiation */ |
37 | #define SEND 1 /* sub-negotiation */ |
38 | |
39 | #define IAC_BINARY 0 /* Transmit Binary */ |
40 | #define IAC_ECHO 1 /* Echo Option */ |
41 | #define IAC_SGA 3 /* Suppress Go Ahead Option */ |
42 | #define IAC_STATUS 5 /* Status Option */ |
43 | #define IAC_TM 6 /* Timing Mark Option */ |
44 | #define IAC_NAOCRD 10 /* Output Carriage-Return Disposition Option */ |
45 | #define IAC_NAOHTS 11 /* Output Horizontal Tabstops Option */ |
46 | #define IAC_NAOHTD 12 /* Output Horizontal Tab Disposition Option */ |
47 | #define IAC_NAOFFD 13 /* Output Formfeed Disposition Option */ |
48 | #define IAC_NAOVTS 14 /* Output Vertical Tabstops Option */ |
49 | #define IAC_NAOVTD 15 /* Output Vertical Tab Disposition Option */ |
50 | #define IAC_NAOLFD 16 /* Output Linefeed Disposition */ |
51 | #define IAC_XASCII 17 /* Extended Ascii Option */ |
52 | #define IAC_LOGOUT 18 /* Logout Option */ |
53 | #define IAC_BM 19 /* Byte Macro Option */ |
54 | #define IAC_SUPDUP 22 /* SUPDUP-OUTPUT Option */ |
55 | #define IAC_SENDLOC 23 /* SEND-LOCATION Option */ |
56 | #define IAC_TTYPE 24 /* Terminal Type Option */ |
57 | #define IAC_EOR 25 /* End of Record Option */ |
58 | #define IAC_OUTMRK 27 /* Marking Telnet Option */ |
59 | #define IAC_TTYLOC 28 /* Terminal Location Number Option */ |
60 | #define IAC_DET 20 /* Data Entry Terminal Option DODIIS */ |
61 | #define IAC_X3PAD 30 /* X.3 PAD Option */ |
62 | #define IAC_NAWS 31 /* Window Size Option */ |
63 | #define IAC_TSPEED 32 /* Terminal Speed Option */ |
64 | #define IAC_FLOWCTRL 33 /* Remote Flow Control Option */ |
65 | #define IAC_LINEMODE 34 /* Linemode Option */ |
66 | #define IAC_XDISPLOC 35 /* X Display Location Option */ |
67 | #define IAC_ENVIRON 36 /* Environment Option */ |
68 | #define IAC_AUTH 37 /* Authentication */ |
69 | #define IAC_ENCRYPT 38 /* Encryption Option */ |
70 | #define IAC_NEWENV 39 /* Environment Option */ |
71 | #define IAC_CHARSET 42 /* Charset Option */ |
72 | #define IAC_COMPORT 44 /* Com Port Control Option */ |
73 | |
74 | enum { |
75 | TELNET_PB_STATE_UNUSED = 0, |
76 | TELNET_PB_STATE_LISTENING, |
77 | TELNET_PB_STATE_CONNECTED |
78 | }; |
79 | |
80 | enum { |
81 | TELNET_IAC_STATE_IDLE = 0, |
82 | TELNET_IAC_STATE_IAC, |
83 | TELNET_IAC_STATE_WILL, |
84 | TELNET_IAC_STATE_WONT, |
85 | TELNET_IAC_STATE_DO, |
86 | TELNET_IAC_STATE_DONT, |
87 | TELNET_IAC_STATE_SB |
88 | }; |
89 | |
90 | struct telnet_node { |
91 | short id; |
92 | short state; |
93 | char name[8]; |
94 | ip_addr ip; |
95 | char ip_s[20]; |
96 | struct session *session; |
97 | unsigned short obuflen; |
98 | unsigned char obuf[1024]; |
99 | unsigned char ibuf[64]; |
100 | unsigned short ibuflen; |
101 | unsigned short ibufoff; |
102 | unsigned short sending_iac; |
103 | short iac_state; |
104 | unsigned char iac_sb[128]; |
105 | short sb_len; |
106 | unsigned short naws_count; |
107 | /* docs say 4*MTU+1024, but MTU will probably be <1500 */ |
108 | unsigned char tcp_buf[(4 * 1500) + 1024]; |
109 | bool from_trusted_proxy; |
110 | TCPiopb rcv_pb, send_pb, listen_pb; |
111 | StreamPtr stream; |
112 | wdsEntry tcp_wds[2]; |
113 | }; |
114 | |
115 | /* add one to be able to print busy messages */ |
116 | #define MAX_TELNET_NODES (MAX_SESSIONS + 1) |
117 | |
118 | static struct telnet_node *telnet_nodes[MAX_TELNET_NODES] = { NULL }; |
119 | static struct telnet_node *telnet_listener_node = NULL; |
120 | static TCPiopb telnet_exit_pb; |
121 | static TCPStatusPB telnet_status_pb; |
122 | static bool did_initial_log = false; |
123 | static UDPiopb udp_ban_pb; |
124 | static StreamPtr udp_ban_stream = 0; |
125 | static char *udp_ban_rcv_buf; |
126 | #define UDP_BAN_RCV_BUF_SIZE 2048 |
127 | static wdsEntry udp_ban_wds[2]; |
128 | static char udp_ban_send_buf[16]; |
129 | |
130 | /* terminals that will have vt100 set by default */ |
131 | static const char * vt100_terms[] = { |
132 | "ansi", |
133 | "syncterm", |
134 | "xterm", |
135 | "tmux", |
136 | "screen", |
137 | "vt100", |
138 | NULL, |
139 | }; |
140 | |
141 | void telnet_deinit(void); |
142 | void telnet_setup(struct session *session); |
143 | void telnet_listen_on_node(struct telnet_node *node); |
144 | short telnet_escape_output(struct session *session); |
145 | void telnet_output_iac(struct session *session, const char *iacs, |
146 | size_t len); |
147 | void telnet_print_busy(struct telnet_node *node); |
148 | |
149 | struct node_funcs telnet_node_funcs = { |
150 | telnet_setup, |
151 | telnet_input, |
152 | telnet_output, |
153 | telnet_close |
154 | }; |
155 | |
156 | void |
157 | telnet_init(void) |
158 | { |
159 | short n, error; |
160 | |
161 | if (!db->config.telnet_port) |
162 | return; |
163 | |
164 | if (!did_initial_log) |
165 | logger_printf("[telnet] Initializing MacTCP"); |
166 | |
167 | if (_TCPInit() != noErr) |
168 | panic("Failed initializing MacTCP"); |
169 | |
170 | /* pre-allocate nodes */ |
171 | for (n = 0; n < MAX_TELNET_NODES; n++) { |
172 | if (telnet_nodes[n] == NULL) { |
173 | telnet_nodes[n] = xmalloczero(sizeof(struct telnet_node)); |
174 | if (telnet_nodes[n] == NULL) |
175 | panic("Failed allocating telnet node %d", n); |
176 | } |
177 | telnet_nodes[n]->id = n; |
178 | } |
179 | |
180 | if (db->config.trusted_proxy_ip != 0 && |
181 | db->config.trusted_proxy_udp_port != 0) { |
182 | udp_ban_rcv_buf = xmalloc(UDP_BAN_RCV_BUF_SIZE); |
183 | if (udp_ban_rcv_buf == NULL) |
184 | panic("Failed allocating UDP ban buf"); |
185 | error = _UDPCreate(&udp_ban_pb, &udp_ban_stream, |
186 | (Ptr)udp_ban_rcv_buf, UDP_BAN_RCV_BUF_SIZE, NULL, NULL, |
187 | NULL, false); |
188 | if (error) |
189 | panic("UDPCreate failed: %d", error); |
190 | } |
191 | } |
192 | |
193 | bool |
194 | telnet_reinit(void) |
195 | { |
196 | telnet_deinit(); |
197 | telnet_init(); |
198 | did_initial_log = false; |
199 | return true; |
200 | } |
201 | |
202 | void |
203 | telnet_deinit(void) |
204 | { |
205 | if (telnet_listener_node) { |
206 | _TCPRelease(&telnet_exit_pb, telnet_listener_node->stream, nil, |
207 | nil, false); |
208 | telnet_listener_node->state = TELNET_PB_STATE_UNUSED; |
209 | telnet_listener_node = NULL; |
210 | } |
211 | |
212 | if (udp_ban_stream) { |
213 | _UDPRelease(&udp_ban_pb, udp_ban_stream, NULL, NULL, false); |
214 | udp_ban_stream = 0; |
215 | } |
216 | |
217 | if (udp_ban_rcv_buf) |
218 | xfree(&udp_ban_rcv_buf); |
219 | } |
220 | |
221 | void |
222 | telnet_atexit(void) |
223 | { |
224 | struct telnet_node *node; |
225 | short n; |
226 | |
227 | for (n = 0; n < MAX_TELNET_NODES; n++) { |
228 | node = telnet_nodes[n]; |
229 | if (!node) |
230 | continue; |
231 | |
232 | if (node->state > TELNET_PB_STATE_UNUSED) |
233 | _TCPRelease(&telnet_exit_pb, node->stream, nil, nil, false); |
234 | |
235 | xfree(&node); |
236 | telnet_nodes[n] = NULL; |
237 | } |
238 | |
239 | telnet_deinit(); |
240 | } |
241 | |
242 | void |
243 | telnet_listen_on_node(struct telnet_node *node) |
244 | { |
245 | char ip_s[20]; |
246 | short error, id; |
247 | ip_addr ip; |
248 | tcp_port port; |
249 | long mask; |
250 | |
251 | id = node->id; |
252 | memset(node, 0, sizeof(struct telnet_node)); |
253 | node->id = id; |
254 | |
255 | snprintf(node->name, sizeof(node->name), "ttyt%d", id); |
256 | |
257 | error = _TCPCreate(&node->listen_pb, &node->stream, |
258 | (Ptr)&node->tcp_buf, sizeof(node->tcp_buf), nil, nil, nil, false); |
259 | if (error) |
260 | panic("TCPCreate[%d] failed: %d", node->id, error); |
261 | |
262 | error = _TCPGetOurIP(&ip, &mask); |
263 | if (error) |
264 | panic("TCPGetOurIP failed: %d", error); |
265 | |
266 | port = db->config.telnet_port; |
267 | |
268 | if (!did_initial_log) { |
269 | long2ip(ip, ip_s); |
270 | logger_printf("[telnet] Listening on %s:%d", ip_s, port); |
271 | did_initial_log = true; |
272 | } |
273 | |
274 | error = _TCPPassiveOpen(&node->listen_pb, node->stream, nil, nil, &ip, |
275 | &port, nil, nil, true); |
276 | if (error) |
277 | panic("TCPPassiveOpen[%d] on port %d failed: %d", |
278 | node->id, db->config.telnet_port, error); |
279 | |
280 | node->state = TELNET_PB_STATE_LISTENING; |
281 | telnet_listener_node = node; |
282 | } |
283 | |
284 | void |
285 | telnet_idle(void) |
286 | { |
287 | struct telnet_node *node; |
288 | short n, error; |
289 | |
290 | if (!db->config.telnet_port) |
291 | return; |
292 | |
293 | for (n = 0; n < MAX_TELNET_NODES; n++) { |
294 | node = telnet_nodes[n]; |
295 | |
296 | switch (node->state) { |
297 | case TELNET_PB_STATE_UNUSED: |
298 | if (telnet_listener_node == NULL) |
299 | telnet_listen_on_node(node); |
300 | break; |
301 | case TELNET_PB_STATE_LISTENING: |
302 | error = _TCPStatus(&node->send_pb, node->stream, |
303 | &telnet_status_pb, nil, nil, false); |
304 | if (error == connectionDoesntExist) { |
305 | /* |
306 | * The connection closed before it could be handled, but |
307 | * telnet_status_pb.connectionState will still be |
308 | * listening, so force it into a bogus state to recycle it. |
309 | */ |
310 | telnet_status_pb.connectionState = -1; |
311 | } |
312 | |
313 | switch (telnet_status_pb.connectionState) { |
314 | case ConnectionStateListening: |
315 | goto next_node; |
316 | case ConnectionStateSYNReceived: |
317 | case ConnectionStateSYNSent: |
318 | /* TODO: enforce our own timeout on these? */ |
319 | break; |
320 | case ConnectionStateEstablished: { |
321 | char *loc = NULL; |
322 | |
323 | /* start up a new socket for listening */ |
324 | telnet_listener_node = NULL; |
325 | |
326 | node->ip = telnet_status_pb.remoteHost; |
327 | long2ip(telnet_status_pb.remoteHost, node->ip_s); |
328 | |
329 | if (db->config.trusted_proxy_ip != 0 && |
330 | db->config.trusted_proxy_ip == node->ip) { |
331 | node->from_trusted_proxy = true; |
332 | snprintf(node->name, sizeof(node->name), "ttyw%d", |
333 | node->id); |
334 | } |
335 | |
336 | if (db->ipdb && !node->from_trusted_proxy) |
337 | loc = ipdb_lookup(db->ipdb, node->ip); |
338 | |
339 | logger_printf("[%s] New telnet connection from %s%s%s%s%s", |
340 | node->name, node->ip_s, |
341 | (loc ? " (" : ""), (loc ? loc : ""), (loc ? ") " : ""), |
342 | node->from_trusted_proxy ? " (via trusted proxy)" : ""); |
343 | |
344 | node->state = TELNET_PB_STATE_CONNECTED; |
345 | node->session = session_create(node->name, |
346 | node->from_trusted_proxy ? "web" : "telnet", |
347 | &telnet_node_funcs); |
348 | if (node->session == NULL) { |
349 | logger_printf("[%s] No free nodes, disconnecting", |
350 | node->name); |
351 | telnet_print_busy(node); |
352 | _TCPRelease(&node->listen_pb, node->stream, nil, |
353 | nil, false); |
354 | node->state = TELNET_PB_STATE_UNUSED; |
355 | if (loc) |
356 | xfree(&loc); |
357 | goto next_node; |
358 | } |
359 | node->session->cookie = (void *)node; |
360 | node->session->tspeed = 19200; |
361 | node->session->is_telnet = true; |
362 | node->session->log.ip_address = telnet_status_pb.remoteHost; |
363 | if (node->from_trusted_proxy) |
364 | node->session->can_nomodem = true; |
365 | if (loc) { |
366 | strlcpy(node->session->location, loc, |
367 | sizeof(node->session->location)); |
368 | strlcpy(node->session->log.location, loc, |
369 | sizeof(node->session->log.location)); |
370 | xfree(&loc); |
371 | } |
372 | break; |
373 | } |
374 | default: |
375 | if (telnet_status_pb.remoteHost == 0) |
376 | long2ip(node->listen_pb.csParam.open.remoteHost, |
377 | node->ip_s); |
378 | else |
379 | long2ip(telnet_status_pb.remoteHost, node->ip_s); |
380 | |
381 | logger_printf("[%s] Telnet connection from " |
382 | "%s in state %d, aborting", node->name, node->ip_s, |
383 | telnet_status_pb.connectionState); |
384 | |
385 | if (node->session) |
386 | node->session->ending = 1; |
387 | else { |
388 | _TCPRelease(&node->listen_pb, node->stream, nil, nil, |
389 | false); |
390 | node->state = TELNET_PB_STATE_UNUSED; |
391 | telnet_listener_node = NULL; |
392 | } |
393 | goto next_node; |
394 | } |
395 | break; |
396 | case TELNET_PB_STATE_CONNECTED: |
397 | if (!node->session) { |
398 | node->state = TELNET_PB_STATE_UNUSED; |
399 | break; |
400 | } |
401 | |
402 | /* |
403 | * Send buffered data any time we can, the user might not be |
404 | * in an explicit session_flush() like during chat |
405 | */ |
406 | telnet_output(node->session); |
407 | break; |
408 | } |
409 | |
410 | next_node: |
411 | continue; |
412 | } |
413 | } |
414 | |
415 | void |
416 | telnet_setup(struct session *session) |
417 | { |
418 | const char iacs[] = { |
419 | IAC, WILL, IAC_SGA, /* we will supress go-ahead */ |
420 | IAC, WILL, IAC_ECHO, /* and we will echo for you */ |
421 | IAC, WILL, IAC_BINARY, /* don't mess with binary data we send */ |
422 | IAC, DO, IAC_BINARY, /* and you can send us binary */ |
423 | IAC, DO, IAC_TSPEED, /* send your TSPEED */ |
424 | IAC, DO, IAC_NAWS, /* send your NAWS */ |
425 | IAC, DO, IAC_TTYPE, /* send your TTYPE */ |
426 | IAC, DO, IAC_NEWENV, /* send your NEWENV vars */ |
427 | }; |
428 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
429 | size_t size = sizeof(iacs); |
430 | |
431 | if (!node->from_trusted_proxy) |
432 | /* we only care about NEWENV when we need REMOTE_ADDR */ |
433 | size -= 3; |
434 | |
435 | telnet_output_iac(session, iacs, size); |
436 | session_flush(session); |
437 | |
438 | /* see if we can quickly read and parse any incoming IAC */ |
439 | telnet_input(session); |
440 | session_flush(session); |
441 | } |
442 | |
443 | short |
444 | telnet_input(struct session *session) |
445 | { |
446 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
447 | unsigned short rlen; |
448 | short error, n, j, used; |
449 | unsigned char c; |
450 | char iac_out[8] = { IAC, 0 }; |
451 | |
452 | error = _TCPStatus(&node->rcv_pb, node->stream, &telnet_status_pb, nil, |
453 | nil, false); |
454 | if (error || |
455 | telnet_status_pb.connectionState != ConnectionStateEstablished) { |
456 | session->ending = 1; |
457 | return 0; |
458 | } |
459 | |
460 | if (telnet_status_pb.amtUnreadData == 0) |
461 | return 0; |
462 | |
463 | rlen = telnet_status_pb.amtUnreadData; |
464 | |
465 | if (session->direct_output) { |
466 | if (session->ibuflen + session->ibufoff + rlen > |
467 | sizeof(session->ibuf)) { |
468 | /* if we're already halfway through the buffer, reset */ |
469 | if (session->ibufoff >= (sizeof(session->ibuf) / 2)) { |
470 | memmove(session->ibuf, session->ibuf + session->ibufoff, |
471 | session->ibuflen); |
472 | session->ibufoff = 0; |
473 | } |
474 | rlen = sizeof(session->ibuf) - session->ibuflen - |
475 | session->ibufoff; |
476 | if (rlen == 0) |
477 | return 0; |
478 | } |
479 | |
480 | error = _TCPRcv(&node->rcv_pb, node->stream, |
481 | (Ptr)(session->ibuf + session->ibufoff + session->ibuflen), &rlen, |
482 | nil, nil, false); |
483 | if (error) { |
484 | session_logf(session, "TCP read failed (%d), closing connection", |
485 | error); |
486 | session->ending = 1; |
487 | return 0; |
488 | } |
489 | if (rlen == 0) |
490 | return 0; |
491 | session->last_input_at = Time; |
492 | session->ibuflen += rlen; |
493 | return rlen; |
494 | } |
495 | |
496 | if (node->ibuflen + rlen > sizeof(node->ibuf)) |
497 | rlen = sizeof(node->ibuf) - node->ibuflen; |
498 | |
499 | error = _TCPRcv(&node->rcv_pb, node->stream, |
500 | (Ptr)(node->ibuf + node->ibuflen), &rlen, nil, nil, false); |
501 | if (error) { |
502 | session_logf(session, "TCP read failed (%d), closing connection", |
503 | error); |
504 | session->ending = true; |
505 | return 0; |
506 | } |
507 | |
508 | if (rlen == 0) |
509 | return 0; |
510 | |
511 | session->last_input_at = Time; |
512 | node->ibuflen += rlen; |
513 | |
514 | used = 0; |
515 | for (n = 0; n < node->ibuflen; n++) { |
516 | if (session->ibuflen >= sizeof(session->ibuf)) |
517 | break; |
518 | |
519 | c = node->ibuf[n]; |
520 | used++; |
521 | |
522 | switch (node->iac_state) { |
523 | case TELNET_IAC_STATE_IDLE: |
524 | if (c == IAC) |
525 | node->iac_state = TELNET_IAC_STATE_IAC; |
526 | else |
527 | session->ibuf[session->ibuflen++] = c; |
528 | break; |
529 | case TELNET_IAC_STATE_IAC: |
530 | switch (c) { |
531 | case IAC: |
532 | /* escaped iac */ |
533 | session->ibuf[session->ibuflen++] = c; |
534 | node->iac_state = TELNET_IAC_STATE_IDLE; |
535 | break; |
536 | case NOP: |
537 | node->iac_state = TELNET_IAC_STATE_IDLE; |
538 | break; |
539 | case WILL: |
540 | /* client will do something */ |
541 | node->iac_state = TELNET_IAC_STATE_WILL; |
542 | break; |
543 | case WONT: |
544 | /* client will not do something */ |
545 | node->iac_state = TELNET_IAC_STATE_WONT; |
546 | break; |
547 | case DO: |
548 | /* client wants us to do something */ |
549 | node->iac_state = TELNET_IAC_STATE_DO; |
550 | break; |
551 | case DONT: |
552 | /* client wants us not to do something */ |
553 | node->iac_state = TELNET_IAC_STATE_DONT; |
554 | break; |
555 | case SB: |
556 | /* sub-negotiate */ |
557 | node->iac_state = TELNET_IAC_STATE_SB; |
558 | node->sb_len = 0; |
559 | break; |
560 | default: |
561 | /* unsupported command, ignore it */ |
562 | node->iac_state = TELNET_IAC_STATE_IDLE; |
563 | break; |
564 | } |
565 | break; |
566 | case TELNET_IAC_STATE_SB: { |
567 | unsigned short sboff; |
568 | |
569 | /* keep reading until we see [^IAC] IAC SE */ |
570 | if (c == SE && node->sb_len > 0 && |
571 | node->iac_sb[node->sb_len - 1] == IAC) { |
572 | switch (node->iac_sb[0]) { |
573 | case IAC_NAWS: |
574 | /* IAC SB NAWS 0 80 0 24 (80x24) */ |
575 | /* IAC SB NAWS 1 255 255 1 123 (256x378) */ |
576 | /* if either value is 255, it will be 255,255 */ |
577 | sboff = 1; |
578 | |
579 | if (node->iac_sb[sboff] == 255) |
580 | sboff++; |
581 | session->terminal_columns = (node->iac_sb[sboff++] << 8); |
582 | if (node->iac_sb[sboff] == 255) |
583 | sboff++; |
584 | session->terminal_columns |= node->iac_sb[sboff++]; |
585 | |
586 | if (node->iac_sb[sboff] == 255) |
587 | sboff++; |
588 | session->terminal_lines = (node->iac_sb[sboff++] << 8); |
589 | if (node->iac_sb[sboff] == 255) |
590 | sboff++; |
591 | session->terminal_lines |= node->iac_sb[sboff++]; |
592 | if (node->naws_count++ < 20) |
593 | session_logf_buffered(session, "IAC NAWS %d, %d", |
594 | session->terminal_columns, |
595 | session->terminal_lines); |
596 | break; |
597 | case IAC_TTYPE: |
598 | /* IAC SB TTYPE IS XTERM IAC SE */ |
599 | node->iac_sb[node->sb_len - 1] = '\0'; |
600 | strlcpy(session->terminal_type, |
601 | (char *)&node->iac_sb + 2, |
602 | sizeof(session->terminal_type)); |
603 | session_logf_buffered(session, "IAC TTYPE IS %s", |
604 | session->terminal_type); |
605 | |
606 | for (j = 0; vt100_terms[j] != NULL; j++) { |
607 | if (strncasecmp(session->terminal_type, |
608 | vt100_terms[j], strlen(vt100_terms[j])) == 0) { |
609 | session_logf_buffered(session, |
610 | "Activating vt100 ANSI support for " |
611 | "matching %s", vt100_terms[j]); |
612 | session->vt100 = true; |
613 | break; |
614 | } |
615 | } |
616 | |
617 | break; |
618 | case IAC_NEWENV: { |
619 | char k[sizeof(node->iac_sb)], v[sizeof(node->iac_sb)]; |
620 | unsigned char l = 0; |
621 | bool inv = false; |
622 | char *loc = NULL; |
623 | |
624 | k[0] = v[0] = '\0'; |
625 | |
626 | /* \40\0\0USER\1blah\0DISPLAY\1... */ |
627 | for (j = 3; j < node->sb_len; j++) { |
628 | if (j == node->sb_len - 1 || |
629 | node->iac_sb[j] == 0 || node->iac_sb[j] == 3) { |
630 | v[l] = '\0'; |
631 | if (l) { |
632 | loc = NULL; |
633 | |
634 | if (node->from_trusted_proxy && |
635 | strcmp(k, "REMOTE_ADDR") == 0) { |
636 | node->ip = ip2long(v); |
637 | strlcpy(node->ip_s, v, |
638 | sizeof(node->ip_s)); |
639 | node->session->log.ip_address = |
640 | node->ip; |
641 | |
642 | if (db->ipdb && |
643 | (loc = ipdb_lookup(db->ipdb, |
644 | node->ip))) { |
645 | strlcpy(node->session->location, |
646 | loc, |
647 | sizeof(node->session->location)); |
648 | strlcpy(node->session->log.location, |
649 | loc, |
650 | sizeof(node->session->log.location)); |
651 | } |
652 | } |
653 | |
654 | session_logf_buffered(session, |
655 | "NEWENV \"%s\" = \"%s\"%s%s%s", k, v, |
656 | loc ? " (" : "", |
657 | loc ? loc : "", |
658 | loc ? ")" : ""); |
659 | |
660 | if (loc) |
661 | xfree(&loc); |
662 | } |
663 | l = 0; |
664 | inv = false; |
665 | } else if (node->iac_sb[j] == 1) { |
666 | k[l] = '\0'; |
667 | l = 0; |
668 | inv = true; |
669 | } else { |
670 | if (inv) |
671 | v[l++] = node->iac_sb[j]; |
672 | else |
673 | k[l++] = node->iac_sb[j]; |
674 | } |
675 | } |
676 | break; |
677 | } |
678 | case IAC_TSPEED: { |
679 | unsigned long tspeed; |
680 | |
681 | /* IAC SB TSPEED IS 38400,19200 SE */ |
682 | node->iac_sb[node->sb_len - 1] = '\0'; |
683 | |
684 | session_logf_buffered(session, "IAC TSPEED IS %s", |
685 | node->iac_sb + 2); |
686 | |
687 | for (j = 2; j < node->sb_len; j++) { |
688 | if (node->iac_sb[j] == ',') { |
689 | node->iac_sb[j] = '\0'; |
690 | break; |
691 | } |
692 | } |
693 | |
694 | tspeed = atol((const char *)(node->iac_sb + 2)); |
695 | if (tspeed > 0) { |
696 | if (tspeed > 57600) |
697 | tspeed = 57600; |
698 | node->session->tspeed = node->session->log.tspeed = |
699 | (unsigned short)tspeed; |
700 | } |
701 | break; |
702 | } |
703 | } |
704 | node->iac_state = TELNET_IAC_STATE_IDLE; |
705 | } else { |
706 | /* accumulate bytes into iac_sb buffer */ |
707 | if (node->sb_len < sizeof(node->iac_sb)) |
708 | node->iac_sb[node->sb_len++] = c; |
709 | else { |
710 | /* IAC overflow */ |
711 | node->iac_state = TELNET_IAC_STATE_IDLE; |
712 | node->sb_len = 0; |
713 | session_logf(session, "IAC SB overflow"); |
714 | } |
715 | } |
716 | break; |
717 | } |
718 | case TELNET_IAC_STATE_WILL: |
719 | switch (c) { |
720 | case IAC_ECHO: |
721 | case IAC_SGA: |
722 | iac_out[1] = DO; |
723 | iac_out[2] = IAC_ECHO; |
724 | telnet_output_iac(session, iac_out, 3); |
725 | break; |
726 | case IAC_NEWENV: |
727 | if (!node->from_trusted_proxy) |
728 | break; |
729 | case IAC_TTYPE: |
730 | case IAC_TSPEED: |
731 | iac_out[1] = SB; |
732 | iac_out[2] = c; |
733 | iac_out[3] = SEND; |
734 | iac_out[4] = IAC; |
735 | iac_out[5] = SE; |
736 | telnet_output_iac(session, iac_out, 6); |
737 | break; |
738 | case IAC_LINEMODE: |
739 | #if 0 |
740 | iac_out[1] = SB; |
741 | iac_out[2] = IAC_LINEMODE; |
742 | iac_out[3] = 1; |
743 | iac_out[4] = 0; |
744 | iac_out[5] = IAC; |
745 | iac_out[6] = SE; |
746 | telnet_output_iac(session, iac_out, 7); |
747 | #endif |
748 | break; |
749 | case IAC_ENCRYPT: |
750 | iac_out[1] = DONT; |
751 | iac_out[2] = IAC_ENCRYPT; |
752 | telnet_output_iac(session, iac_out, 3); |
753 | break; |
754 | } |
755 | node->iac_state = TELNET_IAC_STATE_IDLE; |
756 | break; |
757 | case TELNET_IAC_STATE_WONT: |
758 | /* we don't care about any of these yet */ |
759 | node->iac_state = TELNET_IAC_STATE_IDLE; |
760 | break; |
761 | case TELNET_IAC_STATE_DO: |
762 | switch (c) { |
763 | case IAC_BINARY: |
764 | iac_out[1] = WILL; |
765 | iac_out[2] = IAC_BINARY; |
766 | telnet_output_iac(session, iac_out, 3); |
767 | break; |
768 | case IAC_ECHO: |
769 | case IAC_NAWS: |
770 | case IAC_TSPEED: |
771 | case IAC_FLOWCTRL: |
772 | case IAC_SGA: |
773 | case IAC_LINEMODE: |
774 | /* we already sent WILLs for these at the beginning */ |
775 | break; |
776 | default: |
777 | iac_out[1] = WONT; |
778 | iac_out[2] = c; |
779 | telnet_output_iac(session, iac_out, 3); |
780 | break; |
781 | } |
782 | node->iac_state = TELNET_IAC_STATE_IDLE; |
783 | break; |
784 | case TELNET_IAC_STATE_DONT: |
785 | node->iac_state = TELNET_IAC_STATE_IDLE; |
786 | break; |
787 | default: |
788 | warn("telnet_input in bogus IAC state %d", node->iac_state); |
789 | break; |
790 | } |
791 | } |
792 | |
793 | if (used == node->ibuflen) |
794 | node->ibuflen = 0; |
795 | else { |
796 | memmove(node->ibuf, node->ibuf + used, node->ibuflen - used); |
797 | node->ibuflen -= used; |
798 | } |
799 | |
800 | if (node->obuflen) |
801 | session_flush(session); |
802 | |
803 | return rlen; |
804 | } |
805 | |
806 | short |
807 | telnet_escape_output(struct session *session) |
808 | { |
809 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
810 | unsigned char c; |
811 | unsigned long added = 0; |
812 | |
813 | if (session->obuflen == 0 || session->ending) |
814 | return 0; |
815 | |
816 | if (session->obuflen == 1 && session->obuf[0] != IAC) { |
817 | node->obuf[node->obuflen++] = session->obuf[0]; |
818 | session->obuflen = 0; |
819 | } else if (session->direct_output) { |
820 | added = MIN(session->obuflen, sizeof(node->obuf) - node->obuflen); |
821 | memcpy(node->obuf + node->obuflen, session->obuf, added); |
822 | node->obuflen += added; |
823 | } else { |
824 | /* copy session obuf to node buffer, escaping IACs */ |
825 | for (added = 0; added < session->obuflen; added++) { |
826 | if (node->obuflen >= sizeof(node->obuf) - 1) |
827 | break; |
828 | |
829 | c = session->obuf[added]; |
830 | |
831 | if (!node->sending_iac && c == IAC) { |
832 | node->obuf[node->obuflen++] = IAC; |
833 | node->obuf[node->obuflen++] = IAC; |
834 | } else |
835 | node->obuf[node->obuflen++] = c; |
836 | } |
837 | } |
838 | |
839 | if (added > 0) { |
840 | if (added < session->obuflen) |
841 | memmove(session->obuf, session->obuf + added, |
842 | session->obuflen - added); |
843 | |
844 | session->obuflen -= added; |
845 | } |
846 | |
847 | return added; |
848 | } |
849 | |
850 | short |
851 | telnet_output(struct session *session) |
852 | { |
853 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
854 | unsigned long now = Ticks; |
855 | short error, ret = 0; |
856 | |
857 | if (session->obuflen != 0) |
858 | telnet_escape_output(session); |
859 | |
860 | if (node->obuflen == 0 || session->ending) |
861 | return 0; |
862 | |
863 | /* wait for previous _TCPSend to complete */ |
864 | while (node->send_pb.ioResult > 0) { |
865 | if (uthread_current == NULL) |
866 | /* this was an opportunistic flush anyway, no sense waiting */ |
867 | return 0; |
868 | |
869 | uthread_yield(); |
870 | |
871 | if (session->ending) |
872 | return 0; |
873 | } |
874 | |
875 | process_result: |
876 | if (node->tcp_wds[0].length) { |
877 | /* shift out old data */ |
878 | if (node->obuflen < node->tcp_wds[0].length) |
879 | panic("data in tcp_wds > obuflen"); |
880 | |
881 | if (node->obuflen > node->tcp_wds[0].length) { |
882 | memmove(node->obuf, node->obuf + node->tcp_wds[0].length, |
883 | node->obuflen - node->tcp_wds[0].length); |
884 | node->obuflen -= node->tcp_wds[0].length; |
885 | } else |
886 | node->obuflen = 0; |
887 | |
888 | node->tcp_wds[0].length = 0; |
889 | |
890 | if (node->obuflen == 0) |
891 | return ret; |
892 | } |
893 | |
894 | /* |
895 | * _TCPSend only knows how many wds pointers were passed in when it |
896 | * reads the next one and its pointer is zero (or size is zero?) |
897 | */ |
898 | node->tcp_wds[0].ptr = (Ptr)&node->obuf; |
899 | node->tcp_wds[0].length = node->obuflen; |
900 | node->tcp_wds[1].ptr = 0; |
901 | node->tcp_wds[1].length = 0; |
902 | |
903 | ret = node->tcp_wds[0].length; |
904 | |
905 | error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil, |
906 | true); |
907 | if (error) { |
908 | warn("TCPSend[%d] failed: %d", node->id, error); |
909 | session->ending = true; |
910 | } |
911 | |
912 | /* if we can send in less than 500ms, avoid a uthread switch */ |
913 | while (Ticks - now <= 30) { |
914 | if (node->send_pb.ioResult <= 0) |
915 | goto process_result; |
916 | } |
917 | |
918 | return ret; |
919 | } |
920 | |
921 | void |
922 | telnet_output_iac(struct session *session, const char *iacs, size_t len) |
923 | { |
924 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
925 | |
926 | if (session->ending) |
927 | return; |
928 | |
929 | telnet_escape_output(session); |
930 | |
931 | node->sending_iac = 1; |
932 | session_output(session, iacs, len); |
933 | telnet_escape_output(session); |
934 | node->sending_iac = 0; |
935 | } |
936 | |
937 | void |
938 | telnet_close(struct session *session) |
939 | { |
940 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
941 | size_t len; |
942 | short error; |
943 | unsigned char *tmp; |
944 | |
945 | if (node->from_trusted_proxy) |
946 | session->ban_node_source = false; |
947 | |
948 | if (session->ban_node_source && !node->from_trusted_proxy && |
949 | db->config.trusted_proxy_ip != 0 && |
950 | db->config.trusted_proxy_udp_port != 0) { |
951 | session_logf(session, "Closing telnet connection from %s and " |
952 | "banning IP", node->ip_s); |
953 | |
954 | tmp = (unsigned char *)&node->ip; |
955 | len = snprintf(udp_ban_send_buf, sizeof(udp_ban_send_buf), |
956 | "%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]); |
957 | udp_ban_wds[0].ptr = (Ptr)&udp_ban_send_buf; |
958 | udp_ban_wds[0].length = len; |
959 | udp_ban_wds[1].ptr = 0; |
960 | udp_ban_wds[1].length = 0; |
961 | |
962 | error = _UDPSend(&udp_ban_pb, udp_ban_stream, udp_ban_wds, |
963 | db->config.trusted_proxy_ip, |
964 | db->config.trusted_proxy_udp_port, NULL, NULL, false); |
965 | if (error) |
966 | session_logf(session, "Failed sending IP ban UDP packet: %d", |
967 | error); |
968 | } else { |
969 | session_logf(session, "Closing telnet connection from %s", |
970 | node->ip_s); |
971 | |
972 | _TCPClose(&telnet_exit_pb, node->stream, nil, nil, false); |
973 | /* TODO: allow some time to fully close? */ |
974 | } |
975 | |
976 | error = _TCPRelease(&telnet_exit_pb, node->stream, nil, nil, false); |
977 | if (error) { |
978 | warn("error shutting down telnet session: %d", error); |
979 | return; |
980 | } |
981 | |
982 | session->cookie = NULL; |
983 | node->state = TELNET_PB_STATE_UNUSED; |
984 | } |
985 | |
986 | void |
987 | telnet_print_busy(struct telnet_node *node) |
988 | { |
989 | size_t len, n, olen; |
990 | unsigned long now; |
991 | short error; |
992 | char *data = NULL; |
993 | |
994 | /* manually print to node without session_output */ |
995 | if (db->views[DB_VIEW_NO_FREE_NODES]) { |
996 | data = db->views[DB_VIEW_NO_FREE_NODES]; |
997 | len = strlen(data); |
998 | for (n = 0, olen = 0; n < len && olen < sizeof(node->obuf) - 1; |
999 | n++) { |
1000 | node->obuf[olen++] = data[n]; |
1001 | if (data[n] == '\r' && data[n + 1] != '\n') |
1002 | node->obuf[olen++] = '\n'; |
1003 | } |
1004 | } else { |
1005 | len = snprintf((char *)&node->obuf, sizeof(node->obuf), |
1006 | "No free nodes, please call back later.\r\n"); |
1007 | } |
1008 | |
1009 | node->tcp_wds[0].ptr = (Ptr)&node->obuf; |
1010 | node->tcp_wds[0].length = len; |
1011 | node->tcp_wds[1].ptr = 0; |
1012 | node->tcp_wds[1].length = 0; |
1013 | |
1014 | now = Ticks; |
1015 | error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil, |
1016 | true); |
1017 | if (error) |
1018 | return; |
1019 | |
1020 | for (;;) { |
1021 | /* spin for 500ms to let the send finish */ |
1022 | if (Ticks - now > 30 || node->send_pb.ioResult <= 0) |
1023 | return; |
1024 | } |
1025 | } |