More unused
[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 == 2) {
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       else {
686         /* recheck capabilities after STLS completes */
687         ret = pop_capabilities (pop_data, 1);
688         if (ret == PQ_NOT_CONNECTED)
689           goto err_conn;
690         if (ret == PQ_ERR) {
691           mutt_sleep (2);
692           return PQ_ERR;
693         }
694       }
695     }
696   }
697
698   if (mod_ssl.force_tls && !pop_data->conn->ssf) {
699     mutt_error _("Encrypted connection unavailable");
700     mutt_sleep (1);
701     return -2;
702   }
703
704   ret = pop_authenticate (pop_data);
705   if (ret == PQ_NOT_CONNECTED)
706     goto err_conn;
707   if (ret == PFD_FUNCT_ERROR)
708     mutt_clear_error ();
709   if (ret != PQ_OK)
710     return ret;
711
712   /* recheck capabilities after authentication */
713   ret = pop_capabilities (pop_data, 2);
714   if (ret == PQ_NOT_CONNECTED)
715     goto err_conn;
716   if (ret == PQ_ERR) {
717     mutt_sleep (2);
718     return PQ_ERR;
719   }
720
721   /* get total size of mailbox */
722   ret = pop_query(pop_data, buf, sizeof(buf), "STAT");
723   if (ret == PQ_NOT_CONNECTED)
724     goto err_conn;
725   if (ret == PQ_ERR) {
726     mutt_error ("%s", pop_data->err_msg);
727     mutt_sleep (2);
728     return ret;
729   }
730
731   sscanf (buf, "+OK %u %u", &n, &size);
732   pop_data->size = size;
733   return PQ_OK;
734
735 err_conn:
736   pop_data->status = POP_DISCONNECTED;
737   mutt_error _("Server closed connection!");
738
739   mutt_sleep (2);
740   return PQ_NOT_CONNECTED;
741 }
742
743 /* find message with this UIDL and set refno */
744 static int check_uidl (char *line, void *data)
745 {
746   int i, idx;
747   CONTEXT *ctx = (CONTEXT *)data;
748
749   sscanf (line, "%u %s", &idx, line);
750   for (i = 0; i < ctx->msgcount; i++) {
751     if (!m_strcmp(ctx->hdrs[i]->data, line)) {
752       ctx->hdrs[i]->refno = idx;
753       break;
754     }
755   }
756
757   return 0;
758 }
759
760 /* reconnect and verify indexes if connection was lost */
761 static pop_query_status pop_reconnect (CONTEXT * ctx)
762 {
763   pop_query_status ret;
764   pop_data_t *pop_data = (pop_data_t *) ctx->data;
765   progress_t bar;
766
767   if (pop_data->status == POP_CONNECTED)
768     return PQ_OK;
769   if (pop_data->status == POP_BYE)
770     return PQ_NOT_CONNECTED;
771
772   for (;;) {
773     mutt_socket_close (pop_data->conn);
774
775     ret = pop_open_connection(pop_data);
776     if (ret == PQ_OK) {
777       int i;
778
779       bar.msg = _("Verifying message indexes...");
780       bar.size = 0;
781       mutt_progress_bar (&bar, 0);
782
783       for (i = 0; i < ctx->msgcount; i++)
784         ctx->hdrs[i]->refno = -1;
785
786       ret = pop_fetch_data(pop_data, "UIDL\r\n", &bar, check_uidl, ctx);
787       if (ret == PQ_ERR) {
788         mutt_error ("%s", pop_data->err_msg);
789         mutt_sleep (2);
790       }
791     }
792     if (ret == PQ_OK)
793       return PQ_OK;
794
795     pop_logout (ctx);
796
797     if (ret == PQ_ERR)
798       return PQ_NOT_CONNECTED;
799
800     if (query_quadoption (OPT_POPRECONNECT,
801                           _("Connection lost. Reconnect to POP server?")) !=
802         M_YES)
803       return PQ_NOT_CONNECTED;
804   }
805 }
806
807 /* write line to file */
808 static int fetch_message (char *line, void *file)
809 {
810   FILE *f = (FILE *) file;
811
812   fputs (line, f);
813   if (fputc ('\n', f) == EOF)
814     return -1;
815
816   return 0;
817 }
818
819 /*
820  * Read header
821  * returns:
822  *  0 on success
823  * -1 - conection lost,
824  * -2 - invalid command or execution error,
825  * -3 - error writing to tempfile
826  */
827 static pop_query_status pop_read_header (pop_data_t * pop_data, HEADER * h)
828 {
829   FILE *f;
830   int idx;
831   pop_query_status ret;
832   long length;
833   char buf[LONG_STRING];
834
835   f = tmpfile();
836   if (!f) {
837     mutt_error(_("Could not create temporary file"));
838     return PFD_FUNCT_ERROR;
839   }
840
841   ret = pop_query(pop_data, buf, sizeof(buf), "LIST %d", h->refno);
842   if (ret == PQ_OK) {
843     sscanf (buf, "+OK %d %ld", &idx, &length);
844
845     snprintf (buf, sizeof (buf), "TOP %d 0\r\n", h->refno);
846     ret = pop_fetch_data(pop_data, buf, NULL, fetch_message, f);
847
848     if (pop_data->cmd_top == CMD_UNKNOWN) {
849       if (ret == PQ_OK) {
850         pop_data->cmd_top = CMD_AVAILABLE;
851       }
852
853       if (ret == PQ_ERR) {
854         pop_data->cmd_top = CMD_NOT_AVAILABLE;
855         snprintf (pop_data->err_msg, sizeof (pop_data->err_msg),
856                   _("Command TOP is not supported by server."));
857       }
858     }
859   }
860
861   switch (ret) {
862   case PQ_OK:
863     {
864       rewind (f);
865       h->env = mutt_read_rfc822_header (f, h, 0, 0);
866       h->content->length = length - h->content->offset + 1;
867       rewind (f);
868       while (!feof (f)) {
869         h->content->length--;
870         fgets (buf, sizeof (buf), f);
871       }
872       break;
873     }
874   case PQ_ERR:
875     {
876       mutt_error ("%s", pop_data->err_msg);
877       break;
878     }
879   case PFD_FUNCT_ERROR:
880     {
881       mutt_error _("Can't write header to temporary file!");
882
883       break;
884     }
885   case PQ_NOT_CONNECTED:
886     {
887       mutt_error _("Can't fetch header: Not connected!");
888       break;
889     }
890   }
891
892   m_fclose(&f);
893   return ret;
894 }
895
896 /* parse UIDL */
897 static int fetch_uidl (char *line, void *data)
898 {
899   int i, idx;
900   CONTEXT *ctx = (CONTEXT *) data;
901   pop_data_t *pop_data = (pop_data_t *) ctx->data;
902
903   sscanf (line, "%d %s", &idx, line);
904   for (i = 0; i < ctx->msgcount; i++)
905     if (!m_strcmp(line, ctx->hdrs[i]->data))
906       break;
907
908   if (i == ctx->msgcount) {
909     if (i >= ctx->hdrmax)
910       mx_alloc_memory (ctx);
911
912     ctx->msgcount++;
913     ctx->hdrs[i] = header_new();
914     ctx->hdrs[i]->data = m_strdup(line);
915   }
916   else if (ctx->hdrs[i]->index != idx - 1)
917     pop_data->clear_cache = 1;
918
919   ctx->hdrs[i]->refno = idx;
920   ctx->hdrs[i]->index = idx - 1;
921
922   return 0;
923 }
924
925 /*
926  * Read headers
927  * returns:
928  *  0 on success
929  * -1 - conection lost,
930  * -2 - invalid command or execution error,
931  * -3 - error writing to tempfile
932  */
933 static int pop_fetch_headers (CONTEXT * ctx)
934 {
935   int i, old_count, new_count;
936   pop_query_status ret;
937   pop_data_t *pop_data = (pop_data_t *) ctx->data;
938
939   time (&pop_data->check_time);
940   pop_data->clear_cache = 0;
941
942   for (i = 0; i < ctx->msgcount; i++)
943     ctx->hdrs[i]->refno = -1;
944
945   old_count = ctx->msgcount;
946   ret = pop_fetch_data(pop_data, "UIDL\r\n", NULL, fetch_uidl, ctx);
947   new_count = ctx->msgcount;
948   ctx->msgcount = old_count;
949
950   if (pop_data->cmd_uidl == CMD_UNKNOWN) {
951     if (ret == PQ_OK) {
952       pop_data->cmd_uidl = CMD_AVAILABLE;
953     }
954
955     if (ret == PQ_ERR && pop_data->cmd_uidl == CMD_UNKNOWN) {
956       pop_data->cmd_uidl = CMD_NOT_AVAILABLE;
957
958       snprintf (pop_data->err_msg, sizeof (pop_data->err_msg),
959                 _("Command UIDL is not supported by server."));
960     }
961   }
962
963   if (ret == PQ_OK) {
964     for (i = 0; i < old_count; i++)
965       if (ctx->hdrs[i]->refno == -1)
966         ctx->hdrs[i]->deleted = 1;
967
968     for (i = old_count; i < new_count; i++) {
969       mutt_message (_("Fetching message headers... [%d/%d]"),
970                     i + 1 - old_count, new_count - old_count);
971
972       ret = pop_read_header (pop_data, ctx->hdrs[i]);
973       if (ret != PQ_OK)
974         break;
975
976       ctx->msgcount++;
977     }
978
979     if (i > old_count)
980       mx_update_context (ctx, i - old_count);
981   }
982
983   if (ret != PQ_OK) {
984     for (i = ctx->msgcount; i < new_count; i++)
985       header_delete(&ctx->hdrs[i]);
986     return ret;
987   }
988
989   mutt_clear_error ();
990   return new_count - old_count;
991 }
992
993 /* delete all cached messages */
994 static void pop_clear_cache (pop_data_t * pop_data)
995 {
996   int i;
997
998   if (!pop_data->clear_cache)
999     return;
1000
1001   for (i = 0; i < POP_CACHE_LEN; i++) {
1002     if (pop_data->cache[i].path) {
1003       unlink (pop_data->cache[i].path);
1004       p_delete(&pop_data->cache[i].path);
1005     }
1006   }
1007 }
1008
1009 /* pop_mx functions {{{ */
1010
1011 static int pop_is_magic(const char* path, struct stat* st)
1012 {
1013     url_scheme_t s = url_check_scheme(NONULL(path));
1014     return s == U_POP || s == U_POPS ? M_POP : -1;
1015 }
1016
1017 static int pop_open_mailbox (CONTEXT * ctx)
1018 {
1019   int ret;
1020   char buf[LONG_STRING];
1021   CONNECTION *conn;
1022   ACCOUNT act;
1023   pop_data_t *pop_data;
1024   ciss_url_t url;
1025
1026   if (pop_parse_path (ctx->path, &act)) {
1027     mutt_error (_("%s is an invalid POP path"), ctx->path);
1028     mutt_sleep (2);
1029     return -1;
1030   }
1031
1032   mutt_account_tourl (&act, &url);
1033   url.path = NULL;
1034   url_ciss_tostring (&url, buf, sizeof (buf), 0);
1035   conn = mutt_conn_find (NULL, &act);
1036   if (!conn)
1037     return -1;
1038
1039   p_delete(&ctx->path);
1040   ctx->path = m_strdup(buf);
1041
1042   pop_data = p_new(pop_data_t, 1);
1043   pop_data->conn = conn;
1044   ctx->data = pop_data;
1045
1046   if (pop_open_connection(pop_data) != PQ_OK)
1047     return -1;
1048
1049   conn->data = pop_data;
1050
1051   for (;;) {
1052     if (pop_reconnect (ctx) != PQ_OK)
1053       return -1;
1054
1055     mutt_message _("Fetching list of messages...");
1056     ctx->size = pop_data->size;
1057     ret = pop_fetch_headers (ctx);
1058
1059     if (ret >= 0)
1060       return 0;
1061
1062     if (ret < -1) {
1063       mutt_sleep (2);
1064       return -1;
1065     }
1066   }
1067 }
1068
1069 static int pop_acl_check(CONTEXT *ctx, int bit)
1070 {
1071     switch (bit) {
1072       case ACL_DELETE:    /* (un)deletion */
1073       case ACL_SEEN:      /* mark as read */
1074         return 1;
1075       case ACL_INSERT:    /* editing messages */
1076       case ACL_WRITE:     /* change importance */
1077       default:
1078         return 0;
1079     }
1080 }
1081
1082 static int pop_check_mailbox(CONTEXT * ctx, int *index_hint, int unused)
1083 {
1084   int ret;
1085   pop_data_t *pop_data = (pop_data_t *) ctx->data;
1086
1087   if ((pop_data->check_time + PopCheckTimeout) > time (NULL))
1088     return 0;
1089
1090   pop_logout (ctx);
1091
1092   mutt_socket_close (pop_data->conn);
1093
1094   if (pop_open_connection (pop_data) < 0)
1095     return -1;
1096
1097   mutt_message _("Checking for new messages...");
1098   ctx->size = pop_data->size;
1099   ret = pop_fetch_headers (ctx);
1100   pop_clear_cache (pop_data);
1101
1102   if (ret < 0)
1103     return -1;
1104
1105   if (ret > 0)
1106     return M_NEW_MAIL;
1107
1108   return 0;
1109 }
1110
1111 static void pop_close_mailbox (CONTEXT * ctx)
1112 {
1113   pop_data_t *pop_data = (pop_data_t *) ctx->data;
1114
1115   if (!pop_data)
1116     return;
1117
1118   pop_logout (ctx);
1119
1120   if (pop_data->status != POP_NONE)
1121     mutt_socket_close (pop_data->conn);
1122
1123   pop_data->status = POP_NONE;
1124
1125   pop_data->clear_cache = 1;
1126   pop_clear_cache (pop_data);
1127
1128   if (!pop_data->conn->data)
1129     mutt_socket_free (pop_data->conn);
1130
1131   return;
1132 }
1133
1134 static int pop_sync_mailbox(CONTEXT * ctx, int unused, int *index_hint)
1135 {
1136   int i;
1137   pop_query_status ret;
1138   char buf[LONG_STRING];
1139   pop_data_t *pop_data = (pop_data_t *) ctx->data;
1140
1141   pop_data->check_time = 0;
1142
1143   for (;;) {
1144     if (pop_reconnect (ctx) != PQ_OK)
1145       return PQ_NOT_CONNECTED;
1146
1147     mutt_message (_("Marking %d messages deleted..."), ctx->deleted);
1148
1149     for (i = 0, ret = 0; ret == 0 && i < ctx->msgcount; i++) {
1150       if (ctx->hdrs[i]->deleted) {
1151         ret = pop_query(pop_data, buf, sizeof(buf), "DELE %d",
1152                         ctx->hdrs[i]->refno);
1153       }
1154     }
1155
1156     if (ret == PQ_OK) {
1157       ret = pop_query(pop_data, buf, sizeof(buf), "QUIT");
1158     }
1159
1160     if (ret == PQ_OK) {
1161       pop_data->clear_cache = 1;
1162       pop_clear_cache (pop_data);
1163       pop_data->status = POP_DISCONNECTED;
1164       return PQ_OK;
1165     }
1166
1167     if (ret == PQ_ERR) {
1168       mutt_error ("%s", pop_data->err_msg);
1169       mutt_sleep (2);
1170       return PQ_NOT_CONNECTED;
1171     }
1172   }
1173 }
1174
1175 /* fetch message from POP server */
1176 static int pop_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno)
1177 {
1178   int ret;
1179   void *uidl;
1180   char buf[LONG_STRING];
1181   char path[_POSIX_PATH_MAX];
1182   progress_t bar;
1183   pop_data_t *pop_data = (pop_data_t *) ctx->data;
1184   POP_CACHE *cache;
1185   HEADER *h = ctx->hdrs[msgno];
1186
1187   /* see if we already have the message in our cache */
1188   cache = &pop_data->cache[h->index % POP_CACHE_LEN];
1189
1190   if (cache->path) {
1191     if (cache->index == h->index) {
1192       /* yes, so just return a pointer to the message */
1193       msg->fp = fopen (cache->path, "r");
1194       if (msg->fp)
1195         return 0;
1196
1197       mutt_perror (cache->path);
1198       mutt_sleep (2);
1199       return -1;
1200     }
1201     else {
1202       /* clear the previous entry */
1203       unlink (cache->path);
1204       p_delete(&cache->path);
1205     }
1206   }
1207
1208   for (;;) {
1209     if (pop_reconnect (ctx) != PQ_OK)
1210       return -1;
1211
1212     /* verify that massage index is correct */
1213     if (h->refno < 0) {
1214       mutt_error
1215         _("The message index is incorrect. Try reopening the mailbox.");
1216       mutt_sleep (2);
1217       return -1;
1218     }
1219
1220     bar.size = h->content->length + h->content->offset - 1;
1221     bar.msg = _("Fetching message...");
1222     mutt_progress_bar (&bar, 0);
1223
1224     msg->fp = m_tempfile(path, sizeof(path), NONULL(mod_core.tmpdir), NULL);
1225     if (!msg->fp) {
1226       mutt_error(_("Could not create temporary file"));
1227       mutt_sleep(2);
1228       return -1;
1229     }
1230
1231     snprintf(buf, sizeof(buf), "RETR %d\r\n", h->refno);
1232     ret = pop_fetch_data(pop_data, buf, &bar, fetch_message, msg->fp);
1233     if (ret == PQ_OK)
1234       break;
1235
1236     m_fclose(&msg->fp);
1237     unlink (path);
1238
1239     if (ret == PQ_ERR) {
1240       mutt_error ("%s", pop_data->err_msg);
1241       mutt_sleep (2);
1242       return -1;
1243     }
1244
1245     if (ret == PFD_FUNCT_ERROR) {
1246       mutt_error _("Can't write message to temporary file!");
1247
1248       mutt_sleep (2);
1249       return -1;
1250     }
1251   }
1252
1253   /* Update the header information.  Previously, we only downloaded a
1254    * portion of the headers, those required for the main display.
1255    */
1256   cache->index = h->index;
1257   cache->path = m_strdup(path);
1258   rewind (msg->fp);
1259   uidl = h->data;
1260   envelope_delete(&h->env);
1261   h->env = mutt_read_rfc822_header (msg->fp, h, 0, 0);
1262   h->data = uidl;
1263   h->lines = 0;
1264   fgets (buf, sizeof (buf), msg->fp);
1265   while (!feof (msg->fp)) {
1266     ctx->hdrs[msgno]->lines++;
1267     fgets (buf, sizeof (buf), msg->fp);
1268   }
1269
1270   h->content->length = ftello (msg->fp) - h->content->offset;
1271
1272   /* This needs to be done in case this is a multipart message */
1273   h->security = crypt_query (h->content);
1274
1275   mutt_clear_error ();
1276   rewind (msg->fp);
1277
1278   return 0;
1279 }
1280
1281 /* }}} */
1282
1283 mx_t const pop_mx = {
1284     M_POP,
1285     0,
1286     pop_is_magic,
1287     NULL,
1288     NULL,
1289     pop_open_mailbox,
1290     NULL,
1291     pop_fetch_message,
1292     pop_acl_check,
1293     pop_check_mailbox,
1294     pop_close_mailbox,
1295     pop_sync_mailbox,
1296     NULL,
1297 };
1298
1299 /* public API {{{ */
1300
1301 void pop_fetch_mail (void)
1302 {
1303   char buffer[LONG_STRING];
1304   char msgbuf[STRING];
1305   char *url, *p;
1306   int i, delanswer, last = 0, msgs, bytes, rset = 0;
1307   pop_query_status ret;
1308   CONNECTION *conn;
1309   CONTEXT ctx;
1310   MESSAGE *msg = NULL;
1311   ACCOUNT act;
1312   pop_data_t *pop_data;
1313   ssize_t plen;
1314
1315   if (m_strisempty(PopHost)) {
1316     mutt_error _("POP host is not defined.");
1317     return;
1318   }
1319
1320   plen = m_strlen(PopHost) + 7;
1321   url  = p = p_new(char, plen);
1322   if (url_check_scheme (PopHost) == U_UNKNOWN) {
1323       snprintf(p, plen, "pop://%s", PopHost);
1324   } else {
1325       m_strcpy(p, plen, PopHost);
1326   }
1327
1328   ret = pop_parse_path (url, &act);
1329   p_delete(&url);
1330   if (ret) {
1331     mutt_error (_("%s is an invalid POP path"), PopHost);
1332     return;
1333   }
1334
1335   conn = mutt_conn_find (NULL, &act);
1336   if (!conn)
1337     return;
1338
1339   pop_data = p_new(pop_data_t, 1);
1340   pop_data->conn = conn;
1341
1342   if (pop_open_connection (pop_data) < 0) {
1343     mutt_socket_free (pop_data->conn);
1344     p_delete(&pop_data);
1345     return;
1346   }
1347
1348   conn->data = pop_data;
1349
1350   mutt_message _("Checking for new messages...");
1351
1352   /* find out how many messages are in the mailbox. */
1353   ret = pop_query(pop_data, buffer, sizeof(buffer), "STAT");
1354   if (ret == PQ_NOT_CONNECTED)
1355     goto fail;
1356   if (ret == PQ_ERR) {
1357     mutt_error ("%s", pop_data->err_msg);
1358     goto finish;
1359   }
1360
1361   sscanf (buffer, "+OK %d %d", &msgs, &bytes);
1362
1363   /* only get unread messages */
1364   if (msgs > 0 && option (OPTPOPLAST)) {
1365     ret = pop_query(pop_data, buffer, sizeof(buffer), "LAST");
1366     if (ret == PQ_NOT_CONNECTED)
1367       goto fail;
1368     if (ret == PQ_OK)
1369       sscanf (buffer, "+OK %d", &last);
1370   }
1371
1372   if (msgs <= last) {
1373     mutt_message _("No new mail in POP mailbox.");
1374
1375     goto finish;
1376   }
1377
1378   if (mx_open_mailbox (NONULL (Spoolfile), M_APPEND, &ctx) == NULL)
1379     goto finish;
1380
1381   delanswer =
1382     query_quadoption (OPT_POPDELETE, _("Delete messages from server?"));
1383
1384   snprintf (msgbuf, sizeof (msgbuf), _("Reading new messages (%d bytes)..."),
1385             bytes);
1386   mutt_message ("%s", msgbuf);
1387
1388   for (i = last + 1; i <= msgs; i++) {
1389     if ((msg = mx_open_new_message (&ctx, NULL, M_ADD_FROM)) == NULL)
1390       ret = -3;
1391     else {
1392       snprintf (buffer, sizeof (buffer), "RETR %d\r\n", i);
1393       ret = pop_fetch_data(pop_data, buffer, NULL, fetch_message, msg->fp);
1394       if (ret == PFD_FUNCT_ERROR)
1395         rset = 1;
1396
1397       if (ret == PQ_OK && mx_commit_message (msg, &ctx) != 0) {
1398         rset = 1;
1399         ret = PFD_FUNCT_ERROR;
1400       }
1401
1402       mx_close_message (&msg);
1403     }
1404
1405     if (ret == PQ_OK && delanswer == M_YES) {
1406       ret = pop_query(pop_data, buffer, sizeof(buffer), "DELE %d", i);
1407     }
1408
1409     if (ret == PQ_NOT_CONNECTED) {
1410       mx_close_mailbox (&ctx, NULL);
1411       goto fail;
1412     }
1413     if (ret == PQ_ERR) {
1414       mutt_error ("%s", pop_data->err_msg);
1415       break;
1416     }
1417     if (ret == -3) { /* this is -3 when ret != 0, because it will keep the value from before *gna* */
1418       mutt_error _("Error while writing mailbox!");
1419
1420       break;
1421     }
1422
1423     mutt_message (_("%s [%d of %d messages read]"), msgbuf, i - last,
1424                   msgs - last);
1425   }
1426
1427   mx_close_mailbox (&ctx, NULL);
1428
1429   if (rset) {
1430     /* make sure no messages get deleted */
1431     if (pop_query(pop_data, buffer, sizeof(buffer), "RSET") ==
1432         PQ_NOT_CONNECTED)
1433       goto fail;
1434   }
1435
1436 finish:
1437   if (pop_query(pop_data, buffer, sizeof(buffer), "QUIT") == PQ_NOT_CONNECTED)
1438     goto fail;
1439   mutt_socket_close (conn);
1440   p_delete(&pop_data);
1441   return;
1442
1443 fail:
1444   mutt_error _("Server closed connection!");
1445   mutt_socket_close (conn);
1446   p_delete(&pop_data);
1447 }
1448
1449 /* }}} */