]> Pileus Git - ~andy/fetchmail/blob - imap.c
b7dae1af6aefdc414989f76e5eda940c6891f685
[~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 #include  <limits.h>
15 #include  <errno.h>
16 #endif
17 #include  "fetchmail.h"
18 #include  "socket.h"
19
20 #include  "i18n.h"
21
22 /* imap_version values */
23 #define IMAP2           -1      /* IMAP2 or IMAP2BIS, RFC1176 */
24 #define IMAP4           0       /* IMAP4 rev 0, RFC1730 */
25 #define IMAP4rev1       1       /* IMAP4 rev 1, RFC2060 */
26
27 /* global variables: please reinitialize them explicitly for proper
28  * working in daemon mode */
29
30 /* TODO: session variables to be initialized before server greeting */
31 static int preauth = FALSE;
32
33 /* session variables initialized in capa_probe() or imap_getauth() */
34 static char capabilities[MSGBUFSIZE+1];
35 static int imap_version = IMAP4;
36 static flag do_idle = FALSE, has_idle = FALSE;
37 static int expunge_period = 1;
38
39 /* mailbox variables initialized in imap_getrange() */
40 static int count = 0, recentcount = 0, unseen = 0, deletions = 0;
41 static unsigned int startcount = 1;
42 static int expunged = 0;
43 static unsigned int *unseen_messages;
44
45 /* for "IMAP> EXPUNGE" */
46 static int recentcount_ok = 0;
47
48 /* for "IMAP> IDLE" */
49 static int saved_timeout = 0;
50
51 static int imap_ok(int sock, char *argbuf)
52 /* parse command response */
53 {
54     char buf[MSGBUFSIZE+1];
55
56     do {
57         int     ok;
58         char    *cp;
59
60         if ((ok = gen_recv(sock, buf, sizeof(buf))))
61             return(ok);
62
63         /* all tokens in responses are caseblind */
64         for (cp = buf; *cp; cp++)
65             if (islower((unsigned char)*cp))
66                 *cp = toupper((unsigned char)*cp);
67
68         /* interpret untagged status responses */
69         if (strstr(buf, "* CAPABILITY"))
70         {
71             strlcpy(capabilities, buf + 12, sizeof(capabilities));
72         }
73         else if (strstr(buf, "EXISTS"))
74         {
75             count = atoi(buf+2);
76             /*
77              * Don't trust the message count passed by the server.
78              * Without this check, it might be possible to do a
79              * DNS-spoofing attack that would pass back a ridiculous 
80              * count, and allocate a malloc area that would overlap
81              * a portion of the stack.
82              */
83             if (count > INT_MAX/sizeof(int))
84             {
85                 report(stderr, GT_("bogus message count!"));
86                 return(PS_PROTOCOL);
87             }
88
89             /*
90              * Nasty kluge to handle RFC2177 IDLE.  If we know we're idling
91              * we can't wait for the tag matching the IDLE; we have to tell the
92              * server the IDLE is finished by shipping back a DONE when we
93              * see an EXISTS.  Only after that will a tagged response be
94              * shipped.  The idling flag also gets cleared on a timeout.
95              */
96             if (stage == STAGE_IDLE)
97             {
98                 /* If IDLE isn't supported, we were only sending NOOPs anyway. */
99                 if (has_idle)
100                 {
101                     /* we do our own write and report here to disable tagging */
102                     SockWrite(sock, "DONE\r\n", 6);
103                     if (outlevel >= O_MONITOR)
104                         report(stdout, "IMAP> DONE\n");
105                 }
106
107                 mytimeout = saved_timeout;
108                 stage = STAGE_FETCH;
109             }
110         }
111         /* a space is required to avoid confusion with the \Recent flag */
112         else if (strstr(buf, " RECENT"))
113         {
114             recentcount_ok = 1;
115             recentcount = atoi(buf+2);
116         }
117         else if (strstr(buf, "EXPUNGE") && !strstr(buf, "OK"))
118         {
119             /* the response "* 10 EXPUNGE" means that the currently
120              * tenth (i.e. only one) message has been deleted */
121             if (atoi(buf+2) > 0)
122                 count--;
123             if (count < 0)
124                 count = 0;
125         }
126         else if (strstr(buf, "PREAUTH"))
127             preauth = TRUE;
128         /*
129          * The server may decide to make the mailbox read-only, 
130          * which causes fetchmail to go into a endless loop
131          * fetching the same message over and over again. 
132          * 
133          * However, for check_only, we use EXAMINE which will
134          * mark the mailbox read-only as per the RFC.
135          * 
136          * This checks for the condition and aborts if 
137          * the mailbox is read-only. 
138          *
139          * See RFC 2060 section 6.3.1 (SELECT).
140          * See RFC 2060 section 6.3.2 (EXAMINE).
141          */ 
142         else if (!check_only && strstr(buf, "[READ-ONLY]"))
143             return(PS_LOCKBUSY);
144     } while
145         (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
146
147     if (tag[0] == '\0')
148     {
149         if (argbuf)
150             strcpy(argbuf, buf);
151         return(PS_SUCCESS); 
152     }
153     else
154     {
155         char    *cp;
156
157         /* skip the tag */
158         for (cp = buf; !isspace((unsigned char)*cp); cp++)
159             continue;
160         while (isspace((unsigned char)*cp))
161             cp++;
162
163         if (strncasecmp(cp, "OK", 2) == 0)
164         {
165             if (argbuf)
166                 strcpy(argbuf, cp);
167             return(PS_SUCCESS);
168         }
169         else if (strncasecmp(cp, "BAD", 3) == 0)
170             return(PS_ERROR);
171         else if (strncasecmp(cp, "NO", 2) == 0)
172         {
173             if (stage == STAGE_GETAUTH) 
174                 return(PS_AUTHFAIL);    /* RFC2060, 6.2.2 */
175             else
176                 return(PS_ERROR);
177         }
178         else
179             return(PS_PROTOCOL);
180     }
181 }
182
183 #ifdef NTLM_ENABLE
184 #include "ntlm.h"
185
186 /*
187  * NTLM support by Grant Edwards.
188  *
189  * Handle MS-Exchange NTLM authentication method.  This is the same
190  * as the NTLM auth used by Samba for SMB related services. We just
191  * encode the packets in base64 instead of sending them out via a
192  * network interface.
193  * 
194  * Much source (ntlm.h, smb*.c smb*.h) was borrowed from Samba.
195  */
196
197 static int do_imap_ntlm(int sock, struct query *ctl)
198 {
199     tSmbNtlmAuthRequest request;
200     tSmbNtlmAuthChallenge challenge;
201     tSmbNtlmAuthResponse response;
202
203     char msgbuf[2048];
204     int result,len;
205
206     gen_send(sock, "AUTHENTICATE NTLM");
207
208     if ((result = gen_recv(sock, msgbuf, sizeof msgbuf)))
209         return result;
210   
211     if (msgbuf[0] != '+')
212         return PS_AUTHFAIL;
213   
214     buildSmbNtlmAuthRequest(&request,ctl->remotename,NULL);
215
216     if (outlevel >= O_DEBUG)
217         dumpSmbNtlmAuthRequest(stdout, &request);
218
219     memset(msgbuf,0,sizeof msgbuf);
220     to64frombits (msgbuf, (unsigned char*)&request, SmbLength(&request));
221   
222     if (outlevel >= O_MONITOR)
223         report(stdout, "IMAP> %s\n", msgbuf);
224   
225     strcat(msgbuf,"\r\n");
226     SockWrite (sock, msgbuf, strlen (msgbuf));
227
228     if ((gen_recv(sock, msgbuf, sizeof msgbuf)))
229         return result;
230   
231     len = from64tobits ((char*)&challenge, msgbuf, sizeof(challenge));
232     
233     if (outlevel >= O_DEBUG)
234         dumpSmbNtlmAuthChallenge(stdout, &challenge);
235     
236     buildSmbNtlmAuthResponse(&challenge, &response,ctl->remotename,ctl->password);
237   
238     if (outlevel >= O_DEBUG)
239         dumpSmbNtlmAuthResponse(stdout, &response);
240   
241     memset(msgbuf,0,sizeof msgbuf);
242     to64frombits (msgbuf, (unsigned char*)&response, SmbLength(&response));
243
244     if (outlevel >= O_MONITOR)
245         report(stdout, "IMAP> %s\n", msgbuf);
246       
247     strcat(msgbuf,"\r\n");
248     SockWrite (sock, msgbuf, strlen (msgbuf));
249   
250     if ((result = gen_recv (sock, msgbuf, sizeof msgbuf)))
251         return result;
252   
253     if (strstr (msgbuf, "OK"))
254         return PS_SUCCESS;
255     else
256         return PS_AUTHFAIL;
257 }
258 #endif /* NTLM */
259
260 static int imap_canonicalize(char *result, char *raw, int maxlen)
261 /* encode an IMAP password as per RFC1730's quoting conventions */
262 {
263     int i, j;
264
265     j = 0;
266     for (i = 0; i < strlen(raw) && i < maxlen; i++)
267     {
268         if ((raw[i] == '\\') || (raw[i] == '"'))
269             result[j++] = '\\';
270         result[j++] = raw[i];
271     }
272     result[j] = '\0';
273
274     return(i);
275 }
276
277 static void capa_probe(int sock, struct query *ctl)
278 /* set capability variables from a CAPA probe */
279 {
280     int ok;
281
282     /* probe to see if we're running IMAP4 and can use RFC822.PEEK */
283     capabilities[0] = '\0';
284     if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS)
285     {
286         char    *cp;
287
288         /* capability checks are supposed to be caseblind */
289         for (cp = capabilities; *cp; cp++)
290             *cp = toupper((unsigned char)*cp);
291
292         /* UW-IMAP server 10.173 notifies in all caps, but RFC2060 says we
293            should expect a response in mixed-case */
294         if (strstr(capabilities, "IMAP4REV1"))
295         {
296             imap_version = IMAP4rev1;
297             if (outlevel >= O_DEBUG)
298                 report(stdout, GT_("Protocol identified as IMAP4 rev 1\n"));
299         }
300         else
301         {
302             imap_version = IMAP4;
303             if (outlevel >= O_DEBUG)
304                 report(stdout, GT_("Protocol identified as IMAP4 rev 0\n"));
305         }
306     }
307     else if (ok == PS_ERROR)
308     {
309         imap_version = IMAP2;
310         if (outlevel >= O_DEBUG)
311             report(stdout, GT_("Protocol identified as IMAP2 or IMAP2BIS\n"));
312     }
313
314     /* 
315      * Handle idling.  We depend on coming through here on startup
316      * and after each timeout (including timeouts during idles).
317      */
318     do_idle = ctl->idle;
319     if (ctl->idle)
320     {
321         if (strstr(capabilities, "IDLE"))
322             has_idle = TRUE;
323         else
324             has_idle = FALSE;
325         if (outlevel >= O_VERBOSE)
326             report(stdout, GT_("will idle after poll\n"));
327     }
328
329     peek_capable = (imap_version >= IMAP4);
330 }
331
332 static int imap_getauth(int sock, struct query *ctl, char *greeting)
333 /* apply for connection authorization */
334 {
335     int ok = 0;
336 #ifdef SSL_ENABLE
337     flag did_stls = FALSE;
338 #endif /* SSL_ENABLE */
339
340     /*
341      * Assumption: expunges are cheap, so we want to do them
342      * after every message unless user said otherwise.
343      */
344     if (NUM_SPECIFIED(ctl->expunge))
345         expunge_period = NUM_VALUE_OUT(ctl->expunge);
346     else
347         expunge_period = 1;
348
349     capa_probe(sock, ctl);
350
351     /* 
352      * If either (a) we saw a PREAUTH token in the greeting, or
353      * (b) the user specified ssh preauthentication, then we're done.
354      */
355     if (preauth || ctl->server.authenticate == A_SSH)
356     {
357         preauth = FALSE;  /* reset for the next session */
358         return(PS_SUCCESS);
359     }
360
361 #ifdef SSL_ENABLE
362     if ((!ctl->sslproto || !strcmp(ctl->sslproto,"tls1"))
363         && !ctl->use_ssl
364         && strstr(capabilities, "STARTTLS"))
365     {
366            char *realhost;
367
368            realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
369            ok = gen_transact(sock, "STARTTLS");
370
371            /* We use "tls1" instead of ctl->sslproto, as we want STARTTLS,
372             * not other SSL protocols
373             */
374            if (ok == PS_SUCCESS &&
375                SSLOpen(sock,ctl->sslcert,ctl->sslkey,"tls1",ctl->sslcertck, ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1)
376            {
377                if (!ctl->sslproto && !ctl->wehaveauthed)
378                {
379                    ctl->sslproto = xstrdup("");
380                    /* repoll immediately */
381                    return(PS_REPOLL);
382                }
383                report(stderr,
384                       GT_("SSL connection failed.\n"));
385                return PS_SOCKET;
386            }
387            did_stls = TRUE;
388
389            /*
390             * RFC 2595 says this:
391             *
392             * "Once TLS has been started, the client MUST discard cached
393             * information about server capabilities and SHOULD re-issue the
394             * CAPABILITY command.  This is necessary to protect against
395             * man-in-the-middle attacks which alter the capabilities list prior
396             * to STARTTLS.  The server MAY advertise different capabilities
397             * after STARTTLS."
398             */
399            capa_probe(sock, ctl);
400     }
401 #endif /* SSL_ENABLE */
402
403     /*
404      * Time to authenticate the user.
405      * Try the protocol variants that don't require passwords first.
406      */
407     ok = PS_AUTHFAIL;
408
409 #ifdef GSSAPI
410     if ((ctl->server.authenticate == A_ANY 
411          || ctl->server.authenticate == A_GSSAPI)
412         && strstr(capabilities, "AUTH=GSSAPI"))
413     {
414         if ((ok = do_gssauth(sock, "AUTHENTICATE", "imap",
415                         ctl->server.truename, ctl->remotename)))
416         {
417             /* SASL cancellation of authentication */
418             gen_send(sock, "*");
419             if (ctl->server.authenticate != A_ANY)
420                 return ok;
421         } else  {
422             return ok;
423         }
424     }
425 #endif /* GSSAPI */
426
427 #ifdef KERBEROS_V4
428     if ((ctl->server.authenticate == A_ANY 
429          || ctl->server.authenticate == A_KERBEROS_V4
430          || ctl->server.authenticate == A_KERBEROS_V5) 
431         && strstr(capabilities, "AUTH=KERBEROS_V4"))
432     {
433         if ((ok = do_rfc1731(sock, "AUTHENTICATE", ctl->server.truename)))
434         {
435             /* SASL cancellation of authentication */
436             gen_send(sock, "*");
437             if(ctl->server.authenticate != A_ANY)
438                 return ok;
439         }
440         else
441             return ok;
442     }
443 #endif /* KERBEROS_V4 */
444
445     /*
446      * No such luck.  OK, now try the variants that mask your password
447      * in a challenge-response.
448      */
449
450     if ((ctl->server.authenticate == A_ANY && strstr(capabilities, "AUTH=CRAM-MD5"))
451         || ctl->server.authenticate == A_CRAM_MD5)
452     {
453         if ((ok = do_cram_md5 (sock, "AUTHENTICATE", ctl, NULL)))
454         {
455             /* SASL cancellation of authentication */
456             gen_send(sock, "*");
457             if(ctl->server.authenticate != A_ANY)
458                 return ok;
459         }
460         else
461             return ok;
462     }
463
464 #ifdef OPIE_ENABLE
465     if ((ctl->server.authenticate == A_ANY 
466          || ctl->server.authenticate == A_OTP)
467         && strstr(capabilities, "AUTH=X-OTP")) {
468         if ((ok = do_otp(sock, "AUTHENTICATE", ctl)))
469         {
470             /* SASL cancellation of authentication */
471             gen_send(sock, "*");
472             if(ctl->server.authenticate != A_ANY)
473                 return ok;
474         } else {
475             return ok;
476         }
477     }
478 #else
479     if (ctl->server.authenticate == A_OTP)
480     {
481         report(stderr, 
482            GT_("Required OTP capability not compiled into fetchmail\n"));
483     }
484 #endif /* OPIE_ENABLE */
485
486 #ifdef NTLM_ENABLE
487     if ((ctl->server.authenticate == A_ANY 
488          || ctl->server.authenticate == A_NTLM) 
489         && strstr (capabilities, "AUTH=NTLM")) {
490         if ((ok = do_imap_ntlm(sock, ctl)))
491         {
492             /* SASL cancellation of authentication */
493             gen_send(sock, "*");
494             if(ctl->server.authenticate != A_ANY)
495                 return ok;
496         }
497         else
498             return(ok);
499     }
500 #else
501     if (ctl->server.authenticate == A_NTLM)
502     {
503         report(stderr, 
504            GT_("Required NTLM capability not compiled into fetchmail\n"));
505     }
506 #endif /* NTLM_ENABLE */
507
508 #ifdef __UNUSED__       /* The Cyrus IMAP4rev1 server chokes on this */
509     /* this handles either AUTH=LOGIN or AUTH-LOGIN */
510     if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN")))
511     {
512         report(stderr, 
513                GT_("Required LOGIN capability not supported by server\n"));
514     }
515 #endif /* __UNUSED__ */
516
517     /* 
518      * We're stuck with sending the password en clair.
519      * The reason for this odd-looking logic is that some
520      * servers return LOGINDISABLED even though login 
521      * actually works.  So arrange things in such a way that
522      * setting auth passwd makes it ignore this capability.
523      */
524     if((ctl->server.authenticate==A_ANY&&!strstr(capabilities,"LOGINDISABLED"))
525         || ctl->server.authenticate == A_PASSWORD)
526     {
527         /* these sizes guarantee no buffer overflow */
528         char *remotename, *password;
529         size_t rnl, pwl;
530         rnl = 2 * strlen(ctl->remotename) + 1;
531         pwl = 2 * strlen(ctl->password) + 1;
532         remotename = xmalloc(rnl);
533         password = xmalloc(pwl);
534
535         imap_canonicalize(remotename, ctl->remotename, rnl);
536         imap_canonicalize(password, ctl->password, pwl);
537
538         snprintf(shroud, sizeof (shroud), "\"%s\"", password);
539         ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password);
540         shroud[0] = '\0';
541         free(password);
542         free(remotename);
543 #ifdef SSL_ENABLE
544         /* this is for servers which claim to support TLS, but actually
545          * don't! */
546         if (did_stls && ok == PS_SOCKET && !ctl->sslproto && !ctl->wehaveauthed)
547         {
548             ctl->sslproto = xstrdup("");
549             /* repoll immediately */
550             ok = PS_REPOLL;
551         }
552 #endif
553         if (ok)
554         {
555             /* SASL cancellation of authentication */
556             gen_send(sock, "*");
557             if(ctl->server.authenticate != A_ANY)
558                 return ok;
559         }
560         else
561             return(ok);
562     }
563
564     return(ok);
565 }
566
567 static int internal_expunge(int sock)
568 /* ship an expunge, resetting associated counters */
569 {
570     int ok;
571
572     recentcount_ok = 0;
573
574     if ((ok = gen_transact(sock, "EXPUNGE")))
575         return(ok);
576
577     /* some servers do not report RECENT after an EXPUNGE. in this case, 
578      * the previous value of recentcount is just ignored. */
579     if (!recentcount_ok)
580         recentcount = 0;
581
582     expunged += deletions;
583     deletions = 0;
584
585 #ifdef IMAP_UID /* not used */
586     expunge_uids(ctl);
587 #endif /* IMAP_UID */
588
589     return(PS_SUCCESS);
590 }
591
592 static int imap_idle(int sock)
593 /* start an RFC2177 IDLE, or fake one if unsupported */
594 {
595     int ok;
596
597     stage = STAGE_IDLE;
598     saved_timeout = mytimeout;
599
600     if (has_idle) {
601         /* special timeout to terminate the IDLE and re-issue it
602          * at least every 28 minutes:
603          * (the server may have an inactivity timeout) */
604         mytimeout = 1680; /* 28 min */
605         /* enter IDLE mode */
606         ok = gen_transact(sock, "IDLE");
607
608         if (ok == PS_IDLETIMEOUT) {
609             /* send "DONE" continuation */
610             SockWrite(sock, "DONE\r\n", 6);
611             if (outlevel >= O_MONITOR)
612                 report(stdout, "IMAP> DONE\n");
613         } else
614             /* not idle timeout */
615             return ok;
616     } else {  /* no idle support, fake it */
617         /* when faking an idle, we can't assume the server will
618          * send us the new messages out of the blue (RFC2060);
619          * this timeout is potentially the delay before we notice
620          * new mail (can be small since NOOP checking is cheap) */
621         mytimeout = 28;
622         ok = gen_transact(sock, "NOOP");
623         /* if there's an error (not likely) or we just found mail (stage 
624          * has changed, timeout has also been restored), we're done */
625         if (ok != 0 || stage != STAGE_IDLE)
626             return(ok);
627
628         /* wait (briefly) for an unsolicited status update */
629         ok = imap_ok(sock, NULL);
630         /* again, this is new mail or an error */
631         if (ok != PS_IDLETIMEOUT)
632             return(ok);
633     }
634
635     /* restore normal timeout value */
636     mytimeout = saved_timeout;
637     stage = STAGE_FETCH;
638
639     /* get OK IDLE message */
640     if (has_idle)
641         return imap_ok(sock, NULL);
642
643     return PS_SUCCESS;
644 }
645
646 static int imap_getrange(int sock, 
647                          struct query *ctl, 
648                          const char *folder, 
649                          int *countp, int *newp, int *bytes)
650 /* get range of messages to be fetched */
651 {
652     int ok;
653     char buf[MSGBUFSIZE+1], *cp;
654
655     /* find out how many messages are waiting */
656     *bytes = -1;
657
658     if (pass > 1)
659     {
660         /* deleted mails have already been expunged by
661          * end_mailbox_poll().
662          *
663          * recentcount is already set here by the last imap command which
664          * returned RECENT on detecting new mail. if recentcount is 0, wait
665          * for new mail.
666          *
667          * this is a while loop because imap_idle() might return on other
668          * mailbox changes also */
669         while (recentcount == 0 && do_idle) {
670             smtp_close(ctl, 1);
671             ok = imap_idle(sock);
672             if (ok)
673             {
674                 report(stderr, GT_("re-poll failed\n"));
675                 return(ok);
676             }
677         }
678         /* if recentcount is 0, return no mail */
679         if (recentcount == 0)
680                 count = 0;
681         if (outlevel >= O_DEBUG)
682             report(stdout, ngettext("%d message waiting after re-poll\n",
683                                     "%d messages waiting after re-poll\n",
684                                     count), count);
685     }
686     else
687     {
688         count = 0;
689         ok = gen_transact(sock, 
690                           check_only ? "EXAMINE \"%s\"" : "SELECT \"%s\"",
691                           folder ? folder : "INBOX");
692         if (ok != 0)
693         {
694             report(stderr, GT_("mailbox selection failed\n"));
695             return(ok);
696         }
697         else if (outlevel >= O_DEBUG)
698             report(stdout, ngettext("%d message waiting after first poll\n",
699                                     "%d messages waiting after first poll\n",
700                                     count), count);
701
702         /* no messages?  then we may need to idle until we get some */
703         while (count == 0 && do_idle) {
704             ok = imap_idle(sock);
705             if (ok)
706             {
707                 report(stderr, GT_("re-poll failed\n"));
708                 return(ok);
709             }
710         }
711
712         /*
713          * We should have an expunge here to
714          * a) avoid fetching deleted mails during 'fetchall'
715          * b) getting a wrong count of mails during 'no fetchall'
716          */
717         if (!check_only && !ctl->keep && count > 0)
718         {
719             ok = internal_expunge(sock);
720             if (ok)
721             {
722                 report(stderr, GT_("expunge failed\n"));
723                 return(ok);
724             }
725             if (outlevel >= O_DEBUG)
726                 report(stdout, ngettext("%d message waiting after expunge\n",
727                                         "%d messages waiting after expunge\n",
728                                         count), count);
729         }
730     }
731
732     *countp = count;
733     recentcount = 0;
734     startcount = 1;
735
736     /* OK, now get a count of unseen messages and their indices */
737     if (!ctl->fetchall && count > 0)
738     {
739         if (unseen_messages)
740             free(unseen_messages);
741         unseen_messages = xmalloc(count * sizeof(unsigned int));
742         memset(unseen_messages, 0, count * sizeof(unsigned int));
743         unseen = 0;
744
745         /* don't count deleted messages, in case user enabled keep last time */
746         gen_send(sock, "SEARCH UNSEEN NOT DELETED");
747         do {
748             ok = gen_recv(sock, buf, sizeof(buf));
749             if (ok != 0)
750             {
751                 report(stderr, GT_("search for unseen messages failed\n"));
752                 return(PS_PROTOCOL);
753             }
754             else if ((cp = strstr(buf, "* SEARCH")))
755             {
756                 char    *ep;
757
758                 cp += 8;        /* skip "* SEARCH" */
759                 /* startcount is higher than count so that if there are no
760                  * unseen messages, imap_getsizes() will not need to do
761                  * anything! */
762                 startcount = count + 1;
763
764                 while (*cp && unseen < count)
765                 {
766                     /* skip whitespace */
767                     while (*cp && isspace((unsigned char)*cp))
768                         cp++;
769                     if (*cp) 
770                     {
771                         unsigned int um;
772                         /*
773                          * Message numbers are between 1 and 2^32 inclusive,
774                          * so unsigned int is large enough.
775                          */
776                         um=(unsigned int)strtol(cp,&ep,10);
777                         if (um <= count)
778                         {
779                             unseen_messages[unseen++] = um;
780                             if (outlevel >= O_DEBUG)
781                                 report(stdout, GT_("%u is unseen\n"), um);
782                             if (startcount > um)
783                                 startcount = um;
784                         }
785                         cp = ep;
786                     }
787                 }
788             }
789         } while
790             (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
791
792         if (outlevel >= O_DEBUG && unseen > 0)
793             report(stdout, GT_("%u is first unseen\n"), startcount);
794     } else
795         unseen = -1;
796
797     *newp = unseen;
798     expunged = 0;
799     deletions = 0;
800
801     return(PS_SUCCESS);
802 }
803
804 static int imap_getpartialsizes(int sock, int first, int last, int *sizes)
805 /* capture the sizes of messages #first-#last */
806 {
807     char buf [MSGBUFSIZE+1];
808
809     /*
810      * Some servers (as in, PMDF5.1-9.1 under OpenVMS 6.1)
811      * won't accept 1:1 as valid set syntax.  Some implementors
812      * should be taken out and shot for excessive anality.
813      *
814      * Microsoft Exchange (brain-dead piece of crap that it is) 
815      * sometimes gets its knickers in a knot about bodiless messages.
816      * You may see responses like this:
817      *
818      *  fetchmail: IMAP> A0004 FETCH 1:9 RFC822.SIZE
819      *  fetchmail: IMAP< * 2 FETCH (RFC822.SIZE 1187)
820      *  fetchmail: IMAP< * 3 FETCH (RFC822.SIZE 3954)
821      *  fetchmail: IMAP< * 4 FETCH (RFC822.SIZE 1944)
822      *  fetchmail: IMAP< * 5 FETCH (RFC822.SIZE 2933)
823      *  fetchmail: IMAP< * 6 FETCH (RFC822.SIZE 1854)
824      *  fetchmail: IMAP< * 7 FETCH (RFC822.SIZE 34054)
825      *  fetchmail: IMAP< * 8 FETCH (RFC822.SIZE 5561)
826      *  fetchmail: IMAP< * 9 FETCH (RFC822.SIZE 1101)
827      *  fetchmail: IMAP< A0004 NO The requested item could not be found.
828      *
829      * This means message 1 has only headers.  For kicks and grins
830      * you can telnet in and look:
831      *  A003 FETCH 1 FULL
832      *  A003 NO The requested item could not be found.
833      *  A004 fetch 1 rfc822.header
834      *  A004 NO The requested item could not be found.
835      *  A006 FETCH 1 BODY
836      *  * 1 FETCH (BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 35 3))
837      *  A006 OK FETCH completed.
838      *
839      * To get around this, we terminate the read loop on a NO and count
840      * on the fact that the sizes array has been preinitialized with a
841      * known-bad size value.
842      */
843
844     /* expunges change the fetch numbers */
845     first -= expunged;
846     last -= expunged;
847
848     if (last == first)
849         gen_send(sock, "FETCH %d RFC822.SIZE", last);
850     else if (last > first)
851         gen_send(sock, "FETCH %d:%d RFC822.SIZE", first, last);
852     else /* no unseen messages! */
853         return(PS_SUCCESS);
854     for (;;)
855     {
856         unsigned int num, size;
857         int ok;
858         char *cp;
859
860         if ((ok = gen_recv(sock, buf, sizeof(buf))))
861             return(ok);
862         /* we want response matching to be case-insensitive */
863         for (cp = buf; *cp; cp++)
864             *cp = toupper((unsigned char)*cp);
865         /* an untagged NO means that a message was not readable */
866         if (strstr(buf, "* NO"))
867             ;
868         else if (strstr(buf, "OK") || strstr(buf, "NO"))
869             break;
870         else if (sscanf(buf, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2) 
871         {
872             if (num >= first && num <= last)
873                 sizes[num - first] = size;
874             else
875                 report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n");
876         }
877     }
878
879     return(PS_SUCCESS);
880 }
881
882 static int imap_getsizes(int sock, int count, int *sizes)
883 /* capture the sizes of all messages */
884 {
885     return imap_getpartialsizes(sock, 1, count, sizes);
886 }
887
888 static int imap_is_old(int sock, struct query *ctl, int number)
889 /* is the given message old? */
890 {
891     flag seen = TRUE;
892     int i;
893
894     /* 
895      * Expunges change the fetch numbers, but unseen_messages contains
896      * indices from before any expungees were done.  So neither the
897      * argument nor the values in message_sequence need to be decremented.
898      */
899
900     seen = TRUE;
901     for (i = 0; i < unseen; i++)
902         if (unseen_messages[i] == number)
903         {
904             seen = FALSE;
905             break;
906         }
907
908     return(seen);
909 }
910
911 static char *skip_token(char *ptr)
912 {
913     while(isspace((unsigned char)*ptr)) ptr++;
914     while(!isspace((unsigned char)*ptr) && !iscntrl((unsigned char)*ptr)) ptr++;
915     while(isspace((unsigned char)*ptr)) ptr++;
916     return(ptr);
917 }
918
919 static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
920 /* request headers of nth message */
921 {
922     char buf [MSGBUFSIZE+1];
923     int num;
924
925     /* expunges change the fetch numbers */
926     number -= expunged;
927
928     /*
929      * This is blessed by RFC1176, RFC1730, RFC2060.
930      * According to the RFCs, it should *not* set the \Seen flag.
931      */
932     gen_send(sock, "FETCH %d RFC822.HEADER", number);
933
934     /* looking for FETCH response */
935     for (;;) 
936     {
937         int     ok;
938         char    *ptr;
939
940         if ((ok = gen_recv(sock, buf, sizeof(buf))))
941             return(ok);
942         ptr = skip_token(buf);  /* either "* " or "AXXXX " */
943         if (sscanf(ptr, "%d FETCH (%*s {%d}", &num, lenp) == 2)
944             break;
945         /* try to recover from chronically fucked-up M$ Exchange servers */
946         else if (!strncmp(ptr, "NO", 2))
947         {
948             /* wait for a tagged response */
949             if (strstr (buf, "* NO"))
950                 imap_ok (sock, 0);
951             return(PS_TRANSIENT);
952         }
953         else if (!strncmp(ptr, "BAD", 3))
954         {
955             /* wait for a tagged response */
956             if (strstr (buf, "* BAD"))
957                 imap_ok (sock, 0);
958             return(PS_TRANSIENT);
959         }
960     }
961
962     if (num != number)
963         return(PS_ERROR);
964     else
965         return(PS_SUCCESS);
966 }
967
968 static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
969 /* request body of nth message */
970 {
971     char buf [MSGBUFSIZE+1], *cp;
972     int num;
973
974     /* expunges change the fetch numbers */
975     number -= expunged;
976
977     /*
978      * If we're using IMAP4, we can fetch the message without setting its
979      * seen flag.  This is good!  It means that if the protocol exchange
980      * craps out during the message, it will still be marked `unseen' on
981      * the server.
982      *
983      * According to RFC2060, and Mark Crispin the IMAP maintainer,
984      * FETCH %d BODY[TEXT] and RFC822.TEXT are "functionally 
985      * equivalent".  However, we know of at least one server that
986      * treats them differently in the presence of MIME attachments;
987      * the latter form downloads the attachment, the former does not.
988      * The server is InterChange, and the fool who implemented this
989      * misfeature ought to be strung up by his thumbs.  
990      *
991      * When I tried working around this by disabling use of the 4rev1 form,
992      * I found that doing this breaks operation with M$ Exchange.
993      * Annoyingly enough, Exchange's refusal to cope is technically legal
994      * under RFC2062.  Trust Microsoft, the Great Enemy of interoperability
995      * standards, to find a way to make standards compliance irritating....
996      */
997     switch (imap_version)
998     {
999     case IMAP4rev1:     /* RFC 2060 */
1000         gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number);
1001         break;
1002
1003     case IMAP4:         /* RFC 1730 */
1004         gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number);
1005         break;
1006
1007     default:            /* RFC 1176 */
1008         gen_send(sock, "FETCH %d RFC822.TEXT", number);
1009         break;
1010     }
1011
1012     /* looking for FETCH response */
1013     do {
1014         int     ok;
1015
1016         if ((ok = gen_recv(sock, buf, sizeof(buf))))
1017             return(ok);
1018     } while
1019         (!strstr(buf+4, "FETCH") || sscanf(buf+2, "%d", &num) != 1);
1020
1021     if (num != number)
1022         return(PS_ERROR);
1023
1024     /*
1025      * Try to extract a length from the FETCH response.  RFC2060 requires
1026      * it to be present, but at least one IMAP server (Novell GroupWise)
1027      * botches this.  The overflow check is needed because of a broken
1028      * server called dbmail that returns huge garbage lengths.
1029      */
1030     if ((cp = strchr(buf, '{'))) {
1031         errno = 0;
1032         *lenp = (int)strtol(cp + 1, (char **)NULL, 10);
1033         if (errno == ERANGE && (*lenp == LONG_MAX || *lenp == LONG_MIN))
1034             *lenp = -1;    /* length is too big/small for us to handle */
1035     }
1036     else
1037         *lenp = -1;     /* missing length part in FETCH reponse */
1038
1039     return(PS_SUCCESS);
1040 }
1041
1042 static int imap_trail(int sock, struct query *ctl, int number, const char *tag)
1043 /* discard tail of FETCH response after reading message text */
1044 {
1045     /* expunges change the fetch numbers */
1046     /* number -= expunged; */
1047
1048     for (;;)
1049     {
1050         char buf[MSGBUFSIZE+1], *t;
1051         int ok;
1052
1053         if ((ok = gen_recv(sock, buf, sizeof(buf))))
1054             return(ok);
1055
1056         /* UW IMAP returns "OK FETCH", Cyrus returns "OK Completed" */
1057         if (strncmp(buf, tag, strlen(tag)) == 0) {
1058             t = buf + strlen(tag);
1059             t += strspn(t, " \t");
1060             if (strncmp(t, "OK", 2) == 0)
1061                 break;
1062         }
1063     }
1064
1065     return(PS_SUCCESS);
1066 }
1067
1068 static int imap_delete(int sock, struct query *ctl, int number)
1069 /* set delete flag for given message */
1070 {
1071     int ok;
1072
1073     /* expunges change the fetch numbers */
1074     number -= expunged;
1075
1076     /*
1077      * Use SILENT if possible as a minor throughput optimization.
1078      * Note: this has been dropped from IMAP4rev1.
1079      *
1080      * We set Seen because there are some IMAP servers (notably HP
1081      * OpenMail) that do message-receipt DSNs, but only when the seen
1082      * bit is set.  This is the appropriate time -- we get here right
1083      * after the local SMTP response that says delivery was
1084      * successful.
1085      */
1086     if ((ok = gen_transact(sock,
1087                         imap_version == IMAP4 
1088                                 ? "STORE %d +FLAGS.SILENT (\\Seen \\Deleted)"
1089                                 : "STORE %d +FLAGS (\\Seen \\Deleted)", 
1090                         number)))
1091         return(ok);
1092     else
1093         deletions++;
1094
1095     /*
1096      * We do an expunge after expunge_period messages, rather than
1097      * just before quit, so that a line hit during a long session
1098      * won't result in lots of messages being fetched again during
1099      * the next session.
1100      */
1101     if (NUM_NONZERO(expunge_period) && (deletions % expunge_period) == 0)
1102         internal_expunge(sock);
1103
1104     return(PS_SUCCESS);
1105 }
1106
1107 static int imap_mark_seen(int sock, struct query *ctl, int number)
1108 /* mark the given message as seen */
1109 {
1110     return(gen_transact(sock,
1111         imap_version == IMAP4
1112         ? "STORE %d +FLAGS.SILENT (\\Seen)"
1113         : "STORE %d +FLAGS (\\Seen)",
1114         number));
1115 }
1116
1117 static int imap_end_mailbox_poll(int sock, struct query *ctl)
1118 /* cleanup mailbox before we idle or switch to another one */
1119 {
1120     if (deletions)
1121         internal_expunge(sock);
1122     return(PS_SUCCESS);
1123 }
1124
1125 static int imap_logout(int sock, struct query *ctl)
1126 /* send logout command */
1127 {
1128     /* if any un-expunged deletions remain, ship an expunge now */
1129     if (deletions)
1130         internal_expunge(sock);
1131
1132 #ifdef USE_SEARCH
1133     /* Memory clean-up */
1134     if (unseen_messages)
1135         free(unseen_messages);
1136 #endif /* USE_SEARCH */
1137
1138     return(gen_transact(sock, "LOGOUT"));
1139 }
1140
1141 static const struct method imap =
1142 {
1143     "IMAP",             /* Internet Message Access Protocol */
1144     "imap",
1145     "imaps",
1146     TRUE,               /* this is a tagged protocol */
1147     FALSE,              /* no message delimiter */
1148     imap_ok,            /* parse command response */
1149     imap_getauth,       /* get authorization */
1150     imap_getrange,      /* query range of messages */
1151     imap_getsizes,      /* get sizes of messages (used for ESMTP SIZE option) */
1152     imap_getpartialsizes,       /* get sizes of subset of messages (used for ESMTP SIZE option) */
1153     imap_is_old,        /* no UID check */
1154     imap_fetch_headers, /* request given message headers */
1155     imap_fetch_body,    /* request given message body */
1156     imap_trail,         /* eat message trailer */
1157     imap_delete,        /* delete the message */
1158     imap_mark_seen,     /* how to mark a message as seen */
1159     imap_end_mailbox_poll,      /* end-of-mailbox processing */
1160     imap_logout,        /* expunge and exit */
1161     TRUE,               /* yes, we can re-poll */
1162 };
1163
1164 int doIMAP(struct query *ctl)
1165 /* retrieve messages using IMAP Version 2bis or Version 4 */
1166 {
1167     return(do_protocol(ctl, &imap));
1168 }
1169
1170 /* imap.c ends here */