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.
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
============================================
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 <<EOF || exit 1
+proxywrite unix - - n - 1 proxymap
+EOF
+ }
+
# Report (but do not remove) obsolete files.
test -n "$obsolete" && {
(default: see "postconf -d" output)</b></DT><DD>
<p>
-The lookup tables that the <a href="proxymap.8.html">proxymap(8)</a> server is allowed to access.
-Table references that don't begin with <a href="proxymap.8.html">proxy</a>: are ignored. The
-<a href="proxymap.8.html">proxymap(8)</a> table accesses are read-only.
+The lookup tables that the <a href="proxymap.8.html">proxymap(8)</a> server is allowed to
+access for the read-only service.
+Table references that don't begin with <a href="proxymap.8.html">proxy</a>: are ignored.
</p>
<p>
</p>
+</DD>
+
+<DT><b><a name="proxy_write_maps">proxy_write_maps</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The lookup tables that the <a href="proxymap.8.html">proxymap(8)</a> server is allowed to
+access for the read-write service.
+Table references that don't begin with <a href="proxymap.8.html">proxy</a>: are ignored.
+</p>
+
+<p>
+This feature is available in Postfix 2.5 and later.
+</p>
+
+
</DD>
<DT><b><a name="qmgr_clog_warn_time">qmgr_clog_warn_time</a>
<b>proxymap</b> [generic Postfix daemon options]
<b>DESCRIPTION</b>
- The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server provides read-only table lookup
- service to Postfix processes. The purpose of the service
+ The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server provides read-only or read-write
+ table lookup service to Postfix processes. These services
+ are implemented with distinct service names: <b>proxymap</b> and
+ <b>proxywrite</b>, respectively. The purpose of these services
is:
<b>o</b> To overcome chroot restrictions. For example, a
The total number of connections is limited by the
number of proxymap server processes.
+ <b>o</b> To provide single-updater functionality for lookup
+ tables that do not reliably support multiple writ-
+ ers (i.e. all file-based tables).
+
The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server implements the following requests:
<b>open</b> <i>maptype:mapname flags</i>
<b>lookup</b> <i>maptype:mapname flags key</i>
Look up the data stored under the requested key.
- The reply is the request completion status code
- (below) and the lookup result value. The <i>map-</i>
- <i>type:mapname</i> and <i>flags</i> are the same as with the
- <b>open</b> request.
+ The reply is the request completion status code and
+ the lookup result value. The <i>maptype:mapname</i> and
+ <i>flags</i> are the same as with the <b>open</b> request.
+
+ <b>update</b> <i>maptype:mapname flags key value</i>
+ Update the data stored under the requested key.
+ The reply is the request completion status code.
+ The <i>maptype:mapname</i> and <i>flags</i> are the same as with
+ the <b>open</b> request.
+
+ To implement single-updater maps, specify a process
+ limit of 1 in the <a href="master.5.html">master.cf</a> 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 <b>close</b> command, nor are tables implicitly
closed when a client disconnects. The purpose is to share
<b>SECURITY</b>
The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server opens only tables that are approved
- via the <b><a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a></b> 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 <b><a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a></b> or <b><a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a></b> 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 <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server is not a trusted daemon process,
and must not be used to look up sensitive information such
clients, and must therefore not be used for tables that
have high-latency lookups.
+ The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> 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.
+
<b>CONFIGURATION PARAMETERS</b>
On busy mail systems a long time may pass before <a href="proxymap.8.html"><b>prox-</b></a>
<a href="proxymap.8.html"><b>ymap</b>(8)</a> relevant changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up. Use the
<b><a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a> (see 'postconf -d' output)</b>
The lookup tables that the <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server is
- allowed to access.
+ allowed to access for the read-only service.
+
+ Available in Postfix 2.5 and later:
+
+ <b><a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a> (see 'postconf -d' output)</b>
+ The lookup tables that the <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server is
+ allowed to access for the read-write service.
<b>SEE ALSO</b>
<a href="postconf.5.html">postconf(5)</a>, configuration parameters
<a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>HISTORY</b>
.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.
.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
.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"
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.
.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.
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
.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
s;\bpropagate_unmatched_extensions\b;<a href="postconf.5.html#propagate_unmatched_extensions">$&</a>;g;
s;\bproxy_inter[-</bB>]*\n* *[<bB>]*faces\b;<a href="postconf.5.html#proxy_interfaces">$&</a>;g;
s;\bproxy_read_maps\b;<a href="postconf.5.html#proxy_read_maps">$&</a>;g;
+ s;\bproxy_write_maps\b;<a href="postconf.5.html#proxy_write_maps">$&</a>;g;
s;\bqmgr_clog_warn_time\b;<a href="postconf.5.html#qmgr_clog_warn_time">$&</a>;g;
s;\bqmgr_fudge_factor\b;<a href="postconf.5.html#qmgr_fudge_factor">$&</a>;g;
s;\bqmgr_message_active_limit\b;<a href="postconf.5.html#qmgr_message_active_limit">$&</a>;g;
%PARAM proxy_read_maps see "postconf -d" output
<p>
-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.
</p>
<p>
This feature is available in Postfix 2.0 and later.
</p>
+%PARAM proxy_write_maps see "postconf -d" output
+
+<p>
+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.
+</p>
+
+<p>
+This feature is available in Postfix 2.5 and later.
+</p>
+
%PARAM qmgr_clog_warn_time 300s
<p>
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 \
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 \
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
/* 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
}
}
+/* 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)
/*
* 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);
/*
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);
* 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)
*/
#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 */
" $" 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.
*/
#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"
/*
* 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
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
#include <dict_cdb.h>
#include <dict_dbm.h>
#include <dict_sdbm.h>
+#include <dict_proxy.h>
#include <sigdelay.h>
#include <mymalloc.h>
} MKMAP_OPEN_INFO;
MKMAP_OPEN_INFO mkmap_types[] = {
+ DICT_TYPE_PROXY, mkmap_proxy_open,
#ifdef HAS_CDB
DICT_TYPE_CDB, mkmap_cdb_open,
#endif
--- /dev/null
+/*++
+/* NAME
+/* mkmap_proxy 3
+/* SUMMARY
+/* create or proxied database
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* 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 <sys_defs.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <dict_proxy.h>
+
+/* 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);
+}
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;
}
/*
# 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
#include <mail_version.h>
#include <mkmap.h>
#include <mail_task.h>
+#include <dict_proxy.h>
/* Application-specific. */
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);
}
* 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.
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);
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)
# 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
#include <mail_version.h>
#include <mkmap.h>
#include <mail_task.h>
+#include <dict_proxy.h>
/* Application-specific. */
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);
}
* 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.
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);
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)
/* 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
/* .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"
/* 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.
/* 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.
/* 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
/* .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
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.
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 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,
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);
}
*
* 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);
}
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)
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 {
/* 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;
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);
* 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 */
{
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);
}
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,
};
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);
}
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;
}
/*
#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)