1/*++
2/* NAME
3/*	attr_scan64 3
4/* SUMMARY
5/*	recover attributes from byte stream
6/* SYNOPSIS
7/*	#include <attr.h>
8/*
9/*	int	attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END)
10/*	VSTREAM	fp;
11/*	int	flags;
12/*	int	type;
13/*	char	*name;
14/*
15/*	int	attr_vscan64(fp, flags, ap)
16/*	VSTREAM	fp;
17/*	int	flags;
18/*	va_list	ap;
19/* DESCRIPTION
20/*	attr_scan64() takes zero or more (name, value) request attributes
21/*	and recovers the attribute values from the byte stream that was
22/*	possibly generated by attr_print64().
23/*
24/*	attr_vscan64() provides an alternative interface that is convenient
25/*	for calling from within a variadic function.
26/*
27/*	The input stream is formatted as follows, where (item)* stands
28/*	for zero or more instances of the specified item, and where
29/*	(item1 | item2) stands for choice:
30/*
31/* .in +5
32/*	attr-list :== simple-attr* newline
33/* .br
34/*	simple-attr :== attr-name colon attr-value newline
35/* .br
36/*	attr-name :== any base64 encoded string
37/* .br
38/*	attr-value :== any base64 encoded string
39/* .br
40/*	colon :== the ASCII colon character
41/* .br
42/*	newline :== the ASCII newline character
43/* .in
44/*
45/*	All attribute names and attribute values are sent as base64-encoded
46/*	strings. Each base64 encoding 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 with
51/*	the attr_scan64() argument list.  The input stream may contain additional
52/*	attributes at any point in the input stream, including additional
53/*	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_scan64() operations from the
78/*	same input attribute list.
79/*	By default, attr_scan64() skips forward past the input attribute list
80/*	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_scan64() and attr_vscan64() return -1 when malformed input is
126/*	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_print64(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_scan64_string - pull a string from the input stream */
168
169static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
170{
171    static VSTRING *base64_buf = 0;
172
173#if 0
174    extern int var_line_limit;		/* XXX */
175    int     limit = var_line_limit * 4;
176
177#endif
178    int     ch;
179
180    if (base64_buf == 0)
181	base64_buf = vstring_alloc(10);
182
183    VSTRING_RESET(base64_buf);
184    while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') {
185	if (ch == VSTREAM_EOF) {
186	    msg_warn("%s on %s while reading %s",
187		vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
188		     VSTREAM_PATH(fp), context);
189	    return (-1);
190	}
191	VSTRING_ADDCH(base64_buf, ch);
192#if 0
193	if (LEN(base64_buf) > limit) {
194	    msg_warn("string length > %d characters from %s while reading %s",
195		     limit, VSTREAM_PATH(fp), context);
196	    return (-1);
197	}
198#endif
199    }
200    VSTRING_TERMINATE(base64_buf);
201    if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
202	msg_warn("malformed base64 data from %s: %.100s",
203		 VSTREAM_PATH(fp), STR(base64_buf));
204	return (-1);
205    }
206    if (msg_verbose)
207	msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
208    return (ch);
209}
210
211/* attr_scan64_number - pull a number from the input stream */
212
213static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
214			              const char *context)
215{
216    char    junk = 0;
217    int     ch;
218
219    if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
220	return (-1);
221    if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
222	msg_warn("malformed numerical data from %s while reading %s: %.100s",
223		 VSTREAM_PATH(fp), context, STR(str_buf));
224	return (-1);
225    }
226    return (ch);
227}
228
229/* attr_scan64_long_number - pull a number from the input stream */
230
231static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr,
232				           VSTRING *str_buf,
233				           const char *context)
234{
235    char    junk = 0;
236    int     ch;
237
238    if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
239	return (-1);
240    if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
241	msg_warn("malformed numerical data from %s while reading %s: %.100s",
242		 VSTREAM_PATH(fp), context, STR(str_buf));
243	return (-1);
244    }
245    return (ch);
246}
247
248/* attr_vscan64 - receive attribute list from stream */
249
250int     attr_vscan64(VSTREAM *fp, int flags, va_list ap)
251{
252    const char *myname = "attr_scan64";
253    static VSTRING *str_buf = 0;
254    static VSTRING *name_buf = 0;
255    int     wanted_type = -1;
256    char   *wanted_name;
257    unsigned int *number;
258    unsigned long *long_number;
259    VSTRING *string;
260    HTABLE *hash_table;
261    int     ch;
262    int     conversions;
263    ATTR_SCAN_SLAVE_FN scan_fn;
264    void   *scan_arg;
265
266    /*
267     * Sanity check.
268     */
269    if (flags & ~ATTR_FLAG_ALL)
270	msg_panic("%s: bad flags: 0x%x", myname, flags);
271
272    /*
273     * EOF check.
274     */
275    if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
276	return (0);
277    vstream_ungetc(fp, ch);
278
279    /*
280     * Initialize.
281     */
282    if (str_buf == 0) {
283	str_buf = vstring_alloc(10);
284	name_buf = vstring_alloc(10);
285    }
286
287    /*
288     * Iterate over all (type, name, value) triples.
289     */
290    for (conversions = 0; /* void */ ; conversions++) {
291
292	/*
293	 * Determine the next attribute type and attribute name on the
294	 * caller's wish list.
295	 *
296	 * If we're reading into a hash table, we already know that the
297	 * attribute value is string-valued, and we get the attribute name
298	 * from the input stream instead. This is secure only when the
299	 * resulting table is queried with known to be good attribute names.
300	 */
301	if (wanted_type != ATTR_TYPE_HASH) {
302	    wanted_type = va_arg(ap, int);
303	    if (wanted_type == ATTR_TYPE_END) {
304		if ((flags & ATTR_FLAG_MORE) != 0)
305		    return (conversions);
306		wanted_name = "(list terminator)";
307	    } else if (wanted_type == ATTR_TYPE_HASH) {
308		wanted_name = "(any attribute name or list terminator)";
309		hash_table = va_arg(ap, HTABLE *);
310		if (va_arg(ap, int) != ATTR_TYPE_END)
311		    msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END",
312			      myname);
313	    } else if (wanted_type != ATTR_TYPE_FUNC) {
314		wanted_name = va_arg(ap, char *);
315	    }
316	}
317
318	/*
319	 * Locate the next attribute of interest in the input stream.
320	 */
321	while (wanted_type != ATTR_TYPE_FUNC) {
322
323	    /*
324	     * Get the name of the next attribute. Hitting EOF is always bad.
325	     * Hitting the end-of-input early is OK if the caller is prepared
326	     * to deal with missing inputs.
327	     */
328	    if (msg_verbose)
329		msg_info("%s: wanted attribute: %s",
330			 VSTREAM_PATH(fp), wanted_name);
331	    if ((ch = attr_scan64_string(fp, name_buf,
332				    "input attribute name")) == VSTREAM_EOF)
333		return (-1);
334	    if (ch == '\n' && LEN(name_buf) == 0) {
335		if (wanted_type == ATTR_TYPE_END
336		    || wanted_type == ATTR_TYPE_HASH)
337		    return (conversions);
338		if ((flags & ATTR_FLAG_MISSING) != 0)
339		    msg_warn("missing attribute %s in input from %s",
340			     wanted_name, VSTREAM_PATH(fp));
341		return (conversions);
342	    }
343
344	    /*
345	     * See if the caller asks for this attribute.
346	     */
347	    if (wanted_type == ATTR_TYPE_HASH
348		|| (wanted_type != ATTR_TYPE_END
349		    && strcmp(wanted_name, STR(name_buf)) == 0))
350		break;
351	    if ((flags & ATTR_FLAG_EXTRA) != 0) {
352		msg_warn("unexpected attribute %s from %s (expecting: %s)",
353			 STR(name_buf), VSTREAM_PATH(fp), wanted_name);
354		return (conversions);
355	    }
356
357	    /*
358	     * Skip over this attribute. The caller does not ask for it.
359	     */
360	    while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
361		 /* void */ ;
362	}
363
364	/*
365	 * Do the requested conversion. If the target attribute is a
366	 * non-array type, disallow sending a multi-valued attribute, and
367	 * disallow sending no value. If the target attribute is an array
368	 * type, allow the sender to send a zero-element array (i.e. no value
369	 * at all). XXX Need to impose a bound on the number of array
370	 * elements.
371	 */
372	switch (wanted_type) {
373	case ATTR_TYPE_INT:
374	    if (ch != ':') {
375		msg_warn("missing value for number attribute %s from %s",
376			 STR(name_buf), VSTREAM_PATH(fp));
377		return (-1);
378	    }
379	    number = va_arg(ap, unsigned int *);
380	    if ((ch = attr_scan64_number(fp, number, str_buf,
381					 "input attribute value")) < 0)
382		return (-1);
383	    if (ch != '\n') {
384		msg_warn("multiple values for attribute %s from %s",
385			 STR(name_buf), VSTREAM_PATH(fp));
386		return (-1);
387	    }
388	    break;
389	case ATTR_TYPE_LONG:
390	    if (ch != ':') {
391		msg_warn("missing value for number attribute %s from %s",
392			 STR(name_buf), VSTREAM_PATH(fp));
393		return (-1);
394	    }
395	    long_number = va_arg(ap, unsigned long *);
396	    if ((ch = attr_scan64_long_number(fp, long_number, str_buf,
397					      "input attribute value")) < 0)
398		return (-1);
399	    if (ch != '\n') {
400		msg_warn("multiple values for attribute %s from %s",
401			 STR(name_buf), VSTREAM_PATH(fp));
402		return (-1);
403	    }
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_scan64_string(fp, string,
413					 "input attribute value")) < 0)
414		return (-1);
415	    if (ch != '\n') {
416		msg_warn("multiple values for attribute %s from %s",
417			 STR(name_buf), VSTREAM_PATH(fp));
418		return (-1);
419	    }
420	    break;
421	case ATTR_TYPE_DATA:
422	    if (ch != ':') {
423		msg_warn("missing value for data attribute %s from %s",
424			 STR(name_buf), VSTREAM_PATH(fp));
425		return (-1);
426	    }
427	    string = va_arg(ap, VSTRING *);
428	    if ((ch = attr_scan64_string(fp, string,
429					 "input attribute value")) < 0)
430		return (-1);
431	    if (ch != '\n') {
432		msg_warn("multiple values for attribute %s from %s",
433			 STR(name_buf), VSTREAM_PATH(fp));
434		return (-1);
435	    }
436	    break;
437	case ATTR_TYPE_FUNC:
438	    scan_fn = va_arg(ap, ATTR_SCAN_SLAVE_FN);
439	    scan_arg = va_arg(ap, void *);
440	    if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
441		return (-1);
442	    break;
443	case ATTR_TYPE_HASH:
444	    if (ch != ':') {
445		msg_warn("missing value for string attribute %s from %s",
446			 STR(name_buf), VSTREAM_PATH(fp));
447		return (-1);
448	    }
449	    if ((ch = attr_scan64_string(fp, str_buf,
450					 "input attribute value")) < 0)
451		return (-1);
452	    if (ch != '\n') {
453		msg_warn("multiple values for attribute %s from %s",
454			 STR(name_buf), VSTREAM_PATH(fp));
455		return (-1);
456	    }
457	    if (htable_locate(hash_table, STR(name_buf)) != 0) {
458		if ((flags & ATTR_FLAG_EXTRA) != 0) {
459		    msg_warn("duplicate attribute %s in input from %s",
460			     STR(name_buf), VSTREAM_PATH(fp));
461		    return (conversions);
462		}
463	    } else if (hash_table->used >= ATTR_HASH_LIMIT) {
464		msg_warn("attribute count exceeds limit %d in input from %s",
465			 ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
466		return (conversions);
467	    } else {
468		htable_enter(hash_table, STR(name_buf),
469			     mystrdup(STR(str_buf)));
470	    }
471	    break;
472	default:
473	    msg_panic("%s: unknown type code: %d", myname, wanted_type);
474	}
475    }
476}
477
478/* attr_scan64 - read attribute list from stream */
479
480int     attr_scan64(VSTREAM *fp, int flags,...)
481{
482    va_list ap;
483    int     ret;
484
485    va_start(ap, flags);
486    ret = attr_vscan64(fp, flags, ap);
487    va_end(ap);
488    return (ret);
489}
490
491#ifdef TEST
492
493 /*
494  * Proof of concept test program.  Mirror image of the attr_scan64 test
495  * program.
496  */
497#include <msg_vstream.h>
498
499int     var_line_limit = 2048;
500
501int     main(int unused_argc, char **used_argv)
502{
503    VSTRING *data_val = vstring_alloc(1);
504    VSTRING *str_val = vstring_alloc(1);
505    HTABLE *table = htable_create(1);
506    HTABLE_INFO **ht_info_list;
507    HTABLE_INFO **ht;
508    int     int_val;
509    long    long_val;
510    int     ret;
511
512    msg_verbose = 1;
513    msg_vstream_init(used_argv[0], VSTREAM_ERR);
514    if ((ret = attr_scan64(VSTREAM_IN,
515			   ATTR_FLAG_STRICT,
516			   ATTR_TYPE_INT, ATTR_NAME_INT, &int_val,
517			   ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val,
518			   ATTR_TYPE_STR, ATTR_NAME_STR, str_val,
519			   ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val,
520			   ATTR_TYPE_HASH, table,
521			   ATTR_TYPE_END)) > 4) {
522	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
523	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
524	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
525	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
526	ht_info_list = htable_list(table);
527	for (ht = ht_info_list; *ht; ht++)
528	    vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value);
529	myfree((char *) ht_info_list);
530    } else {
531	vstream_printf("return: %d\n", ret);
532    }
533    if ((ret = attr_scan64(VSTREAM_IN,
534			   ATTR_FLAG_STRICT,
535			   ATTR_TYPE_INT, ATTR_NAME_INT, &int_val,
536			   ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val,
537			   ATTR_TYPE_STR, ATTR_NAME_STR, str_val,
538			   ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val,
539			   ATTR_TYPE_END)) == 4) {
540	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
541	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
542	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
543	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
544	ht_info_list = htable_list(table);
545	for (ht = ht_info_list; *ht; ht++)
546	    vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value);
547	myfree((char *) ht_info_list);
548    } else {
549	vstream_printf("return: %d\n", ret);
550    }
551    if (vstream_fflush(VSTREAM_OUT) != 0)
552	msg_fatal("write error: %m");
553
554    vstring_free(data_val);
555    vstring_free(str_val);
556    htable_free(table, myfree);
557
558    return (0);
559}
560
561#endif
562