-TDICT_ENV
-TDICT_HT
-TDICT_LDAP
+-TDICT_DEBUG
-TDICT_MYSQL
-TDICT_NI
-TDICT_NIS
You can specify one or more hosts, domains, addresses or net/masks.
-3 - Making daemon programs more verbose
-=======================================
+2b - Record the SMTP connection with a sniffer
+==============================================
+
+This example uses tcpdump. In order to record a conversation you
+need to specify a large enough buffer or else you will miss some
+or all of the packet payload.
+
+ tcpdump -w /file/name -s 2000 host hostname and port 25
+
+Run this for a while, stop with Ctrl-C when done. To view the data
+use a binary viewer, or use my tcpdumpx utility that is available
+from ftp://ftp.porcupine.org/pub/debugging.
+
+3 - Making Postfix daemon programs more verbose
+===============================================
Append one or more -v options to selected daemon definitions in
/etc/postfix/master.cf and type "postfix reload". This will cause
bug that causes mail delivery problems when "." and "CRLF"
arrive in separate packets. File: html/faq.html.
-20010131
-
- The code that reports a DNS lookup error now includes the
- record type that was being looked up, so that people will
- not misinterpret an MX lookup problem as an A record lookup
- problem. File: dns/dns_lookup.c.
-
20010201
Bugfix: another missing initialization in the mysql client.
Feature: disable mailbox size limits for the local and
virtual delivery agents by setting mailbox_size_limit or
virtual_mailbox_limit to zero.
+
+20010203
+
+ Update: null candidate patch from Patrick Rak. Files:
+ nqmgr/qmgr_entry.c nqmgr/qmgr_job.c nqmgr/qmgr_message.c.
+
+ Cleanup: added one gruesome command to the postlink script
+ for hyperlinking nroff manual page output. Word abbreviation
+ broke some <a href...> </a> instances across line boundaries.
+ sed(1) is an amazing tool. File: mantools/postlink.
+
+20010204
+
+ Laid the ground work for logging of table accesses. This
+ will give more insight into how Postfix uses its lookup
+ tables. User interface comes later. File: util/dict_debug.c.
requests from the queue manager. Each request specifies a
queue file, a sender address, a domain or host name that
is treated as the reason for non-delivery, and recipient
- information. This program expects to be run from the <a href="master.8.html"><b>mas-</b>
- <b>ter</b>(8)</a> process manager.
+ information. This program expects to be run from the <a href="master.8.html"><b>mas-</b></a>
+ <a href="master.8.html"><b>ter</b>(8)</a> process manager.
The error mailer client forces all recipients to bounce,
using the domain or host information as the reason for
Postfix queue manager to deliver mail to local recipients.
Each delivery request specifies a queue file, a sender
address, a domain or host to deliver to, and one or more
- recipients. This program expects to be run from the <a href="master.8.html"><b>mas-</b>
- <b>ter</b>(8)</a> process manager.
+ recipients. This program expects to be run from the <a href="master.8.html"><b>mas-</b></a>
+ <a href="master.8.html"><b>ter</b>(8)</a> process manager.
The <b>local</b> daemon updates queue files and marks recipients
as finished, or it informs the queue manager that delivery
<b>DESCRIPTION</b>
The <b>nqmgr</b> daemon awaits the arrival of incoming mail and
arranges for its delivery via Postfix delivery processes.
- The actual mail routing strategy is delegated to the <a href="trivial-rewrite.8.html"><b>triv-</b>
- <b>ial-rewrite</b>(8)</a> daemon. This program expects to be run
+ The actual mail routing strategy is delegated to the <a href="trivial-rewrite.8.html"><b>triv-</b></a>
+ <a href="trivial-rewrite.8.html"><b>ial-rewrite</b>(8)</a> daemon. This program expects to be run
from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
Mail addressed to the local <b>double-bounce</b> address is
and with group write permission to the <b>maildrop</b> queue
directory.
- The <b>postdrop</b> command is automatically invoked by the <a href="sendmail.1.html"><b>send-</b>
- <b>mail</b>(1)</a> mail posting agent when the <b>maildrop</b> queue direc-
+ The <b>postdrop</b> command is automatically invoked by the <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> mail posting agent when the <b>maildrop</b> queue direc-
tory is not world-writable.
Options:
<b>DESCRIPTION</b>
The <b>qmgr</b> daemon awaits the arrival of incoming mail and
arranges for its delivery via Postfix delivery processes.
- The actual mail routing strategy is delegated to the <a href="trivial-rewrite.8.html"><b>triv-</b>
- <b>ial-rewrite</b>(8)</a> daemon. This program expects to be run
+ The actual mail routing strategy is delegated to the <a href="trivial-rewrite.8.html"><b>triv-</b></a>
+ <a href="trivial-rewrite.8.html"><b>ial-rewrite</b>(8)</a> daemon. This program expects to be run
from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
Mail addressed to the local <b>double-bounce</b> address is
The <b>trivial-rewrite</b> daemon by default only distin-
guishes between local and non-local mail. For finer
- control over mail routing, use the optional <a href="transport.5.html"><b>trans-</b>
- <b>port</b>(5)</a> lookup table.
+ control over mail routing, use the optional <a href="transport.5.html"><b>trans-</b></a>
+ <a href="transport.5.html"><b>port</b>(5)</a> lookup table.
This program expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process
manager.
<dt>Default:
-<dd><b>maps_rbl_domains = rbl.maps.vix.com, dul.maps.vix.com</b>
+<dd><b>maps_rbl_domains = blackholes.mail-abuse.org</b>
<p>
s/[<bB>]*trans[-</bB>]*\n*[ <bB>]*port[</bB>]*(5)/<a href="transport.5.html">&<\/a>/
s/[<bB>]*virtual[</bB>]*(5)/<a href="virtual.5.html">&<\/a>/
s/[<bB>]*virtual[</bB>]*(8)/<a href="virtual.8.html">&<\/a>/
+ s/\(<a href="[^"]*">\)\([<bB>]*[a-z0-9-]*[-</bB>]*\)\(\n *\)\([<bB>]*[a-z0-9-]*[</bB>]*([0-9])\)\(<\/a>\)/\1\2\5\3\1\4\5/
s/RFC *\([0-9]*\)/<a href="http:\/\/www.faqs.org\/rfcs\/rfc\1.html">&<\/a>/
' "$@"
len = res_search((char *) name, C_IN, type, reply->buf, sizeof(reply->buf));
if (len < 0) {
if (why)
- vstring_sprintf(why, "Name service error for %s (%s) while looking up the %s record.",
- name, dns_strerror(h_errno), dns_strtype(type));
+ vstring_sprintf(why, "Name service error for %s: %s",
+ name, dns_strerror(h_errno));
if (msg_verbose)
msg_info("dns_query: %s (%s): %s",
name, dns_strtype(type), dns_strerror(h_errno));
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20010202"
+#define DEF_MAIL_VERSION "Snapshot-20010204"
extern char *var_mail_version;
/* LICENSE
* the queue is not marked as a blocker anymore, with extra handling of
* queues which were declared dead.
*
+ * Note that changing the blocker status also affects the candidate cache.
+ * Most of the cases would be automatically recognized by the current job
+ * change, but we play safe and reset the cache explicitly below.
+ *
* Keeping the transport blocker tag odd is an easy way to make sure the tag
* never matches jobs that are not explicitly marked as blockers.
*/
if (queue->window > queue->busy_refcount && queue->todo.next != 0) {
transport->blocker_tag += 2;
transport->job_current = transport->job_list.next;
+ transport->candidate_cache_current = 0;
}
if (queue->window > queue->busy_refcount || queue->window == 0)
queue->blocker_tag = 0;
* Fetch the result directly from the cache if the cache is still valid.
*
* Note that we cache negative results too, so the cache must be invalidated
- * by resetting the cache time or current job pointer, not the candidate
- * pointer itself.
+ * by resetting the cached current job pointer, not the candidate pointer
+ * itself.
+ *
+ * In case the cache is valid and contains no candidate, we can ignore the
+ * time change, as it affects only which candidate is the best, not if
+ * one exists. However, this feature requires that we no longer relax the
+ * cache resetting rules, depending on the automatic cache timeout.
*/
if (transport->candidate_cache_current == current
- && transport->candidate_cache_time == now)
+ && (transport->candidate_cache_time == now
+ || transport->candidate_cache == 0))
return (transport->candidate_cache);
/*
*/
job->stack_level = 0;
+ /*
+ * Explicitely reset the candidate cache. It's not worth trying to skip
+ * this under some complicated conditions - in most cases the popped job
+ * is the current job so we would have to reset it anyway.
+ */
+ RESET_CANDIDATE_CACHE(transport);
+
/*
* Here we leave the remaining work involving the proper placement on the
* job list to the caller. The most important reason for this is that it
* allows us not to look up where exactly to place the job.
*
- * The caller is also made responsible for invalidating the candidate and
- * current job caches if necessary.
+ * The caller is also made responsible for invalidating the current job
+ * cache if necessary.
*/
#if 0
QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers);
QMGR_LIST_LINK(transport->job_list, some_prev, job, some_next, transport_peers);
- RESET_CANDIDATE_CACHE(transport);
if (transport->job_current == job)
transport->job_current = job->transport_peers.next;
#endif
/*
* Note that even if qmgr_job_obtain() reset the job candidate cache of
* all transports to which we assigned new recipients, this message may
- * have other jobs which we didn't touch at all this time. But as the
- * number of unread recipients affecting the candidate selection might
- * have changed considerably, let's invalidate the caches if it seems it
- * might be of some use. It's not critical though because the cache will
- * expire within one second anyway.
+ * have other jobs which we didn't touch at all this time. But the number
+ * of unread recipients affecting the candidate selection might have
+ * changed considerably, so we must invalidate the caches if it might be
+ * of some use.
*/
for (job = message->job_list.next; job; job = job->message_peers.next)
if (job->selected_entries < job->read_entries
dns_rr_free(mx_names);
if (addr_list == 0) {
smtp_errno = SMTP_RETRY;
- msg_warn("MX hosts for %s have no valid A record", name);
+ msg_warn("no MX host for %s has a valid A record", name);
break;
}
best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
stream_connect.c stream_trigger.c dict_regexp.c mac_expand.c \
clean_env.c watchdog.c spawn_command.c duplex_pipe.c sane_rename.c \
sane_link.c unescape.c timed_read.c timed_write.c dict_tcp.c \
- hex_quote.c dict_alloc.c rand_sleep.c sane_time.c
+ hex_quote.c dict_alloc.c rand_sleep.c sane_time.c dict_debug.c
OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.o \
close_on_exec.o concatenate.o dict.o dict_db.o dict_dbm.o \
dict_env.o dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \
stream_connect.o stream_trigger.o dict_regexp.o mac_expand.o \
clean_env.o watchdog.o spawn_command.o duplex_pipe.o sane_rename.o \
sane_link.o unescape.o timed_read.o timed_write.o dict_tcp.o \
- hex_quote.o dict_alloc.o rand_sleep.o sane_time.o
+ hex_quote.o dict_alloc.o rand_sleep.o sane_time.o dict_debug.o
HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.h \
dict_dbm.h dict_env.h dict_ht.h dict_ldap.h dict_mysql.h \
dict_ni.h dict_nis.h dict_nisplus.h dir_forest.h events.h \
dict_db.o: dict_db.h
dict_dbm.o: dict_dbm.c
dict_dbm.o: sys_defs.h
+dict_debug.o: dict_debug.c
+dict_debug.o: sys_defs.h
+dict_debug.o: msg.h
+dict_debug.o: mymalloc.h
+dict_debug.o: dict.h
+dict_debug.o: vstream.h
+dict_debug.o: vbuf.h
+dict_debug.o: argv.h
dict_env.o: dict_env.c
dict_env.o: sys_defs.h
dict_env.o: mymalloc.h
/* dict_sequence() steps throuh the named dictionary and returns
/* keys and values in some implementation-defined order. The func
/* argument is DICT_SEQ_FUN_FIRST to set the cursor to the first
-/* entry or DICT_SEQ_FUN_NEXT so select the next entry. The result
+/* entry or DICT_SEQ_FUN_NEXT to select the next entry. The result
/* is owned by the underlying dictionary method. Make a copy if the
/* result is to be modified, or if the result is to survive multiple
/* dict_sequence() calls.
extern DICT *dict_alloc(const char *, const char *, int);
extern void dict_free(DICT *);
+extern DICT *dict_debug(DICT *);
+#define DICT_DEBUG(d) ((d)->flags & DICT_FLAG_DEBUG ? dict_debug(d) : (d))
+
#define DICT_FLAG_DUP_WARN (1<<0) /* if file, warn about dups */
#define DICT_FLAG_DUP_IGNORE (1<<1) /* if file, ignore dups */
#define DICT_FLAG_TRY0NULL (1<<2) /* do not append 0 to key/value */
#define DICT_FLAG_LOCK (1<<6) /* lock before access */
#define DICT_FLAG_DUP_REPLACE (1<<7) /* if file, replace dups */
#define DICT_FLAG_SYNC_UPDATE (1<<8) /* if file, sync updates */
+#define DICT_FLAG_DEBUG (1<<9) /* log access */
extern int dict_unknown_allowed;
extern int dict_errno;
dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
dict_db->db = db;
myfree(db_path);
- return (&dict_db->dict);
+ return (DICT_DEBUG(&dict_db->dict));
}
/* dict_hash_open - create association with data base */
dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
dict_dbm->dbm = dbm;
- return (&dict_dbm->dict);
+ return (DICT_DEBUG(&dict_dbm->dict));
}
#endif
--- /dev/null
+/*++
+/* NAME
+/* dict_debug 3
+/* SUMMARY
+/* dictionary manager, logging proxy
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_debug(dict_handle)
+/* DICT *dict_handle;
+/*
+/* DICT *DICT_DEBUG(dict_handle)
+/* DICT *dict_handle;
+/* DESCRIPTION
+/* dict_debug() encapsulates the given dictionary object and returns
+/* a proxy object that logs all access to the encapsulated object.
+/* This is more convenient than having to add logging capability
+/* to each individual dictionary access method.
+/*
+/* DICT_DEBUG() is an unsafe macro that returns the original object if
+/* the object's debugging flag is not set, and that otherwise encapsulates
+/* the object with dict_debug(). This macro simplifies usage by avoiding
+/* clumsy expressions. The macro evaluates its argument multiple times.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* 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 libraries. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <dict.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* the proxy service */
+ DICT *real_dict; /* encapsulated object */
+} DICT_DEBUG;
+
+/* dict_debug_lookup - log lookup operation */
+
+static const char *dict_debug_lookup(DICT *dict, const char *key)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ const char *result;
+
+ result = dict_get(dict_debug->real_dict, key);
+ msg_info("%s:%s lookup: \"%s\" = \"%s\"", dict->type, dict->name, key,
+ result ? result : dict_errno ? "try again" : "not_found");
+ return (result);
+}
+
+/* dict_debug_update - log update operation */
+
+static void dict_debug_update(DICT *dict, const char *key, const char *value)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+
+ msg_info("%s:%s update: \"%s\" = \"%s\"", dict->type, dict->name,
+ key, value);
+ dict_put(dict_debug->real_dict, key, value);
+}
+
+/* dict_debug_delete - log delete operation */
+
+static int dict_debug_delete(DICT *dict, const char *key)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ int result;
+
+ result = dict_del(dict_debug->real_dict, key);
+ msg_info("%s:%s delete: \"%s\" = \"%s\"", dict->type, dict->name, key,
+ result ? "failed" : "success");
+ return (result);
+}
+
+/* dict_debug_sequence - log sequence operation */
+
+static int dict_debug_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ int result;
+
+ result = dict_seq(dict_debug->real_dict, function, key, value);
+ if (result == 0)
+ msg_info("%s:%s sequence: \"%s\" = \"%s\"", dict->type, dict->name,
+ *key, *value);
+ else
+ msg_info("%s:%s sequence: found EOF", dict->type, dict->name);
+ return (result);
+}
+
+/* dict_debug_close - log operation */
+
+static void dict_debug_close(DICT *dict)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+
+ dict_close(dict_debug->real_dict);
+ dict_free(dict);
+}
+
+/* dict_debug - encapsulate dictionary object and install proxies */
+
+DICT *dict_debug(DICT *real_dict)
+{
+ DICT_DEBUG *dict_debug;
+
+ dict_debug = (DICT_DEBUG *) dict_alloc(real_dict->type,
+ real_dict->name, sizeof(*dict_debug));
+ dict_debug->dict.flags = real_dict->flags; /* XXX not synchronized */
+ dict_debug->dict.lookup = dict_debug_lookup;
+ dict_debug->dict.update = dict_debug_update;
+ dict_debug->dict.delete = dict_debug_delete;
+ dict_debug->dict.sequence = dict_debug_sequence;
+ dict_debug->dict.close = dict_debug_close;
+ dict_debug->real_dict = real_dict;
+ return (&dict_debug->dict);
+}
dict->update = dict_env_update;
dict->close = dict_env_close;
dict->flags = dict_flags | DICT_FLAG_FIXED;
- return (dict);
+ return (DICT_DEBUG(dict));
}
/*
* Otherwise, we're all set. Return the new dict_ldap structure.
*/
- return (&dict_ldap->dict);
+ return (DICT_DEBUG(&dict_ldap->dict));
}
#endif
if (dict_mysql->pldb == NULL)
msg_fatal("couldn't intialize pldb!\n");
dict_register(name, (DICT *) dict_mysql);
- return &dict_mysql->dict;
+ return (DICT_DEBUG(&dict_mysql->dict));
}
/* mysqlname_parse - parse mysql configuration file */
d->dict.close = dict_ni_close;
d->dict.flags = dict_flags | DICT_FLAG_FIXED;
- return &d->dict;
+ return (DICT_DEBUG(&d->dict));
}
#endif
dict_nis->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
if (dict_nis_domain == 0)
dict_nis_init();
- return (&dict_nis->dict);
+ return (DICT_DEBUG(&dict_nis->dict));
}
#endif
sizeof(*dict_nisplus));
dict_nisplus->dict.close = dict_nisplus_close;
dict_nisplus->dict.flags = dict_flags | DICT_FLAG_FIXED;
- return (&dict_nisplus->dict);
+ return (DICT_DEBUG(&dict_nisplus->dict));
}
vstring_free(line_buffer);
vstream_fclose(map_fp);
- return (&dict_pcre->dict);
+ return (DICT_DEBUG(&dict_pcre->dict));
}
#endif /* HAS_PCRE */
line_buffer = vstring_alloc(100);
- dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, map,
- sizeof(*dict_regexp));
+ dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, map,
+ sizeof(*dict_regexp));
dict_regexp->dict.lookup = dict_regexp_lookup;
dict_regexp->dict.close = dict_regexp_close;
dict_regexp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
vstring_free(line_buffer);
vstream_fclose(map_fp);
- return (&dict_regexp->dict);
+ return (DICT_DEBUG(&dict_regexp->dict));
}
#endif
dict_tcp->dict.lookup = dict_tcp_lookup;
dict_tcp->dict.close = dict_tcp_close;
dict_tcp->dict.flags = dict_flags | DICT_FLAG_FIXED;
- return (&dict_tcp->dict);
+ return (DICT_DEBUG(&dict_tcp->dict));
}
dict_unix->dict.lookup = lp->lookup;
dict_unix->dict.close = dict_unix_close;
dict_unix->dict.flags = dict_flags | DICT_FLAG_FIXED;
- return (&dict_unix->dict);
+ return (DICT_DEBUG(&dict_unix->dict));
}
/* unsigned variation;
/* DESCRIPTION
/* rand_sleep() blocks the current process for an amount of time
-/* pseudo-randomly chosen from the interval (delay += variation/2).
+/* pseudo-randomly chosen from the interval (delay +- variation/2).
/*
/* Arguments:
/* .IP delay