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