/* * Copyright (c) 2018 Lars Thrane A/S * SPDX-License-Identifier: GPL-2.0 */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/nvt_log.h" #include "lib/nvt_socket.h" #include "telnet.h" #define NET_BUF_SIZE 1024 #define TMP_BUF_SIZE 1024 #define FD_NET 0 #define FD_MASTER 1 #define FD_SLAVE 2 static struct ttynvt_param { unsigned int major; unsigned int minor; const char *dev_name; const char *server; unsigned int quiet; unsigned int debug; int logstd; int raw; /* No telnet processing */ const char *close_delay; /* Derived */ unsigned int close_delay_us; /* Picked up */ int foreground; } ttynvt_param; typedef struct { pthread_mutex_t tty_lock; pthread_mutex_t poll_lock; pthread_mutex_t read_lock; pthread_mutex_t write_lock; pthread_mutex_t ioctl_lock; pthread_t ptid_poll; pthread_t ptid_read; struct fuse_pollhandle *ph; volatile int rel_pending; volatile int slave_suspended; /* Waiting for application to read data */ volatile int master_suspended; /* Server is not ready to receive data */ struct pollfd fds[3]; char net_buf[NET_BUF_SIZE]; int net_cnt; struct termios2 tio; int cmcr; /* Commanded by ioctl */ volatile int smcr; /* Status from modem */ int smcr_last; volatile int pollin; volatile int pollout; volatile int error; int epipe; tn_ctx_t *tn; int seq; struct ttynvt_cli_s *cli; } ttynvt_t; static ttynvt_t *ptty = NULL; typedef struct ttynvt_cli_s { int id; ttynvt_t *tty; struct ttynvt_cli_s *next; } ttynvt_cli_t; static pthread_mutex_t access_lock = PTHREAD_MUTEX_INITIALIZER; static volatile char _is_interrupted = 0; static void sig_handler(int sig) { DBG("Interruped, signal = %d\n", sig); _is_interrupted = 1; if (sig != SIGUSR2) exit(0); } static void set_signal_handler(void) { struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sig_handler; sigemptyset(&(sa.sa_mask)); sa.sa_flags = 0; sigaction(SIGUSR2, &sa, NULL); sigaction(SIGTERM, &sa, NULL); } static void ttynvt_interrupted(fuse_req_t req, void *data) { pthread_t thread = (pthread_t) data; pthread_kill(thread, SIGUSR2); } static void _ttynvt_sleep_us(unsigned int tus) { struct timespec ts; ts.tv_sec = tus / 1000000; tus -= ts.tv_sec * 1000000; ts.tv_nsec = tus * 1000; while (nanosleep(&ts, &ts)) ; } static int _check_dev(void) { int rc; char dev_nbuf[128]; struct stat st; snprintf(dev_nbuf, sizeof(dev_nbuf), "/dev/%s", ttynvt_param.dev_name); rc = stat(dev_nbuf, &st); DBG("Checking for %s: %d\n", dev_nbuf, rc); return rc; } static void _fd_close(int fd) { if (fd >= 0) close(fd); } static void _notify(ttynvt_t * tty) { pthread_mutex_lock(&tty->poll_lock); if (tty->ph) { fuse_lowlevel_notify_poll(tty->ph); fuse_pollhandle_destroy(tty->ph); tty->ph = NULL; } pthread_mutex_unlock(&tty->poll_lock); } static void _update_notify(ttynvt_t * tty, struct fuse_pollhandle *ph) { pthread_mutex_lock(&tty->poll_lock); struct fuse_pollhandle *tmp_ph = tty->ph; tty->ph = ph; pthread_mutex_unlock(&tty->poll_lock); if (tmp_ph) fuse_pollhandle_destroy(tmp_ph); } static int _net_read_check_exit(ttynvt_t * tty) { if ((tty->tio.c_cflag & CLOCAL) == 0 && (tty->smcr_last & TIOCM_CD) != 0 && (tty->smcr & TIOCM_CD) == 0) { /* CLOCAL is not set (i.e. do not ignore modem control lines) AND * DCD dropped */ DBG("GOT DCD DROP\n"); close(tty->fds[FD_MASTER].fd); tty->fds[FD_MASTER].fd = -1; return 1; } return 0; } static void *_read_net(void *arg) { ttynvt_t *tty = (ttynvt_t *) arg; char buf[TMP_BUF_SIZE]; int res; pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); DBG2("%s: net_cnt=%d\n", __func__, tty->net_cnt); while (!tty->rel_pending) { tty->fds[FD_NET].revents = tty->fds[FD_MASTER].revents = tty->fds[FD_SLAVE].revents = 0; tty->fds[FD_SLAVE].events = tty->slave_suspended ? POLLPRI : POLLIN; tty->fds[FD_MASTER].events = tty->master_suspended ? POLLPRI : POLLIN; res = poll(tty->fds, 3, -1); DBG2("%s: res=%d events-N/M/S=%x:%x/%x:%x/%x:%x SS=%d MS=%d\n", __func__, res, tty->fds[FD_NET].events, tty->fds[FD_NET].revents, tty->fds[FD_MASTER].events, tty->fds[FD_MASTER].revents, tty->fds[FD_SLAVE].events, tty->fds[FD_SLAVE].revents, tty->slave_suspended, tty->master_suspended); if (res < 0) { if (errno == EINTR) continue; break; } if (tty->rel_pending) break; if (tty->fds[FD_NET].revents & POLLIN) { res = read(tty->fds[FD_NET].fd, tty->net_buf + tty->net_cnt, NET_BUF_SIZE - tty->net_cnt); if (res < 0) { nvt_log(LOG_ERR, "net read error: %m\n"); break; } if (res == 0) { nvt_log(LOG_NOTICE, "Connection closed by remote host\n"); break; } tty->net_cnt += res; DBG2_BUF("Serv in A", tty->net_buf, tty->net_cnt); res = telnet_rx(tty->tn, tty->net_buf, &tty->net_cnt); DBG2_BUF("Serv in B", tty->net_buf, tty->net_cnt); if (tty->net_cnt > 0 && res == 0) { DBG2_BUF("TtyM out ", tty->net_buf, tty->net_cnt); res = write(tty->fds[FD_MASTER].fd, tty->net_buf, tty->net_cnt); if (res < 0) { nvt_log(LOG_ERR, "master write error: %m\n"); break; } tty->net_cnt = 0; } if (_net_read_check_exit(tty)) break; } if ((tty->fds[FD_MASTER].revents & POLLIN) && !tty->master_suspended) { res = read(tty->fds[FD_MASTER].fd, buf, TMP_BUF_SIZE); DBG("%s: read res=%d pollout=%d\n", __func__, res, tty->pollout); if (res < 0) { nvt_log(LOG_ERR, "master read error: %m\n"); break; } if (tty->pollout == 0) { tty->pollout = 1; _notify(tty); } DBG2_BUF("TtyM in ", buf, res); telnet_tx(tty->tn, buf, res); } if (tty->fds[FD_SLAVE].revents & POLLIN) { tty->pollin = 1; tty->slave_suspended = 1; /* Disable polling of Slave */ _notify(tty); } } DBG2("%s: done\n", __func__); if (tty->ptid_read) pthread_kill(tty->ptid_read, SIGUSR2); tty->error = 1; _notify(tty); return 0; } static int _srv_write(void *cctx, const void *buf, int len) { ttynvt_t *tty = cctx; int res; DBG2_BUF("Serv out ", buf, len); res = write(tty->fds[FD_NET].fd, buf, len); DBG2("%s: fd=%d: len=%u: res=%d\n", __func__, tty->fds[FD_NET].fd, len, res); return res; } static int _srv_read(void *cctx, int timeout) { ttynvt_t *tty = cctx; int res; DBG2("%s: net_cnt=%d\n", __func__, tty->net_cnt); while (1) { res = poll(&tty->fds[FD_NET], 1, timeout); DBG("%s: res=%d events-N=%#x\n", __func__, res, tty->fds[FD_NET].revents); if (res <= 0) return res; if (tty->fds[FD_NET].revents & POLLIN) { res = read(tty->fds[FD_NET].fd, tty->net_buf + tty->net_cnt, NET_BUF_SIZE - tty->net_cnt); DBG2("%s: fd=%d: res=%d net_cnt=%d\n", __func__, tty->fds[FD_NET].fd, res, tty->net_cnt); if (res <= 0) return res; tty->net_cnt += res; DBG2_BUF("Serv in a", tty->net_buf, tty->net_cnt); res = telnet_rx(tty->tn, tty->net_buf, &tty->net_cnt); DBG2_BUF("Serv in b", tty->net_buf, tty->net_cnt); DBG2("%s: res=%d net_cnt=%d\n", __func__, res, tty->net_cnt); if (res == 0) break; } } return 1; } static void _modem_status_cb(void *cctx, int status) { ttynvt_t *tty = cctx; int mcr = 0; DBG("MCR: CTS=%c DSR=%c RI=%c DCD=%c\n", status & TNS_STATE_CTS ? '1' : '0', status & TNS_STATE_DSR ? '1' : '0', status & TNS_STATE_RI ? '1' : '0', status & TNS_STATE_DCD ? '1' : '0'); if (status & TNS_STATE_DCD) mcr |= TIOCM_CD; if (status & TNS_STATE_RI) mcr |= TIOCM_RI; if (status & TNS_STATE_DSR) mcr |= TIOCM_DSR; if (status & TNS_STATE_CTS) mcr |= TIOCM_CTS; 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, tty->master_suspended); pthread_mutex_lock(&tty->tty_lock); tty->smcr_last = tty->smcr; tty->smcr = mcr; pthread_mutex_unlock(&tty->tty_lock); } static void ttynvt_init(void *userdata, struct fuse_conn_info *conn) { set_signal_handler(); } static ttynvt_t *_ttynvt_ctx_create(void) { ttynvt_t *tty; int i; tty = calloc(1, sizeof(ttynvt_t)); if (!tty) return NULL; tty->tn = telnet_ctx_init(tty, _srv_write, _srv_read, _modem_status_cb); if (!tty->tn) { free(tty); return NULL; } for (i = 0; i < 3; i++) tty->fds[i].fd = -1; tty->pollout = 1; return tty; } static void _ttynvt_ctx_destroy(ttynvt_t * tty) { if (tty) free(tty->tn); free(tty); } static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info) { ttynvt_t *tty = ptty; ttynvt_cli_t *tty_cli; char *slave_name; struct termios ios; int fd; int res; int n; tty_cli = calloc(1, sizeof(ttynvt_cli_t)); if (!tty_cli) { fuse_reply_err(req, ENOMEM); return; } pthread_mutex_lock(&access_lock); if (tty == NULL) tty = _ttynvt_ctx_create(); if (!tty) { fuse_reply_err(req, ENOMEM); free(tty_cli); pthread_mutex_unlock(&access_lock); return; } tty_cli->id = tty->seq++; tty_cli->tty = tty; tty_cli->next = tty->cli; tty->cli = tty_cli; nvt_log(LOG_INFO, "Client open (%d)\n", tty_cli->id); if (ptty) goto out; ptty = tty; nvt_log(LOG_INFO, "Connecting to server: %s\n", ttynvt_param.server); fd = socket_create_client(ttynvt_param.server); if (fd < 0) { errno = EHOSTUNREACH; goto open_err; } tty->fds[FD_NET].fd = fd; tty->fds[FD_NET].events = POLLIN; tty->net_cnt = 0; if ((fd = open("/dev/ptmx", O_RDWR)) < 0) /* Open master */ goto open_err; tty->fds[FD_MASTER].fd = fd; tty->fds[FD_MASTER].events = POLLIN; memset(&ios, 0, sizeof(ios)); ios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); ios.c_oflag &= ~OPOST; ios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); ios.c_cflag &= ~(CSIZE | PARENB); ios.c_cflag |= CS8; if (ioctl(fd, TCSETS, &ios) < 0) goto open_err; grantpt(fd); /* Change permission of slave */ unlockpt(fd); /* Unlock slave */ slave_name = ptsname(fd); /* Get name of slave */ if ((fd = open(slave_name, O_RDWR | O_NOCTTY)) < 0) /* Open slave */ goto open_err; ioctl(fd, TCGETS, &tty->tio); tty->tio.c_cflag &= ~(CSIZE | PARENB); tty->tio.c_cflag |= B115200 | CS8 | CREAD | CLOCAL; ioctl(fd, TCSETS, &tty->tio); tty->fds[FD_SLAVE].fd = fd; tty->fds[FD_SLAVE].events = POLLIN; res = telnet_open(tty->tn, ttynvt_param.raw ? TN_MODE_RAW : TN_MODE_TELNET); if (res < 0) { errno = EPROTO; goto open_err; } if ((res = pthread_mutex_init(&tty->tty_lock, NULL) < 0) || (res = pthread_mutex_init(&tty->poll_lock, NULL) < 0) || (res = pthread_mutex_init(&tty->ioctl_lock, NULL) < 0) || (res = pthread_mutex_init(&tty->read_lock, NULL) < 0) || (res = pthread_mutex_init(&tty->write_lock, NULL) < 0) || (res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty))) { errno = res; goto open_err; } nvt_log(LOG_INFO, "Connected to server: %s\n", ttynvt_param.server); out: info->fh = (uintptr_t) tty_cli; info->nonseekable = 1; info->direct_io = 1; fuse_reply_open(req, info); pthread_mutex_unlock(&access_lock); return; open_err: for (n = 0; n < 3; n++) _fd_close(tty->fds[n].fd); free(tty_cli); _ttynvt_ctx_destroy(tty); fuse_reply_err(req, errno); nvt_log(LOG_ERR, "Connection to server: %s failed: %m\n", ttynvt_param.server); pthread_mutex_unlock(&access_lock); } static void ttynvt_read(fuse_req_t req, size_t size, off_t off, struct fuse_file_info *info) { ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh; ttynvt_t *tty = tty_cli->tty; char buf[TMP_BUF_SIZE]; int res, nr; pthread_mutex_lock(&tty->read_lock); DBG2("%s (%d)\n", __func__, tty_cli->id); if (tty->error) { DBG2("%s: tty->error=%d\n", __func__, tty->error); if (tty->epipe == 0) { nvt_log(LOG_WARNING, "Rx EPIPE\n"); tty->epipe = 1; fuse_reply_buf(req, NULL, 0); } else fuse_reply_err(req, EBADFD); goto out; } if (size > TMP_BUF_SIZE) size = TMP_BUF_SIZE; tty->ptid_read = pthread_self(); fuse_req_interrupt_func(req, ttynvt_interrupted, (void *)tty->ptid_read); res = _is_interrupted ? -2 : read(tty->fds[FD_SLAVE].fd, buf, size); _is_interrupted = 0; tty->ptid_read = 0; tty->slave_suspended = 0; /* Enable polling of Slave */ fuse_req_interrupt_func(req, NULL, NULL); if (res < (int)size) { tty->pollin = 0; } else { nr = 0; ioctl(tty->fds[FD_SLAVE].fd, FIONREAD, &nr); tty->pollin = nr > 0; } if (res < 0) { DBG2("%s: error: %m\n", __func__); fuse_reply_err(req, errno); goto out; } DBG2("%s (%d): fd=%d: sz=%u/%u: '%.*s'\n", __func__, tty_cli->id, tty->fds[FD_SLAVE].fd, res, (int)size, res, buf); fuse_reply_buf(req, buf, res); out: pthread_mutex_unlock(&tty->read_lock); } static void ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off, struct fuse_file_info *info) { ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh; ttynvt_t *tty = tty_cli->tty; int res; pthread_mutex_lock(&tty->write_lock); DBG2("%s (%d): fd=%d: sz=%u: '%.*s'\n", __func__, tty_cli->id, tty->fds[FD_SLAVE].fd, (int)size, (int)size, data); if (size == 0) { fuse_reply_write(req, 0); goto out; } if (tty->error) { DBG2("%s: tty->error=%d\n", __func__, tty->error); fuse_reply_err(req, EPIPE); goto out; } res = write(tty->fds[FD_SLAVE].fd, data, size); if (res < 0) { DBG2("%s: error: %m\n", __func__); tty->error = 1; fuse_reply_err(req, errno); goto out; } else if (res != (int)size) { DBG("%s: caution: tty->pollout set to 0\n", __func__); tty->pollout = 0; } fuse_reply_write(req, res); out: pthread_mutex_unlock(&tty->write_lock); } static int baudrate(speed_t speed) { /**INDENT-OFF**/ return speed == B50 ? 50 : speed == B75 ? 75 : speed == B110 ? 110 : speed == B134 ? 134 : speed == B150 ? 150 : speed == B200 ? 200 : speed == B300 ? 300 : speed == B600 ? 600 : speed == B1200 ? 1200 : speed == B1800 ? 1800 : speed == B2400 ? 2400 : speed == B4800 ? 4800 : speed == B9600 ? 9600 : speed == B19200 ? 19200 : speed == B38400 ? 38400 : speed == B57600 ? 57600 : speed == B115200 ? 115200 : speed == B230400 ? 230400 : 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' }; #define CASE(name) case name: return #name static const char *_ioctl_name(int cmd) { static char buf[16]; switch (cmd) { default: snprintf(buf, sizeof(buf), "0x%x", cmd); return buf; CASE(TCSETSF); CASE(TCSETSF2); CASE(TCSETSW); CASE(TCSETSW2); CASE(TCSETS); CASE(TCSETS2); CASE(TCGETS); CASE(TCGETS2); CASE(TIOCINQ); CASE(TIOCOUTQ); CASE(TCFLSH); CASE(TIOCMGET); CASE(TIOCMBIS); CASE(TIOCMBIC); CASE(TIOCMSET); CASE(TCSBRK); CASE(TIOCGWINSZ); CASE(TIOCSWINSZ); /* Controlling terminal */ CASE(TIOCSCTTY); CASE(TIOCNOTTY); /* Exclusive mode */ CASE(TIOCEXCL); CASE(TIOCGEXCL); CASE(TIOCNXCL); /* Line discipline */ CASE(TIOCGETD); CASE(TIOCSETD); /*PPP support */ CASE(PPPIOCGCHAN); } } static unsigned int _tio_parity(const struct termios2 *tio) { unsigned int par; if ((tio->c_cflag & PARENB) && (tio->c_iflag & IGNPAR)) par = 1; #ifdef CMSPAR else if ((tio->c_cflag & PARENB) && (tio->c_iflag & CMSPAR)) { if (tio->c_cflag & PARODD) par = 4; else par = 5; } #endif else if (tio->c_cflag & PARENB) par = 3; else par = 1; return par; } static unsigned int _tio_baud(const struct termios2 *tio) { return baudrate(tio->c_cflag & (CBAUD | CBAUDEX)); } 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; if ((tio->c_cflag & CSIZE) == CS5) csize = 5; else if ((tio->c_cflag & CSIZE) == CS6) csize = 6; else if ((tio->c_cflag & CSIZE) == CS7) csize = 7; else csize = 8; return csize; } static unsigned int _tio_stopb(const struct termios2 *tio) { unsigned int stopb; if (tio->c_cflag & CSTOPB) stopb = 2; else stopb = 1; return stopb; } static void _show_termios(const char *txt, const struct termios2 *tio) { DBG("%s: %u%c%u%u %cCTSRTS %cCREAD %cHUPCL %cCLOCAL\n", txt, _tio_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 ? '+' : '-'); } 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) \ if (in_bufsz < sz) do { in_bufsz = sz; goto retry_in; } while(0) #define CHECK_BUF_OUT(sz) \ if (out_bufsz < sz) do { out_bufsz = sz; goto retry_out; } while(0) static void ttynvt_ioctl(fuse_req_t req, int cmd, void *arg, struct fuse_file_info *info, unsigned int flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz) { ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh; ttynvt_t *tty = tty_cli->tty; struct termios2 *tio = &tty->tio; struct winsize ws; unsigned int tiocm, baud; int mcr; int val; int res; uint8_t byte; uint32_t byte4; pthread_mutex_lock(&tty->ioctl_lock); DBG("%s (%d): cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__, tty_cli->id, _ioctl_name(cmd), arg, in_buf, (unsigned int)in_bufsz, (unsigned int)out_bufsz); if (ttynvt_param.raw) { fuse_reply_ioctl(req, 0, 0, 0); goto out; } switch (cmd) { case TCSETSF: case TCSETSW: case TCSETS: 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! */ byte4 = htonl(baud); 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->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; case TCGETS: CHECK_BUF_OUT(sizeof(struct termios)); ioctl(tty->fds[FD_SLAVE].fd, TCGETS, tio); _show_termios("TCGETS", tio); fuse_reply_ioctl(req, 0, tio, sizeof(struct termios)); 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 * buffer */ CHECK_BUF_OUT(sizeof(int)); ioctl(tty->fds[FD_SLAVE].fd, TIOCINQ, &val); fuse_reply_ioctl(req, 0, &val, sizeof(int)); break; case TIOCOUTQ: /* Get the number of bytes in the output * buffer */ CHECK_BUF_OUT(sizeof(int)); ioctl(tty->fds[FD_SLAVE].fd, TIOCOUTQ, &val); fuse_reply_ioctl(req, 0, &val, sizeof(int)); break; case TCFLSH: switch ((size_t)arg) { default: case TCIFLUSH: byte = 1; tty->slave_suspended = 0; /* Re-enable polling of Slave */ tty->pollin = 0; break; case TCOFLUSH: byte = 2; tty->pollout = 1; break; case TCIOFLUSH: byte = 3; tty->slave_suspended = 0; /* Re-enable polling of Slave */ tty->pollout = 1; tty->pollin = 0; break; } telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &byte, 1); ioctl(tty->fds[FD_SLAVE].fd, TCFLSH, arg); fuse_reply_ioctl(req, 0, 0, 0); break; case TIOCMGET: CHECK_BUF_OUT(sizeof(int)); pthread_mutex_lock(&tty->tty_lock); mcr = tty->cmcr | tty->smcr; pthread_mutex_unlock(&tty->tty_lock); fuse_reply_ioctl(req, 0, &mcr, sizeof(int)); break; case TIOCMBIS: CHECK_BUF_IN(sizeof(int)); tiocm = *(const unsigned int *)in_buf; fuse_reply_ioctl(req, 0, 0, 0); DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); if (tiocm & TIOCM_RTS) { tty->cmcr |= TIOCM_RTS; telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_ON); } if (tiocm & TIOCM_DTR) { tty->cmcr |= TIOCM_DTR; telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON); } goto show_mctl; case TIOCMBIC: CHECK_BUF_IN(sizeof(int)); tiocm = *(const unsigned int *)in_buf; fuse_reply_ioctl(req, 0, 0, 0); DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); if (tiocm & TIOCM_RTS) { tty->cmcr &= ~TIOCM_RTS; telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_OFF); } if (tiocm & TIOCM_DTR) { tty->cmcr &= ~TIOCM_DTR; telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_OFF); } goto show_mctl; case TIOCMSET: CHECK_BUF_IN(sizeof(int)); tiocm = *(const unsigned int *)in_buf; fuse_reply_ioctl(req, 0, 0, 0); DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm); tty->cmcr &= ~(TIOCM_RTS | TIOCM_DTR); tty->cmcr |= tiocm & (TIOCM_RTS | TIOCM_DTR); telnet_rfc2217_ctl(tty->tn, (tiocm & TIOCM_DTR) ? TNS_CTL_DTR_ON : TNS_CTL_DTR_OFF); telnet_rfc2217_ctl(tty->tn, (tiocm & TIOCM_RTS) ? TNS_CTL_RTS_ON : TNS_CTL_RTS_OFF); goto show_mctl; show_mctl: DBG("MCR: RTS=%c DTR=%c\n", tty->cmcr & TIOCM_RTS ? '1' : '0', tty->cmcr & TIOCM_DTR ? '1' : '0'); break; case TCSBRK: fuse_reply_ioctl(req, 0, 0, 0); break; case TIOCGWINSZ: CHECK_BUF_OUT(sizeof(struct winsize)); res = ioctl(tty->fds[FD_SLAVE].fd, TIOCGWINSZ, &ws); if (res < 0) { fuse_reply_err(req, errno); goto out; } fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize)); break; case TIOCSWINSZ: CHECK_BUF_IN(sizeof(struct winsize)); ws = *(const struct winsize *)in_buf; res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSWINSZ, &ws); if (res < 0) { fuse_reply_err(req, errno); goto out; } fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize)); break; case TIOCEXCL: ioctl(tty->fds[FD_SLAVE].fd, TIOCEXCL, NULL); fuse_reply_ioctl(req, 0, 0, 0); break; case TIOCGEXCL: CHECK_BUF_OUT(sizeof(int)); ioctl(tty->fds[FD_SLAVE].fd, TIOCGEXCL, &val); fuse_reply_ioctl(req, 0, &val, sizeof(int)); break; case TIOCNXCL: ioctl(tty->fds[FD_SLAVE].fd, TIOCNXCL, NULL); fuse_reply_ioctl(req, 0, 0, 0); break; case TIOCSETD: CHECK_BUF_IN(sizeof(int)); val = *(const int *)in_buf; res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSETD, &val); if (res < 0) fuse_reply_err(req, errno); else fuse_reply_ioctl(req, 0, 0, 0); break; case TIOCGETD: CHECK_BUF_OUT(sizeof(int)); res = ioctl(tty->fds[FD_SLAVE].fd, TIOCGETD, &val); if (res < 0) fuse_reply_err(req, errno); else fuse_reply_ioctl(req, 0, &val, sizeof(int)); break; case PPPIOCGCHAN: CHECK_BUF_OUT(sizeof(int)); res = ioctl(tty->fds[FD_SLAVE].fd, PPPIOCGCHAN, &val); if (res < 0) fuse_reply_err(req, errno); else fuse_reply_ioctl(req, 0, &val, sizeof(int)); break; default: DBG("ERROR: Unsupported IOCTL\n"); fuse_reply_err(req, ENOSYS); break; retry_in: { struct iovec iov = { arg, in_bufsz }; fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); } break; retry_out: { struct iovec iov = { arg, out_bufsz }; fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); } break; } out: pthread_mutex_unlock(&tty->ioctl_lock); } static void ttynvt_poll(fuse_req_t req, struct fuse_file_info *info, struct fuse_pollhandle *ph) { ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh; ttynvt_t *tty = tty_cli->tty; int revents = 0; DBG2("%s (%d): tty->pollin=%d tty->error/epipe=%d/%d\n", __func__, tty_cli->id, tty->pollin, tty->error, tty->epipe); _update_notify(tty, ph); if (tty->pollout) { revents = POLLOUT; } if (tty->pollin) { revents |= POLLIN; } if (tty->error) { if (tty->epipe == 0) revents |= POLLHUP | POLLIN; else revents = 0; } DBG2("%s: revents=%#x\n", __func__, revents); fuse_reply_poll(req, revents); } static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info) { ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh; ttynvt_t *tty = tty_cli->tty; ttynvt_cli_t *tmp, *prev; DBG("%s\n", __func__); pthread_mutex_lock(&access_lock); for (tmp = tty->cli, prev = NULL; tmp; prev = tmp, tmp = tmp->next) if (tmp == tty_cli) { nvt_log(LOG_INFO, "Client release (%d)\n", tmp->id); if (prev) prev->next = tmp->next; else tty->cli = tmp->next; free(tmp); break; } if (tty->cli) goto out; tty->rel_pending = 1; pthread_cancel(tty->ptid_poll); pthread_join(tty->ptid_poll, NULL); pthread_mutex_destroy(&tty->tty_lock); pthread_mutex_destroy(&tty->poll_lock); pthread_mutex_destroy(&tty->ioctl_lock); pthread_mutex_destroy(&tty->read_lock); pthread_mutex_destroy(&tty->write_lock); if (ttynvt_param.close_delay_us) _ttynvt_sleep_us(ttynvt_param.close_delay_us); _fd_close(tty->fds[FD_NET].fd); _fd_close(tty->fds[FD_MASTER].fd); _fd_close(tty->fds[FD_SLAVE].fd); if (tty->ph) fuse_pollhandle_destroy(tty->ph); _ttynvt_ctx_destroy(tty); nvt_log(LOG_INFO, "Disconnected from server: %s\n", ttynvt_param.server); out: fuse_reply_err(req, 0); pthread_mutex_unlock(&access_lock); } static const struct cuse_lowlevel_ops ttynvt_op = { .init = ttynvt_init, .open = ttynvt_open, .read = ttynvt_read, .write = ttynvt_write, .ioctl = ttynvt_ioctl, .poll = ttynvt_poll, .release = ttynvt_release, }; #define TTYNET_OPT(t, p, v) { t, offsetof(struct ttynvt_param, p), v } static const struct fuse_opt ttynvt_opts[] = { TTYNET_OPT("-D %d", debug, 1), TTYNET_OPT("-E", logstd, 1), TTYNET_OPT("-M %u", major, 1), TTYNET_OPT("--maj=%u", major, 1), TTYNET_OPT("-m %u", minor, 1), TTYNET_OPT("--min=%u", minor, 1), TTYNET_OPT("-n %s", dev_name, 0), TTYNET_OPT("--name=%s", dev_name, 0), TTYNET_OPT("-q", quiet, 1), TTYNET_OPT("-r", raw, 1), TTYNET_OPT("-s %s", close_delay, 0), TTYNET_OPT("-S %s", server, 0), TTYNET_OPT("--server=%s", server, 0), FUSE_OPT_KEY("-h", 0), FUSE_OPT_KEY("--help", 0), FUSE_OPT_END }; static void print_help(void) { fprintf(stderr, "ttynvt Usage:\n" "\t-D level\tEnable debug\n" "\t-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 minor, --min=minor\n" "\t-n name, --name=name (default: ttyNVT0)\n" "\t\tDevice will be created in /dev/$name.\n" "\t-q\t\tBe quiet (suppress informational messages)\n" "\t-r\t\tDisable telnet processing\n" "\t-s delay \t\tDelay at close in ms (use u suffix for us)\n" "\t-S server, --server=host:port\n"); } static int process_arg(void *data, const char *arg, int key, struct fuse_args *outargs) { switch (key) { case 0: print_help(); return fuse_opt_add_arg(outargs, "-ho"); default: if (strcmp(arg, "-d") == 0 || strcmp(arg, "-f") == 0) ttynvt_param.foreground = 1; return 1; } } int main(int argc, char *argv[]) { int rc; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); char dev_name[128]; const char *dev_info_argv[] = { dev_name }; pid_t cpid; openlog("ttynvt", LOG_PID, LOG_DAEMON); if (fuse_opt_parse(&args, &ttynvt_param, ttynvt_opts, process_arg)) { fprintf(stderr, "Failed to parse arguments\n"); return 1; } nvt_log_level(ttynvt_param.quiet, ttynvt_param.debug); if (ttynvt_param.logstd) nvt_log_dest(NVT_LOG_STDERR); if (!ttynvt_param.dev_name) ttynvt_param.dev_name = "ttyNVT0"; if (!ttynvt_param.server) { fprintf(stderr, "Server must be specified\n"); return 1; } snprintf(dev_name, sizeof(dev_name), "DEVNAME=%s", ttynvt_param.dev_name); if (ttynvt_param.close_delay) { char ch = 'm'; sscanf(ttynvt_param.close_delay, "%u%c", &ttynvt_param.close_delay_us, &ch); if (ch != 'u') ttynvt_param.close_delay_us *= 1000; // To milliseconds } struct cuse_info ci = { .dev_major = ttynvt_param.major, .dev_minor = ttynvt_param.minor, .dev_info_argc = sizeof(dev_info_argv) / sizeof(char *), .dev_info_argv = dev_info_argv, .flags = CUSE_UNRESTRICTED_IOCTL, }; if (ttynvt_param.foreground) { return cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL); } cpid = fork(); switch (cpid) { case -1: nvt_log(LOG_ERR, "fork(): %m\n"); return -1; case 0: /* Child */ DBG("Child proceeding...\n"); rc = cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL); if (rc != 0) nvt_log(LOG_ERR, "ttynvt failed rc=%d\n", rc); else DBG("Child exited\n"); return rc; default: /* Parent */ break; } DBG("Parent proceeding (child=%d)...\n", cpid); for (rc = 0; rc < 10; rc++) { if (_check_dev() == 0) break; usleep(500000); } DBG("Parent exiting\n"); return 0; }