1/* $NetBSD: netstring.c,v 1.3 2020/03/18 19:05:21 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/* http://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 163/* System library. */ 164 165#include <sys_defs.h> 166#include <stdarg.h> 167#include <ctype.h> 168 169/* Utility library. */ 170 171#include <msg.h> 172#include <vstream.h> 173#include <vstring.h> 174#include <compat_va_copy.h> 175#include <netstring.h> 176 177/* Application-specific. */ 178 179#define STR(x) vstring_str(x) 180#define LEN(x) VSTRING_LEN(x) 181 182/* netstring_setup - initialize netstring stream */ 183 184void netstring_setup(VSTREAM *stream, int timeout) 185{ 186 vstream_control(stream, 187 CA_VSTREAM_CTL_TIMEOUT(timeout), 188 CA_VSTREAM_CTL_EXCEPT, 189 CA_VSTREAM_CTL_END); 190} 191 192/* netstring_except - process netstring stream exception */ 193 194void netstring_except(VSTREAM *stream, int exception) 195{ 196 vstream_longjmp(stream, exception); 197} 198 199/* netstring_get_length - read netstring length + terminator */ 200 201ssize_t netstring_get_length(VSTREAM *stream) 202{ 203 const char *myname = "netstring_get_length"; 204 ssize_t len = 0; 205 int ch; 206 int digit; 207 208 for (;;) { 209 switch (ch = VSTREAM_GETC(stream)) { 210 case VSTREAM_EOF: 211 netstring_except(stream, vstream_ftimeout(stream) ? 212 NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); 213 case ':': 214 if (msg_verbose > 1) 215 msg_info("%s: read netstring length %ld", myname, (long) len); 216 return (len); 217 default: 218 if (!ISDIGIT(ch)) 219 netstring_except(stream, NETSTRING_ERR_FORMAT); 220 digit = ch - '0'; 221 if (len > SSIZE_T_MAX / 10 222 || (len *= 10) > SSIZE_T_MAX - digit) 223 netstring_except(stream, NETSTRING_ERR_SIZE); 224 len += digit; 225 break; 226 } 227 } 228} 229 230/* netstring_get_data - read netstring payload + terminator */ 231 232VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, ssize_t len) 233{ 234 const char *myname = "netstring_get_data"; 235 236 /* 237 * Read the payload and absorb the terminator. 238 */ 239 if (vstream_fread_buf(stream, buf, len) != len) 240 netstring_except(stream, vstream_ftimeout(stream) ? 241 NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); 242 if (msg_verbose > 1) 243 msg_info("%s: read netstring data %.*s", 244 myname, (int) (len < 30 ? len : 30), STR(buf)); 245 netstring_get_terminator(stream); 246 247 /* 248 * Return the buffer. 249 */ 250 return (buf); 251} 252 253/* netstring_get_terminator - absorb netstring terminator */ 254 255void netstring_get_terminator(VSTREAM *stream) 256{ 257 if (VSTREAM_GETC(stream) != ',') 258 netstring_except(stream, NETSTRING_ERR_FORMAT); 259} 260 261/* netstring_get - read string from netstring stream */ 262 263VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, ssize_t limit) 264{ 265 ssize_t len; 266 267 len = netstring_get_length(stream); 268 if (ENFORCING_SIZE_LIMIT(limit) && len > limit) 269 netstring_except(stream, NETSTRING_ERR_SIZE); 270 netstring_get_data(stream, buf, len); 271 return (buf); 272} 273 274/* netstring_put - send string as netstring */ 275 276void netstring_put(VSTREAM *stream, const char *data, ssize_t len) 277{ 278 const char *myname = "netstring_put"; 279 280 if (msg_verbose > 1) 281 msg_info("%s: write netstring len %ld data %.*s", 282 myname, (long) len, (int) (len < 30 ? len : 30), data); 283 vstream_fprintf(stream, "%ld:", (long) len); 284 vstream_fwrite(stream, data, len); 285 VSTREAM_PUTC(',', stream); 286} 287 288/* netstring_put_multi - send multiple strings as one netstring */ 289 290void netstring_put_multi(VSTREAM *stream,...) 291{ 292 const char *myname = "netstring_put_multi"; 293 ssize_t total; 294 char *data; 295 ssize_t data_len; 296 va_list ap; 297 va_list ap2; 298 299 /* 300 * Initialize argument lists. 301 */ 302 va_start(ap, stream); 303 VA_COPY(ap2, ap); 304 305 /* 306 * Figure out the total result size. 307 */ 308 for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len) 309 if ((data_len = va_arg(ap, ssize_t)) < 0) 310 msg_panic("%s: bad data length %ld", myname, (long) data_len); 311 va_end(ap); 312 if (total < 0) 313 msg_panic("%s: bad total length %ld", myname, (long) total); 314 if (msg_verbose > 1) 315 msg_info("%s: write total length %ld", myname, (long) total); 316 317 /* 318 * Send the length, content and terminator. 319 */ 320 vstream_fprintf(stream, "%ld:", (long) total); 321 while ((data = va_arg(ap2, char *)) != 0) { 322 data_len = va_arg(ap2, ssize_t); 323 if (msg_verbose > 1) 324 msg_info("%s: write netstring len %ld data %.*s", 325 myname, (long) data_len, 326 (int) (data_len < 30 ? data_len : 30), data); 327 if (vstream_fwrite(stream, data, data_len) != data_len) 328 netstring_except(stream, vstream_ftimeout(stream) ? 329 NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); 330 } 331 va_end(ap2); 332 vstream_fwrite(stream, ",", 1); 333} 334 335/* netstring_fflush - flush netstring stream */ 336 337void netstring_fflush(VSTREAM *stream) 338{ 339 if (vstream_fflush(stream) == VSTREAM_EOF) 340 netstring_except(stream, vstream_ftimeout(stream) ? 341 NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); 342} 343 344/* netstring_memcpy - copy data as in-memory netstring */ 345 346VSTRING *netstring_memcpy(VSTRING *buf, const char *src, ssize_t len) 347{ 348 vstring_sprintf(buf, "%ld:", (long) len); 349 vstring_memcat(buf, src, len); 350 VSTRING_ADDCH(buf, ','); 351 return (buf); 352} 353 354/* netstring_memcat - append data as in-memory netstring */ 355 356VSTRING *netstring_memcat(VSTRING *buf, const char *src, ssize_t len) 357{ 358 vstring_sprintf_append(buf, "%ld:", (long) len); 359 vstring_memcat(buf, src, len); 360 VSTRING_ADDCH(buf, ','); 361 return (buf); 362} 363 364/* netstring_strerror - convert error number to string */ 365 366const char *netstring_strerror(int err) 367{ 368 switch (err) { 369 case NETSTRING_ERR_EOF: 370 return ("unexpected disconnect"); 371 case NETSTRING_ERR_TIME: 372 return ("time limit exceeded"); 373 case NETSTRING_ERR_FORMAT: 374 return ("input format error"); 375 case NETSTRING_ERR_SIZE: 376 return ("input exceeds size limit"); 377 default: 378 return ("unknown netstring error"); 379 } 380} 381 382 /* 383 * Proof-of-concept netstring encoder/decoder. 384 * 385 * Usage: netstring command... 386 * 387 * Run the command as a child process. Then, convert between plain strings on 388 * our own stdin/stdout, and netstrings on the child program's stdin/stdout. 389 * 390 * Example (socketmap test server): netstring nc -l 9999 391 */ 392#ifdef TEST 393#include <unistd.h> 394#include <stdlib.h> 395#include <events.h> 396 397static VSTRING *stdin_read_buf; /* stdin line buffer */ 398static VSTRING *child_read_buf; /* child read buffer */ 399static VSTREAM *child_stream; /* child stream (full-duplex) */ 400 401/* stdin_read_event - line-oriented event handler */ 402 403static void stdin_read_event(int event, void *context) 404{ 405 int ch; 406 407 /* 408 * Send a netstring to the child when we have accumulated an entire line 409 * of input. 410 * 411 * Note: the first VSTREAM_GETCHAR() call implicitly fills the VSTREAM 412 * buffer. We must drain the entire VSTREAM buffer before requesting the 413 * next read(2) event. 414 */ 415 do { 416 ch = VSTREAM_GETCHAR(); 417 switch (ch) { 418 default: 419 VSTRING_ADDCH(stdin_read_buf, ch); 420 break; 421 case '\n': 422 NETSTRING_PUT_BUF(child_stream, stdin_read_buf); 423 vstream_fflush(child_stream); 424 VSTRING_RESET(stdin_read_buf); 425 break; 426 case VSTREAM_EOF: 427 /* Better: wait for child to terminate. */ 428 sleep(1); 429 exit(0); 430 } 431 } while (vstream_peek(VSTREAM_IN) > 0); 432} 433 434/* child_read_event - netstring-oriented event handler */ 435 436static void child_read_event(int event, void *context) 437{ 438 439 /* 440 * Read an entire netstring from the child and send the result to stdout. 441 * 442 * This is a simplistic implementation that assumes a server will not 443 * trickle its data. 444 * 445 * Note: the first netstring_get() call implicitly fills the VSTREAM buffer. 446 * We must drain the entire VSTREAM buffer before requesting the next 447 * read(2) event. 448 */ 449 do { 450 netstring_get(child_stream, child_read_buf, 10000); 451 vstream_fwrite(VSTREAM_OUT, STR(child_read_buf), LEN(child_read_buf)); 452 VSTREAM_PUTC('\n', VSTREAM_OUT); 453 vstream_fflush(VSTREAM_OUT); 454 } while (vstream_peek(child_stream) > 0); 455} 456 457int main(int argc, char **argv) 458{ 459 int err; 460 461 /* 462 * Sanity check. 463 */ 464 if (argv[1] == 0) 465 msg_fatal("usage: %s command...", argv[0]); 466 467 /* 468 * Run the specified command as a child process with stdin and stdout 469 * connected to us. 470 */ 471 child_stream = vstream_popen(O_RDWR, CA_VSTREAM_POPEN_ARGV(argv + 1), 472 CA_VSTREAM_POPEN_END); 473 vstream_control(child_stream, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END); 474 netstring_setup(child_stream, 10); 475 476 /* 477 * Buffer plumbing. 478 */ 479 stdin_read_buf = vstring_alloc(100); 480 child_read_buf = vstring_alloc(100); 481 482 /* 483 * Monitor both the child's stdout stream and our own stdin stream. If 484 * there is activity on the child stdout stream, read an entire netstring 485 * or EOF. If there is activity on stdin, send a netstring to the child 486 * when we have read an entire line, or terminate in case of EOF. 487 */ 488 event_enable_read(vstream_fileno(VSTREAM_IN), stdin_read_event, (void *) 0); 489 event_enable_read(vstream_fileno(child_stream), child_read_event, 490 (void *) 0); 491 492 if ((err = vstream_setjmp(child_stream)) == 0) { 493 for (;;) 494 event_loop(-1); 495 } else { 496 msg_fatal("%s: %s", argv[1], netstring_strerror(err)); 497 } 498} 499 500#endif 501