reinstate cmd_user, and continue to simplify pop code.
[apps/madmutt.git] / pop.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2000-2002 Vsevolod Volkov <vvv@mutt.org.ua>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-lib/lib-lib.h>
11
12 #include <sasl/sasl.h>
13 #include <sasl/saslutil.h>
14
15 #include <lib-sys/mutt_socket.h>
16 #include <lib-ui/curses.h>
17
18 #include "crypt.h"
19 #include "mutt.h"
20 #include "mutt_sasl.h"
21 #include "pop.h"
22
23 #define POP_PORT            110
24 #define POP_SSL_PORT        995
25 #define POP_CACHE_LEN        10
26 /* maximal length of the server response (RFC1939) */
27 #define POP_CMD_RESPONSE    512
28
29 enum {
30     /* Status */
31     POP_NONE = 0,
32     POP_CONNECTED,
33     POP_DISCONNECTED,
34     POP_BYE
35 };
36
37 typedef enum {
38     POP_A_SUCCESS = 0,
39     POP_A_SOCKET,
40     POP_A_FAILURE,
41     POP_A_UNAVAIL
42 } pop_auth_res_t;
43
44 typedef struct {
45   int index;
46   char *path;
47 } POP_CACHE;
48
49 typedef enum {
50     /* pop_fetch_data uses pop_query_status and this return value */
51     PFD_FUNCT_ERROR = -3,
52     PQ_ERR = -2,
53     PQ_NOT_CONNECTED = -1,
54     PQ_OK = 0
55 } pop_query_status;
56
57 typedef enum {
58     CMD_NOT_AVAILABLE = 0,
59     CMD_AVAILABLE,
60     CMD_UNKNOWN /* unknown whether it is available or not */
61 } cmd_status;
62
63 typedef struct {
64     CONNECTION *conn;
65
66     unsigned status : 2;
67     unsigned capabilities : 1;
68     unsigned use_stls : 2;
69     cmd_status cmd_capa : 2;      /* optional command CAPA */
70     cmd_status cmd_stls : 2;      /* optional command STLS */
71     cmd_status cmd_uidl : 2;      /* optional command UIDL */
72     cmd_status cmd_top  : 2;      /* optional command TOP  */
73     cmd_status cmd_user : 2;      /* optional command USER */
74     unsigned resp_codes : 1;      /* server supports extended response codes */
75     unsigned expire : 1;          /* expire is greater than 0 */
76     unsigned clear_cache : 1;
77
78     ssize_t size;
79     time_t check_time;
80     time_t login_delay;           /* minimal login delay  capability */
81     char *auth_list;              /* list of auth mechanisms */
82     char *timestamp;
83     char err_msg[POP_CMD_RESPONSE];
84     POP_CACHE cache[POP_CACHE_LEN];
85 } pop_data_t;
86
87 /* pop low level functions {{{ */
88
89 static void pop_error(pop_data_t *pop_data, const char *msg)
90 {
91     if (!m_strncmp(msg, "-ERR ", 5)) {
92         const char *s = skipspaces(msg + 5);
93         if (*s)
94             msg = s;
95     }
96
97     m_strcat(pop_data->err_msg, sizeof(pop_data->err_msg), msg);
98     m_strrtrim(pop_data->err_msg);
99 }
100
101 /*
102  * Send data from buffer and receive answer to the same buffer
103  *  0 - successful,
104  * -1 - conection lost,
105  * -2 - invalid command or execution error.
106 */
107 static pop_query_status pop_query(pop_data_t *pop_data, char *buf, ssize_t buflen)
108 {
109     char *c;
110
111     if (pop_data->status != POP_CONNECTED)
112         return PQ_NOT_CONNECTED;
113
114     mutt_socket_write(pop_data->conn, buf);
115
116     c  = strpbrk(buf, " \r\n");
117     *c = '\0';
118     snprintf(pop_data->err_msg, sizeof(pop_data->err_msg), "%s: ", buf);
119
120     if (mutt_socket_readln(buf, buflen, pop_data->conn) < 0) {
121         pop_data->status = POP_DISCONNECTED;
122         return PQ_NOT_CONNECTED;
123     }
124     if (!m_strncmp(buf, "+OK", 3))
125         return PQ_OK;
126
127     pop_error(pop_data, buf);
128     return PQ_ERR;
129 }
130
131 /*
132  * Open connection
133  *  0 - successful,
134  * -1 - conection lost,
135  * -2 - invalid response.
136 */
137 static pop_query_status pop_connect(pop_data_t * pop_data)
138 {
139     char buf[LONG_STRING];
140     const char *p, *q;
141
142     if (mutt_socket_open(pop_data->conn) < 0
143     ||  mutt_socket_readln(buf, sizeof(buf), pop_data->conn) < 0)
144     {
145         mutt_error(_("Error connecting to server: %s"),
146                    pop_data->conn->account.host);
147         pop_data->status = POP_NONE;
148         return PQ_NOT_CONNECTED;
149     }
150
151     pop_data->status = POP_CONNECTED;
152     if (m_strncmp(buf, "+OK", 3)) {
153         pop_data->err_msg[0] = '\0';
154         pop_error(pop_data, buf);
155         mutt_error("%s", pop_data->err_msg);
156         return PQ_ERR;
157     }
158
159     p_delete(&pop_data->timestamp);
160     if ((p = strchr(buf, '<')) && (q = strchr(p, '>'))) {
161         pop_data->timestamp = p_dupstr(p, q - p);
162     }
163     return PQ_OK;
164 }
165
166 static void pop_logout (CONTEXT * ctx)
167 {
168   pop_query_status ret = 0;
169   char buf[LONG_STRING];
170   pop_data_t *pop_data = (pop_data_t *) ctx->data;
171
172   if (pop_data->status == POP_CONNECTED) {
173     mutt_message _("Closing connection to POP server...");
174
175     if (ctx->readonly) {
176       m_strcpy(buf, sizeof(buf), "RSET\r\n");
177       ret = pop_query (pop_data, buf, sizeof (buf));
178     }
179
180     if (ret != PQ_NOT_CONNECTED) {
181       m_strcpy(buf, sizeof(buf), "QUIT\r\n");
182       pop_query (pop_data, buf, sizeof (buf));
183     }
184
185     mutt_clear_error ();
186   }
187
188   pop_data->status = POP_DISCONNECTED;
189   return;
190 }
191
192 /* }}} */
193 /* Authentication {{{ */
194
195 static pop_auth_res_t pop_auth_sasl(pop_data_t *pop_data, const char *method)
196 {
197     sasl_conn_t *saslconn;
198     sasl_interact_t *interaction = NULL;
199
200     char buf[LONG_STRING], inbuf[LONG_STRING];
201     const char *mech, *pc = NULL;
202     unsigned int len, olen;
203     unsigned char client_start;
204     int rc;
205
206     if (mutt_sasl_client_new(pop_data->conn, &saslconn) < 0) {
207         return POP_A_FAILURE;
208     }
209
210     if (!method)
211         method = pop_data->auth_list;
212
213     for (;;) {
214         rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen,
215                                &mech);
216         if (rc != SASL_INTERACT)
217             break;
218         mutt_sasl_interact(interaction);
219     }
220
221     if (rc != SASL_OK && rc != SASL_CONTINUE) {
222         return POP_A_UNAVAIL;
223     }
224
225     client_start = (olen > 0);
226     mutt_message(_("Authenticating (SASL)..."));
227     snprintf(buf, sizeof (buf), "AUTH %s", mech);
228     olen = strlen (buf);
229
230     /* looping protocol */
231     for (;;) {
232         m_strcpy(buf + olen, sizeof(buf) - olen, "\r\n");
233         mutt_socket_write(pop_data->conn, buf);
234         if (mutt_socket_readln(inbuf, sizeof(inbuf), pop_data->conn) < 0) {
235             sasl_dispose(&saslconn);
236             pop_data->status = POP_DISCONNECTED;
237             return POP_A_SOCKET;
238         }
239
240         if (rc != SASL_CONTINUE)
241             break;
242
243         if (!m_strncmp(inbuf, "+ ", 2)
244         &&   sasl_decode64(inbuf, strlen(inbuf),
245                            buf, sizeof(buf) - 1, &len) != SASL_OK)
246         {
247             goto bail;
248         }
249
250         if (!client_start) {
251             for (;;) {
252                 rc = sasl_client_step(saslconn, buf, len, &interaction, &pc,
253                                       &olen);
254                 if (rc != SASL_INTERACT)
255                     break;
256                 mutt_sasl_interact(interaction);
257             }
258         } else {
259             client_start = 0;
260         }
261
262         if (rc != SASL_CONTINUE && (olen == 0 || rc != SASL_OK))
263             break;
264
265         /* send out response, or line break if none needed */
266         if (pc) {
267             if (sasl_encode64(pc, olen, buf, sizeof(buf), &olen) != SASL_OK) {
268                 goto bail;
269             }
270             p_delete((char **)&pc);
271         }
272     }
273
274     if (rc != SASL_OK)
275         goto bail;
276
277     if (!m_strncmp(inbuf, "+OK", 3)) {
278         mutt_sasl_setup_conn(pop_data->conn, saslconn);
279         return POP_A_SUCCESS;
280     }
281
282   bail:
283     sasl_dispose(&saslconn);
284     p_delete((char **)&pc);
285
286     /* terminate SASL session if the last responce is not +OK nor -ERR */
287     if (!m_strncmp(inbuf, "+ ", 2)) {
288         snprintf(buf, sizeof(buf), "*\r\n");
289         if (pop_query(pop_data, buf, sizeof(buf)) == PQ_NOT_CONNECTED)
290             return POP_A_SOCKET;
291     }
292
293     mutt_error(_("SASL authentication failed."));
294     mutt_sleep(2);
295
296     return POP_A_FAILURE;
297 }
298
299 static pop_auth_res_t pop_auth_apop(pop_data_t *pop_data, const char *method)
300 {
301     MD5_CTX mdContext;
302     unsigned char digest[16];
303     char hash[33];
304     char buf[LONG_STRING];
305     int i;
306
307     if (!pop_data->timestamp)
308         return POP_A_UNAVAIL;
309
310     mutt_message _("Authenticating (APOP)...");
311
312     /* Compute the authentication hash to send to the server */
313     MD5Init(&mdContext);
314     MD5Update(&mdContext, (unsigned char *)pop_data->timestamp,
315               strlen(pop_data->timestamp));
316     MD5Update(&mdContext, (unsigned char *)pop_data->conn->account.pass,
317               strlen(pop_data->conn->account.pass));
318     MD5Final(digest, &mdContext);
319
320     for (i = 0; i < countof(digest); i++)
321         sprintf(hash + 2 * i, "%02x", digest[i]);
322
323     /* Send APOP command to server */
324     snprintf(buf, sizeof(buf), "APOP %s %s\r\n", pop_data->conn->account.user,
325              hash);
326
327     switch (pop_query(pop_data, buf, sizeof(buf))) {
328       case PQ_OK:
329         return POP_A_SUCCESS;
330       case PQ_NOT_CONNECTED:
331         return POP_A_SOCKET;
332       default:
333         mutt_error("%s %s", _("APOP authentication failed."), pop_data->err_msg);
334         mutt_sleep(2);
335         return POP_A_FAILURE;
336     }
337 }
338
339 static pop_auth_res_t pop_auth_user(pop_data_t *pop_data, const char *method)
340 {
341     char buf[LONG_STRING];
342     pop_query_status ret;
343
344     if (pop_data->cmd_user == CMD_NOT_AVAILABLE)
345         return POP_A_UNAVAIL;
346
347     mutt_message _("Authenticating (USER)...");
348     snprintf(buf, sizeof(buf), "USER %s\r\n", pop_data->conn->account.user);
349     ret = pop_query(pop_data, buf, sizeof (buf));
350
351     if (pop_data->cmd_user == CMD_UNKNOWN) {
352         if (ret == PQ_OK)
353             pop_data->cmd_user = CMD_AVAILABLE;
354         if (ret == PQ_ERR)
355             pop_data->cmd_user = CMD_NOT_AVAILABLE;
356     }
357
358     if (ret == PQ_OK) {
359         snprintf(buf, sizeof(buf), "PASS %s\r\n",
360                  pop_data->conn->account.pass);
361         ret = pop_query(pop_data, buf, sizeof(buf));
362     }
363
364     switch (ret) {
365       case PQ_OK:
366         return POP_A_SUCCESS;
367       case PQ_NOT_CONNECTED:
368         return POP_A_SOCKET;
369       default:
370         mutt_error("%s %s", _("USER authentication failed."), pop_data->err_msg);
371         mutt_sleep(2);
372         return POP_A_FAILURE;
373     }
374 }
375
376 typedef struct {
377     pop_auth_res_t (*do_auth)(pop_data_t *, const char *);
378     const char *method;
379 } pop_auth_t;
380
381 static pop_query_status pop_authenticate (pop_data_t * pop_data)
382 {
383     static pop_auth_t const pop_authenticators[] = {
384         {pop_auth_sasl, NULL},
385         {pop_auth_apop, "apop"},
386         {pop_auth_user, "user"},
387         {NULL, NULL}
388     };
389
390     ACCOUNT *act = &pop_data->conn->account;
391     const pop_auth_t *auth;
392     int attempts = 0;
393
394     if (mutt_account_getuser(act) || !act->user[0]
395     ||  mutt_account_getpass(act) || !act->pass[0])
396     {
397         return PFD_FUNCT_ERROR;
398     }
399
400     if (!m_strisempty(PopAuthenticators)) {
401         const char *p, *q;
402         char buf[STRING];
403
404         for (p = PopAuthenticators; ; p = q) {
405             while (*p == ':')
406                 p++;
407             if (!*p)
408                 break;
409
410             q = strchrnul(p, ':');
411             m_strncpy(buf, sizeof(buf), p, q - p);
412
413             for (auth = pop_authenticators; auth->do_auth; auth++) {
414                 if (auth->method && ascii_strcasecmp(auth->method, buf))
415                     continue;
416
417                 switch (auth->do_auth(pop_data, buf)) {
418                   case POP_A_SUCCESS:
419                     return PQ_OK;
420                   case POP_A_SOCKET:
421                     return PQ_NOT_CONNECTED;
422                   case POP_A_UNAVAIL:
423                     break;
424                   case POP_A_FAILURE:
425                     attempts++;
426                     break;
427                 }
428             }
429         }
430     } else {
431         for (auth = pop_authenticators; auth->do_auth; auth++) {
432             switch (auth->do_auth(pop_data, auth->method)) {
433               case POP_A_SUCCESS:
434                 return PQ_OK;
435               case POP_A_SOCKET:
436                 return PQ_NOT_CONNECTED;
437               case POP_A_UNAVAIL:
438                 break;
439               case POP_A_FAILURE:
440                 attempts++;
441                 break;
442             }
443         }
444     }
445
446     if (!attempts)
447         mutt_error(_("No authenticators available"));
448     return PQ_ERR;
449 }
450
451 /* }}} */
452
453 /*
454  * This function calls  funct(*line, *data)  for each received line,
455  * funct(NULL, *data)  if  rewind(*data)  needs, exits when fail or done.
456  * Returned codes:
457  *  0 - successful,
458  * -1 - conection lost,
459  * -2 - invalid command or execution error,
460  * -3 - error in funct(*line, *data)
461  */
462 static pop_query_status
463 pop_fetch_data(pop_data_t *pop_data, const char *query, progress_t *bar,
464                int (*funct)(char *, void *), void *data)
465 {
466   char buf[LONG_STRING];
467   char *inbuf;
468   char *p;
469   pop_query_status ret;
470   int chunk = 0;
471   long pos = 0;
472   ssize_t lenbuf = 0;
473
474   m_strcpy(buf, sizeof(buf), query);
475   ret = pop_query (pop_data, buf, sizeof (buf));
476   if (ret != PQ_OK)
477     return ret;
478
479   inbuf = p_new(char, sizeof(buf));
480
481   for (;;) {
482     chunk =
483       mutt_socket_readln(buf, sizeof (buf), pop_data->conn);
484     if (chunk < 0) {
485       pop_data->status = POP_DISCONNECTED;
486       ret = PQ_NOT_CONNECTED;
487       break;
488     }
489
490     p = buf;
491     if (!lenbuf && buf[0] == '.') {
492       if (buf[1] != '.')
493         break;
494       p++;
495     }
496
497     m_strcpy(inbuf + lenbuf,sizeof(buf), p);
498     pos += chunk;
499
500     if (chunk >= ssizeof(buf)) {
501       lenbuf += strlen (p);
502     } else {
503       if (bar)
504         mutt_progress_bar (bar, pos);
505       if (ret == 0 && funct (inbuf, data) < 0)
506         ret = PFD_FUNCT_ERROR;
507       lenbuf = 0;
508     }
509
510     p_realloc(&inbuf, lenbuf + sizeof(buf));
511   }
512
513   p_delete(&inbuf);
514   return ret;
515 }
516
517 static int fetch_capa (char *line, void *data)
518 {
519   pop_data_t *pop_data = (pop_data_t *) data;
520   char *c;
521
522   if (!ascii_strncasecmp (line, "SASL", 4)) {
523     p_delete(&pop_data->auth_list);
524     c = vskipspaces(line + 4);
525     pop_data->auth_list = m_strdup(c);
526   }
527
528   else if (!ascii_strncasecmp (line, "STLS", 4))
529     pop_data->cmd_stls = CMD_AVAILABLE;
530
531   else if (!ascii_strncasecmp (line, "UIDL", 4))
532     pop_data->cmd_uidl = CMD_AVAILABLE;
533
534   else if (!ascii_strncasecmp (line, "TOP", 3))
535     pop_data->cmd_top = CMD_AVAILABLE;
536
537   return 0;
538 }
539
540 static int fetch_auth (char *line, void *data)
541 {
542   pop_data_t *pop_data = (pop_data_t *) data;
543   ssize_t auth_list_len;
544
545   if (!pop_data->auth_list) {
546     auth_list_len = m_strlen(line) + 1;
547     pop_data->auth_list = p_new(char, auth_list_len);
548   } else {
549     auth_list_len = m_strlen(pop_data->auth_list) + m_strlen(line) + 2;
550     p_realloc(&pop_data->auth_list, auth_list_len);
551     m_strcat(pop_data->auth_list, auth_list_len, " ");
552   }
553   m_strcat(pop_data->auth_list, auth_list_len, line);
554
555   return 0;
556 }
557
558 /*
559  * Get capabilities
560  *  0 - successful,
561  * -1 - conection lost,
562  * -2 - execution error.
563 */
564 static pop_query_status pop_capabilities (pop_data_t * pop_data, int mode)
565 {
566   char buf[LONG_STRING];
567
568   /* don't check capabilities on reconnect */
569   if (pop_data->capabilities)
570     return 0;
571
572   /* init capabilities */
573   if (mode == 0) {
574     pop_data->cmd_capa = CMD_NOT_AVAILABLE;
575     pop_data->cmd_stls = CMD_NOT_AVAILABLE;
576     pop_data->cmd_uidl = CMD_NOT_AVAILABLE;
577     pop_data->cmd_top = CMD_NOT_AVAILABLE;
578     pop_data->resp_codes = 0;
579     pop_data->expire = 1;
580     pop_data->login_delay = 0;
581     p_delete(&pop_data->auth_list);
582   }
583
584   /* Execute CAPA command */
585   if (mode == 0 || pop_data->cmd_capa != CMD_NOT_AVAILABLE) {
586     m_strcpy(buf, sizeof(buf), "CAPA\r\n");
587     switch (pop_fetch_data (pop_data, buf, NULL, fetch_capa, pop_data)) {
588     case PQ_OK:
589       pop_data->cmd_capa = CMD_AVAILABLE;
590       break;
591     case PFD_FUNCT_ERROR:
592     case PQ_ERR:
593       pop_data->cmd_capa = CMD_NOT_AVAILABLE;
594       break;
595     case PQ_NOT_CONNECTED:
596       return PQ_NOT_CONNECTED;
597     }
598   }
599
600   /* CAPA not supported, use defaults */
601   if (mode == 0 && pop_data->cmd_capa == CMD_NOT_AVAILABLE) {
602     pop_data->cmd_uidl = CMD_UNKNOWN;
603     pop_data->cmd_top = CMD_UNKNOWN;
604
605     m_strcpy(buf, sizeof(buf), "AUTH\r\n");
606     if (pop_fetch_data (pop_data, buf, NULL, fetch_auth, pop_data) == PQ_NOT_CONNECTED)
607       return PQ_NOT_CONNECTED;
608   }
609
610   /* Check capabilities */
611   if (mode == 2) {
612     char *msg = NULL;
613
614     if (!pop_data->expire)
615       msg = _("Unable to leave messages on server.");
616     if (pop_data->cmd_top == CMD_NOT_AVAILABLE)
617       msg = _("Command TOP is not supported by server.");
618     if (pop_data->cmd_uidl == CMD_NOT_AVAILABLE)
619       msg = _("Command UIDL is not supported by server.");
620     if (msg && pop_data->cmd_capa != CMD_AVAILABLE) {
621       mutt_error (msg);
622       return PQ_ERR;
623     }
624     pop_data->capabilities = 1;
625   }
626
627   return PQ_OK;
628 }
629
630 /* given an POP mailbox name, return host, port, username and password */
631 static int pop_parse_path (const char *path, ACCOUNT * act)
632 {
633   ciss_url_t url;
634   char *c;
635   int ret = -1;
636
637   /* Defaults */
638   act->flags = 0;
639   act->port = POP_PORT;
640   act->type = M_ACCT_TYPE_POP;
641
642   c = m_strdup(path);
643   url_parse_ciss (&url, c);
644
645   if (url.scheme == U_POP || url.scheme == U_POPS) {
646     if (url.scheme == U_POPS) {
647       act->flags |= M_ACCT_SSL;
648       act->port = POP_SSL_PORT;
649     }
650
651     if ((!url.path || !*url.path) && mutt_account_fromurl (act, &url) == 0)
652       ret = 0;
653   }
654
655   p_delete(&c);
656   return ret;
657 }
658
659 /*
660  * Open connection and authenticate
661  *  0 - successful,
662  * -1 - conection lost,
663  * -2 - invalid command or execution error,
664  * -3 - authentication canceled.
665 */
666 static pop_query_status pop_open_connection (pop_data_t * pop_data)
667 {
668   pop_query_status ret;
669   int n, size;
670   char buf[LONG_STRING];
671
672   ret = pop_connect(pop_data);
673   if (ret != PQ_OK) {
674     mutt_sleep (2);
675     return ret;
676   }
677
678   ret = pop_capabilities (pop_data, 0);
679   if (ret == PQ_NOT_CONNECTED)
680     goto err_conn;
681   if (ret == PQ_ERR) {
682     mutt_sleep (2);
683     return PQ_ERR;
684   }
685
686   /* Attempt STLS if available and desired. */
687   if (!pop_data->conn->ssf && (pop_data->cmd_stls || mod_ssl.force_tls)) {
688     if (mod_ssl.force_tls)
689       pop_data->use_stls = 2;
690     if (pop_data->use_stls == 0) {
691       pop_data->use_stls = 1;
692       if (mod_ssl.starttls)
693         pop_data->use_stls = 2;
694     }
695     if (pop_data->use_stls == 2) {
696       m_strcpy(buf, sizeof(buf), "STLS\r\n");
697       ret = pop_query (pop_data, buf, sizeof (buf));
698       if (ret == PQ_NOT_CONNECTED)
699         goto err_conn;
700       if (ret != PQ_OK) {
701         mutt_error ("%s", pop_data->err_msg);
702         mutt_sleep (2);
703       }
704       else if (mutt_ssl_starttls (pop_data->conn))
705       {
706         mutt_error (_("Could not negotiate TLS connection"));
707         mutt_sleep (2);
708         return PQ_ERR;
709       }
710       else {
711         /* recheck capabilities after STLS completes */
712         ret = pop_capabilities (pop_data, 1);
713         if (ret == PQ_NOT_CONNECTED)
714           goto err_conn;
715         if (ret == PQ_ERR) {
716           mutt_sleep (2);
717           return PQ_ERR;
718         }
719       }
720     }
721   }
722
723   if (mod_ssl.force_tls && !pop_data->conn->ssf) {
724     mutt_error _("Encrypted connection unavailable");
725     mutt_sleep (1);
726     return -2;
727   }
728
729   ret = pop_authenticate (pop_data);
730   if (ret == PQ_NOT_CONNECTED)
731     goto err_conn;
732   if (ret == PFD_FUNCT_ERROR)
733     mutt_clear_error ();
734   if (ret != PQ_OK)
735     return ret;
736
737   /* recheck capabilities after authentication */
738   ret = pop_capabilities (pop_data, 2);
739   if (ret == PQ_NOT_CONNECTED)
740     goto err_conn;
741   if (ret == PQ_ERR) {
742     mutt_sleep (2);
743     return PQ_ERR;
744   }
745
746   /* get total size of mailbox */
747   m_strcpy(buf, sizeof(buf), "STAT\r\n");
748   ret = pop_query (pop_data, buf, sizeof (buf));
749   if (ret == PQ_NOT_CONNECTED)
750     goto err_conn;
751   if (ret == PQ_ERR) {
752     mutt_error ("%s", pop_data->err_msg);
753     mutt_sleep (2);
754     return ret;
755   }
756
757   sscanf (buf, "+OK %u %u", &n, &size);
758   pop_data->size = size;
759   return PQ_OK;
760
761 err_conn:
762   pop_data->status = POP_DISCONNECTED;
763   mutt_error _("Server closed connection!");
764
765   mutt_sleep (2);
766   return PQ_NOT_CONNECTED;
767 }
768
769 /* find message with this UIDL and set refno */
770 static int check_uidl (char *line, void *data)
771 {
772   int i, idx;
773   CONTEXT *ctx = (CONTEXT *)data;
774
775   sscanf (line, "%u %s", &idx, line);
776   for (i = 0; i < ctx->msgcount; i++) {
777     if (!m_strcmp(ctx->hdrs[i]->data, line)) {
778       ctx->hdrs[i]->refno = idx;
779       break;
780     }
781   }
782
783   return 0;
784 }
785
786 /* reconnect and verify indexes if connection was lost */
787 static pop_query_status pop_reconnect (CONTEXT * ctx)
788 {
789   pop_query_status ret;
790   pop_data_t *pop_data = (pop_data_t *) ctx->data;
791   progress_t bar;
792
793   if (pop_data->status == POP_CONNECTED)
794     return PQ_OK;
795   if (pop_data->status == POP_BYE)
796     return PQ_NOT_CONNECTED;
797
798   for (;;) {
799     mutt_socket_close (pop_data->conn);
800
801     ret = pop_open_connection(pop_data);
802     if (ret == PQ_OK) {
803       int i;
804
805       bar.msg = _("Verifying message indexes...");
806       bar.size = 0;
807       mutt_progress_bar (&bar, 0);
808
809       for (i = 0; i < ctx->msgcount; i++)
810         ctx->hdrs[i]->refno = -1;
811
812       ret = pop_fetch_data(pop_data, "UIDL\r\n", &bar, check_uidl, ctx);
813       if (ret == PQ_ERR) {
814         mutt_error ("%s", pop_data->err_msg);
815         mutt_sleep (2);
816       }
817     }
818     if (ret == PQ_OK)
819       return PQ_OK;
820
821     pop_logout (ctx);
822
823     if (ret == PQ_ERR)
824       return PQ_NOT_CONNECTED;
825
826     if (query_quadoption (OPT_POPRECONNECT,
827                           _("Connection lost. Reconnect to POP server?")) !=
828         M_YES)
829       return PQ_NOT_CONNECTED;
830   }
831 }
832
833 /* write line to file */
834 static int fetch_message (char *line, void *file)
835 {
836   FILE *f = (FILE *) file;
837
838   fputs (line, f);
839   if (fputc ('\n', f) == EOF)
840     return -1;
841
842   return 0;
843 }
844
845 /*
846  * Read header
847  * returns:
848  *  0 on success
849  * -1 - conection lost,
850  * -2 - invalid command or execution error,
851  * -3 - error writing to tempfile
852  */
853 static pop_query_status pop_read_header (pop_data_t * pop_data, HEADER * h)
854 {
855   FILE *f;
856   int idx;
857   pop_query_status ret;
858   long length;
859   char buf[LONG_STRING];
860   char tempfile[_POSIX_PATH_MAX];
861
862   f = m_tempfile(tempfile, sizeof(tempfile), NONULL(MCore.tmpdir), NULL);
863   if (!f) {
864     mutt_error(_("Could not create temporary file"));
865     return PFD_FUNCT_ERROR;
866   }
867
868   snprintf (buf, sizeof (buf), "LIST %d\r\n", h->refno);
869   ret = pop_query (pop_data, buf, sizeof (buf));
870   if (ret == PQ_OK) {
871     sscanf (buf, "+OK %d %ld", &idx, &length);
872
873     snprintf (buf, sizeof (buf), "TOP %d 0\r\n", h->refno);
874     ret = pop_fetch_data (pop_data, buf, NULL, fetch_message, f);
875
876     if (pop_data->cmd_top == CMD_UNKNOWN) {
877       if (ret == PQ_OK) {
878         pop_data->cmd_top = CMD_AVAILABLE;
879       }
880
881       if (ret == PQ_ERR) {
882         pop_data->cmd_top = CMD_NOT_AVAILABLE;
883         snprintf (pop_data->err_msg, sizeof (pop_data->err_msg),
884                   _("Command TOP is not supported by server."));
885       }
886     }
887   }
888
889   switch (ret) {
890   case PQ_OK:
891     {
892       rewind (f);
893       h->env = mutt_read_rfc822_header (f, h, 0, 0);
894       h->content->length = length - h->content->offset + 1;
895       rewind (f);
896       while (!feof (f)) {
897         h->content->length--;
898         fgets (buf, sizeof (buf), f);
899       }
900       break;
901     }
902   case PQ_ERR:
903     {
904       mutt_error ("%s", pop_data->err_msg);
905       break;
906     }
907   case PFD_FUNCT_ERROR:
908     {
909       mutt_error _("Can't write header to temporary file!");
910
911       break;
912     }
913   case PQ_NOT_CONNECTED:
914     {
915       mutt_error _("Can't fetch header: Not connected!");
916       break;
917     }
918   }
919
920   m_fclose(&f);
921   unlink (tempfile);
922   return ret;
923 }
924
925 /* parse UIDL */
926 static int fetch_uidl (char *line, void *data)
927 {
928   int i, idx;
929   CONTEXT *ctx = (CONTEXT *) data;
930   pop_data_t *pop_data = (pop_data_t *) ctx->data;
931
932   sscanf (line, "%d %s", &idx, line);
933   for (i = 0; i < ctx->msgcount; i++)
934     if (!m_strcmp(line, ctx->hdrs[i]->data))
935       break;
936
937   if (i == ctx->msgcount) {
938     if (i >= ctx->hdrmax)
939       mx_alloc_memory (ctx);
940
941     ctx->msgcount++;
942     ctx->hdrs[i] = header_new();
943     ctx->hdrs[i]->data = m_strdup(line);
944   }
945   else if (ctx->hdrs[i]->index != idx - 1)
946     pop_data->clear_cache = 1;
947
948   ctx->hdrs[i]->refno = idx;
949   ctx->hdrs[i]->index = idx - 1;
950
951   return 0;
952 }
953
954 /*
955  * Read headers
956  * returns:
957  *  0 on success
958  * -1 - conection lost,
959  * -2 - invalid command or execution error,
960  * -3 - error writing to tempfile
961  */
962 static int pop_fetch_headers (CONTEXT * ctx)
963 {
964   int i, old_count, new_count;
965   pop_query_status ret;
966   pop_data_t *pop_data = (pop_data_t *) ctx->data;
967
968   time (&pop_data->check_time);
969   pop_data->clear_cache = 0;
970
971   for (i = 0; i < ctx->msgcount; i++)
972     ctx->hdrs[i]->refno = -1;
973
974   old_count = ctx->msgcount;
975   ret = pop_fetch_data (pop_data, "UIDL\r\n", NULL, fetch_uidl, ctx);
976   new_count = ctx->msgcount;
977   ctx->msgcount = old_count;
978
979   if (pop_data->cmd_uidl == CMD_UNKNOWN) {
980     if (ret == PQ_OK) {
981       pop_data->cmd_uidl = CMD_AVAILABLE;
982     }
983
984     if (ret == PQ_ERR && pop_data->cmd_uidl == CMD_UNKNOWN) {
985       pop_data->cmd_uidl = CMD_NOT_AVAILABLE;
986
987       snprintf (pop_data->err_msg, sizeof (pop_data->err_msg),
988                 _("Command UIDL is not supported by server."));
989     }
990   }
991
992   if (ret == PQ_OK) {
993     for (i = 0; i < old_count; i++)
994       if (ctx->hdrs[i]->refno == -1)
995         ctx->hdrs[i]->deleted = 1;
996
997     for (i = old_count; i < new_count; i++) {
998       mutt_message (_("Fetching message headers... [%d/%d]"),
999                     i + 1 - old_count, new_count - old_count);
1000
1001       ret = pop_read_header (pop_data, ctx->hdrs[i]);
1002       if (ret != PQ_OK)
1003         break;
1004
1005       ctx->msgcount++;
1006     }
1007
1008     if (i > old_count)
1009       mx_update_context (ctx, i - old_count);
1010   }
1011
1012   if (ret != PQ_OK) {
1013     for (i = ctx->msgcount; i < new_count; i++)
1014       header_delete(&ctx->hdrs[i]);
1015     return ret;
1016   }
1017
1018   mutt_clear_error ();
1019   return (new_count - old_count);
1020 }
1021
1022 /* delete all cached messages */
1023 static void pop_clear_cache (pop_data_t * pop_data)
1024 {
1025   int i;
1026
1027   if (!pop_data->clear_cache)
1028     return;
1029
1030   for (i = 0; i < POP_CACHE_LEN; i++) {
1031     if (pop_data->cache[i].path) {
1032       unlink (pop_data->cache[i].path);
1033       p_delete(&pop_data->cache[i].path);
1034     }
1035   }
1036 }
1037
1038 /* pop_mx functions {{{ */
1039
1040 static int pop_is_magic(const char* path, struct stat* st)
1041 {
1042     url_scheme_t s = url_check_scheme(NONULL(path));
1043     return s == U_POP || s == U_POPS ? M_POP : -1;
1044 }
1045
1046 static int pop_open_mailbox (CONTEXT * ctx)
1047 {
1048   int ret;
1049   char buf[LONG_STRING];
1050   CONNECTION *conn;
1051   ACCOUNT act;
1052   pop_data_t *pop_data;
1053   ciss_url_t url;
1054
1055   if (pop_parse_path (ctx->path, &act)) {
1056     mutt_error (_("%s is an invalid POP path"), ctx->path);
1057     mutt_sleep (2);
1058     return -1;
1059   }
1060
1061   mutt_account_tourl (&act, &url);
1062   url.path = NULL;
1063   url_ciss_tostring (&url, buf, sizeof (buf), 0);
1064   conn = mutt_conn_find (NULL, &act);
1065   if (!conn)
1066     return -1;
1067
1068   p_delete(&ctx->path);
1069   ctx->path = m_strdup(buf);
1070
1071   pop_data = p_new(pop_data_t, 1);
1072   pop_data->conn = conn;
1073   ctx->data = pop_data;
1074
1075   if (pop_open_connection(pop_data) != PQ_OK)
1076     return -1;
1077
1078   conn->data = pop_data;
1079
1080   for (;;) {
1081     if (pop_reconnect (ctx) != PQ_OK)
1082       return -1;
1083
1084     ctx->size = pop_data->size;
1085
1086     mutt_message _("Fetching list of messages...");
1087
1088     ret = pop_fetch_headers (ctx);
1089
1090     if (ret >= 0)
1091       return 0;
1092
1093     if (ret < -1) {
1094       mutt_sleep (2);
1095       return -1;
1096     }
1097   }
1098 }
1099
1100 static int pop_acl_check(CONTEXT *ctx, int bit)
1101 {
1102     switch (bit) {
1103       case ACL_DELETE:    /* (un)deletion */
1104       case ACL_SEEN:      /* mark as read */
1105         return 1;
1106       case ACL_INSERT:    /* editing messages */
1107       case ACL_WRITE:     /* change importance */
1108       default:
1109         return 0;
1110     }
1111 }
1112
1113 static int pop_check_mailbox(CONTEXT * ctx, int *index_hint, int unused)
1114 {
1115   int ret;
1116   pop_data_t *pop_data = (pop_data_t *) ctx->data;
1117
1118   if ((pop_data->check_time + PopCheckTimeout) > time (NULL))
1119     return 0;
1120
1121   pop_logout (ctx);
1122
1123   mutt_socket_close (pop_data->conn);
1124
1125   if (pop_open_connection (pop_data) < 0)
1126     return -1;
1127
1128   ctx->size = pop_data->size;
1129
1130   mutt_message _("Checking for new messages...");
1131
1132   ret = pop_fetch_headers (ctx);
1133   pop_clear_cache (pop_data);
1134
1135   if (ret < 0)
1136     return -1;
1137
1138   if (ret > 0)
1139     return M_NEW_MAIL;
1140
1141   return 0;
1142 }
1143
1144 static void pop_close_mailbox (CONTEXT * ctx)
1145 {
1146   pop_data_t *pop_data = (pop_data_t *) ctx->data;
1147
1148   if (!pop_data)
1149     return;
1150
1151   pop_logout (ctx);
1152
1153   if (pop_data->status != POP_NONE)
1154     mutt_socket_close (pop_data->conn);
1155
1156   pop_data->status = POP_NONE;
1157
1158   pop_data->clear_cache = 1;
1159   pop_clear_cache (pop_data);
1160
1161   if (!pop_data->conn->data)
1162     mutt_socket_free (pop_data->conn);
1163
1164   return;
1165 }
1166
1167 static int pop_sync_mailbox(CONTEXT * ctx, int unused, int *index_hint)
1168 {
1169   int i;
1170   pop_query_status ret;
1171   char buf[LONG_STRING];
1172   pop_data_t *pop_data = (pop_data_t *) ctx->data;
1173
1174   pop_data->check_time = 0;
1175
1176   for (;;) {
1177     if (pop_reconnect (ctx) != PQ_OK)
1178       return PQ_NOT_CONNECTED;
1179
1180     mutt_message (_("Marking %d messages deleted..."), ctx->deleted);
1181
1182     for (i = 0, ret = 0; ret == 0 && i < ctx->msgcount; i++) {
1183       if (ctx->hdrs[i]->deleted) {
1184         snprintf (buf, sizeof (buf), "DELE %d\r\n", ctx->hdrs[i]->refno);
1185         ret = pop_query (pop_data, buf, sizeof (buf));
1186       }
1187     }
1188
1189     if (ret == PQ_OK) {
1190       m_strcpy(buf, sizeof(buf), "QUIT\r\n");
1191       ret = pop_query (pop_data, buf, sizeof (buf));
1192     }
1193
1194     if (ret == PQ_OK) {
1195       pop_data->clear_cache = 1;
1196       pop_clear_cache (pop_data);
1197       pop_data->status = POP_DISCONNECTED;
1198       return PQ_OK;
1199     }
1200
1201     if (ret == PQ_ERR) {
1202       mutt_error ("%s", pop_data->err_msg);
1203       mutt_sleep (2);
1204       return PQ_NOT_CONNECTED;
1205     }
1206   }
1207 }
1208
1209 /* }}} */
1210
1211 mx_t const pop_mx = {
1212     M_POP,
1213     0,
1214     pop_is_magic,
1215     NULL,
1216     NULL,
1217     pop_open_mailbox,
1218     NULL,
1219     pop_acl_check,
1220     pop_check_mailbox,
1221     pop_close_mailbox,
1222     pop_sync_mailbox,
1223     NULL,
1224 };
1225
1226 /* public API {{{ */
1227
1228 void pop_fetch_mail (void)
1229 {
1230   char buffer[LONG_STRING];
1231   char msgbuf[STRING];
1232   char *url, *p;
1233   int i, delanswer, last = 0, msgs, bytes, rset = 0;
1234   pop_query_status ret;
1235   CONNECTION *conn;
1236   CONTEXT ctx;
1237   MESSAGE *msg = NULL;
1238   ACCOUNT act;
1239   pop_data_t *pop_data;
1240   ssize_t plen;
1241
1242   if (!PopHost) {
1243     mutt_error _("POP host is not defined.");
1244
1245     return;
1246   }
1247
1248   plen = m_strlen(PopHost) + 7;
1249   url = p = p_new(char, plen);
1250   if (url_check_scheme (PopHost) == U_UNKNOWN) {
1251     plen -= m_strcpy(url, plen, "pop://");
1252     p += plen;
1253   }
1254   m_strcpy(p, plen, PopHost);
1255
1256   ret = pop_parse_path (url, &act);
1257   p_delete(&url);
1258   if (ret) {
1259     mutt_error (_("%s is an invalid POP path"), PopHost);
1260     return;
1261   }
1262
1263   conn = mutt_conn_find (NULL, &act);
1264   if (!conn)
1265     return;
1266
1267   pop_data = p_new(pop_data_t, 1);
1268   pop_data->conn = conn;
1269
1270   if (pop_open_connection (pop_data) < 0) {
1271     mutt_socket_free (pop_data->conn);
1272     p_delete(&pop_data);
1273     return;
1274   }
1275
1276   conn->data = pop_data;
1277
1278   mutt_message _("Checking for new messages...");
1279
1280   /* find out how many messages are in the mailbox. */
1281   m_strcpy(buffer, sizeof(buffer), "STAT\r\n");
1282   ret = pop_query (pop_data, buffer, sizeof (buffer));
1283   if (ret == PQ_NOT_CONNECTED)
1284     goto fail;
1285   if (ret == PQ_ERR) {
1286     mutt_error ("%s", pop_data->err_msg);
1287     goto finish;
1288   }
1289
1290   sscanf (buffer, "+OK %d %d", &msgs, &bytes);
1291
1292   /* only get unread messages */
1293   if (msgs > 0 && option (OPTPOPLAST)) {
1294     m_strcpy(buffer, sizeof(buffer), "LAST\r\n");
1295     ret = pop_query (pop_data, buffer, sizeof (buffer));
1296     if (ret == PQ_NOT_CONNECTED)
1297       goto fail;
1298     if (ret == PQ_OK)
1299       sscanf (buffer, "+OK %d", &last);
1300   }
1301
1302   if (msgs <= last) {
1303     mutt_message _("No new mail in POP mailbox.");
1304
1305     goto finish;
1306   }
1307
1308   if (mx_open_mailbox (NONULL (Spoolfile), M_APPEND, &ctx) == NULL)
1309     goto finish;
1310
1311   delanswer =
1312     query_quadoption (OPT_POPDELETE, _("Delete messages from server?"));
1313
1314   snprintf (msgbuf, sizeof (msgbuf), _("Reading new messages (%d bytes)..."),
1315             bytes);
1316   mutt_message ("%s", msgbuf);
1317
1318   for (i = last + 1; i <= msgs; i++) {
1319     if ((msg = mx_open_new_message (&ctx, NULL, M_ADD_FROM)) == NULL)
1320       ret = -3;
1321     else {
1322       snprintf (buffer, sizeof (buffer), "RETR %d\r\n", i);
1323       ret = pop_fetch_data (pop_data, buffer, NULL, fetch_message, msg->fp);
1324       if (ret == PFD_FUNCT_ERROR)
1325         rset = 1;
1326
1327       if (ret == PQ_OK && mx_commit_message (msg, &ctx) != 0) {
1328         rset = 1;
1329         ret = PFD_FUNCT_ERROR;
1330       }
1331
1332       mx_close_message (&msg);
1333     }
1334
1335     if (ret == PQ_OK && delanswer == M_YES) {
1336       /* delete the message on the server */
1337       snprintf (buffer, sizeof (buffer), "DELE %d\r\n", i);
1338       ret = pop_query (pop_data, buffer, sizeof (buffer));
1339     }
1340
1341     if (ret == PQ_NOT_CONNECTED) {
1342       mx_close_mailbox (&ctx, NULL);
1343       goto fail;
1344     }
1345     if (ret == PQ_ERR) {
1346       mutt_error ("%s", pop_data->err_msg);
1347       break;
1348     }
1349     if (ret == -3) { /* this is -3 when ret != 0, because it will keep the value from before *gna* */
1350       mutt_error _("Error while writing mailbox!");
1351
1352       break;
1353     }
1354
1355     mutt_message (_("%s [%d of %d messages read]"), msgbuf, i - last,
1356                   msgs - last);
1357   }
1358
1359   mx_close_mailbox (&ctx, NULL);
1360
1361   if (rset) {
1362     /* make sure no messages get deleted */
1363     m_strcpy(buffer, sizeof(buffer), "RSET\r\n");
1364     if (pop_query (pop_data, buffer, sizeof (buffer)) == PQ_NOT_CONNECTED)
1365       goto fail;
1366   }
1367
1368 finish:
1369   /* exit gracefully */
1370   m_strcpy(buffer, sizeof(buffer), "QUIT\r\n");
1371   if (pop_query (pop_data, buffer, sizeof (buffer)) == PQ_NOT_CONNECTED)
1372     goto fail;
1373   mutt_socket_close (conn);
1374   p_delete(&pop_data);
1375   return;
1376
1377 fail:
1378   mutt_error _("Server closed connection!");
1379   mutt_socket_close (conn);
1380   p_delete(&pop_data);
1381 }
1382
1383 /* fetch message from POP server */
1384 int pop_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno)
1385 {
1386   int ret;
1387   void *uidl;
1388   char buf[LONG_STRING];
1389   char path[_POSIX_PATH_MAX];
1390   progress_t bar;
1391   pop_data_t *pop_data = (pop_data_t *) ctx->data;
1392   POP_CACHE *cache;
1393   HEADER *h = ctx->hdrs[msgno];
1394
1395   /* see if we already have the message in our cache */
1396   cache = &pop_data->cache[h->index % POP_CACHE_LEN];
1397
1398   if (cache->path) {
1399     if (cache->index == h->index) {
1400       /* yes, so just return a pointer to the message */
1401       msg->fp = fopen (cache->path, "r");
1402       if (msg->fp)
1403         return 0;
1404
1405       mutt_perror (cache->path);
1406       mutt_sleep (2);
1407       return -1;
1408     }
1409     else {
1410       /* clear the previous entry */
1411       unlink (cache->path);
1412       p_delete(&cache->path);
1413     }
1414   }
1415
1416   for (;;) {
1417     if (pop_reconnect (ctx) != PQ_OK)
1418       return -1;
1419
1420     /* verify that massage index is correct */
1421     if (h->refno < 0) {
1422       mutt_error
1423         _("The message index is incorrect. Try reopening the mailbox.");
1424       mutt_sleep (2);
1425       return -1;
1426     }
1427
1428     bar.size = h->content->length + h->content->offset - 1;
1429     bar.msg = _("Fetching message...");
1430     mutt_progress_bar (&bar, 0);
1431
1432     msg->fp = m_tempfile(path, sizeof(path), NONULL(MCore.tmpdir), NULL);
1433     if (!msg->fp) {
1434       mutt_error(_("Could not create temporary file"));
1435       mutt_sleep(2);
1436       return -1;
1437     }
1438
1439     snprintf (buf, sizeof (buf), "RETR %d\r\n", h->refno);
1440
1441     ret = pop_fetch_data (pop_data, buf, &bar, fetch_message, msg->fp);
1442     if (ret == PQ_OK)
1443       break;
1444
1445     m_fclose(&msg->fp);
1446     unlink (path);
1447
1448     if (ret == PQ_ERR) {
1449       mutt_error ("%s", pop_data->err_msg);
1450       mutt_sleep (2);
1451       return -1;
1452     }
1453
1454     if (ret == PFD_FUNCT_ERROR) {
1455       mutt_error _("Can't write message to temporary file!");
1456
1457       mutt_sleep (2);
1458       return -1;
1459     }
1460   }
1461
1462   /* Update the header information.  Previously, we only downloaded a
1463    * portion of the headers, those required for the main display.
1464    */
1465   cache->index = h->index;
1466   cache->path = m_strdup(path);
1467   rewind (msg->fp);
1468   uidl = h->data;
1469   envelope_delete(&h->env);
1470   h->env = mutt_read_rfc822_header (msg->fp, h, 0, 0);
1471   h->data = uidl;
1472   h->lines = 0;
1473   fgets (buf, sizeof (buf), msg->fp);
1474   while (!feof (msg->fp)) {
1475     ctx->hdrs[msgno]->lines++;
1476     fgets (buf, sizeof (buf), msg->fp);
1477   }
1478
1479   h->content->length = ftello (msg->fp) - h->content->offset;
1480
1481   /* This needs to be done in case this is a multipart message */
1482   h->security = crypt_query (h->content);
1483
1484   mutt_clear_error ();
1485   rewind (msg->fp);
1486
1487   return 0;
1488 }
1489
1490 /* }}} */