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