38 Commits

Author SHA1 Message Date
Frank Rolsted Jensen 7c80995256 fix missing deref of tty data when destroyed (#2) 2024-03-21 14:54:34 +01:00
Frank Rolsted Jensen 299cfcabb5 fix mssing deref of tty data when destroyed 2024-03-21 13:03:43 +01:00
Frank Rolsted Jensen b47cb98a69 Comments updated 2023-10-13 09:24:44 +02:00
Frank Rolsted Jensen cb6509861f Client index included in debug output 2023-10-13 09:23:18 +02:00
Frank Rolsted Jensen 2f0cea6f73 fix mutex unlock if open fails 2023-10-13 09:21:25 +02:00
Frank Rolsted Jensen 015da8a55e Allow multiple clients to open the ttynvt device
This patch allows multiple clients to open the ttynvt device at the same time.
This is useful if an application wants to get/set termios (ioctls) while the
device is already opened by an other application.
2023-10-04 10:53:39 +02:00
Kim Woelders 4fce33a5d7 ttynvt version 0.16 2023-03-13 09:40:44 +01:00
Kim Woelders b2115e5fea nvtsrv: Optionally echo all input from client 2023-02-17 06:51:27 +01:00
Kim Woelders b25eb00bc5 nvtsrv: Use nvt_log for printing output 2023-02-17 06:50:47 +01:00
Kim Woelders b6faf87c3a nvtsrv: Input processing changes, send all from console 2023-02-17 06:45:20 +01:00
Kim Woelders 674ed45ebf nvttest: Use nvt_log for printing output 2023-02-17 06:45:20 +01:00
Kim Woelders f02de1ae99 nvttest: Write more data 2023-02-17 06:45:20 +01:00
Kim Woelders b65d44abe5 ttynvt: Ignore ioctls entirely in raw mode
Without this minicom Tx does not reach the server.
There may be better solutions.
2023-02-16 18:39:29 +01:00
Kim Woelders d1f46f7dbe ttynvt: Enable disabling telnet processing
Similar to suggestion by Jo Luxer:
"Make applications work, which expect a tty but the remote end is just a
network port."
https://gitlab.com/lars-thrane-as/ttynvt/-/merge_requests/9
2023-02-16 17:16:21 +01:00
Kim Woelders 20c393c053 ttynvt: Add some parentheses for clarity
Not strictly required.
2023-02-16 11:39:43 +01:00
Kim Woelders 02d90b7634 nvttest: Add test tool 2023-02-15 19:33:41 +01:00
Kim Woelders 4e14ef1956 nvtsrv: Add test server 2023-02-15 19:33:41 +01:00
Kim Woelders d0bf492b10 ttynvt: Some cosmetics in the telnet department 2023-02-15 19:33:41 +01:00
Kim Woelders 84c54c5504 socket: Add server socket fucntions
About to be used in test code.
2023-02-15 19:33:41 +01:00
Kim Woelders a978e86c82 socket: Move host:port parsing to socket function 2023-02-15 19:33:41 +01:00
Kim Woelders a49f95a300 build: Move stuff to be shared to lib/ (nvt_log, socket) 2023-02-15 19:33:41 +01:00
Kim Woelders ac58d1a470 ttynvt: Move socket function to seperate file 2023-02-15 19:33:41 +01:00
Kim Woelders cc038383fe autofoo: Move warning flags setup to configure.ac 2023-02-15 19:33:41 +01:00
Kim Woelders ad077571f8 ttynvt: Optionally add delay during close
Optionally "Give network and remote end some time to close the
connection".

More or less like suggested by Jo Luxer:
https://gitlab.com/lars-thrane-as/ttynvt/-/merge_requests/4
2023-02-15 19:33:41 +01:00
Kim Woelders 8bf4b7705d ttynvt: Add overall access lock
Different processes or threads accessing the device simultaneously or
rapidly after each other could cause problems.

Based on suggestion by Jo Luxer:
https://gitlab.com/lars-thrane-as/ttynvt/-/merge_requests/4
2023-02-15 19:33:41 +01:00
Kim Woelders cac5be0ddc ttynvt: Some refactoring
Move ttynvt_release() to avoid forward declaration.
Introduce _ttynvt_ctx_create/destroy().
2023-02-15 19:33:41 +01:00
Kim Woelders 3ab1dd5cc7 ttynvt: Avoid potential "net read error" in _read_net() when closing
ttynvt_release() may be called when the poll thread is blocked in a
read(FD_NET) (maybe also read(FD_MASTER) - not sure).
So ttynvt_release() sets rel_pending before closing the fd, but too late
as the read() will return an error when the fd is closed, and before the
thread is cancelled.

Now we cancel/join the thread before closing the file descriptor(s).
2023-02-15 19:33:41 +01:00
Kim Woelders 94bd59e1e0 ttynvt: Partial read fix
Somehow forgotten initially.
2023-02-15 19:33:41 +01:00
Kim Woelders 5fb11097e1 logging: Log to stderr instead of stdout
Logging to stdout often becomes garbled due to the multiple treads
involved.
2023-02-12 10:22:26 +01:00
Kim Woelders 895cfffc6b logging: Add quiet option
Mostly for debug?
Sometimes logging changes timing.
2023-02-12 10:22:26 +01:00
Kim Woelders 6e5c44eb29 logging: Some refactoring 2023-02-10 13:19:02 +01:00
Kim Woelders 05b9b13b04 ttynvt: Tweak some log/debug messages 2023-02-10 13:18:49 +01:00
Kim Woelders 9e99ec69b4 configure.ac: Cleanups
- Drop pkg_version/pkg_revision stuff
- Drop obsolete AC_HEADER_STDC
2022-08-19 07:18:26 +02:00
Kim Woelders 72f0058e27 Add some compiler warnings 2022-08-19 06:45:48 +02:00
Kim Woelders 50a6b600bf ttynvt version 0.15 2022-08-10 07:25:52 +02:00
Kim Woelders 465f31ba87 Avoid some cast-qual warnings 2022-01-16 20:10:48 +01:00
Frank Rolsted Jensen 90c5387613 Clear pollin flag when input buffer is flushed
The pollin flag must be cleared when the input buffer (pty slave) is
flushed. This ensures that the POLLIN event is not signaled to the application,
when there is not data to read.
2022-01-16 19:43:37 +01:00
Frank Rolsted Jensen 6bf10a6901 Support for software flow control 2021-10-29 17:57:34 +02:00
21 changed files with 1686 additions and 195 deletions
+3 -1
View File
@@ -3,10 +3,12 @@
/autom4te.cache/
/compile
/config.h.in
/config.h.in~
/config.guess
/config.sub
/configure
/depcomp
/install-sh
/ltmain.sh
/missing
Makefile.in
+5 -1
View File
@@ -4,4 +4,8 @@ MAINTAINERCLEANFILES = \
depcomp install-sh missing \
Makefile.in */Makefile.in
SUBDIRS = src
SUBDIRS = lib src
if BUILD_TESTTOOLS
SUBDIRS += nvtsrv
SUBDIRS += nvttest
endif
+18 -7
View File
@@ -1,9 +1,6 @@
m4_define([pkg_version], [0.14])
#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])
AC_INIT([ttynvt],[0.16],[frj@thrane.eu])
AM_INIT_AUTOMAKE([foreign dist-xz])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AC_USE_SYSTEM_EXTENSIONS
@@ -12,12 +9,12 @@ 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
LT_INIT()
AC_C_BIGENDIAN
AC_C_CONST
AC_C_INLINE
@@ -29,10 +26,24 @@ 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"
CFLAGS_WARN="$CFLAGS_WARN -Wcast-align -Wcast-qual"
AC_SUBST(CFLAGS_WARN)
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([
Makefile
lib/Makefile
src/Makefile
nvtsrv/Makefile
nvttest/Makefile
])
AC_OUTPUT
+6
View File
@@ -0,0 +1,6 @@
noinst_LTLIBRARIES = libnvt.la
libnvt_la_CFLAGS = $(CFLAGS_WARN)
libnvt_la_SOURCES = nvt_log.c nvt_log.h
libnvt_la_SOURCES += nvt_socket.c nvt_socket.h
+33 -3
View File
@@ -14,7 +14,34 @@
#define gettid() (pid_t)syscall(__NR_gettid)
char log_level = 0;
char log_stdout = 0;
static char log_threshold = LOG_DEBUG;
static FILE *log_dest = NULL;
void nvt_log_dest(int dest)
{
switch (dest)
{
default:
case NVT_LOG_SYSLOG:
log_dest = NULL;
break;
case NVT_LOG_STDOUT:
log_dest = stdout;
break;
case NVT_LOG_STDERR:
log_dest = stderr;
break;
}
}
void nvt_log_level(int quiet, int level)
{
if (quiet)
log_threshold = LOG_NOTICE;
log_level = level;
}
static const char *_hms_txt(char *buf, unsigned int len)
{
@@ -40,13 +67,16 @@ void nvt_log(int prio, const char *fmt, ...)
const char *eol;
int len;
if (prio > log_threshold)
return;
va_start(arg, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, arg);
if (log_stdout)
if (log_dest)
{
eol = (len > 0 && buf[len - 1] != '\n') ? "\n" : "";
printf("%s: [%d] %d: %s%s",
fprintf(log_dest, "%s: [%d] %d: %s%s",
_hms_txt(buft, sizeof(buft)), gettid(), prio, buf, eol);
}
else
+6 -1
View File
@@ -9,12 +9,17 @@
#define _PRF_N_(no) __attribute__((__format__(__printf__, (no), (no) + 1)))
#define _PRF2_ _PRF_N_(2)
void nvt_log_dest(int dest);
void nvt_log_level(int quiet, int level);
_PRF2_ void nvt_log(int prio, const char *fmt, ...);
void nvt_log_buf(int prio, const char *txt,
const void *ptr, unsigned int len);
#define NVT_LOG_SYSLOG 0
#define NVT_LOG_STDOUT 1
#define NVT_LOG_STDERR 2
extern char log_level;
extern char log_stdout;
#define DBG(...) \
if (log_level > 0) nvt_log(LOG_DEBUG, __VA_ARGS__)
+149
View File
@@ -0,0 +1,149 @@
/*
* Socket interface API
*/
#include <netdb.h> /* getaddrinfo() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include "nvt_log.h"
#include "nvt_socket.h"
typedef struct {
unsigned short type; // socket type
unsigned short slen; // sockaddr length
union {
struct sockaddr gen;
struct sockaddr_in ipv4;
};
} sa_t;
static int _sa_host_lookup(sa_t * sa, const char *host)
{
struct hostent *server;
server = gethostbyname(host);
if (server == NULL)
{
nvt_log(LOG_ERR, "Cannot resolve host name: %m\n");
return -1;
}
sa->slen = sizeof(sa->ipv4);
sa->ipv4.sin_family = AF_INET;
memcpy(&sa->ipv4.sin_addr.s_addr, server->h_addr, server->h_length);
return 0;
}
static int _sa_addr_parse(sa_t * sa, const char *addr)
{
char host[64], port[64];
memset(sa, 0, sizeof(sa_t));
sscanf(addr, "%63[^:]:%63s", host, port);
if (_sa_host_lookup(sa, host) != 0)
return -1;
sa->type = SOCK_STREAM;
sa->ipv4.sin_port = htons(atoi(port));
return 0;
}
int socket_create_client(const char *addr)
{
sa_t sa;
int fd, err;
if (_sa_addr_parse(&sa, addr) != 0)
return -1;
fd = socket(sa.gen.sa_family, sa.type, 0);
if (fd < 0)
{
nvt_log(LOG_ERR, "%s: Could not find address: '%s'", __func__, addr);
return fd;
}
err = connect(fd, &sa.gen, sa.slen);
if (err < 0)
{
nvt_log(LOG_ERR, "Failed to connect to: %s: %m\n", addr);
close(fd);
return -1;
}
return fd;
}
int socket_create_server(const char *addr)
{
sa_t sa;
int err, fd, opt;
err = _sa_addr_parse(&sa, addr);
if (err != 0)
{
nvt_log(LOG_ERR, "%s: Could not find address: '%s'", __func__, addr);
return -1;
}
fd = socket(sa.gen.sa_family, sa.type, 0);
if (fd < 0)
{
nvt_log(LOG_ERR, "%s: *** socket(%s): %m", __func__, addr);
return -1;
}
if (sa.gen.sa_family == AF_INET)
{
/* Avoid TIME_WAIT state on socket close */
opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
err = bind(fd, &sa.gen, sa.slen);
if (err != 0)
{
nvt_log(LOG_ERR, "%s: *** bind(%s): %m", __func__, addr);
close(fd);
return -1;
}
if (sa.type == SOCK_STREAM)
{
err = listen(fd, 5);
if (err != 0)
{
nvt_log(LOG_ERR, "%s: *** listen(%s): %m", __func__, addr);
close(fd);
return -1;
}
}
return fd;
}
int socket_create_server_client(int sfd)
{
int cfd;
socklen_t slen;
slen = 0;
cfd = accept(sfd, NULL, &slen);
if (cfd < 0)
{
nvt_log(LOG_ERR, "%s: *** accept(): %m", __func__);
return -1;
}
return cfd;
}
+12
View File
@@ -0,0 +1,12 @@
/*
* Socket functions
*/
#ifndef NVT_SOCKET_H
#define NVT_SOCKET_H
int socket_create_client(const char *addr);
int socket_create_server(const char *addr);
int socket_create_server_client(int sfd);
#endif /* NVT_SOCKET_H */
+10
View File
@@ -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
+240
View File
@@ -0,0 +1,240 @@
/*
* RFC2217 test server
*/
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lib/nvt_log.h"
#include "lib/nvt_socket.h"
#include "telnet.h"
#define HELP \
"Usage: nvtsrv -deq\n"
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;
nvt_log(LOG_INFO, "%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;
DBG2_BUF("Tx", buf, len);
if (fd_cli < 0)
{
nvt_log(LOG_NOTICE, "Write fd is closed - drop Tx\n");
return;
}
nw = write(fd_cli, buf, len);
if (nw != len)
{
nvt_log(LOG_ERR, "Write failed %d != %d\n", nw, len);
_tn_cli_close();
}
}
static void _tn_ss(void *cctx, int signal, int value)
{
nvt_log(LOG_INFO, "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, int len)
{
int val;
if (line[0] == '*' && len > 2)
{
/* Special features */
if (line[1] == 'm')
{
sscanf(line + 2, "%x", &val);
telnet_rfc2217_change_modemstate(tcc, val);
}
}
else
{
/* Send all input */
_tn_tx(NULL, line, len);
}
}
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)
{
int opt;
char buf[128];
int rc, nr, i, len;
int debug, quiet;
bool do_echo;
debug = quiet = 0;
do_echo = false;
while ((opt = getopt(argc, argv, "deq")) != -1)
{
switch (opt)
{
default: /* '?' */
fprintf(stderr, HELP);
exit(EXIT_FAILURE);
case 'd':
debug += 1;
break;
case 'e': /* Echo received data */
do_echo = true;
break;
case 'q':
quiet = 1;
break;
}
}
argc -= optind;
argv += optind;
if (argc <= 0)
_nvtsrv_usage();
nvt_log_level(quiet, debug);
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)
{
nvt_log(LOG_ERR, "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;
DBG2_BUF("Con Rx", buf, nr);
_nvtsrv_cmd(dd.tnct, buf, nr);
}
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;
DBG2_BUF("Rx", buf, nr);
len = nr;
rc = telnet_rx(dd.tnct, buf, &len);
if (do_echo)
{
/* Echo received */
_tn_tx(NULL, buf, len);
}
}
}
i = 1;
if (pfds[i].revents)
{
if (pfds[2].fd >= 0)
{
nvt_log(LOG_NOTICE, "Client already connected on fd=%d\n",
pfds[2].fd);
break;
}
pfds[2].fd = socket_create_server_client(pfds[i].fd);
nvt_log(LOG_INFO, "%d: fd=%d: Connected\n", 2, pfds[2].fd);
pfds[2].events = POLLIN;
nfds = 3;
_nvtsrv_init();
}
}
return 0;
}
+63
View File
@@ -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 */
+275
View File
@@ -0,0 +1,275 @@
/*
* Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0
*
* Telnet protocol handling - server
*/
#include <stdlib.h>
#include <string.h>
#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;
}
+123
View File
@@ -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 */
+256
View File
@@ -0,0 +1,256 @@
/*
* Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0
*
* Telnet RFC-2217 handling - server
*/
#include <arpa/inet.h>
#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;
}
+9
View File
@@ -0,0 +1,9 @@
bin_PROGRAMS = nvttest
nvttest_SOURCES = \
nvttest.c
nvttest_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) $(CFLAGS_WARN)
nvttest_LDADD = $(top_builddir)/lib/libnvt.la
+175
View File
@@ -0,0 +1,175 @@
/*
* ttynvt stress tool
*/
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/termbits.h>
#include "lib/nvt_log.h"
#define HELP \
"Usage: nvttest -dqr [-n loops] [-t threads]\n"
static const char *dev = "/dev/ttyNVT0";
typedef struct {
int nthread;
int ithread;
int nloop;
bool opt_rand_delay;
} topts_t;
static void *_worker(void *arg)
{
topts_t *topts = arg;
int iloop;
int fd, err, nw;
struct termios ios;
for (iloop = 0; iloop < topts->nloop; iloop++)
{
nvt_log(LOG_INFO, "Thread %d/%d run %d/%d\n",
topts->ithread, topts->nthread, iloop + 1, topts->nloop);
{
fd = open(dev, O_RDWR);
if (fd < 0)
{
nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev);
break;
}
memset(&ios, 0, sizeof(ios));
err = ioctl(fd, TCGETS, &ios);
if (err != 0)
{
nvt_log(LOG_ERR, "ioctl(TCGETS) failed: %m\n");
break;
}
err = ioctl(fd, TCSETS, &ios);
if (err != 0)
{
nvt_log(LOG_ERR, "ioctl(TCSETS) failed: %m\n");
break;
}
close(fd);
}
{
int i, len;
char buf[1024];
for (i = 0; i < (int)sizeof(buf); i++)
buf[i] = '0' + i % 10;
fd = open(dev, O_RDWR);
if (fd < 0)
{
nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev);
break;
}
for (i = 0; i < 10; i++)
{
len = rand() & 0xff;
nw = write(fd, buf, len);
if (nw < 0)
{
nvt_log(LOG_ERR, "write failed: %m\n");
break;
}
if (nw < len)
{
nvt_log(LOG_ERR, "write short: %u/%u\n", nw, len);
break;
}
}
close(fd);
}
if (topts->opt_rand_delay)
usleep(rand() & 0xfff);
}
return NULL;
}
int main(int argc, char **argv)
{
int err;
int opt;
int ithr, nthr;
topts_t topts = { }, *popts;
pthread_t *ptids;
int debug, quiet;
debug = quiet = 0;
nthr = 1;
topts.nloop = 1;
while ((opt = getopt(argc, argv, "dn:qrt:")) != -1)
{
switch (opt)
{
default: /* '?' */
fprintf(stderr, HELP);
exit(EXIT_FAILURE);
case 'd':
debug += 1;
break;
case 'n':
topts.nloop = atoi(optarg);
break;
case 'q':
quiet = 1;
break;
case 'r':
topts.opt_rand_delay = true;
break;
case 't':
nthr = atoi(optarg);
break;
}
}
nvt_log_level(quiet, debug);
nvt_log_dest(NVT_LOG_STDOUT);
topts.nthread = nthr;
ptids = malloc(nthr * sizeof(pthread_t));
for (ithr = 0; ithr < nthr; ithr++)
{
nvt_log(LOG_INFO, "Create worker thread %d/%d\n", ithr + 1, nthr);
popts = malloc(sizeof(topts_t));
*popts = topts;
popts->ithread = ithr + 1;
err = pthread_create(&ptids[ithr], NULL, _worker, popts);
if (err)
{
nvt_log(LOG_ERR, "Failed to create thread %d/%d\n", ithr, nthr);
break;
}
}
for (ithr = 0; ithr < nthr; ithr++)
{
if (ptids[ithr] == 0)
break;
pthread_join(ptids[ithr], NULL);
nvt_log(LOG_INFO, "Joined worker thread %d/%d\n", ithr + 1, nthr);
}
return 0;
}
+4 -6
View File
@@ -1,12 +1,10 @@
CFLAGS_WARN = -Wall -Wextra -Werror -Wno-unused-parameter
bin_PROGRAMS = ttynvt
ttynvt_SOURCES = \
ttynvt.c nvt_log.c nvt_log.h \
telnet_basic.c telnet_rfc2217.c telnet.h telnet_param.h
ttynvt.c \
telnet_basic.c telnet_client_rfc2217.c telnet.h telnet_param.h
ttynvt_CPPFLAGS = -I$(top_builddir) $(FUSE_CFLAGS) $(CFLAGS_WARN)
ttynvt_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) $(FUSE_CFLAGS) $(CFLAGS_WARN)
ttynvt_LDADD = $(FUSE_LIBS)
ttynvt_LDADD = $(top_builddir)/lib/libnvt.la $(FUSE_LIBS)
+12 -7
View File
@@ -2,10 +2,10 @@
* Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0
*
* Telnet interface
* Telnet interface - client
*/
#ifndef TELNET_H
#define TELNET_H
#ifndef TELNET_CLIENT_H
#define TELNET_CLIENT_H
typedef int (srv_tx_f) (void *cctx, const void *buf, int len);
typedef int (srv_rx_f) (void *cctx, int timeout);
@@ -21,7 +21,7 @@ typedef struct {
unsigned int bytes_rx; /* Rx byte count */
unsigned int off_rx; /* Rx buffer offset */
unsigned char mode_telnet; /* Telnet mode */
signed char mode_telnet; /* Telnet mode */
unsigned char mode_rfc2217; /* RFC-2217 mode */
unsigned char telnet_ack[256]; /* telnet option ack counter */
} tn_ctx_t;
@@ -29,7 +29,7 @@ typedef struct {
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_open(tn_ctx_t * tcc, int mode);
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);
@@ -44,6 +44,10 @@ 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, unsigned int val);
/* Telnet mode */
#define TN_MODE_RAW 0 /* Telnet processing off */
#define TN_MODE_TELNET 1 /* Telnet mode */
/*RFC2217 modem state*/
#define TNS_STATE_DCD 0x80
#define TNS_STATE_RI 0x40
@@ -60,11 +64,12 @@ extern void telnet_rfc2217_ctl(tn_ctx_t * tcc, unsigned int val);
/*RFC2217 control commands*/
#define TNS_CTL_NOFLOW 1
#define TNS_CTL_XONOFF 2
#define TNS_CTL_XONOFF_OUT 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
#define TNS_CTL_XONOFF_IN 15
#endif /*TELNET_H */
#endif /* TELNET_CLIENT_H */
+14 -5
View File
@@ -2,9 +2,8 @@
* Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0
*
* Telnet server - Telnet protocol handling
* Telnet protocol handling - client
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -129,6 +128,10 @@ int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen)
int np, nd;
int b1, b2;
/* Skip telnet processing entirely in raw mode */
if (tcc->mode_telnet < 0)
return 0;
len = *plen;
tcc->bytes_rx += len;
@@ -144,7 +147,7 @@ int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen)
/* We have IAC (= 0xff) */
np = rem - (p - s); /* N. chars available incl. IAC */
if (np < 2)
return 1;
goto tn_more;
pu = (unsigned char *)p;
b1 = pu[1];
@@ -237,7 +240,7 @@ int telnet_tx(tn_ctx_t * tcc, const char *buf, int len)
for (s = buf, rem = len; rem > 0; s = p)
{
if (tcc->mode_telnet)
if (tcc->mode_telnet > 0)
p = memchr(s, TNC_FF_IAC, rem);
else
p = NULL;
@@ -260,12 +263,18 @@ int telnet_tx(tn_ctx_t * tcc, const char *buf, int len)
return 0;
}
int telnet_open(tn_ctx_t * tcc)
int telnet_open(tn_ctx_t * tcc, int mode)
{
time_t t = time(NULL);;
char buf[3];
int res;
if (mode == TN_MODE_RAW)
{
tcc->mode_telnet = -1; /* Raw mode - no telnet processing */
return 0;
}
buf[0] = TNC_FF_IAC;
buf[1] = TNC_FB_WILL;
buf[2] = TNO_CPCO;
@@ -2,11 +2,8 @@
* Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0
*
* Telnet server - RFC-2217 handling
* Telnet RFC-2217 handling - client
*/
#include <stddef.h>
#include "telnet.h"
#include "telnet_param.h"
+271 -159
View File
@@ -7,7 +7,6 @@
#include <cuse_lowlevel.h>
#include <errno.h>
#include <fuse_opt.h>
#include <netdb.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
@@ -21,13 +20,12 @@
#include <unistd.h>
#include <asm/ioctls.h>
#include <asm/termbits.h>
#include <linux/sockios.h>
#include <linux/ppp-ioctl.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "nvt_log.h"
#include "lib/nvt_log.h"
#include "lib/nvt_socket.h"
#include "telnet.h"
#define NET_BUF_SIZE 1024
@@ -42,12 +40,14 @@ static struct ttynvt_param {
unsigned int major;
unsigned int minor;
const char *dev_name;
char *server;
const char *server;
unsigned int quiet;
unsigned int debug;
int logstd;
int raw; /* No telnet processing */
const char *close_delay;
/* Derived */
char host[64];
unsigned int port;
unsigned int close_delay_us;
/* Picked up */
int foreground;
} ttynvt_param;
@@ -55,14 +55,17 @@ static struct ttynvt_param {
typedef struct {
pthread_mutex_t tty_lock;
pthread_mutex_t poll_lock;
pthread_mutex_t read_lock;
pthread_mutex_t write_lock;
pthread_mutex_t ioctl_lock;
pthread_t ptid_poll;
pthread_t ptid_read;
struct fuse_pollhandle *ph;
volatile int rel_pending;
volatile int slave_suspended; /*waiting for application to read data */
volatile int master_suspended; /*server is not ready to receive data */
volatile int slave_suspended; /* Waiting for application to read data */
volatile int master_suspended; /* Server is not ready to receive data */
struct pollfd fds[3];
char net_buf[NET_BUF_SIZE];
@@ -80,13 +83,20 @@ typedef struct {
int epipe;
tn_ctx_t *tn;
int seq;
struct ttynvt_cli_s *cli;
} ttynvt_t;
static void _fd_close(int fd)
{
if (fd >= 0)
close(fd);
}
static ttynvt_t *ptty = NULL;
typedef struct ttynvt_cli_s {
int id;
ttynvt_t *tty;
struct ttynvt_cli_s *next;
} ttynvt_cli_t;
static pthread_mutex_t access_lock = PTHREAD_MUTEX_INITIALIZER;
static volatile char _is_interrupted = 0;
@@ -117,6 +127,19 @@ static void ttynvt_interrupted(fuse_req_t req, void *data)
pthread_kill(thread, SIGUSR2);
}
static void _ttynvt_sleep_us(unsigned int tus)
{
struct timespec ts;
ts.tv_sec = tus / 1000000;
tus -= ts.tv_sec * 1000000;
ts.tv_nsec = tus * 1000;
while (nanosleep(&ts, &ts))
;
}
static int _check_dev(void)
{
int rc;
@@ -132,29 +155,10 @@ static int _check_dev(void)
return rc;
}
static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
static void _fd_close(int fd)
{
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
nvt_log(LOG_INFO, "connection closed\n");
tty->rel_pending = 1;
_fd_close(tty->fds[FD_NET].fd);
_fd_close(tty->fds[FD_MASTER].fd);
_fd_close(tty->fds[FD_SLAVE].fd);
pthread_cancel(tty->ptid_poll);
pthread_join(tty->ptid_poll, 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);
if (fd >= 0)
close(fd);
}
static void _notify(ttynvt_t * tty)
@@ -238,7 +242,7 @@ static void *_read_net(void *arg)
}
if (res == 0)
{
nvt_log(LOG_INFO, "Connection closed by remote host\n");
nvt_log(LOG_NOTICE, "Connection closed by remote host\n");
break;
}
tty->net_cnt += res;
@@ -297,44 +301,6 @@ static void *_read_net(void *arg)
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)
{
nvt_log(LOG_ERR, "Socket open failed: %m\n");
return sockfd;
}
server = gethostbyname(host);
if (server == NULL)
{
nvt_log(LOG_ERR, "Cannot resolve host name: %m\n");
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)
{
nvt_log(LOG_ERR, "Failed to connect to: %s:%u: %m\n", host, port);
close(sockfd);
return -1;
}
return sockfd;
}
static int _srv_write(void *cctx, const void *buf, int len)
{
ttynvt_t *tty = cctx;
@@ -418,51 +384,94 @@ 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)
static ttynvt_t *_ttynvt_ctx_create(void)
{
ttynvt_t *tty;
char *slave_name;
struct termios ios;
int sockfd;
int fd;
int res;
int n;
int i;
DBG("%s\n", __func__);
tty = calloc(1, sizeof(*tty));
tty = calloc(1, sizeof(ttynvt_t));
if (!tty)
{
fuse_reply_err(req, ENOMEM);
return;
}
for (n = 0; n < 3; n++)
tty->fds[n].fd = -1;
tty->pollout = 1;
return NULL;
tty->tn = telnet_ctx_init(tty, _srv_write, _srv_read, _modem_status_cb);
if (!tty->tn)
{
free(tty);
return NULL;
}
for (i = 0; i < 3; i++)
tty->fds[i].fd = -1;
tty->pollout = 1;
return tty;
}
static void _ttynvt_ctx_destroy(ttynvt_t * tty)
{
if (tty)
free(tty->tn);
free(tty);
}
static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_t *tty = ptty;
ttynvt_cli_t *tty_cli;
char *slave_name;
struct termios ios;
int fd;
int res;
int n;
tty_cli = calloc(1, sizeof(ttynvt_cli_t));
if (!tty_cli)
{
fuse_reply_err(req, ENOMEM);
return;
}
sockfd = _srv_connect(ttynvt_param.host, ttynvt_param.port);
if (sockfd < 0)
pthread_mutex_lock(&access_lock);
if (tty == NULL)
tty = _ttynvt_ctx_create();
if (!tty)
{
free(tty->tn);
free(tty);
fuse_reply_err(req, EHOSTUNREACH);
fuse_reply_err(req, ENOMEM);
free(tty_cli);
pthread_mutex_unlock(&access_lock);
return;
}
tty->fds[FD_NET].fd = sockfd;
tty_cli->id = tty->seq++;
tty_cli->tty = tty;
tty_cli->next = tty->cli;
tty->cli = tty_cli;
nvt_log(LOG_INFO, "Client open (%d)\n", tty_cli->id);
if (ptty)
goto out;
ptty = tty;
nvt_log(LOG_INFO, "Connecting to server: %s\n", ttynvt_param.server);
fd = socket_create_client(ttynvt_param.server);
if (fd < 0)
{
errno = EHOSTUNREACH;
goto open_err;
}
tty->fds[FD_NET].fd = fd;
tty->fds[FD_NET].events = POLLIN;
tty->net_cnt = 0;
if ((fd = open("/dev/ptmx", O_RDWR)) < 0) /* open master */
if ((fd = open("/dev/ptmx", O_RDWR)) < 0) /* Open master */
goto open_err;
tty->fds[FD_MASTER].fd = fd;
@@ -480,11 +489,11 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
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 */
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 */
if ((fd = open(slave_name, O_RDWR | O_NOCTTY)) < 0) /* Open slave */
goto open_err;
ioctl(fd, TCGETS, &tty->tio);
@@ -495,53 +504,65 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
tty->fds[FD_SLAVE].fd = fd;
tty->fds[FD_SLAVE].events = POLLIN;
res = telnet_open(tty->tn);
res =
telnet_open(tty->tn, ttynvt_param.raw ? TN_MODE_RAW : TN_MODE_TELNET);
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_mutex_init(&tty->ioctl_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->read_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->write_lock, NULL) < 0) ||
(res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty)))
{
errno = res;
goto open_err;
}
info->fh = (uintptr_t) tty;
nvt_log(LOG_INFO, "Connected to server: %s\n", ttynvt_param.server);
out:
info->fh = (uintptr_t) tty_cli;
info->nonseekable = 1;
info->direct_io = 1;
nvt_log(LOG_INFO, "Connected to server: %s:%d\n",
ttynvt_param.host, ttynvt_param.port);
fuse_reply_open(req, info);
pthread_mutex_unlock(&access_lock);
return;
open_err:
nvt_log(LOG_INFO, "Connection to server: %s:%d failed: %m\n",
ttynvt_param.host, ttynvt_param.port);
for (n = 0; n < 3; n++)
_fd_close(tty->fds[n].fd);
free(tty->tn);
free(tty);
free(tty_cli);
_ttynvt_ctx_destroy(tty);
ptty = NULL;
fuse_reply_err(req, errno);
nvt_log(LOG_ERR, "Connection to server: %s failed: %m\n",
ttynvt_param.server);
pthread_mutex_unlock(&access_lock);
}
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;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
char buf[TMP_BUF_SIZE];
int res, nr;
DBG2("%s\n", __func__);
pthread_mutex_lock(&tty->read_lock);
DBG2("%s (%d)\n", __func__, tty_cli->id);
if (tty->error)
{
@@ -555,7 +576,7 @@ ttynvt_read(fuse_req_t req, size_t size, off_t off,
else
fuse_reply_err(req, EBADFD);
return;
goto out;
}
if (size > TMP_BUF_SIZE)
@@ -582,36 +603,42 @@ ttynvt_read(fuse_req_t req, size_t size, off_t off,
{
DBG2("%s: error: %m\n", __func__);
fuse_reply_err(req, errno);
return;
goto out;
}
DBG2("%s: fd=%d: sz=%u/%u: '%.*s'\n", __func__,
tty->fds[FD_SLAVE].fd, res, (int)size, res, buf);
DBG2("%s (%d): fd=%d: sz=%u/%u: '%.*s'\n", __func__,
tty_cli->id, tty->fds[FD_SLAVE].fd, res, (int)size, res, buf);
fuse_reply_buf(req, buf, res);
out:
pthread_mutex_unlock(&tty->read_lock);
}
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;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
int res;
DBG2("%s: fd=%d: sz=%u: '%.*s'\n", __func__,
tty->fds[FD_SLAVE].fd, (int)size, (int)size, data);
pthread_mutex_lock(&tty->write_lock);
DBG2("%s (%d): fd=%d: sz=%u: '%.*s'\n", __func__,
tty_cli->id, tty->fds[FD_SLAVE].fd, (int)size, (int)size, data);
if (size == 0)
{
fuse_reply_write(req, 0);
return;
goto out;
}
if (tty->error)
{
DBG2("%s: tty->error=%d\n", __func__, tty->error);
fuse_reply_err(req, EPIPE);
return;
goto out;
}
res = write(tty->fds[FD_SLAVE].fd, data, size);
@@ -620,7 +647,7 @@ ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
DBG2("%s: error: %m\n", __func__);
tty->error = 1;
fuse_reply_err(req, errno);
return;
goto out;
}
else if (res != (int)size)
{
@@ -629,6 +656,9 @@ ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
}
fuse_reply_write(req, res);
out:
pthread_mutex_unlock(&tty->write_lock);
}
static int baudrate(speed_t speed)
@@ -717,10 +747,10 @@ static unsigned int _tio_parity(const struct termios2 *tio)
{
unsigned int par;
if (tio->c_cflag & PARENB && tio->c_iflag & IGNPAR)
if ((tio->c_cflag & PARENB) && (tio->c_iflag & IGNPAR))
par = 1;
#ifdef CMSPAR
else if (tio->c_cflag & PARENB && tio->c_iflag & CMSPAR)
else if ((tio->c_cflag & PARENB) && (tio->c_iflag & CMSPAR))
{
if (tio->c_cflag & PARODD)
par = 4;
@@ -806,7 +836,8 @@ 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;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
struct termios2 *tio = &tty->tio;
struct winsize ws;
unsigned int tiocm, baud;
@@ -816,10 +847,18 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
uint8_t byte;
uint32_t byte4;
DBG("%s: cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__,
_ioctl_name(cmd), arg, in_buf,
pthread_mutex_lock(&tty->ioctl_lock);
DBG("%s (%d): cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__,
tty_cli->id, _ioctl_name(cmd), arg, in_buf,
(unsigned int)in_bufsz, (unsigned int)out_bufsz);
if (ttynvt_param.raw)
{
fuse_reply_ioctl(req, 0, 0, 0);
goto out;
}
switch (cmd)
{
case TCSETSF:
@@ -855,6 +894,14 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
tty->master_suspended = (tty->smcr & TIOCM_CTS) ? 0 : 1;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_CTSRTS);
}
else if (tio->c_iflag & (IXON | IXOFF))
{
tty->master_suspended = 0;
if (tio->c_iflag & IXON)
telnet_rfc2217_ctl(tty->tn, TNS_CTL_XONOFF_OUT);
if (tio->c_iflag & IXOFF)
telnet_rfc2217_ctl(tty->tn, TNS_CTL_XONOFF_IN);
}
else
{
tty->master_suspended = 0;
@@ -918,6 +965,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
case TCIFLUSH:
byte = 1;
tty->slave_suspended = 0; /* Re-enable polling of Slave */
tty->pollin = 0;
break;
case TCOFLUSH:
byte = 2;
@@ -927,6 +975,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
byte = 3;
tty->slave_suspended = 0; /* Re-enable polling of Slave */
tty->pollout = 1;
tty->pollin = 0;
break;
}
telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &byte, 1);
@@ -942,7 +991,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
break;
case TIOCMBIS:
CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf;
tiocm = *(const 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)
@@ -958,7 +1007,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
goto show_mctl;
case TIOCMBIC:
CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf;
tiocm = *(const 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)
@@ -974,7 +1023,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
goto show_mctl;
case TIOCMSET:
CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf;
tiocm = *(const 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);
@@ -998,18 +1047,18 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
if (res < 0)
{
fuse_reply_err(req, errno);
return;
goto out;
}
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
break;
case TIOCSWINSZ:
CHECK_BUF_IN(sizeof(struct winsize));
ws = *(struct winsize *)in_buf;
ws = *(const struct winsize *)in_buf;
res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSWINSZ, &ws);
if (res < 0)
{
fuse_reply_err(req, errno);
return;
goto out;
}
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
break;
@@ -1028,7 +1077,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
break;
case TIOCSETD:
CHECK_BUF_IN(sizeof(int));
val = *(int *)in_buf;
val = *(const int *)in_buf;
res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSETD, &val);
if (res < 0)
fuse_reply_err(req, errno);
@@ -1070,17 +1119,21 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
}
break;
}
out:
pthread_mutex_unlock(&tty->ioctl_lock);
}
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;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
int revents = 0;
DBG2("%s: tty->pollin=%d tty->error/epipe=%d/%d\n",
__func__, tty->pollin, tty->error, tty->epipe);
DBG2("%s (%d): tty->pollin=%d tty->error/epipe=%d/%d\n",
__func__, tty_cli->id, tty->pollin, tty->error, tty->epipe);
_update_notify(tty, ph);
@@ -1105,6 +1158,62 @@ ttynvt_poll(fuse_req_t req, struct fuse_file_info *info,
fuse_reply_poll(req, revents);
}
static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
ttynvt_cli_t *tmp, *prev;
DBG("%s\n", __func__);
pthread_mutex_lock(&access_lock);
for (tmp = tty->cli, prev = NULL; tmp; prev = tmp, tmp = tmp->next)
if (tmp == tty_cli)
{
nvt_log(LOG_INFO, "Client release (%d)\n", tmp->id);
if (prev)
prev->next = tmp->next;
else
tty->cli = tmp->next;
free(tmp);
break;
}
if (tty->cli)
goto out;
tty->rel_pending = 1;
pthread_cancel(tty->ptid_poll);
pthread_join(tty->ptid_poll, NULL);
pthread_mutex_destroy(&tty->tty_lock);
pthread_mutex_destroy(&tty->poll_lock);
pthread_mutex_destroy(&tty->ioctl_lock);
pthread_mutex_destroy(&tty->read_lock);
pthread_mutex_destroy(&tty->write_lock);
if (ttynvt_param.close_delay_us)
_ttynvt_sleep_us(ttynvt_param.close_delay_us);
_fd_close(tty->fds[FD_NET].fd);
_fd_close(tty->fds[FD_MASTER].fd);
_fd_close(tty->fds[FD_SLAVE].fd);
if (tty->ph)
fuse_pollhandle_destroy(tty->ph);
_ttynvt_ctx_destroy(tty);
ptty = NULL;
nvt_log(LOG_INFO, "Disconnected from server: %s\n", ttynvt_param.server);
out:
fuse_reply_err(req, 0);
pthread_mutex_unlock(&access_lock);
}
static const struct cuse_lowlevel_ops ttynvt_op = {
.init = ttynvt_init,
.open = ttynvt_open,
@@ -1126,6 +1235,9 @@ static const struct fuse_opt ttynvt_opts[] = {
TTYNET_OPT("--min=%u", minor, 1),
TTYNET_OPT("-n %s", dev_name, 0),
TTYNET_OPT("--name=%s", dev_name, 0),
TTYNET_OPT("-q", quiet, 1),
TTYNET_OPT("-r", raw, 1),
TTYNET_OPT("-s %s", close_delay, 0),
TTYNET_OPT("-S %s", server, 0),
TTYNET_OPT("--server=%s", server, 0),
FUSE_OPT_KEY("-h", 0),
@@ -1139,11 +1251,14 @@ static void print_help(void)
"\t-D level\tEnable debug\n"
"\t-d\t\tStay in foreground, do not detach\n"
"\t-f\t\tStay in foreground, do not detach\n"
"\t-E\t\tPrint debug to stdout (default is syslog)\n"
"\t-E\t\tPrint debug to stderr (default is syslog)\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-q\t\tBe quiet (suppress informational messages)\n"
"\t-r\t\tDisable telnet processing\n"
"\t-s delay \t\tDelay at close in ms (use u suffix for us)\n"
"\t-S server, --server=host:port\n");
}
@@ -1179,8 +1294,9 @@ int main(int argc, char *argv[])
return 1;
}
log_level = ttynvt_param.debug;
log_stdout = ttynvt_param.logstd;
nvt_log_level(ttynvt_param.quiet, ttynvt_param.debug);
if (ttynvt_param.logstd)
nvt_log_dest(NVT_LOG_STDERR);
if (!ttynvt_param.dev_name)
ttynvt_param.dev_name = "ttyNVT0";
@@ -1191,22 +1307,18 @@ int main(int argc, char *argv[])
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);
if (ttynvt_param.close_delay)
{
char ch = 'm';
sscanf(ttynvt_param.close_delay, "%u%c",
&ttynvt_param.close_delay_us, &ch);
if (ch != 'u')
ttynvt_param.close_delay_us *= 1000; // To milliseconds
}
struct cuse_info ci = {
.dev_major = ttynvt_param.major,
.dev_minor = ttynvt_param.minor,