1 /**
2  * @file
3  * POP network mailbox
4  *
5  * @authors
6  * Copyright (C) 2000-2002 Vsevolod Volkov <vvv@mutt.org.ua>
7  * Copyright (C) 2006-2007,2009 Rocco Rutte <pdmef@gmx.net>
8  * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
9  *
10  * @copyright
11  * This program is free software: you can redistribute it and/or modify it under
12  * the terms of the GNU General Public License as published by the Free Software
13  * Foundation, either version 2 of the License, or (at your option) any later
14  * version.
15  *
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License along with
22  * this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 /**
26  * @page pop_pop POP network mailbox
27  *
28  * POP network mailbox
29  *
30  * Implementation: #MxPopOps
31  */
32 
33 #include "config.h"
34 #include <errno.h>
35 #include <limits.h>
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include "private.h"
42 #include "mutt/lib.h"
43 #include "config/lib.h"
44 #include "email/lib.h"
45 #include "core/lib.h"
46 #include "conn/lib.h"
47 #include "lib.h"
48 #include "bcache/lib.h"
49 #include "ncrypt/lib.h"
50 #include "progress/lib.h"
51 #include "question/lib.h"
52 #include "adata.h"
53 #include "edata.h"
54 #include "hook.h"
55 #include "mutt_account.h"
56 #include "mutt_header.h"
57 #include "mutt_logging.h"
58 #include "mutt_socket.h"
59 #include "muttlib.h"
60 #include "mx.h"
61 #ifdef ENABLE_NLS
62 #include <libintl.h>
63 #endif
64 #ifdef USE_HCACHE
65 #include "hcache/lib.h"
66 #endif
67 
68 struct BodyCache;
69 struct stat;
70 
71 #define HC_FNAME "neomutt" /* filename for hcache as POP lacks paths */
72 #define HC_FEXT "hcache"   /* extension for hcache as POP lacks paths */
73 
74 /**
75  * cache_id - Make a message-cache-compatible id
76  * @param id POP message id
77  * @retval ptr Sanitised string
78  *
79  * The POP message id may contain '/' and other awkward characters.
80  *
81  * @note This function returns a pointer to a static buffer.
82  */
cache_id(const char * id)83 static const char *cache_id(const char *id)
84 {
85   static char clean[128];
86   mutt_str_copy(clean, id, sizeof(clean));
87   mutt_file_sanitize_filename(clean, true);
88   return clean;
89 }
90 
91 /**
92  * fetch_message - Write line to file - Implements ::pop_fetch_t - @ingroup pop_fetch_api
93  * @param line String to write
94  * @param data FILE pointer to write to
95  * @retval  0 Success
96  * @retval -1 Failure
97  */
fetch_message(const char * line,void * data)98 static int fetch_message(const char *line, void *data)
99 {
100   FILE *fp = data;
101 
102   fputs(line, fp);
103   if (fputc('\n', fp) == EOF)
104     return -1;
105 
106   return 0;
107 }
108 
109 /**
110  * pop_read_header - Read header
111  * @param adata POP Account data
112  * @param e     Email
113  * @retval  0 Success
114  * @retval -1 Connection lost
115  * @retval -2 Invalid command or execution error
116  * @retval -3 Error writing to tempfile
117  */
pop_read_header(struct PopAccountData * adata,struct Email * e)118 static int pop_read_header(struct PopAccountData *adata, struct Email *e)
119 {
120   FILE *fp = mutt_file_mkstemp();
121   if (!fp)
122   {
123     mutt_perror(_("Can't create temporary file"));
124     return -3;
125   }
126 
127   int index = 0;
128   size_t length = 0;
129   char buf[1024];
130 
131   struct PopEmailData *edata = pop_edata_get(e);
132 
133   snprintf(buf, sizeof(buf), "LIST %d\r\n", edata->refno);
134   int rc = pop_query(adata, buf, sizeof(buf));
135   if (rc == 0)
136   {
137     sscanf(buf, "+OK %d %zu", &index, &length);
138 
139     snprintf(buf, sizeof(buf), "TOP %d 0\r\n", edata->refno);
140     rc = pop_fetch_data(adata, buf, NULL, fetch_message, fp);
141 
142     if (adata->cmd_top == 2)
143     {
144       if (rc == 0)
145       {
146         adata->cmd_top = 1;
147 
148         mutt_debug(LL_DEBUG1, "set TOP capability\n");
149       }
150 
151       if (rc == -2)
152       {
153         adata->cmd_top = 0;
154 
155         mutt_debug(LL_DEBUG1, "unset TOP capability\n");
156         snprintf(adata->err_msg, sizeof(adata->err_msg), "%s",
157                  _("Command TOP is not supported by server"));
158       }
159     }
160   }
161 
162   switch (rc)
163   {
164     case 0:
165     {
166       rewind(fp);
167       e->env = mutt_rfc822_read_header(fp, e, false, false);
168       e->body->length = length - e->body->offset + 1;
169       rewind(fp);
170       while (!feof(fp))
171       {
172         e->body->length--;
173         fgets(buf, sizeof(buf), fp);
174       }
175       break;
176     }
177     case -2:
178     {
179       mutt_error("%s", adata->err_msg);
180       break;
181     }
182     case -3:
183     {
184       mutt_error(_("Can't write header to temporary file"));
185       break;
186     }
187   }
188 
189   mutt_file_fclose(&fp);
190   return rc;
191 }
192 
193 /**
194  * fetch_uidl - Parse UIDL - Implements ::pop_fetch_t - @ingroup pop_fetch_api
195  * @param line String to parse
196  * @param data Mailbox
197  * @retval  0 Success
198  * @retval -1 Failure
199  */
fetch_uidl(const char * line,void * data)200 static int fetch_uidl(const char *line, void *data)
201 {
202   struct Mailbox *m = data;
203   struct PopAccountData *adata = pop_adata_get(m);
204   char *endp = NULL;
205 
206   errno = 0;
207   int index = strtol(line, &endp, 10);
208   if (errno)
209     return -1;
210   while (*endp == ' ')
211     endp++;
212   line = endp;
213 
214   /* uid must be at least be 1 byte */
215   if (strlen(line) == 0)
216     return -1;
217 
218   int i;
219   for (i = 0; i < m->msg_count; i++)
220   {
221     struct PopEmailData *edata = pop_edata_get(m->emails[i]);
222     if (mutt_str_equal(line, edata->uid))
223       break;
224   }
225 
226   if (i == m->msg_count)
227   {
228     mutt_debug(LL_DEBUG1, "new header %d %s\n", index, line);
229 
230     if (i >= m->email_max)
231       mx_alloc_memory(m);
232 
233     m->msg_count++;
234     m->emails[i] = email_new();
235 
236     m->emails[i]->edata = pop_edata_new(line);
237     m->emails[i]->edata_free = pop_edata_free;
238   }
239   else if (m->emails[i]->index != index - 1)
240     adata->clear_cache = true;
241 
242   m->emails[i]->index = index - 1;
243 
244   struct PopEmailData *edata = pop_edata_get(m->emails[i]);
245   edata->refno = index;
246 
247   return 0;
248 }
249 
250 /**
251  * msg_cache_check - Check the Body Cache for an ID - Implements ::bcache_list_t - @ingroup bcache_list_api
252  */
msg_cache_check(const char * id,struct BodyCache * bcache,void * data)253 static int msg_cache_check(const char *id, struct BodyCache *bcache, void *data)
254 {
255   struct Mailbox *m = data;
256   if (!m)
257     return -1;
258 
259   struct PopAccountData *adata = pop_adata_get(m);
260   if (!adata)
261     return -1;
262 
263 #ifdef USE_HCACHE
264   /* keep hcache file if hcache == bcache */
265   if (strcmp(HC_FNAME "." HC_FEXT, id) == 0)
266     return 0;
267 #endif
268 
269   for (int i = 0; i < m->msg_count; i++)
270   {
271     struct PopEmailData *edata = pop_edata_get(m->emails[i]);
272     /* if the id we get is known for a header: done (i.e. keep in cache) */
273     if (edata->uid && mutt_str_equal(edata->uid, id))
274       return 0;
275   }
276 
277   /* message not found in context -> remove it from cache
278    * return the result of bcache, so we stop upon its first error */
279   return mutt_bcache_del(bcache, cache_id(id));
280 }
281 
282 #ifdef USE_HCACHE
283 /**
284  * pop_hcache_namer - Create a header cache filename for a POP mailbox - Implements ::hcache_namer_t - @ingroup hcache_namer_api
285  */
pop_hcache_namer(const char * path,struct Buffer * dest)286 static void pop_hcache_namer(const char *path, struct Buffer *dest)
287 {
288   mutt_buffer_printf(dest, "%s." HC_FEXT, path);
289 }
290 
291 /**
292  * pop_hcache_open - Open the header cache
293  * @param adata POP Account data
294  * @param path  Path to the mailbox
295  * @retval ptr Header cache
296  */
pop_hcache_open(struct PopAccountData * adata,const char * path)297 static struct HeaderCache *pop_hcache_open(struct PopAccountData *adata, const char *path)
298 {
299   const char *const c_header_cache =
300       cs_subset_path(NeoMutt->sub, "header_cache");
301   if (!adata || !adata->conn)
302     return mutt_hcache_open(c_header_cache, path, NULL);
303 
304   struct Url url = { 0 };
305   char p[1024];
306 
307   mutt_account_tourl(&adata->conn->account, &url);
308   url.path = HC_FNAME;
309   url_tostring(&url, p, sizeof(p), U_PATH);
310   return mutt_hcache_open(c_header_cache, p, pop_hcache_namer);
311 }
312 #endif
313 
314 /**
315  * pop_fetch_headers - Read headers
316  * @param m Mailbox
317  * @retval  0 Success
318  * @retval -1 Connection lost
319  * @retval -2 Invalid command or execution error
320  * @retval -3 Error writing to tempfile
321  */
pop_fetch_headers(struct Mailbox * m)322 static int pop_fetch_headers(struct Mailbox *m)
323 {
324   if (!m)
325     return -1;
326 
327   struct PopAccountData *adata = pop_adata_get(m);
328   struct Progress *progress = NULL;
329 
330 #ifdef USE_HCACHE
331   struct HeaderCache *hc = pop_hcache_open(adata, mailbox_path(m));
332 #endif
333 
334   adata->check_time = mutt_date_epoch();
335   adata->clear_cache = false;
336 
337   for (int i = 0; i < m->msg_count; i++)
338   {
339     struct PopEmailData *edata = pop_edata_get(m->emails[i]);
340     edata->refno = -1;
341   }
342 
343   const int old_count = m->msg_count;
344   int rc = pop_fetch_data(adata, "UIDL\r\n", NULL, fetch_uidl, m);
345   const int new_count = m->msg_count;
346   m->msg_count = old_count;
347 
348   if (adata->cmd_uidl == 2)
349   {
350     if (rc == 0)
351     {
352       adata->cmd_uidl = 1;
353 
354       mutt_debug(LL_DEBUG1, "set UIDL capability\n");
355     }
356 
357     if ((rc == -2) && (adata->cmd_uidl == 2))
358     {
359       adata->cmd_uidl = 0;
360 
361       mutt_debug(LL_DEBUG1, "unset UIDL capability\n");
362       snprintf(adata->err_msg, sizeof(adata->err_msg), "%s",
363                _("Command UIDL is not supported by server"));
364     }
365   }
366 
367   if (m->verbose)
368   {
369     progress = progress_new(_("Fetching message headers..."),
370                             MUTT_PROGRESS_READ, new_count - old_count);
371   }
372 
373   if (rc == 0)
374   {
375     int i, deleted;
376     for (i = 0, deleted = 0; i < old_count; i++)
377     {
378       struct PopEmailData *edata = pop_edata_get(m->emails[i]);
379       if (edata->refno == -1)
380       {
381         m->emails[i]->deleted = true;
382         deleted++;
383       }
384     }
385     if (deleted > 0)
386     {
387       mutt_error(
388           ngettext("%d message has been lost. Try reopening the mailbox.",
389                    "%d messages have been lost. Try reopening the mailbox.", deleted),
390           deleted);
391     }
392 
393     bool hcached = false;
394     for (i = old_count; i < new_count; i++)
395     {
396       if (m->verbose)
397         progress_update(progress, i + 1 - old_count, -1);
398       struct PopEmailData *edata = pop_edata_get(m->emails[i]);
399 #ifdef USE_HCACHE
400       struct HCacheEntry hce = mutt_hcache_fetch(hc, edata->uid, strlen(edata->uid), 0);
401       if (hce.email)
402       {
403         /* Detach the private data */
404         m->emails[i]->edata = NULL;
405 
406         int index = m->emails[i]->index;
407         /* - POP dynamically numbers headers and relies on e->refno
408          *   to map messages; so restore header and overwrite restored
409          *   refno with current refno, same for index
410          * - e->data needs to a separate pointer as it's driver-specific
411          *   data freed separately elsewhere
412          *   (the old e->data should point inside a malloc'd block from
413          *   hcache so there shouldn't be a memleak here) */
414         email_free(&m->emails[i]);
415         m->emails[i] = hce.email;
416         m->emails[i]->index = index;
417 
418         /* Reattach the private data */
419         m->emails[i]->edata = edata;
420         m->emails[i]->edata_free = pop_edata_free;
421         rc = 0;
422         hcached = true;
423       }
424       else
425 #endif
426           if ((rc = pop_read_header(adata, m->emails[i])) < 0)
427         break;
428 #ifdef USE_HCACHE
429       else
430       {
431         mutt_hcache_store(hc, edata->uid, strlen(edata->uid), m->emails[i], 0);
432       }
433 #endif
434 
435       /* faked support for flags works like this:
436        * - if 'hcached' is true, we have the message in our hcache:
437        *        - if we also have a body: read
438        *        - if we don't have a body: old
439        *          (if $mark_old is set which is maybe wrong as
440        *          $mark_old should be considered for syncing the
441        *          folder and not when opening it XXX)
442        * - if 'hcached' is false, we don't have the message in our hcache:
443        *        - if we also have a body: read
444        *        - if we don't have a body: new */
445       const bool bcached =
446           (mutt_bcache_exists(adata->bcache, cache_id(edata->uid)) == 0);
447       m->emails[i]->old = false;
448       m->emails[i]->read = false;
449       if (hcached)
450       {
451         const bool c_mark_old = cs_subset_bool(NeoMutt->sub, "mark_old");
452         if (bcached)
453           m->emails[i]->read = true;
454         else if (c_mark_old)
455           m->emails[i]->old = true;
456       }
457       else
458       {
459         if (bcached)
460           m->emails[i]->read = true;
461       }
462 
463       m->msg_count++;
464     }
465   }
466   progress_free(&progress);
467 
468 #ifdef USE_HCACHE
469   mutt_hcache_close(hc);
470 #endif
471 
472   if (rc < 0)
473   {
474     for (int i = m->msg_count; i < new_count; i++)
475       email_free(&m->emails[i]);
476     return rc;
477   }
478 
479   /* after putting the result into our structures,
480    * clean up cache, i.e. wipe messages deleted outside
481    * the availability of our cache */
482   const bool c_message_cache_clean =
483       cs_subset_bool(NeoMutt->sub, "message_cache_clean");
484   if (c_message_cache_clean)
485     mutt_bcache_list(adata->bcache, msg_cache_check, m);
486 
487   mutt_clear_error();
488   return new_count - old_count;
489 }
490 
491 /**
492  * pop_clear_cache - Delete all cached messages
493  * @param adata POP Account data
494  */
pop_clear_cache(struct PopAccountData * adata)495 static void pop_clear_cache(struct PopAccountData *adata)
496 {
497   if (!adata->clear_cache)
498     return;
499 
500   mutt_debug(LL_DEBUG1, "delete cached messages\n");
501 
502   for (int i = 0; i < POP_CACHE_LEN; i++)
503   {
504     if (adata->cache[i].path)
505     {
506       unlink(adata->cache[i].path);
507       FREE(&adata->cache[i].path);
508     }
509   }
510 }
511 
512 /**
513  * pop_fetch_mail - Fetch messages and save them in $spool_file
514  */
pop_fetch_mail(void)515 void pop_fetch_mail(void)
516 {
517   const char *const c_pop_host = cs_subset_string(NeoMutt->sub, "pop_host");
518   if (!c_pop_host)
519   {
520     mutt_error(_("POP host is not defined"));
521     return;
522   }
523 
524   char buf[1024];
525   char msgbuf[128];
526   int last = 0, msgs, bytes, rset = 0, ret;
527   struct ConnAccount cac = { { 0 } };
528 
529   char *p = mutt_mem_calloc(strlen(c_pop_host) + 7, sizeof(char));
530   char *url = p;
531   if (url_check_scheme(c_pop_host) == U_UNKNOWN)
532   {
533     strcpy(url, "pop://");
534     p = strchr(url, '\0');
535   }
536   strcpy(p, c_pop_host);
537 
538   ret = pop_parse_path(url, &cac);
539   FREE(&url);
540   if (ret)
541   {
542     mutt_error(_("%s is an invalid POP path"), c_pop_host);
543     return;
544   }
545 
546   struct Connection *conn = mutt_conn_find(&cac);
547   if (!conn)
548     return;
549 
550   struct PopAccountData *adata = pop_adata_new();
551   adata->conn = conn;
552 
553   if (pop_open_connection(adata) < 0)
554   {
555     //XXX mutt_socket_free(adata->conn);
556     pop_adata_free((void **) &adata);
557     return;
558   }
559 
560   mutt_message(_("Checking for new messages..."));
561 
562   /* find out how many messages are in the mailbox. */
563   mutt_str_copy(buf, "STAT\r\n", sizeof(buf));
564   ret = pop_query(adata, buf, sizeof(buf));
565   if (ret == -1)
566     goto fail;
567   if (ret == -2)
568   {
569     mutt_error("%s", adata->err_msg);
570     goto finish;
571   }
572 
573   sscanf(buf, "+OK %d %d", &msgs, &bytes);
574 
575   /* only get unread messages */
576   const bool c_pop_last = cs_subset_bool(NeoMutt->sub, "pop_last");
577   if ((msgs > 0) && c_pop_last)
578   {
579     mutt_str_copy(buf, "LAST\r\n", sizeof(buf));
580     ret = pop_query(adata, buf, sizeof(buf));
581     if (ret == -1)
582       goto fail;
583     if (ret == 0)
584       sscanf(buf, "+OK %d", &last);
585   }
586 
587   if (msgs <= last)
588   {
589     mutt_message(_("No new mail in POP mailbox"));
590     goto finish;
591   }
592 
593   const char *const c_spool_file = cs_subset_string(NeoMutt->sub, "spool_file");
594   struct Mailbox *m_spool = mx_path_resolve(c_spool_file);
595 
596   if (!mx_mbox_open(m_spool, MUTT_OPEN_NO_FLAGS))
597   {
598     mailbox_free(&m_spool);
599     goto finish;
600   }
601   bool old_append = m_spool->append;
602   m_spool->append = true;
603 
604   const enum QuadOption c_pop_delete =
605       cs_subset_quad(NeoMutt->sub, "pop_delete");
606   enum QuadOption delanswer =
607       query_quadoption(c_pop_delete, _("Delete messages from server?"));
608 
609   snprintf(msgbuf, sizeof(msgbuf),
610            ngettext("Reading new messages (%d byte)...",
611                     "Reading new messages (%d bytes)...", bytes),
612            bytes);
613   mutt_message("%s", msgbuf);
614 
615   for (int i = last + 1; i <= msgs; i++)
616   {
617     struct Message *msg = mx_msg_open_new(m_spool, NULL, MUTT_ADD_FROM);
618     if (msg)
619     {
620       snprintf(buf, sizeof(buf), "RETR %d\r\n", i);
621       ret = pop_fetch_data(adata, buf, NULL, fetch_message, msg->fp);
622       if (ret == -3)
623         rset = 1;
624 
625       if ((ret == 0) && (mx_msg_commit(m_spool, msg) != 0))
626       {
627         rset = 1;
628         ret = -3;
629       }
630 
631       mx_msg_close(m_spool, &msg);
632     }
633     else
634     {
635       ret = -3;
636     }
637 
638     if ((ret == 0) && (delanswer == MUTT_YES))
639     {
640       /* delete the message on the server */
641       snprintf(buf, sizeof(buf), "DELE %d\r\n", i);
642       ret = pop_query(adata, buf, sizeof(buf));
643     }
644 
645     if (ret == -1)
646     {
647       m_spool->append = old_append;
648       mx_mbox_close(m_spool);
649       goto fail;
650     }
651     if (ret == -2)
652     {
653       mutt_error("%s", adata->err_msg);
654       break;
655     }
656     if (ret == -3)
657     {
658       mutt_error(_("Error while writing mailbox"));
659       break;
660     }
661 
662     /* L10N: The plural is picked by the second numerical argument, i.e.
663        the %d right before 'messages', i.e. the total number of messages. */
664     mutt_message(ngettext("%s [%d of %d message read]",
665                           "%s [%d of %d messages read]", msgs - last),
666                  msgbuf, i - last, msgs - last);
667   }
668 
669   m_spool->append = old_append;
670   mx_mbox_close(m_spool);
671 
672   if (rset)
673   {
674     /* make sure no messages get deleted */
675     mutt_str_copy(buf, "RSET\r\n", sizeof(buf));
676     if (pop_query(adata, buf, sizeof(buf)) == -1)
677       goto fail;
678   }
679 
680 finish:
681   /* exit gracefully */
682   mutt_str_copy(buf, "QUIT\r\n", sizeof(buf));
683   if (pop_query(adata, buf, sizeof(buf)) == -1)
684     goto fail;
685   mutt_socket_close(conn);
686   FREE(&conn);
687   pop_adata_free((void **) &adata);
688   return;
689 
690 fail:
691   mutt_error(_("Server closed connection"));
692   mutt_socket_close(conn);
693   pop_adata_free((void **) &adata);
694 }
695 
696 /**
697  * pop_ac_owns_path - Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() - @ingroup mx_ac_owns_path
698  */
pop_ac_owns_path(struct Account * a,const char * path)699 static bool pop_ac_owns_path(struct Account *a, const char *path)
700 {
701   struct Url *url = url_parse(path);
702   if (!url)
703     return false;
704 
705   struct PopAccountData *adata = a->adata;
706   struct ConnAccount *cac = &adata->conn->account;
707 
708   const bool ret = mutt_istr_equal(url->host, cac->host) &&
709                    mutt_istr_equal(url->user, cac->user);
710   url_free(&url);
711   return ret;
712 }
713 
714 /**
715  * pop_ac_add - Add a Mailbox to an Account - Implements MxOps::ac_add() - @ingroup mx_ac_add
716  */
pop_ac_add(struct Account * a,struct Mailbox * m)717 static bool pop_ac_add(struct Account *a, struct Mailbox *m)
718 {
719   if (a->adata)
720     return true;
721 
722   struct ConnAccount cac = { { 0 } };
723   struct PopAccountData *adata = pop_adata_new();
724   a->adata = adata;
725   a->adata_free = pop_adata_free;
726 
727   if (pop_parse_path(mailbox_path(m), &cac))
728   {
729     mutt_error(_("%s is an invalid POP path"), mailbox_path(m));
730     return false;
731   }
732 
733   adata->conn = mutt_conn_new(&cac);
734   if (!adata->conn)
735   {
736     pop_adata_free((void **) &adata);
737     return false;
738   }
739 
740   return true;
741 }
742 
743 /**
744  * pop_mbox_open - Open a Mailbox - Implements MxOps::mbox_open() - @ingroup mx_mbox_open
745  *
746  * Fetch only headers
747  */
pop_mbox_open(struct Mailbox * m)748 static enum MxOpenReturns pop_mbox_open(struct Mailbox *m)
749 {
750   if (!m->account)
751     return MX_OPEN_ERROR;
752 
753   char buf[PATH_MAX];
754   struct ConnAccount cac = { { 0 } };
755   struct Url url = { 0 };
756 
757   if (pop_parse_path(mailbox_path(m), &cac))
758   {
759     mutt_error(_("%s is an invalid POP path"), mailbox_path(m));
760     return MX_OPEN_ERROR;
761   }
762 
763   mutt_account_tourl(&cac, &url);
764   url.path = NULL;
765   url_tostring(&url, buf, sizeof(buf), U_NO_FLAGS);
766 
767   mutt_buffer_strcpy(&m->pathbuf, buf);
768   mutt_str_replace(&m->realpath, mailbox_path(m));
769 
770   struct PopAccountData *adata = m->account->adata;
771   if (!adata)
772   {
773     adata = pop_adata_new();
774     m->account->adata = adata;
775     m->account->adata_free = pop_adata_free;
776   }
777 
778   struct Connection *conn = adata->conn;
779   if (!conn)
780   {
781     adata->conn = mutt_conn_new(&cac);
782     conn = adata->conn;
783     if (!conn)
784       return MX_OPEN_ERROR;
785   }
786 
787   if (conn->fd < 0)
788     mutt_account_hook(m->realpath);
789 
790   if (pop_open_connection(adata) < 0)
791     return MX_OPEN_ERROR;
792 
793   adata->bcache = mutt_bcache_open(&cac, NULL);
794 
795   /* init (hard-coded) ACL rights */
796   m->rights = MUTT_ACL_SEEN | MUTT_ACL_DELETE;
797 #ifdef USE_HCACHE
798   /* flags are managed using header cache, so it only makes sense to
799    * enable them in that case */
800   m->rights |= MUTT_ACL_WRITE;
801 #endif
802 
803   while (true)
804   {
805     if (pop_reconnect(m) < 0)
806       return MX_OPEN_ERROR;
807 
808     m->size = adata->size;
809 
810     mutt_message(_("Fetching list of messages..."));
811 
812     const int rc = pop_fetch_headers(m);
813 
814     if (rc >= 0)
815       return MX_OPEN_OK;
816 
817     if (rc < -1)
818       return MX_OPEN_ERROR;
819   }
820 }
821 
822 /**
823  * pop_mbox_check - Check for new mail - Implements MxOps::mbox_check() - @ingroup mx_mbox_check
824  */
pop_mbox_check(struct Mailbox * m)825 static enum MxStatus pop_mbox_check(struct Mailbox *m)
826 {
827   struct PopAccountData *adata = pop_adata_get(m);
828 
829   const short c_pop_check_interval =
830       cs_subset_number(NeoMutt->sub, "pop_check_interval");
831   if ((adata->check_time + c_pop_check_interval) > mutt_date_epoch())
832     return MX_STATUS_OK;
833 
834   pop_logout(m);
835 
836   mutt_socket_close(adata->conn);
837 
838   if (pop_open_connection(adata) < 0)
839     return MX_STATUS_ERROR;
840 
841   m->size = adata->size;
842 
843   mutt_message(_("Checking for new messages..."));
844 
845   int old_msg_count = m->msg_count;
846   int rc = pop_fetch_headers(m);
847   pop_clear_cache(adata);
848   if (m->msg_count > old_msg_count)
849     mailbox_changed(m, NT_MAILBOX_INVALID);
850 
851   if (rc < 0)
852     return MX_STATUS_ERROR;
853 
854   if (rc > 0)
855     return MX_STATUS_NEW_MAIL;
856 
857   return MX_STATUS_OK;
858 }
859 
860 /**
861  * pop_mbox_sync - Save changes to the Mailbox - Implements MxOps::mbox_sync() - @ingroup mx_mbox_sync
862  *
863  * Update POP mailbox, delete messages from server
864  */
pop_mbox_sync(struct Mailbox * m)865 static enum MxStatus pop_mbox_sync(struct Mailbox *m)
866 {
867   int i, j, rc = 0;
868   char buf[1024];
869   struct PopAccountData *adata = pop_adata_get(m);
870 #ifdef USE_HCACHE
871   struct HeaderCache *hc = NULL;
872 #endif
873 
874   adata->check_time = 0;
875 
876   int num_deleted = 0;
877   for (i = 0; i < m->msg_count; i++)
878   {
879     if (m->emails[i]->deleted)
880       num_deleted++;
881   }
882 
883   while (true)
884   {
885     if (pop_reconnect(m) < 0)
886       return MX_STATUS_ERROR;
887 
888 #ifdef USE_HCACHE
889     hc = pop_hcache_open(adata, mailbox_path(m));
890 #endif
891 
892     struct Progress *progress = NULL;
893     if (m->verbose)
894     {
895       progress = progress_new(_("Marking messages deleted..."),
896                               MUTT_PROGRESS_WRITE, num_deleted);
897     }
898 
899     for (i = 0, j = 0, rc = 0; (rc == 0) && (i < m->msg_count); i++)
900     {
901       struct PopEmailData *edata = pop_edata_get(m->emails[i]);
902       if (m->emails[i]->deleted && (edata->refno != -1))
903       {
904         j++;
905         if (m->verbose)
906           progress_update(progress, j, -1);
907         snprintf(buf, sizeof(buf), "DELE %d\r\n", edata->refno);
908         rc = pop_query(adata, buf, sizeof(buf));
909         if (rc == 0)
910         {
911           mutt_bcache_del(adata->bcache, cache_id(edata->uid));
912 #ifdef USE_HCACHE
913           mutt_hcache_delete_record(hc, edata->uid, strlen(edata->uid));
914 #endif
915         }
916       }
917 
918 #ifdef USE_HCACHE
919       if (m->emails[i]->changed)
920       {
921         mutt_hcache_store(hc, edata->uid, strlen(edata->uid), m->emails[i], 0);
922       }
923 #endif
924     }
925     progress_free(&progress);
926 
927 #ifdef USE_HCACHE
928     mutt_hcache_close(hc);
929 #endif
930 
931     if (rc == 0)
932     {
933       mutt_str_copy(buf, "QUIT\r\n", sizeof(buf));
934       rc = pop_query(adata, buf, sizeof(buf));
935     }
936 
937     if (rc == 0)
938     {
939       adata->clear_cache = true;
940       pop_clear_cache(adata);
941       adata->status = POP_DISCONNECTED;
942       return MX_STATUS_OK;
943     }
944 
945     if (rc == -2)
946     {
947       mutt_error("%s", adata->err_msg);
948       return MX_STATUS_ERROR;
949     }
950   }
951 }
952 
953 /**
954  * pop_mbox_close - Close a Mailbox - Implements MxOps::mbox_close() - @ingroup mx_mbox_close
955  */
pop_mbox_close(struct Mailbox * m)956 static enum MxStatus pop_mbox_close(struct Mailbox *m)
957 {
958   struct PopAccountData *adata = pop_adata_get(m);
959   if (!adata)
960     return MX_STATUS_OK;
961 
962   pop_logout(m);
963 
964   if (adata->status != POP_NONE)
965   {
966     mutt_socket_close(adata->conn);
967     // FREE(&adata->conn);
968   }
969 
970   adata->status = POP_NONE;
971 
972   adata->clear_cache = true;
973   pop_clear_cache(adata);
974 
975   mutt_bcache_close(&adata->bcache);
976 
977   return MX_STATUS_OK;
978 }
979 
980 /**
981  * pop_msg_open - Open an email message in a Mailbox - Implements MxOps::msg_open() - @ingroup mx_msg_open
982  */
pop_msg_open(struct Mailbox * m,struct Message * msg,int msgno)983 static bool pop_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
984 {
985   char buf[1024];
986   struct PopAccountData *adata = pop_adata_get(m);
987   struct Email *e = m->emails[msgno];
988   struct PopEmailData *edata = pop_edata_get(e);
989   bool bcache = true;
990   bool success = false;
991   struct Buffer *path = NULL;
992 
993   /* see if we already have the message in body cache */
994   msg->fp = mutt_bcache_get(adata->bcache, cache_id(edata->uid));
995   if (msg->fp)
996     return true;
997 
998   /* see if we already have the message in our cache in
999    * case $message_cachedir is unset */
1000   struct PopCache *cache = &adata->cache[e->index % POP_CACHE_LEN];
1001 
1002   if (cache->path)
1003   {
1004     if (cache->index == e->index)
1005     {
1006       /* yes, so just return a pointer to the message */
1007       msg->fp = fopen(cache->path, "r");
1008       if (msg->fp)
1009         return true;
1010 
1011       mutt_perror(cache->path);
1012       return false;
1013     }
1014     else
1015     {
1016       /* clear the previous entry */
1017       unlink(cache->path);
1018       FREE(&cache->path);
1019     }
1020   }
1021 
1022   path = mutt_buffer_pool_get();
1023 
1024   while (true)
1025   {
1026     if (pop_reconnect(m) < 0)
1027       goto cleanup;
1028 
1029     /* verify that massage index is correct */
1030     if (edata->refno < 0)
1031     {
1032       mutt_error(
1033           _("The message index is incorrect. Try reopening the mailbox."));
1034       goto cleanup;
1035     }
1036 
1037     /* see if we can put in body cache; use our cache as fallback */
1038     msg->fp = mutt_bcache_put(adata->bcache, cache_id(edata->uid));
1039     if (!msg->fp)
1040     {
1041       /* no */
1042       bcache = false;
1043       mutt_buffer_mktemp(path);
1044       msg->fp = mutt_file_fopen(mutt_buffer_string(path), "w+");
1045       if (!msg->fp)
1046       {
1047         mutt_perror(mutt_buffer_string(path));
1048         goto cleanup;
1049       }
1050     }
1051 
1052     snprintf(buf, sizeof(buf), "RETR %d\r\n", edata->refno);
1053 
1054     struct Progress *progress = progress_new(_("Fetching message..."), MUTT_PROGRESS_NET,
1055                                              e->body->length + e->body->offset - 1);
1056     const int ret = pop_fetch_data(adata, buf, progress, fetch_message, msg->fp);
1057     progress_free(&progress);
1058 
1059     if (ret == 0)
1060       break;
1061 
1062     mutt_file_fclose(&msg->fp);
1063 
1064     /* if RETR failed (e.g. connection closed), be sure to remove either
1065      * the file in bcache or from POP's own cache since the next iteration
1066      * of the loop will re-attempt to put() the message */
1067     if (!bcache)
1068       unlink(mutt_buffer_string(path));
1069 
1070     if (ret == -2)
1071     {
1072       mutt_error("%s", adata->err_msg);
1073       goto cleanup;
1074     }
1075 
1076     if (ret == -3)
1077     {
1078       mutt_error(_("Can't write message to temporary file"));
1079       goto cleanup;
1080     }
1081   }
1082 
1083   /* Update the header information.  Previously, we only downloaded a
1084    * portion of the headers, those required for the main display.  */
1085   if (bcache)
1086     mutt_bcache_commit(adata->bcache, cache_id(edata->uid));
1087   else
1088   {
1089     cache->index = e->index;
1090     cache->path = mutt_buffer_strdup(path);
1091   }
1092   rewind(msg->fp);
1093 
1094   /* Detach the private data */
1095   e->edata = NULL;
1096 
1097   /* we replace envelope, key in subj_hash has to be updated as well */
1098   if (m->subj_hash && e->env->real_subj)
1099     mutt_hash_delete(m->subj_hash, e->env->real_subj, e);
1100   mutt_label_hash_remove(m, e);
1101   mutt_env_free(&e->env);
1102   e->env = mutt_rfc822_read_header(msg->fp, e, false, false);
1103   if (m->subj_hash && e->env->real_subj)
1104     mutt_hash_insert(m->subj_hash, e->env->real_subj, e);
1105   mutt_label_hash_add(m, e);
1106 
1107   /* Reattach the private data */
1108   e->edata = edata;
1109   e->edata_free = pop_edata_free;
1110 
1111   e->lines = 0;
1112   fgets(buf, sizeof(buf), msg->fp);
1113   while (!feof(msg->fp))
1114   {
1115     m->emails[msgno]->lines++;
1116     fgets(buf, sizeof(buf), msg->fp);
1117   }
1118 
1119   e->body->length = ftello(msg->fp) - e->body->offset;
1120 
1121   /* This needs to be done in case this is a multipart message */
1122   if (!WithCrypto)
1123     e->security = crypt_query(e->body);
1124 
1125   mutt_clear_error();
1126   rewind(msg->fp);
1127 
1128   success = true;
1129 
1130 cleanup:
1131   mutt_buffer_pool_release(&path);
1132   return success;
1133 }
1134 
1135 /**
1136  * pop_msg_close - Close an email - Implements MxOps::msg_close() - @ingroup mx_msg_close
1137  * @retval 0   Success
1138  * @retval EOF Error, see errno
1139  */
pop_msg_close(struct Mailbox * m,struct Message * msg)1140 static int pop_msg_close(struct Mailbox *m, struct Message *msg)
1141 {
1142   return mutt_file_fclose(&msg->fp);
1143 }
1144 
1145 /**
1146  * pop_msg_save_hcache - Save message to the header cache - Implements MxOps::msg_save_hcache() - @ingroup mx_msg_save_hcache
1147  */
pop_msg_save_hcache(struct Mailbox * m,struct Email * e)1148 static int pop_msg_save_hcache(struct Mailbox *m, struct Email *e)
1149 {
1150   int rc = 0;
1151 #ifdef USE_HCACHE
1152   struct PopAccountData *adata = pop_adata_get(m);
1153   struct PopEmailData *edata = e->edata;
1154   struct HeaderCache *hc = pop_hcache_open(adata, mailbox_path(m));
1155   rc = mutt_hcache_store(hc, edata->uid, strlen(edata->uid), e, 0);
1156   mutt_hcache_close(hc);
1157 #endif
1158 
1159   return rc;
1160 }
1161 
1162 /**
1163  * pop_path_probe - Is this a POP Mailbox? - Implements MxOps::path_probe() - @ingroup mx_path_probe
1164  */
pop_path_probe(const char * path,const struct stat * st)1165 enum MailboxType pop_path_probe(const char *path, const struct stat *st)
1166 {
1167   if (mutt_istr_startswith(path, "pop://"))
1168     return MUTT_POP;
1169 
1170   if (mutt_istr_startswith(path, "pops://"))
1171     return MUTT_POP;
1172 
1173   return MUTT_UNKNOWN;
1174 }
1175 
1176 /**
1177  * pop_path_canon - Canonicalise a Mailbox path - Implements MxOps::path_canon() - @ingroup mx_path_canon
1178  */
pop_path_canon(char * buf,size_t buflen)1179 static int pop_path_canon(char *buf, size_t buflen)
1180 {
1181   return 0;
1182 }
1183 
1184 /**
1185  * pop_path_pretty - Abbreviate a Mailbox path - Implements MxOps::path_pretty() - @ingroup mx_path_pretty
1186  */
pop_path_pretty(char * buf,size_t buflen,const char * folder)1187 static int pop_path_pretty(char *buf, size_t buflen, const char *folder)
1188 {
1189   /* Succeed, but don't do anything, for now */
1190   return 0;
1191 }
1192 
1193 /**
1194  * pop_path_parent - Find the parent of a Mailbox path - Implements MxOps::path_parent() - @ingroup mx_path_parent
1195  */
pop_path_parent(char * buf,size_t buflen)1196 static int pop_path_parent(char *buf, size_t buflen)
1197 {
1198   /* Succeed, but don't do anything, for now */
1199   return 0;
1200 }
1201 
1202 /**
1203  * MxPopOps - POP Mailbox - Implements ::MxOps - @ingroup mx_api
1204  */
1205 struct MxOps MxPopOps = {
1206   // clang-format off
1207   .type            = MUTT_POP,
1208   .name             = "pop",
1209   .is_local         = false,
1210   .ac_owns_path     = pop_ac_owns_path,
1211   .ac_add           = pop_ac_add,
1212   .mbox_open        = pop_mbox_open,
1213   .mbox_open_append = NULL,
1214   .mbox_check       = pop_mbox_check,
1215   .mbox_check_stats = NULL,
1216   .mbox_sync        = pop_mbox_sync,
1217   .mbox_close       = pop_mbox_close,
1218   .msg_open         = pop_msg_open,
1219   .msg_open_new     = NULL,
1220   .msg_commit       = NULL,
1221   .msg_close        = pop_msg_close,
1222   .msg_padding_size = NULL,
1223   .msg_save_hcache  = pop_msg_save_hcache,
1224   .tags_edit        = NULL,
1225   .tags_commit      = NULL,
1226   .path_probe       = pop_path_probe,
1227   .path_canon       = pop_path_canon,
1228   .path_pretty      = pop_path_pretty,
1229   .path_parent      = pop_path_parent,
1230   .path_is_empty    = NULL,
1231   // clang-format on
1232 };
1233