]> Pileus Git - ~andy/fetchmail/blob - imap.c
Debian 5.9.10 fixes.
[~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 recentcount is 0, return no mail */
521         if (recentcount == 0)
522                 count = 0;
523         if (outlevel >= O_DEBUG)
524             report(stdout, GT_("%d messages waiting after re-poll\n"), count);
525     }
526     else
527     {
528         count = 0;
529         ok = gen_transact(sock, 
530                           check_only ? "EXAMINE \"%s\"" : "SELECT \"%s\"",
531                           folder ? folder : "INBOX");
532         if (ok != 0)
533         {
534             report(stderr, GT_("mailbox selection failed\n"));
535             return(ok);
536         }
537         else if (outlevel >= O_DEBUG)
538             report(stdout, GT_("%d messages waiting after first poll\n"), count);
539
540         /* no messages?  then we may need to idle until we get some */
541         while (count == 0 && do_idle) {
542             ok = imap_idle(sock);
543             if (ok)
544             {
545                 report(stderr, GT_("re-poll failed\n"));
546                 return(ok);
547             }
548         }
549
550         /*
551          * We should have an expunge here to
552          * a) avoid fetching deleted mails during 'fetchall'
553          * b) getting a wrong count of mails during 'no fetchall'
554          */
555         if (!check_only && !ctl->keep && count > 0)
556         {
557             ok = internal_expunge(sock);
558             if (ok)
559             {
560                 report(stderr, GT_("expunge failed\n"));
561                 return(ok);
562             }
563             if (outlevel >= O_DEBUG)
564                 report(stdout, GT_("%d messages waiting after expunge\n"), count);
565         }
566     }
567
568     *countp = count;
569     recentcount = 0;
570
571     /* OK, now get a count of unseen messages and their indices */
572     if (!ctl->fetchall && count > 0)
573     {
574         if (unseen_messages)
575             free(unseen_messages);
576         unseen_messages = xmalloc(count * sizeof(unsigned int));
577         memset(unseen_messages, 0, count * sizeof(unsigned int));
578         unseen = 0;
579
580         gen_send(sock, "SEARCH UNSEEN");
581         do {
582             ok = gen_recv(sock, buf, sizeof(buf));
583             if (ok != 0)
584             {
585                 report(stderr, GT_("search for unseen messages failed\n"));
586                 return(PS_PROTOCOL);
587             }
588             else if ((cp = strstr(buf, "* SEARCH")))
589             {
590                 char    *ep;
591
592                 cp += 8;        /* skip "* SEARCH" */
593
594                 while (*cp && unseen < count)
595                 {
596                     /* skip whitespace */
597                     while (*cp && isspace(*cp))
598                         cp++;
599                     if (*cp) 
600                     {
601                         /*
602                          * Message numbers are between 1 and 2^32 inclusive,
603                          * so unsigned int is large enough.
604                          */
605                         unseen_messages[unseen]=(unsigned int)strtol(cp,&ep,10);
606
607                         if (outlevel >= O_DEBUG)
608                             report(stdout, 
609                                    GT_("%u is unseen\n"), 
610                                    unseen_messages[unseen]);
611                 
612                         unseen++;
613                         cp = ep;
614                     }
615                 }
616             }
617         } while
618             (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
619     } else
620         unseen = -1;
621
622     *newp = unseen;
623     count = 0;
624     expunged = 0;
625     deletions = 0;
626
627     return(PS_SUCCESS);
628 }
629
630 static int imap_getsizes(int sock, int count, int *sizes)
631 /* capture the sizes of all messages */
632 {
633     char buf [MSGBUFSIZE+1];
634
635     /*
636      * Some servers (as in, PMDF5.1-9.1 under OpenVMS 6.1)
637      * won't accept 1:1 as valid set syntax.  Some implementors
638      * should be taken out and shot for excessive anality.
639      *
640      * Microsoft Exchange (brain-dead piece of crap that it is) 
641      * sometimes gets its knickers in a knot about bodiless messages.
642      * You may see responses like this:
643      *
644      *  fetchmail: IMAP> A0004 FETCH 1:9 RFC822.SIZE
645      *  fetchmail: IMAP< * 2 FETCH (RFC822.SIZE 1187)
646      *  fetchmail: IMAP< * 3 FETCH (RFC822.SIZE 3954)
647      *  fetchmail: IMAP< * 4 FETCH (RFC822.SIZE 1944)
648      *  fetchmail: IMAP< * 5 FETCH (RFC822.SIZE 2933)
649      *  fetchmail: IMAP< * 6 FETCH (RFC822.SIZE 1854)
650      *  fetchmail: IMAP< * 7 FETCH (RFC822.SIZE 34054)
651      *  fetchmail: IMAP< * 8 FETCH (RFC822.SIZE 5561)
652      *  fetchmail: IMAP< * 9 FETCH (RFC822.SIZE 1101)
653      *  fetchmail: IMAP< A0004 NO The requested item could not be found.
654      *
655      * This means message 1 has only headers.  For kicks and grins
656      * you can telnet in and look:
657      *  A003 FETCH 1 FULL
658      *  A003 NO The requested item could not be found.
659      *  A004 fetch 1 rfc822.header
660      *  A004 NO The requested item could not be found.
661      *  A006 FETCH 1 BODY
662      *  * 1 FETCH (BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 35 3))
663      *  A006 OK FETCH completed.
664      *
665      * To get around this, we terminate the read loop on a NO and count
666      * on the fact that the sizes array has been preinitialized with a
667      * known-bad size value.
668      */
669     if (count == 1)
670         gen_send(sock, "FETCH 1 RFC822.SIZE", count);
671     else
672         gen_send(sock, "FETCH 1:%d RFC822.SIZE", count);
673     for (;;)
674     {
675         unsigned int num, size;
676         int ok;
677
678         if ((ok = gen_recv(sock, buf, sizeof(buf))))
679             return(ok);
680         else if (strstr(buf, "OK") || strstr(buf, "NO"))
681             break;
682         else if (sscanf(buf, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2) {
683             if (num > 0 && num <= count)
684                 sizes[num - 1] = size;
685             else
686                 report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n");
687         }
688     }
689
690     return(PS_SUCCESS);
691 }
692
693 static int imap_is_old(int sock, struct query *ctl, int number)
694 /* is the given message old? */
695 {
696     flag seen = TRUE;
697     int i;
698
699     /* 
700      * Expunges change the fetch numbers, but unseen_messages contains
701      * indices from before any expungees were done.  So neither the
702      * argument nor the values in message_sequence need to be decremented.
703      */
704
705     seen = TRUE;
706     for (i = 0; i < unseen; i++)
707         if (unseen_messages[i] == number)
708         {
709             seen = FALSE;
710             break;
711         }
712
713     return(seen);
714 }
715
716 static char *skip_token(char *ptr)
717 {
718     while(isspace(*ptr)) ptr++;
719     while(!isspace(*ptr) && !iscntrl(*ptr)) ptr++;
720     while(isspace(*ptr)) ptr++;
721     return(ptr);
722 }
723
724 static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
725 /* request headers of nth message */
726 {
727     char buf [MSGBUFSIZE+1];
728     int num;
729
730     /* expunges change the fetch numbers */
731     number -= expunged;
732
733     /*
734      * This is blessed by RFC1176, RFC1730, RFC2060.
735      * According to the RFCs, it should *not* set the \Seen flag.
736      */
737     gen_send(sock, "FETCH %d RFC822.HEADER", number);
738
739     /* looking for FETCH response */
740     for (;;) 
741     {
742         int     ok;
743         char    *ptr;
744
745         if ((ok = gen_recv(sock, buf, sizeof(buf))))
746             return(ok);
747         ptr = skip_token(buf);  /* either "* " or "AXXXX " */
748         if (sscanf(ptr, "%d FETCH (%*s {%d}", &num, lenp) == 2)
749             break;
750         /* try to recover from chronically fucked-up M$ Exchange servers */
751         else if (!strncmp(ptr, "NO", 2))
752             return(PS_TRANSIENT);
753         else if (!strncmp(ptr, "BAD", 3))
754             return(PS_TRANSIENT);
755     }
756
757     if (num != number)
758         return(PS_ERROR);
759     else
760         return(PS_SUCCESS);
761 }
762
763 static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
764 /* request body of nth message */
765 {
766     char buf [MSGBUFSIZE+1], *cp;
767     int num;
768
769     /* expunges change the fetch numbers */
770     number -= expunged;
771
772     /*
773      * If we're using IMAP4, we can fetch the message without setting its
774      * seen flag.  This is good!  It means that if the protocol exchange
775      * craps out during the message, it will still be marked `unseen' on
776      * the server.
777      *
778      * However...*don't* do this if we're using keep to suppress deletion!
779      * In that case, marking the seen flag is the only way to prevent the
780      * message from being re-fetched on subsequent runs (and according
781      * to RFC2060 p.43 this fetch should set Seen as a side effect).
782      *
783      * According to RFC2060, and Mark Crispin the IMAP maintainer,
784      * FETCH %d BODY[TEXT] and RFC822.TEXT are "functionally 
785      * equivalent".  However, we know of at least one server that
786      * treats them differently in the presence of MIME attachments;
787      * the latter form downloads the attachment, the former does not.
788      * The server is InterChange, and the fool who implemented this
789      * misfeature ought to be strung up by his thumbs.  
790      *
791      * When I tried working around this by disabling use of the 4rev1 form,
792      * I found that doing this breaks operation with M$ Exchange.
793      * Annoyingly enough, Exchange's refusal to cope is technically legal
794      * under RFC2062.  Trust Microsoft, the Great Enemy of interoperability
795      * standards, to find a way to make standards compliance irritating....
796      */
797     switch (imap_version)
798     {
799     case IMAP4rev1:     /* RFC 2060 */
800         if (!ctl->keep)
801             gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number);
802         else
803             gen_send(sock, "FETCH %d BODY[TEXT]", number);
804         break;
805
806     case IMAP4:         /* RFC 1730 */
807         if (!ctl->keep)
808             gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number);
809         else
810             gen_send(sock, "FETCH %d RFC822.TEXT", number);
811         break;
812
813     default:            /* RFC 1176 */
814         gen_send(sock, "FETCH %d RFC822.TEXT", number);
815         break;
816     }
817
818     /* looking for FETCH response */
819     do {
820         int     ok;
821
822         if ((ok = gen_recv(sock, buf, sizeof(buf))))
823             return(ok);
824     } while
825         (!strstr(buf+4, "FETCH") || sscanf(buf+2, "%d", &num) != 1);
826
827     if (num != number)
828         return(PS_ERROR);
829
830     /*
831      * Try to extract a length from the FETCH response.  RFC2060 requires
832      * it to be present, but at least one IMAP server (Novell GroupWise)
833      * botches this.
834      */
835     if ((cp = strchr(buf, '{')))
836         *lenp = atoi(cp + 1);
837     else
838         *lenp = -1;     /* missing length part in FETCH reponse */
839
840     return(PS_SUCCESS);
841 }
842
843 static int imap_trail(int sock, struct query *ctl, int number)
844 /* discard tail of FETCH response after reading message text */
845 {
846     /* expunges change the fetch numbers */
847     /* number -= expunged; */
848
849     for (;;)
850     {
851         char buf[MSGBUFSIZE+1];
852         int ok;
853
854         if ((ok = gen_recv(sock, buf, sizeof(buf))))
855             return(ok);
856
857         /* UW IMAP returns "OK FETCH", Cyrus returns "OK Completed" */
858         if (strstr(buf, "OK"))
859             break;
860
861 #ifdef __UNUSED__
862         /*
863          * Any IMAP server that fails to set Seen on a BODY[TEXT]
864          * fetch violates RFC2060 p.43 (top).  This becomes an issue
865          * when keep is on, because seen messages aren't deleted and
866          * get refetched on each poll.  As a workaround, if keep is on
867          * we can set the Seen flag explicitly.
868          *
869          * This code isn't used yet because we don't know of any IMAP
870          * servers broken in this way.
871          */
872         if (ctl->keep)
873             if ((ok = gen_transact(sock,
874                         imap_version == IMAP4 
875                                 ? "STORE %d +FLAGS.SILENT (\\Seen)"
876                                 : "STORE %d +FLAGS (\\Seen)", 
877                         number)))
878                 return(ok);
879 #endif /* __UNUSED__ */
880     }
881
882     return(PS_SUCCESS);
883 }
884
885 static int imap_delete(int sock, struct query *ctl, int number)
886 /* set delete flag for given message */
887 {
888     int ok;
889
890     /* expunges change the fetch numbers */
891     number -= expunged;
892
893     /*
894      * Use SILENT if possible as a minor throughput optimization.
895      * Note: this has been dropped from IMAP4rev1.
896      *
897      * We set Seen because there are some IMAP servers (notably HP
898      * OpenMail) that do message-receipt DSNs, but only when the seen
899      * bit is set.  This is the appropriate time -- we get here right
900      * after the local SMTP response that says delivery was
901      * successful.
902      */
903     if ((ok = gen_transact(sock,
904                         imap_version == IMAP4 
905                                 ? "STORE %d +FLAGS.SILENT (\\Seen \\Deleted)"
906                                 : "STORE %d +FLAGS (\\Seen \\Deleted)", 
907                         number)))
908         return(ok);
909     else
910         deletions++;
911
912     /*
913      * We do an expunge after expunge_period messages, rather than
914      * just before quit, so that a line hit during a long session
915      * won't result in lots of messages being fetched again during
916      * the next session.
917      */
918     if (NUM_NONZERO(expunge_period) && (deletions % expunge_period) == 0)
919         internal_expunge(sock);
920
921     return(PS_SUCCESS);
922 }
923
924 static int imap_logout(int sock, struct query *ctl)
925 /* send logout command */
926 {
927     /* if any un-expunged deletions remain, ship an expunge now */
928     if (deletions)
929         internal_expunge(sock);
930
931 #ifdef USE_SEARCH
932     /* Memory clean-up */
933     if (unseen_messages)
934         free(unseen_messages);
935 #endif /* USE_SEARCH */
936
937     return(gen_transact(sock, "LOGOUT"));
938 }
939
940 const static struct method imap =
941 {
942     "IMAP",             /* Internet Message Access Protocol */
943 #if INET6_ENABLE
944     "imap",
945     "imaps",
946 #else /* INET6_ENABLE */
947     143,                /* standard IMAP2bis/IMAP4 port */
948     993,                /* ssl IMAP2bis/IMAP4 port */
949 #endif /* INET6_ENABLE */
950     TRUE,               /* this is a tagged protocol */
951     FALSE,              /* no message delimiter */
952     imap_ok,            /* parse command response */
953     imap_getauth,       /* get authorization */
954     imap_getrange,      /* query range of messages */
955     imap_getsizes,      /* get sizes of messages (used for ESMTP SIZE option) */
956     imap_is_old,        /* no UID check */
957     imap_fetch_headers, /* request given message headers */
958     imap_fetch_body,    /* request given message body */
959     imap_trail,         /* eat message trailer */
960     imap_delete,        /* delete the message */
961     imap_logout,        /* expunge and exit */
962     TRUE,               /* yes, we can re-poll */
963 };
964
965 int doIMAP(struct query *ctl)
966 /* retrieve messages using IMAP Version 2bis or Version 4 */
967 {
968     return(do_protocol(ctl, &imap));
969 }
970
971 /* imap.c ends here */