Skip to content

Commit

Permalink
nets: Detect terminal type and speed.
Browse files Browse the repository at this point in the history
For interactive terminal network protocols (Telnet, SSH, RLogin),
detect the terminal type and speed, if supported by the protocol,
so that TERM can be set appropriately when needed, like executing
an external program.

Also detect if a term type is in use that isn't in the terminfo
database, and add syncterm terminfo support to the server setup
script.

This also revamps the options negotiation in net_telnet to be more
robust and more compatible with different terminal emulators.
  • Loading branch information
InterLinked1 committed Mar 16, 2024
1 parent 327fff8 commit ca02735
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 114 deletions.
21 changes: 21 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,27 @@ Can I run SSH and SFTP on the same port?
Yes (and, in fact, you must, if you wish to enable both).
Originally, SSH and SFTP were provided by 2 independent modules. They are now combined, allowing for same-port usage, which users expect.

What terminal emulators are supported?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Most common terminal emulators should work fine. The emulator's terminal type is used, if sent, and some terminal autodetection is also performed.

Some emulators are particularly good. Of all the well-known ones, these three terminal emulators are particularly recommended for BBSing on Windows:

* **SyncTERM** - Works well, looks nice. You **must** use the `newer 1.2 version <https://github.com/bbs-io/syncterm-windows/releases/tag/dev>`_. The more commonly downloaded 1.1 version has major bugs.
* **qodem** - Initial configuration slightly unintuitive, but otherwise works very well, with excellent support for non-standard display sizes.
* **PuTTY** (and forks, like KiTTY) - Works well, no known issues. Not "retro" at all, but does the job fine.

Most other terminal emulators tested tend to have various setup, compatibility, or runtime issues. In particular:

* **NetRunner** - Not recommended. Poorer support for ANSI escape sequences and Telnet options. Does not send a terminal type! Poor support for ncurses applications.

I see warnings about a terminal type not being in the terminfo database.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This typically happens for terminal emulators that report non-standard terminal types that are not installed by default on the system.
This can be resolved by installing the appropriate terminfo file. See :code:`scripts/server_setup.sh` for an example of adding :code:`syncterm` support in this manner.

When using private namespace IRC channels, channel messages get sent to me as private messages.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
40 changes: 28 additions & 12 deletions bbs/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ static void node_free(struct bbs_node *node)
FREE(node->vars); /* Free the list itself */
}
free_if(node->ip);
free_if(node->term);
bbs_debug(4, "Node %d now freed\n", node->id);
bbs_verb(3, "Node %d has exited\n", node->id);
bbs_node_unlock(node);
Expand Down Expand Up @@ -765,7 +766,17 @@ static int cli_nodes(struct bbs_cli_args *a)
int c = 0;
time_t now = time(NULL);

bbs_dprintf(a->fdout, "%3s %9s %9s %7s %4s %5s %4s %-15s %-25s %15s %5s %1s %1s %1s %7s %3s %3s %3s %3s %3s %3s %3s %s\n", "#", "PROTOCOL", "ELAPSED", "TRM SZE", "ANSI", "SPEED", "SLOW", "USER", "MENU/PAGE/LOCATION", "IP ADDRESS", "RPORT", "E", "B", "!", "TID", "SFD", "FD", "RFD", "WFD", "MST", "SLV", "SPY", "SLV NAME");
bbs_dprintf(a->fdout, "%3s %9s %9s %-15s %-25s"
" %15s %5s %7s %3s %3s %3s %3s"
" %3s %3s %3s"
" %1s %1s %1s"
" %7s %8s %4s %5s %6s %4s"
"\n",
"#", "PROTOCOL", "ELAPSED", "USER", "MENU/PAGE/LOCATION",
"IP ADDRESS", "RPORT", "TID", "SFD", "FD", "RFD", "WFD",
"MST", "SLV", "SPY",
"E", "B", "!",
"TRM SZE", "TYPE", "ANSI", "SPEED", "BPS", "SLOW");

RWLIST_RDLOCK(&nodes);
RWLIST_TRAVERSE(&nodes, n, entry) {
Expand All @@ -785,22 +796,25 @@ static int cli_nodes(struct bbs_cli_args *a)
snprintf(menufull, sizeof(menufull), "%s%s%s%s", S_IF(n->menu), n->menuitem ? " (" : "", S_IF(n->menuitem), n->menuitem ? ")" : "");
lwp = bbs_pthread_tid(n->thread);

bbs_dprintf(a->fdout, "%3d %9s %9s %-15s %-25s"
" %15s %5u %7d %3d %3d %3d %3d",
n->id, n->protname, elapsed, bbs_username(n->user), menufull,
n->ip, n->rport, lwp, n->sfd, n->fd, n->rfd, n->wfd);
if (NODE_INTERACTIVE(n)) {
/* If the size is speculative, put a '?' afterwards */
snprintf(termsize, sizeof(termsize), "%dx%d%s", n->cols, n->rows, n->dimensions ? "" : "?");
bbs_node_format_speed(n, speed, sizeof(speed));
bbs_dprintf(a->fdout,
" %3d %3d %3d"
" %1s %1s %1s"
" %7s %8s %4s %5s %6u %4s"
"\n",
n->amaster, n->slavefd, n->spyfd,
BBS_YN(n->echo), BBS_YN(n->buffered), bbs_node_interrupted(n) ? "*" : "",
termsize, S_IF(n->term), BBS_YESNO(n->ansi), speed, n->reportedbps, BBS_YN(n->slow));
} else {
termsize[0] = speed[0] = '\0';
bbs_dprintf(a->fdout, "\n");
}

bbs_dprintf(a->fdout, "%3d %9s %9s %7s %4s %5s %4s %-15s %-25s %15s %5u %1s %1s %1s %7d %3d %3d %3d %3d",
n->id, n->protname, elapsed, termsize, NODE_INTERACTIVE(n) ? BBS_YESNO(n->ansi) : "", speed, NODE_INTERACTIVE(n) ? BBS_YN(n->slow) : " ", bbs_username(n->user), menufull, n->ip, n->rport, NODE_INTERACTIVE(n) ? BBS_YN(n->echo) : "", NODE_INTERACTIVE(n) ? BBS_YN(n->buffered) : "",
bbs_node_interrupted(n) ? "*" : "",
lwp, n->sfd, n->fd, n->rfd, n->wfd);
if (NODE_INTERACTIVE(n)) {
bbs_dprintf(a->fdout, " %3d %3d %3d %s", n->amaster, n->slavefd, n->spyfd, n->slavename);
}
bbs_dprintf(a->fdout, "\n");
c++;
}
RWLIST_UNLOCK(&nodes);
Expand Down Expand Up @@ -947,8 +961,10 @@ static int node_info(int fd, unsigned int nodenum)
char speed[NODE_SPEED_BUFSIZ_LARGE];
bbs_node_format_speed(n, speed, sizeof(speed));
bbs_dprintf(fd, BBS_FMT_DSDS, "Term Size", n->cols, "x", n->rows, n->dimensions ? "" : "?");
bbs_dprintf(fd, BBS_FMT_S, "Term Type", S_IF(n->term));
bbs_dprintf(fd, BBS_FMT_S, "Term ANSI", BBS_YN(n->ansi));
bbs_dprintf(fd, BBS_FMT_S, "Term Speed", speed);
bbs_dprintf(fd, BBS_FMT_S, "Term Speed (Measured)", speed);
bbs_dprintf(fd, BBS_FMT_D, "Term Speed (Reported)", n->reportedbps);
bbs_dprintf(fd, BBS_FMT_S, "Term Echo", BBS_YN(n->echo));
bbs_dprintf(fd, BBS_FMT_S, "Term Buffered", BBS_YN(n->buffered));
}
Expand Down
52 changes: 50 additions & 2 deletions bbs/system.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,48 @@ static int set_controlling_term(int fd)
return pres;
}

static int term_type_exists(const char *term)
{
/* Check if a term type exists in the system.
* libtermcap doesn't seem to offer an easy way to query
* without actual messing with terminal stuff.
*
* Most systems do not include "syncterm" by default,
* and if an unrecognized terminal type is used,
* programs that depend on termcap will fail
* and exit with Operation not permitted.
*
* We thus want to check to see if that was because the
* TERM doesn't exist, in which case the sysop
* may need to add that to the termcap database.
*/
pid_t pid;
int status;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
#pragma GCC diagnostic ignored "-Wcast-qual"
char *const argv[] = { (char*) "infocmp", term, NULL };

pid = fork();
if (pid < 0) {
bbs_error("fork failed: %s\n", strerror(errno));
return -1;
} else if (!pid) {
execvp("infocmp", argv);
_exit(errno);
}
#pragma GCC diagnostic pop
waitpid(pid, &status, WUNTRACED | WCONTINUED);
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
if (status) {
return 0;
}
}

return 1;
}

static void waitpidexit(pid_t pid, const char *filename, int *res)
{
pid_t w;
Expand Down Expand Up @@ -724,8 +766,8 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
fdin = fdout = fd;
bbs_assert(isatty(fd));
/* Don't call tcgetsid here, it will fail */
bbs_debug(6, "sid: %d, tcpgrp: %d\n", getsid(getpid()), tcgetpgrp(fd));
safe_strncpy(fullterm, "TERM=xterm", sizeof(fullterm)); /* Many interactive programs will whine if $TERM is not set */
bbs_debug(6, "sid: %d, tcpgrp: %d, term: %s\n", getsid(getpid()), tcgetpgrp(fd), S_IF(node->term));
snprintf(fullterm, sizeof(fullterm), "TERM=%s", S_OR(node->term, "xterm")); /* Many interactive programs will whine if $TERM is not set */
}
if (fdout == -1) {
/* If no node and no output fd, create file descriptors using a temporary pipe */
Expand Down Expand Up @@ -1009,6 +1051,12 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd

bbs_debug(5, "Waiting for process %d to exit\n", pid);
waitpidexit(pid, filename, &res);
if (res == 1) {
/* Check if this failed because the $TERM used is not in the termcap database. */
if (node && !strlen_zero(node->term) && !term_type_exists(node->term)) {
bbs_warning("Terminal type '%s' is not in the terminfo database\n", node->term);
}
}
if (node) {
/* If we're being shut down right now, it's likely the child process
* was actually killed in node_shutdown, in which case we're not going
Expand Down
29 changes: 22 additions & 7 deletions doors/door_usermgmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,29 @@ static int termmgmt_exec(struct bbs_node *node, const char *args)

bbs_node_clear_screen(node);
bbs_node_writef(node, "%s%s%s\n", COLOR(COLOR_PRIMARY), "Terminal Settings", COLOR_RESET);
bbs_node_writef(node, "%s%10s%s\n", COLOR(COLOR_SECONDARY), "Speed", COLOR_RESET);

if (node->calcbps) {
bbs_node_writef(node, "%s%16s%s %ld bps\n", COLOR(COLOR_WHITE), "Measured", COLOR_RESET, node->calcbps);
} else {
bbs_node_writef(node, "%s%16s%s %s\n", COLOR(COLOR_WHITE), "Measured", COLOR_RESET, "Broadband");
}
if (node->bps) {
bbs_node_writef(node, "%s%16s%s %u bps\n", COLOR(COLOR_WHITE), "Throttle", COLOR_RESET, node->bps);
} else {
bbs_node_writef(node, "%s%16s%s %s\n", COLOR(COLOR_WHITE), "Throttle", COLOR_RESET, "Unthrottled");
}
if (node->reportedbps) {
bbs_node_writef(node, "%s%16s%s %u\n", COLOR(COLOR_WHITE), "Reported", COLOR_RESET, node->reportedbps);
} else {
bbs_node_writef(node, "%s%16s%s %s\n", COLOR(COLOR_WHITE), "Reported", COLOR_RESET, "Unreported");
}

bbs_node_writef(node, "%s%10s %s\n", COLOR(COLOR_SECONDARY), "Protocol", node->protname);
bbs_node_writef(node, "%s%10s %s\n", COLOR(COLOR_SECONDARY), "Term Type", S_OR(node->term, "(Unreported)"));
bbs_node_writef(node, "%s%10s %s\n", COLOR(COLOR_SECONDARY), "ANSI", node->ansi ? "Yes" : "No");
if (node->ansi) {
#define DUMP_ANSI_SUPPORT(flag, name) bbs_node_writef(node, "%s - %-15s%s%3s\n", COLOR(COLOR_WHITE), name, node->ans & flag ? COLOR(COLOR_GREEN) : COLOR(COLOR_RED), node->ans & flag ? "Yes" : "No")
#define DUMP_ANSI_SUPPORT(flag, name) bbs_node_writef(node, "%s - %-18s%s%3s\n", COLOR(COLOR_WHITE), name, node->ans & flag ? COLOR(COLOR_GREEN) : COLOR(COLOR_RED), node->ans & flag ? "Yes" : "No")
DUMP_ANSI_SUPPORT(ANSI_CURSOR_QUERY, "Cursor Query");
DUMP_ANSI_SUPPORT(ANSI_CURSOR_SET, "Cursor Set");
DUMP_ANSI_SUPPORT(ANSI_COLORS, "Colors");
Expand All @@ -274,13 +294,8 @@ static int termmgmt_exec(struct bbs_node *node, const char *args)
DUMP_ANSI_SUPPORT(ANSI_TERM_TITLE, "Term Titles");
#undef DUMP_ANSI_SUPPORT
}
if (node->bps) {
bbs_node_writef(node, "%s%10s%s %d bps\n", COLOR(COLOR_SECONDARY), "Speed", COLOR_RESET, node->bps);
} else {
bbs_node_writef(node, "%s%10s%s %s\n", COLOR(COLOR_SECONDARY), "Speed", COLOR_RESET, "Unthrottled");
}
bbs_node_writef(node, "%s<A>%s Toggle ANSI %s<*>%s Exit\n", COLOR(COLOR_SECONDARY), COLOR_RESET, COLOR(COLOR_SECONDARY), COLOR_RESET);
c = bbs_node_tread(node, MIN_MS(2));
c = bbs_node_tread(node, MIN_MS(5));
switch (c) {
case 'a':
case 'A':
Expand Down
2 changes: 2 additions & 0 deletions include/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ struct bbs_node {
const char *menu; /*!< Current menu */
const char *menuitem; /*!< Currently executed menu item */
int menustack; /*!< Current menu stack level */
char *term; /*!< Terminal type (TERM) */
char *ip; /*!< Remote IP Address */
unsigned short int rport; /*!< Remote port number */
unsigned short int port; /*!< Local port number */
Expand All @@ -62,6 +63,7 @@ struct bbs_node {
time_t created; /*!< Creation time */
pid_t childpid; /*!< Child PID of process node is currently exec'ing (0 if none) */
long int calcbps; /*!< Calculated terminal speed (from measurements) */
unsigned int reportedbps; /*!< Reported terminal speed (by client) */
unsigned int bps; /*!< Emulated terminal speed */
unsigned int speed; /*!< Pause time for emulated terminal speed, in us */
/* Node flags */
Expand Down
11 changes: 11 additions & 0 deletions nets/net_rlogin.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ static int rlogin_handshake(struct bbs_node *node)
s3 = s2 + strlen(s2) + 1;
s4 = s3 + strlen(s3) + 1;
bbs_debug(3, "Got %ld-byte connection string (%s/%s/%s/%s)\n", res, s1, s2, s3, s4);
if (!strlen_zero(s4)) {
char *tmp;
tmp = strchr(s4, '/');
if (tmp) {
*tmp++ = '\0';
if (!strlen_zero(tmp)) {
node->reportedbps = (unsigned int) atoi(tmp);
}
}
REPLACE(node->term, s4);
}
if (SWRITE(node->fd, "\0") != STRLEN("\0")) { /* Send 0-byte to ACK and change to data transfer mode */
return -1;
}
Expand Down
12 changes: 5 additions & 7 deletions nets/net_ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
*
* \brief SSH (Secure Shell) and SFTP (Secure File Transfer Protocol) server
*
* \note Supports RFC 4251 architecture
* \note Supports RFC 4252 authentication
* \note Supports RFC 4253 protocol
* \note Supports RFC 4254 protocol
*
* \author Naveen Albert <[email protected]>
*/
Expand Down Expand Up @@ -443,9 +445,7 @@ static int pty_request(ssh_session session, ssh_channel channel, const char *ter
{
struct channel_data_struct *cdata = (struct channel_data_struct *) userdata;

UNUSED(session);
UNUSED(channel);
UNUSED(term);

cdata->winsize->ws_row = (short unsigned int) rows;
cdata->winsize->ws_col = (short unsigned int) cols;
Expand Down Expand Up @@ -483,6 +483,7 @@ static int pty_request(ssh_session session, ssh_channel channel, const char *ter
if (!cdata->node) {
return SSH_ERROR;
}
REPLACE(cdata->node->term, term);
/* Attach the user that we set earlier.
* If we didn't set one, it's still NULL, so fine either way. */
if (!bbs_node_attach_user(cdata->node, *cdata->user)) {
Expand Down Expand Up @@ -1353,14 +1354,11 @@ static int do_sftp(struct bbs_node *node, ssh_session session, ssh_channel chann
for (;;) {
char userpath[256];
sftp_client_message msg;
#if 0
/*! \todo BUGBUG FIXME For some reason, this doesn't work (probably can't poll directly on the fd, see if there's a libssh API to do this) */
int pres = bbs_poll(node->fd, bbs_transfer_timeout());
int pres = ssh_channel_poll_timeout(channel, bbs_transfer_timeout(), 0);
if (pres <= 0) {
bbs_debug(3, "poll returned %d, terminating SFTP session\n", pres);
bbs_debug(3, "ssh_channel_poll_timeout returned %d, terminating SFTP session\n", pres);
break;
}
#endif
msg = sftp_get_client_message(sftp); /* This will block, so if we want a timeout, we need to do it beforehand */
if (!msg) {
break;
Expand Down
Loading

0 comments on commit ca02735

Please sign in to comment.