]> Pileus Git - ~andy/fetchmail/commitdiff
Initial revision
authorEric S. Raymond <esr@thyrsus.com>
Mon, 26 Aug 1996 21:19:15 +0000 (21:19 -0000)
committerEric S. Raymond <esr@thyrsus.com>
Mon, 26 Aug 1996 21:19:15 +0000 (21:19 -0000)
svn path=/trunk/; revision=64

imap.c [new file with mode: 0644]

diff --git a/imap.c b/imap.c
new file mode 100644 (file)
index 0000000..bc7884e
--- /dev/null
+++ b/imap.c
@@ -0,0 +1,517 @@
+/* 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);
+#endif
+
+#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.
+ *********************************************************************/
+
+int doIMAP (queryctl)
+struct hostrec *queryctl;
+{
+    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;
+    }
+
+    /* 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;
+
+    /* 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;
+       }
+    }
+
+    /* 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
+           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;
+    }
+
+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 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);
+
+  va_start(ap);
+  vsprintf(buf + strlen(buf), fmt, ap);
+  va_end(ap);
+
+  SockPuts(socket, buf);
+
+  if (outlevel == O_VERBOSE)
+    fprintf(stderr,"> %s\n", buf);
+}
+
+/*********************************************************************
+  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
+
+  return value:  none.
+  calls:         SockPuts, IMAP_OK.
+  globals:       reads outlevel.
+ *********************************************************************/
+
+int IMAP_cmd(socket, fmt, va_alist)
+int socket;
+const char *fmt;
+va_dcl {
+
+  int ok;
+  char buf [POPBUFSIZE];
+  va_list ap;
+
+  (void) sprintf(buf, "%s ", GENSYM);
+
+  va_start(ap);
+  vsprintf(buf + strlen(buf), fmt, ap);
+  va_end(ap);
+
+  SockPuts(socket, buf);
+
+  if (outlevel == O_VERBOSE)
+    fprintf(stderr,"> %s\n", buf);
+
+  ok = IMAP_OK(buf,socket);
+  if (ok != 0 && outlevel > O_SILENT && outlevel < O_VERBOSE)
+    fprintf(stderr,"%s\n",buf);
+
+  return(ok);
+}
+
+/*********************************************************************
+  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);
+        }
+      }
+    }
+
+    /*
+     * Edit some headers so that replies will work properly.
+     */
+    if (inheaders && rewrite)
+      reply_hack(bufp, pophost);
+
+    /* write this line to the file */
+    if (write(mboxfd,bufp,strlen(bufp)) < 0) {
+      perror("IMAP_readmsg: write");
+      return(PS_IOERR);
+    }
+
+    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
+    }
+
+  /* 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);
+}
+
+
+