2c1b06c07ff805170f58432fdf403ae764692d70
[platform/upstream/cmake.git] / Utilities / cmcurl / lib / pop3.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2022, 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  * RFC1734 POP3 Authentication
22  * RFC1939 POP3 protocol
23  * RFC2195 CRAM-MD5 authentication
24  * RFC2384 POP URL Scheme
25  * RFC2449 POP3 Extension Mechanism
26  * RFC2595 Using TLS with IMAP, POP3 and ACAP
27  * RFC2831 DIGEST-MD5 authentication
28  * RFC4422 Simple Authentication and Security Layer (SASL)
29  * RFC4616 PLAIN authentication
30  * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism
31  * RFC5034 POP3 SASL Authentication Mechanism
32  * RFC6749 OAuth 2.0 Authorization Framework
33  * RFC8314 Use of TLS for Email Submission and Access
34  * Draft   LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
35  *
36  ***************************************************************************/
37
38 #include "curl_setup.h"
39
40 #ifndef CURL_DISABLE_POP3
41
42 #ifdef HAVE_NETINET_IN_H
43 #include <netinet/in.h>
44 #endif
45 #ifdef HAVE_ARPA_INET_H
46 #include <arpa/inet.h>
47 #endif
48 #ifdef HAVE_UTSNAME_H
49 #include <sys/utsname.h>
50 #endif
51 #ifdef HAVE_NETDB_H
52 #include <netdb.h>
53 #endif
54 #ifdef __VMS
55 #include <in.h>
56 #include <inet.h>
57 #endif
58
59 #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
60 #undef in_addr_t
61 #define in_addr_t unsigned long
62 #endif
63
64 #include <curl/curl.h>
65 #include "urldata.h"
66 #include "sendf.h"
67 #include "hostip.h"
68 #include "progress.h"
69 #include "transfer.h"
70 #include "escape.h"
71 #include "http.h" /* for HTTP proxy tunnel stuff */
72 #include "socks.h"
73 #include "pop3.h"
74 #include "strtoofft.h"
75 #include "strcase.h"
76 #include "vtls/vtls.h"
77 #include "connect.h"
78 #include "select.h"
79 #include "multiif.h"
80 #include "url.h"
81 #include "bufref.h"
82 #include "curl_sasl.h"
83 #include "curl_md5.h"
84 #include "warnless.h"
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 pop3_regular_transfer(struct Curl_easy *data, bool *done);
92 static CURLcode pop3_do(struct Curl_easy *data, bool *done);
93 static CURLcode pop3_done(struct Curl_easy *data, CURLcode status,
94                           bool premature);
95 static CURLcode pop3_connect(struct Curl_easy *data, bool *done);
96 static CURLcode pop3_disconnect(struct Curl_easy *data,
97                                 struct connectdata *conn, bool dead);
98 static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done);
99 static int pop3_getsock(struct Curl_easy *data,
100                         struct connectdata *conn, curl_socket_t *socks);
101 static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done);
102 static CURLcode pop3_setup_connection(struct Curl_easy *data,
103                                       struct connectdata *conn);
104 static CURLcode pop3_parse_url_options(struct connectdata *conn);
105 static CURLcode pop3_parse_url_path(struct Curl_easy *data);
106 static CURLcode pop3_parse_custom_request(struct Curl_easy *data);
107 static CURLcode pop3_perform_auth(struct Curl_easy *data, const char *mech,
108                                   const struct bufref *initresp);
109 static CURLcode pop3_continue_auth(struct Curl_easy *data, const char *mech,
110                                    const struct bufref *resp);
111 static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech);
112 static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out);
113
114 /*
115  * POP3 protocol handler.
116  */
117
118 const struct Curl_handler Curl_handler_pop3 = {
119   "POP3",                           /* scheme */
120   pop3_setup_connection,            /* setup_connection */
121   pop3_do,                          /* do_it */
122   pop3_done,                        /* done */
123   ZERO_NULL,                        /* do_more */
124   pop3_connect,                     /* connect_it */
125   pop3_multi_statemach,             /* connecting */
126   pop3_doing,                       /* doing */
127   pop3_getsock,                     /* proto_getsock */
128   pop3_getsock,                     /* doing_getsock */
129   ZERO_NULL,                        /* domore_getsock */
130   ZERO_NULL,                        /* perform_getsock */
131   pop3_disconnect,                  /* disconnect */
132   ZERO_NULL,                        /* readwrite */
133   ZERO_NULL,                        /* connection_check */
134   ZERO_NULL,                        /* attach connection */
135   PORT_POP3,                        /* defport */
136   CURLPROTO_POP3,                   /* protocol */
137   CURLPROTO_POP3,                   /* family */
138   PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
139   PROTOPT_URLOPTIONS
140 };
141
142 #ifdef USE_SSL
143 /*
144  * POP3S protocol handler.
145  */
146
147 const struct Curl_handler Curl_handler_pop3s = {
148   "POP3S",                          /* scheme */
149   pop3_setup_connection,            /* setup_connection */
150   pop3_do,                          /* do_it */
151   pop3_done,                        /* done */
152   ZERO_NULL,                        /* do_more */
153   pop3_connect,                     /* connect_it */
154   pop3_multi_statemach,             /* connecting */
155   pop3_doing,                       /* doing */
156   pop3_getsock,                     /* proto_getsock */
157   pop3_getsock,                     /* doing_getsock */
158   ZERO_NULL,                        /* domore_getsock */
159   ZERO_NULL,                        /* perform_getsock */
160   pop3_disconnect,                  /* disconnect */
161   ZERO_NULL,                        /* readwrite */
162   ZERO_NULL,                        /* connection_check */
163   ZERO_NULL,                        /* attach connection */
164   PORT_POP3S,                       /* defport */
165   CURLPROTO_POP3S,                  /* protocol */
166   CURLPROTO_POP3,                   /* family */
167   PROTOPT_CLOSEACTION | PROTOPT_SSL
168   | PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS /* flags */
169 };
170 #endif
171
172 /* SASL parameters for the pop3 protocol */
173 static const struct SASLproto saslpop3 = {
174   "pop",                /* The service name */
175   pop3_perform_auth,    /* Send authentication command */
176   pop3_continue_auth,   /* Send authentication continuation */
177   pop3_cancel_auth,     /* Send authentication cancellation */
178   pop3_get_message,     /* Get SASL response message */
179   255 - 8,              /* Max line len - strlen("AUTH ") - 1 space - crlf */
180   '*',                  /* Code received when continuation is expected */
181   '+',                  /* Code to receive upon authentication success */
182   SASL_AUTH_DEFAULT,    /* Default mechanisms */
183   SASL_FLAG_BASE64      /* Configuration flags */
184 };
185
186 #ifdef USE_SSL
187 static void pop3_to_pop3s(struct connectdata *conn)
188 {
189   /* Change the connection handler */
190   conn->handler = &Curl_handler_pop3s;
191
192   /* Set the connection's upgraded to TLS flag */
193   conn->bits.tls_upgraded = TRUE;
194 }
195 #else
196 #define pop3_to_pop3s(x) Curl_nop_stmt
197 #endif
198
199 /***********************************************************************
200  *
201  * pop3_endofresp()
202  *
203  * Checks for an ending POP3 status code at the start of the given string, but
204  * also detects the APOP timestamp from the server greeting and various
205  * capabilities from the CAPA response including the supported authentication
206  * types and allowed SASL mechanisms.
207  */
208 static bool pop3_endofresp(struct Curl_easy *data, struct connectdata *conn,
209                            char *line, size_t len, int *resp)
210 {
211   struct pop3_conn *pop3c = &conn->proto.pop3c;
212   (void)data;
213
214   /* Do we have an error response? */
215   if(len >= 4 && !memcmp("-ERR", line, 4)) {
216     *resp = '-';
217
218     return TRUE;
219   }
220
221   /* Are we processing CAPA command responses? */
222   if(pop3c->state == POP3_CAPA) {
223     /* Do we have the terminating line? */
224     if(len >= 1 && line[0] == '.')
225       /* Treat the response as a success */
226       *resp = '+';
227     else
228       /* Treat the response as an untagged continuation */
229       *resp = '*';
230
231     return TRUE;
232   }
233
234   /* Do we have a success response? */
235   if(len >= 3 && !memcmp("+OK", line, 3)) {
236     *resp = '+';
237
238     return TRUE;
239   }
240
241   /* Do we have a continuation response? */
242   if(len >= 1 && line[0] == '+') {
243     *resp = '*';
244
245     return TRUE;
246   }
247
248   return FALSE; /* Nothing for us */
249 }
250
251 /***********************************************************************
252  *
253  * pop3_get_message()
254  *
255  * Gets the authentication message from the response buffer.
256  */
257 static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out)
258 {
259   char *message = data->state.buffer;
260   size_t len = strlen(message);
261
262   if(len > 2) {
263     /* Find the start of the message */
264     len -= 2;
265     for(message += 2; *message == ' ' || *message == '\t'; message++, len--)
266       ;
267
268     /* Find the end of the message */
269     while(len--)
270       if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' &&
271          message[len] != '\t')
272         break;
273
274     /* Terminate the message */
275     message[++len] = '\0';
276     Curl_bufref_set(out, message, len, NULL);
277   }
278   else
279     /* junk input => zero length output */
280     Curl_bufref_set(out, "", 0, NULL);
281
282   return CURLE_OK;
283 }
284
285 /***********************************************************************
286  *
287  * state()
288  *
289  * This is the ONLY way to change POP3 state!
290  */
291 static void state(struct Curl_easy *data, pop3state newstate)
292 {
293   struct pop3_conn *pop3c = &data->conn->proto.pop3c;
294 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
295   /* for debug purposes */
296   static const char * const names[] = {
297     "STOP",
298     "SERVERGREET",
299     "CAPA",
300     "STARTTLS",
301     "UPGRADETLS",
302     "AUTH",
303     "APOP",
304     "USER",
305     "PASS",
306     "COMMAND",
307     "QUIT",
308     /* LAST */
309   };
310
311   if(pop3c->state != newstate)
312     infof(data, "POP3 %p state change from %s to %s",
313           (void *)pop3c, names[pop3c->state], names[newstate]);
314 #endif
315
316   pop3c->state = newstate;
317 }
318
319 /***********************************************************************
320  *
321  * pop3_perform_capa()
322  *
323  * Sends the CAPA command in order to obtain a list of server side supported
324  * capabilities.
325  */
326 static CURLcode pop3_perform_capa(struct Curl_easy *data,
327                                   struct connectdata *conn)
328 {
329   CURLcode result = CURLE_OK;
330   struct pop3_conn *pop3c = &conn->proto.pop3c;
331
332   pop3c->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */
333   pop3c->sasl.authused = SASL_AUTH_NONE;  /* Clear the auth. mechanism used */
334   pop3c->tls_supported = FALSE;           /* Clear the TLS capability */
335
336   /* Send the CAPA command */
337   result = Curl_pp_sendf(data, &pop3c->pp, "%s", "CAPA");
338
339   if(!result)
340     state(data, POP3_CAPA);
341
342   return result;
343 }
344
345 /***********************************************************************
346  *
347  * pop3_perform_starttls()
348  *
349  * Sends the STLS command to start the upgrade to TLS.
350  */
351 static CURLcode pop3_perform_starttls(struct Curl_easy *data,
352                                       struct connectdata *conn)
353 {
354   /* Send the STLS command */
355   CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "STLS");
356
357   if(!result)
358     state(data, POP3_STARTTLS);
359
360   return result;
361 }
362
363 /***********************************************************************
364  *
365  * pop3_perform_upgrade_tls()
366  *
367  * Performs the upgrade to TLS.
368  */
369 static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data,
370                                          struct connectdata *conn)
371 {
372   /* Start the SSL connection */
373   struct pop3_conn *pop3c = &conn->proto.pop3c;
374   CURLcode result =
375     Curl_ssl_connect_nonblocking(data, conn, FALSE, FIRSTSOCKET,
376                                  &pop3c->ssldone);
377
378   if(!result) {
379     if(pop3c->state != POP3_UPGRADETLS)
380       state(data, POP3_UPGRADETLS);
381
382     if(pop3c->ssldone) {
383       pop3_to_pop3s(conn);
384       result = pop3_perform_capa(data, conn);
385     }
386   }
387
388   return result;
389 }
390
391 /***********************************************************************
392  *
393  * pop3_perform_user()
394  *
395  * Sends a clear text USER command to authenticate with.
396  */
397 static CURLcode pop3_perform_user(struct Curl_easy *data,
398                                   struct connectdata *conn)
399 {
400   CURLcode result = CURLE_OK;
401
402   /* Check we have a username and password to authenticate with and end the
403      connect phase if we don't */
404   if(!data->state.aptr.user) {
405     state(data, POP3_STOP);
406
407     return result;
408   }
409
410   /* Send the USER command */
411   result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "USER %s",
412                          conn->user ? conn->user : "");
413   if(!result)
414     state(data, POP3_USER);
415
416   return result;
417 }
418
419 #ifndef CURL_DISABLE_CRYPTO_AUTH
420 /***********************************************************************
421  *
422  * pop3_perform_apop()
423  *
424  * Sends an APOP command to authenticate with.
425  */
426 static CURLcode pop3_perform_apop(struct Curl_easy *data,
427                                   struct connectdata *conn)
428 {
429   CURLcode result = CURLE_OK;
430   struct pop3_conn *pop3c = &conn->proto.pop3c;
431   size_t i;
432   struct MD5_context *ctxt;
433   unsigned char digest[MD5_DIGEST_LEN];
434   char secret[2 * MD5_DIGEST_LEN + 1];
435
436   /* Check we have a username and password to authenticate with and end the
437      connect phase if we don't */
438   if(!data->state.aptr.user) {
439     state(data, POP3_STOP);
440
441     return result;
442   }
443
444   /* Create the digest */
445   ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
446   if(!ctxt)
447     return CURLE_OUT_OF_MEMORY;
448
449   Curl_MD5_update(ctxt, (const unsigned char *) pop3c->apoptimestamp,
450                   curlx_uztoui(strlen(pop3c->apoptimestamp)));
451
452   Curl_MD5_update(ctxt, (const unsigned char *) conn->passwd,
453                   curlx_uztoui(strlen(conn->passwd)));
454
455   /* Finalise the digest */
456   Curl_MD5_final(ctxt, digest);
457
458   /* Convert the calculated 16 octet digest into a 32 byte hex string */
459   for(i = 0; i < MD5_DIGEST_LEN; i++)
460     msnprintf(&secret[2 * i], 3, "%02x", digest[i]);
461
462   result = Curl_pp_sendf(data, &pop3c->pp, "APOP %s %s", conn->user, secret);
463
464   if(!result)
465     state(data, POP3_APOP);
466
467   return result;
468 }
469 #endif
470
471 /***********************************************************************
472  *
473  * pop3_perform_auth()
474  *
475  * Sends an AUTH command allowing the client to login with the given SASL
476  * authentication mechanism.
477  */
478 static CURLcode pop3_perform_auth(struct Curl_easy *data,
479                                   const char *mech,
480                                   const struct bufref *initresp)
481 {
482   CURLcode result = CURLE_OK;
483   struct pop3_conn *pop3c = &data->conn->proto.pop3c;
484   const char *ir = (const char *) Curl_bufref_ptr(initresp);
485
486   if(ir) {                                  /* AUTH <mech> ...<crlf> */
487     /* Send the AUTH command with the initial response */
488     result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", mech, ir);
489   }
490   else {
491     /* Send the AUTH command */
492     result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s", mech);
493   }
494
495   return result;
496 }
497
498 /***********************************************************************
499  *
500  * pop3_continue_auth()
501  *
502  * Sends SASL continuation data.
503  */
504 static CURLcode pop3_continue_auth(struct Curl_easy *data,
505                                    const char *mech,
506                                    const struct bufref *resp)
507 {
508   struct pop3_conn *pop3c = &data->conn->proto.pop3c;
509
510   (void)mech;
511
512   return Curl_pp_sendf(data, &pop3c->pp,
513                        "%s", (const char *) Curl_bufref_ptr(resp));
514 }
515
516 /***********************************************************************
517  *
518  * pop3_cancel_auth()
519  *
520  * Sends SASL cancellation.
521  */
522 static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech)
523 {
524   struct pop3_conn *pop3c = &data->conn->proto.pop3c;
525
526   (void)mech;
527
528   return Curl_pp_sendf(data, &pop3c->pp, "*");
529 }
530
531 /***********************************************************************
532  *
533  * pop3_perform_authentication()
534  *
535  * Initiates the authentication sequence, with the appropriate SASL
536  * authentication mechanism, falling back to APOP and clear text should a
537  * common mechanism not be available between the client and server.
538  */
539 static CURLcode pop3_perform_authentication(struct Curl_easy *data,
540                                             struct connectdata *conn)
541 {
542   CURLcode result = CURLE_OK;
543   struct pop3_conn *pop3c = &conn->proto.pop3c;
544   saslprogress progress = SASL_IDLE;
545
546   /* Check we have enough data to authenticate with and end the
547      connect phase if we don't */
548   if(!Curl_sasl_can_authenticate(&pop3c->sasl, data)) {
549     state(data, POP3_STOP);
550     return result;
551   }
552
553   if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_SASL) {
554     /* Calculate the SASL login details */
555     result = Curl_sasl_start(&pop3c->sasl, data, FALSE, &progress);
556
557     if(!result)
558       if(progress == SASL_INPROGRESS)
559         state(data, POP3_AUTH);
560   }
561
562   if(!result && progress == SASL_IDLE) {
563 #ifndef CURL_DISABLE_CRYPTO_AUTH
564     if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_APOP)
565       /* Perform APOP authentication */
566       result = pop3_perform_apop(data, conn);
567     else
568 #endif
569     if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_CLEARTEXT)
570       /* Perform clear text authentication */
571       result = pop3_perform_user(data, conn);
572     else {
573       /* Other mechanisms not supported */
574       infof(data, "No known authentication mechanisms supported");
575       result = CURLE_LOGIN_DENIED;
576     }
577   }
578
579   return result;
580 }
581
582 /***********************************************************************
583  *
584  * pop3_perform_command()
585  *
586  * Sends a POP3 based command.
587  */
588 static CURLcode pop3_perform_command(struct Curl_easy *data)
589 {
590   CURLcode result = CURLE_OK;
591   struct connectdata *conn = data->conn;
592   struct POP3 *pop3 = data->req.p.pop3;
593   const char *command = NULL;
594
595   /* Calculate the default command */
596   if(pop3->id[0] == '\0' || data->set.list_only) {
597     command = "LIST";
598
599     if(pop3->id[0] != '\0')
600       /* Message specific LIST so skip the BODY transfer */
601       pop3->transfer = PPTRANSFER_INFO;
602   }
603   else
604     command = "RETR";
605
606   /* Send the command */
607   if(pop3->id[0] != '\0')
608     result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s %s",
609                            (pop3->custom && pop3->custom[0] != '\0' ?
610                             pop3->custom : command), pop3->id);
611   else
612     result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s",
613                            (pop3->custom && pop3->custom[0] != '\0' ?
614                             pop3->custom : command));
615
616   if(!result)
617     state(data, POP3_COMMAND);
618
619   return result;
620 }
621
622 /***********************************************************************
623  *
624  * pop3_perform_quit()
625  *
626  * Performs the quit action prior to sclose() be called.
627  */
628 static CURLcode pop3_perform_quit(struct Curl_easy *data,
629                                   struct connectdata *conn)
630 {
631   /* Send the QUIT command */
632   CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "QUIT");
633
634   if(!result)
635     state(data, POP3_QUIT);
636
637   return result;
638 }
639
640 /* For the initial server greeting */
641 static CURLcode pop3_state_servergreet_resp(struct Curl_easy *data,
642                                             int pop3code,
643                                             pop3state instate)
644 {
645   CURLcode result = CURLE_OK;
646   struct connectdata *conn = data->conn;
647   struct pop3_conn *pop3c = &conn->proto.pop3c;
648   const char *line = data->state.buffer;
649   size_t len = strlen(line);
650
651   (void)instate; /* no use for this yet */
652
653   if(pop3code != '+') {
654     failf(data, "Got unexpected pop3-server response");
655     result = CURLE_WEIRD_SERVER_REPLY;
656   }
657   else {
658     /* Does the server support APOP authentication? */
659     if(len >= 4 && line[len - 2] == '>') {
660       /* Look for the APOP timestamp */
661       size_t i;
662       for(i = 3; i < len - 2; ++i) {
663         if(line[i] == '<') {
664           /* Calculate the length of the timestamp */
665           size_t timestamplen = len - 1 - i;
666           char *at;
667           if(!timestamplen)
668             break;
669
670           /* Allocate some memory for the timestamp */
671           pop3c->apoptimestamp = (char *)calloc(1, timestamplen + 1);
672
673           if(!pop3c->apoptimestamp)
674             break;
675
676           /* Copy the timestamp */
677           memcpy(pop3c->apoptimestamp, line + i, timestamplen);
678           pop3c->apoptimestamp[timestamplen] = '\0';
679
680           /* If the timestamp does not contain '@' it is not (as required by
681              RFC-1939) conformant to the RFC-822 message id syntax, and we
682              therefore do not use APOP authentication. */
683           at = strchr(pop3c->apoptimestamp, '@');
684           if(!at)
685             Curl_safefree(pop3c->apoptimestamp);
686           else
687             /* Store the APOP capability */
688             pop3c->authtypes |= POP3_TYPE_APOP;
689           break;
690         }
691       }
692     }
693
694     result = pop3_perform_capa(data, conn);
695   }
696
697   return result;
698 }
699
700 /* For CAPA responses */
701 static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code,
702                                      pop3state instate)
703 {
704   CURLcode result = CURLE_OK;
705   struct connectdata *conn = data->conn;
706   struct pop3_conn *pop3c = &conn->proto.pop3c;
707   const char *line = data->state.buffer;
708   size_t len = strlen(line);
709
710   (void)instate; /* no use for this yet */
711
712   /* Do we have a untagged continuation response? */
713   if(pop3code == '*') {
714     /* Does the server support the STLS capability? */
715     if(len >= 4 && !memcmp(line, "STLS", 4))
716       pop3c->tls_supported = TRUE;
717
718     /* Does the server support clear text authentication? */
719     else if(len >= 4 && !memcmp(line, "USER", 4))
720       pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
721
722     /* Does the server support SASL based authentication? */
723     else if(len >= 5 && !memcmp(line, "SASL ", 5)) {
724       pop3c->authtypes |= POP3_TYPE_SASL;
725
726       /* Advance past the SASL keyword */
727       line += 5;
728       len -= 5;
729
730       /* Loop through the data line */
731       for(;;) {
732         size_t llen;
733         size_t wordlen;
734         unsigned short mechbit;
735
736         while(len &&
737               (*line == ' ' || *line == '\t' ||
738                *line == '\r' || *line == '\n')) {
739
740           line++;
741           len--;
742         }
743
744         if(!len)
745           break;
746
747         /* Extract the word */
748         for(wordlen = 0; wordlen < len && line[wordlen] != ' ' &&
749               line[wordlen] != '\t' && line[wordlen] != '\r' &&
750               line[wordlen] != '\n';)
751           wordlen++;
752
753         /* Test the word for a matching authentication mechanism */
754         mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
755         if(mechbit && llen == wordlen)
756           pop3c->sasl.authmechs |= mechbit;
757
758         line += wordlen;
759         len -= wordlen;
760       }
761     }
762   }
763   else {
764     /* Clear text is supported when CAPA isn't recognised */
765     if(pop3code != '+')
766       pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
767
768     if(!data->set.use_ssl || conn->ssl[FIRSTSOCKET].use)
769       result = pop3_perform_authentication(data, conn);
770     else if(pop3code == '+' && pop3c->tls_supported)
771       /* Switch to TLS connection now */
772       result = pop3_perform_starttls(data, conn);
773     else if(data->set.use_ssl <= CURLUSESSL_TRY)
774       /* Fallback and carry on with authentication */
775       result = pop3_perform_authentication(data, conn);
776     else {
777       failf(data, "STLS not supported.");
778       result = CURLE_USE_SSL_FAILED;
779     }
780   }
781
782   return result;
783 }
784
785 /* For STARTTLS responses */
786 static CURLcode pop3_state_starttls_resp(struct Curl_easy *data,
787                                          struct connectdata *conn,
788                                          int pop3code,
789                                          pop3state instate)
790 {
791   CURLcode result = CURLE_OK;
792   (void)instate; /* no use for this yet */
793
794   /* Pipelining in response is forbidden. */
795   if(data->conn->proto.pop3c.pp.cache_size)
796     return CURLE_WEIRD_SERVER_REPLY;
797
798   if(pop3code != '+') {
799     if(data->set.use_ssl != CURLUSESSL_TRY) {
800       failf(data, "STARTTLS denied");
801       result = CURLE_USE_SSL_FAILED;
802     }
803     else
804       result = pop3_perform_authentication(data, conn);
805   }
806   else
807     result = pop3_perform_upgrade_tls(data, conn);
808
809   return result;
810 }
811
812 /* For SASL authentication responses */
813 static CURLcode pop3_state_auth_resp(struct Curl_easy *data,
814                                      int pop3code,
815                                      pop3state instate)
816 {
817   CURLcode result = CURLE_OK;
818   struct connectdata *conn = data->conn;
819   struct pop3_conn *pop3c = &conn->proto.pop3c;
820   saslprogress progress;
821
822   (void)instate; /* no use for this yet */
823
824   result = Curl_sasl_continue(&pop3c->sasl, data, pop3code, &progress);
825   if(!result)
826     switch(progress) {
827     case SASL_DONE:
828       state(data, POP3_STOP);  /* Authenticated */
829       break;
830     case SASL_IDLE:            /* No mechanism left after cancellation */
831 #ifndef CURL_DISABLE_CRYPTO_AUTH
832       if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_APOP)
833         /* Perform APOP authentication */
834         result = pop3_perform_apop(data, conn);
835       else
836 #endif
837       if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_CLEARTEXT)
838         /* Perform clear text authentication */
839         result = pop3_perform_user(data, conn);
840       else {
841         failf(data, "Authentication cancelled");
842         result = CURLE_LOGIN_DENIED;
843       }
844       break;
845     default:
846       break;
847     }
848
849   return result;
850 }
851
852 #ifndef CURL_DISABLE_CRYPTO_AUTH
853 /* For APOP responses */
854 static CURLcode pop3_state_apop_resp(struct Curl_easy *data, int pop3code,
855                                      pop3state instate)
856 {
857   CURLcode result = CURLE_OK;
858   (void)instate; /* no use for this yet */
859
860   if(pop3code != '+') {
861     failf(data, "Authentication failed: %d", pop3code);
862     result = CURLE_LOGIN_DENIED;
863   }
864   else
865     /* End of connect phase */
866     state(data, POP3_STOP);
867
868   return result;
869 }
870 #endif
871
872 /* For USER responses */
873 static CURLcode pop3_state_user_resp(struct Curl_easy *data, int pop3code,
874                                      pop3state instate)
875 {
876   CURLcode result = CURLE_OK;
877   struct connectdata *conn = data->conn;
878   (void)instate; /* no use for this yet */
879
880   if(pop3code != '+') {
881     failf(data, "Access denied. %c", pop3code);
882     result = CURLE_LOGIN_DENIED;
883   }
884   else
885     /* Send the PASS command */
886     result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "PASS %s",
887                            conn->passwd ? conn->passwd : "");
888   if(!result)
889     state(data, POP3_PASS);
890
891   return result;
892 }
893
894 /* For PASS responses */
895 static CURLcode pop3_state_pass_resp(struct Curl_easy *data, int pop3code,
896                                      pop3state instate)
897 {
898   CURLcode result = CURLE_OK;
899   (void)instate; /* no use for this yet */
900
901   if(pop3code != '+') {
902     failf(data, "Access denied. %c", pop3code);
903     result = CURLE_LOGIN_DENIED;
904   }
905   else
906     /* End of connect phase */
907     state(data, POP3_STOP);
908
909   return result;
910 }
911
912 /* For command responses */
913 static CURLcode pop3_state_command_resp(struct Curl_easy *data,
914                                         int pop3code,
915                                         pop3state instate)
916 {
917   CURLcode result = CURLE_OK;
918   struct connectdata *conn = data->conn;
919   struct POP3 *pop3 = data->req.p.pop3;
920   struct pop3_conn *pop3c = &conn->proto.pop3c;
921   struct pingpong *pp = &pop3c->pp;
922
923   (void)instate; /* no use for this yet */
924
925   if(pop3code != '+') {
926     state(data, POP3_STOP);
927     return CURLE_WEIRD_SERVER_REPLY;
928   }
929
930   /* This 'OK' line ends with a CR LF pair which is the two first bytes of the
931      EOB string so count this is two matching bytes. This is necessary to make
932      the code detect the EOB if the only data than comes now is %2e CR LF like
933      when there is no body to return. */
934   pop3c->eob = 2;
935
936   /* But since this initial CR LF pair is not part of the actual body, we set
937      the strip counter here so that these bytes won't be delivered. */
938   pop3c->strip = 2;
939
940   if(pop3->transfer == PPTRANSFER_BODY) {
941     /* POP3 download */
942     Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1);
943
944     if(pp->cache) {
945       /* The header "cache" contains a bunch of data that is actually body
946          content so send it as such. Note that there may even be additional
947          "headers" after the body */
948
949       if(!data->set.opt_no_body) {
950         result = Curl_pop3_write(data, pp->cache, pp->cache_size);
951         if(result)
952           return result;
953       }
954
955       /* Free the cache */
956       Curl_safefree(pp->cache);
957
958       /* Reset the cache size */
959       pp->cache_size = 0;
960     }
961   }
962
963   /* End of DO phase */
964   state(data, POP3_STOP);
965
966   return result;
967 }
968
969 static CURLcode pop3_statemachine(struct Curl_easy *data,
970                                   struct connectdata *conn)
971 {
972   CURLcode result = CURLE_OK;
973   curl_socket_t sock = conn->sock[FIRSTSOCKET];
974   int pop3code;
975   struct pop3_conn *pop3c = &conn->proto.pop3c;
976   struct pingpong *pp = &pop3c->pp;
977   size_t nread = 0;
978   (void)data;
979
980   /* Busy upgrading the connection; right now all I/O is SSL/TLS, not POP3 */
981   if(pop3c->state == POP3_UPGRADETLS)
982     return pop3_perform_upgrade_tls(data, conn);
983
984   /* Flush any data that needs to be sent */
985   if(pp->sendleft)
986     return Curl_pp_flushsend(data, pp);
987
988  do {
989     /* Read the response from the server */
990    result = Curl_pp_readresp(data, sock, pp, &pop3code, &nread);
991    if(result)
992      return result;
993
994     if(!pop3code)
995       break;
996
997     /* We have now received a full POP3 server response */
998     switch(pop3c->state) {
999     case POP3_SERVERGREET:
1000       result = pop3_state_servergreet_resp(data, pop3code, pop3c->state);
1001       break;
1002
1003     case POP3_CAPA:
1004       result = pop3_state_capa_resp(data, pop3code, pop3c->state);
1005       break;
1006
1007     case POP3_STARTTLS:
1008       result = pop3_state_starttls_resp(data, conn, pop3code, pop3c->state);
1009       break;
1010
1011     case POP3_AUTH:
1012       result = pop3_state_auth_resp(data, pop3code, pop3c->state);
1013       break;
1014
1015 #ifndef CURL_DISABLE_CRYPTO_AUTH
1016     case POP3_APOP:
1017       result = pop3_state_apop_resp(data, pop3code, pop3c->state);
1018       break;
1019 #endif
1020
1021     case POP3_USER:
1022       result = pop3_state_user_resp(data, pop3code, pop3c->state);
1023       break;
1024
1025     case POP3_PASS:
1026       result = pop3_state_pass_resp(data, pop3code, pop3c->state);
1027       break;
1028
1029     case POP3_COMMAND:
1030       result = pop3_state_command_resp(data, pop3code, pop3c->state);
1031       break;
1032
1033     case POP3_QUIT:
1034       state(data, POP3_STOP);
1035       break;
1036
1037     default:
1038       /* internal error */
1039       state(data, POP3_STOP);
1040       break;
1041     }
1042   } while(!result && pop3c->state != POP3_STOP && Curl_pp_moredata(pp));
1043
1044   return result;
1045 }
1046
1047 /* Called repeatedly until done from multi.c */
1048 static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done)
1049 {
1050   CURLcode result = CURLE_OK;
1051   struct connectdata *conn = data->conn;
1052   struct pop3_conn *pop3c = &conn->proto.pop3c;
1053
1054   if((conn->handler->flags & PROTOPT_SSL) && !pop3c->ssldone) {
1055     result = Curl_ssl_connect_nonblocking(data, conn, FALSE,
1056                                           FIRSTSOCKET, &pop3c->ssldone);
1057     if(result || !pop3c->ssldone)
1058       return result;
1059   }
1060
1061   result = Curl_pp_statemach(data, &pop3c->pp, FALSE, FALSE);
1062   *done = (pop3c->state == POP3_STOP) ? TRUE : FALSE;
1063
1064   return result;
1065 }
1066
1067 static CURLcode pop3_block_statemach(struct Curl_easy *data,
1068                                      struct connectdata *conn,
1069                                      bool disconnecting)
1070 {
1071   CURLcode result = CURLE_OK;
1072   struct pop3_conn *pop3c = &conn->proto.pop3c;
1073
1074   while(pop3c->state != POP3_STOP && !result)
1075     result = Curl_pp_statemach(data, &pop3c->pp, TRUE, disconnecting);
1076
1077   return result;
1078 }
1079
1080 /* Allocate and initialize the POP3 struct for the current Curl_easy if
1081    required */
1082 static CURLcode pop3_init(struct Curl_easy *data)
1083 {
1084   CURLcode result = CURLE_OK;
1085   struct POP3 *pop3;
1086
1087   pop3 = data->req.p.pop3 = calloc(sizeof(struct POP3), 1);
1088   if(!pop3)
1089     result = CURLE_OUT_OF_MEMORY;
1090
1091   return result;
1092 }
1093
1094 /* For the POP3 "protocol connect" and "doing" phases only */
1095 static int pop3_getsock(struct Curl_easy *data,
1096                         struct connectdata *conn, curl_socket_t *socks)
1097 {
1098   return Curl_pp_getsock(data, &conn->proto.pop3c.pp, socks);
1099 }
1100
1101 /***********************************************************************
1102  *
1103  * pop3_connect()
1104  *
1105  * This function should do everything that is to be considered a part of the
1106  * connection phase.
1107  *
1108  * The variable 'done' points to will be TRUE if the protocol-layer connect
1109  * phase is done when this function returns, or FALSE if not.
1110  */
1111 static CURLcode pop3_connect(struct Curl_easy *data, bool *done)
1112 {
1113   CURLcode result = CURLE_OK;
1114   struct connectdata *conn = data->conn;
1115   struct pop3_conn *pop3c = &conn->proto.pop3c;
1116   struct pingpong *pp = &pop3c->pp;
1117
1118   *done = FALSE; /* default to not done yet */
1119
1120   /* We always support persistent connections in POP3 */
1121   connkeep(conn, "POP3 default");
1122
1123   PINGPONG_SETUP(pp, pop3_statemachine, pop3_endofresp);
1124
1125   /* Set the default preferred authentication type and mechanism */
1126   pop3c->preftype = POP3_TYPE_ANY;
1127   Curl_sasl_init(&pop3c->sasl, data, &saslpop3);
1128
1129   /* Initialise the pingpong layer */
1130   Curl_pp_setup(pp);
1131   Curl_pp_init(data, pp);
1132
1133   /* Parse the URL options */
1134   result = pop3_parse_url_options(conn);
1135   if(result)
1136     return result;
1137
1138   /* Start off waiting for the server greeting response */
1139   state(data, POP3_SERVERGREET);
1140
1141   result = pop3_multi_statemach(data, done);
1142
1143   return result;
1144 }
1145
1146 /***********************************************************************
1147  *
1148  * pop3_done()
1149  *
1150  * The DONE function. This does what needs to be done after a single DO has
1151  * performed.
1152  *
1153  * Input argument is already checked for validity.
1154  */
1155 static CURLcode pop3_done(struct Curl_easy *data, CURLcode status,
1156                           bool premature)
1157 {
1158   CURLcode result = CURLE_OK;
1159   struct POP3 *pop3 = data->req.p.pop3;
1160
1161   (void)premature;
1162
1163   if(!pop3)
1164     return CURLE_OK;
1165
1166   if(status) {
1167     connclose(data->conn, "POP3 done with bad status");
1168     result = status;         /* use the already set error code */
1169   }
1170
1171   /* Cleanup our per-request based variables */
1172   Curl_safefree(pop3->id);
1173   Curl_safefree(pop3->custom);
1174
1175   /* Clear the transfer mode for the next request */
1176   pop3->transfer = PPTRANSFER_BODY;
1177
1178   return result;
1179 }
1180
1181 /***********************************************************************
1182  *
1183  * pop3_perform()
1184  *
1185  * This is the actual DO function for POP3. Get a message/listing according to
1186  * the options previously setup.
1187  */
1188 static CURLcode pop3_perform(struct Curl_easy *data, bool *connected,
1189                              bool *dophase_done)
1190 {
1191   /* This is POP3 and no proxy */
1192   CURLcode result = CURLE_OK;
1193   struct connectdata *conn = data->conn;
1194   struct POP3 *pop3 = data->req.p.pop3;
1195
1196   DEBUGF(infof(data, "DO phase starts"));
1197
1198   if(data->set.opt_no_body) {
1199     /* Requested no body means no transfer */
1200     pop3->transfer = PPTRANSFER_INFO;
1201   }
1202
1203   *dophase_done = FALSE; /* not done yet */
1204
1205   /* Start the first command in the DO phase */
1206   result = pop3_perform_command(data);
1207   if(result)
1208     return result;
1209
1210   /* Run the state-machine */
1211   result = pop3_multi_statemach(data, dophase_done);
1212   *connected = conn->bits.tcpconnect[FIRSTSOCKET];
1213
1214   if(*dophase_done)
1215     DEBUGF(infof(data, "DO phase is complete"));
1216
1217   return result;
1218 }
1219
1220 /***********************************************************************
1221  *
1222  * pop3_do()
1223  *
1224  * This function is registered as 'curl_do' function. It decodes the path
1225  * parts etc as a wrapper to the actual DO function (pop3_perform).
1226  *
1227  * The input argument is already checked for validity.
1228  */
1229 static CURLcode pop3_do(struct Curl_easy *data, bool *done)
1230 {
1231   CURLcode result = CURLE_OK;
1232   *done = FALSE; /* default to false */
1233
1234   /* Parse the URL path */
1235   result = pop3_parse_url_path(data);
1236   if(result)
1237     return result;
1238
1239   /* Parse the custom request */
1240   result = pop3_parse_custom_request(data);
1241   if(result)
1242     return result;
1243
1244   result = pop3_regular_transfer(data, done);
1245
1246   return result;
1247 }
1248
1249 /***********************************************************************
1250  *
1251  * pop3_disconnect()
1252  *
1253  * Disconnect from an POP3 server. Cleanup protocol-specific per-connection
1254  * resources. BLOCKING.
1255  */
1256 static CURLcode pop3_disconnect(struct Curl_easy *data,
1257                                 struct connectdata *conn, bool dead_connection)
1258 {
1259   struct pop3_conn *pop3c = &conn->proto.pop3c;
1260   (void)data;
1261
1262   /* We cannot send quit unconditionally. If this connection is stale or
1263      bad in any way, sending quit and waiting around here will make the
1264      disconnect wait in vain and cause more problems than we need to. */
1265
1266   if(!dead_connection && conn->bits.protoconnstart) {
1267     if(!pop3_perform_quit(data, conn))
1268       (void)pop3_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */
1269   }
1270
1271   /* Disconnect from the server */
1272   Curl_pp_disconnect(&pop3c->pp);
1273
1274   /* Cleanup the SASL module */
1275   Curl_sasl_cleanup(conn, pop3c->sasl.authused);
1276
1277   /* Cleanup our connection based variables */
1278   Curl_safefree(pop3c->apoptimestamp);
1279
1280   return CURLE_OK;
1281 }
1282
1283 /* Call this when the DO phase has completed */
1284 static CURLcode pop3_dophase_done(struct Curl_easy *data, bool connected)
1285 {
1286   (void)data;
1287   (void)connected;
1288
1289   return CURLE_OK;
1290 }
1291
1292 /* Called from multi.c while DOing */
1293 static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done)
1294 {
1295   CURLcode result = pop3_multi_statemach(data, dophase_done);
1296
1297   if(result)
1298     DEBUGF(infof(data, "DO phase failed"));
1299   else if(*dophase_done) {
1300     result = pop3_dophase_done(data, FALSE /* not connected */);
1301
1302     DEBUGF(infof(data, "DO phase is complete"));
1303   }
1304
1305   return result;
1306 }
1307
1308 /***********************************************************************
1309  *
1310  * pop3_regular_transfer()
1311  *
1312  * The input argument is already checked for validity.
1313  *
1314  * Performs all commands done before a regular transfer between a local and a
1315  * remote host.
1316  */
1317 static CURLcode pop3_regular_transfer(struct Curl_easy *data,
1318                                       bool *dophase_done)
1319 {
1320   CURLcode result = CURLE_OK;
1321   bool connected = FALSE;
1322
1323   /* Make sure size is unknown at this point */
1324   data->req.size = -1;
1325
1326   /* Set the progress data */
1327   Curl_pgrsSetUploadCounter(data, 0);
1328   Curl_pgrsSetDownloadCounter(data, 0);
1329   Curl_pgrsSetUploadSize(data, -1);
1330   Curl_pgrsSetDownloadSize(data, -1);
1331
1332   /* Carry out the perform */
1333   result = pop3_perform(data, &connected, dophase_done);
1334
1335   /* Perform post DO phase operations if necessary */
1336   if(!result && *dophase_done)
1337     result = pop3_dophase_done(data, connected);
1338
1339   return result;
1340 }
1341
1342 static CURLcode pop3_setup_connection(struct Curl_easy *data,
1343                                       struct connectdata *conn)
1344 {
1345   /* Initialise the POP3 layer */
1346   CURLcode result = pop3_init(data);
1347   if(result)
1348     return result;
1349
1350   /* Clear the TLS upgraded flag */
1351   conn->bits.tls_upgraded = FALSE;
1352
1353   return CURLE_OK;
1354 }
1355
1356 /***********************************************************************
1357  *
1358  * pop3_parse_url_options()
1359  *
1360  * Parse the URL login options.
1361  */
1362 static CURLcode pop3_parse_url_options(struct connectdata *conn)
1363 {
1364   CURLcode result = CURLE_OK;
1365   struct pop3_conn *pop3c = &conn->proto.pop3c;
1366   const char *ptr = conn->options;
1367
1368   while(!result && ptr && *ptr) {
1369     const char *key = ptr;
1370     const char *value;
1371
1372     while(*ptr && *ptr != '=')
1373         ptr++;
1374
1375     value = ptr + 1;
1376
1377     while(*ptr && *ptr != ';')
1378       ptr++;
1379
1380     if(strncasecompare(key, "AUTH=", 5)) {
1381       result = Curl_sasl_parse_url_auth_option(&pop3c->sasl,
1382                                                value, ptr - value);
1383
1384       if(result && strncasecompare(value, "+APOP", ptr - value)) {
1385         pop3c->preftype = POP3_TYPE_APOP;
1386         pop3c->sasl.prefmech = SASL_AUTH_NONE;
1387         result = CURLE_OK;
1388       }
1389     }
1390     else
1391       result = CURLE_URL_MALFORMAT;
1392
1393     if(*ptr == ';')
1394       ptr++;
1395   }
1396
1397   if(pop3c->preftype != POP3_TYPE_APOP)
1398     switch(pop3c->sasl.prefmech) {
1399     case SASL_AUTH_NONE:
1400       pop3c->preftype = POP3_TYPE_NONE;
1401       break;
1402     case SASL_AUTH_DEFAULT:
1403       pop3c->preftype = POP3_TYPE_ANY;
1404       break;
1405     default:
1406       pop3c->preftype = POP3_TYPE_SASL;
1407       break;
1408     }
1409
1410   return result;
1411 }
1412
1413 /***********************************************************************
1414  *
1415  * pop3_parse_url_path()
1416  *
1417  * Parse the URL path into separate path components.
1418  */
1419 static CURLcode pop3_parse_url_path(struct Curl_easy *data)
1420 {
1421   /* The POP3 struct is already initialised in pop3_connect() */
1422   struct POP3 *pop3 = data->req.p.pop3;
1423   const char *path = &data->state.up.path[1]; /* skip leading path */
1424
1425   /* URL decode the path for the message ID */
1426   return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL);
1427 }
1428
1429 /***********************************************************************
1430  *
1431  * pop3_parse_custom_request()
1432  *
1433  * Parse the custom request.
1434  */
1435 static CURLcode pop3_parse_custom_request(struct Curl_easy *data)
1436 {
1437   CURLcode result = CURLE_OK;
1438   struct POP3 *pop3 = data->req.p.pop3;
1439   const char *custom = data->set.str[STRING_CUSTOMREQUEST];
1440
1441   /* URL decode the custom request */
1442   if(custom)
1443     result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL);
1444
1445   return result;
1446 }
1447
1448 /***********************************************************************
1449  *
1450  * Curl_pop3_write()
1451  *
1452  * This function scans the body after the end-of-body and writes everything
1453  * until the end is found.
1454  */
1455 CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread)
1456 {
1457   /* This code could be made into a special function in the handler struct */
1458   CURLcode result = CURLE_OK;
1459   struct SingleRequest *k = &data->req;
1460   struct connectdata *conn = data->conn;
1461   struct pop3_conn *pop3c = &conn->proto.pop3c;
1462   bool strip_dot = FALSE;
1463   size_t last = 0;
1464   size_t i;
1465
1466   /* Search through the buffer looking for the end-of-body marker which is
1467      5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches
1468      the eob so the server will have prefixed it with an extra dot which we
1469      need to strip out. Additionally the marker could of course be spread out
1470      over 5 different data chunks. */
1471   for(i = 0; i < nread; i++) {
1472     size_t prev = pop3c->eob;
1473
1474     switch(str[i]) {
1475     case 0x0d:
1476       if(pop3c->eob == 0) {
1477         pop3c->eob++;
1478
1479         if(i) {
1480           /* Write out the body part that didn't match */
1481           result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last],
1482                                      i - last);
1483
1484           if(result)
1485             return result;
1486
1487           last = i;
1488         }
1489       }
1490       else if(pop3c->eob == 3)
1491         pop3c->eob++;
1492       else
1493         /* If the character match wasn't at position 0 or 3 then restart the
1494            pattern matching */
1495         pop3c->eob = 1;
1496       break;
1497
1498     case 0x0a:
1499       if(pop3c->eob == 1 || pop3c->eob == 4)
1500         pop3c->eob++;
1501       else
1502         /* If the character match wasn't at position 1 or 4 then start the
1503            search again */
1504         pop3c->eob = 0;
1505       break;
1506
1507     case 0x2e:
1508       if(pop3c->eob == 2)
1509         pop3c->eob++;
1510       else if(pop3c->eob == 3) {
1511         /* We have an extra dot after the CRLF which we need to strip off */
1512         strip_dot = TRUE;
1513         pop3c->eob = 0;
1514       }
1515       else
1516         /* If the character match wasn't at position 2 then start the search
1517            again */
1518         pop3c->eob = 0;
1519       break;
1520
1521     default:
1522       pop3c->eob = 0;
1523       break;
1524     }
1525
1526     /* Did we have a partial match which has subsequently failed? */
1527     if(prev && prev >= pop3c->eob) {
1528       /* Strip can only be non-zero for the very first mismatch after CRLF
1529          and then both prev and strip are equal and nothing will be output
1530          below */
1531       while(prev && pop3c->strip) {
1532         prev--;
1533         pop3c->strip--;
1534       }
1535
1536       if(prev) {
1537         /* If the partial match was the CRLF and dot then only write the CRLF
1538            as the server would have inserted the dot */
1539         if(strip_dot && prev - 1 > 0) {
1540           result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB,
1541                                      prev - 1);
1542         }
1543         else if(!strip_dot) {
1544           result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB,
1545                                      prev);
1546         }
1547         else {
1548           result = CURLE_OK;
1549         }
1550
1551         if(result)
1552           return result;
1553
1554         last = i;
1555         strip_dot = FALSE;
1556       }
1557     }
1558   }
1559
1560   if(pop3c->eob == POP3_EOB_LEN) {
1561     /* We have a full match so the transfer is done, however we must transfer
1562     the CRLF at the start of the EOB as this is considered to be part of the
1563     message as per RFC-1939, sect. 3 */
1564     result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, 2);
1565
1566     k->keepon &= ~KEEP_RECV;
1567     pop3c->eob = 0;
1568
1569     return result;
1570   }
1571
1572   if(pop3c->eob)
1573     /* While EOB is matching nothing should be output */
1574     return CURLE_OK;
1575
1576   if(nread - last) {
1577     result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last],
1578                                nread - last);
1579   }
1580
1581   return result;
1582 }
1583
1584 #endif /* CURL_DISABLE_POP3 */