]> Pileus Git - ~andy/fetchmail/blobdiff - pop3.c
Drop back to using SockGets/SockWrite.
[~andy/fetchmail] / pop3.c
diff --git a/pop3.c b/pop3.c
index 4f74c0c382f70831b6560df4daebe39b374ebc12..0238fc3573bf19e5c380af2b092871d36778af02 100644 (file)
--- a/pop3.c
+++ b/pop3.c
@@ -1,72 +1,74 @@
-/* Copyright 1993-95 by Carl Harris, Jr. Copyright 1996 by Eric S. Raymond
- * All rights reserved.
+/*
+ * pop3.c -- POP3 protocol methods
+ *
  * For license terms, see the file COPYING in this directory.
  */
 
-/***********************************************************************
-  module:       pop3.c
-  project:      fetchmail
-  programmer:   Carl Harris, ceharris@mal.com
-               Hacks and bug fixes by esr.
-  description:  POP3 client code.
-
- ***********************************************************************/
-
 #include  <config.h>
+
 #include  <stdio.h>
-#include  "socket.h"
+#include  <string.h>
+#include  <ctype.h>
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#if defined(STDC_HEADERS)
+#include  <stdlib.h>
+#endif
 #include  "fetchmail.h"
+#include  "socket.h"
 
-int pop3_ok (argbuf,socket)
-/* parse command response */
-char *argbuf;
-int socket;
-{
-  int ok;
-  char buf [POPBUFSIZE+1];
-  char *bufp;
+#define PROTOCOL_ERROR {error(0, 0, "protocol error"); return(PS_ERROR);}
 
-  if (SockGets(socket, buf, sizeof(buf)) >= 0) {
-    if (outlevel == O_VERBOSE)
-      fprintf(stderr,"%s\n",buf);
+static int last;
 
-    bufp = buf;
-    if (*bufp == '+' || *bufp == '-')
-      bufp++;
-    else
-      return(PS_PROTOCOL);
+int pop3_ok (FILE *sockfp, char *argbuf)
+/* parse command response */
+{
+    int ok;
+    char buf [POPBUFSIZE+1];
+    char *bufp;
+
+    if (SockGets(buf, sizeof(buf), sockfp)) {
+       if (buf[strlen(buf)-1] == '\n')
+           buf[strlen(buf)-1] = '\0';
+       if (buf[strlen(buf)-1] == '\r')
+           buf[strlen(buf)-1] = '\r';
+       if (outlevel == O_VERBOSE)
+           error(0, 0, "POP3< %s", buf);
+
+       bufp = buf;
+       if (*bufp == '+' || *bufp == '-')
+           bufp++;
+       else
+           return(PS_PROTOCOL);
 
-    while (isalpha(*bufp))
-      bufp++;
-    *(bufp++) = '\0';
+       while (isalpha(*bufp))
+           bufp++;
+       *(bufp++) = '\0';
 
-    if (strcmp(buf,"+OK") == 0)
-      ok = 0;
-    else if (strcmp(buf,"-ERR") == 0)
-      ok = PS_ERROR;
-    else
-      ok = PS_PROTOCOL;
+       if (strcmp(buf,"+OK") == 0)
+           ok = 0;
+       else if (strcmp(buf,"-ERR") == 0)
+           ok = PS_ERROR;
+       else
+           ok = PS_PROTOCOL;
 
-    if (argbuf != NULL)
-      strcpy(argbuf,bufp);
-  }
-  else 
-    ok = PS_SOCKET;
+       if (argbuf != NULL)
+           strcpy(argbuf,bufp);
+    }
+    else 
+       ok = PS_SOCKET;
 
-  return(ok);
+    return(ok);
 }
 
-int pop3_getauth(socket, queryctl, greeting)
+int pop3_getauth(FILE *sockfp, struct query *ctl, char *greeting)
 /* apply for connection authorization */
-int socket;
-struct hostrec *queryctl;
-char *greeting;
 {
-    char buf [POPBUFSIZE+1];
-
-#if defined(HAVE_APOP_SUPPORT)
     /* build MD5 digest from greeting timestamp + password */
-    if (queryctl->protocol == P_APOP) 
+    if (ctl->protocol == P_APOP) 
     {
        char *start,*end;
        char *msg;
@@ -75,88 +77,64 @@ char *greeting;
        for (start = greeting;  *start != 0 && *start != '<';  start++)
            continue;
        if (*start == 0) {
-           fprintf(stderr,"Required APOP timestamp not found in greeting\n");
+           error(0, 0, "Required APOP timestamp not found in greeting");
            return(PS_AUTHFAIL);
        }
 
        /* find end of timestamp */
        for (end = start;  *end != 0  && *end != '>';  end++)
            continue;
-       if (*end == 0 || (end - start - 1) == 1) {
-           fprintf(stderr,"Timestamp syntax error in greeting\n");
+       if (*end == 0 || end == start + 1) {
+           error(0, 0, "Timestamp syntax error in greeting");
            return(PS_AUTHFAIL);
        }
+       else
+           *++end = '\0';
 
        /* copy timestamp and password into digestion buffer */
-       msg = (char *) malloc((end-start-1) + strlen(queryctl->password) + 1);
-       *(++end) = 0;
+       msg = (char *)xmalloc((end-start+1) + strlen(ctl->password) + 1);
        strcpy(msg,start);
-       strcat(msg,queryctl->password);
+       strcat(msg,ctl->password);
 
-       strcpy(queryctl->digest, MD5Digest(msg));
+       strcpy(ctl->digest, MD5Digest(msg));
        free(msg);
     }
-#endif  /* HAVE_APOP_SUPPORT */
 
-    switch (queryctl->protocol) {
+    switch (ctl->protocol) {
     case P_POP3:
-       gen_send(socket,"USER %s", queryctl->remotename);
-       if (pop3_ok(buf,socket) != 0)
-           goto badAuth;
-
-       gen_send(socket, "PASS %s", queryctl->password);
-       if (pop3_ok(buf,socket) != 0)
-           goto badAuth;
-       break;
-
-    case P_RPOP:
-       gen_send(socket,"USER %s", queryctl->remotename);
-       if (pop3_ok(buf,socket) != 0)
-           goto badAuth;
+       if ((gen_transact(sockfp,"USER %s", ctl->remotename)) != 0)
+           PROTOCOL_ERROR
 
-       gen_send(socket, "RPOP %s", queryctl->password);
-       if (pop3_ok(buf,socket) != 0)
-           goto badAuth;
+       if ((gen_transact(sockfp, "PASS %s", ctl->password)) != 0)
+           PROTOCOL_ERROR
        break;
 
-#if defined(HAVE_APOP_SUPPORT)
     case P_APOP:
-       gen_send(socket,"APOP %s %s", queryctl->remotename, queryctl->digest);
-       if (pop3_ok(buf,socket) != 0) 
-           goto badAuth;
+       if ((gen_transact(sockfp, "APOP %s %s",
+                         ctl->remotename, ctl->digest)) != 0)
+           PROTOCOL_ERROR
        break;
-#endif  /* HAVE_APOP_SUPPORT */
 
     default:
-       fprintf(stderr,"Undefined protocol request in POP3_auth\n");
+       error(0, 0, "Undefined protocol request in POP3_auth");
     }
 
     /* we're approved */
     return(0);
-
-    /*NOTREACHED*/
-
-badAuth:
-    if (outlevel > O_SILENT && outlevel < O_VERBOSE)
-       fprintf(stderr,"%s\n",buf);
-    return(PS_ERROR);
 }
 
-static int use_uidl;
-
-static pop3_getrange(socket, queryctl, countp, firstp)
+static int pop3_getrange(FILE *sockfp, struct query *ctl, int*countp, int*newp)
 /* get range of messages to be fetched */
-int socket;
-struct hostrec *queryctl;
-int *countp;
-int *firstp;
 {
     int ok;
     char buf [POPBUFSIZE+1];
 
+    /* Ensure that the new list is properly empty */
+    ctl->newsaved = (struct idlist *)NULL;
+
     /* get the total message count */
-    gen_send(socket, "STAT");
-    ok = pop3_ok(buf,socket);
+    gen_send(sockfp, "STAT");
+    ok = pop3_ok(sockfp, buf);
     if (ok == 0)
        sscanf(buf,"%d %*d", countp);
     else
@@ -164,128 +142,131 @@ int *firstp;
 
     /*
      * Newer, RFC-1725-conformant POP servers may not have the LAST command.
-     * Try LAST first as it simplifies life.  If it fails, go through the
-     * contortions with UID lists required.
-     *
-     * We could reverse this order (checking for UID capabilities first),
-     * but if LAST works we can finish faster and do fewer mallocs.
+     * We work as hard as possible to hide this ugliness, but it makes 
+     * counting new messages intrinsically quadratic in the worst case.
      */
-    *firstp = 1;
-    use_uidl = 0;
-    if (*countp > 0 && !queryctl->fetchall) {
+    last = 0;
+    *newp = -1;
+    if (*countp > 0 && !ctl->fetchall)
+    {
        char id [IDLEN+1];
-       int num;
-
-       /* try LAST first */
-       gen_send(socket,"LAST");
-       ok = pop3_ok(buf,socket);
-       if (ok == 0 && sscanf(buf, "%d", &num) == 0)
-           return(PS_ERROR);
 
-       /* crucial fork in the road here */
-       use_uidl = (ok != 0); 
-
-       if (!use_uidl)
-           *firstp = num + 1;
-       else
+       gen_send(sockfp,"LAST");
+       ok = pop3_ok(sockfp, buf);
+       if (ok == 0)
        {
-           /* grab the mailbox's UID list */
-           gen_send(socket, "UIDL");
-           if ((ok = pop3_ok(buf, socket)) == 0) {
-               while (SockGets(socket, buf, sizeof(buf)) >= 0) {
-                   if (outlevel == O_VERBOSE)
-                       fprintf(stderr,"%s\n",buf);
-                   if (strcmp(buf, ".\n") == 0) {
-                       break;
-                   }
-                   if (sscanf(buf, "%d %s\n", &num, id) == 2)
-                       save_uid(&queryctl->mailbox, num, id);
-               }
-           }
+           if (sscanf(buf, "%d", &last) == 0)
+               PROTOCOL_ERROR
+           *newp = (*countp - last);
        }
+       else
+       {
+           /* grab the mailbox's UID list */
+           if ((ok = gen_transact(sockfp, "UIDL")) != 0)
+               PROTOCOL_ERROR
+           else
+           {
+               int     num;
+
+               *newp = 0;
+               while (SockGets(buf, sizeof(buf), sockfp))
+               {
+                   if (buf[strlen(buf)-1] == '\n')
+                       buf[strlen(buf)-1] = '\0';
+                   if (buf[strlen(buf)-1] == '\r')
+                       buf[strlen(buf)-1] = '\r';
+                   if (outlevel == O_VERBOSE)
+                       error(0, 0, "POP3< %s", buf);
+                   if (buf[0] == '.')
+                       break;
+                   else if (sscanf(buf, "%d %s", &num, id) == 2)
+                   {
+                       save_str(&ctl->newsaved, num, id);
+
+                       /* note: ID comparison is caseblind */
+                       if (!str_in_list(&ctl->oldsaved, id))
+                           (*newp)++;
+                   }
+               }
+           }
+       }
     }
 
     return(0);
 }
 
-static pop3_is_old(socket, queryctl, number)
-/* check a given message for age by UID */
-int socket;
-struct hostrec *queryctl;
-int number;
+static int pop3_getsizes(FILE *sockfp, int count, int *sizes)
+/* capture the sizes of all messages */
 {
-    if (!use_uidl)
-       return(0);
+    int        ok;
+
+    if ((ok = gen_transact(sockfp, "LIST")) != 0)
+       return(ok);
     else
     {
        char buf [POPBUFSIZE+1];
-       int ok;
 
-       gen_send(socket, "UIDL %d", number);
-       if ((ok = pop3_ok(socket, buf)) != 0)
-           return(ok);
-       else
+       while (SockGets(buf, sizeof(buf), sockfp))
        {
-           char        id[IDLEN+1];
-
-           if (sscanf(buf, "%*d %s", id) == 2)
-               return(uid_in_list(&queryctl->saved, id));
+           int num, size;
+
+           if (buf[strlen(buf)-1] == '\n')
+               buf[strlen(buf)-1] = '\0';
+           if (buf[strlen(buf)-1] == '\r')
+               buf[strlen(buf)-1] = '\r';
+           if (outlevel == O_VERBOSE)
+               error(0, 0, "POP3< %s", buf);
+           if (buf[0] == '.')
+               break;
+           else if (sscanf(buf, "%d %d", &num, &size) == 2)
+               sizes[num - 1] = size;
            else
-               return(0);
+               sizes[num - 1] = -1;
        }
+
+       return(0);
     }
 }
 
-static int pop3_fetch(socket, number, limit, lenp)
-/* request nth message */
-int socket;
-int number;
-int limit;
-int *lenp; 
+static int pop3_is_old(FILE *sockfp, struct query *ctl, int num)
+/* is the given message old? */
 {
-    *lenp = 0;
-    if (limit) 
-        return(gen_transact(socket, "TOP %d %d", number, limit));
-      else 
-        return(gen_transact(socket, "RETR %d", number));
+    if (!ctl->oldsaved)
+       return (num <= last);
+    else
+       /* note: ID comparison is caseblind */
+        return (str_in_list(&ctl->oldsaved,
+                           str_find (&ctl->newsaved, num)));
 }
 
-static pop3_delete(socket, queryctl, number)
-/* delete a given message */
-int socket;
-struct hostrec *queryctl;
-int number;
+static int pop3_fetch(FILE *sockfp, int number, int *lenp)
+/* request nth message */
 {
-    int        ok;
+    int ok;
+    char buf [POPBUFSIZE+1], *cp;
 
-    /* send the deletion request */
-    if ((ok = gen_transact(socket, "DELE %d", number)) != 0)
+    gen_send(sockfp, "RETR %d", number);
+    if ((ok = pop3_ok(sockfp, buf)) != 0)
        return(ok);
-
-    /* we may need to nuke the message's UID out of the mailbox list */
-    if (use_uidl)
+    /* look for "nnn octets" -- there may or may not be preceding cruft */
+    if ((cp = strstr(buf, " octets")) == (char *)NULL)
+       *lenp = 0;
+    else
     {
-       char buf [POPBUFSIZE+1];
-
-       /*
-        * This isn't strictly necessary.  We should probably just
-        * stash the UID result from the last is_old call.
-        */
-       gen_send(socket, "UIDL %d", number);
-       if ((ok = pop3_ok(socket, buf)) != 0)
-           return(ok);
-       else
-       {
-           char        id[IDLEN+1];
-
-           if (sscanf(buf, "%*d %s", id) == 2)
-               delete_uid(&queryctl->mailbox, id); 
-           return(0);
-       }
+       while (--cp > buf && isdigit(*cp))
+           continue;
+       *lenp = atoi(cp);
     }
+    return(0);
 }
 
-static struct method pop3 =
+static int pop3_delete(FILE *sockfp, struct query *ctl, int number)
+/* delete a given message */
+{
+    return(gen_transact(sockfp, "DELE %d", number));
+}
+
+const static struct method pop3 =
 {
     "POP3",            /* Post Office Protocol v3 */
     110,               /* standard POP3 port */
@@ -294,7 +275,8 @@ static struct method pop3 =
     pop3_ok,           /* parse command response */
     pop3_getauth,      /* get authorization */
     pop3_getrange,     /* query range of messages */
-    pop3_is_old,       /* check for age by UID */
+    pop3_getsizes,     /* we can get a list of sizes */
+    pop3_is_old,       /* how do we tell a message is old? */
     pop3_fetch,                /* request given message */
     NULL,              /* no message trailer */
     pop3_delete,       /* how to delete a message */
@@ -302,16 +284,15 @@ static struct method pop3 =
     "QUIT",            /* the POP3 exit command */
 };
 
-int doPOP3 (queryctl)
+int doPOP3 (struct query *ctl)
 /* retrieve messages using POP3 */
-struct hostrec *queryctl;
 {
-    if (queryctl->remotefolder[0]) {
+    if (ctl->mailbox[0]) {
        fprintf(stderr,"Option --remote is not supported with POP3\n");
        return(PS_SYNTAX);
     }
-
-    return(do_protocol(queryctl, &pop3));
+    peek_capable = FALSE;
+    return(do_protocol(ctl, &pop3));
 }
 
-
+/* pop3.c ends here */