diff options
Diffstat (limited to 'src/global/dict_proxy.c')
-rw-r--r-- | src/global/dict_proxy.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/src/global/dict_proxy.c b/src/global/dict_proxy.c new file mode 100644 index 0000000..5aa9153 --- /dev/null +++ b/src/global/dict_proxy.c @@ -0,0 +1,532 @@ +/*++ +/* NAME +/* dict_proxy 3 +/* SUMMARY +/* generic dictionary proxy client +/* SYNOPSIS +/* #include <dict_proxy.h> +/* +/* DICT *dict_proxy_open(map, open_flags, dict_flags) +/* const char *map; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_proxy_open() relays read-only or read-write operations +/* through the Postfix proxymap server. +/* +/* The \fIopen_flags\fR argument must specify O_RDONLY +/* or O_RDWR. Depending on this, the client +/* connects to the proxymap multiserver or to the +/* proxywrite single updater. +/* +/* The connection to the Postfix proxymap server is automatically +/* closed after $ipc_idle seconds of idle time, or after $ipc_ttl +/* seconds of activity. +/* SECURITY +/* The proxy map server is not meant to be a trusted process. Proxy +/* maps must not be used to look up security sensitive information +/* such as user/group IDs, output files, or external commands. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* clnt_stream(3) client endpoint connection management +/* DIAGNOSTICS +/* Fatal errors: out of memory, unimplemented operation, +/* bad request parameter, map not approved for proxy access. +/* 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 <sys_defs.h> +#include <errno.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <vstring.h> +#include <vstream.h> +#include <attr.h> +#include <dict.h> + +/* Global library. */ + +#include <mail_proto.h> +#include <mail_params.h> +#include <clnt_stream.h> +#include <dict_proxy.h> + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + CLNT_STREAM *clnt; /* client handle (shared) */ + const char *service; /* service name */ + int inst_flags; /* saved dict flags */ + VSTRING *reskey; /* result key storage */ + VSTRING *result; /* storage */ +} DICT_PROXY; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define VSTREQ(v,s) (strcmp(STR(v),s) == 0) + + /* + * All proxied maps of the same type share the same query/reply socket. + */ +static CLNT_STREAM *proxymap_stream; /* read-only maps */ +static CLNT_STREAM *proxywrite_stream; /* read-write maps */ + +/* dict_proxy_handshake - receive server protocol announcement */ + +static int dict_proxy_handshake(VSTREAM *stream) +{ + return (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP), + ATTR_TYPE_END)); +} + +/* dict_proxy_sequence - find first/next entry */ + +static int dict_proxy_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + const char *myname = "dict_proxy_sequence"; + DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; + VSTREAM *stream; + int status; + int count = 0; + int request_flags; + + /* + * The client and server live in separate processes that may start and + * terminate independently. We cannot rely on a persistent connection, + * let alone on persistent state (such as a specific open table) that is + * associated with a specific connection. Each lookup needs to specify + * the table and the flags that were specified to dict_proxy_open(). + */ + VSTRING_RESET(dict_proxy->reskey); + VSTRING_TERMINATE(dict_proxy->reskey); + VSTRING_RESET(dict_proxy->result); + VSTRING_TERMINATE(dict_proxy->result); + request_flags = dict_proxy->inst_flags + | (dict->flags & DICT_FLAG_RQST_MASK); + for (;;) { + stream = clnt_stream_access(dict_proxy->clnt); + errno = 0; + count += 1; + if (stream == 0 + || attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_SEQUENCE), + SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name), + SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags), + SEND_ATTR_INT(MAIL_ATTR_FUNC, function), + ATTR_TYPE_END) != 0 + || vstream_fflush(stream) + || attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + RECV_ATTR_STR(MAIL_ATTR_KEY, dict_proxy->reskey), + RECV_ATTR_STR(MAIL_ATTR_VALUE, dict_proxy->result), + ATTR_TYPE_END) != 3) { + if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT)) + msg_warn("%s: service %s: %m", myname, dict_proxy->service); + } else { + if (msg_verbose) + msg_info("%s: table=%s flags=%s func=%d -> status=%d key=%s val=%s", + myname, dict->name, dict_flags_str(request_flags), + function, status, STR(dict_proxy->reskey), + STR(dict_proxy->result)); + switch (status) { + case PROXY_STAT_BAD: + msg_fatal("%s sequence failed for table \"%s\" function %d: " + "invalid request", + dict_proxy->service, dict->name, function); + case PROXY_STAT_DENY: + msg_fatal("%s service is not configured for table \"%s\"", + dict_proxy->service, dict->name); + case PROXY_STAT_OK: + *key = STR(dict_proxy->reskey); + *value = STR(dict_proxy->result); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); + case PROXY_STAT_NOKEY: + *key = *value = 0; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); + case PROXY_STAT_RETRY: + *key = *value = 0; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); + case PROXY_STAT_CONFIG: + *key = *value = 0; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); + default: + msg_warn("%s sequence failed for table \"%s\" function %d: " + "unexpected reply status %d", + dict_proxy->service, dict->name, function, status); + } + } + clnt_stream_recover(dict_proxy->clnt); + sleep(1); /* XXX make configurable */ + } +} + +/* dict_proxy_lookup - find table entry */ + +static const char *dict_proxy_lookup(DICT *dict, const char *key) +{ + const char *myname = "dict_proxy_lookup"; + DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; + VSTREAM *stream; + int status; + int count = 0; + int request_flags; + + /* + * The client and server live in separate processes that may start and + * terminate independently. We cannot rely on a persistent connection, + * let alone on persistent state (such as a specific open table) that is + * associated with a specific connection. Each lookup needs to specify + * the table and the flags that were specified to dict_proxy_open(). + */ + VSTRING_RESET(dict_proxy->result); + VSTRING_TERMINATE(dict_proxy->result); + request_flags = dict_proxy->inst_flags + | (dict->flags & DICT_FLAG_RQST_MASK); + for (;;) { + stream = clnt_stream_access(dict_proxy->clnt); + errno = 0; + count += 1; + if (stream == 0 + || attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_LOOKUP), + SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name), + SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags), + SEND_ATTR_STR(MAIL_ATTR_KEY, key), + ATTR_TYPE_END) != 0 + || vstream_fflush(stream) + || attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + RECV_ATTR_STR(MAIL_ATTR_VALUE, dict_proxy->result), + ATTR_TYPE_END) != 2) { + if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT)) + msg_warn("%s: service %s: %m", myname, dict_proxy->service); + } else { + if (msg_verbose) + msg_info("%s: table=%s flags=%s key=%s -> status=%d result=%s", + myname, dict->name, + dict_flags_str(request_flags), key, + status, STR(dict_proxy->result)); + switch (status) { + case PROXY_STAT_BAD: + msg_fatal("%s lookup failed for table \"%s\" key \"%s\": " + "invalid request", + dict_proxy->service, dict->name, key); + case PROXY_STAT_DENY: + msg_fatal("%s service is not configured for table \"%s\"", + dict_proxy->service, dict->name); + case PROXY_STAT_OK: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, STR(dict_proxy->result)); + case PROXY_STAT_NOKEY: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, (char *) 0); + case PROXY_STAT_RETRY: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0); + case PROXY_STAT_CONFIG: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, (char *) 0); + default: + msg_warn("%s lookup failed for table \"%s\" key \"%s\": " + "unexpected reply status %d", + dict_proxy->service, dict->name, key, status); + } + } + clnt_stream_recover(dict_proxy->clnt); + sleep(1); /* XXX make configurable */ + } +} + +/* dict_proxy_update - update table entry */ + +static int dict_proxy_update(DICT *dict, const char *key, const char *value) +{ + const char *myname = "dict_proxy_update"; + DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; + VSTREAM *stream; + int status; + int count = 0; + int request_flags; + + /* + * The client and server live in separate processes that may start and + * terminate independently. We cannot rely on a persistent connection, + * let alone on persistent state (such as a specific open table) that is + * associated with a specific connection. Each lookup needs to specify + * the table and the flags that were specified to dict_proxy_open(). + */ + request_flags = dict_proxy->inst_flags + | (dict->flags & DICT_FLAG_RQST_MASK); + for (;;) { + stream = clnt_stream_access(dict_proxy->clnt); + errno = 0; + count += 1; + if (stream == 0 + || attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_UPDATE), + SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name), + SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags), + SEND_ATTR_STR(MAIL_ATTR_KEY, key), + SEND_ATTR_STR(MAIL_ATTR_VALUE, value), + ATTR_TYPE_END) != 0 + || vstream_fflush(stream) + || attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1) { + if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT)) + msg_warn("%s: service %s: %m", myname, dict_proxy->service); + } else { + if (msg_verbose) + msg_info("%s: table=%s flags=%s key=%s value=%s -> status=%d", + myname, dict->name, dict_flags_str(request_flags), + key, value, status); + switch (status) { + case PROXY_STAT_BAD: + msg_fatal("%s update failed for table \"%s\" key \"%s\": " + "invalid request", + dict_proxy->service, dict->name, key); + case PROXY_STAT_DENY: + msg_fatal("%s update access is not configured for table \"%s\"", + dict_proxy->service, dict->name); + case PROXY_STAT_OK: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); + case PROXY_STAT_NOKEY: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); + case PROXY_STAT_RETRY: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); + case PROXY_STAT_CONFIG: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); + default: + msg_warn("%s update failed for table \"%s\" key \"%s\": " + "unexpected reply status %d", + dict_proxy->service, dict->name, key, status); + } + } + clnt_stream_recover(dict_proxy->clnt); + sleep(1); /* XXX make configurable */ + } +} + +/* dict_proxy_delete - delete table entry */ + +static int dict_proxy_delete(DICT *dict, const char *key) +{ + const char *myname = "dict_proxy_delete"; + DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; + VSTREAM *stream; + int status; + int count = 0; + int request_flags; + + /* + * The client and server live in separate processes that may start and + * terminate independently. We cannot rely on a persistent connection, + * let alone on persistent state (such as a specific open table) that is + * associated with a specific connection. Each lookup needs to specify + * the table and the flags that were specified to dict_proxy_open(). + */ + request_flags = dict_proxy->inst_flags + | (dict->flags & DICT_FLAG_RQST_MASK); + for (;;) { + stream = clnt_stream_access(dict_proxy->clnt); + errno = 0; + count += 1; + if (stream == 0 + || attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_DELETE), + SEND_ATTR_STR(MAIL_ATTR_TABLE, dict->name), + SEND_ATTR_INT(MAIL_ATTR_FLAGS, request_flags), + SEND_ATTR_STR(MAIL_ATTR_KEY, key), + ATTR_TYPE_END) != 0 + || vstream_fflush(stream) + || attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1) { + if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != + ENOENT)) + msg_warn("%s: service %s: %m", myname, dict_proxy->service); + } else { + if (msg_verbose) + msg_info("%s: table=%s flags=%s key=%s -> status=%d", + myname, dict->name, dict_flags_str(request_flags), + key, status); + switch (status) { + case PROXY_STAT_BAD: + msg_fatal("%s delete failed for table \"%s\" key \"%s\": " + "invalid request", + dict_proxy->service, dict->name, key); + case PROXY_STAT_DENY: + msg_fatal("%s update access is not configured for table \"%s\"", + dict_proxy->service, dict->name); + case PROXY_STAT_OK: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); + case PROXY_STAT_NOKEY: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); + case PROXY_STAT_RETRY: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); + case PROXY_STAT_CONFIG: + DICT_ERR_VAL_RETURN(dict, DICT_ERR_CONFIG, DICT_STAT_ERROR); + default: + msg_warn("%s delete failed for table \"%s\" key \"%s\": " + "unexpected reply status %d", + dict_proxy->service, dict->name, key, status); + } + } + clnt_stream_recover(dict_proxy->clnt); + sleep(1); /* XXX make configurable */ + } +} + +/* dict_proxy_close - disconnect */ + +static void dict_proxy_close(DICT *dict) +{ + DICT_PROXY *dict_proxy = (DICT_PROXY *) dict; + + vstring_free(dict_proxy->reskey); + vstring_free(dict_proxy->result); + dict_free(dict); +} + +/* dict_proxy_open - open remote map */ + +DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) +{ + const char *myname = "dict_proxy_open"; + DICT_PROXY *dict_proxy; + VSTREAM *stream; + int server_flags; + int status; + const char *service; + char *relative_path; + char *kludge = 0; + char *prefix; + CLNT_STREAM **pstream; + + /* + * If this map can't be proxied then we silently do a direct open. This + * allows sites to benefit from proxying the virtual mailbox maps without + * unnecessary pain. + */ + if (dict_flags & DICT_FLAG_NO_PROXY) + return (dict_open(map, open_flags, dict_flags)); + + /* + * Use a shared stream for proxied table lookups of the same type. + * + * XXX A complete implementation would also allow O_RDWR without O_CREAT. + * But we must not pass on every possible set of flags to the proxy + * server; only sets that make sense. For now, the flags are passed + * implicitly by choosing between the proxymap or proxywrite service. + * + * XXX Use absolute pathname to make this work from non-daemon processes. + */ + if (open_flags == O_RDONLY) { + pstream = &proxymap_stream; + service = var_proxymap_service; + } else if ((open_flags & O_RDWR) == O_RDWR) { + pstream = &proxywrite_stream; + service = var_proxywrite_service; + } else + msg_fatal("%s: %s map open requires O_RDONLY or O_RDWR mode", + map, DICT_TYPE_PROXY); + + if (*pstream == 0) { + relative_path = concatenate(MAIL_CLASS_PRIVATE "/", + service, (char *) 0); + if (access(relative_path, F_OK) == 0) + prefix = MAIL_CLASS_PRIVATE; + else + prefix = kludge = concatenate(var_queue_dir, "/", + MAIL_CLASS_PRIVATE, (char *) 0); + *pstream = clnt_stream_create(prefix, service, var_ipc_idle_limit, + var_ipc_ttl_limit, + dict_proxy_handshake); + if (kludge) + myfree(kludge); + myfree(relative_path); + } + + /* + * Local initialization. + */ + dict_proxy = (DICT_PROXY *) + dict_alloc(DICT_TYPE_PROXY, map, sizeof(*dict_proxy)); + dict_proxy->dict.lookup = dict_proxy_lookup; + dict_proxy->dict.update = dict_proxy_update; + dict_proxy->dict.delete = dict_proxy_delete; + dict_proxy->dict.sequence = dict_proxy_sequence; + dict_proxy->dict.close = dict_proxy_close; + dict_proxy->inst_flags = (dict_flags & DICT_FLAG_INST_MASK); + dict_proxy->reskey = vstring_alloc(10); + dict_proxy->result = vstring_alloc(10); + dict_proxy->clnt = *pstream; + dict_proxy->service = service; + + /* + * Establish initial contact and get the map type specific flags. + * + * XXX Should retrieve flags from local instance. + */ + for (;;) { + stream = clnt_stream_access(dict_proxy->clnt); + errno = 0; + if (stream == 0 + || attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_REQ, PROXY_REQ_OPEN), + SEND_ATTR_STR(MAIL_ATTR_TABLE, dict_proxy->dict.name), + SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict_proxy->inst_flags), + ATTR_TYPE_END) != 0 + || vstream_fflush(stream) + || attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &server_flags), + ATTR_TYPE_END) != 2) { + if (msg_verbose || (errno != EPIPE && errno != ENOENT)) + msg_warn("%s: service %s: %m", myname, dict_proxy->service); + } else { + if (msg_verbose) + msg_info("%s: connect to map=%s status=%d server_flags=%s", + myname, dict_proxy->dict.name, status, + dict_flags_str(server_flags)); + switch (status) { + case PROXY_STAT_BAD: + msg_fatal("%s open failed for table \"%s\": invalid request", + dict_proxy->service, dict_proxy->dict.name); + case PROXY_STAT_DENY: + msg_fatal("%s service is not configured for table \"%s\"", + dict_proxy->service, dict_proxy->dict.name); + case PROXY_STAT_OK: + dict_proxy->dict.flags = (dict_flags & ~DICT_FLAG_IMPL_MASK) + | (server_flags & DICT_FLAG_IMPL_MASK); + return (DICT_DEBUG (&dict_proxy->dict)); + default: + msg_warn("%s open failed for table \"%s\": unexpected status %d", + dict_proxy->service, dict_proxy->dict.name, status); + } + } + clnt_stream_recover(dict_proxy->clnt); + sleep(1); /* XXX make configurable */ + } +} |