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