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