diff options
Diffstat (limited to 'src/proxymap/proxymap.c')
-rw-r--r-- | src/proxymap/proxymap.c | 851 |
1 files changed, 851 insertions, 0 deletions
diff --git a/src/proxymap/proxymap.c b/src/proxymap/proxymap.c new file mode 100644 index 0000000..abdcf3a --- /dev/null +++ b/src/proxymap/proxymap.c @@ -0,0 +1,851 @@ +/*++ +/* NAME +/* proxymap 8 +/* SUMMARY +/* Postfix lookup table proxy server +/* SYNOPSIS +/* \fBproxymap\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBproxymap\fR(8) server provides read-only or read-write +/* table lookup service to Postfix processes. These services are +/* implemented with distinct service names: \fBproxymap\fR and +/* \fBproxywrite\fR, respectively. The purpose of these services is: +/* .IP \(bu +/* To overcome chroot restrictions. For example, a chrooted SMTP +/* server needs access to the system passwd file in order to +/* reject mail for non-existent local addresses, but it is not +/* practical to maintain a copy of the passwd file in the chroot +/* jail. The solution: +/* .sp +/* .nf +/* local_recipient_maps = +/* proxy:unix:passwd.byname $alias_maps +/* .fi +/* .IP \(bu +/* To consolidate the number of open lookup tables by sharing +/* one open table among multiple processes. For example, making +/* mysql connections from every Postfix daemon process results +/* in "too many connections" errors. The solution: +/* .sp +/* .nf +/* virtual_alias_maps = +/* proxy:mysql:/etc/postfix/virtual_alias.cf +/* .fi +/* .sp +/* The total number of connections is limited by the number of +/* proxymap server processes. +/* .IP \(bu +/* To provide single-updater functionality for lookup tables +/* that do not reliably support multiple writers (i.e. all +/* file-based tables). +/* .PP +/* The \fBproxymap\fR(8) server implements the following requests: +/* .IP "\fBopen\fR \fImaptype:mapname flags\fR" +/* Open the table with type \fImaptype\fR and name \fImapname\fR, +/* as controlled by \fIflags\fR. The reply includes the \fImaptype\fR +/* dependent flags (to distinguish a fixed string table from a regular +/* expression table). +/* .IP "\fBlookup\fR \fImaptype:mapname flags key\fR" +/* Look up the data stored under the requested key. +/* The reply is the request completion status code and +/* the lookup result value. +/* The \fImaptype:mapname\fR and \fIflags\fR are the same +/* as with the \fBopen\fR request. +/* .IP "\fBupdate\fR \fImaptype:mapname flags key value\fR" +/* Update the data stored under the requested key. +/* The reply is the request completion status code. +/* The \fImaptype:mapname\fR and \fIflags\fR are the same +/* as with the \fBopen\fR request. +/* .sp +/* To implement single-updater maps, specify a process limit +/* of 1 in the master.cf file entry for the \fBproxywrite\fR +/* service. +/* .sp +/* This request is supported in Postfix 2.5 and later. +/* .IP "\fBdelete\fR \fImaptype:mapname flags key\fR" +/* Delete the data stored under the requested key. +/* The reply is the request completion status code. +/* The \fImaptype:mapname\fR and \fIflags\fR are the same +/* as with the \fBopen\fR request. +/* .sp +/* This request is supported in Postfix 2.5 and later. +/* .IP "\fBsequence\fR \fImaptype:mapname flags function\fR" +/* Iterate over the specified database. The \fIfunction\fR +/* is one of DICT_SEQ_FUN_FIRST or DICT_SEQ_FUN_NEXT. +/* The reply is the request completion status code and +/* a lookup key and result value, if found. +/* .sp +/* This request is supported in Postfix 2.9 and later. +/* .PP +/* The request completion status is one of OK, RETRY, NOKEY +/* (lookup failed because the key was not found), BAD (malformed +/* request) or DENY (the table is not approved for proxy read +/* or update access). +/* +/* There is no \fBclose\fR command, nor are tables implicitly closed +/* when a client disconnects. The purpose is to share tables among +/* multiple client processes. +/* SERVER PROCESS MANAGEMENT +/* .ad +/* .fi +/* \fBproxymap\fR(8) servers run under control by the Postfix +/* \fBmaster\fR(8) +/* server. Each server can handle multiple simultaneous connections. +/* When all servers are busy while a client connects, the \fBmaster\fR(8) +/* creates a new \fBproxymap\fR(8) server process, provided that the +/* process limit is not exceeded. +/* Each server terminates after serving at least \fB$max_use\fR clients +/* or after \fB$max_idle\fR seconds of idle time. +/* SECURITY +/* .ad +/* .fi +/* The \fBproxymap\fR(8) server opens only tables that are +/* approved via the \fBproxy_read_maps\fR or \fBproxy_write_maps\fR +/* configuration parameters, does not talk to +/* users, and can run at fixed low privilege, chrooted or not. +/* However, running the proxymap server chrooted severely limits +/* usability, because it can open only chrooted tables. +/* +/* The \fBproxymap\fR(8) server is not a trusted daemon process, and must +/* not be used to look up sensitive information such as UNIX user or +/* group IDs, mailbox file/directory names or external commands. +/* +/* In Postfix version 2.2 and later, the proxymap client recognizes +/* requests to access a table for security-sensitive purposes, +/* and opens the table directly. This allows the same main.cf +/* setting to be used by sensitive and non-sensitive processes. +/* +/* Postfix-writable data files should be stored under a dedicated +/* directory that is writable only by the Postfix mail system, +/* such as the Postfix-owned \fBdata_directory\fR. +/* +/* In particular, Postfix-writable files should never exist +/* in root-owned directories. That would open up a particular +/* type of security hole where ownership of a file or directory +/* does not match the provider of its content. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* The \fBproxymap\fR(8) server provides service to multiple clients, +/* and must therefore not be used for tables that have high-latency +/* lookups. +/* +/* The \fBproxymap\fR(8) read-write service does not explicitly +/* close lookup tables (even if it did, this could not be relied on, +/* because the process may be terminated between table updates). +/* The read-write service should therefore not be used with tables that +/* leave persistent storage in an inconsistent state between +/* updates (for example, CDB). Tables that support "sync on +/* update" should be safe (for example, Berkeley DB) as should +/* tables that are implemented by a real DBMS. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* On busy mail systems a long time may pass before +/* \fBproxymap\fR(8) relevant +/* changes to \fBmain.cf\fR are picked up. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdata_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix-writable data files (for example: +/* caches, pseudo-random numbers). +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBproxy_read_maps (see 'postconf -d' output)\fR" +/* The lookup tables that the \fBproxymap\fR(8) server is allowed to +/* access for the read-only service. +/* .PP +/* Available in Postfix 2.5 and later: +/* .IP "\fBdata_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix-writable data files (for example: +/* caches, pseudo-random numbers). +/* .IP "\fBproxy_write_maps (see 'postconf -d' output)\fR" +/* The lookup tables that the \fBproxymap\fR(8) server is allowed to +/* access for the read-write service. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* DATABASE_README, Postfix lookup table overview +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* The proxymap service was introduced with Postfix 2.0. +/* 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 <string.h> +#include <stdlib.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <htable.h> +#include <stringops.h> +#include <dict.h> +#include <dict_pipe.h> +#include <dict_union.h> + +/* Global library. */ + +#include <mail_conf.h> +#include <mail_params.h> +#include <mail_version.h> +#include <mail_proto.h> +#include <dict_proxy.h> + +/* Server skeleton. */ + +#include <mail_server.h> + +/* Application-specific. */ + + /* + * XXX All but the last are needed here so that $name expansion dependencies + * aren't too broken. The fix is to gather all parameter default settings in + * one place. + */ +char *var_alias_maps; +char *var_local_rcpt_maps; +char *var_virt_alias_maps; +char *var_virt_alias_doms; +char *var_virt_mailbox_maps; +char *var_virt_mailbox_doms; +char *var_relay_rcpt_maps; +char *var_canonical_maps; +char *var_send_canon_maps; +char *var_rcpt_canon_maps; +char *var_relocated_maps; +char *var_transport_maps; +char *var_verify_map; +char *var_smtpd_snd_auth_maps; +char *var_psc_cache_map; +char *var_proxy_read_maps; +char *var_proxy_write_maps; + + /* + * The pre-approved, pre-parsed list of maps. + */ +static HTABLE *proxy_auth_maps; + + /* + * Shared and static to reduce memory allocation overhead. + */ +static VSTRING *request; +static VSTRING *request_map; +static VSTRING *request_key; +static VSTRING *request_value; +static VSTRING *map_type_name_flags; + + /* + * Are we a proxy writer or not? + */ +static int proxy_writer; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define VSTREQ(x,y) (strcmp(STR(x),y) == 0) + +/* get_nested_dict_name - return nested dictionary name pointer, or null */ + +static char *get_nested_dict_name(char *type_name) +{ + const struct { + const char *type_col; + ssize_t type_col_len; + } *prefix, prefixes[] = { + DICT_TYPE_UNION ":", (sizeof(DICT_TYPE_UNION ":") - 1), + DICT_TYPE_PIPE ":", (sizeof(DICT_TYPE_PIPE ":") - 1), + }; + +#define COUNT_OF(x) (sizeof(x)/sizeof((x)[0])) + + for (prefix = prefixes; prefix < prefixes + COUNT_OF(prefixes); prefix++) { + if (strncmp(type_name, prefix->type_col, prefix->type_col_len) == 0) + return (type_name + prefix->type_col_len); + } + return (0); +} + +/* proxy_map_find - look up or open table */ + +static DICT *proxy_map_find(const char *map_type_name, int request_flags, + int *statp) +{ + DICT *dict; + +#define PROXY_COLON DICT_TYPE_PROXY ":" +#define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1) +#define READ_OPEN_FLAGS O_RDONLY +#define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT) + + /* + * Canonicalize the map name. If the map is not on the approved list, + * deny the request. + */ +#define PROXY_MAP_FIND_ERROR_RETURN(x) { *statp = (x); return (0); } +#define PROXY_MAP_PARAM_NAME(proxy_writer) \ + ((proxy_writer) == 0 ? VAR_PROXY_READ_MAPS : VAR_PROXY_WRITE_MAPS) + + while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0) + map_type_name += PROXY_COLON_LEN; + /* XXX The following breaks with maps that have ':' in their name. */ + if (strchr(map_type_name, ':') == 0) + PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD); + if (htable_locate(proxy_auth_maps, map_type_name) == 0) { + msg_warn("request for unapproved table: \"%s\"", map_type_name); + msg_warn("to approve this table for %s access, list %s:%s in %s:%s", + proxy_writer == 0 ? "read-only" : "read-write", + DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE, + PROXY_MAP_PARAM_NAME(proxy_writer)); + PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY); + } + + /* + * Open one instance of a map for each combination of name+flags. + * + * Assume that a map instance can be shared among clients with different + * paranoia flag settings and with different map lookup flag settings. + * + * XXX The open() flags are passed implicitly, via the selection of the + * service name. For a more sophisticated interface, appropriate subsets + * of open() flags should be received directly from the client. + */ + vstring_sprintf(map_type_name_flags, "%s:%s", map_type_name, + dict_flags_str(request_flags & DICT_FLAG_INST_MASK)); + if (msg_verbose) + msg_info("proxy_map_find: %s", STR(map_type_name_flags)); + if ((dict = dict_handle(STR(map_type_name_flags))) == 0) { + dict = dict_open(map_type_name, proxy_writer ? + WRITE_OPEN_FLAGS : READ_OPEN_FLAGS, + request_flags); + if (dict == 0) + msg_panic("proxy_map_find: dict_open null result"); + dict_register(STR(map_type_name_flags), dict); + } + dict->error = 0; + return (dict); +} + +/* proxymap_sequence_service - remote sequence service */ + +static void proxymap_sequence_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int request_func; + const char *reply_key; + const char *reply_value; + int dict_status; + int reply_status; + + /* + * Process the request. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + RECV_ATTR_INT(MAIL_ATTR_FUNC, &request_func), + ATTR_TYPE_END) != 3 + || (request_func != DICT_SEQ_FUN_FIRST + && request_func != DICT_SEQ_FUN_NEXT)) { + reply_status = PROXY_STAT_BAD; + reply_key = reply_value = ""; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + reply_key = reply_value = ""; + } else { + dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) + | (request_flags & DICT_FLAG_RQST_MASK)); + dict_status = dict_seq(dict, request_func, &reply_key, &reply_value); + if (dict_status == 0) { + reply_status = PROXY_STAT_OK; + } else if (dict->error == 0) { + reply_status = PROXY_STAT_NOKEY; + reply_key = reply_value = ""; + } else { + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); + reply_key = reply_value = ""; + } + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + SEND_ATTR_STR(MAIL_ATTR_KEY, reply_key), + SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value), + ATTR_TYPE_END); +} + +/* proxymap_lookup_service - remote lookup service */ + +static void proxymap_lookup_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + const char *reply_value; + int reply_status; + + /* + * Process the request. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), + ATTR_TYPE_END) != 3) { + reply_status = PROXY_STAT_BAD; + reply_value = ""; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + reply_value = ""; + } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) + | (request_flags & DICT_FLAG_RQST_MASK)), + (reply_value = dict_get(dict, STR(request_key))) != 0) { + reply_status = PROXY_STAT_OK; + } else if (dict->error == 0) { + reply_status = PROXY_STAT_NOKEY; + reply_value = ""; + } else { + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); + reply_value = ""; + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value), + ATTR_TYPE_END); +} + +/* proxymap_update_service - remote update service */ + +static void proxymap_update_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int dict_status; + int reply_status; + + /* + * Process the request. + * + * XXX We don't close maps, so we must turn on synchronous update to ensure + * that the on-disk data is in a consistent state between updates. + * + * XXX We ignore duplicates, because the proxymap server would abort + * otherwise. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), + RECV_ATTR_STR(MAIL_ATTR_VALUE, request_value), + ATTR_TYPE_END) != 4) { + reply_status = PROXY_STAT_BAD; + } else if (proxy_writer == 0) { + msg_warn("refusing %s update request on non-%s service", + STR(request_map), MAIL_SERVICE_PROXYWRITE); + reply_status = PROXY_STAT_DENY; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + /* void */ ; + } else { + dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) + | (request_flags & DICT_FLAG_RQST_MASK) + | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE); + dict_status = dict_put(dict, STR(request_key), STR(request_value)); + if (dict_status == 0) { + reply_status = PROXY_STAT_OK; + } else if (dict->error == 0) { + reply_status = PROXY_STAT_NOKEY; + } else { + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); + } + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + ATTR_TYPE_END); +} + +/* proxymap_delete_service - remote delete service */ + +static void proxymap_delete_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int dict_status; + int reply_status; + + /* + * Process the request. + * + * XXX We don't close maps, so we must turn on synchronous update to ensure + * that the on-disk data is in a consistent state between updates. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), + ATTR_TYPE_END) != 3) { + reply_status = PROXY_STAT_BAD; + } else if (proxy_writer == 0) { + msg_warn("refusing %s delete request on non-%s service", + STR(request_map), MAIL_SERVICE_PROXYWRITE); + reply_status = PROXY_STAT_DENY; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + /* void */ ; + } else { + dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) + | (request_flags & DICT_FLAG_RQST_MASK) + | DICT_FLAG_SYNC_UPDATE); + dict_status = dict_del(dict, STR(request_key)); + if (dict_status == 0) { + reply_status = PROXY_STAT_OK; + } else if (dict->error == 0) { + reply_status = PROXY_STAT_NOKEY; + } else { + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); + } + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + ATTR_TYPE_END); +} + +/* proxymap_open_service - open remote lookup table */ + +static void proxymap_open_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int reply_status; + int reply_flags; + + /* + * Process the request. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + ATTR_TYPE_END) != 2) { + reply_status = PROXY_STAT_BAD; + reply_flags = 0; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + reply_flags = 0; + } else { + reply_status = PROXY_STAT_OK; + reply_flags = dict->flags; + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + SEND_ATTR_INT(MAIL_ATTR_FLAGS, reply_flags), + ATTR_TYPE_END); +} + +/* proxymap_service - perform service for client */ + +static void proxymap_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Deadline enforcement. + */ + if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0) + vstream_control(client_stream, + CA_VSTREAM_CTL_TIMEOUT(1), + CA_VSTREAM_CTL_END); + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the proxymap service. All connection-management stuff is handled by + * the common code in multi_server.c. + */ + vstream_control(client_stream, + CA_VSTREAM_CTL_START_DEADLINE, + CA_VSTREAM_CTL_END); + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_REQ, request), + ATTR_TYPE_END) == 1) { + if (VSTREQ(request, PROXY_REQ_LOOKUP)) { + proxymap_lookup_service(client_stream); + } else if (VSTREQ(request, PROXY_REQ_UPDATE)) { + proxymap_update_service(client_stream); + } else if (VSTREQ(request, PROXY_REQ_DELETE)) { + proxymap_delete_service(client_stream); + } else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) { + proxymap_sequence_service(client_stream); + } else if (VSTREQ(request, PROXY_REQ_OPEN)) { + proxymap_open_service(client_stream); + } else { + msg_warn("unrecognized request: \"%s\", ignored", STR(request)); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, PROXY_STAT_BAD), + ATTR_TYPE_END); + } + } + vstream_control(client_stream, + CA_VSTREAM_CTL_START_DEADLINE, + CA_VSTREAM_CTL_END); + vstream_fflush(client_stream); +} + +/* dict_proxy_open - intercept remote map request from inside library */ + +DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) +{ + if (msg_verbose) + msg_info("dict_proxy_open(%s, 0%o, 0%o) called from internal routine", + map, open_flags, dict_flags); + while (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) == 0) + map += PROXY_COLON_LEN; + return (dict_open(map, open_flags, dict_flags)); +} + +/* authorize_proxied_maps - recursively authorize maps */ + +static void authorize_proxied_maps(char *bp) +{ + const char *sep = CHARS_COMMA_SP; + const char *parens = CHARS_BRACE; + char *type_name; + + while ((type_name = mystrtokq(&bp, sep, parens)) != 0) { + char *nested_info; + + /* Maybe { maptype:mapname attr=value... } */ + if (*type_name == parens[0]) { + char *err; + + /* Warn about blatant syntax error. */ + if ((err = extpar(&type_name, parens, EXTPAR_FLAG_NONE)) != 0) { + msg_warn("bad %s parameter value: %s", + PROXY_MAP_PARAM_NAME(proxy_writer), err); + myfree(err); + continue; + } + /* Don't try to second-guess the semantics of { }. */ + if ((type_name = mystrtokq(&type_name, sep, parens)) == 0) + continue; + } + /* Recurse into nested map (pipemap, unionmap). */ + if ((nested_info = get_nested_dict_name(type_name)) != 0) { + char *err; + + if (*nested_info != parens[0]) + continue; + /* Warn about blatant syntax error. */ + if ((err = extpar(&nested_info, parens, EXTPAR_FLAG_NONE)) != 0) { + msg_warn("bad %s parameter value: %s", + PROXY_MAP_PARAM_NAME(proxy_writer), err); + myfree(err); + continue; + } + authorize_proxied_maps(nested_info); + continue; + } + if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)) + continue; + do { + type_name += PROXY_COLON_LEN; + } while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)); + if (strchr(type_name, ':') != 0 + && htable_locate(proxy_auth_maps, type_name) == 0) { + (void) htable_enter(proxy_auth_maps, type_name, (void *) 0); + if (msg_verbose) + msg_info("allowlisting %s from %s", type_name, + PROXY_MAP_PARAM_NAME(proxy_writer)); + } + } +} + +/* post_jail_init - initialization after privilege drop */ + +static void post_jail_init(char *service_name, char **unused_argv) +{ + char *saved_filter; + + /* + * Are we proxy writer? + */ + if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0) + proxy_writer = 1; + else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0) + msg_fatal("service name must be one of %s or %s", + MAIL_SERVICE_PROXYMAP, MAIL_SERVICE_PROXYMAP); + + /* + * Pre-allocate buffers. + */ + request = vstring_alloc(10); + request_map = vstring_alloc(10); + request_key = vstring_alloc(10); + request_value = vstring_alloc(10); + map_type_name_flags = vstring_alloc(10); + + /* + * Prepare the pre-approved list of proxied tables. + */ + saved_filter = mystrdup(proxy_writer ? var_proxy_write_maps : + var_proxy_read_maps); + proxy_auth_maps = htable_create(13); + authorize_proxied_maps(saved_filter); + myfree(saved_filter); + + /* + * Never, ever, get killed by a master signal, as that could corrupt a + * persistent database when we're in the middle of an update. + */ + if (proxy_writer != 0) + setsid(); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if (proxy_writer == 0 && (table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* post_accept - announce our protocol name */ + +static void post_accept(VSTREAM *stream, char *unused_name, char **unused_argv, + HTABLE *unused_attr) +{ + + /* + * Announce the protocol. + */ + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP), + ATTR_TYPE_END); + (void) vstream_fflush(stream); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0, + VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0, + VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, + VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0, + VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, + VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0, + VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0, + VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0, + VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0, + VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0, + VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0, + VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0, + VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0, + /* The following two must be last for $mapname to work as expected. */ + VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0, + VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + multi_server_main(argc, argv, proxymap_service, + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_POST_ACCEPT(post_accept), + /* XXX CA_MAIL_SERVER_SOLITARY if proxywrite */ + 0); +} |