]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
snapshot-19991115
authorWietse Venema <wietse@porcupine.org>
Mon, 15 Nov 1999 05:00:00 +0000 (00:00 -0500)
committerWietse Venema <wietse@porcupine.org>
Thu, 17 Jan 2013 23:09:46 +0000 (18:09 -0500)
24 files changed:
postfix/HISTORY
postfix/INSTALL.sh
postfix/Makefile.in
postfix/conf/sample-smtpd.cf
postfix/dns/dns_lookup.c
postfix/dns/snd.h [deleted file]
postfix/dns/test_dns_lookup.c
postfix/global/mail_params.h
postfix/global/mail_version.h
postfix/html/uce.html
postfix/local/recipient.c
postfix/local/resolve.c
postfix/postconf/extract.awk
postfix/qmgr/qmgr_message.c
postfix/smtpd/smtpd.c
postfix/smtpd/smtpd_check.c
postfix/smtpd/smtpd_check.in2
postfix/smtpd/smtpd_check.ref
postfix/smtpd/smtpd_check.ref2
postfix/smtpd/smtpd_token.c
postfix/smtpd/smtpd_token.h
postfix/smtpd/smtpd_token.in
postfix/smtpd/smtpd_token.ref
postfix/trivial-rewrite/resolve.c

index 2f8367cb26b0681c8662d62bf65cbca5d4b1a944..9996bd4345538d724623c51796d91a41ad537988 100644 (file)
@@ -3170,8 +3170,56 @@ Apologies for any names omitted.
        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.
index 0b740f319cd4071c7ce24d8f92b1db5261384ae2..6684494e69c296b8e188cbeb989e2f6cad38205e 100644 (file)
@@ -6,6 +6,9 @@
 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.
@@ -138,7 +141,14 @@ do
    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
@@ -146,7 +156,7 @@ bin/postmap -c ./conf -q "$owner" unix:passwd.byname >/dev/null || {
 
 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
@@ -239,6 +249,16 @@ compare_or_replace a+x,go-w $postfix_script $config_directory/postfix-script ||
 
 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
index 9c14f10a560b01ebeb1be3340f34f6e4e8e16a4c..3e685f7367c7ead99c44afceb94791ebbd9052f0 100644 (file)
@@ -22,6 +22,9 @@ update printfck:
 
 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; \
index 521e9c45a0294fef8a609f686dd65d6de368ab5b..b5cb6450893881a5ca9b2f018fdd5ec0e27d4f6b 100644 (file)
@@ -191,8 +191,10 @@ smtpd_sender_restrictions =
 #   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.
index 90344cd5272a4ed476416bc854a53fd21ae39902..e548f2c0fa03fcbdd1741319f20a8b065708597c 100644 (file)
 #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. */
 
@@ -246,8 +248,13 @@ static DNS_RR *dns_get_rr(DNS_REPLY *reply, unsigned char *pos,
                                  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);
@@ -287,6 +294,14 @@ static DNS_RR *dns_get_rr(DNS_REPLY *reply, unsigned char *pos,
        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));
 }
diff --git a/postfix/dns/snd.h b/postfix/dns/snd.h
deleted file mode 100644 (file)
index e69de29..0000000
index 5f1a9e63ea75a46cff7c0091a1bde55feaf8c442..aabdd98ec44e5e7eb383953680d819d4bb232671 100644 (file)
@@ -55,6 +55,7 @@ static void print_rr(DNS_RR *rr)
        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:
index 6561a2d36e1d202cf9e31188cfeea97544a104f0..53c2f1800cd86d923f4b66361a1989a95020605c 100644 (file)
@@ -208,11 +208,11 @@ extern char *var_db_type;
 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.
@@ -773,6 +773,7 @@ extern int var_non_fqdn_code;
 #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"
index 287086ab2edf4b801b405be7f843c78ecd2596a7..070732ad2d4abc923d796d675dac1065aa19ff4a 100644 (file)
@@ -15,7 +15,7 @@
   * 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
index 84abddfbcf33daca847141261629d3b38a24ad49..fdabb0c01ebc855e5e5e90e482df91c3a5992f99 100644 (file)
@@ -537,20 +537,30 @@ reject_unauth_destination</b>
 
 <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>
 
index f962c7608344048546edbe9a5979c86192e686b5..3a6a2991f761477e8f360e935b20b68c83e97c74 100644 (file)
@@ -221,6 +221,13 @@ int     deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr)
        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.
      */
index 4c410461bb47e7d24b4007bbd4480afa4f3a748b..b6b1837b67d19d996ac342063d6f95c949be510b 100644 (file)
@@ -62,6 +62,7 @@
 #include <rewrite_clnt.h>
 #include <tok822.h>
 #include <mail_params.h>
+#include <resolve_local.h>
 
 /* Application-specific. */
 
@@ -138,7 +139,7 @@ int     deliver_resolve_tree(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr
      * 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);
index 9d3a33635e6696e2d6f2cb87a72d9120f546b9ee..43cb9604c023cbde54d7822e6fe489b51ff603c6 100644 (file)
@@ -18,3 +18,7 @@
        print | "sort -u >bool_table.h" 
     }
 }
+
+# Workaround for broken gawk versions.
+
+END { exit(0); }
index a5bfaff2cdefe0faac1440ed2ab88347a9e8bc51..522a0dcd9f3c20415b59177862be4fcb5daf1795 100644 (file)
 #include <deliver_completed.h>
 #include <mail_addr_find.h>
 #include <opened.h>
+#include <resolve_local.h>
 
 /* Client stubs. */
 
@@ -476,7 +477,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
        /*
         * 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));
@@ -515,7 +516,7 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
         * 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
index 32468aa8dfef82dae8a6769eac8abda4111dc6ba..a5f65544ef608353f05deb4e11e0d02afd597d90 100644 (file)
 /*     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
@@ -284,6 +290,7 @@ char   *var_always_bcc;
 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
@@ -291,17 +298,23 @@ char   *var_rest_classes;
   */
 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 */
@@ -320,7 +333,8 @@ static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
        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) {
@@ -349,7 +363,8 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
        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) {
@@ -427,46 +442,94 @@ static void mail_open_stream(SMTPD_STATE *state)
 
 /* 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 */
@@ -498,7 +561,16 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
        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
@@ -596,7 +668,16 @@ static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
        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");
@@ -845,13 +926,25 @@ static int vrfy_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
      * 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);
 
@@ -1216,6 +1309,7 @@ int     main(int argc, char **argv)
     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[] = {
index 4cc27582e1db6bbc9951525f0026fd665ef5d384..8febad8c2863dbe54c8c3aadfc2a62035c2d894c 100644 (file)
 /*     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.
@@ -709,7 +712,7 @@ static int check_relay_domains(SMTPD_STATE *state, char *recipient,
      * 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;
@@ -728,6 +731,43 @@ static int check_relay_domains(SMTPD_STATE *state, char *recipient,
                               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)
@@ -748,7 +788,7 @@ 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;
@@ -850,7 +890,7 @@ static int permit_mx_backup(SMTPD_STATE *unused_state, const char *recipient)
      * 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;
@@ -984,7 +1024,7 @@ static int reject_unknown_address(SMTPD_STATE *state, char *addr,
     /*
      * 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;
@@ -1024,7 +1064,7 @@ static int permit_rcpt_map(char *table, char *reply_name)
        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);
 
     /*
@@ -1338,6 +1378,9 @@ static int reject_maps_rbl(SMTPD_STATE *state)
     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;
@@ -1371,11 +1414,22 @@ static int reject_maps_rbl(SMTPD_STATE *state)
     /*
      * 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;
 
     /*
@@ -1549,6 +1603,9 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
        } 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);
index ba539f00ee0c1700a6ebf32470db972be691423c..68d1d31a42453ad345f3a9042e1de2b4d163b3da 100644 (file)
@@ -94,3 +94,14 @@ rcpt wietse@porcupine.org
 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
index 9c7727171ef5116e465ae0100524d16e4323f523..1ed6fca90777876534dd4ad9ad0075e7e75b5d06 100644 (file)
@@ -182,8 +182,8 @@ OK
 >>> 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
 >>> #
index f6cb0c849c368b5c0152745869290e1db0ca5656..cbb4a3280302ada0824efc42352733a574042ea7 100644 (file)
@@ -172,8 +172,8 @@ OK
 >>> 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
 >>> #
@@ -193,3 +193,24 @@ OK
 >>> 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
index 7de161fa6d89c5aaf97043c060f9c4e7f86d8aef..ed0f5c360d1a85d7c61ebd80721d1d544bdc7c6c 100644 (file)
@@ -31,7 +31,7 @@
 /*     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 (;;) {
@@ -77,25 +87,22 @@ static char *smtp_quoted(char *cp, SMTPD_TOKEN *arg, int start, int last)
            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);
 }
 
@@ -106,12 +113,15 @@ static char *smtp_next_token(char *cp, SMTPD_TOKEN *arg)
     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))
@@ -120,8 +130,6 @@ static char *smtp_next_token(char *cp, SMTPD_TOKEN *arg)
                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... */
@@ -138,7 +146,7 @@ static char *smtp_next_token(char *cp, SMTPD_TOKEN *arg)
            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);
@@ -197,15 +205,20 @@ main(int unused_argc, char **unused_argv)
        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);
 }
index 841ddb3ce3d460471099f95e914315eced1567e3..88489fa220b242b7fbd79e5f780718df11b4f8c6 100644 (file)
@@ -17,6 +17,7 @@
   * External interface.
   */
 typedef struct SMTPD_TOKEN {
+    int     tokval;
     char   *strval;
     VSTRING *vstrval;
 } SMTPD_TOKEN;
index 3ef8f7596bb91a140f7bbeb12cd48ef8ec1e066b..d67d5d04d9994c47e13cdee1e031826dd339d189 100644 (file)
@@ -7,3 +7,6 @@ mail from:<"wietse venema" <wietse@porcupine.org>>
 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]>
index 5d962fbf64cd63706681599878c2a52e873bd181..bba391549b02fd40f37f78c12fbb226319339896 100644 (file)
@@ -1,36 +1,84 @@
 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]>
index 5f185882cb0f67c798152fba7781bcf6557e83b3..f298da4795ddac5974e3dab0a760e5162e809537 100644 (file)
@@ -151,39 +151,43 @@ void    resolve_addr(char *addr, VSTRING *channel, VSTRING *nexthop,
     }
 
     /*
-     * 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.