SMTP client to report about sessions that fail because a
table is unavailable. Files: global/mail_error.[hc],
smtpd/smtpd_check.c, smtp/smtp_trouble.c.
+
+20111215
+
+ Fine tuning: SMTP server error messages. File: smtpd/smtpd.c.
+
+ Fine tuning: documentation. Files: proto/MEMCACHE_README.html.
+ proto/memcache_table.html.
+
+ Apply "gradual degradation" also when an unsupported database
+ *type* is specified. File: util/dict_open.c.
+
+ Cleanup: tiny memory leaks after surrogate database opens.
+ Files: util/dict_cidr.c, util/dict_db.c.
virtual_mailbox_maps (these specify UNIX process privileges or "/file/name"
destinations). Typically, a memcache database is writable by any process
that can talk to the memcache server; in contrast, security-sensitive
- tables must not be writable by the unprivileged Postfix user.
+ tables must never be writable by the unprivileged Postfix user.
* The Postfix memcache client requires additional configuration when used as
postscreen(8) or verify(8) cache. For details see the backup and ttl
specify UNIX process privileges or "<tt>/file/name</tt>" destinations).
Typically, a memcache database is writable by any process that can
talk to the memcache server; in contrast, security-sensitive tables
-must not be writable by the unprivileged Postfix user. </p>
+must never be writable by the unprivileged Postfix user. </p>
<li> <p> The Postfix memcache client requires additional configuration
when used as <a href="postscreen.8.html">postscreen(8)</a> or <a href="verify.8.html">verify(8)</a> cache. For details see the
in all Postfix instances except for one instance
that will be responsible for cache cleanup.
- NOTE 2: In the case of a proxied database, the full
- database name (including the "<a href="proxymap.8.html">proxy</a>:" prefix) must
- be specified in the proxymap server's
- <a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a> or <a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a> setting
- (depending on whether the access is read-only or
- read-write).
+ NOTE 2: In the case of a proxied backup database,
+ the full backup database name (including the
+ "<a href="proxymap.8.html">proxy</a>:" prefix) must be specified in the proxymap
+ server's <a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a> or <a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a> set-
+ ting (depending on whether the access is read-only
+ or read-write).
<b>flags (default: 0)</b>
Optional flags that should be stored along with a
destinations). In a typical deployment a memcache data-
base is writable by any process that can talk to the mem-
cache server; in contrast, security-sensitive tables must
- not be writable by the unprivileged Postfix user.
+ never be writable by the unprivileged Postfix user.
The Postfix memcache client requires additional configura-
tion when used as <a href="postscreen.8.html"><b>postscreen</b>(8)</a> or <a href="verify.8.html"><b>verify</b>(8)</a> cache. For
all Postfix instances except for one instance that will be
responsible for cache cleanup.
-NOTE 2: In the case of a proxied database, the full database
+NOTE 2: In the case of a proxied backup database, the full
+backup database
name (including the "proxy:" prefix) must be specified in
the proxymap server's proxy_read_maps or proxy_write_maps
setting (depending on whether the access is read-only or
privileges or "\fI/file/name\fR" destinations). In a typical
deployment a memcache database is writable by any process
that can talk to the memcache server; in contrast,
-security-sensitive tables must not be writable by the
+security-sensitive tables must never be writable by the
unprivileged Postfix user.
The Postfix memcache client requires additional configuration
specify UNIX process privileges or "<tt>/file/name</tt>" destinations).
Typically, a memcache database is writable by any process that can
talk to the memcache server; in contrast, security-sensitive tables
-must not be writable by the unprivileged Postfix user. </p>
+must never be writable by the unprivileged Postfix user. </p>
<li> <p> The Postfix memcache client requires additional configuration
when used as postscreen(8) or verify(8) cache. For details see the
# all Postfix instances except for one instance that will be
# responsible for cache cleanup.
#
-# NOTE 2: In the case of a proxied database, the full database
+# NOTE 2: In the case of a proxied backup database, the full
+# backup database
# name (including the "proxy:" prefix) must be specified in
# the proxymap server's proxy_read_maps or proxy_write_maps
# setting (depending on whether the access is read-only or
# privileges or "\fI/file/name\fR" destinations). In a typical
# deployment a memcache database is writable by any process
# that can talk to the memcache server; in contrast,
-# security-sensitive tables must not be writable by the
+# security-sensitive tables must never be writable by the
# unprivileged Postfix user.
#
# The Postfix memcache client requires additional configuration
diff maps.ref maps.tmp
rm -f maps.tmp
+surrogate_test: mail_dict surrogate.ref
+ cp /dev/null surrogate.tmp
+ echo get foo|./mail_dict ldap:/xx write >>surrogate.tmp 2>&1
+ echo get foo|./mail_dict ldap:/xx read >>surrogate.tmp 2>&1
+ echo get foo|./mail_dict mysql:/xx write >>surrogate.tmp 2>&1
+ echo get foo|./mail_dict mysql:/xx read >>surrogate.tmp 2>&1
+ echo get foo|./mail_dict pgsql:/xx write >>surrogate.tmp 2>&1
+ echo get foo|./mail_dict pgsql:/xx read >>surrogate.tmp 2>&1
+ echo get foo|./mail_dict sqlite:/xx write >>surrogate.tmp 2>&1
+ echo get foo|./mail_dict sqlite:/xx read >>surrogate.tmp 2>&1
+ echo get foo|./mail_dict memcache:/xx read >>surrogate.tmp 2>&1
+ diff surrogate.ref surrogate.tmp
+ rm -f surrogate.tmp
+
# Requires: Postfix running, root privileges
rewrite_clnt_test: rewrite_clnt rewrite_clnt.in rewrite_clnt.ref
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20120114"
+#define MAIL_RELEASE_DATE "20120115"
#define MAIL_VERSION_NUMBER "2.9"
#ifdef SNAPSHOT
./namadr_list: warning: bad net/mask pattern: "168.100.589.0/28"
dummy/168.100.189.16: ERROR
dummy/168.100.989.16: NO
-./namadr_list: fatal: unsupported dictionary type: 2001
+./namadr_list: error: unsupported dictionary type: 2001
+./namadr_list: warning: 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 is unavailable. unsupported dictionary type: 2001
+./namadr_list: warning: 2001:240:5c7:0:2d0:b7ff:fe88:2ca7: table lookup problem
+dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca7: ERROR
dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca7: YES
dummy/2001:240:5c7:0:2d0:b7ff:fe88:2ca8: NO
./namadr_list: warning: non-null host address bits in "2001:240:5c7:0:2d0:b7ff:fe88:2ca7/64", perhaps you should use "2001:240:5c7::/64" instead
--- /dev/null
+./mail_dict: error: ldap:/xx map requires O_RDONLY access mode
+> get foo
+./mail_dict: warning: ldap:/xx is unavailable. ldap:/xx map requires O_RDONLY access mode
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: ldap:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./mail_dict: error: mysql:/xx map requires O_RDONLY access mode
+> get foo
+./mail_dict: warning: mysql:/xx is unavailable. mysql:/xx map requires O_RDONLY access mode
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: mysql:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./mail_dict: error: pgsql:/xx map requires O_RDONLY access mode
+> get foo
+./mail_dict: warning: pgsql:/xx is unavailable. pgsql:/xx map requires O_RDONLY access mode
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: pgsql:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./mail_dict: error: sqlite:/xx map requires O_RDONLY access mode
+> get foo
+./mail_dict: warning: sqlite:/xx is unavailable. sqlite:/xx map requires O_RDONLY access mode
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: sqlite:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./mail_dict: error: open /xx: No such file or directory
+> get foo
+./mail_dict: warning: memcache:/xx is unavailable. open /xx: No such file or directory
+foo: error
static NORETURN cant_announce_feature(SMTPD_STATE *state, const char *feature)
{
- msg_warn("don't know if feature %s should be announced to %s",
+ msg_warn("don't know if EHLO feature %s should be announced to %s",
feature, state->namaddr);
vstream_longjmp(state->client, SMTP_ERR_DATA);
}
if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
msg_info("discarding EHLO keywords: %s", str_ehlo_mask(discard_mask));
if (ehlo_discard_maps && ehlo_discard_maps->error) {
- msg_warn("don't know what features to announce in EHLO");
+ msg_warn("don't know what EHLO features to announce to %s",
+ state->namaddr);
vstream_longjmp(state->client, SMTP_ERR_DATA);
}
case SMTP_ERR_DATA:
msg_info("%s: reject: %s from %s: "
- "421 4.3.0 %s Server configuration error",
+ "421 4.3.0 %s Server local data error",
(state->queue_id ? state->queue_id : "NOQUEUE"),
state->where, state->namaddr, var_myhostname);
state->error_mask |= MAIL_ERROR_DATA;
if (vstream_setjmp(state->client) == 0)
- smtpd_chat_reply(state, "421 4.3.0 %s Server configuration error",
+ smtpd_chat_reply(state, "421 4.3.0 %s Server local data error",
var_myhostname);
break;
attr_scan64_test attr_scan0_test dict_pcre_test host_port_test \
dict_cidr_test attr_scan_plain_test htable_test hex_code_test \
myaddrinfo_test format_tv_test ip_match_test name_mask_tests \
- base32_code_test dict_thash_test
+ base32_code_test dict_thash_test surrogate_test
root_tests:
sort dict_thash.tmp | diff -b dict_thash.map -
rm -f dict_thash.tmp
+surrogate_test: dict_open surrogate.ref
+ cp /dev/null surrogate.tmp
+ echo get foo|./dict_open cidr:/xx write >>surrogate.tmp 2>&1
+ echo get foo|./dict_open cidr:/xx read >>surrogate.tmp 2>&1
+ echo get foo|./dict_open pcre:/xx write >>surrogate.tmp 2>&1
+ echo get foo|./dict_open pcre:/xx read >>surrogate.tmp 2>&1
+ echo get foo|./dict_open regexp:/xx write >>surrogate.tmp 2>&1
+ echo get foo|./dict_open regexp:/xx read >>surrogate.tmp 2>&1
+ echo get foo|./dict_open unix:xx write >>surrogate.tmp 2>&1
+ echo get foo|./dict_open unix:xx read >>surrogate.tmp 2>&1
+ echo get foo|./dict_open texthash:/xx write >>surrogate.tmp 2>&1
+ echo get foo|./dict_open texthash:/xx read >>surrogate.tmp 2>&1
+ echo get foo|./dict_open hash:/xx read >>surrogate.tmp 2>&1
+ diff surrogate.ref surrogate.tmp
+ rm -f surrogate.tmp
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
DICT_CIDR *dict_cidr;
VSTREAM *map_fp;
struct stat st;
- VSTRING *line_buffer = vstring_alloc(100);
- VSTRING *why = vstring_alloc(100);
+ VSTRING *line_buffer;
+ VSTRING *why;
DICT_CIDR_ENTRY *rule;
DICT_CIDR_ENTRY *last_rule = 0;
int lineno = 0;
if (fstat(vstream_fileno(map_fp), &st) < 0)
msg_fatal("fstat %s: %m", mapname);
+ /*
+ * No early returns without memory leaks.
+ */
+ line_buffer = vstring_alloc(100);
+ why = vstring_alloc(100);
+
/*
* XXX Eliminate unnecessary queries by setting a flag that says "this
* map matches network addresses only".
{
DICT_DB *dict_db;
struct stat st;
- DB *db;
- char *db_path;
+ DB *db = 0;
+ char *db_path = 0;
int lock_fd = -1;
int dbfd;
* db_open() create a non-existent file for us.
*/
#define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC))
+#define FREE_RETURN(e) do { \
+ DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \
+ if (db_path) myfree(db_path); return (_dict); \
+ } while (0)
if (dict_flags & DICT_FLAG_LOCK) {
if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) {
if (errno != ENOENT)
- return (dict_surrogate(class, path, open_flags, dict_flags,
- "open database %s: %m", db_path));
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
} else {
if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
msg_fatal("shared-lock database %s for open: %m", db_path);
*/
#if DB_VERSION_MAJOR < 2
if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0)
- return (dict_surrogate(class, path, open_flags, dict_flags,
- "open database %s: %m", db_path));
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
dbfd = db->fd(db);
#endif
if (open_flags & O_TRUNC)
db_flags |= DB_TRUNCATE;
if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0)
- return (dict_surrogate(class, path, open_flags, dict_flags,
- "open database %s: %m", db_path));
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
if (db == 0)
msg_panic("db_open null result");
if ((errno = db->fd(db, &dbfd)) != 0)
msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM);
#if DB_VERSION_MAJOR == 5 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0)
if ((errno = db->open(db, 0, db_path, 0, type, db_flags, 0644)) != 0)
- return (dict_surrogate(class, path, open_flags, dict_flags,
- "open database %s: %m", db_path));
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
#elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4)
if ((errno = db->open(db, db_path, 0, type, db_flags, 0644)) != 0)
- return (dict_surrogate(class, path, open_flags, dict_flags,
- "open database %s: %m", db_path));
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
#else
#error "Unsupported Berkeley DB version"
#endif
if (dict_open_hash == 0)
dict_open_init();
if ((dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type)) == 0)
- msg_fatal("unsupported dictionary type: %s", dict_type);
+ return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags,
+ "unsupported dictionary type: %s", dict_type));
if ((dict = dp->open(dict_name, open_flags, dict_flags)) == 0)
- msg_fatal("opening %s:%s %m", dict_type, dict_name);
+ return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags,
+ "cannot open %s:%s: %m", dict_type, dict_name));
if (msg_verbose)
msg_info("%s: %s:%s", myname, dict_type, dict_name);
/* XXX the choice between wait-for-lock or no-wait is hard-coded. */
/* SYNOPSIS
/* #include <dict_surrogate.h>
/*
-/* DICT *dict_surrogate(dict_type, dict_name,
+/* DICT *dict_surrogate(dict_type, dict_name,
/* open_flags, dict_flags,
/* format, ...)
/* const char *dict_type;
/*
/* The global dict_allow_surrogate variable controls the choice
/* between fatal error or reduced functionality. The default
-/* value is zero (fatal error).
+/* value is zero (fatal error). This is appropriate for user
+/* commands; the non-default is more appropriate for daemons.
/*
/* Arguments:
/* .IP dict_type
/* The parameters to the failed dictionary open() request.
/* .IP format, ...
/* The reason why the table could not be opened. This text is
-/* logged immediately, and upon every attempt to access the
+/* logged immediately as an "error" class message, and is logged
+/* as a "warning" class message upon every attempt to access the
/* surrogate dictionary, before returning a "failed" completion
/* status.
/* SEE ALSO
/* System library. */
#include <sys_defs.h>
+#include <errno.h>
/* Utility library. */
dict_free(dict);
}
-int dict_allow_surrogate = 0;
+int dict_allow_surrogate = 0;
/* dict_surrogate - terminate or provide surrogate dictionary */
DICT_SURROGATE *dp;
VSTRING *buf;
void (*log_fn) (const char *, va_list);
+ int saved_errno = errno;
/*
* Log the problem immediately when it is detected. The table may not be
dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
dp->dict.owner.status = DICT_OWNER_TRUSTED;
buf = vstring_alloc(10);
+ errno = saved_errno;
va_start(ap, fmt);
vstring_vsprintf(buf, fmt, ap);
va_end(ap);
struct stat st;
time_t before;
time_t after;
- VSTRING *line_buffer = vstring_alloc(100);
+ VSTRING *line_buffer = 0;
int lineno;
char *key;
char *value;
return (dict_surrogate(DICT_TYPE_THASH, path, open_flags, dict_flags,
"open database %s: %m", path));
}
+ if (line_buffer == 0)
+ line_buffer = vstring_alloc(100);
lineno = 0;
table = htable_create(13);
while (readlline(line_buffer, fp, &lineno)) {
--- /dev/null
+./dict_open: error: cidr:/xx map requires O_RDONLY access mode
+> get foo
+./dict_open: warning: cidr:/xx is unavailable. cidr:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+> get foo
+./dict_open: warning: cidr:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: pcre:/xx map requires O_RDONLY access mode
+> get foo
+./dict_open: warning: pcre:/xx is unavailable. pcre:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+> get foo
+./dict_open: warning: pcre:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: regexp:/xx map requires O_RDONLY access mode
+> get foo
+./dict_open: warning: regexp:/xx is unavailable. regexp:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+> get foo
+./dict_open: warning: regexp:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: unix:xx map requires O_RDONLY access mode
+> get foo
+./dict_open: warning: unix:xx is unavailable. unix:xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: unknown table: unix:xx
+> get foo
+./dict_open: warning: unix:xx is unavailable. unknown table: unix:xx
+foo: error
+./dict_open: error: texthash:/xx map requires O_RDONLY access mode
+> get foo
+./dict_open: warning: texthash:/xx is unavailable. texthash:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open database /xx: No such file or directory
+> get foo
+./dict_open: warning: texthash:/xx is unavailable. open database /xx: No such file or directory
+foo: error
+./dict_open: error: open database /xx.db: No such file or directory
+> get foo
+./dict_open: warning: hash:/xx is unavailable. open database /xx.db: No such file or directory
+foo: error