/*++ /* NAME /* netstring 3 /* SUMMARY /* netstring stream I/O support /* SYNOPSIS /* #include /* /* void netstring_setup(stream, timeout) /* VSTREAM *stream; /* int timeout; /* /* void netstring_except(stream, exception) /* VSTREAM *stream; /* int exception; /* /* const char *netstring_strerror(err) /* int err; /* /* VSTRING *netstring_get(stream, buf, limit) /* VSTREAM *stream; /* VSTRING *buf; /* ssize_t limit; /* /* void netstring_put(stream, data, len) /* VSTREAM *stream; /* const char *data; /* ssize_t len; /* /* void netstring_put_multi(stream, data, len, data, len, ..., 0) /* VSTREAM *stream; /* const char *data; /* ssize_t len; /* /* void NETSTRING_PUT_BUF(stream, buf) /* VSTREAM *stream; /* VSTRING *buf; /* /* void netstring_fflush(stream) /* VSTREAM *stream; /* /* VSTRING *netstring_memcpy(buf, data, len) /* VSTRING *buf; /* const char *data; /* ssize_t len; /* /* VSTRING *netstring_memcat(buf, data, len) /* VSTRING *buf; /* const char *src; /* ssize_t len; /* AUXILIARY ROUTINES /* ssize_t netstring_get_length(stream) /* VSTREAM *stream; /* /* VSTRING *netstring_get_data(stream, buf, len) /* VSTREAM *stream; /* VSTRING *buf; /* ssize_t len; /* /* void netstring_get_terminator(stream) /* VSTREAM *stream; /* DESCRIPTION /* This module reads and writes netstrings with error detection: /* timeouts, unexpected end-of-file, or format errors. Netstring /* is a data format designed by Daniel Bernstein. /* /* netstring_setup() arranges for a time limit on the netstring /* read and write operations described below. /* This routine alters the behavior of streams as follows: /* .IP \(bu /* The read/write timeout is set to the specified value. /* .IP \(bu /* The stream is configured to enable exception handling. /* .PP /* netstring_except() raises the specified exception on the /* named stream. See the DIAGNOSTICS section below. /* /* netstring_strerror() converts an exception number to string. /* /* netstring_get() reads a netstring from the specified stream /* and extracts its content. The limit specifies a maximal size. /* Specify zero to disable the size limit. The result is not null /* terminated. The result value is the buf argument. /* /* netstring_put() encapsulates the specified string as a netstring /* and sends the result to the specified stream. /* The stream output buffer is not flushed. /* /* netstring_put_multi() encapsulates the content of multiple strings /* as one netstring and sends the result to the specified stream. The /* argument list must be terminated with a null data pointer. /* The stream output buffer is not flushed. /* /* NETSTRING_PUT_BUF() is a macro that provides a VSTRING-based /* wrapper for the netstring_put() routine. /* /* netstring_fflush() flushes the output buffer of the specified /* stream and handles any errors. /* /* netstring_memcpy() encapsulates the specified data as a netstring /* and copies the result over the specified buffer. The result /* value is the buffer. /* /* netstring_memcat() encapsulates the specified data as a netstring /* and appends the result to the specified buffer. The result /* value is the buffer. /* /* The following routines provide low-level access to a netstring /* stream. /* /* netstring_get_length() reads a length field from the specified /* stream, and absorbs the netstring length field terminator. /* /* netstring_get_data() reads the specified number of bytes from the /* specified stream into the specified buffer, and absorbs the /* netstring terminator. The result value is the buf argument. /* /* netstring_get_terminator() reads the netstring terminator from /* the specified stream. /* DIAGNOSTICS /* .fi /* .ad /* In case of error, a vstream_longjmp() call is performed to the /* caller-provided context specified with vstream_setjmp(). /* Error codes passed along with vstream_longjmp() are: /* .IP NETSTRING_ERR_EOF /* An I/O error happened, or the peer has disconnected unexpectedly. /* .IP NETSTRING_ERR_TIME /* The time limit specified to netstring_setup() was exceeded. /* .IP NETSTRING_ERR_FORMAT /* The input contains an unexpected character value. /* .IP NETSTRING_ERR_SIZE /* The input is larger than acceptable. /* BUGS /* The timeout deadline affects all I/O on the named stream, not /* just the I/O done on behalf of this module. /* /* The timeout deadline overwrites any previously set up state on /* the named stream. /* /* netstrings are not null terminated, which makes printing them /* a bit awkward. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* SEE ALSO /* http://cr.yp.to/proto/netstrings.txt, netstring definition /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* System library. */ #include #include #include /* Utility library. */ #include #include #include #include #include /* Application-specific. */ #define STR(x) vstring_str(x) #define LEN(x) VSTRING_LEN(x) /* netstring_setup - initialize netstring stream */ void netstring_setup(VSTREAM *stream, int timeout) { vstream_control(stream, CA_VSTREAM_CTL_TIMEOUT(timeout), CA_VSTREAM_CTL_EXCEPT, CA_VSTREAM_CTL_END); } /* netstring_except - process netstring stream exception */ void netstring_except(VSTREAM *stream, int exception) { vstream_longjmp(stream, exception); } /* netstring_get_length - read netstring length + terminator */ ssize_t netstring_get_length(VSTREAM *stream) { const char *myname = "netstring_get_length"; ssize_t len = 0; int ch; int digit; for (;;) { switch (ch = VSTREAM_GETC(stream)) { case VSTREAM_EOF: netstring_except(stream, vstream_ftimeout(stream) ? NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); case ':': if (msg_verbose > 1) msg_info("%s: read netstring length %ld", myname, (long) len); return (len); default: if (!ISDIGIT(ch)) netstring_except(stream, NETSTRING_ERR_FORMAT); digit = ch - '0'; if (len > SSIZE_T_MAX / 10 || (len *= 10) > SSIZE_T_MAX - digit) netstring_except(stream, NETSTRING_ERR_SIZE); len += digit; break; } } } /* netstring_get_data - read netstring payload + terminator */ VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, ssize_t len) { const char *myname = "netstring_get_data"; /* * Read the payload and absorb the terminator. */ if (vstream_fread_buf(stream, buf, len) != len) netstring_except(stream, vstream_ftimeout(stream) ? NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); if (msg_verbose > 1) msg_info("%s: read netstring data %.*s", myname, (int) (len < 30 ? len : 30), STR(buf)); netstring_get_terminator(stream); /* * Return the buffer. */ return (buf); } /* netstring_get_terminator - absorb netstring terminator */ void netstring_get_terminator(VSTREAM *stream) { if (VSTREAM_GETC(stream) != ',') netstring_except(stream, NETSTRING_ERR_FORMAT); } /* netstring_get - read string from netstring stream */ VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, ssize_t limit) { ssize_t len; len = netstring_get_length(stream); if (ENFORCING_SIZE_LIMIT(limit) && len > limit) netstring_except(stream, NETSTRING_ERR_SIZE); netstring_get_data(stream, buf, len); return (buf); } /* netstring_put - send string as netstring */ void netstring_put(VSTREAM *stream, const char *data, ssize_t len) { const char *myname = "netstring_put"; if (msg_verbose > 1) msg_info("%s: write netstring len %ld data %.*s", myname, (long) len, (int) (len < 30 ? len : 30), data); vstream_fprintf(stream, "%ld:", (long) len); vstream_fwrite(stream, data, len); VSTREAM_PUTC(',', stream); } /* netstring_put_multi - send multiple strings as one netstring */ void netstring_put_multi(VSTREAM *stream,...) { const char *myname = "netstring_put_multi"; ssize_t total; char *data; ssize_t data_len; va_list ap; va_list ap2; /* * Initialize argument lists. */ va_start(ap, stream); VA_COPY(ap2, ap); /* * Figure out the total result size. */ for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len) if ((data_len = va_arg(ap, ssize_t)) < 0) msg_panic("%s: bad data length %ld", myname, (long) data_len); va_end(ap); if (total < 0) msg_panic("%s: bad total length %ld", myname, (long) total); if (msg_verbose > 1) msg_info("%s: write total length %ld", myname, (long) total); /* * Send the length, content and terminator. */ vstream_fprintf(stream, "%ld:", (long) total); while ((data = va_arg(ap2, char *)) != 0) { data_len = va_arg(ap2, ssize_t); if (msg_verbose > 1) msg_info("%s: write netstring len %ld data %.*s", myname, (long) data_len, (int) (data_len < 30 ? data_len : 30), data); if (vstream_fwrite(stream, data, data_len) != data_len) netstring_except(stream, vstream_ftimeout(stream) ? NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); } va_end(ap2); vstream_fwrite(stream, ",", 1); } /* netstring_fflush - flush netstring stream */ void netstring_fflush(VSTREAM *stream) { if (vstream_fflush(stream) == VSTREAM_EOF) netstring_except(stream, vstream_ftimeout(stream) ? NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); } /* netstring_memcpy - copy data as in-memory netstring */ VSTRING *netstring_memcpy(VSTRING *buf, const char *src, ssize_t len) { vstring_sprintf(buf, "%ld:", (long) len); vstring_memcat(buf, src, len); VSTRING_ADDCH(buf, ','); return (buf); } /* netstring_memcat - append data as in-memory netstring */ VSTRING *netstring_memcat(VSTRING *buf, const char *src, ssize_t len) { vstring_sprintf_append(buf, "%ld:", (long) len); vstring_memcat(buf, src, len); VSTRING_ADDCH(buf, ','); return (buf); } /* netstring_strerror - convert error number to string */ const char *netstring_strerror(int err) { switch (err) { case NETSTRING_ERR_EOF: return ("unexpected disconnect"); case NETSTRING_ERR_TIME: return ("time limit exceeded"); case NETSTRING_ERR_FORMAT: return ("input format error"); case NETSTRING_ERR_SIZE: return ("input exceeds size limit"); default: return ("unknown netstring error"); } } /* * Proof-of-concept netstring encoder/decoder. * * Usage: netstring command... * * Run the command as a child process. Then, convert between plain strings on * our own stdin/stdout, and netstrings on the child program's stdin/stdout. * * Example (socketmap test server): netstring nc -l 9999 */ #ifdef TEST #include #include #include static VSTRING *stdin_read_buf; /* stdin line buffer */ static VSTRING *child_read_buf; /* child read buffer */ static VSTREAM *child_stream; /* child stream (full-duplex) */ /* stdin_read_event - line-oriented event handler */ static void stdin_read_event(int event, void *context) { int ch; /* * Send a netstring to the child when we have accumulated an entire line * of input. * * Note: the first VSTREAM_GETCHAR() call implicitly fills the VSTREAM * buffer. We must drain the entire VSTREAM buffer before requesting the * next read(2) event. */ do { ch = VSTREAM_GETCHAR(); switch (ch) { default: VSTRING_ADDCH(stdin_read_buf, ch); break; case '\n': NETSTRING_PUT_BUF(child_stream, stdin_read_buf); vstream_fflush(child_stream); VSTRING_RESET(stdin_read_buf); break; case VSTREAM_EOF: /* Better: wait for child to terminate. */ sleep(1); exit(0); } } while (vstream_peek(VSTREAM_IN) > 0); } /* child_read_event - netstring-oriented event handler */ static void child_read_event(int event, void *context) { /* * Read an entire netstring from the child and send the result to stdout. * * This is a simplistic implementation that assumes a server will not * trickle its data. * * Note: the first netstring_get() call implicitly fills the VSTREAM buffer. * We must drain the entire VSTREAM buffer before requesting the next * read(2) event. */ do { netstring_get(child_stream, child_read_buf, 10000); vstream_fwrite(VSTREAM_OUT, STR(child_read_buf), LEN(child_read_buf)); VSTREAM_PUTC('\n', VSTREAM_OUT); vstream_fflush(VSTREAM_OUT); } while (vstream_peek(child_stream) > 0); } int main(int argc, char **argv) { int err; /* * Sanity check. */ if (argv[1] == 0) msg_fatal("usage: %s command...", argv[0]); /* * Run the specified command as a child process with stdin and stdout * connected to us. */ child_stream = vstream_popen(O_RDWR, CA_VSTREAM_POPEN_ARGV(argv + 1), CA_VSTREAM_POPEN_END); vstream_control(child_stream, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END); netstring_setup(child_stream, 10); /* * Buffer plumbing. */ stdin_read_buf = vstring_alloc(100); child_read_buf = vstring_alloc(100); /* * Monitor both the child's stdout stream and our own stdin stream. If * there is activity on the child stdout stream, read an entire netstring * or EOF. If there is activity on stdin, send a netstring to the child * when we have read an entire line, or terminate in case of EOF. */ event_enable_read(vstream_fileno(VSTREAM_IN), stdin_read_event, (void *) 0); event_enable_read(vstream_fileno(child_stream), child_read_event, (void *) 0); if ((err = vstream_setjmp(child_stream)) == 0) { for (;;) event_loop(-1); } else { msg_fatal("%s: %s", argv[1], netstring_strerror(err)); } } #endif