7 /* recover attributes from byte stream
11 /* int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END)
17 /* int attr_vscan64(fp, flags, ap)
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().
26 /* attr_vscan64() provides an alternative interface that is convenient
27 /* for calling from within a variadic function.
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:
34 /* attr-list :== simple-attr* newline
36 /* simple-attr :== attr-name colon attr-value newline
38 /* attr-name :== any base64 encoded string
40 /* attr-value :== any base64 encoded string
42 /* colon :== the ASCII colon character
44 /* newline :== the ASCII newline character
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.
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.
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
65 /* Stream to recover the input attributes from.
67 /* The bit-wise OR of zero or more of the following.
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.
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
83 /* .IP ATTR_FLAG_STRICT
84 /* For convenience, this value combines both ATTR_FLAG_MISSING and
87 /* For convenience, this value requests none of the above.
90 /* The type argument determines the arguments that follow.
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
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.
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.
117 /* N.B. This construct must be followed by an ATTR_TYPE_END argument.
119 /* This argument terminates the requested attribute list.
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.
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).
133 /* Panic: interface violation. All system call errors are fatal.
135 /* attr_print64(3) send attributes over byte stream.
139 /* The Secure Mailer license must be distributed with this software.
142 /* IBM T.J. Watson Research
144 /* Yorktown Heights, NY 10598, USA
147 /* System library. */
149 #include <sys_defs.h>
154 /* Utility library. */
157 #include <mymalloc.h>
161 #include <base64_code.h>
164 /* Application specific. */
166 #define STR(x) vstring_str(x)
167 #define LEN(x) VSTRING_LEN(x)
169 /* attr_scan64_string - pull a string from the input stream */
171 static int attr_scan64_string(VSTREAM
*fp
, VSTRING
*plain_buf
, const char *context
)
173 static VSTRING
*base64_buf
= 0;
176 extern int var_line_limit
; /* XXX */
177 int limit
= var_line_limit
* 4;
183 base64_buf
= vstring_alloc(10);
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
);
193 VSTRING_ADDCH(base64_buf
, ch
);
195 if (LEN(base64_buf
) > limit
) {
196 msg_warn("string length > %d characters from %s while reading %s",
197 limit
, VSTREAM_PATH(fp
), context
);
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
));
209 msg_info("%s: %s", context
, *STR(plain_buf
) ? STR(plain_buf
) : "(end)");
213 /* attr_scan64_number - pull a number from the input stream */
215 static int attr_scan64_number(VSTREAM
*fp
, unsigned *ptr
, VSTRING
*str_buf
,
221 if ((ch
= attr_scan64_string(fp
, str_buf
, context
)) < 0)
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
));
231 /* attr_scan64_long_number - pull a number from the input stream */
233 static int attr_scan64_long_number(VSTREAM
*fp
, unsigned long *ptr
,
240 if ((ch
= attr_scan64_string(fp
, str_buf
, context
)) < 0)
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
));
250 /* attr_vscan64 - receive attribute list from stream */
252 int attr_vscan64(VSTREAM
*fp
, int flags
, va_list ap
)
254 const char *myname
= "attr_scan64";
255 static VSTRING
*str_buf
= 0;
256 static VSTRING
*name_buf
= 0;
257 int wanted_type
= -1;
259 unsigned int *number
;
260 unsigned long *long_number
;
265 ATTR_SCAN_SLAVE_FN scan_fn
;
271 if (flags
& ~ATTR_FLAG_ALL
)
272 msg_panic("%s: bad flags: 0x%x", myname
, flags
);
277 if ((ch
= VSTREAM_GETC(fp
)) == VSTREAM_EOF
)
279 vstream_ungetc(fp
, ch
);
285 str_buf
= vstring_alloc(10);
286 name_buf
= vstring_alloc(10);
290 * Iterate over all (type, name, value) triples.
292 for (conversions
= 0; /* void */ ; conversions
++) {
295 * Determine the next attribute type and attribute name on the
296 * caller's wish list.
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.
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",
315 } else if (wanted_type
!= ATTR_TYPE_FUNC
) {
316 wanted_name
= va_arg(ap
, char *);
321 * Locate the next attribute of interest in the input stream.
323 while (wanted_type
!= ATTR_TYPE_FUNC
) {
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.
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
)
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
);
347 * See if the caller asks for this attribute.
349 if (wanted_type
== ATTR_TYPE_HASH
350 || (wanted_type
!= ATTR_TYPE_END
351 && strcmp(wanted_name
, STR(name_buf
)) == 0))
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
);
360 * Skip over this attribute. The caller does not ask for it.
362 while (ch
!= '\n' && (ch
= VSTREAM_GETC(fp
)) != VSTREAM_EOF
)
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
374 switch (wanted_type
) {
377 msg_warn("missing value for number attribute %s from %s",
378 STR(name_buf
), VSTREAM_PATH(fp
));
381 number
= va_arg(ap
, unsigned int *);
382 if ((ch
= attr_scan64_number(fp
, number
, str_buf
,
383 "input attribute value")) < 0)
386 msg_warn("multiple values for attribute %s from %s",
387 STR(name_buf
), VSTREAM_PATH(fp
));
393 msg_warn("missing value for number attribute %s from %s",
394 STR(name_buf
), VSTREAM_PATH(fp
));
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)
402 msg_warn("multiple values for attribute %s from %s",
403 STR(name_buf
), VSTREAM_PATH(fp
));
409 msg_warn("missing value for string attribute %s from %s",
410 STR(name_buf
), VSTREAM_PATH(fp
));
413 string
= va_arg(ap
, VSTRING
*);
414 if ((ch
= attr_scan64_string(fp
, string
,
415 "input attribute value")) < 0)
418 msg_warn("multiple values for attribute %s from %s",
419 STR(name_buf
), VSTREAM_PATH(fp
));
425 msg_warn("missing value for data attribute %s from %s",
426 STR(name_buf
), VSTREAM_PATH(fp
));
429 string
= va_arg(ap
, VSTRING
*);
430 if ((ch
= attr_scan64_string(fp
, string
,
431 "input attribute value")) < 0)
434 msg_warn("multiple values for attribute %s from %s",
435 STR(name_buf
), VSTREAM_PATH(fp
));
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)
447 msg_warn("missing value for string attribute %s from %s",
448 STR(name_buf
), VSTREAM_PATH(fp
));
451 if ((ch
= attr_scan64_string(fp
, str_buf
,
452 "input attribute value")) < 0)
455 msg_warn("multiple values for attribute %s from %s",
456 STR(name_buf
), VSTREAM_PATH(fp
));
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
);
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
);
470 htable_enter(hash_table
, STR(name_buf
),
471 mystrdup(STR(str_buf
)));
475 msg_panic("%s: unknown type code: %d", myname
, wanted_type
);
480 /* attr_scan64 - read attribute list from stream */
482 int attr_scan64(VSTREAM
*fp
, int flags
,...)
488 ret
= attr_vscan64(fp
, flags
, ap
);
496 * Proof of concept test program. Mirror image of the attr_scan64 test
499 #include <msg_vstream.h>
501 int var_line_limit
= 2048;
503 int main(int unused_argc
, char **used_argv
)
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
;
515 msg_vstream_init(used_argv
[0], VSTREAM_ERR
);
516 if ((ret
= attr_scan64(VSTREAM_IN
,
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
);
533 vstream_printf("return: %d\n", ret
);
535 if ((ret
= attr_scan64(VSTREAM_IN
,
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
);
551 vstream_printf("return: %d\n", ret
);
553 if (vstream_fflush(VSTREAM_OUT
) != 0)
554 msg_fatal("write error: %m");
556 vstring_free(data_val
);
557 vstring_free(str_val
);
558 htable_free(table
, myfree
);