]> Pileus Git - ~andy/fetchmail/blob - imap.c
Added autoprobe logic.
[~andy/fetchmail] / imap.c
1 /* Copyright 1996 by Eric S. Raymond
2  * All rights reserved.
3  * For license terms, see the file COPYING in this directory.
4  */
5
6 /***********************************************************************
7   module:       imap.c
8   project:      popclient
9   programmer:   Eric S. Raymond
10   description:  IMAP client code
11
12  ***********************************************************************/
13
14 #include  <config.h>
15 #include  <varargs.h>
16
17 #include  <stdio.h>
18 #if defined(STDC_HEADERS)
19 #include  <string.h>
20 #endif
21 #if defined(HAVE_UNISTD_H)
22 #include  <unistd.h>
23 #endif
24
25 #include  <sys/time.h>
26 #include  <ctype.h>
27 #include  <errno.h>
28
29 #include  "socket.h"
30 #include  "popclient.h"
31
32 #define   IMAP_PORT     143
33
34 #ifdef HAVE_PROTOTYPES
35 /* prototypes for internal functions */
36 int IMAP_OK (char *buf, int socket);
37 void IMAP_send ();
38 int IMAP_cmd ();
39 int IMAP_readmsg (int socket, int mboxfd, int len,
40        char *host, int topipe, int rewrite);
41 #endif
42
43 #define TAGLEN  5
44 static char tag[TAGLEN];
45 static int tagnum;
46 #define GENSYM  (sprintf(tag, "a%04d", ++tagnum), tag)
47
48 static int exists;
49 static int unseen;
50 static int recent;
51
52 /*********************************************************************
53   function:      doIMAP
54   description:   retrieve messages from the specified mail server
55                  using IMAP Version 2bis or Version 4.
56
57   arguments:     
58     queryctl     fully-specified options (i.e. parsed, defaults invoked,
59                  etc).
60
61   return value:  exit code from the set of PS_.* constants defined in 
62                  popclient.h
63   calls:
64   globals:       reads outlevel.
65  *********************************************************************/
66
67 int doIMAP (queryctl)
68 struct hostrec *queryctl;
69 {
70     int ok, num, len;
71     int mboxfd;
72     char buf [POPBUFSIZE];
73     int socket;
74     int first,number,count;
75
76     tagnum = 0;
77
78     /* open stdout or the mailbox, locking it if it is a folder */
79     if (queryctl->output == TO_FOLDER || queryctl->output == TO_STDOUT) 
80         if ((mboxfd = openuserfolder(queryctl)) < 0) 
81             return(PS_IOERR);
82     
83     /* open the socket */
84     if ((socket = Socket(queryctl->servername,IMAP_PORT)) < 0) {
85         perror("doIMAP: socket");
86         ok = PS_SOCKET;
87         goto closeUp;
88     }
89
90     /* accept greeting message from IMAP server */
91     ok = IMAP_OK(buf,socket);
92     if (ok != 0) {
93         if (ok != PS_SOCKET)
94             IMAP_cmd(socket, "LOGOUT");
95         close(socket);
96         goto closeUp;
97     }
98
99     /* print the greeting */
100     if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
101         fprintf(stderr,"IMAP greeting: %s\n",buf);
102
103     /* try to get authorized */
104     ok = IMAP_cmd(socket,
105                   "LOGIN %s %s",
106                   queryctl->remotename, queryctl->password);
107     if (ok == PS_ERROR)
108         ok = PS_AUTHFAIL;
109     if (ok != 0)
110         goto cleanUp;
111
112     /* find out how many messages are waiting */
113     exists = unseen = recent = -1;
114     ok = IMAP_cmd(socket,
115                   "SELECT %s",
116                   queryctl->remotefolder[0] ? queryctl->remotefolder : "INBOX");
117     if (ok != 0)
118         goto cleanUp;
119
120     /* compute size of message run */
121     count = exists;
122     if (queryctl->fetchall)
123         first = 1;
124     else {
125         if (exists > 0 && unseen == -1) {
126             fprintf(stderr,
127                     "no UNSEEN response; assuming all %d RECENT messages are unseen\n",
128                     recent);
129             first = exists - recent + 1;
130         } else {
131             first = unseen;
132         }
133     }
134
135     /* show them how many messages we'll be downloading */
136     if (outlevel > O_SILENT && outlevel < O_VERBOSE)
137         if (first > 1) 
138             fprintf(stderr,"%d messages in folder, %d new messages.\n", 
139                     count, count - first + 1);
140         else
141             fprintf(stderr,"%d %smessages in folder.\n", count, ok ? "" : "new ");
142
143     if (count > 0) { 
144         for (number = queryctl->flush ? 1 : first;  number<=count; number++) {
145
146             char *cp;
147
148             /* open the mail pipe if we're using an MDA */
149             if (queryctl->output == TO_MDA
150                 && (queryctl->fetchall || number >= first)) {
151                 ok = (mboxfd = openmailpipe(queryctl)) < 0 ? -1 : 0;
152                 if (ok != 0)
153                     goto cleanUp;
154             }
155            
156             if (queryctl->flush && number < first && !queryctl->fetchall) 
157                 ok = 0;  /* no command to send here, will delete message below */
158             else if (linelimit) 
159                 IMAP_send(socket,
160                           "PARTIAL %d RFC822 0 %d",
161                           number, linelimit);
162             else 
163                 IMAP_send(socket,
164                           "FETCH %d RFC822",
165                           number);
166
167             if (number >= first || queryctl->fetchall) {
168                 /* looking for FETCH response */
169                 do {
170                     if (SockGets(socket, buf,sizeof(buf)) < 0)
171                         return(PS_SOCKET);
172                 } while
173                        (sscanf(buf+2, "%d FETCH (RFC822 {%d}", &num, &len)!=2);
174                 if (outlevel == O_VERBOSE)
175                     fprintf(stderr,"fetching message %d (%d bytes)\n",num,len);
176                 ok = IMAP_readmsg(socket,mboxfd, len,
177                                   queryctl->servername,
178                                   queryctl->output == TO_MDA, 
179                                   queryctl->rewrite);
180                 /* discard tail of FETCH response */
181                 if (SockGets(socket, buf,sizeof(buf)) < 0)
182                     return(PS_SOCKET);
183             }
184             else
185                 ok = 0;
186
187             if (ok != 0)
188                 goto cleanUp;
189
190             /* maybe we delete this message now? */
191             if ((number < first && queryctl->flush) || !queryctl->keep) {
192                 if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
193                     fprintf(stderr,"flushing message %d\n", number);
194                 else
195                     ;
196                 ok = IMAP_cmd(socket,
197                               "STORE %d +FLAGS (\\Deleted)",
198                               number);
199                 if (ok != 0)
200                     goto cleanUp;
201             }
202
203             /* close the mail pipe, we'll reopen before next message */
204             if (queryctl->output == TO_MDA
205                 && (queryctl->fetchall || number >= first)) {
206                 ok = closemailpipe(mboxfd);
207                 if (ok != 0)
208                     goto cleanUp;
209             }
210         }
211
212         /* remove all messages flagged for deletion */
213         if (!queryctl->keep)
214         {
215             ok = IMAP_cmd(socket, "EXPUNGE");
216             if (ok != 0)
217                 goto cleanUp;
218         }
219
220         ok = IMAP_cmd(socket, "LOGOUT");
221         if (ok == 0)
222             ok = PS_SUCCESS;
223         close(socket);
224         goto closeUp;
225     }
226     else {
227         ok = IMAP_cmd(socket, "LOGOUT");
228         if (ok == 0)
229             ok = PS_NOMAIL;
230         close(socket);
231         goto closeUp;
232     }
233
234 cleanUp:
235     if (ok != 0 && ok != PS_SOCKET)
236         IMAP_cmd(socket, "LOGOUT");
237
238 closeUp:
239     if (queryctl->output == TO_FOLDER)
240         if (closeuserfolder(mboxfd) < 0 && ok == 0)
241             ok = PS_IOERR;
242     
243     if (ok == PS_IOERR || ok == PS_SOCKET) 
244         perror("doIMAP: cleanUp");
245
246     return(ok);
247 }
248
249 /*********************************************************************
250   function:      IMAP_OK
251   description:   get the server's response to a command, and return
252                  the extra arguments sent with the response.
253   arguments:     
254     argbuf       buffer to receive the argument string.
255     socket       socket to which the server is connected.
256
257   return value:  zero if okay, else return code.
258   calls:         SockGets
259   globals:       reads outlevel.
260  *********************************************************************/
261
262 int IMAP_OK (argbuf,socket)
263 char *argbuf;
264 int socket;
265 {
266   int ok;
267   char buf [POPBUFSIZE];
268   char *bufp;
269   int n;
270
271   do {
272     if (SockGets(socket, buf, sizeof(buf)) < 0)
273       return(PS_SOCKET);
274
275     if (outlevel == O_VERBOSE)
276       fprintf(stderr,"%s\n",buf);
277
278     /* interpret untagged status responses */
279     if (strstr(buf, "EXISTS"))
280         exists = atoi(buf+2);
281     if (strstr(buf, "RECENT"))
282         recent = atoi(buf+2);
283     if (sscanf(buf + 2, "OK [UNSEEN %d]", &n) == 1)
284         unseen = n;
285
286   } while
287       (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
288
289   if (tag[0] == '\0')
290     return(0); 
291   else {
292     if (strncmp(buf + TAGLEN + 1, "OK", 2) == 0) {
293       strcpy(argbuf, buf + TAGLEN);
294       return(0);
295     }
296     else if (strncmp(buf + TAGLEN + 1, "BAD", 2) == 0)
297       return(PS_ERROR);
298     else
299       return(PS_PROTOCOL);
300   }
301 }
302
303 /*********************************************************************
304   function:      IMAP_send
305   description:   Assemble command in print style and send to the server
306
307   arguments:     
308     socket       socket to which the server is connected.
309     fmt          printf-style format
310
311   return value:  none.
312   calls:         SockPuts.
313   globals:       reads outlevel.
314  *********************************************************************/
315
316 void IMAP_send(socket, fmt, va_alist)
317 int socket;
318 const char *fmt;
319 va_dcl {
320
321   char buf [POPBUFSIZE];
322   va_list ap;
323
324   (void) sprintf(buf, "%s ", GENSYM);
325
326   va_start(ap);
327   vsprintf(buf + strlen(buf), fmt, ap);
328   va_end(ap);
329
330   SockPuts(socket, buf);
331
332   if (outlevel == O_VERBOSE)
333     fprintf(stderr,"> %s\n", buf);
334 }
335
336 /*********************************************************************
337   function:      IMAP_cmd
338   description:   Assemble command in print style and send to the server
339
340   arguments:     
341     socket       socket to which the server is connected.
342     fmt          printf-style format
343
344   return value:  none.
345   calls:         SockPuts, IMAP_OK.
346   globals:       reads outlevel.
347  *********************************************************************/
348
349 int IMAP_cmd(socket, fmt, va_alist)
350 int socket;
351 const char *fmt;
352 va_dcl {
353
354   int ok;
355   char buf [POPBUFSIZE];
356   va_list ap;
357
358   (void) sprintf(buf, "%s ", GENSYM);
359
360   va_start(ap);
361   vsprintf(buf + strlen(buf), fmt, ap);
362   va_end(ap);
363
364   SockPuts(socket, buf);
365
366   if (outlevel == O_VERBOSE)
367     fprintf(stderr,"> %s\n", buf);
368
369   ok = IMAP_OK(buf,socket);
370   if (ok != 0 && outlevel > O_SILENT && outlevel < O_VERBOSE)
371     fprintf(stderr,"%s\n",buf);
372
373   return(ok);
374 }
375
376 /*********************************************************************
377   function:      IMAP_readmsg
378   description:   Read the message content 
379
380  as described in RFC 1225.
381   arguments:     
382     socket       ... to which the server is connected.
383     mboxfd       open file descriptor to which the retrieved message will
384                  be written.
385     len          lrength of text 
386     pophost      name of the POP host 
387     topipe       true if we're writing to the system mailbox pipe.
388
389   return value:  zero if success else PS_* return code.
390   calls:         SockGets.
391   globals:       reads outlevel. 
392  *********************************************************************/
393
394 int IMAP_readmsg (socket,mboxfd,len,pophost,topipe,rewrite)
395 int socket;
396 int mboxfd;
397 int len;
398 char *pophost;
399 int topipe;
400 int rewrite;
401
402   char buf [MSGBUFSIZE]; 
403   char *bufp;
404   char savec;
405   char fromBuf[MSGBUFSIZE];
406   int n;
407   int needFrom;
408   int inheaders;
409   int lines,sizeticker;
410   time_t now;
411   /* This keeps the retrieved message count for display purposes */
412   static int msgnum = 0;  
413
414   /* set up for status message if outlevel allows it */
415   if (outlevel > O_SILENT && outlevel < O_VERBOSE) {
416     fprintf(stderr,"reading message %d",++msgnum);
417     /* won't do the '...' if retrieved messages are being sent to stdout */
418     if (mboxfd == 1)
419       fputs(".\n",stderr);
420     else
421       ;
422   }
423   else
424     ;
425
426   /* read the message content from the server */
427   inheaders = 1;
428   lines = 0;
429   sizeticker = MSGBUFSIZE;
430   while (len > 0) {
431     if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
432       return(PS_SOCKET);
433     len -= n;
434     bufp = buf;
435     if (buf[0] == '\r' || buf[0] == '\n')
436       inheaders = 0;
437     if (*bufp == '.') {
438       bufp++;
439     }
440     strcat(bufp,"\n");
441      
442     /* Check for Unix 'From' header, and add a bogus one if it's not
443        present -- only if not using an MDA.
444        XXX -- should probably parse real From: header and use its 
445               address field instead of bogus 'POPmail' string. 
446     */
447     if (!topipe && lines == 0) {
448       if (strlen(bufp) >= strlen("From ")) {
449         savec = *(bufp + 5);
450         *(bufp + 5) = 0;
451         needFrom = strcmp(bufp,"From ") != 0;
452         *(bufp + 5) = savec;
453       }
454       else
455         needFrom = 1;
456       if (needFrom) {
457         now = time(NULL);
458         sprintf(fromBuf,"From POPmail %s",ctime(&now));
459         if (write(mboxfd,fromBuf,strlen(fromBuf)) < 0) {
460           perror("IMAP_readmsg: write");
461           return(PS_IOERR);
462         }
463       }
464     }
465
466     /*
467      * Edit some headers so that replies will work properly.
468      */
469     if (inheaders && rewrite)
470       reply_hack(bufp, pophost);
471
472     /* write this line to the file */
473     if (write(mboxfd,bufp,strlen(bufp)) < 0) {
474       perror("IMAP_readmsg: write");
475       return(PS_IOERR);
476     }
477
478     sizeticker -= strlen(bufp);
479     if (sizeticker <= 0) {
480       if (outlevel > O_SILENT && outlevel < O_VERBOSE && mboxfd != 1)
481         fputc('.',stderr);
482       sizeticker = MSGBUFSIZE;
483     }
484     lines++;
485   }
486
487   if (!topipe) {
488     /* The server may not write the extra newline required by the Unix
489        mail folder format, so we write one here just in case */
490     if (write(mboxfd,"\n",1) < 0) {
491       perror("IMAP_readmsg: write");
492       return(PS_IOERR);
493     }
494   }
495   else {
496     /* The mail delivery agent may require a terminator.  Write it if
497        it has been defined */
498 #ifdef BINMAIL_TERM
499     if (write(mboxfd,BINMAIL_TERM,strlen(BINMAIL_TERM)) < 0) {
500       perror("IMAP_readmsg: write");
501       return(PS_IOERR);
502     }
503 #endif
504     }
505
506   /* finish up display output */
507   if (outlevel == O_VERBOSE)
508     fprintf(stderr,"(%d lines of message content)\n",lines);
509   else if (outlevel > O_SILENT && mboxfd != 1) 
510     fputs(".\n",stderr);
511   else
512     ;
513   return(0);
514 }
515
516
517