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