Download
jcs
/subtext
/telnet.c
(View History)
jcs telnet: Static init nit | Latest amendment: 595 on 2024-02-19 |
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 = NULL; |
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 (_TCPInit() != noErr) |
165 | panic("Failed initializing MacTCP"); |
166 | |
167 | /* pre-allocate nodes */ |
168 | for (n = 0; n < MAX_TELNET_NODES; n++) { |
169 | if (telnet_nodes[n] == NULL) { |
170 | telnet_nodes[n] = xmalloczero(sizeof(struct telnet_node)); |
171 | if (telnet_nodes[n] == NULL) |
172 | panic("Failed allocating telnet node %d", n); |
173 | } |
174 | telnet_nodes[n]->id = n; |
175 | } |
176 | |
177 | if (db->config.trusted_proxy_ip != 0 && |
178 | db->config.trusted_proxy_udp_port != 0) { |
179 | udp_ban_rcv_buf = xmalloc(UDP_BAN_RCV_BUF_SIZE); |
180 | if (udp_ban_rcv_buf == NULL) |
181 | panic("Failed allocating UDP ban buf"); |
182 | error = _UDPCreate(&udp_ban_pb, &udp_ban_stream, |
183 | (Ptr)udp_ban_rcv_buf, UDP_BAN_RCV_BUF_SIZE, NULL, NULL, |
184 | NULL, false); |
185 | if (error) |
186 | panic("UDPCreate failed: %d", error); |
187 | } |
188 | } |
189 | |
190 | bool |
191 | telnet_reinit(void) |
192 | { |
193 | telnet_deinit(); |
194 | telnet_init(); |
195 | did_initial_log = false; |
196 | return true; |
197 | } |
198 | |
199 | void |
200 | telnet_deinit(void) |
201 | { |
202 | if (telnet_listener_node) { |
203 | _TCPRelease(&telnet_exit_pb, telnet_listener_node->stream, nil, |
204 | nil, false); |
205 | telnet_listener_node->state = TELNET_PB_STATE_UNUSED; |
206 | telnet_listener_node = NULL; |
207 | } |
208 | |
209 | if (udp_ban_stream) { |
210 | _UDPRelease(&udp_ban_pb, udp_ban_stream, NULL, NULL, false); |
211 | udp_ban_stream = 0; |
212 | } |
213 | |
214 | if (udp_ban_rcv_buf) |
215 | xfree(&udp_ban_rcv_buf); |
216 | } |
217 | |
218 | void |
219 | telnet_atexit(void) |
220 | { |
221 | struct telnet_node *node; |
222 | short n; |
223 | |
224 | for (n = 0; n < MAX_TELNET_NODES; n++) { |
225 | node = telnet_nodes[n]; |
226 | if (!node) |
227 | continue; |
228 | |
229 | if (node->state > TELNET_PB_STATE_UNUSED) |
230 | _TCPRelease(&telnet_exit_pb, node->stream, nil, nil, false); |
231 | |
232 | xfree(&node); |
233 | telnet_nodes[n] = NULL; |
234 | } |
235 | |
236 | telnet_deinit(); |
237 | } |
238 | |
239 | void |
240 | telnet_listen_on_node(struct telnet_node *node) |
241 | { |
242 | char ip_s[20]; |
243 | short error, id; |
244 | ip_addr ip; |
245 | tcp_port port; |
246 | long mask; |
247 | |
248 | id = node->id; |
249 | memset(node, 0, sizeof(struct telnet_node)); |
250 | node->id = id; |
251 | |
252 | snprintf(node->name, sizeof(node->name), "ttyt%d", id); |
253 | |
254 | error = _TCPCreate(&node->listen_pb, &node->stream, |
255 | (Ptr)&node->tcp_buf, sizeof(node->tcp_buf), nil, nil, nil, false); |
256 | if (error) |
257 | panic("TCPCreate[%d] failed: %d", node->id, error); |
258 | |
259 | error = _TCPGetOurIP(&ip, &mask); |
260 | if (error) |
261 | panic("TCPGetOurIP failed: %d", error); |
262 | |
263 | port = db->config.telnet_port; |
264 | |
265 | if (!did_initial_log) { |
266 | long2ip(ip, ip_s); |
267 | logger_printf("[telnet] Listening on %s:%d", ip_s, port); |
268 | did_initial_log = true; |
269 | } |
270 | |
271 | error = _TCPPassiveOpen(&node->listen_pb, node->stream, nil, nil, &ip, |
272 | &port, nil, nil, true); |
273 | if (error) |
274 | panic("TCPPassiveOpen[%d] on port %d failed: %d", |
275 | node->id, db->config.telnet_port, error); |
276 | |
277 | node->state = TELNET_PB_STATE_LISTENING; |
278 | telnet_listener_node = node; |
279 | } |
280 | |
281 | void |
282 | telnet_idle(void) |
283 | { |
284 | struct telnet_node *node; |
285 | short n, error; |
286 | |
287 | if (!db->config.telnet_port) |
288 | return; |
289 | |
290 | for (n = 0; n < MAX_TELNET_NODES; n++) { |
291 | node = telnet_nodes[n]; |
292 | |
293 | switch (node->state) { |
294 | case TELNET_PB_STATE_UNUSED: |
295 | if (telnet_listener_node == NULL) |
296 | telnet_listen_on_node(node); |
297 | break; |
298 | case TELNET_PB_STATE_LISTENING: |
299 | error = _TCPStatus(&node->send_pb, node->stream, |
300 | &telnet_status_pb, nil, nil, false); |
301 | if (error == connectionDoesntExist) { |
302 | /* |
303 | * The connection closed before it could be handled, but |
304 | * telnet_status_pb.connectionState will still be |
305 | * listening, so force it into a bogus state to recycle it. |
306 | */ |
307 | telnet_status_pb.connectionState = -1; |
308 | } |
309 | |
310 | switch (telnet_status_pb.connectionState) { |
311 | case ConnectionStateListening: |
312 | goto next_node; |
313 | case ConnectionStateSYNReceived: |
314 | case ConnectionStateSYNSent: |
315 | /* TODO: enforce our own timeout on these? */ |
316 | break; |
317 | case ConnectionStateEstablished: { |
318 | char *loc = NULL; |
319 | |
320 | /* start up a new socket for listening */ |
321 | telnet_listener_node = NULL; |
322 | |
323 | node->ip = telnet_status_pb.remoteHost; |
324 | long2ip(telnet_status_pb.remoteHost, node->ip_s); |
325 | |
326 | if (db->config.trusted_proxy_ip != 0 && |
327 | db->config.trusted_proxy_ip == node->ip) { |
328 | node->from_trusted_proxy = true; |
329 | snprintf(node->name, sizeof(node->name), "ttyw%d", |
330 | node->id); |
331 | } |
332 | |
333 | if (db->ipdb && !node->from_trusted_proxy) |
334 | loc = ipdb_lookup(db->ipdb, node->ip); |
335 | |
336 | logger_printf("[%s] New telnet connection from %s%s%s%s%s", |
337 | node->name, node->ip_s, |
338 | (loc ? " (" : ""), (loc ? loc : ""), (loc ? ") " : ""), |
339 | node->from_trusted_proxy ? " (via trusted proxy)" : ""); |
340 | |
341 | node->state = TELNET_PB_STATE_CONNECTED; |
342 | node->session = session_create(node->name, |
343 | node->from_trusted_proxy ? "web" : "telnet", |
344 | &telnet_node_funcs); |
345 | if (node->session == NULL) { |
346 | logger_printf("[%s] No free nodes, disconnecting", |
347 | node->name); |
348 | telnet_print_busy(node); |
349 | _TCPRelease(&node->listen_pb, node->stream, nil, |
350 | nil, false); |
351 | node->state = TELNET_PB_STATE_UNUSED; |
352 | if (loc) |
353 | xfree(&loc); |
354 | goto next_node; |
355 | } |
356 | node->session->cookie = (void *)node; |
357 | node->session->tspeed = 19200; |
358 | node->session->is_telnet = true; |
359 | node->session->log.ip_address = telnet_status_pb.remoteHost; |
360 | if (node->from_trusted_proxy) |
361 | node->session->can_nomodem = true; |
362 | if (loc) { |
363 | strlcpy(node->session->location, loc, |
364 | sizeof(node->session->location)); |
365 | strlcpy(node->session->log.location, loc, |
366 | sizeof(node->session->log.location)); |
367 | xfree(&loc); |
368 | } |
369 | break; |
370 | } |
371 | default: |
372 | if (telnet_status_pb.remoteHost == 0) |
373 | long2ip(node->listen_pb.csParam.open.remoteHost, |
374 | node->ip_s); |
375 | else |
376 | long2ip(telnet_status_pb.remoteHost, node->ip_s); |
377 | |
378 | logger_printf("[%s] Telnet connection from " |
379 | "%s in state %d, aborting", node->name, node->ip_s, |
380 | telnet_status_pb.connectionState); |
381 | |
382 | if (node->session) |
383 | node->session->ending = 1; |
384 | else { |
385 | _TCPRelease(&node->listen_pb, node->stream, nil, nil, |
386 | false); |
387 | node->state = TELNET_PB_STATE_UNUSED; |
388 | telnet_listener_node = NULL; |
389 | } |
390 | goto next_node; |
391 | } |
392 | break; |
393 | case TELNET_PB_STATE_CONNECTED: |
394 | if (!node->session) { |
395 | node->state = TELNET_PB_STATE_UNUSED; |
396 | break; |
397 | } |
398 | |
399 | /* |
400 | * Send buffered data any time we can, the user might not be |
401 | * in an explicit session_flush() like during chat |
402 | */ |
403 | telnet_output(node->session); |
404 | break; |
405 | } |
406 | |
407 | next_node: |
408 | continue; |
409 | } |
410 | } |
411 | |
412 | void |
413 | telnet_setup(struct session *session) |
414 | { |
415 | const char iacs[] = { |
416 | IAC, WILL, IAC_SGA, /* we will supress go-ahead */ |
417 | IAC, WILL, IAC_ECHO, /* and we will echo for you */ |
418 | IAC, WILL, IAC_BINARY, /* don't mess with binary data we send */ |
419 | IAC, DO, IAC_BINARY, /* and you can send us binary */ |
420 | IAC, DO, IAC_TSPEED, /* send your TSPEED */ |
421 | IAC, DO, IAC_NAWS, /* send your NAWS */ |
422 | IAC, DO, IAC_TTYPE, /* send your TTYPE */ |
423 | IAC, DO, IAC_NEWENV, /* send your NEWENV vars */ |
424 | }; |
425 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
426 | size_t size = sizeof(iacs); |
427 | |
428 | if (!node->from_trusted_proxy) |
429 | /* we only care about NEWENV when we need REMOTE_ADDR */ |
430 | size -= 3; |
431 | |
432 | telnet_output_iac(session, iacs, size); |
433 | session_flush(session); |
434 | |
435 | /* see if we can quickly read and parse any incoming IAC */ |
436 | telnet_input(session); |
437 | session_flush(session); |
438 | } |
439 | |
440 | short |
441 | telnet_input(struct session *session) |
442 | { |
443 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
444 | unsigned short rlen; |
445 | short error, n, j, used; |
446 | unsigned char c; |
447 | char iac_out[8] = { IAC, 0 }; |
448 | |
449 | error = _TCPStatus(&node->rcv_pb, node->stream, &telnet_status_pb, nil, |
450 | nil, false); |
451 | if (error || |
452 | telnet_status_pb.connectionState != ConnectionStateEstablished) { |
453 | session->ending = 1; |
454 | return 0; |
455 | } |
456 | |
457 | if (telnet_status_pb.amtUnreadData == 0) |
458 | return 0; |
459 | |
460 | rlen = telnet_status_pb.amtUnreadData; |
461 | |
462 | if (session->direct_output) { |
463 | if (session->ibuflen + session->ibufoff + rlen > |
464 | sizeof(session->ibuf)) { |
465 | /* if we're already halfway through the buffer, reset */ |
466 | if (session->ibufoff >= (sizeof(session->ibuf) / 2)) { |
467 | memmove(session->ibuf, session->ibuf + session->ibufoff, |
468 | session->ibuflen); |
469 | session->ibufoff = 0; |
470 | } |
471 | rlen = sizeof(session->ibuf) - session->ibuflen - |
472 | session->ibufoff; |
473 | if (rlen == 0) |
474 | return 0; |
475 | } |
476 | |
477 | error = _TCPRcv(&node->rcv_pb, node->stream, |
478 | (Ptr)(session->ibuf + session->ibufoff + session->ibuflen), &rlen, |
479 | nil, nil, false); |
480 | if (error) { |
481 | session_logf(session, "TCP read failed (%d), closing connection", |
482 | error); |
483 | session->ending = 1; |
484 | return 0; |
485 | } |
486 | if (rlen == 0) |
487 | return 0; |
488 | session->last_input_at = Time; |
489 | session->ibuflen += rlen; |
490 | return rlen; |
491 | } |
492 | |
493 | if (node->ibuflen + rlen > sizeof(node->ibuf)) |
494 | rlen = sizeof(node->ibuf) - node->ibuflen; |
495 | |
496 | error = _TCPRcv(&node->rcv_pb, node->stream, |
497 | (Ptr)(node->ibuf + node->ibuflen), &rlen, nil, nil, false); |
498 | if (error) { |
499 | session_logf(session, "TCP read failed (%d), closing connection", |
500 | error); |
501 | session->ending = true; |
502 | return 0; |
503 | } |
504 | |
505 | if (rlen == 0) |
506 | return 0; |
507 | |
508 | session->last_input_at = Time; |
509 | node->ibuflen += rlen; |
510 | |
511 | used = 0; |
512 | for (n = 0; n < node->ibuflen; n++) { |
513 | if (session->ibuflen >= sizeof(session->ibuf)) |
514 | break; |
515 | |
516 | c = node->ibuf[n]; |
517 | used++; |
518 | |
519 | switch (node->iac_state) { |
520 | case TELNET_IAC_STATE_IDLE: |
521 | if (c == IAC) |
522 | node->iac_state = TELNET_IAC_STATE_IAC; |
523 | else |
524 | session->ibuf[session->ibuflen++] = c; |
525 | break; |
526 | case TELNET_IAC_STATE_IAC: |
527 | switch (c) { |
528 | case IAC: |
529 | /* escaped iac */ |
530 | session->ibuf[session->ibuflen++] = c; |
531 | node->iac_state = TELNET_IAC_STATE_IDLE; |
532 | break; |
533 | case NOP: |
534 | node->iac_state = TELNET_IAC_STATE_IDLE; |
535 | break; |
536 | case WILL: |
537 | /* client will do something */ |
538 | node->iac_state = TELNET_IAC_STATE_WILL; |
539 | break; |
540 | case WONT: |
541 | /* client will not do something */ |
542 | node->iac_state = TELNET_IAC_STATE_WONT; |
543 | break; |
544 | case DO: |
545 | /* client wants us to do something */ |
546 | node->iac_state = TELNET_IAC_STATE_DO; |
547 | break; |
548 | case DONT: |
549 | /* client wants us not to do something */ |
550 | node->iac_state = TELNET_IAC_STATE_DONT; |
551 | break; |
552 | case SB: |
553 | /* sub-negotiate */ |
554 | node->iac_state = TELNET_IAC_STATE_SB; |
555 | node->sb_len = 0; |
556 | break; |
557 | default: |
558 | /* unsupported command, ignore it */ |
559 | node->iac_state = TELNET_IAC_STATE_IDLE; |
560 | break; |
561 | } |
562 | break; |
563 | case TELNET_IAC_STATE_SB: { |
564 | unsigned short sboff; |
565 | |
566 | /* keep reading until we see [^IAC] IAC SE */ |
567 | if (c == SE && node->sb_len > 0 && |
568 | node->iac_sb[node->sb_len - 1] == IAC) { |
569 | switch (node->iac_sb[0]) { |
570 | case IAC_NAWS: |
571 | /* IAC SB NAWS 0 80 0 24 (80x24) */ |
572 | /* IAC SB NAWS 1 255 255 1 123 (256x378) */ |
573 | /* if either value is 255, it will be 255,255 */ |
574 | sboff = 1; |
575 | |
576 | if (node->iac_sb[sboff] == 255) |
577 | sboff++; |
578 | session->terminal_columns = (node->iac_sb[sboff++] << 8); |
579 | if (node->iac_sb[sboff] == 255) |
580 | sboff++; |
581 | session->terminal_columns |= node->iac_sb[sboff++]; |
582 | |
583 | if (node->iac_sb[sboff] == 255) |
584 | sboff++; |
585 | session->terminal_lines = (node->iac_sb[sboff++] << 8); |
586 | if (node->iac_sb[sboff] == 255) |
587 | sboff++; |
588 | session->terminal_lines |= node->iac_sb[sboff++]; |
589 | if (node->naws_count++ < 20) |
590 | session_logf_buffered(session, "IAC NAWS %d, %d", |
591 | session->terminal_columns, |
592 | session->terminal_lines); |
593 | break; |
594 | case IAC_TTYPE: |
595 | /* IAC SB TTYPE IS XTERM IAC SE */ |
596 | node->iac_sb[node->sb_len - 1] = '\0'; |
597 | strlcpy(session->terminal_type, |
598 | (char *)&node->iac_sb + 2, |
599 | sizeof(session->terminal_type)); |
600 | session_logf_buffered(session, "IAC TTYPE IS %s", |
601 | session->terminal_type); |
602 | |
603 | for (j = 0; vt100_terms[j] != NULL; j++) { |
604 | if (strncasecmp(session->terminal_type, |
605 | vt100_terms[j], strlen(vt100_terms[j])) == 0) { |
606 | session_logf_buffered(session, |
607 | "Activating vt100 ANSI support for " |
608 | "matching %s", vt100_terms[j]); |
609 | session->vt100 = true; |
610 | break; |
611 | } |
612 | } |
613 | |
614 | break; |
615 | case IAC_NEWENV: { |
616 | char k[sizeof(node->iac_sb)], v[sizeof(node->iac_sb)]; |
617 | unsigned char l = 0; |
618 | bool inv = false; |
619 | char *loc = NULL; |
620 | |
621 | k[0] = v[0] = '\0'; |
622 | |
623 | /* \40\0\0USER\1blah\0DISPLAY\1... */ |
624 | for (j = 3; j < node->sb_len; j++) { |
625 | if (j == node->sb_len - 1 || |
626 | node->iac_sb[j] == 0 || node->iac_sb[j] == 3) { |
627 | v[l] = '\0'; |
628 | if (l) { |
629 | loc = NULL; |
630 | |
631 | if (node->from_trusted_proxy && |
632 | strcmp(k, "REMOTE_ADDR") == 0) { |
633 | node->ip = ip2long(v); |
634 | strlcpy(node->ip_s, v, |
635 | sizeof(node->ip_s)); |
636 | node->session->log.ip_address = |
637 | node->ip; |
638 | |
639 | if (db->ipdb && |
640 | (loc = ipdb_lookup(db->ipdb, |
641 | node->ip))) { |
642 | strlcpy(node->session->location, |
643 | loc, |
644 | sizeof(node->session->location)); |
645 | strlcpy(node->session->log.location, |
646 | loc, |
647 | sizeof(node->session->log.location)); |
648 | } |
649 | } |
650 | |
651 | session_logf_buffered(session, |
652 | "NEWENV \"%s\" = \"%s\"%s%s%s", k, v, |
653 | loc ? " (" : "", |
654 | loc ? loc : "", |
655 | loc ? ")" : ""); |
656 | |
657 | if (loc) |
658 | xfree(&loc); |
659 | } |
660 | l = 0; |
661 | inv = false; |
662 | } else if (node->iac_sb[j] == 1) { |
663 | k[l] = '\0'; |
664 | l = 0; |
665 | inv = true; |
666 | } else { |
667 | if (inv) |
668 | v[l++] = node->iac_sb[j]; |
669 | else |
670 | k[l++] = node->iac_sb[j]; |
671 | } |
672 | } |
673 | break; |
674 | } |
675 | case IAC_TSPEED: { |
676 | unsigned long tspeed; |
677 | |
678 | /* IAC SB TSPEED IS 38400,19200 SE */ |
679 | node->iac_sb[node->sb_len - 1] = '\0'; |
680 | |
681 | session_logf_buffered(session, "IAC TSPEED IS %s", |
682 | node->iac_sb + 2); |
683 | |
684 | for (j = 2; j < node->sb_len; j++) { |
685 | if (node->iac_sb[j] == ',') { |
686 | node->iac_sb[j] = '\0'; |
687 | break; |
688 | } |
689 | } |
690 | |
691 | tspeed = atol((const char *)(node->iac_sb + 2)); |
692 | if (tspeed > 0) { |
693 | if (tspeed > 57600) |
694 | tspeed = 57600; |
695 | node->session->tspeed = node->session->log.tspeed = |
696 | (unsigned short)tspeed; |
697 | } |
698 | break; |
699 | } |
700 | } |
701 | node->iac_state = TELNET_IAC_STATE_IDLE; |
702 | } else { |
703 | /* accumulate bytes into iac_sb buffer */ |
704 | if (node->sb_len < sizeof(node->iac_sb)) |
705 | node->iac_sb[node->sb_len++] = c; |
706 | else { |
707 | /* IAC overflow */ |
708 | node->iac_state = TELNET_IAC_STATE_IDLE; |
709 | node->sb_len = 0; |
710 | session_logf(session, "IAC SB overflow"); |
711 | } |
712 | } |
713 | break; |
714 | } |
715 | case TELNET_IAC_STATE_WILL: |
716 | switch (c) { |
717 | case IAC_ECHO: |
718 | case IAC_SGA: |
719 | iac_out[1] = DO; |
720 | iac_out[2] = IAC_ECHO; |
721 | telnet_output_iac(session, iac_out, 3); |
722 | break; |
723 | case IAC_NEWENV: |
724 | if (!node->from_trusted_proxy) |
725 | break; |
726 | case IAC_TTYPE: |
727 | case IAC_TSPEED: |
728 | iac_out[1] = SB; |
729 | iac_out[2] = c; |
730 | iac_out[3] = SEND; |
731 | iac_out[4] = IAC; |
732 | iac_out[5] = SE; |
733 | telnet_output_iac(session, iac_out, 6); |
734 | break; |
735 | case IAC_LINEMODE: |
736 | #if 0 |
737 | iac_out[1] = SB; |
738 | iac_out[2] = IAC_LINEMODE; |
739 | iac_out[3] = 1; |
740 | iac_out[4] = 0; |
741 | iac_out[5] = IAC; |
742 | iac_out[6] = SE; |
743 | telnet_output_iac(session, iac_out, 7); |
744 | #endif |
745 | break; |
746 | case IAC_ENCRYPT: |
747 | iac_out[1] = DONT; |
748 | iac_out[2] = IAC_ENCRYPT; |
749 | telnet_output_iac(session, iac_out, 3); |
750 | break; |
751 | } |
752 | node->iac_state = TELNET_IAC_STATE_IDLE; |
753 | break; |
754 | case TELNET_IAC_STATE_WONT: |
755 | /* we don't care about any of these yet */ |
756 | node->iac_state = TELNET_IAC_STATE_IDLE; |
757 | break; |
758 | case TELNET_IAC_STATE_DO: |
759 | switch (c) { |
760 | case IAC_BINARY: |
761 | iac_out[1] = WILL; |
762 | iac_out[2] = IAC_BINARY; |
763 | telnet_output_iac(session, iac_out, 3); |
764 | break; |
765 | case IAC_ECHO: |
766 | case IAC_NAWS: |
767 | case IAC_TSPEED: |
768 | case IAC_FLOWCTRL: |
769 | case IAC_SGA: |
770 | case IAC_LINEMODE: |
771 | /* we already sent WILLs for these at the beginning */ |
772 | break; |
773 | default: |
774 | iac_out[1] = WONT; |
775 | iac_out[2] = c; |
776 | telnet_output_iac(session, iac_out, 3); |
777 | break; |
778 | } |
779 | node->iac_state = TELNET_IAC_STATE_IDLE; |
780 | break; |
781 | case TELNET_IAC_STATE_DONT: |
782 | node->iac_state = TELNET_IAC_STATE_IDLE; |
783 | break; |
784 | default: |
785 | warn("telnet_input in bogus IAC state %d", node->iac_state); |
786 | break; |
787 | } |
788 | } |
789 | |
790 | if (used == node->ibuflen) |
791 | node->ibuflen = 0; |
792 | else { |
793 | memmove(node->ibuf, node->ibuf + used, node->ibuflen - used); |
794 | node->ibuflen -= used; |
795 | } |
796 | |
797 | if (node->obuflen) |
798 | session_flush(session); |
799 | |
800 | return rlen; |
801 | } |
802 | |
803 | short |
804 | telnet_escape_output(struct session *session) |
805 | { |
806 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
807 | unsigned char c; |
808 | unsigned long added = 0; |
809 | |
810 | if (session->obuflen == 0 || session->ending) |
811 | return 0; |
812 | |
813 | if (session->obuflen == 1 && session->obuf[0] != IAC) { |
814 | node->obuf[node->obuflen++] = session->obuf[0]; |
815 | session->obuflen = 0; |
816 | } else if (session->direct_output) { |
817 | added = MIN(session->obuflen, sizeof(node->obuf) - node->obuflen); |
818 | memcpy(node->obuf + node->obuflen, session->obuf, added); |
819 | node->obuflen += added; |
820 | } else { |
821 | /* copy session obuf to node buffer, escaping IACs */ |
822 | for (added = 0; added < session->obuflen; added++) { |
823 | if (node->obuflen >= sizeof(node->obuf) - 1) |
824 | break; |
825 | |
826 | c = session->obuf[added]; |
827 | |
828 | if (!node->sending_iac && c == IAC) { |
829 | node->obuf[node->obuflen++] = IAC; |
830 | node->obuf[node->obuflen++] = IAC; |
831 | } else |
832 | node->obuf[node->obuflen++] = c; |
833 | } |
834 | } |
835 | |
836 | if (added > 0) { |
837 | if (added < session->obuflen) |
838 | memmove(session->obuf, session->obuf + added, |
839 | session->obuflen - added); |
840 | |
841 | session->obuflen -= added; |
842 | } |
843 | |
844 | return added; |
845 | } |
846 | |
847 | short |
848 | telnet_output(struct session *session) |
849 | { |
850 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
851 | unsigned long now = Ticks; |
852 | short error, ret = 0; |
853 | |
854 | if (session->obuflen != 0) |
855 | telnet_escape_output(session); |
856 | |
857 | if (node->obuflen == 0 || session->ending) |
858 | return 0; |
859 | |
860 | /* wait for previous _TCPSend to complete */ |
861 | while (node->send_pb.ioResult > 0) { |
862 | if (uthread_current == NULL) |
863 | /* this was an opportunistic flush anyway, no sense waiting */ |
864 | return 0; |
865 | |
866 | uthread_yield(); |
867 | |
868 | if (session->ending) |
869 | return 0; |
870 | } |
871 | |
872 | process_result: |
873 | if (node->tcp_wds[0].length) { |
874 | /* shift out old data */ |
875 | if (node->obuflen < node->tcp_wds[0].length) |
876 | panic("data in tcp_wds > obuflen"); |
877 | |
878 | if (node->obuflen > node->tcp_wds[0].length) { |
879 | memmove(node->obuf, node->obuf + node->tcp_wds[0].length, |
880 | node->obuflen - node->tcp_wds[0].length); |
881 | node->obuflen -= node->tcp_wds[0].length; |
882 | } else |
883 | node->obuflen = 0; |
884 | |
885 | node->tcp_wds[0].length = 0; |
886 | |
887 | if (node->obuflen == 0) |
888 | return ret; |
889 | } |
890 | |
891 | /* |
892 | * _TCPSend only knows how many wds pointers were passed in when it |
893 | * reads the next one and its pointer is zero (or size is zero?) |
894 | */ |
895 | node->tcp_wds[0].ptr = (Ptr)&node->obuf; |
896 | node->tcp_wds[0].length = node->obuflen; |
897 | node->tcp_wds[1].ptr = 0; |
898 | node->tcp_wds[1].length = 0; |
899 | |
900 | ret = node->tcp_wds[0].length; |
901 | |
902 | error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil, |
903 | true); |
904 | if (error) { |
905 | warn("TCPSend[%d] failed: %d", node->id, error); |
906 | session->ending = true; |
907 | } |
908 | |
909 | /* if we can send in less than 500ms, avoid a uthread switch */ |
910 | while (Ticks - now <= 30) { |
911 | if (node->send_pb.ioResult <= 0) |
912 | goto process_result; |
913 | } |
914 | |
915 | return ret; |
916 | } |
917 | |
918 | void |
919 | telnet_output_iac(struct session *session, const char *iacs, size_t len) |
920 | { |
921 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
922 | |
923 | if (session->ending) |
924 | return; |
925 | |
926 | telnet_escape_output(session); |
927 | |
928 | node->sending_iac = 1; |
929 | session_output(session, iacs, len); |
930 | telnet_escape_output(session); |
931 | node->sending_iac = 0; |
932 | } |
933 | |
934 | void |
935 | telnet_close(struct session *session) |
936 | { |
937 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
938 | size_t len; |
939 | short error; |
940 | unsigned char *tmp; |
941 | |
942 | if (node->from_trusted_proxy) |
943 | session->ban_node_source = false; |
944 | |
945 | if (session->ban_node_source && !node->from_trusted_proxy && |
946 | db->config.trusted_proxy_ip != 0 && |
947 | db->config.trusted_proxy_udp_port != 0) { |
948 | session_logf(session, "Closing telnet connection from %s and " |
949 | "banning IP", node->ip_s); |
950 | |
951 | tmp = (unsigned char *)&node->ip; |
952 | len = snprintf(udp_ban_send_buf, sizeof(udp_ban_send_buf), |
953 | "%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]); |
954 | udp_ban_wds[0].ptr = (Ptr)&udp_ban_send_buf; |
955 | udp_ban_wds[0].length = len; |
956 | udp_ban_wds[1].ptr = 0; |
957 | udp_ban_wds[1].length = 0; |
958 | |
959 | error = _UDPSend(&udp_ban_pb, udp_ban_stream, udp_ban_wds, |
960 | db->config.trusted_proxy_ip, |
961 | db->config.trusted_proxy_udp_port, NULL, NULL, false); |
962 | if (error) |
963 | session_logf(session, "Failed sending IP ban UDP packet: %d", |
964 | error); |
965 | } else { |
966 | session_logf(session, "Closing telnet connection from %s", |
967 | node->ip_s); |
968 | |
969 | _TCPClose(&telnet_exit_pb, node->stream, nil, nil, false); |
970 | /* TODO: allow some time to fully close? */ |
971 | } |
972 | |
973 | error = _TCPRelease(&telnet_exit_pb, node->stream, nil, nil, false); |
974 | if (error) { |
975 | warn("error shutting down telnet session: %d", error); |
976 | return; |
977 | } |
978 | |
979 | session->cookie = NULL; |
980 | node->state = TELNET_PB_STATE_UNUSED; |
981 | } |
982 | |
983 | void |
984 | telnet_print_busy(struct telnet_node *node) |
985 | { |
986 | size_t len, n, olen; |
987 | unsigned long now; |
988 | short error; |
989 | char *data = NULL; |
990 | |
991 | /* manually print to node without session_output */ |
992 | if (db->views[DB_VIEW_NO_FREE_NODES]) { |
993 | data = db->views[DB_VIEW_NO_FREE_NODES]; |
994 | len = strlen(data); |
995 | for (n = 0, olen = 0; n < len && olen < sizeof(node->obuf) - 1; |
996 | n++) { |
997 | node->obuf[olen++] = data[n]; |
998 | if (data[n] == '\r' && data[n + 1] != '\n') |
999 | node->obuf[olen++] = '\n'; |
1000 | } |
1001 | } else { |
1002 | len = snprintf((char *)&node->obuf, sizeof(node->obuf), |
1003 | "No free nodes, please call back later.\r\n"); |
1004 | } |
1005 | |
1006 | node->tcp_wds[0].ptr = (Ptr)&node->obuf; |
1007 | node->tcp_wds[0].length = len; |
1008 | node->tcp_wds[1].ptr = 0; |
1009 | node->tcp_wds[1].length = 0; |
1010 | |
1011 | now = Ticks; |
1012 | error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil, |
1013 | true); |
1014 | if (error) |
1015 | return; |
1016 | |
1017 | for (;;) { |
1018 | /* spin for 500ms to let the send finish */ |
1019 | if (Ticks - now > 30 || node->send_pb.ioResult <= 0) |
1020 | return; |
1021 | } |
1022 | } |