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