Code cleanup: in order to make postsuper -d more usable,
the showq command was extended to safely list the possibly
world-writable maildrop directory. File: showq/showq.c.
+
+20010505
+
+ RFC 2821 feature: an SMTP server must accept a recipient
+ address of "postmaster" without domain name. File:
+ smtpd/smtpd_check.c.
+
+ RFC 2821 recommendation: reply with 503 to commands sent
+ after 554 greeting. File: smtpd/smtpd.c.
+
+ RFC 2821 recommendation: if VRFY is enabled, list it in
+ the EHLO response.
+
+20010507
+
+ Bugfix: with soft_bounce=yes, the SMTP server would log
+ 5xx replies even though it would send 4xx replies to the
+ client (Phil Howard, ipal.net). File: smtpd/smtpd_check.c.
+
+20010515
+
+ Compatibility: Microsoft sends "AUTH=MBS_BASIC LOGIN".
+ Updated the parsing code in smtp/smtp_proto.c. Problem
+ reported by Ralf Tessmann, Godot GmbH.
+
+20010520
+
+ Standard: deleted the "via" portion from Received: headers
+ generated by Postfix bounce or other notification processes.
+ File: global/post_mail.c.
+
+ Robustness: eliminated stack-based recursion from the RFC
+ 822 address parser. File: global/tok822_parse.c.
+
+ Standard: annotated the source code with comments based on
+ RFC 2821 and 2822. Not all the changes make sense.
+
+ Cleanup: moved ownership of the debug_peer parameters from
+ the applications to the library, so that a Postfix shared
+ library does not suffer from undefined references. Files:
+ smtp/smtp.c, lmtp/lmtp.c, smtpd/smtpd.c, global/mail_params.c.
+ LaMont Jones, for Debian.
#inet_interfaces = $myhostname, localhost
# The mydestination parameter specifies the list of domains that this
-# machine considers itself the final destination for. That does not
-# include domains that are hosted on this machine. Those domains are
+# machine considers itself the final destination for. That includes
+# Sendmail-style virtual domains hosted on this machine.
+#
+# Do not include Postfix-style virtual domains - those domains are
# specified elsewhere (see sample-virtual.cf, and sample-transport.cf).
#
# The default is $myhostname + localhost.$mydomain. On a mail domain
max_use = 100
# The mydestination parameter specifies the list of domains that this
-# machine considers itself the final destination for.
+# machine considers itself the final destination for. That includes
+# Sendmail-style virtual domains hosted on this machine.
+#
+# Do not include Postfix-style virtual domains - those domains are
+# specified elsewhere (see sample-virtual.cf, and sample-transport.cf).
#
# The default is $myhostname + localhost.$mydomain. On a mail domain
# gateway, you should also include $mydomain. Do not specify the
Some people read the RFCs such that one IP address can have multiple
PTR records, but that makes PTR records even less useful than they
already are. And in any case, having multiple names per IP address
-would only worsen the problem of finding out the "official name"
-of a machine's IP address.
+only worsens the problem of finding out the SMTP client hostname.
+
+<hr>
<a name="open_relay"><h3>Help! Postfix is an open relay</h3>
daemon.
<b>newaliases</b>
- Initialize the alias database. If no alias database
- type is specified, the program uses the type speci-
- fied in the <b>database</b><i>_</i><b>type</b> configuration parameter;
- if no input file is specified, the program pro-
- cesses the file(s) specified with the
- <b>alias</b><i>_</i><b>database</b> configuration parameter. This mode
- of operation is implemented by running the <b>postal-</b>
- <b>ias</b>(1) command.
+ Initialize the alias database. If no input file is
+ specified (with the <b>-oA</b> option, see below), the
+ program processes the file(s) specified with the
+ <b>alias</b><i>_</i><b>database</b> configuration parameter. If no
+ alias database type is specified, the program uses
+ the type specified with the <b>database</b><i>_</i><b>type</b> configu-
+ ration parameter. This mode of operation is imple-
+ mented by running the <a href="postalias.1.html"><b>postalias</b>(1)</a> command.
Note: it may take a minute or so before an alias
database update becomes visible. Use the <b>postfix</b>
the reason for failure is shown. This mode of operation is implemented
by connecting to the \fBshowq\fR(8) daemon.
.IP \fBnewaliases\fR
-Initialize the alias database. If no alias database type is
-specified, the program uses the type specified in the
-\fBdatabase_type\fR configuration parameter; if no input file
-is specified, the program processes the file(s) specified with the
-\fBalias_database\fR configuration parameter. This mode of operation
-is implemented by running the \fBpostalias\fR(1) command.
+Initialize the alias database. If no input file is specified (with
+the \fB-oA\fR option, see below), the program processes the file(s)
+specified with the \fBalias_database\fR configuration parameter.
+If no alias database type is specified, the program uses the type
+specified with the \fBdatabase_type\fR configuration parameter.
+This mode of operation is implemented by running the \fBpostalias\fR(1)
+command.
.sp
Note: it may take a minute or so before an alias database update
becomes visible. Use the \fBpostfix reload\fR command to eliminate
postmaster = flush ? var_2bounce_rcpt : var_delay_rcpt;
if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
postmaster,
- NULL_CLEANUP_FLAGS,
- "BOUNCE")) != 0) {
+ NULL_CLEANUP_FLAGS)) != 0) {
/*
* Double bounce to Postmaster. This is the last opportunity
*/
else {
if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient,
- NULL_CLEANUP_FLAGS,
- "BOUNCE")) != 0) {
+ NULL_CLEANUP_FLAGS)) != 0) {
/*
* Send the bounce message header, some boilerplate text that
postmaster = flush ? var_bounce_rcpt : var_delay_rcpt;
if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(),
postmaster,
- NULL_CLEANUP_FLAGS,
- "BOUNCE")) != 0) {
+ NULL_CLEANUP_FLAGS)) != 0) {
if (bounce_header(bounce, bounce_info, postmaster) == 0
&& bounce_diagnostic_log(bounce, bounce_info) == 0
&& bounce_header_dsn(bounce, bounce_info) == 0
* we should do with this header: delete, count, rewrite. Note that we
* should examine headers even when they will be deleted from the output,
* because the addresses in those headers might be needed elsewhere.
+ *
+ * XXX 2821: Return-path breakage.
+ *
+ * RFC 821 specifies: When the receiver-SMTP makes the "final delivery" of a
+ * message it inserts at the beginning of the mail data a return path
+ * line. The return path line preserves the information in the
+ * <reverse-path> from the MAIL command. Here, final delivery means the
+ * message leaves the SMTP world. Normally, this would mean it has been
+ * delivered to the destination user, but in some cases it may be further
+ * processed and transmitted by another mail system.
+ *
+ * And that is what Postfix implements. Delivery agents prepend
+ * Return-Path:. In order to avoid cluttering up the message with
+ * possibly inconsistent Return-Path: information (the sender can change
+ * as the result of mail forwarding or mailing list delivery), Postfix
+ * removes any existing Return-Path: headers.
+ *
+ * RFC 2821 Section 4.4 specifies: A message-originating SMTP system
+ * SHOULD NOT send a message that already contains a Return-path header.
+ * SMTP servers performing a relay function MUST NOT inspect the message
+ * data, and especially not to the extent needed to determine if
+ * Return-path headers are present. SMTP servers making final delivery
+ * MAY remove Return-path headers before adding their own.
*/
else {
state->headers_seen |= (1 << hdr_opts->type);
/*
* Add a missing (Resent-)Message-Id: header. The message ID gives the
* time in GMT units, plus the local queue ID.
+ *
+ * XXX Message-Id is not a required message header (RFC 822 and RFC 2822).
+ *
+ * XXX It is the queue ID non-inode bits that prevent messages from getting
+ * the same Message-Id within the same second.
*/
if ((state->headers_seen & (1 << (state->resent[0] ?
HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) {
CLEANUP_OUT_BUF(state, REC_TYPE_NORM, state->temp2);
}
+ /*
+ * XXX 2821: Appendix B: The return address in the MAIL command SHOULD,
+ * if possible, be derived from the system's identity for the submitting
+ * (local) user, and the "From:" header field otherwise. If there is a
+ * system identity available, it SHOULD also be copied to the Sender
+ * header field if it is different from the address in the From header
+ * field. (Any Sender field that was already there SHOULD be removed.)
+ * Similar wording appears in RFC 2822 section 3.6.2.
+ *
+ * Postfix presently does not insert a Sender: header if envelope and From:
+ * address differ. Older Postfix versions assumed that the envelope
+ * sender address specifies the system identity and inserted Sender:
+ * whenever envelope and From: differed. This was wrong with relayed
+ * mail, and was often not even desirable with original submissions.
+ *
+ * XXX 2822 Section 3.6.2, as well as RFC 822 Section 4.1: FROM headers can
+ * contain multiple addresses. If this is the case, then a Sender: header
+ * must be provided with a single address.
+ *
+ * Postfix does not count the number of addresses in a From: header
+ * (although doing so is trivial, once the address is parsed).
+ */
+
/*
* Add a missing destination header.
*/
mail_scan_dir.c mail_stream.c mail_task.c mail_trigger.c maps.c \
mark_corrupt.c mkmap_db.c mkmap_dbm.c mkmap_open.c mynetworks.c \
mypwd.c namadr_list.c off_cvt.c opened.c own_inet_addr.c \
- peer_name.c pipe_command.c post_mail.c quote_821_local.c \
+ pipe_command.c post_mail.c quote_821_local.c \
quote_822_local.c rec_streamlf.c rec_type.c recipient_list.c \
record.c remove.c resolve_clnt.c resolve_local.c rewrite_clnt.c \
sent.c smtp_stream.c split_addr.c string_list.c sys_exits.c \
mail_scan_dir.o mail_stream.o mail_task.o mail_trigger.o maps.o \
mark_corrupt.o mkmap_db.o mkmap_dbm.o mkmap_open.o mynetworks.o \
mypwd.o namadr_list.o off_cvt.o opened.o own_inet_addr.o \
- peer_name.o pipe_command.o post_mail.o quote_821_local.o \
+ pipe_command.o post_mail.o quote_821_local.o \
quote_822_local.o rec_streamlf.o rec_type.o recipient_list.o \
record.o remove.o resolve_clnt.o resolve_local.o rewrite_clnt.o \
sent.o smtp_stream.o split_addr.o string_list.o sys_exits.o \
mail_proto.h mail_queue.h mail_run.h mail_scan_dir.h mail_stream.h \
mail_task.h mail_version.h maps.h mark_corrupt.h mkmap.h \
mynetworks.h mypwd.h namadr_list.h off_cvt.h opened.h \
- own_inet_addr.h peer_name.h pipe_command.h post_mail.h \
+ own_inet_addr.h pipe_command.h post_mail.h \
quote_821_local.h quote_822_local.h rec_streamlf.h rec_type.h \
recipient_list.h record.h resolve_clnt.h resolve_local.h \
rewrite_clnt.h sent.h smtp_stream.h split_addr.h string_list.h \
LIB = libglobal.a
TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \
mail_addr_map mail_date maps mynetworks mypwd namadr_list \
- off_cvt peer_name quote_822_local rec2stream recdump resolve_clnt \
+ off_cvt quote_822_local rec2stream recdump resolve_clnt \
resolve_local rewrite_clnt stream2rec string_list tok822_parse \
quote_821_local mail_conf_time
const char *cp;
int c;
+ /*
+ * XXX RFC 2822 Section 4.5.2, Obsolete header fields: whitespace may
+ * appear between header label and ":" (see: RFC 822, Section 3.4.2.).
+ *
+ * The code below allows no such whitespace. This has never been a problem,
+ * and therefore we're not inclined to add code for it.
+ */
for (cp = str; (c = *(unsigned char *) cp) != 0; cp++) {
if (c == ':')
return (cp > str);
/*
/* char *var_import_environ;
/* char *var_export_environ;
+/* char *var_debug_peer_list;
+/* int var_debug_peer_level;
/*
/* void mail_params_init()
/* DESCRIPTION
char *var_import_environ;
char *var_export_environ;
+char *var_debug_peer_list;
+int var_debug_peer_level;
/* check_myhostname - lookup hostname and validate */
VAR_IMPORT_ENVIRON, DEF_IMPORT_ENVIRON, &var_import_environ, 0, 0,
VAR_DEF_TRANSPORT, DEF_DEF_TRANSPORT, &var_def_transport, 0, 0,
VAR_MYNETWORKS_STYLE, DEF_MYNETWORKS_STYLE, &var_mynetworks_style, 1, 0,
+ VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0,
0,
};
static CONFIG_STR_FN_TABLE function_str_defaults_2[] = {
VAR_HASH_QUEUE_DEPTH, DEF_HASH_QUEUE_DEPTH, &var_hash_queue_depth, 1, 0,
VAR_FORK_TRIES, DEF_FORK_TRIES, &var_fork_tries, 1, 0,
VAR_FLOCK_TRIES, DEF_FLOCK_TRIES, &var_flock_tries, 1, 0,
+ VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0,
0,
};
static CONFIG_TIME_TABLE time_defaults[] = {
/*
* What to put in the To: header when no recipients were disclosed.
+ *
+ * XXX 2822: When no recipient headers remain, a system should insert a Bcc:
+ * header without additional information. That is not so great given that
+ * MTAs routinely strip Bcc: headers from message headers.
*/
#define VAR_RCPT_WITHELD "undisclosed_recipients_header"
#define DEF_RCPT_WITHELD "To: undisclosed-recipients:;"
* determines how many recipient addresses the SMTP client sends along with
* each message. Unfortunately, some mailers misbehave and disconnect (smap)
* when given more recipients than they are willing to handle.
+ *
+ * XXX 2821: A mail system is supposed to use EHLO instead of HELO, and to fall
+ * back to HELO if EHLO is not supported.
*/
#define VAR_BESTMX_TRANSP "best_mx_transport"
#define DEF_BESTMX_TRANSP ""
extern bool var_skip_quit_resp;
#define VAR_SMTP_ALWAYS_EHLO "smtp_always_send_ehlo"
+#ifdef RFC821_SYNTAX
#define DEF_SMTP_ALWAYS_EHLO 0
+#else
+#define DEF_SMTP_ALWAYS_EHLO 1
+#endif
extern bool var_smtp_always_ehlo;
#define VAR_SMTP_NEVER_EHLO "smtp_never_send_ehlo"
file_id = get_file_id(fd);
GETTIMEOFDAY(&tv);
+ /*
+ * XXX Some systems seem to have clocks that correlate with process
+ * scheduling or something. Unfortunately, we cannot add random
+ * quantities to the time, because the non-inode part of a queue ID must
+ * not repeat within the same second. The queue ID is the sole thing that
+ * prevents multiple messages from getting the same Message-ID value.
+ */
for (count = 0;; count++) {
vstring_sprintf(id_buf, "%05X%s", (int) tv.tv_usec, file_id);
mail_queue_path(path_buf, queue_name, STR(id_buf));
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20010502"
+#define DEF_MAIL_VERSION "Snapshot-20010520"
extern char *var_mail_version;
/* LICENSE
/* SYNOPSIS
/* #include <post_mail.h>
/*
-/* VSTREAM *post_mail_fopen(sender, recipient, flags, via)
+/* VSTREAM *post_mail_fopen(sender, recipient, flags)
/* const char *sender;
/* const char *recipient;
/* int flags;
-/* const char *via;
/*
-/* VSTREAM *post_mail_fopen_nowait(sender, recipient, flags, via)
+/* VSTREAM *post_mail_fopen_nowait(sender, recipient, flags)
/* const char *sender;
/* const char *recipient;
/* int flags;
-/* const char *via;
/*
/* int post_mail_fprintf(stream, format, ...)
/* VSTREAM *stream;
/* post_mail_init - initial negotiations */
static void post_mail_init(VSTREAM *stream, const char *sender,
- const char *recipient, int flags, const char *via)
+ const char *recipient, int flags)
{
VSTRING *id = vstring_alloc(100);
long now = time((time_t *) 0);
* Do the Received: and Date: header lines. This allows us to shave a few
* cycles by using the expensive date conversion result for both.
*/
- post_mail_fprintf(stream, "Received: by %s (%s) via %s",
- var_myhostname, var_mail_name, via);
+ post_mail_fprintf(stream, "Received: by %s (%s)",
+ var_myhostname, var_mail_name);
post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date);
post_mail_fprintf(stream, "Date: %s", date);
vstring_free(id);
/* post_mail_fopen - prepare for posting a message */
-VSTREAM *post_mail_fopen(const char *sender, const char *recipient,
- int flags, const char *via)
+VSTREAM *post_mail_fopen(const char *sender, const char *recipient, int flags)
{
VSTREAM *stream;
stream = mail_connect_wait(MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP);
- post_mail_init(stream, sender, recipient, flags, via);
+ post_mail_init(stream, sender, recipient, flags);
return (stream);
}
/* post_mail_fopen_nowait - prepare for posting a message */
VSTREAM *post_mail_fopen_nowait(const char *sender, const char *recipient,
- int flags, const char *via)
+ int flags)
{
VSTREAM *stream;
if ((stream = mail_connect(MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP,
BLOCKING)) != 0)
- post_mail_init(stream, sender, recipient, flags, via);
+ post_mail_init(stream, sender, recipient, flags);
return (stream);
}
/*
* External interface.
*/
-extern VSTREAM *post_mail_fopen(const char *, const char *, int, const char *);
-extern VSTREAM *post_mail_fopen_nowait(const char *, const char *,
- int, const char *);
+extern VSTREAM *post_mail_fopen(const char *, const char *, int);
+extern VSTREAM *post_mail_fopen_nowait(const char *, const char *, int);
extern int PRINTFLIKE(2, 3) post_mail_fprintf(VSTREAM *, const char *,...);
extern int post_mail_fputs(VSTREAM *, const char *);
extern int post_mail_buffer(VSTREAM *, const char *, int);
* Allow for partial long lines (we will read the remainder later) and
* allow for lines ending in bare LF. The idea is to be liberal in what
* we accept, strict in what we send.
+ *
+ * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize
+ * bare LF as record terminator.
*/
smtp_timeout_reset(stream);
last_char = (bound == 0 ? vstring_get(vp, stream) :
#define TOK822_DOMLIT 259 /* stuff between [] not nesting */
#define TOK822_ADDR 260 /* actually a token group */
#define TOK822_STARTGRP 261 /* start of named group */
-#define TOK822_COMMENT_TEXT 262 /* comment text */
-#define TOK822_MAXTOK 262
+#define TOK822_MAXTOK 261
/*
* tok822_node.c
TOK822 *tp;
#define CONTAINER_TOKEN(x) \
- ((x) == TOK822_ADDR || (x) == TOK822_COMMENT || (x) == TOK822_STARTGRP)
+ ((x) == TOK822_ADDR || (x) == TOK822_STARTGRP)
tp = (TOK822 *) mymalloc(sizeof(*tp));
tp->type = type;
tok822_internalize(vp, tp->head, TOK822_STR_NONE);
break;
case TOK822_COMMENT:
- VSTRING_ADDCH(vp, '(');
- tok822_internalize(vp, tp->head, TOK822_STR_NONE);
- VSTRING_ADDCH(vp, ')');
- break;
case TOK822_ATOM:
- case TOK822_COMMENT_TEXT:
case TOK822_QSTRING:
vstring_strcat(vp, vstring_str(tp->vstr));
break;
tok822_externalize(vp, tp->head, TOK822_STR_NONE);
break;
case TOK822_ATOM:
- vstring_strcat(vp, vstring_str(tp->vstr));
- break;
case TOK822_COMMENT:
- VSTRING_ADDCH(vp, '(');
- tok822_externalize(vp, tp->head, TOK822_STR_NONE);
- VSTRING_ADDCH(vp, ')');
- break;
- case TOK822_COMMENT_TEXT:
- tok822_copy_quoted(vp, vstring_str(tp->vstr), "()\\");
+ vstring_strcat(vp, vstring_str(tp->vstr));
break;
case TOK822_QSTRING:
VSTRING_ADDCH(vp, '"');
TOK822 *tp;
int ch;
+ /*
+ * XXX 2822 new feature: Section 4.1 allows "." to appear in a phrase (to
+ * allow for forms such as: Johnny B. Goode <johhny@domain.org>. I cannot
+ * handle that at the tokenizer level - it is not context sensitive. And
+ * to fix this at the parser level requires radical changes to preserve
+ * white space as part of the token stream. Thanks a lot, people.
+ */
while ((ch = *(unsigned char *) str++) != 0) {
if (ISSPACE(ch))
continue;
static const char *tok822_comment(TOK822 *tp, const char *str)
{
- TOK822 *tc = 0;
+ int level = 1;
int ch;
-#define COMMENT_TEXT_TOKEN(t) ((t) && (t)->type == TOK822_COMMENT_TEXT)
-
-#define APPEND_NEW_TOKEN(tp, type, strval) \
- tok822_sub_append(tp, tok822_alloc(type, strval))
+ /*
+ * XXX We cheat by storing comments in their external form. Otherwise it
+ * would be a royal pain to preserve \ before (. That would require a
+ * recursive parser, which could consume unreasonable amounts of memory.
+ */
+ VSTRING_ADDCH(tp->vstr, '(');
while ((ch = *(unsigned char *) str) != 0) {
+ VSTRING_ADDCH(tp->vstr, ch);
str++;
if (ch == '(') { /* comments can nest! */
- if (COMMENT_TEXT_TOKEN(tc))
- VSTRING_TERMINATE(tc->vstr);
- tc = APPEND_NEW_TOKEN(tp, TOK822_COMMENT, (char *) 0);
- str = tok822_comment(tc, str);
+ level++;
} else if (ch == ')') {
- break;
- } else {
- if (ch == '\\') {
- if ((ch = *(unsigned char *) str) == 0)
- break;
- str++;
- }
- if (!COMMENT_TEXT_TOKEN(tc))
- tc = APPEND_NEW_TOKEN(tp, TOK822_COMMENT_TEXT, (char *) 0);
- VSTRING_ADDCH(tc->vstr, ch);
+ if (--level == 0)
+ break;
+ } else if (ch == '\\') {
+ if ((ch = *(unsigned char *) str) == 0)
+ break;
+ VSTRING_ADDCH(tp->vstr, ch);
+ str++;
}
}
- if (COMMENT_TEXT_TOKEN(tc))
- VSTRING_TERMINATE(tc->vstr);
+ VSTRING_TERMINATE(tp->vstr);
return (str);
}
} else if (tp->type == TOK822_ADDR) {
vstream_printf("%*s %s\n", indent, "", "address");
tok822_print(tp->head, indent + 2);
- } else if (tp->type == TOK822_COMMENT) {
- vstream_printf("%*s %s\n", indent, "", "comment");
- tok822_print(tp->head, indent + 2);
} else if (tp->type == TOK822_STARTGRP) {
vstream_printf("%*s %s\n", indent, "", "group \":\"");
} else {
vstream_printf("%*s %s \"%s\"\n", indent, "",
- tp->type == TOK822_COMMENT_TEXT ? "text" :
+ tp->type == TOK822_COMMENT ? "comment" :
tp->type == TOK822_ATOM ? "atom" :
tp->type == TOK822_QSTRING ? "quoted string" :
tp->type == TOK822_DOMLIT ? "domain literal" :
OP ","
address
atom "venema"
- comment
- text ""wietse "
+ comment "("wietse )"
OP ","
address
quoted string ")"
OP "."
atom "org"
OP ")"
- comment
- text " "
- comment
- text ""wietse "
- text " venema""
+ comment "( ("wietse ) venema")"
Internalized:
wietse venema@porcupine.org) ( ("wietse ) venema")
atom "wietse"
OP "@"
atom "foo"
- comment
- text "wietse
- venema"
+ comment "(wietse
+ venema)"
Internalized:
wietse@foo (wietse
int var_lmtp_data1_tmout;
int var_lmtp_data2_tmout;
int var_lmtp_quit_tmout;
-char *var_debug_peer_list;
-int var_debug_peer_level;
int var_lmtp_cache_conn;
int var_lmtp_skip_quit_resp;
char *var_notify_classes;
int main(int argc, char **argv)
{
static CONFIG_STR_TABLE str_table[] = {
- VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0,
VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_lmtp_sasl_passwd, 0, 0,
};
static CONFIG_INT_TABLE int_table[] = {
VAR_LMTP_TCP_PORT, DEF_LMTP_TCP_PORT, &var_lmtp_tcp_port, 0, 0,
- VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0,
0,
};
static CONFIG_TIME_TABLE time_table[] = {
notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
var_error_rcpt,
- NULL_CLEANUP_FLAGS, "NOTICE");
+ NULL_CLEANUP_FLAGS);
if (notice == 0) {
msg_warn("postmaster notify: %m");
return;
postsuper.o: ../../include/safe.h
postsuper.o: ../../include/set_ugid.h
postsuper.o: ../../include/argv.h
+postsuper.o: ../../include/vstring_vstream.h
postsuper.o: ../../include/mail_task.h
postsuper.o: ../../include/mail_conf.h
postsuper.o: ../../include/mail_params.h
postsuper.o: ../../include/mail_queue.h
+postsuper.o: ../../include/mail_open_ok.h
MAIL_QUEUE_ACTIVE, /* foolproof but adequate */
0,
};
+ const char *log_queue_names[] = {
+ MAIL_QUEUE_BOUNCE,
+ MAIL_QUEUE_DEFER,
+ 0,
+ };
struct stat st;
- const char **cpp;
- const char *path;
+ const char **msg_qpp;
+ const char **log_qpp;
+ const char *msg_path;
+ VSTRING *log_path_buf = vstring_alloc(100);
int found = 0;
/*
- * Do not delete defer or bounce logfiles, because we could lose a race
- * and delete a defer/bounce logfile from a message that reuses the queue
- * ID.
+ * Delete defer or bounce logfiles before deleting the corresponding
+ * message file, and only if the message file exists. This minimizes but
+ * does not eliminate a race condition with queue ID reuse which results
+ * in deleting the wrong files.
*/
- for (cpp = msg_queue_names; *cpp != 0; cpp++) {
- if (!mail_open_ok(*cpp, queue_id, &st, &path)) {
+ for (msg_qpp = msg_queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (!mail_open_ok(*msg_qpp, queue_id, &st, &msg_path))
continue;
- } else if (unlink(path) == 0) {
+ for (log_qpp = log_queue_names; *log_qpp != 0; log_qpp++)
+ (void) mail_queue_path(log_path_buf, *log_qpp, queue_id);
+ if (unlink(STR(log_path_buf)) < 0 && errno != ENOENT)
+ msg_warn("remove file %s: %m", STR(log_path_buf));
+ if (unlink(msg_path) == 0) {
found = 1;
- msg_info("removed file %s", path);
+ msg_info("removed file %s", msg_path);
break;
- } else if (errno != ENOENT) {
- msg_warn("remove file %s: %m", path);
+ }
+ if (errno != ENOENT) {
+ msg_warn("remove file %s: %m", msg_path);
} else if (msg_verbose) {
- msg_info("remove file %s: %m", path);
+ msg_info("remove file %s: %m", msg_path);
}
}
+ vstring_free(log_path_buf);
return (found);
}
/* the reason for failure is shown. This mode of operation is implemented
/* by connecting to the \fBshowq\fR(8) daemon.
/* .IP \fBnewaliases\fR
-/* Initialize the alias database. If no alias database type is
-/* specified, the program uses the type specified in the
-/* \fBdatabase_type\fR configuration parameter; if no input file
-/* is specified, the program processes the file(s) specified with the
-/* \fBalias_database\fR configuration parameter. This mode of operation
-/* is implemented by running the \fBpostalias\fR(1) command.
+/* Initialize the alias database. If no input file is specified (with
+/* the \fB-oA\fR option, see below), the program processes the file(s)
+/* specified with the \fBalias_database\fR configuration parameter.
+/* If no alias database type is specified, the program uses the type
+/* specified with the \fBdatabase_type\fR configuration parameter.
+/* This mode of operation is implemented by running the \fBpostalias\fR(1)
+/* command.
/* .sp
/* Note: it may take a minute or so before an alias database update
/* becomes visible. Use the \fBpostfix reload\fR command to eliminate
int var_smtp_data2_tmout;
int var_smtp_quit_tmout;
char *var_inet_interfaces;
-char *var_debug_peer_list;
-int var_debug_peer_level;
char *var_notify_classes;
int var_smtp_skip_4xx_greeting;
int var_smtp_skip_5xx_greeting;
int main(int argc, char **argv)
{
static CONFIG_STR_TABLE str_table[] = {
- VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0,
VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
VAR_FALLBACK_RELAY, DEF_FALLBACK_RELAY, &var_fallback_relay, 0, 0,
VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0,
0,
};
static CONFIG_INT_TABLE int_table[] = {
- VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0,
0,
};
static CONFIG_BOOL_TABLE bool_table[] = {
/*
* As long as we are able to look up any host address, we ignore problems
* with DNS lookups.
+ *
+ * XXX 2821: update smtp_errno (0->FAIL upon unrecoverable lookup error,
+ * any->RETRY upon temporary lookup error) so that we can correctly
+ * handle the case of no resolvable MX host. Currently this is always
+ * treated as a soft error. RFC 2821 wants a more precise response.
*/
for (rr = mx_names; rr; rr = rr->next) {
if (rr->type != T_MX)
* as we're looking up all the hosts, it would be better to look up the
* least preferred host first, so that DNS lookup error messages make
* more sense.
+ *
+ * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX
+ * hosts, whereas multiple A records per hostname must be used in the
+ * order as received. They make the bogus assumption that a hostname with
+ * multiple A records corresponds to one machine with multiple network
+ * interfaces.
+ *
+ * XXX 2821: Postfix recognizes the local machine by looking for its own IP
+ * address in the list of mail exchangers. RFC 2821 says one has to look
+ * at the mail exchanger hostname as well, making the bogus assumption
+ * that an IP address is listed only under one hostname. However, looking
+ * at hostnames provides a partial solution for MX hosts behind a NAT
+ * gateway.
*/
switch (dns_lookup(name, T_MX, 0, &mx_names, (VSTRING *) 0, why)) {
default:
notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
var_error_rcpt,
- NULL_CLEANUP_FLAGS, "NOTICE");
+ NULL_CLEANUP_FLAGS);
if (notice == 0) {
msg_warn("postmaster notify: %m");
return;
* overflow detection, ignore the message size limit advertised by the
* SMTP server. Otherwise, we might do the wrong thing when the server
* advertises a really huge message size limit.
+ *
+ * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...", because
+ * MicroSoft implemented AUTH based on an old draft.
*/
lines = resp->str;
while ((words = mystrtok(&lines, "\n")) != 0) {
- if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) {
+ if (mystrtok(&words, "- =") && (word = mystrtok(&words, " \t")) != 0) {
if (strcasecmp(word, "8BITMIME") == 0)
state->features |= SMTP_FEATURE_8BITMIME;
else if (strcasecmp(word, "PIPELINING") == 0)
#ifdef USE_SASL_AUTH
else if (var_smtp_sasl_enable && strcasecmp(word, "AUTH") == 0)
smtp_sasl_helo_auth(state, words);
- else if (var_smtp_sasl_enable && strncasecmp(word, "AUTH=", 5) == 0)
- smtp_sasl_helo_auth(state, word + 5);
#endif
else if (strcasecmp(word, var_myhostname) == 0) {
msg_warn("host %s replied to HELO/EHLO with my own hostname %s",
* rejected, ignore RCPT TO responses: all recipients are
* dead already. When all recipients are rejected the
* receiver may apply a course correction.
+ *
+ * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply
+ * must be treated as if the server replied with 452.
*/
case SMTP_STATE_RCPT:
if (!mail_from_rejected) {
+#ifndef RFC821_SYNTAX
+ if (resp->code == 552)
+ resp->code = 452;
+#endif
if (resp->code / 100 == 2) {
++nrcpt;
} else {
int var_smtpd_hard_erlim;
int var_queue_minfree; /* XXX use off_t */
char *var_smtpd_banner;
-char *var_debug_peer_list;
-int var_debug_peer_level;
char *var_notify_classes;
char *var_client_checks;
char *var_helo_checks;
{
char *err;
+ /*
+ * XXX 2821 new feature: Section 4.1.4 specifies that a server must clear
+ * all buffers and reset the state exactly as if a RSET command had been
+ * issued.
+ */
if (argc < 2) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: EHLO hostname");
}
if (state->helo_name != 0)
helo_reset(state);
-#if 0
+#ifndef RFC821_SYNTAX
mail_reset(state);
rcpt_reset(state);
#endif
(unsigned long) var_message_limit); /* XXX */
else
smtpd_chat_reply(state, "250-SIZE");
+ if (var_disable_vrfy_cmd == 0)
+ smtpd_chat_reply(state, "250-VRFY");
smtpd_chat_reply(state, "250-ETRN");
#ifdef USE_SASL_AUTH
if (var_smtpd_sasl_enable) {
{
char *postdrop_command;
+ /*
+ * XXX 2821: An SMTP server is not allowed to "clean up" mail except in
+ * the case of original submissions. Presently, Postfix always runs all
+ * mail through the cleanup server.
+ *
+ * We could approximate the RFC as follows: Postfix rewrites mail if it
+ * comes from a source that we are willing to relay for. This way, we
+ * avoid rewriting most mail that comes from elsewhere. However, that
+ * requires moving functionality away from the cleanup daemon elsewhere,
+ * such as virtual address expansion, and header/body pattern matching.
+ */
+
/*
* If running from the master or from inetd, connect to the cleanup
* service.
/*
* Sanity checks. XXX Ignore bad SIZE= values until we can reliably and
* portably detect overflows while converting from string to off_t.
+ *
+ * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a
+ * command in which invalid character codes have been employed, and for
+ * which there are no other reasons for rejection, MUST reject that
+ * command with a 501 response. So much for the principle of "be liberal
+ * in what you accept, be strict in what you send".
*/
if (var_helo_required && state->helo_name == 0) {
state->error_mask |= MAIL_ERROR_POLICY;
/*
* Sanity checks.
+ *
+ * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a
+ * command in which invalid character codes have been employed, and for
+ * which there are no other reasons for rejection, MUST reject that
+ * command with a 501 response. So much for the principle of "be liberal
+ * in what you accept, be strict in what you send".
*/
if (state->cleanup == 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
static int noop_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
{
+ /*
+ * XXX 2821 incompatibility: Section 4.1.1.9 says that NOOP can have a
+ * parameter string which is to be ignored. NOOP instructions with
+ * parameters? Go figure.
+ *
+ * RFC 2821 violates RFC 821, which says that NOOP takes no parameters.
+ */
+#ifdef RFC821_SYNTAX
+
/*
* Sanity checks.
*/
smtpd_chat_reply(state, "501 Syntax: NOOP");
return (-1);
}
+#endif
smtpd_chat_reply(state, "250 Ok");
return (0);
}
* address forms. Therefore we must parse out the address, or we must
* stop doing recipient restriction checks and lose the opportunity to
* say "user unknown" at the SMTP port.
+ *
+ * XXX 2821 incompatibility and brain damage: Section 4.5.1 requires that
+ * VRFY is implemented. RFC 821 specifies that VRFY is optional. It gets
+ * even worse: section 3.5.3 says that a 502 (command recognized but not
+ * implemented) reply is not fully compliant.
+ *
+ * Thus, an RFC 2821 compliant implementation cannot refuse to supply
+ * information in reply to VRFY queries. That is simply bogus. The only
+ * reply we could supply is a generic 252 reply. This causes spammers to
+ * add tons of bogus addresses to their mailing lists (spam harvesting by
+ * trying out large lists of potential recipient names with VRFY).
*/
#define SLOPPY 0
smtpd_chat_reply(state, "%s", err);
return (-1);
}
- smtpd_chat_reply(state, "252 <%s>", argv[1].strval);
+
+ /*
+ * XXX 2821 new feature: Section 3.5.1 requires that the VRFY response is
+ * either "full name <user@domain>" or "user@domain". Postfix replies
+ * with the address that was provided by the client, whether or not it is
+ * in fully qualified domain form or not.
+ *
+ * Reply code 250 is reserved for the case where the address is verified;
+ * reply code 252 should be used when no definitive certainty exists.
+ */
+ smtpd_chat_reply(state, "252 %s", argv[1].strval);
return (0);
}
continue;
}
if (state->access_denied && cmdp->action != quit_cmd) {
- smtpd_chat_reply(state, "%s", state->access_denied);
+ smtpd_chat_reply(state, "503 Error: access denied for %s",
+ state->namaddr); /* RFC 2821 Sec 3.1 */
state->error_count++;
continue;
}
VAR_SMTPD_SOFT_ERLIM, DEF_SMTPD_SOFT_ERLIM, &var_smtpd_soft_erlim, 1, 0,
VAR_SMTPD_HARD_ERLIM, DEF_SMTPD_HARD_ERLIM, &var_smtpd_hard_erlim, 1, 0,
VAR_QUEUE_MINFREE, DEF_QUEUE_MINFREE, &var_queue_minfree, 0, 0,
- VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0,
VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code, 0, 0,
VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code, 0, 0,
VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code, 0, 0,
};
static CONFIG_STR_TABLE str_table[] = {
VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0,
- VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0,
VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
VAR_CLIENT_CHECKS, DEF_CLIENT_CHECKS, &var_client_checks, 0, 0,
VAR_HELO_CHECKS, DEF_HELO_CHECKS, &var_helo_checks, 0, 0,
notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
var_error_rcpt,
- NULL_CLEANUP_FLAGS, "NOTICE");
+ NULL_CLEANUP_FLAGS);
if (notice == 0) {
msg_warn("postmaster notify: %m");
return;
}
printable(STR(error_text), ' ');
+ /*
+ * XXX The code below also appears in the SMTP server reply output
+ * routine. It is duplicated here in order to avoid discrepancies between
+ * the reply codes that are shown in "reject" logging and the reply codes
+ * that are actually sent to the SMTP client.
+ *
+ * Implementing the soft_bounce safety net in the SMTP server reply output
+ * routine has the advantage that it covers all 5xx replies, including
+ * SMTP protocol or syntax errors, which makes soft_bounce great for
+ * non-destructive tests (especially by people who are paranoid about
+ * losing mail).
+ *
+ * We could eliminate the code duplication and implement the soft_bounce
+ * safety net only in the code below. But then the safety net would cover
+ * the UCE restrictions only. This would be at odds with the documentation
+ * which says soft_bounce changes all 5xx replies into 4xx ones.
+ */
+ if (var_soft_bounce && STR(error_text)[0] == '5')
+ STR(error_text)[0] = '4';
+
/*
* Log what is happening. When the sysadmin discards policy violation
* postmaster notices, this may be the only trace left that service was
if (recipient == 0)
return (0);
+ /*
+ * XXX 2821: Section 3.6 requires that "postmaster" be accepted even when
+ * specified without a fully qualified domain name.
+ */
+ if (strcasecmp(recipient, "postmaster") == 0)
+ return (0);
+
/*
* Minor kluge so that we can delegate work to the generic routine and so
* that we can syslog the recipient with the reject messages.