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