]> Pileus Git - ~andy/fetchmail/blobdiff - imap.c
Drop back to using SockGets/SockWrite.
[~andy/fetchmail] / imap.c
diff --git a/imap.c b/imap.c
index bc7884ecc8aefcf41b15871fc0aeaec5843b292f..f9fdbbaa9f5f2463663efa9416367ae4be748cf3 100644 (file)
--- a/imap.c
+++ b/imap.c
-/* Copyright 1996 by Eric S. Raymond
+/*
+ * imap.c -- IMAP2bis/IMAP4 protocol methods
+ *
+ * Copyright 1996 by Eric S. Raymond
  * All rights reserved.
  * For license terms, see the file COPYING in this directory.
  */
 
-/***********************************************************************
-  module:       imap.c
-  project:      popclient
-  programmer:   Eric S. Raymond
-  description:  IMAP client code
-
- ***********************************************************************/
-
 #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  "socket.h"
-#include  "popclient.h"
-
-#define          IMAP_PORT     143
-
-#ifdef HAVE_PROTOTYPES
-/* prototypes for internal functions */
-int IMAP_OK (char *buf, int socket);
-void IMAP_send ();
-int IMAP_cmd ();
-int IMAP_readmsg (int socket, int mboxfd, int len,
-       char *host, int topipe, int rewrite);
+#if defined(STDC_HEADERS)
+#include  <stdlib.h>
 #endif
+#include  "fetchmail.h"
+#include  "socket.h"
 
-#define TAGLEN 5
-static char tag[TAGLEN];
-static int tagnum;
-#define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag)
-
-static int exists;
-static int unseen;
-static int recent;
-
-/*********************************************************************
-  function:      doIMAP
-  description:   retrieve messages from the specified mail server
-                 using IMAP Version 2bis or Version 4.
-
-  arguments:     
-    queryctl     fully-specified options (i.e. parsed, defaults invoked,
-                 etc).
-
-  return value:  exit code from the set of PS_.* constants defined in 
-                 popclient.h
-  calls:
-  globals:       reads outlevel.
- *********************************************************************/
+static int count, seen, recent, unseen, imap4;
 
-int doIMAP (queryctl)
-struct hostrec *queryctl;
+int imap_ok (FILE *sockfp,  char *argbuf)
+/* parse command response */
 {
-    int ok, num, len;
-    int mboxfd;
-    char buf [POPBUFSIZE];
-    int socket;
-    int first,number,count;
-
-    tagnum = 0;
-
-    /* open stdout or the mailbox, locking it if it is a folder */
-    if (queryctl->output == TO_FOLDER || queryctl->output == TO_STDOUT) 
-       if ((mboxfd = openuserfolder(queryctl)) < 0) 
-           return(PS_IOERR);
-    
-    /* open the socket */
-    if ((socket = Socket(queryctl->servername,IMAP_PORT)) < 0) {
-       perror("doIMAP: socket");
-       ok = PS_SOCKET;
-       goto closeUp;
+    char buf [POPBUFSIZE+1];
+
+    seen = 0;
+    do {
+       if (!SockGets(buf, sizeof(buf), sockfp))
+           return(PS_SOCKET);
+       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, "IMAP< %s", buf);
+
+       /* interpret untagged status responses */
+       if (strstr(buf, "EXISTS"))
+           count = atoi(buf+2);
+       if (strstr(buf, "RECENT"))
+           recent = atoi(buf+2);
+       if (strstr(buf, "UNSEEN"))
+           unseen = atoi(buf+2);
+       if (strstr(buf, "FLAGS"))
+           seen = (strstr(buf, "Seen") != (char *)NULL);
+    } while
+       (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
+
+    if (tag[0] == '\0')
+    {
+       strcpy(argbuf, buf);
+       return(0); 
     }
+    else
+    {
+       char    *cp;
 
-    /* accept greeting message from IMAP server */
-    ok = IMAP_OK(buf,socket);
-    if (ok != 0) {
-       if (ok != PS_SOCKET)
-           IMAP_cmd(socket, "LOGOUT");
-       close(socket);
-       goto closeUp;
-    }
-
-    /* print the greeting */
-    if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
-       fprintf(stderr,"%s\n",buf);
-
-    /* try to get authorized */
-    ok = IMAP_cmd(socket,
-                 "LOGIN %s %s",
-                 queryctl->remotename, queryctl->password);
-    if (ok == PS_ERROR)
-       ok = PS_AUTHFAIL;
-    if (ok != 0)
-       goto cleanUp;
+       /* skip the tag */
+       for (cp = buf; !isspace(*cp); cp++)
+           continue;
+       while (isspace(*cp))
+           cp++;
 
-    /* find out how many messages are waiting */
-    exists = unseen = recent = -1;
-    ok = IMAP_cmd(socket,
-                 "SELECT %s",
-                 queryctl->remotefolder[0] ? queryctl->remotefolder : "INBOX");
-    if (ok != 0)
-       goto cleanUp;
-
-    /* compute size of message run */
-    count = exists;
-    if (queryctl->fetchall)
-       first = 1;
-    else {
-       if (exists > 0 && unseen == -1) {
-           fprintf(stderr,
-                   "no UNSEEN response; assuming all %d RECENT messages are unseen\n",
-                   recent);
-           first = exists - recent + 1;
-       } else {
-           first = unseen;
+       if (strncmp(cp, "OK", 2) == 0)
+       {
+           strcpy(argbuf, cp);
+           return(0);
        }
-    }
-
-    /* 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);
+       else if (strncmp(cp, "BAD", 2) == 0)
+           return(PS_ERROR);
        else
-           fprintf(stderr,"%d %smessages in folder.\n", count, ok ? "" : "new ");
-
-    if (count > 0) { 
-       for (number = queryctl->flush ? 1 : first;  number<=count; number++) {
-
-           char *cp;
-
-           /* open the mail pipe if we're using an MDA */
-           if (queryctl->output == TO_MDA
-               && (queryctl->fetchall || number >= first)) {
-               ok = (mboxfd = openmailpipe(queryctl)) < 0 ? -1 : 0;
-               if (ok != 0)
-                   goto cleanUp;
-           }
-           
-           if (queryctl->flush && number < first && !queryctl->fetchall) 
-               ok = 0;  /* no command to send here, will delete message below */
-           else if (linelimit) 
-               IMAP_send(socket,
-                         "PARTIAL %d RFC822 0 %d",
-                         number, linelimit);
-           else 
-               IMAP_send(socket,
-                         "FETCH %d RFC822",
-                         number);
-
-           if (number >= first || queryctl->fetchall) {
-               /* looking for FETCH response */
-               do {
-                   if (SockGets(socket, buf,sizeof(buf)) < 0)
-                       return(PS_SOCKET);
-               } while
-                      (sscanf(buf+2, "%d FETCH (RFC822 {%d}", &num, &len)!=2);
-               if (outlevel == O_VERBOSE)
-                   fprintf(stderr,"fetching message %d (%d bytes)\n",num,len);
-               ok = IMAP_readmsg(socket,mboxfd, len,
-                                 queryctl->servername,
-                                 queryctl->output == TO_MDA, 
-                                 queryctl->rewrite);
-               /* discard tail of FETCH response */
-               if (SockGets(socket, buf,sizeof(buf)) < 0)
-                   return(PS_SOCKET);
-           }
-           else
-               ok = 0;
-
-           if (ok != 0)
-               goto cleanUp;
-
-           /* 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 = IMAP_cmd(socket,
-                             "STORE %d +FLAGS (\\Deleted)",
-                             number);
-               if (ok != 0)
-                   goto cleanUp;
-           }
-
-           /* close the mail pipe, we'll reopen before next message */
-           if (queryctl->output == TO_MDA
-               && (queryctl->fetchall || number >= first)) {
-               ok = closemailpipe(mboxfd);
-               if (ok != 0)
-                   goto cleanUp;
-           }
-       }
-
-       /* remove all messages flagged for deletion */
-        if (!queryctl->keep)
-       {
-           ok = IMAP_cmd(socket, "EXPUNGE");
-           if (ok != 0)
-               goto cleanUp;
-        }
-
-       ok = IMAP_cmd(socket, "LOGOUT");
-       if (ok == 0)
-           ok = PS_SUCCESS;
-       close(socket);
-       goto closeUp;
-    }
-    else {
-       ok = IMAP_cmd(socket, "LOGOUT");
-       if (ok == 0)
-           ok = PS_NOMAIL;
-       close(socket);
-       goto closeUp;
+           return(PS_PROTOCOL);
     }
-
-cleanUp:
-    if (ok != 0 && ok != PS_SOCKET)
-       IMAP_cmd(socket, "LOGOUT");
-
-closeUp:
-    if (queryctl->output == TO_FOLDER)
-       if (closeuserfolder(mboxfd) < 0 && ok == 0)
-           ok = PS_IOERR;
-    
-    if (ok == PS_IOERR || ok == PS_SOCKET) 
-       perror("doIMAP: cleanUp");
-
-    return(ok);
 }
 
-/*********************************************************************
-  function:      IMAP_OK
-  description:   get the server's response to a command, and return
-                 the extra arguments sent with the response.
-  arguments:     
-    argbuf       buffer to receive the argument string.
-    socket       socket to which the server is connected.
-
-  return value:  zero if okay, else return code.
-  calls:         SockGets
-  globals:       reads outlevel.
- *********************************************************************/
-
-int IMAP_OK (argbuf,socket)
-char *argbuf;
-int socket;
+int imap_getauth(FILE *sockfp, struct query *ctl, char *buf)
+/* apply for connection authorization */
 {
-  int ok;
-  char buf [POPBUFSIZE];
-  char *bufp;
-  int n;
-
-  do {
-    if (SockGets(socket, buf, sizeof(buf)) < 0)
-      return(PS_SOCKET);
-
-    if (outlevel == O_VERBOSE)
-      fprintf(stderr,"%s\n",buf);
-
-    /* interpret untagged status responses */
-    if (strstr(buf, "EXISTS"))
-       exists = atoi(buf+2);
-    if (strstr(buf, "RECENT"))
-       recent = atoi(buf+2);
-    if (sscanf(buf + 2, "OK [UNSEEN %d]", &n) == 1)
-       unseen = n;
-
-  } while
-      (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
-
-  if (tag[0] == '\0')
-    return(0); 
-  else {
-    if (strncmp(buf + TAGLEN + 1, "OK", 2) == 0) {
-      strcpy(argbuf, buf + TAGLEN);
-      return(0);
-    }
-    else if (strncmp(buf + TAGLEN + 1, "BAD", 2) == 0)
-      return(PS_ERROR);
-    else
-      return(PS_PROTOCOL);
-  }
-}
-
-/*********************************************************************
-  function:      IMAP_send
-  description:   Assemble command in print style and send to the server
-
-  arguments:     
-    socket       socket to which the server is connected.
-    fmt          printf-style format
-
-  return value:  none.
-  calls:         SockPuts.
-  globals:       reads outlevel.
- *********************************************************************/
-
-void IMAP_send(socket, fmt, va_alist)
-int socket;
-const char *fmt;
-va_dcl {
-
-  char buf [POPBUFSIZE];
-  va_list ap;
-
-  (void) sprintf(buf, "%s ", GENSYM);
+    /* try to get authorized */
+    int ok = gen_transact(sockfp,
+                 "LOGIN %s \"%s\"",
+                 ctl->remotename, ctl->password);
 
-  va_start(ap);
-  vsprintf(buf + strlen(buf), fmt, ap);
-  va_end(ap);
+    if (ok)
+       return(ok);
 
-  SockPuts(socket, buf);
+    /* probe to see if we're running IMAP4 and can use RFC822.PEEK */
+    imap4 = ((gen_transact(sockfp, "CAPABILITY")) == 0);
 
-  if (outlevel == O_VERBOSE)
-    fprintf(stderr,"> %s\n", buf);
+    return(0);
 }
 
-/*********************************************************************
-  function:      IMAP_cmd
-  description:   Assemble command in print style and send to the server
-
-  arguments:     
-    socket       socket to which the server is connected.
-    fmt          printf-style format
+static int imap_getrange(FILE *sockfp, struct query *ctl, int*countp, int*newp)
+/* get range of messages to be fetched */
+{
+    int ok;
 
-  return value:  none.
-  calls:         SockPuts, IMAP_OK.
-  globals:       reads outlevel.
- *********************************************************************/
+    /* find out how many messages are waiting */
+    recent = unseen = 0;
+    ok = gen_transact(sockfp,
+                 "SELECT %s",
+                 ctl->mailbox[0] ? ctl->mailbox : "INBOX");
+    if (ok != 0)
+       return(ok);
 
-int IMAP_cmd(socket, fmt, va_alist)
-int socket;
-const char *fmt;
-va_dcl {
+    *countp = count;
 
-  int ok;
-  char buf [POPBUFSIZE];
-  va_list ap;
+    if (unseen)                /* optional response, but better if we see it */
+       *newp = unseen;
+    else if (recent)   /* mandatory */
+       *newp = recent;
+    else
+       *newp = -1;     /* should never happen, RECENT is mandatory */ 
 
-  (void) sprintf(buf, "%s ", GENSYM);
+    return(0);
+}
 
-  va_start(ap);
-  vsprintf(buf + strlen(buf), fmt, ap);
-  va_end(ap);
+static int imap_getsizes(FILE *sockfp, int count, int *sizes)
+/* capture the sizes of all messages */
+{
+    char buf [POPBUFSIZE+1];
+
+    gen_send(sockfp, "FETCH 1:%d RFC822.SIZE", count);
+    while (SockGets(buf, sizeof(buf), sockfp))
+    {
+       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, "IMAP< %s", buf);
+       if (strstr(buf, "OK"))
+           break;
+       else if (sscanf(buf, "* %d FETCH (RFC822.SIZE %d)", &num, &size) == 2)
+           sizes[num - 1] = size;
+       else
+           sizes[num - 1] = -1;
+    }
 
-  SockPuts(socket, buf);
+    return(0);
+}
 
-  if (outlevel == O_VERBOSE)
-    fprintf(stderr,"> %s\n", buf);
+static int imap_is_old(FILE *sockfp, struct query *ctl, int num)
+/* is the given message old? */
+{
+    int ok;
 
-  ok = IMAP_OK(buf,socket);
-  if (ok != 0 && outlevel > O_SILENT && outlevel < O_VERBOSE)
-    fprintf(stderr,"%s\n",buf);
+    if ((ok = gen_transact(sockfp, "FETCH %d FLAGS", num)) != 0)
+       return(PS_ERROR);
 
-  return(ok);
+    return(seen);
 }
 
-/*********************************************************************
-  function:      IMAP_readmsg
-  description:   Read the message content 
-
- as described in RFC 1225.
-  arguments:     
-    socket       ... to which the server is connected.
-    mboxfd       open file descriptor to which the retrieved message will
-                 be written.
-    len          lrength of text 
-    pophost      name of the POP host 
-    topipe       true if we're writing to the system mailbox pipe.
-
-  return value:  zero if success else PS_* return code.
-  calls:         SockGets.
-  globals:       reads outlevel. 
- *********************************************************************/
-
-int IMAP_readmsg (socket,mboxfd,len,pophost,topipe,rewrite)
-int socket;
-int mboxfd;
-int len;
-char *pophost;
-int topipe;
-int rewrite;
-{ 
-  char buf [MSGBUFSIZE]; 
-  char *bufp;
-  char savec;
-  char fromBuf[MSGBUFSIZE];
-  int n;
-  int needFrom;
-  int inheaders;
-  int lines,sizeticker;
-  time_t now;
-  /* This keeps the retrieved message count for display purposes */
-  static int msgnum = 0;  
-
-  /* set up for status message if outlevel allows it */
-  if (outlevel > O_SILENT && outlevel < O_VERBOSE) {
-    fprintf(stderr,"reading message %d",++msgnum);
-    /* won't do the '...' if retrieved messages are being sent to stdout */
-    if (mboxfd == 1)
-      fputs(".\n",stderr);
-    else
-      ;
-  }
-  else
-    ;
-
-  /* read the message content from the server */
-  inheaders = 1;
-  lines = 0;
-  sizeticker = MSGBUFSIZE;
-  while (len > 0) {
-    if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
-      return(PS_SOCKET);
-    len -= n;
-    bufp = buf;
-    if (buf[0] == '\r' || buf[0] == '\n')
-      inheaders = 0;
-    if (*bufp == '.') {
-      bufp++;
-    }
-    strcat(bufp,"\n");
-     
-    /* Check for Unix 'From' header, and add a bogus one if it's not
-       present -- only if not using an MDA.
-       XXX -- should probably parse real From: header and use its 
-              address field instead of bogus 'POPmail' string. 
-    */
-    if (!topipe && lines == 0) {
-      if (strlen(bufp) >= strlen("From ")) {
-        savec = *(bufp + 5);
-        *(bufp + 5) = 0;
-        needFrom = strcmp(bufp,"From ") != 0;
-        *(bufp + 5) = savec;
-      }
-      else
-        needFrom = 1;
-      if (needFrom) {
-        now = time(NULL);
-        sprintf(fromBuf,"From POPmail %s",ctime(&now));
-        if (write(mboxfd,fromBuf,strlen(fromBuf)) < 0) {
-          perror("IMAP_readmsg: write");
-          return(PS_IOERR);
-        }
-      }
-    }
+static int imap_fetch(FILE *sockfp, int number, int *lenp)
+/* request nth message */
+{
+    char buf [POPBUFSIZE+1];
+    int        num;
 
     /*
-     * Edit some headers so that replies will work properly.
+     * If we're using IMAP4, we can fetch the message without setting its
+     * seen flag.  This is good!  It means that if the protocol exchange
+     * craps out during the message, it will still be marked `unseen' on
+     * the server.
      */
-    if (inheaders && rewrite)
-      reply_hack(bufp, pophost);
+    if (imap4)
+       gen_send(sockfp, "FETCH %d RFC822.PEEK", number);
+    else
+       gen_send(sockfp, "FETCH %d RFC822", number);
 
-    /* write this line to the file */
-    if (write(mboxfd,bufp,strlen(bufp)) < 0) {
-      perror("IMAP_readmsg: write");
-      return(PS_IOERR);
-    }
+    /* looking for FETCH response */
+    do {
+       if (!SockGets(buf, sizeof(buf), sockfp))
+           return(PS_SOCKET);
+    } while
+           (sscanf(buf+2, "%d FETCH (RFC822 {%d}", &num, lenp) != 2);
 
-    sizeticker -= strlen(bufp);
-    if (sizeticker <= 0) {
-      if (outlevel > O_SILENT && outlevel < O_VERBOSE && mboxfd != 1)
-        fputc('.',stderr);
-      sizeticker = MSGBUFSIZE;
-    }
-    lines++;
-  }
-
-  if (!topipe) {
-    /* 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("IMAP_readmsg: write");
-      return(PS_IOERR);
-    }
-  }
-  else {
-    /* The mail delivery agent may require a terminator.  Write it if
-       it has been defined */
-#ifdef BINMAIL_TERM
-    if (write(mboxfd,BINMAIL_TERM,strlen(BINMAIL_TERM)) < 0) {
-      perror("IMAP_readmsg: write");
-      return(PS_IOERR);
-    }
-#endif
-    }
+    if (num != number)
+       return(PS_ERROR);
+    else
+       return(0);
+}
+
+static int imap_trail(FILE *sockfp, struct query *ctl, int number)
+/* discard tail of FETCH response after reading message text */
+{
+    char buf [POPBUFSIZE+1];
 
-  /* 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);
-  else
-    ;
-  return(0);
+    if (!SockGets(buf, sizeof(buf), sockfp))
+       return(PS_SOCKET);
+    else
+       return(0);
 }
 
+static int imap_delete(FILE *sockfp, struct query *ctl, int number)
+/* set delete flag for given message */
+{
+    /* use SILENT if possible as a minor throughput optimization */
+    return(gen_transact(sockfp,
+                       imap4 
+                               ? "STORE %d +FLAGS.SILENT (\\Deleted)"
+                               : "STORE %d +FLAGS (\\Deleted)", 
+                       number));
+}
 
+const static struct method imap =
+{
+    "IMAP",            /* Internet Message Access Protocol */
+    143,               /* standard IMAP2bis/IMAP4 port */
+    1,                 /* this is a tagged protocol */
+    0,                 /* no message delimiter */
+    imap_ok,           /* parse command response */
+    imap_getauth,      /* get authorization */
+    imap_getrange,     /* query range of messages */
+    imap_getsizes,     /* grab message sizes */
+    imap_is_old,       /* no UID check */
+    imap_fetch,                /* request given message */
+    imap_trail,                /* eat message trailer */
+    imap_delete,       /* set IMAP delete flag */
+    "EXPUNGE",         /* the IMAP expunge command */
+    "LOGOUT",          /* the IMAP exit command */
+};
+
+int doIMAP(struct query *ctl)
+/* retrieve messages using IMAP Version 2bis or Version 4 */
+{
+    return(do_protocol(ctl, &imap));
+}
 
+/* imap.c ends here */