66 Commits

Author SHA1 Message Date
Kim Woelders 1081fd9235 ttynvt version 0.17 2025-06-25 17:32:41 +02:00
Frank Rolsted Jensen 72e09527b9 Handling of pthread_mutex_init failure fixed
pthread_mutex_init() returns errno on failure
2025-06-04 12:02:39 +02:00
Frank Rolsted Jensen 10d5c9cf3b Handling of initial protocol error changed
The client accepts a long(er) response time when waiting for server to
enable RFC2217 (telnet option).

Bug in hangling of rx timeout fixed
2025-06-04 12:02:34 +02:00
Kim Woelders 4d3f7d384b ttynvt: Move optional delay during close
Move to after closing the fds.

As suggested in https://gitlab.com/lars-thrane-as/ttynvt/-/issues/11.
2025-02-25 20:03:05 +01:00
Kim Woelders 1f516288ab test: Initial testing framework
Configure with --enable-testtools.

BLD_ROOT is now the top build directory.

Current test requires ttynvt to be started (by root) like
 # BLD_ROOT/src/ttynvt -d -E -S localhost:1234 -n ttyNVT99
Device and server name parameters must be as shown above.

Run tests like
 $ make -C BLD_ROOT test 	# Builds the other programs too
 $ make -C BLD_ROOT/test	# Only running tests
 $ make -C BLD_ROOT/test V=1 RUN_OPTS="--gtest_filter=Test1.t1"
2024-11-10 15:01:12 +01:00
Kim Woelders 55c5d0d226 nvtsrv: Change some log messages to debug 2024-11-10 13:34:38 +01:00
Kim Woelders cd5de2fe5d nvttest: Return error if anything fails 2024-11-10 13:31:30 +01:00
Kim Woelders da027d4de1 nvttest: Rework thread handling 2024-11-10 13:31:30 +01:00
Kim Woelders 3bcbf9c8c1 nvttest: Enable specifying device
Also check that the device file exists.
2024-11-10 11:30:16 +01:00
Kim Woelders 611ab3cbf3 nvttest: Improve help
And do help stuff like in nvtsrv.
2024-11-10 11:29:22 +01:00
Kim Woelders 6e2e530215 nvtsrv: Fix inconsistent help 2024-11-10 11:20:52 +01:00
Kim Woelders 3d5848aff0 ttynvt: On TCSET* skip rfc2217 baud rate change if zero
https://gitlab.com/lars-thrane-as/ttynvt/-/merge_requests/15
2024-10-26 10:52:40 +02:00
Nebojsa Sabovic 4223a4b92a Support odd parity. 2024-10-24 19:02:08 +02:00
Kim Woelders 2c090f3721 Re-indent everything with indent-2.2.13 2024-03-06 17:21:48 +01:00
Kim Woelders 797ccf08c8 socket: Indent according to profile 2024-03-06 17:18:46 +01:00
s-wakaba@gitlab.com 9739087594 support TCP/IPv6 address and UNIX domain socket path as rfc2217 server 2024-03-06 17:13:28 +01:00
Kim Woelders f41067c3be nvttest: Add missing -lpthread 2024-03-05 11:20:09 +01: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
Kim Woelders f759c0e3cc Correct termios2 baud rate calculation
Based on suggestion by joluxer <linuxer@quantentunnel.de>.
2021-06-18 17:10:22 +02:00
Kim Woelders 0606891b78 Add indent profile + formatting tweak 2021-06-18 16:17:13 +02:00
joluxer 899596fd5b Use all baudrate for traditional termios known to linux kernel 5.10. 2021-06-18 16:17:13 +02:00
joluxer a21ba0cb9c Make some variable storage bit width agnostic for transport. 2021-06-18 16:17:13 +02:00
Kim Woelders 8f03384964 print_help(): Add missing options to run in foreground
Suggested by joluxer <linuxer@quantentunnel.de>.
2021-06-18 16:17:13 +02:00
Kim Woelders b314659125 Fix potentially incorrect speed check
We don't know if tio is termios or termios2.
2021-06-18 07:21:53 +02:00
Kim Woelders 42a4e2e062 ttynvt_ioctl(): Cleanups around TCSET.. handling
Avoid nested switches and unnecessary block level.
2021-06-18 07:21:53 +02:00
Kim Woelders 0b402cdb6b Remove pointless CFLAGS_OPT
It would normally be overridden by the default automake CFLAGS="-g -O2".
2021-06-14 18:35:10 +02:00
Kim Woelders 302ed6d796 Include asm/ioctls.h for TCSETSF2 and friends
Some targets appear to need that.
2021-01-20 18:34:53 +01:00
Kim Woelders bf11b1dd15 Add support for termios2 ioctls
Based on patch by Thomas Shaddack <shaddack@shaddack.mauriceward.com>
https://www.improwis.com/projects/sw_SerialOverIP_RFC2217/
2021-01-14 07:41:43 +01:00
Kim Woelders 33de2e875c Add a couple of debug logs
From patch by Thomas Shaddack <shaddack@shaddack.mauriceward.com>
https://www.improwis.com/projects/sw_SerialOverIP_RFC2217/
2021-01-14 07:40:40 +01:00
Kim Woelders eccd32fdfb telnet_param.h: Show hex codes too
Inspired by Thomas Shaddack <shaddack@shaddack.mauriceward.com>
https://www.improwis.com/projects/sw_SerialOverIP_RFC2217/
2021-01-14 07:19:05 +01:00
Kim Woelders 089b733f27 Indent 2021-01-14 07:18:48 +01:00
Kim Woelders 41802ce7e9 Include poll.h, not sys/poll.h
... the posixly correct location.
2020-10-08 17:36:55 +02:00
Kim Woelders e51a98e4b2 Handle CRTSCTS properly 2019-09-10 06:52:00 +02:00
Kim Woelders 9db6c5d83c Move logging functions to separate file 2019-09-05 11:36:49 +02:00
Kim Woelders e3675b9e89 Move ttynvt source to src/ 2019-09-05 11:36:49 +02:00
29 changed files with 2477 additions and 448 deletions
+4 -2
View File
@@ -3,10 +3,12 @@
/autom4te.cache/ /autom4te.cache/
/compile /compile
/config.h.in /config.h.in
/config.h.in~ /config.guess
/config.sub
/configure /configure
/depcomp /depcomp
/install-sh /install-sh
/ltmain.sh
/missing /missing
Makefile.in Makefile.in
@@ -20,7 +22,7 @@ Makefile
# build # build
*.o *.o
/ttynvt /src/ttynvt
/ttynvt-*.tar.gz /ttynvt-*.tar.gz
/ttynvt-*.tar.xz /ttynvt-*.tar.xz
Vendored
+6
View File
@@ -0,0 +1,6 @@
-kr -i4 --no-tabs --line-length79 -di16
--braces-after-if-line --brace-indent0 --dont-cuddle-else --cuddle-do-while
--blank-lines-after-declarations --blank-lines-after-procedures
--break-after-boolean-operator
--no-space-after-casts
--else-endif-column0
+13 -14
View File
@@ -1,20 +1,19 @@
# Build ttynvt
#
MAINTAINERCLEANFILES = \ MAINTAINERCLEANFILES = \
aclocal.m4 autom4te.cache compile config.h.in config.h.in~ configure \ aclocal.m4 autom4te.cache compile config.h.in config.h.in~ configure \
depcomp install-sh missing \ depcomp install-sh missing \
Makefile.in Makefile.in */Makefile.in
CFLAGS_OPT = -O3 SUBDIRS = lib src
CFLAGS_WARN = -Wall -Wextra -Werror -Wno-unused-parameter if BUILD_TESTTOOLS
SUBDIRS += nvtsrv
SUBDIRS += nvttest
endif
if BUILD_TEST
SUBDIRS += test
endif
bin_PROGRAMS = ttynvt .PHONY: $(SUBDIRS) test
$(SUBDIRS) test:
ttynvt_SOURCES = \ $(MAKE) -C $@
ttynvt.c \ test: $(SUBDIRS)
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)
+23 -8
View File
@@ -1,23 +1,20 @@
m4_define([pkg_version], [0.14]) AC_INIT([ttynvt],[0.17],[frj@thrane.eu])
#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]) AM_INIT_AUTOMAKE([foreign dist-xz])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AC_USE_SYSTEM_EXTENSIONS AC_USE_SYSTEM_EXTENSIONS
AC_PROG_CC AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL AC_PROG_INSTALL
AC_HEADER_STDC
define([AC_LIBTOOL_LANG_CXX_CONFIG], [:])dnl
define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl
define([AC_LIBTOOL_LANG_GCJ_CONFIG], [:])dnl define([AC_LIBTOOL_LANG_GCJ_CONFIG], [:])dnl
LT_INIT()
AC_C_BIGENDIAN AC_C_BIGENDIAN
AC_C_CONST AC_C_CONST
AC_C_INLINE AC_C_INLINE
@@ -29,9 +26,27 @@ PKG_PROG_PKG_CONFIG
PKG_CHECK_MODULES(FUSE, fuse) 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"])
AM_CONDITIONAL(BUILD_TEST, false)
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_HEADERS([config.h])
AC_CONFIG_FILES([ AC_CONFIG_FILES([
Makefile Makefile
lib/Makefile
src/Makefile
nvtsrv/Makefile
nvttest/Makefile
test/Makefile
]) ])
AC_OUTPUT 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
+114
View File
@@ -0,0 +1,114 @@
/*
* Logging functions
*/
#include <stdarg.h>
#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include "nvt_log.h"
#include <sys/syscall.h>
#define gettid() (pid_t)syscall(__NR_gettid)
char log_level = 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)
{
struct timeval tv;
unsigned int sod, hh, mm, ss;
gettimeofday(&tv, NULL);
sod = tv.tv_sec % (24 * 3600);
hh = sod / 3600;
mm = (sod - hh * 3600) / 60;
ss = sod - hh * 3600 - mm * 60;
snprintf(buf, len, "%02d:%02d:%02d.%06d",
hh, mm, ss, (unsigned int)tv.tv_usec);
return buf;
}
void nvt_log(int prio, const char *fmt, ...)
{
va_list arg;
char buf[256], buft[64];
const char *eol;
int len;
if (prio > log_threshold)
return;
va_start(arg, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, arg);
if (log_dest)
{
eol = (len > 0 && buf[len - 1] != '\n') ? "\n" : "";
fprintf(log_dest, "%s: [%d] %d: %s%s",
_hms_txt(buft, sizeof(buft)), gettid(), prio, buf, eol);
}
else
{
syslog(prio, "[%d] %s", gettid(), buf);
}
va_end(arg);
}
#define BPL 16 /* Bytes per line */
void nvt_log_buf(int prio, const char *txt, const void *ptr_, unsigned int len)
{
unsigned int ix, i, imax;
int l, n;
char buf[128];
const unsigned char *ptr = ptr_;
for (ix = 0; ix < len || (len == 0 && ix == 0); ix += BPL)
{
n = snprintf(buf, sizeof(buf), "%s: %04x:", txt, ix);
if (n <= 0)
return; /* Just playing it safe */
imax = (len - ix < BPL) ? len - ix : BPL;
for (i = 0; i < imax; i++)
{
l = snprintf(buf + n, sizeof(buf) - n, " %02x", *ptr++);
if (l <= 0)
return; /* Just playing it safe */
n += l;
}
nvt_log(prio, "%s\n", buf);
}
}
+31
View File
@@ -0,0 +1,31 @@
/*
* Logging functions
*/
#ifndef NVT_LOG_H
#define NVT_LOG_H
#include <syslog.h>
#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;
#define DBG(...) \
if (log_level > 0) nvt_log(LOG_DEBUG, __VA_ARGS__)
#define DBG2(...) \
if (log_level > 1) nvt_log(LOG_DEBUG, __VA_ARGS__)
#define DBG2_BUF(txt, ptr, len) \
if (log_level > 1) nvt_log_buf(LOG_DEBUG, txt, ptr, len)
#endif /* NVT_LOG_H */
+217
View File
@@ -0,0 +1,217 @@
/*
* Socket interface API
*/
#include <netdb.h> /* getaddrinfo() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.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;
struct sockaddr_in6 ipv6;
struct sockaddr_un un;
};
} sa_t;
static int _sa_host_lookup(sa_t *sa, const char *host, const char *port)
{
struct addrinfo hint, *adrinf = NULL, *p;
int ret = -1;
memset(&hint, 0, sizeof(struct addrinfo));
if (getaddrinfo(host, port, &hint, &adrinf) != 0)
{
nvt_log(LOG_ERR, "Cannot resolve host name: %m\n");
return -1;
}
// Priority is given to the use of IPv4
// This code must be modified if the preffered protocol is changed or varied.
for (p = adrinf; p; p = p->ai_next)
{
if (p->ai_family == AF_INET)
{
sa->slen = sizeof(sa->ipv4);
memcpy(&sa->ipv4, p->ai_addr, sizeof(struct sockaddr_in));
ret = 0;
goto exit_host_lookup;
}
}
for (p = adrinf; p; p = p->ai_next)
{
if (p->ai_family == AF_INET6)
{
sa->slen = sizeof(sa->ipv6);
memcpy(&sa->ipv6, p->ai_addr, sizeof(struct sockaddr_in6));
ret = 0;
goto exit_host_lookup;
}
}
exit_host_lookup:
freeaddrinfo(adrinf);
return ret;
}
static int _sa_addr_parse(sa_t *sa, const char *addr)
{
ssize_t host_len;
char host[64];
const char *port;
memset(sa, 0, sizeof(sa_t));
if (strncmp(addr, "unix:", 5) == 0)
{ // case of unix domain sockets ...
addr += 5; // skip "unix:" prefix
if (strlen(addr) > sizeof(sa->un.sun_path) - 1)
{
nvt_log(LOG_ERR, "UNIX domain socket path is too long.\n");
return -1;
}
sa->slen = sizeof(sa->un);
sa->un.sun_family = AF_UNIX;
strcpy(sa->un.sun_path, addr);
sa->type = SOCK_STREAM;
return 0;
}
if (!(port = strrchr(addr, ':')))
{
nvt_log(LOG_ERR, "bad server address format.\n");
return -1;
}
host_len = (port++) - addr;
// *port indicates the last ':', its next is 1st char of the port number
if (host_len >= 2 && addr[0] == '[' && addr[host_len - 1] == ']')
{
// IPv6 addresses are enclosed in pair of square brackets.
addr += 1;
host_len -= 2;
}
else
{
if (strchr(addr, ':') != port - 1)
{
nvt_log(LOG_ERR,
"addresses containing colons must be enclosed in square brackets.\n");
return -1;
}
}
if ((ssize_t) sizeof(host) <= host_len)
{
nvt_log(LOG_ERR, "host name is too long.\n");
return -1;
}
strncpy(host, addr, host_len);
host[host_len] = '\0'; // strncpy doesn't add null char
if (_sa_host_lookup(sa, host, port) != 0)
return -1;
sa->type = SOCK_STREAM;
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 || sa.gen.sa_family == AF_INET6)
{
/* 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
+246
View File
@@ -0,0 +1,246 @@
/*
* 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"
/**INDENT-OFF**/
static const char help_text[] =
"Usage: nvtsrv OPTIONS... HOST:PORT\n"
"OPTIONS:\n"
" -d : Enable debug\n"
" -e : Echo received data\n"
" -q : Quiet\n"
;
/**INDENT-ON**/
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 _usage(int rc)
{
printf(help_text);
exit(rc);
}
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)
{
DBG("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: /* '?' */
_usage(0);
break;
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)
_usage(1);
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 */
+254
View File
@@ -0,0 +1,254 @@
/*
* 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;
DBG("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;
DBG("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 -lpthread
+220
View File
@@ -0,0 +1,220 @@
/*
* ttynvt stress tool
*/
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/termbits.h>
#include "lib/nvt_log.h"
/**INDENT-OFF**/
static const char help_text[] =
"Usage: nvttest OPTIONS...\n"
"OPTIONS:\n"
" -D name : Specify device name (/dev/name, default /dev/ttyNVT0)\n"
" -d : Enable debug\n"
" -n loops : Test loops per thread\n"
" -q : Quiet\n"
" -r : Insert random delay in loops\n"
" -t threads : N. threads\n"
;
/**INDENT-ON**/
static char dev[64] = "/dev/ttyNVT0";
typedef struct {
int nthread;
int ithread;
int nloop;
bool opt_rand_delay;
} topts_t;
typedef struct {
pthread_t thr;
topts_t opts;
int errors;
} thr_t;
static void _usage(int rc)
{
printf(help_text);
exit(rc);
}
static void *_worker(void *arg)
{
thr_t *thr = arg;
int iloop;
int fd, err, nw;
struct termios ios;
for (iloop = 0; iloop < thr->opts.nloop; iloop++)
{
nvt_log(LOG_INFO, "Thread %d/%d run %d/%d\n",
thr->opts.ithread, thr->opts.nthread,
iloop + 1, thr->opts.nloop);
{
fd = open(dev, O_RDWR);
if (fd < 0)
{
nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev);
thr->errors++;
break;
}
memset(&ios, 0, sizeof(ios));
err = ioctl(fd, TCGETS, &ios);
if (err != 0)
{
nvt_log(LOG_ERR, "ioctl(TCGETS) failed: %m\n");
thr->errors++;
break;
}
err = ioctl(fd, TCSETS, &ios);
if (err != 0)
{
nvt_log(LOG_ERR, "ioctl(TCSETS) failed: %m\n");
thr->errors++;
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);
thr->errors++;
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");
thr->errors++;
break;
}
if (nw < len)
{
nvt_log(LOG_ERR, "write short: %u/%u\n", nw, len);
thr->errors++;
break;
}
}
close(fd);
}
if (thr->opts.opt_rand_delay)
usleep(rand() & 0xfff);
}
return NULL;
}
int main(int argc, char **argv)
{
int err, errors;
int opt;
int ithr, nthr;
thr_t *threads, *thr;
topts_t topts = { };
int debug, quiet;
struct stat st;
debug = quiet = 0;
nthr = 1;
topts.nloop = 1;
while ((opt = getopt(argc, argv, "D:dn:qrt:")) != -1)
{
switch (opt)
{
default: /* '?' */
_usage(1);
break;
case 'D':
snprintf(dev, sizeof(dev), "/dev/%s", optarg);
break;
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;
}
}
if (stat(dev, &st) || !S_ISCHR(st.st_mode))
{
fprintf(stderr, "Invalid device file: '%s'\n", dev);
exit(EXIT_FAILURE);
}
nvt_log_level(quiet, debug);
nvt_log_dest(NVT_LOG_STDOUT);
topts.nthread = nthr;
threads = calloc(nthr, sizeof(thr_t));
for (ithr = 0; ithr < nthr; ithr++)
{
nvt_log(LOG_INFO, "Create worker thread %d/%d\n", ithr + 1, nthr);
thr = &threads[ithr];
thr->opts = topts;
thr->opts.ithread = ithr + 1;
err = pthread_create(&thr->thr, NULL, _worker, thr);
if (err)
{
nvt_log(LOG_ERR, "Failed to create thread %d/%d\n", ithr, nthr);
break;
}
}
errors = 0;
for (ithr = 0; ithr < nthr; ithr++)
{
thr = &threads[ithr];
if (thr->thr == 0)
break;
pthread_join(thr->thr, NULL);
errors += thr->errors;
nvt_log(LOG_INFO, "Joined worker thread %d/%d (err=%d)\n",
ithr + 1, nthr, thr->errors);
}
return errors ? 1 : 0;
}
+10
View File
@@ -0,0 +1,10 @@
bin_PROGRAMS = ttynvt
ttynvt_SOURCES = \
ttynvt.c \
telnet_basic.c telnet_client_rfc2217.c telnet.h telnet_param.h
ttynvt_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) $(FUSE_CFLAGS) $(CFLAGS_WARN)
ttynvt_LDADD = $(top_builddir)/lib/libnvt.la $(FUSE_LIBS)
+12 -7
View File
@@ -2,10 +2,10 @@
* Copyright (c) 2018 Lars Thrane A/S * Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0 * SPDX-License-Identifier: GPL-2.0
* *
* Telnet interface * Telnet interface - client
*/ */
#ifndef TELNET_H #ifndef TELNET_CLIENT_H
#define TELNET_H #define TELNET_CLIENT_H
typedef int (srv_tx_f) (void *cctx, const void *buf, int len); typedef int (srv_tx_f) (void *cctx, const void *buf, int len);
typedef int (srv_rx_f) (void *cctx, int timeout); typedef int (srv_rx_f) (void *cctx, int timeout);
@@ -21,7 +21,7 @@ typedef struct {
unsigned int bytes_rx; /* Rx byte count */ unsigned int bytes_rx; /* Rx byte count */
unsigned int off_rx; /* Rx buffer offset */ 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 mode_rfc2217; /* RFC-2217 mode */
unsigned char telnet_ack[256]; /* telnet option ack counter */ unsigned char telnet_ack[256]; /* telnet option ack counter */
} tn_ctx_t; } 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, extern tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f * ftx, srv_rx_f * frx,
srv_ms_f * fss); 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_rx(tn_ctx_t * tcc, char *buf, int *plen);
extern int telnet_tx(tn_ctx_t * tcc, const char *buf, int len); 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); const void *val, int nd);
extern void telnet_rfc2217_ctl(tn_ctx_t * tcc, unsigned int val); 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*/ /*RFC2217 modem state*/
#define TNS_STATE_DCD 0x80 #define TNS_STATE_DCD 0x80
#define TNS_STATE_RI 0x40 #define TNS_STATE_RI 0x40
@@ -60,11 +64,12 @@ extern void telnet_rfc2217_ctl(tn_ctx_t * tcc, unsigned int val);
/*RFC2217 control commands*/ /*RFC2217 control commands*/
#define TNS_CTL_NOFLOW 1 #define TNS_CTL_NOFLOW 1
#define TNS_CTL_XONOFF 2 #define TNS_CTL_XONOFF_OUT 2
#define TNS_CTL_CTSRTS 3 #define TNS_CTL_CTSRTS 3
#define TNS_CTL_DTR_ON 8 #define TNS_CTL_DTR_ON 8
#define TNS_CTL_DTR_OFF 9 #define TNS_CTL_DTR_OFF 9
#define TNS_CTL_RTS_ON 11 #define TNS_CTL_RTS_ON 11
#define TNS_CTL_RTS_OFF 12 #define TNS_CTL_RTS_OFF 12
#define TNS_CTL_XONOFF_IN 15
#endif /*TELNET_H */ #endif /* TELNET_CLIENT_H */
+27 -17
View File
@@ -2,12 +2,12 @@
* Copyright (c) 2018 Lars Thrane A/S * Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0 * SPDX-License-Identifier: GPL-2.0
* *
* Telnet server - Telnet protocol handling * Telnet protocol handling - client
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <errno.h>
#include "telnet.h" #include "telnet.h"
#include "telnet_param.h" #include "telnet_param.h"
@@ -39,7 +39,7 @@ static const unsigned char tn_opt[256] = {
}; };
static void _telnet_handle_req(tn_ctx_t * tcc, int cmd, int opt) static void _telnet_handle_req(tn_ctx_t *tcc, int cmd, int opt)
{ {
char buf[3]; char buf[3];
@@ -68,7 +68,7 @@ static void _telnet_handle_req(tn_ctx_t * tcc, int cmd, int opt)
tcc->srv_tx(tcc->cctx, buf, 3); tcc->srv_tx(tcc->cctx, buf, 3);
} }
static void _telnet_handle_resp(tn_ctx_t * tcc, int cmd, int opt) static void _telnet_handle_resp(tn_ctx_t *tcc, int cmd, int opt)
{ {
if (opt == TNO_CPCO) if (opt == TNO_CPCO)
{ {
@@ -77,7 +77,7 @@ static void _telnet_handle_resp(tn_ctx_t * tcc, int cmd, int opt)
} }
} }
void telnet_set_opt(tn_ctx_t * tcc, int opt, int prm, const void *val, int len) void telnet_set_opt(tn_ctx_t *tcc, int opt, int prm, const void *val, int len)
{ {
unsigned char buf[128], *pu = buf; unsigned char buf[128], *pu = buf;
const unsigned char *pv; const unsigned char *pv;
@@ -107,7 +107,7 @@ void telnet_set_opt(tn_ctx_t * tcc, int opt, int prm, const void *val, int len)
tcc->srv_tx(tcc->cctx, buf, len); tcc->srv_tx(tcc->cctx, buf, len);
} }
static void _telnet_handle_opt(tn_ctx_t * tcc, int opt, static void _telnet_handle_opt(tn_ctx_t *tcc, int opt,
unsigned char *pu, int nd) unsigned char *pu, int nd)
{ {
switch (opt) switch (opt)
@@ -121,7 +121,7 @@ static void _telnet_handle_opt(tn_ctx_t * tcc, int opt,
} }
} }
int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen) int telnet_rx(tn_ctx_t *tcc, char *buf, int *plen)
{ {
char *s, *p; char *s, *p;
unsigned char *pu; unsigned char *pu;
@@ -129,6 +129,10 @@ int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen)
int np, nd; int np, nd;
int b1, b2; int b1, b2;
/* Skip telnet processing entirely in raw mode */
if (tcc->mode_telnet < 0)
return 0;
len = *plen; len = *plen;
tcc->bytes_rx += len; tcc->bytes_rx += len;
@@ -144,7 +148,7 @@ int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen)
/* We have IAC (= 0xff) */ /* We have IAC (= 0xff) */
np = rem - (p - s); /* N. chars available incl. IAC */ np = rem - (p - s); /* N. chars available incl. IAC */
if (np < 2) if (np < 2)
return 1; goto tn_more;
pu = (unsigned char *)p; pu = (unsigned char *)p;
b1 = pu[1]; b1 = pu[1];
@@ -230,14 +234,14 @@ int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen)
return 0; return 0;
} }
int telnet_tx(tn_ctx_t * tcc, const char *buf, int len) int telnet_tx(tn_ctx_t *tcc, const char *buf, int len)
{ {
const char *s, *p; const char *s, *p;
int rem, nd; int rem, nd;
for (s = buf, rem = len; rem > 0; s = p) 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); p = memchr(s, TNC_FF_IAC, rem);
else else
p = NULL; p = NULL;
@@ -260,12 +264,18 @@ int telnet_tx(tn_ctx_t * tcc, const char *buf, int len)
return 0; return 0;
} }
int telnet_open(tn_ctx_t * tcc) int telnet_open(tn_ctx_t *tcc, int mode)
{ {
time_t t = time(NULL);; time_t t = time(NULL);;
char buf[3]; char buf[3];
int res; int res;
if (mode == TN_MODE_RAW)
{
tcc->mode_telnet = -1; /* Raw mode - no telnet processing */
return 0;
}
buf[0] = TNC_FF_IAC; buf[0] = TNC_FF_IAC;
buf[1] = TNC_FB_WILL; buf[1] = TNC_FB_WILL;
buf[2] = TNO_CPCO; buf[2] = TNO_CPCO;
@@ -275,17 +285,17 @@ int telnet_open(tn_ctx_t * tcc)
while (1) while (1)
{ {
res = tcc->srv_rx(tcc->cctx, 1000); res = tcc->srv_rx(tcc->cctx, 1000);
if (res <= 0) if (res < 0)
return -1; return errno;
else if (tcc->mode_rfc2217) else if (tcc->mode_rfc2217)
return 0; return 0;
else if (t - time(NULL) > 2) else if (time(NULL) - t > 10)
return -1; return EPROTO;
} }
} }
tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f * ftx, tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f *ftx,
srv_rx_f * frx, srv_ms_f * fms) srv_rx_f *frx, srv_ms_f *fms)
{ {
tn_ctx_t *tcc; tn_ctx_t *tcc;
@@ -2,15 +2,12 @@
* Copyright (c) 2018 Lars Thrane A/S * Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0 * SPDX-License-Identifier: GPL-2.0
* *
* Telnet server - RFC-2217 handling * Telnet RFC-2217 handling - client
*/ */
#include <stddef.h>
#include "telnet.h" #include "telnet.h"
#include "telnet_param.h" #include "telnet_param.h"
void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt, void telnet_rfc2217_handle_opt(tn_ctx_t *tcc, int opt,
unsigned char *pu, int nd) unsigned char *pu, int nd)
{ {
unsigned int sub; unsigned int sub;
@@ -35,7 +32,7 @@ void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
} }
} }
void telnet_rfc2217_cfg(tn_ctx_t * tcc, int cmd, const void *val, int nd) void telnet_rfc2217_cfg(tn_ctx_t *tcc, int cmd, const void *val, int nd)
{ {
if (!tcc->mode_rfc2217) if (!tcc->mode_rfc2217)
return; return;
@@ -43,9 +40,9 @@ void telnet_rfc2217_cfg(tn_ctx_t * tcc, int cmd, const void *val, int nd)
telnet_set_opt(tcc, TNO_CPCO, cmd, val, nd); telnet_set_opt(tcc, TNO_CPCO, cmd, val, nd);
} }
void telnet_rfc2217_ctl(tn_ctx_t * tcc, unsigned int val) void telnet_rfc2217_ctl(tn_ctx_t *tcc, unsigned int val)
{ {
unsigned char byte = val; unsigned char byte = val;
if (!tcc->mode_rfc2217) if (!tcc->mode_rfc2217)
return; return;
+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 */
+309 -269
View File
@@ -7,9 +7,10 @@
#include <cuse_lowlevel.h> #include <cuse_lowlevel.h>
#include <errno.h> #include <errno.h>
#include <fuse_opt.h> #include <fuse_opt.h>
#include <netdb.h> #include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <signal.h> #include <signal.h>
#include <stdint.h>
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
@@ -17,16 +18,14 @@
#include <string.h> #include <string.h>
#include <syslog.h> #include <syslog.h>
#include <unistd.h> #include <unistd.h>
#include <asm/ioctls.h>
#include <asm/termbits.h> #include <asm/termbits.h>
#include <linux/sockios.h>
#include <linux/ppp-ioctl.h> #include <linux/ppp-ioctl.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include "lib/nvt_log.h"
#include "lib/nvt_socket.h"
#include "telnet.h" #include "telnet.h"
#define NET_BUF_SIZE 1024 #define NET_BUF_SIZE 1024
@@ -36,20 +35,19 @@
#define FD_MASTER 1 #define FD_MASTER 1
#define FD_SLAVE 2 #define FD_SLAVE 2
#define _PRF_N_(no) __attribute__((__format__(__printf__, (no), (no) + 1)))
#define _PRF2_ _PRF_N_(2)
static struct ttynvt_param { static struct ttynvt_param {
unsigned int major; unsigned int major;
unsigned int minor; unsigned int minor;
const char *dev_name; const char *dev_name;
char *server; const char *server;
unsigned int quiet;
unsigned int debug; unsigned int debug;
int logstd; int logstd;
int raw; /* No telnet processing */
const char *close_delay;
/* Derived */ /* Derived */
char host[64]; unsigned int close_delay_us;
unsigned int port;
/* Picked up */ /* Picked up */
int foreground; int foreground;
} ttynvt_param; } ttynvt_param;
@@ -70,7 +68,7 @@ typedef struct {
char net_buf[NET_BUF_SIZE]; char net_buf[NET_BUF_SIZE];
int net_cnt; int net_cnt;
struct termios tio; struct termios2 tio;
int cmcr; /* Commanded by ioctl */ int cmcr; /* Commanded by ioctl */
volatile int smcr; /* Status from modem */ volatile int smcr; /* Status from modem */
@@ -84,85 +82,8 @@ typedef struct {
tn_ctx_t *tn; tn_ctx_t *tn;
} ttynvt_t; } ttynvt_t;
#include <sys/syscall.h>
#define gettid() (pid_t)syscall(__NR_gettid)
static const char *_hms_txt(char *buf, unsigned int len) static pthread_mutex_t access_lock = PTHREAD_MUTEX_INITIALIZER;
{
struct timeval tv;
unsigned int sod, hh, mm, ss;
gettimeofday(&tv, NULL);
sod = tv.tv_sec % (24 * 3600);
hh = sod / 3600;
mm = (sod - hh * 3600) / 60;
ss = sod - hh * 3600 - mm * 60;
snprintf(buf, len, "%02d:%02d:%02d.%06d",
hh, mm, ss, (unsigned int)tv.tv_usec);
return buf;
}
_PRF2_ static void _log(int prio, const char *fmt, ...)
{
va_list arg;
char buf[256], buft[64];
va_start(arg, fmt);
vsnprintf(buf, sizeof(buf), fmt, arg);
if (ttynvt_param.logstd)
{
printf("%s: [%d] %d: %s",
_hms_txt(buft, sizeof(buft)), gettid(), prio, buf);
}
else
{
syslog(prio, "[%d] %s", gettid(), buf);
}
va_end(arg);
}
#define BPL 16 /* Bytes per line */
void _log_buf(int prio, const char *txt, const void *ptr_, unsigned int len)
{
unsigned int ix, i, imax;
int l, n;
char buf[128];
const unsigned char *ptr = ptr_;
for (ix = 0; ix < len || (len == 0 && ix == 0); ix += BPL)
{
n = snprintf(buf, sizeof(buf), "%s: %04x:", txt, ix);
if (n <= 0)
return; /* Just playing it safe */
imax = (len - ix < BPL) ? len - ix : BPL;
for (i = 0; i < imax; i++)
{
l = snprintf(buf + n, sizeof(buf) - n, " %02x", *ptr++);
if (l <= 0)
return; /* Just playing it safe */
n += l;
}
_log(prio, "%s\n", buf);
}
}
#define DBG(...) \
if (ttynvt_param.debug > 0) _log(LOG_DEBUG, __VA_ARGS__)
#define DBG2(...) \
if (ttynvt_param.debug > 1) _log(LOG_DEBUG, __VA_ARGS__)
#define DBG2_BUF(txt, ptr, len) \
if (ttynvt_param.debug > 1) _log_buf(LOG_DEBUG, txt, ptr, len)
static void _fd_close(int fd)
{
if (fd >= 0)
close(fd);
}
static volatile char _is_interrupted = 0; static volatile char _is_interrupted = 0;
@@ -193,6 +114,20 @@ static void ttynvt_interrupted(fuse_req_t req, void *data)
pthread_kill(thread, SIGUSR2); 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) static int _check_dev(void)
{ {
int rc; int rc;
@@ -209,31 +144,13 @@ static int _check_dev(void)
} }
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; if (fd >= 0)
close(fd);
_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);
} }
static void _notify(ttynvt_t * tty) static void _notify(ttynvt_t *tty)
{ {
pthread_mutex_lock(&tty->poll_lock); pthread_mutex_lock(&tty->poll_lock);
if (tty->ph) if (tty->ph)
@@ -245,7 +162,7 @@ static void _notify(ttynvt_t * tty)
pthread_mutex_unlock(&tty->poll_lock); pthread_mutex_unlock(&tty->poll_lock);
} }
static void _update_notify(ttynvt_t * tty, struct fuse_pollhandle *ph) static void _update_notify(ttynvt_t *tty, struct fuse_pollhandle *ph)
{ {
pthread_mutex_lock(&tty->poll_lock); pthread_mutex_lock(&tty->poll_lock);
struct fuse_pollhandle *tmp_ph = tty->ph; struct fuse_pollhandle *tmp_ph = tty->ph;
@@ -256,7 +173,7 @@ static void _update_notify(ttynvt_t * tty, struct fuse_pollhandle *ph)
fuse_pollhandle_destroy(tmp_ph); fuse_pollhandle_destroy(tmp_ph);
} }
static int _net_read_check_exit(ttynvt_t * tty) static int _net_read_check_exit(ttynvt_t *tty)
{ {
if ((tty->tio.c_cflag & CLOCAL) == 0 && if ((tty->tio.c_cflag & CLOCAL) == 0 &&
(tty->smcr_last & TIOCM_CD) != 0 && (tty->smcr & TIOCM_CD) == 0) (tty->smcr_last & TIOCM_CD) != 0 && (tty->smcr & TIOCM_CD) == 0)
@@ -309,12 +226,12 @@ static void *_read_net(void *arg)
NET_BUF_SIZE - tty->net_cnt); NET_BUF_SIZE - tty->net_cnt);
if (res < 0) if (res < 0)
{ {
_log(LOG_ERR, "net read error: %m\n"); nvt_log(LOG_ERR, "net read error: %m\n");
break; break;
} }
if (res == 0) if (res == 0)
{ {
_log(LOG_INFO, "Connection closed by remote host\n"); nvt_log(LOG_NOTICE, "Connection closed by remote host\n");
break; break;
} }
tty->net_cnt += res; tty->net_cnt += res;
@@ -328,7 +245,7 @@ static void *_read_net(void *arg)
write(tty->fds[FD_MASTER].fd, tty->net_buf, tty->net_cnt); write(tty->fds[FD_MASTER].fd, tty->net_buf, tty->net_cnt);
if (res < 0) if (res < 0)
{ {
_log(LOG_ERR, "master write error: %m\n"); nvt_log(LOG_ERR, "master write error: %m\n");
break; break;
} }
tty->net_cnt = 0; tty->net_cnt = 0;
@@ -343,7 +260,7 @@ static void *_read_net(void *arg)
DBG("%s: read res=%d pollout=%d\n", __func__, res, tty->pollout); DBG("%s: read res=%d pollout=%d\n", __func__, res, tty->pollout);
if (res < 0) if (res < 0)
{ {
_log(LOG_ERR, "master read error: %m\n"); nvt_log(LOG_ERR, "master read error: %m\n");
break; break;
} }
if (tty->pollout == 0) if (tty->pollout == 0)
@@ -373,44 +290,6 @@ static void *_read_net(void *arg)
return 0; 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: %m\n");
return sockfd;
}
server = gethostbyname(host);
if (server == NULL)
{
_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)
{
_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) static int _srv_write(void *cctx, const void *buf, int len)
{ {
ttynvt_t *tty = cctx; ttynvt_t *tty = cctx;
@@ -478,7 +357,8 @@ static void _modem_status_cb(void *cctx, int status)
if (status & TNS_STATE_CTS) if (status & TNS_STATE_CTS)
mcr |= TIOCM_CTS; mcr |= TIOCM_CTS;
tty->master_suspended = (mcr & TIOCM_CTS) ? 0 : 1; if (tty->tio.c_cflag & CRTSCTS)
tty->master_suspended = (mcr & TIOCM_CTS) ? 0 : 1;
DBG("%s: tty=%p mcr=%x MS=%d\n", __func__, tty, mcr, DBG("%s: tty=%p mcr=%x MS=%d\n", __func__, tty, mcr,
tty->master_suspended); tty->master_suspended);
@@ -493,47 +373,65 @@ static void ttynvt_init(void *userdata, struct fuse_conn_info *conn)
set_signal_handler(); 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; ttynvt_t *tty;
char *slave_name; int i;
struct termios ios;
int sockfd;
int fd;
int res;
int n;
DBG("%s\n", __func__); tty = calloc(1, sizeof(ttynvt_t));
tty = calloc(1, sizeof(*tty));
if (!tty) if (!tty)
{ return NULL;
fuse_reply_err(req, ENOMEM);
return;
}
for (n = 0; n < 3; n++)
tty->fds[n].fd = -1;
tty->pollout = 1;
tty->tn = telnet_ctx_init(tty, _srv_write, _srv_read, _modem_status_cb); tty->tn = telnet_ctx_init(tty, _srv_write, _srv_read, _modem_status_cb);
if (!tty->tn) if (!tty->tn)
{ {
free(tty); 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;
char *slave_name;
struct termios ios;
int fd;
int res;
int n;
pthread_mutex_lock(&access_lock);
nvt_log(LOG_INFO, "Connecting to server: %s\n", ttynvt_param.server);
tty = _ttynvt_ctx_create();
if (!tty)
{
fuse_reply_err(req, ENOMEM); fuse_reply_err(req, ENOMEM);
return; return;
} }
sockfd = _srv_connect(ttynvt_param.host, ttynvt_param.port); fd = socket_create_client(ttynvt_param.server);
if (sockfd < 0) if (fd < 0)
{ {
free(tty->tn); errno = EHOSTUNREACH;
free(tty); goto open_err;
fuse_reply_err(req, EHOSTUNREACH);
return;
} }
tty->fds[FD_NET].fd = sockfd; tty->fds[FD_NET].fd = fd;
tty->fds[FD_NET].events = POLLIN; tty->fds[FD_NET].events = POLLIN;
tty->net_cnt = 0; tty->net_cnt = 0;
@@ -570,14 +468,16 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
tty->fds[FD_SLAVE].fd = fd; tty->fds[FD_SLAVE].fd = fd;
tty->fds[FD_SLAVE].events = POLLIN; tty->fds[FD_SLAVE].events = POLLIN;
res = telnet_open(tty->tn); res =
if (res < 0) telnet_open(tty->tn, ttynvt_param.raw ? TN_MODE_RAW : TN_MODE_TELNET);
if (res != 0)
{ {
errno = EPROTO; errno = res;
goto open_err; goto open_err;
} }
if ((res = pthread_mutex_init(&tty->tty_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->poll_lock, NULL) < 0) || if ((res = pthread_mutex_init(&tty->tty_lock, NULL)) ||
(res = pthread_mutex_init(&tty->poll_lock, NULL)) ||
(res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty))) (res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty)))
{ {
errno = res; errno = res;
@@ -588,24 +488,23 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
info->nonseekable = 1; info->nonseekable = 1;
info->direct_io = 1; info->direct_io = 1;
_log(LOG_INFO, "Connected to server: %s:%d\n", nvt_log(LOG_INFO, "Connected to server: %s\n", ttynvt_param.server);
ttynvt_param.host, ttynvt_param.port);
fuse_reply_open(req, info); fuse_reply_open(req, info);
return; return;
open_err: open_err:
_log(LOG_INFO, "Connection to server: %s:%d failed: %m\n",
ttynvt_param.host, ttynvt_param.port);
for (n = 0; n < 3; n++) for (n = 0; n < 3; n++)
_fd_close(tty->fds[n].fd); _fd_close(tty->fds[n].fd);
free(tty->tn); _ttynvt_ctx_destroy(tty);
free(tty);
fuse_reply_err(req, errno); 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 static void
@@ -623,7 +522,7 @@ ttynvt_read(fuse_req_t req, size_t size, off_t off,
DBG2("%s: tty->error=%d\n", __func__, tty->error); DBG2("%s: tty->error=%d\n", __func__, tty->error);
if (tty->epipe == 0) if (tty->epipe == 0)
{ {
_log(LOG_WARNING, "Rx EPIPE\n"); nvt_log(LOG_WARNING, "Rx EPIPE\n");
tty->epipe = 1; tty->epipe = 1;
fuse_reply_buf(req, NULL, 0); fuse_reply_buf(req, NULL, 0);
} }
@@ -699,6 +598,7 @@ ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
} }
else if (res != (int)size) else if (res != (int)size)
{ {
DBG("%s: caution: tty->pollout set to 0\n", __func__);
tty->pollout = 0; tty->pollout = 0;
} }
@@ -707,9 +607,13 @@ ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
static int baudrate(speed_t speed) static int baudrate(speed_t speed)
{ {
/**INDENT-OFF**/
return speed == B50 ? 50 : return speed == B50 ? 50 :
speed == B75 ? 75 : speed == B75 ? 75 :
speed == B110 ? 110 : speed == B110 ? 110 :
speed == B134 ? 134 :
speed == B150 ? 150 :
speed == B200 ? 200 :
speed == B300 ? 300 : speed == B300 ? 300 :
speed == B600 ? 600 : speed == B600 ? 600 :
speed == B1200 ? 1200 : speed == B1200 ? 1200 :
@@ -720,7 +624,21 @@ static int baudrate(speed_t speed)
speed == B19200 ? 19200 : speed == B19200 ? 19200 :
speed == B38400 ? 38400 : speed == B38400 ? 38400 :
speed == B57600 ? 57600 : speed == B57600 ? 57600 :
speed == B115200 ? 115200 : speed == B230400 ? 230400 : 0; speed == B115200 ? 115200 :
speed == B230400 ? 230400 :
speed == B460800 ? 460800 :
speed == B500000 ? 500000 :
speed == B576000 ? 576000 :
speed == B921600 ? 921600 :
speed == B1000000 ? 1000000 :
speed == B1152000 ? 1152000 :
speed == B1500000 ? 1500000 :
speed == B2000000 ? 2000000 :
speed == B2500000 ? 2500000 :
speed == B3000000 ? 3000000 :
speed == B3500000 ? 3500000 :
speed == B4000000 ? 4000000 : 0;
/**INDENT-ON**/
} }
static const char pstr[] = { ' ', 'N', 'O', 'E', 'M', 'S' }; static const char pstr[] = { ' ', 'N', 'O', 'E', 'M', 'S' };
@@ -737,9 +655,13 @@ static const char *_ioctl_name(int cmd)
snprintf(buf, sizeof(buf), "0x%x", cmd); snprintf(buf, sizeof(buf), "0x%x", cmd);
return buf; return buf;
CASE(TCSETSF); CASE(TCSETSF);
CASE(TCSETSF2);
CASE(TCSETSW); CASE(TCSETSW);
CASE(TCSETSW2);
CASE(TCSETS); CASE(TCSETS);
CASE(TCSETS2);
CASE(TCGETS); CASE(TCGETS);
CASE(TCGETS2);
CASE(TIOCINQ); CASE(TIOCINQ);
CASE(TIOCOUTQ); CASE(TIOCOUTQ);
CASE(TCFLSH); CASE(TCFLSH);
@@ -765,14 +687,16 @@ static const char *_ioctl_name(int cmd)
} }
} }
static unsigned int _tio_parity(const struct termios *tio) static unsigned int _tio_parity(const struct termios2 *tio)
{ {
unsigned int par; unsigned int par;
if (tio->c_cflag & PARENB && tio->c_iflag & IGNPAR) if (!(tio->c_cflag & PARENB))
{
par = 1; par = 1;
}
#ifdef CMSPAR #ifdef CMSPAR
else if (tio->c_cflag & PARENB && tio->c_iflag & CMSPAR) else if (tio->c_iflag & CMSPAR)
{ {
if (tio->c_cflag & PARODD) if (tio->c_cflag & PARODD)
par = 4; par = 4;
@@ -780,20 +704,32 @@ static unsigned int _tio_parity(const struct termios *tio)
par = 5; par = 5;
} }
#endif #endif
else if (tio->c_cflag & PARENB) else if (tio->c_cflag & PARODD)
par = 3; {
par = 2;
}
else else
par = 1; {
par = 3;
}
return par; return par;
} }
static unsigned int _tio_baud(const struct termios *tio) static unsigned int _tio_baud(const struct termios2 *tio)
{ {
return baudrate(tio->c_cflag & (CBAUD | CBAUDEX)); return baudrate(tio->c_cflag & (CBAUD | CBAUDEX));
} }
static unsigned int _tio_csize(const struct termios *tio) static unsigned int _tio2_baud(const struct termios2 *tio2)
{
if ((tio2->c_cflag & CBAUD) == BOTHER)
return tio2->c_ospeed;
return _tio_baud(tio2);
}
static unsigned int _tio_csize(const struct termios2 *tio)
{ {
unsigned int csize; unsigned int csize;
@@ -809,7 +745,7 @@ static unsigned int _tio_csize(const struct termios *tio)
return csize; return csize;
} }
static unsigned int _tio_stopb(const struct termios *tio) static unsigned int _tio_stopb(const struct termios2 *tio)
{ {
unsigned int stopb; unsigned int stopb;
@@ -821,7 +757,7 @@ static unsigned int _tio_stopb(const struct termios *tio)
return stopb; return stopb;
} }
static void _show_termios(const char *txt, const struct termios *tio) static void _show_termios(const char *txt, const struct termios2 *tio)
{ {
DBG("%s: %u%c%u%u %cCTSRTS %cCREAD %cHUPCL %cCLOCAL\n", txt, DBG("%s: %u%c%u%u %cCTSRTS %cCREAD %cHUPCL %cCLOCAL\n", txt,
_tio_baud(tio), pstr[_tio_parity(tio)], _tio_csize(tio), _tio_baud(tio), pstr[_tio_parity(tio)], _tio_csize(tio),
@@ -830,6 +766,16 @@ static void _show_termios(const char *txt, const struct termios *tio)
tio->c_cflag & HUPCL ? '+' : '-', tio->c_cflag & CLOCAL ? '+' : '-'); tio->c_cflag & HUPCL ? '+' : '-', tio->c_cflag & CLOCAL ? '+' : '-');
} }
static void _show_termios2(const char *txt, const struct termios2 *tio)
{
DBG("%s: %u%c%u%u %cCTSRTS %cCREAD %cHUPCL %cCLOCAL\n", txt,
_tio2_baud(tio), pstr[_tio_parity(tio)], _tio_csize(tio),
_tio_stopb(tio),
tio->c_cflag & CRTSCTS ? '+' : '-', tio->c_cflag & CREAD ? '+' : '-',
tio->c_cflag & HUPCL ? '+' : '-', tio->c_cflag & CLOCAL ? '+' : '-');
}
#define CHECK_BUF_IN(sz) \ #define CHECK_BUF_IN(sz) \
if (in_bufsz < sz) do { in_bufsz = sz; goto retry_in; } while(0) if (in_bufsz < sz) do { in_bufsz = sz; goto retry_in; } while(0)
#define CHECK_BUF_OUT(sz) \ #define CHECK_BUF_OUT(sz) \
@@ -841,60 +787,103 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
const void *in_buf, size_t in_bufsz, size_t out_bufsz) const void *in_buf, size_t in_bufsz, size_t out_bufsz)
{ {
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh; ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
struct termios *tio = &tty->tio; struct termios2 *tio = &tty->tio;
struct winsize ws; struct winsize ws;
unsigned int tiocm = 0; unsigned int tiocm, baud;
int mcr; int mcr;
int val; int val;
int res; int res;
unsigned char byte; uint8_t byte;
unsigned int byte4; uint32_t byte4;
DBG("%s: cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__, DBG("%s: cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__,
_ioctl_name(cmd), arg, in_buf, _ioctl_name(cmd), arg, in_buf,
(unsigned int)in_bufsz, (unsigned int)out_bufsz); (unsigned int)in_bufsz, (unsigned int)out_bufsz);
if (ttynvt_param.raw)
{
fuse_reply_ioctl(req, 0, 0, 0);
return;
}
switch (cmd) switch (cmd)
{ {
case TCSETSF: case TCSETSF:
case TCSETSW: case TCSETSW:
case TCSETS: case TCSETS:
CHECK_BUF_IN(sizeof(struct termios)); CHECK_BUF_IN(sizeof(struct termios));
memcpy(tio, in_buf, in_bufsz);
baud = _tio_baud(tio);
goto do_tcset;
case TCSETSF2:
case TCSETSW2:
case TCSETS2:
CHECK_BUF_IN(sizeof(struct termios2));
memcpy(tio, in_buf, in_bufsz);
baud = _tio2_baud(tio);
goto do_tcset;
do_tcset:
/* BEWARE of termios/termios2 buf structure difference! */
if (baud != 0)
{ {
memcpy(tio, in_buf, in_bufsz); byte4 = htonl(baud);
byte4 = htonl(_tio_baud(tio));
telnet_rfc2217_cfg(tty->tn, TNS_SET_BAUDRATE, &byte4, 4); telnet_rfc2217_cfg(tty->tn, TNS_SET_BAUDRATE, &byte4, 4);
byte = _tio_csize(tio);
telnet_rfc2217_cfg(tty->tn, TNS_SET_DATASIZE, &byte, 1);
byte = _tio_parity(tio);
telnet_rfc2217_cfg(tty->tn, TNS_SET_PARITY, &byte, 1);
byte = _tio_stopb(tio);
telnet_rfc2217_cfg(tty->tn, TNS_SET_STOPSIZE, &byte, 1);
if (tio->c_cflag & CRTSCTS)
{
tty->cmcr |= TIOCM_RTS;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_CTSRTS);
}
if (!(tty->cmcr & TIOCM_DTR) && _tio_baud(tio) != 0)
{
tty->cmcr |= TIOCM_DTR;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON);
}
_show_termios("TCSETS", tio);
ioctl(tty->fds[FD_SLAVE].fd, cmd, tio);
if (cmd == TCSETSF)
tty->slave_suspended = 0; /* Re-enable polling of Slave */
fuse_reply_ioctl(req, 0, 0, 0);
} }
byte = _tio_csize(tio);
telnet_rfc2217_cfg(tty->tn, TNS_SET_DATASIZE, &byte, 1);
byte = _tio_parity(tio);
telnet_rfc2217_cfg(tty->tn, TNS_SET_PARITY, &byte, 1);
byte = _tio_stopb(tio);
telnet_rfc2217_cfg(tty->tn, TNS_SET_STOPSIZE, &byte, 1);
if (tio->c_cflag & CRTSCTS)
{
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;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_NOFLOW);
}
if (!(tty->cmcr & TIOCM_DTR) && baud != 0)
{
tty->cmcr |= TIOCM_DTR;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON);
}
switch (cmd)
{
case TCSETSF:
case TCSETSW:
case TCSETS:
_show_termios("TCSETS", tio);
break;
case TCSETSF2:
case TCSETSW2:
case TCSETS2:
_show_termios2("TCSETS2", tio);
break;
}
ioctl(tty->fds[FD_SLAVE].fd, cmd, tio);
if (cmd == TCSETSF || cmd == TCSETSF2)
tty->slave_suspended = 0; /* Re-enable polling of Slave */
fuse_reply_ioctl(req, 0, 0, 0);
break; break;
case TCGETS: case TCGETS:
CHECK_BUF_OUT(sizeof(struct termios)); CHECK_BUF_OUT(sizeof(struct termios));
@@ -902,6 +891,12 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
_show_termios("TCGETS", tio); _show_termios("TCGETS", tio);
fuse_reply_ioctl(req, 0, tio, sizeof(struct termios)); fuse_reply_ioctl(req, 0, tio, sizeof(struct termios));
break; break;
case TCGETS2:
CHECK_BUF_OUT(sizeof(struct termios2));
ioctl(tty->fds[FD_SLAVE].fd, TCGETS2, tio);
_show_termios2("TCGETS2", tio);
fuse_reply_ioctl(req, 0, tio, sizeof(struct termios2));
break;
case TIOCINQ: /* Get the number of bytes in the input case TIOCINQ: /* Get the number of bytes in the input
* buffer */ * buffer */
CHECK_BUF_OUT(sizeof(int)); CHECK_BUF_OUT(sizeof(int));
@@ -915,12 +910,13 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
fuse_reply_ioctl(req, 0, &val, sizeof(int)); fuse_reply_ioctl(req, 0, &val, sizeof(int));
break; break;
case TCFLSH: case TCFLSH:
switch ((size_t) arg) switch ((size_t)arg)
{ {
default: default:
case TCIFLUSH: case TCIFLUSH:
byte = 1; byte = 1;
tty->slave_suspended = 0; /* Re-enable polling of Slave */ tty->slave_suspended = 0; /* Re-enable polling of Slave */
tty->pollin = 0;
break; break;
case TCOFLUSH: case TCOFLUSH:
byte = 2; byte = 2;
@@ -930,6 +926,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
byte = 3; byte = 3;
tty->slave_suspended = 0; /* Re-enable polling of Slave */ tty->slave_suspended = 0; /* Re-enable polling of Slave */
tty->pollout = 1; tty->pollout = 1;
tty->pollin = 0;
break; break;
} }
telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &byte, 1); telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &byte, 1);
@@ -945,7 +942,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
break; break;
case TIOCMBIS: case TIOCMBIS:
CHECK_BUF_IN(sizeof(int)); CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf; tiocm = *(const unsigned int *)in_buf;
fuse_reply_ioctl(req, 0, 0, 0); fuse_reply_ioctl(req, 0, 0, 0);
DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm);
if (tiocm & TIOCM_RTS) if (tiocm & TIOCM_RTS)
@@ -961,7 +958,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
goto show_mctl; goto show_mctl;
case TIOCMBIC: case TIOCMBIC:
CHECK_BUF_IN(sizeof(int)); CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf; tiocm = *(const unsigned int *)in_buf;
fuse_reply_ioctl(req, 0, 0, 0); fuse_reply_ioctl(req, 0, 0, 0);
DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm);
if (tiocm & TIOCM_RTS) if (tiocm & TIOCM_RTS)
@@ -977,7 +974,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
goto show_mctl; goto show_mctl;
case TIOCMSET: case TIOCMSET:
CHECK_BUF_IN(sizeof(int)); CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf; tiocm = *(const unsigned int *)in_buf;
fuse_reply_ioctl(req, 0, 0, 0); fuse_reply_ioctl(req, 0, 0, 0);
DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm);
tty->cmcr &= ~(TIOCM_RTS | TIOCM_DTR); tty->cmcr &= ~(TIOCM_RTS | TIOCM_DTR);
@@ -1007,7 +1004,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
break; break;
case TIOCSWINSZ: case TIOCSWINSZ:
CHECK_BUF_IN(sizeof(struct winsize)); 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); res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSWINSZ, &ws);
if (res < 0) if (res < 0)
{ {
@@ -1031,7 +1028,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
break; break;
case TIOCSETD: case TIOCSETD:
CHECK_BUF_IN(sizeof(int)); CHECK_BUF_IN(sizeof(int));
val = *(int *)in_buf; val = *(const int *)in_buf;
res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSETD, &val); res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSETD, &val);
if (res < 0) if (res < 0)
fuse_reply_err(req, errno); fuse_reply_err(req, errno);
@@ -1055,6 +1052,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
fuse_reply_ioctl(req, 0, &val, sizeof(int)); fuse_reply_ioctl(req, 0, &val, sizeof(int));
break; break;
default: default:
DBG("ERROR: Unsupported IOCTL\n");
fuse_reply_err(req, ENOSYS); fuse_reply_err(req, ENOSYS);
break; break;
@@ -1107,6 +1105,38 @@ ttynvt_poll(fuse_req_t req, struct fuse_file_info *info,
fuse_reply_poll(req, revents); fuse_reply_poll(req, revents);
} }
static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
DBG("%s\n", __func__);
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);
_fd_close(tty->fds[FD_NET].fd);
_fd_close(tty->fds[FD_MASTER].fd);
_fd_close(tty->fds[FD_SLAVE].fd);
if (ttynvt_param.close_delay_us)
_ttynvt_sleep_us(ttynvt_param.close_delay_us);
if (tty->ph)
fuse_pollhandle_destroy(tty->ph);
_ttynvt_ctx_destroy(tty);
fuse_reply_err(req, 0);
nvt_log(LOG_INFO, "Disconnected from server: %s\n", ttynvt_param.server);
pthread_mutex_unlock(&access_lock);
}
static const struct cuse_lowlevel_ops ttynvt_op = { static const struct cuse_lowlevel_ops ttynvt_op = {
.init = ttynvt_init, .init = ttynvt_init,
.open = ttynvt_open, .open = ttynvt_open,
@@ -1128,6 +1158,9 @@ static const struct fuse_opt ttynvt_opts[] = {
TTYNET_OPT("--min=%u", minor, 1), TTYNET_OPT("--min=%u", minor, 1),
TTYNET_OPT("-n %s", dev_name, 0), TTYNET_OPT("-n %s", dev_name, 0),
TTYNET_OPT("--name=%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("-S %s", server, 0),
TTYNET_OPT("--server=%s", server, 0), TTYNET_OPT("--server=%s", server, 0),
FUSE_OPT_KEY("-h", 0), FUSE_OPT_KEY("-h", 0),
@@ -1139,12 +1172,19 @@ static void print_help(void)
{ {
fprintf(stderr, "ttynvt Usage:\n" fprintf(stderr, "ttynvt Usage:\n"
"\t-D level\tEnable debug\n" "\t-D level\tEnable debug\n"
"\t-E\t\tPrint debug to stdout (default is syslog)\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 stderr (default is syslog)\n"
"\t-M major, --maj=major\n" "\t-M major, --maj=major\n"
"\t-m minor, --min=minor\n" "\t-m minor, --min=minor\n"
"\t-n name, --name=name (default: ttyNVT0)\n" "\t-n name, --name=name (default: ttyNVT0)\n"
"\t\tDevice will be created in /dev/$name.\n" "\t\tDevice will be created in /dev/$name.\n"
"\t-S server, --server=host:port\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"
"\t\t\t[ipv6addr]:port (connect via IPv6)\n"
"\t\t\tunix:/path/to.socket (connect via UNIX domain socket)\n");
} }
static int static int
@@ -1179,6 +1219,10 @@ int main(int argc, char *argv[])
return 1; return 1;
} }
nvt_log_level(ttynvt_param.quiet, ttynvt_param.debug);
if (ttynvt_param.logstd)
nvt_log_dest(NVT_LOG_STDERR);
if (!ttynvt_param.dev_name) if (!ttynvt_param.dev_name)
ttynvt_param.dev_name = "ttyNVT0"; ttynvt_param.dev_name = "ttyNVT0";
@@ -1188,22 +1232,18 @@ int main(int argc, char *argv[])
return 1; 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); 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 = { struct cuse_info ci = {
.dev_major = ttynvt_param.major, .dev_major = ttynvt_param.major,
.dev_minor = ttynvt_param.minor, .dev_minor = ttynvt_param.minor,
@@ -1221,14 +1261,14 @@ int main(int argc, char *argv[])
switch (cpid) switch (cpid)
{ {
case -1: case -1:
_log(LOG_ERR, "fork(): %m\n"); nvt_log(LOG_ERR, "fork(): %m\n");
return -1; return -1;
case 0: /* Child */ case 0: /* Child */
DBG("Child proceeding...\n"); DBG("Child proceeding...\n");
rc = cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL); rc = cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL);
if (rc != 0) if (rc != 0)
_log(LOG_ERR, "ttynvt failed rc=%d\n", rc); nvt_log(LOG_ERR, "ttynvt failed rc=%d\n", rc);
else else
DBG("Child exited\n"); DBG("Child exited\n");
return rc; return rc;
-123
View File
@@ -1,123 +0,0 @@
/*
* 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 */
+48
View File
@@ -0,0 +1,48 @@
# Unit test makefile
#
.NOTPARALLEL:
noinst_PROGRAMS = $(GTESTS)
GTEST_LIBS = -lgtest -lstdc++
GTESTS = test_misc test_nvttest
AM_CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter
AM_CFLAGS += $(CFLAGS_ASAN)
AM_CXXFLAGS = $(AM_CFLAGS)
AM_CPPFLAGS = -I $(top_builddir)
AM_CPPFLAGS += -D TOP_SRC_DIR='"$(top_srcdir)"' -D TOP_BLD_DIR='"$(top_builddir)"'
AM_CPPFLAGS += -D BUILD_TEST=1
LIBS += $(GTEST_LIBS)
TEST_COMMON = test.cpp test.h
test_misc_SOURCES = $(TEST_COMMON) test_misc.cpp
test_misc_LDADD = $(LIBS)
test_nvttest_SOURCES = $(TEST_COMMON) test_nvttest.cpp
test_nvttest_LDADD = $(LIBS)
TESTS_RUN = $(addprefix run-, $(GTESTS))
VG_PROG = valgrind --leak-check=full
all-local: run
.PHONY: run $(TESTS_RUN)
run: $(TESTS_RUN)
$(TESTS_RUN): run-%: %
# $(TEST_ENV) ./.libs/$* $(RUN_OPTS)
$(TEST_ENV) ./$* $(RUN_OPTS)
TESTS_RUN_VG = $(addprefix run-vg-, $(GTESTS))
.PHONY: run-vg $(TESTS_RUN_VG)
run-vg: $(TESTS_RUN_VG)
$(TESTS_RUN_VG): run-vg-%: %
# $(TEST_ENV) $(VG_PROG) ./.libs/$* $(RUN_OPTS)
$(TEST_ENV) $(VG_PROG) ./$* $(RUN_OPTS)
+70
View File
@@ -0,0 +1,70 @@
/*
* General test stuff
*
* - The main() function
* - Some printing functions
*/
#include "test.h"
int debug = 0;
static bool ttyout = false;
int main(int argc, char **argv)
{
const char *s;
ttyout = isatty(STDOUT_FILENO);
::testing::InitGoogleTest(&argc, argv);
for (argc--, argv++; argc > 0; argc--, argv++)
{
s = argv[0];
if (*s++ != '-')
break;
again:
switch (*s++)
{
case 'd':
debug++;
goto again;
}
}
return RUN_ALL_TESTS();
}
#include <stdarg.h>
void _pr_text(const char *col, const char *fmt, va_list args)
{
char fmtx[1024];
if (ttyout)
snprintf(fmtx, sizeof(fmtx), "%s[ ] - %s%s\n",
col, fmt, COL_RST);
else
snprintf(fmtx, sizeof(fmtx), "[ ] - %s\n", fmt);
fmt = fmtx;
vprintf(fmt, args);
}
void pr_text(const char *col, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
_pr_text(col, fmt, args);
va_end(args);
}
void pr_info(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
_pr_text(COL_YEL, fmt, args);
va_end(args);
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef TEST_H
#define TEST_H 1
#include <gtest/gtest.h>
#include "config.h"
#define D(...) do{ if (debug) printf(__VA_ARGS__); }while(0)
#define D2(...) do{ if (debug > 1) printf(__VA_ARGS__); }while(0)
extern int debug;
#define COL_RST "\x1B[0m"
#define COL_RED "\x1B[31m"
#define COL_GRN "\x1B[32m"
#define COL_YEL "\x1B[33m"
#define COL_BLU "\x1B[34m"
#define COL_MAG "\x1B[35m"
#define COL_CYN "\x1B[36m"
#define COL_WHT "\x1B[37m"
void pr_text(const char *col, const char *fmt, ...);
void pr_info(const char *fmt, ...);
#endif /* TEST_H */
+6
View File
@@ -0,0 +1,6 @@
#include "test.h"
TEST(Misc, dummy)
{
EXPECT_EQ(1, 1);
}
+216
View File
@@ -0,0 +1,216 @@
#include "test.h"
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#define NVT_NAME "ttyNVT99"
#define NVT_DEV "/dev/" NVT_NAME
#define NVT_SRV TOP_BLD_DIR "/nvtsrv/nvtsrv"
#define NVT_TEST TOP_BLD_DIR "/nvttest/nvttest"
#define NVT_OPTS "-D" NVT_NAME
#define LAUNCH_NVTTEST(...) proc_launch(NVT_TEST, NVT_OPTS, __VA_ARGS__)
pid_t proc_spawn(char **argv)
{
pid_t pid;
char buf[1024];
for (int i = 0, len = 0; argv[i]; i++)
len += snprintf(buf + len, sizeof(buf) - len, "%s ", argv[i]);
pr_info("Launching: %s", buf);
pid = fork();
if (pid > 0)
{
pr_info("... ok, pid = %d", pid);
return pid;
}
// Child
execv(argv[0], argv);
pr_info("Launch failed: %m");
EXPECT_EQ(1, 0);
exit(1);
}
void proc_waitfor(const char *name, pid_t pid)
{
int err, wstatus;
pr_info("Wait for %s (pid %d) to finish", name, pid);
wstatus = 0;
err = waitpid(pid, &wstatus, 0);
if (err > 0)
pr_info("... ok, status = %x", wstatus);
else
pr_info("... error: %m");
EXPECT_GT(err, 0);
EXPECT_EQ(WEXITSTATUS(wstatus), 0);
}
static pid_t proc_launch(const char *prog, ...)
{
char *argv[16], *arg;
int i;
pid_t pid;
va_list args;
va_start(args, prog);
arg = (char *)prog;
i = 0;
argv[i++] = arg;
for (; arg != NULL;)
{
arg = va_arg(args, char *);
argv[i++] = arg;
}
va_end(args);
pid = proc_spawn(argv);
return pid;
}
bool ttynvt_check()
{
struct stat st;
bool ok;
ok = stat(NVT_DEV, &st) == 0 && S_ISCHR(st.st_mode);
if (!ok)
{
fprintf(stderr, "\n%s"
"************************************************************\n"
"*** Invalid device file: '%s'\n"
"*** Ensure that ttynvt is launched correcly, e.g.:\n"
"# ttynvt -d -E -S localhost:1234 -n %s\n"
"*** ttynvt must run as root, and the device and server names\n"
"*** must be as shown above.\n"
"************************************************************\n"
"%s\n", COL_CYN, NVT_DEV, NVT_NAME, COL_RST);
}
return ok;
}
TEST(Test1, t1)
{
int err;
pid_t pid_srv, pid;
ASSERT_TRUE(ttynvt_check());
pid_srv = proc_launch(NVT_SRV, "-de", "localhost:1234", NULL);
ASSERT_GT(pid_srv, 0);
usleep(100000);
pid = LAUNCH_NVTTEST(NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
pr_info("Terminate nvtsrv");
err = kill(pid_srv, SIGTERM);
EXPECT_EQ(err, 0);
proc_waitfor("nvtsrv", pid_srv);
}
/* Using fixture */
/**INDENT-OFF**/
class Test2:public::testing::Test {
pid_t pid_srv;
protected:
Test2() { }
virtual ~Test2() { }
virtual void SetUp() {
// Ensure ttynvt is running
ASSERT_TRUE(ttynvt_check());
// NB! Quiet nvtsrv
pid_srv = proc_launch(NVT_SRV, "-qe", "localhost:1234", NULL);
ASSERT_GT(pid_srv, 0);
usleep(100000);
}
virtual void TearDown() {
int err;
pr_info("Terminate nvtsrv");
err = kill(pid_srv, SIGTERM);
EXPECT_EQ(err, 0);
proc_waitfor("nvtsrv", pid_srv);
}
};
/**INDENT-ON**/
TEST_F(Test2, t1)
{
pid_t pid;
pid = LAUNCH_NVTTEST(NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple threads
TEST_F(Test2, t2)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-t5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple loops
TEST_F(Test2, t3)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-n5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple threads and loops
TEST_F(Test2, t4)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-n5", "-t5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}