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