/* * Copyright (c) 2018 Lars Thrane A/S * SPDX-License-Identifier: GPL-2.0 * * Telnet protocol handling - server */ #include #include #include "lib/nvt_log.h" #include "telnet.h" #include "telnet_param.h" /* Telnet commands */ static const char *const tn_cmd[] = { "SE", "NOP", "MARK", "BRK", "IP", "AO", "AYT", "EC", "EL", "GA", "SB", "WILL", "WONT", "DO", "DONT", "IAC" }; #define OPT_NACK 0 /* Reject (DONT/WONT) */ #define OPT_IGNORE 1 /* Ignore the command (?) */ #define OPT_ACK 2 /* Acknowledge (DO /WILL) */ /* Telnet option support */ static const unsigned char tn_opt[256] = { /* *INDENT-OFF* */ [TNO_BINARY] = OPT_ACK, [TNO_SUPP_GA] = OPT_ACK, // [TNO_TTYP] = OPT_NACK, // [TNO_NAWS] = OPT_NACK, // [TNO_NEO] = OPT_NACK, [TNO_CPCO] = OPT_ACK, // [TNO_KERMIT] = OPT_NACK, /* *INDENT-ON* */ }; static void _telnet_handle_req(tn_ctx_t *tcc, int cmd, int opt) { char buf[3]; switch (tn_opt[opt]) { default: case OPT_IGNORE: return; case OPT_ACK: cmd = (cmd == TNC_FB_WILL) ? TNC_FD_DO : TNC_FB_WILL; break; case OPT_NACK: cmd = (cmd == TNC_FB_WILL) ? TNC_FE_DONT : TNC_FC_WONT; break; } buf[0] = TNC_FF_IAC; buf[1] = cmd; buf[2] = opt; DBG("TNo: Op2 %02x %02x: %-4s %d", cmd, opt, tn_cmd[cmd & 0xf], opt); tcc->cli_tx(tcc->cctx, buf, 3); if (opt == TNO_CPCO && (cmd == TNC_FB_WILL || cmd == TNC_FD_DO)) telnet_rfc2217_init(tcc, TNI_ON); } void telnet_reply_opt(tn_ctx_t *tcc, int opt, int prm, const void *val, int len) { unsigned char buf[128], *pu = buf; const unsigned char *pv; int i; *pu++ = TNC_FF_IAC; *pu++ = TNC_FA_SB; *pu++ = opt; *pu++ = prm; if (len > 0 && len <= 32) { pv = val; for (i = 0; i < len; i++) { *pu++ = *pv; if (*pv++ == TNC_FF_IAC) *pu++ = TNC_FF_IAC; } } *pu++ = TNC_FF_IAC; *pu++ = TNC_F0_SE; len = pu - buf; DBG2_BUF("opto", buf, len); tcc->cli_tx(tcc->cctx, buf, len); } static void _telnet_handle_opt(tn_ctx_t *tcc, int opt, unsigned char *pu, int nd) { DBG2_BUF("opti", pu, nd); switch (opt) { default: nvt_log(LOG_WARNING, "Unexpected SB: Opt = %d", opt); break; case TNO_CPCO: /* RFC 2217 */ tcc->mode_rfc2217 = 1; telnet_rfc2217_handle_opt(tcc, opt, pu, nd); break; } } int telnet_rx(tn_ctx_t *tcc, char *buf, int *plen) { char *s, *p; unsigned char *pu; int len, rem; int np, nd; int b1, b2; /* Drop telnet processing entirely if we haven't seen any telnet * negotiation in the beginning */ if (tcc->bytes_rx >= 32 && !tcc->mode_telnet) return 0; len = *plen; tcc->bytes_rx += len; if (len < (int)tcc->off_rx) tcc->off_rx = 0; for (s = buf + tcc->off_rx, rem = len - tcc->off_rx; rem > 0; s = p) { DBG2_BUF("****", s, rem); p = memchr(s, TNC_FF_IAC, rem); if (!p) break; /* We have IAC (= 0xff) */ np = rem - (p - s); /* N. chars available incl. IAC */ if (np < 2) goto tn_more; pu = (unsigned char *)p; b1 = pu[1]; switch (b1) { default: case TNC_F0_SE: case TNC_F1_NOP: case TNC_F2_MARK: case TNC_F3_BRK: case TNC_F4_IP: case TNC_F5_AO: case TNC_F6_AYT: case TNC_F7_EC: case TNC_F8_EL: case TNC_F9_GA: DBG("TNi: Op1 %02x : %s", b1, tn_cmd[b1 & 0xf]); nd = 2; /* Drop 2 bytes */ goto tn_drop; case TNC_FB_WILL: case TNC_FC_WONT: case TNC_FD_DO: case TNC_FE_DONT: if (np < 3) goto tn_more; /* Need more */ tcc->mode_telnet = 1; /* Any such sequence activates TN */ b2 = pu[2]; DBG("TNi: Op2 %02x %02x: %-4s %d", b1, b2, tn_cmd[b1 & 0xf], b2); if (b1 == TNC_FB_WILL || b1 == TNC_FD_DO) _telnet_handle_req(tcc, b1, b2); nd = 3; /* Drop 3 bytes */ goto tn_drop; case TNC_FA_SB: for (nd = 2; nd < np - 1; nd++) { if (pu[nd] == TNC_FF_IAC && pu[nd + 1] == TNC_F0_SE) { nd -= 2; if (nd <= 0) break; b2 = pu[2]; tcc->mode_telnet = 1; /* Any such sequence activates TN */ DBG("TNi: Op2 %02x %02x: %-4s %d", b1, b2, tn_cmd[b1 & 0xf], b2); _telnet_handle_opt(tcc, b2, pu + 3, nd - 1); nd += 4; goto tn_drop; } } if (np < 16) goto tn_more; /* Not done */ /* This does not look right */ nvt_log_buf(LOG_WARNING, "tnrx", p, np); rem = 0; /* Quit loop */ break; case TNC_FF_IAC: p++; nd = 1; /* Drop 1 bytes */ goto tn_drop; tn_drop: if (!tcc->mode_telnet) goto tn_break; DBG2_BUF("drop", p, nd); rem -= nd + (p - s); len -= nd; memmove(p, p + nd, rem); break; tn_break: rem = 0; /* Quit loop */ break; tn_more: /* Request more data */ tcc->off_rx = len - np; *plen = len; return 1; } } tcc->off_rx = 0; *plen = len; return 0; } int telnet_tx(tn_ctx_t *tcc, const char *buf, int len) { const char *s, *p; int rem, nd; for (s = buf, rem = len; rem > 0; s = p) { if (tcc->mode_telnet) p = memchr(s, TNC_FF_IAC, rem); else p = NULL; if (!p) { /* Send the rest */ nd = rem; tcc->cli_tx(tcc->cctx, s, nd); } else { nd = p - s + 1; tcc->cli_tx(tcc->cctx, s, nd); tcc->cli_tx(tcc->cctx, p, 1); /* One TNC_FF_IAC */ p++; } rem -= nd; } return 0; } tn_ctx_t *telnet_ctx_init(void *cctx, cli_tx_f *ftx, cli_ss_f *fss) { tn_ctx_t *tcc; tcc = calloc(1, sizeof(tn_ctx_t)); tcc->cctx = cctx; tcc->cli_tx = ftx; /* Write to client */ tcc->cli_ss = fss; /* Set state (line/modem) */ telnet_rfc2217_init(tcc, TNI_INIT); return tcc; }