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