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