/* * Copyright (c) 2021 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "logger.h" #include "session.h" #include "subtext.h" #include "telnet.h" #include "tcp.h" #include "util.h" #define SE 240 /* end of sub-negotiation options */ #define NOP 241 /* no operation */ #define SB 250 /* start of sub-negotiation options */ #define WILL 251 /* confirm willingness to negotiate */ #define WONT 252 /* confirm unwillingness to negotiate */ #define DO 253 /* indicate willingness to negotiate */ #define DONT 254 /* indicate unwillingness to negotiate */ #define IAC 255 /* start of a negotiation sequence */ #define IS 0 /* sub-negotiation */ #define SEND 1 /* sub-negotiation */ #define IAC_BINARY 0 /* Transmit Binary */ #define IAC_ECHO 1 /* Echo Option */ #define IAC_SGA 3 /* Suppress Go Ahead Option */ #define IAC_STATUS 5 /* Status Option */ #define IAC_TM 6 /* Timing Mark Option */ #define IAC_NAOCRD 10 /* Output Carriage-Return Disposition Option */ #define IAC_NAOHTS 11 /* Output Horizontal Tabstops Option */ #define IAC_NAOHTD 12 /* Output Horizontal Tab Disposition Option */ #define IAC_NAOFFD 13 /* Output Formfeed Disposition Option */ #define IAC_NAOVTS 14 /* Output Vertical Tabstops Option */ #define IAC_NAOVTD 15 /* Output Vertical Tab Disposition Option */ #define IAC_NAOLFD 16 /* Output Linefeed Disposition */ #define IAC_XASCII 17 /* Extended Ascii Option */ #define IAC_LOGOUT 18 /* Logout Option */ #define IAC_BM 19 /* Byte Macro Option */ #define IAC_SUPDUP 22 /* SUPDUP-OUTPUT Option */ #define IAC_SENDLOC 23 /* SEND-LOCATION Option */ #define IAC_TTYPE 24 /* Terminal Type Option */ #define IAC_EOR 25 /* End of Record Option */ #define IAC_OUTMRK 27 /* Marking Telnet Option */ #define IAC_TTYLOC 28 /* Terminal Location Number Option */ #define IAC_DET 20 /* Data Entry Terminal Option DODIIS */ #define IAC_X3PAD 30 /* X.3 PAD Option */ #define IAC_NAWS 31 /* Window Size Option */ #define IAC_TSPEED 32 /* Terminal Speed Option */ #define IAC_FLOWCTRL 33 /* Remote Flow Control Option */ #define IAC_LINEMODE 34 /* Linemode Option */ #define IAC_XDISPLOC 35 /* X Display Location Option */ #define IAC_ENVIRON 36 /* Environment Option */ #define IAC_AUTH 37 /* Authentication */ #define IAC_ENCRYPT 38 /* Encryption Option */ #define IAC_NEWENV 39 /* Environment Option */ #define IAC_CHARSET 42 /* Charset Option */ #define IAC_COMPORT 44 /* Com Port Control Option */ enum { TELNET_PB_STATE_UNUSED = 0, TELNET_PB_STATE_LISTENING, TELNET_PB_STATE_CONNECTED }; enum { TELNET_IAC_STATE_IDLE = 0, TELNET_IAC_STATE_IAC, TELNET_IAC_STATE_WILL, TELNET_IAC_STATE_WONT, TELNET_IAC_STATE_DO, TELNET_IAC_STATE_DONT, TELNET_IAC_STATE_SB }; struct telnet_node { short id; short state; char name[8]; ip_addr ip; char ip_s[20]; struct session *session; unsigned short obuflen; unsigned char obuf[1024]; unsigned char ibuf[64]; unsigned short ibuflen; unsigned short ibufoff; unsigned short sending_iac; short iac_state; unsigned char iac_sb[128]; short sb_len; unsigned short naws_count; /* docs say 4*MTU+1024, but MTU will probably be <1500 */ unsigned char tcp_buf[(4 * 1500) + 1024]; bool from_trusted_proxy; TCPiopb rcv_pb, send_pb, listen_pb; StreamPtr stream; wdsEntry tcp_wds[2]; }; /* add one to be able to print busy messages */ #define MAX_TELNET_NODES (MAX_SESSIONS + 1) static struct telnet_node *telnet_nodes[MAX_TELNET_NODES] = { NULL }; static struct telnet_node *telnet_listener_node = NULL; static TCPiopb telnet_exit_pb; static TCPStatusPB telnet_status_pb; static bool did_initial_log = false; static UDPiopb udp_ban_pb; static StreamPtr udp_ban_stream = 0; static char *udp_ban_rcv_buf = NULL; #define UDP_BAN_RCV_BUF_SIZE 2048 static wdsEntry udp_ban_wds[2]; static char udp_ban_send_buf[16]; /* terminals that will have vt100 set by default */ static const char * vt100_terms[] = { "ansi", "syncterm", "xterm", "tmux", "screen", "vt100", NULL, }; void telnet_deinit(void); void telnet_setup(struct session *session); void telnet_listen_on_node(struct telnet_node *node); short telnet_escape_output(struct session *session); void telnet_output_iac(struct session *session, const char *iacs, size_t len); void telnet_print_busy(struct telnet_node *node); struct node_funcs telnet_node_funcs = { telnet_setup, telnet_input, telnet_output, telnet_close }; void telnet_init(void) { short n, error; if (!db->config.telnet_port) return; if (_TCPInit() != noErr) panic("Failed initializing MacTCP"); /* pre-allocate nodes */ for (n = 0; n < MAX_TELNET_NODES; n++) { if (telnet_nodes[n] == NULL) { telnet_nodes[n] = xmalloczero(sizeof(struct telnet_node)); if (telnet_nodes[n] == NULL) panic("Failed allocating telnet node %d", n); } telnet_nodes[n]->id = n; } if (db->config.trusted_proxy_ip != 0 && db->config.trusted_proxy_udp_port != 0) { udp_ban_rcv_buf = xmalloc(UDP_BAN_RCV_BUF_SIZE); if (udp_ban_rcv_buf == NULL) panic("Failed allocating UDP ban buf"); error = _UDPCreate(&udp_ban_pb, &udp_ban_stream, (Ptr)udp_ban_rcv_buf, UDP_BAN_RCV_BUF_SIZE, NULL, NULL, NULL, false); if (error) panic("UDPCreate failed: %d", error); } } bool telnet_reinit(void) { telnet_deinit(); telnet_init(); did_initial_log = false; return true; } void telnet_deinit(void) { if (telnet_listener_node) { _TCPRelease(&telnet_exit_pb, telnet_listener_node->stream, nil, nil, false); telnet_listener_node->state = TELNET_PB_STATE_UNUSED; telnet_listener_node = NULL; } if (udp_ban_stream) { _UDPRelease(&udp_ban_pb, udp_ban_stream, NULL, NULL, false); udp_ban_stream = 0; } if (udp_ban_rcv_buf) xfree(&udp_ban_rcv_buf); } void telnet_atexit(void) { struct telnet_node *node; short n; for (n = 0; n < MAX_TELNET_NODES; n++) { node = telnet_nodes[n]; if (!node) continue; if (node->state > TELNET_PB_STATE_UNUSED) _TCPRelease(&telnet_exit_pb, node->stream, nil, nil, false); xfree(&node); telnet_nodes[n] = NULL; } telnet_deinit(); } void telnet_listen_on_node(struct telnet_node *node) { char ip_s[20]; short error, id; ip_addr ip; tcp_port port; long mask; id = node->id; memset(node, 0, sizeof(struct telnet_node)); node->id = id; snprintf(node->name, sizeof(node->name), "ttyt%d", id); error = _TCPCreate(&node->listen_pb, &node->stream, (Ptr)&node->tcp_buf, sizeof(node->tcp_buf), nil, nil, nil, false); if (error) panic("TCPCreate[%d] failed: %d", node->id, error); error = _TCPGetOurIP(&ip, &mask); if (error) panic("TCPGetOurIP failed: %d", error); port = db->config.telnet_port; if (!did_initial_log) { long2ip(ip, ip_s); logger_printf("[telnet] Listening on %s:%d", ip_s, port); did_initial_log = true; } error = _TCPPassiveOpen(&node->listen_pb, node->stream, nil, nil, &ip, &port, nil, nil, true); if (error) panic("TCPPassiveOpen[%d] on port %d failed: %d", node->id, db->config.telnet_port, error); node->state = TELNET_PB_STATE_LISTENING; telnet_listener_node = node; } void telnet_idle(void) { struct telnet_node *node; short n, error; if (!db->config.telnet_port) return; for (n = 0; n < MAX_TELNET_NODES; n++) { node = telnet_nodes[n]; switch (node->state) { case TELNET_PB_STATE_UNUSED: if (telnet_listener_node == NULL) telnet_listen_on_node(node); break; case TELNET_PB_STATE_LISTENING: error = _TCPStatus(&node->send_pb, node->stream, &telnet_status_pb, nil, nil, false); if (error == connectionDoesntExist) { /* * The connection closed before it could be handled, but * telnet_status_pb.connectionState will still be * listening, so force it into a bogus state to recycle it. */ telnet_status_pb.connectionState = -1; } switch (telnet_status_pb.connectionState) { case ConnectionStateListening: goto next_node; case ConnectionStateSYNReceived: case ConnectionStateSYNSent: /* TODO: enforce our own timeout on these? */ break; case ConnectionStateEstablished: { char *loc = NULL; /* start up a new socket for listening */ telnet_listener_node = NULL; node->ip = telnet_status_pb.remoteHost; long2ip(telnet_status_pb.remoteHost, node->ip_s); if (db->config.trusted_proxy_ip != 0 && db->config.trusted_proxy_ip == node->ip) { node->from_trusted_proxy = true; snprintf(node->name, sizeof(node->name), "ttyw%d", node->id); } if (db->ipdb && !node->from_trusted_proxy) loc = ipdb_lookup(db->ipdb, node->ip); logger_printf("[%s] New telnet connection from %s%s%s%s%s", node->name, node->ip_s, (loc ? " (" : ""), (loc ? loc : ""), (loc ? ") " : ""), node->from_trusted_proxy ? " (via trusted proxy)" : ""); node->state = TELNET_PB_STATE_CONNECTED; node->session = session_create(node->name, node->from_trusted_proxy ? "web" : "telnet", &telnet_node_funcs); if (node->session == NULL) { logger_printf("[%s] No free nodes, disconnecting", node->name); telnet_print_busy(node); _TCPRelease(&node->listen_pb, node->stream, nil, nil, false); node->state = TELNET_PB_STATE_UNUSED; if (loc) xfree(&loc); goto next_node; } node->session->cookie = (void *)node; node->session->tspeed = 19200; node->session->is_telnet = true; node->session->log.ip_address = telnet_status_pb.remoteHost; if (node->from_trusted_proxy) node->session->can_nomodem = true; if (loc) { strlcpy(node->session->location, loc, sizeof(node->session->location)); strlcpy(node->session->log.location, loc, sizeof(node->session->log.location)); xfree(&loc); } break; } default: if (telnet_status_pb.remoteHost == 0) long2ip(node->listen_pb.csParam.open.remoteHost, node->ip_s); else long2ip(telnet_status_pb.remoteHost, node->ip_s); logger_printf("[%s] Telnet connection from " "%s in state %d, aborting", node->name, node->ip_s, telnet_status_pb.connectionState); if (node->session) node->session->ending = 1; else { _TCPRelease(&node->listen_pb, node->stream, nil, nil, false); node->state = TELNET_PB_STATE_UNUSED; telnet_listener_node = NULL; } goto next_node; } break; case TELNET_PB_STATE_CONNECTED: if (!node->session) { node->state = TELNET_PB_STATE_UNUSED; break; } /* * Send buffered data any time we can, the user might not be * in an explicit session_flush() like during chat */ telnet_output(node->session); break; } next_node: continue; } } void telnet_setup(struct session *session) { const char iacs[] = { IAC, WILL, IAC_SGA, /* we will supress go-ahead */ IAC, WILL, IAC_ECHO, /* and we will echo for you */ IAC, WILL, IAC_BINARY, /* don't mess with binary data we send */ IAC, DO, IAC_BINARY, /* and you can send us binary */ IAC, DO, IAC_TSPEED, /* send your TSPEED */ IAC, DO, IAC_NAWS, /* send your NAWS */ IAC, DO, IAC_TTYPE, /* send your TTYPE */ IAC, DO, IAC_NEWENV, /* send your NEWENV vars */ }; struct telnet_node *node = (struct telnet_node *)session->cookie; size_t size = sizeof(iacs); if (!node->from_trusted_proxy) /* we only care about NEWENV when we need REMOTE_ADDR */ size -= 3; telnet_output_iac(session, iacs, size); session_flush(session); /* see if we can quickly read and parse any incoming IAC */ telnet_input(session); session_flush(session); } short telnet_input(struct session *session) { struct telnet_node *node = (struct telnet_node *)session->cookie; unsigned short rlen; short error, n, j, used; unsigned char c; char iac_out[8] = { IAC, 0 }; error = _TCPStatus(&node->rcv_pb, node->stream, &telnet_status_pb, nil, nil, false); if (error || telnet_status_pb.connectionState != ConnectionStateEstablished) { session->ending = 1; return 0; } if (telnet_status_pb.amtUnreadData == 0) return 0; rlen = telnet_status_pb.amtUnreadData; if (session->direct_output) { if (session->ibuflen + session->ibufoff + rlen > sizeof(session->ibuf)) { /* if we're already halfway through the buffer, reset */ if (session->ibufoff >= (sizeof(session->ibuf) / 2)) { memmove(session->ibuf, session->ibuf + session->ibufoff, session->ibuflen); session->ibufoff = 0; } rlen = sizeof(session->ibuf) - session->ibuflen - session->ibufoff; if (rlen == 0) return 0; } error = _TCPRcv(&node->rcv_pb, node->stream, (Ptr)(session->ibuf + session->ibufoff + session->ibuflen), &rlen, nil, nil, false); if (error) { session_logf(session, "TCP read failed (%d), closing connection", error); session->ending = 1; return 0; } if (rlen == 0) return 0; session->last_input_at = Time; session->ibuflen += rlen; return rlen; } if (node->ibuflen + rlen > sizeof(node->ibuf)) rlen = sizeof(node->ibuf) - node->ibuflen; error = _TCPRcv(&node->rcv_pb, node->stream, (Ptr)(node->ibuf + node->ibuflen), &rlen, nil, nil, false); if (error) { session_logf(session, "TCP read failed (%d), closing connection", error); session->ending = true; return 0; } if (rlen == 0) return 0; session->last_input_at = Time; node->ibuflen += rlen; used = 0; for (n = 0; n < node->ibuflen; n++) { if (session->ibuflen >= sizeof(session->ibuf)) break; c = node->ibuf[n]; used++; switch (node->iac_state) { case TELNET_IAC_STATE_IDLE: if (c == IAC) node->iac_state = TELNET_IAC_STATE_IAC; else session->ibuf[session->ibuflen++] = c; break; case TELNET_IAC_STATE_IAC: switch (c) { case IAC: /* escaped iac */ session->ibuf[session->ibuflen++] = c; node->iac_state = TELNET_IAC_STATE_IDLE; break; case NOP: node->iac_state = TELNET_IAC_STATE_IDLE; break; case WILL: /* client will do something */ node->iac_state = TELNET_IAC_STATE_WILL; break; case WONT: /* client will not do something */ node->iac_state = TELNET_IAC_STATE_WONT; break; case DO: /* client wants us to do something */ node->iac_state = TELNET_IAC_STATE_DO; break; case DONT: /* client wants us not to do something */ node->iac_state = TELNET_IAC_STATE_DONT; break; case SB: /* sub-negotiate */ node->iac_state = TELNET_IAC_STATE_SB; node->sb_len = 0; break; default: /* unsupported command, ignore it */ node->iac_state = TELNET_IAC_STATE_IDLE; break; } break; case TELNET_IAC_STATE_SB: { unsigned short sboff; /* keep reading until we see [^IAC] IAC SE */ if (c == SE && node->sb_len > 0 && node->iac_sb[node->sb_len - 1] == IAC) { switch (node->iac_sb[0]) { case IAC_NAWS: /* IAC SB NAWS 0 80 0 24 (80x24) */ /* IAC SB NAWS 1 255 255 1 123 (256x378) */ /* if either value is 255, it will be 255,255 */ sboff = 1; if (node->iac_sb[sboff] == 255) sboff++; session->terminal_columns = (node->iac_sb[sboff++] << 8); if (node->iac_sb[sboff] == 255) sboff++; session->terminal_columns |= node->iac_sb[sboff++]; if (node->iac_sb[sboff] == 255) sboff++; session->terminal_lines = (node->iac_sb[sboff++] << 8); if (node->iac_sb[sboff] == 255) sboff++; session->terminal_lines |= node->iac_sb[sboff++]; if (node->naws_count++ < 20) session_logf_buffered(session, "IAC NAWS %d, %d", session->terminal_columns, session->terminal_lines); break; case IAC_TTYPE: /* IAC SB TTYPE IS XTERM IAC SE */ node->iac_sb[node->sb_len - 1] = '\0'; strlcpy(session->terminal_type, (char *)&node->iac_sb + 2, sizeof(session->terminal_type)); session_logf_buffered(session, "IAC TTYPE IS %s", session->terminal_type); for (j = 0; vt100_terms[j] != NULL; j++) { if (strncasecmp(session->terminal_type, vt100_terms[j], strlen(vt100_terms[j])) == 0) { session_logf_buffered(session, "Activating vt100 ANSI support for " "matching %s", vt100_terms[j]); session->vt100 = true; break; } } break; case IAC_NEWENV: { char k[sizeof(node->iac_sb)], v[sizeof(node->iac_sb)]; unsigned char l = 0; bool inv = false; char *loc = NULL; k[0] = v[0] = '\0'; /* \40\0\0USER\1blah\0DISPLAY\1... */ for (j = 3; j < node->sb_len; j++) { if (j == node->sb_len - 1 || node->iac_sb[j] == 0 || node->iac_sb[j] == 3) { v[l] = '\0'; if (l) { loc = NULL; if (node->from_trusted_proxy && strcmp(k, "REMOTE_ADDR") == 0) { node->ip = ip2long(v); strlcpy(node->ip_s, v, sizeof(node->ip_s)); node->session->log.ip_address = node->ip; if (db->ipdb && (loc = ipdb_lookup(db->ipdb, node->ip))) { strlcpy(node->session->location, loc, sizeof(node->session->location)); strlcpy(node->session->log.location, loc, sizeof(node->session->log.location)); } } session_logf_buffered(session, "NEWENV \"%s\" = \"%s\"%s%s%s", k, v, loc ? " (" : "", loc ? loc : "", loc ? ")" : ""); if (loc) xfree(&loc); } l = 0; inv = false; } else if (node->iac_sb[j] == 1) { k[l] = '\0'; l = 0; inv = true; } else { if (inv) v[l++] = node->iac_sb[j]; else k[l++] = node->iac_sb[j]; } } break; } case IAC_TSPEED: { unsigned long tspeed; /* IAC SB TSPEED IS 38400,19200 SE */ node->iac_sb[node->sb_len - 1] = '\0'; session_logf_buffered(session, "IAC TSPEED IS %s", node->iac_sb + 2); for (j = 2; j < node->sb_len; j++) { if (node->iac_sb[j] == ',') { node->iac_sb[j] = '\0'; break; } } tspeed = atol((const char *)(node->iac_sb + 2)); if (tspeed > 0) { if (tspeed > 57600) tspeed = 57600; node->session->tspeed = node->session->log.tspeed = (unsigned short)tspeed; } break; } } node->iac_state = TELNET_IAC_STATE_IDLE; } else { /* accumulate bytes into iac_sb buffer */ if (node->sb_len < sizeof(node->iac_sb)) node->iac_sb[node->sb_len++] = c; else { /* IAC overflow */ node->iac_state = TELNET_IAC_STATE_IDLE; node->sb_len = 0; session_logf(session, "IAC SB overflow"); } } break; } case TELNET_IAC_STATE_WILL: switch (c) { case IAC_ECHO: case IAC_SGA: iac_out[1] = DO; iac_out[2] = IAC_ECHO; telnet_output_iac(session, iac_out, 3); break; case IAC_NEWENV: if (!node->from_trusted_proxy) break; case IAC_TTYPE: case IAC_TSPEED: iac_out[1] = SB; iac_out[2] = c; iac_out[3] = SEND; iac_out[4] = IAC; iac_out[5] = SE; telnet_output_iac(session, iac_out, 6); break; case IAC_LINEMODE: #if 0 iac_out[1] = SB; iac_out[2] = IAC_LINEMODE; iac_out[3] = 1; iac_out[4] = 0; iac_out[5] = IAC; iac_out[6] = SE; telnet_output_iac(session, iac_out, 7); #endif break; case IAC_ENCRYPT: iac_out[1] = DONT; iac_out[2] = IAC_ENCRYPT; telnet_output_iac(session, iac_out, 3); break; } node->iac_state = TELNET_IAC_STATE_IDLE; break; case TELNET_IAC_STATE_WONT: /* we don't care about any of these yet */ node->iac_state = TELNET_IAC_STATE_IDLE; break; case TELNET_IAC_STATE_DO: switch (c) { case IAC_BINARY: iac_out[1] = WILL; iac_out[2] = IAC_BINARY; telnet_output_iac(session, iac_out, 3); break; case IAC_ECHO: case IAC_NAWS: case IAC_TSPEED: case IAC_FLOWCTRL: case IAC_SGA: case IAC_LINEMODE: /* we already sent WILLs for these at the beginning */ break; default: iac_out[1] = WONT; iac_out[2] = c; telnet_output_iac(session, iac_out, 3); break; } node->iac_state = TELNET_IAC_STATE_IDLE; break; case TELNET_IAC_STATE_DONT: node->iac_state = TELNET_IAC_STATE_IDLE; break; default: warn("telnet_input in bogus IAC state %d", node->iac_state); break; } } if (used == node->ibuflen) node->ibuflen = 0; else { memmove(node->ibuf, node->ibuf + used, node->ibuflen - used); node->ibuflen -= used; } if (node->obuflen) session_flush(session); return rlen; } short telnet_escape_output(struct session *session) { struct telnet_node *node = (struct telnet_node *)session->cookie; unsigned char c; unsigned long added = 0; if (session->obuflen == 0 || session->ending) return 0; if (session->obuflen == 1 && session->obuf[0] != IAC) { node->obuf[node->obuflen++] = session->obuf[0]; session->obuflen = 0; } else if (session->direct_output) { added = MIN(session->obuflen, sizeof(node->obuf) - node->obuflen); memcpy(node->obuf + node->obuflen, session->obuf, added); node->obuflen += added; } else { /* copy session obuf to node buffer, escaping IACs */ for (added = 0; added < session->obuflen; added++) { if (node->obuflen >= sizeof(node->obuf) - 1) break; c = session->obuf[added]; if (!node->sending_iac && c == IAC) { node->obuf[node->obuflen++] = IAC; node->obuf[node->obuflen++] = IAC; } else node->obuf[node->obuflen++] = c; } } if (added > 0) { if (added < session->obuflen) memmove(session->obuf, session->obuf + added, session->obuflen - added); session->obuflen -= added; } return added; } short telnet_output(struct session *session) { struct telnet_node *node = (struct telnet_node *)session->cookie; unsigned long now = Ticks; short error, ret = 0; if (session->obuflen != 0) telnet_escape_output(session); if (node->obuflen == 0 || session->ending) return 0; /* wait for previous _TCPSend to complete */ while (node->send_pb.ioResult > 0) { if (uthread_current == NULL) /* this was an opportunistic flush anyway, no sense waiting */ return 0; uthread_yield(); if (session->ending) return 0; } process_result: if (node->tcp_wds[0].length) { /* shift out old data */ if (node->obuflen < node->tcp_wds[0].length) panic("data in tcp_wds > obuflen"); if (node->obuflen > node->tcp_wds[0].length) { memmove(node->obuf, node->obuf + node->tcp_wds[0].length, node->obuflen - node->tcp_wds[0].length); node->obuflen -= node->tcp_wds[0].length; } else node->obuflen = 0; node->tcp_wds[0].length = 0; if (node->obuflen == 0) return ret; } /* * _TCPSend only knows how many wds pointers were passed in when it * reads the next one and its pointer is zero (or size is zero?) */ node->tcp_wds[0].ptr = (Ptr)&node->obuf; node->tcp_wds[0].length = node->obuflen; node->tcp_wds[1].ptr = 0; node->tcp_wds[1].length = 0; ret = node->tcp_wds[0].length; error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil, true); if (error) { warn("TCPSend[%d] failed: %d", node->id, error); session->ending = true; } /* if we can send in less than 500ms, avoid a uthread switch */ while (Ticks - now <= 30) { if (node->send_pb.ioResult <= 0) goto process_result; } return ret; } void telnet_output_iac(struct session *session, const char *iacs, size_t len) { struct telnet_node *node = (struct telnet_node *)session->cookie; if (session->ending) return; telnet_escape_output(session); node->sending_iac = 1; session_output(session, iacs, len); telnet_escape_output(session); node->sending_iac = 0; } void telnet_close(struct session *session) { struct telnet_node *node = (struct telnet_node *)session->cookie; size_t len; short error; unsigned char *tmp; if (node->from_trusted_proxy) session->ban_node_source = false; if (session->ban_node_source && !node->from_trusted_proxy && db->config.trusted_proxy_ip != 0 && db->config.trusted_proxy_udp_port != 0) { session_logf(session, "Closing telnet connection from %s and " "banning IP", node->ip_s); tmp = (unsigned char *)&node->ip; len = snprintf(udp_ban_send_buf, sizeof(udp_ban_send_buf), "%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]); udp_ban_wds[0].ptr = (Ptr)&udp_ban_send_buf; udp_ban_wds[0].length = len; udp_ban_wds[1].ptr = 0; udp_ban_wds[1].length = 0; error = _UDPSend(&udp_ban_pb, udp_ban_stream, udp_ban_wds, db->config.trusted_proxy_ip, db->config.trusted_proxy_udp_port, NULL, NULL, false); if (error) session_logf(session, "Failed sending IP ban UDP packet: %d", error); } else { session_logf(session, "Closing telnet connection from %s", node->ip_s); _TCPClose(&telnet_exit_pb, node->stream, nil, nil, false); /* TODO: allow some time to fully close? */ } error = _TCPRelease(&telnet_exit_pb, node->stream, nil, nil, false); if (error) { warn("error shutting down telnet session: %d", error); return; } session->cookie = NULL; node->state = TELNET_PB_STATE_UNUSED; } void telnet_print_busy(struct telnet_node *node) { size_t len, n, olen; unsigned long now; short error; char *data = NULL; /* manually print to node without session_output */ if (db->views[DB_VIEW_NO_FREE_NODES]) { data = db->views[DB_VIEW_NO_FREE_NODES]; len = strlen(data); for (n = 0, olen = 0; n < len && olen < sizeof(node->obuf) - 1; n++) { node->obuf[olen++] = data[n]; if (data[n] == '\r' && data[n + 1] != '\n') node->obuf[olen++] = '\n'; } } else { len = snprintf((char *)&node->obuf, sizeof(node->obuf), "No free nodes, please call back later.\r\n"); } node->tcp_wds[0].ptr = (Ptr)&node->obuf; node->tcp_wds[0].length = len; node->tcp_wds[1].ptr = 0; node->tcp_wds[1].length = 0; now = Ticks; error = _TCPSend(&node->send_pb, node->stream, node->tcp_wds, nil, nil, true); if (error) return; for (;;) { /* spin for 500ms to let the send finish */ if (Ticks - now > 30 || node->send_pb.ioResult <= 0) return; } }