${name:text} expands to text when name is undefined,
otherwise the result is empty. File: util/mac_expand.c.
- Feature: conditional macro expansion of the forward_path and
- mailbox_command configuration parameters of $user, $home,
- $shell, $recipient, $extension, $domain, $mailbox and
- $recipient_delimiter. Files: local/command.c,
- local/dotforward.c, local/local_expand.c.
+ Feature: conditional macro expansion of the forward_path
+ configuration parameters of $user, $home, $shell, $recipient,
+ $extension, $domain, $mailbox and $recipient_delimiter.
+ Files: local/dotforward.c, local/local_expand.c.
19990506
19990509
- Feature: command_expansion_filter to control what characters
- may appearin message attributes that are exported via
- environment variables.
+ Feature: USER, EXTENSION and DOMAIN environment variables
+ are exported to shell commands (including mailbox_command).
+
+ Feature: new command_expansion_filter parameter to control
+ what characters may appear in message attributes that are
+ exported via environment variables.
Cleanup: SMTPD reject messages are more informative, and
more complete sender/recipient information is logged for
the local sysadmin.
+
+19990510
+
+ Bugfix: missing MIME header in postmaster bounce notices.
+ Found by Samuel Tardieu, Ecole Nationale Superieure des
+ Telecommunications, France.
+
+ Feature: UCE restrictions are always delayed until RCPT
+ TO, VRFY or ETRN. To change back to the default specify
+ "smtpd_delay_reject = no" in /etc/postfix/main.cf.
+
+ Bugfix: missing duplicate filter call. This caused too many
+ deliveries when a user is listed multiple times in an alias.
+ Reported by Hideyuki Suzuki, School of Engineering, University
+ of Tokyo.
+
+ Feature: it is now possible to move queue files back into
+ the maildrop queue, so that they can benefit from changes
+ in canonical and virtual mappings. In order to make this
+ possible, some restrictions on queue file contents were
+ relaxed. Files: pickup/pickup.c, cleanup/cleanup_extracted.c.
+
+ Feature: made a start with integrating Joerg Henne's
+ dictionary extensions to remove entries and to iterate over
+ entries. That code is almost four months old by now.
Incompatible changes with snapshot-19990509:
===========================================
+- The SMTP server now always delays UCE restrictions until RCPT
+TO, VRFY or ETRN command. To change back to the default specify
+"smtpd_delay_reject = no" in /etc/postfix/main.cf.
+
- The Postfix local delivery agent no longer automatically propagates
the address extension to aliases/include/forward addresses. Specify
"propagate_unmatched_extensions = canonical, virtual, alias, forward,
post_mail_fputs(bounce, "");
post_mail_fputs(bounce, "This is a MIME-encapsulated message.");
post_mail_fputs(bounce, "");
+
+ /*
+ * More MIME header.
+ */
+ post_mail_fprintf(bounce, "--%s", boundary);
+ post_mail_fprintf(bounce, "Content-Description: %s", "Notification");
+ post_mail_fprintf(bounce, "Content-Type: %s", "text/plain");
+ post_mail_fputs(bounce, "");
+
return (vstream_ferror(bounce));
}
const char *boundary, int flush)
{
- /*
- * MIME header.
- */
- post_mail_fprintf(bounce, "--%s", boundary);
- post_mail_fprintf(bounce, "Content-Description: %s", "Notification");
- post_mail_fprintf(bounce, "Content-Type: %s", "text/plain");
- post_mail_fputs(bounce, "");
-
/*
* Print the message body with the problem report. XXX For now, we use a
* fixed bounce template. We could use a site-specific parametrized
#include <cleanup_user.h>
#include <record.h>
#include <rec_type.h>
+#include <mail_params.h>
+#include <ext_prop.h>
/* Application-specific. */
#include "cleanup.h"
+#define STR(x) vstring_str(x)
+
/* cleanup_extracted - generate segment with header-extracted information */
void cleanup_extracted(void)
{
+ VSTRING *clean_addr;
ARGV *rcpt;
char **cpp;
int type;
*
* XXX Rely on the front-end programs to enforce record size limits.
*/
- if ((type = rec_get(cleanup_src, cleanup_inbuf, 0)) < 0) {
- cleanup_errs |= CLEANUP_STAT_BAD;
- return;
- }
-
- /*
- * Require that the client sends an empty record group. It is OUR job to
- * extract information from message headers. Clients can specify options
- * via special message header lines.
- *
- * XXX Keep reading in case of trouble, so that the sender is ready for our
- * status report.
- */
- if (type != REC_TYPE_END) {
- msg_warn("unexpected record type %d in extracted segment", type);
- cleanup_errs |= CLEANUP_STAT_BAD;
- if (type >= 0)
- cleanup_skip();
- return;
+ while (CLEANUP_OUT_OK()) {
+ if ((type = rec_get(cleanup_src, cleanup_inbuf, 0)) < 0) {
+ cleanup_errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (type == REC_TYPE_RRTO) {
+ /* XXX Use extracted information instead. */ ;
+ } else if (type == REC_TYPE_ERTO) {
+ /* XXX Use extracted information instead. */ ;
+ } else if (type == REC_TYPE_RCPT) {
+ clean_addr = vstring_alloc(100);
+ cleanup_rewrite_internal(clean_addr, *STR(cleanup_inbuf) ?
+ STR(cleanup_inbuf) : var_empty_addr);
+ if (cleanup_rcpt_canon_maps)
+ cleanup_map11_internal(clean_addr, cleanup_rcpt_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_comm_canon_maps)
+ cleanup_map11_internal(clean_addr, cleanup_comm_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ cleanup_out_recipient(STR(clean_addr));
+ if (cleanup_recip == 0)
+ cleanup_recip = mystrdup(STR(clean_addr));
+ vstring_free(clean_addr);
+ } else if (type == REC_TYPE_END) {
+ break;
+ } else {
+ msg_warn("unexpected record type %d in extracted segment", type);
+ cleanup_errs |= CLEANUP_STAT_BAD;
+ if (type >= 0)
+ cleanup_skip();
+ return;
+ }
}
/*
fallback_transport =
fork_attempts = 5
fork_delay = 1
-forward_expansion_filter = 1234567890!@%-_=+:,./abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
forward_path = $home/.forward${recipient_delimiter}${extension},$home/.forward
hash_queue_depth = 2
hash_queue_names = defer
mail_name = Postfix
mail_owner = postfix
mail_spool_directory = /var/mail
-mail_version = Snapshot-19990509
+mail_version = Snapshot-19990510
mailbox_command =
mailbox_transport =
maps_rbl_domains = rbl.maps.vix.com
smtp_skip_quit_response = yes
smtpd_banner = $myhostname ESMTP $mail_name
smtpd_client_restrictions =
+smtpd_delay_reject = yes
smtpd_error_sleep_time = 5
smtpd_etrn_restrictions =
smtpd_hard_error_limit = 100
# This directs mail for \fIuser\fR@\fBfoo.org\fR to host \fBbar.org\fR
# port \fB2025\fR. Instead of a numerical port a symbolic name may be
# used. Specify [] around the destination in order to disable MX lookups.
+#
+# The error mailer can be used to bounce mail:
+#
+# .ti +5
+# \fB\&.foo.org error:mail for *.foo.org is not deliverable\fR
+#
+# This causes all mail for \fIuser\fR@\fIanything\fBfoo.org\fR
+# to be bounced.
# CONFIGURATION PARAMETERS
# .ad
# .fi
msg_info("deliver_message: from %s", request->sender);
/*
- * Sanity checks. The smtp server is unprivileged and chrooted, so we can
- * afford to distribute the data censoring code, instead of having it all
- * in one place.
+ * Sanity checks.
*/
if (request->nexthop[0] == 0)
msg_fatal("empty nexthop hostname");
ABCDEFGHIJKLMNOPQRSTUVWXYZ"
extern char *var_cmd_exp_filter;
-#define VAR_FWD_EXP_FILTER "forward_expansion_filter"
-#define DEF_FWD_EXP_FILTER "1234567890!@%-_=+:,./\
-abcdefghijklmnopqrstuvwxyz\
-ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-extern char *var_fwd_exp_filter;
-
#define VAR_RCPT_FDELIM "recipient_feature_delimiter"
#define DEF_RCPT_FDELIM ""
extern char *var_rcpt_fdelim;
#define DEF_MAPS_RBL_DOMAINS "rbl.maps.vix.com"
extern char *var_maps_rbl_domains;
+#define VAR_SMTPD_DELAY_REJECT "smtpd_delay_reject"
+#define DEF_SMTPD_DELAY_REJECT 1
+extern int var_smtpd_delay_reject;
+
/*
* Other.
*/
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-19990509"
+#define DEF_MAIL_VERSION "Snapshot-19990510"
extern char *var_mail_version;
/* LICENSE
address extension), <b>$domain</b> (recipient domain), <b>mailbox</b>
(entire recipient address localpart) and <b>$recipient</b><i>_</i><b>delim-</b>
<b>iter.</b> The forms <i>${name?value}</i> and <i>${name:value}</i> expand
- conditionally to <i>value</i> when <i>$name</i> is (is not) defined. In
- the result of <i>name</i> expansion, characters that have special
- meaning to the shell are replaced by underscores. The list
- of legal characters is specified with the <b>forward</b><i>_</i><b>expan-</b>
- <b>sion</b><i>_</i><b>filter</b> configuration parameter.
+ conditionally to <i>value</i> when <i>$name</i> is (is not) defined.
An alias or ~/.<b>forward</b> file may list any combination of
external commands, destination file names, <b>:include:</b>
When an address is found in its own alias expansion,
delivery is made to the user instead. When a user is
listed in the user's own ~/.<b>forward</b> file, delivery is made
+ to the user's mailbox instead. An empty ~/.<b>forward</b> file
+ means do not forward mail.
+
+ In order to prevent the mail system from using up
LOCAL(8) LOCAL(8)
- to the user's mailbox instead. An empty ~/.<b>forward</b> file
- means do not forward mail.
-
- In order to prevent the mail system from using up unrea-
- sonable amounts of memory, input records read from
+ unreasonable amounts of memory, input records read from
<b>:include:</b> or from ~/.<b>forward</b> files are broken up into
chunks of length <b>line</b><i>_</i><b>length</b><i>_</i><b>limit</b>.
In the case of UNIX-style mailbox delivery, the <b>local</b> dae-
mon prepends a "<b>From</b> <i>sender</i> <i>time_stamp</i>" envelope header to
each message, prepends a <b>Delivered-To:</b> header with the
+ envelope recipient address, prepends a <b>Return-Path:</b> header
+ with the envelope sender address, prepends a > character
+ to lines beginning with "<b>From</b> ", and appends an empty
+ line. The mailbox is locked for exclusive access while
LOCAL(8) LOCAL(8)
- envelope recipient address, prepends a <b>Return-Path:</b> header
- with the envelope sender address, prepends a > character
- to lines beginning with "<b>From</b> ", and appends an empty
- line. The mailbox is locked for exclusive access while
delivery is in progress. In case of problems, an attempt
is made to truncate the mailbox to its original length.
dependent default path, and the <b>TZ</b> (time zone) environment
variable is always passed on without change.
+ The current working directory is the mail queue directory.
+
+ The <b>local</b> daemon prepends a "<b>From</b> <i>sender</i> <i>time_stamp</i>" enve-
+ lope header to each message, prepends a <b>Delivered-To:</b>
LOCAL(8) LOCAL(8)
- The current working directory is the mail queue directory.
-
- The <b>local</b> daemon prepends a "<b>From</b> <i>sender</i> <i>time_stamp</i>" enve-
- lope header to each message, prepends a <b>Delivered-To:</b>
header with the recipient envelope address, prepends a
<b>Return-Path:</b> header with the sender envelope address, and
appends an empty line.
superuser, delivery is made with the rights specified with
the <b>default</b><i>_</i><b>privs</b> configuration parameter.
+<b>STANDARDS</b>
+ RFC 822 (ARPA Internet Text Messages)
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8).
LOCAL(8) LOCAL(8)
-<b>STANDARDS</b>
- RFC 822 (ARPA Internet Text Messages)
-
-<b>DIAGNOSTICS</b>
- Problems and transactions are logged to <b>syslogd</b>(8). Cor-
- rupted message files are marked so that the queue manager
- can move them to the <b>corrupt</b> queue afterwards.
+ Corrupted message files are marked so that the queue man-
+ ager can move them to the <b>corrupt</b> queue afterwards.
Depending on the setting of the <b>notify</b><i>_</i><b>classes</b> parameter,
the postmaster is notified of bounces and of other trou-
<b>Mailbox</b> <b>delivery</b>
<b>fallback</b><i>_</i><b>transport</b>
Message transport for recipients that are not found
+ in the UNIX passwd database. This parameter over-
+ rides <b>luser</b><i>_</i><b>relay</b>.
+
+ <b>home</b><i>_</i><b>mailbox</b>
+ Pathname of a mailbox relative to a user's home
LOCAL(8) LOCAL(8)
- in the UNIX passwd database. This parameter over-
- rides <b>luser</b><i>_</i><b>relay</b>.
-
- <b>home</b><i>_</i><b>mailbox</b>
- Pathname of a mailbox relative to a user's home
directory. Specify a path ending in <b>/</b> for maildir-
style delivery.
Limit the amount of memory used for processing a
partial input line.
+ <b>local</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b><i>_</i><b>limit</b>
+ Limit the number of parallel deliveries to the same
+ user. The default limit is taken from the
+ <b>default</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b><i>_</i><b>limit</b> parameter.
+
6
LOCAL(8) LOCAL(8)
- <b>local</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b><i>_</i><b>limit</b>
- Limit the number of parallel deliveries to the same
- user. The default limit is taken from the
- <b>default</b><i>_</i><b>destination</b><i>_</i><b>concurrency</b><i>_</i><b>limit</b> parameter.
-
<b>local</b><i>_</i><b>destination</b><i>_</i><b>recipient</b><i>_</i><b>limit</b>
Limit the number of recipients per message deliv-
ery. The default limit is taken from the
Default rights for delivery to external file or
command.
- <b>forward</b><i>_</i><b>expansion</b><i>_</i><b>filter</b>
- What characters are allowed to appear in $name
- expansions of forward_path. Illegal characters are
- replaced by underscores.
-
<b>HISTORY</b>
The <b>Delivered-To:</b> header appears in the <b>qmail</b> system by
Daniel Bernstein.
<b>AUTHOR(S)</b>
Wietse Venema
IBM T.J. Watson Research
-
-
-
- 7
-
-
-
-
-
-LOCAL(8) LOCAL(8)
-
-
P.O. Box 704
Yorktown Heights, NY 10598, USA
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 8
+ 7
</pre> </body> </html>
lookup_status = -1;
while ((lhs = mystrtok(&next, ", \t\r\n")) != 0) {
- expand_status = local_expand(path, lhs, &state,
- &usr_attr, var_fwd_exp_filter);
+ expand_status = local_expand(path, lhs, &state, &usr_attr, (char *) 0);
if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) {
lookup_status =
lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid);
/* \fB$recipient_delimiter.\fR The forms \fI${name?value}\fR and
/* \fI${name:value}\fR expand conditionally to \fIvalue\fR when
/* \fI$name\fR is (is not) defined.
-/* In the result of \fIname\fR expansion, characters that have special
-/* meaning to the shell are replaced by underscores. The list of legal
-/* characters is specified with the \fBforward_expansion_filter\fR
-/* configuration parameter.
/*
/* An alias or ~/.\fBforward\fR file may list any combination of external
/* commands, destination file names, \fB:include:\fR directives, or
/* mailbox_command. Illegal characters are replaced by underscores.
/* .IP \fBdefault_privs\fR
/* Default rights for delivery to external file or command.
-/* .IP \fBforward_expansion_filter\fR
-/* What characters are allowed to appear in $name expansions of
-/* forward_path. Illegal characters are replaced by underscores.
/* HISTORY
/* .ad
/* .fi
char *var_fallback_transport;
char *var_forward_path;
char *var_cmd_exp_filter;
-char *var_fwd_exp_filter;
char *var_prop_extension;
int local_cmd_deliver_mask;
VAR_MAILBOX_TRANSP, DEF_MAILBOX_TRANSP, &var_mailbox_transport, 0, 0,
VAR_FALLBACK_TRANSP, DEF_FALLBACK_TRANSP, &var_fallback_transport, 0, 0,
VAR_CMD_EXP_FILTER, DEF_CMD_EXP_FILTER, &var_cmd_exp_filter, 1, 0,
- VAR_FWD_EXP_FILTER, DEF_FWD_EXP_FILTER, &var_fwd_exp_filter, 1, 0,
VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0,
0,
};
if (msg_verbose)
MSG_LOG_STATE(myname, state);
+ /*
+ * Duplicate filter.
+ */
+ if (been_here(state.dup_filter, "recipient %s", state.msg_attr.recipient))
+ return (0);
+
/*
* With each level of recursion, detect and break external message
* forwarding loops.
\fB$recipient_delimiter.\fR The forms \fI${name?value}\fR and
\fI${name:value}\fR expand conditionally to \fIvalue\fR when
\fI$name\fR is (is not) defined.
-In the result of \fIname\fR expansion, characters that have special
-meaning to the shell are replaced by underscores. The list of legal
-characters is specified with the \fBforward_expansion_filter\fR
-configuration parameter.
An alias or ~/.\fBforward\fR file may list any combination of external
commands, destination file names, \fB:include:\fR directives, or
mailbox_command. Illegal characters are replaced by underscores.
.IP \fBdefault_privs\fR
Default rights for delivery to external file or command.
-.IP \fBforward_expansion_filter\fR
-What characters are allowed to appear in $name expansions of
-forward_path. Illegal characters are replaced by underscores.
.SH HISTORY
.na
.nf
return (status);
/*
- * Send the segment with information extracted from message headers. At
- * this stage of the mail processing pipeline, that segment should be
- * empty, so we require that the it is. This record group ends with a
- * type REC_TYPE_END record.
+ * Send the segment with information extracted from message headers.
+ * Permit a non-empty extracted segment, so that list manager software
+ * can to output recipients after the message, and so that sysadmins can
+ * re-inject messages after a change of configuration.
*/
rec_fputs(cleanup, REC_TYPE_XTRA, "");
- if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_NOEXTRACT)) != 0)
+ if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_EXTRACT)) != 0)
return (status);
/*
int var_non_fqdn_code;
char *var_always_bcc;
char *var_error_rcpt;
+int var_smtpd_delay_reject;
/*
* Global state, for stand-alone mode queue file cleanup. When this is
return (-1);
}
collapse_args(argc, argv);
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
state->helo_name = mystrdup(printable(argv[1].strval, '?'));
state->protocol = "SMTP";
smtpd_chat_reply(state, "250 %s", var_myhostname);
return (-1);
}
collapse_args(argc, argv);
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
state->helo_name = mystrdup(printable(argv[1].strval, '?'));
state->protocol = "ESMTP";
smtpd_chat_reply(state, "250-%s", var_myhostname);
}
}
state->time = time((time_t *) 0);
+ if (SMTPD_STAND_ALONE(state) == 0
+ && var_smtpd_delay_reject == 0
+ && (err = smtpd_check_mail(state, argv[3].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
if ((err = smtpd_check_size(state, size)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
smtpd_chat_reply(state, "452 Error: too many recipients");
return (-1);
}
- if (SMTPD_STAND_ALONE(state) == 0) {
- if ((err = smtpd_check_client(state)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
- if (state->helo_name
- && (err = smtpd_check_helo(state, state->helo_name)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
- if (state->sender
- && (err = smtpd_check_mail(state, state->sender)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
- if ((err = smtpd_check_rcpt(state, argv[3].strval)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_rcpt(state, argv[3].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
}
/*
return (-1);
}
collapse_args(argc, argv);
- if (SMTPD_STAND_ALONE(state) == 0) {
- if ((err = smtpd_check_client(state)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
- if (state->helo_name
- && (err = smtpd_check_helo(state, state->helo_name)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
- if (state->sender
- && (err = smtpd_check_mail(state, state->sender)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
+ if (SMTPD_STAND_ALONE(state) == 0)
err = smtpd_check_rcpt(state, argv[1].strval);
- }
/*
* End untokenize.
* rejected. RFC 1985 requires that 459 be sent when the server refuses
* to perform the request.
*/
- if (SMTPD_STAND_ALONE(state) == 0) {
- if ((err = smtpd_check_client(state)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
- if (state->helo_name
- && (err = smtpd_check_helo(state, state->helo_name)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
- if ((err = smtpd_check_etrn(state, argv[1].strval)) != 0) {
- smtpd_chat_reply(state, "%s", err);
- return (-1);
- }
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_etrn(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
}
/*
debug_peer_check(state.name, state.addr);
/*
- * Greet the client and log the connection event.
+ * See if we want to talk to this client at all. Then, log the connection
+ * event.
*/
- smtpd_chat_reply(&state, "220 %s", var_smtpd_banner);
- msg_info("connect from %s[%s]", state.name, state.addr);
+ if (var_smtpd_delay_reject == 0
+ && (state.access_denied = smtpd_check_client(&state)) != 0) {
+ smtpd_chat_reply(&state, "%s", state.access_denied);
+ } else {
+ smtpd_chat_reply(&state, "220 %s", var_smtpd_banner);
+ msg_info("connect from %s[%s]", state.name, state.addr);
+ }
/*
* Provide the SMTP service.
};
static CONFIG_BOOL_TABLE bool_table[] = {
VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required,
+ VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
0,
};
static CONFIG_STR_TABLE str_table[] = {
char *name;
int status;
char *saved_recipient = state->recipient;
+ char *err;
+
+ /*
+ * Apply delayed restrictions.
+ */
+ if (var_smtpd_delay_reject)
+ if ((err = smtpd_check_client(state)) != 0
+ || (err = smtpd_check_helo(state, state->helo_name)) != 0
+ || (err = smtpd_check_mail(state, state->sender)) != 0)
+ return (err);
/*
* Initialize.
char **cpp;
char *name;
int status;
+ char *err;
+
+ /*
+ * Apply delayed restrictions.
+ */
+ if (var_smtpd_delay_reject)
+ if ((err = smtpd_check_client(state)) != 0
+ || (err = smtpd_check_helo(state, state->helo_name)) != 0)
+ return (err);
/*
* Initialize.
int var_access_map_code;
int var_reject_code;
int var_non_fqdn_code;
+int var_smtpd_delay_reject;
static INT_TABLE int_table[] = {
"msg_verbose", 0, &msg_verbose,
VAR_ACCESS_MAP_CODE, DEF_ACCESS_MAP_CODE, &var_access_map_code,
VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code,
VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code,
+ VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
0,
};
#
#! ../bin/postmap smtpd_check_access
#msg_verbose 1
+smtpd_delay_reject 0
mynetworks 127.0.0.0/8,168.100.189.0/28
relay_domains porcupine.org
#
#
#! ../bin/postmap smtpd_check_access
#msg_verbose 1
+smtpd_delay_reject 0
mynetworks 127.0.0.0/8,168.100.189.0/28
relay_domains porcupine.org
#
>>> #
>>> #! ../bin/postmap smtpd_check_access
>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
>>> mynetworks 127.0.0.0/8,168.100.189.0/28
OK
>>> relay_domains porcupine.org
>>> #
>>> #! ../bin/postmap smtpd_check_access
>>> #msg_verbose 1
+>>> smtpd_delay_reject 0
+OK
>>> mynetworks 127.0.0.0/8,168.100.189.0/28
OK
>>> relay_domains porcupine.org
/* const char *dict_name;
/* const char *member;
/*
+/* int dict_delete(dict_name, member)
+/* const char *dict_name;
+/* const char *member;
+/*
+/* int dict_sequence(dict_name, func, member, value)
+/* const char *dict_name;
+/* int func;
+/* const char **member;
+/* const char **value;
+/*
/* const char *dict_eval(dict_name, string, int recursive)
/* const char *dict_name;
/* const char *string;
/* underlying dictionary method. Make a copy if the result is to be
/* modified, or if the result is to survive multiple dict_lookup() calls.
/*
+/* dict_delete() removes the named member from the named dictionary.
+/* The result is non-zero when the member does not exist.
+/*
+/* 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
+/* 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.
+/*
/* dict_eval() expands macro references in the specified string.
/* The result is owned by the dictionary manager. Make a copy if the
/* result is to survive multiple dict_eval() calls. When the
return (ret);
}
+/* dict_delete - delete dictionary entry */
+
+int dict_delete(const char *dict_name, const char *member)
+{
+ char *myname = "dict_delete";
+ DICT_NODE *node;
+ DICT *dict;
+ int result;
+
+ if ((node = dict_node(dict_name)) == 0) {
+ if (dict_unknown_allowed == 0)
+ msg_fatal("%s: unknown dictionary: %s", myname, dict_name);
+ dict = dict_ht_open(htable_create(0), myfree);
+ dict_register(dict_name, dict);
+ } else
+ dict = node->dict;
+ if (msg_verbose > 1)
+ msg_info("%s: delete %s", myname, member);
+ if ((result = dict->delete(dict, member)) != 0 && dict_unknown_allowed == 0)
+ msg_fatal("%s: dictionary %s: unknown member: %s",
+ myname, dict_name, member);
+ return (result);
+}
+
+/* dict_sequence - traverse dictionary */
+
+int dict_sequence(const char *dict_name, const int func,
+ const char **member, const char **value)
+{
+ char *myname = "dict_sequence";
+ DICT_NODE *node;
+ DICT *dict;
+
+ if ((node = dict_node(dict_name)) == 0) {
+ if (dict_unknown_allowed == 0)
+ msg_fatal("%s: unknown dictionary: %s", myname, dict_name);
+ dict = dict_ht_open(htable_create(0), myfree);
+ dict_register(dict_name, dict);
+ } else
+ dict = node->dict;
+ if (msg_verbose > 1)
+ msg_info("%s: sequence func %d", myname, func);
+ return (dict->sequence(dict, func, member, value));
+}
+
/* dict_load_file - read entries from text file */
void dict_load_file(const char *dict_name, const char *path)
} else {
vstring_strcat(ctxt->buf, STR(buf));
}
- return(0);
+ return (0);
}
/* dict_eval - expand embedded dictionary references */
* Utility library.
*/
#include <vstream.h>
+#include <vstring.h>
/*
* Generic dictionary interface - in reality, a dictionary extends this
int flags; /* see below */
const char *(*lookup) (struct DICT *, const char *);
void (*update) (struct DICT *, const char *, const char *);
+ int (*delete) (struct DICT *, const char *);
+ int (*sequence) (struct DICT *, int, const char **, const char **);
void (*close) (struct DICT *);
int fd; /* for dict_update() lock */
time_t mtime; /* mod time at open */
#define DICT_FLAG_FIXED (1<<4) /* fixed key map */
#define DICT_FLAG_PATTERN (1<<5) /* keys are patterns */
#define DICT_FLAG_LOCK (1<<6) /* lock before access */
+#define DICT_FLAG_DUP_REPLACE (1<<7) /* if file, replace dups */
extern int dict_unknown_allowed;
extern int dict_errno;
#define DICT_ERR_RETRY 1 /* soft error */
/*
- * High-level interface, with logical dictionary names and with implied
- * locking.
+ * Sequence function types.
+ */
+#define DICT_SEQ_FUN_FIRST 0 /* set cursor to first record */
+#define DICT_SEQ_FUN_NEXT 1 /* set cursor to next record */
+
+ /*
+ * High-level interface, with logical dictionary names.
*/
extern void dict_register(const char *, DICT *);
extern DICT *dict_handle(const char *);
extern void dict_unregister(const char *);
extern void dict_update(const char *, const char *, const char *);
extern const char *dict_lookup(const char *, const char *);
+extern int dict_delete(const char *, const char *);
+extern int dict_sequence(const char *, const int, const char **, const char **);
extern void dict_load_file(const char *, const char *);
extern void dict_load_fp(const char *, VSTREAM *);
extern const char *dict_eval(const char *, const char *, int);
/*
- * Low-level interface, with physical dictionary handles and no implied
- * locking.
+ * Low-level interface, with physical dictionary handles.
*/
extern DICT *dict_open(const char *, int, int);
extern DICT *dict_open3(const char *, const char *, int, int);
extern void dict_open_register(const char *, DICT *(*) (const char *, int, int));
+
#define dict_get(dp, key) (dp)->lookup((dp), (key))
#define dict_put(dp, key, val) (dp)->update((dp), (key), (val))
+#define dict_del(dp, key) (dp)->delete((dp), (key))
+#define dict_seq(dp, f, key, val) (dp)->sequence((dp), (f), (key), (val))
#define dict_close(dp) (dp)->close(dp)
typedef void (*DICT_WALK_ACTION) (const char *, DICT *, char *);
extern void dict_walk(DICT_WALK_ACTION, char *);
/*
* Do the update.
*/
- if ((status = db->put(db, &db_key, &db_value, R_NOOVERWRITE)) < 0)
+ if ((status = db->put(db, &db_key, &db_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : R_NOOVERWRITE)) < 0)
msg_fatal("error writing %s: %m", dict_db->path);
if (status) {
if (dict->flags & DICT_FLAG_DUP_IGNORE)
msg_fatal("%s: unlock dictionary: %m", dict_db->path);
}
+/* delete one entry from the dictionary */
+
+static int dict_db_delete(DICT *dict, const char *key)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ int status;
+ int flags = 0;
+
+ db_key.data = (void *) key;
+ db_key.size = strlen(key);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef DB_NO_TRAILING_NULL
+ dict->flags = DICT_FLAG_TRY0NULL;
+#else
+ dict->flags = DICT_FLAG_TRY1NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ db_key.size++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->fd, MYFLOCK_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->path);
+
+ /*
+ * Do the delete operation.
+ */
+ if ((status = db->del(db, &db_key, flags)) < 0)
+ msg_fatal("error deleting %s: %m", dict_db->path);
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->fd, MYFLOCK_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->path);
+
+ return status;
+}
+
+/* dict_db_sequence - traverse the dictionary */
+
+static int dict_db_sequence(DICT *dict, const int function,
+ const char **key, const char **value)
+{
+ char *myname = "dict_db_sequence";
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ DBT db_value;
+ int status = 0;
+ int db_function;
+ static VSTRING *key_buf;
+ static VSTRING *value_buf;
+
+ /*
+ * determine the function
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ db_function = R_FIRST;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ db_function = R_NEXT;
+ break;
+ default:
+ msg_panic("%s: invalid function %d", myname, function);
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->fd, MYFLOCK_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->path);
+
+ if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0)
+ msg_fatal("error seeking %s: %m", dict_db->path);
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->fd, MYFLOCK_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->path);
+
+ if (status == 0) {
+
+ /*
+ * See if this DB file was written with one null byte appended to key
+ * and value or not.
+ */
+ if (((char *) db_key.data)[db_key.size] == 0) {
+ *key = db_key.data;
+ } else {
+ if (key_buf == 0)
+ key_buf = vstring_alloc(10);
+ vstring_strncpy(key_buf, db_key.data, db_key.size);
+ *key = vstring_str(key_buf);
+ }
+ if (((char *) db_value.data)[db_value.size] == 0) {
+ *value = db_value.data;
+ } else {
+ if (value_buf == 0)
+ value_buf = vstring_alloc(10);
+ vstring_strncpy(value_buf, db_value.data, db_value.size);
+ *value = vstring_str(value_buf);
+ }
+ }
+ return status;
+}
+
+
+
/* dict_db_close - close data base */
static void dict_db_close(DICT *dict)
dict_db = (DICT_DB *) mymalloc(sizeof(*dict_db));
dict_db->dict.lookup = dict_db_lookup;
dict_db->dict.update = dict_db_update;
+ dict_db->dict.delete = dict_db_delete;
+ dict_db->dict.sequence = dict_db_sequence;
dict_db->dict.close = dict_db_close;
dict_db->dict.fd = db->fd(db);
if (fstat(dict_db->dict.fd, &st) < 0)
/*
* Do the update.
*/
- if ((status = dbm_store(dict_dbm->dbm, dbm_key, dbm_value, DBM_INSERT)) < 0)
+ if ((status = dbm_store(dict_dbm->dbm, dbm_key, dbm_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0)
msg_fatal("error writing DBM database %s: %m", dict_dbm->path);
if (status) {
if (dict->flags & DICT_FLAG_DUP_IGNORE)
msg_fatal("%s: unlock dictionary: %m", dict_dbm->path);
}
+/* dict_dbm_delete - delete one entry from the dictionary */
+
+static int dict_dbm_delete(DICT *dict, const char *key)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ int status;
+ int flags = 0;
+
+ dbm_key.dptr = (void *) key;
+ dbm_key.dsize = strlen(key);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef DBM_NO_TRAILING_NULL
+ dict->flags = DICT_FLAG_TRY0NULL;
+#else
+ dict->flags = DICT_FLAG_TRY1NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dsize++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->fd, MYFLOCK_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->path);
+
+ /*
+ * Do the delete operation.
+ */
+ if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0)
+ msg_fatal("error deleting %s: %m", dict_dbm->path);
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->fd, MYFLOCK_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->path);
+
+ return (status);
+}
+
+/* traverse the dictionary */
+
+static int dict_dbm_sequence(DICT *dict, const int function,
+ const char **key, const char **value)
+{
+ char *myname = "dict_dbm_sequence";
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status = 0;
+ static VSTRING *key_buf;
+ static VSTRING *value_buf;
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->fd, MYFLOCK_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->path);
+
+ /*
+ * Determine and execute the seek function. It returns the key.
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ dbm_key = dbm_firstkey(dict_dbm->dbm);
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ dbm_key = dbm_nextkey(dict_dbm->dbm);
+ break;
+ default:
+ msg_panic("%s: invalid function: %d", myname, function);
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK) && myflock(dict->fd, MYFLOCK_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->path);
+
+ if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
+
+ /*
+ * See if this DB file was written with one null byte appended to key
+ * an d value or not. If necessary, copy the key.
+ */
+ if (((char *) dbm_key.dptr)[dbm_key.dsize - 1] == 0) {
+ *key = dbm_key.dptr;
+ } else {
+ if (key_buf == 0)
+ key_buf = vstring_alloc(10);
+ vstring_strncpy(key_buf, dbm_key.dptr, dbm_key.dsize);
+ *key = vstring_str(key_buf);
+ }
+
+ /*
+ * Fetch the corresponding value.
+ */
+ dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
+
+ if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
+
+ /*
+ * See if this DB file was written with one null byte appended to
+ * key and value or not. If necessary, copy the key.
+ */
+ if (((char *) dbm_value.dptr)[dbm_value.dsize - 1] == 0) {
+ *value = dbm_value.dptr;
+ } else {
+ if (value_buf == 0)
+ value_buf = vstring_alloc(10);
+ vstring_strncpy(value_buf, dbm_value.dptr, dbm_value.dsize);
+ *value = vstring_str(value_buf);
+ }
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error
+ * condition.
+ */
+ if (dbm_error(dict_dbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_dbm->path);
+ return (1); /* no error: eof/not found
+ * (should not happen!) */
+ }
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error condition.
+ */
+ if (dbm_error(dict_dbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_dbm->path);
+ return (1); /* no error: eof/not found */
+ }
+ return (0);
+}
+
/* dict_dbm_close - disassociate from data base */
static void dict_dbm_close(DICT *dict)
dict_dbm = (DICT_DBM *) mymalloc(sizeof(*dict_dbm));
dict_dbm->dict.lookup = dict_dbm_lookup;
dict_dbm->dict.update = dict_dbm_update;
+ dict_dbm->dict.delete = dict_dbm_delete;
+ dict_dbm->dict.sequence = dict_dbm_sequence;
dict_dbm->dict.close = dict_dbm_close;
dict_dbm->dict.fd = dbm_pagfno(dbm);
if (fstat(dict_dbm->dict.fd, &st) < 0)
/* DICT *dict;
/* const char *key;
/*
+/* char *dict_del(dict, key)
+/* DICT *dict;
+/* const char *key;
+/*
+/* void dict_seq(dict, func, key, value)
+/* DICT *dict;
+/* int func;
+/* const char **key;
+/* const char **value;
+/*
/* void dict_close(dict)
/* DICT *dict;
/*
/* Ignore duplicate keys if the underlying database does not
/* support duplicate keys. The default is to terminate with a fatal
/* error.
+/* .IP DICT_FLAG_DUP_REPLACE
+/* Replace duplicate keys if the underlying database supports such
+/* an operation. The default is to terminate with a fatal error.
/* .IP DICT_FLAG_TRY0NULL
/* With maps where this is appropriate, append no null byte to
/* keys and values.
/* dict_put() stores the specified key and value into the named
/* dictionary.
/*
+/* dict_del() removes a dictionary entry, and returns non-zero
+/* in case of problems.
+/*
+/* dict_seq() iterates over all members in the named dictionary.
+/* func is define DICT_SEQ_FUN_FIRST (select first member) or
+/* DICT_SEQ_FUN_NEXT (select next member). A null result means
+/* there is more.
+/*
/* dict_close() closes the specified dictionary and cleans up the
/* associated data structures.
/*
/* DESCRIPTION
/* This module implements parameter-less macro expansions, both
/* conditional and unconditional, and both recursive and non-recursive.
-/* The algorithm can search multiple user-specified symbol tables.
/*
-/* In the text below, an attribute is considered "undefined" when its
-/* value is a null pointer. In all other cases the attribute is
-/* considered "defined".
+/* In this text, an attribute is considered "undefined" when its value
+/* is a null pointer. Otherwise, the attribute is considered "defined"
+/* and is expected to have as value a null-terminated string.
/*
/* The following expansions are implemented:
/* .IP "$name, ${name}, $(name)"
/* .RS
/* .IP MAC_EXP_FLAG_RECURSE
/* Expand $name recursively.
-/* .RE
+/* .PP
/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
+/* .RE
/* .IP filter
/* A null pointer, or a null-terminated array of characters that
/* are allowed to appear in an expansion. Illegal characters are
return (mc->status);
/*
- * $Name reference.
+ * $Name etc. reference.
*/
if (type == MAC_PARSE_VARNAME) {
/*
* Look for the ? or : delimiter. In case of a syntax error, return
- * without doing damage. We do not have enough context to produce an
- * understandable error message, so don't try.
+ * without doing damage, and issue a warning instead.
*/
for (cp = vstring_str(buf); /* void */ ; cp++) {
if ((ch = *cp) == 0) {