]> Pileus Git - ~andy/fetchmail/blobdiff - transact.c
Stefan Esser's security patch.
[~andy/fetchmail] / transact.c
index 2214b9ffb78ba9deef2654f28590b37ccb7a8ea2..c0f79253bdeea17b287fc6b8394d1fd07bae760f 100644 (file)
@@ -40,7 +40,7 @@ extern char *strstr();        /* needed on sysV68 R3V7.1. */
 
 int mytimeout;         /* value of nonreponse timeout */
 int suppress_tags;     /* emit tags? */
-char shroud[PASSWORDLEN];      /* string to shroud in debug output */
+char shroud[PASSWORDLEN*2+1];  /* string to shroud in debug output */
 struct msgblk msgblk;
 
 char tag[TAGLEN];
@@ -66,7 +66,7 @@ static void map_name(const char *name, struct query *ctl, struct idlist **xmit_n
     if (lname != (char *)NULL)
     {
        if (outlevel >= O_DEBUG)
-           report(stdout, _("mapped %s to local %s\n"), name, lname);
+           report(stdout, GT_("mapped %s to local %s\n"), name, lname);
        save_str(xmit_names, lname, XMIT_ACCEPT);
        accept_count++;
     }
@@ -92,6 +92,13 @@ static void find_server_names(const char *hdr,
        {
            char        *atsign;
 
+           /* 
+            * Handle empty address from a To: header containing only 
+            * a comment.
+            */
+           if (!*cp)
+               continue;
+
            /*
             * If the name of the user begins with a qmail virtual
             * domain prefix, ignore the prefix.  Doing this here
@@ -123,7 +130,7 @@ static void find_server_names(const char *hdr,
                        strcasecmp(rhs, idp->id) == 0)
                    {
                        if (outlevel >= O_DEBUG)
-                           report(stdout, _("passed through %s matching %s\n"), 
+                           report(stdout, GT_("passed through %s matching %s\n"), 
                                  cp, idp->id);
                        save_str(xmit_names, cp, XMIT_ACCEPT);
                        accept_count++;
@@ -181,6 +188,8 @@ static char *parse_received(struct query *ctl, char *bufp)
     char *base, *ok = (char *)NULL;
     static char rbuf[HOSTLEN + USERNAMELEN + 4]; 
 
+#define RBUF_WRITE(value) if (tp < rbuf+sizeof(rbuf)-1) *tp++=value
+
     /*
      * Try to extract the real envelope addressee.  We look here
      * specifically for the mailserver's Received line.
@@ -190,7 +199,7 @@ static char *parse_received(struct query *ctl, char *bufp)
      * does this when the mail has a single recipient.
      */
     if (outlevel >= O_DEBUG)
-       report(stdout, _("analyzing Received line:\n%s"), bufp);
+       report(stdout, GT_("analyzing Received line:\n%s"), bufp);
 
     /* search for whitepace-surrounded "by" followed by valid address */
     for (base = bufp;  ; base = ok + 2)
@@ -208,7 +217,7 @@ static char *parse_received(struct query *ctl, char *bufp)
                continue;
            tp = rbuf;
            for (; !isspace(*sp); sp++)
-               *tp++ = *sp;
+               RBUF_WRITE(*sp);
            *tp = '\0';
 
            /* look for valid address */
@@ -229,13 +238,13 @@ static char *parse_received(struct query *ctl, char *bufp)
        {
            if (outlevel >= O_DEBUG)
                report(stdout, 
-                     _("line accepted, %s is an alias of the mailserver\n"), rbuf);
+                     GT_("line accepted, %s is an alias of the mailserver\n"), rbuf);
        }
        else
        {
            if (outlevel >= O_DEBUG)
                report(stdout, 
-                     _("line rejected, %s is not an alias of the mailserver\n"), 
+                     GT_("line rejected, %s is not an alias of the mailserver\n"), 
                      rbuf);
            return(NULL);
        }
@@ -256,7 +265,7 @@ static char *parse_received(struct query *ctl, char *bufp)
                    continue;
                tp = rbuf;
                for (; !isspace(*sp); sp++)
-                   *tp++ = *sp;
+                   RBUF_WRITE(*sp);
                *tp = '\0';
 
                if (strchr(rbuf, '@'))
@@ -274,8 +283,8 @@ static char *parse_received(struct query *ctl, char *bufp)
            for (sp = ok + 4; isspace(*sp); sp++)
                continue;
            tp = rbuf;
-           *tp++ = ':';        /* Here is the hack.  This is to be friends */
-           *tp++ = ' ';        /* with nxtaddr()... */
+           RBUF_WRITE(':');    /* Here is the hack.  This is to be friends */
+           RBUF_WRITE(' ');    /* with nxtaddr()... */
            if (*sp == '<')
            {
                want_gt = TRUE;
@@ -288,14 +297,17 @@ static char *parse_received(struct query *ctl, char *bufp)
                    && (want_gt ? (*sp != '>') : !isspace(*sp))
                    && *sp != ';')
                if (!isspace(*sp))
-                   *tp++ = *sp++;
+               {
+                   RBUF_WRITE(*sp);
+                   sp++;
+               }    
                else
                {
                    /* uh oh -- whitespace here can't be right! */
                    ok = (char *)NULL;
                    break;
                }
-           *tp++ = '\n';
+           RBUF_WRITE('\n');
            *tp = '\0';
            if (strlen(rbuf) <= 3)      /* apparently nothing has been found */
                ok = NULL;
@@ -306,7 +318,7 @@ static char *parse_received(struct query *ctl, char *bufp)
     if (!ok)
     {
        if (outlevel >= O_DEBUG)
-           report(stdout, _("no Received address found\n"));
+           report(stdout, GT_("no Received address found\n"));
        return(NULL);
     }
     else
@@ -315,7 +327,7 @@ static char *parse_received(struct query *ctl, char *bufp)
            char *lf = rbuf + strlen(rbuf)-1;
            *lf = '\0';
            if (outlevel >= O_DEBUG)
-               report(stdout, _("found Received address `%s'\n"), rbuf+2);
+               report(stdout, GT_("found Received address `%s'\n"), rbuf+2);
            *lf = '\n';
        }
        return(rbuf);
@@ -377,6 +389,11 @@ int readheaders(int sock,
     if (msgblk.headers)
        free(msgblk.headers);
 
+    /* initially, no message ID */
+    if (ctl->thisid)
+       free(ctl->thisid);
+    ctl->thisid = NULL;
+
     msgblk.headers = received_for = delivered_to = NULL;
     from_offs = reply_to_offs = resent_from_offs = app_from_offs = 
        sender_offs = resent_sender_offs = env_offs = -1;
@@ -469,6 +486,7 @@ int readheaders(int sock,
             */
            if (protocol->delimited && line[0] == '.' && EMPTYLINE(line+1))
            {
+               headers_ok = FALSE;
                has_nuls = (linelen != strlen(line));
                free(line);
                goto process_headers;
@@ -483,7 +501,7 @@ int readheaders(int sock,
             */
            if (!isspace(line[0]) && !strchr(line, ':'))
            {
-               headers_ok = TRUE;
+               headers_ok = FALSE;
                has_nuls = (linelen != strlen(line));
                free(line);
                goto process_headers;
@@ -514,44 +532,9 @@ int readheaders(int sock,
        /* we see an ordinary (non-header, non-message-delimiter line */
        has_nuls = (linelen != strlen(line));
 
-       /*
-        * When mail delivered to a multidrop mailbox on the server is
-        * addressed to multiple people on the client machine, there
-        * will be one copy left in the box for each recipient.  Thus,
-        * if the mail is addressed to N people, each recipient will
-        * get N copies.  This is bad when N > 1.
-        *
-        * Foil this by suppressing all but one copy of a message with
-        * a given Message-ID.  The accept_count test ensures that
-        * multiple pieces of email with the same Message-ID, each
-        * with a *single* addressee (the N == 1 case), won't be 
-        * suppressed.
-        *
-        * Note: This implementation only catches runs of successive
-        * messages with the same ID, but that should be good
-        * enough. A more general implementation would have to store
-        * ever-growing lists of seen message-IDs; in a long-running
-        * daemon this would turn into a memory leak even if the 
-        * implementation were perfect.
-        * 
-        * Don't mess with this code casually.  It would be way too easy
-        * to break it in a way that blackholed mail.  Better to pass
-        * the occasional duplicate than to do that...
-        */
+       /* save the message's ID, we may use it for killing duplicates later */
        if (MULTIDROP(ctl) && !strncasecmp(line, "Message-ID:", 11))
-       {
-           if (ctl->lastid && !strcasecmp(ctl->lastid, line))
-           {
-               if (accept_count > 1)
-                   return(PS_REFUSED);
-           }
-           else
-           {
-               if (ctl->lastid)
-                   free(ctl->lastid);
-               ctl->lastid = strdup(line);
-           }
-       }
+           ctl->thisid = xstrdup(line);
 
        /*
         * The University of Washington IMAP server (the reference
@@ -682,7 +665,8 @@ int readheaders(int sock,
         */
        if (!strncasecmp("Return-Path:", line, 12) && (cp = nxtaddr(line)))
        {
-           strcpy(msgblk.return_path, cp);
+           strncpy(msgblk.return_path, cp, sizeof(msgblk.return_path));
+           msgblk.return_path[sizeof(msgblk.return_path)-1] = '\0';
            if (!ctl->mda) {
                free(line);
                continue;
@@ -715,6 +699,7 @@ int readheaders(int sock,
            oldlen = newlen;
        }
 
+       /* find offsets of various special headers */
        if (!strncasecmp("From:", line, 5))
            from_offs = (line - msgblk.headers);
        else if (!strncasecmp("Reply-To:", line, 9))
@@ -741,9 +726,9 @@ int readheaders(int sock,
         * (RFC2822 says the condents of Sender must be a valid mailbox
         * address, which is also what RFC822 4.4.4 implies.)
         */
-       else if (!strncasecmp("Sender:", line, 7) && strchr(line, '@'))
+       else if (!strncasecmp("Sender:", line, 7) && (strchr(line, '@') || strchr(line, '!')))
            sender_offs = (line - msgblk.headers);
-       else if (!strncasecmp("Resent-Sender:", line, 14) && strchr(line, '@'))
+       else if (!strncasecmp("Resent-Sender:", line, 14) && (strchr(line, '@') || strchr(line, '!')))
            resent_sender_offs = (line - msgblk.headers);
 
 #ifdef __UNUSED__
@@ -764,54 +749,100 @@ int readheaders(int sock,
        }
 #endif /* __UNUSED__ */
 
-       else if (!MULTIDROP(ctl))
-           continue;
-
-       else if (!strncasecmp("To:", line, 3)
-                       || !strncasecmp("Cc:", line, 3)
-                       || !strncasecmp("Bcc:", line, 4)
-                       || !strncasecmp("Apparently-To:", line, 14))
+       /* if multidrop is on, gather addressee headers */
+       if (MULTIDROP(ctl))
        {
-           *to_chainptr = xmalloc(sizeof(struct addrblk));
-           (*to_chainptr)->offset = (line - msgblk.headers);
-           to_chainptr = &(*to_chainptr)->next; 
-           *to_chainptr = NULL;
-       }
+           if (!strncasecmp("To:", line, 3)
+               || !strncasecmp("Cc:", line, 3)
+               || !strncasecmp("Bcc:", line, 4)
+               || !strncasecmp("Apparently-To:", line, 14))
+           {
+               *to_chainptr = xmalloc(sizeof(struct addrblk));
+               (*to_chainptr)->offset = (line - msgblk.headers);
+               to_chainptr = &(*to_chainptr)->next; 
+               *to_chainptr = NULL;
+           }
 
-       else if (!strncasecmp("Resent-To:", line, 10)
-                       || !strncasecmp("Resent-Cc:", line, 10)
-                       || !strncasecmp("Resent-Bcc:", line, 11))
-       {
-           *resent_to_chainptr = xmalloc(sizeof(struct addrblk));
-           (*resent_to_chainptr)->offset = (line - msgblk.headers);
-           resent_to_chainptr = &(*resent_to_chainptr)->next; 
-           *resent_to_chainptr = NULL;
-       }
+           else if (!strncasecmp("Resent-To:", line, 10)
+                    || !strncasecmp("Resent-Cc:", line, 10)
+                    || !strncasecmp("Resent-Bcc:", line, 11))
+           {
+               *resent_to_chainptr = xmalloc(sizeof(struct addrblk));
+               (*resent_to_chainptr)->offset = (line - msgblk.headers);
+               resent_to_chainptr = &(*resent_to_chainptr)->next; 
+               *resent_to_chainptr = NULL;
+           }
 
-       else if (ctl->server.envelope != STRING_DISABLED)
-       {
-           if (ctl->server.envelope 
-                       && strcasecmp(ctl->server.envelope, "Received"))
+           else if (ctl->server.envelope != STRING_DISABLED)
            {
-               if (env_offs == -1 && !strncasecmp(ctl->server.envelope,
-                                               line,
-                                               strlen(ctl->server.envelope)))
-               {                               
+               if (ctl->server.envelope 
+                   && strcasecmp(ctl->server.envelope, "Received"))
+               {
+                   if (env_offs == -1 && !strncasecmp(ctl->server.envelope,
+                                                      line,
+                                                      strlen(ctl->server.envelope)))
+                   {                           
+                       if (skipcount++ < ctl->server.envskip)
+                           continue;
+                       env_offs = (line - msgblk.headers);
+                   }    
+               }
+               else if (!received_for && !strncasecmp("Received:", line, 9))
+               {
                    if (skipcount++ < ctl->server.envskip)
                        continue;
-                   env_offs = (line - msgblk.headers);
-               }    
-           }
-           else if (!received_for && !strncasecmp("Received:", line, 9))
-           {
-               if (skipcount++ < ctl->server.envskip)
-                   continue;
-               received_for = parse_received(ctl, line);
+                   received_for = parse_received(ctl, line);
+               }
            }
        }
     }
 
- process_headers:
+ process_headers:    
+    /*
+     * When mail delivered to a multidrop mailbox on the server is
+     * addressed to multiple people on the client machine, there will
+     * be one copy left in the box for each recipient.  This is not a
+     * problem if we have the actual recipient address to dispatch on
+     * (e.g. because we've mined it out of sendmail trace headers, or
+     * a qmail Delivered-To line, or a declared sender envelope line).
+     *
+     * But if we're mining addressees out of the To/Cc/Bcc fields, and
+     * if the mail is addressed to N people, each recipient will
+     * get N copies.  This is bad when N > 1.
+     *
+     * Foil this by suppressing all but one copy of a message with
+     * a given Message-ID.  The accept_count test ensures that
+     * multiple pieces of email with the same Message-ID, each
+     * with a *single* addressee (the N == 1 case), won't be 
+     * suppressed.
+     *
+     * Note: This implementation only catches runs of successive
+     * messages with the same ID, but that should be good
+     * enough. A more general implementation would have to store
+     * ever-growing lists of seen message-IDs; in a long-running
+     * daemon this would turn into a memory leak even if the 
+     * implementation were perfect.
+     * 
+     * Don't mess with this code casually.  It would be way too easy
+     * to break it in a way that blackholed mail.  Better to pass
+     * the occasional duplicate than to do that...
+     */
+    if (!received_for && env_offs == -1 && !delivered_to)
+    {
+       if (ctl->lastid && ctl->thisid && !strcasecmp(ctl->lastid, ctl->thisid))
+       {
+           if (accept_count > 1)
+               return(PS_REFUSED);
+       }
+       else
+       {
+           if (ctl->lastid)
+               free(ctl->lastid);
+           ctl->lastid = ctl->thisid;
+           ctl->thisid = NULL;
+       }
+    }
+
     /*
      * We want to detect this early in case there are so few headers that the
      * dispatch logic barfs.
@@ -820,7 +851,7 @@ int readheaders(int sock,
     {
        if (outlevel > O_SILENT)
            report(stdout,
-                  _("message delimiter found while scanning headers\n"));
+                  GT_("message delimiter found while scanning headers\n"));
     }
 
     /*
@@ -886,8 +917,10 @@ int readheaders(int sock,
        else if (reply_to_offs >= 0 && (ap = nxtaddr(msgblk.headers + reply_to_offs)));
        else if (app_from_offs >= 0 && (ap = nxtaddr(msgblk.headers + app_from_offs)));
        /* multi-line MAIL FROM addresses confuse SMTP terribly */
-       if (ap && !strchr(ap, '\n')) 
-           strcpy(msgblk.return_path, ap);
+       if (ap && !strchr(ap, '\n')) {
+           strncpy(msgblk.return_path, ap, sizeof(msgblk.return_path));
+           msgblk.return_path[sizeof(msgblk.return_path)-1] = '\0';
+       }
     }
 
     /* cons up a list of local recipients */
@@ -958,7 +991,7 @@ int readheaders(int sock,
            save_str(&msgblk.recipients, run.postmaster, XMIT_ACCEPT);
            if (outlevel >= O_DEBUG)
                report(stdout,
-                     _("no local matches, forwarding to %s\n"),
+                     GT_("no local matches, forwarding to %s\n"),
                      run.postmaster);
        }
     }
@@ -973,7 +1006,7 @@ int readheaders(int sock,
     {
        if (outlevel >= O_DEBUG)
            report(stdout,
-                  _("forwarding and deletion suppressed due to DNS errors\n"));
+                  GT_("forwarding and deletion suppressed due to DNS errors\n"));
        free(msgblk.headers);
        msgblk.headers = NULL;
        free_str_list(&msgblk.recipients);
@@ -1072,33 +1105,20 @@ int readheaders(int sock,
 #else
                    sprintf(buf+1,
 #endif /* HAVE_SNPRINTF */
-                           "for %s@%s (by default); ",
-                           user, ctl->destaddr);
+                           "for %s (by default); ",
+                           rcpt_address (ctl, run.postmaster, 0));
                }
                else if (good_addresses == 1)
                {
                    for (idp = msgblk.recipients; idp; idp = idp->next)
                        if (idp->val.status.mark == XMIT_ACCEPT)
                            break;      /* only report first address */
-                   if (strchr(idp->id, '@'))
 #ifdef HAVE_SNPRINTF
                    snprintf(buf+1, sizeof(buf)-1,
 #else                       
                    sprintf(buf+1,
 #endif /* HAVE_SNPRINTF */
-                           "for %s", idp->id);
-                   else
-                       /*
-                        * This could be a bit misleading, as destaddr is
-                        * the forwarding host rather than the actual 
-                        * destination.  Most of the time they coincide.
-                        */
-#ifdef HAVE_SNPRINTF
-                       snprintf(buf+1, sizeof(buf)-1,
-#else                       
-                       sprintf(buf+1,
-#endif /* HAVE_SNPRINTF */
-                               "for %s@%s", idp->id, ctl->destaddr);
+                           "for %s", rcpt_address (ctl, idp->id, 1));
                    sprintf(buf+strlen(buf), " (%s); ",
                            MULTIDROP(ctl) ? "multi-drop" : "single-drop");
                }
@@ -1122,7 +1142,7 @@ int readheaders(int sock,
 
     if (n == -1)
     {
-       report(stdout, _("writing RFC822 msgblk.headers\n"));
+       report(stdout, GT_("writing RFC822 msgblk.headers\n"));
        release_sink(ctl);
        free(msgblk.headers);
        msgblk.headers = NULL;
@@ -1143,28 +1163,45 @@ int readheaders(int sock,
        if (no_local_matches)
        {
            if (reject_count != 1)
-               strcat(errhd, _("no recipient addresses matched declared local names"));
+               strcat(errhd, GT_("no recipient addresses matched declared local names"));
            else
            {
                for (idp = msgblk.recipients; idp; idp = idp->next)
                    if (idp->val.status.mark == XMIT_REJECT)
                        break;
-               sprintf(errhd+strlen(errhd), _("recipient address %s didn't match any local name"), idp->id);
+#ifdef HAVE_SNPRINTF
+               snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd),
+#else
+               sprintf(errhd+strlen(errhd),
+#endif /* HAVE_SNPRINTF */
+                       GT_("recipient address %s didn't match any local name"), idp->id);
            }
        }
 
        if (has_nuls)
        {
            if (errhd[sizeof("X-Fetchmail-Warning: ")])
+#ifdef HAVE_SNPRINTF
+               snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd), "; ");
+           snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd),
+#else
                strcat(errhd, "; ");
-           strcat(errhd, _("message has embedded NULs"));
+           strcat(errhd,
+#endif /* HAVE_SNPRINTF */
+                       GT_("message has embedded NULs"));
        }
 
        if (bad_addresses)
        {
            if (errhd[sizeof("X-Fetchmail-Warning: ")])
+#ifdef HAVE_SNPRINTF
+               snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd), "; ");
+           snprintf(errhd+strlen(errhd), sizeof(errhd)-strlen(errhd),
+#else
                strcat(errhd, "; ");
-           strcat(errhd, _("SMTP listener rejected local recipient addresses: "));
+           strcat(errhd,
+#endif /* HAVE_SNPRINTF */
+                       GT_("SMTP listener rejected local recipient addresses: "));
            errlen = strlen(errhd);
            for (idp = msgblk.recipients; idp; idp = idp->next)
                if (idp->val.status.mark == XMIT_RCPTBAD)
@@ -1293,7 +1330,7 @@ int readbody(int sock, struct query *ctl, flag forward, int len)
 
            if (n < 0)
            {
-               report(stdout, _("writing message text\n"));
+               report(stdout, GT_("writing message text\n"));
                release_sink(ctl);
                return(PS_IOERR);
            }
@@ -1316,6 +1353,23 @@ void init_transact(const struct method *proto)
     protocol = (struct method *)proto;
 }
 
+static void enshroud(char *buf)
+/* shroud a password in the given buffer */
+{
+    char *cp;
+
+    if (shroud[0] && (cp = strstr(buf, shroud)))
+    {
+       char    *sp;
+
+       sp = cp + strlen(shroud);
+       *cp = '*';
+       while (*sp)
+           *cp++ = *sp++;
+       *cp = '\0';
+    }
+}
+
 #if defined(HAVE_STDARG_H)
 void gen_send(int sock, const char *fmt, ... )
 #else
@@ -1340,7 +1394,7 @@ va_dcl
     va_start(ap);
 #endif
 #ifdef HAVE_VSNPRINTF
-    vsnprintf(buf + strlen(buf), sizeof(buf), fmt, ap);
+    vsnprintf(buf + strlen(buf), sizeof(buf)-strlen(buf), fmt, ap);
 #else
     vsprintf(buf + strlen(buf), fmt, ap);
 #endif
@@ -1355,18 +1409,7 @@ va_dcl
 
     if (outlevel >= O_MONITOR)
     {
-       char *cp;
-
-       if (shroud[0] && (cp = strstr(buf, shroud)))
-       {
-           char        *sp;
-
-           sp = cp + strlen(shroud);
-           *cp++ = '*';
-           while (*sp)
-               *cp++ = *sp++;
-           *cp = '\0';
-       }
+       enshroud(buf);
        buf[strlen(buf)-2] = '\0';
        report(stdout, "%s> %s\n", protocol->name, buf);
     }
@@ -1430,7 +1473,7 @@ va_dcl
     va_start(ap);
 #endif
 #ifdef HAVE_VSNPRINTF
-    vsnprintf(buf + strlen(buf), sizeof(buf), fmt, ap);
+    vsnprintf(buf + strlen(buf), sizeof(buf)-strlen(buf), fmt, ap);
 #else
     vsprintf(buf + strlen(buf), fmt, ap);
 #endif
@@ -1445,19 +1488,8 @@ va_dcl
 
     if (outlevel >= O_MONITOR)
     {
-       char *cp;
-
-       if (shroud && shroud[0] && (cp = strstr(buf, shroud)))
-       {
-           char        *sp;
-
-           sp = cp + strlen(shroud);
-           *cp++ = '*';
-           while (*sp)
-               *cp++ = *sp++;
-           *cp = '\0';
-       }
-       buf[strlen(buf)-1] = '\0';
+       enshroud(buf);
+       buf[strlen(buf)-2] = '\0';
        report(stdout, "%s> %s\n", protocol->name, buf);
     }