]> Pileus Git - ~andy/fetchmail/blobdiff - driver.c
Eliminate some unnecessary headers.
[~andy/fetchmail] / driver.c
index cce53fe5029170aeeb1b32c8dedfc5570d3b6887..53b41e5acd49e371828984a93de075ceed7175ed 100644 (file)
--- a/driver.c
+++ b/driver.c
@@ -5,30 +5,20 @@
 
 /***********************************************************************
   module:       driver.c
-  project:      popclient
+  project:      fetchmail
   programmer:   Eric S. Raymond
   description:  Generic driver for mail fetch method protocols
 
  ***********************************************************************/
 
 #include  <config.h>
-#include  <varargs.h>
-
 #include  <stdio.h>
-#if defined(STDC_HEADERS)
-#include  <string.h>
-#endif
-#if defined(HAVE_UNISTD_H)
-#include  <unistd.h>
-#endif
-
-#include  <sys/time.h>
-#include  <ctype.h>
-#include  <errno.h>
 #include  <malloc.h>
+#include  <varargs.h>
+#include  <sys/time.h>
 
 #include  "socket.h"
-#include  "popclient.h"
+#include  "fetchmail.h"
 #include  "smtp.h"
 
 static struct method *protocol;
@@ -39,8 +29,10 @@ char tag[TAGLEN];
 static int tagnum;
 #define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag)
 
+#ifdef HAVE_PROTOTYPES
 static int gen_readmsg (int socket, int mboxfd, long len, int delimited,
-       char *host, int topipe, int rewrite);
+       char *user, char *host, int topipe, int rewrite);
+#endif /* HAVE_PROTOTYPES */
 
 /*********************************************************************
   function:      do_protocol
@@ -53,7 +45,7 @@ static int gen_readmsg (int socket, int mboxfd, long len, int delimited,
     proto        protocol method pointer
 
   return value:  exit code from the set of PS_.* constants defined in 
-                 popclient.h
+                 fetchmail.h
   calls:
   globals:       reads outlevel.
  *********************************************************************/
@@ -64,7 +56,7 @@ struct method *proto;
 {
     int ok, len;
     int mboxfd;
-    char buf [POPBUFSIZE], host[HOSTLEN];
+    char buf [POPBUFSIZE+1], host[HOSTLEN+1];
     int socket;
     int first,number,count;
 
@@ -76,8 +68,15 @@ struct method *proto;
        if ((mboxfd = openuserfolder(queryctl)) < 0) 
            return(PS_IOERR);
     } else if (queryctl->output == TO_SMTP) {
-       if ((mboxfd = Socket(queryctl->smtphost,SMTP_PORT)) < 0) 
+       if ((mboxfd = Socket(queryctl->smtphost, SMTP_PORT)) < 0) 
            return(PS_SOCKET);
+
+       /* eat the greeting message */
+       if (SMTP_ok(mboxfd, NULL) != SM_OK) {
+           close(mboxfd);
+           mboxfd = 0;
+           return(PS_SMTP);
+       }
     
        /* make it look like mail is coming from the server */
        if (SMTP_helo(mboxfd,queryctl->servername) != SM_OK) {
@@ -88,7 +87,9 @@ struct method *proto;
     }
 
     /* open a socket to the mail server */
-    if ((socket = Socket(queryctl->servername,protocol->port)) < 0) {
+    if ((socket = Socket(queryctl->servername,
+                        queryctl->port ? queryctl->port : protocol->port))<0)
+    {
        perror("do_protocol: socket");
        ok = PS_SOCKET;
        goto closeUp;
@@ -103,10 +104,6 @@ struct method *proto;
        goto closeUp;
     }
 
-    /* print the greeting */
-    if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
-       fprintf(stderr,"%s greeting: %s\n", protocol->name, buf);
-
     /* try to get authorized to fetch mail */
     ok = (protocol->getauth)(socket, queryctl, buf);
     if (ok == PS_ERROR)
@@ -120,11 +117,19 @@ struct method *proto;
 
     /* show them how many messages we'll be downloading */
     if (outlevel > O_SILENT && outlevel < O_VERBOSE)
-       if (first > 1) 
-           fprintf(stderr,"%d messages in folder, %d new messages.\n", 
-                   count, count - first + 1);
+       if (count == 0)
+           fprintf(stderr, "No mail from %s\n", queryctl->servername);
+       else if (first > 1) 
+           fprintf(stderr,
+                   "%d message%s from %s, %d new messages.\n", 
+                   count, count > 1 ? "s" : "", 
+                   queryctl->servername, count - first + 1);
        else
-           fprintf(stderr,"%d %smessages in folder.\n", count, ok ? "" : "new ");
+           fprintf(stderr,
+                   "%d %smessage%s from %s.\n",
+                   count, ok ? "" : "new ", 
+                   count > 1 ? "s" : "", 
+                   queryctl->servername);
 
     if (count > 0) { 
        for (number = queryctl->flush ? 1 : first;  number<=count; number++) {
@@ -150,6 +155,7 @@ struct method *proto;
                    else
                        fprintf(stderr,"fetching message %d (%d bytes)\n",number,len);
                ok = gen_readmsg(socket,mboxfd,len,protocol->delimited,
+                                queryctl->localname,
                                 queryctl->servername,
                                 queryctl->output, 
                                 !queryctl->norewrite);
@@ -160,14 +166,17 @@ struct method *proto;
            }
 
            /* maybe we delete this message now? */
-           if ((number < first && queryctl->flush) || !queryctl->keep) {
-               if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
-                   fprintf(stderr,"flushing message %d\n", number);
-               else
-                   ;
-               ok = gen_transact(socket, protocol->delete_cmd, number);
-               if (ok != 0)
-                   goto cleanUp;
+           if (protocol->delete_cmd)
+           {
+               if ((number < first && queryctl->flush) || !queryctl->keep) {
+                   if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
+                       fprintf(stderr,"flushing message %d\n", number);
+                   else
+                       ;
+                   ok = gen_transact(socket, protocol->delete_cmd, number);
+                   if (ok != 0)
+                       goto cleanUp;
+               }
            }
 
            /* close the mail pipe, we'll reopen before next message */
@@ -211,8 +220,10 @@ closeUp:
        if (closeuserfolder(mboxfd) < 0 && ok == 0)
            ok = PS_IOERR;
     }
-    else if (queryctl->output == TO_SMTP && mboxfd  > 0)
+    else if (queryctl->output == TO_SMTP && mboxfd > 0) {
+       SMTP_quit(mboxfd);
        close(mboxfd);
+    }
 
     if (ok == PS_IOERR || ok == PS_SOCKET) 
        perror("do_protocol: cleanUp");
@@ -238,7 +249,7 @@ int socket;
 const char *fmt;
 va_dcl {
 
-  char buf [POPBUFSIZE];
+  char buf [POPBUFSIZE+1];
   va_list ap;
 
   if (protocol->tagged)
@@ -276,7 +287,7 @@ const char *fmt;
 va_dcl {
 
   int ok;
-  char buf [POPBUFSIZE];
+  char buf [POPBUFSIZE+1];
   va_list ap;
 
   if (protocol->tagged)
@@ -294,12 +305,234 @@ va_dcl {
     fprintf(stderr,"> %s\n", buf);
 
   ok = (protocol->parse_response)(buf,socket);
-  if (ok != 0 && outlevel > O_SILENT && outlevel < O_VERBOSE)
+  if (ok != 0 && outlevel > O_SILENT && outlevel <= O_VERBOSE)
     fprintf(stderr,"%s\n",buf);
 
   return(ok);
 }
 
+/*********************************************************************
+  function:      
+  description:   hack message headers so replies will work properly
+
+  arguments:
+    after        where to put the hacked header
+    before       header to hack
+    host         name of the pop header
+
+  return value:  none.
+  calls:         none.
+ *********************************************************************/
+
+static void reply_hack(buf, host)
+/* hack local mail IDs -- code by Eric S. Raymond 20 Jun 1996 */
+char *buf;
+const char *host;
+{
+    const char *from;
+    int state = 0;
+    char mycopy[POPBUFSIZE+1];
+
+    if (strncmp("From: ", buf, 6)
+       && strncmp("To: ", buf, 4)
+       && strncmp("Reply-", buf, 6)
+       && strncmp("Cc: ", buf, 4)
+       && strncmp("Bcc: ", buf, 5)) {
+       return;
+    }
+
+    strcpy(mycopy, buf);
+    for (from = mycopy; *from; from++)
+    {
+       switch (state)
+       {
+       case 0:   /* before header colon */
+           if (*from == ':')
+               state = 1;
+           break;
+
+       case 1:   /* we've seen the colon, we're looking for addresses */
+           if (*from == '"')
+               state = 2;
+           else if (*from == '(')
+               state = 3;    
+           else if (*from == '<' || isalnum(*from))
+               state = 4;
+           break;
+
+       case 2:   /* we're in a quoted human name, copy and ignore */
+           if (*from == '"')
+               state = 1;
+           break;
+
+       case 3:   /* we're in a parenthesized human name, copy and ignore */
+           if (*from == ')')
+               state = 1;
+           break;
+
+       case 4:   /* the real work gets done here */
+           /*
+            * We're in something that might be an address part,
+            * either a bare unquoted/unparenthesized text or text
+            * enclosed in <> as per RFC822.
+            */
+           /* if the address part contains an @, don't mess with it */
+           if (*from == '@')
+               state = 5;
+
+           /* If the address token is not properly terminated, ignore it. */
+           else if (*from == ' ' || *from == '\t')
+               state = 1;
+
+           /*
+            * On proper termination with no @, insert hostname.
+            * Case '>' catches <>-enclosed mail IDs.  Case ',' catches
+            * comma-separated bare IDs.  Cases \r and \n catch the case
+            * of a single ID alone on the line.
+            */
+           else if (strchr(">,\r\n", *from))
+           {
+               strcpy(buf, "@");
+               strcat(buf, host);
+               buf += strlen(buf);
+               state = 1;
+           }
+
+           /* everything else, including alphanumerics, just passes through */
+           break;
+
+       case 5:   /* we're in a remote mail ID, no need to append hostname */
+           if (*from == '>' || *from == ',' || isspace(*from))
+               state = 1;
+           break;
+       }
+
+       /* all characters from the old buffer get copied to the new one */
+       *buf++ = *from;
+    }
+    *buf++ = '\0';
+}
+
+/*********************************************************************
+  function:      nxtaddr
+  description:   Parse addresses in succession out of a specified RFC822
+                 header.  Note 1: RFC822 escaping with \ is *not* handled.
+                 Note 2: it is important that this routine not stop on \r,
+                 since we use \r as a marker for RFC822 continuations below.
+  arguments:     
+    hdr          header line to be parsed, NUL to continue in previous hdr
+
+  return value:  next address, or NUL if there is no next address
+  calls:         none
+ *********************************************************************/
+
+static char *nxtaddr(hdr)
+char *hdr;
+{
+    static char        *hp, *tp, address[POPBUFSIZE+1];
+    static     state;
+
+    if (hdr)
+    {
+       hp = hdr;
+       state = 0;
+    }
+
+    for (; *hp; hp++)
+    {
+       switch (state)
+       {
+       case 0:   /* before header colon */
+           if (*hp == '\n')
+               return(NULL);
+           else if (*hp == ':')
+           {
+               state = 1;
+               tp = address;
+           }
+           break;
+
+       case 1:   /* we've seen the colon, now grab the address */
+           if ((*hp == '\n') || (*hp == ','))  /* end of address list */
+           {
+               *tp++ = '\0';
+               return(address);
+           }
+           else if (*hp == '"') /* quoted string */
+           {
+               state = 2;
+               *tp++ = *hp;
+           }
+           else if (*hp == '(') /* address comment -- ignore */
+               state = 3;    
+           else if (*hp == '<') /* begin <address> */
+           {
+               state = 4;
+               tp = address;
+           }
+           else if (isspace(*hp)) /* ignore space */
+               state = 1;
+           else   /* just take it */
+           {
+               state = 1;
+               *tp++ = *hp;
+           }
+           break;
+
+       case 2:   /* we're in a quoted string, copy verbatim */
+           if (*hp == '\n')
+               return(NULL);
+           if (*hp != '"')
+               *tp++ = *hp;
+           else if (*hp == '"')
+           {
+               *tp++ = *hp;
+               state = 1;
+           }
+           break;
+
+       case 3:   /* we're in a parenthesized comment, ignore */
+           if (*hp == '\n')
+               return(NULL);
+           else if (*hp == ')')
+               state = 1;
+           break;
+
+       case 4:   /* possible <>-enclosed address */
+           if (*hp == '>') /* end of address */
+           {
+               *tp++ = '\0';
+               state = 1;
+               return(address);
+           }
+           else if (*hp == '<')  /* nested <> */
+               tp = address;
+           else if (*hp == '"') /* quoted address */
+           {
+               *tp++ = *hp;
+               state = 5;
+           }
+           else  /* just copy address */
+               *tp++ = *hp;
+           break;
+
+       case 5:   /* we're in a quoted address, copy verbatim */
+           if (*hp == '\n')  /* mismatched quotes */
+               return(NULL);
+           if (*hp != '"')  /* just copy it if it isn't a quote */
+               *tp++ = *hp;
+           else if (*hp == '"')  /* end of quoted string */
+           {
+               *tp++ = *hp;
+               state = 4;
+           }
+           break;
+       }
+    }
+
+    return(NULL);
+}
+
 /*********************************************************************
   function:      gen_readmsg
   description:   Read the message content 
@@ -310,6 +543,7 @@ va_dcl {
     mboxfd       open file descriptor to which the retrieved message will
                  be written.
     len          length of text 
+    popuser      name of the POP user 
     pophost      name of the POP host 
     output       output mode
 
@@ -318,17 +552,18 @@ va_dcl {
   globals:       reads outlevel. 
  *********************************************************************/
 
-int gen_readmsg (socket,mboxfd,len,delimited,pophost,output,rewrite)
+int gen_readmsg (socket,mboxfd,len,delimited,popuser,pophost,output,rewrite)
 int socket;
 int mboxfd;
 long len;
 int delimited;
+char *popuser;
 char *pophost;
 int output;
 int rewrite;
 { 
-    char buf [MSGBUFSIZE]; 
-    char fromBuf[MSGBUFSIZE];
+    char buf [MSGBUFSIZE+1]; 
+    char fromBuf[MSGBUFSIZE+1];
     char *bufp, *headers, *unixfrom, *fromhdr, *tohdr, *cchdr, *bcchdr;
     int n, oldlen;
     int inheaders;
@@ -342,14 +577,14 @@ int rewrite;
        fprintf(stderr,"reading message %d",++msgnum);
        /* won't do the '...' if retrieved messages are being sent to stdout */
        if (mboxfd == 1)
-           fputs(".\n",stderr);
+           fputs("\n",stderr);
     }
 
     /* read the message content from the server */
     inheaders = 1;
     headers = unixfrom = fromhdr = tohdr = cchdr = bcchdr = NULL;
     lines = 0;
-    sizeticker = MSGBUFSIZE;
+    sizeticker = 0;
     while (delimited || len > 0) {
        if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
            return(PS_SOCKET);
@@ -362,7 +597,7 @@ int rewrite;
            if (delimited && *bufp == 0)
                break;  /* end of message */
        }
-       strcat(bufp,"\n");
+       strcat(bufp, output == TO_SMTP && !inheaders ? "\r\n" : "\n");
      
        if (inheaders)
         {
@@ -380,8 +615,18 @@ int rewrite;
            }
            else
            {
-               int     newlen = oldlen + strlen(bufp);
-
+               int     newlen;
+
+               /*
+                * We deal with RFC822 continuation lines here.
+                * Replace previous '\n' with '\r' so nxtaddr 
+                * and reply-hack will be able to see past it.
+                * We'll undo this before writing the header.
+                */
+               if (isspace(bufp[0]))
+                   headers[oldlen-1] = '\r';
+
+               newlen = oldlen + strlen(bufp);
                headers = realloc(headers, newlen + 1);
                if (headers == NULL)
                    return(PS_IOERR);
@@ -392,23 +637,61 @@ int rewrite;
 
            if (!strncmp(bufp,"From ",5))
                unixfrom = bufp;
-           else if (strncmp("From: ", bufp, 6))
-               tohdr = bufp;
-           else if (strncmp("To: ", bufp, 4))
+           else if (!strncmp("From: ", bufp, 6))
                fromhdr = bufp;
-           else if (strncmp("Cc: ", bufp, 4))
+           else if (!strncmp("To: ", bufp, 4))
+               tohdr = bufp;
+           else if (!strncmp("Cc: ", bufp, 4))
                cchdr = bufp;
-           else if (strncmp("Bcc: ", bufp, 5))
+           else if (!strncmp("Bcc: ", bufp, 5))
                bcchdr = bufp;
 
            goto skipwrite;
        }
        else if (headers)
        {
+           char        *cp;
+
            switch (output)
            {
            case TO_SMTP:
+               if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
+                   return(PS_SMTP);
+#ifdef SMTP_RESEND
+               /*
+                * This is what we'd do if fetchmail were a real MDA
+                * a la sendmail -- crack all the destination headers
+                * and send to every address we can reach via SMTP.
+                */
+               if ((cp = nxtaddr(tohdr)) != (char *)NULL)
+                   do {
+                       if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
+                           return(PS_SMTP);
+                   } while
+                       (cp = nxtaddr(NULL));
+               if ((cp = nxtaddr(cchdr)) != (char *)NULL)
+                   do {
+                       if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
+                           return(PS_SMTP);
+                   } while
+                       (cp = nxtaddr(NULL));
+               if ((cp = nxtaddr(bcchdr)) != (char *)NULL)
+                   do {
+                       if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
+                           return(PS_SMTP);
+                   } while
+                       (cp = nxtaddr(NULL));
+#else
+               /*
+                * Since we're really only fetching mail for one user
+                * per host query, we can be simpler
+                */
+               if (SMTP_rcpt(mboxfd, popuser) == SM_UNRECOVERABLE)
+                   return(PS_SMTP);
+#endif /* SMTP_RESEND */
                SMTP_data(mboxfd);
+               if (outlevel == O_VERBOSE)
+                   fputs("SMTP> ", stderr);
                break;
 
            case TO_FOLDER:
@@ -418,11 +701,16 @@ int rewrite;
                else
                {
                    now = time(NULL);
-                   sprintf(fromBuf,"From POPmail %s",ctime(&now));
+                   if (fromhdr && (cp = nxtaddr(fromhdr)))
+                       sprintf(fromBuf,
+                               "From %s %s", cp, ctime(&now));
+                   else
+                       sprintf(fromBuf,
+                               "From POPmail %s",ctime(&now));
                }
 
                if (write(mboxfd,fromBuf,strlen(fromBuf)) < 0) {
-                   perror("gen_readmsg: write");
+                   perror("gen_readmsg: writing From header");
                    return(PS_IOERR);
                }
                break;
@@ -431,13 +719,20 @@ int rewrite;
                break;
            }
 
+           /* change continuation markers back to regular newlines */
+           for (cp = headers; cp < headers +  oldlen; cp++)
+               if (*cp == '\r')
+                   *cp = '\n';
+
            if (write(mboxfd,headers,oldlen) < 0)
            {
                free(headers);
                headers = NULL;
-               perror("gen_readmsg: write");
+               perror("gen_readmsg: writing RFC822 headers");
                return(PS_IOERR);
            }
+           else if (outlevel == O_VERBOSE)
+               fputs("#", stderr);
            free(headers);
            headers = NULL;
        }
@@ -445,31 +740,32 @@ int rewrite;
        /* write this line to the file */
        if (write(mboxfd,bufp,strlen(bufp)) < 0)
        {
-           perror("gen_readmsg: write");
+           perror("gen_readmsg: writing message text");
            return(PS_IOERR);
        }
+       else if (outlevel == O_VERBOSE)
+           fputc('*', stderr);
 
     skipwrite:;
 
-       sizeticker -= strlen(bufp);
-       if (sizeticker <= 0)
+       sizeticker += strlen(bufp);
+       while (sizeticker >= MSGBUFSIZE)
        {
            if (outlevel > O_SILENT && outlevel < O_VERBOSE && mboxfd != 1)
                fputc('.',stderr);
-           sizeticker = MSGBUFSIZE;
+           sizeticker -= MSGBUFSIZE;
        }
        lines++;
     }
 
+    if (outlevel == O_VERBOSE)
+       fputc('\n', stderr);
+
     /* write message terminator, if any */
     switch (output)
     {
     case TO_SMTP:
-       if (write(mboxfd,".\r\n",3) < 0) {
-           perror("gen_readmsg: write");
-           return(PS_IOERR);
-       }
-       if (SMTP_ok(mboxfd, NULL) != SM_OK)
+       if (SMTP_eom(mboxfd) != SM_OK)
            return(PS_SMTP);
        break;
 
@@ -478,7 +774,7 @@ int rewrite;
        /* The server may not write the extra newline required by the Unix
           mail folder format, so we write one here just in case */
        if (write(mboxfd,"\n",1) < 0) {
-           perror("gen_readmsg: write");
+           perror("gen_readmsg: writing terminator");
            return(PS_IOERR);
        }
        break;
@@ -488,17 +784,18 @@ int rewrite;
           it has been defined */
 #ifdef BINMAIL_TERM
        if (write(mboxfd,BINMAIL_TERM,strlen(BINMAIL_TERM)) < 0) {
-           perror("gen_readmsg: write");
+           perror("gen_readmsg: writing terminator");
            return(PS_IOERR);
        }
 #endif /* BINMAIL_TERM */
+       break;
     }
 
     /* finish up display output */
     if (outlevel == O_VERBOSE)
        fprintf(stderr,"(%d lines of message content)\n",lines);
     else if (outlevel > O_SILENT && mboxfd != 1) 
-       fputs(".\n",stderr);
+       fputs("\n",stderr);
     else
        ;
     return(0);