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