/*++ /* NAME /* attr_scan0 3 /* SUMMARY /* recover attributes from byte stream /* SYNOPSIS /* #include /* /* int attr_scan0(fp, flags, type, name, ..., ATTR_TYPE_END) /* VSTREAM *fp; /* int flags; /* int type; /* char *name; /* /* int attr_vscan0(fp, flags, ap) /* VSTREAM *fp; /* int flags; /* va_list ap; /* /* int attr_scan_more0(fp) /* VSTREAM *fp; /* DESCRIPTION /* attr_scan0() takes zero or more (name, value) request attributes /* and recovers the attribute values from the byte stream that was /* possibly generated by attr_print0(). /* /* attr_vscan0() provides an alternative interface that is convenient /* for calling from within a variadic function. /* /* attr_scan_more0() returns 0 when a terminator is found (and /* consumes that terminator), returns 1 when more input is /* expected (without consuming input), and returns -1 otherwise /* (error). /* /* 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 | multi-attr)* null /* .br /* multi-attr :== "{" null simple-attr* "}" null /* .br /* simple-attr :== attr-name null attr-value null /* .br /* attr-name :== any string not containing null /* .br /* attr-value :== any string not containing null /* .br /* null :== the ASCII null character /* .in /* /* All attribute names and attribute values are sent as null terminated /* strings. Each string must be no longer than 4*var_line_limit /* characters including the terminator. /* These formatting rules favor implementations in C. /* /* Normally, attributes must be received in the sequence as specified with /* the attr_scan0() 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_scan0() operations from the /* same input attribute list. /* By default, attr_scan0() skips forward past the input attribute list /* terminator. /* .IP ATTR_FLAG_PRINTABLE /* Santize received string values with printable(_, '?'). /* .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 List of attributes followed by terminator: /* .RS /* .IP "RECV_ATTR_INT(const char *name, int *ptr)" /* This argument is followed by an attribute name and an integer pointer. /* .IP "RECV_ATTR_LONG(const char *name, long *ptr)" /* This argument is followed by an attribute name and a long pointer. /* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)" /* This argument is followed by an attribute name and a VSTRING pointer. /* .IP "RECV_ATTR_STREQ(const char *name, const char *value)" /* The name and value must match what the client sends. /* This attribute does not increment the result value. /* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)" /* This argument is followed by an attribute name and a VSTRING pointer. /* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)" /* This argument is followed by a function pointer and a generic data /* pointer. The caller-specified function returns < 0 in case of /* error. /* .IP "RECV_ATTR_HASH(HTABLE *table)" /* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)" /* Receive a sequence of attribute names and string values. /* 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 /* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests /* format their payload as a multi-attr sequence (see syntax /* above). When the receiver's input does not start with a /* multi-attr delimiter (i.e. the sender did not request /* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will /* store all attribute names and values up to the attribute /* list terminator. In terms of code, this means that the /* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed /* by ATTR_TYPE_END. /* .IP ATTR_TYPE_END /* This argument terminates the requested attribute list. /* .RE /* BUGS /* RECV_ATTR_HASH (RECV_ATTR_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_scan0() and attr_vscan0() 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_print0(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 /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* System library. */ #include #include #include #include /* Utility library. */ #include #include #include #include #include #include #include #include #include /* Application specific. */ #define STR(x) vstring_str(x) #define LEN(x) VSTRING_LEN(x) /* attr_scan0_string - pull a string from the input stream */ static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) { int ch; if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) { msg_warn("%s on %s while reading %s", vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", VSTREAM_PATH(fp), context); return (-1); } if (ch != 0) { msg_warn("unexpected end-of-input from %s while reading %s", VSTREAM_PATH(fp), context); return (-1); } if (msg_verbose) msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); return (ch); } /* attr_scan0_data - pull a data blob from the input stream */ static int attr_scan0_data(VSTREAM *fp, VSTRING *str_buf, const char *context) { static VSTRING *base64_buf = 0; int ch; if (base64_buf == 0) base64_buf = vstring_alloc(10); if ((ch = attr_scan0_string(fp, base64_buf, context)) < 0) return (-1); if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) { msg_warn("malformed base64 data from %s while reading %s: %.100s", VSTREAM_PATH(fp), context, STR(base64_buf)); return (-1); } return (ch); } /* attr_scan0_number - pull a number from the input stream */ static int attr_scan0_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, const char *context) { char junk = 0; int ch; if ((ch = attr_scan0_string(fp, str_buf, context)) < 0) return (-1); if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { msg_warn("malformed numerical data from %s while reading %s: %.100s", VSTREAM_PATH(fp), context, STR(str_buf)); return (-1); } return (ch); } /* attr_scan0_long_number - pull a number from the input stream */ static int attr_scan0_long_number(VSTREAM *fp, unsigned long *ptr, VSTRING *str_buf, const char *context) { char junk = 0; int ch; if ((ch = attr_scan0_string(fp, str_buf, context)) < 0) return (-1); if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { msg_warn("malformed numerical data from %s while reading %s: %.100s", VSTREAM_PATH(fp), context, STR(str_buf)); return (-1); } return (ch); } /* attr_vscan0 - receive attribute list from stream */ int attr_vscan0(VSTREAM *fp, int flags, va_list ap) { const char *myname = "attr_scan0"; static VSTRING *str_buf = 0; static VSTRING *name_buf = 0; int wanted_type = -1; char *wanted_name; unsigned int *number; unsigned long *long_number; VSTRING *string; HTABLE *hash_table; int ch; int conversions; ATTR_SCAN_CUSTOM_FN scan_fn; void *scan_arg; const char *expect_val; /* * Sanity check. */ if (flags & ~ATTR_FLAG_ALL) msg_panic("%s: bad flags: 0x%x", myname, flags); /* * EOF check. */ if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) return (0); vstream_ungetc(fp, ch); /* * Initialize. */ if (str_buf == 0) { str_buf = vstring_alloc(10); name_buf = vstring_alloc(10); } /* * 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 != ATTR_TYPE_CLOSE) { 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, HTABLE *); } 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 (msg_verbose) msg_info("%s: wanted attribute: %s", VSTREAM_PATH(fp), wanted_name); if ((ch = attr_scan0_string(fp, name_buf, "input attribute name")) == VSTREAM_EOF) return (-1); if (LEN(name_buf) == 0) { if (wanted_type == ATTR_TYPE_END || wanted_type == ATTR_TYPE_HASH) return (conversions); if ((flags & ATTR_FLAG_MISSING) != 0) msg_warn("missing attribute %s in input from %s", wanted_name, VSTREAM_PATH(fp)); return (conversions); } /* * See if the caller asks for this attribute. */ if (wanted_type == ATTR_TYPE_HASH && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) { wanted_type = ATTR_TYPE_CLOSE; wanted_name = "(any attribute name or '}')"; /* Advance in the input stream. */ continue; } else if (wanted_type == ATTR_TYPE_CLOSE && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) { /* Advance in the argument list. */ wanted_type = -1; break; } if (wanted_type == ATTR_TYPE_HASH || wanted_type == ATTR_TYPE_CLOSE || (wanted_type != ATTR_TYPE_END && strcmp(wanted_name, STR(name_buf)) == 0)) break; if ((flags & ATTR_FLAG_EXTRA) != 0) { msg_warn("unexpected attribute %s from %s (expecting: %s)", STR(name_buf), VSTREAM_PATH(fp), wanted_name); return (conversions); } /* * Skip over this attribute. The caller does not ask for it. */ (void) attr_scan0_string(fp, str_buf, "input attribute value"); } /* * Do the requested conversion. */ switch (wanted_type) { case ATTR_TYPE_INT: number = va_arg(ap, unsigned int *); if ((ch = attr_scan0_number(fp, number, str_buf, "input attribute value")) < 0) return (-1); break; case ATTR_TYPE_LONG: long_number = va_arg(ap, unsigned long *); if ((ch = attr_scan0_long_number(fp, long_number, str_buf, "input attribute value")) < 0) return (-1); break; case ATTR_TYPE_STR: string = va_arg(ap, VSTRING *); if ((ch = attr_scan0_string(fp, string, "input attribute value")) < 0) return (-1); if (flags & ATTR_FLAG_PRINTABLE) (void) printable(STR(string), '?'); break; case ATTR_TYPE_DATA: string = va_arg(ap, VSTRING *); if ((ch = attr_scan0_data(fp, string, "input attribute value")) < 0) return (-1); break; case ATTR_TYPE_FUNC: scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN); scan_arg = va_arg(ap, void *); if (scan_fn(attr_scan0, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) return (-1); break; case ATTR_TYPE_STREQ: expect_val = va_arg(ap, const char *); if ((ch = attr_scan0_string(fp, str_buf, "input attribute value")) < 0) return (-1); if (strcmp(expect_val, STR(str_buf)) != 0) { msg_warn("unexpected %s %s from %s (expected: %s)", STR(name_buf), STR(str_buf), VSTREAM_PATH(fp), expect_val); return (-1); } conversions -= 1; break; case ATTR_TYPE_HASH: case ATTR_TYPE_CLOSE: if ((ch = attr_scan0_string(fp, str_buf, "input attribute value")) < 0) return (-1); if (flags & ATTR_FLAG_PRINTABLE) { (void) printable(STR(name_buf), '?'); (void) printable(STR(str_buf), '?'); } if (htable_locate(hash_table, STR(name_buf)) != 0) { if ((flags & ATTR_FLAG_EXTRA) != 0) { msg_warn("duplicate attribute %s in input from %s", STR(name_buf), VSTREAM_PATH(fp)); return (conversions); } } else if (hash_table->used >= ATTR_HASH_LIMIT) { msg_warn("attribute count exceeds limit %d in input from %s", ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); return (conversions); } else { htable_enter(hash_table, STR(name_buf), mystrdup(STR(str_buf))); } break; case -1: conversions -= 1; break; default: msg_panic("%s: unknown type code: %d", myname, wanted_type); } } } /* attr_scan0 - read attribute list from stream */ int attr_scan0(VSTREAM *fp, int flags,...) { va_list ap; int ret; va_start(ap, flags); ret = attr_vscan0(fp, flags, ap); va_end(ap); return (ret); } /* attr_scan_more0 - look ahead for more */ int attr_scan_more0(VSTREAM *fp) { int ch; switch (ch = VSTREAM_GETC(fp)) { case 0: if (msg_verbose) msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp)); return (0); case VSTREAM_EOF: if (msg_verbose) msg_info("%s: EOF", VSTREAM_PATH(fp)); return (-1); default: if (msg_verbose) msg_info("%s: non-terminator '%c' (lookahead)", VSTREAM_PATH(fp), ch); (void) vstream_ungetc(fp, ch); return (1); } } #ifdef TEST /* * Proof of concept test program. Mirror image of the attr_scan0 test * program. */ #include int var_line_limit = 2048; int main(int unused_argc, char **used_argv) { VSTRING *data_val = vstring_alloc(1); VSTRING *str_val = vstring_alloc(1); HTABLE *table = htable_create(1); HTABLE_INFO **ht_info_list; HTABLE_INFO **ht; int int_val; long long_val; long long_val2; int ret; msg_verbose = 1; msg_vstream_init(used_argv[0], VSTREAM_ERR); if ((ret = attr_scan0(VSTREAM_IN, ATTR_FLAG_STRICT, RECV_ATTR_STREQ("protocol", "test"), RECV_ATTR_INT(ATTR_NAME_INT, &int_val), RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), RECV_ATTR_STR(ATTR_NAME_STR, str_val), RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), RECV_ATTR_HASH(table), RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2), ATTR_TYPE_END)) > 4) { vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(str_val)); ht_info_list = htable_list(table); for (ht = ht_info_list; *ht; ht++) vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); myfree((void *) ht_info_list); vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2); } else { vstream_printf("return: %d\n", ret); } if ((ret = attr_scan0(VSTREAM_IN, ATTR_FLAG_STRICT, RECV_ATTR_STREQ("protocol", "test"), RECV_ATTR_INT(ATTR_NAME_INT, &int_val), RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), RECV_ATTR_STR(ATTR_NAME_STR, str_val), RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), ATTR_TYPE_END)) == 4) { vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); ht_info_list = htable_list(table); for (ht = ht_info_list; *ht; ht++) vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); myfree((void *) ht_info_list); } else { vstream_printf("return: %d\n", ret); } if ((ret = attr_scan0(VSTREAM_IN, ATTR_FLAG_STRICT, RECV_ATTR_STREQ("protocol", "test"), ATTR_TYPE_END)) != 0) vstream_printf("return: %d\n", ret); if (vstream_fflush(VSTREAM_OUT) != 0) msg_fatal("write error: %m"); vstring_free(data_val); vstring_free(str_val); htable_free(table, myfree); return (0); } #endif