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