-/* 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 */