diff options
Diffstat (limited to '')
-rw-r--r-- | src/util/dict.c | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/src/util/dict.c b/src/util/dict.c new file mode 100644 index 0000000..5d53860 --- /dev/null +++ b/src/util/dict.c @@ -0,0 +1,664 @@ +/*++ +/* NAME +/* dict 3 +/* SUMMARY +/* dictionary manager +/* SYNOPSIS +/* #include <dict.h> +/* +/* void dict_register(dict_name, dict_info) +/* const char *dict_name; +/* DICT *dict_info; +/* +/* DICT *dict_handle(dict_name) +/* const char *dict_name; +/* +/* void dict_unregister(dict_name) +/* const char *dict_name; +/* +/* int dict_update(dict_name, member, value) +/* const char *dict_name; +/* const char *member; +/* const char *value; +/* +/* const char *dict_lookup(dict_name, member) +/* const char *dict_name; +/* const char *member; +/* +/* int dict_delete(dict_name, member) +/* const char *dict_name; +/* const char *member; +/* +/* int dict_sequence(dict_name, func, member, value) +/* const char *dict_name; +/* int func; +/* const char **member; +/* const char **value; +/* +/* const char *dict_eval(dict_name, string, int recursive) +/* const char *dict_name; +/* const char *string; +/* int recursive; +/* +/* int dict_walk(action, context) +/* void (*action)(dict_name, dict_handle, context) +/* void *context; +/* +/* int dict_error(dict_name) +/* const char *dict_name; +/* +/* const char *dict_changed_name() +/* +/* void DICT_OWNER_AGGREGATE_INIT(aggregate) +/* DICT_OWNER aggregate; +/* +/* void DICT_OWNER_AGGREGATE_UPDATE(aggregate, source) +/* DICT_OWNER aggregate; +/* DICT_OWNER source; +/* AUXILIARY FUNCTIONS +/* int dict_load_file_xt(dict_name, path) +/* const char *dict_name; +/* const char *path; +/* +/* void dict_load_fp(dict_name, fp) +/* const char *dict_name; +/* VSTREAM *fp; +/* +/* const char *dict_flags_str(dict_flags) +/* int dict_flags; +/* +/* int dict_flags_mask(names) +/* const char *names; +/* DESCRIPTION +/* This module maintains a collection of name-value dictionaries. +/* Each dictionary has its own name and has its own methods to read +/* or update members. Examples of dictionaries that can be accessed +/* in this manner are the global UNIX-style process environment, +/* hash tables, NIS maps, DBM files, and so on. Dictionary values +/* are not limited to strings but can be arbitrary objects as long +/* as they can be represented by character pointers. +/* FEATURES +/* .fi +/* .ad +/* Notable features of this module are: +/* .IP "macro expansion (string-valued dictionaries only)" +/* Macros of the form $\fIname\fR can be expanded to the current +/* value of \fIname\fR. The forms $(\fIname\fR) and ${\fIname\fR} are +/* also supported. +/* .IP "unknown names" +/* An update request for an unknown dictionary name will trigger +/* the instantiation of an in-memory dictionary with that name. +/* A lookup request (including delete and sequence) for an +/* unknown dictionary will result in a "not found" and "no +/* error" result. +/* .PP +/* dict_register() adds a new dictionary, including access methods, +/* to the list of known dictionaries, or increments the reference +/* count for an existing (name, dictionary) pair. Otherwise, it is +/* an error to pass an existing name (this would cause a memory leak). +/* +/* dict_handle() returns the generic dictionary handle of the +/* named dictionary, or a null pointer when the named dictionary +/* is not found. +/* +/* dict_unregister() decrements the reference count of the named +/* dictionary. When the reference count reaches zero, dict_unregister() +/* breaks the (name, dictionary) association and executes the +/* dictionary's optional \fIremove\fR method. +/* +/* dict_update() updates the value of the named dictionary member. +/* The dictionary member and the named dictionary are instantiated +/* on the fly. The result value is zero (DICT_STAT_SUCCESS) +/* when the update was made. +/* +/* dict_lookup() returns the value of the named member (i.e. without +/* expanding macros in the member value). The \fIdict_name\fR argument +/* specifies the dictionary to search. The result is a null pointer +/* when no value is found, otherwise the result is owned by the +/* underlying dictionary method. Make a copy if the result is to be +/* modified, or if the result is to survive multiple dict_lookup() calls. +/* +/* dict_delete() removes the named member from the named dictionary. +/* The result value is zero (DICT_STAT_SUCCESS) when the member +/* was found. +/* +/* dict_sequence() steps through the named dictionary and returns +/* keys and values in some implementation-defined order. The func +/* argument is DICT_SEQ_FUN_FIRST to set the cursor to the first +/* entry or DICT_SEQ_FUN_NEXT to select the next entry. The result +/* is owned by the underlying dictionary method. Make a copy if the +/* result is to be modified, or if the result is to survive multiple +/* dict_sequence() calls. The result value is zero (DICT_STAT_SUCCESS) +/* when a member was found. +/* +/* dict_eval() expands macro references in the specified string. +/* The result is owned by the dictionary manager. Make a copy if the +/* result is to survive multiple dict_eval() calls. When the +/* \fIrecursive\fR argument is non-zero, macro references in macro +/* lookup results are expanded recursively. +/* +/* dict_walk() iterates over all registered dictionaries in some +/* arbitrary order, and invokes the specified action routine with +/* as arguments: +/* .IP "const char *dict_name" +/* Dictionary name. +/* .IP "DICT *dict_handle" +/* Generic dictionary handle. +/* .IP "char *context" +/* Application context from the caller. +/* .PP +/* dict_changed_name() returns non-zero when any dictionary needs to +/* be re-opened because it has changed or because it was unlinked. +/* A non-zero result is the name of a changed dictionary. +/* +/* dict_load_file_xt() reads name-value entries from the named file. +/* Lines that begin with whitespace are concatenated to the preceding +/* line (the newline is deleted). +/* Each entry is stored in the dictionary named by \fIdict_name\fR. +/* The result is zero if the file could not be opened. +/* +/* dict_load_fp() reads name-value entries from an open stream. +/* It has the same semantics as the dict_load_file_xt() function. +/* +/* dict_flags_str() returns a printable representation of the +/* specified dictionary flags. The result is overwritten upon +/* each call. +/* +/* dict_flags_mask() returns the bitmask for the specified +/* comma/space-separated dictionary flag names. +/* TRUST AND PROVENANCE +/* .ad +/* .fi +/* Each dictionary has an owner attribute that contains (status, +/* uid) information about the owner of a dictionary. The +/* status is one of the following: +/* .IP DICT_OWNER_TRUSTED +/* The dictionary is owned by a trusted user. The uid is zero, +/* and specifies a UNIX user ID. +/* .IP DICT_OWNER_UNTRUSTED +/* The dictionary is owned by an untrusted user. The uid is +/* non-zero, and specifies a UNIX user ID. +/* .IP DICT_OWNER_UNKNOWN +/* The dictionary is owned by an unspecified user. For example, +/* the origin is unauthenticated, or different parts of a +/* dictionary aggregate (see below) are owned by different +/* untrusted users. The uid is non-zero and does not specify +/* a UNIX user ID. +/* .PP +/* Note that dictionary ownership does not necessarily imply +/* ownership of lookup results. For example, a PCRE table may +/* be owned by the trusted root user, but the result of $number +/* expansion can contain data from an arbitrary remote SMTP +/* client. See dict_open(3) for how to disallow $number +/* expansions with security-sensitive operations. +/* +/* Two macros are available to help determine the provenance +/* and trustworthiness of a dictionary aggregate. The macros +/* are unsafe because they may evaluate arguments more than +/* once. +/* +/* DICT_OWNER_AGGREGATE_INIT() initialize aggregate owner +/* attributes to the highest trust level. +/* +/* DICT_OWNER_AGGREGATE_UPDATE() updates the aggregate owner +/* attributes with the attributes of the specified source, and +/* reduces the aggregate trust level as appropriate. +/* SEE ALSO +/* htable(3) +/* BUGS +/* DIAGNOSTICS +/* Fatal errors: out of memory, malformed macro name. +/* +/* The lookup routine returns non-null when the request is +/* satisfied. The update, delete and sequence routines return +/* zero (DICT_STAT_SUCCESS) when the request is satisfied. +/* The dict_error() function returns non-zero only when the +/* last operation was not satisfied due to a dictionary access +/* error. The result can have the following values: +/* .IP DICT_ERR_NONE(zero) +/* There was no dictionary access error. For example, the +/* request was satisfied, the requested information did not +/* exist in the dictionary, or the information already existed +/* when it should not exist (collision). +/* .IP DICT_ERR_RETRY(<0) +/* The dictionary was temporarily unavailable. This can happen +/* with network-based services. +/* .IP DICT_ERR_CONFIG(<0) +/* The dictionary was unavailable due to a configuration error. +/* .PP +/* Generally, a program is expected to test the function result +/* value for "success" first. If the operation was not successful, +/* a program is expected to test for a non-zero dict->error +/* status to distinguish between a data notfound/collision +/* condition or a dictionary access error. +/* 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 +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> +#include <string.h> +#include <time.h> + +/* Utility library. */ + +#include "msg.h" +#include "htable.h" +#include "mymalloc.h" +#include "vstream.h" +#include "vstring.h" +#include "readlline.h" +#include "mac_expand.h" +#include "stringops.h" +#include "iostuff.h" +#include "name_mask.h" +#include "dict.h" +#include "dict_ht.h" +#include "warn_stat.h" +#include "line_number.h" + +static HTABLE *dict_table; + + /* + * Each (name, dictionary) instance has a reference count. The count is part + * of the name, not the dictionary. The same dictionary may be registered + * under multiple names. The structure below keeps track of instances and + * reference counts. + */ +typedef struct { + DICT *dict; + int refcount; +} DICT_NODE; + +#define dict_node(dict) \ + (dict_table ? (DICT_NODE *) htable_find(dict_table, dict) : 0) + +/* Find a dictionary handle by name for lookup purposes. */ + +#define DICT_FIND_FOR_LOOKUP(dict, dict_name) do { \ + DICT_NODE *node; \ + if ((node = dict_node(dict_name)) != 0) \ + dict = node->dict; \ + else \ + dict = 0; \ +} while (0) + +/* Find a dictionary handle by name for update purposes. */ + +#define DICT_FIND_FOR_UPDATE(dict, dict_name) do { \ + DICT_NODE *node; \ + if ((node = dict_node(dict_name)) == 0) { \ + dict = dict_ht_open(dict_name, O_CREAT | O_RDWR, 0); \ + dict_register(dict_name, dict); \ + } else \ + dict = node->dict; \ +} while (0) + +#define STR(x) vstring_str(x) + +/* dict_register - make association with dictionary */ + +void dict_register(const char *dict_name, DICT *dict_info) +{ + const char *myname = "dict_register"; + DICT_NODE *node; + + if (dict_table == 0) + dict_table = htable_create(0); + if ((node = dict_node(dict_name)) == 0) { + node = (DICT_NODE *) mymalloc(sizeof(*node)); + node->dict = dict_info; + node->refcount = 0; + htable_enter(dict_table, dict_name, (void *) node); + } else if (dict_info != node->dict) + msg_fatal("%s: dictionary name exists: %s", myname, dict_name); + node->refcount++; + if (msg_verbose > 1) + msg_info("%s: %s %d", myname, dict_name, node->refcount); +} + +/* dict_handle - locate generic dictionary handle */ + +DICT *dict_handle(const char *dict_name) +{ + DICT_NODE *node; + + return ((node = dict_node(dict_name)) != 0 ? node->dict : 0); +} + +/* dict_node_free - dict_unregister() callback */ + +static void dict_node_free(void *ptr) +{ + DICT_NODE *node = (DICT_NODE *) ptr; + DICT *dict = node->dict; + + if (dict->close) + dict->close(dict); + myfree((void *) node); +} + +/* dict_unregister - break association with named dictionary */ + +void dict_unregister(const char *dict_name) +{ + const char *myname = "dict_unregister"; + DICT_NODE *node; + + if ((node = dict_node(dict_name)) == 0) + msg_panic("non-existing dictionary: %s", dict_name); + if (msg_verbose > 1) + msg_info("%s: %s %d", myname, dict_name, node->refcount); + if (--(node->refcount) == 0) + htable_delete(dict_table, dict_name, dict_node_free); +} + +/* dict_update - replace or add dictionary entry */ + +int dict_update(const char *dict_name, const char *member, const char *value) +{ + const char *myname = "dict_update"; + DICT *dict; + + DICT_FIND_FOR_UPDATE(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, value); + return (dict->update(dict, member, value)); +} + +/* dict_lookup - look up dictionary entry */ + +const char *dict_lookup(const char *dict_name, const char *member) +{ + const char *myname = "dict_lookup"; + DICT *dict; + const char *ret; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (dict != 0) { + ret = dict->lookup(dict, member); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, ret ? ret : + dict->error ? "(error)" : "(notfound)"); + return (ret); + } else { + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, "(notfound)"); + return (0); + } +} + +/* dict_delete - delete dictionary entry */ + +int dict_delete(const char *dict_name, const char *member) +{ + const char *myname = "dict_delete"; + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: delete %s", myname, member); + return (dict ? dict->delete(dict, member) : DICT_STAT_FAIL); +} + +/* dict_sequence - traverse dictionary */ + +int dict_sequence(const char *dict_name, const int func, + const char **member, const char **value) +{ + const char *myname = "dict_sequence"; + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: sequence func %d", myname, func); + return (dict ? dict->sequence(dict, func, member, value) : DICT_STAT_FAIL); +} + +/* dict_error - return last error */ + +int dict_error(const char *dict_name) +{ + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + return (dict ? dict->error : DICT_ERR_NONE); +} + +/* dict_load_file_xt - read entries from text file */ + +int dict_load_file_xt(const char *dict_name, const char *path) +{ + VSTREAM *fp; + struct stat st; + time_t before; + time_t after; + + /* + * Read the file again if it is hot. This may result in reading a partial + * parameter name when a file changes in the middle of a read. + */ + for (before = time((time_t *) 0); /* see below */ ; before = after) { + if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) + return (0); + dict_load_fp(dict_name, fp); + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", path); + if (vstream_ferror(fp) || vstream_fclose(fp)) + msg_fatal("read %s: %m", path); + after = time((time_t *) 0); + if (st.st_mtime < before - 1 || st.st_mtime > after) + break; + if (msg_verbose > 1) + msg_info("pausing to let %s cool down", path); + doze(300000); + } + return (1); +} + +/* dict_load_fp - read entries from open stream */ + +void dict_load_fp(const char *dict_name, VSTREAM *fp) +{ + const char *myname = "dict_load_fp"; + VSTRING *buf; + char *member; + char *val; + const char *old; + int last_line; + int lineno; + const char *err; + struct stat st; + DICT *dict; + + /* + * Instantiate the dictionary even if the file is empty. + */ + DICT_FIND_FOR_UPDATE(dict, dict_name); + buf = vstring_alloc(100); + last_line = 0; + + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", VSTREAM_PATH(fp)); + while (readllines(buf, fp, &last_line, &lineno)) { + if ((err = split_nameval(STR(buf), &member, &val)) != 0) + msg_fatal("%s, line %d: %s: \"%s\"", + VSTREAM_PATH(fp), + lineno, + err, STR(buf)); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, val); + if ((old = dict->lookup(dict, member)) != 0 + && strcmp(old, val) != 0) + msg_warn("%s, line %d: overriding earlier entry: %s=%s", + VSTREAM_PATH(fp), lineno, member, old); + if (dict->update(dict, member, val) != 0) + msg_fatal("%s, line %d: unable to update %s:%s", + VSTREAM_PATH(fp), lineno, dict->type, dict->name); + } + vstring_free(buf); + dict->owner.uid = st.st_uid; + dict->owner.status = (st.st_uid != 0); +} + +/* dict_eval_lookup - macro parser call-back routine */ + +static const char *dict_eval_lookup(const char *key, int unused_type, + void *context) +{ + char *dict_name = (char *) context; + const char *pp = 0; + DICT *dict; + + /* + * XXX how would one recover? + */ + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (dict != 0 + && (pp = dict->lookup(dict, key)) == 0 && dict->error != 0) + msg_fatal("dictionary %s: lookup %s: operation failed", dict_name, key); + return (pp); +} + +/* dict_eval - expand embedded dictionary references */ + +const char *dict_eval(const char *dict_name, const char *value, int recursive) +{ + const char *myname = "dict_eval"; + static VSTRING *buf; + int status; + + /* + * Initialize. + */ + if (buf == 0) + buf = vstring_alloc(10); + + /* + * Expand macros, possibly recursively. + */ +#define DONT_FILTER (char *) 0 + + status = mac_expand(buf, value, + recursive ? MAC_EXP_FLAG_RECURSE : MAC_EXP_FLAG_NONE, + DONT_FILTER, dict_eval_lookup, (void *) dict_name); + if (status & MAC_PARSE_ERROR) + msg_fatal("dictionary %s: macro processing error", dict_name); + if (msg_verbose > 1) { + if (strcmp(value, STR(buf)) != 0) + msg_info("%s: expand %s -> %s", myname, value, STR(buf)); + else + msg_info("%s: const %s", myname, value); + } + return (STR(buf)); +} + +/* dict_walk - iterate over all dictionaries in arbitrary order */ + +void dict_walk(DICT_WALK_ACTION action, void *ptr) +{ + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + HTABLE_INFO *h; + + ht_info_list = htable_list(dict_table); + for (ht = ht_info_list; (h = *ht) != 0; ht++) + action(h->key, (DICT *) h->value, ptr); + myfree((void *) ht_info_list); +} + +/* dict_changed_name - see if any dictionary has changed */ + +const char *dict_changed_name(void) +{ + const char *myname = "dict_changed_name"; + struct stat st; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + HTABLE_INFO *h; + const char *status; + DICT *dict; + + ht_info_list = htable_list(dict_table); + for (status = 0, ht = ht_info_list; status == 0 && (h = *ht) != 0; ht++) { + dict = ((DICT_NODE *) h->value)->dict; + if (dict->stat_fd < 0) /* not file-based */ + continue; + if (dict->mtime == 0) /* not bloody likely */ + msg_warn("%s: table %s: null time stamp", myname, h->key); + if (fstat(dict->stat_fd, &st) < 0) + msg_fatal("%s: fstat: %m", myname); + if (((dict->flags & DICT_FLAG_MULTI_WRITER) == 0 + && st.st_mtime != dict->mtime) + || st.st_nlink == 0) + status = h->key; + } + myfree((void *) ht_info_list); + return (status); +} + +/* dict_changed - backwards compatibility */ + +int dict_changed(void) +{ + return (dict_changed_name() != 0); +} + + /* + * Mapping between flag names and flag values. + */ +static const NAME_MASK dict_mask[] = { + "warn_dup", DICT_FLAG_DUP_WARN, /* if file, warn about dups */ + "ignore_dup", DICT_FLAG_DUP_IGNORE, /* if file, ignore dups */ + "try0null", DICT_FLAG_TRY0NULL, /* do not append 0 to key/value */ + "try1null", DICT_FLAG_TRY1NULL, /* append 0 to key/value */ + "fixed", DICT_FLAG_FIXED, /* fixed key map */ + "pattern", DICT_FLAG_PATTERN, /* keys are patterns */ + "lock", DICT_FLAG_LOCK, /* lock before access */ + "replace", DICT_FLAG_DUP_REPLACE, /* if file, replace dups */ + "sync_update", DICT_FLAG_SYNC_UPDATE, /* if file, sync updates */ + "debug", DICT_FLAG_DEBUG, /* log access */ + "no_regsub", DICT_FLAG_NO_REGSUB, /* disallow regexp substitution */ + "no_proxy", DICT_FLAG_NO_PROXY, /* disallow proxy mapping */ + "no_unauth", DICT_FLAG_NO_UNAUTH, /* disallow unauthenticated data */ + "fold_fix", DICT_FLAG_FOLD_FIX, /* case-fold with fixed-case key map */ + "fold_mul", DICT_FLAG_FOLD_MUL, /* case-fold with multi-case key map */ + "open_lock", DICT_FLAG_OPEN_LOCK, /* permanent lock upon open */ + "bulk_update", DICT_FLAG_BULK_UPDATE, /* bulk update if supported */ + "multi_writer", DICT_FLAG_MULTI_WRITER, /* multi-writer safe */ + "utf8_request", DICT_FLAG_UTF8_REQUEST, /* request UTF-8 activation */ + "utf8_active", DICT_FLAG_UTF8_ACTIVE, /* UTF-8 is activated */ + "src_rhs_is_file", DICT_FLAG_SRC_RHS_IS_FILE, /* value from file */ + 0, +}; + +/* dict_flags_str - convert bitmask to symbolic flag names */ + +const char *dict_flags_str(int dict_flags) +{ + static VSTRING *buf = 0; + + if (buf == 0) + buf = vstring_alloc(1); + + return (str_name_mask_opt(buf, "dictionary flags", dict_mask, dict_flags, + NAME_MASK_NUMBER | NAME_MASK_PIPE)); +} + +/* dict_flags_mask - convert symbolic flag names to bitmask */ + +int dict_flags_mask(const char *names) +{ + return (name_mask("dictionary flags", dict_mask, names)); +} |