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