]> Pileus Git - ~andy/fetchmail/blob - imap.c
Sunil Shetye's fixews for IMAP and SMTP edge cases.
[~andy/fetchmail] / imap.c
1 /*
2  * imap.c -- IMAP2bis/IMAP4 protocol methods
3  *
4  * Copyright 1997 by Eric S. Raymond
5  * For license terms, see the file COPYING in this directory.
6  */
7
8 #include  "config.h"
9 #include  <stdio.h>
10 #include  <string.h>
11 #include  <ctype.h>
12 #if defined(STDC_HEADERS)
13 #include  <stdlib.h>
14 #endif
15 #include  "fetchmail.h"
16 #include  "socket.h"
17
18 #include  "i18n.h"
19
20 #if OPIE_ENABLE
21 #endif /* OPIE_ENABLE */
22
23 #ifndef strstr          /* glibc-2.1 declares this as a macro */
24 extern char *strstr();  /* needed on sysV68 R3V7.1. */
25 #endif /* strstr */
26
27 /* imap_version values */
28 #define IMAP2           -1      /* IMAP2 or IMAP2BIS, RFC1176 */
29 #define IMAP4           0       /* IMAP4 rev 0, RFC1730 */
30 #define IMAP4rev1       1       /* IMAP4 rev 1, RFC2060 */
31
32 static int count = 0, recentcount = 0, unseen = 0, deletions = 0;
33 static int expunged, expunge_period, saved_timeout = 0;
34 static int imap_version, preauth;
35 static flag do_idle;
36 static char capabilities[MSGBUFSIZE+1];
37 static unsigned int *unseen_messages;
38
39 static int imap_ok(int sock, char *argbuf)
40 /* parse command response */
41 {
42     char buf[MSGBUFSIZE+1];
43
44     do {
45         int     ok;
46         char    *cp;
47
48         if ((ok = gen_recv(sock, buf, sizeof(buf))))
49             return(ok);
50
51         /* all tokens in responses are caseblind */
52         for (cp = buf; *cp; cp++)
53             if (islower(*cp))
54                 *cp = toupper(*cp);
55
56         /* interpret untagged status responses */
57         if (strstr(buf, "* CAPABILITY"))
58             strncpy(capabilities, buf + 12, sizeof(capabilities));
59         else if (strstr(buf, "EXISTS"))
60         {
61             count = atoi(buf+2);
62             /*
63              * Nasty kluge to handle RFC2177 IDLE.  If we know we're idling
64              * we can't wait for the tag matching the IDLE; we have to tell the
65              * server the IDLE is finished by shipping back a DONE when we
66              * see an EXISTS.  Only after that will a tagged response be
67              * shipped.  The idling flag also gets cleared on a timeout.
68              */
69             if (stage == STAGE_IDLE)
70             {
71                 /* we do our own write and report here to disable tagging */
72                 SockWrite(sock, "DONE\r\n", 6);
73                 if (outlevel >= O_MONITOR)
74                     report(stdout, "IMAP> DONE\n");
75
76                 mytimeout = saved_timeout;
77                 stage = STAGE_FETCH;
78             }
79         }
80         else if (strstr(buf, "RECENT"))
81         {
82             recentcount = atoi(buf+2);
83         }
84         else if (strstr(buf, "PREAUTH"))
85             preauth = TRUE;
86         /*
87          * The server may decide to make the mailbox read-only, 
88          * which causes fetchmail to go into a endless loop
89          * fetching the same message over and over again. 
90          * 
91          * However, for check_only, we use EXAMINE which will
92          * mark the mailbox read-only as per the RFC.
93          * 
94          * This checks for the condition and aborts if 
95          * the mailbox is read-only. 
96          *
97          * See RFC 2060 section 6.3.1 (SELECT).
98          * See RFC 2060 section 6.3.2 (EXAMINE).
99          */ 
100         else if (!check_only && strstr(buf, "[READ-ONLY]"))
101             return(PS_LOCKBUSY);
102     } while
103         (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
104
105     if (tag[0] == '\0')
106     {
107         if (argbuf)
108             strcpy(argbuf, buf);
109         return(PS_SUCCESS); 
110     }
111     else
112     {
113         char    *cp;
114
115         /* skip the tag */
116         for (cp = buf; !isspace(*cp); cp++)
117             continue;
118         while (isspace(*cp))
119             cp++;
120
121         if (strncmp(cp, "OK", 2) == 0)
122         {
123             if (argbuf)
124                 strcpy(argbuf, cp);
125             return(PS_SUCCESS);
126         }
127         else if (strncmp(cp, "BAD", 3) == 0)
128             return(PS_ERROR);
129         else if (strncmp(cp, "NO", 2) == 0)
130         {
131             if (stage == STAGE_GETAUTH) 
132                 return(PS_AUTHFAIL);    /* RFC2060, 6.2.2 */
133             else
134                 return(PS_ERROR);
135         }
136         else
137             return(PS_PROTOCOL);
138     }
139 }
140
141 #if NTLM_ENABLE
142 #include "ntlm.h"
143
144 static tSmbNtlmAuthRequest   request;              
145 static tSmbNtlmAuthChallenge challenge;
146 static tSmbNtlmAuthResponse  response;
147
148 /*
149  * NTLM support by Grant Edwards.
150  *
151  * Handle MS-Exchange NTLM authentication method.  This is the same
152  * as the NTLM auth used by Samba for SMB related services. We just
153  * encode the packets in base64 instead of sending them out via a
154  * network interface.
155  * 
156  * Much source (ntlm.h, smb*.c smb*.h) was borrowed from Samba.
157  */
158
159 static int do_imap_ntlm(int sock, struct query *ctl)
160 {
161     char msgbuf[2048];
162     int result,len;
163   
164     gen_send(sock, "AUTHENTICATE NTLM");
165
166     if ((result = gen_recv(sock, msgbuf, sizeof msgbuf)))
167         return result;
168   
169     if (msgbuf[0] != '+')
170         return PS_AUTHFAIL;
171   
172     buildSmbNtlmAuthRequest(&request,ctl->remotename,NULL);
173
174     if (outlevel >= O_DEBUG)
175         dumpSmbNtlmAuthRequest(stdout, &request);
176
177     memset(msgbuf,0,sizeof msgbuf);
178     to64frombits (msgbuf, (unsigned char*)&request, SmbLength(&request));
179   
180     if (outlevel >= O_MONITOR)
181         report(stdout, "IMAP> %s\n", msgbuf);
182   
183     strcat(msgbuf,"\r\n");
184     SockWrite (sock, msgbuf, strlen (msgbuf));
185
186     if ((gen_recv(sock, msgbuf, sizeof msgbuf)))
187         return result;
188   
189     len = from64tobits ((unsigned char*)&challenge, msgbuf, sizeof(msgbuf));
190     
191     if (outlevel >= O_DEBUG)
192         dumpSmbNtlmAuthChallenge(stdout, &challenge);
193     
194     buildSmbNtlmAuthResponse(&challenge, &response,ctl->remotename,ctl->password);
195   
196     if (outlevel >= O_DEBUG)
197         dumpSmbNtlmAuthResponse(stdout, &response);
198   
199     memset(msgbuf,0,sizeof msgbuf);
200     to64frombits (msgbuf, (unsigned char*)&response, SmbLength(&response));
201
202     if (outlevel >= O_MONITOR)
203         report(stdout, "IMAP> %s\n", msgbuf);
204       
205     strcat(msgbuf,"\r\n");
206     SockWrite (sock, msgbuf, strlen (msgbuf));
207   
208     if ((result = gen_recv (sock, msgbuf, sizeof msgbuf)))
209         return result;
210   
211     if (strstr (msgbuf, "OK"))
212         return PS_SUCCESS;
213     else
214         return PS_AUTHFAIL;
215 }
216 #endif /* NTLM */
217
218 static int imap_canonicalize(char *result, char *raw, int maxlen)
219 /* encode an IMAP password as per RFC1730's quoting conventions */
220 {
221     int i, j;
222
223     j = 0;
224     for (i = 0; i < strlen(raw) && i < maxlen; i++)
225     {
226         if ((raw[i] == '\\') || (raw[i] == '"'))
227             result[j++] = '\\';
228         result[j++] = raw[i];
229     }
230     result[j] = '\0';
231
232     return(i);
233 }
234
235 static int imap_getauth(int sock, struct query *ctl, char *greeting)
236 /* apply for connection authorization */
237 {
238     int ok = 0;
239
240     /* probe to see if we're running IMAP4 and can use RFC822.PEEK */
241     capabilities[0] = '\0';
242     if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS)
243     {
244         char    *cp;
245
246         /* capability checks are supposed to be caseblind */
247         for (cp = capabilities; *cp; cp++)
248             *cp = toupper(*cp);
249
250         /* UW-IMAP server 10.173 notifies in all caps, but RFC2060 says we
251            should expect a response in mixed-case */
252         if (strstr(capabilities, "IMAP4REV1"))
253         {
254             imap_version = IMAP4rev1;
255             if (outlevel >= O_DEBUG)
256                 report(stdout, GT_("Protocol identified as IMAP4 rev 1\n"));
257         }
258         else
259         {
260             imap_version = IMAP4;
261             if (outlevel >= O_DEBUG)
262                 report(stdout, GT_("Protocol identified as IMAP4 rev 0\n"));
263         }
264     }
265     else if (ok == PS_ERROR)
266     {
267         imap_version = IMAP2;
268         if (outlevel >= O_DEBUG)
269             report(stdout, GT_("Protocol identified as IMAP2 or IMAP2BIS\n"));
270     }
271     else
272         return(ok);
273
274     peek_capable = (imap_version >= IMAP4);
275
276     /* 
277      * Assumption: expunges are cheap, so we want to do them
278      * after every message unless user said otherwise.
279      */
280     if (NUM_SPECIFIED(ctl->expunge))
281         expunge_period = NUM_VALUE_OUT(ctl->expunge);
282     else
283         expunge_period = 1;
284
285     /* 
286      * Handle idling.  We depend on coming through here on startup
287      * and after each timeout (including timeouts during idles).
288      */
289     if (strstr(capabilities, "IDLE") && ctl->idle)
290     {
291         do_idle = TRUE;
292         if (outlevel >= O_VERBOSE)
293             report(stdout, GT_("will idle after poll\n"));
294     }
295
296     /* 
297      * If either (a) we saw a PREAUTH token in the greeting, or
298      * (b) the user specified ssh preauthentication, then we're done.
299      */
300     if (preauth || ctl->server.authenticate == A_SSH)
301     {
302         preauth = FALSE;  /* reset for the next session */
303         return(PS_SUCCESS);
304     }
305
306     /*
307      * Time to authenticate the user.
308      * Try the protocol variants that don't require passwords first.
309      */
310     ok = PS_AUTHFAIL;
311
312 #ifdef GSSAPI
313     if ((ctl->server.authenticate == A_ANY 
314          || ctl->server.authenticate == A_GSSAPI)
315         && strstr(capabilities, "AUTH=GSSAPI"))
316         if(ok = do_gssauth(sock, "AUTHENTICATE", ctl->server.truename, ctl->remotename))
317         {
318             /* SASL cancellation of authentication */
319             gen_send(sock, "*");
320             if(ctl->server.authenticate != A_ANY)
321                 return ok;
322         }
323         else
324             return ok;
325 #endif /* GSSAPI */
326
327 #ifdef KERBEROS_V4
328     if ((ctl->server.authenticate == A_ANY 
329          || ctl->server.authenticate == A_KERBEROS_V4
330          || ctl->server.authenticate == A_KERBEROS_V5) 
331         && strstr(capabilities, "AUTH=KERBEROS_V4"))
332     {
333         if ((ok = do_rfc1731(sock, "AUTHENTICATE", ctl->server.truename)))
334         {
335             /* SASL cancellation of authentication */
336             gen_send(sock, "*");
337             if(ctl->server.authenticate != A_ANY)
338                 return ok;
339         }
340         else
341             return ok;
342     }
343 #endif /* KERBEROS_V4 */
344
345     /*
346      * No such luck.  OK, now try the variants that mask your password
347      * in a challenge-response.
348      */
349
350     if ((ctl->server.authenticate == A_ANY 
351          || ctl->server.authenticate == A_CRAM_MD5)
352         && strstr(capabilities, "AUTH=CRAM-MD5"))
353     {
354         if ((ok = do_cram_md5 (sock, "AUTHENTICATE", ctl, NULL)))
355         {
356             /* SASL cancellation of authentication */
357             gen_send(sock, "*");
358             if(ctl->server.authenticate != A_ANY)
359                 return ok;
360         }
361         else
362             return ok;
363     }
364
365 #if OPIE_ENABLE
366     if ((ctl->server.authenticate == A_ANY 
367          || ctl->server.authenticate == A_OTP)
368         && strstr(capabilities, "AUTH=X-OTP"))
369         if ((ok = do_otp(sock, "AUTHENTICATE", ctl)))
370         {
371             /* SASL cancellation of authentication */
372             gen_send(sock, "*");
373             if(ctl->server.authenticate != A_ANY)
374                 return ok;
375         }
376         else
377             return ok;
378 #else
379     if (ctl->server.authenticate == A_OTP)
380     {
381         report(stderr, 
382            GT_("Required OTP capability not compiled into fetchmail\n"));
383     }
384 #endif /* OPIE_ENABLE */
385
386 #ifdef NTLM_ENABLE
387     if ((ctl->server.authenticate == A_ANY 
388          || ctl->server.authenticate == A_NTLM) 
389         && strstr (capabilities, "AUTH=NTLM")) {
390         if ((ok = do_imap_ntlm(sock, ctl)))
391         {
392             /* SASL cancellation of authentication */
393             gen_send(sock, "*");
394             if(ctl->server.authenticate != A_ANY)
395                 return ok;
396         }
397         else
398             return(ok);
399     }
400 #else
401     if (ctl->server.authenticate == A_NTLM)
402     {
403         report(stderr, 
404            GT_("Required NTLM capability not compiled into fetchmail\n"));
405     }
406 #endif /* NTLM_ENABLE */
407
408 #ifdef __UNUSED__       /* The Cyrus IMAP4rev1 server chokes on this */
409     /* this handles either AUTH=LOGIN or AUTH-LOGIN */
410     if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN")))
411     {
412         report(stderr, 
413                GT_("Required LOGIN capability not supported by server\n"));
414     }
415 #endif /* __UNUSED__ */
416
417     /* we're stuck with sending the password en clair */
418     if ((ctl->server.authenticate == A_ANY 
419          || ctl->server.authenticate == A_PASSWORD) 
420         && !strstr (capabilities, "LOGINDISABLED"))
421     {
422         /* these sizes guarantee no buffer overflow */
423         char    remotename[NAMELEN*2+1], password[PASSWORDLEN*2+1];
424
425         imap_canonicalize(remotename, ctl->remotename, NAMELEN);
426         imap_canonicalize(password, ctl->password, PASSWORDLEN);
427
428         strcpy(shroud, ctl->password);
429         ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password);
430         shroud[0] = '\0';
431         if (ok)
432         {
433             /* SASL cancellation of authentication */
434             gen_send(sock, "*");
435             if(ctl->server.authenticate != A_ANY)
436                 return ok;
437         }
438         else
439             return(ok);
440     }
441
442     return(ok);
443 }
444
445 static int internal_expunge(int sock)
446 /* ship an expunge, resetting associated counters */
447 {
448     int ok;
449
450     if ((ok = gen_transact(sock, "EXPUNGE")))
451         return(ok);
452
453     expunged += deletions;
454     deletions = 0;
455
456 #ifdef IMAP_UID /* not used */
457     expunge_uids(ctl);
458 #endif /* IMAP_UID */
459
460     return(PS_SUCCESS);
461 }
462
463 static int imap_idle(int sock)
464 /* start an RFC2177 IDLE */
465 {
466     stage = STAGE_IDLE;
467     saved_timeout = mytimeout;
468     mytimeout = 0;
469
470     return (gen_transact(sock, "IDLE"));
471 }
472
473 static int imap_getrange(int sock, 
474                          struct query *ctl, 
475                          const char *folder, 
476                          int *countp, int *newp, int *bytes)
477 /* get range of messages to be fetched */
478 {
479     int ok;
480     char buf[MSGBUFSIZE+1], *cp;
481
482     /* find out how many messages are waiting */
483     *bytes = -1;
484
485     if (pass > 1)
486     {
487         /* 
488          * We have to have an expunge here, otherwise the re-poll will
489          * infinite-loop picking up un-expunged messages -- unless the
490          * expunge period is one and we've been nuking each message 
491          * just after deletion.
492          */
493         ok = 0;
494         if (deletions) {
495             ok = internal_expunge(sock);
496             if (ok)
497             {
498                 report(stderr, GT_("expunge failed\n"));
499                 return(ok);
500             }
501         }
502
503         /*
504          * recentcount is already set here by the last imap command which
505          * returned RECENT on detecting new mail. if recentcount is 0, wait
506          * for new mail.
507          */
508
509         /* this is a while loop because imap_idle() might return on other
510          * mailbox changes also */
511         while (recentcount == 0 && do_idle) {
512             smtp_close(ctl, 1);
513             ok = imap_idle(sock);
514             if (ok)
515             {
516                 report(stderr, GT_("re-poll failed\n"));
517                 return(ok);
518             }
519         }
520         if (outlevel >= O_DEBUG)
521             report(stdout, GT_("%d messages waiting after re-poll\n"), count);
522     }
523     else
524     {
525         count = 0;
526         ok = gen_transact(sock, 
527                           check_only ? "EXAMINE \"%s\"" : "SELECT \"%s\"",
528                           folder ? folder : "INBOX");
529         if (ok != 0)
530         {
531             report(stderr, GT_("mailbox selection failed\n"));
532             return(ok);
533         }
534         else if (outlevel >= O_DEBUG)
535             report(stdout, GT_("%d messages waiting after first poll\n"), count);
536
537         /* no messages?  then we may need to idle until we get some */
538         if (count == 0 && do_idle)
539             imap_idle(sock);
540
541         /*
542          * We should have an expunge here to
543          * a) avoid fetching deleted mails during 'fetchall'
544          * b) getting a wrong count of mails during 'no fetchall'
545          */
546         if (!check_only && !ctl->keep && count > 0)
547         {
548             ok = internal_expunge(sock);
549             if (ok)
550             {
551                 report(stderr, GT_("expunge failed\n"));
552                 return(ok);
553             }
554             if (outlevel >= O_DEBUG)
555                 report(stdout, GT_("%d messages waiting after expunge\n"), count);
556         }
557     }
558
559     *countp = count;
560     recentcount = 0;
561
562     /* OK, now get a count of unseen messages and their indices */
563     if (!ctl->fetchall && count > 0)
564     {
565         if (unseen_messages)
566             free(unseen_messages);
567         unseen_messages = xmalloc(count * sizeof(unsigned int));
568         memset(unseen_messages, 0, count * sizeof(unsigned int));
569         unseen = 0;
570
571         gen_send(sock, "SEARCH UNSEEN");
572         do {
573             ok = gen_recv(sock, buf, sizeof(buf));
574             if (ok != 0)
575             {
576                 report(stderr, GT_("search for unseen messages failed\n"));
577                 return(PS_PROTOCOL);
578             }
579             else if ((cp = strstr(buf, "* SEARCH")))
580             {
581                 char    *ep;
582
583                 cp += 8;        /* skip "* SEARCH" */
584
585                 while (*cp && unseen < count)
586                 {
587                     /* skip whitespace */
588                     while (*cp && isspace(*cp))
589                         cp++;
590                     if (*cp) 
591                     {
592                         /*
593                          * Message numbers are between 1 and 2^32 inclusive,
594                          * so unsigned int is large enough.
595                          */
596                         unseen_messages[unseen]=(unsigned int)strtol(cp,&ep,10);
597
598                         if (outlevel >= O_DEBUG)
599                             report(stdout, 
600                                    GT_("%u is unseen\n"), 
601                                    unseen_messages[unseen]);
602                 
603                         unseen++;
604                         cp = ep;
605                     }
606                 }
607             }
608         } while
609             (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
610     } else
611         unseen = -1;
612
613     *newp = unseen;
614     expunged = 0;
615     deletions = 0;
616
617     return(PS_SUCCESS);
618 }
619
620 static int imap_getsizes(int sock, int count, int *sizes)
621 /* capture the sizes of all messages */
622 {
623     char buf [MSGBUFSIZE+1];
624
625     /*
626      * Some servers (as in, PMDF5.1-9.1 under OpenVMS 6.1)
627      * won't accept 1:1 as valid set syntax.  Some implementors
628      * should be taken out and shot for excessive anality.
629      *
630      * Microsoft Exchange (brain-dead piece of crap that it is) 
631      * sometimes gets its knickers in a knot about bodiless messages.
632      * You may see responses like this:
633      *
634      *  fetchmail: IMAP> A0004 FETCH 1:9 RFC822.SIZE
635      *  fetchmail: IMAP< * 2 FETCH (RFC822.SIZE 1187)
636      *  fetchmail: IMAP< * 3 FETCH (RFC822.SIZE 3954)
637      *  fetchmail: IMAP< * 4 FETCH (RFC822.SIZE 1944)
638      *  fetchmail: IMAP< * 5 FETCH (RFC822.SIZE 2933)
639      *  fetchmail: IMAP< * 6 FETCH (RFC822.SIZE 1854)
640      *  fetchmail: IMAP< * 7 FETCH (RFC822.SIZE 34054)
641      *  fetchmail: IMAP< * 8 FETCH (RFC822.SIZE 5561)
642      *  fetchmail: IMAP< * 9 FETCH (RFC822.SIZE 1101)
643      *  fetchmail: IMAP< A0004 NO The requested item could not be found.
644      *
645      * This means message 1 has only headers.  For kicks and grins
646      * you can telnet in and look:
647      *  A003 FETCH 1 FULL
648      *  A003 NO The requested item could not be found.
649      *  A004 fetch 1 rfc822.header
650      *  A004 NO The requested item could not be found.
651      *  A006 FETCH 1 BODY
652      *  * 1 FETCH (BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 35 3))
653      *  A006 OK FETCH completed.
654      *
655      * To get around this, we terminate the read loop on a NO and count
656      * on the fact that the sizes array has been preinitialized with a
657      * known-bad size value.
658      */
659     if (count == 1)
660         gen_send(sock, "FETCH 1 RFC822.SIZE", count);
661     else
662         gen_send(sock, "FETCH 1:%d RFC822.SIZE", count);
663     for (;;)
664     {
665         unsigned int num, size;
666         int ok;
667
668         if ((ok = gen_recv(sock, buf, sizeof(buf))))
669             return(ok);
670         else if (strstr(buf, "OK") || strstr(buf, "NO"))
671             break;
672         else if (sscanf(buf, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2) {
673             if (num > 0 && num <= count)
674                 sizes[num - 1] = size;
675             else
676                 report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n");
677         }
678     }
679
680     return(PS_SUCCESS);
681 }
682
683 static int imap_is_old(int sock, struct query *ctl, int number)
684 /* is the given message old? */
685 {
686     flag seen = TRUE;
687     int i;
688
689     /* 
690      * Expunges change the fetch numbers, but unseen_messages contains
691      * indices from before any expungees were done.  So neither the
692      * argument nor the values in message_sequence need to be decremented.
693      */
694
695     seen = TRUE;
696     for (i = 0; i < unseen; i++)
697         if (unseen_messages[i] == number)
698         {
699             seen = FALSE;
700             break;
701         }
702
703     return(seen);
704 }
705
706 static char *skip_token(char *ptr)
707 {
708     while(isspace(*ptr)) ptr++;
709     while(!isspace(*ptr) && !iscntrl(*ptr)) ptr++;
710     while(isspace(*ptr)) ptr++;
711     return(ptr);
712 }
713
714 static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
715 /* request headers of nth message */
716 {
717     char buf [MSGBUFSIZE+1];
718     int num;
719
720     /* expunges change the fetch numbers */
721     number -= expunged;
722
723     /*
724      * This is blessed by RFC1176, RFC1730, RFC2060.
725      * According to the RFCs, it should *not* set the \Seen flag.
726      */
727     gen_send(sock, "FETCH %d RFC822.HEADER", number);
728
729     /* looking for FETCH response */
730     for (;;) 
731     {
732         int     ok;
733         char    *ptr;
734
735         if ((ok = gen_recv(sock, buf, sizeof(buf))))
736             return(ok);
737         ptr = skip_token(buf);  /* either "* " or "AXXXX " */
738         if (sscanf(ptr, "%d FETCH (%*s {%d}", &num, lenp) == 2)
739             break;
740         /* try to recover from chronically fucked-up M$ Exchange servers */
741         else if (!strncmp(ptr, "NO", 2))
742             return(PS_TRANSIENT);
743         else if (!strncmp(ptr, "BAD", 3))
744             return(PS_TRANSIENT);
745     }
746
747     if (num != number)
748         return(PS_ERROR);
749     else
750         return(PS_SUCCESS);
751 }
752
753 static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
754 /* request body of nth message */
755 {
756     char buf [MSGBUFSIZE+1], *cp;
757     int num;
758
759     /* expunges change the fetch numbers */
760     number -= expunged;
761
762     /*
763      * If we're using IMAP4, we can fetch the message without setting its
764      * seen flag.  This is good!  It means that if the protocol exchange
765      * craps out during the message, it will still be marked `unseen' on
766      * the server.
767      *
768      * However...*don't* do this if we're using keep to suppress deletion!
769      * In that case, marking the seen flag is the only way to prevent the
770      * message from being re-fetched on subsequent runs (and according
771      * to RFC2060 p.43 this fetch should set Seen as a side effect).
772      *
773      * According to RFC2060, and Mark Crispin the IMAP maintainer,
774      * FETCH %d BODY[TEXT] and RFC822.TEXT are "functionally 
775      * equivalent".  However, we know of at least one server that
776      * treats them differently in the presence of MIME attachments;
777      * the latter form downloads the attachment, the former does not.
778      * The server is InterChange, and the fool who implemented this
779      * misfeature ought to be strung up by his thumbs.  
780      *
781      * When I tried working around this by disabling use of the 4rev1 form,
782      * I found that doing this breaks operation with M$ Exchange.
783      * Annoyingly enough, Exchange's refusal to cope is technically legal
784      * under RFC2062.  Trust Microsoft, the Great Enemy of interoperability
785      * standards, to find a way to make standards compliance irritating....
786      */
787     switch (imap_version)
788     {
789     case IMAP4rev1:     /* RFC 2060 */
790         if (!ctl->keep)
791             gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number);
792         else
793             gen_send(sock, "FETCH %d BODY[TEXT]", number);
794         break;
795
796     case IMAP4:         /* RFC 1730 */
797         if (!ctl->keep)
798             gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number);
799         else
800             gen_send(sock, "FETCH %d RFC822.TEXT", number);
801         break;
802
803     default:            /* RFC 1176 */
804         gen_send(sock, "FETCH %d RFC822.TEXT", number);
805         break;
806     }
807
808     /* looking for FETCH response */
809     do {
810         int     ok;
811
812         if ((ok = gen_recv(sock, buf, sizeof(buf))))
813             return(ok);
814     } while
815         (!strstr(buf+4, "FETCH") || sscanf(buf+2, "%d", &num) != 1);
816
817     if (num != number)
818         return(PS_ERROR);
819
820     /*
821      * Try to extract a length from the FETCH response.  RFC2060 requires
822      * it to be present, but at least one IMAP server (Novell GroupWise)
823      * botches this.
824      */
825     if ((cp = strchr(buf, '{')))
826         *lenp = atoi(cp + 1);
827     else
828         *lenp = -1;     /* missing length part in FETCH reponse */
829
830     return(PS_SUCCESS);
831 }
832
833 static int imap_trail(int sock, struct query *ctl, int number)
834 /* discard tail of FETCH response after reading message text */
835 {
836     /* expunges change the fetch numbers */
837     /* number -= expunged; */
838
839     for (;;)
840     {
841         char buf[MSGBUFSIZE+1];
842         int ok;
843
844         if ((ok = gen_recv(sock, buf, sizeof(buf))))
845             return(ok);
846
847         /* UW IMAP returns "OK FETCH", Cyrus returns "OK Completed" */
848         if (strstr(buf, "OK"))
849             break;
850
851 #ifdef __UNUSED__
852         /*
853          * Any IMAP server that fails to set Seen on a BODY[TEXT]
854          * fetch violates RFC2060 p.43 (top).  This becomes an issue
855          * when keep is on, because seen messages aren't deleted and
856          * get refetched on each poll.  As a workaround, if keep is on
857          * we can set the Seen flag explicitly.
858          *
859          * This code isn't used yet because we don't know of any IMAP
860          * servers broken in this way.
861          */
862         if (ctl->keep)
863             if ((ok = gen_transact(sock,
864                         imap_version == IMAP4 
865                                 ? "STORE %d +FLAGS.SILENT (\\Seen)"
866                                 : "STORE %d +FLAGS (\\Seen)", 
867                         number)))
868                 return(ok);
869 #endif /* __UNUSED__ */
870     }
871
872     return(PS_SUCCESS);
873 }
874
875 static int imap_delete(int sock, struct query *ctl, int number)
876 /* set delete flag for given message */
877 {
878     int ok;
879
880     /* expunges change the fetch numbers */
881     number -= expunged;
882
883     /*
884      * Use SILENT if possible as a minor throughput optimization.
885      * Note: this has been dropped from IMAP4rev1.
886      *
887      * We set Seen because there are some IMAP servers (notably HP
888      * OpenMail) that do message-receipt DSNs, but only when the seen
889      * bit is set.  This is the appropriate time -- we get here right
890      * after the local SMTP response that says delivery was
891      * successful.
892      */
893     if ((ok = gen_transact(sock,
894                         imap_version == IMAP4 
895                                 ? "STORE %d +FLAGS.SILENT (\\Seen \\Deleted)"
896                                 : "STORE %d +FLAGS (\\Seen \\Deleted)", 
897                         number)))
898         return(ok);
899     else
900         deletions++;
901
902     /*
903      * We do an expunge after expunge_period messages, rather than
904      * just before quit, so that a line hit during a long session
905      * won't result in lots of messages being fetched again during
906      * the next session.
907      */
908     if (NUM_NONZERO(expunge_period) && (deletions % expunge_period) == 0)
909         internal_expunge(sock);
910
911     return(PS_SUCCESS);
912 }
913
914 static int imap_logout(int sock, struct query *ctl)
915 /* send logout command */
916 {
917     /* if any un-expunged deletions remain, ship an expunge now */
918     if (deletions)
919         internal_expunge(sock);
920
921 #ifdef USE_SEARCH
922     /* Memory clean-up */
923     if (unseen_messages)
924         free(unseen_messages);
925 #endif /* USE_SEARCH */
926
927     return(gen_transact(sock, "LOGOUT"));
928 }
929
930 const static struct method imap =
931 {
932     "IMAP",             /* Internet Message Access Protocol */
933 #if INET6_ENABLE
934     "imap",
935     "imaps",
936 #else /* INET6_ENABLE */
937     143,                /* standard IMAP2bis/IMAP4 port */
938     993,                /* ssl IMAP2bis/IMAP4 port */
939 #endif /* INET6_ENABLE */
940     TRUE,               /* this is a tagged protocol */
941     FALSE,              /* no message delimiter */
942     imap_ok,            /* parse command response */
943     imap_getauth,       /* get authorization */
944     imap_getrange,      /* query range of messages */
945     imap_getsizes,      /* get sizes of messages (used for ESMTP SIZE option) */
946     imap_is_old,        /* no UID check */
947     imap_fetch_headers, /* request given message headers */
948     imap_fetch_body,    /* request given message body */
949     imap_trail,         /* eat message trailer */
950     imap_delete,        /* delete the message */
951     imap_logout,        /* expunge and exit */
952     TRUE,               /* yes, we can re-poll */
953 };
954
955 int doIMAP(struct query *ctl)
956 /* retrieve messages using IMAP Version 2bis or Version 4 */
957 {
958     return(do_protocol(ctl, &imap));
959 }
960
961 /* imap.c ends here */