]> Pileus Git - ~andy/fetchmail/blobdiff - transact.c
Typo fix.
[~andy/fetchmail] / transact.c
index a5e40cbc411239c035c11dc7b87324305ba09f5c..08639d105ea922b1dc5b0b1de7f620edad46f1b3 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+3];  /* 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);
@@ -325,19 +337,30 @@ static char *parse_received(struct query *ctl, char *bufp)
 /* shared by readheaders and readbody */
 static int sizeticker;
 
-#define EMPTYLINE(s)   ((s)[0] == '\r' && (s)[1] == '\n' && (s)[2] == '\0')
+#define EMPTYLINE(s)   (((s)[0] == '\r' && (s)[1] == '\n' && (s)[2] == '\0') \
+                       || ((s)[0] == '\n' && (s)[1] == '\0'))
+
+static int end_of_header (const char *s)
+/* accept "\r*\n" as EOH in order to be bulletproof against broken survers */
+{
+    while (s[0] == '\r')
+       s++;
+    return (s[0] == '\n' && s[1] == '\0');
+}
 
 int readheaders(int sock,
                       long fetchlen,
                       long reallen,
                       struct query *ctl,
-                      int num)
+                      int num,
+                      flag *suppress_readbody)
 /* read message headers and ship to SMTP or MDA */
 /*   sock:             to which the server is connected */
 /*   fetchlen:         length of message according to fetch response */
 /*   reallen:          length of message according to getsizes */
 /*   ctl:              query control record */
 /*   num:              index of message */
+/*   suppress_readbody:        whether call to readbody() should be supressed */
 {
     struct addrblk
     {
@@ -353,15 +376,18 @@ int readheaders(int sock,
     int                        from_offs, reply_to_offs, resent_from_offs;
     int                        app_from_offs, sender_offs, resent_sender_offs;
     int                        env_offs;
-    char               *received_for, *rcv, *cp, *delivered_to;
+    char               *received_for, *rcv, *cp;
+    static char                *delivered_to = NULL;
     int                n, linelen, oldlen, ch, remaining, skipcount;
     struct idlist      *idp;
     flag               no_local_matches = FALSE;
-    flag               headers_ok, has_nuls;
+    flag               has_nuls;
     int                        olderrs, good_addresses, bad_addresses;
+    int                        retain_mail = 0, refuse_mail = 0;
+    flag               already_has_return_path = FALSE;
 
     sizeticker = 0;
-    has_nuls = headers_ok = FALSE;
+    has_nuls = FALSE;
     msgblk.return_path[0] = '\0';
     olderrs = ctl->errcount;
 
@@ -376,6 +402,14 @@ int readheaders(int sock,
      */
     if (msgblk.headers)
        free(msgblk.headers);
+    free_str_list(&msgblk.recipients);
+    if (delivered_to)
+       free(delivered_to);
+
+    /* 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 = 
@@ -385,79 +419,103 @@ int readheaders(int sock,
     skipcount = 0;
     ctl->mimemsg = 0;
 
-    for (remaining = fetchlen; remaining > 0 || protocol->delimited; remaining -= linelen)
+    for (remaining = fetchlen; remaining > 0 || protocol->delimited; )
     {
-       char *line;
+       char *line, *rline;
        int overlong = FALSE;
 
        line = xmalloc(sizeof(buf));
        linelen = 0;
        line[0] = '\0';
        do {
-           set_timeout(mytimeout);
-           if ((n = SockRead(sock, buf, sizeof(buf)-1)) == -1) {
+           do {
+               char    *sp, *tp;
+
+               set_timeout(mytimeout);
+               if ((n = SockRead(sock, buf, sizeof(buf)-1)) == -1) {
+                   set_timeout(0);
+                   free(line);
+                   free(msgblk.headers);
+                   msgblk.headers = NULL;
+                   return(PS_SOCKET);
+               }
                set_timeout(0);
-               free(line);
-               free(msgblk.headers);
-               msgblk.headers = NULL;
-               return(PS_SOCKET);
-           }
-           set_timeout(0);
-           linelen += n;
-           msgblk.msglen += n;
 
                /*
-                * Try to gracefully handle the case, where the length of a
-                * line exceeds MSGBUFSIZE.
+                * Smash out any NULs, they could wreak havoc later on.
+                * Some network stacks seem to generate these at random,
+                * espcially (according to reports) at the beginning of the
+                * first read.  NULs are illegal in RFC822 format.
                 */
-               if ( n && buf[n-1] != '\n' ) {
-                       unsigned int llen = strlen(line);
-                       overlong = TRUE;
-                       line = realloc(line, llen + n + 1);
-                       strcpy(line + llen, buf);
-                       ch = ' '; /* So the next iteration starts */
-                       continue;
-               }
+               for (sp = tp = buf; sp < buf + n; sp++)
+                   if (*sp)
+                       *tp++ = *sp;
+               *tp = '\0';
+               n = tp - buf;
+           } while
+                 (n == 0);
 
-           /* lines may not be properly CRLF terminated; fix this for qmail */
-           if (ctl->forcecr)
+           remaining -= n;
+           linelen += n;
+           msgblk.msglen += n;
+
+           /*
+            * Try to gracefully handle the case where the length of a
+            * line exceeds MSGBUFSIZE.
+            */
+           if (n && buf[n-1] != '\n') 
            {
-               cp = buf + strlen(buf) - 1;
-               if (*cp == '\n' && (cp == buf || cp[-1] != '\r'))
+               overlong = TRUE;
+               rline = (char *) realloc(line, linelen + 1);
+               if (rline == NULL)
                {
-                   *cp++ = '\r';
-                   *cp++ = '\n';
-                   *cp++ = '\0';
+                   free (line);
+                   return(PS_IOERR);
                }
+               line = rline;
+               memcpy(line + linelen - n, buf, n);
+               line[linelen] = '\0';
+               ch = ' '; /* So the next iteration starts */
+               continue;
            }
 
-               /*
-                * Decode MIME encoded headers. We MUST do this before
-                * looking at the Content-Type / Content-Transfer-Encoding
-                * headers (RFC 2046).
-                */
-               if ( ctl->mimedecode && overlong ) {
-                       /*
-                        * If we received an overlong line, we have to decode the
-                        * whole line at once.
-                        */
-                       line = (char *) realloc(line, strlen(line) + strlen(buf) +1);
-                       strcat(line, buf);
-                       UnMimeHeader(line);
+           /* lines may not be properly CRLF terminated; fix this for qmail */
+           /* we don't want to overflow the buffer here */
+           if (ctl->forcecr && buf[n-1]=='\n' && (n==1 || buf[n-2]!='\r'))
+           {
+               char * tcp;
+               rline = (char *) realloc(line, linelen + 2);
+               if (rline == NULL)
+               {
+                   free (line);
+                   return(PS_IOERR);
                }
-               else {
-                       if ( ctl->mimedecode )
-                               UnMimeHeader(buf);
-
-                       line = (char *) realloc(line, strlen(line) + strlen(buf) +1);
-                       strcat(line, buf);
+               line = rline;
+               memcpy(line + linelen - n, buf, n - 1);
+               tcp = line + linelen - 1;
+               *tcp++ = '\r';
+               *tcp++ = '\n';
+               *tcp++ = '\0';
+               n++;
+               linelen++;
+           }
+           else
+           {
+               rline = (char *) realloc(line, linelen + 1);
+               if (rline == NULL)
+               {
+                   free (line);
+                   return(PS_IOERR);
                }
+               line = rline;
+               memcpy(line + linelen - n, buf, n + 1);
+           }
 
            /* check for end of headers */
-           if (EMPTYLINE(line))
+           if (end_of_header(line))
            {
-               headers_ok = TRUE;
-               has_nuls = (linelen != strlen(line));
+               if (linelen != strlen (line))
+                   has_nuls = TRUE;
                free(line);
                goto process_headers;
            }
@@ -469,7 +527,13 @@ int readheaders(int sock,
             */
            if (protocol->delimited && line[0] == '.' && EMPTYLINE(line+1))
            {
-               has_nuls = (linelen != strlen(line));
+               if (outlevel > O_SILENT)
+                   report(stdout,
+                          GT_("message delimiter found while scanning headers\n"));
+               if (suppress_readbody)
+                   *suppress_readbody = TRUE;
+               if (linelen != strlen (line))
+                   has_nuls = TRUE;
                free(line);
                goto process_headers;
            }
@@ -478,15 +542,18 @@ int readheaders(int sock,
             * At least one brain-dead website (netmind.com) is known to
             * send out robotmail that's missing the RFC822 delimiter blank
             * line before the body! Without this check fetchmail segfaults.
-            * With it, we treat such messages as though they had the missing
-            * blank line.
+            * With it, we treat such messages as spam and refuse them.
             */
-           if (!isspace(line[0]) && !strchr(line, ':'))
+           if (!refuse_mail && !isspace(line[0]) && !strchr(line, ':'))
            {
-               headers_ok = TRUE;
-               has_nuls = (linelen != strlen(line));
-               free(line);
-               goto process_headers;
+               if (linelen != strlen (line))
+                   has_nuls = TRUE;
+               if (outlevel > O_SILENT)
+                   report(stdout,
+                          GT_("incorrect header line found while scanning headers\n"));
+               if (outlevel >= O_VERBOSE)
+                   report (stdout, GT_("line: %s"), line);
+               refuse_mail = 1;
            }
 
            /* check for RFC822 continuations */
@@ -502,7 +569,7 @@ int readheaders(int sock,
            sizeticker += linelen;
            while (sizeticker >= SIZETICKER)
            {
-               if ((!run.use_syslog && !isafile(1)) || run.showdots)
+               if (outlevel > O_SILENT && run.showdots)
                {
                    fputc('.', stdout);
                    fflush(stdout);
@@ -510,49 +577,38 @@ int readheaders(int sock,
                sizeticker -= SIZETICKER;
            }
        }
+               /*
+                * Decode MIME encoded headers. We MUST do this before
+                * looking at the Content-Type / Content-Transfer-Encoding
+                * headers (RFC 2046).
+                */
+               if ( ctl->mimedecode )
+               {
+                   char *tcp;
+                   UnMimeHeader(line);
+                   /* the line is now shorter. So we retrace back till we find our terminating
+                    * combination \n\0, we move backwards to make sure that we don't catch som
+                    * \n\0 stored in the decoded part of the message */
+                   for(tcp = line + linelen - 1; tcp > line && (*tcp != 0 || tcp[-1] != '\n'); tcp--);
+                   if(tcp > line) linelen = tcp - line;
+               }
 
-       /* 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...
-        */
-       if (MULTIDROP(ctl) && !strncasecmp(line, "Message-ID:", 11))
+       /* skip processing if we are going to retain or refuse this mail */
+       if (retain_mail || refuse_mail)
        {
-           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);
-           }
+           free(line);
+           continue;
        }
 
+       /* we see an ordinary (non-header, non-message-delimiter line */
+       if (linelen != strlen (line))
+           has_nuls = TRUE;
+
+       /* save the message's ID, we may use it for killing duplicates later */
+       if (MULTIDROP(ctl) && !strncasecmp(line, "Message-ID:", 11))
+           ctl->thisid = xstrdup(line);
+
        /*
         * The University of Washington IMAP server (the reference
         * implementation of IMAP4 written by Mark Crispin) relies
@@ -575,9 +631,8 @@ int readheaders(int sock,
 #endif /* POP2_ENABLE */
            if (num == 1 && !strncasecmp(line, "X-IMAP:", 7)) {
                free(line);
-               free(msgblk.headers);
-               msgblk.headers = NULL;
-               return(PS_RETAINED);
+               retain_mail = 1;
+               continue;
            }
 
        /*
@@ -656,7 +711,7 @@ int readheaders(int sock,
        }
 
        if (ctl->rewrite)
-           line = reply_hack(line, ctl->server.truename);
+           line = reply_hack(line, ctl->server.truename, &linelen);
 
        /*
         * OK, this is messy.  If we're forwarding by SMTP, it's the
@@ -679,10 +734,19 @@ int readheaders(int sock,
         * not trigger bounces if delivery fails.  What we *do* need to do is
         * make sure we never try to rewrite such a blank Return-Path.  We
         * handle this with a check for <> in the rewrite logic above.
+        *
+        * Also, if an email has multiple Return-Path: statement, we only
+        * read the first occurance, as some spam email has more than one
+        * Return-Path.
+        *
         */
-       if (!strncasecmp("Return-Path:", line, 12) && (cp = nxtaddr(line)))
+       if ((already_has_return_path==FALSE) && !strncasecmp("Return-Path:", line, 12) && (cp = nxtaddr(line)))
        {
-           strcpy(msgblk.return_path, cp);
+           already_has_return_path = TRUE;
+           if (cp[0]=='\0')    /* nxtaddr() strips the brackets... */
+               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;
@@ -691,9 +755,10 @@ int readheaders(int sock,
 
        if (!msgblk.headers)
        {
-           oldlen = strlen(line);
+           oldlen = linelen;
            msgblk.headers = xmalloc(oldlen + 1);
-           (void) strcpy(msgblk.headers, line);
+           (void) memcpy(msgblk.headers, line, linelen);
+           msgblk.headers[oldlen] = '\0';
            free(line);
            line = msgblk.headers;
        }
@@ -702,19 +767,21 @@ int readheaders(int sock,
            char *newhdrs;
            int newlen;
 
-           newlen = oldlen + strlen(line);
+           newlen = oldlen + linelen;
            newhdrs = (char *) realloc(msgblk.headers, newlen + 1);
            if (newhdrs == NULL) {
                free(line);
                return(PS_IOERR);
            }
            msgblk.headers = newhdrs;
-           strcpy(msgblk.headers + oldlen, line);
+           memcpy(msgblk.headers + oldlen, line, linelen);
+           msgblk.headers[newlen] = '\0';
            free(line);
            line = msgblk.headers + oldlen;
            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))
@@ -735,15 +802,15 @@ int readheaders(int sock,
         * human user or a computer program) rather than a standard
         * address."  That implies that the contents of the Sender
         * field don't need to be a legal email address at all So
-        * ignore any Sender or Resent-Semnder lines unless they
+        * ignore any Sender or Resent-Sender lines unless they
         * contain @.
         *
-        * (RFC2822 says the condents of Sender must be a valid mailbox
+        * (RFC2822 says the contents 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,63 +831,107 @@ 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:    
+
+    if (retain_mail)
+    {
+       free(msgblk.headers);
+       msgblk.headers = NULL;
+       return(PS_RETAINED);
+    }
+    if (refuse_mail)
+       return(PS_REFUSED);
     /*
-     * We want to detect this early in case there are so few headers that the
-     * dispatch logic barfs.
+     * 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 (!headers_ok)
+    if (!received_for && env_offs == -1 && !delivered_to)
     {
-       if (outlevel > O_SILENT)
-           report(stdout,
-                  _("message delimiter found while scanning headers\n"));
+       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;
+       }
     }
 
     /*
@@ -886,8 +997,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 */
@@ -913,6 +1026,7 @@ int readheaders(int sock,
    {
            find_server_names(delivered_to, ctl, &msgblk.recipients);
        free(delivered_to);
+       delivered_to = NULL;
    }
        else if (received_for)
            /*
@@ -958,7 +1072,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 +1087,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 +1186,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 +1223,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;
@@ -1130,7 +1231,7 @@ int readheaders(int sock,
        return(PS_IOERR);
     }
     else if ((run.poll_interval == 0 || nodetach) && outlevel >= O_VERBOSE && !isafile(2))
-       fputs("#", stderr);
+       fputs("#", stdout);
 
     /* write error notifications */
     if (no_local_matches || has_nuls || bad_addresses)
@@ -1143,28 +1244,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)
@@ -1195,9 +1313,7 @@ int readheaders(int sock,
     *cp++ = '\0';
     stuffline(ctl, buf);
 
-/*    free(msgblk.headers); */
-    free_str_list(&msgblk.recipients);
-    return(headers_ok ? PS_SUCCESS : PS_TRUNCATED);
+    return(PS_SUCCESS);
 }
 
 int readbody(int sock, struct query *ctl, flag forward, int len)
@@ -1239,7 +1355,7 @@ int readbody(int sock, struct query *ctl, flag forward, int len)
            sizeticker += linelen;
            while (sizeticker >= SIZETICKER)
            {
-               if (outlevel > O_SILENT && (((run.poll_interval == 0 || nodetach) && !isafile(1)) || run.showdots))
+               if (outlevel > O_SILENT && run.showdots)
                {
                    fputc('.', stdout);
                    fflush(stdout);
@@ -1252,9 +1368,7 @@ int readbody(int sock, struct query *ctl, flag forward, int len)
        /* check for end of message */
        if (protocol->delimited && *inbufp == '.')
        {
-           if (inbufp[1] == '\r' && inbufp[2] == '\n' && inbufp[3] == '\0')
-               break;
-           else if (inbufp[1] == '\n' && inbufp[2] == '\0')
+           if (EMPTYLINE(inbufp+1))
                break;
            else
                msgblk.msglen--;        /* subtract the size of the dot escape */
@@ -1293,7 +1407,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 +1430,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 +1471,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 +1486,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);
     }
@@ -1386,7 +1506,13 @@ int size;        /* length of buffer */
     {
        set_timeout(0);
        phase = oldphase;
-       return(PS_SOCKET);
+       if(isidletimeout())
+       {
+         resetidletimeout();
+         return(PS_IDLETIMEOUT);
+       }
+       else
+         return(PS_SOCKET);
     }
     else
     {
@@ -1430,7 +1556,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 +1571,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);
     }