diff --git a/postfix/HISTORY b/postfix/HISTORY index 50aee39bd..815a6d9c4 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -13878,3 +13878,22 @@ Apologies for any names omitted. Bugfix: don't update the back-to-back delivery time stamp while deferring mail. File: *qmgr/qmgr_entry.c. + +20071203 + + Feature: support for read-write tables in the proxymap + service. This is implemented with a separate master.cf entry + named "proxywrite" that should run with process limit of 1 + if you want to update Berkeley DB like tables. This feature + requires that tables be authorized with the proxy_write_maps + configuration parameter. Files: global/dict_procy.[hc], + proxymap/proxymap.c. + + Human factors: the postmap and postalias commands now produce + nicer diagnostics when asked to do something with a proxied + map that they can't do. Files: postmap/postmap.c, + postalias/postalias.c. + + Bugfix: the proxymap client didn't properly propagate the + postmap (postalias) -r and -w options to the proxymap server. + File: util/dict.h. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 60752c5c4..ff9099f94 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -17,6 +17,14 @@ Incompatibility with Postfix 2.3 and earlier If you upgrade from Postfix 2.3 or earlier, read RELEASE_NOTES-2.4 before proceeding. +Incompatibility with Postfix snapshot 20071203 +============================================== + +The "make upgrade" procedure adds a new service "proxywrite" to the +master.cf file, for read/write lookup table access. If you copy +your old configuration file over the updated one, you will have +to run "postfix upgrade-configuration" again. + Major changes with Postfix snapshot 20071202 ============================================ diff --git a/postfix/conf/post-install b/postfix/conf/post-install index 6620d6322..245edfaef 100644 --- a/postfix/conf/post-install +++ b/postfix/conf/post-install @@ -659,6 +659,15 @@ retry unix - - n - - error EOF } + # Add missing proxywrite service to master.cf. + + grep '^proxywrite.*proxymap' $config_directory/master.cf >/dev/null || { + echo Editing $config_directory/master.cf, adding missing entry for proxywrite service + cat >>$config_directory/master.cf <

-The lookup tables that the proxymap(8) server is allowed to access. -Table references that don't begin with proxy: are ignored. The -proxymap(8) table accesses are read-only. +The lookup tables that the proxymap(8) server is allowed to +access for the read-only service. +Table references that don't begin with proxy: are ignored.

@@ -6162,6 +6162,22 @@ This feature is available in Postfix 2.0 and later.

+
+ +
proxy_write_maps +(default: see "postconf -d" output)
+ +

+The lookup tables that the proxymap(8) server is allowed to +access for the read-write service. +Table references that don't begin with proxy: are ignored. +

+ +

+This feature is available in Postfix 2.5 and later. +

+ +
qmgr_clog_warn_time diff --git a/postfix/html/proxymap.8.html b/postfix/html/proxymap.8.html index f3a5270b6..11c11055d 100644 --- a/postfix/html/proxymap.8.html +++ b/postfix/html/proxymap.8.html @@ -13,8 +13,10 @@ PROXYMAP(8) PROXYMAP(8) proxymap [generic Postfix daemon options] DESCRIPTION - The proxymap(8) server provides read-only table lookup - service to Postfix processes. The purpose of the service + The proxymap(8) server provides read-only or read-write + table lookup service to Postfix processes. These services + are implemented with distinct service names: proxymap and + proxywrite, respectively. The purpose of these services is: o To overcome chroot restrictions. For example, a @@ -39,6 +41,10 @@ PROXYMAP(8) PROXYMAP(8) The total number of connections is limited by the number of proxymap server processes. + o To provide single-updater functionality for lookup + tables that do not reliably support multiple writ- + ers (i.e. all file-based tables). + The proxymap(8) server implements the following requests: open maptype:mapname flags @@ -49,10 +55,26 @@ PROXYMAP(8) PROXYMAP(8) lookup maptype:mapname flags key Look up the data stored under the requested key. - The reply is the request completion status code - (below) and the lookup result value. The map- - type:mapname and flags are the same as with the - open request. + The reply is the request completion status code and + the lookup result value. The maptype:mapname and + flags are the same as with the open request. + + update maptype:mapname flags key value + Update the data stored under the requested key. + The reply is the request completion status code. + The maptype:mapname and flags are the same as with + the open request. + + To implement single-updater maps, specify a process + limit of 1 in the master.cf file entry for the + proxywrite service. + + This request is supported in Postfix 2.5 and later. + + The request completion status is one of OK, RETRY, NOKEY + (lookup failed because the key was not found), BAD (mal- + formed request) or DENY (the table is not approved for + proxy read or update access). There is no close command, nor are tables implicitly closed when a client disconnects. The purpose is to share @@ -69,11 +91,11 @@ PROXYMAP(8) PROXYMAP(8) SECURITY The proxymap(8) server opens only tables that are approved - via the proxy_read_maps configuration parameter, 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. + via the proxy_read_maps or proxy_write_maps 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 proxymap(8) server is not a trusted daemon process, and must not be used to look up sensitive information such @@ -94,6 +116,16 @@ PROXYMAP(8) PROXYMAP(8) clients, and must therefore not be used for tables that have high-latency lookups. + The proxymap(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 imple- + mented by a real DBMS. + CONFIGURATION PARAMETERS On busy mail systems a long time may pass before prox- ymap(8) relevant changes to main.cf are picked up. Use the @@ -135,7 +167,13 @@ PROXYMAP(8) PROXYMAP(8) proxy_read_maps (see 'postconf -d' output) The lookup tables that the proxymap(8) server is - allowed to access. + allowed to access for the read-only service. + + Available in Postfix 2.5 and later: + + proxy_write_maps (see 'postconf -d' output) + The lookup tables that the proxymap(8) server is + allowed to access for the read-write service. SEE ALSO postconf(5), configuration parameters @@ -145,7 +183,7 @@ PROXYMAP(8) PROXYMAP(8) DATABASE_README, Postfix lookup table overview LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. HISTORY diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 0620f2267..c14d6a850 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -3423,11 +3423,17 @@ proxy_interfaces = 1.2.3.4 .ad .ft R .SH proxy_read_maps (default: see "postconf -d" output) -The lookup tables that the \fBproxymap\fR(8) server is allowed to access. -Table references that don't begin with proxy: are ignored. The -\fBproxymap\fR(8) table accesses are read-only. +The lookup tables that the \fBproxymap\fR(8) server is allowed to +access for the read-only service. +Table references that don't begin with proxy: are ignored. .PP This feature is available in Postfix 2.0 and later. +.SH proxy_write_maps (default: see "postconf -d" output) +The lookup tables that the \fBproxymap\fR(8) server is allowed to +access for the read-write service. +Table references that don't begin with proxy: are ignored. +.PP +This feature is available in Postfix 2.5 and later. .SH qmgr_clog_warn_time (default: 300s) The minimal delay between warnings that a specific destination is clogging up the Postfix active queue. Specify 0 to disable. diff --git a/postfix/man/man8/proxymap.8 b/postfix/man/man8/proxymap.8 index 3e31515c5..74a161c16 100644 --- a/postfix/man/man8/proxymap.8 +++ b/postfix/man/man8/proxymap.8 @@ -12,9 +12,10 @@ Postfix lookup table proxy server .SH DESCRIPTION .ad .fi -The \fBproxymap\fR(8) server provides read-only table -lookup service to Postfix processes. The purpose -of the service is: +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 @@ -39,6 +40,10 @@ virtual_alias_maps = .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" @@ -48,11 +53,26 @@ 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 (below) and +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 proxywrite service. +.sp +This request is supported in Postfix 2.5 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. @@ -74,8 +94,9 @@ or after \fB$max_idle\fR seconds of idle time. .nf .ad .fi -The \fBproxymap\fR(8) server opens only tables that are approved via the -\fBproxy_read_maps\fR configuration parameter, does not talk to +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. @@ -98,6 +119,15 @@ Problems and transactions are logged to \fBsyslogd\fR(8). 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. .SH "CONFIGURATION PARAMETERS" .na .nf @@ -130,7 +160,13 @@ 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. +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 "\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. .SH "SEE ALSO" .na .nf diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index f4e9b4409..1812212c3 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -328,6 +328,7 @@ while (<>) { s;\bpropagate_unmatched_extensions\b;$&;g; s;\bproxy_inter[-]*\n* *[]*faces\b;$&;g; s;\bproxy_read_maps\b;$&;g; + s;\bproxy_write_maps\b;$&;g; s;\bqmgr_clog_warn_time\b;$&;g; s;\bqmgr_fudge_factor\b;$&;g; s;\bqmgr_message_active_limit\b;$&;g; diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index f5f1771eb..d5002584f 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -7204,15 +7204,27 @@ This is a read-only parameter. %PARAM proxy_read_maps see "postconf -d" output

-The lookup tables that the proxymap(8) server is allowed to access. -Table references that don't begin with proxy: are ignored. The -proxymap(8) table accesses are read-only. +The lookup tables that the proxymap(8) server is allowed to +access for the read-only service. +Table references that don't begin with proxy: are ignored.

This feature is available in Postfix 2.0 and later.

+%PARAM proxy_write_maps see "postconf -d" output + +

+The lookup tables that the proxymap(8) server is allowed to +access for the read-write service. +Table references that don't begin with proxy: are ignored. +

+ +

+This feature is available in Postfix 2.5 and later. +

+ %PARAM qmgr_clog_warn_time 300s

diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index e354ed69e..b87e77335 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -28,7 +28,7 @@ SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \ tok822_resolve.c tok822_rewrite.c tok822_tree.c trace.c \ user_acl.c valid_mailhost_addr.c verify.c verify_clnt.c \ verp_sender.c wildcard_inet_addr.c xtext.c delivered_hdr.c \ - fold_addr.c header_body_checks.c + fold_addr.c header_body_checks.c mkmap_proxy.c OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \ canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \ clnt_stream.o conv_time.o db_common.o debug_peer.o debug_process.o \ @@ -58,7 +58,7 @@ OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \ tok822_resolve.o tok822_rewrite.o tok822_tree.o trace.o \ user_acl.o valid_mailhost_addr.o verify.o verify_clnt.o \ verp_sender.o wildcard_inet_addr.o xtext.o delivered_hdr.o \ - fold_addr.o header_body_checks.o + fold_addr.o header_body_checks.o mkmap_proxy.o HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \ canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \ conv_time.h db_common.h debug_peer.h debug_process.h defer.h \ @@ -1451,8 +1451,19 @@ mkmap_open.o: ../../include/sys_defs.h mkmap_open.o: ../../include/vbuf.h mkmap_open.o: ../../include/vstream.h mkmap_open.o: ../../include/vstring.h +mkmap_open.o: dict_proxy.h mkmap_open.o: mkmap.h mkmap_open.o: mkmap_open.c +mkmap_proxy.o: ../../include/argv.h +mkmap_proxy.o: ../../include/dict.h +mkmap_proxy.o: ../../include/mymalloc.h +mkmap_proxy.o: ../../include/sys_defs.h +mkmap_proxy.o: ../../include/vbuf.h +mkmap_proxy.o: ../../include/vstream.h +mkmap_proxy.o: ../../include/vstring.h +mkmap_proxy.o: dict_proxy.h +mkmap_proxy.o: mkmap.h +mkmap_proxy.o: mkmap_proxy.c mkmap_sdbm.o: ../../include/argv.h mkmap_sdbm.o: ../../include/dict.h mkmap_sdbm.o: ../../include/dict_sdbm.h diff --git a/postfix/src/global/dict_proxy.c b/postfix/src/global/dict_proxy.c index 264b5ed39..e4f788473 100644 --- a/postfix/src/global/dict_proxy.c +++ b/postfix/src/global/dict_proxy.c @@ -14,7 +14,10 @@ /* dict_proxy_open() relays read-only operations through /* the Postfix proxymap server. /* -/* The \fIopen_flags\fR argument must specify O_RDONLY. +/* The \fIopen_flags\fR argument must specify O_RDONLY +/* or O_RDWR|O_CREAT. 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 @@ -154,6 +157,69 @@ static const char *dict_proxy_lookup(DICT *dict, const char *key) } } +/* dict_proxy_update - update table entry */ + +static void 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->in_flags & DICT_FLAG_RQST_MASK) + | (dict->flags & DICT_FLAG_RQST_MASK); + for (;;) { + stream = clnt_stream_access(proxy_stream); + errno = 0; + count += 1; + if (attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_STR, MAIL_ATTR_REQ, PROXY_REQ_UPDATE, + ATTR_TYPE_STR, MAIL_ATTR_TABLE, dict->name, + ATTR_TYPE_INT, MAIL_ATTR_FLAGS, request_flags, + ATTR_TYPE_STR, MAIL_ATTR_KEY, key, + ATTR_TYPE_STR, MAIL_ATTR_VALUE, value, + ATTR_TYPE_END) != 0 + || vstream_fflush(stream) + || attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_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, VSTREAM_PATH(stream)); + } 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 lookup failed for table \"%s\" key \"%s\": " + "invalid request", + MAIL_SERVICE_PROXYMAP, dict->name, key); + case PROXY_STAT_DENY: + msg_fatal("%s update access is not configured for table \"%s\"", + MAIL_SERVICE_PROXYMAP, dict->name); + case PROXY_STAT_OK: + return; + default: + msg_warn("%s update failed for table \"%s\" key \"%s\": " + "unexpected reply status %d", + MAIL_SERVICE_PROXYMAP, dict->name, key, status); + } + } + clnt_stream_recover(proxy_stream); + sleep(1); /* XXX make configurable */ + } +} + /* dict_proxy_close - disconnect */ static void dict_proxy_close(DICT *dict) @@ -178,9 +244,14 @@ DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) /* * Sanity checks. + * + * 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. */ - if (open_flags != O_RDONLY) - msg_fatal("%s: %s map open requires O_RDONLY access mode", + if (open_flags != O_RDONLY && open_flags != (O_RDWR | O_CREAT)) + msg_fatal("%s: %s map open requires O_RDONLY or O_RDWR|O_CREAT mode", map, DICT_TYPE_PROXY); /* @@ -197,6 +268,7 @@ DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) 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.close = dict_proxy_close; dict_proxy->in_flags = dict_flags; dict_proxy->result = vstring_alloc(10); @@ -207,13 +279,18 @@ DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) * XXX Use absolute pathname to make this work from non-daemon processes. */ if (proxy_stream == 0) { - if (access(MAIL_CLASS_PRIVATE "/" MAIL_SERVICE_PROXYMAP, F_OK) == 0) + if (access(open_flags == O_RDONLY ? + MAIL_CLASS_PRIVATE "/" MAIL_SERVICE_PROXYMAP : + MAIL_CLASS_PRIVATE "/" MAIL_SERVICE_PROXYWRITE, + F_OK) == 0) prefix = MAIL_CLASS_PRIVATE; else prefix = kludge = concatenate(var_queue_dir, "/", MAIL_CLASS_PRIVATE, (char *) 0); proxy_stream = clnt_stream_create(prefix, - MAIL_SERVICE_PROXYMAP, + open_flags == O_RDONLY ? + MAIL_SERVICE_PROXYMAP : + MAIL_SERVICE_PROXYWRITE, var_ipc_idle_limit, var_ipc_ttl_limit); if (kludge) diff --git a/postfix/src/global/dict_proxy.h b/postfix/src/global/dict_proxy.h index ce4962609..2a1e3fd21 100644 --- a/postfix/src/global/dict_proxy.h +++ b/postfix/src/global/dict_proxy.h @@ -28,6 +28,7 @@ extern DICT *dict_proxy_open(const char *, int, int); */ #define PROXY_REQ_OPEN "open" #define PROXY_REQ_LOOKUP "lookup" +#define PROXY_REQ_UPDATE "update" #define PROXY_STAT_OK 0 /* operation succeeded */ #define PROXY_STAT_NOKEY 1 /* requested key not found */ diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index cb46de241..ca7679f47 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -2014,6 +2014,10 @@ extern int var_local_rcpt_code; " $" VAR_MYNETWORKS extern char *var_proxy_read_maps; +#define VAR_PROXY_WRITE_MAPS "proxy_write_maps" +#define DEF_PROXY_WRITE_MAPS "" /* Add here: "$" VAR_AUTH_FAIL_MAP */ +extern char *var_proxy_write_maps; + /* * Other. */ diff --git a/postfix/src/global/mail_proto.h b/postfix/src/global/mail_proto.h index 89b16883b..71b5779ef 100644 --- a/postfix/src/global/mail_proto.h +++ b/postfix/src/global/mail_proto.h @@ -56,6 +56,7 @@ #define MAIL_SERVICE_TRACE "trace" #define MAIL_SERVICE_RELAY "relay" #define MAIL_SERVICE_PROXYMAP "proxymap" +#define MAIL_SERVICE_PROXYWRITE "proxywrite" #define MAIL_SERVICE_SCACHE "scache" /* diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 0ea521443..dd540c643 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20071130" +#define MAIL_RELEASE_DATE "2007111203" #define MAIL_VERSION_NUMBER "2.5" #ifdef SNAPSHOT diff --git a/postfix/src/global/mkmap.h b/postfix/src/global/mkmap.h index 45e32c69c..969b2a733 100644 --- a/postfix/src/global/mkmap.h +++ b/postfix/src/global/mkmap.h @@ -40,6 +40,7 @@ extern MKMAP *mkmap_cdb_open(const char *); extern MKMAP *mkmap_hash_open(const char *); extern MKMAP *mkmap_btree_open(const char *); extern MKMAP *mkmap_sdbm_open(const char *); +extern MKMAP *mkmap_proxy_open(const char *); /* LICENSE /* .ad diff --git a/postfix/src/global/mkmap_open.c b/postfix/src/global/mkmap_open.c index a3caf952f..f96cd472e 100644 --- a/postfix/src/global/mkmap_open.c +++ b/postfix/src/global/mkmap_open.c @@ -66,6 +66,7 @@ #include #include #include +#include #include #include @@ -83,6 +84,7 @@ typedef struct { } MKMAP_OPEN_INFO; MKMAP_OPEN_INFO mkmap_types[] = { + DICT_TYPE_PROXY, mkmap_proxy_open, #ifdef HAS_CDB DICT_TYPE_CDB, mkmap_cdb_open, #endif diff --git a/postfix/src/global/mkmap_proxy.c b/postfix/src/global/mkmap_proxy.c new file mode 100644 index 000000000..e4f4f34f1 --- /dev/null +++ b/postfix/src/global/mkmap_proxy.c @@ -0,0 +1,58 @@ +/*++ +/* NAME +/* mkmap_proxy 3 +/* SUMMARY +/* create or proxied database +/* SYNOPSIS +/* #include +/* +/* MKMAP *mkmap_proxy_open(path) +/* const char *path; +/* DESCRIPTION +/* This module implements support for updating proxy databases. +/* +/* mkmap_proxy_open() is a proxymap-specific helper for the +/* more general mkmap_open() routine. +/* +/* All errors are fatal. +/* SEE ALSO +/* dict_proxy(3), proxy client interface. +/* 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 library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "mkmap.h" + +/* mkmap_proxy_open - create or open database */ + +MKMAP *mkmap_proxy_open(const char *unused_path) +{ + MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap)); + + /* + * Fill in the generic members. + */ + mkmap->open = dict_proxy_open; + mkmap->after_open = 0; + mkmap->after_close = 0; + + return (mkmap); +} diff --git a/postfix/src/oqmgr/qmgr_queue.c b/postfix/src/oqmgr/qmgr_queue.c index 22bf0921c..ae3de84c2 100644 --- a/postfix/src/oqmgr/qmgr_queue.c +++ b/postfix/src/oqmgr/qmgr_queue.c @@ -304,7 +304,7 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) queue->fail_cohorts += 1.0 / queue->window; if (transport->fail_cohort_limit > 0 && queue->fail_cohorts >= transport->fail_cohort_limit) - queue->window = 0; + queue->window = QMGR_QUEUE_STAT_THROTTLED; } /* diff --git a/postfix/src/postalias/Makefile.in b/postfix/src/postalias/Makefile.in index 2e748382e..a10bdd1f7 100644 --- a/postfix/src/postalias/Makefile.in +++ b/postfix/src/postalias/Makefile.in @@ -83,6 +83,7 @@ depend: $(MAKES) # do not edit below this line - it is generated by 'make depend' postalias.o: ../../include/argv.h postalias.o: ../../include/dict.h +postalias.o: ../../include/dict_proxy.h postalias.o: ../../include/mail_conf.h postalias.o: ../../include/mail_dict.h postalias.o: ../../include/mail_params.h diff --git a/postfix/src/postalias/postalias.c b/postfix/src/postalias/postalias.c index 955116b99..186601f56 100644 --- a/postfix/src/postalias/postalias.c +++ b/postfix/src/postalias/postalias.c @@ -239,6 +239,7 @@ #include #include #include +#include /* Application-specific. */ @@ -275,6 +276,8 @@ static void postalias(char *map_type, char *path_name, int postalias_flags, if ((open_flags & O_TRUNC) == 0) { source_fp = VSTREAM_IN; vstream_control(source_fp, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); + } else if (strcmp(map_type, DICT_TYPE_PROXY) == 0) { + msg_fatal("can't create maps via the proxy service"); } else if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) { msg_fatal("open %s: %m", path_name); } @@ -524,10 +527,14 @@ static int postalias_deletes(VSTREAM *in, char **maps, const int map_count, * Open maps ahead of time. */ dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); - for (n = 0; n < map_count; n++) - dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + for (n = 0; n < map_count; n++) { + map_name = split_at(maps[n], ':'); + if (map_name && strcmp(maps[n], DICT_TYPE_PROXY) == 0) + msg_fatal("can't delete map entries via the proxy service"); + dicts[n] = (map_name != 0 ? dict_open3(maps[n], map_name, O_RDWR, dict_flags) : dict_open3(var_db_type, maps[n], O_RDWR, dict_flags)); + } /* * Perform all requests. @@ -556,6 +563,8 @@ static int postalias_delete(const char *map_type, const char *map_name, DICT *dict; int status; + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + msg_fatal("can't delete map entries via the proxy service"); dict = dict_open3(map_type, map_name, O_RDWR, dict_flags); status = dict_del(dict, key); dict_close(dict); @@ -572,6 +581,8 @@ static void postalias_seq(const char *map_type, const char *map_name, const char *value; int func; + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + msg_fatal("can't sequence maps via the proxy service"); dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags); for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) { if (dict_seq(dict, func, &key, &value) != 0) diff --git a/postfix/src/postmap/Makefile.in b/postfix/src/postmap/Makefile.in index 508fae4ec..3ff41f672 100644 --- a/postfix/src/postmap/Makefile.in +++ b/postfix/src/postmap/Makefile.in @@ -83,6 +83,7 @@ depend: $(MAKES) # do not edit below this line - it is generated by 'make depend' postmap.o: ../../include/argv.h postmap.o: ../../include/dict.h +postmap.o: ../../include/dict_proxy.h postmap.o: ../../include/mail_conf.h postmap.o: ../../include/mail_dict.h postmap.o: ../../include/mail_params.h diff --git a/postfix/src/postmap/postmap.c b/postfix/src/postmap/postmap.c index 0f09630bb..2631c7e45 100644 --- a/postfix/src/postmap/postmap.c +++ b/postfix/src/postmap/postmap.c @@ -250,6 +250,7 @@ #include #include #include +#include /* Application-specific. */ @@ -279,6 +280,8 @@ static void postmap(char *map_type, char *path_name, int postmap_flags, if ((open_flags & O_TRUNC) == 0) { source_fp = VSTREAM_IN; vstream_control(source_fp, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); + } else if (strcmp(map_type, DICT_TYPE_PROXY) == 0) { + msg_fatal("can't create maps via the proxy service"); } else if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) { msg_fatal("open %s: %m", path_name); } @@ -471,10 +474,14 @@ static int postmap_deletes(VSTREAM *in, char **maps, const int map_count, * Open maps ahead of time. */ dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); - for (n = 0; n < map_count; n++) - dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + for (n = 0; n < map_count; n++) { + map_name = split_at(maps[n], ':'); + if (map_name && strcmp(maps[n], DICT_TYPE_PROXY) == 0) + msg_fatal("can't delete map entries via the proxy service"); + dicts[n] = (map_name != 0 ? dict_open3(maps[n], map_name, O_RDWR, dict_flags) : dict_open3(var_db_type, maps[n], O_RDWR, dict_flags)); + } /* * Perform all requests. @@ -503,6 +510,8 @@ static int postmap_delete(const char *map_type, const char *map_name, DICT *dict; int status; + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + msg_fatal("can't delete map entries via the proxy service"); dict = dict_open3(map_type, map_name, O_RDWR, dict_flags); status = dict_del(dict, key); dict_close(dict); @@ -519,6 +528,8 @@ static void postmap_seq(const char *map_type, const char *map_name, const char *value; int func; + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + msg_fatal("can't sequence maps via the proxy service"); dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags); for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) { if (dict_seq(dict, func, &key, &value) != 0) diff --git a/postfix/src/proxymap/proxymap.c b/postfix/src/proxymap/proxymap.c index a4412058e..a23a31031 100644 --- a/postfix/src/proxymap/proxymap.c +++ b/postfix/src/proxymap/proxymap.c @@ -6,9 +6,10 @@ /* SYNOPSIS /* \fBproxymap\fR [generic Postfix daemon options] /* DESCRIPTION -/* The \fBproxymap\fR(8) server provides read-only table -/* lookup service to Postfix processes. The purpose -/* of the service is: +/* 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 @@ -33,6 +34,10 @@ /* .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" @@ -42,11 +47,26 @@ /* 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 (below) and +/* 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 proxywrite service. +/* .sp +/* This request is supported in Postfix 2.5 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. @@ -64,8 +84,9 @@ /* SECURITY /* .ad /* .fi -/* The \fBproxymap\fR(8) server opens only tables that are approved via the -/* \fBproxy_read_maps\fR configuration parameter, does not talk to +/* 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. @@ -84,6 +105,15 @@ /* 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 @@ -114,7 +144,13 @@ /* .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. +/* 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 "\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. /* SEE ALSO /* postconf(5), configuration parameters /* master(5), generic daemon options @@ -189,11 +225,12 @@ char *var_rcpt_canon_maps; char *var_relocated_maps; char *var_transport_maps; char *var_proxy_read_maps; +char *var_proxy_write_maps; /* * The pre-approved, pre-parsed list of maps. */ -static HTABLE *proxy_read_maps; +static HTABLE *proxy_auth_maps; /* * Shared and static to reduce memory allocation overhead. @@ -201,8 +238,14 @@ static HTABLE *proxy_read_maps; 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. */ @@ -219,6 +262,7 @@ static DICT *proxy_map_find(const char *map_type_name, int request_flags, #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, @@ -230,11 +274,13 @@ static DICT *proxy_map_find(const char *map_type_name, int request_flags, map_type_name += PROXY_COLON_LEN; if (strchr(map_type_name, ':') == 0) PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD); - if (htable_locate(proxy_read_maps, map_type_name) == 0) { + 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", - MAIL_SERVICE_PROXYMAP, DICT_TYPE_PROXY, map_type_name, - MAIN_CONF_FILE, VAR_PROXY_READ_MAPS); + proxy_writer == 0 ? "read-only" : "read-write", + DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE, + proxy_writer == 0 ? VAR_PROXY_READ_MAPS : + VAR_PROXY_WRITE_MAPS); PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY); } @@ -243,13 +289,21 @@ static DICT *proxy_map_find(const char *map_type_name, int request_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_NP_INST_MASK)); if ((dict = dict_handle(STR(map_type_name_flags))) == 0) - dict = dict_open(map_type_name, READ_OPEN_FLAGS, request_flags); + 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"); + if (proxy_writer) + dict->flags |= DICT_FLAG_SYNC_UPDATE; dict_register(STR(map_type_name_flags), dict); return (dict); } @@ -297,6 +351,46 @@ static void proxymap_lookup_service(VSTREAM *client_stream) ATTR_TYPE_END); } +/* proxymap_update_service - remote update service */ + +static void proxymap_update_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int reply_status; + + /* + * Process the request. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, + ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, + ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key, + ATTR_TYPE_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_put(dict, STR(request_key), STR(request_value)); + reply_status = PROXY_STAT_OK; + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, + ATTR_TYPE_END); +} + /* proxymap_open_service - open remote lookup table */ static void proxymap_open_service(VSTREAM *client_stream) @@ -355,6 +449,8 @@ static void proxymap_service(VSTREAM *client_stream, char *unused_service, 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_OPEN)) { proxymap_open_service(client_stream); } else { @@ -381,26 +477,37 @@ DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) /* post_jail_init - initialization after privilege drop */ -static void post_jail_init(char *unused_name, char **unused_argv) +static void post_jail_init(char *service_name, char **unused_argv) { const char *sep = ", \t\r\n"; char *saved_filter; char *bp; char *type_name; + /* + * 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 = bp = mystrdup(var_proxy_read_maps); - proxy_read_maps = htable_create(13); + saved_filter = bp = mystrdup(proxy_writer ? var_proxy_write_maps : + var_proxy_read_maps); + proxy_auth_maps = htable_create(13); while ((type_name = mystrtok(&bp, sep)) != 0) { if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)) continue; @@ -408,8 +515,8 @@ static void post_jail_init(char *unused_name, char **unused_argv) type_name += PROXY_COLON_LEN; } while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)); if (strchr(type_name, ':') != 0 - && htable_locate(proxy_read_maps, type_name) == 0) - (void) htable_enter(proxy_read_maps, type_name, (char *) 0); + && htable_locate(proxy_auth_maps, type_name) == 0) + (void) htable_enter(proxy_auth_maps, type_name, (char *) 0); } myfree(saved_filter); @@ -418,6 +525,13 @@ static void post_jail_init(char *unused_name, char **unused_argv) * time, so we don't have to do it another time. */ var_idle_limit = 1; + + /* + * 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 */ @@ -426,7 +540,7 @@ static void pre_accept(char *unused_name, char **unused_argv) { const char *table; - if ((table = dict_changed_name()) != 0) { + if (proxy_writer == 0 && (table = dict_changed_name()) != 0) { msg_info("table %s has changed -- restarting", table); exit(0); } @@ -452,6 +566,7 @@ int main(int argc, char **argv) VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0, VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0, 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, }; @@ -464,5 +579,6 @@ int main(int argc, char **argv) MAIL_SERVER_STR_TABLE, str_table, MAIL_SERVER_POST_INIT, post_jail_init, MAIL_SERVER_PRE_ACCEPT, pre_accept, + /* XXX MAIL_SERVER_SOLITARY if proxywrite */ 0); } diff --git a/postfix/src/qmgr/qmgr_queue.c b/postfix/src/qmgr/qmgr_queue.c index f96e853b4..8d9a1528e 100644 --- a/postfix/src/qmgr/qmgr_queue.c +++ b/postfix/src/qmgr/qmgr_queue.c @@ -306,7 +306,7 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) queue->fail_cohorts += 1.0 / queue->window; if (transport->fail_cohort_limit > 0 && queue->fail_cohorts >= transport->fail_cohort_limit) - queue->window = 0; + queue->window = QMGR_QUEUE_STAT_THROTTLED; } /* diff --git a/postfix/src/util/dict.h b/postfix/src/util/dict.h index de1691f08..9829d28ef 100644 --- a/postfix/src/util/dict.h +++ b/postfix/src/util/dict.h @@ -95,7 +95,9 @@ extern DICT *dict_debug(DICT *); #define DICT_FLAG_PARANOID \ (DICT_FLAG_NO_REGSUB | DICT_FLAG_NO_PROXY | DICT_FLAG_NO_UNAUTH) #define DICT_FLAG_IMPL_MASK (DICT_FLAG_FIXED | DICT_FLAG_PATTERN) -#define DICT_FLAG_RQST_MASK DICT_FLAG_FOLD_ANY +#define DICT_FLAG_RQST_MASK (DICT_FLAG_FOLD_ANY | DICT_FLAG_LOCK | \ + DICT_FLAG_DUP_REPLACE | DICT_FLAG_DUP_WARN | \ + DICT_FLAG_SYNC_UPDATE) #define DICT_FLAG_NP_INST_MASK ~(DICT_FLAG_IMPL_MASK | DICT_FLAG_RQST_MASK) #define DICT_FLAG_INST_MASK (DICT_FLAG_NP_INST_MASK | DICT_FLAG_PARANOID)