1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.haxx.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * RFC2195 CRAM-MD5 authentication
22 * RFC2595 Using TLS with IMAP, POP3 and ACAP
23 * RFC2831 DIGEST-MD5 authentication
24 * RFC3501 IMAPv4 protocol
25 * RFC4422 Simple Authentication and Security Layer (SASL)
26 * RFC4616 PLAIN authentication
27 * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism
28 * RFC4959 IMAP Extension for SASL Initial Client Response
29 * RFC5092 IMAP URL Scheme
30 * RFC6749 OAuth 2.0 Authorization Framework
31 * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
32 *
33 ***************************************************************************/
34
35 #include "curl_setup.h"
36
37 #ifndef CURL_DISABLE_IMAP
38
39 #ifdef HAVE_NETINET_IN_H
40 #include <netinet/in.h>
41 #endif
42 #ifdef HAVE_ARPA_INET_H
43 #include <arpa/inet.h>
44 #endif
45 #ifdef HAVE_UTSNAME_H
46 #include <sys/utsname.h>
47 #endif
48 #ifdef HAVE_NETDB_H
49 #include <netdb.h>
50 #endif
51 #ifdef __VMS
52 #include <in.h>
53 #include <inet.h>
54 #endif
55
56 #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
57 #undef in_addr_t
58 #define in_addr_t unsigned long
59 #endif
60
61 #include <curl/curl.h>
62 #include "urldata.h"
63 #include "sendf.h"
64 #include "hostip.h"
65 #include "progress.h"
66 #include "transfer.h"
67 #include "escape.h"
68 #include "http.h" /* for HTTP proxy tunnel stuff */
69 #include "socks.h"
70 #include "imap.h"
71
72 #include "strtoofft.h"
73 #include "strequal.h"
74 #include "vtls/vtls.h"
75 #include "connect.h"
76 #include "strerror.h"
77 #include "select.h"
78 #include "multiif.h"
79 #include "url.h"
80 #include "rawstr.h"
81 #include "curl_sasl.h"
82 #include "warnless.h"
83
84 /* The last 3 #include files should be in this order */
85 #include "curl_printf.h"
86 #include "curl_memory.h"
87 #include "memdebug.h"
88
89 /* Local API functions */
90 static CURLcode imap_regular_transfer(struct connectdata *conn, bool *done);
91 static CURLcode imap_do(struct connectdata *conn, bool *done);
92 static CURLcode imap_done(struct connectdata *conn, CURLcode status,
93 bool premature);
94 static CURLcode imap_connect(struct connectdata *conn, bool *done);
95 static CURLcode imap_disconnect(struct connectdata *conn, bool dead);
96 static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done);
97 static int imap_getsock(struct connectdata *conn, curl_socket_t *socks,
98 int numsocks);
99 static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done);
100 static CURLcode imap_setup_connection(struct connectdata *conn);
101 static char *imap_atom(const char *str, bool escape_only);
102 static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...);
103 static CURLcode imap_parse_url_options(struct connectdata *conn);
104 static CURLcode imap_parse_url_path(struct connectdata *conn);
105 static CURLcode imap_parse_custom_request(struct connectdata *conn);
106 static CURLcode imap_perform_authenticate(struct connectdata *conn,
107 const char *mech,
108 const char *initresp);
109 static CURLcode imap_continue_authenticate(struct connectdata *conn,
110 const char *resp);
111 static void imap_get_message(char *buffer, char** outptr);
112
113 /*
114 * IMAP protocol handler.
115 */
116
117 const struct Curl_handler Curl_handler_imap = {
118 "IMAP", /* scheme */
119 imap_setup_connection, /* setup_connection */
120 imap_do, /* do_it */
121 imap_done, /* done */
122 ZERO_NULL, /* do_more */
123 imap_connect, /* connect_it */
124 imap_multi_statemach, /* connecting */
125 imap_doing, /* doing */
126 imap_getsock, /* proto_getsock */
127 imap_getsock, /* doing_getsock */
128 ZERO_NULL, /* domore_getsock */
129 ZERO_NULL, /* perform_getsock */
130 imap_disconnect, /* disconnect */
131 ZERO_NULL, /* readwrite */
132 PORT_IMAP, /* defport */
133 CURLPROTO_IMAP, /* protocol */
134 PROTOPT_CLOSEACTION /* flags */
135 };
136
137 #ifdef USE_SSL
138 /*
139 * IMAPS protocol handler.
140 */
141
142 const struct Curl_handler Curl_handler_imaps = {
143 "IMAPS", /* scheme */
144 imap_setup_connection, /* setup_connection */
145 imap_do, /* do_it */
146 imap_done, /* done */
147 ZERO_NULL, /* do_more */
148 imap_connect, /* connect_it */
149 imap_multi_statemach, /* connecting */
150 imap_doing, /* doing */
151 imap_getsock, /* proto_getsock */
152 imap_getsock, /* doing_getsock */
153 ZERO_NULL, /* domore_getsock */
154 ZERO_NULL, /* perform_getsock */
155 imap_disconnect, /* disconnect */
156 ZERO_NULL, /* readwrite */
157 PORT_IMAPS, /* defport */
158 CURLPROTO_IMAPS, /* protocol */
159 PROTOPT_CLOSEACTION | PROTOPT_SSL /* flags */
160 };
161 #endif
162
163 #ifndef CURL_DISABLE_HTTP
164 /*
165 * HTTP-proxyed IMAP protocol handler.
166 */
167
168 static const struct Curl_handler Curl_handler_imap_proxy = {
169 "IMAP", /* scheme */
170 Curl_http_setup_conn, /* setup_connection */
171 Curl_http, /* do_it */
172 Curl_http_done, /* done */
173 ZERO_NULL, /* do_more */
174 ZERO_NULL, /* connect_it */
175 ZERO_NULL, /* connecting */
176 ZERO_NULL, /* doing */
177 ZERO_NULL, /* proto_getsock */
178 ZERO_NULL, /* doing_getsock */
179 ZERO_NULL, /* domore_getsock */
180 ZERO_NULL, /* perform_getsock */
181 ZERO_NULL, /* disconnect */
182 ZERO_NULL, /* readwrite */
183 PORT_IMAP, /* defport */
184 CURLPROTO_HTTP, /* protocol */
185 PROTOPT_NONE /* flags */
186 };
187
188 #ifdef USE_SSL
189 /*
190 * HTTP-proxyed IMAPS protocol handler.
191 */
192
193 static const struct Curl_handler Curl_handler_imaps_proxy = {
194 "IMAPS", /* scheme */
195 Curl_http_setup_conn, /* setup_connection */
196 Curl_http, /* do_it */
197 Curl_http_done, /* done */
198 ZERO_NULL, /* do_more */
199 ZERO_NULL, /* connect_it */
200 ZERO_NULL, /* connecting */
201 ZERO_NULL, /* doing */
202 ZERO_NULL, /* proto_getsock */
203 ZERO_NULL, /* doing_getsock */
204 ZERO_NULL, /* domore_getsock */
205 ZERO_NULL, /* perform_getsock */
206 ZERO_NULL, /* disconnect */
207 ZERO_NULL, /* readwrite */
208 PORT_IMAPS, /* defport */
209 CURLPROTO_HTTP, /* protocol */
210 PROTOPT_NONE /* flags */
211 };
212 #endif
213 #endif
214
215 /* SASL parameters for the imap protocol */
216 static const struct SASLproto saslimap = {
217 "imap", /* The service name */
218 '+', /* Code received when continuation is expected */
219 'O', /* Code to receive upon authentication success */
220 0, /* Maximum initial response length (no max) */
221 imap_perform_authenticate, /* Send authentication command */
222 imap_continue_authenticate, /* Send authentication continuation */
223 imap_get_message /* Get SASL response message */
224 };
225
226
227 #ifdef USE_SSL
imap_to_imaps(struct connectdata * conn)228 static void imap_to_imaps(struct connectdata *conn)
229 {
230 /* Change the connection handler */
231 conn->handler = &Curl_handler_imaps;
232
233 /* Set the connection's upgraded to TLS flag */
234 conn->tls_upgraded = TRUE;
235 }
236 #else
237 #define imap_to_imaps(x) Curl_nop_stmt
238 #endif
239
240 /***********************************************************************
241 *
242 * imap_matchresp()
243 *
244 * Determines whether the untagged response is related to the specified
245 * command by checking if it is in format "* <command-name> ..." or
246 * "* <number> <command-name> ...".
247 *
248 * The "* " marker is assumed to have already been checked by the caller.
249 */
imap_matchresp(const char * line,size_t len,const char * cmd)250 static bool imap_matchresp(const char *line, size_t len, const char *cmd)
251 {
252 const char *end = line + len;
253 size_t cmd_len = strlen(cmd);
254
255 /* Skip the untagged response marker */
256 line += 2;
257
258 /* Do we have a number after the marker? */
259 if(line < end && ISDIGIT(*line)) {
260 /* Skip the number */
261 do
262 line++;
263 while(line < end && ISDIGIT(*line));
264
265 /* Do we have the space character? */
266 if(line == end || *line != ' ')
267 return FALSE;
268
269 line++;
270 }
271
272 /* Does the command name match and is it followed by a space character or at
273 the end of line? */
274 if(line + cmd_len <= end && Curl_raw_nequal(line, cmd, cmd_len) &&
275 (line[cmd_len] == ' ' || line + cmd_len + 2 == end))
276 return TRUE;
277
278 return FALSE;
279 }
280
281 /***********************************************************************
282 *
283 * imap_endofresp()
284 *
285 * Checks whether the given string is a valid tagged, untagged or continuation
286 * response which can be processed by the response handler.
287 */
imap_endofresp(struct connectdata * conn,char * line,size_t len,int * resp)288 static bool imap_endofresp(struct connectdata *conn, char *line, size_t len,
289 int *resp)
290 {
291 struct IMAP *imap = conn->data->req.protop;
292 struct imap_conn *imapc = &conn->proto.imapc;
293 const char *id = imapc->resptag;
294 size_t id_len = strlen(id);
295
296 /* Do we have a tagged command response? */
297 if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') {
298 line += id_len + 1;
299 len -= id_len + 1;
300
301 if(len >= 2 && !memcmp(line, "OK", 2))
302 *resp = 'O';
303 else if(len >= 2 && !memcmp(line, "NO", 2))
304 *resp = 'N';
305 else if(len >= 3 && !memcmp(line, "BAD", 3))
306 *resp = 'B';
307 else {
308 failf(conn->data, "Bad tagged response");
309 *resp = -1;
310 }
311
312 return TRUE;
313 }
314
315 /* Do we have an untagged command response? */
316 if(len >= 2 && !memcmp("* ", line, 2)) {
317 switch(imapc->state) {
318 /* States which are interested in untagged responses */
319 case IMAP_CAPABILITY:
320 if(!imap_matchresp(line, len, "CAPABILITY"))
321 return FALSE;
322 break;
323
324 case IMAP_LIST:
325 if((!imap->custom && !imap_matchresp(line, len, "LIST")) ||
326 (imap->custom && !imap_matchresp(line, len, imap->custom) &&
327 (strcmp(imap->custom, "STORE") ||
328 !imap_matchresp(line, len, "FETCH")) &&
329 strcmp(imap->custom, "SELECT") &&
330 strcmp(imap->custom, "EXAMINE") &&
331 strcmp(imap->custom, "SEARCH") &&
332 strcmp(imap->custom, "EXPUNGE") &&
333 strcmp(imap->custom, "LSUB") &&
334 strcmp(imap->custom, "UID") &&
335 strcmp(imap->custom, "NOOP")))
336 return FALSE;
337 break;
338
339 case IMAP_SELECT:
340 /* SELECT is special in that its untagged responses do not have a
341 common prefix so accept anything! */
342 break;
343
344 case IMAP_FETCH:
345 if(!imap_matchresp(line, len, "FETCH"))
346 return FALSE;
347 break;
348
349 case IMAP_SEARCH:
350 if(!imap_matchresp(line, len, "SEARCH"))
351 return FALSE;
352 break;
353
354 /* Ignore other untagged responses */
355 default:
356 return FALSE;
357 }
358
359 *resp = '*';
360 return TRUE;
361 }
362
363 /* Do we have a continuation response? This should be a + symbol followed by
364 a space and optionally some text as per RFC-3501 for the AUTHENTICATE and
365 APPEND commands and as outlined in Section 4. Examples of RFC-4959 but
366 some e-mail servers ignore this and only send a single + instead. */
367 if(imap && !imap->custom && ((len == 3 && !memcmp("+", line, 1)) ||
368 (len >= 2 && !memcmp("+ ", line, 2)))) {
369 switch(imapc->state) {
370 /* States which are interested in continuation responses */
371 case IMAP_AUTHENTICATE:
372 case IMAP_APPEND:
373 *resp = '+';
374 break;
375
376 default:
377 failf(conn->data, "Unexpected continuation response");
378 *resp = -1;
379 break;
380 }
381
382 return TRUE;
383 }
384
385 return FALSE; /* Nothing for us */
386 }
387
388 /***********************************************************************
389 *
390 * imap_get_message()
391 *
392 * Gets the authentication message from the response buffer.
393 */
imap_get_message(char * buffer,char ** outptr)394 static void imap_get_message(char *buffer, char** outptr)
395 {
396 size_t len = 0;
397 char* message = NULL;
398
399 /* Find the start of the message */
400 for(message = buffer + 2; *message == ' ' || *message == '\t'; message++)
401 ;
402
403 /* Find the end of the message */
404 for(len = strlen(message); len--;)
405 if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' &&
406 message[len] != '\t')
407 break;
408
409 /* Terminate the message */
410 if(++len) {
411 message[len] = '\0';
412 }
413
414 *outptr = message;
415 }
416
417 /***********************************************************************
418 *
419 * state()
420 *
421 * This is the ONLY way to change IMAP state!
422 */
state(struct connectdata * conn,imapstate newstate)423 static void state(struct connectdata *conn, imapstate newstate)
424 {
425 struct imap_conn *imapc = &conn->proto.imapc;
426 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
427 /* for debug purposes */
428 static const char * const names[]={
429 "STOP",
430 "SERVERGREET",
431 "CAPABILITY",
432 "STARTTLS",
433 "UPGRADETLS",
434 "AUTHENTICATE",
435 "LOGIN",
436 "LIST",
437 "SELECT",
438 "FETCH",
439 "FETCH_FINAL",
440 "APPEND",
441 "APPEND_FINAL",
442 "SEARCH",
443 "LOGOUT",
444 /* LAST */
445 };
446
447 if(imapc->state != newstate)
448 infof(conn->data, "IMAP %p state change from %s to %s\n",
449 (void *)imapc, names[imapc->state], names[newstate]);
450 #endif
451
452 imapc->state = newstate;
453 }
454
455 /***********************************************************************
456 *
457 * imap_perform_capability()
458 *
459 * Sends the CAPABILITY command in order to obtain a list of server side
460 * supported capabilities.
461 */
imap_perform_capability(struct connectdata * conn)462 static CURLcode imap_perform_capability(struct connectdata *conn)
463 {
464 CURLcode result = CURLE_OK;
465 struct imap_conn *imapc = &conn->proto.imapc;
466
467 imapc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */
468 imapc->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */
469 imapc->tls_supported = FALSE; /* Clear the TLS capability */
470
471 /* Send the CAPABILITY command */
472 result = imap_sendf(conn, "CAPABILITY");
473
474 if(!result)
475 state(conn, IMAP_CAPABILITY);
476
477 return result;
478 }
479
480 /***********************************************************************
481 *
482 * imap_perform_starttls()
483 *
484 * Sends the STARTTLS command to start the upgrade to TLS.
485 */
imap_perform_starttls(struct connectdata * conn)486 static CURLcode imap_perform_starttls(struct connectdata *conn)
487 {
488 CURLcode result = CURLE_OK;
489
490 /* Send the STARTTLS command */
491 result = imap_sendf(conn, "STARTTLS");
492
493 if(!result)
494 state(conn, IMAP_STARTTLS);
495
496 return result;
497 }
498
499 /***********************************************************************
500 *
501 * imap_perform_upgrade_tls()
502 *
503 * Performs the upgrade to TLS.
504 */
imap_perform_upgrade_tls(struct connectdata * conn)505 static CURLcode imap_perform_upgrade_tls(struct connectdata *conn)
506 {
507 CURLcode result = CURLE_OK;
508 struct imap_conn *imapc = &conn->proto.imapc;
509
510 /* Start the SSL connection */
511 result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone);
512
513 if(!result) {
514 if(imapc->state != IMAP_UPGRADETLS)
515 state(conn, IMAP_UPGRADETLS);
516
517 if(imapc->ssldone) {
518 imap_to_imaps(conn);
519 result = imap_perform_capability(conn);
520 }
521 }
522
523 return result;
524 }
525
526 /***********************************************************************
527 *
528 * imap_perform_login()
529 *
530 * Sends a clear text LOGIN command to authenticate with.
531 */
imap_perform_login(struct connectdata * conn)532 static CURLcode imap_perform_login(struct connectdata *conn)
533 {
534 CURLcode result = CURLE_OK;
535 char *user;
536 char *passwd;
537
538 /* Check we have a username and password to authenticate with and end the
539 connect phase if we don't */
540 if(!conn->bits.user_passwd) {
541 state(conn, IMAP_STOP);
542
543 return result;
544 }
545
546 /* Make sure the username and password are in the correct atom format */
547 user = imap_atom(conn->user, false);
548 passwd = imap_atom(conn->passwd, false);
549
550 /* Send the LOGIN command */
551 result = imap_sendf(conn, "LOGIN %s %s", user ? user : "",
552 passwd ? passwd : "");
553
554 free(user);
555 free(passwd);
556
557 if(!result)
558 state(conn, IMAP_LOGIN);
559
560 return result;
561 }
562
563 /***********************************************************************
564 *
565 * imap_perform_authenticate()
566 *
567 * Sends an AUTHENTICATE command allowing the client to login with the given
568 * SASL authentication mechanism.
569 */
imap_perform_authenticate(struct connectdata * conn,const char * mech,const char * initresp)570 static CURLcode imap_perform_authenticate(struct connectdata *conn,
571 const char *mech,
572 const char *initresp)
573 {
574 CURLcode result = CURLE_OK;
575
576 if(initresp) {
577 /* Send the AUTHENTICATE command with the initial response */
578 result = imap_sendf(conn, "AUTHENTICATE %s %s", mech, initresp);
579 }
580 else {
581 /* Send the AUTHENTICATE command */
582 result = imap_sendf(conn, "AUTHENTICATE %s", mech);
583 }
584
585 return result;
586 }
587
588 /***********************************************************************
589 *
590 * imap_continue_authenticate()
591 *
592 * Sends SASL continuation data or cancellation.
593 */
imap_continue_authenticate(struct connectdata * conn,const char * resp)594 static CURLcode imap_continue_authenticate(struct connectdata *conn,
595 const char *resp)
596 {
597 struct imap_conn *imapc = &conn->proto.imapc;
598
599 return Curl_pp_sendf(&imapc->pp, "%s", resp);
600 }
601
602 /***********************************************************************
603 *
604 * imap_perform_authentication()
605 *
606 * Initiates the authentication sequence, with the appropriate SASL
607 * authentication mechanism, falling back to clear text should a common
608 * mechanism not be available between the client and server.
609 */
imap_perform_authentication(struct connectdata * conn)610 static CURLcode imap_perform_authentication(struct connectdata *conn)
611 {
612 CURLcode result = CURLE_OK;
613 struct imap_conn *imapc = &conn->proto.imapc;
614 saslprogress progress;
615
616 /* Check we have enough data to authenticate with and end the
617 connect phase if we don't */
618 if(!Curl_sasl_can_authenticate(&imapc->sasl, conn)) {
619 state(conn, IMAP_STOP);
620 return result;
621 }
622
623 /* Calculate the SASL login details */
624 result = Curl_sasl_start(&imapc->sasl, conn, imapc->ir_supported, &progress);
625
626 if(!result) {
627 if(progress == SASL_INPROGRESS)
628 state(conn, IMAP_AUTHENTICATE);
629 else if(!imapc->login_disabled && (imapc->preftype & IMAP_TYPE_CLEARTEXT))
630 /* Perform clear text authentication */
631 result = imap_perform_login(conn);
632 else {
633 /* Other mechanisms not supported */
634 infof(conn->data, "No known authentication mechanisms supported!\n");
635 result = CURLE_LOGIN_DENIED;
636 }
637 }
638
639 return result;
640 }
641
642 /***********************************************************************
643 *
644 * imap_perform_list()
645 *
646 * Sends a LIST command or an alternative custom request.
647 */
imap_perform_list(struct connectdata * conn)648 static CURLcode imap_perform_list(struct connectdata *conn)
649 {
650 CURLcode result = CURLE_OK;
651 struct Curl_easy *data = conn->data;
652 struct IMAP *imap = data->req.protop;
653 char *mailbox;
654
655 if(imap->custom)
656 /* Send the custom request */
657 result = imap_sendf(conn, "%s%s", imap->custom,
658 imap->custom_params ? imap->custom_params : "");
659 else {
660 /* Make sure the mailbox is in the correct atom format if necessary */
661 mailbox = imap->mailbox ? imap_atom(imap->mailbox, true) : strdup("");
662 if(!mailbox)
663 return CURLE_OUT_OF_MEMORY;
664
665 /* Send the LIST command */
666 result = imap_sendf(conn, "LIST \"%s\" *", mailbox);
667
668 free(mailbox);
669 }
670
671 if(!result)
672 state(conn, IMAP_LIST);
673
674 return result;
675 }
676
677 /***********************************************************************
678 *
679 * imap_perform_select()
680 *
681 * Sends a SELECT command to ask the server to change the selected mailbox.
682 */
imap_perform_select(struct connectdata * conn)683 static CURLcode imap_perform_select(struct connectdata *conn)
684 {
685 CURLcode result = CURLE_OK;
686 struct Curl_easy *data = conn->data;
687 struct IMAP *imap = data->req.protop;
688 struct imap_conn *imapc = &conn->proto.imapc;
689 char *mailbox;
690
691 /* Invalidate old information as we are switching mailboxes */
692 Curl_safefree(imapc->mailbox);
693 Curl_safefree(imapc->mailbox_uidvalidity);
694
695 /* Check we have a mailbox */
696 if(!imap->mailbox) {
697 failf(conn->data, "Cannot SELECT without a mailbox.");
698 return CURLE_URL_MALFORMAT;
699 }
700
701 /* Make sure the mailbox is in the correct atom format */
702 mailbox = imap_atom(imap->mailbox, false);
703 if(!mailbox)
704 return CURLE_OUT_OF_MEMORY;
705
706 /* Send the SELECT command */
707 result = imap_sendf(conn, "SELECT %s", mailbox);
708
709 free(mailbox);
710
711 if(!result)
712 state(conn, IMAP_SELECT);
713
714 return result;
715 }
716
717 /***********************************************************************
718 *
719 * imap_perform_fetch()
720 *
721 * Sends a FETCH command to initiate the download of a message.
722 */
imap_perform_fetch(struct connectdata * conn)723 static CURLcode imap_perform_fetch(struct connectdata *conn)
724 {
725 CURLcode result = CURLE_OK;
726 struct IMAP *imap = conn->data->req.protop;
727
728 /* Check we have a UID */
729 if(!imap->uid) {
730 failf(conn->data, "Cannot FETCH without a UID.");
731 return CURLE_URL_MALFORMAT;
732 }
733
734 /* Send the FETCH command */
735 if(imap->partial)
736 result = imap_sendf(conn, "FETCH %s BODY[%s]<%s>",
737 imap->uid,
738 imap->section ? imap->section : "",
739 imap->partial);
740 else
741 result = imap_sendf(conn, "FETCH %s BODY[%s]",
742 imap->uid,
743 imap->section ? imap->section : "");
744
745 if(!result)
746 state(conn, IMAP_FETCH);
747
748 return result;
749 }
750
751 /***********************************************************************
752 *
753 * imap_perform_append()
754 *
755 * Sends an APPEND command to initiate the upload of a message.
756 */
imap_perform_append(struct connectdata * conn)757 static CURLcode imap_perform_append(struct connectdata *conn)
758 {
759 CURLcode result = CURLE_OK;
760 struct IMAP *imap = conn->data->req.protop;
761 char *mailbox;
762
763 /* Check we have a mailbox */
764 if(!imap->mailbox) {
765 failf(conn->data, "Cannot APPEND without a mailbox.");
766 return CURLE_URL_MALFORMAT;
767 }
768
769 /* Check we know the size of the upload */
770 if(conn->data->state.infilesize < 0) {
771 failf(conn->data, "Cannot APPEND with unknown input file size\n");
772 return CURLE_UPLOAD_FAILED;
773 }
774
775 /* Make sure the mailbox is in the correct atom format */
776 mailbox = imap_atom(imap->mailbox, false);
777 if(!mailbox)
778 return CURLE_OUT_OF_MEMORY;
779
780 /* Send the APPEND command */
781 result = imap_sendf(conn, "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}",
782 mailbox, conn->data->state.infilesize);
783
784 free(mailbox);
785
786 if(!result)
787 state(conn, IMAP_APPEND);
788
789 return result;
790 }
791
792 /***********************************************************************
793 *
794 * imap_perform_search()
795 *
796 * Sends a SEARCH command.
797 */
imap_perform_search(struct connectdata * conn)798 static CURLcode imap_perform_search(struct connectdata *conn)
799 {
800 CURLcode result = CURLE_OK;
801 struct IMAP *imap = conn->data->req.protop;
802
803 /* Check we have a query string */
804 if(!imap->query) {
805 failf(conn->data, "Cannot SEARCH without a query string.");
806 return CURLE_URL_MALFORMAT;
807 }
808
809 /* Send the SEARCH command */
810 result = imap_sendf(conn, "SEARCH %s", imap->query);
811
812 if(!result)
813 state(conn, IMAP_SEARCH);
814
815 return result;
816 }
817
818 /***********************************************************************
819 *
820 * imap_perform_logout()
821 *
822 * Performs the logout action prior to sclose() being called.
823 */
imap_perform_logout(struct connectdata * conn)824 static CURLcode imap_perform_logout(struct connectdata *conn)
825 {
826 CURLcode result = CURLE_OK;
827
828 /* Send the LOGOUT command */
829 result = imap_sendf(conn, "LOGOUT");
830
831 if(!result)
832 state(conn, IMAP_LOGOUT);
833
834 return result;
835 }
836
837 /* For the initial server greeting */
imap_state_servergreet_resp(struct connectdata * conn,int imapcode,imapstate instate)838 static CURLcode imap_state_servergreet_resp(struct connectdata *conn,
839 int imapcode,
840 imapstate instate)
841 {
842 CURLcode result = CURLE_OK;
843 struct Curl_easy *data = conn->data;
844
845 (void)instate; /* no use for this yet */
846
847 if(imapcode != 'O') {
848 failf(data, "Got unexpected imap-server response");
849 result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */
850 }
851 else
852 result = imap_perform_capability(conn);
853
854 return result;
855 }
856
857 /* For CAPABILITY responses */
imap_state_capability_resp(struct connectdata * conn,int imapcode,imapstate instate)858 static CURLcode imap_state_capability_resp(struct connectdata *conn,
859 int imapcode,
860 imapstate instate)
861 {
862 CURLcode result = CURLE_OK;
863 struct Curl_easy *data = conn->data;
864 struct imap_conn *imapc = &conn->proto.imapc;
865 const char *line = data->state.buffer;
866 size_t wordlen;
867
868 (void)instate; /* no use for this yet */
869
870 /* Do we have a untagged response? */
871 if(imapcode == '*') {
872 line += 2;
873
874 /* Loop through the data line */
875 for(;;) {
876 while(*line &&
877 (*line == ' ' || *line == '\t' ||
878 *line == '\r' || *line == '\n')) {
879
880 line++;
881 }
882
883 if(!*line)
884 break;
885
886 /* Extract the word */
887 for(wordlen = 0; line[wordlen] && line[wordlen] != ' ' &&
888 line[wordlen] != '\t' && line[wordlen] != '\r' &&
889 line[wordlen] != '\n';)
890 wordlen++;
891
892 /* Does the server support the STARTTLS capability? */
893 if(wordlen == 8 && !memcmp(line, "STARTTLS", 8))
894 imapc->tls_supported = TRUE;
895
896 /* Has the server explicitly disabled clear text authentication? */
897 else if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13))
898 imapc->login_disabled = TRUE;
899
900 /* Does the server support the SASL-IR capability? */
901 else if(wordlen == 7 && !memcmp(line, "SASL-IR", 7))
902 imapc->ir_supported = TRUE;
903
904 /* Do we have a SASL based authentication mechanism? */
905 else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) {
906 size_t llen;
907 unsigned int mechbit;
908
909 line += 5;
910 wordlen -= 5;
911
912 /* Test the word for a matching authentication mechanism */
913 mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
914 if(mechbit && llen == wordlen)
915 imapc->sasl.authmechs |= mechbit;
916 }
917
918 line += wordlen;
919 }
920 }
921 else if(imapcode == 'O') {
922 if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) {
923 /* We don't have a SSL/TLS connection yet, but SSL is requested */
924 if(imapc->tls_supported)
925 /* Switch to TLS connection now */
926 result = imap_perform_starttls(conn);
927 else if(data->set.use_ssl == CURLUSESSL_TRY)
928 /* Fallback and carry on with authentication */
929 result = imap_perform_authentication(conn);
930 else {
931 failf(data, "STARTTLS not supported.");
932 result = CURLE_USE_SSL_FAILED;
933 }
934 }
935 else
936 result = imap_perform_authentication(conn);
937 }
938 else
939 result = imap_perform_authentication(conn);
940
941 return result;
942 }
943
944 /* For STARTTLS responses */
imap_state_starttls_resp(struct connectdata * conn,int imapcode,imapstate instate)945 static CURLcode imap_state_starttls_resp(struct connectdata *conn,
946 int imapcode,
947 imapstate instate)
948 {
949 CURLcode result = CURLE_OK;
950 struct Curl_easy *data = conn->data;
951
952 (void)instate; /* no use for this yet */
953
954 if(imapcode != 'O') {
955 if(data->set.use_ssl != CURLUSESSL_TRY) {
956 failf(data, "STARTTLS denied. %c", imapcode);
957 result = CURLE_USE_SSL_FAILED;
958 }
959 else
960 result = imap_perform_authentication(conn);
961 }
962 else
963 result = imap_perform_upgrade_tls(conn);
964
965 return result;
966 }
967
968 /* For SASL authentication responses */
imap_state_auth_resp(struct connectdata * conn,int imapcode,imapstate instate)969 static CURLcode imap_state_auth_resp(struct connectdata *conn,
970 int imapcode,
971 imapstate instate)
972 {
973 CURLcode result = CURLE_OK;
974 struct Curl_easy *data = conn->data;
975 struct imap_conn *imapc = &conn->proto.imapc;
976 saslprogress progress;
977
978 (void)instate; /* no use for this yet */
979
980 result = Curl_sasl_continue(&imapc->sasl, conn, imapcode, &progress);
981 if(!result)
982 switch(progress) {
983 case SASL_DONE:
984 state(conn, IMAP_STOP); /* Authenticated */
985 break;
986 case SASL_IDLE: /* No mechanism left after cancellation */
987 if((!imapc->login_disabled) && (imapc->preftype & IMAP_TYPE_CLEARTEXT))
988 /* Perform clear text authentication */
989 result = imap_perform_login(conn);
990 else {
991 failf(data, "Authentication cancelled");
992 result = CURLE_LOGIN_DENIED;
993 }
994 break;
995 default:
996 break;
997 }
998
999 return result;
1000 }
1001
1002 /* For LOGIN responses */
imap_state_login_resp(struct connectdata * conn,int imapcode,imapstate instate)1003 static CURLcode imap_state_login_resp(struct connectdata *conn,
1004 int imapcode,
1005 imapstate instate)
1006 {
1007 CURLcode result = CURLE_OK;
1008 struct Curl_easy *data = conn->data;
1009
1010 (void)instate; /* no use for this yet */
1011
1012 if(imapcode != 'O') {
1013 failf(data, "Access denied. %c", imapcode);
1014 result = CURLE_LOGIN_DENIED;
1015 }
1016 else
1017 /* End of connect phase */
1018 state(conn, IMAP_STOP);
1019
1020 return result;
1021 }
1022
1023 /* For LIST and SEARCH responses */
imap_state_listsearch_resp(struct connectdata * conn,int imapcode,imapstate instate)1024 static CURLcode imap_state_listsearch_resp(struct connectdata *conn,
1025 int imapcode,
1026 imapstate instate)
1027 {
1028 CURLcode result = CURLE_OK;
1029 char *line = conn->data->state.buffer;
1030 size_t len = strlen(line);
1031
1032 (void)instate; /* No use for this yet */
1033
1034 if(imapcode == '*') {
1035 /* Temporarily add the LF character back and send as body to the client */
1036 line[len] = '\n';
1037 result = Curl_client_write(conn, CLIENTWRITE_BODY, line, len + 1);
1038 line[len] = '\0';
1039 }
1040 else if(imapcode != 'O')
1041 result = CURLE_QUOTE_ERROR; /* TODO: Fix error code */
1042 else
1043 /* End of DO phase */
1044 state(conn, IMAP_STOP);
1045
1046 return result;
1047 }
1048
1049 /* For SELECT responses */
imap_state_select_resp(struct connectdata * conn,int imapcode,imapstate instate)1050 static CURLcode imap_state_select_resp(struct connectdata *conn, int imapcode,
1051 imapstate instate)
1052 {
1053 CURLcode result = CURLE_OK;
1054 struct Curl_easy *data = conn->data;
1055 struct IMAP *imap = conn->data->req.protop;
1056 struct imap_conn *imapc = &conn->proto.imapc;
1057 const char *line = data->state.buffer;
1058 char tmp[20];
1059
1060 (void)instate; /* no use for this yet */
1061
1062 if(imapcode == '*') {
1063 /* See if this is an UIDVALIDITY response */
1064 if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) {
1065 Curl_safefree(imapc->mailbox_uidvalidity);
1066 imapc->mailbox_uidvalidity = strdup(tmp);
1067 }
1068 }
1069 else if(imapcode == 'O') {
1070 /* Check if the UIDVALIDITY has been specified and matches */
1071 if(imap->uidvalidity && imapc->mailbox_uidvalidity &&
1072 strcmp(imap->uidvalidity, imapc->mailbox_uidvalidity)) {
1073 failf(conn->data, "Mailbox UIDVALIDITY has changed");
1074 result = CURLE_REMOTE_FILE_NOT_FOUND;
1075 }
1076 else {
1077 /* Note the currently opened mailbox on this connection */
1078 imapc->mailbox = strdup(imap->mailbox);
1079
1080 if(imap->custom)
1081 result = imap_perform_list(conn);
1082 else if(imap->query)
1083 result = imap_perform_search(conn);
1084 else
1085 result = imap_perform_fetch(conn);
1086 }
1087 }
1088 else {
1089 failf(data, "Select failed");
1090 result = CURLE_LOGIN_DENIED;
1091 }
1092
1093 return result;
1094 }
1095
1096 /* For the (first line of the) FETCH responses */
imap_state_fetch_resp(struct connectdata * conn,int imapcode,imapstate instate)1097 static CURLcode imap_state_fetch_resp(struct connectdata *conn, int imapcode,
1098 imapstate instate)
1099 {
1100 CURLcode result = CURLE_OK;
1101 struct Curl_easy *data = conn->data;
1102 struct imap_conn *imapc = &conn->proto.imapc;
1103 struct pingpong *pp = &imapc->pp;
1104 const char *ptr = data->state.buffer;
1105 bool parsed = FALSE;
1106 curl_off_t size = 0;
1107
1108 (void)instate; /* no use for this yet */
1109
1110 if(imapcode != '*') {
1111 Curl_pgrsSetDownloadSize(data, -1);
1112 state(conn, IMAP_STOP);
1113 return CURLE_REMOTE_FILE_NOT_FOUND; /* TODO: Fix error code */
1114 }
1115
1116 /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse
1117 the continuation data contained within the curly brackets */
1118 while(*ptr && (*ptr != '{'))
1119 ptr++;
1120
1121 if(*ptr == '{') {
1122 char *endptr;
1123 size = curlx_strtoofft(ptr + 1, &endptr, 10);
1124 if(endptr - ptr > 1 && endptr[0] == '}' &&
1125 endptr[1] == '\r' && endptr[2] == '\0')
1126 parsed = TRUE;
1127 }
1128
1129 if(parsed) {
1130 infof(data, "Found %" CURL_FORMAT_CURL_OFF_TU " bytes to download\n",
1131 size);
1132 Curl_pgrsSetDownloadSize(data, size);
1133
1134 if(pp->cache) {
1135 /* At this point there is a bunch of data in the header "cache" that is
1136 actually body content, send it as body and then skip it. Do note
1137 that there may even be additional "headers" after the body. */
1138 size_t chunk = pp->cache_size;
1139
1140 if(chunk > (size_t)size)
1141 /* The conversion from curl_off_t to size_t is always fine here */
1142 chunk = (size_t)size;
1143
1144 result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk);
1145 if(result)
1146 return result;
1147
1148 data->req.bytecount += chunk;
1149
1150 infof(data, "Written %" CURL_FORMAT_CURL_OFF_TU
1151 " bytes, %" CURL_FORMAT_CURL_OFF_TU
1152 " bytes are left for transfer\n", (curl_off_t)chunk,
1153 size - chunk);
1154
1155 /* Have we used the entire cache or just part of it?*/
1156 if(pp->cache_size > chunk) {
1157 /* Only part of it so shrink the cache to fit the trailing data */
1158 memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk);
1159 pp->cache_size -= chunk;
1160 }
1161 else {
1162 /* Free the cache */
1163 Curl_safefree(pp->cache);
1164
1165 /* Reset the cache size */
1166 pp->cache_size = 0;
1167 }
1168 }
1169
1170 if(data->req.bytecount == size)
1171 /* The entire data is already transferred! */
1172 Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
1173 else {
1174 /* IMAP download */
1175 data->req.maxdownload = size;
1176 Curl_setup_transfer(conn, FIRSTSOCKET, size, FALSE, NULL, -1, NULL);
1177 }
1178 }
1179 else {
1180 /* We don't know how to parse this line */
1181 failf(pp->conn->data, "Failed to parse FETCH response.");
1182 result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */
1183 }
1184
1185 /* End of DO phase */
1186 state(conn, IMAP_STOP);
1187
1188 return result;
1189 }
1190
1191 /* For final FETCH responses performed after the download */
imap_state_fetch_final_resp(struct connectdata * conn,int imapcode,imapstate instate)1192 static CURLcode imap_state_fetch_final_resp(struct connectdata *conn,
1193 int imapcode,
1194 imapstate instate)
1195 {
1196 CURLcode result = CURLE_OK;
1197
1198 (void)instate; /* No use for this yet */
1199
1200 if(imapcode != 'O')
1201 result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: Fix error code */
1202 else
1203 /* End of DONE phase */
1204 state(conn, IMAP_STOP);
1205
1206 return result;
1207 }
1208
1209 /* For APPEND responses */
imap_state_append_resp(struct connectdata * conn,int imapcode,imapstate instate)1210 static CURLcode imap_state_append_resp(struct connectdata *conn, int imapcode,
1211 imapstate instate)
1212 {
1213 CURLcode result = CURLE_OK;
1214 struct Curl_easy *data = conn->data;
1215
1216 (void)instate; /* No use for this yet */
1217
1218 if(imapcode != '+') {
1219 result = CURLE_UPLOAD_FAILED;
1220 }
1221 else {
1222 /* Set the progress upload size */
1223 Curl_pgrsSetUploadSize(data, data->state.infilesize);
1224
1225 /* IMAP upload */
1226 Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL);
1227
1228 /* End of DO phase */
1229 state(conn, IMAP_STOP);
1230 }
1231
1232 return result;
1233 }
1234
1235 /* For final APPEND responses performed after the upload */
imap_state_append_final_resp(struct connectdata * conn,int imapcode,imapstate instate)1236 static CURLcode imap_state_append_final_resp(struct connectdata *conn,
1237 int imapcode,
1238 imapstate instate)
1239 {
1240 CURLcode result = CURLE_OK;
1241
1242 (void)instate; /* No use for this yet */
1243
1244 if(imapcode != 'O')
1245 result = CURLE_UPLOAD_FAILED;
1246 else
1247 /* End of DONE phase */
1248 state(conn, IMAP_STOP);
1249
1250 return result;
1251 }
1252
imap_statemach_act(struct connectdata * conn)1253 static CURLcode imap_statemach_act(struct connectdata *conn)
1254 {
1255 CURLcode result = CURLE_OK;
1256 curl_socket_t sock = conn->sock[FIRSTSOCKET];
1257 int imapcode;
1258 struct imap_conn *imapc = &conn->proto.imapc;
1259 struct pingpong *pp = &imapc->pp;
1260 size_t nread = 0;
1261
1262 /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */
1263 if(imapc->state == IMAP_UPGRADETLS)
1264 return imap_perform_upgrade_tls(conn);
1265
1266 /* Flush any data that needs to be sent */
1267 if(pp->sendleft)
1268 return Curl_pp_flushsend(pp);
1269
1270 do {
1271 /* Read the response from the server */
1272 result = Curl_pp_readresp(sock, pp, &imapcode, &nread);
1273 if(result)
1274 return result;
1275
1276 /* Was there an error parsing the response line? */
1277 if(imapcode == -1)
1278 return CURLE_FTP_WEIRD_SERVER_REPLY;
1279
1280 if(!imapcode)
1281 break;
1282
1283 /* We have now received a full IMAP server response */
1284 switch(imapc->state) {
1285 case IMAP_SERVERGREET:
1286 result = imap_state_servergreet_resp(conn, imapcode, imapc->state);
1287 break;
1288
1289 case IMAP_CAPABILITY:
1290 result = imap_state_capability_resp(conn, imapcode, imapc->state);
1291 break;
1292
1293 case IMAP_STARTTLS:
1294 result = imap_state_starttls_resp(conn, imapcode, imapc->state);
1295 break;
1296
1297 case IMAP_AUTHENTICATE:
1298 result = imap_state_auth_resp(conn, imapcode, imapc->state);
1299 break;
1300
1301 case IMAP_LOGIN:
1302 result = imap_state_login_resp(conn, imapcode, imapc->state);
1303 break;
1304
1305 case IMAP_LIST:
1306 result = imap_state_listsearch_resp(conn, imapcode, imapc->state);
1307 break;
1308
1309 case IMAP_SELECT:
1310 result = imap_state_select_resp(conn, imapcode, imapc->state);
1311 break;
1312
1313 case IMAP_FETCH:
1314 result = imap_state_fetch_resp(conn, imapcode, imapc->state);
1315 break;
1316
1317 case IMAP_FETCH_FINAL:
1318 result = imap_state_fetch_final_resp(conn, imapcode, imapc->state);
1319 break;
1320
1321 case IMAP_APPEND:
1322 result = imap_state_append_resp(conn, imapcode, imapc->state);
1323 break;
1324
1325 case IMAP_APPEND_FINAL:
1326 result = imap_state_append_final_resp(conn, imapcode, imapc->state);
1327 break;
1328
1329 case IMAP_SEARCH:
1330 result = imap_state_listsearch_resp(conn, imapcode, imapc->state);
1331 break;
1332
1333 case IMAP_LOGOUT:
1334 /* fallthrough, just stop! */
1335 default:
1336 /* internal error */
1337 state(conn, IMAP_STOP);
1338 break;
1339 }
1340 } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp));
1341
1342 return result;
1343 }
1344
1345 /* Called repeatedly until done from multi.c */
imap_multi_statemach(struct connectdata * conn,bool * done)1346 static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done)
1347 {
1348 CURLcode result = CURLE_OK;
1349 struct imap_conn *imapc = &conn->proto.imapc;
1350
1351 if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
1352 result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone);
1353 if(result || !imapc->ssldone)
1354 return result;
1355 }
1356
1357 result = Curl_pp_statemach(&imapc->pp, FALSE);
1358 *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE;
1359
1360 return result;
1361 }
1362
imap_block_statemach(struct connectdata * conn)1363 static CURLcode imap_block_statemach(struct connectdata *conn)
1364 {
1365 CURLcode result = CURLE_OK;
1366 struct imap_conn *imapc = &conn->proto.imapc;
1367
1368 while(imapc->state != IMAP_STOP && !result)
1369 result = Curl_pp_statemach(&imapc->pp, TRUE);
1370
1371 return result;
1372 }
1373
1374 /* Allocate and initialize the struct IMAP for the current Curl_easy if
1375 required */
imap_init(struct connectdata * conn)1376 static CURLcode imap_init(struct connectdata *conn)
1377 {
1378 CURLcode result = CURLE_OK;
1379 struct Curl_easy *data = conn->data;
1380 struct IMAP *imap;
1381
1382 imap = data->req.protop = calloc(sizeof(struct IMAP), 1);
1383 if(!imap)
1384 result = CURLE_OUT_OF_MEMORY;
1385
1386 return result;
1387 }
1388
1389 /* For the IMAP "protocol connect" and "doing" phases only */
imap_getsock(struct connectdata * conn,curl_socket_t * socks,int numsocks)1390 static int imap_getsock(struct connectdata *conn, curl_socket_t *socks,
1391 int numsocks)
1392 {
1393 return Curl_pp_getsock(&conn->proto.imapc.pp, socks, numsocks);
1394 }
1395
1396 /***********************************************************************
1397 *
1398 * imap_connect()
1399 *
1400 * This function should do everything that is to be considered a part of the
1401 * connection phase.
1402 *
1403 * The variable 'done' points to will be TRUE if the protocol-layer connect
1404 * phase is done when this function returns, or FALSE if not.
1405 */
imap_connect(struct connectdata * conn,bool * done)1406 static CURLcode imap_connect(struct connectdata *conn, bool *done)
1407 {
1408 CURLcode result = CURLE_OK;
1409 struct imap_conn *imapc = &conn->proto.imapc;
1410 struct pingpong *pp = &imapc->pp;
1411
1412 *done = FALSE; /* default to not done yet */
1413
1414 /* We always support persistent connections in IMAP */
1415 connkeep(conn, "IMAP default");
1416
1417 /* Set the default response time-out */
1418 pp->response_time = RESP_TIMEOUT;
1419 pp->statemach_act = imap_statemach_act;
1420 pp->endofresp = imap_endofresp;
1421 pp->conn = conn;
1422
1423 /* Set the default preferred authentication type and mechanism */
1424 imapc->preftype = IMAP_TYPE_ANY;
1425 Curl_sasl_init(&imapc->sasl, &saslimap);
1426
1427 /* Initialise the pingpong layer */
1428 Curl_pp_init(pp);
1429
1430 /* Parse the URL options */
1431 result = imap_parse_url_options(conn);
1432 if(result)
1433 return result;
1434
1435 /* Start off waiting for the server greeting response */
1436 state(conn, IMAP_SERVERGREET);
1437
1438 /* Start off with an response id of '*' */
1439 strcpy(imapc->resptag, "*");
1440
1441 result = imap_multi_statemach(conn, done);
1442
1443 return result;
1444 }
1445
1446 /***********************************************************************
1447 *
1448 * imap_done()
1449 *
1450 * The DONE function. This does what needs to be done after a single DO has
1451 * performed.
1452 *
1453 * Input argument is already checked for validity.
1454 */
imap_done(struct connectdata * conn,CURLcode status,bool premature)1455 static CURLcode imap_done(struct connectdata *conn, CURLcode status,
1456 bool premature)
1457 {
1458 CURLcode result = CURLE_OK;
1459 struct Curl_easy *data = conn->data;
1460 struct IMAP *imap = data->req.protop;
1461
1462 (void)premature;
1463
1464 if(!imap)
1465 return CURLE_OK;
1466
1467 if(status) {
1468 connclose(conn, "IMAP done with bad status"); /* marked for closure */
1469 result = status; /* use the already set error code */
1470 }
1471 else if(!data->set.connect_only && !imap->custom &&
1472 (imap->uid || data->set.upload)) {
1473 /* Handle responses after FETCH or APPEND transfer has finished */
1474 if(!data->set.upload)
1475 state(conn, IMAP_FETCH_FINAL);
1476 else {
1477 /* End the APPEND command first by sending an empty line */
1478 result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", "");
1479 if(!result)
1480 state(conn, IMAP_APPEND_FINAL);
1481 }
1482
1483 /* Run the state-machine
1484
1485 TODO: when the multi interface is used, this _really_ should be using
1486 the imap_multi_statemach function but we have no general support for
1487 non-blocking DONE operations!
1488 */
1489 if(!result)
1490 result = imap_block_statemach(conn);
1491 }
1492
1493 /* Cleanup our per-request based variables */
1494 Curl_safefree(imap->mailbox);
1495 Curl_safefree(imap->uidvalidity);
1496 Curl_safefree(imap->uid);
1497 Curl_safefree(imap->section);
1498 Curl_safefree(imap->partial);
1499 Curl_safefree(imap->query);
1500 Curl_safefree(imap->custom);
1501 Curl_safefree(imap->custom_params);
1502
1503 /* Clear the transfer mode for the next request */
1504 imap->transfer = FTPTRANSFER_BODY;
1505
1506 return result;
1507 }
1508
1509 /***********************************************************************
1510 *
1511 * imap_perform()
1512 *
1513 * This is the actual DO function for IMAP. Fetch or append a message, or do
1514 * other things according to the options previously setup.
1515 */
imap_perform(struct connectdata * conn,bool * connected,bool * dophase_done)1516 static CURLcode imap_perform(struct connectdata *conn, bool *connected,
1517 bool *dophase_done)
1518 {
1519 /* This is IMAP and no proxy */
1520 CURLcode result = CURLE_OK;
1521 struct Curl_easy *data = conn->data;
1522 struct IMAP *imap = data->req.protop;
1523 struct imap_conn *imapc = &conn->proto.imapc;
1524 bool selected = FALSE;
1525
1526 DEBUGF(infof(conn->data, "DO phase starts\n"));
1527
1528 if(conn->data->set.opt_no_body) {
1529 /* Requested no body means no transfer */
1530 imap->transfer = FTPTRANSFER_INFO;
1531 }
1532
1533 *dophase_done = FALSE; /* not done yet */
1534
1535 /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
1536 has already been selected on this connection */
1537 if(imap->mailbox && imapc->mailbox &&
1538 !strcmp(imap->mailbox, imapc->mailbox) &&
1539 (!imap->uidvalidity || !imapc->mailbox_uidvalidity ||
1540 !strcmp(imap->uidvalidity, imapc->mailbox_uidvalidity)))
1541 selected = TRUE;
1542
1543 /* Start the first command in the DO phase */
1544 if(conn->data->set.upload)
1545 /* APPEND can be executed directly */
1546 result = imap_perform_append(conn);
1547 else if(imap->custom && (selected || !imap->mailbox))
1548 /* Custom command using the same mailbox or no mailbox */
1549 result = imap_perform_list(conn);
1550 else if(!imap->custom && selected && imap->uid)
1551 /* FETCH from the same mailbox */
1552 result = imap_perform_fetch(conn);
1553 else if(!imap->custom && selected && imap->query)
1554 /* SEARCH the current mailbox */
1555 result = imap_perform_search(conn);
1556 else if(imap->mailbox && !selected &&
1557 (imap->custom || imap->uid || imap->query))
1558 /* SELECT the mailbox */
1559 result = imap_perform_select(conn);
1560 else
1561 /* LIST */
1562 result = imap_perform_list(conn);
1563
1564 if(result)
1565 return result;
1566
1567 /* Run the state-machine */
1568 result = imap_multi_statemach(conn, dophase_done);
1569
1570 *connected = conn->bits.tcpconnect[FIRSTSOCKET];
1571
1572 if(*dophase_done)
1573 DEBUGF(infof(conn->data, "DO phase is complete\n"));
1574
1575 return result;
1576 }
1577
1578 /***********************************************************************
1579 *
1580 * imap_do()
1581 *
1582 * This function is registered as 'curl_do' function. It decodes the path
1583 * parts etc as a wrapper to the actual DO function (imap_perform).
1584 *
1585 * The input argument is already checked for validity.
1586 */
imap_do(struct connectdata * conn,bool * done)1587 static CURLcode imap_do(struct connectdata *conn, bool *done)
1588 {
1589 CURLcode result = CURLE_OK;
1590
1591 *done = FALSE; /* default to false */
1592
1593 /* Parse the URL path */
1594 result = imap_parse_url_path(conn);
1595 if(result)
1596 return result;
1597
1598 /* Parse the custom request */
1599 result = imap_parse_custom_request(conn);
1600 if(result)
1601 return result;
1602
1603 result = imap_regular_transfer(conn, done);
1604
1605 return result;
1606 }
1607
1608 /***********************************************************************
1609 *
1610 * imap_disconnect()
1611 *
1612 * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
1613 * resources. BLOCKING.
1614 */
imap_disconnect(struct connectdata * conn,bool dead_connection)1615 static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection)
1616 {
1617 struct imap_conn *imapc = &conn->proto.imapc;
1618
1619 /* We cannot send quit unconditionally. If this connection is stale or
1620 bad in any way, sending quit and waiting around here will make the
1621 disconnect wait in vain and cause more problems than we need to. */
1622
1623 /* The IMAP session may or may not have been allocated/setup at this
1624 point! */
1625 if(!dead_connection && imapc->pp.conn && imapc->pp.conn->bits.protoconnstart)
1626 if(!imap_perform_logout(conn))
1627 (void)imap_block_statemach(conn); /* ignore errors on LOGOUT */
1628
1629 /* Disconnect from the server */
1630 Curl_pp_disconnect(&imapc->pp);
1631
1632 /* Cleanup the SASL module */
1633 Curl_sasl_cleanup(conn, imapc->sasl.authused);
1634
1635 /* Cleanup our connection based variables */
1636 Curl_safefree(imapc->mailbox);
1637 Curl_safefree(imapc->mailbox_uidvalidity);
1638
1639 return CURLE_OK;
1640 }
1641
1642 /* Call this when the DO phase has completed */
imap_dophase_done(struct connectdata * conn,bool connected)1643 static CURLcode imap_dophase_done(struct connectdata *conn, bool connected)
1644 {
1645 struct IMAP *imap = conn->data->req.protop;
1646
1647 (void)connected;
1648
1649 if(imap->transfer != FTPTRANSFER_BODY)
1650 /* no data to transfer */
1651 Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
1652
1653 return CURLE_OK;
1654 }
1655
1656 /* Called from multi.c while DOing */
imap_doing(struct connectdata * conn,bool * dophase_done)1657 static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done)
1658 {
1659 CURLcode result = imap_multi_statemach(conn, dophase_done);
1660
1661 if(result)
1662 DEBUGF(infof(conn->data, "DO phase failed\n"));
1663 else if(*dophase_done) {
1664 result = imap_dophase_done(conn, FALSE /* not connected */);
1665
1666 DEBUGF(infof(conn->data, "DO phase is complete\n"));
1667 }
1668
1669 return result;
1670 }
1671
1672 /***********************************************************************
1673 *
1674 * imap_regular_transfer()
1675 *
1676 * The input argument is already checked for validity.
1677 *
1678 * Performs all commands done before a regular transfer between a local and a
1679 * remote host.
1680 */
imap_regular_transfer(struct connectdata * conn,bool * dophase_done)1681 static CURLcode imap_regular_transfer(struct connectdata *conn,
1682 bool *dophase_done)
1683 {
1684 CURLcode result = CURLE_OK;
1685 bool connected = FALSE;
1686 struct Curl_easy *data = conn->data;
1687
1688 /* Make sure size is unknown at this point */
1689 data->req.size = -1;
1690
1691 /* Set the progress data */
1692 Curl_pgrsSetUploadCounter(data, 0);
1693 Curl_pgrsSetDownloadCounter(data, 0);
1694 Curl_pgrsSetUploadSize(data, -1);
1695 Curl_pgrsSetDownloadSize(data, -1);
1696
1697 /* Carry out the perform */
1698 result = imap_perform(conn, &connected, dophase_done);
1699
1700 /* Perform post DO phase operations if necessary */
1701 if(!result && *dophase_done)
1702 result = imap_dophase_done(conn, connected);
1703
1704 return result;
1705 }
1706
imap_setup_connection(struct connectdata * conn)1707 static CURLcode imap_setup_connection(struct connectdata *conn)
1708 {
1709 struct Curl_easy *data = conn->data;
1710
1711 /* Initialise the IMAP layer */
1712 CURLcode result = imap_init(conn);
1713 if(result)
1714 return result;
1715
1716 /* Clear the TLS upgraded flag */
1717 conn->tls_upgraded = FALSE;
1718
1719 /* Set up the proxy if necessary */
1720 if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) {
1721 /* Unless we have asked to tunnel IMAP operations through the proxy, we
1722 switch and use HTTP operations only */
1723 #ifndef CURL_DISABLE_HTTP
1724 if(conn->handler == &Curl_handler_imap)
1725 conn->handler = &Curl_handler_imap_proxy;
1726 else {
1727 #ifdef USE_SSL
1728 conn->handler = &Curl_handler_imaps_proxy;
1729 #else
1730 failf(data, "IMAPS not supported!");
1731 return CURLE_UNSUPPORTED_PROTOCOL;
1732 #endif
1733 }
1734
1735 /* set it up as an HTTP connection instead */
1736 return conn->handler->setup_connection(conn);
1737 #else
1738 failf(data, "IMAP over http proxy requires HTTP support built-in!");
1739 return CURLE_UNSUPPORTED_PROTOCOL;
1740 #endif
1741 }
1742
1743 data->state.path++; /* don't include the initial slash */
1744
1745 return CURLE_OK;
1746 }
1747
1748 /***********************************************************************
1749 *
1750 * imap_sendf()
1751 *
1752 * Sends the formated string as an IMAP command to the server.
1753 *
1754 * Designed to never block.
1755 */
imap_sendf(struct connectdata * conn,const char * fmt,...)1756 static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...)
1757 {
1758 CURLcode result = CURLE_OK;
1759 struct imap_conn *imapc = &conn->proto.imapc;
1760 char *taggedfmt;
1761 va_list ap;
1762
1763 DEBUGASSERT(fmt);
1764
1765 /* Calculate the next command ID wrapping at 3 digits */
1766 imapc->cmdid = (imapc->cmdid + 1) % 1000;
1767
1768 /* Calculate the tag based on the connection ID and command ID */
1769 snprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
1770 'A' + curlx_sltosi(conn->connection_id % 26), imapc->cmdid);
1771
1772 /* Prefix the format with the tag */
1773 taggedfmt = aprintf("%s %s", imapc->resptag, fmt);
1774 if(!taggedfmt)
1775 return CURLE_OUT_OF_MEMORY;
1776
1777 /* Send the data with the tag */
1778 va_start(ap, fmt);
1779 result = Curl_pp_vsendf(&imapc->pp, taggedfmt, ap);
1780 va_end(ap);
1781
1782 free(taggedfmt);
1783
1784 return result;
1785 }
1786
1787 /***********************************************************************
1788 *
1789 * imap_atom()
1790 *
1791 * Checks the input string for characters that need escaping and returns an
1792 * atom ready for sending to the server.
1793 *
1794 * The returned string needs to be freed.
1795 *
1796 */
imap_atom(const char * str,bool escape_only)1797 static char *imap_atom(const char *str, bool escape_only)
1798 {
1799 /* !checksrc! disable PARENBRACE 1 */
1800 const char atom_specials[] = "(){ %*]";
1801 const char *p1;
1802 char *p2;
1803 size_t backsp_count = 0;
1804 size_t quote_count = 0;
1805 bool others_exists = FALSE;
1806 size_t newlen = 0;
1807 char *newstr = NULL;
1808
1809 if(!str)
1810 return NULL;
1811
1812 /* Look for "atom-specials", counting the backslash and quote characters as
1813 these will need escapping */
1814 p1 = str;
1815 while(*p1) {
1816 if(*p1 == '\\')
1817 backsp_count++;
1818 else if(*p1 == '"')
1819 quote_count++;
1820 else if(!escape_only) {
1821 const char *p3 = atom_specials;
1822
1823 while(*p3 && !others_exists) {
1824 if(*p1 == *p3)
1825 others_exists = TRUE;
1826
1827 p3++;
1828 }
1829 }
1830
1831 p1++;
1832 }
1833
1834 /* Does the input contain any "atom-special" characters? */
1835 if(!backsp_count && !quote_count && !others_exists)
1836 return strdup(str);
1837
1838 /* Calculate the new string length */
1839 newlen = strlen(str) + backsp_count + quote_count + (others_exists ? 2 : 0);
1840
1841 /* Allocate the new string */
1842 newstr = (char *) malloc((newlen + 1) * sizeof(char));
1843 if(!newstr)
1844 return NULL;
1845
1846 /* Surround the string in quotes if necessary */
1847 p2 = newstr;
1848 if(others_exists) {
1849 newstr[0] = '"';
1850 newstr[newlen - 1] = '"';
1851 p2++;
1852 }
1853
1854 /* Copy the string, escaping backslash and quote characters along the way */
1855 p1 = str;
1856 while(*p1) {
1857 if(*p1 == '\\' || *p1 == '"') {
1858 *p2 = '\\';
1859 p2++;
1860 }
1861
1862 *p2 = *p1;
1863
1864 p1++;
1865 p2++;
1866 }
1867
1868 /* Terminate the string */
1869 newstr[newlen] = '\0';
1870
1871 return newstr;
1872 }
1873
1874 /***********************************************************************
1875 *
1876 * imap_is_bchar()
1877 *
1878 * Portable test of whether the specified char is a "bchar" as defined in the
1879 * grammar of RFC-5092.
1880 */
imap_is_bchar(char ch)1881 static bool imap_is_bchar(char ch)
1882 {
1883 switch(ch) {
1884 /* bchar */
1885 case ':': case '@': case '/':
1886 /* bchar -> achar */
1887 case '&': case '=':
1888 /* bchar -> achar -> uchar -> unreserved */
1889 case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1890 case '7': case '8': case '9':
1891 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1892 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1893 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1894 case 'V': case 'W': case 'X': case 'Y': case 'Z':
1895 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1896 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1897 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1898 case 'v': case 'w': case 'x': case 'y': case 'z':
1899 case '-': case '.': case '_': case '~':
1900 /* bchar -> achar -> uchar -> sub-delims-sh */
1901 case '!': case '$': case '\'': case '(': case ')': case '*':
1902 case '+': case ',':
1903 /* bchar -> achar -> uchar -> pct-encoded */
1904 case '%': /* HEXDIG chars are already included above */
1905 return true;
1906
1907 default:
1908 return false;
1909 }
1910 }
1911
1912 /***********************************************************************
1913 *
1914 * imap_parse_url_options()
1915 *
1916 * Parse the URL login options.
1917 */
imap_parse_url_options(struct connectdata * conn)1918 static CURLcode imap_parse_url_options(struct connectdata *conn)
1919 {
1920 CURLcode result = CURLE_OK;
1921 struct imap_conn *imapc = &conn->proto.imapc;
1922 const char *ptr = conn->options;
1923
1924 imapc->sasl.resetprefs = TRUE;
1925
1926 while(!result && ptr && *ptr) {
1927 const char *key = ptr;
1928 const char *value;
1929
1930 while(*ptr && *ptr != '=')
1931 ptr++;
1932
1933 value = ptr + 1;
1934
1935 while(*ptr && *ptr != ';')
1936 ptr++;
1937
1938 if(strnequal(key, "AUTH=", 5))
1939 result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
1940 value, ptr - value);
1941 else
1942 result = CURLE_URL_MALFORMAT;
1943
1944 if(*ptr == ';')
1945 ptr++;
1946 }
1947
1948 switch(imapc->sasl.prefmech) {
1949 case SASL_AUTH_NONE:
1950 imapc->preftype = IMAP_TYPE_NONE;
1951 break;
1952 case SASL_AUTH_DEFAULT:
1953 imapc->preftype = IMAP_TYPE_ANY;
1954 break;
1955 default:
1956 imapc->preftype = IMAP_TYPE_SASL;
1957 break;
1958 }
1959
1960 return result;
1961 }
1962
1963 /***********************************************************************
1964 *
1965 * imap_parse_url_path()
1966 *
1967 * Parse the URL path into separate path components.
1968 *
1969 */
imap_parse_url_path(struct connectdata * conn)1970 static CURLcode imap_parse_url_path(struct connectdata *conn)
1971 {
1972 /* The imap struct is already initialised in imap_connect() */
1973 CURLcode result = CURLE_OK;
1974 struct Curl_easy *data = conn->data;
1975 struct IMAP *imap = data->req.protop;
1976 const char *begin = data->state.path;
1977 const char *ptr = begin;
1978
1979 /* See how much of the URL is a valid path and decode it */
1980 while(imap_is_bchar(*ptr))
1981 ptr++;
1982
1983 if(ptr != begin) {
1984 /* Remove the trailing slash if present */
1985 const char *end = ptr;
1986 if(end > begin && end[-1] == '/')
1987 end--;
1988
1989 result = Curl_urldecode(data, begin, end - begin, &imap->mailbox, NULL,
1990 TRUE);
1991 if(result)
1992 return result;
1993 }
1994 else
1995 imap->mailbox = NULL;
1996
1997 /* There can be any number of parameters in the form ";NAME=VALUE" */
1998 while(*ptr == ';') {
1999 char *name;
2000 char *value;
2001 size_t valuelen;
2002
2003 /* Find the length of the name parameter */
2004 begin = ++ptr;
2005 while(*ptr && *ptr != '=')
2006 ptr++;
2007
2008 if(!*ptr)
2009 return CURLE_URL_MALFORMAT;
2010
2011 /* Decode the name parameter */
2012 result = Curl_urldecode(data, begin, ptr - begin, &name, NULL, TRUE);
2013 if(result)
2014 return result;
2015
2016 /* Find the length of the value parameter */
2017 begin = ++ptr;
2018 while(imap_is_bchar(*ptr))
2019 ptr++;
2020
2021 /* Decode the value parameter */
2022 result = Curl_urldecode(data, begin, ptr - begin, &value, &valuelen, TRUE);
2023 if(result) {
2024 free(name);
2025 return result;
2026 }
2027
2028 DEBUGF(infof(conn->data, "IMAP URL parameter '%s' = '%s'\n", name, value));
2029
2030 /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION and
2031 PARTIAL) stripping of the trailing slash character if it is present.
2032
2033 Note: Unknown parameters trigger a URL_MALFORMAT error. */
2034 if(Curl_raw_equal(name, "UIDVALIDITY") && !imap->uidvalidity) {
2035 if(valuelen > 0 && value[valuelen - 1] == '/')
2036 value[valuelen - 1] = '\0';
2037
2038 imap->uidvalidity = value;
2039 value = NULL;
2040 }
2041 else if(Curl_raw_equal(name, "UID") && !imap->uid) {
2042 if(valuelen > 0 && value[valuelen - 1] == '/')
2043 value[valuelen - 1] = '\0';
2044
2045 imap->uid = value;
2046 value = NULL;
2047 }
2048 else if(Curl_raw_equal(name, "SECTION") && !imap->section) {
2049 if(valuelen > 0 && value[valuelen - 1] == '/')
2050 value[valuelen - 1] = '\0';
2051
2052 imap->section = value;
2053 value = NULL;
2054 }
2055 else if(Curl_raw_equal(name, "PARTIAL") && !imap->partial) {
2056 if(valuelen > 0 && value[valuelen - 1] == '/')
2057 value[valuelen - 1] = '\0';
2058
2059 imap->partial = value;
2060 value = NULL;
2061 }
2062 else {
2063 free(name);
2064 free(value);
2065
2066 return CURLE_URL_MALFORMAT;
2067 }
2068
2069 free(name);
2070 free(value);
2071 }
2072
2073 /* Does the URL contain a query parameter? Only valid when we have a mailbox
2074 and no UID as per RFC-5092 */
2075 if(imap->mailbox && !imap->uid && *ptr == '?') {
2076 /* Find the length of the query parameter */
2077 begin = ++ptr;
2078 while(imap_is_bchar(*ptr))
2079 ptr++;
2080
2081 /* Decode the query parameter */
2082 result = Curl_urldecode(data, begin, ptr - begin, &imap->query, NULL,
2083 TRUE);
2084 if(result)
2085 return result;
2086 }
2087
2088 /* Any extra stuff at the end of the URL is an error */
2089 if(*ptr)
2090 return CURLE_URL_MALFORMAT;
2091
2092 return CURLE_OK;
2093 }
2094
2095 /***********************************************************************
2096 *
2097 * imap_parse_custom_request()
2098 *
2099 * Parse the custom request.
2100 */
imap_parse_custom_request(struct connectdata * conn)2101 static CURLcode imap_parse_custom_request(struct connectdata *conn)
2102 {
2103 CURLcode result = CURLE_OK;
2104 struct Curl_easy *data = conn->data;
2105 struct IMAP *imap = data->req.protop;
2106 const char *custom = data->set.str[STRING_CUSTOMREQUEST];
2107
2108 if(custom) {
2109 /* URL decode the custom request */
2110 result = Curl_urldecode(data, custom, 0, &imap->custom, NULL, TRUE);
2111
2112 /* Extract the parameters if specified */
2113 if(!result) {
2114 const char *params = imap->custom;
2115
2116 while(*params && *params != ' ')
2117 params++;
2118
2119 if(*params) {
2120 imap->custom_params = strdup(params);
2121 imap->custom[params - imap->custom] = '\0';
2122
2123 if(!imap->custom_params)
2124 result = CURLE_OUT_OF_MEMORY;
2125 }
2126 }
2127 }
2128
2129 return result;
2130 }
2131
2132 #endif /* CURL_DISABLE_IMAP */
2133