]> Pileus Git - ~andy/fetchmail/blobdiff - sink.c
Back out the InterChange workaround.
[~andy/fetchmail] / sink.c
diff --git a/sink.c b/sink.c
index 5f2aefac98b9551b0c756fbf92496c7d5261c6a4..e5b49955112faa7813906c411e56f290c1917b6b 100644 (file)
--- a/sink.c
+++ b/sink.c
@@ -3,7 +3,7 @@
  *
  * The interface of this module (open_sink(), stuff_line(), close_sink(),
  * release_sink()) seals off the delivery logic from the protocol machine,
- * so the latter won't have to care whether it's shipping to an SMTP
+ * so the latter won't have to care whether it's shipping to an [SL]MTP
  * listener daemon or an MDA pipe.
  *
  * Copyright 1998 by Eric S. Raymond
@@ -15,6 +15,7 @@
 #include  <errno.h>
 #include  <string.h>
 #include  <signal.h>
+#include  <time.h>
 #ifdef HAVE_MEMORY_H
 #include  <memory.h>
 #endif /* HAVE_MEMORY_H */
 #if defined(HAVE_UNISTD_H)
 #include <unistd.h>
 #endif
-#if defined(HAVE_ALLOCA_H)
-#include <alloca.h>
+#if defined(HAVE_STDARG_H)
+#include  <stdarg.h>
 #else
-#ifdef _AIX
- #pragma alloca
-#endif
+#include  <varargs.h>
 #endif
+#include  <ctype.h>
 
 #include  "fetchmail.h"
 #include  "socket.h"
 #include  "smtp.h"
+#include  "i18n.h"
 
 /* BSD portability hack...I know, this is an ugly place to put it */
 #if !defined(SIGCHLD) && defined(SIGCLD)
 #define SIGCHLD        SIGCLD
 #endif
 
-#if INET6
-#define        SMTP_PORT       "smtp"  /* standard SMTP service port */
-#else /* INET6 */
-#define        SMTP_PORT       25      /* standard SMTP service port */
-#endif /* INET6 */
+/* makes the open_sink()/close_sink() pair non-reentrant */
+static int lmtp_responses;
 
 static int smtp_open(struct query *ctl)
 /* try to open a socket to the appropriate SMTP server for this query */ 
 {
     /* maybe it's time to close the socket in order to force delivery */
-    if (NUM_NONZERO(ctl->batchlimit) && (ctl->smtp_socket != -1) && batchcount++ == ctl->batchlimit)
+    if (NUM_NONZERO(ctl->batchlimit) && (ctl->smtp_socket != -1) && ++batchcount == ctl->batchlimit)
     {
-       close(ctl->smtp_socket);
+       SockClose(ctl->smtp_socket);
        ctl->smtp_socket = -1;
        batchcount = 0;
     }
@@ -80,7 +78,7 @@ static int smtp_open(struct query *ctl)
         * What it will affect is the listener's logging.
         */
        struct idlist   *idp;
-       char *id_me = run.invisible ? ctl->server.truename : fetchmailhost;
+       const char *id_me = run.invisible ? ctl->server.truename : fetchmailhost;
        int oldphase = phase;
 
        errno = 0;
@@ -96,12 +94,14 @@ static int smtp_open(struct query *ctl)
        set_timeout(ctl->server.timeout);
        for (idp = ctl->smtphunt; idp; idp = idp->next)
        {
-           char        *cp, *parsed_host = alloca(strlen(idp->id) + 1);
-#ifdef INET6 
+           char        *cp, *parsed_host;
+#ifdef INET6_ENABLE 
            char        *portnum = SMTP_PORT;
 #else
            int         portnum = SMTP_PORT;
-#endif /* INET6 */
+#endif /* INET6_ENABLE */
+
+           xalloca(parsed_host, char *, strlen(idp->id) + 1);
 
            ctl->smtphost = idp->id;  /* remember last host tried. */
 
@@ -109,16 +109,20 @@ static int smtp_open(struct query *ctl)
            if ((cp = strrchr(parsed_host, '/')))
            {
                *cp++ = 0;
-#ifdef INET6 
+#ifdef INET6_ENABLE 
                portnum = cp;
 #else
                portnum = atoi(cp);
-#endif /* INET6 */
+#endif /* INET6_ENABLE */
            }
 
-           if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,NULL)) == -1)
+           if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,NULL,
+                                            ctl->server.plugout)) == -1)
                continue;
 
+           /* are we doing SMTP or LMTP? */
+           SMTP_setmode(ctl->listener);
+
            /* first, probe for ESMTP */
            if (SMTP_ok(ctl->smtp_socket) == SM_OK &&
                    SMTP_ehlo(ctl->smtp_socket, id_me,
@@ -133,7 +137,8 @@ static int smtp_open(struct query *ctl)
            ctl->smtp_socket = -1;
 
            /* if opening for ESMTP failed, try SMTP */
-           if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,NULL)) == -1)
+           if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,NULL,
+                                            ctl->server.plugout)) == -1)
                continue;
 
            if (SMTP_ok(ctl->smtp_socket) == SM_OK && 
@@ -156,15 +161,15 @@ static int smtp_open(struct query *ctl)
      */
     ctl->destaddr = ctl->smtpaddress ? ctl->smtpaddress : ( ctl->smtphost ? ctl->smtphost : "localhost");
 
-    if (outlevel >= O_VERBOSE && ctl->smtp_socket != -1)
-       error(0, 0, "forwarding to %s", ctl->smtphost);
+    if (outlevel >= O_DEBUG && ctl->smtp_socket != -1)
+       report(stdout, _("forwarding to %s\n"), ctl->smtphost);
 
     return(ctl->smtp_socket);
 }
 
 /* these are shared by open_sink and stuffline */
 static FILE *sinkfp;
-static RETSIGTYPE (*sigchld)();
+static RETSIGTYPE (*sigchld)(int);
 
 int stuffline(struct query *ctl, char *buf)
 /* ship a line to the given control block's output sink (SMTP server or MDA) */
@@ -199,6 +204,7 @@ int stuffline(struct query *ctl, char *buf)
      * decorated any . lines it sends back up.
      */
     if (*buf == '.')
+    {
        if (ctl->server.base_protocol->delimited)       /* server has already byte-stuffed */
        {
            if (ctl->mda)
@@ -213,6 +219,7 @@ int stuffline(struct query *ctl, char *buf)
            else
                /* leave it alone */;
        }
+    }
 
     /* we may need to strip carriage returns */
     if (ctl->stripcr)
@@ -227,7 +234,7 @@ int stuffline(struct query *ctl, char *buf)
     }
 
     n = 0;
-    if (ctl->mda)
+    if (ctl->mda || ctl->bsmtp)
        n = fwrite(buf, 1, last - buf, sinkfp);
     else if (ctl->smtp_socket != -1)
        n = SockWrite(ctl->smtp_socket, buf, last - buf);
@@ -240,17 +247,243 @@ int stuffline(struct query *ctl, char *buf)
 static void sanitize(char *s)
 /* replace unsafe shellchars by an _ */
 {
-    static char *ok_chars = " 1234567890!@%-_=+:,./abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    const static char *ok_chars = " 1234567890!@%-_=+:,./abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
     char *cp;
 
     for (cp = s; *(cp += strspn(cp, ok_chars)); /* NO INCREMENT */)
        *cp = '_';
 }
 
-int open_sink(struct query *ctl, 
-             char *return_path,
-             struct idlist *xmit_names,
-             long reallen,
+static int send_bouncemail(struct query *ctl, struct msgblk *msg,
+                          int userclass, char *message,
+                          int nerrors, char *errors[])
+/* bounce back an error report a la RFC 1892 */
+{
+    char daemon_name[18 + HOSTLEN] = "FETCHMAIL-DAEMON@";
+    char boundary[BUFSIZ], *ts, *bounce_to;
+    int sock;
+
+    /* don't bounce  in reply to undeliverable bounces */
+    if (!msg->return_path[0] || strcmp(msg->return_path, "<>") == 0)
+       return(FALSE);
+
+    bounce_to = (run.bouncemail ? msg->return_path : run.postmaster);
+
+    SMTP_setmode(SMTP_MODE);
+
+    strcat(daemon_name, fetchmailhost);
+
+    /* we need only SMTP for this purpose */
+    if ((sock = SockOpen("localhost", SMTP_PORT, NULL, NULL)) == -1
+               || SMTP_ok(sock) != SM_OK 
+               || SMTP_helo(sock, "localhost") != SM_OK
+               || SMTP_from(sock, daemon_name, (char *)NULL) != SM_OK
+               || SMTP_rcpt(sock, bounce_to) != SM_OK
+               || SMTP_data(sock) != SM_OK)
+       return(FALSE);
+
+    /* our first duty is to keep the sacred foo counters turning... */
+    sprintf(boundary, 
+           "foo-mani-padme-hum-%d-%d-%ld", 
+           (int)getpid(), (int)getppid(), time((time_t *)NULL));
+
+    ts = rfc822timestamp();
+
+    if (outlevel >= O_VERBOSE)
+       report(stdout, "SMTP: (bounce-message body)\n");
+    else
+       /* this will usually go to sylog... */
+       report(stderr, "mail from %s bounced to %s\n",
+              daemon_name, bounce_to);
+
+    /* bouncemail headers */
+    SockPrintf(sock, "Return-Path: <>\r\n");
+    SockPrintf(sock, "From: %s\r\n", daemon_name);
+    SockPrintf(sock, "To: %s\r\n", bounce_to);
+    SockPrintf(sock, "MIME-Version: 1.0\r\n");
+    SockPrintf(sock, "Content-Type: multipart/report; report-type=delivery-status;\r\n\tboundary=\"%s\"\r\n", boundary);
+    SockPrintf(sock, "\r\n");
+
+    /* RFC1892 part 1 -- human-readable message */
+    SockPrintf(sock, "--%s\r\n", boundary); 
+    SockPrintf(sock,"Content-Type: text/plain\r\n");
+    SockPrintf(sock, "\r\n");
+    SockWrite(sock, message, strlen(message));
+    SockPrintf(sock, "\r\n");
+    SockPrintf(sock, "\r\n");
+
+    if (nerrors)
+    {
+       struct idlist   *idp;
+       int             nusers;
+       
+       /* RFC1892 part 2 -- machine-readable responses */
+       SockPrintf(sock, "--%s\r\n", boundary); 
+       SockPrintf(sock,"Content-Type: message/delivery-status\r\n");
+       SockPrintf(sock, "\r\n");
+       SockPrintf(sock, "Reporting-MTA: dns; %s\r\n", fetchmailhost);
+
+       nusers = 0;
+       for (idp = msg->recipients; idp; idp = idp->next)
+           if (idp->val.status.mark == userclass)
+           {
+               char    *error;
+               /* Minimum RFC1894 compliance + Diagnostic-Code field */
+               SockPrintf(sock, "\r\n");
+               SockPrintf(sock, "Final-Recipient: rfc822; %s\r\n", idp->id);
+               SockPrintf(sock, "Last-Attempt-Date: %s\r\n", ts);
+               SockPrintf(sock, "Action: failed\r\n");
+
+               if (nerrors == 1)
+                   /* one error applies to all users */
+                   error = errors[0];
+               else if (nerrors > nusers)
+               {
+                   SockPrintf(sock, "Internal error: SMTP error count doesn't match number of recipients.\r\n");
+                   break;
+               }
+               else
+                   /* errors correspond 1-1 to selected users */
+                   error = errors[nusers++];
+               
+               if (strlen(error) > 9 && isdigit(error[4])
+                       && error[5] == '.' && isdigit(error[6])
+                       && error[7] == '.' && isdigit(error[8]))
+                   /* Enhanced status code available, use it */
+                   SockPrintf(sock, "Status: %5.5s\r\n", &(error[4]));
+               else
+                   /* Enhanced status code not available, fake one */
+                   SockPrintf(sock, "Status: %c.0.0\r\n", error[0]);
+               SockPrintf(sock, "Diagnostic-Code: %s\r\n", error);
+           }
+       SockPrintf(sock, "\r\n");
+    }
+
+    /* RFC1892 part 3 -- headers of undelivered message */
+    SockPrintf(sock, "--%s\r\n", boundary); 
+    SockPrintf(sock, "Content-Type: text/rfc822-headers\r\n");
+    SockPrintf(sock, "\r\n");
+    SockWrite(sock, msg->headers, strlen(msg->headers));
+    SockPrintf(sock, "\r\n");
+    SockPrintf(sock, "--%s--\r\n", boundary); 
+
+    if (SMTP_eom(sock) != SM_OK || SMTP_quit(sock))
+       return(FALSE);
+
+    SockClose(sock);
+
+    return(TRUE);
+}
+
+static int handle_smtp_report(struct query *ctl, struct msgblk *msg)
+/* handle SMTP errors based on the content of SMTP_response */
+/* return of PS_REFUSED deletes mail from the server; PS_TRANSIENT keeps it */
+{
+    int smtperr = atoi(smtp_response);
+    char *responses[1];
+
+    xalloca(responses[0], char *, strlen(smtp_response)+1);
+    strcpy(responses[0], smtp_response);
+
+    SMTP_rset(ctl->smtp_socket);    /* stay on the safe site */
+
+    if (outlevel >= O_DEBUG)
+       report(stdout, "Saved error is still %d\n", smtperr);
+
+    /*
+     * Note: send_bouncemail message strings are not made subject
+     * to gettext translation because (a) they're going to be 
+     * embedded in a text/plain 7bit part, and (b) they're
+     * going to be associated with listener error-response
+     * messages, which are probably in English (none of the
+     * MTAs I know about are internationalized).
+     */
+    if (str_find(&ctl->antispam, smtperr))
+    {
+       /*
+        * SMTP listener explicitly refuses to deliver mail
+        * coming from this address, probably due to an
+        * anti-spam domain exclusion.  Respect this.  Don't
+        * try to ship the message, and don't prevent it from
+        * being deleted.  Default values:
+        *
+        * 571 = sendmail's "unsolicited email refused"
+        * 550 = exim's new antispam response (temporary)
+        * 501 = exim's old antispam response
+        * 554 = Postfix antispam response.
+        *
+        */
+       send_bouncemail(ctl, msg, XMIT_ACCEPT,
+                       "Our spam filter rejected this transaction.\r\n", 
+                       1, responses);
+       return(PS_REFUSED);
+    }
+
+    /*
+     * Suppress error message only if the response specifically 
+     * meant `excluded for policy reasons'.  We *should* see
+     * an error when the return code is less specific.
+     */
+    if (smtperr >= 400)
+       report(stderr, _("%cMTP error: %s\n"), 
+             ctl->listener,
+             smtp_response);
+
+    switch (smtperr)
+    {
+    case 552: /* message exceeds fixed maximum message size */
+       /*
+        * Permanent no-go condition on the
+        * ESMTP server.  Don't try to ship the message, 
+        * and allow it to be deleted.
+        */
+       send_bouncemail(ctl, msg, XMIT_ACCEPT,
+                       "This message was too large (SMTP error 552).\r\n", 
+                       1, responses);
+       return(run.bouncemail ? PS_REFUSED : PS_TRANSIENT);
+  
+    case 553: /* invalid sending domain */
+       /*
+        * These latter days 553 usually means a spammer is trying to
+        * cover his tracks.  We never bouncemail on these, because 
+        * (a) the return address is invalid by definition, and 
+        * (b) we wouldn't want spammers to get confirmation that
+        * this address is live, anyway.
+        */
+       send_bouncemail(ctl, msg, XMIT_ACCEPT,
+                       "Invalid address in MAIL FROM (SMTP error 553).\r\n", 
+                       1, responses);
+       return(PS_REFUSED);
+
+    default:
+       /* bounce non-transient errors back to the sender */
+       if (smtperr >= 500 && smtperr <= 599)
+           if (send_bouncemail(ctl, msg, XMIT_ACCEPT,
+                               "General SMTP/ESMTP error.\r\n", 
+                               1, responses))
+               return(run.bouncemail ? PS_REFUSED : PS_TRANSIENT);
+       /*
+        * We're going to end up here on 4xx errors, like:
+        *
+        * 451: temporarily unable to identify sender (exim)
+        * 452: temporary out-of-queue-space condition on the ESMTP server.
+        *
+        * These are temporary errors.  Don't try to ship the message,
+        * and suppress deletion so it can be retried on a future
+        * retrieval cycle.
+        *
+        * Bouncemail *might* be appropriate here as a delay
+        * notification (note; if we ever add this, we must make
+        * sure the RFC1894 Action field is "delayed" rather thwn
+        * "failed").  But it's not really necessary because
+        * these are not actual failures, we're very likely to be
+        * able to recover on the next cycle.
+        */
+       return(PS_TRANSIENT);
+    }
+}
+
+int open_sink(struct query *ctl, struct msgblk *msg,
              int *good_addresses, int *bad_addresses)
 /* set up sinkfp to be an input sink we can ship a message to */
 {
@@ -258,14 +491,63 @@ int open_sink(struct query *ctl,
 
     *bad_addresses = *good_addresses = 0;
 
-    if (ctl->mda)              /* we have a declared MDA */
+    if (ctl->bsmtp)            /* dump to a BSMTP batch file */
+    {
+       if (strcmp(ctl->bsmtp, "-") == 0)
+           sinkfp = stdout;
+       else
+           sinkfp = fopen(ctl->bsmtp, "a");
+
+       /* see the ap computation under the SMTP branch */
+       fprintf(sinkfp, 
+               "MAIL FROM: %s", (msg->return_path[0]) ? msg->return_path : user);
+
+       if (ctl->pass8bits || (ctl->mimemsg & MSG_IS_8BIT))
+           fputs(" BODY=8BITMIME", sinkfp);
+       else if (ctl->mimemsg & MSG_IS_7BIT)
+           fputs(" BODY=7BIT", sinkfp);
+
+       /* exim's BSMTP processor does not handle SIZE */
+       /* fprintf(sinkfp, " SIZE=%d\r\n", msg->reallen); */
+
+       /*
+        * RFC 1123 requires that the domain name part of the
+        * RCPT TO address be "canonicalized", that is a FQDN
+        * or MX but not a CNAME.  Some listeners (like exim)
+        * enforce this.  Now that we have the actual hostname,
+        * compute what we should canonicalize with.
+        */
+       ctl->destaddr = ctl->smtpaddress ? ctl->smtpaddress : "localhost";
+
+       *bad_addresses = 0;
+       for (idp = msg->recipients; idp; idp = idp->next)
+           if (idp->val.status.mark == XMIT_ACCEPT)
+           {
+               if (strchr(idp->id, '@'))
+                   fprintf(sinkfp,
+                               "RCPT TO: %s\r\n", idp->id);
+               else
+                   fprintf(sinkfp,
+                               "RCPT TO: %s@%s\r\n", idp->id, ctl->destaddr);
+               *good_addresses = 0;
+           }
+
+       fputs("DATA\r\n", sinkfp);
+
+       if (ferror(sinkfp))
+       {
+           report(stderr, _("BSMTP file open or preamble write failed\n"));
+           return(PS_BSMTP);
+       }
+    }
+    else if (ctl->mda)         /* we have a declared MDA */
     {
        int     length = 0, fromlen = 0, nameslen = 0;
        char    *names = NULL, *before, *after, *from = NULL;
 
        ctl->destaddr = "localhost";
 
-       for (idp = xmit_names; idp; idp = idp->next)
+       for (idp = msg->recipients; idp; idp = idp->next)
            if (idp->val.status.mark == XMIT_ACCEPT)
                (*good_addresses)++;
 
@@ -280,10 +562,10 @@ int open_sink(struct query *ctl,
             * long lists of users and (re)implement %s.
             */
            nameslen = 0;
-           for (idp = xmit_names; idp; idp = idp->next)
-               if (idp->val.status.mark == XMIT_ACCEPT)
+           for (idp = msg->recipients; idp; idp = idp->next)
+               if ((idp->val.status.mark == XMIT_ACCEPT))
                    nameslen += (strlen(idp->id) + 1);  /* string + ' ' */
-           if (*good_addresses = 0)
+           if ((*good_addresses == 0))
                nameslen = strlen(run.postmaster);
 
            names = (char *)xmalloc(nameslen + 1);      /* account for '\0' */
@@ -292,7 +574,7 @@ int open_sink(struct query *ctl,
            else
            {
                names[0] = '\0';
-               for (idp = xmit_names; idp; idp = idp->next)
+               for (idp = msg->recipients; idp; idp = idp->next)
                    if (idp->val.status.mark == XMIT_ACCEPT)
                    {
                        strcat(names, idp->id);
@@ -308,7 +590,7 @@ int open_sink(struct query *ctl,
        /* get From address for %F */
        if (strstr(before, "%F"))
        {
-           from = xstrdup(return_path);
+           from = xstrdup(msg->return_path);
 
            /* sanitize from in order to contain *only* harmless shell chars */
            sanitize(from);
@@ -323,17 +605,17 @@ int open_sink(struct query *ctl,
 
            /* find length of resulting mda string */
            sp = before;
-           while (sp = strstr(sp, "%s")) {
+           while ((sp = strstr(sp, "%s"))) {
                length += nameslen - 2; /* subtract %s */
                sp += 2;
            }
            sp = before;
-           while (sp = strstr(sp, "%T")) {
+           while ((sp = strstr(sp, "%T"))) {
                length += nameslen - 2; /* subtract %T */
                sp += 2;
            }
            sp = before;
-           while (sp = strstr(sp, "%F")) {
+           while ((sp = strstr(sp, "%F"))) {
                length += fromlen - 2;  /* subtract %F */
                sp += 2;
            }
@@ -341,7 +623,7 @@ int open_sink(struct query *ctl,
            after = xmalloc(length + 1);
 
            /* copy mda source string to after, while expanding %[sTF] */
-           for (dp = after, sp = before; *dp = *sp; dp++, sp++) {
+           for (dp = after, sp = before; (*dp = *sp); dp++, sp++) {
                if (sp[0] != '%')       continue;
 
                /* need to expand? BTW, no here overflow, because in
@@ -374,8 +656,8 @@ int open_sink(struct query *ctl,
        }
 
 
-       if (outlevel == O_VERBOSE)
-           error(0, 0, "about to deliver with: %s", before);
+       if (outlevel >= O_DEBUG)
+           report(stdout, _("about to deliver with: %s\n"), before);
 
 #ifdef HAVE_SETEUID
        /*
@@ -398,20 +680,25 @@ int open_sink(struct query *ctl,
 
        if (!sinkfp)
        {
-           error(0, 0, "MDA open failed");
+           report(stderr, _("MDA open failed\n"));
            return(PS_IOERR);
        }
 
        sigchld = signal(SIGCHLD, SIG_DFL);
     }
-    else
+    else /* forward to an SMTP or LMTP listener */
     {
-       char    *ap, options[MSGBUFSIZE], addr[128];
+       const char      *ap;
+       char            options[MSGBUFSIZE]; 
+       char            addr[HOSTLEN+USERNAMELEN+1];
+       char            **from_responses;
+       int             total_addresses;
 
        /* build a connection to the SMTP listener */
        if ((smtp_open(ctl) == -1))
        {
-           error(0, errno, "SMTP connect to %s failed",
+           report(stderr, _("%cMTP connect to %s failed\n"),
+                 ctl->listener,
                  ctl->smtphost ? ctl->smtphost : "localhost");
            return(PS_SMTP);
        }
@@ -427,109 +714,54 @@ int open_sink(struct query *ctl,
                strcpy(options, " BODY=7BIT");
         }
 
-       if ((ctl->server.esmtp_options & ESMTP_SIZE) && reallen > 0)
-           sprintf(options + strlen(options), " SIZE=%ld", reallen);
+       if ((ctl->server.esmtp_options & ESMTP_SIZE) && msg->reallen > 0)
+           sprintf(options + strlen(options), " SIZE=%d", msg->reallen);
 
        /*
         * Try to get the SMTP listener to take the Return-Path
-        * address as MAIL FROM .  If it won't, fall back on the
-        * calling-user ID.  This won't affect replies, which use the
-        * header From address anyway.
+        * address as MAIL FROM.  If it won't, fall back on the
+        * remotename and mailserver host.  This won't affect replies,
+        * which use the header From address anyway; the MAIL FROM
+        * address is a place for the SMTP listener to send
+        * bouncemail.  The point is to guarantee a FQDN in the MAIL
+        * FROM line -- some SMTP listeners, like smail, become
+        * unhappy otherwise.
         *
         * RFC 1123 requires that the domain name part of the
         * MAIL FROM address be "canonicalized", that is a
-        * FQDN or MX but not a CNAME.  We'll assume the From
+        * FQDN or MX but not a CNAME.  We'll assume the Return-Path
         * header is already in this form here (it certainly
         * is if rewrite is on).  RFC 1123 is silent on whether
         * a nonexistent hostname part is considered canonical.
         *
         * This is a potential problem if the MTAs further upstream
         * didn't pass canonicalized From/Return-Path lines, *and* the
-        * local SMTP listener insists on them.
-        *
-        * None of these error conditions generates bouncemail.  Comments
-        * below explain for each case why this is so.
+        * local SMTP listener insists on them. 
         */
-       ap = (return_path[0]) ? return_path : user;
-       if (SMTP_from(ctl->smtp_socket, ap, options) != SM_OK)
+       if (!msg->return_path[0])
        {
-           int smtperr = atoi(smtp_response);
-
-           if (str_find(&ctl->antispam, smtperr))
-           {
-               /*
-                * SMTP listener explicitly refuses to deliver mail
-                * coming from this address, probably due to an
-                * anti-spam domain exclusion.  Respect this.  Don't
-                * try to ship the message, and don't prevent it from
-                * being deleted.  Typical values:
-                *
-                * 501 = exim's old antispam response
-                * 550 = exim's new antispam response (temporary)
-                * 553 = sendmail 8.8.7's generic REJECT 
-                * 571 = sendmail's "unsolicited email refused"
-                *
-                * We don't send bouncemail on antispam failures because
-                * we don't want the scumbags to know the address is even
-                * valid.
-                */
-               SMTP_rset(ctl->smtp_socket);    /* required by RFC1870 */
-               return(PS_REFUSED);
-           }
-
-           /*
-            * Suppress error message only if the response specifically 
-            * meant `excluded for policy reasons'.  We *should* see
-            * an error when the return code is less specific.
-            */
-           if (smtperr >= 400)
-               error(0, -1, "SMTP error: %s", smtp_response);
-
-           switch (smtperr)
-           {
-           case 452: /* insufficient system storage */
-               /*
-                * Temporary out-of-queue-space condition on the
-                * ESMTP server.  Don't try to ship the message, 
-                * and suppress deletion so it can be retried on
-                * a future retrieval cycle. 
-                *
-                * Bouncemail *might* be appropriate here as a delay
-                * notification.  But it's not really necessary because
-                * this is not an actual failure, we're very likely to be
-                * able to recover on the next cycle.
-                */
-               SMTP_rset(ctl->smtp_socket);    /* required by RFC1870 */
-               return(PS_TRANSIENT);
-
-           case 552: /* message exceeds fixed maximum message size */
-           case 553: /* invalid sending domain */
-               /*
-                * Permanent no-go condition on the
-                * ESMTP server.  Don't try to ship the message, 
-                * and allow it to be deleted.
-                *
-                * Bouncemail would be appropriate for 552, but in these 
-                * latter days 553 usually means a spammer is trying to
-                * cover his tracks.  We'd rather deny the scumbags any
-                * feedback that the address is valid.
-                */
-               SMTP_rset(ctl->smtp_socket);    /* required by RFC1870 */
-               return(PS_REFUSED);
-
-           default:    /* retry with postmaster's address */
-               if (SMTP_from(ctl->smtp_socket,run.postmaster,options)!=SM_OK)
-               {
-                   error(0, -1, "SMTP error: %s", smtp_response);
-                   return(PS_SMTP);    /* should never happen */
-               }
-           }
+           sprintf(addr, "%s@%s", ctl->remotename, ctl->server.truename);
+           ap = addr;
+       }
+       else if (strchr(msg->return_path, '@'))
+           ap = msg->return_path;
+       else            /* in case Return-Path existed but was local */
+       {
+           sprintf(addr, "%s@%s", msg->return_path, ctl->server.truename);
+           ap = addr;
        }
 
+       if (SMTP_from(ctl->smtp_socket, ap, options) != SM_OK)
+           return(handle_smtp_report(ctl, msg));
+
        /*
         * Now list the recipient addressees
         */
-       for (idp = xmit_names; idp; idp = idp->next)
+       total_addresses = 0;
+       for (idp = msg->recipients; idp; idp = idp->next)
+           total_addresses++;
+       xalloca(from_responses, char **, sizeof(char *) * total_addresses);
+       for (idp = msg->recipients; idp; idp = idp->next)
            if (idp->val.status.mark == XMIT_ACCEPT)
            {
                if (strchr(idp->id, '@'))
@@ -545,39 +777,83 @@ int open_sink(struct query *ctl,
                    (*good_addresses)++;
                else
                {
+                   char        errbuf[POPBUFSIZE];
+
+                   strcpy(errbuf, idp->id);
+                   strcat(errbuf, ": ");
+                   strcat(errbuf, smtp_response);
+
+                   xalloca(from_responses[*bad_addresses], 
+                           char *, 
+                           strlen(errbuf)+1);
+                   strcpy(from_responses[*bad_addresses], errbuf);
+
                    (*bad_addresses)++;
-                   idp->val.status.mark = XMIT_ANTISPAM;
-                   error(0, 0, 
-                         "SMTP listener doesn't like recipient address `%s'",
-                         addr);
+                   idp->val.status.mark = XMIT_RCPTBAD;
+                   if (outlevel >= O_VERBOSE)
+                       report(stderr, 
+                             _("%cMTP listener doesn't like recipient address `%s'\n"),
+                             ctl->listener, addr);
                }
            }
-       if (!(*good_addresses))
+       if (*bad_addresses)
+           send_bouncemail(ctl, msg, XMIT_RCPTBAD,
+                            "Some addresses were rejected by the MDA fetchmail forwards to.\r\n",
+                            *bad_addresses, from_responses);
+       /*
+        * It's tempting to do local notification only if bouncemail was
+        * insufficient -- that is, to add && total_addresses > *bad_addresses
+        * to the test here.  The problem with this theory is that it would
+        * make initial diagnosis of a broken multidrop configuration very
+        * hard -- most single-recipient messages would just invisibly bounce.
+        */
+       if (!(*good_addresses)) 
        {
+           if (strchr(run.postmaster, '@'))
+               strcpy(addr, run.postmaster);
+           else
+           {
 #ifdef HAVE_SNPRINTF
-           snprintf(addr, sizeof(addr)-1, "%s@%s", run.postmaster, ctl->destaddr);
+               snprintf(addr, sizeof(addr)-1, "%s@%s", run.postmaster, ctl->destaddr);
 #else
-           sprintf(addr, "%s@%s", run.postmaster, ctl->destaddr);
+               sprintf(addr, "%s@%s", run.postmaster, ctl->destaddr);
 #endif /* HAVE_SNPRINTF */
+           }
 
            if (SMTP_rcpt(ctl->smtp_socket, addr) != SM_OK)
            {
-               error(0, 0, "can't even send to %s!", run.postmaster);
+               report(stderr, _("can't even send to %s!\n"), run.postmaster);
+               SMTP_rset(ctl->smtp_socket);    /* required by RFC1870 */
                return(PS_SMTP);
            }
+
+           if (outlevel >= O_VERBOSE)
+               report(stderr, _("no address matches; forwarding to %s.\n"), run.postmaster);
        }
 
-       /* tell it we're ready to send data */
-       SMTP_data(ctl->smtp_socket);
+       /* 
+        * Tell the listener we're ready to send data.
+        * Some listeners (like zmailer) may return antispam errors here.
+        */
+       if (SMTP_data(ctl->smtp_socket) != SM_OK)
+           return(handle_smtp_report(ctl, msg));
     }
 
+    /*
+     * We need to stash this away in order to know how many
+     * response lines to expect after the LMTP end-of-message.
+     */
+    lmtp_responses = *good_addresses;
+
     return(PS_SUCCESS);
 }
 
 void release_sink(struct query *ctl)
 /* release the per-message output sink, whether it's a pipe or SMTP socket */
 {
-    if (ctl->mda)
+    if (ctl->bsmtp)
+       fclose(sinkfp);
+    else if (ctl->mda)
     {
        if (sinkfp)
        {
@@ -588,7 +864,7 @@ void release_sink(struct query *ctl)
     }
 }
 
-int close_sink(struct query *ctl, flag forward)
+int close_sink(struct query *ctl, struct msgblk *msg, flag forward)
 /* perform end-of-message actions on the current output sink */
 {
     if (ctl->mda)
@@ -606,21 +882,201 @@ int close_sink(struct query *ctl, flag forward)
        signal(SIGCHLD, sigchld);
        if (rc)
        {
-           error(0, -1, "MDA exited abnormally or returned nonzero status");
+           report(stderr, 
+                  _("MDA exited abnormally or returned nonzero status\n"));
+           return(FALSE);
+       }
+    }
+    else if (ctl->bsmtp)
+    {
+       /* implicit disk-full check here... */
+       fputs(".\r\n", sinkfp);
+       if (strcmp(ctl->bsmtp, "-"))
+           fclose(sinkfp);
+       if (ferror(sinkfp))
+       {
+           report(stderr, 
+                  _("Message termination or close of BSMTP file failed\n"));
            return(FALSE);
        }
     }
     else if (forward)
     {
-                               /* write message terminator */
+       /* write message terminator */
        if (SMTP_eom(ctl->smtp_socket) != SM_OK)
        {
-           error(0, -1, "SMTP listener refused delivery");
-           return(FALSE);
+           if (handle_smtp_report(ctl, msg) != PS_REFUSED)
+               return(FALSE);
+           else
+           {
+               report(stderr, _("SMTP listener refused delivery\n"));
+               return(TRUE);
+           }
+       }
+
+       /*
+        * If this is an SMTP connection, SMTP_eom() ate the response.
+        * But could be this is an LMTP connection, in which case we have to
+        * interpret either (a) a single 503 response meaning there
+        * were no successful RCPT TOs, or (b) a variable number of
+        * responses, one for each successful RCPT TO.  We need to send
+        * bouncemail on each failed response and then return TRUE anyway,
+        * otherwise the message will get left in the queue and resent
+        * to people who got it the first time.
+        */
+       if (ctl->listener == LMTP_MODE)
+       {
+           if (lmtp_responses == 0)
+           {
+               SMTP_ok(ctl->smtp_socket); 
+
+               /*
+                * According to RFC2033, 503 is the only legal response
+                * if no RCPT TO commands succeeded.  No error recovery
+                * is really possible here, as we have no idea what
+                * insane thing the listener might be doing if it doesn't
+                * comply.
+                */
+               if (atoi(smtp_response) == 503)
+                   report(stderr, _("LMTP delivery error on EOM\n"));
+               else
+                   report(stderr,
+                         _("Unexpected non-503 response to LMTP EOM: %s\n"),
+                         smtp_response);
+
+               /*
+                * It's not completely clear what to do here.  We choose to
+                * interpret delivery failure here as a transient error, 
+                * the same way SMTP delivery failure is handled.  If we're
+                * wrong, an undead message will get stuck in the queue.
+                */
+               return(FALSE);
+           }
+           else
+           {
+               int     i, errors;
+               char    **responses;
+
+               /* eat the RFC2033-required responses, saving errors */ 
+               xalloca(responses, char **, sizeof(char *) * lmtp_responses);
+               for (errors = i = 0; i < lmtp_responses; i++)
+               {
+                   if (SMTP_ok(ctl->smtp_socket) == SM_OK)
+                       responses[i] = (char *)NULL;
+                   else
+                   {
+                       xalloca(responses[errors], 
+                               char *, 
+                               strlen(smtp_response)+1);
+                       strcpy(responses[errors], smtp_response);
+                       errors++;
+                   }
+               }
+
+               if (errors == 0)
+                   return(TRUE);       /* all deliveries succeeded */
+               else
+                   /*
+                    * One or more deliveries failed.
+                    * If we can bounce a failures list back to the
+                    * sender, and the postmaster does not want to
+                    * deal with the bounces return TRUE, deleting the
+                    * message from the server so it won't be
+                    * re-forwarded on subsequent poll cycles.
+                    */
+                 return(send_bouncemail(ctl, msg, XMIT_ACCEPT,
+                                        "LSMTP partial delivery failure.\r\n",
+                                        errors, responses));
+           }
        }
     }
 
     return(TRUE);
 }
 
+int open_warning_by_mail(struct query *ctl, struct msgblk *msg)
+/* set up output sink for a mailed warning to calling user */
+{
+    int        good, bad;
+
+    /*
+     * Dispatching warning email is a little complicated.  The problem is
+     * that we have to deal with three distinct cases:
+     *
+     * 1. Single-drop running from user account.  Warning mail should
+     * go to the local name for which we're collecting (coincides
+     * with calling user).
+     *
+     * 2. Single-drop running from root or other privileged ID, with rc
+     * file generated on the fly (Ken Estes's weird setup...)  Mail
+     * should go to the local name for which we're collecting (does not 
+     * coincide with calling user).
+     * 
+     * 3. Multidrop.  Mail must go to postmaster.  We leave the recipients
+     * member null so this message will fall through to run.postmaster.
+     *
+     * The zero in the reallen element means we won't pass a SIZE
+     * option to ESMTP; the message length would be more trouble than
+     * it's worth to compute.
+     */
+    struct msgblk reply = {NULL, NULL, "FETCHMAIL-DAEMON@", 0};
+
+    strcat(reply.return_path, fetchmailhost);
+
+    if (!MULTIDROP(ctl))               /* send to calling user */
+    {
+       int status;
+
+       save_str(&reply.recipients, ctl->localnames->id, XMIT_ACCEPT);
+       status = open_sink(ctl, &reply, &good, &bad);
+       free_str_list(&reply.recipients);
+       return(status);
+    }
+    else                               /* send to postmaster  */
+       return(open_sink(ctl, &reply, &good, &bad));
+}
+
+#if defined(HAVE_STDARG_H)
+void stuff_warning(struct query *ctl, const char *fmt, ... )
+#else
+void stuff_warning(struct query *ctl, fmt, va_alist)
+struct query *ctl;
+const char *fmt;       /* printf-style format */
+va_dcl
+#endif
+/* format and ship a warning message line by mail */
+{
+    char       buf[POPBUFSIZE];
+    va_list ap;
+
+    /*
+     * stuffline() requires its input to be writeable (for CR stripping),
+     * so we needed to copy the message to a writeable buffer anyway in
+     * case it was a string constant.  We make a virtue of that necessity
+     * here by supporting stdargs/varargs.
+     */
+#if defined(HAVE_STDARG_H)
+    va_start(ap, fmt) ;
+#else
+    va_start(ap);
+#endif
+#ifdef HAVE_VSNPRINTF
+    vsnprintf(buf, sizeof(buf), fmt, ap);
+#else
+    vsprintf(buf, fmt, ap);
+#endif
+    va_end(ap);
+
+    strcat(buf, "\r\n");
+
+    stuffline(ctl, buf);
+}
+
+void close_warning_by_mail(struct query *ctl, struct msgblk *msg)
+/* sign and send mailed warnings */
+{
+    stuff_warning(ctl, "--\r\n\t\t\t\tThe Fetchmail Daemon\r\n");
+    close_sink(ctl, msg, TRUE);
+}
+
 /* sink.c ends here */