]> Pileus Git - ~andy/fetchmail/blobdiff - sink.c
Note STARTTLS.
[~andy/fetchmail] / sink.c
diff --git a/sink.c b/sink.c
index 6ade2018e7aa5791ebcacf55cad440c36a3c6e37..8d487b6c4f2d0bd3c869f569fe18c4093350efa2 100644 (file)
--- a/sink.c
+++ b/sink.c
 /* makes the open_sink()/close_sink() pair non-reentrant */
 static int lmtp_responses;
 
+void smtp_close(struct query *ctl, int sayquit)
+/* close the socket to SMTP server */
+{
+    if (ctl->smtp_socket != -1)
+    {
+       if (sayquit)
+           SMTP_quit(ctl->smtp_socket);
+       SockClose(ctl->smtp_socket);
+       ctl->smtp_socket = -1;
+    }
+    batchcount = 0;
+}
+
 int smtp_open(struct query *ctl)
 /* try to open a socket to the appropriate SMTP server for this query */ 
 {
     char *parsed_host = NULL;
 
     /* 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)
-    {
-       SockClose(ctl->smtp_socket);
-       ctl->smtp_socket = -1;
-       batchcount = 0;
+    if (NUM_NONZERO(ctl->batchlimit)) {
+       if (batchcount == ctl->batchlimit)
+           smtp_close(ctl, 1);
+       batchcount++;
     }
 
     /* if no socket to any SMTP host is already set up, try to open one */
@@ -131,23 +143,27 @@ int smtp_open(struct query *ctl)
 
            /* return immediately for ODMR */
            if (ctl->server.protocol == P_ODMR)
+           {
+              set_timeout(0);
+              phase = oldphase;
                return(ctl->smtp_socket); /* success */
+           }
 
            /* 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,
-                         &ctl->server.esmtp_options) == SM_OK)
+                   SMTP_ehlo(ctl->smtp_socket, id_me, 
+                             ctl->server.esmtp_name, ctl->server.esmtp_password,
+                             &ctl->server.esmtp_options) == SM_OK)
               break;  /* success */
 
            /*
             * RFC 1869 warns that some listeners hang up on a failed EHLO,
             * so it's safest not to assume the socket will still be good.
             */
-           SockClose(ctl->smtp_socket);
-           ctl->smtp_socket = -1;
+           smtp_close(ctl, 0);
 
            /* if opening for ESMTP failed, try SMTP */
            if ((ctl->smtp_socket = SockOpen(parsed_host,portnum,NULL,
@@ -158,8 +174,7 @@ int smtp_open(struct query *ctl)
                    SMTP_helo(ctl->smtp_socket, id_me) == SM_OK)
                break;  /* success */
 
-           SockClose(ctl->smtp_socket);
-           ctl->smtp_socket = -1;
+           smtp_close(ctl, 0);
        }
        set_timeout(0);
        phase = oldphase;
@@ -222,14 +237,25 @@ static int send_bouncemail(struct query *ctl, struct msgblk *msg,
     strcat(daemon_name, host_fqdn());
 
     /* we need only SMTP for this purpose */
-    if ((sock = SockOpen("localhost", SMTP_PORT, NULL, NULL)) == -1
-               || SMTP_ok(sock) != SM_OK 
-               || SMTP_helo(sock, fetchmailhost) != SM_OK
-               || SMTP_from(sock, daemon_name, (char *)NULL) != SM_OK
-               || SMTP_rcpt(sock, bounce_to) != SM_OK
-               || SMTP_data(sock) != SM_OK)
+    if ((sock = SockOpen("localhost", SMTP_PORT, NULL, NULL)) == -1)
        return(FALSE);
 
+    if (SMTP_ok(sock) != SM_OK)
+    {
+       SockClose(sock);
+       return FALSE;
+    }
+
+    if (SMTP_helo(sock, fetchmailhost) != SM_OK
+       || SMTP_from(sock, daemon_name, (char *)NULL) != SM_OK
+       || SMTP_rcpt(sock, bounce_to) != SM_OK
+       || SMTP_data(sock) != SM_OK) 
+    {
+       SMTP_quit(sock);
+       SockClose(sock);
+       return(FALSE);
+    }
+
     /* our first duty is to keep the sacred foo counters turning... */
 #ifdef HAVE_SNPRINTF
     snprintf(boundary, sizeof(boundary),
@@ -288,7 +314,7 @@ static int send_bouncemail(struct query *ctl, struct msgblk *msg,
                if (nerrors == 1)
                    /* one error applies to all users */
                    error = errors[0];
-               else if (nerrors > nusers)
+               else if (nerrors <= nusers)
                {
                    SockPrintf(sock, "Internal error: SMTP error count doesn't match number of recipients.\r\n");
                    break;
@@ -319,7 +345,10 @@ static int send_bouncemail(struct query *ctl, struct msgblk *msg,
     SockPrintf(sock, "--%s--\r\n", boundary); 
 
     if (SMTP_eom(sock) != SM_OK || SMTP_quit(sock))
+    {
+       SockClose(sock);
        return(FALSE);
+    }
 
     SockClose(sock);
 
@@ -378,9 +407,19 @@ static int handle_smtp_report(struct query *ctl, struct msgblk *msg)
         *
         */
        if (run.spambounce)
+     {
+       char rejmsg[160];
+#ifdef HAVE_SNPRINTF
+       snprintf(rejmsg, sizeof(rejmsg),
+#else
+       sprintf(rejmsg,
+#endif /* HAVE_SNPRINTF */
+               "spam filter or virus scanner rejected message because:\r\n"
+               "%s\r\n", responses[0]);
+         
                send_bouncemail(ctl, msg, XMIT_ACCEPT,
-                       "Our spam filter rejected this transaction.\r\n", 
-                       1, responses);
+                      rejmsg, 1, responses);
+     }
        return(PS_REFUSED);
     }
 
@@ -402,10 +441,11 @@ static int handle_smtp_report(struct query *ctl, struct msgblk *msg)
         * ESMTP server.  Don't try to ship the message, 
         * and allow it to be deleted.
         */
-       send_bouncemail(ctl, msg, XMIT_ACCEPT,
+       if (run.bouncemail)
+           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);
+       return(PS_REFUSED);
   
     case 553: /* invalid sending domain */
        /*
@@ -415,9 +455,12 @@ static int handle_smtp_report(struct query *ctl, struct msgblk *msg)
         * (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", 
+#ifdef __DONT_FEED_THE_SPAMMERS__
+       if (run.bouncemail)
+           send_bouncemail(ctl, msg, XMIT_ACCEPT,
+                       "Invalid address in MAIL FROM/RCPT TO (SMTP error 553).\r\n", 
                        1, responses);
+#endif /* __DONT_FEED_THE_SPAMMERS__ */
        return(PS_REFUSED);
 
     default:
@@ -439,7 +482,7 @@ static int handle_smtp_report(struct query *ctl, struct msgblk *msg)
         *
         * 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
+        * sure the RFC1894 Action field is "delayed" rather than
         * "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.
@@ -448,6 +491,48 @@ static int handle_smtp_report(struct query *ctl, struct msgblk *msg)
     }
 }
 
+static int handle_smtp_report_without_bounce(struct query *ctl, struct msgblk *msg)
+/* handle SMTP errors based on the content of SMTP_response */
+/* atleast one PS_TRANSIENT: do not send the bounce mail, keep the mail;
+ * no PS_TRANSIENT, atleast one PS_SUCCESS: send the bounce mail, delete the mail;
+ * no PS_TRANSIENT, no PS_SUCCESS: do not send the bounce mail, delete the mail */
+{
+    int smtperr = atoi(smtp_response);
+
+    if (str_find(&ctl->antispam, smtperr))
+    {
+       if (run.spambounce)
+        return(PS_SUCCESS);
+       return(PS_REFUSED);
+    }
+
+    if (smtperr >= 400)
+       report(stderr, GT_("%cMTP error: %s\n"), 
+             ctl->listener,
+             smtp_response);
+
+    switch (smtperr)
+    {
+    case 552: /* message exceeds fixed maximum message size */
+       if (run.bouncemail)
+           return(PS_SUCCESS);
+       return(PS_REFUSED);
+
+    case 553: /* invalid sending domain */
+#ifdef __DONT_FEED_THE_SPAMMERS__
+       if (run.bouncemail)
+           return(PS_SUCCESS);
+#endif /* __DONT_FEED_THE_SPAMMERS__ */
+       return(PS_REFUSED);
+
+    default:
+       /* bounce non-transient errors back to the sender */
+       if (smtperr >= 500 && smtperr <= 599)
+           return(PS_SUCCESS);
+       return(PS_TRANSIENT);
+    }
+}
+
 /* these are shared by open_sink and stuffline */
 static FILE *sinkfp;
 
@@ -587,6 +672,55 @@ static int open_bsmtp_sink(struct query *ctl, struct msgblk *msg,
 /* this is experimental and will be removed if double bounces are reported */
 #define EXPLICIT_BOUNCE_ON_BAD_ADDRESS
 
+
+static const char *is_quad(const char *q)
+/* Check if the string passed in points to what could be one quad of a
+ * dotted-quad IP address.  Requirements are that the string is not a
+ * NULL pointer, begins with a period (which is skipped) or a digit
+ * and ends with a period or a NULL.  If these requirements are met, a
+ * pointer to the last character (the period or the NULL character) is
+ * returned; otherwise NULL.
+ */
+{
+  const char *r;
+  
+  if (!q || !*q)
+    return NULL;
+  if (*q == '.')
+    q++;
+  for(r=q;isdigit(*r);r++)
+    ;
+  if ( ((*r) && (*r != '.')) || ((r-q) < 1) || ((r-q)>3) )
+    return NULL;
+  /* Make sure quad is < 255 */
+  if ( (r-q) == 3)
+  {
+    if (*q > '2')
+      return NULL;
+    else if (*q == '2')
+    {
+      if (*(q+1) > '5')
+        return NULL;
+      else if (*(q+1) == '5')
+      {
+        if (*(q+2) > '5')
+          return NULL;
+      }
+    }
+  }
+  return r;
+}
+
+static int is_dottedquad(const char *hostname)
+/* Returns a true value if the passed in string looks like an IP
+ *  address in dotted-quad form, and a false value otherwise.
+ */
+
+{
+  return ((hostname=is_quad(is_quad(is_quad(is_quad(hostname))))) != NULL) &&
+    (*hostname == '\0');
+}
+
 static int open_smtp_sink(struct query *ctl, struct msgblk *msg,
              int *good_addresses, int *bad_addresses)
 /* open an SMTP stream */
@@ -599,6 +733,7 @@ static int open_smtp_sink(struct query *ctl, struct msgblk *msg,
     char               **from_responses;
 #endif /* EXPLICIT_BOUNCE_ON_BAD_ADDRESS */
     int                total_addresses;
+    int                force_transient_error = 0;
 
     /*
      * Compute ESMTP options.
@@ -639,27 +774,54 @@ static int open_smtp_sink(struct query *ctl, struct msgblk *msg,
      * path equal to "@".  Ghod knows why anyone does this, but 
      * it's been reported to happen in mail from Amazon.com and
      * Motorola.
+     *
+     * Also, if the hostname is a dotted quad, wrap it in square brackets.
+     * Apparently this is required by RFC2821, section 4.1.3.
      */
     if (!msg->return_path[0] || (0 == strcmp(msg->return_path, "@")))
     {
+      if (is_dottedquad(ctl->server.truename))
+      {
+#ifdef HAVE_SNPRINTF
+       snprintf(addr, sizeof(addr),
+#else
+                 sprintf(addr,
+#endif /* HAVE_SNPRINTF */
+             "%s@[%s]", ctl->remotename, ctl->server.truename);
+      }
+      else
+      {
 #ifdef HAVE_SNPRINTF
        snprintf(addr, sizeof(addr),
 #else
        sprintf(addr,
 #endif /* HAVE_SNPRINTF */
              "%s@%s", ctl->remotename, ctl->server.truename);
+      }
        ap = addr;
     }
     else if (strchr(msg->return_path,'@') || strchr(msg->return_path,'!'))
        ap = msg->return_path;
     else               /* in case Return-Path existed but was local */
     {
+      if (is_dottedquad(ctl->server.truename))
+      {
+#ifdef HAVE_SNPRINTF
+       snprintf(addr, sizeof(addr),
+#else
+       sprintf(addr,
+#endif /* HAVE_SNPRINTF */
+               "%s@[%s]", msg->return_path, ctl->server.truename);
+      }
+      else
+      {
 #ifdef HAVE_SNPRINTF
        snprintf(addr, sizeof(addr),
 #else
        sprintf(addr,
 #endif /* HAVE_SNPRINTF */
                "%s@%s", msg->return_path, ctl->server.truename);
+      }
        ap = addr;
     }
 
@@ -688,14 +850,14 @@ static int open_smtp_sink(struct query *ctl, struct msgblk *msg,
            else {
                if (ctl->smtpname) {
 #ifdef HAVE_SNPRINTF
-                   snprintf(addr, sizeof(addr)-1, "%s", ctl->smtpname);
+                   snprintf(addr, sizeof(addr), "%s", ctl->smtpname);
 #else
                    sprintf(addr, "%s", ctl->smtpname);
 #endif /* HAVE_SNPRINTF */
 
                } else {
 #ifdef HAVE_SNPRINTF
-                 snprintf(addr, sizeof(addr)-1, "%s@%s", idp->id, ctl->destaddr);
+                 snprintf(addr, sizeof(addr), "%s@%s", idp->id, ctl->destaddr);
 #else
                  sprintf(addr, "%s@%s", idp->id, ctl->destaddr);
 #endif /* HAVE_SNPRINTF */
@@ -705,36 +867,46 @@ static int open_smtp_sink(struct query *ctl, struct msgblk *msg,
                (*good_addresses)++;
            else
            {
-#ifdef EXPLICIT_BOUNCE_ON_BAD_ADDRESS
-                   char errbuf[POPBUFSIZE];
-#endif /* EXPLICIT_BOUNCE_ON_BAD_ADDRESS */
-               handle_smtp_report(ctl, msg);
+               switch (handle_smtp_report_without_bounce(ctl, msg))
+               {
+                   case PS_TRANSIENT:
+                   force_transient_error = 1;
+                   break;
 
+                   case PS_SUCCESS:
 #ifdef EXPLICIT_BOUNCE_ON_BAD_ADDRESS
-#ifdef HAVE_SNPRINTF
-               snprintf(errbuf, sizeof(errbuf), "%s: %s",
-                               idp->id, smtp_response);
-#else
-               strncpy(errbuf, idp->id, sizeof(errbuf));
-               strcat(errbuf, ": ");
-               strcat(errbuf, smtp_response);
-#endif /* HAVE_SNPRINTF */
-
-               xalloca(from_responses[*bad_addresses], 
-                       char *, 
-                       strlen(errbuf)+1);
-               strcpy(from_responses[*bad_addresses], errbuf);
+                   xalloca(from_responses[*bad_addresses],
+                           char *,
+                           strlen(smtp_response)+1);
+                   strcpy(from_responses[*bad_addresses], smtp_response);
 #endif /* EXPLICIT_BOUNCE_ON_BAD_ADDRESS */
 
-               (*bad_addresses)++;
-               idp->val.status.mark = XMIT_RCPTBAD;
-               if (outlevel >= O_VERBOSE)
-                   report(stderr, 
-                         GT_("%cMTP listener doesn't like recipient address `%s'\n"),
-                         ctl->listener, addr);
+                   (*bad_addresses)++;
+                   idp->val.status.mark = XMIT_RCPTBAD;
+                   if (outlevel >= O_VERBOSE)
+                       report(stderr,
+                             GT_("%cMTP listener doesn't like recipient address `%s'\n"),
+                             ctl->listener, addr);
+                   break;
+
+                   case PS_REFUSED:
+                   if (outlevel >= O_VERBOSE)
+                       report(stderr,
+                             GT_("%cMTP listener doesn't really like recipient address `%s'\n"),
+                             ctl->listener, addr);
+                   break;
+               }
            }
        }
 
+    if (force_transient_error) {
+           /* do not risk dataloss due to overengineered multidrop
+            * crap. If one of the recipients returned PS_TRANSIENT,
+            * we return exactly that.
+            */
+           SMTP_rset(ctl->smtp_socket);        /* required by RFC1870 */
+           return(PS_TRANSIENT);
+    }
 #ifdef EXPLICIT_BOUNCE_ON_BAD_ADDRESS
     /*
      * This should not be necessary, because the SMTP listener itself
@@ -767,7 +939,7 @@ static int open_smtp_sink(struct query *ctl, struct msgblk *msg,
        else
        {
 #ifdef HAVE_SNPRINTF
-           snprintf(addr, sizeof(addr)-1, "%s@%s", run.postmaster, ctl->destaddr);
+           snprintf(addr, sizeof(addr), "%s@%s", run.postmaster, ctl->destaddr);
 #else
            sprintf(addr, "%s@%s", run.postmaster, ctl->destaddr);
 #endif /* HAVE_SNPRINTF */
@@ -794,6 +966,12 @@ static int open_smtp_sink(struct query *ctl, struct msgblk *msg,
        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);
 }
 
@@ -804,6 +982,9 @@ static int open_mda_sink(struct query *ctl, struct msgblk *msg,
 #ifdef HAVE_SIGACTION
     struct      sigaction sa_new;
 #endif /* HAVE_SIGACTION */
+#ifdef HAVE_SETEUID
+    uid_t orig_uid;
+#endif /* HAVE_SETEUID */
     struct     idlist *idp;
     int        length = 0, fromlen = 0, nameslen = 0;
     char       *names = NULL, *before, *after, *from = NULL;
@@ -929,6 +1110,7 @@ static int open_mda_sink(struct query *ctl, struct msgblk *msg,
      * MDA creates properly.  (The seteuid call is available
      * under all BSDs and Linux)
      */
+    orig_uid = getuid();
     seteuid(ctl->uid);
 #endif /* HAVE_SETEUID */
 
@@ -938,7 +1120,7 @@ static int open_mda_sink(struct query *ctl, struct msgblk *msg,
 
 #ifdef HAVE_SETEUID
     /* this will fail quietly if we didn't start as root */
-    seteuid(0);
+    seteuid(orig_uid);
 #endif /* HAVE_SETEUID */
 
     if (!sinkfp)
@@ -1005,8 +1187,11 @@ int open_sink(struct query *ctl, struct msgblk *msg,
        /* 
         * User was delivering locally.  We have a fallback MDA.
         * Latch it in place, logging the error, and fall through.
+        * Set stripcr as we would if MDA had been the initial transport
         */
        ctl->mda = FALLBACK_MDA;
+       if (!ctl->forcecr)
+           ctl->stripcr = TRUE;
 
        report(stderr, GT_("can't raise the listener; falling back to %s"),
                         FALLBACK_MDA);
@@ -1016,12 +1201,6 @@ int open_sink(struct query *ctl, struct msgblk *msg,
     if (ctl->mda)              /* must deliver through an MDA */
        return(open_mda_sink(ctl, msg, good_addresses, bad_addresses));
 
-    /*
-     * 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);
 }
 
@@ -1029,7 +1208,13 @@ void release_sink(struct query *ctl)
 /* release the per-message output sink, whether it's a pipe or SMTP socket */
 {
     if (ctl->bsmtp && sinkfp)
-       fclose(sinkfp);
+    {
+       if (strcmp(ctl->bsmtp, "-"))
+       {
+           fclose(sinkfp);
+           sinkfp = (FILE *)NULL;
+       }
+    }
     else if (ctl->mda)
     {
        if (sinkfp)
@@ -1074,7 +1259,10 @@ int close_sink(struct query *ctl, struct msgblk *msg, flag forward)
        fputs(".\r\n", sinkfp);
        error = ferror(sinkfp);
        if (strcmp(ctl->bsmtp, "-"))
+       {
            if (fclose(sinkfp) == EOF) error = 1;
+           sinkfp = (FILE *)NULL;
+       }
        if (error)
        {
            report(stderr, 
@@ -1208,7 +1396,8 @@ int open_warning_by_mail(struct query *ctl, struct msgblk *msg)
     struct msgblk reply = {NULL, NULL, "FETCHMAIL-DAEMON@", 0};
     int status;
 
-    strcat(reply.return_path, fetchmailhost);
+    strcat(reply.return_path, ctl->smtpaddress ? ctl->smtpaddress :
+           fetchmailhost);
 
     if (!MULTIDROP(ctl))               /* send to calling user */
     {