Current File : //root/postfix-3.2.0/src/util/netstring.c |
/*++
/* 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
/*--*/
/* 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";
/*
* Allocate buffer space.
*/
VSTRING_RESET(buf);
VSTRING_SPACE(buf, len);
/*
* Read the payload and absorb the terminator.
*/
if (vstream_fread(stream, STR(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);
/*
* Position the buffer.
*/
VSTRING_AT_OFFSET(buf, len);
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