/*++ * NAME * attr_scan64 3 * SUMMARY * recover attributes from byte stream * SYNOPSIS * #include * * int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END) * ACL_VSTREAM fp; * int flags; * int type; * char *name; * * int attr_vscan64(fp, flags, ap) * ACL_VSTREAM fp; * int flags; * va_list ap; * DESCRIPTION * attr_scan64() takes zero or more (name, value) request attributes * and recovers the attribute values from the byte stream that was * possibly generated by attr_print64(). * * attr_vscan64() provides an alternative interface that is convenient * for calling from within a variadic function. * * The input stream is formatted as follows, where (item)* stands * for zero or more instances of the specified item, and where * (item1 | item2) stands for choice: * * .in +5 * attr-list :== simple-attr* newline * .br * simple-attr :== attr-name colon attr-value newline * .br * attr-name :== any base64 encoded string * .br * attr-value :== any base64 encoded string * .br * colon :== the ASCII colon character * .br * newline :== the ASCII newline character * .in * * All attribute names and attribute values are sent as base64-encoded * strings. Each base64 encoding must be no longer than 4*var_line_limit * characters. The formatting rules aim to make implementations in PERL * and other languages easy. * * Normally, attributes must be received in the sequence as specified with * the attr_scan64() argument list. The input stream may contain additional * attributes at any point in the input stream, including additional * instances of requested attributes. * * Additional input attributes or input attribute instances are silently * skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified * (see below). This allows for some flexibility in the evolution of * protocols while still providing the option of being strict where * this is desirable. * * Arguments: * .IP fp * Stream to recover the input attributes from. * .IP flags * The bit-wise OR of zero or more of the following. * .RS * .IP ATTR_FLAG_MISSING * Log a warning when the input attribute list terminates before all * requested attributes are recovered. It is always an error when the * input stream ends without the newline attribute list terminator. * .IP ATTR_FLAG_EXTRA * Log a warning and stop attribute recovery when the input stream * contains an attribute that was not requested. This includes the * case of additional instances of a requested attribute. * .IP ATTR_FLAG_MORE * After recovering the requested attributes, leave the input stream * in a state that is usable for more attr_scan64() operations from the * same input attribute list. * By default, attr_scan64() skips forward past the input attribute list * terminator. * .IP ATTR_FLAG_STRICT * For convenience, this value combines both ATTR_FLAG_MISSING and * ATTR_FLAG_EXTRA. * .IP ATTR_FLAG_NONE * For convenience, this value requests none of the above. * .RE * .IP type * The type argument determines the arguments that follow. * .RS * .IP "ATTR_TYPE_INT (char *, int *)" * This argument is followed by an attribute name and an integer pointer. * .IP "ATTR_TYPE_LONG (char *, long *)" * This argument is followed by an attribute name and a long pointer. * .IP "ATTR_TYPE_STR (char *, ACL_VSTRING *)" * This argument is followed by an attribute name and a ACL_VSTRING pointer. * .IP "ATTR_TYPE_DATA (char *, ACL_VSTRING *)" * This argument is followed by an attribute name and a ACL_VSTRING pointer. * .IP "ATTR_TYPE_FUNC (ATTR_SCAN_SLAVE_FN, void *)" * This argument is followed by a function pointer and a generic data * pointer. The caller-specified function returns < 0 in case of * error. * .IP "ATTR_TYPE_HASH (ACL_HTABLE *)" * .IP "ATTR_TYPE_NAMEVAL (NVTABLE *)" * All further input attributes are processed as string attributes. * No specific attribute sequence is enforced. * All attributes up to the attribute list terminator are read, * but only the first instance of each attribute is stored. * There can be no more than 1024 attributes in a hash table. * .sp * The attribute string values are stored in the hash table under * keys equal to the attribute name (obtained from the input stream). * Values from the input stream are added to the hash table. Existing * hash table entries are not replaced. * .sp * N.B. This construct must be followed by an ATTR_TYPE_END argument. * .IP ATTR_TYPE_END * This argument terminates the requested attribute list. * .RE * BUGS * ATTR_TYPE_HASH (ATTR_TYPE_NAMEVAL) accepts attributes with arbitrary * names from possibly untrusted sources. * This is unsafe, unless the resulting table is queried only with * known to be good attribute names. * DIAGNOSTICS * attr_scan64() and attr_vscan64() return -1 when malformed input is * detected (string too long, incomplete line, missing end marker). * Otherwise, the result value is the number of attributes that were * successfully recovered from the input stream (a hash table counts * as the number of entries stored into the table). * * Panic: interface violation. All system call errors are fatal. * SEE ALSO * attr_print64(3) send attributes over byte stream. * LICENSE * .ad * .fi * The Secure Mailer license must be distributed with this software. * AUTHOR(S) * Wietse Venema * IBM T.J. Watson Research * P.O. Box 704 * Yorktown Heights, NY 10598, USA *--*/ #include "StdAfx.h" #include #include #include #include "attr.h" /* Application specific. */ #define STR(x) acl_vstring_str(x) #define LEN(x) ACL_VSTRING_LEN(x) static void free_vstring_fn(void *arg) { ACL_VSTRING *s = (ACL_VSTRING*) arg; acl_vstring_free(s); } /* attr_scan64_string - pull a string from the input stream */ static int attr_scan64_string(ACL_VSTREAM *fp, ACL_VSTRING *plain_buf, const char *context) { const char *myname = "attr_scan64_string"; static __thread ACL_VSTRING *base64_buf = 0; #if 0 extern int var_line_limit; /* XXX */ int limit = var_line_limit * 4; #endif int ch; if (base64_buf == 0) { base64_buf = acl_vstring_alloc(10); acl_pthread_atexit_add(base64_buf, free_vstring_fn); } ACL_VSTRING_RESET(base64_buf); while ((ch = ACL_VSTREAM_GETC(fp)) != ':' && ch != '\n') { if (ch == ACL_VSTREAM_EOF) { acl_msg_warn("%s: %s on %s while reading %s", myname, acl_vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", ACL_VSTREAM_PATH(fp), context); return (-1); } ACL_VSTRING_ADDCH(base64_buf, ch); #if 0 if (LEN(base64_buf) > limit) { acl_msg_warn("%s: string length > %d characters from %s while reading %s", myname, limit, ACL_VSTREAM_PATH(fp), context); return (-1); } #endif } ACL_VSTRING_TERMINATE(base64_buf); if (acl_vstring_base64_decode(plain_buf, STR(base64_buf), (int) LEN(base64_buf)) == 0) { acl_msg_warn("%s: malformed base64 data from %s: %.100s", myname, ACL_VSTREAM_PATH(fp), STR(base64_buf)); return (-1); } if (acl_msg_verbose) acl_msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); return (ch); } /* attr_scan64_number - pull a number from the input stream */ static int attr_scan64_number(ACL_VSTREAM *fp, unsigned *ptr, ACL_VSTRING *str_buf, const char *context) { const char *myname = "attr_scan64_number"; char junk = 0; int ch; if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) return (-1); if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { acl_msg_warn("%s: malformed numerical data from %s while reading %s: %.100s", myname, ACL_VSTREAM_PATH(fp), context, STR(str_buf)); return (-1); } return (ch); } /* attr_scan64_long_number - pull a number from the input stream */ static int attr_scan64_long_number(ACL_VSTREAM *fp, unsigned long *ptr, ACL_VSTRING *str_buf, const char *context) { const char *myname = "attr_scan64_long_number"; char junk = 0; int ch; if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) return (-1); if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { acl_msg_warn("%s: malformed numerical data from %s while reading %s: %.100s", myname, ACL_VSTREAM_PATH(fp), context, STR(str_buf)); return (-1); } return (ch); } /* attr_vscan64 - receive attribute list from stream */ int attr_vscan64(ACL_VSTREAM *fp, int flags, va_list ap) { const char *myname = "attr_scan64"; static __thread ACL_VSTRING *str_buf = 0; static __thread ACL_VSTRING *name_buf = 0; int wanted_type = -1; char *wanted_name = 0; unsigned int *number; unsigned long *long_number; ACL_VSTRING *string; ACL_HTABLE *hash_table = 0; int ch; int conversions; ATTR_SCAN_SLAVE_FN scan_fn; void *scan_arg; /* * Sanity check. */ if (flags & ~ATTR_FLAG_ALL) acl_msg_panic("%s: bad flags: 0x%x", myname, flags); /* * EOF check. */ if ((ch = ACL_VSTREAM_GETC(fp)) == ACL_VSTREAM_EOF) return (0); acl_vstream_ungetc(fp, ch); /* * Initialize. */ if (str_buf == 0) { str_buf = acl_vstring_alloc(10); name_buf = acl_vstring_alloc(10); acl_pthread_atexit_add(str_buf, free_vstring_fn); acl_pthread_atexit_add(name_buf, free_vstring_fn); } /* * Iterate over all (type, name, value) triples. */ for (conversions = 0; /* void */ ; conversions++) { /* * Determine the next attribute type and attribute name on the * caller's wish list. * * If we're reading into a hash table, we already know that the * attribute value is string-valued, and we get the attribute name * from the input stream instead. This is secure only when the * resulting table is queried with known to be good attribute names. */ if (wanted_type != ATTR_TYPE_HASH) { wanted_type = va_arg(ap, int); if (wanted_type == ATTR_TYPE_END) { if ((flags & ATTR_FLAG_MORE) != 0) return (conversions); wanted_name = "(list terminator)"; } else if (wanted_type == ATTR_TYPE_HASH) { wanted_name = "(any attribute name or list terminator)"; hash_table = va_arg(ap, ACL_HTABLE *); if (va_arg(ap, int) != ATTR_TYPE_END) acl_msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END", myname); } else if (wanted_type != ATTR_TYPE_FUNC) { wanted_name = va_arg(ap, char *); } } /* * Locate the next attribute of interest in the input stream. */ while (wanted_type != ATTR_TYPE_FUNC) { /* * Get the name of the next attribute. Hitting EOF is always bad. * Hitting the end-of-input early is OK if the caller is prepared * to deal with missing inputs. */ if (acl_msg_verbose) acl_msg_info("%s: wanted attribute: %s", ACL_VSTREAM_PATH(fp), wanted_name); if ((ch = attr_scan64_string(fp, name_buf, "input attribute name")) == ACL_VSTREAM_EOF) return (-1); if (ch == '\n' && LEN(name_buf) == 0) { if (wanted_type == ATTR_TYPE_END || wanted_type == ATTR_TYPE_HASH) return (conversions); if ((flags & ATTR_FLAG_MISSING) != 0) acl_msg_warn("%s: missing attribute %s in input from %s", myname, wanted_name, ACL_VSTREAM_PATH(fp)); return (conversions); } /* * See if the caller asks for this attribute. */ if (wanted_type == ATTR_TYPE_HASH || (wanted_type != ATTR_TYPE_END && strcmp(wanted_name, STR(name_buf)) == 0)) break; if ((flags & ATTR_FLAG_EXTRA) != 0) { acl_msg_warn("%s: unexpected attribute %s from %s (expecting: %s)", myname, STR(name_buf), ACL_VSTREAM_PATH(fp), wanted_name); return (conversions); } /* * Skip over this attribute. The caller does not ask for it. */ while (ch != '\n' && (ch = ACL_VSTREAM_GETC(fp)) != ACL_VSTREAM_EOF) /* void */ ; } /* * Do the requested conversion. If the target attribute is a * non-array type, disallow sending a multi-valued attribute, and * disallow sending no value. If the target attribute is an array * type, allow the sender to send a zero-element array (i.e. no value * at all). XXX Need to impose a bound on the number of array * elements. */ switch (wanted_type) { case ATTR_TYPE_INT: if (ch != ':') { acl_msg_warn("%s: missing value for number attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } number = va_arg(ap, unsigned int *); if ((ch = attr_scan64_number(fp, number, str_buf, "input attribute value")) < 0) return (-1); if (ch != '\n') { acl_msg_warn("%s: multiple values for attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } break; case ATTR_TYPE_LONG: if (ch != ':') { acl_msg_warn("%s: missing value for number attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } long_number = va_arg(ap, unsigned long *); if ((ch = attr_scan64_long_number(fp, long_number, str_buf, "input attribute value")) < 0) return (-1); if (ch != '\n') { acl_msg_warn("%s: multiple values for attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } break; case ATTR_TYPE_STR: if (ch != ':') { acl_msg_warn("%s: missing value for string attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } string = va_arg(ap, ACL_VSTRING *); if ((ch = attr_scan64_string(fp, string, "input attribute value")) < 0) return (-1); if (ch != '\n') { acl_msg_warn("%s: multiple values for attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } break; case ATTR_TYPE_DATA: if (ch != ':') { acl_msg_warn("%s: missing value for data attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } string = va_arg(ap, ACL_VSTRING *); if ((ch = attr_scan64_string(fp, string, "input attribute value")) < 0) return (-1); if (ch != '\n') { acl_msg_warn("%s: multiple values for attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } break; case ATTR_TYPE_FUNC: scan_fn = va_arg(ap, ATTR_SCAN_SLAVE_FN); scan_arg = va_arg(ap, void *); if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) return (-1); break; case ATTR_TYPE_HASH: if (ch != ':') { acl_msg_warn("%s: missing value for string attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } if ((ch = attr_scan64_string(fp, str_buf, "input attribute value")) < 0) return (-1); if (ch != '\n') { acl_msg_warn("%s: multiple values for attribute %s from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (-1); } if (acl_htable_locate(hash_table, STR(name_buf)) != 0) { if ((flags & ATTR_FLAG_EXTRA) != 0) { acl_msg_warn("%s: duplicate attribute %s in input from %s", myname, STR(name_buf), ACL_VSTREAM_PATH(fp)); return (conversions); } } else if (acl_htable_used(hash_table) >= ATTR_HASH_LIMIT) { acl_msg_warn("%s: attribute count exceeds limit %d in input from %s", myname, ATTR_HASH_LIMIT, ACL_VSTREAM_PATH(fp)); return (conversions); } else { acl_htable_enter(hash_table, STR(name_buf), acl_mystrdup(STR(str_buf))); } break; default: acl_msg_panic("%s: unknown type code: %d", myname, wanted_type); } } } /* attr_scan64 - read attribute list from stream */ int attr_scan64(ACL_VSTREAM *fp, int flags,...) { va_list ap; int ret; va_start(ap, flags); ret = attr_vscan64(fp, flags, ap); va_end(ap); return (ret); } #ifdef TEST /* * Proof of concept test program. Mirror image of the attr_scan64 test * program. */ #include int var_line_limit = 2048; int main(int unused_argc, char **used_argv) { ACL_VSTRING *data_val = acl_vstring_alloc(1); ACL_VSTRING *str_val = acl_vstring_alloc(1); ACL_HTABLE *table = acl_htable_create(1); ACL_HTABLE_INFO **ht_info_list; ACL_HTABLE_INFO **ht; int int_val; long long_val; int ret; acl_msg_verbose = 1; acl_msg_acl_vstream_init(used_argv[0], ACL_VSTREAM_ERR); if ((ret = attr_scan64(ACL_VSTREAM_IN, ATTR_FLAG_STRICT, ATTR_TYPE_INT, ATTR_NAME_INT, &int_val, ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, ATTR_TYPE_STR, ATTR_NAME_STR, str_val, ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val, ATTR_TYPE_HASH, table, ATTR_TYPE_END)) > 4) { acl_vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); acl_vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); acl_vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); acl_vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); ht_info_list = acl_htable_list(table); for (ht = ht_info_list; *ht; ht++) acl_vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); myfree((char *) ht_info_list); } else { acl_vstream_printf("return: %d\n", ret); } if ((ret = attr_scan64(ACL_VSTREAM_IN, ATTR_FLAG_STRICT, ATTR_TYPE_INT, ATTR_NAME_INT, &int_val, ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, ATTR_TYPE_STR, ATTR_NAME_STR, str_val, ATTR_TYPE_DATA, ATTR_NAME_DATA, data_val, ATTR_TYPE_END)) == 4) { acl_vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); acl_vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); acl_vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); acl_vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); ht_info_list = acl_htable_list(table); for (ht = ht_info_list; *ht; ht++) acl_vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); myfree((char *) ht_info_list); } else { acl_vstream_printf("return: %d\n", ret); } if (acl_vstream_fflush(ACL_VSTREAM_OUT) != 0) acl_msg_fatal("%s: write error: %s", __FUNCTION__, acl_last_serror()); acl_vstring_free(data_val); acl_vstring_free(str_val); acl_htable_free(table, myfree); return (0); } #endif