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 
netstring_setup(VSTREAM * stream,int timeout)184 void    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 
netstring_except(VSTREAM * stream,int exception)194 void    netstring_except(VSTREAM *stream, int exception)
195 {
196     vstream_longjmp(stream, exception);
197 }
198 
199 /* netstring_get_length - read netstring length + terminator */
200 
netstring_get_length(VSTREAM * stream)201 ssize_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 
netstring_get_data(VSTREAM * stream,VSTRING * buf,ssize_t len)232 VSTRING *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 
netstring_get_terminator(VSTREAM * stream)255 void    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 
netstring_get(VSTREAM * stream,VSTRING * buf,ssize_t limit)263 VSTRING *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 
netstring_put(VSTREAM * stream,const char * data,ssize_t len)276 void    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 
netstring_put_multi(VSTREAM * stream,...)290 void    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 
netstring_fflush(VSTREAM * stream)337 void    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 
netstring_memcpy(VSTRING * buf,const char * src,ssize_t len)346 VSTRING *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 
netstring_memcat(VSTRING * buf,const char * src,ssize_t len)356 VSTRING *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 
netstring_strerror(int err)366 const 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 
397 static VSTRING *stdin_read_buf;		/* stdin line buffer */
398 static VSTRING *child_read_buf;		/* child read buffer */
399 static VSTREAM *child_stream;		/* child stream (full-duplex) */
400 
401 /* stdin_read_event - line-oriented event handler */
402 
stdin_read_event(int event,void * context)403 static 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 
child_read_event(int event,void * context)436 static 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 
main(int argc,char ** argv)457 int     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