1/*++
2/* NAME
3/*	attr_scan0 3
4/* SUMMARY
5/*	recover attributes from byte stream
6/* SYNOPSIS
7/*	#include <attr.h>
8/*
9/*	int	attr_scan0(fp, flags, type, name, ..., ATTR_TYPE_END)
10/*	VSTREAM	fp;
11/*	int	flags;
12/*	int	type;
13/*	char	*name;
14/*
15/*	int	attr_vscan0(fp, flags, ap)
16/*	VSTREAM	fp;
17/*	int	flags;
18/*	va_list	ap;
19/* DESCRIPTION
20/*	attr_scan0() 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_print0().
23/*
24/*	attr_vscan0() 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* null
33/* .br
34/*	simple-attr :== attr-name null attr-value null
35/* .br
36/*	attr-name :== any string not containing null
37/* .br
38/*	attr-value :== any string not containing null
39/* .br
40/*	null :== the ASCII null character
41/* .in
42/*
43/*	All attribute names and attribute values are sent as null terminated
44/*	strings. Each string must be no longer than 4*var_line_limit
45/*	characters including the terminator.
46/*	These formatting rules favor implementations in C.
47/*
48/*      Normally, attributes must be received in the sequence as specified with
49/*	the attr_scan0() argument list.  The input stream may contain additional
50/*	attributes at any point in the input stream, including additional
51/*	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_scan0() operations from the
76/*	same input attribute list.
77/*	By default, attr_scan0() skips forward past the input attribute list
78/*	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_scan0() and attr_vscan0() return -1 when malformed input is
124/*	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_print0(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 <vstring_vstream.h>
157#include <htable.h>
158#include <base64_code.h>
159#include <attr.h>
160
161/* Application specific. */
162
163#define STR(x)	vstring_str(x)
164#define LEN(x)	VSTRING_LEN(x)
165
166/* attr_scan0_string - pull a string from the input stream */
167
168static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
169{
170    int     ch;
171
172    if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) {
173	msg_warn("%s on %s while reading %s",
174		 vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
175		 VSTREAM_PATH(fp), context);
176	return (-1);
177    }
178    if (ch != 0) {
179	msg_warn("unexpected end-of-input from %s while reading %s",
180		 VSTREAM_PATH(fp), context);
181	return (-1);
182    }
183    if (msg_verbose)
184	msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
185    return (ch);
186}
187
188/* attr_scan0_data - pull a data blob from the input stream */
189
190static int attr_scan0_data(VSTREAM *fp, VSTRING *str_buf,
191			           const char *context)
192{
193    static VSTRING *base64_buf = 0;
194    int     ch;
195
196    if (base64_buf == 0)
197	base64_buf = vstring_alloc(10);
198    if ((ch = attr_scan0_string(fp, base64_buf, context)) < 0)
199	return (-1);
200    if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
201	msg_warn("malformed base64 data from %s while reading %s: %.100s",
202		 VSTREAM_PATH(fp), context, STR(base64_buf));
203	return (-1);
204    }
205    return (ch);
206}
207
208/* attr_scan0_number - pull a number from the input stream */
209
210static int attr_scan0_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
211			             const char *context)
212{
213    char    junk = 0;
214    int     ch;
215
216    if ((ch = attr_scan0_string(fp, str_buf, context)) < 0)
217	return (-1);
218    if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
219	msg_warn("malformed numerical data from %s while reading %s: %.100s",
220		 VSTREAM_PATH(fp), context, STR(str_buf));
221	return (-1);
222    }
223    return (ch);
224}
225
226/* attr_scan0_long_number - pull a number from the input stream */
227
228static int attr_scan0_long_number(VSTREAM *fp, unsigned long *ptr,
229				          VSTRING *str_buf,
230				          const char *context)
231{
232    char    junk = 0;
233    int     ch;
234
235    if ((ch = attr_scan0_string(fp, str_buf, context)) < 0)
236	return (-1);
237    if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
238	msg_warn("malformed numerical data from %s while reading %s: %.100s",
239		 VSTREAM_PATH(fp), context, STR(str_buf));
240	return (-1);
241    }
242    return (ch);
243}
244
245/* attr_vscan0 - receive attribute list from stream */
246
247int     attr_vscan0(VSTREAM *fp, int flags, va_list ap)
248{
249    const char *myname = "attr_scan0";
250    static VSTRING *str_buf = 0;
251    static VSTRING *name_buf = 0;
252    int     wanted_type = -1;
253    char   *wanted_name;
254    unsigned int *number;
255    unsigned long *long_number;
256    VSTRING *string;
257    HTABLE *hash_table;
258    int     ch;
259    int     conversions;
260    ATTR_SCAN_SLAVE_FN scan_fn;
261    void   *scan_arg;
262
263    /*
264     * Sanity check.
265     */
266    if (flags & ~ATTR_FLAG_ALL)
267	msg_panic("%s: bad flags: 0x%x", myname, flags);
268
269    /*
270     * EOF check.
271     */
272    if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
273	return (0);
274    vstream_ungetc(fp, ch);
275
276    /*
277     * Initialize.
278     */
279    if (str_buf == 0) {
280	str_buf = vstring_alloc(10);
281	name_buf = vstring_alloc(10);
282    }
283
284    /*
285     * Iterate over all (type, name, value) triples.
286     */
287    for (conversions = 0; /* void */ ; conversions++) {
288
289	/*
290	 * Determine the next attribute type and attribute name on the
291	 * caller's wish list.
292	 *
293	 * If we're reading into a hash table, we already know that the
294	 * attribute value is string-valued, and we get the attribute name
295	 * from the input stream instead. This is secure only when the
296	 * resulting table is queried with known to be good attribute names.
297	 */
298	if (wanted_type != ATTR_TYPE_HASH) {
299	    wanted_type = va_arg(ap, int);
300	    if (wanted_type == ATTR_TYPE_END) {
301		if ((flags & ATTR_FLAG_MORE) != 0)
302		    return (conversions);
303		wanted_name = "(list terminator)";
304	    } else if (wanted_type == ATTR_TYPE_HASH) {
305		wanted_name = "(any attribute name or list terminator)";
306		hash_table = va_arg(ap, HTABLE *);
307		if (va_arg(ap, int) != ATTR_TYPE_END)
308		    msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END",
309			      myname);
310	    } else if (wanted_type != ATTR_TYPE_FUNC) {
311		wanted_name = va_arg(ap, char *);
312	    }
313	}
314
315	/*
316	 * Locate the next attribute of interest in the input stream.
317	 */
318	while (wanted_type != ATTR_TYPE_FUNC) {
319
320	    /*
321	     * Get the name of the next attribute. Hitting EOF is always bad.
322	     * Hitting the end-of-input early is OK if the caller is prepared
323	     * to deal with missing inputs.
324	     */
325	    if (msg_verbose)
326		msg_info("%s: wanted attribute: %s",
327			 VSTREAM_PATH(fp), wanted_name);
328	    if ((ch = attr_scan0_string(fp, name_buf,
329				    "input attribute name")) == VSTREAM_EOF)
330		return (-1);
331	    if (LEN(name_buf) == 0) {
332		if (wanted_type == ATTR_TYPE_END
333		    || wanted_type == ATTR_TYPE_HASH)
334		    return (conversions);
335		if ((flags & ATTR_FLAG_MISSING) != 0)
336		    msg_warn("missing attribute %s in input from %s",
337			     wanted_name, VSTREAM_PATH(fp));
338		return (conversions);
339	    }
340
341	    /*
342	     * See if the caller asks for this attribute.
343	     */
344	    if (wanted_type == ATTR_TYPE_HASH
345		|| (wanted_type != ATTR_TYPE_END
346		    && strcmp(wanted_name, STR(name_buf)) == 0))
347		break;
348	    if ((flags & ATTR_FLAG_EXTRA) != 0) {
349		msg_warn("unexpected attribute %s from %s (expecting: %s)",
350			 STR(name_buf), VSTREAM_PATH(fp), wanted_name);
351		return (conversions);
352	    }
353
354	    /*
355	     * Skip over this attribute. The caller does not ask for it.
356	     */
357	    (void) attr_scan0_string(fp, str_buf, "input attribute value");
358	}
359
360	/*
361	 * Do the requested conversion.
362	 */
363	switch (wanted_type) {
364	case ATTR_TYPE_INT:
365	    number = va_arg(ap, unsigned int *);
366	    if ((ch = attr_scan0_number(fp, number, str_buf,
367					"input attribute value")) < 0)
368		return (-1);
369	    break;
370	case ATTR_TYPE_LONG:
371	    long_number = va_arg(ap, unsigned long *);
372	    if ((ch = attr_scan0_long_number(fp, long_number, str_buf,
373					     "input attribute value")) < 0)
374		return (-1);
375	    break;
376	case ATTR_TYPE_STR:
377	    string = va_arg(ap, VSTRING *);
378	    if ((ch = attr_scan0_string(fp, string,
379					"input attribute value")) < 0)
380		return (-1);
381	    break;
382	case ATTR_TYPE_DATA:
383	    string = va_arg(ap, VSTRING *);
384	    if ((ch = attr_scan0_data(fp, string,
385				      "input attribute value")) < 0)
386		return (-1);
387	    break;
388	case ATTR_TYPE_FUNC:
389	    scan_fn = va_arg(ap, ATTR_SCAN_SLAVE_FN);
390	    scan_arg = va_arg(ap, void *);
391	    if (scan_fn(attr_scan0, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
392		return (-1);
393	    break;
394	case ATTR_TYPE_HASH:
395	    if ((ch = attr_scan0_string(fp, str_buf,
396					"input attribute value")) < 0)
397		return (-1);
398	    if (htable_locate(hash_table, STR(name_buf)) != 0) {
399		if ((flags & ATTR_FLAG_EXTRA) != 0) {
400		    msg_warn("duplicate attribute %s in input from %s",
401			     STR(name_buf), VSTREAM_PATH(fp));
402		    return (conversions);
403		}
404	    } else if (hash_table->used >= ATTR_HASH_LIMIT) {
405		msg_warn("attribute count exceeds limit %d in input from %s",
406			 ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
407		return (conversions);
408	    } else {
409		htable_enter(hash_table, STR(name_buf),
410			     mystrdup(STR(str_buf)));
411	    }
412	    break;
413	default:
414	    msg_panic("%s: unknown type code: %d", myname, wanted_type);
415	}
416    }
417}
418
419/* attr_scan0 - read attribute list from stream */
420
421int     attr_scan0(VSTREAM *fp, int flags,...)
422{
423    va_list ap;
424    int     ret;
425
426    va_start(ap, flags);
427    ret = attr_vscan0(fp, flags, ap);
428    va_end(ap);
429    return (ret);
430}
431
432#ifdef TEST
433
434 /*
435  * Proof of concept test program.  Mirror image of the attr_scan0 test
436  * program.
437  */
438#include <msg_vstream.h>
439
440int     var_line_limit = 2048;
441
442int     main(int unused_argc, char **used_argv)
443{
444    VSTRING *data_val = vstring_alloc(1);
445    VSTRING *str_val = vstring_alloc(1);
446    HTABLE *table = htable_create(1);
447    HTABLE_INFO **ht_info_list;
448    HTABLE_INFO **ht;
449    int     int_val;
450    long    long_val;
451    int     ret;
452
453    msg_verbose = 1;
454    msg_vstream_init(used_argv[0], VSTREAM_ERR);
455    if ((ret = attr_scan0(VSTREAM_IN,
456			  ATTR_FLAG_STRICT,
457			  ATTR_TYPE_INT, ATTR_NAME_INT, &int_val,
458			  ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val,
459			  ATTR_TYPE_STR, ATTR_NAME_STR, str_val,
460			  ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val,
461			  ATTR_TYPE_HASH, table,
462			  ATTR_TYPE_END)) > 4) {
463	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
464	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
465	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
466	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(str_val));
467	ht_info_list = htable_list(table);
468	for (ht = ht_info_list; *ht; ht++)
469	    vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value);
470	myfree((char *) ht_info_list);
471    } else {
472	vstream_printf("return: %d\n", ret);
473    }
474    if ((ret = attr_scan0(VSTREAM_IN,
475			  ATTR_FLAG_STRICT,
476			  ATTR_TYPE_INT, ATTR_NAME_INT, &int_val,
477			  ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val,
478			  ATTR_TYPE_STR, ATTR_NAME_STR, str_val,
479			  ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val,
480			  ATTR_TYPE_END)) == 4) {
481	vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
482	vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
483	vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
484	vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
485	ht_info_list = htable_list(table);
486	for (ht = ht_info_list; *ht; ht++)
487	    vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value);
488	myfree((char *) ht_info_list);
489    } else {
490	vstream_printf("return: %d\n", ret);
491    }
492    if (vstream_fflush(VSTREAM_OUT) != 0)
493	msg_fatal("write error: %m");
494
495    vstring_free(data_val);
496    vstring_free(str_val);
497    htable_free(table, myfree);
498
499    return (0);
500}
501
502#endif
503