Bugfix: LDAP lookup timeout settings were ignored. Patch
by John Hensley. File: util/dict_ldap.c.
-19991110
+19991108
+
+ Bugfix: when doing a fresh install, INSTALL.sh didn't set
+ main.cf:mail_owner properly (Simon J. Mudd).
+
+19991109
+
+ Bugfix: when doing a fresh install, INSTALL.sh no longer
+ worked (missing main.cf file). Fix: add "-c" argument to
+ the postmap commands (Lars Hecking @ nmrc.ucc.ie).
+
+ Documentation: removed spurious "do not edit" comments from
+ the sample pcre and regexp configuration files.
+
+19991110-13
Code cleanup: greatly simplified the SMTPD command parser
and somewhat simplified the code that groks RFC 822-style
address syntax in MAIL FROM and RCPT TO commands.
+
+ New parameter: strict_rfc821_envelopes (default: no) to
+ reject RFC 822 address forms (with comments etc.) in SMTP
+ envelopes. By default, the Postfix SMTP server only logs
+ a warning.
+
+19991113
+
+ Oops, also updated the SMTP VRFY code in the light of
+ changes to the SMTPD command parser.
+
+ Cleanup: the local delivery agent now explicitly rejects
+ recipients with an empty username.
+
+19991114
+
+ Workaround: with some gawk versions, postconf/extract.awk
+ reportedly returns a non-zero exit status upon success.
+ Added an explicit exit(0) statement.
+
+19991115
+
+ Feature: DNS TXT record lookup support, based on initial
+ code by Simon J Mudd. File: dns/dns_lookup.c.
+
+ Feature: RBL TXT record lookups, based on initial code by
+ Simon J Mudd. File: smtpd/smtpd_check.c.
+
+ Feature: permit_auth_destination restriction based on code
+ by Jesper Skriver @ skriver.dk.
+
+ Code cleanup: the transport table now can override local
+ deliveries. Postfix no longer uses the "empty next-hop
+ hostname hack" to remember that a destination is local.
PATH=/bin:/usr/bin:/usr/sbin:/usr/etc:/sbin:/etc
umask 022
+# Workaround, should edit main.cf in place.
+trap 'rm -f ./main.cf; exit' 0 1 2 3 15
+
cat <<EOF
Warning: this script replaces existing sendmail or Postfix programs.
esac
done
-bin/postmap -c ./conf -q "$owner" unix:passwd.byname >/dev/null || {
+# Workaround, should edit main.cf in place.
+test -f $config_directory/main.cf || {
+ echo "mail_owner = $owner" >./main.cf
+ echo "myhostname = xx.yy" >>./main.cf
+ alt_main="-c ."
+}
+
+bin/postmap $alt_main -q "$owner" unix:passwd.byname >/dev/null || {
echo "$owner needs an entry in the passwd file" 1>&2
echo "Remember, $owner must have a dedicated user id and group id." 1>&2
exit 1
case $setgid in
no) ;;
- *) bin/postmap -c ./conf -q "$setgid" unix:group.byname >/dev/null || {
+ *) bin/postmap $alt_main -q "$setgid" unix:group.byname >/dev/null || {
echo "$setgid needs an entry in the group file" 1>&2
echo "Remember, $setgid must have a dedicated group id." 1>&2
exit 1
case $manpages in
no) ;;
- *) test -d $manpages || mkdir -p $manpages || exit 1
- (cd man && tar cf - man?) | (cd $manpages && tar xf -)
+ *) (
+ cd man || exit 1
+ for dir in man?
+ do mkdir -p $manpages/$dir || exit 1
+ done
+ for file in man?/*
+ do
+ rm -f $manpages/$file
+ cp $file $manpages/$file || exit 1
+ chmod 644 $manpages/$file || exit 1
+ done
+ )
esac
printfck: update
+install: update
+ sh INSTALL.sh
+
depend clean:
set -e; for i in $(DIRS); do \
(set -e; echo "[$$i]"; cd $$i; $(MAKE) $@) || exit 1; \
# reject_invalid_hostname: reject HELO hostname with bad syntax.
# reject_unknown_hostname: reject HELO hostname without DNS A or MX record.
# reject_unknown_sender_domain: reject sender domain without A or MX record.
-# check_relay_domains: permit only mail from/to domains in $relay_domains.
-# reject_unauth_destination: reject mail not to domains in $relay_domains.
+# check_relay_domains: permit only mail from/to domains in $relay_domains
+ or to the local machine.
+# permit_auth_destination: permit mail to self or to $relay_domains.
+# reject_unauth_destination: reject mail not to self or to $relay_domains.
# reject_unauth_pipelining: reject mail from improperly pipelining spamware
# permit_mx_backup: accept mail for sites that list me as MX host.
# reject_unknown_recipient_domain: reject domains without A or MX record.
#include <stdlib.h> /* BSDI stdarg.h uses abort() */
#include <stdarg.h>
#include <string.h>
+#include <ctype.h>
/* Utility library. */
#include <vstring.h>
#include <msg.h>
#include <valid_hostname.h>
+#include <stringops.h>
/* DNS library. */
char *rr_name, DNS_FIXED *fixed)
{
char temp[DNS_NAME_LEN];
- int data_len = fixed->length;
+ int data_len;
unsigned pref = 0;
+ unsigned char *src;
+ unsigned char *dst;
+ int ch;
+
+#define MIN2(a, b) ((unsigned)(a) < (unsigned)(b) ? (a) : (b))
if (pos + fixed->length > reply->end)
return (0);
memcpy(temp, pos, fixed->length);
data_len = fixed->length;
break;
+ case T_TXT:
+ data_len = MIN2(fixed->length + 1, sizeof(temp));
+ for (src = pos, dst = temp; dst < temp + data_len - 1; /* void */ ) {
+ ch = *src++;
+ *dst++ = (ISPRINT(ch) ? ch : ' ');
+ }
+ *dst = 0;
+ break;
}
return (dns_rr_create(rr_name, fixed, pref, temp, data_len));
}
case T_MR:
case T_NS:
case T_PTR:
+ case T_TXT:
printf("%s: %s\n", dns_strtype(rr->type), rr->data);
break;
case T_MX:
extern char *var_always_bcc;
/*
- * Standards violation: permit RFC 822-style addresses in SMTP commands.
+ * Standards violation: allow/permit RFC 822-style addresses in SMTP commands.
*/
-#define VAR_ALLOW_RFC822_ENV "allow_rfc822_envelopes"
-#define DEF_ALLOW_RFC822_ENV 1
-extern bool var_allow_rfc822_envelopes;
+#define VAR_STRICT_RFC821_ENV "strict_rfc821_envelopes"
+#define DEF_STRICT_RFC821_ENV 0
+extern bool var_strict_rfc821_env;
/*
* trivial rewrite/resolve service: mapping tables.
#define DEF_UNK_ADDR_CODE 450
extern int var_unk_addr_code;
+#define PERMIT_AUTH_DEST "permit_auth_destination"
#define REJECT_UNAUTH_DEST "reject_unauth_destination"
#define CHECK_RELAY_DOMAINS "check_relay_domains"
#define VAR_RELAY_CODE "relay_domains_reject_code"
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-19991110"
+#define DEF_MAIL_VERSION "Snapshot-19991115"
extern char *var_mail_version;
/* LICENSE
<dt> <b>check_relay_domains</b> <dd> Permit the request when the
client hostname matches <a href="#relay_domains">$relay_domains</a>,
-or when the resolved destination address matches <a href="#relay_domains">
-$relay_domains</a>, otherwise reject. The <b>relay_domains_reject_code</b>
-parameter specifies the response code for rejected requests (default:
+or when the resolved destination address matches the the local
+machine or <a href="#relay_domains"> $relay_domains</a>, otherwise
+reject the request. The <b>relay_domains_reject_code</b> parameter
+specifies the response code for rejected requests (default:
<b>554</b>).
<p>
+<a name="permit_auth_destination">
+
+<dt> <b>permit_auth_destination</b> <dd> Ignore the client hostname.
+Permit the request when the resolved destination address matches
+the local machine or <a href="#relay_domains"> $relay_domains</a>.
+
+<p>
+
<a name="reject_unauth_destination">
<dt> <b>reject_unauth_destination</b> <dd> Ignore the client
hostname. Reject the request when the resolved destination address
-does not match <a href="#relay_domains"> $relay_domains</a>. The
-<b>relay_domains_reject_code</b> parameter specifies the response
-code for rejected requests (default: <b>554</b>).
+does not match the local machine or <a href="#relay_domains">
+$relay_domains</a>. The <b>relay_domains_reject_code</b> parameter
+specifies the response code for rejected requests (default:
+<b>554</b>).
<p>
state.msg_attr.extension = 0;
state.msg_attr.unmatched = state.msg_attr.extension;
+ /*
+ * Do not allow null usernames.
+ */
+ if (state.msg_attr.user[0] == 0)
+ return (bounce_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr),
+ "null username in %s", state.msg_attr.recipient));
+
/*
* Run the recipient through the delivery switch.
*/
#include <rewrite_clnt.h>
#include <tok822.h>
#include <mail_params.h>
+#include <resolve_local.h>
/* Application-specific. */
* ugly code to force local recursive alias expansions on a host with no
* authority over the local domain, but that code was just too unclean.
*/
- if (VSTRING_LEN(reply.nexthop) == 0) {
+ if (resolve_local(STR(reply.nexthop))) {
status = deliver_recipient(state, usr_attr);
} else {
status = deliver_indirect(state);
print | "sort -u >bool_table.h"
}
}
+
+# Workaround for broken gawk versions.
+
+END { exit(0); }
#include <deliver_completed.h>
#include <mail_addr_find.h>
#include <opened.h>
+#include <resolve_local.h>
/* Client stubs. */
/*
* Bounce mail to non-existent users in virtual domains.
*/
- if (VSTRING_LEN(reply.nexthop) > 0
+ if (!resolve_local(STR(reply.nexthop))
&& qmgr_virtual != 0
&& (at = strrchr(recipient->address, '@')) != 0) {
domain = lowercase(mystrdup(at + 1));
* job requires knowledge of local aliases. Yuck! I don't want to
* duplicate delivery-agent specific knowledge in the queue manager.
*/
- if (VSTRING_LEN(reply.nexthop) == 0) {
+ if (resolve_local(STR(reply.nexthop))) {
vstring_strcpy(reply.nexthop, STR(reply.recipient));
(void) split_at_right(STR(reply.nexthop), '@');
#if 0
/* this program. See the Postfix \fBmain.cf\fR file for syntax details
/* and for default values. Use the \fBpostfix reload\fR command after
/* a configuration change.
+/* .SH "Compatibility controls"
+/* .ad
+/* .fi
+/* .IP \fBstrict_rfc821_envelopes\fR
+/* Disallow non-RFC 821 style addresses in envelopes. For example,
+/* allow RFC822-style address forms with comments, like Sendmail does.
/* .SH Miscellaneous
/* .ad
/* .fi
char *var_error_rcpt;
int var_smtpd_delay_reject;
char *var_rest_classes;
+int var_strict_rfc821_env;
/*
* Global state, for stand-alone mode queue file cleanup. When this is
*/
char *smtpd_path;
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
/* collapse_args - put arguments together again */
static void collapse_args(int argc, SMTPD_TOKEN *argv)
{
int i;
- for (i = 2; i < argc; i++) {
- vstring_strcat(argv[1].vstrval, " ");
- vstring_strcat(argv[1].vstrval, argv[i].strval);
+ for (i = 1; i < argc; i++) {
+ vstring_strcat(argv[0].vstrval, " ");
+ vstring_strcat(argv[0].vstrval, argv[i].strval);
}
- argv[1].strval = vstring_str(argv[1].vstrval);
+ argv[0].strval = STR(argv[0].vstrval);
}
/* helo_cmd - process HELO command */
smtpd_chat_reply(state, "503 Duplicate HELO/EHLO");
return (-1);
}
- collapse_args(argc, argv);
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
if (SMTPD_STAND_ALONE(state) == 0
&& var_smtpd_delay_reject == 0
&& (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
smtpd_chat_reply(state, "503 Error: duplicate HELO/EHLO");
return (-1);
}
- collapse_args(argc, argv);
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
if (SMTPD_STAND_ALONE(state) == 0
&& var_smtpd_delay_reject == 0
&& (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
/* extract_addr - extract address from rubble */
-static VSTRING *extract_addr(SMTPD_STATE *state, VSTRING *buf)
+static char *extract_addr(SMTPD_STATE *state, SMTPD_TOKEN *arg,
+ int allow_empty_addr)
{
char *myname = "extract_addr";
TOK822 *tree;
TOK822 *tp;
+ TOK822 *addr = 0;
int naddr;
int non_addr;
+ char *err = 0;
+
+ /*
+ * Special case.
+ */
+#define PERMIT_EMPTY_ADDR 1
+#define REJECT_EMPTY_ADDR 0
+
+ if (allow_empty_addr && strcmp(STR(arg->vstrval), "<>") == 0) {
+ if (msg_verbose)
+ msg_info("%s: empty address", myname);
+ VSTRING_RESET(arg->vstrval);
+ VSTRING_TERMINATE(arg->vstrval);
+ arg->strval = STR(arg->vstrval);
+ return (0);
+ }
/*
* Some mailers send RFC822-style address forms (with comments and such)
* in SMTP envelopes. We cannot blame users for this: the blame is with
* programmers violating the RFC, and with sendmail for being permissive.
*
- * Extract the address from any surrounding junk. XXX Because of this, the
- * SMTP command tokenizer must leave the address in externalized (quoted)
- * form.
+ * XXX The SMTP command tokenizer must leave the address in externalized
+ * (quoted) form, so that the address parser can correctly extract the
+ * address from surrounding junk.
+ *
+ * XXX We have only one address parser, written according to the rules of
+ * RFC 822. That standard differs subtly from RFC 821.
*/
-#define STR(x) vstring_str(x)
-#define LEN(x) VSTRING_LEN(x)
-
if (msg_verbose)
- msg_info("%s: input: %s", myname, STR(buf));
- tree = tok822_parse(STR(buf));
+ msg_info("%s: input: %s", myname, STR(arg->vstrval));
+ tree = tok822_parse(STR(arg->vstrval));
+
+ /*
+ * Find trouble.
+ */
for (naddr = non_addr = 0, tp = tree; tp != 0; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
- if (++naddr == 1)
- tok822_internalize(buf, tp->head, TOK822_STR_DEFL);
- else if (naddr == 2)
- msg_warn("Multiple addresses from %s in %s command: %s",
- state->namaddr, state->where, STR(buf));
- } else if (tp->type != '<' && tp->type != '>') {
- if (++non_addr == 1)
- msg_warn("Non-RFC 821 syntax from %s in %s command: %s",
- state->namaddr, state->where, STR(buf));
+ addr = tp;
+ naddr += 1; /* count address forms */
+ } else if (tp->type == '<' || tp->type == '>') {
+ /* void */ ; /* ignore brackets */
+ } else {
+ non_addr += 1; /* count non-address forms */
}
}
+
+ /*
+ * Report trouble.
+ */
+ if (naddr != 1) { /* sorry, no can do */
+ msg_warn("Illegal address syntax from %s in %s command: %s",
+ state->namaddr, state->where, STR(arg->vstrval));
+ err = "501 Bad address syntax";
+ } else if (non_addr > 0) { /* it works with Sendmail... */
+ msg_warn("Illegal address syntax from %s in %s command: %s",
+ state->namaddr, state->where, STR(arg->vstrval));
+ err = (var_strict_rfc821_env ? "501 Bad address syntax" : 0);
+ }
+
+ /*
+ * Overwrite the input with the extracted address. This seems bad design,
+ * but we really are not going to use the original data anymore. What we
+ * start with is quoted (external) form, and what we need is unquoted
+ * (internal form).
+ */
+ if (addr)
+ tok822_internalize(arg->vstrval, addr->head, TOK822_STR_DEFL);
+ else
+ vstring_strcat(arg->vstrval, "");
+ arg->strval = STR(arg->vstrval);
+
+ /*
+ * Cleanup.
+ */
tok822_free_tree(tree);
if (msg_verbose)
- msg_info("%s: result: %s", myname, STR(buf));
- return (buf);
+ msg_info("%s: result: %s", myname, STR(arg->vstrval));
+ return (err);
}
/* mail_cmd - process MAIL command */
smtpd_chat_reply(state, "501 Syntax: MAIL FROM: <address>");
return (-1);
}
- argv[2].strval = STR(extract_addr(state, argv[2].vstrval));
+ if (argv[2].tokval == SMTPD_TOK_ERROR) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 Bad address syntax");
+ return (-1);
+ }
+ if ((err = extract_addr(state, argv + 2, PERMIT_EMPTY_ADDR)) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
for (narg = 3; narg < argc; narg++) {
arg = argv[narg].strval;
if (strcasecmp(arg, "BODY=8BITMIME") == 0
smtpd_chat_reply(state, "501 Syntax: RCPT TO: <address>");
return (-1);
}
- argv[2].strval = STR(extract_addr(state, argv[2].vstrval));
+ if (argv[2].tokval == SMTPD_TOK_ERROR) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "501 Bad address syntax");
+ return (-1);
+ }
+ if ((err = extract_addr(state, argv + 2, REJECT_EMPTY_ADDR)) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
if (var_smtpd_rcpt_limit && state->rcpt_count >= var_smtpd_rcpt_limit) {
state->error_mask |= MAIL_ERROR_POLICY;
smtpd_chat_reply(state, "452 Error: too many recipients");
* The SMTP standard (RFC 821) disallows unquoted special characters in
* the VRFY argument. Common practice violates the standard, however.
* Postfix accomodates common practice where it violates the standard.
+ *
+ * XXX Impedance mismatch! The SMTP command tokenizer preserves quoting,
+ * whereas the recipient restrictions checks expect unquoted (internal)
+ * 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.
*/
if (argc < 2) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: VRFY address");
return (-1);
}
- collapse_args(argc, argv);
+ if (argc > 2)
+ collapse_args(argc - 1, argv + 1);
+ if ((err = extract_addr(state, argv + 1, REJECT_EMPTY_ADDR)) != 0) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
if (SMTPD_STAND_ALONE(state) == 0)
err = smtpd_check_rcpt(state, argv[1].strval);
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,
+ VAR_STRICT_RFC821_ENV, DEF_STRICT_RFC821_ENV, &var_strict_rfc821_env,
0,
};
static CONFIG_STR_TABLE str_table[] = {
/* parameter. Reject the request otherwise.
/* The \fIrelay_domains_reject_code\fR configuration parameter specifies
/* the reject status code (default: 554).
+/* .IP permit_auth_destination
+/* Permit the request when the resolved recipient domain matches
+/* the local machine or the \fIrelay_domains\fR configuration parameter.
/* .IP reject_unauth_destination
/* Reject the request when the resolved recipient domain does not match
-/* the \fIrelay_domains\fR configuration parameter. Same error code as
-/* check_relay_domains.
+/* the local machine or the \fIrelay_domains\fR configuration parameter.
+/* Same error code as check_relay_domains.
/* .IP reject_unauth_pipelining
/* Reject the request when the client has already sent the next request
/* without being told that the server implements SMTP command pipelining.
* Permit if destination is local. XXX This must be generalized for
* per-domain user tables and for non-UNIX local delivery agents.
*/
- if (STR(reply.nexthop)[0] == 0
+ if (resolve_local(STR(reply.nexthop))
|| (domain = strrchr(STR(reply.recipient), '@')) == 0)
return (SMTPD_CHECK_OK);
domain += 1;
var_relay_code, reply_name, reply_class));
}
+/* permit_auth_destination - OK for message relaying */
+
+static int permit_auth_destination(SMTPD_STATE *state, char *recipient)
+{
+ char *myname = "permit_auth_destination";
+ char *domain;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Resolve the address.
+ */
+ canon_addr_internal(query, recipient);
+ resolve_clnt_query(STR(query), &reply);
+
+ /*
+ * Permit if destination is local. XXX This must be generalized for
+ * per-domain user tables and for non-UNIX local delivery agents.
+ */
+ if (resolve_local(STR(reply.nexthop))
+ || (domain = strrchr(STR(reply.recipient), '@')) == 0)
+ return (SMTPD_CHECK_OK);
+ domain += 1;
+
+ /*
+ * Permit if the destination matches the relay_domains list.
+ */
+ if (domain_list_match(relay_domains, domain))
+ return (SMTPD_CHECK_OK);
+
+ /*
+ * Skip when not matched
+ */
+ return (SMTPD_CHECK_DUNNO);
+}
+
/* reject_unauth_destination - FAIL for message relaying */
static int reject_unauth_destination(SMTPD_STATE *state, char *recipient)
* Pass if destination is local. XXX This must be generalized for
* per-domain user tables and for non-UNIX local delivery agents.
*/
- if (STR(reply.nexthop)[0] == 0
+ if (resolve_local(STR(reply.nexthop))
|| (domain = strrchr(STR(reply.recipient), '@')) == 0)
return (SMTPD_CHECK_DUNNO);
domain += 1;
* If the destination is local, it is acceptable, because we are
* supposedly MX for our own address.
*/
- if (STR(reply.nexthop)[0] == 0
+ if (resolve_local(STR(reply.nexthop))
|| (domain = strrchr(STR(reply.recipient), '@')) == 0)
return (SMTPD_CHECK_OK);
domain += 1;
/*
* Skip local destinations and non-DNS forms.
*/
- if (STR(reply.nexthop)[0] == 0
+ if (resolve_local(STR(reply.nexthop))
|| (domain = strrchr(STR(reply.recipient), '@')) == 0)
return (SMTPD_CHECK_DUNNO);
domain += 1;
return (SMTPD_CHECK_DUNNO);
domain += 1;
if (domain[0] == '#' || domain[0] == '[')
- if (STR(reply.nexthop)[0] != 0)
+ if (!resolve_local(STR(reply.nexthop)))
return (SMTPD_CHECK_DUNNO);
/*
char *saved_domains = mystrdup(var_maps_rbl_domains);
char *bp = saved_domains;
char *rbl_domain;
+ char *rbl_reason;
+ char *rbl_fodder;
+ DNS_RR *txt_list;
int reverse_len;
int dns_status = DNS_FAIL;
int i;
/*
* Report the result.
*/
- if (dns_status == DNS_OK)
+ if (dns_status == DNS_OK) {
+ if (dns_lookup(STR(query), T_TXT, 0, &txt_list,
+ (VSTRING *) 0, (VSTRING *) 0) == DNS_OK) {
+ rbl_fodder = ", reason: ";
+ rbl_reason = (char *) txt_list->data;
+ } else {
+ txt_list = 0;
+ rbl_fodder = rbl_reason = "";
+ }
result = smtpd_check_reject(state, MAIL_ERROR_POLICY,
- "%d Service unavailable; [%s] blocked using %s",
- var_maps_rbl_code, state->addr, rbl_domain);
- else
+ "%d Service unavailable; [%s] blocked using %s%s%s",
+ var_maps_rbl_code, state->addr, rbl_domain,
+ rbl_fodder, rbl_reason);
+ if (txt_list)
+ dns_rr_free(txt_list);
+ } else
result = SMTPD_CHECK_DUNNO;
/*
} else if (strcasecmp(name, PERMIT_MX_BACKUP) == 0) {
if (state->recipient)
status = permit_mx_backup(state, state->recipient);
+ } else if (strcasecmp(name, PERMIT_AUTH_DEST) == 0) {
+ if (state->recipient)
+ status = permit_auth_destination(state, state->recipient);
} else if (strcasecmp(name, REJECT_UNAUTH_DEST) == 0) {
if (state->recipient)
status = reject_unauth_destination(state, state->recipient);
rcpt wietse@no.recipient.domain
mail wietse@no.sender.domain
rcpt wietse@porcupine.org
+#
+# {permit_auth,reject_unauth}_destination
+#
+relay_domains foo.com,bar.com
+mail user@some.where
+recipient_restrictions permit_auth_destination,reject
+rcpt user@foo.org
+rcpt user@foo.com
+recipient_restrictions reject_unauth_destination,permit
+rcpt user@foo.org
+rcpt user@foo.com
>>> client spike.porcupine.org 168.100.189.2
OK
>>> client foo 127.0.0.2
-./smtpd_check: reject: CONNECT from foo[127.0.0.2]: 554 Service unavailable; [127.0.0.2] blocked using rbl.maps.vix.com; from=<foo@friend.bad.domain>
-554 Service unavailable; [127.0.0.2] blocked using rbl.maps.vix.com
+./smtpd_check: reject: CONNECT from foo[127.0.0.2]: 554 Service unavailable; [127.0.0.2] blocked using rbl.maps.vix.com, reason: EBlackholed - see <URL:http://mail-abuse.org/cgi-bin/lookup?127.0.0.2>; from=<foo@friend.bad.domain>
+554 Service unavailable; [127.0.0.2] blocked using rbl.maps.vix.com, reason: EBlackholed - see <URL:http://mail-abuse.org/cgi-bin/lookup?127.0.0.2>
>>> #
>>> # Hybrids
>>> #
>>> client spike.porcupine.org 168.100.189.2
OK
>>> client foo 127.0.0.2
-./smtpd_check: reject: CONNECT from foo[127.0.0.2]: 554 Service unavailable; [127.0.0.2] blocked using rbl.maps.vix.com; from=<foo@friend.bad.domain>
-554 Service unavailable; [127.0.0.2] blocked using rbl.maps.vix.com
+./smtpd_check: reject: CONNECT from foo[127.0.0.2]: 554 Service unavailable; [127.0.0.2] blocked using rbl.maps.vix.com, reason: EBlackholed - see <URL:http://mail-abuse.org/cgi-bin/lookup?127.0.0.2>; from=<foo@friend.bad.domain>
+554 Service unavailable; [127.0.0.2] blocked using rbl.maps.vix.com, reason: EBlackholed - see <URL:http://mail-abuse.org/cgi-bin/lookup?127.0.0.2>
>>> #
>>> # unknown sender/recipient domain
>>> #
>>> rcpt wietse@porcupine.org
./smtpd_check: reject: RCPT from foo[127.0.0.2]: 554 <wietse@no.sender.domain>: Sender address rejected: Domain not found; from=<wietse@no.sender.domain> to=<wietse@porcupine.org>
554 <wietse@no.sender.domain>: Sender address rejected: Domain not found
+>>> #
+>>> # {permit_auth,reject_unauth}_destination
+>>> #
+>>> relay_domains foo.com,bar.com
+OK
+>>> mail user@some.where
+OK
+>>> recipient_restrictions permit_auth_destination,reject
+OK
+>>> rcpt user@foo.org
+./smtpd_check: reject: RCPT from foo[127.0.0.2]: 554 <user@foo.org>: Recipient address rejected: Access denied; from=<user@some.where> to=<user@foo.org>
+554 <user@foo.org>: Recipient address rejected: Access denied
+>>> rcpt user@foo.com
+OK
+>>> recipient_restrictions reject_unauth_destination,permit
+OK
+>>> rcpt user@foo.org
+./smtpd_check: reject: RCPT from foo[127.0.0.2]: 554 <user@foo.org>: Relay access denied; from=<user@some.where> to=<user@foo.org>
+554 <user@foo.org>: Relay access denied
+>>> rcpt user@foo.com
+OK
/* It understands backslash escapes, white space, quoted strings,
/* and addresses (including quoted text) enclosed by < and >.
/* The input is broken up into tokens by whitespace, except for
-/* whitespace that is protected by quites etc.
+/* whitespace that is protected by quotes etc.
/* LICENSE
/* .ad
/* .fi
static char *smtp_quoted(char *cp, SMTPD_TOKEN *arg, int start, int last)
{
static VSTRING *stack;
+ int wanted;
int c;
+ /*
+ * Parser stack. `ch' is always the most-recently entered character.
+ */
+#define ENTER_CHAR(buf, ch) VSTRING_ADDCH(buf, ch);
+#define LEAVE_CHAR(buf, ch) { \
+ vstring_truncate(buf, VSTRING_LEN(buf) - 1); \
+ ch = vstring_end(buf)[-1]; \
+ }
+
if (stack == 0)
stack = vstring_alloc(1);
VSTRING_RESET(stack);
- VSTRING_ADDCH(stack, last);
+ ENTER_CHAR(stack, wanted = last);
VSTRING_ADDCH(arg->vstrval, start);
for (;;) {
break;
cp++;
VSTRING_ADDCH(arg->vstrval, c);
- if (c == vstring_end(stack)[-1]) { /* closing quote etc. */
- vstring_truncate(stack, VSTRING_LEN(stack) - 1);
- if (VSTRING_LEN(stack) == 0)
+ if (c == '\\') { /* parse escape sequence */
+ if ((c = *cp) == 0)
break;
- } else {
- if (c == '\\') { /* parse escape sequence */
- if ((c = *cp) == 0)
- break;
- cp++;
- VSTRING_ADDCH(arg->vstrval, c);
- } else if (c == '"') {
- VSTRING_ADDCH(stack, '"'); /* highest precedence */
- } else if (c == '(' && vstring_end(stack)[-1] != '"') {
- VSTRING_ADDCH(stack, ')'); /* medium precedence */
- } else if (c == '<' && vstring_end(stack)[-1] == '>') {
- VSTRING_ADDCH(stack, '>'); /* lowest precedence */
- }
+ cp++;
+ VSTRING_ADDCH(arg->vstrval, c);
+ } else if (c == wanted) { /* closing quote etc. */
+ if (VSTRING_LEN(stack) == 1)
+ return (cp);
+ LEAVE_CHAR(stack, wanted);
+ } else if (c == '"') {
+ ENTER_CHAR(stack, wanted = '"'); /* highest precedence */
+ } else if (c == '<' && wanted == '>') {
+ ENTER_CHAR(stack, wanted = '>'); /* lowest precedence */
}
}
+ arg->tokval = SMTPD_TOK_ERROR; /* missing end */
return (cp);
}
int c;
VSTRING_RESET(arg->vstrval);
+ arg->tokval = SMTPD_TOK_OTHER;
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
-#define STREQ(x,y,l) ((x)[0] == (x)[0] && strncasecmp((x), (y), (l)) == 0)
+#define STREQ(x,y,l) (strncasecmp((x), (y), (l)) == 0)
- while ((c = *cp) != 0) {
+ for (;;) {
+ if ((c = *cp) == 0) /* end of input */
+ break;
cp++;
if (ISSPACE(c)) { /* whitespace, skip */
while (*cp && ISSPACE(*cp))
break;
} else if (c == '<') { /* <stuff> */
cp = smtp_quoted(cp, arg, c, '>');
- } else if (c == '[') { /* [stuff] */
- cp = smtp_quoted(cp, arg, c, ']');
} else if (c == '"') { /* "stuff" */
cp = smtp_quoted(cp, arg, c, c);
} else if (c == ':') { /* this is gross, but... */
VSTRING_ADDCH(arg->vstrval, c);
}
}
- if (LEN(arg->vstrval) == 0) /* no token found */
+ if (LEN(arg->vstrval) <= 0) /* no token found */
return (0);
VSTRING_TERMINATE(arg->vstrval);
arg->strval = vstring_str(arg->vstrval);
if (isatty(STDIN_FILENO))
vstream_printf("enter SMTPD command: ");
vstream_fflush(VSTREAM_OUT);
- if (vstring_fgets(vp, VSTREAM_IN) == 0)
+ if (vstring_get_nonl(vp, VSTREAM_IN) == VSTREAM_EOF)
break;
if (*vstring_str(vp) == '#')
continue;
if (!isatty(STDIN_FILENO))
- vstream_fputs(vstring_str(vp), VSTREAM_OUT);
+ vstream_printf("%s\n", vstring_str(vp));
tok_argc = smtpd_token(vstring_str(vp), &tok_argv);
- for (i = 0; i < tok_argc; i++)
+ for (i = 0; i < tok_argc; i++) {
+ vstream_printf("Token type: %s\n",
+ tok_argv[i].tokval == SMTPD_TOK_OTHER ? "other" :
+ tok_argv[i].tokval == SMTPD_TOK_ERROR ? "error" :
+ "unknown");
vstream_printf("Token value: %s\n", tok_argv[i].strval);
+ }
}
exit(0);
}
* External interface.
*/
typedef struct SMTPD_TOKEN {
+ int tokval;
char *strval;
VSTRING *vstrval;
} SMTPD_TOKEN;
mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )>
mail from:"wietse venema"@porcupine.org
mail from:wietse\ venema@porcupine.org
+mail to:<"wietse venema>
+mail to:<wietse@[stuff>
+mail to:<wietse@["stuff]>
mail from:<wietse@porcupine.org>
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: <wietse@porcupine.org>
mail from:<"wietse venema"@porcupine.org>
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: <"wietse venema"@porcupine.org>
mail from:wietse@porcupine.org
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: wietse@porcupine.org
mail from:<wietse @ porcupine.org>
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: <wietse @ porcupine.org>
mail from:<"wietse venema"@porcupine.org ("wietse ) venema")>
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: <"wietse venema"@porcupine.org ("wietse ) venema")>
mail from:<"wietse venema" <wietse@porcupine.org>>
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: <"wietse venema" <wietse@porcupine.org>>
mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )>
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: <"wietse venema"@porcupine.org ( ("wietse ) venema") )>
mail from:"wietse venema"@porcupine.org
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: "wietse venema"@porcupine.org
mail from:wietse\ venema@porcupine.org
+Token type: other
Token value: mail
+Token type: other
Token value: from:
+Token type: other
Token value: wietse venema@porcupine.org
+mail to:<"wietse venema>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: error
+Token value: <"wietse venema>
+mail to:<wietse@[stuff>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: other
+Token value: <wietse@[stuff>
+mail to:<wietse@["stuff]>
+Token type: other
+Token value: mail
+Token type: other
+Token value: to:
+Token type: error
+Token value: <wietse@["stuff]>
}
/*
- * Non-local delivery: if no transport is specified, assume the transport
- * specified in var_def_transport. If no mail relay is specified in
- * var_relayhost, forward to the domain's mail exchanger.
+ * The transport map, if specified, overrides default routing.
*/
- if (domain != 0) {
- if (*var_transport_maps == 0
- || (tok822_internalize(addr_buf, domain->next, TOK822_STR_DEFL),
- transport_lookup(STR(addr_buf), channel, nexthop) == 0)) {
+ if (*var_transport_maps == 0
+ || (tok822_internalize(addr_buf, domain->next, TOK822_STR_DEFL),
+ transport_lookup(STR(addr_buf), channel, nexthop)) == 0) {
+
+ /*
+ * Non-local delivery. Use the default transport specified in
+ * var_def_transport. If no default mail relay is specified in
+ * var_relayhost, forward to the domain's mail exchanger.
+ */
+ if (domain != 0) {
vstring_strcpy(channel, var_def_transport);
if (*var_relayhost)
vstring_strcpy(nexthop, var_relayhost);
else
tok822_internalize(nexthop, domain->next, TOK822_STR_DEFL);
}
- tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL);
- }
- /*
- * Local delivery: if no domain was specified, assume the local machine.
- * See above for what happens with an empty localpart.
- */
- else {
- vstring_strcpy(channel, MAIL_SERVICE_LOCAL);
- vstring_strcpy(nexthop, "");
- if (saved_domain) {
- tok822_sub_append(tree, saved_domain);
- saved_domain = 0;
- } else {
- tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
- tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ /*
+ * Local delivery. Use the default transport and next-hop hostname.
+ * If no domain was specified, assume the local machine. See above
+ * for what happens with an empty localpart.
+ */
+ else {
+ vstring_strcpy(channel, MAIL_SERVICE_LOCAL);
+ vstring_strcpy(nexthop, var_myhostname);
+ if (saved_domain) {
+ tok822_sub_append(tree, saved_domain);
+ saved_domain = 0;
+ } else {
+ tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ }
}
- tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL);
}
+ tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL);
/*
* Clean up.