Download
jcs
/subtext
/telnet.c
(View History)
jcs telnet: Add failsafe to avoid sending more than sizeof(obuf) | Latest amendment: 267 on 2022-10-26 |
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 SB 250 /* start of sub-negotiation options */ |
29 | #define WILL 251 /* confirm willingness to negotiate */ |
30 | #define WONT 252 /* confirm unwillingness to negotiate */ |
31 | #define DO 253 /* indicate willingness to negotiate */ |
32 | #define DONT 254 /* indicate unwillingness to negotiate */ |
33 | #define IAC 255 /* start of a negotiation sequence */ |
34 | |
35 | #define IS 0 /* sub-negotiation */ |
36 | #define SEND 1 /* sub-negotiation */ |
37 | |
38 | #define IAC_BINARY 0 /* Transmit Binary */ |
39 | #define IAC_ECHO 1 /* Echo Option */ |
40 | #define IAC_SGA 3 /* Suppress Go Ahead Option */ |
41 | #define IAC_STATUS 5 /* Status Option */ |
42 | #define IAC_TM 6 /* Timing Mark Option */ |
43 | #define IAC_NAOCRD 10 /* Output Carriage-Return Disposition Option */ |
44 | #define IAC_NAOHTS 11 /* Output Horizontal Tabstops Option */ |
45 | #define IAC_NAOHTD 12 /* Output Horizontal Tab Disposition Option */ |
46 | #define IAC_NAOFFD 13 /* Output Formfeed Disposition Option */ |
47 | #define IAC_NAOVTS 14 /* Output Vertical Tabstops Option */ |
48 | #define IAC_NAOVTD 15 /* Output Vertical Tab Disposition Option */ |
49 | #define IAC_NAOLFD 16 /* Output Linefeed Disposition */ |
50 | #define IAC_XASCII 17 /* Extended Ascii Option */ |
51 | #define IAC_LOGOUT 18 /* Logout Option */ |
52 | #define IAC_BM 19 /* Byte Macro Option */ |
53 | #define IAC_SUPDUP 22 /* SUPDUP-OUTPUT Option */ |
54 | #define IAC_SENDLOC 23 /* SEND-LOCATION Option */ |
55 | #define IAC_TTYPE 24 /* Terminal Type Option */ |
56 | #define IAC_EOR 25 /* End of Record Option */ |
57 | #define IAC_OUTMRK 27 /* Marking Telnet Option */ |
58 | #define IAC_TTYLOC 28 /* Terminal Location Number Option */ |
59 | #define IAC_DET 20 /* Data Entry Terminal Option DODIIS */ |
60 | #define IAC_X3PAD 30 /* X.3 PAD Option */ |
61 | #define IAC_NAWS 31 /* Window Size Option */ |
62 | #define IAC_TSPEED 32 /* Terminal Speed Option */ |
63 | #define IAC_FLOWCTRL 33 /* Remote Flow Control Option */ |
64 | #define IAC_LINEMODE 34 /* Linemode Option */ |
65 | #define IAC_XDISPLOC 35 /* X Display Location Option */ |
66 | #define IAC_ENVIRON 36 /* Environment Option */ |
67 | #define IAC_AUTH 37 /* Authentication */ |
68 | #define IAC_ENCRYPT 38 /* Encryption Option */ |
69 | #define IAC_NEWENV 39 /* Environment Option */ |
70 | #define IAC_CHARSET 42 /* Charset Option */ |
71 | #define IAC_COMPORT 44 /* Com Port Control Option */ |
72 | |
73 | enum { |
74 | TELNET_PB_STATE_UNUSED = 0, |
75 | TELNET_PB_STATE_LISTENING, |
76 | TELNET_PB_STATE_CONNECTED |
77 | }; |
78 | |
79 | enum { |
80 | TELNET_IAC_STATE_IDLE = 0, |
81 | TELNET_IAC_STATE_IAC, |
82 | TELNET_IAC_STATE_WILL, |
83 | TELNET_IAC_STATE_WONT, |
84 | TELNET_IAC_STATE_DO, |
85 | TELNET_IAC_STATE_DONT, |
86 | TELNET_IAC_STATE_SB |
87 | }; |
88 | |
89 | struct telnet_node { |
90 | short id; |
91 | short state; |
92 | char name[8]; |
93 | ip_addr ip; |
94 | char ip_s[20]; |
95 | struct session *session; |
96 | unsigned short obuflen; |
97 | unsigned char obuf[512]; |
98 | unsigned char ibuf[64]; |
99 | unsigned short ibuflen; |
100 | unsigned short ibufoff; |
101 | unsigned short sending_iac; |
102 | unsigned short escaped_obuflen; |
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; |
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_setup(struct session *session); |
142 | void telnet_listen_on_node(struct telnet_node *node); |
143 | void telnet_output_iac(struct session *session, const char *iacs, |
144 | size_t len); |
145 | void telnet_print_busy(struct telnet_node *node); |
146 | |
147 | struct node_funcs telnet_node_funcs = { |
148 | telnet_setup, |
149 | telnet_input, |
150 | telnet_output, |
151 | telnet_close |
152 | }; |
153 | |
154 | void |
155 | telnet_init(void) |
156 | { |
157 | short n, error; |
158 | |
159 | if (!db->config.telnet_port) |
160 | return; |
161 | |
162 | logger_printf("[telnet] Initializing MacTCP"); |
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 | telnet_nodes[n] = xmalloczero(sizeof(struct telnet_node), |
170 | "telnet node"); |
171 | telnet_nodes[n]->id = n; |
172 | |
173 | if (n == 0) |
174 | telnet_listen_on_node(telnet_nodes[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, "udp_ban_rcv_buf"); |
180 | error = _UDPCreate(&udp_ban_pb, &udp_ban_stream, |
181 | (Ptr)udp_ban_rcv_buf, UDP_BAN_RCV_BUF_SIZE, NULL, NULL, |
182 | NULL, false); |
183 | if (error) |
184 | panic("UDPCreate failed: %d", error); |
185 | } |
186 | } |
187 | |
188 | void |
189 | telnet_atexit(void) |
190 | { |
191 | struct telnet_node *node; |
192 | short n, error; |
193 | |
194 | for (n = 0; n < MAX_TELNET_NODES; n++) { |
195 | node = telnet_nodes[n]; |
196 | if (!node) |
197 | continue; |
198 | |
199 | if (node->state > TELNET_PB_STATE_UNUSED) |
200 | _TCPRelease(&telnet_exit_pb, node->stream, nil, nil, false); |
201 | |
202 | xfree(&node); |
203 | telnet_nodes[n] = NULL; |
204 | } |
205 | |
206 | telnet_listener_node = NULL; |
207 | |
208 | if (db->config.trusted_proxy_ip != 0 && |
209 | db->config.trusted_proxy_udp_port != 0) |
210 | _UDPRelease(&udp_ban_pb, udp_ban_stream, NULL, NULL, false); |
211 | } |
212 | |
213 | void |
214 | telnet_listen_on_node(struct telnet_node *node) |
215 | { |
216 | char ip_s[20]; |
217 | short error, id; |
218 | ip_addr ip; |
219 | tcp_port port; |
220 | long mask; |
221 | |
222 | id = node->id; |
223 | memset(node, 0, sizeof(struct telnet_node)); |
224 | node->id = id; |
225 | |
226 | snprintf(node->name, sizeof(node->name), "ttyt%d", id); |
227 | |
228 | error = _TCPCreate(&node->listen_pb, &node->stream, |
229 | (Ptr)&node->tcp_buf, sizeof(node->tcp_buf), nil, nil, nil, false); |
230 | if (error) |
231 | panic("TCPCreate[%d] failed: %d", node->id, error); |
232 | |
233 | error = _TCPGetOurIP(&ip, &mask); |
234 | if (error) |
235 | panic("TCPGetOurIP failed: %d", error); |
236 | |
237 | port = db->config.telnet_port; |
238 | |
239 | if (!did_initial_log) { |
240 | long2ip(ip, ip_s); |
241 | logger_printf("[telnet] Listening on %s:%d", ip_s, port); |
242 | did_initial_log = true; |
243 | } |
244 | |
245 | error = _TCPPassiveOpen(&node->listen_pb, node->stream, nil, nil, &ip, |
246 | &port, nil, nil, true); |
247 | if (error) |
248 | panic("TCPPassiveOpen[%d] on port %d failed: %d", |
249 | node->id, db->config.telnet_port, error); |
250 | |
251 | node->state = TELNET_PB_STATE_LISTENING; |
252 | telnet_listener_node = node; |
253 | } |
254 | |
255 | void |
256 | telnet_idle(void) |
257 | { |
258 | struct telnet_node *node; |
259 | short n, j, error; |
260 | |
261 | if (!db->config.telnet_port) |
262 | return; |
263 | |
264 | for (n = 0; n < MAX_TELNET_NODES; n++) { |
265 | node = telnet_nodes[n]; |
266 | |
267 | switch (node->state) { |
268 | case TELNET_PB_STATE_UNUSED: |
269 | if (telnet_listener_node == NULL) |
270 | telnet_listen_on_node(node); |
271 | break; |
272 | case TELNET_PB_STATE_LISTENING: |
273 | error = _TCPStatus(&node->send_pb, node->stream, |
274 | &telnet_status_pb, nil, nil, false); |
275 | if (error == connectionDoesntExist) { |
276 | /* |
277 | * The connection closed before it could be handled, but |
278 | * telnet_status_pb.connectionState will still be |
279 | * listening, so force it into a bogus state to recycle it. |
280 | */ |
281 | telnet_status_pb.connectionState = -1; |
282 | } |
283 | |
284 | switch (telnet_status_pb.connectionState) { |
285 | case ConnectionStateListening: |
286 | goto next_node; |
287 | case ConnectionStateSYNReceived: |
288 | case ConnectionStateSYNSent: |
289 | /* TODO: enforce our own timeout on these? */ |
290 | break; |
291 | case ConnectionStateEstablished: |
292 | /* start up a new socket for listening */ |
293 | telnet_listener_node = NULL; |
294 | |
295 | node->ip = telnet_status_pb.remoteHost; |
296 | long2ip(telnet_status_pb.remoteHost, node->ip_s); |
297 | |
298 | if (db->config.trusted_proxy_ip != 0 && |
299 | db->config.trusted_proxy_ip == node->ip) { |
300 | node->from_trusted_proxy = true; |
301 | snprintf(node->name, sizeof(node->name), "ttyw%d", |
302 | node->id); |
303 | } |
304 | |
305 | logger_printf("[%s] New telnet connection from %s%s", |
306 | node->name, node->ip_s, |
307 | node->from_trusted_proxy ? " (via trusted proxy)" : ""); |
308 | |
309 | node->state = TELNET_PB_STATE_CONNECTED; |
310 | node->session = session_create(node->name, |
311 | node->from_trusted_proxy ? "web" : "telnet", |
312 | &telnet_node_funcs); |
313 | if (node->session == NULL) { |
314 | logger_printf("[%s] No free nodes, disconnecting", |
315 | node->name); |
316 | telnet_print_busy(node); |
317 | _TCPRelease(&node->listen_pb, node->stream, nil, |
318 | nil, false); |
319 | node->state = TELNET_PB_STATE_UNUSED; |
320 | goto next_node; |
321 | } |
322 | node->session->cookie = (void *)node; |
323 | node->session->tspeed = 19200; |
324 | node->session->is_telnet = true; |
325 | node->session->log.ip_address = telnet_status_pb.remoteHost; |
326 | break; |
327 | default: |
328 | if (telnet_status_pb.remoteHost == 0) |
329 | long2ip(node->listen_pb.csParam.open.remoteHost, |
330 | node->ip_s); |
331 | else |
332 | long2ip(telnet_status_pb.remoteHost, node->ip_s); |
333 | |
334 | logger_printf("[%s] Telnet connection from " |
335 | "%s in state %d, aborting", node->name, node->ip_s, |
336 | telnet_status_pb.connectionState); |
337 | |
338 | if (node->session) |
339 | node->session->ending = 1; |
340 | else { |
341 | _TCPRelease(&node->listen_pb, node->stream, nil, nil, |
342 | false); |
343 | node->state = TELNET_PB_STATE_UNUSED; |
344 | telnet_listener_node = NULL; |
345 | } |
346 | goto next_node; |
347 | } |
348 | break; |
349 | case TELNET_PB_STATE_CONNECTED: |
350 | if (!node->session) { |
351 | node->state = TELNET_PB_STATE_UNUSED; |
352 | break; |
353 | } |
354 | |
355 | /* |
356 | * Send buffered data any time we can, the user might not be |
357 | * in an explicit session_flush() like during chat |
358 | */ |
359 | telnet_output(node->session); |
360 | break; |
361 | } |
362 | |
363 | next_node: |
364 | continue; |
365 | } |
366 | } |
367 | |
368 | void |
369 | telnet_setup(struct session *session) |
370 | { |
371 | const char iacs[] = { |
372 | IAC, WILL, IAC_SGA, /* we will supress go-ahead */ |
373 | IAC, WILL, IAC_ECHO, /* and we will echo for you */ |
374 | IAC, DO, IAC_TSPEED, /* send your TSPEED */ |
375 | IAC, DO, IAC_NAWS, /* send your NAWS */ |
376 | IAC, DO, IAC_TTYPE, /* send your TTYPE */ |
377 | IAC, DO, IAC_NEWENV, /* send your NEWENV vars */ |
378 | |
379 | /* IAC, DO, IAC_LINEMODE, */ |
380 | }; |
381 | |
382 | telnet_output_iac(session, iacs, sizeof(iacs)); |
383 | } |
384 | |
385 | short |
386 | telnet_input(struct session *session) |
387 | { |
388 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
389 | unsigned short rlen; |
390 | short error, n, j, used; |
391 | unsigned char c; |
392 | char iac_out[8] = { IAC, 0 }; |
393 | |
394 | error = _TCPStatus(&node->rcv_pb, node->stream, &telnet_status_pb, nil, |
395 | nil, false); |
396 | if (error || |
397 | telnet_status_pb.connectionState != ConnectionStateEstablished) { |
398 | session->ending = 1; |
399 | return 0; |
400 | } |
401 | |
402 | if (telnet_status_pb.amtUnreadData == 0) |
403 | return 0; |
404 | |
405 | rlen = telnet_status_pb.amtUnreadData; |
406 | |
407 | if (session->transferring_file) { |
408 | if (session->ibuflen + session->ibufoff + rlen > |
409 | sizeof(session->ibuf)) { |
410 | /* if we're already halfway through the buffer, reset */ |
411 | if (session->ibufoff >= (sizeof(session->ibuf) / 2)) { |
412 | memmove(session->ibuf, session->ibuf + session->ibufoff, |
413 | session->ibuflen); |
414 | session->ibufoff = 0; |
415 | } |
416 | rlen = sizeof(session->ibuf) - session->ibuflen - |
417 | session->ibufoff; |
418 | if (rlen == 0) |
419 | return 0; |
420 | } |
421 | |
422 | error = _TCPRcv(&node->rcv_pb, node->stream, |
423 | (Ptr)(session->ibuf + session->ibufoff + session->ibuflen), &rlen, |
424 | nil, nil, false); |
425 | if (error) { |
426 | session_logf(session, "TCP read failed (%d), closing connection", |
427 | error); |
428 | session->ending = 1; |
429 | return 0; |
430 | } |
431 | if (rlen == 0) |
432 | return 0; |
433 | session->last_input_at = Time; |
434 | session->ibuflen += rlen; |
435 | return rlen; |
436 | } |
437 | |
438 | if (node->ibuflen + rlen > sizeof(node->ibuf)) |
439 | rlen = sizeof(node->ibuf) - node->ibuflen; |
440 | |
441 | error = _TCPRcv(&node->rcv_pb, node->stream, |
442 | (Ptr)(node->ibuf + node->ibuflen), &rlen, nil, nil, false); |
443 | if (error) { |
444 | session_logf(session, "TCP read failed (%d), closing connection", |
445 | error); |
446 | session->ending = 1; |
447 | return 0; |
448 | } |
449 | |
450 | if (rlen == 0) |
451 | return 0; |
452 | |
453 | session->last_input_at = Time; |
454 | node->ibuflen += rlen; |
455 | |
456 | used = 0; |
457 | for (n = 0; n < node->ibuflen; n++) { |
458 | if (session->ibuflen >= sizeof(session->ibuf)) |
459 | break; |
460 | |
461 | c = node->ibuf[n]; |
462 | used++; |
463 | |
464 | switch (node->iac_state) { |
465 | case TELNET_IAC_STATE_IDLE: |
466 | if (c == IAC) |
467 | node->iac_state = TELNET_IAC_STATE_IAC; |
468 | else |
469 | session->ibuf[session->ibuflen++] = c; |
470 | break; |
471 | case TELNET_IAC_STATE_IAC: |
472 | switch (c) { |
473 | case IAC: |
474 | /* escaped iac */ |
475 | session->ibuf[session->ibuflen++] = c; |
476 | node->iac_state = TELNET_IAC_STATE_IDLE; |
477 | break; |
478 | case WILL: |
479 | /* client will do something */ |
480 | node->iac_state = TELNET_IAC_STATE_WILL; |
481 | break; |
482 | case WONT: |
483 | /* client will not do something */ |
484 | node->iac_state = TELNET_IAC_STATE_WONT; |
485 | break; |
486 | case DO: |
487 | /* client wants us to do something */ |
488 | node->iac_state = TELNET_IAC_STATE_DO; |
489 | break; |
490 | case DONT: |
491 | /* client wants us not to do something */ |
492 | node->iac_state = TELNET_IAC_STATE_DONT; |
493 | break; |
494 | case SB: |
495 | /* sub-negotiate */ |
496 | node->iac_state = TELNET_IAC_STATE_SB; |
497 | node->sb_len = 0; |
498 | break; |
499 | default: |
500 | node->iac_state = TELNET_IAC_STATE_IDLE; |
501 | /* XXX: what are we supposed to do here? */ |
502 | session->ibuf[session->ibuflen++] = IAC; |
503 | session->ibuf[session->ibuflen++] = c; |
504 | break; |
505 | } |
506 | break; |
507 | case TELNET_IAC_STATE_SB: { |
508 | unsigned short sboff; |
509 | |
510 | /* keep reading until we see [^IAC] IAC SE */ |
511 | if (c == SE && node->sb_len > 0 && |
512 | node->iac_sb[node->sb_len - 1] == IAC) { |
513 | switch (node->iac_sb[0]) { |
514 | case IAC_NAWS: |
515 | /* IAC SB NAWS 0 80 0 24 (80x24) */ |
516 | /* IAC SB NAWS 1 255 255 1 123 (256x378) */ |
517 | /* if either value is 255, it will be 255,255 */ |
518 | sboff = 1; |
519 | |
520 | if (node->iac_sb[sboff] == 255) |
521 | sboff++; |
522 | session->terminal_columns = (node->iac_sb[sboff++] << 8); |
523 | if (node->iac_sb[sboff] == 255) |
524 | sboff++; |
525 | session->terminal_columns |= node->iac_sb[sboff++]; |
526 | |
527 | if (node->iac_sb[sboff] == 255) |
528 | sboff++; |
529 | session->terminal_lines = (node->iac_sb[sboff++] << 8); |
530 | if (node->iac_sb[sboff] == 255) |
531 | sboff++; |
532 | session->terminal_lines |= node->iac_sb[sboff++]; |
533 | if (node->naws_count++ < 20) |
534 | session_logf_buffered(session, "IAC NAWS %d, %d", |
535 | session->terminal_columns, |
536 | session->terminal_lines); |
537 | break; |
538 | case IAC_TTYPE: |
539 | /* IAC SB TTYPE IS XTERM IAC SE */ |
540 | node->iac_sb[node->sb_len - 1] = '\0'; |
541 | strlcpy(session->terminal_type, |
542 | (char *)&node->iac_sb + 2, |
543 | sizeof(session->terminal_type)); |
544 | session_logf_buffered(session, "IAC TTYPE IS %s", |
545 | session->terminal_type); |
546 | |
547 | for (j = 0; vt100_terms[j] != NULL; j++) { |
548 | if (strncasecmp(session->terminal_type, |
549 | vt100_terms[j], strlen(vt100_terms[j])) == 0) { |
550 | session_logf_buffered(session, |
551 | "Activating vt100 ANSI support for " |
552 | "matching %s", vt100_terms[j]); |
553 | session->vt100 = true; |
554 | break; |
555 | } |
556 | } |
557 | |
558 | break; |
559 | case IAC_NEWENV: { |
560 | char k[sizeof(node->iac_sb)], v[sizeof(node->iac_sb)]; |
561 | char l = 0; |
562 | bool inv = false; |
563 | |
564 | k[0] = v[0] = '\0'; |
565 | |
566 | /* \40\0\0USER\1blah\0DISPLAY\1... */ |
567 | for (j = 3; j < node->sb_len; j++) { |
568 | if (j == node->sb_len - 1 || |
569 | node->iac_sb[j] == 0 || node->iac_sb[j] == 3) { |
570 | v[l] = '\0'; |
571 | if (l) { |
572 | session_logf_buffered(session, |
573 | "NEWENV \"%s\" = \"%s\"", k, v); |
574 | |
575 | if (node->from_trusted_proxy && |
576 | strcmp(k, "REMOTE_ADDR") == 0) { |
577 | node->ip = ip2long(v); |
578 | strlcpy(node->ip_s, v, |
579 | sizeof(node->ip_s)); |
580 | node->session->log.ip_address = |
581 | node->ip; |
582 | } |
583 | } |
584 | l = 0; |
585 | inv = false; |
586 | } else if (node->iac_sb[j] == 1) { |
587 | k[l] = '\0'; |
588 | l = 0; |
589 | inv = true; |
590 | } else { |
591 | if (inv) |
592 | v[l++] = node->iac_sb[j]; |
593 | else |
594 | k[l++] = node->iac_sb[j]; |
595 | } |
596 | } |
597 | break; |
598 | } |
599 | case IAC_TSPEED: { |
600 | unsigned long tspeed; |
601 | |
602 | /* IAC SB TSPEED IS 38400,19200 SE */ |
603 | node->iac_sb[node->sb_len - 1] = '\0'; |
604 | |
605 | session_logf_buffered(session, "IAC TSPEED IS %s", |
606 | node->iac_sb + 2); |
607 | |
608 | for (j = 2; j < node->sb_len; j++) { |
609 | if (node->iac_sb[j] == ',') { |
610 | node->iac_sb[j] = '\0'; |
611 | break; |
612 | } |
613 | } |
614 | |
615 | tspeed = atol((const char *)(node->iac_sb + 2)); |
616 | if (tspeed > 0) { |
617 | if (tspeed > 57600) |
618 | tspeed = 57600; |
619 | node->session->tspeed = |
620 | node->session->log.tspeed = |
621 | (unsigned short)tspeed; |
622 | } |
623 | break; |
624 | } |
625 | } |
626 | node->iac_state = TELNET_IAC_STATE_IDLE; |
627 | } else { |
628 | /* accumulate bytes into iac_sb buffer */ |
629 | if (node->sb_len < sizeof(node->iac_sb)) |
630 | node->iac_sb[node->sb_len++] = c; |
631 | else { |
632 | /* IAC overflow */ |
633 | node->iac_state = TELNET_IAC_STATE_IDLE; |
634 | node->sb_len = 0; |
635 | session_logf(session, "IAC SB overflow"); |
636 | } |
637 | } |
638 | break; |
639 | } |
640 | case TELNET_IAC_STATE_WILL: |
641 | switch (c) { |
642 | case IAC_ECHO: |
643 | case IAC_SGA: |
644 | iac_out[1] = DO; |
645 | iac_out[2] = IAC_ECHO; |
646 | telnet_output_iac(session, iac_out, 3); |
647 | break; |
648 | case IAC_NEWENV: |
649 | case IAC_TTYPE: |
650 | case IAC_TSPEED: |
651 | iac_out[1] = SB; |
652 | iac_out[2] = c; |
653 | iac_out[3] = SEND; |
654 | iac_out[4] = IAC; |
655 | iac_out[5] = SE; |
656 | telnet_output_iac(session, iac_out, 6); |
657 | break; |
658 | case IAC_LINEMODE: |
659 | #if 0 |
660 | iac_out[1] = SB; |
661 | iac_out[2] = IAC_LINEMODE; |
662 | iac_out[3] = 1; |
663 | iac_out[4] = 0; |
664 | iac_out[5] = IAC; |
665 | iac_out[6] = SE; |
666 | telnet_output_iac(session, iac_out, 7); |
667 | #endif |
668 | break; |
669 | case IAC_ENCRYPT: |
670 | iac_out[1] = DONT; |
671 | iac_out[2] = IAC_ENCRYPT; |
672 | telnet_output_iac(session, iac_out, 3); |
673 | break; |
674 | } |
675 | node->iac_state = TELNET_IAC_STATE_IDLE; |
676 | break; |
677 | case TELNET_IAC_STATE_WONT: |
678 | /* we don't care about any of these yet */ |
679 | node->iac_state = TELNET_IAC_STATE_IDLE; |
680 | break; |
681 | case TELNET_IAC_STATE_DO: |
682 | switch (c) { |
683 | case IAC_BINARY: |
684 | iac_out[1] = WILL; |
685 | iac_out[2] = IAC_BINARY; |
686 | telnet_output_iac(session, iac_out, 3); |
687 | break; |
688 | case IAC_ECHO: |
689 | case IAC_NAWS: |
690 | case IAC_TSPEED: |
691 | case IAC_FLOWCTRL: |
692 | case IAC_SGA: |
693 | case IAC_LINEMODE: |
694 | /* we already sent WILLs for these at the beginning */ |
695 | break; |
696 | default: |
697 | iac_out[1] = WONT; |
698 | iac_out[2] = c; |
699 | telnet_output_iac(session, iac_out, 3); |
700 | break; |
701 | } |
702 | node->iac_state = TELNET_IAC_STATE_IDLE; |
703 | break; |
704 | case TELNET_IAC_STATE_DONT: |
705 | node->iac_state = TELNET_IAC_STATE_IDLE; |
706 | break; |
707 | default: |
708 | warn("telnet_input in bogus IAC state %d", node->iac_state); |
709 | break; |
710 | } |
711 | } |
712 | |
713 | if (used == node->ibuflen) |
714 | node->ibuflen = 0; |
715 | else { |
716 | memmove(node->ibuf, node->ibuf + used, node->ibuflen - used); |
717 | node->ibuflen -= used; |
718 | } |
719 | |
720 | return rlen; |
721 | } |
722 | |
723 | short |
724 | telnet_output(struct session *session) |
725 | { |
726 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
727 | short n, error; |
728 | unsigned char c; |
729 | unsigned long now; |
730 | |
731 | if (session->obuflen == 0 || session->ending) |
732 | return 0; |
733 | |
734 | if (node->send_pb.ioResult > 0) |
735 | /* previous _TCPSend has not completed yet */ |
736 | return 0; |
737 | |
738 | process_result: |
739 | if (node->tcp_wds[0].length) { |
740 | /* previous _TCPSend completed, shift out those bytes */ |
741 | if (session->obuflen < node->obuflen) { |
742 | warn("bogus obuflen %d", session->obuflen); |
743 | session->obuflen = 0; |
744 | } else |
745 | session->obuflen -= node->obuflen; |
746 | if (session->obuflen > 0) |
747 | memmove(session->obuf, session->obuf + node->obuflen, |
748 | session->obuflen); |
749 | |
750 | node->tcp_wds[0].length = 0; |
751 | |
752 | if (session->obuflen == 0) |
753 | return node->obuflen; |
754 | } |
755 | |
756 | if (session->obuflen == 1 && session->obuf[0] != IAC) { |
757 | node->obuf[0] = session->obuf[0]; |
758 | node->escaped_obuflen = node->obuflen = 1; |
759 | } else if (session->transferring_file) { |
760 | memcpy(node->obuf, session->obuf, session->obuflen); |
761 | node->escaped_obuflen = node->obuflen = session->obuflen; |
762 | } else { |
763 | /* copy obuf to node buffer, escaping IACs */ |
764 | for (node->escaped_obuflen = 0, node->obuflen = 0; |
765 | node->obuflen < session->obuflen && |
766 | node->escaped_obuflen < sizeof(node->obuf); |
767 | node->obuflen++) { |
768 | c = session->obuf[node->obuflen]; |
769 | |
770 | if (!node->sending_iac && c == IAC) { |
771 | node->obuf[node->escaped_obuflen++] = IAC; |
772 | node->obuf[node->escaped_obuflen++] = IAC; |
773 | } else |
774 | node->obuf[node->escaped_obuflen++] = c; |
775 | } |
776 | } |
777 | |
778 | if (node->escaped_obuflen > sizeof(node->obuf)) { |
779 | warn("bogus obuflen %d > %ld", node->escaped_obuflen, |
780 | sizeof(node->obuf)); |
781 | session->ending = true; |
782 | session_close(session); |
783 | return 0; |
784 | } |
785 | |
786 | /* |
787 | * _TCPSend only knows how many wds pointers were passed in when it |
788 | * reads the next one and its pointer is zero (or size is zero?) |
789 | */ |
790 | node->tcp_wds[0].ptr = (Ptr)&node->obuf; |
791 | node->tcp_wds[0].length = node->escaped_obuflen; |
792 | node->tcp_wds[1].ptr = 0; |
793 | node->tcp_wds[1].length = 0; |
794 | |
795 | now = Ticks; |
796 | error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil, |
797 | true); |
798 | if (error) { |
799 | warn("TCPSend[%d] failed: %d", node->id, error); |
800 | session->ending = true; |
801 | } |
802 | |
803 | /* if we can send in less than 500ms, avoid a uthread switch */ |
804 | while (Ticks - now <= 30) { |
805 | if (node->send_pb.ioResult <= 0) |
806 | goto process_result; |
807 | } |
808 | |
809 | return 0; |
810 | } |
811 | |
812 | void |
813 | telnet_output_iac(struct session *session, const char *iacs, size_t len) |
814 | { |
815 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
816 | |
817 | session_flush(session); |
818 | if (session->ending) |
819 | return; |
820 | |
821 | node->sending_iac = 1; |
822 | session_output(session, iacs, len); |
823 | session_flush(session); |
824 | node->sending_iac = 0; |
825 | } |
826 | |
827 | void |
828 | telnet_close(struct session *session) |
829 | { |
830 | struct telnet_node *node = (struct telnet_node *)session->cookie; |
831 | size_t len; |
832 | short error, n; |
833 | unsigned char *tmp; |
834 | |
835 | if (node->from_trusted_proxy) |
836 | session->ban_node_source = false; |
837 | |
838 | if (session->ban_node_source && !node->from_trusted_proxy && |
839 | db->config.trusted_proxy_ip != 0 && |
840 | db->config.trusted_proxy_udp_port != 0) { |
841 | session_logf(session, "Closing telnet connection from %s and " |
842 | "banning IP", node->ip_s); |
843 | |
844 | tmp = (unsigned char *)&node->ip; |
845 | len = snprintf(udp_ban_send_buf, sizeof(udp_ban_send_buf), |
846 | "%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]); |
847 | udp_ban_wds[0].ptr = (Ptr)&udp_ban_send_buf; |
848 | udp_ban_wds[0].length = len; |
849 | udp_ban_wds[1].ptr = 0; |
850 | udp_ban_wds[1].length = 0; |
851 | |
852 | error = _UDPSend(&udp_ban_pb, udp_ban_stream, udp_ban_wds, |
853 | db->config.trusted_proxy_ip, |
854 | db->config.trusted_proxy_udp_port, NULL, NULL, false); |
855 | if (error) |
856 | session_logf(session, "Failed sending IP ban UDP packet: %d", |
857 | error); |
858 | } else { |
859 | session_logf(session, "Closing telnet connection from %s", |
860 | node->ip_s); |
861 | |
862 | error = _TCPClose(&telnet_exit_pb, node->stream, nil, nil, false); |
863 | /* TODO: allow some time to fully close? */ |
864 | } |
865 | |
866 | error = _TCPRelease(&telnet_exit_pb, node->stream, nil, nil, false); |
867 | if (error) { |
868 | warn("error shutting down telnet session: %d", error); |
869 | return; |
870 | } |
871 | |
872 | session->cookie = NULL; |
873 | node->state = TELNET_PB_STATE_UNUSED; |
874 | } |
875 | |
876 | void |
877 | telnet_print_busy(struct telnet_node *node) |
878 | { |
879 | size_t len, n, olen; |
880 | unsigned long now; |
881 | short error; |
882 | char *data = NULL; |
883 | |
884 | if ((len = bile_read_alloc(db->bile, DB_TEXT_TYPE, |
885 | DB_TEXT_NO_FREE_NODES_ID, &data)) == 0) { |
886 | len = snprintf((char *)&node->obuf, sizeof(node->obuf), |
887 | "No free nodes, please call back later.\r\n"); |
888 | } else { |
889 | for (n = 0, olen = 0; n < len && olen < sizeof(node->obuf) - 1; |
890 | n++) { |
891 | node->obuf[olen++] = data[n]; |
892 | if (data[n] == '\r' && data[n + 1] != '\n') |
893 | node->obuf[olen++] = '\n'; |
894 | } |
895 | xfree(&data); |
896 | } |
897 | |
898 | node->tcp_wds[0].ptr = (Ptr)&node->obuf; |
899 | node->tcp_wds[0].length = len; |
900 | node->tcp_wds[1].ptr = 0; |
901 | node->tcp_wds[1].length = 0; |
902 | |
903 | now = Ticks; |
904 | error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil, |
905 | true); |
906 | if (error) |
907 | return; |
908 | |
909 | for (;;) { |
910 | /* spin for 500ms to let the send finish */ |
911 | if (Ticks - now > 30 || node->send_pb.ioResult <= 0) |
912 | return; |
913 | } |
914 | } |