Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package microcom for openSUSE:Factory checked in at 2024-10-25 19:20:15 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/microcom (Old) and /work/SRC/openSUSE:Factory/.microcom.new.2020 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "microcom" Fri Oct 25 19:20:15 2024 rev:3 rq:1218364 version:2023.09.0.0 Changes: -------- --- /work/SRC/openSUSE:Factory/microcom/microcom.changes 2022-09-16 13:32:51.577369269 +0200 +++ /work/SRC/openSUSE:Factory/.microcom.new.2020/microcom.changes 2024-10-25 19:22:59.379002898 +0200 @@ -1,0 +2,8 @@ +Fri Oct 25 13:54:08 UTC 2024 - Matthias Brugger <mbrug...@suse.com> + +- Updat to version 2023.09.0.0 + * use flock(2) instead of POSIX locking + * A new command sendescape to send a Ctrl-\. This makes it possible to drive a nested microcom instance. + * Several fixes, e.g. consistently use LFS and improve on rfc2217 subnegotiation handling + +------------------------------------------------------------------- Old: ---- microcom-2019.01.0.11.tar New: ---- microcom-2023.09.0.0.tar ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ microcom.spec ++++++ --- /var/tmp/diff_new_pack.k5Vgu6/_old 2024-10-25 19:23:00.411045939 +0200 +++ /var/tmp/diff_new_pack.k5Vgu6/_new 2024-10-25 19:23:00.431046773 +0200 @@ -17,7 +17,7 @@ Name: microcom -Version: 2019.01.0.11 +Version: 2023.09.0.0 Release: 0 Summary: Minimalistic terminal program License: GPL-2.0-only ++++++ microcom-2019.01.0.11.tar -> microcom-2023.09.0.0.tar ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/.github/workflows/build.yml new/microcom-2023.09.0.0/.github/workflows/build.yml --- old/microcom-2019.01.0.11/.github/workflows/build.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/microcom-2023.09.0.0/.github/workflows/build.yml 2023-09-07 11:51:05.000000000 +0200 @@ -0,0 +1,38 @@ +--- +name: Build test +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install Dependencies + run: + sudo apt install + libreadline6-dev + autoconf + automake + + - name: Prepare (autoreconf) + run: autoreconf -i + + - name: Prepare (configure) + run: ./configure + + - name: Build + run: make + + - name: Run check + run: make check + + - name: Run distcheck + run: make distcheck diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/.travis.yml new/microcom-2023.09.0.0/.travis.yml --- old/microcom-2019.01.0.11/.travis.yml 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/.travis.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,7 +0,0 @@ -language: c -compiler: gcc -dist: trusty -script: - - autoreconf -fi - - ./configure - - make distcheck diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/can.c new/microcom-2023.09.0.0/can.c --- old/microcom-2019.01.0.11/can.c 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/can.c 2023-09-07 11:51:05.000000000 +0200 @@ -14,6 +14,7 @@ * GNU General Public License for more details at www.gnu.org * */ +#include "config.h" #include <stdlib.h> #include <string.h> @@ -134,7 +135,6 @@ ios->set_flow = can_set_flow; ios->send_break = can_send_break; ios->exit = can_exit; - ios->istelnet = false; /* * the string is supposed to be formated this way: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/commands.c new/microcom-2023.09.0.0/commands.c --- old/microcom-2019.01.0.11/commands.c 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/commands.c 2023-09-07 11:51:05.000000000 +0200 @@ -6,6 +6,8 @@ * Foundation; either version 2 of the License, or (at your option) any later * version. */ +#include "config.h" + #include <stdlib.h> #include "microcom.h" @@ -151,6 +153,12 @@ return 0; } +static int cmd_sendescape(int argc, char *argv[]) +{ + ios->write(ios, "\x1c", 1); + return 0; +} + static int cmd_help(int argc, char *argv[]) { struct cmd *cmd; @@ -224,6 +232,10 @@ .fn = cmd_break, .info = "send break", }, { + .name = "sendescape", + .fn = cmd_sendescape, + .info = "send a Ctrl-\\", + }, { .name = "quit", .fn = cmd_quit, .info = "quit microcom", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/commands_fsl_imx.c new/microcom-2023.09.0.0/commands_fsl_imx.c --- old/microcom-2019.01.0.11/commands_fsl_imx.c 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/commands_fsl_imx.c 2023-09-07 11:51:05.000000000 +0200 @@ -6,6 +6,8 @@ * Foundation; either version 2 of the License, or (at your option) any later * version. */ +#include "config.h" + #include <stdio.h> #include <sys/select.h> #include <stdint.h> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/configure.ac new/microcom-2023.09.0.0/configure.ac --- old/microcom-2019.01.0.11/configure.ac 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/configure.ac 2023-09-07 11:51:05.000000000 +0200 @@ -1,4 +1,4 @@ -AC_INIT([microcom], [2019.01.0], [oss-to...@pengutronix.de]) +AC_INIT([microcom], [2023.09.0], [oss-to...@pengutronix.de]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([dist-xz]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/microcom.h new/microcom-2023.09.0.0/microcom.h --- old/microcom-2019.01.0.11/microcom.h 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/microcom.h 2023-09-07 11:51:05.000000000 +0200 @@ -51,7 +51,6 @@ int (*set_handshake_line)(struct ios_ops *, int pin, int enable); int (*send_break)(struct ios_ops *); void (*exit)(struct ios_ops *); - bool istelnet; int fd; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/mux.c new/microcom-2023.09.0.0/mux.c --- old/microcom-2019.01.0.11/mux.c 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/mux.c 2023-09-07 11:51:05.000000000 +0200 @@ -17,196 +17,13 @@ **************************************************************************** ** Rev. 1.0 - Feb. 2000 ****************************************************************************/ +#include "config.h" + #include "microcom.h" -#include <arpa/telnet.h> -#include <arpa/inet.h> -#include <stdarg.h> #include <stdbool.h> #define BUFSIZE 1024 -/* This is called with buf[-2:0] being IAC SB COM_PORT_OPTION */ -static int do_com_port_option(struct ios_ops *ios, unsigned char *buf, int len) -{ - int i = 2; - - switch (buf[1]) { - case SET_BAUDRATE_CS: - dbg_printf("SET_BAUDRATE_CS "); - break; - case SET_DATASIZE_CS: - dbg_printf("SET_DATASIZE_CS "); - break; - case SET_PARITY_CS: - dbg_printf("SET_PARITY_CS "); - break; - case SET_STOPSIZE_CS: - dbg_printf("SET_STOPSIZE_CS "); - break; - case SET_CONTROL_CS: - dbg_printf("SET_CONTROL_CS "); - break; - case NOTIFY_LINESTATE_CS: - dbg_printf("NOTIFY_LINESTATE_CS "); - break; - case NOTIFY_MODEMSTATE_CS: - dbg_printf("NOTIFY_MODEMSTATE_CS "); - break; - case FLOWCONTROL_SUSPEND_CS: - dbg_printf("FLOWCONTROL_SUSPEND_CS "); - break; - case FLOWCONTROL_RESUME_CS: - dbg_printf("FLOWCONTROL_RESUME_CS "); - break; - case SET_LINESTATE_MASK_CS: - dbg_printf("SET_LINESTATE_MASK_CS "); - break; - case SET_MODEMSTATE_MASK_CS: - dbg_printf("SET_MODEMSTATE_MASK_CS "); - break; - case PURGE_DATA_CS: - dbg_printf("PURGE_DATA_CS "); - break; - case SET_BAUDRATE_SC: - dbg_printf("SET_BAUDRATE_SC %d ", - buf[2] << 24 | buf[3] << 16 | buf[4] << 8 | buf[5]); - i += 4; - break; - case SET_DATASIZE_SC: - dbg_printf("SET_DATASIZE_SC "); - break; - case SET_PARITY_SC: - dbg_printf("SET_PARITY_SC "); - break; - case SET_STOPSIZE_SC: - dbg_printf("SET_STOPSIZE_SC "); - break; - case SET_CONTROL_SC: - i++; - dbg_printf("SET_CONTROL_SC 0x%02x ", buf[i]); - break; - case NOTIFY_LINESTATE_SC: - dbg_printf("NOTIFY_LINESTATE_SC "); - break; - case NOTIFY_MODEMSTATE_SC: - i++; - dbg_printf("NOTIFY_MODEMSTATE_SC 0x%02x ", buf[i]); - break; - case FLOWCONTROL_SUSPEND_SC: - dbg_printf("FLOWCONTROL_SUSPEND_SC "); - break; - case FLOWCONTROL_RESUME_SC: - dbg_printf("FLOWCONTROL_RESUME_SC "); - break; - case SET_LINESTATE_MASK_SC: - dbg_printf("SET_LINESTATE_MASK_SC "); - break; - case SET_MODEMSTATE_MASK_SC: - dbg_printf("SET_MODEMSTATE_MASK_SC "); - break; - case PURGE_DATA_SC: - dbg_printf("PURGE_DATA_SC "); - break; - default: - dbg_printf("??? %d ", buf[i]); - break; - } - - while (i < len) { - if (buf[i] == IAC) { - if (i + 1 < len && buf[i+1] == IAC) { - /* quoted IAC -> unquote */ - ++i; - } else if (i + 1 < len && buf[i+1] == SE) { - dbg_printf("IAC SE\n"); - return i + 2; - } - } - dbg_printf("%d ", buf[i]); - - ++i; - } - - fprintf(stderr, "Incomplete SB string\n"); - return -EINVAL; -} - -struct telnet_option { - unsigned char id; - const char *name; - int (*subneg_handler)(struct ios_ops *ios, unsigned char *buf, int len); - bool sent_will; -}; - -#define TELNET_OPTION(x) .id = TELNET_OPTION_ ## x, .name = #x - -static const struct telnet_option telnet_options[] = { - { - TELNET_OPTION(COM_PORT_CONTROL), - .subneg_handler = do_com_port_option, - .sent_will = true, - }, { - TELNET_OPTION(BINARY_TRANSMISSION), - }, { - TELNET_OPTION(ECHO), - }, { - TELNET_OPTION(SUPPRESS_GO_AHEAD), - } -}; - -static const struct telnet_option *get_telnet_option(unsigned char id) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(telnet_options); ++i) { - if (id == telnet_options[i].id) - return &telnet_options[i]; - } - - return NULL; -} - - -/* This function is called with buf[-2:-1] being IAC SB */ -static int do_subneg(struct ios_ops *ios, unsigned char *buf, int len) -{ - const struct telnet_option *option = get_telnet_option(buf[0]); - - if (option) - dbg_printf("%s ", option->name); - if (option->subneg_handler) { - return option->subneg_handler(ios, buf, len); - } else { - /* skip over subneg string */ - int i; - for (i = 0; i < len - 1; ++i) { - if (buf[i] != IAC) { - dbg_printf("%d ", buf[i]); - continue; - } - - if (buf[i + 1] == SE) { - dbg_printf("IAC SE\n"); - return i + 1; - } - - /* skip over IAC IAC */ - if (buf[i + 1] == IAC) { - dbg_printf("%d \n", IAC); - i++; - } - } - - /* the subneg string isn't finished yet */ - if (i == len - 1) - dbg_printf("%d", buf[i]); - dbg_printf("\\\n"); - fprintf(stderr, "Incomplete SB string\n"); - - return -EINVAL; - } -} - static int logfd = -1; char *answerback; @@ -220,123 +37,9 @@ write(logfd, buf, len); } -static int ios_printf(struct ios_ops *ios, const char *format, ...) -{ - char buf[20]; - int size, written = 0; - ssize_t ret; - va_list args; - - va_start(args, format); - - size = vsnprintf(buf, sizeof(buf), format, args); - - va_end(args); - - if (size >= sizeof(buf)) { - /* truncated output */ - errno = EIO; - return -1; - } - - while (written < size) { - ret = ios->write(ios, buf + written, size - written); - if (ret < 0) - return ret; - - written += ret; - assert(written <= size); - } - - return written; -} - -/* This function is called with buf[0] being IAC. */ -static int handle_command(struct ios_ops *ios, unsigned char *buf, int len) -{ - int ret; - const struct telnet_option *option; - - switch (buf[1]) { - case SB: - dbg_printf("SB "); - ret = do_subneg(ios, &buf[2], len - 2); - if (ret < 0) - return ret; - return ret + 2; - - case IAC: - /* escaped IAC -> unescape */ - write_receive_buf(&buf[1], 1); - return 2; - - case WILL: - option = get_telnet_option(buf[2]); - if (option) - dbg_printf("WILL %s", option->name); - else - dbg_printf("WILL #%d", buf[2]); - - if (option && option->subneg_handler) { - /* ok, we already requested that, so take this as - * confirmation to actually do COM_PORT stuff. - * Everything is fine. Don't reconfirm to prevent an - * request/confirm storm. - */ - dbg_printf("\n"); - } else { - /* unknown/unimplemented option -> DONT */ - dbg_printf(" -> DONT\n"); - ios_printf(ios, "%c%c%c", IAC, DONT, buf[2]); - } - return 3; - - case WONT: - option = get_telnet_option(buf[2]); - if (option) - dbg_printf("WONT %s\n", option->name); - else - dbg_printf("WONT #%d\n", buf[2]); - return 3; - - case DO: - option = get_telnet_option(buf[2]); - if (option) - dbg_printf("DO %s", option->name); - else - dbg_printf("DO #%d", buf[2]); - - if (option && option->sent_will) { - /* - * This is a confirmation of an WILL sent by us before. - * There is nothing to do now. - */ - dbg_printf("\n"); - } else { - /* Oh, cannot handle that one, so send a WONT */ - dbg_printf(" -> WONT\n"); - ios_printf(ios, "%c%c%c", IAC, WONT, buf[2]); - } - return 3; - - case DONT: - option = get_telnet_option(buf[2]); - if (option) - dbg_printf("DONT %s\n", option->name); - else - dbg_printf("DONT #%d\n", buf[2]); - return 3; - - default: - dbg_printf("??? %d\n", buf[1]); - return 1; - } -} - static int handle_receive_buf(struct ios_ops *ios, unsigned char *buf, int len) { unsigned char *sendbuf = buf; - int i; while (len) { switch (*buf) { @@ -351,19 +54,6 @@ len -= 1; sendbuf = buf; break; - case IAC: - if (ios->istelnet) { - write_receive_buf(sendbuf, buf - sendbuf); - i = handle_command(ios, buf, len); - if (i < 0) - return i; - - buf += i; - len -= i; - sendbuf = buf; - break; - } - /* fall through */ default: buf += 1; len -= 1; @@ -447,19 +137,20 @@ /* pf has characters for us */ len = ios->read(ios, buf, BUFSIZE); if (len < 0) { - ret = -errno; - fprintf(stderr, "%s\n", strerror(-ret)); - return ret; - } - if (len == 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + ret = -errno; + fprintf(stderr, "%s\n", strerror(-ret)); + return ret; + } + } else if (len == 0) { fprintf(stderr, "Got EOF from port\n"); return -EINVAL; - } - - i = handle_receive_buf(ios, buf, len); - if (i < 0) { - fprintf(stderr, "%s\n", strerror(-i)); - return i; + } else { + i = handle_receive_buf(ios, buf, len); + if (i < 0) { + fprintf(stderr, "%s\n", strerror(-i)); + return i; + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/parser.c new/microcom-2023.09.0.0/parser.c --- old/microcom-2019.01.0.11/parser.c 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/parser.c 2023-09-07 11:51:05.000000000 +0200 @@ -6,6 +6,8 @@ * Foundation; either version 2 of the License, or (at your option) any later * version. */ +#include "config.h" + #include <stdio.h> #include <readline/readline.h> #include <readline/history.h> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/serial.c new/microcom-2023.09.0.0/serial.c --- old/microcom-2019.01.0.11/serial.c 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/serial.c 2023-09-07 11:51:05.000000000 +0200 @@ -19,6 +19,7 @@ ** Rev. 1.01 - March 2000 ** Rev. 1.02 - June 2000 ****************************************************************************/ +#include "config.h" #include <limits.h> #include <sys/file.h> @@ -203,9 +204,20 @@ static int serial_send_break(struct ios_ops *ios) { - tcsendbreak(ios->fd, 0); + int ret; + struct timeval delay = { + .tv_sec = 0, + .tv_usec = 400000, + }; - return 0; + ret = ioctl(ios->fd, TIOCSBRK, NULL); + if (ret < 0) + return ret; + + select(0, NULL, NULL, NULL, &delay); + + ret = ioctl(ios->fd, TIOCCBRK, NULL); + return ret; } /* restore original terminal settings on exit */ @@ -233,7 +245,6 @@ ops->set_handshake_line = serial_set_handshake_line; ops->send_break = serial_send_break; ops->exit = serial_exit; - ops->istelnet = false; /* open the device */ fd = open(device, O_RDWR | O_NONBLOCK); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/microcom-2019.01.0.11/telnet.c new/microcom-2023.09.0.0/telnet.c --- old/microcom-2019.01.0.11/telnet.c 2022-09-07 12:24:58.000000000 +0200 +++ new/microcom-2023.09.0.0/telnet.c 2023-09-07 11:51:05.000000000 +0200 @@ -15,6 +15,7 @@ ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details at www.gnu.org ****************************************************************************/ +#include "config.h" #include <stdlib.h> #include <arpa/telnet.h> @@ -23,28 +24,464 @@ #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> +#include <stdarg.h> +#include <string.h> #include "microcom.h" +static int telnet_printf(struct ios_ops *ios, const char *format, ...) +{ + char buf[20]; + int size, written = 0; + ssize_t ret; + va_list args; + + va_start(args, format); + + size = vsnprintf(buf, sizeof(buf), format, args); + + va_end(args); + + if (size >= sizeof(buf)) { + /* truncated output */ + errno = EIO; + return -1; + } + + while (written < size) { + ret = write(ios->fd, buf + written, size - written); + if (ret < 0) + return ret; + + written += ret; + assert(written <= size); + } + + return written; +} + +static ssize_t get(unsigned char *buf, unsigned char *out, size_t len) +{ + if (!len) + return -1; + + if (buf[0] == IAC) { + if (len < 1) + return -1; + if (buf[1] == IAC) { + *out = IAC; + return 2; + } + return -1; + } else { + *out = buf[0]; + return 1; + } +} + +static size_t getl(unsigned char *buf, uint32_t *out, size_t len) +{ + *out = 0; + int i; + size_t offset = 0; + + for (i = 0; i < 4; ++i) { + ssize_t getres; + unsigned char c; + + getres = get(buf + offset, &c, len - offset); + if (getres < 0) + return getres; + + *out <<= 8; + *out |= c; + + offset += getres; + } + + return offset; +} + +/* This is called with buf[-2:0] being IAC SB COM_PORT_OPTION */ +static int do_com_port_option(struct ios_ops *ios, unsigned char *buf, int len) +{ + int i = 2; + + switch (buf[1]) { + case SET_BAUDRATE_CS: + dbg_printf("SET_BAUDRATE_CS "); + break; + case SET_DATASIZE_CS: + dbg_printf("SET_DATASIZE_CS "); + break; + case SET_PARITY_CS: + dbg_printf("SET_PARITY_CS "); + break; + case SET_STOPSIZE_CS: + dbg_printf("SET_STOPSIZE_CS "); + break; + case SET_CONTROL_CS: + dbg_printf("SET_CONTROL_CS "); + break; + case NOTIFY_LINESTATE_CS: + dbg_printf("NOTIFY_LINESTATE_CS "); + break; + case NOTIFY_MODEMSTATE_CS: + dbg_printf("NOTIFY_MODEMSTATE_CS "); + break; + case FLOWCONTROL_SUSPEND_CS: + dbg_printf("FLOWCONTROL_SUSPEND_CS "); + break; + case FLOWCONTROL_RESUME_CS: + dbg_printf("FLOWCONTROL_RESUME_CS "); + break; + case SET_LINESTATE_MASK_CS: + dbg_printf("SET_LINESTATE_MASK_CS "); + break; + case SET_MODEMSTATE_MASK_CS: + dbg_printf("SET_MODEMSTATE_MASK_CS "); + break; + case PURGE_DATA_CS: + dbg_printf("PURGE_DATA_CS "); + break; + case SET_BAUDRATE_SC: + { + uint32_t baudrate; + ssize_t getres = getl(buf + 2, &baudrate, len - 2); + + if (getres < 0) { + fprintf(stderr, "Incomplete or broken SB (SET_BAUDRATE_SC)\n"); + return getres; + } + dbg_printf("SET_BAUDRATE_SC %u ", baudrate); + i += getres;; + } + break; + case SET_DATASIZE_SC: + dbg_printf("SET_DATASIZE_SC "); + break; + case SET_PARITY_SC: + dbg_printf("SET_PARITY_SC "); + break; + case SET_STOPSIZE_SC: + dbg_printf("SET_STOPSIZE_SC "); + break; + case SET_CONTROL_SC: + { + unsigned char ctrl; + ssize_t getres = get(buf + 2, &ctrl, len - 2); + + if (getres < 0) { + fprintf(stderr, "Incomplete or broken SB (SET_CONTROL_SC)\n"); + return getres; + } + + dbg_printf("SET_CONTROL_SC 0x%02x ", ctrl); + i += getres; + } + break; + case NOTIFY_LINESTATE_SC: + dbg_printf("NOTIFY_LINESTATE_SC "); + break; + case NOTIFY_MODEMSTATE_SC: + { + unsigned char ms; + ssize_t getres = get(buf + 2, &ms, len - 2); + + if (getres < 0) { + fprintf(stderr, "Incomplete or broken SB (NOTIFY_MODEMSTATE_SC)\n"); + return getres; + } + + dbg_printf("NOTIFY_MODEMSTATE_SC 0x%02x ", ms); + i += getres; + } + case FLOWCONTROL_SUSPEND_SC: + dbg_printf("FLOWCONTROL_SUSPEND_SC "); + break; + case FLOWCONTROL_RESUME_SC: + dbg_printf("FLOWCONTROL_RESUME_SC "); + break; + case SET_LINESTATE_MASK_SC: + dbg_printf("SET_LINESTATE_MASK_SC "); + break; + case SET_MODEMSTATE_MASK_SC: + dbg_printf("SET_MODEMSTATE_MASK_SC "); + break; + case PURGE_DATA_SC: + dbg_printf("PURGE_DATA_SC "); + break; + default: + dbg_printf("??? %d ", buf[1]); + break; + } + + while (i < len) { + if (buf[i] == IAC) { + if (i + 1 < len && buf[i+1] == IAC) { + /* quoted IAC -> unquote */ + ++i; + } else if (i + 1 < len && buf[i+1] == SE) { + dbg_printf("IAC SE\n"); + return i + 2; + } + } + dbg_printf("%d ", buf[i]); + + ++i; + } + + fprintf(stderr, "Incomplete SB string\n"); + return -EINVAL; +} + +/* This is called with buf[-2:0] being IAC SB COM_PORT_OPTION */ +static int do_binary_transmission_option(struct ios_ops *ios, unsigned char *buf, int len) +{ + /* There are no subcommands for the BINARY_TRANSMISSION option (rfc856) */ + return -EINVAL; +} + +struct telnet_option { + unsigned char id; + const char *name; + int (*subneg_handler)(struct ios_ops *ios, unsigned char *buf, int len); + bool sent_will; +}; + +#define TELNET_OPTION(x) .id = TELNET_OPTION_ ## x, .name = #x + +static const struct telnet_option telnet_options[] = { + { + TELNET_OPTION(COM_PORT_CONTROL), + .subneg_handler = do_com_port_option, + .sent_will = true, + }, { + TELNET_OPTION(BINARY_TRANSMISSION), + .subneg_handler = do_binary_transmission_option, + .sent_will = true, + }, { + TELNET_OPTION(ECHO), + }, { + TELNET_OPTION(SUPPRESS_GO_AHEAD), + } +}; + +static const struct telnet_option *get_telnet_option(unsigned char id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(telnet_options); ++i) { + if (id == telnet_options[i].id) + return &telnet_options[i]; + } + + return NULL; +} + + +/* This function is called with buf[-2:-1] being IAC SB */ +static int do_subneg(struct ios_ops *ios, unsigned char *buf, int len) +{ + const struct telnet_option *option = get_telnet_option(buf[0]); + + if (option) + dbg_printf("%s ", option->name); + if (option->subneg_handler) { + return option->subneg_handler(ios, buf, len); + } else { + /* skip over subneg string */ + int i; + for (i = 0; i < len - 1; ++i) { + if (buf[i] != IAC) { + dbg_printf("%d ", buf[i]); + continue; + } + + if (buf[i + 1] == SE) { + dbg_printf("IAC SE\n"); + return i + 1; + } + + /* skip over IAC IAC */ + if (buf[i + 1] == IAC) { + dbg_printf("%d \n", IAC); + i++; + } + } + + /* the subneg string isn't finished yet */ + if (i == len - 1) + dbg_printf("%d", buf[i]); + dbg_printf("\\\n"); + fprintf(stderr, "Incomplete SB string\n"); + + return -EINVAL; + } +} + +/* This function is called with buf[0] being IAC. */ +static int handle_command(struct ios_ops *ios, unsigned char *buf, int len) +{ + int ret; + const struct telnet_option *option; + + /* possible out-of-bounds access */ + switch (buf[1]) { + case SB: + dbg_printf("SB "); + ret = do_subneg(ios, &buf[2], len - 2); + if (ret < 0) + return ret; + return ret + 2; + + case WILL: + option = get_telnet_option(buf[2]); + if (option) + dbg_printf("WILL %s", option->name); + else + dbg_printf("WILL #%d", buf[2]); + + if (option && option->subneg_handler) { + /* ok, we already requested that, so take this as + * confirmation to actually do COM_PORT stuff. + * Everything is fine. Don't reconfirm to prevent an + * request/confirm storm. + */ + dbg_printf("\n"); + } else { + /* unknown/unimplemented option -> DONT */ + dbg_printf(" -> DONT\n"); + telnet_printf(ios, "%c%c%c", IAC, DONT, buf[2]); + } + return 3; + + case WONT: + option = get_telnet_option(buf[2]); + if (option) + dbg_printf("WONT %s\n", option->name); + else + dbg_printf("WONT #%d\n", buf[2]); + return 3; + + case DO: + option = get_telnet_option(buf[2]); + if (option) + dbg_printf("DO %s", option->name); + else + dbg_printf("DO #%d", buf[2]); + + if (option && option->sent_will) { + /* + * This is a confirmation of an WILL sent by us before. + * There is nothing to do now. + */ + dbg_printf("\n"); + } else { + /* Oh, cannot handle that one, so send a WONT */ + dbg_printf(" -> WONT\n"); + telnet_printf(ios, "%c%c%c", IAC, WONT, buf[2]); + } + return 3; + + case DONT: + option = get_telnet_option(buf[2]); + if (option) + dbg_printf("DONT %s\n", option->name); + else + dbg_printf("DONT #%d\n", buf[2]); + return 3; + + default: + dbg_printf("??? %d\n", buf[1]); + return 1; + } +} + static ssize_t telnet_write(struct ios_ops *ios, const void *buf, size_t count) { - return write(ios->fd, buf, count); + size_t handled = 0; + ssize_t ret; + void *iac; + + /* + * To send an IAC character in the data stream, two IACs must be sent. + * So find the first IAC in the data to be send (if any), send the data + * before that IAC unquoted, then send the double IAC. Repeat until + * all IACs are handled. + */ + while ((iac = memchr(buf + handled, IAC, count - handled)) != NULL) { + if (iac - (buf + handled)) { + ret = write(ios->fd, buf + handled, iac - (buf + handled)); + if (ret < 0) + return ret; + handled += ret; + } else { + dprintf(ios->fd, "%c%c", IAC, IAC); + handled += 1; + } + } + + /* Send the remaining data that needs no quoting. */ + ret = write(ios->fd, buf + handled, count - handled); + if (ret < 0) + return ret; + return ret + handled; } static ssize_t telnet_read(struct ios_ops *ios, void *buf, size_t count) { - return read(ios->fd, buf, count); + ssize_t ret = read(ios->fd, buf, count); + void *iac; + size_t handled = 0; + + if (ret <= 0) + return ret; + + while ((iac = memchr(buf + handled, IAC, ret - handled)) != NULL) { + handled = iac - buf; + + /* XXX: possible out-of-bounds access */ + if (((unsigned char *)iac)[1] == IAC) { + /* duplicated IAC = one payload IAC */ + ret -= 1; + memmove(iac, iac + 1, ret - (iac - buf)); + handled += 1; + } else { + int iaclen = handle_command(ios, iac, ret - handled); + + if (iaclen < 0) + return iaclen; + + memmove(iac, iac + iaclen, ret - (handled + iaclen)); + ret -= iaclen; + } + } + if (ret) { + return ret; + } else { + errno = EAGAIN; + return -1; + } } static int telnet_set_speed(struct ios_ops *ios, unsigned long speed) { + unsigned char buf2[14] = {IAC, SB, TELNET_OPTION_COM_PORT_CONTROL, SET_BAUDRATE_CS}; + size_t offset = 4; + int i; + + for (i = 0; i < 4; ++i) { + buf2[offset] = (speed >> (24 - 8 * i)) & 0xff; + if (buf2[offset++] == IAC) + buf2[offset++] = IAC; + } - unsigned char buf2[] = {IAC, SB, TELNET_OPTION_COM_PORT_CONTROL, SET_BAUDRATE_CS, 0, 0, 0, 0, IAC, SE}; - int *speedp = (int *)&buf2[4]; + buf2[offset++] = IAC; + buf2[offset++] = SE; - *speedp = htonl(speed); dbg_printf("-> IAC SB COM_PORT_CONTROL SET_BAUDRATE_CS 0x%lx IAC SE\n", speed); - write(ios->fd, buf2, 10); + write(ios->fd, buf2, offset); return 0; } @@ -108,7 +545,6 @@ ios->set_flow = telnet_set_flow; ios->send_break = telnet_send_break; ios->exit = telnet_exit; - ios->istelnet = true; memset(&hints, '\0', sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG; @@ -169,11 +605,13 @@ } printf("connected to %s (port %s)\n", connected_host, connected_port); - /* send intent to do and accept COM_PORT stuff */ + /* send intent we WILL do COM_PORT stuff */ dbg_printf("-> WILL COM_PORT_CONTROL\n"); dprintf(sock, "%c%c%c", IAC, WILL, TELNET_OPTION_COM_PORT_CONTROL); - dbg_printf("-> DO COM_PORT_CONTROL\n"); - dprintf(sock, "%c%c%c", IAC, DO, TELNET_OPTION_COM_PORT_CONTROL); + dbg_printf("-> DO BINARY_TRANSMISSION\n"); + dprintf(sock, "%c%c%c", IAC, DO, TELNET_OPTION_BINARY_TRANSMISSION); + dbg_printf("-> WILL BINARY_TRANSMISSION\n"); + dprintf(sock, "%c%c%c", IAC, WILL, TELNET_OPTION_BINARY_TRANSMISSION); goto out; }