xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/attr_scan_plain.c (revision 413d532bcc3f62d122e56d92e13ac64825a40baf)
1 /*	$NetBSD: attr_scan_plain.c,v 1.1.1.1 2009/06/23 10:08:58 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	attr_scan_plain 3
6 /* SUMMARY
7 /*	recover attributes from byte stream
8 /* SYNOPSIS
9 /*	#include <attr.h>
10 /*
11 /*	int	attr_scan_plain(fp, flags, type, name, ..., ATTR_TYPE_END)
12 /*	VSTREAM	fp;
13 /*	int	flags;
14 /*	int	type;
15 /*	char	*name;
16 /*
17 /*	int	attr_vscan_plain(fp, flags, ap)
18 /*	VSTREAM	fp;
19 /*	int	flags;
20 /*	va_list	ap;
21 /* DESCRIPTION
22 /*	attr_scan_plain() takes zero or more (name, value) request attributes
23 /*	and recovers the attribute values from the byte stream that was
24 /*	possibly generated by attr_print_plain().
25 /*
26 /*	attr_vscan_plain() provides an alternative interface that is convenient
27 /*	for calling from within a variadic function.
28 /*
29 /*	The input stream is formatted as follows, where (item)* stands
30 /*	for zero or more instances of the specified item, and where
31 /*	(item1 | item2) stands for choice:
32 /*
33 /* .in +5
34 /*	attr-list :== simple-attr* newline
35 /* .br
36 /*	simple-attr :== attr-name "=" attr-value newline
37 /* .br
38 /*	attr-name :== any string without null or "=" or newline.
39 /* .br
40 /*	attr-value :== any string without null or newline.
41 /* .br
42 /*	newline :== the ASCII newline character
43 /* .in
44 /*
45 /*	All attribute names and attribute values are sent as plain
46 /*	strings. Each string must be no longer than 4*var_line_limit
47 /*	characters. The formatting rules aim to make implementations in PERL
48 /*	and other languages easy.
49 /*
50 /*	Normally, attributes must be received in the sequence as specified
51 /*	with the attr_scan_plain() argument list.  The input stream may
52 /*	contain additional attributes at any point in the input stream,
53 /*	including additional instances of requested attributes.
54 /*
55 /*	Additional input attributes or input attribute instances are silently
56 /*	skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
57 /*	(see below). This allows for some flexibility in the evolution of
58 /*	protocols while still providing the option of being strict where
59 /*	this is desirable.
60 /*
61 /*	Arguments:
62 /* .IP fp
63 /*	Stream to recover the input attributes from.
64 /* .IP flags
65 /*	The bit-wise OR of zero or more of the following.
66 /* .RS
67 /* .IP ATTR_FLAG_MISSING
68 /*	Log a warning when the input attribute list terminates before all
69 /*	requested attributes are recovered. It is always an error when the
70 /*	input stream ends without the newline attribute list terminator.
71 /* .IP ATTR_FLAG_EXTRA
72 /*	Log a warning and stop attribute recovery when the input stream
73 /*	contains an attribute that was not requested. This includes the
74 /*	case of additional instances of a requested attribute.
75 /* .IP ATTR_FLAG_MORE
76 /*	After recovering the requested attributes, leave the input stream
77 /*	in a state that is usable for more attr_scan_plain() operations
78 /*	from the same input attribute list.
79 /*	By default, attr_scan_plain() skips forward past the input attribute
80 /*	list terminator.
81 /* .IP ATTR_FLAG_STRICT
82 /*	For convenience, this value combines both ATTR_FLAG_MISSING and
83 /*	ATTR_FLAG_EXTRA.
84 /* .IP ATTR_FLAG_NONE
85 /*	For convenience, this value requests none of the above.
86 /* .RE
87 /* .IP type
88 /*	The type argument determines the arguments that follow.
89 /* .RS
90 /* .IP "ATTR_TYPE_INT (char *, int *)"
91 /*	This argument is followed by an attribute name and an integer pointer.
92 /* .IP "ATTR_TYPE_LONG (char *, long *)"
93 /*	This argument is followed by an attribute name and a long pointer.
94 /* .IP "ATTR_TYPE_STR (char *, VSTRING *)"
95 /*	This argument is followed by an attribute name and a VSTRING pointer.
96 /* .IP "ATTR_TYPE_DATA (char *, VSTRING *)"
97 /*	This argument is followed by an attribute name and a VSTRING pointer.
98 /* .IP "ATTR_TYPE_FUNC (ATTR_SCAN_SLAVE_FN, void *)"
99 /*	This argument is followed by a function pointer and a generic data
100 /*	pointer. The caller-specified function returns < 0 in case of
101 /*	error.
102 /* .IP "ATTR_TYPE_HASH (HTABLE *)"
103 /* .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)"
104 /*	All further input attributes are processed as string attributes.
105 /*	No specific attribute sequence is enforced.
106 /*	All attributes up to the attribute list terminator are read,
107 /*	but only the first instance of each attribute is stored.
108 /*	There can be no more than 1024 attributes in a hash table.
109 /* .sp
110 /*	The attribute string values are stored in the hash table under
111 /*	keys equal to the attribute name (obtained from the input stream).
112 /*	Values from the input stream are added to the hash table. Existing
113 /*	hash table entries are not replaced.
114 /* .sp
115 /*	N.B. This construct must be followed by an ATTR_TYPE_END argument.
116 /* .IP ATTR_TYPE_END
117 /*	This argument terminates the requested attribute list.
118 /* .RE
119 /* BUGS
120 /*	ATTR_TYPE_HASH (ATTR_TYPE_NAMEVAL) accepts attributes with arbitrary
121 /*	names from possibly untrusted sources.
122 /*	This is unsafe, unless the resulting table is queried only with
123 /*	known to be good attribute names.
124 /* DIAGNOSTICS
125 /*	attr_scan_plain() and attr_vscan_plain() return -1 when malformed input
126 /*	is detected (string too long, incomplete line, missing end marker).
127 /*	Otherwise, the result value is the number of attributes that were
128 /*	successfully recovered from the input stream (a hash table counts
129 /*	as the number of entries stored into the table).
130 /*
131 /*	Panic: interface violation. All system call errors are fatal.
132 /* SEE ALSO
133 /*	attr_print_plain(3) send attributes over byte stream.
134 /* LICENSE
135 /* .ad
136 /* .fi
137 /*	The Secure Mailer license must be distributed with this software.
138 /* AUTHOR(S)
139 /*	Wietse Venema
140 /*	IBM T.J. Watson Research
141 /*	P.O. Box 704
142 /*	Yorktown Heights, NY 10598, USA
143 /*--*/
144 
145 /* System library. */
146 
147 #include <sys_defs.h>
148 #include <stdarg.h>
149 #include <string.h>
150 #include <stdio.h>
151 
152 /* Utility library. */
153 
154 #include <msg.h>
155 #include <mymalloc.h>
156 #include <vstream.h>
157 #include <vstring.h>
158 #include <htable.h>
159 #include <base64_code.h>
160 #include <attr.h>
161 
162 /* Application specific. */
163 
164 #define STR(x)	vstring_str(x)
165 #define LEN(x)	VSTRING_LEN(x)
166 
167 /* attr_scan_plain_string - pull a string from the input stream */
168 
169 static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf,
170 				        int terminator, const char *context)
171 {
172 #if 0
173     extern int var_line_limit;		/* XXX */
174     int     limit = var_line_limit * 4;
175 
176 #endif
177     int     ch;
178 
179     VSTRING_RESET(plain_buf);
180     while ((ch = VSTREAM_GETC(fp)) != '\n'
181 	   && (terminator == 0 || ch != terminator)) {
182 	if (ch == VSTREAM_EOF) {
183 	    msg_warn("%s on %s while reading %s",
184 		vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
185 		     VSTREAM_PATH(fp), context);
186 	    return (-1);
187 	}
188 	VSTRING_ADDCH(plain_buf, ch);
189 #if 0
190 	if (LEN(plain_buf) > limit) {
191 	    msg_warn("string length > %d characters from %s while reading %s",
192 		     limit, VSTREAM_PATH(fp), context);
193 	    return (-1);
194 	}
195 #endif
196     }
197     VSTRING_TERMINATE(plain_buf);
198 
199     if (msg_verbose)
200 	msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
201     return (ch);
202 }
203 
204 /* attr_scan_plain_data - pull a data blob from the input stream */
205 
206 static int attr_scan_plain_data(VSTREAM *fp, VSTRING *str_buf,
207 				        int terminator,
208 				        const char *context)
209 {
210     static VSTRING *base64_buf = 0;
211     int     ch;
212 
213     if (base64_buf == 0)
214 	base64_buf = vstring_alloc(10);
215     if ((ch = attr_scan_plain_string(fp, base64_buf, terminator, context)) < 0)
216 	return (-1);
217     if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
218 	msg_warn("malformed base64 data from %s while reading %s: %.100s",
219 		 VSTREAM_PATH(fp), context, STR(base64_buf));
220 	return (-1);
221     }
222     return (ch);
223 }
224 
225 /* attr_scan_plain_number - pull a number from the input stream */
226 
227 static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
228 				        int terminator, const char *context)
229 {
230     char    junk = 0;
231     int     ch;
232 
233     if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
234 	return (-1);
235     if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
236 	msg_warn("malformed numerical data from %s while reading %s: %.100s",
237 		 VSTREAM_PATH(fp), context, STR(str_buf));
238 	return (-1);
239     }
240     return (ch);
241 }
242 
243 /* attr_scan_plain_long_number - pull a number from the input stream */
244 
245 static int attr_scan_plain_long_number(VSTREAM *fp, unsigned long *ptr,
246 				               VSTRING *str_buf,
247 				               int terminator,
248 				               const char *context)
249 {
250     char    junk = 0;
251     int     ch;
252 
253     if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
254 	return (-1);
255     if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
256 	msg_warn("malformed numerical data from %s while reading %s: %.100s",
257 		 VSTREAM_PATH(fp), context, STR(str_buf));
258 	return (-1);
259     }
260     return (ch);
261 }
262 
263 /* attr_vscan_plain - receive attribute list from stream */
264 
265 int     attr_vscan_plain(VSTREAM *fp, int flags, va_list ap)
266 {
267     const char *myname = "attr_scan_plain";
268     static VSTRING *str_buf = 0;
269     static VSTRING *name_buf = 0;
270     int     wanted_type = -1;
271     char   *wanted_name;
272     unsigned int *number;
273     unsigned long *long_number;
274     VSTRING *string;
275     HTABLE *hash_table;
276     int     ch;
277     int     conversions;
278     ATTR_SCAN_SLAVE_FN scan_fn;
279     void   *scan_arg;
280 
281     /*
282      * Sanity check.
283      */
284     if (flags & ~ATTR_FLAG_ALL)
285 	msg_panic("%s: bad flags: 0x%x", myname, flags);
286 
287     /*
288      * EOF check.
289      */
290     if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
291 	return (0);
292     vstream_ungetc(fp, ch);
293 
294     /*
295      * Initialize.
296      */
297     if (str_buf == 0) {
298 	str_buf = vstring_alloc(10);
299 	name_buf = vstring_alloc(10);
300     }
301 
302     /*
303      * Iterate over all (type, name, value) triples.
304      */
305     for (conversions = 0; /* void */ ; conversions++) {
306 
307 	/*
308 	 * Determine the next attribute type and attribute name on the
309 	 * caller's wish list.
310 	 *
311 	 * If we're reading into a hash table, we already know that the
312 	 * attribute value is string-valued, and we get the attribute name
313 	 * from the input stream instead. This is secure only when the
314 	 * resulting table is queried with known to be good attribute names.
315 	 */
316 	if (wanted_type != ATTR_TYPE_HASH) {
317 	    wanted_type = va_arg(ap, int);
318 	    if (wanted_type == ATTR_TYPE_END) {
319 		if ((flags & ATTR_FLAG_MORE) != 0)
320 		    return (conversions);
321 		wanted_name = "(list terminator)";
322 	    } else if (wanted_type == ATTR_TYPE_HASH) {
323 		wanted_name = "(any attribute name or list terminator)";
324 		hash_table = va_arg(ap, HTABLE *);
325 		if (va_arg(ap, int) != ATTR_TYPE_END)
326 		    msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END",
327 			      myname);
328 	    } else if (wanted_type != ATTR_TYPE_FUNC) {
329 		wanted_name = va_arg(ap, char *);
330 	    }
331 	}
332 
333 	/*
334 	 * Locate the next attribute of interest in the input stream.
335 	 */
336 	while (wanted_type != ATTR_TYPE_FUNC) {
337 
338 	    /*
339 	     * Get the name of the next attribute. Hitting EOF is always bad.
340 	     * Hitting the end-of-input early is OK if the caller is prepared
341 	     * to deal with missing inputs.
342 	     */
343 	    if (msg_verbose)
344 		msg_info("%s: wanted attribute: %s",
345 			 VSTREAM_PATH(fp), wanted_name);
346 	    if ((ch = attr_scan_plain_string(fp, name_buf, '=',
347 				    "input attribute name")) == VSTREAM_EOF)
348 		return (-1);
349 	    if (ch == '\n' && LEN(name_buf) == 0) {
350 		if (wanted_type == ATTR_TYPE_END
351 		    || wanted_type == ATTR_TYPE_HASH)
352 		    return (conversions);
353 		if ((flags & ATTR_FLAG_MISSING) != 0)
354 		    msg_warn("missing attribute %s in input from %s",
355 			     wanted_name, VSTREAM_PATH(fp));
356 		return (conversions);
357 	    }
358 
359 	    /*
360 	     * See if the caller asks for this attribute.
361 	     */
362 	    if (wanted_type == ATTR_TYPE_HASH
363 		|| (wanted_type != ATTR_TYPE_END
364 		    && strcmp(wanted_name, STR(name_buf)) == 0))
365 		break;
366 	    if ((flags & ATTR_FLAG_EXTRA) != 0) {
367 		msg_warn("unexpected attribute %s from %s (expecting: %s)",
368 			 STR(name_buf), VSTREAM_PATH(fp), wanted_name);
369 		return (conversions);
370 	    }
371 
372 	    /*
373 	     * Skip over this attribute. The caller does not ask for it.
374 	     */
375 	    while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
376 		 /* void */ ;
377 	}
378 
379 	/*
380 	 * Do the requested conversion.
381 	 */
382 	switch (wanted_type) {
383 	case ATTR_TYPE_INT:
384 	    if (ch != '=') {
385 		msg_warn("missing value for number attribute %s from %s",
386 			 STR(name_buf), VSTREAM_PATH(fp));
387 		return (-1);
388 	    }
389 	    number = va_arg(ap, unsigned int *);
390 	    if ((ch = attr_scan_plain_number(fp, number, str_buf, 0,
391 					     "input attribute value")) < 0)
392 		return (-1);
393 	    break;
394 	case ATTR_TYPE_LONG:
395 	    if (ch != '=') {
396 		msg_warn("missing value for number attribute %s from %s",
397 			 STR(name_buf), VSTREAM_PATH(fp));
398 		return (-1);
399 	    }
400 	    long_number = va_arg(ap, unsigned long *);
401 	    if ((ch = attr_scan_plain_long_number(fp, long_number, str_buf,
402 					   0, "input attribute value")) < 0)
403 		return (-1);
404 	    break;
405 	case ATTR_TYPE_STR:
406 	    if (ch != '=') {
407 		msg_warn("missing value for string attribute %s from %s",
408 			 STR(name_buf), VSTREAM_PATH(fp));
409 		return (-1);
410 	    }
411 	    string = va_arg(ap, VSTRING *);
412 	    if ((ch = attr_scan_plain_string(fp, string, 0,
413 					     "input attribute value")) < 0)
414 		return (-1);
415 	    break;
416 	case ATTR_TYPE_DATA:
417 	    if (ch != '=') {
418 		msg_warn("missing value for data attribute %s from %s",
419 			 STR(name_buf), VSTREAM_PATH(fp));
420 		return (-1);
421 	    }
422 	    string = va_arg(ap, VSTRING *);
423 	    if ((ch = attr_scan_plain_data(fp, string, 0,
424 					   "input attribute value")) < 0)
425 		return (-1);
426 	    break;
427 	case ATTR_TYPE_FUNC:
428 	    scan_fn = va_arg(ap, ATTR_SCAN_SLAVE_FN);
429 	    scan_arg = va_arg(ap, void *);
430 	    if (scan_fn(attr_scan_plain, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
431 		return (-1);
432 	    break;
433 	case ATTR_TYPE_HASH:
434 	    if (ch != '=') {
435 		msg_warn("missing value for string attribute %s from %s",
436 			 STR(name_buf), VSTREAM_PATH(fp));
437 		return (-1);
438 	    }
439 	    if ((ch = attr_scan_plain_string(fp, str_buf, 0,
440 					     "input attribute value")) < 0)
441 		return (-1);
442 	    if (htable_locate(hash_table, STR(name_buf)) != 0) {
443 		if ((flags & ATTR_FLAG_EXTRA) != 0) {
444 		    msg_warn("duplicate attribute %s in input from %s",
445 			     STR(name_buf), VSTREAM_PATH(fp));
446 		    return (conversions);
447 		}
448 	    } else if (hash_table->used >= ATTR_HASH_LIMIT) {
449 		msg_warn("attribute count exceeds limit %d in input from %s",
450 			 ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
451 		return (conversions);
452 	    } else {
453 		htable_enter(hash_table, STR(name_buf),
454 			     mystrdup(STR(str_buf)));
455 	    }
456 	    break;
457 	default:
458 	    msg_panic("%s: unknown type code: %d", myname, wanted_type);
459 	}
460     }
461 }
462 
463 /* attr_scan_plain - read attribute list from stream */
464 
465 int     attr_scan_plain(VSTREAM *fp, int flags,...)
466 {
467     va_list ap;
468     int     ret;
469 
470     va_start(ap, flags);
471     ret = attr_vscan_plain(fp, flags, ap);
472     va_end(ap);
473     return (ret);
474 }
475 
476 #ifdef TEST
477 
478  /*
479   * Proof of concept test program.  Mirror image of the attr_scan_plain test
480   * program.
481   */
482 #include <msg_vstream.h>
483 
484 int     var_line_limit = 2048;
485 
486 int     main(int unused_argc, char **used_argv)
487 {
488     VSTRING *data_val = vstring_alloc(1);
489     VSTRING *str_val = vstring_alloc(1);
490     HTABLE *table = htable_create(1);
491     HTABLE_INFO **ht_info_list;
492     HTABLE_INFO **ht;
493     int     int_val;
494     long    long_val;
495     int     ret;
496 
497     msg_verbose = 1;
498     msg_vstream_init(used_argv[0], VSTREAM_ERR);
499     if ((ret = attr_scan_plain(VSTREAM_IN,
500 			       ATTR_FLAG_STRICT,
501 			       ATTR_TYPE_INT, ATTR_NAME_INT, &int_val,
502 			       ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val,
503 			       ATTR_TYPE_STR, ATTR_NAME_STR, str_val,
504 			       ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val,
505 			       ATTR_TYPE_HASH, table,
506 			       ATTR_TYPE_END)) > 4) {
507 	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
508 	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
509 	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
510 	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
511 	ht_info_list = htable_list(table);
512 	for (ht = ht_info_list; *ht; ht++)
513 	    vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value);
514 	myfree((char *) ht_info_list);
515     } else {
516 	vstream_printf("return: %d\n", ret);
517     }
518     if ((ret = attr_scan_plain(VSTREAM_IN,
519 			       ATTR_FLAG_STRICT,
520 			       ATTR_TYPE_INT, ATTR_NAME_INT, &int_val,
521 			       ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val,
522 			       ATTR_TYPE_STR, ATTR_NAME_STR, str_val,
523 			       ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val,
524 			       ATTR_TYPE_END)) == 4) {
525 	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
526 	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
527 	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
528 	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
529 	ht_info_list = htable_list(table);
530 	for (ht = ht_info_list; *ht; ht++)
531 	    vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value);
532 	myfree((char *) ht_info_list);
533     } else {
534 	vstream_printf("return: %d\n", ret);
535     }
536     if (vstream_fflush(VSTREAM_OUT) != 0)
537 	msg_fatal("write error: %m");
538 
539     vstring_free(data_val);
540     vstring_free(str_val);
541     htable_free(table, myfree);
542 
543     return (0);
544 }
545 
546 #endif
547