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