From e68e140ef06fa71ac1cbc28d1a564752694d81c8 Mon Sep 17 00:00:00 2001 From: Frank Rolsted Jensen Date: Tue, 11 Sep 2018 10:21:35 +0200 Subject: [PATCH] ttynvt version 0.10 --- .gitignore | 26 ++ AUTHORS | 2 + LICENSE => COPYING | 8 +- Makefile.am | 20 + README | 75 ++++ configure.ac | 42 ++ telnet.h | 70 +++ telnet_basic.c | 299 +++++++++++++ telnet_param.h | 123 ++++++ telnet_rfc2217.c | 52 +++ ttynvt.c | 1040 ++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1753 insertions(+), 4 deletions(-) create mode 100644 .gitignore create mode 100644 AUTHORS rename LICENSE => COPYING (98%) create mode 100644 Makefile.am create mode 100644 README create mode 100644 configure.ac create mode 100644 telnet.h create mode 100644 telnet_basic.c create mode 100644 telnet_param.h create mode 100644 telnet_rfc2217.c create mode 100644 ttynvt.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4d932e --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# autoreconf +/aclocal.m4 +/autom4te.cache/ +/compile +/config.h.in +/config.h.in~ +/configure +/depcomp +/install-sh +/missing +Makefile.in + +# configure +/config.h +/config.log +/config.status +/stamp-h1 +.deps +Makefile + +# build +*.o +/ttynvt + +/ttynvt-*.tar.gz +/ttynvt-*.tar.xz diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..1024d8b --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Frank Rolsted Jensen +Kim Woelders diff --git a/LICENSE b/COPYING similarity index 98% rename from LICENSE rename to COPYING index 80f4947..d159169 100644 --- a/LICENSE +++ b/COPYING @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc., + Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -290,8 +290,8 @@ to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - ttynvt - Copyright (C) 2018 lars-thrane-as + + Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -329,7 +329,7 @@ necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. - {signature of Ty Coon}, 1 April 1989 + , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..2929308 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,20 @@ +# Build ttynvt +# + +MAINTAINERCLEANFILES = \ + aclocal.m4 autom4te.cache compile config.h.in config.h.in~ configure \ + depcomp install-sh missing \ + Makefile.in + +CFLAGS_OPT = -O3 +CFLAGS_WARN = -Wall -Wextra -Werror -Wno-unused-parameter + +bin_PROGRAMS = ttynvt + +ttynvt_SOURCES = \ + ttynvt.c \ + telnet_basic.c telnet_rfc2217.c telnet.h telnet_param.h + +ttynvt_CPPFLAGS = -I$(top_builddir) $(FUSE_CFLAGS) $(CFLAGS_OPT) $(CFLAGS_WARN) + +ttynvt_LDADD = $(FUSE_LIBS) diff --git a/README b/README new file mode 100644 index 0000000..911b255 --- /dev/null +++ b/README @@ -0,0 +1,75 @@ +ttynvt makes a virtual serial device (tty) and connects +the device to a Network Virtual Terminal (NVT). + + +-ttynvt-+------------------------+ + | |\ | + | | +---------------+ | + | | | | + | | | | + /dev/ttyNVT | pty /dev/ppp pty | + | | slave | master | + | | | | | | + -------------------------------------------------- + | | | | | | + | | | | | | + +--FUSE--+ +-tty--ldisc------+ TCP--->host + + +1 Build environment + gcc is required. + glibc must support pthreads. + libfuse - Filesystem in Userspace - development support is needed. + +2 Target + The linux kernel at the target system must support FUSE (FUSE_FS=y) and + libpthread and libfuse must be installed in the root filesystem. + +3 Build + $ autoreconf -vif + Example configuration for native build: + $ ./configure + Example configuration for cross build: + $ ./configure --host=ARCH-unknown-linux \ + CC=PATH-TO-CROSS-GCC \ + PKG_CONFIG_LIBDIR=PATH-TO-CROSS-PKG_CONFIG_LIBDIR" + or + $ ./configure --host=ARCH-unknown-linux \ + CC=PATH-TO-CROSS-GCC \ + LDFLAGS="-L PATH-TO-LOCATION-OF-libfuse.so" + $ make + + The default target in the Makefile builds the Network Virtual Terminal. + Output name is: ttynvt. + +4 Run + The application (ttynvt) installs a virtual serial device in the /dev + directory (by default: /dev/ttyNVT0). + The major/minor number of the device, the address (name/IP) and the port + number of the serial server must be specified when the application + is started (command-line options). + + E.g. + ./ttynvt -M 199 -m 6 -n ttyNVT0 -S 169.254.1.1:5020 + + To get a list of all command-line options: + ./ttynvt --help + +5. Test + The serial server: ser2net (http://ser2net.sourceforge.net/) might be used + to test the virtual serial server on the target. + Install the server (ser2net) and add following line to the configuration + file (/etc/ser2net.conf): + 5020:telnet:0:/dev/ttyS0:115200 remctl NONE 1STOPBIT 8DATABITS RTSCTS HANGUP_WHEN_DONE + At the server, plug-in a loopback connector in the serial port (ttyS0). + At the target, open the virtual serial port, with a termial program, + e.g minicom: + minicom -D /dev/ttyNVT0 + +6 Diagnostic + Add the command-line option '-D ' to enable logging of diagnostic information. + The log is forwarded to syslog with the debug level. + Wireshark is also very useful when debugging the interface between the client + and the server. + +Ref: RFC-854 - Telnet Protocol Specification + RFC-2217 - Telnet Com Port Control Option diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..a80501e --- /dev/null +++ b/configure.ac @@ -0,0 +1,42 @@ +m4_define([pkg_version], [0.10]) +#m4_define([pkg_revision], [005]) +m4_define([pkg_version], m4_ifdef([pkg_revision], [pkg_version.pkg_revision], [pkg_version])) + +AC_INIT([ttynvt],[pkg_version],[frj@thrane.eu]) +AM_INIT_AUTOMAKE([foreign dist-xz]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AC_USE_SYSTEM_EXTENSIONS + +AC_PROG_CC + +AC_PROG_INSTALL + +AC_HEADER_STDC + +define([AC_LIBTOOL_LANG_CXX_CONFIG], [:])dnl +define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl +define([AC_LIBTOOL_LANG_GCJ_CONFIG], [:])dnl + +AC_C_BIGENDIAN +AC_C_CONST +AC_C_INLINE + +AC_CHECK_SIZEOF(int, 4) +AC_CHECK_SIZEOF(long, 4) + +PKG_PROG_PKG_CONFIG + +PKG_CHECK_MODULES(FUSE, fuse) + +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([ +Makefile +]) +AC_OUTPUT + +echo +echo "------------------------------------------------------------------------" +echo "$PACKAGE Version $VERSION configured" +echo "------------------------------------------------------------------------" +echo diff --git a/telnet.h b/telnet.h new file mode 100644 index 0000000..4d49fab --- /dev/null +++ b/telnet.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Lars Thrane A/S + * SPDX-License-Identifier: GPL-2.0 + * + * Telnet interface + */ +#ifndef TELNET_H +#define TELNET_H + +typedef int (srv_tx_f) (void *cctx, const void *buf, int len); +typedef int (srv_rx_f) (void *cctx, int timeout); +typedef void (srv_ms_f) (void *cctx, int status); + +/* Telnet context */ +typedef struct { + void *cctx; /* Client context */ + + srv_tx_f *srv_tx; /* Write to server */ + srv_rx_f *srv_rx; /* Read from server */ + srv_ms_f *srv_ms; /* Modem status from server */ + + 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 char telnet_ack[256]; /* telnet option ack counter */ +} tn_ctx_t; + +extern tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f * ftx, srv_rx_f * frx, + srv_ms_f * fss); + +extern int telnet_open(tn_ctx_t * tcc); + +extern int telnet_rx(tn_ctx_t * tcc, char *buf, size_t * plen); +extern int telnet_tx(tn_ctx_t * tcc, const char *buf, int len); + +extern void telnet_set_opt(tn_ctx_t * tcc, int opt, int prm, + const void *val, int len); + +extern void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt, + unsigned char *pu, int nd); + +extern void telnet_rfc2217_cfg(tn_ctx_t * tcc, int cmd, + const void *val, int nd); +extern void telnet_rfc2217_ctl(tn_ctx_t * tcc, int cmd); + +/*RFC2217 modem state*/ +#define TNS_STATE_CD 0x80 +#define TNS_STATE_RI 0x40 +#define TNS_STATE_DSR 0x20 +#define TNS_STATE_CTS 0x10 + +/*RFC2217 configuration commands*/ +#define TNS_SET_BAUDRATE 1 +#define TNS_SET_DATASIZE 2 +#define TNS_SET_PARITY 3 +#define TNS_SET_STOPSIZE 4 +#define TNS_SET_CONTROL 5 +#define TNS_SET_PURGE 12 + +/*RFC2217 control commands*/ +#define TNS_CTL_NOFLOW 1 +#define TNS_CTL_XONOFF 2 +#define TNS_CTL_CTSRTS 3 +#define TNS_CTL_DTR_ON 8 +#define TNS_CTL_DTR_OFF 9 +#define TNS_CTL_RTS_ON 11 +#define TNS_CTL_RTS_OFF 12 + +#endif /*TELNET_H */ diff --git a/telnet_basic.c b/telnet_basic.c new file mode 100644 index 0000000..645783b --- /dev/null +++ b/telnet_basic.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2018 Lars Thrane A/S + * SPDX-License-Identifier: GPL-2.0 + * + * Telnet server - Telnet protocol handling + */ + +#include +#include +#include + +#include "telnet.h" +#include "telnet_param.h" + + +/* Telnet commands */ +#if 0 +static const char *const tn_cmd[] = { + "SE", "NOP", "MARK", "BRK", "IP", "AO", "AYT", "EC", + "EL", "GA", "SB", "WILL", "WONT", "DO", "DONT", "IAC" +}; +#endif + +#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]; + + if (tcc->telnet_ack[opt] > 0) + return; + + switch (tn_opt[opt]) + { + default: + case OPT_IGNORE: + return; + case OPT_ACK: + cmd = TNC_FD_DO; + break; + case OPT_NACK: + cmd = TNC_FE_DONT; + break; + } + + buf[0] = TNC_FF_IAC; + buf[1] = cmd; + buf[2] = opt; + + tcc->telnet_ack[opt]++; + + tcc->srv_tx(tcc->cctx, buf, 3); +} + +static void _telnet_handle_resp(tn_ctx_t * tcc, int cmd, int opt) +{ + if (opt == TNO_CPCO) + { + if (cmd == TNC_FD_DO) + tcc->mode_rfc2217 = 1; + } +} + +void telnet_set_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; + + tcc->srv_tx(tcc->cctx, buf, len); +} + +static void _telnet_handle_opt(tn_ctx_t * tcc, int opt, + unsigned char *pu, int nd) +{ + switch (opt) + { + default: + 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, size_t * plen) +{ + char *s, *p; + unsigned char *pu; + size_t len, rem; + int np, nd; + int b1, b2; + + len = *plen; + tcc->bytes_rx += len; + + if (len < (size_t) tcc->off_rx) + tcc->off_rx = 0; + + for (s = buf + tcc->off_rx, rem = len - tcc->off_rx; rem > 0; s = p) + { + 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) + return 1; + + 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: + 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]; + if (b1 == TNC_FB_WILL) + _telnet_handle_req(tcc, b1, b2); + else if (b1 == TNC_FD_DO) + _telnet_handle_resp(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 */ + _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 */ + 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; + 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->srv_tx(tcc->cctx, s, nd); + } + else + { + nd = p - s + 1; + tcc->srv_tx(tcc->cctx, s, nd); + tcc->srv_tx(tcc->cctx, p, 1); /* One TNC_FF_IAC */ + p++; + } + rem -= nd; + } + + return 0; +} + +int telnet_open(tn_ctx_t * tcc) +{ + time_t t = time(NULL);; + char buf[3]; + int res; + + buf[0] = TNC_FF_IAC; + buf[1] = TNC_FB_WILL; + buf[2] = TNO_CPCO; + + tcc->srv_tx(tcc->cctx, buf, 3); + + while (1) + { + res = tcc->srv_rx(tcc->cctx, 1000); + if (res <= 0) + return -1; + else if (tcc->mode_rfc2217) + return 0; + else if (t - time(NULL) > 2) + return -1; + } +} + +tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f * ftx, + srv_rx_f * frx, srv_ms_f * fms) +{ + tn_ctx_t *tcc; + + tcc = calloc(1, sizeof(tn_ctx_t)); + tcc->cctx = cctx; + tcc->srv_tx = ftx; /* Write to server */ + tcc->srv_rx = frx; /* Read from server */ + tcc->srv_ms = fms; /* Set modem state */ + + return tcc; +} diff --git a/telnet_param.h b/telnet_param.h new file mode 100644 index 0000000..e7d9eb0 --- /dev/null +++ b/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 Binary Transmission [RFC856] + * 1 Echo [RFC857] + * 2 Reconnection [DDN Protocol Handbook, ... ] + * 3 Suppress Go Ahead [RFC858] + * 4 Approx Message Size Negotiation ["The Ethernet, ... ] + * 5 Status [RFC859] + * 6 Timing Mark [RFC860] + * 7 Remote Controlled Trans and Echo [RFC726] + * 8 Output Line Width [DDN Protocol Handbook, ... ] + * 9 Output Page Size [DDN Protocol Handbook, ... ] + * 10 Output Carriage-Return Disposition [RFC652] + * 11 Output Horizontal Tab Stops [RFC653] + * 12 Output Horizontal Tab Disposition [RFC654] + * 13 Output Formfeed Disposition [RFC655] + * 14 Output Vertical Tabstops [RFC656] + * 15 Output Vertical Tab Disposition [RFC657] + * 16 Output Linefeed Disposition [RFC658] + * 17 Extended ASCII [RFC698] + * 18 Logout [RFC727] + * 19 Byte Macro [RFC735] + * 20 Data Entry Terminal [RFC1043][RFC732] + * 21 SUPDUP [RFC736][RFC734] + * 22 SUPDUP Output [RFC749] + * 23 Send Location [RFC779] + * 24 Terminal Type [RFC1091] + * 25 End of Record [RFC885] + * 26 TACACS User Identification [RFC927] + * 27 Output Marking [RFC933] + * 28 Terminal Location Number [RFC946] + * 29 Telnet 3270 Regime [RFC1041] + * 30 X.3 PAD [RFC1053] + * 31 Negotiate About Window Size [RFC1073] + * 32 Terminal Speed [RFC1079] + * 33 Remote Flow Control [RFC1372] + * 34 Linemode [RFC1184] + * 35 X Display Location [RFC1096] + * 36 Environment Option [RFC1408] + * 37 Authentication Option [RFC2941] + * 38 Encryption Option [RFC2946] + * 39 New Environment Option [RFC1572] + * 40 TN3270E [RFC2355] + * 41 XAUTH [Rob_Earhart] + * 42 CHARSET [RFC2066] + * 43 Telnet Remote Serial Port (RSP) [Robert_Barnes] + * 44 Com Port Control Option [RFC2217] + * 45 Telnet Suppress Local Echo [Wirt_Atmar] + * 46 Telnet Start TLS [Michael_Boe] + * 47 KERMIT [RFC2840] + * 48 SEND-URL [David_Croft] + * 49 FORWARD_X [Jeffrey_Altman] + * 50-137 Unassigned [IANA] + * 138 TELOPT PRAGMA LOGON [Steve_McGregory] + * 139 TELOPT SSPI LOGON [Steve_McGregory] + * 140 TELOPT PRAGMA HEARTBEAT [Steve_McGregory] + * 141-254 Unassigned + * 255 Extended-Options-List [RFC861] +*/ + +#define TNO_BINARY 0 /* Binary Transmission */ +#define TNO_ECHO 1 /* Echo */ +#define TNO_SUPP_GA 3 /* Suppress Go Ahead */ +#define TNO_LOGOUT 18 /* Logout */ +#define TNO_TTYP 24 /* Terminal Type */ +#define TNO_NAWS 31 /* Negotiate About Window Size */ +#define TNO_NEO 39 /* New Environment Option */ +#define TNO_CPCO 44 /* RFC 2217 */ +#define TNO_KERMIT 47 /* Kermit */ + +#endif /* TELNET_PARAM_H */ diff --git a/telnet_rfc2217.c b/telnet_rfc2217.c new file mode 100644 index 0000000..94d23ef --- /dev/null +++ b/telnet_rfc2217.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Lars Thrane A/S + * SPDX-License-Identifier: GPL-2.0 + * + * Telnet server - RFC-2217 handling + */ + +#include + +#include "telnet.h" +#include "telnet_param.h" + +void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt, + unsigned char *pu, int nd) +{ + unsigned int sub; + + sub = pu[0]; + switch (sub) + { + default: + break; + case 100: /* SIGNATURE */ + telnet_set_opt(tcc, opt, 0, "LT-NVT", 6); + break; + case 106: /* NOTIFY-LINESTATE */ + break; + case 107: /* NOTIFY-MODEMSTATE */ + if (tcc->srv_ms) + tcc->srv_ms(tcc->cctx, pu[1]); + break; + case 108: /* FLOWCONTROL-SUSPEND */ + case 109: /* FLOWCONTROL-RESUME */ + break; + } +} + +void telnet_rfc2217_cfg(tn_ctx_t * tcc, int cmd, const void *val, int nd) +{ + if (!tcc->mode_rfc2217) + return; + + telnet_set_opt(tcc, TNO_CPCO, cmd, val, nd); +} + +void telnet_rfc2217_ctl(tn_ctx_t * tcc, int cmd) +{ + if (!tcc->mode_rfc2217) + return; + + telnet_set_opt(tcc, TNO_CPCO, TNS_SET_CONTROL, &cmd, 1); +} diff --git a/ttynvt.c b/ttynvt.c new file mode 100644 index 0000000..43368ff --- /dev/null +++ b/ttynvt.c @@ -0,0 +1,1040 @@ +/* + * Copyright (c) 2018 Lars Thrane A/S + * SPDX-License-Identifier: GPL-2.0 + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "telnet.h" + +#define NET_BUF_SIZE 1024 +#define TMP_BUF_SIZE 1024 + +#define FD_NET 0 +#define FD_SLAVE 1 +#define FD_MASTER 2 + +#define _PRF_N_(no) __attribute__((__format__(__printf__, (no), (no) + 1))) +#define _PRF2_ _PRF_N_(2) + + +static struct ttynvt_param { + unsigned int major; + unsigned int minor; + const char *dev_name; + char *server; + unsigned int debug; + /* Derived */ + char host[64]; + unsigned int port; + /* Picked up */ + int foreground; +} ttynvt_param; + +typedef struct { + pthread_mutex_t tty_lock; + pthread_mutex_t poll_lock; + pthread_t thread_id; + + struct fuse_pollhandle *ph; + + struct pollfd fds[3]; + + char net_buf[NET_BUF_SIZE]; + size_t net_cnt; + char tmp_buf[TMP_BUF_SIZE]; + + struct termios tio; + int cmcr; + int epipe; + + volatile int smcr; + volatile int error; + volatile int pollin; + + tn_ctx_t *tn; +} ttynvt_t; + +#include +#define gettid() (pid_t)syscall(__NR_gettid) + +_PRF2_ static void _log(int prio, const char *fmt, ...) +{ + va_list arg; + char buf[256]; + + va_start(arg, fmt); +#if 0 + vsyslog(prio, fmt, arg); +#else + vsnprintf(buf, sizeof(buf), fmt, arg); + syslog(prio, "[%d] %s", gettid(), buf); +#endif + va_end(arg); +} + +#define DBG(...) \ + if (ttynvt_param.debug > 0) _log(LOG_DEBUG, __VA_ARGS__) +#define DBG2(...) \ + if (ttynvt_param.debug > 1) _log(LOG_DEBUG, __VA_ARGS__) + + +static void sig_handler(int sig) +{ +} + +static void set_signal_handler(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = sig_handler; + sigemptyset(&(sa.sa_mask)); + sa.sa_flags = 0; + sigaction(SIGUSR2, &sa, NULL); +} + +static void ttynvt_interrupted(fuse_req_t req, void *data) +{ + pthread_t thread = (pthread_t) data; + + pthread_kill(thread, SIGUSR2); +} + +static int _check_dev(void) +{ + int rc; + char dev_nbuf[128]; + struct stat st; + + snprintf(dev_nbuf, sizeof(dev_nbuf), "/dev/%s", ttynvt_param.dev_name); + + rc = stat(dev_nbuf, &st); + + DBG("Checking for %s: %d\n", dev_nbuf, rc); + + return rc; +} + + +static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info) +{ + ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh; + + _log(LOG_INFO, "connection closed\n"); + + close(tty->fds[FD_NET].fd); + close(tty->fds[FD_MASTER].fd); + close(tty->fds[FD_SLAVE].fd); + + pthread_cancel(tty->thread_id); + pthread_join(tty->thread_id, NULL); + pthread_mutex_destroy(&tty->tty_lock); + pthread_mutex_destroy(&tty->poll_lock); + + if (tty->ph) + fuse_pollhandle_destroy(tty->ph); + + free(tty->tn); + free(tty); + fuse_reply_err(req, 0); +} + +static void _notify(ttynvt_t * tty) +{ + pthread_mutex_lock(&tty->poll_lock); + if (tty->ph) + { + fuse_lowlevel_notify_poll(tty->ph); + fuse_pollhandle_destroy(tty->ph); + tty->ph = NULL; + } + pthread_mutex_unlock(&tty->poll_lock); +} + +static void _update_notify(ttynvt_t * tty, struct fuse_pollhandle *ph) +{ + pthread_mutex_lock(&tty->poll_lock); + struct fuse_pollhandle *tmp_ph = tty->ph; + + tty->ph = ph; + pthread_mutex_unlock(&tty->poll_lock); + if (tmp_ph) + fuse_pollhandle_destroy(tmp_ph); +} + +static void *_read_net(void *arg) +{ + ttynvt_t *tty = (ttynvt_t *) arg; + ssize_t res; + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + while (1) + { + res = poll(tty->fds, 3, -1); + if (res < 0) + { + if (errno == EINTR) + continue; + else + break; + } + if (tty->fds[FD_NET].revents & POLLIN) + { + res = read(tty->fds[FD_NET].fd, tty->net_buf + tty->net_cnt, + NET_BUF_SIZE - tty->net_cnt); + if (res < 0) + { + _log(LOG_ERR, "net read error: %s\n", strerror(errno)); + break; + } + else if (res == 0) + { + _log(LOG_INFO, "Connection closed by remote host\n"); + break; + } + tty->net_cnt += res; + res = telnet_rx(tty->tn, tty->net_buf, &tty->net_cnt); + if (tty->net_cnt > 0 && res == 0) + { + res = + write(tty->fds[FD_MASTER].fd, tty->net_buf, tty->net_cnt); + if (res < 0) + { + _log(LOG_ERR, "master write error:%s\n", strerror(errno)); + break; + } + tty->net_cnt = 0; + } + } + if (tty->fds[FD_MASTER].revents & POLLIN) + { + res = read(tty->fds[FD_MASTER].fd, tty->tmp_buf, TMP_BUF_SIZE); + if (res < 0) + { + _log(LOG_ERR, "master read error:%s\n", strerror(errno)); + break; + } + telnet_tx(tty->tn, tty->tmp_buf, res); + } + if (tty->fds[FD_SLAVE].revents & POLLIN) + { + tty->pollin = 1; + _notify(tty); + } + } + + tty->error = 1; + _notify(tty); + + return 0; +} + +static int _srv_connect(const char *host, unsigned int port) +{ + struct sockaddr_in serveraddr; + struct hostent *server; + int sockfd, err; + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + { + _log(LOG_ERR, "Socket open failed: %s\n", strerror(errno)); + return sockfd; + } + + server = gethostbyname(host); + if (server == NULL) + { + _log(LOG_ERR, "Cannot resolve host name: %s\n", strerror(errno)); + close(sockfd); + return -1; + } + + bzero((char *)&serveraddr, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + bcopy((char *)server->h_addr, (char *)&serveraddr.sin_addr.s_addr, + server->h_length); + serveraddr.sin_port = htons(port); + + err = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); + if (err < 0) + { + _log(LOG_ERR, "Failed to connect to: %s:%u:%s\n", + host, port, strerror(errno)); + close(sockfd); + return -1; + } + + return sockfd; +} + +static int _srv_write(void *cctx, const void *buf, int len) +{ + ttynvt_t *tty = cctx; + int res; + + res = write(tty->fds[FD_NET].fd, buf, len); + + return res; +} + +static int _srv_read(void *cctx, int timeout) +{ + ttynvt_t *tty = cctx; + int res; + + while (1) + { + res = poll(&tty->fds[FD_NET], 1, timeout); + if (res <= 0) + return res; + + if (tty->fds[FD_NET].revents & POLLIN) + { + res = read(tty->fds[FD_NET].fd, tty->net_buf + tty->net_cnt, + NET_BUF_SIZE - tty->net_cnt); + if (res <= 0) + return res; + tty->net_cnt += (size_t) res; + res = telnet_rx(tty->tn, tty->net_buf, &tty->net_cnt); + if (res == 0) + break; + } + } + return 1; +} + +static void _modem_status_cb(void *cctx, int status) +{ + ttynvt_t *tty = cctx; + int mcr = 0; + + DBG("MCR: DSR=%c CTS=%c RI=%c CD=%c\n", + status & TNS_STATE_DSR ? '1' : '0', + status & TNS_STATE_CTS ? '1' : '0', + status & TNS_STATE_RI ? '1' : '0', status & TNS_STATE_CD ? '1' : '0'); + + if (status & TNS_STATE_CD) + mcr |= TIOCM_CD; + if (status & TNS_STATE_RI) + mcr |= TIOCM_RI; + if (status & TNS_STATE_CTS) + mcr |= TIOCM_CTS; + if (status & TNS_STATE_DSR) + mcr |= TIOCM_DSR; + + pthread_mutex_lock(&tty->tty_lock); + tty->smcr = mcr; + pthread_mutex_unlock(&tty->tty_lock); +} + +static void ttynvt_init(void *userdata, struct fuse_conn_info *conn) +{ + set_signal_handler(); +} + +static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info) +{ + ttynvt_t *tty; + char *slave_name; + struct termios ios; + int sockfd; + int fd; + int res; + int n; + + DBG2("open: thread:%lu\n", pthread_self()); + + tty = calloc(1, sizeof(*tty)); + if (!tty) + { + fuse_reply_err(req, ENOMEM); + return; + } + for (n = 0; n < 3; n++) + tty->fds[n].fd = -1; + + tty->tn = telnet_ctx_init(tty, _srv_write, _srv_read, _modem_status_cb); + if (!tty->tn) + { + free(tty); + fuse_reply_err(req, ENOMEM); + return; + } + + sockfd = _srv_connect(ttynvt_param.host, ttynvt_param.port); + if (sockfd < 0) + { + free(tty->tn); + free(tty); + fuse_reply_err(req, EHOSTUNREACH); + return; + } + + tty->fds[FD_NET].fd = sockfd; + tty->fds[FD_NET].events = POLLIN; + tty->net_cnt = 0; + + if ((fd = open("/dev/ptmx", O_RDWR)) < 0) /* open master */ + goto open_err; + + tty->fds[FD_MASTER].fd = fd; + tty->fds[FD_MASTER].events = POLLIN; + + ios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP + | INLCR | IGNCR | ICRNL | IXON); + ios.c_oflag &= ~OPOST; + ios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + ios.c_cflag &= ~(CSIZE | PARENB); + ios.c_cflag |= CS8; + + if (ioctl(fd, TCSETS, &ios) < 0) + goto open_err; + + grantpt(fd); /* change permission of slave */ + unlockpt(fd); /* unlock slave */ + slave_name = ptsname(fd); /* get name of slave */ + + if ((fd = open(slave_name, O_RDWR | O_NOCTTY)) < 0) /* open slave */ + goto open_err; + + tty->fds[FD_SLAVE].fd = fd; + tty->fds[FD_SLAVE].events = POLLIN; + + res = telnet_open(tty->tn); + if (res < 0) + { + errno = EPROTO; + goto open_err; + } + if ((res = pthread_mutex_init(&tty->tty_lock, NULL) < 0) || + (res = pthread_mutex_init(&tty->poll_lock, NULL) < 0) || + (res = pthread_create(&tty->thread_id, NULL, &_read_net, tty))) + { + errno = res; + goto open_err; + } + + info->fh = (uintptr_t) tty; + info->nonseekable = 1; + info->direct_io = 1; + + _log(LOG_INFO, "Connected to server: %s:%d\n", + ttynvt_param.host, ttynvt_param.port); + + fuse_reply_open(req, info); + + return; + + open_err: + + _log(LOG_INFO, "Connection to server: %s:%d failed:%s\n", + ttynvt_param.host, ttynvt_param.port, strerror(errno)); + + for (n = 0; n < 3; n++) + if (tty->fds[n].fd >= 0) + close(tty->fds[n].fd); + + free(tty->tn); + free(tty); + fuse_reply_err(req, errno); +} + +static void +ttynvt_read(fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *info) +{ + ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh; + int res; + + if (tty->error) + { + DBG2("read (slave) in error state, thread:%lu\n", pthread_self()); + if (tty->epipe == 0) + { + _log(LOG_WARNING, "Rx EPIPE\n"); + tty->epipe = 1; + fuse_reply_buf(req, tty->tmp_buf, 0); + } + else + fuse_reply_err(req, EBADFD); + + return; + } + + if (size > TMP_BUF_SIZE) + size = TMP_BUF_SIZE; + + fuse_req_interrupt_func(req, ttynvt_interrupted, (void *)pthread_self()); + res = read(tty->fds[FD_SLAVE].fd, tty->tmp_buf, size); + fuse_req_interrupt_func(req, NULL, NULL); + if (res < 0) + { + DBG2("read (slave) error: %s\n", strerror(errno)); + fuse_reply_err(req, errno); + return; + } + + DBG2("read (slave): size:%d, thread:%lu\n", res, pthread_self()); + + fuse_reply_buf(req, tty->tmp_buf, res); +} + +static void +ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off, + struct fuse_file_info *info) +{ + ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh; + ssize_t res; + + + if (size == 0) + { + fuse_reply_write(req, 0); + return; + } + + if (tty->error) + { + fuse_reply_err(req, EPIPE); + return; + } + + res = write(tty->fds[FD_SLAVE].fd, data, size); + if (res < 0) + { + DBG2("write (slave) error: %s\n", strerror(errno)); + tty->error = 1; + fuse_reply_err(req, errno); + return; + } + + DBG2("write (slave): size:%d, thread:%lu\n", (int)size, pthread_self()); + + fuse_reply_write(req, res); +} + +static int baudrate(speed_t speed) +{ + return speed == B50 ? 50 : + speed == B75 ? 75 : + speed == B110 ? 110 : + speed == B300 ? 300 : + speed == B600 ? 600 : + speed == B1200 ? 1200 : + speed == B1800 ? 1800 : + speed == B2400 ? 2400 : + speed == B4800 ? 4800 : + speed == B9600 ? 9600 : + speed == B19200 ? 19200 : + speed == B38400 ? 38400 : + speed == B57600 ? 57600 : + speed == B115200 ? 115200 : speed == B230400 ? 230400 : 0; +} + +static const char pstr[] = { ' ', 'N', 'O', 'E', 'M', 'S' }; + +#define CASE(name) case name: return #name + +static const char *_ioctl_name(int cmd) +{ + static char buf[16]; + + switch (cmd) + { + default: + snprintf(buf, sizeof(buf), "0x%x", cmd); + return buf; + CASE(TCSETSF); + CASE(TCSETSW); + CASE(TCSETS); + CASE(TCGETS); + CASE(TIOCINQ); + CASE(TIOCOUTQ); + CASE(TCFLSH); + CASE(TCIFLUSH); + CASE(TCOFLUSH); + CASE(TCIOFLUSH); + CASE(TIOCMGET); + CASE(TIOCMBIS); + CASE(TIOCMBIC); + CASE(TIOCMSET); + CASE(TCSBRK); + CASE(TIOCGWINSZ); + CASE(TIOCSWINSZ); + /* Exclusive mode */ + CASE(TIOCEXCL); + CASE(TIOCGEXCL); + CASE(TIOCNXCL); + /* Line discipline */ + CASE(TIOCGETD); + CASE(TIOCSETD); + /*PPP support */ + CASE(PPPIOCGCHAN); + } +} + +#define CHECK_BUF_IN(sz) \ + if (in_bufsz < sz) do { in_bufsz = sz; goto retry_in; } while(0) +#define CHECK_BUF_OUT(sz) \ + if (out_bufsz < sz) do { out_bufsz = sz; goto retry_out; } while(0) + +static void +ttynvt_ioctl(fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *info, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh; + struct winsize ws; + unsigned int tiocm = 0; + int mcr; + static struct termios tio = { + .c_cflag = B115200 | CS8 | PARODD | CREAD | CLOCAL, + }; + int val; + int res; + + DBG("%s: cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__, + _ioctl_name(cmd), arg, in_buf, + (unsigned int)in_bufsz, (unsigned int)out_bufsz); + + switch (cmd) + { + case TCSETSF: + case TCSETSW: + case TCSETS: + CHECK_BUF_IN(sizeof(struct termios)); + { + speed_t speed; + int csize, par, sb; + + memcpy(&tio, in_buf, in_bufsz); + + speed = tio.c_cflag & (CBAUD | CBAUDEX); + val = htonl(baudrate(speed)); + telnet_rfc2217_cfg(tty->tn, TNS_SET_BAUDRATE, &val, 4); + + if ((tio.c_cflag & CSIZE) == CS5) + csize = 5; + else if ((tio.c_cflag & CSIZE) == CS6) + csize = 6; + else if ((tio.c_cflag & CSIZE) == CS7) + csize = 7; + else + csize = 8; + telnet_rfc2217_cfg(tty->tn, TNS_SET_DATASIZE, &csize, 1); + + if (tio.c_cflag & PARENB && tio.c_iflag & IGNPAR) + par = 1; +#ifdef CMSPAR + else if (tio.c_cflag & PARENB && tio.c_iflag & CMSPAR) + { + if (tio.c_cflag & PARODD) + par = 4; + else + par = 5; + } +#endif + else if (tio.c_cflag & PARENB) + par = 3; + else + par = 1; + telnet_rfc2217_cfg(tty->tn, TNS_SET_PARITY, &par, 1); + + if (tio.c_cflag & CSTOPB) + sb = 2; + else + sb = 1; + telnet_rfc2217_cfg(tty->tn, TNS_SET_STOPSIZE, &sb, 1); + + if (tio.c_cflag & CRTSCTS) + telnet_rfc2217_ctl(tty->tn, TNS_CTL_CTSRTS); + + if (!(tty->cmcr & TIOCM_DTR) && speed != B0) + { + tty->cmcr |= TIOCM_DTR; + telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON); + } + + DBG("termios: %u%c%u%u %cCTSRTS %cCREAD %cHUPCL %cCLOCAL\n", + baudrate(speed), pstr[par], csize, sb, + tio.c_cflag & CRTSCTS ? '+' : '-', + tio.c_cflag & CREAD ? '+' : '-', + tio.c_cflag & HUPCL ? '+' : '-', + tio.c_cflag & CLOCAL ? '+' : '-'); + + ioctl(tty->fds[FD_SLAVE].fd, cmd, &tio); + + fuse_reply_ioctl(req, 0, 0, 0); + } + break; + case TCGETS: + CHECK_BUF_OUT(sizeof(struct termios)); + fuse_reply_ioctl(req, 0, &tio, sizeof(struct termios)); + break; + case TIOCINQ: /* Get the number of bytes in the input + * buffer */ + CHECK_BUF_OUT(sizeof(int)); + ioctl(tty->fds[FD_SLAVE].fd, TIOCINQ, &val); + fuse_reply_ioctl(req, 0, &val, sizeof(int)); + break; + case TIOCOUTQ: /* Get the number of bytes in the output + * buffer */ + CHECK_BUF_OUT(sizeof(int)); + ioctl(tty->fds[FD_SLAVE].fd, TIOCOUTQ, &val); + fuse_reply_ioctl(req, 0, &val, sizeof(int)); + break; + case TCFLSH: + if (arg == TCIFLUSH) + { + /*todo */ + } + else if ((size_t) arg == TCOFLUSH) + { + /*todo */ + } + else if ((size_t) arg == TCIOFLUSH) + { + /*todo */ + } + fuse_reply_ioctl(req, 0, 0, 0); + break; + case TCIFLUSH: + val = 1; + telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1); + fuse_reply_ioctl(req, 0, 0, 0); + break; + case TCOFLUSH: + val = 2; + telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1); + fuse_reply_ioctl(req, 0, 0, 0); + break; + case TCIOFLUSH: + val = 3; + telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1); + fuse_reply_ioctl(req, 0, 0, 0); + break; + case TIOCMGET: + CHECK_BUF_OUT(sizeof(int)); + pthread_mutex_lock(&tty->tty_lock); + mcr = tty->cmcr | tty->smcr; + pthread_mutex_unlock(&tty->tty_lock); + fuse_reply_ioctl(req, 0, &mcr, sizeof(int)); + break; + case TIOCMBIS: + CHECK_BUF_IN(sizeof(int)); + tiocm = *(unsigned int *)in_buf; + fuse_reply_ioctl(req, 0, 0, 0); + DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); + if (tiocm & TIOCM_RTS) + { + tty->cmcr |= TIOCM_RTS; + telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_ON); + } + if (tiocm & TIOCM_DTR) + { + tty->cmcr |= TIOCM_DTR; + telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON); + } + goto show_mctl; + case TIOCMBIC: + CHECK_BUF_IN(sizeof(int)); + tiocm = *(unsigned int *)in_buf; + fuse_reply_ioctl(req, 0, 0, 0); + DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); + if (tiocm & TIOCM_RTS) + { + tty->cmcr &= ~TIOCM_RTS; + telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_OFF); + } + if (tiocm & TIOCM_DTR) + { + tty->cmcr &= ~TIOCM_DTR; + telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_OFF); + } + goto show_mctl; + case TIOCMSET: + CHECK_BUF_IN(sizeof(int)); + tiocm = *(unsigned int *)in_buf; + fuse_reply_ioctl(req, 0, 0, 0); + DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); + tty->cmcr &= ~(TIOCM_RTS | TIOCM_DTR); + tty->cmcr |= tiocm & (TIOCM_RTS | TIOCM_DTR); + telnet_rfc2217_ctl(tty->tn, (tiocm & TIOCM_DTR) ? + TNS_CTL_DTR_ON : TNS_CTL_DTR_OFF); + telnet_rfc2217_ctl(tty->tn, (tiocm & TIOCM_RTS) ? + TNS_CTL_RTS_ON : TNS_CTL_RTS_OFF); + goto show_mctl; + show_mctl: + DBG("MCR: DTR=%c RTS=%c\n", + tty->cmcr & TIOCM_DTR ? '1' : '0', + tty->cmcr & TIOCM_RTS ? '1' : '0'); + break; + case TCSBRK: + fuse_reply_ioctl(req, 0, 0, 0); + break; + case TIOCGWINSZ: + CHECK_BUF_OUT(sizeof(struct winsize)); + res = ioctl(tty->fds[FD_SLAVE].fd, TIOCGWINSZ, &ws); + if (res < 0) + { + fuse_reply_err(req, errno); + return; + } + fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize)); + break; + case TIOCSWINSZ: + CHECK_BUF_IN(sizeof(struct winsize)); + ws = *(struct winsize *)in_buf; + res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSWINSZ, &ws); + if (res < 0) + { + fuse_reply_err(req, errno); + return; + } + fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize)); + break; + case TIOCEXCL: + ioctl(tty->fds[FD_SLAVE].fd, TIOCEXCL, NULL); + fuse_reply_ioctl(req, 0, 0, 0); + break; + case TIOCGEXCL: + CHECK_BUF_OUT(sizeof(int)); + ioctl(tty->fds[FD_SLAVE].fd, TIOCGEXCL, &val); + fuse_reply_ioctl(req, 0, &val, sizeof(int)); + break; + case TIOCNXCL: + ioctl(tty->fds[FD_SLAVE].fd, TIOCNXCL, NULL); + fuse_reply_ioctl(req, 0, 0, 0); + break; + case TIOCSETD: + CHECK_BUF_IN(sizeof(int)); + val = *(int *)in_buf; + + res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSETD, &val); + if (res < 0) + fuse_reply_err(req, errno); + else + fuse_reply_ioctl(req, 0, 0, 0); + break; + case TIOCGETD: + CHECK_BUF_OUT(sizeof(int)); + res = ioctl(tty->fds[FD_SLAVE].fd, TIOCGETD, &val); + if (res < 0) + fuse_reply_err(req, errno); + else + fuse_reply_ioctl(req, 0, &val, sizeof(int)); + break; + case PPPIOCGCHAN: + CHECK_BUF_OUT(sizeof(int)); + res = ioctl(tty->fds[FD_SLAVE].fd, PPPIOCGCHAN, &val); + if (res < 0) + fuse_reply_err(req, errno); + else + fuse_reply_ioctl(req, 0, &val, sizeof(int)); + break; + default: + fuse_reply_err(req, ENOSYS); + break; + + retry_in: + { + struct iovec iov = { arg, in_bufsz }; + fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); + } + break; + + retry_out: + { + struct iovec iov = { arg, out_bufsz }; + fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); + } + break; + } +} + +static void +ttynvt_poll(fuse_req_t req, struct fuse_file_info *info, + struct fuse_pollhandle *ph) +{ + ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh; + int revents = 0; + + _update_notify(tty, ph); + + if (tty->pollin) + { + tty->pollin = 0; + revents |= POLLIN; + } + if (tty->error) + { + if (tty->epipe == 0) + revents = POLLHUP | POLLIN; + else + revents = 0; + } + + fuse_reply_poll(req, revents); +} + +static const struct cuse_lowlevel_ops ttynvt_op = { + .init = ttynvt_init, + .open = ttynvt_open,.read = ttynvt_read,.write = ttynvt_write,.ioctl = + ttynvt_ioctl,.poll = ttynvt_poll,.release = ttynvt_release, +}; + +#define TTYNET_OPT(t, p, v) { t, offsetof(struct ttynvt_param, p), v } + +static const struct fuse_opt ttynvt_opts[] = { + TTYNET_OPT("-D %d", debug, 1), + TTYNET_OPT("-M %u", major, 1), + TTYNET_OPT("--maj=%u", major, 1), + TTYNET_OPT("-m %u", minor, 1), + TTYNET_OPT("--min=%u", minor, 1), + TTYNET_OPT("-n %s", dev_name, 0), + TTYNET_OPT("--name=%s", dev_name, 0), + TTYNET_OPT("-S %s", server, 0), + TTYNET_OPT("--server=%s", server, 0), + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + FUSE_OPT_END +}; + +static void print_help(void) +{ + fprintf(stderr, "ttynvt Usage:\n" + "\t-D level\tEnable debug\n" + "\t-M major, --maj=major\n" + "\t-m minor, --min=minor\n" + "\t-n name, --name=name (default: ttyNVT0)\n" + "\t\tDevice will be created in /dev/$name.\n" + "\t-S server, --server=host:port\n"); +} + +static int +process_arg(void *data, const char *arg, int key, struct fuse_args *outargs) +{ + switch (key) + { + case 0: + print_help(); + return fuse_opt_add_arg(outargs, "-ho"); + default: + if (strcmp(arg, "-d") == 0 || strcmp(arg, "-f") == 0) + ttynvt_param.foreground = 1; + return 1; + } +} + +int main(int argc, char *argv[]) +{ + int rc; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + char dev_name[128]; + const char *dev_info_argv[] = { dev_name }; + pid_t cpid; + + openlog("ttynvt", LOG_PID, LOG_DAEMON); + + if (fuse_opt_parse(&args, &ttynvt_param, ttynvt_opts, process_arg)) + { + fprintf(stderr, "Failed to parse arguments\n"); + return 1; + } + + if (!ttynvt_param.dev_name) + ttynvt_param.dev_name = "ttyNVT0"; + + if (!ttynvt_param.server) + { + fprintf(stderr, "Server must be specified\n"); + return 1; + } + + rc = sscanf(ttynvt_param.server, "%63[^:]:%u", ttynvt_param.host, + &ttynvt_param.port); + if (rc <= 1) + { + fprintf(stderr, "Bad server specification: '%s'\n", + ttynvt_param.server); + return 1; + } + if (ttynvt_param.port == 0) + { + fprintf(stderr, "Bad server port: %u\n", ttynvt_param.port); + return 1; + } + + snprintf(dev_name, sizeof(dev_name), "DEVNAME=%s", ttynvt_param.dev_name); + + struct cuse_info ci = { + .dev_major = ttynvt_param.major, + .dev_minor = ttynvt_param.minor, + .dev_info_argc = sizeof(dev_info_argv) / sizeof(char *), + .dev_info_argv = dev_info_argv, + .flags = CUSE_UNRESTRICTED_IOCTL, + }; + + if (ttynvt_param.foreground) + { + return cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL); + } + + cpid = fork(); + switch (cpid) + { + case -1: + _log(LOG_ERR, "fork(): %m"); + return -1; + + case 0: /* Child */ + DBG("Child proceeding...\n"); + rc = cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL); + if (rc != 0) + _log(LOG_ERR, "ttynvt failed rc=%d", rc); + else + DBG("Child exited\n"); + return rc; + + default: /* Parent */ + break; + } + + DBG("Parent proceeding (child=%d)...\n", cpid); + + for (rc = 0; rc < 10; rc++) + { + if (_check_dev() == 0) + break; + usleep(500000); + } + + DBG("Parent exiting\n"); + + return 0; +}