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