]> Pileus Git - ~andy/fetchmail/blob - imap.c
Better authentication-failure handling.
[~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 #ifdef KERBEROS_V4
19 #ifdef KERBEROS_V5
20 #include <kerberosIV/des.h>
21 #include <kerberosIV/krb.h>
22 #else
23 #if defined (__bsdi__)
24 #include <des.h>
25 #define krb_get_err_text(e) (krb_err_txt[e])
26 #endif
27 #if defined(__NetBSD__) || (__FreeBSD__) || defined(__linux__)
28 #define krb_get_err_text(e) (krb_err_txt[e])
29 #endif
30 #include <krb.h>
31 #endif
32 #endif /* KERBEROS_V4 */
33 #include  "i18n.h"
34
35 #ifdef GSSAPI
36 #ifdef HAVE_GSSAPI_H
37 #include <gssapi.h>
38 #endif
39 #ifdef HAVE_GSSAPI_GSSAPI_H
40 #include <gssapi/gssapi.h>
41 #endif
42 #ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
43 #include <gssapi/gssapi_generic.h>
44 #endif
45 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
46 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
47 #endif
48 #endif
49
50 #include "md5.h"
51
52 #if OPIE_ENABLE
53 #include <opie.h>
54 #endif /* OPIE_ENABLE */
55
56 #ifndef strstr          /* glibc-2.1 declares this as a macro */
57 extern char *strstr();  /* needed on sysV68 R3V7.1. */
58 #endif /* strstr */
59
60 /* imap_version values */
61 #define IMAP2           -1      /* IMAP2 or IMAP2BIS, RFC1176 */
62 #define IMAP4           0       /* IMAP4 rev 0, RFC1730 */
63 #define IMAP4rev1       1       /* IMAP4 rev 1, RFC2060 */
64
65 static int imap_phase;
66 #define PHASE_GETAUTH   0
67 #define PHASE_GETRANGE  1
68 #define PHASE_GETSIZES  2
69 #define PHASE_FETCH     3
70 #define PHASE_LOGOUT    4
71
72 static int count, seen, recent, unseen, deletions, imap_version, preauth; 
73 static int expunged, expunge_period;
74 static char capabilities[MSGBUFSIZE+1];
75
76 int imap_ok(int sock, char *argbuf)
77 /* parse command response */
78 {
79     char buf [MSGBUFSIZE+1];
80
81     seen = 0;
82     do {
83         int     ok;
84         char    *cp;
85
86         if ((ok = gen_recv(sock, buf, sizeof(buf))))
87             return(ok);
88
89         /* all tokens in responses are caseblind */
90         for (cp = buf; *cp; cp++)
91             if (islower(*cp))
92                 *cp = toupper(*cp);
93
94         /* interpret untagged status responses */
95         if (strstr(buf, "* CAPABILITY"))
96             strncpy(capabilities, buf + 12, sizeof(capabilities));
97         if (strstr(buf, "EXISTS"))
98             count = atoi(buf+2);
99         if (strstr(buf, "RECENT"))
100             recent = atoi(buf+2);
101         if (strstr(buf, "UNSEEN"))
102         {
103             char        *cp;
104
105             /*
106              * Handle both "* 42 UNSEEN" (if tha ever happens) and 
107              * "* OK [UNSEEN 42] 42". Note that what this gets us is
108              * a minimum index, not a count.
109              */
110             unseen = 0;
111             for (cp = buf; *cp && !isdigit(*cp); cp++)
112                 continue;
113             unseen = atoi(cp);
114         }
115         if (strstr(buf, "FLAGS"))
116             seen = (strstr(buf, "SEEN") != (char *)NULL);
117         if (strstr(buf, "PREAUTH"))
118             preauth = TRUE;
119     } while
120         (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
121
122     if (tag[0] == '\0')
123     {
124         if (argbuf)
125             strcpy(argbuf, buf);
126         return(PS_SUCCESS); 
127     }
128     else
129     {
130         char    *cp;
131
132         /* skip the tag */
133         for (cp = buf; !isspace(*cp); cp++)
134             continue;
135         while (isspace(*cp))
136             cp++;
137
138         if (strncmp(cp, "PREAUTH", 2) == 0)
139         {
140             if (argbuf)
141                 strcpy(argbuf, cp);
142             preauth = TRUE;
143             return(PS_SUCCESS);
144         }
145         else if (strncmp(cp, "OK", 2) == 0)
146         {
147             if (argbuf)
148                 strcpy(argbuf, cp);
149             return(PS_SUCCESS);
150         }
151         else if (strncmp(cp, "BAD", 3) == 0)
152             return(PS_ERROR);
153         else if (strncmp(cp, "NO", 2) == 0)
154         {
155             if (imap_phase == PHASE_GETAUTH) 
156                 return(PS_AUTHFAIL);    /* RFC2060, 6.2.2 */
157             else
158                 return(PS_ERROR);
159         }
160         else
161             return(PS_PROTOCOL);
162     }
163 }
164
165 #if OPIE_ENABLE
166 static int do_otp(int sock, struct query *ctl)
167 {
168     int i, rval;
169     char buffer[128];
170     char challenge[OPIE_CHALLENGE_MAX+1];
171     char response[OPIE_RESPONSE_MAX+1];
172
173     gen_send(sock, "AUTHENTICATE X-OTP");
174
175     if (rval = gen_recv(sock, buffer, sizeof(buffer)))
176         return rval;
177
178     if ((i = from64tobits(challenge, buffer)) < 0) {
179         report(stderr, _("Could not decode initial BASE64 challenge\n"));
180         return PS_AUTHFAIL;
181     };
182
183
184     to64frombits(buffer, ctl->remotename, strlen(ctl->remotename));
185
186     if (outlevel >= O_MONITOR)
187         report(stdout, "IMAP> %s\n", buffer);
188
189     /* best not to count on the challenge code handling multiple writes */
190     strcat(buffer, "\r\n");
191     SockWrite(sock, buffer, strlen(buffer));
192
193     if (rval = gen_recv(sock, buffer, sizeof(buffer)))
194         return rval;
195
196     if ((i = from64tobits(challenge, buffer)) < 0) {
197         report(stderr, _("Could not decode OTP challenge\n"));
198         return PS_AUTHFAIL;
199     };
200
201     rval = opiegenerator(challenge, !strcmp(ctl->password, "opie") ? "" : ctl->password, response);
202     if ((rval == -2) && !run.poll_interval) {
203         char secret[OPIE_SECRET_MAX+1];
204         fprintf(stderr, _("Secret pass phrase: "));
205         if (opiereadpass(secret, sizeof(secret), 0))
206             rval = opiegenerator(challenge, secret, response);
207         memset(secret, 0, sizeof(secret));
208     };
209
210     if (rval)
211         return(PS_AUTHFAIL);
212
213     to64frombits(buffer, response, strlen(response));
214
215     if (outlevel >= O_MONITOR)
216         report(stdout, "IMAP> %s\n", buffer);
217     strcat(buffer, "\r\n");
218     SockWrite(sock, buffer, strlen(buffer));
219
220     if (rval = gen_recv(sock, buffer, sizeof(buffer)))
221         return rval;
222
223     if (strstr(buffer, "OK"))
224         return PS_SUCCESS;
225     else
226         return PS_AUTHFAIL;
227 };
228 #endif /* OPIE_ENABLE */
229
230 #ifdef KERBEROS_V4
231 #if SIZEOF_INT == 4
232 typedef int     int32;
233 #elif SIZEOF_SHORT == 4
234 typedef short   int32;
235 #elif SIZEOF_LONG == 4
236 typedef long    int32;
237 #else
238 #error Cannot deduce a 32-bit-type
239 #endif
240
241 static int do_rfc1731(int sock, char *truename)
242 /* authenticate as per RFC1731 -- note 32-bit integer requirement here */
243 {
244     int result = 0, len;
245     char buf1[4096], buf2[4096];
246     union {
247       int32 cint;
248       char cstr[4];
249     } challenge1, challenge2;
250     char srvinst[INST_SZ];
251     char *p;
252     char srvrealm[REALM_SZ];
253     KTEXT_ST authenticator;
254     CREDENTIALS credentials;
255     char tktuser[MAX_K_NAME_SZ+1+INST_SZ+1+REALM_SZ+1];
256     char tktinst[INST_SZ];
257     char tktrealm[REALM_SZ];
258     des_cblock session;
259     des_key_schedule schedule;
260
261     gen_send(sock, "AUTHENTICATE KERBEROS_V4");
262
263     /* The data encoded in the first ready response contains a random
264      * 32-bit number in network byte order.  The client should respond
265      * with a Kerberos ticket and an authenticator for the principal
266      * "imap.hostname@realm", where "hostname" is the first component
267      * of the host name of the server with all letters in lower case
268      * and where "realm" is the Kerberos realm of the server.  The
269      * encrypted checksum field included within the Kerberos
270      * authenticator should contain the server provided 32-bit number
271      * in network byte order.
272      */
273
274     if (result = gen_recv(sock, buf1, sizeof buf1)) {
275         return result;
276     }
277
278     len = from64tobits(challenge1.cstr, buf1);
279     if (len < 0) {
280         report(stderr, _("could not decode initial BASE64 challenge\n"));
281         return PS_AUTHFAIL;
282     }
283
284     /* this patch by Dan Root <dar@thekeep.org> solves an endianess
285      * problem. */
286     {
287         char tmp[4];
288
289         *(int *)tmp = ntohl(*(int *) challenge1.cstr);
290         memcpy(challenge1.cstr, tmp, sizeof(tmp));
291     }
292
293     /* Client responds with a Kerberos ticket and an authenticator for
294      * the principal "imap.hostname@realm" where "hostname" is the
295      * first component of the host name of the server with all letters
296      * in lower case and where "realm" is the Kerberos realm of the
297      * server.  The encrypted checksum field included within the
298      * Kerberos authenticator should contain the server-provided
299      * 32-bit number in network byte order.
300      */
301
302     strncpy(srvinst, truename, (sizeof srvinst)-1);
303     srvinst[(sizeof srvinst)-1] = '\0';
304     for (p = srvinst; *p; p++) {
305       if (isupper(*p)) {
306         *p = tolower(*p);
307       }
308     }
309
310     strncpy(srvrealm, (char *)krb_realmofhost(srvinst), (sizeof srvrealm)-1);
311     srvrealm[(sizeof srvrealm)-1] = '\0';
312     if (p = strchr(srvinst, '.')) {
313       *p = '\0';
314     }
315
316     result = krb_mk_req(&authenticator, "imap", srvinst, srvrealm, 0);
317     if (result) {
318         report(stderr, "krb_mq_req: %s\n", krb_get_err_text(result));
319         return PS_AUTHFAIL;
320     }
321
322     result = krb_get_cred("imap", srvinst, srvrealm, &credentials);
323     if (result) {
324         report(stderr, "krb_get_cred: %s\n", krb_get_err_text(result));
325         return PS_AUTHFAIL;
326     }
327
328     memcpy(session, credentials.session, sizeof session);
329     memset(&credentials, 0, sizeof credentials);
330     des_key_sched(&session, schedule);
331
332     result = krb_get_tf_fullname(TKT_FILE, tktuser, tktinst, tktrealm);
333     if (result) {
334         report(stderr, "krb_get_tf_fullname: %s\n", krb_get_err_text(result));
335         return PS_AUTHFAIL;
336     }
337
338     if (strcmp(tktuser, user) != 0) {
339         report(stderr, 
340                _("principal %s in ticket does not match -u %s\n"), tktuser,
341                 user);
342         return PS_AUTHFAIL;
343     }
344
345     if (tktinst[0]) {
346         report(stderr, 
347                _("non-null instance (%s) might cause strange behavior\n"),
348                 tktinst);
349         strcat(tktuser, ".");
350         strcat(tktuser, tktinst);
351     }
352
353     if (strcmp(tktrealm, srvrealm) != 0) {
354         strcat(tktuser, "@");
355         strcat(tktuser, tktrealm);
356     }
357
358     result = krb_mk_req(&authenticator, "imap", srvinst, srvrealm,
359             challenge1.cint);
360     if (result) {
361         report(stderr, "krb_mq_req: %s\n", krb_get_err_text(result));
362         return PS_AUTHFAIL;
363     }
364
365     to64frombits(buf1, authenticator.dat, authenticator.length);
366     if (outlevel >= O_MONITOR) {
367         report(stdout, "IMAP> %s\n", buf1);
368     }
369     strcat(buf1, "\r\n");
370     SockWrite(sock, buf1, strlen(buf1));
371
372     /* Upon decrypting and verifying the ticket and authenticator, the
373      * server should verify that the contained checksum field equals
374      * the original server provided random 32-bit number.  Should the
375      * verification be successful, the server must add one to the
376      * checksum and construct 8 octets of data, with the first four
377      * octets containing the incremented checksum in network byte
378      * order, the fifth octet containing a bit-mask specifying the
379      * protection mechanisms supported by the server, and the sixth
380      * through eighth octets containing, in network byte order, the
381      * maximum cipher-text buffer size the server is able to receive.
382      * The server must encrypt the 8 octets of data in the session key
383      * and issue that encrypted data in a second ready response.  The
384      * client should consider the server authenticated if the first
385      * four octets the un-encrypted data is equal to one plus the
386      * checksum it previously sent.
387      */
388     
389     if (result = gen_recv(sock, buf1, sizeof buf1))
390         return result;
391
392     /* The client must construct data with the first four octets
393      * containing the original server-issued checksum in network byte
394      * order, the fifth octet containing the bit-mask specifying the
395      * selected protection mechanism, the sixth through eighth octets
396      * containing in network byte order the maximum cipher-text buffer
397      * size the client is able to receive, and the following octets
398      * containing a user name string.  The client must then append
399      * from one to eight octets so that the length of the data is a
400      * multiple of eight octets. The client must then PCBC encrypt the
401      * data with the session key and respond to the second ready
402      * response with the encrypted data.  The server decrypts the data
403      * and verifies the contained checksum.  The username field
404      * identifies the user for whom subsequent IMAP operations are to
405      * be performed; the server must verify that the principal
406      * identified in the Kerberos ticket is authorized to connect as
407      * that user.  After these verifications, the authentication
408      * process is complete.
409      */
410
411     len = from64tobits(buf2, buf1);
412     if (len < 0) {
413         report(stderr, _("could not decode BASE64 ready response\n"));
414         return PS_AUTHFAIL;
415     }
416
417     des_ecb_encrypt((des_cblock *)buf2, (des_cblock *)buf2, schedule, 0);
418     memcpy(challenge2.cstr, buf2, 4);
419     if (ntohl(challenge2.cint) != challenge1.cint + 1) {
420         report(stderr, _("challenge mismatch\n"));
421         return PS_AUTHFAIL;
422     }       
423
424     memset(authenticator.dat, 0, sizeof authenticator.dat);
425
426     result = htonl(challenge1.cint);
427     memcpy(authenticator.dat, &result, sizeof result);
428
429     /* The protection mechanisms and their corresponding bit-masks are as
430      * follows:
431      *
432      * 1 No protection mechanism
433      * 2 Integrity (krb_mk_safe) protection
434      * 4 Privacy (krb_mk_priv) protection
435      */
436     authenticator.dat[4] = 1;
437
438     len = strlen(tktuser);
439     strncpy(authenticator.dat+8, tktuser, len);
440     authenticator.length = len + 8 + 1;
441     while (authenticator.length & 7) {
442         authenticator.length++;
443     }
444     des_pcbc_encrypt((des_cblock *)authenticator.dat,
445             (des_cblock *)authenticator.dat, authenticator.length, schedule,
446             &session, 1);
447
448     to64frombits(buf1, authenticator.dat, authenticator.length);
449     if (outlevel >= O_MONITOR) {
450         report(stdout, "IMAP> %s\n", buf1);
451     }
452
453     strcat(buf1, "\r\n");
454     SockWrite(sock, buf1, strlen(buf1));
455
456     if (result = gen_recv(sock, buf1, sizeof buf1))
457         return result;
458
459     if (strstr(buf1, "OK")) {
460         return PS_SUCCESS;
461     }
462     else {
463         return PS_AUTHFAIL;
464     }
465 }
466 #endif /* KERBEROS_V4 */
467
468 #ifdef GSSAPI
469 #define GSSAUTH_P_NONE      1
470 #define GSSAUTH_P_INTEGRITY 2
471 #define GSSAUTH_P_PRIVACY   4
472
473 static int do_gssauth(int sock, char *hostname, char *username)
474 {
475     gss_buffer_desc request_buf, send_token;
476     gss_buffer_t sec_token;
477     gss_name_t target_name;
478     gss_ctx_id_t context;
479     gss_OID mech_name;
480     gss_qop_t quality;
481     int cflags;
482     OM_uint32 maj_stat, min_stat;
483     char buf1[8192], buf2[8192], server_conf_flags;
484     unsigned long buf_size;
485     int result;
486
487     /* first things first: get an imap ticket for host */
488     sprintf(buf1, "imap@%s", hostname);
489     request_buf.value = buf1;
490     request_buf.length = strlen(buf1) + 1;
491     maj_stat = gss_import_name(&min_stat, &request_buf, GSS_C_NT_HOSTBASED_SERVICE,
492         &target_name);
493     if (maj_stat != GSS_S_COMPLETE) {
494         report(stderr, _("Couldn't get service name for [%s]\n"), buf1);
495         return PS_AUTHFAIL;
496     }
497     else if (outlevel >= O_DEBUG) {
498         maj_stat = gss_display_name(&min_stat, target_name, &request_buf,
499             &mech_name);
500         report(stderr, _("Using service name [%s]\n"),request_buf.value);
501         maj_stat = gss_release_buffer(&min_stat, &request_buf);
502     }
503
504     gen_send(sock, "AUTHENTICATE GSSAPI");
505
506     /* upon receipt of the GSSAPI authentication request, server returns
507      * null data ready response. */
508     if (result = gen_recv(sock, buf1, sizeof buf1)) {
509         return result;
510     }
511
512     /* now start the security context initialisation loop... */
513     sec_token = GSS_C_NO_BUFFER;
514     context = GSS_C_NO_CONTEXT;
515     if (outlevel >= O_VERBOSE)
516         report(stdout, _("Sending credentials\n"));
517     do {
518         send_token.length = 0;
519         send_token.value = NULL;
520         maj_stat = gss_init_sec_context(&min_stat, 
521                                         GSS_C_NO_CREDENTIAL,
522                                         &context, 
523                                         target_name, 
524                                         GSS_C_NO_OID, 
525                                         GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 
526                                         0, 
527                                         GSS_C_NO_CHANNEL_BINDINGS, 
528                                         sec_token, 
529                                         NULL, 
530                                         &send_token, 
531                                         NULL, 
532                                         NULL);
533         if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) {
534             report(stderr, _("Error exchanging credentials\n"));
535             gss_release_name(&min_stat, &target_name);
536             /* wake up server and await NO response */
537             SockWrite(sock, "\r\n", 2);
538             if (result = gen_recv(sock, buf1, sizeof buf1))
539                 return result;
540             return PS_AUTHFAIL;
541         }
542         to64frombits(buf1, send_token.value, send_token.length);
543         gss_release_buffer(&min_stat, &send_token);
544         strcat(buf1, "\r\n");
545         SockWrite(sock, buf1, strlen(buf1));
546         if (outlevel >= O_MONITOR)
547             report(stdout, "IMAP> %s\n", buf1);
548         if (maj_stat == GSS_S_CONTINUE_NEEDED) {
549             if (result = gen_recv(sock, buf1, sizeof buf1)) {
550                 gss_release_name(&min_stat, &target_name);
551                 return result;
552             }
553             request_buf.length = from64tobits(buf2, buf1 + 2);
554             request_buf.value = buf2;
555             sec_token = &request_buf;
556         }
557     } while (maj_stat == GSS_S_CONTINUE_NEEDED);
558     gss_release_name(&min_stat, &target_name);
559
560     /* get security flags and buffer size */
561     if (result = gen_recv(sock, buf1, sizeof buf1)) {
562         return result;
563     }
564     request_buf.length = from64tobits(buf2, buf1 + 2);
565     request_buf.value = buf2;
566
567     maj_stat = gss_unwrap(&min_stat, context, &request_buf, &send_token,
568         &cflags, &quality);
569     if (maj_stat != GSS_S_COMPLETE) {
570         report(stderr, _("Couldn't unwrap security level data\n"));
571         gss_release_buffer(&min_stat, &send_token);
572         return PS_AUTHFAIL;
573     }
574     if (outlevel >= O_DEBUG)
575         report(stdout, _("Credential exchange complete\n"));
576     /* first octet is security levels supported. We want none, for now */
577     server_conf_flags = ((char *)send_token.value)[0];
578     if ( !(((char *)send_token.value)[0] & GSSAUTH_P_NONE) ) {
579         report(stderr, _("Server requires integrity and/or privacy\n"));
580         gss_release_buffer(&min_stat, &send_token);
581         return PS_AUTHFAIL;
582     }
583     ((char *)send_token.value)[0] = 0;
584     buf_size = ntohl(*((long *)send_token.value));
585     /* we don't care about buffer size if we don't wrap data */
586     gss_release_buffer(&min_stat, &send_token);
587     if (outlevel >= O_DEBUG) {
588         report(stdout, _("Unwrapped security level flags: %s%s%s\n"),
589             server_conf_flags & GSSAUTH_P_NONE ? "N" : "-",
590             server_conf_flags & GSSAUTH_P_INTEGRITY ? "I" : "-",
591             server_conf_flags & GSSAUTH_P_PRIVACY ? "C" : "-");
592         report(stdout, _("Maximum GSS token size is %ld\n"),buf_size);
593     }
594
595     /* now respond in kind (hack!!!) */
596     buf_size = htonl(buf_size); /* do as they do... only matters if we do enc */
597     memcpy(buf1, &buf_size, 4);
598     buf1[0] = GSSAUTH_P_NONE;
599     strcpy(buf1+4, username); /* server decides if princ is user */
600     request_buf.length = 4 + strlen(username) + 1;
601     request_buf.value = buf1;
602     maj_stat = gss_wrap(&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf,
603         &cflags, &send_token);
604     if (maj_stat != GSS_S_COMPLETE) {
605         report(stderr, _("Error creating security level request\n"));
606         return PS_AUTHFAIL;
607     }
608     to64frombits(buf1, send_token.value, send_token.length);
609     if (outlevel >= O_DEBUG) {
610         report(stdout, _("Requesting authorization as %s\n"), username);
611         report(stdout, "IMAP> %s\n",buf1);
612     }
613     strcat(buf1, "\r\n");
614     SockWrite(sock, buf1, strlen(buf1));
615
616     /* we should be done. Get status and finish up */
617     if (result = gen_recv(sock, buf1, sizeof buf1))
618         return result;
619     if (strstr(buf1, "OK")) {
620         /* flush security context */
621         if (outlevel >= O_DEBUG)
622             report(stdout, _("Releasing GSS credentials\n"));
623         maj_stat = gss_delete_sec_context(&min_stat, &context, &send_token);
624         if (maj_stat != GSS_S_COMPLETE) {
625             report(stderr, _("Error releasing credentials\n"));
626             return PS_AUTHFAIL;
627         }
628         /* send_token may contain a notification to the server to flush
629          * credentials. RFC 1731 doesn't specify what to do, and since this
630          * support is only for authentication, we'll assume the server
631          * knows enough to flush its own credentials */
632         gss_release_buffer(&min_stat, &send_token);
633         return PS_SUCCESS;
634     }
635
636     return PS_AUTHFAIL;
637 }       
638 #endif /* GSSAPI */
639
640 static void hmac_md5 (unsigned char *password,  size_t pass_len,
641                       unsigned char *challenge, size_t chal_len,
642                       unsigned char *response,  size_t resp_len)
643 {
644     int i;
645     unsigned char ipad[64];
646     unsigned char opad[64];
647     unsigned char hash_passwd[16];
648
649     MD5_CTX ctx;
650     
651     if (resp_len != 16)
652         return;
653
654     if (pass_len > sizeof (ipad))
655     {
656         MD5Init (&ctx);
657         MD5Update (&ctx, password, pass_len);
658         MD5Final (hash_passwd, &ctx);
659         password = hash_passwd; pass_len = sizeof (hash_passwd);
660     }
661
662     memset (ipad, 0, sizeof (ipad));
663     memset (opad, 0, sizeof (opad));
664     memcpy (ipad, password, pass_len);
665     memcpy (opad, password, pass_len);
666
667     for (i=0; i<64; i++) {
668         ipad[i] ^= 0x36;
669         opad[i] ^= 0x5c;
670     }
671
672     MD5Init (&ctx);
673     MD5Update (&ctx, ipad, sizeof (ipad));
674     MD5Update (&ctx, challenge, chal_len);
675     MD5Final (response, &ctx);
676
677     MD5Init (&ctx);
678     MD5Update (&ctx, opad, sizeof (opad));
679     MD5Update (&ctx, response, resp_len);
680     MD5Final (response, &ctx);
681 }
682
683 #if NTLM_ENABLE
684 #include "ntlm.h"
685
686 static tSmbNtlmAuthRequest   request;              
687 static tSmbNtlmAuthChallenge challenge;
688 static tSmbNtlmAuthResponse  response;
689
690 /*
691  * NTLM support by Grant Edwards.
692  *
693  * Handle MS-Exchange NTLM authentication method.  This is the same
694  * as the NTLM auth used by Samba for SMB related services. We just
695  * encode the packets in base64 instead of sending them out via a
696  * network interface.
697  * 
698  * Much source (ntlm.h, smb*.c smb*.h) was borrowed from Samba.
699  */
700
701 static int do_imap_ntlm(int sock, struct query *ctl)
702 {
703     char msgbuf[2048];
704     int result,len;
705   
706     gen_send(sock, "AUTHENTICATE NTLM");
707
708     if ((result = gen_recv(sock, msgbuf, sizeof msgbuf)))
709         return result;
710   
711     if (msgbuf[0] != '+')
712         return PS_AUTHFAIL;
713   
714     buildSmbNtlmAuthRequest(&request,ctl->remotename,NULL);
715
716     if (outlevel >= O_DEBUG)
717         dumpSmbNtlmAuthRequest(stdout, &request);
718
719     memset(msgbuf,0,sizeof msgbuf);
720     to64frombits (msgbuf, (unsigned char*)&request, SmbLength(&request));
721   
722     if (outlevel >= O_MONITOR)
723         report(stdout, "IMAP> %s\n", msgbuf);
724   
725     strcat(msgbuf,"\r\n");
726     SockWrite (sock, msgbuf, strlen (msgbuf));
727
728     if ((gen_recv(sock, msgbuf, sizeof msgbuf)))
729         return result;
730   
731     len = from64tobits ((unsigned char*)&challenge, msgbuf);
732     
733     if (outlevel >= O_DEBUG)
734         dumpSmbNtlmAuthChallenge(stdout, &challenge);
735     
736     buildSmbNtlmAuthResponse(&challenge, &response,ctl->remotename,ctl->password);
737   
738     if (outlevel >= O_DEBUG)
739         dumpSmbNtlmAuthResponse(stdout, &response);
740   
741     memset(msgbuf,0,sizeof msgbuf);
742     to64frombits (msgbuf, (unsigned char*)&response, SmbLength(&response));
743
744     if (outlevel >= O_MONITOR)
745         report(stdout, "IMAP> %s\n", msgbuf);
746       
747     strcat(msgbuf,"\r\n");
748
749     SockWrite (sock, msgbuf, strlen (msgbuf));
750   
751     if ((result = gen_recv (sock, msgbuf, sizeof msgbuf)))
752         return result;
753   
754     if (strstr (msgbuf, "OK"))
755         return PS_SUCCESS;
756     else
757         return PS_AUTHFAIL;
758 }
759 #endif /* NTLM */
760
761 static int do_cram_md5 (int sock, struct query *ctl)
762 /* authenticate as per RFC2195 */
763 {
764     int result;
765     int len;
766     unsigned char buf1[1024];
767     unsigned char msg_id[768];
768     unsigned char response[16];
769     unsigned char reply[1024];
770
771     gen_send (sock, "AUTHENTICATE CRAM-MD5");
772
773     /* From RFC2195:
774      * The data encoded in the first ready response contains an
775      * presumptively arbitrary string of random digits, a timestamp, and the
776      * fully-qualified primary host name of the server.  The syntax of the
777      * unencoded form must correspond to that of an RFC 822 'msg-id'
778      * [RFC822] as described in [POP3].
779      */
780
781     if ((result = gen_recv (sock, buf1, sizeof (buf1)))) {
782         return result;
783     }
784
785     len = from64tobits (msg_id, buf1);
786     if (len < 0) {
787         report (stderr, _("could not decode BASE64 challenge\n"));
788         return PS_AUTHFAIL;
789     } else if (len < sizeof (msg_id)) {
790         msg_id[len] = 0;
791     } else {
792         msg_id[sizeof (msg_id)-1] = 0;
793     }
794     if (outlevel >= O_DEBUG) {
795         report (stdout, "decoded as %s\n", msg_id);
796     }
797
798     /* The client makes note of the data and then responds with a string
799      * consisting of the user name, a space, and a 'digest'.  The latter is
800      * computed by applying the keyed MD5 algorithm from [KEYED-MD5] where
801      * the key is a shared secret and the digested text is the timestamp
802      * (including angle-brackets).
803      */
804
805     hmac_md5 (ctl->password, strlen (ctl->password),
806               msg_id, strlen (msg_id),
807               response, sizeof (response));
808
809 #ifdef HAVE_SNPRINTF
810     snprintf (reply, sizeof (reply),
811 #else
812     sprintf(reply,
813 #endif
814               "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 
815               ctl->remotename,
816               response[0], response[1], response[2], response[3],
817               response[4], response[5], response[6], response[7],
818               response[8], response[9], response[10], response[11],
819               response[12], response[13], response[14], response[15]);
820
821     if (outlevel >= O_DEBUG) {
822         report (stdout, "replying with %s\n", reply);
823     }
824
825     to64frombits (buf1, reply, strlen (reply));
826     if (outlevel >= O_MONITOR) {
827         report (stdout, "IMAP> %s\n", buf1);
828     }
829
830     /* PMDF5.2 IMAP has a bug that requires this to be a single write */
831     strcat (buf1, "\r\n");
832     SockWrite (sock, buf1, strlen (buf1));
833
834     if ((result = gen_recv (sock, buf1, sizeof (buf1))))
835         return result;
836
837     if (strstr (buf1, "OK")) {
838         return PS_SUCCESS;
839     } else {
840         return PS_AUTHFAIL;
841     }
842 }
843
844 int imap_canonicalize(char *result, char *raw, int maxlen)
845 /* encode an IMAP password as per RFC1730's quoting conventions */
846 {
847     int i, j;
848
849     j = 0;
850     for (i = 0; i < strlen(raw) && i < maxlen; i++)
851     {
852         if ((raw[i] == '\\') || (raw[i] == '"'))
853             result[j++] = '\\';
854         result[j++] = raw[i];
855     }
856     result[j] = '\0';
857
858     return(i);
859 }
860
861 int imap_getauth(int sock, struct query *ctl, char *greeting)
862 /* apply for connection authorization */
863 {
864     int ok = 0;
865
866     imap_phase = PHASE_GETAUTH;
867
868     /* probe to see if we're running IMAP4 and can use RFC822.PEEK */
869     capabilities[0] = '\0';
870     if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS)
871     {
872         /* UW-IMAP server 10.173 notifies in all caps */
873         if (strstr(capabilities, "IMAP4REV1"))
874         {
875             imap_version = IMAP4rev1;
876             if (outlevel >= O_DEBUG)
877                 report(stdout, _("Protocol identified as IMAP4 rev 1\n"));
878         }
879         else
880         {
881             imap_version = IMAP4;
882             if (outlevel >= O_DEBUG)
883                 report(stdout, _("Protocol identified as IMAP4 rev 0\n"));
884         }
885     }
886     else if (ok == PS_ERROR)
887     {
888         imap_version = IMAP2;
889         if (outlevel >= O_DEBUG)
890             report(stdout, _("Protocol identified as IMAP2 or IMAP2BIS\n"));
891     }
892     else
893         return(ok);
894
895     peek_capable = (imap_version >= IMAP4);
896
897     /* 
898      * Assumption: expunges are cheap, so we want to do them
899      * after every message unless user said otherwise.
900      */
901     if (NUM_SPECIFIED(ctl->expunge))
902         expunge_period = NUM_VALUE_OUT(ctl->expunge);
903     else
904         expunge_period = 1;
905
906     if (preauth)
907         return(PS_SUCCESS);
908
909 #if OPIE_ENABLE
910     if ((ctl->server.protocol == P_IMAP) && strstr(capabilities, "AUTH=X-OTP"))
911     {
912         if (outlevel >= O_DEBUG)
913             report(stdout, _("OTP authentication is supported\n"));
914         if (do_otp(sock, ctl) == PS_SUCCESS)
915             return(PS_SUCCESS);
916     };
917 #endif /* OPIE_ENABLE */
918
919 #ifdef GSSAPI
920     if (strstr(capabilities, "AUTH=GSSAPI"))
921     {
922         if (ctl->server.protocol == P_IMAP_GSS)
923         {
924             if (outlevel >= O_DEBUG)
925                 report(stdout, _("GSS authentication is supported\n"));
926             return do_gssauth(sock, ctl->server.truename, ctl->remotename);
927         }
928     }
929     else if (ctl->server.protocol == P_IMAP_GSS)
930     {
931         report(stderr, 
932                _("Required GSS capability not supported by server\n"));
933         return(PS_AUTHFAIL);
934     }
935 #endif /* GSSAPI */
936
937 #ifdef KERBEROS_V4
938     if (strstr(capabilities, "AUTH=KERBEROS_V4"))
939     {
940         if (outlevel >= O_DEBUG)
941             report(stdout, _("KERBEROS_V4 authentication is supported\n"));
942
943         if (ctl->server.protocol == P_IMAP_K4)
944         {
945             if ((ok = do_rfc1731(sock, ctl->server.truename)))
946             {
947                 if (outlevel >= O_MONITOR)
948                     report(stdout, "IMAP> *\n");
949                 SockWrite(sock, "*\r\n", 3);
950             }
951             
952             return(ok);
953         }
954         /* else fall through to ordinary AUTH=LOGIN case */
955     }
956     else if (ctl->server.protocol == P_IMAP_K4)
957     {
958         report(stderr, 
959                _("Required KERBEROS_V4 capability not supported by server\n"));
960         return(PS_AUTHFAIL);
961     }
962 #endif /* KERBEROS_V4 */
963
964     if (strstr(capabilities, "AUTH=CRAM-MD5"))
965     {
966         if (outlevel >= O_DEBUG)
967             report (stdout, _("CRAM-MD5 authentication is supported\n"));
968         if (ctl->server.protocol != P_IMAP_LOGIN)
969         {
970             if ((ok = do_cram_md5 (sock, ctl)))
971             {
972                 if (outlevel >= O_MONITOR)
973                     report (stdout, "IMAP> *\n");
974                 SockWrite (sock, "*\r\n", 3);
975             }
976             return ok;
977         }
978     }
979     else if (ctl->server.protocol == P_IMAP_CRAM_MD5)
980     {
981         report(stderr,
982                _("Required CRAM-MD5 capability not supported by server\n"));
983         return(PS_AUTHFAIL);
984     }
985
986 #ifdef NTLM_ENABLE
987     if (strstr (capabilities, "AUTH=NTLM"))
988     {
989         if (outlevel >= O_DEBUG)
990             report (stdout, _("NTLM authentication is supported\n"));
991         return do_imap_ntlm (sock, ctl);
992     }
993 #endif /* NTLM_ENABLE */
994
995 #ifdef __UNUSED__       /* The Cyrus IMAP4rev1 server chokes on this */
996     /* this handles either AUTH=LOGIN or AUTH-LOGIN */
997     if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN"))) {
998       report(stderr, 
999              _("Required LOGIN capability not supported by server\n"));
1000       return PS_AUTHFAIL;
1001     };
1002 #endif /* __UNUSED__ */
1003
1004     {
1005         /* these sizes guarantee no buffer overflow */
1006         char    remotename[NAMELEN*2+1], password[PASSWORDLEN*2+1];
1007
1008         imap_canonicalize(remotename, ctl->remotename, NAMELEN);
1009         imap_canonicalize(password, ctl->password, PASSWORDLEN);
1010         ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password);
1011     }
1012
1013     if (ok)
1014         return(ok);
1015     
1016     return(PS_SUCCESS);
1017 }
1018
1019 static int internal_expunge(int sock)
1020 /* ship an expunge, resetting associated counters */
1021 {
1022     int ok;
1023
1024     if ((ok = gen_transact(sock, "EXPUNGE")))
1025         return(ok);
1026
1027     expunged += deletions;
1028     deletions = 0;
1029
1030 #ifdef IMAP_UID /* not used */
1031     expunge_uids(ctl);
1032 #endif /* IMAP_UID */
1033
1034     return(PS_SUCCESS);
1035 }
1036
1037 static int imap_getrange(int sock, 
1038                          struct query *ctl, 
1039                          const char *folder, 
1040                          int *countp, int *newp, int *bytes)
1041 /* get range of messages to be fetched */
1042 {
1043     int ok;
1044
1045     imap_phase = PHASE_GETRANGE;
1046
1047     /* find out how many messages are waiting */
1048     *bytes = recent = unseen = -1;
1049
1050     if (pass > 1)
1051     {
1052         /* 
1053          * We have to have an expunge here, otherwise the re-poll will
1054          * infinite-loop picking up un-expunged messages -- unless the
1055          * expunge period is one and we've been nuking each message 
1056          * just after deletion.
1057          */
1058         ok = 0;
1059         if (deletions && expunge_period != 1)
1060             internal_expunge(sock);
1061         count = -1;
1062         if (ok || gen_transact(sock, "NOOP"))
1063         {
1064             report(stderr, _("re-poll failed\n"));
1065             return(ok);
1066         }
1067         else if (count == -1)   /* no EXISTS response to NOOP */
1068         {
1069             count = recent = 0;
1070             unseen = -1;
1071         }
1072     }
1073     else
1074     {
1075         if (!check_only)
1076             ok = gen_transact(sock, "SELECT %s", folder ? folder : "INBOX");
1077         else
1078             ok = gen_transact(sock, "EXAMINE %s", folder ? folder : "INBOX");
1079         if (ok != 0)
1080         {
1081             report(stderr, _("mailbox selection failed\n"));
1082             return(ok);
1083         }
1084     }
1085
1086     *countp = count;
1087
1088     /*
1089      * Note: because IMAP has an is_old method, this number is used
1090      * only for the "X messages (Y unseen)" notification.  Accordingly
1091      * it doesn't matter much that it can be wrong (e.g. if we see an
1092      * UNSEEN response but not all messages above the first UNSEEN one
1093      * are likewise).
1094      */
1095     if (unseen >= 0)            /* optional, but better if we see it */
1096         *newp = count - unseen + 1;
1097     else if (recent >= 0)       /* mandatory */
1098         *newp = recent;
1099     else
1100         *newp = -1;             /* should never happen, RECENT is mandatory */ 
1101
1102     expunged = 0;
1103
1104     return(PS_SUCCESS);
1105 }
1106
1107 static int imap_getsizes(int sock, int count, int *sizes)
1108 /* capture the sizes of all messages */
1109 {
1110     char buf [MSGBUFSIZE+1];
1111
1112     imap_phase = PHASE_GETSIZES;
1113
1114     /*
1115      * Some servers (as in, PMDF5.1-9.1 under OpenVMS 6.1)
1116      * won't accept 1:1 as valid set syntax.  Some implementors
1117      * should be taken out and shot for excessive anality.
1118      */
1119     if (count == 1)
1120         gen_send(sock, "FETCH 1 RFC822.SIZE", count);
1121     else
1122         gen_send(sock, "FETCH 1:%d RFC822.SIZE", count);
1123     for (;;)
1124     {
1125         int num, size, ok;
1126
1127         if ((ok = gen_recv(sock, buf, sizeof(buf))))
1128             return(ok);
1129         if (strstr(buf, "OK"))
1130             break;
1131         else if (sscanf(buf, "* %d FETCH (RFC822.SIZE %d)", &num, &size) == 2)
1132             sizes[num - 1] = size;
1133     }
1134
1135     imap_phase = PHASE_FETCH;
1136
1137     return(PS_SUCCESS);
1138 }
1139
1140 static int imap_is_old(int sock, struct query *ctl, int number)
1141 /* is the given message old? */
1142 {
1143     int ok;
1144
1145     /* expunges change the fetch numbers */
1146     number -= expunged;
1147
1148     if ((ok = gen_transact(sock, "FETCH %d FLAGS", number)) != 0)
1149         return(PS_ERROR);
1150
1151     return(seen);
1152 }
1153
1154 static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
1155 /* request headers of nth message */
1156 {
1157     char buf [MSGBUFSIZE+1];
1158     int num;
1159
1160     /* expunges change the fetch numbers */
1161     number -= expunged;
1162
1163     /*
1164      * This is blessed by RFC1176, RFC1730, RFC2060.
1165      * According to the RFCs, it should *not* set the \Seen flag.
1166      */
1167     gen_send(sock, "FETCH %d RFC822.HEADER", number);
1168
1169     /* looking for FETCH response */
1170     do {
1171         int     ok;
1172
1173         if ((ok = gen_recv(sock, buf, sizeof(buf))))
1174             return(ok);
1175     } while
1176         (sscanf(buf+2, "%d FETCH (%*s {%d}", &num, lenp) != 2);
1177
1178     if (num != number)
1179         return(PS_ERROR);
1180     else
1181         return(PS_SUCCESS);
1182 }
1183
1184 static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
1185 /* request body of nth message */
1186 {
1187     char buf [MSGBUFSIZE+1], *cp;
1188     int num;
1189
1190     /* expunges change the fetch numbers */
1191     number -= expunged;
1192
1193     /*
1194      * If we're using IMAP4, we can fetch the message without setting its
1195      * seen flag.  This is good!  It means that if the protocol exchange
1196      * craps out during the message, it will still be marked `unseen' on
1197      * the server.
1198      *
1199      * However...*don't* do this if we're using keep to suppress deletion!
1200      * In that case, marking the seen flag is the only way to prevent the
1201      * message from being re-fetched on subsequent runs (and according
1202      * to RFC2060 p.43 this fetch should set Seen as a side effect).
1203      */
1204     switch (imap_version)
1205     {
1206     case IMAP4rev1:     /* RFC 2060 */
1207         if (!ctl->keep)
1208             gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number);
1209         else
1210             gen_send(sock, "FETCH %d BODY[TEXT]", number);
1211         break;
1212
1213     case IMAP4:         /* RFC 1730 */
1214         if (!ctl->keep)
1215             gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number);
1216         else
1217             gen_send(sock, "FETCH %d RFC822.TEXT", number);
1218         break;
1219
1220     default:            /* RFC 1176 */
1221         gen_send(sock, "FETCH %d RFC822.TEXT", number);
1222         break;
1223     }
1224
1225     /* looking for FETCH response */
1226     do {
1227         int     ok;
1228
1229         if ((ok = gen_recv(sock, buf, sizeof(buf))))
1230             return(ok);
1231     } while
1232         (!strstr(buf+4, "FETCH") || sscanf(buf+2, "%d", &num) != 1);
1233
1234     if (num != number)
1235         return(PS_ERROR);
1236
1237     /*
1238      * Try to extract a length from the FETCH response.  RFC2060 requires
1239      * it to be present, but at least one IMAP server (Novell GroupWise)
1240      * botches this.
1241      */
1242     if ((cp = strchr(buf, '{')))
1243         *lenp = atoi(cp + 1);
1244     else
1245         *lenp = -1;     /* missing length part in FETCH reponse */
1246
1247     return(PS_SUCCESS);
1248 }
1249
1250 static int imap_trail(int sock, struct query *ctl, int number)
1251 /* discard tail of FETCH response after reading message text */
1252 {
1253     /* expunges change the fetch numbers */
1254     /* number -= expunged; */
1255
1256     for (;;)
1257     {
1258         char buf[MSGBUFSIZE+1];
1259         int ok;
1260
1261         if ((ok = gen_recv(sock, buf, sizeof(buf))))
1262             return(ok);
1263
1264         /* UW IMAP returns "OK FETCH", Cyrus returns "OK Completed" */
1265         if (strstr(buf, "OK"))
1266             break;
1267
1268 #ifdef __UNUSED__
1269         /*
1270          * Any IMAP server that fails to set Seen on a BODY[TEXT]
1271          * fetch violates RFC2060 p.43 (top).  This becomes an issue
1272          * when keep is on, because seen messages aren't deleted and
1273          * get refetched on each poll.  As a workaround, if keep is on
1274          * we can set the Seen flag explicitly.
1275          *
1276          * This code isn't used yet because we don't know of any IMAP
1277          * servers broken in this way.
1278          */
1279         if (ctl->keep)
1280             if ((ok = gen_transact(sock,
1281                         imap_version == IMAP4 
1282                                 ? "STORE %d +FLAGS.SILENT (\\Seen)"
1283                                 : "STORE %d +FLAGS (\\Seen)", 
1284                         number)))
1285                 return(ok);
1286 #endif /* __UNUSED__ */
1287     }
1288
1289     return(PS_SUCCESS);
1290 }
1291
1292 static int imap_delete(int sock, struct query *ctl, int number)
1293 /* set delete flag for given message */
1294 {
1295     int ok;
1296
1297     /* expunges change the fetch numbers */
1298     number -= expunged;
1299
1300     /*
1301      * Use SILENT if possible as a minor throughput optimization.
1302      * Note: this has been dropped from IMAP4rev1.
1303      *
1304      * We set Seen because there are some IMAP servers (notably HP
1305      * OpenMail) that do message-receipt DSNs, but only when the seen
1306      * bit is set.  This is the appropriate time -- we get here right
1307      * after the local SMTP response that says delivery was
1308      * successful.
1309      */
1310     if ((ok = gen_transact(sock,
1311                         imap_version == IMAP4 
1312                                 ? "STORE %d +FLAGS.SILENT (\\Seen \\Deleted)"
1313                                 : "STORE %d +FLAGS (\\Seen \\Deleted)", 
1314                         number)))
1315         return(ok);
1316     else
1317         deletions++;
1318
1319     /*
1320      * We do an expunge after expunge_period messages, rather than
1321      * just before quit, so that a line hit during a long session
1322      * won't result in lots of messages being fetched again during
1323      * the next session.
1324      */
1325     if (NUM_NONZERO(expunge_period) && (deletions % expunge_period) == 0)
1326         internal_expunge(sock);
1327
1328     return(PS_SUCCESS);
1329 }
1330
1331 static int imap_logout(int sock, struct query *ctl)
1332 /* send logout command */
1333 {
1334     imap_phase = PHASE_LOGOUT;
1335
1336     /* if any un-expunged deletions remain, ship an expunge now */
1337     if (deletions)
1338         internal_expunge(sock);
1339
1340     return(gen_transact(sock, "LOGOUT"));
1341 }
1342
1343 const static struct method imap =
1344 {
1345     "IMAP",             /* Internet Message Access Protocol */
1346 #if INET6_ENABLE
1347     "imap",
1348     "imaps",
1349 #else /* INET6_ENABLE */
1350     143,                /* standard IMAP2bis/IMAP4 port */
1351     993,                /* ssl IMAP2bis/IMAP4 port */
1352 #endif /* INET6_ENABLE */
1353     TRUE,               /* this is a tagged protocol */
1354     FALSE,              /* no message delimiter */
1355     imap_ok,            /* parse command response */
1356     imap_canonicalize,  /* deal with embedded slashes and spaces */
1357     imap_getauth,       /* get authorization */
1358     imap_getrange,      /* query range of messages */
1359     imap_getsizes,      /* get sizes of messages (used for ESMTP SIZE option) */
1360     imap_is_old,        /* no UID check */
1361     imap_fetch_headers, /* request given message headers */
1362     imap_fetch_body,    /* request given message body */
1363     imap_trail,         /* eat message trailer */
1364     imap_delete,        /* delete the message */
1365     imap_logout,        /* expunge and exit */
1366     TRUE,               /* yes, we can re-poll */
1367 };
1368
1369 int doIMAP(struct query *ctl)
1370 /* retrieve messages using IMAP Version 2bis or Version 4 */
1371 {
1372     return(do_protocol(ctl, &imap));
1373 }
1374
1375 /* imap.c ends here */