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