Coverage Report

Created: 2025-04-22 06:17

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