diff options
Diffstat (limited to 'src/util/netstring.c')
-rw-r--r-- | src/util/netstring.c | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/src/util/netstring.c b/src/util/netstring.c new file mode 100644 index 0000000..fae8757 --- /dev/null +++ b/src/util/netstring.c @@ -0,0 +1,498 @@ +/*++ +/* NAME +/* netstring 3 +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include <netstring.h> +/* +/* 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 <sys_defs.h> +#include <stdarg.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <compat_va_copy.h> +#include <netstring.h> + +/* 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 (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 <unistd.h> +#include <stdlib.h> +#include <events.h> + +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 |