Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: netstring.c,v 1.5 2026/05/09 18:49:23 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	netstring 3
      6 /* SUMMARY
      7 /*	netstring stream I/O support
      8 /* SYNOPSIS
      9 /*	#include <netstring.h>
     10 /*
     11 /*	void	netstring_setup(stream, timeout)
     12 /*	VSTREAM *stream;
     13 /*	int	timeout;
     14 /*
     15 /*	void	netstring_except(stream, exception)
     16 /*	VSTREAM	*stream;
     17 /*	int	exception;
     18 /*
     19 /*	const char *netstring_strerror(err)
     20 /*	int	err;
     21 /*
     22 /*	VSTRING	*netstring_get(stream, buf, limit)
     23 /*	VSTREAM	*stream;
     24 /*	VSTRING	*buf;
     25 /*	ssize_t	limit;
     26 /*
     27 /*	void	netstring_put(stream, data, len)
     28 /*	VSTREAM *stream;
     29 /*	const char *data;
     30 /*	ssize_t	len;
     31 /*
     32 /*	void	netstring_put_multi(stream, data, len, data, len, ..., 0)
     33 /*	VSTREAM *stream;
     34 /*	const char *data;
     35 /*	ssize_t	len;
     36 /*
     37 /*	void	NETSTRING_PUT_BUF(stream, buf)
     38 /*	VSTREAM *stream;
     39 /*	VSTRING	*buf;
     40 /*
     41 /*	void	netstring_fflush(stream)
     42 /*	VSTREAM *stream;
     43 /*
     44 /*	VSTRING	*netstring_memcpy(buf, data, len)
     45 /*	VSTRING	*buf;
     46 /*	const char *data;
     47 /*	ssize_t	len;
     48 /*
     49 /*	VSTRING	*netstring_memcat(buf, data, len)
     50 /*	VSTRING	*buf;
     51 /*	const char *src;
     52 /*	ssize_t len;
     53 /* AUXILIARY ROUTINES
     54 /*	ssize_t	netstring_get_length(stream)
     55 /*	VSTREAM *stream;
     56 /*
     57 /*	VSTRING	*netstring_get_data(stream, buf, len)
     58 /*	VSTREAM *stream;
     59 /*	VSTRING	*buf;
     60 /*	ssize_t	len;
     61 /*
     62 /*	void	netstring_get_terminator(stream)
     63 /*	VSTREAM *stream;
     64 /* DESCRIPTION
     65 /*	This module reads and writes netstrings with error detection:
     66 /*	timeouts, unexpected end-of-file, or format errors. Netstring
     67 /*	is a data format designed by Daniel Bernstein.
     68 /*
     69 /*	netstring_setup() arranges for a time limit on the netstring
     70 /*	read and write operations described below.
     71 /*	This routine alters the behavior of streams as follows:
     72 /* .IP \(bu
     73 /*	The read/write timeout is set to the specified value.
     74 /* .IP \(bu
     75 /*	The stream is configured to enable exception handling.
     76 /* .PP
     77 /*	netstring_except() raises the specified exception on the
     78 /*	named stream. See the DIAGNOSTICS section below.
     79 /*
     80 /*	netstring_strerror() converts an exception number to string.
     81 /*
     82 /*	netstring_get() reads a netstring from the specified stream
     83 /*	and extracts its content. The limit specifies a maximal size.
     84 /*	Specify zero to disable the size limit. The result is not null
     85 /*	terminated.  The result value is the buf argument.
     86 /*
     87 /*	netstring_put() encapsulates the specified string as a netstring
     88 /*	and sends the result to the specified stream.
     89 /*	The stream output buffer is not flushed.
     90 /*
     91 /*	netstring_put_multi() encapsulates the content of multiple strings
     92 /*	as one netstring and sends the result to the specified stream. The
     93 /*	argument list must be terminated with a null data pointer.
     94 /*	The stream output buffer is not flushed.
     95 /*
     96 /*	NETSTRING_PUT_BUF() is a macro that provides a VSTRING-based
     97 /*	wrapper for the netstring_put() routine.
     98 /*
     99 /*	netstring_fflush() flushes the output buffer of the specified
    100 /*	stream and handles any errors.
    101 /*
    102 /*	netstring_memcpy() encapsulates the specified data as a netstring
    103 /*	and copies the result over the specified buffer. The result
    104 /*	value is the buffer.
    105 /*
    106 /*	netstring_memcat() encapsulates the specified data as a netstring
    107 /*	and appends the result to the specified buffer. The result
    108 /*	value is the buffer.
    109 /*
    110 /*	The following routines provide low-level access to a netstring
    111 /*	stream.
    112 /*
    113 /*	netstring_get_length() reads a length field from the specified
    114 /*	stream, and absorbs the netstring length field terminator.
    115 /*
    116 /*	netstring_get_data() reads the specified number of bytes from the
    117 /*	specified stream into the specified buffer, and absorbs the
    118 /*	netstring terminator.  The result value is the buf argument.
    119 /*
    120 /*	netstring_get_terminator() reads the netstring terminator from
    121 /*	the specified stream.
    122 /* DIAGNOSTICS
    123 /* .fi
    124 /* .ad
    125 /*	In case of error, a vstream_longjmp() call is performed to the
    126 /*	caller-provided context specified with vstream_setjmp().
    127 /*	Error codes passed along with vstream_longjmp() are:
    128 /* .IP NETSTRING_ERR_EOF
    129 /*	An I/O error happened, or the peer has disconnected unexpectedly.
    130 /* .IP NETSTRING_ERR_TIME
    131 /*	The time limit specified to netstring_setup() was exceeded.
    132 /* .IP NETSTRING_ERR_FORMAT
    133 /*	The input contains an unexpected character value.
    134 /* .IP NETSTRING_ERR_SIZE
    135 /*	The input is larger than acceptable.
    136 /* BUGS
    137 /*	The timeout deadline affects all I/O on the named stream, not
    138 /*	just the I/O done on behalf of this module.
    139 /*
    140 /*	The timeout deadline overwrites any previously set up state on
    141 /*	the named stream.
    142 /*
    143 /*	netstrings are not null terminated, which makes printing them
    144 /*	a bit awkward.
    145 /* LICENSE
    146 /* .ad
    147 /* .fi
    148 /*	The Secure Mailer license must be distributed with this software.
    149 /* SEE ALSO
    150 /*	https://cr.yp.to/proto/netstrings.txt, netstring definition
    151 /* AUTHOR(S)
    152 /*	Wietse Venema
    153 /*	IBM T.J. Watson Research
    154 /*	P.O. Box 704
    155 /*	Yorktown Heights, NY 10598, USA
    156 /*
    157 /*	Wietse Venema
    158 /*	Google, Inc.
    159 /*	111 8th Avenue
    160 /*	New York, NY 10011, USA
    161 /*
    162 /*	Wietse Venema
    163 /*	porcupine.org
    164 /*--*/
    165 
    166 /* System library. */
    167 
    168 #include <sys_defs.h>
    169 #include <stdarg.h>
    170 #include <ctype.h>
    171 #include <errno.h>
    172 
    173 /* Utility library. */
    174 
    175 #include <msg.h>
    176 #include <vstream.h>
    177 #include <vstring.h>
    178 #include <compat_va_copy.h>
    179 #include <netstring.h>
    180 
    181 /* Application-specific. */
    182 
    183 #define STR(x)	vstring_str(x)
    184 #define LEN(x)	VSTRING_LEN(x)
    185 
    186 /* netstring_setup - initialize netstring stream */
    187 
    188 void    netstring_setup(VSTREAM *stream, int timeout)
    189 {
    190     vstream_control(stream,
    191 		    CA_VSTREAM_CTL_TIMEOUT(timeout),
    192 		    CA_VSTREAM_CTL_EXCEPT,
    193 		    CA_VSTREAM_CTL_END);
    194 }
    195 
    196 /* netstring_except - process netstring stream exception */
    197 
    198 void    netstring_except(VSTREAM *stream, int exception)
    199 {
    200     vstream_longjmp(stream, exception);
    201 }
    202 
    203 /* netstring_get_length - read netstring length + terminator */
    204 
    205 ssize_t netstring_get_length(VSTREAM *stream)
    206 {
    207     const char *myname = "netstring_get_length";
    208     ssize_t len = 0;
    209     int     ch;
    210     int     digit;
    211 
    212     for (;;) {
    213 	switch (ch = VSTREAM_GETC(stream)) {
    214 	case VSTREAM_EOF:
    215 	    netstring_except(stream, vstream_ftimeout(stream) ?
    216 			     NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
    217 	case ':':
    218 	    if (msg_verbose > 1)
    219 		msg_info("%s: read netstring length %ld", myname, (long) len);
    220 	    return (len);
    221 	default:
    222 	    if (!ISDIGIT(ch))
    223 		netstring_except(stream, NETSTRING_ERR_FORMAT);
    224 	    digit = ch - '0';
    225 	    if (len > SSIZE_T_MAX / 10
    226 		|| (len *= 10) > SSIZE_T_MAX - digit)
    227 		netstring_except(stream, NETSTRING_ERR_SIZE);
    228 	    len += digit;
    229 	    break;
    230 	}
    231     }
    232 }
    233 
    234 /* netstring_get_data - read netstring payload + terminator */
    235 
    236 VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, ssize_t len)
    237 {
    238     const char *myname = "netstring_get_data";
    239 
    240     /*
    241      * Read the payload and absorb the terminator.
    242      */
    243     if (vstream_fread_buf(stream, buf, len) != len)
    244 	netstring_except(stream, vstream_ftimeout(stream) ?
    245 			 NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
    246     if (msg_verbose > 1)
    247 	msg_info("%s: read netstring data %.*s",
    248 		 myname, (int) (len < 30 ? len : 30), STR(buf));
    249     netstring_get_terminator(stream);
    250 
    251     /*
    252      * Return the buffer.
    253      */
    254     return (buf);
    255 }
    256 
    257 /* netstring_get_terminator - absorb netstring terminator */
    258 
    259 void    netstring_get_terminator(VSTREAM *stream)
    260 {
    261     if (VSTREAM_GETC(stream) != ',')
    262 	netstring_except(stream, NETSTRING_ERR_FORMAT);
    263 }
    264 
    265 /* netstring_get - read string from netstring stream */
    266 
    267 VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, ssize_t limit)
    268 {
    269     ssize_t len;
    270 
    271     len = netstring_get_length(stream);
    272     if (ENFORCING_SIZE_LIMIT(limit) && len > limit)
    273 	netstring_except(stream, NETSTRING_ERR_SIZE);
    274     netstring_get_data(stream, buf, len);
    275     return (buf);
    276 }
    277 
    278 /* netstring_put - send string as netstring */
    279 
    280 void    netstring_put(VSTREAM *stream, const char *data, ssize_t len)
    281 {
    282     const char *myname = "netstring_put";
    283 
    284     if (msg_verbose > 1)
    285 	msg_info("%s: write netstring len %ld data %.*s",
    286 		 myname, (long) len, (int) (len < 30 ? len : 30), data);
    287     vstream_fprintf(stream, "%ld:", (long) len);
    288     vstream_fwrite(stream, data, len);
    289     VSTREAM_PUTC(',', stream);
    290 }
    291 
    292 /* netstring_put_multi - send multiple strings as one netstring */
    293 
    294 void    netstring_put_multi(VSTREAM *stream,...)
    295 {
    296     const char *myname = "netstring_put_multi";
    297     ssize_t total;
    298     char   *data;
    299     ssize_t data_len;
    300     va_list ap;
    301     va_list ap2;
    302 
    303     /*
    304      * Initialize argument lists.
    305      */
    306     va_start(ap, stream);
    307     VA_COPY(ap2, ap);
    308 
    309     /*
    310      * Figure out the total result size. 202604 Claude: move the wrap-around
    311      * guard inside the loop.
    312      */
    313     for (total = 0; (data = va_arg(ap, char *)) != 0; /* see below */ ) {
    314 	if ((data_len = va_arg(ap, ssize_t)) < 0)
    315 	    msg_panic("%s: bad data length %ld", myname, (long) data_len);
    316 	if (data_len > SSIZE_T_MAX - total)
    317 	    msg_panic("%s: total length overflow", myname);
    318 	total += data_len;
    319     }
    320     va_end(ap);
    321     if (msg_verbose > 1)
    322 	msg_info("%s: write total length %ld", myname, (long) total);
    323 
    324     /*
    325      * Send the length, content and terminator.
    326      */
    327     vstream_fprintf(stream, "%ld:", (long) total);
    328     while ((data = va_arg(ap2, char *)) != 0) {
    329 	data_len = va_arg(ap2, ssize_t);
    330 	if (msg_verbose > 1)
    331 	    msg_info("%s: write netstring len %ld data %.*s",
    332 		     myname, (long) data_len,
    333 		     (int) (data_len < 30 ? data_len : 30), data);
    334 	if (vstream_fwrite(stream, data, data_len) != data_len)
    335 	    netstring_except(stream, vstream_ftimeout(stream) ?
    336 			     NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
    337     }
    338     va_end(ap2);
    339     vstream_fwrite(stream, ",", 1);
    340 }
    341 
    342 /* netstring_fflush - flush netstring stream */
    343 
    344 void    netstring_fflush(VSTREAM *stream)
    345 {
    346     if (vstream_fflush(stream) == VSTREAM_EOF)
    347 	netstring_except(stream, vstream_ftimeout(stream) ?
    348 			 NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
    349 }
    350 
    351 /* netstring_memcpy - copy data as in-memory netstring */
    352 
    353 VSTRING *netstring_memcpy(VSTRING *buf, const char *src, ssize_t len)
    354 {
    355     vstring_sprintf(buf, "%ld:", (long) len);
    356     vstring_memcat(buf, src, len);
    357     VSTRING_ADDCH(buf, ',');
    358     return (buf);
    359 }
    360 
    361 /* netstring_memcat - append data as in-memory netstring */
    362 
    363 VSTRING *netstring_memcat(VSTRING *buf, const char *src, ssize_t len)
    364 {
    365     vstring_sprintf_append(buf, "%ld:", (long) len);
    366     vstring_memcat(buf, src, len);
    367     VSTRING_ADDCH(buf, ',');
    368     return (buf);
    369 }
    370 
    371 /* netstring_strerror - convert error number to string */
    372 
    373 const char *netstring_strerror(int err)
    374 {
    375     switch (err) {
    376 	case NETSTRING_ERR_EOF:
    377 	return ("unexpected disconnect");
    378     case NETSTRING_ERR_TIME:
    379 	errno = ETIMEDOUT;
    380 	return ("time limit exceeded");
    381     case NETSTRING_ERR_FORMAT:
    382 	errno = 0;
    383 	return ("input format error");
    384     case NETSTRING_ERR_SIZE:
    385 #ifdef EMSGSIZE
    386 	errno = EMSGSIZE;
    387 #endif
    388 	return ("input exceeds size limit");
    389     default:
    390 	errno = 0;
    391 	return ("unknown netstring error");
    392     }
    393 }
    394 
    395  /*
    396   * Proof-of-concept netstring encoder/decoder.
    397   *
    398   * Usage: netstring command...
    399   *
    400   * Run the command as a child process. Then, convert between plain strings on
    401   * our own stdin/stdout, and netstrings on the child program's stdin/stdout.
    402   *
    403   * Example (socketmap test server): netstring nc -l 9999
    404   */
    405 #ifdef TEST
    406 #include <unistd.h>
    407 #include <stdlib.h>
    408 #include <events.h>
    409 
    410 static VSTRING *stdin_read_buf;		/* stdin line buffer */
    411 static VSTRING *child_read_buf;		/* child read buffer */
    412 static VSTREAM *child_stream;		/* child stream (full-duplex) */
    413 
    414 /* stdin_read_event - line-oriented event handler */
    415 
    416 static void stdin_read_event(int event, void *context)
    417 {
    418     int     ch;
    419 
    420     /*
    421      * Send a netstring to the child when we have accumulated an entire line
    422      * of input.
    423      *
    424      * Note: the first VSTREAM_GETCHAR() call implicitly fills the VSTREAM
    425      * buffer. We must drain the entire VSTREAM buffer before requesting the
    426      * next read(2) event.
    427      */
    428     do {
    429 	ch = VSTREAM_GETCHAR();
    430 	switch (ch) {
    431 	default:
    432 	    VSTRING_ADDCH(stdin_read_buf, ch);
    433 	    break;
    434 	case '\n':
    435 	    NETSTRING_PUT_BUF(child_stream, stdin_read_buf);
    436 	    vstream_fflush(child_stream);
    437 	    VSTRING_RESET(stdin_read_buf);
    438 	    break;
    439 	case VSTREAM_EOF:
    440 	    /* Better: wait for child to terminate. */
    441 	    sleep(1);
    442 	    exit(0);
    443 	}
    444     } while (vstream_peek(VSTREAM_IN) > 0);
    445 }
    446 
    447 /* child_read_event - netstring-oriented event handler */
    448 
    449 static void child_read_event(int event, void *context)
    450 {
    451 
    452     /*
    453      * Read an entire netstring from the child and send the result to stdout.
    454      *
    455      * This is a simplistic implementation that assumes a server will not
    456      * trickle its data.
    457      *
    458      * Note: the first netstring_get() call implicitly fills the VSTREAM buffer.
    459      * We must drain the entire VSTREAM buffer before requesting the next
    460      * read(2) event.
    461      */
    462     do {
    463 	netstring_get(child_stream, child_read_buf, 10000);
    464 	vstream_fwrite(VSTREAM_OUT, STR(child_read_buf), LEN(child_read_buf));
    465 	VSTREAM_PUTC('\n', VSTREAM_OUT);
    466 	vstream_fflush(VSTREAM_OUT);
    467     } while (vstream_peek(child_stream) > 0);
    468 }
    469 
    470 int     main(int argc, char **argv)
    471 {
    472     int     err;
    473 
    474     /*
    475      * Sanity check.
    476      */
    477     if (argv[1] == 0)
    478 	msg_fatal("usage: %s command...", argv[0]);
    479 
    480     /*
    481      * Run the specified command as a child process with stdin and stdout
    482      * connected to us.
    483      */
    484     child_stream = vstream_popen(O_RDWR, CA_VSTREAM_POPEN_ARGV(argv + 1),
    485 				 CA_VSTREAM_POPEN_END);
    486     vstream_control(child_stream, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END);
    487     netstring_setup(child_stream, 10);
    488 
    489     /*
    490      * Buffer plumbing.
    491      */
    492     stdin_read_buf = vstring_alloc(100);
    493     child_read_buf = vstring_alloc(100);
    494 
    495     /*
    496      * Monitor both the child's stdout stream and our own stdin stream. If
    497      * there is activity on the child stdout stream, read an entire netstring
    498      * or EOF. If there is activity on stdin, send a netstring to the child
    499      * when we have read an entire line, or terminate in case of EOF.
    500      */
    501     event_enable_read(vstream_fileno(VSTREAM_IN), stdin_read_event, (void *) 0);
    502     event_enable_read(vstream_fileno(child_stream), child_read_event,
    503 		      (void *) 0);
    504 
    505     if ((err = vstream_setjmp(child_stream)) == 0) {
    506 	for (;;)
    507 	    event_loop(-1);
    508     } else {
    509 	msg_fatal("%s: %s", argv[1], netstring_strerror(err));
    510     }
    511 }
    512 
    513 #endif
    514