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