summaryrefslogtreecommitdiffstats
path: root/src/util/netstring.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/netstring.c498
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..0edd80e
--- /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 (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 <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