diff --git a/Makefile.am b/Makefile.am index 79691c5..a00a682 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,3 +5,6 @@ MAINTAINERCLEANFILES = \ Makefile.in */Makefile.in SUBDIRS = lib src +if BUILD_TESTTOOLS +SUBDIRS += nvtsrv +endif diff --git a/configure.ac b/configure.ac index ca428cd..c77efe5 100644 --- a/configure.ac +++ b/configure.ac @@ -26,6 +26,11 @@ PKG_PROG_PKG_CONFIG PKG_CHECK_MODULES(FUSE, fuse) +AC_ARG_ENABLE(testtools, + AS_HELP_STRING([--enable-testtools], [Build test tools @<:@default=no@:>@]),, + enable_testtools=no) +AM_CONDITIONAL([BUILD_TESTTOOLS], [test "x$enable_testtools" = "xyes"]) + CFLAGS_WARN="$CFLAGS_WARN -Wall -Wextra -Werror -Wno-unused-parameter" CFLAGS_WARN="$CFLAGS_WARN -Wmissing-prototypes -Wmissing-declarations -Wstrict-prototypes" CFLAGS_WARN="$CFLAGS_WARN -Waggregate-return -Wpointer-arith -Wshadow -Wwrite-strings" @@ -37,6 +42,7 @@ AC_CONFIG_FILES([ Makefile lib/Makefile src/Makefile +nvtsrv/Makefile ]) AC_OUTPUT diff --git a/nvtsrv/Makefile.am b/nvtsrv/Makefile.am new file mode 100644 index 0000000..4843891 --- /dev/null +++ b/nvtsrv/Makefile.am @@ -0,0 +1,10 @@ + +bin_PROGRAMS = nvtsrv + +nvtsrv_SOURCES = \ + nvtsrv.c \ + telnet_basic.c telnet_server_rfc2217.c telnet.h telnet_param.h + +nvtsrv_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) $(CFLAGS_WARN) + +nvtsrv_LDADD = $(top_builddir)/lib/libnvt.la diff --git a/nvtsrv/nvtsrv.c b/nvtsrv/nvtsrv.c new file mode 100644 index 0000000..fa33e44 --- /dev/null +++ b/nvtsrv/nvtsrv.c @@ -0,0 +1,221 @@ +/* + * RFC2217 test server + */ +#include +#include +#include +#include +#include +#include + +#include "lib/nvt_log.h" +#include "lib/nvt_socket.h" +#include "telnet.h" + +#define MATCH(s1, s2) (strcmp(s1, s2) == 0) + +struct { + unsigned char mline; + tn_ctx_t *tnct; +} dd; + +struct pollfd pfds[3]; +int nfds; + +#define fd_srv pfds[1].fd +#define fd_cli pfds[2].fd + + +static void _nvtsrv_usage(void) +{ + printf("Usage:\n" " nvtsrv host:port\n"); + exit(0); +} + +static void _tn_cli_close(void) +{ + int i = 2; + + printf("%d: fd=%d: Closed\n", i, pfds[i].fd); + close(pfds[i].fd); + pfds[i].fd = -1; + nfds = 2; +} + +static void _tn_tx(void *cctx, const void *buf, int len) +{ + int nw; + + nvt_log_buf(LOG_INFO, "Tx", buf, len); + + if (fd_cli < 0) + { + printf("Write fd is closed - drop Tx\n"); + return; + } + + nw = write(fd_cli, buf, len); + if (nw != len) + { + printf("Write failed %d != %d\n", nw, len); + _tn_cli_close(); + } +} + +static void _tn_ss(void *cctx, int signal, int value) +{ + printf("RFC2217 signal %d: %02x", signal, value); +} + +static void _nvtsrv_init(void) +{ + free(dd.tnct); + + memset(&dd, 0, sizeof(dd)); + + dd.tnct = telnet_ctx_init(NULL, _tn_tx, _tn_ss); +} + +static void _nvtsrv_cmd(tn_ctx_t * tcc, const char *line) +{ + char cmd[8]; + const char *args; + int len, val; + + cmd[0] = '\0'; + len = 0; + sscanf(line, "%7s %n", cmd, &len); + args = line + len; + + if (MATCH(cmd, "w")) + { + _tn_tx(NULL, args, strlen(args)); + } + else if (MATCH(cmd, "m")) + { + sscanf(args, "%x", &val); + telnet_rfc2217_change_modemstate(tcc, val); + } +} + +static void _nvtsrv_set_signal_handlers(void) +{ + struct sigaction sa = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + sigemptyset(&sa.sa_mask); + sigaction(SIGPIPE, &sa, NULL); +} + +int main(int argc, char **argv) +{ + const char *s; + char buf[128]; + int rc, nr, i, len; + + for (;;) + { + s = *argv++; + argc--; + if (*s != '-') + break; + } + + if (argc <= 0) + _nvtsrv_usage(); + + nvt_log_level(0, 10); + nvt_log_dest(NVT_LOG_STDOUT); + + _nvtsrv_set_signal_handlers(); + + memset(pfds, 0, sizeof(pfds)); + + pfds[0].fd = STDIN_FILENO; + pfds[0].events = POLLIN; + + pfds[1].fd = socket_create_server(argv[0]); + pfds[1].events = POLLIN; + + pfds[2].fd = -1; + pfds[2].events = POLLIN; + + nfds = 2; + + for (;;) + { + for (i = 0; i < 3; i++) + pfds[i].revents = 0; + + rc = poll(pfds, nfds, -1); + if (rc < 0) + { + printf("poll failed: %m\n"); + break; + } + + i = 0; + if (pfds[i].revents) + { + nr = read(pfds[i].fd, buf, sizeof(buf) - 1); + if (nr <= 0) + break; + buf[nr] = 0; + len = strcspn(buf, "\n\r"); + buf[len] = 0; +// printf("%d: fd=%d: r %d: '%.*s'\n", i, pfds[i].fd, nr, nr, buf); + _nvtsrv_cmd(dd.tnct, buf); + } + + i = 2; + if (pfds[i].revents) + { + nr = 0; + if (pfds[i].revents & POLLIN) + { + nr = read(pfds[i].fd, buf, sizeof(buf)); + if (nr <= 0) + { + _tn_cli_close(); + continue; + } + } + if (pfds[i].revents & (POLLERR | POLLHUP)) + { + _tn_cli_close(); + } + + if (nr > 0) + { + buf[nr] = 0; + len = strcspn(buf, "\n\r"); + buf[len] = 0; + nvt_log_buf(LOG_INFO, "Rx", buf, nr); + // printf("%d: fd=%d: r %d: '%.*s'\n", i, pfds[i].fd, nr, nr, buf); + len = nr; + rc = telnet_rx(dd.tnct, buf, &len); + printf("%d: fd=%d: r %d: '%.*s'\n", i, pfds[i].fd, rc, len, + buf); + } + } + + i = 1; + if (pfds[i].revents) + { + if (pfds[2].fd >= 0) + { + printf("Client already connected on fd=%d\n", pfds[2].fd); + break; + } + pfds[2].fd = socket_create_server_client(pfds[i].fd); + printf("%d: fd=%d: Connected\n", 2, pfds[2].fd); + pfds[2].events = POLLIN; + nfds = 3; + _nvtsrv_init(); + } + } + + return 0; +} diff --git a/nvtsrv/telnet.h b/nvtsrv/telnet.h new file mode 100644 index 0000000..7067374 --- /dev/null +++ b/nvtsrv/telnet.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Lars Thrane A/S + * SPDX-License-Identifier: GPL-2.0 + * + * Telnet interface - server + */ +#ifndef TELNET_SERVER_H +#define TELNET_SERVER_H + +typedef void (cli_tx_f) (void *cctx, const void *buf, int len); +typedef void (cli_ss_f) (void *cctx, int signal, int value); + +/* Telnet context */ +typedef struct { + void *cctx; /* Client context */ + + cli_tx_f *cli_tx; /* Write to client */ + cli_ss_f *cli_ss; /* Signal from client */ + + unsigned int bytes_rx; /* Rx byte count */ + unsigned int off_rx; /* Rx buffer offset */ + unsigned char mode_telnet; /* Telnet mode */ + unsigned char mode_rfc2217; /* RFC-2217 mode */ + + unsigned int baudrate; + unsigned char datasize; + unsigned char parity; + unsigned char stopsize; + + unsigned char linestate; + unsigned char modemstate; + + unsigned char linestate_mask; + unsigned char modemstate_mask; + + char dtr_state; + char rts_state; +} tn_ctx_t; + +extern tn_ctx_t *telnet_ctx_init(void *cctx, cli_tx_f * ftx, cli_ss_f * fss); + +extern int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen); +extern int telnet_tx(tn_ctx_t * tcc, const char *buf, int len); + +extern void telnet_reply_opt(tn_ctx_t * tcc, int opt, int prm, + const void *val, int len); + +extern void telnet_rfc2217_init(tn_ctx_t * tcc, int when); +extern void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt, + unsigned char *pu, int nd); + +extern void telnet_rfc2217_change_linestate(tn_ctx_t * tcc, int mask); +extern void telnet_rfc2217_change_modemstate(tn_ctx_t * tcc, int mask); + +/* Init modes */ +#define TNI_INIT 0 /* Init context */ +#define TNI_ON 1 /* Option activated */ + +/* Signals that may be passed to cli_ss_f() */ +#define TNS_DTR 1 +#define TNS_RTS 2 + +#endif /* TELNET_SERVER_H */ diff --git a/nvtsrv/telnet_basic.c b/nvtsrv/telnet_basic.c new file mode 100644 index 0000000..c68384e --- /dev/null +++ b/nvtsrv/telnet_basic.c @@ -0,0 +1,275 @@ +/* + * 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; +} diff --git a/nvtsrv/telnet_param.h b/nvtsrv/telnet_param.h new file mode 100644 index 0000000..268207c --- /dev/null +++ b/nvtsrv/telnet_param.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018 Lars Thrane A/S + * SPDX-License-Identifier: GPL-2.0 + * + * Telnet parameters + */ +#ifndef TELNET_PARAM_H +#define TELNET_PARAM_H + + +/* + * Telnet commands + */ + +#define TNC_F0_SE (0xf0) /* End of subnegotiation parameters */ +#define TNC_F1_NOP (0xf1) /* No operation */ +#define TNC_F2_MARK (0xf2) /* The data stream portion of a Synch. + This should always be accompanied + by a TCP Urgent notification. */ +#define TNC_F3_BRK (0xf3) /* NVT character BRK. */ +#define TNC_F4_IP (0xf4) /* The function IP (interrupt process) */ +#define TNC_F5_AO (0xf5) /* The function AO (abort output) */ +#define TNC_F6_AYT (0xf6) /* The function AYT (are you there) */ +#define TNC_F7_EC (0xf7) /* The function EC (erase charceter) */ +#define TNC_F8_EL (0xf8) /* The function EL (erase line) */ +#define TNC_F9_GA (0xf9) /* The GA signal (go ahead) */ +#define TNC_FA_SB (0xfa) /* Subnegotiation begin */ +#define TNC_FB_WILL (0xfb) /* Indicates the desire to begin + performing, or confirmation that + you are now performing, the + indicated option. */ +#define TNC_FC_WONT (0xfc) /* Indicates the refusal to perform, + or continue performing, the + indicated option. */ +#define TNC_FD_DO (0xfd) /* Indicates the request that the + other party perform, or + confirmation that you are expecting + the other party to perform, the + indicated option. */ +#define TNC_FE_DONT (0xfe) /* Indicates the demand that the + other party stop performing, + or confirmation that you are no + longer expecting the other party + to perform, the indicated option. */ +#define TNC_FF_IAC (0xff) /* Interpret as Command / data byte */ + + +/* + * Telnet options + */ + +/* + * https://www.iana.org/assignments/telnet-options/telnet-options.xhtml + * + * 0 00 Binary Transmission [RFC856] + * 1 01 Echo [RFC857] + * 2 02 Reconnection [DDN Protocol Handbook, ... ] + * 3 03 Suppress Go Ahead [RFC858] + * 4 04 Approx Message Size Negotiation ["The Ethernet, ..." ] + * 5 05 Status [RFC859] + * 6 06 Timing Mark [RFC860] + * 7 07 Remote Controlled Trans and Echo [RFC726] + * 8 08 Output Line Width [DDN Protocol Handbook, ... ] + * 9 09 Output Page Size [DDN Protocol Handbook, ... ] + * 10 0a Output Carriage-Return Disposition [RFC652] + * 11 0b Output Horizontal Tab Stops [RFC653] + * 12 0c Output Horizontal Tab Disposition [RFC654] + * 13 0d Output Formfeed Disposition [RFC655] + * 14 0e Output Vertical Tabstops [RFC656] + * 15 0f Output Vertical Tab Disposition [RFC657] + * 16 10 Output Linefeed Disposition [RFC658] + * 17 11 Extended ASCII [RFC698] + * 18 12 Logout [RFC727] + * 19 13 Byte Macro [RFC735] + * 20 14 Data Entry Terminal [RFC1043][RFC732] + * 21 15 SUPDUP [RFC736][RFC734] + * 22 16 SUPDUP Output [RFC749] + * 23 17 Send Location [RFC779] + * 24 18 Terminal Type [RFC1091] + * 25 19 End of Record [RFC885] + * 26 1a TACACS User Identification [RFC927] + * 27 1b Output Marking [RFC933] + * 28 1c Terminal Location Number [RFC946] + * 29 1d Telnet 3270 Regime [RFC1041] + * 30 1e X.3 PAD [RFC1053] + * 31 1f Negotiate About Window Size [RFC1073] + * 32 20 Terminal Speed [RFC1079] + * 33 21 Remote Flow Control [RFC1372] + * 34 22 Linemode [RFC1184] + * 35 23 X Display Location [RFC1096] + * 36 24 Environment Option [RFC1408] + * 37 25 Authentication Option [RFC2941] + * 38 26 Encryption Option [RFC2946] + * 39 27 New Environment Option [RFC1572] + * 40 28 TN3270E [RFC2355] + * 41 29 XAUTH [Rob_Earhart] + * 42 2a CHARSET [RFC2066] + * 43 2b Telnet Remote Serial Port (RSP) [Robert_Barnes] + * 44 2c Com Port Control Option [RFC2217] + * 45 2d Telnet Suppress Local Echo [Wirt_Atmar] + * 46 2e Telnet Start TLS [Michael_Boe] + * 47 2f KERMIT [RFC2840] + * 48 30 SEND-URL [David_Croft] + * 49 31 FORWARD_X [Jeffrey_Altman] + * 50-137 32-89 Unassigned [IANA] + * 138 8a TELOPT PRAGMA LOGON [Steve_McGregory] + * 139 8b TELOPT SSPI LOGON [Steve_McGregory] + * 140 8c TELOPT PRAGMA HEARTBEAT [Steve_McGregory] + * 141-254 8d-fe Unassigned + * 255 ff Extended-Options-List [RFC861] +*/ + +#define TNO_BINARY 0 /* 00 Binary Transmission */ +#define TNO_ECHO 1 /* 01 Echo */ +#define TNO_SUPP_GA 3 /* 03 Suppress Go Ahead */ +#define TNO_LOGOUT 18 /* 12 Logout */ +#define TNO_TTYP 24 /* 18 Terminal Type */ +#define TNO_NAWS 31 /* 1f Negotiate About Window Size */ +#define TNO_NEO 39 /* 27 New Environment Option */ +#define TNO_CPCO 44 /* 2c RFC 2217 */ +#define TNO_KERMIT 47 /* 2f Kermit */ + +#endif /* TELNET_PARAM_H */ diff --git a/nvtsrv/telnet_server_rfc2217.c b/nvtsrv/telnet_server_rfc2217.c new file mode 100644 index 0000000..7d4261f --- /dev/null +++ b/nvtsrv/telnet_server_rfc2217.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2018 Lars Thrane A/S + * SPDX-License-Identifier: GPL-2.0 + * + * Telnet RFC-2217 handling - server + */ +#include + +#include "lib/nvt_log.h" +#include "telnet.h" +#include "telnet_param.h" + +void telnet_rfc2217_init(tn_ctx_t * tcc, int when) +{ + switch (when) + { + default: /* Ignore */ + break; + case TNI_INIT: /* Init context */ + tcc->baudrate = 115200; + tcc->datasize = 8; /* 8 bit */ + tcc->parity = 1; /* None */ + tcc->stopsize = 1; /* 1 bit */ + + tcc->linestate = 0; + tcc->modemstate = 0; + + tcc->linestate_mask = 0; + tcc->modemstate_mask = 0xff; + + tcc->dtr_state = 0; + tcc->rts_state = 0; + break; + case TNI_ON: /* RFC-2217 activated */ + tcc->mode_rfc2217 = 1; + telnet_rfc2217_change_modemstate(tcc, tcc->modemstate); + break; + } +} + + +void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt, + unsigned char *pu, int nd) +{ + unsigned int sub, vali, valo, val4; + + sub = pu[0]; + switch (sub) + { + default: +// case 20-127: /* Available for Future Use */ + nvt_log(LOG_WARNING, "Unhandled SB: Opt/prm = %d/%d", opt, sub); + break; + case 0: /* SIGNATURE */ + telnet_reply_opt(tcc, opt, 100 + sub, "LT", 2); + DBG("RFC2217: Set signature"); + break; + case 1: /* SET-BAUDRATE */ + if (nd < 4) + break; /* Incorrect */ + vali = (pu[1] << 24) | (pu[2] << 16) | (pu[3] << 8) | pu[4]; + if (vali != 0) /* Set value */ + tcc->baudrate = vali; + valo = tcc->baudrate; + DBG("RFC2217: Set baud %u -> %u", vali, valo); + val4 = htonl(valo); + telnet_reply_opt(tcc, opt, 100 + sub, &val4, 4); + break; + case 2: /* SET-DATASIZE */ + valo = vali = pu[1]; +#if 0 /* Request only */ + if (vali != 0) /* Set value */ + tcc->datasize = vali; + else /* Request value */ +#endif + valo = tcc->datasize; + DBG("RFC2217: Set data %u -> %u", vali, valo); + telnet_reply_opt(tcc, opt, 100 + sub, &valo, 1); + break; + case 3: /* SET-PARITY */ + valo = vali = pu[1]; +#if 0 /* Request only */ + if (vali != 0) /* Set value */ + tcc->parity = vali; + else +#endif + valo = tcc->parity; + DBG("RFC2217: Set parity %u -> %u", vali, valo); + telnet_reply_opt(tcc, opt, 100 + sub, &valo, 1); + break; + case 4: /* SET-STOPSIZE */ + valo = vali = pu[1]; +#if 0 /* Request only */ + if (vali != 0) /* Set value */ + tcc->stopsize = vali; + else +#endif + valo = tcc->stopsize; + DBG("RFC2217: Set stop %u -> %u", vali, valo); + telnet_reply_opt(tcc, opt, 100 + sub, &valo, 1); + break; + case 5: /* SET-CONTROL */ + vali = pu[1]; + valo = 0; + switch (vali) + { + default: + nvt_log(LOG_WARNING, "Unexpected SB: Opt/prm/val = %d/%d/%u", + opt, sub, vali); + break; + + case 0: /* Request Com Port Flow Control Setting + (outbound/both) */ + case 1: /* Use No Flow Control (outbound/both) */ + valo = 1; /* No flow control */ + goto do_ctl_reply; + case 2: /* Use XON/XOFF Flow Control (outbound/both) */ + valo = 1; /* No flow control */ + nvt_log(LOG_WARNING, "RFC2217: Flow O %d -> %d", vali, valo); + goto do_ctl_reply; + case 3: /* Use HARDWARE Flow Control (outbound/both) */ + valo = 3; /* HW flow control */ + goto do_ctl_reply; + + case 4: /* Request BREAK State */ + valo = 6; /* BREAK off */ + nvt_log(LOG_WARNING, "RFC2217: Break st %d -> %d", vali, valo); + goto do_ctl_reply; + case 5: /* Set BREAK State ON */ + case 6: /* Set BREAK State OFF */ + valo = 6; /* BREAK off */ + goto do_ctl_reply; + + case 7: /* Request DTR Signal State */ + valo = (tcc->dtr_state) ? 8 : 9; + goto do_ctl_reply; + case 8: /* Set DTR Signal State ON */ + case 9: /* Set DTR Signal State OFF */ + valo = vali; + tcc->dtr_state = (valo == 8) ? 1 : 0; + nvt_log(LOG_INFO, "RFC2217: Set DTR %s", + tcc->dtr_state ? "on" : "off"); + goto do_ctl_reply; + + case 10: /* Request RTS Signal State */ + valo = (tcc->rts_state) ? 11 : 12; + goto do_ctl_reply; + case 11: /* Set RTS Signal State ON */ + case 12: /* Set RTS Signal State OFF */ + valo = vali; + tcc->rts_state = (valo == 11) ? 1 : 0; + nvt_log(LOG_INFO, "RFC2217: Set RTS %s", + tcc->rts_state ? "on" : "off"); + goto do_ctl_reply; + + case 13: /* Request Com Port Flow Control Setting (inbound) */ + case 14: /* Use No Flow Control (inbound) */ + valo = 14; /* No flow control */ + goto do_ctl_reply; + case 16: /* Use HARDWARE Flow Control (inbound) */ + valo = 16; /* HW flow control */ + goto do_ctl_reply; + case 15: /* Use XON/XOFF Flow Control (inbound) */ + case 17: /* Use DCD Flow Control (outbound/both) */ + case 18: /* Use DTR Flow Control (inbound) */ + case 19: /* Use DSR Flow Control (outbound/both) */ + valo = 14; /* No flow control */ + nvt_log(LOG_WARNING, "RFC2217: Flow I %d -> %d", vali, valo); + goto do_ctl_reply; + + do_ctl_reply: + DBG("RFC2217: Set control %u -> %u", vali, valo); + telnet_reply_opt(tcc, opt, 100 + sub, &valo, 1); + switch (vali) + { + default: /* Do noting */ + break; + case 8: /* Set DTR Signal State ON */ + case 9: /* Set DTR Signal State OFF */ + if (tcc->cli_ss) + tcc->cli_ss(tcc->cctx, TNS_DTR, tcc->dtr_state); + break; + case 11: /* Set RTS Signal State ON */ + case 12: /* Set RTS Signal State OFF */ + if (tcc->cli_ss) + tcc->cli_ss(tcc->cctx, TNS_RTS, tcc->rts_state); + break; + } + break; + } + break; +#if 0 /* Server -> client only */ + case 6: /* NOTIFY-LINESTATE */ + case 7: /* NOTIFY-MODEMSTATE */ + break; +#endif + case 8: /* FLOWCONTROL-SUSPEND */ + case 9: /* FLOWCONTROL-RESUME */ + nvt_log(LOG_WARNING, "RFC2217: Flowcontrol %s", + (sub == 8) ? "suspend" : "resume"); + /* NB! Do not reply */ + break; + case 10: /* SET-LINESTATE-MASK */ + valo = vali = pu[1]; + tcc->linestate_mask = vali; + DBG("RFC2217: Set linestate mask 0x%02x", vali); + telnet_reply_opt(tcc, opt, 100 + sub, &valo, 1); + break; + case 11: /* SET-MODEMSTATE-MASK */ + valo = vali = pu[1]; + tcc->modemstate_mask = vali; + DBG("RFC2217: Set modemstate mask 0x%02x", vali); + telnet_reply_opt(tcc, opt, 100 + sub, &valo, 1); + break; + case 12: /* PURGE-DATA */ + valo = vali = pu[1]; + DBG("RFC2217: Purge %d", vali); + telnet_reply_opt(tcc, opt, 100 + sub, &valo, 1); + break; + } +} + +void telnet_rfc2217_change_linestate(tn_ctx_t * tcc, int mask) +{ + unsigned char buf[1]; + + if (!tcc->mode_rfc2217) + goto done; + + buf[0] = mask & tcc->linestate_mask; + buf[0] &= 0xf0; /* Drop change bits */ + DBG("RFC2217: Notify linestate 0x%02x->0x%02x: 0x%02x", + tcc->linestate, mask, buf[0]); + + telnet_reply_opt(tcc, TNO_CPCO, 100 + 6, buf, 1); + + done: + tcc->linestate = mask; +} + +void telnet_rfc2217_change_modemstate(tn_ctx_t * tcc, int mask) +{ + unsigned char buf[1]; + + if (!tcc->mode_rfc2217) + goto done; + + buf[0] = mask & tcc->modemstate_mask; + DBG("RFC2217: Notify modemstate 0x%02x->0x%02x: 0x%02x", + tcc->modemstate, mask, buf[0]); + + telnet_reply_opt(tcc, TNO_CPCO, 100 + 7, buf, 1); + + done: + tcc->modemstate = mask; +}