1 /**
2 * @file
3 * IMAP helper functions
4 *
5 * @authors
6 * Copyright (C) 1996-1998,2010,2012-2013 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 1996-1999 Brandon Long <blong@fiction.net>
8 * Copyright (C) 1999-2009,2012 Brendan Cully <brendan@kublai.com>
9 * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
10 *
11 * @copyright
12 * This program is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free Software
14 * Foundation, either version 2 of the License, or (at your option) any later
15 * version.
16 *
17 * This program is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 * details.
21 *
22 * You should have received a copy of the GNU General Public License along with
23 * this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26 /**
27 * @page imap_util IMAP helper functions
28 *
29 * IMAP helper functions
30 */
31
32 #include "config.h"
33 #include <ctype.h>
34 #include <errno.h>
35 #include <netdb.h>
36 #include <netinet/in.h>
37 #include <signal.h>
38 #include <stdbool.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <sys/wait.h>
42 #include <time.h>
43 #include <unistd.h>
44 #include "private.h"
45 #include "mutt/lib.h"
46 #include "config/lib.h"
47 #include "email/lib.h"
48 #include "core/lib.h"
49 #include "conn/lib.h"
50 #include "lib.h"
51 #include "bcache/lib.h"
52 #include "question/lib.h"
53 #include "adata.h"
54 #include "edata.h"
55 #include "mdata.h"
56 #include "msn.h"
57 #include "mutt_account.h"
58 #include "mutt_globals.h"
59 #include "options.h"
60 #ifdef USE_HCACHE
61 #include "hcache/lib.h"
62 #endif
63
64 /**
65 * imap_adata_find - Find the Account data for this path
66 * @param path Path to search for
67 * @param adata Imap Account data
68 * @param mdata Imap Mailbox data
69 * @retval 0 Success
70 * @retval -1 Failure
71 */
imap_adata_find(const char * path,struct ImapAccountData ** adata,struct ImapMboxData ** mdata)72 int imap_adata_find(const char *path, struct ImapAccountData **adata,
73 struct ImapMboxData **mdata)
74 {
75 struct ConnAccount cac = { { 0 } };
76 struct ImapAccountData *tmp_adata = NULL;
77 char tmp[1024];
78
79 if (imap_parse_path(path, &cac, tmp, sizeof(tmp)) < 0)
80 return -1;
81
82 struct Account *np = NULL;
83 TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
84 {
85 if (np->type != MUTT_IMAP)
86 continue;
87
88 tmp_adata = np->adata;
89 if (!tmp_adata)
90 continue;
91 if (imap_account_match(&tmp_adata->conn->account, &cac))
92 {
93 *mdata = imap_mdata_new(tmp_adata, tmp);
94 *adata = tmp_adata;
95 return 0;
96 }
97 }
98 mutt_debug(LL_DEBUG3, "no ImapAccountData found\n");
99 return -1;
100 }
101
102 /**
103 * imap_mdata_cache_reset - Release and clear cache data of ImapMboxData structure
104 * @param mdata Imap Mailbox data
105 */
imap_mdata_cache_reset(struct ImapMboxData * mdata)106 void imap_mdata_cache_reset(struct ImapMboxData *mdata)
107 {
108 mutt_hash_free(&mdata->uid_hash);
109 imap_msn_free(&mdata->msn);
110 mutt_bcache_close(&mdata->bcache);
111 }
112
113 /**
114 * imap_get_parent - Get an IMAP folder's parent
115 * @param mbox Mailbox whose parent is to be determined
116 * @param delim Path delimiter
117 * @param buf Buffer for the result
118 * @param buflen Length of the buffer
119 */
imap_get_parent(const char * mbox,char delim,char * buf,size_t buflen)120 void imap_get_parent(const char *mbox, char delim, char *buf, size_t buflen)
121 {
122 /* Make a copy of the mailbox name, but only if the pointers are different */
123 if (mbox != buf)
124 mutt_str_copy(buf, mbox, buflen);
125
126 int n = mutt_str_len(buf);
127
128 /* Let's go backwards until the next delimiter
129 *
130 * If buf[n] is a '/', the first n-- will allow us
131 * to ignore it. If it isn't, then buf looks like
132 * "/aaaaa/bbbb". There is at least one "b", so we can't skip
133 * the "/" after the 'a's.
134 *
135 * If buf == '/', then n-- => n == 0, so the loop ends
136 * immediately */
137 for (n--; (n >= 0) && (buf[n] != delim); n--)
138 ; // do nothing
139
140 /* We stopped before the beginning. There is a trailing slash. */
141 if (n > 0)
142 {
143 /* Strip the trailing delimiter. */
144 buf[n] = '\0';
145 }
146 else
147 {
148 buf[0] = (n == 0) ? delim : '\0';
149 }
150 }
151
152 /**
153 * imap_get_parent_path - Get the path of the parent folder
154 * @param path Mailbox whose parent is to be determined
155 * @param buf Buffer for the result
156 * @param buflen Length of the buffer
157 *
158 * Provided an imap path, returns in buf the parent directory if
159 * existent. Else returns the same path.
160 */
imap_get_parent_path(const char * path,char * buf,size_t buflen)161 void imap_get_parent_path(const char *path, char *buf, size_t buflen)
162 {
163 struct ImapAccountData *adata = NULL;
164 struct ImapMboxData *mdata = NULL;
165 char mbox[1024];
166
167 if (imap_adata_find(path, &adata, &mdata) < 0)
168 {
169 mutt_str_copy(buf, path, buflen);
170 return;
171 }
172
173 /* Gets the parent mbox in mbox */
174 imap_get_parent(mdata->name, adata->delim, mbox, sizeof(mbox));
175
176 /* Returns a fully qualified IMAP url */
177 imap_qualify_path(buf, buflen, &adata->conn->account, mbox);
178 imap_mdata_free((void *) &mdata);
179 }
180
181 /**
182 * imap_clean_path - Cleans an IMAP path using imap_fix_path
183 * @param path Path to be cleaned
184 * @param plen Length of the buffer
185 *
186 * Does it in place.
187 */
imap_clean_path(char * path,size_t plen)188 void imap_clean_path(char *path, size_t plen)
189 {
190 struct ImapAccountData *adata = NULL;
191 struct ImapMboxData *mdata = NULL;
192
193 if (imap_adata_find(path, &adata, &mdata) < 0)
194 return;
195
196 /* Returns a fully qualified IMAP url */
197 imap_qualify_path(path, plen, &adata->conn->account, mdata->name);
198 imap_mdata_free((void *) &mdata);
199 }
200
201 /**
202 * imap_get_field - Get connection login credentials - Implements ConnAccount::get_field()
203 */
imap_get_field(enum ConnAccountField field,void * gf_data)204 static const char *imap_get_field(enum ConnAccountField field, void *gf_data)
205 {
206 switch (field)
207 {
208 case MUTT_CA_LOGIN:
209 return cs_subset_string(NeoMutt->sub, "imap_login");
210 case MUTT_CA_USER:
211 return cs_subset_string(NeoMutt->sub, "imap_user");
212 case MUTT_CA_PASS:
213 return cs_subset_string(NeoMutt->sub, "imap_pass");
214 case MUTT_CA_OAUTH_CMD:
215 return cs_subset_string(NeoMutt->sub, "imap_oauth_refresh_command");
216 case MUTT_CA_HOST:
217 default:
218 return NULL;
219 }
220 }
221
222 #ifdef USE_HCACHE
223 /**
224 * imap_msn_index_to_uid_seqset - Convert MSN index of UIDs to Seqset
225 * @param buf Buffer for the result
226 * @param mdata Imap Mailbox data
227 *
228 * Generates a seqseq of the UIDs in msn_index to persist in the header cache.
229 * Empty spots are stored as 0.
230 */
imap_msn_index_to_uid_seqset(struct Buffer * buf,struct ImapMboxData * mdata)231 static void imap_msn_index_to_uid_seqset(struct Buffer *buf, struct ImapMboxData *mdata)
232 {
233 int first = 1, state = 0;
234 unsigned int cur_uid = 0, last_uid = 0;
235 unsigned int range_begin = 0, range_end = 0;
236 const size_t max_msn = imap_msn_highest(&mdata->msn);
237
238 for (unsigned int msn = 1; msn <= max_msn + 1; msn++)
239 {
240 bool match = false;
241 if (msn <= max_msn)
242 {
243 struct Email *e_cur = imap_msn_get(&mdata->msn, msn - 1);
244 cur_uid = e_cur ? imap_edata_get(e_cur)->uid : 0;
245 if (!state || (cur_uid && ((cur_uid - 1) == last_uid)))
246 match = true;
247 last_uid = cur_uid;
248 }
249
250 if (match)
251 {
252 switch (state)
253 {
254 case 1: /* single: convert to a range */
255 state = 2;
256 /* fall through */
257 case 2: /* extend range ending */
258 range_end = cur_uid;
259 break;
260 default:
261 state = 1;
262 range_begin = cur_uid;
263 break;
264 }
265 }
266 else if (state)
267 {
268 if (first)
269 first = 0;
270 else
271 mutt_buffer_addch(buf, ',');
272
273 if (state == 1)
274 mutt_buffer_add_printf(buf, "%u", range_begin);
275 else if (state == 2)
276 mutt_buffer_add_printf(buf, "%u:%u", range_begin, range_end);
277
278 state = 1;
279 range_begin = cur_uid;
280 }
281 }
282 }
283
284 /**
285 * imap_hcache_namer - Generate a filename for the header cache - Implements ::hcache_namer_t - @ingroup hcache_namer_api
286 */
imap_hcache_namer(const char * path,struct Buffer * dest)287 static void imap_hcache_namer(const char *path, struct Buffer *dest)
288 {
289 mutt_buffer_printf(dest, "%s.hcache", path);
290 }
291
292 /**
293 * imap_hcache_open - Open a header cache
294 * @param adata Imap Account data
295 * @param mdata Imap Mailbox data
296 */
imap_hcache_open(struct ImapAccountData * adata,struct ImapMboxData * mdata)297 void imap_hcache_open(struct ImapAccountData *adata, struct ImapMboxData *mdata)
298 {
299 if (!adata || !mdata)
300 return;
301
302 if (mdata->hcache)
303 return;
304
305 struct HeaderCache *hc = NULL;
306 struct Buffer *mbox = mutt_buffer_pool_get();
307 struct Buffer *cachepath = mutt_buffer_pool_get();
308
309 imap_cachepath(adata->delim, mdata->name, mbox);
310
311 if (strstr(mutt_buffer_string(mbox), "/../") ||
312 mutt_str_equal(mutt_buffer_string(mbox), "..") ||
313 mutt_strn_equal(mutt_buffer_string(mbox), "../", 3))
314 {
315 goto cleanup;
316 }
317 size_t len = mutt_buffer_len(mbox);
318 if ((len > 3) && (strcmp(mutt_buffer_string(mbox) + len - 3, "/..") == 0))
319 goto cleanup;
320
321 struct Url url = { 0 };
322 mutt_account_tourl(&adata->conn->account, &url);
323 url.path = mbox->data;
324 url_tobuffer(&url, cachepath, U_PATH);
325
326 const char *const c_header_cache =
327 cs_subset_path(NeoMutt->sub, "header_cache");
328 hc = mutt_hcache_open(c_header_cache, mutt_buffer_string(cachepath), imap_hcache_namer);
329
330 cleanup:
331 mutt_buffer_pool_release(&mbox);
332 mutt_buffer_pool_release(&cachepath);
333 mdata->hcache = hc;
334 }
335
336 /**
337 * imap_hcache_close - Close the header cache
338 * @param mdata Imap Mailbox data
339 */
imap_hcache_close(struct ImapMboxData * mdata)340 void imap_hcache_close(struct ImapMboxData *mdata)
341 {
342 if (!mdata->hcache)
343 return;
344
345 mutt_hcache_close(mdata->hcache);
346 mdata->hcache = NULL;
347 }
348
349 /**
350 * imap_hcache_get - Get a header cache entry by its UID
351 * @param mdata Imap Mailbox data
352 * @param uid UID to find
353 * @retval ptr Email
354 * @retval NULL Failure
355 */
imap_hcache_get(struct ImapMboxData * mdata,unsigned int uid)356 struct Email *imap_hcache_get(struct ImapMboxData *mdata, unsigned int uid)
357 {
358 if (!mdata->hcache)
359 return NULL;
360
361 char key[16];
362
363 sprintf(key, "/%u", uid);
364 struct HCacheEntry hce =
365 mutt_hcache_fetch(mdata->hcache, key, mutt_str_len(key), mdata->uidvalidity);
366 if (!hce.email && hce.uidvalidity)
367 {
368 mutt_debug(LL_DEBUG3, "hcache uidvalidity mismatch: %u\n", hce.uidvalidity);
369 }
370
371 return hce.email;
372 }
373
374 /**
375 * imap_hcache_put - Add an entry to the header cache
376 * @param mdata Imap Mailbox data
377 * @param e Email
378 * @retval 0 Success
379 * @retval -1 Failure
380 */
imap_hcache_put(struct ImapMboxData * mdata,struct Email * e)381 int imap_hcache_put(struct ImapMboxData *mdata, struct Email *e)
382 {
383 if (!mdata->hcache)
384 return -1;
385
386 char key[16];
387
388 sprintf(key, "/%u", imap_edata_get(e)->uid);
389 return mutt_hcache_store(mdata->hcache, key, mutt_str_len(key), e, mdata->uidvalidity);
390 }
391
392 /**
393 * imap_hcache_del - Delete an item from the header cache
394 * @param mdata Imap Mailbox data
395 * @param uid UID of entry to delete
396 * @retval 0 Success
397 * @retval -1 Failure
398 */
imap_hcache_del(struct ImapMboxData * mdata,unsigned int uid)399 int imap_hcache_del(struct ImapMboxData *mdata, unsigned int uid)
400 {
401 if (!mdata->hcache)
402 return -1;
403
404 char key[16];
405
406 sprintf(key, "/%u", uid);
407 return mutt_hcache_delete_record(mdata->hcache, key, mutt_str_len(key));
408 }
409
410 /**
411 * imap_hcache_store_uid_seqset - Store a UID Sequence Set in the header cache
412 * @param mdata Imap Mailbox data
413 * @retval 0 Success
414 * @retval -1 Error
415 */
imap_hcache_store_uid_seqset(struct ImapMboxData * mdata)416 int imap_hcache_store_uid_seqset(struct ImapMboxData *mdata)
417 {
418 if (!mdata->hcache)
419 return -1;
420
421 /* The seqset is likely large. Preallocate to reduce reallocs */
422 struct Buffer buf = mutt_buffer_make(8192);
423 imap_msn_index_to_uid_seqset(&buf, mdata);
424
425 int rc = mutt_hcache_store_raw(mdata->hcache, "/UIDSEQSET", 10, buf.data,
426 mutt_buffer_len(&buf) + 1);
427 mutt_debug(LL_DEBUG3, "Stored /UIDSEQSET %s\n", buf.data);
428 mutt_buffer_dealloc(&buf);
429 return rc;
430 }
431
432 /**
433 * imap_hcache_clear_uid_seqset - Delete a UID Sequence Set from the header cache
434 * @param mdata Imap Mailbox data
435 * @retval 0 Success
436 * @retval -1 Error
437 */
imap_hcache_clear_uid_seqset(struct ImapMboxData * mdata)438 int imap_hcache_clear_uid_seqset(struct ImapMboxData *mdata)
439 {
440 if (!mdata->hcache)
441 return -1;
442
443 return mutt_hcache_delete_record(mdata->hcache, "/UIDSEQSET", 10);
444 }
445
446 /**
447 * imap_hcache_get_uid_seqset - Get a UID Sequence Set from the header cache
448 * @param mdata Imap Mailbox data
449 * @retval ptr UID Sequence Set
450 * @retval NULL Error
451 */
imap_hcache_get_uid_seqset(struct ImapMboxData * mdata)452 char *imap_hcache_get_uid_seqset(struct ImapMboxData *mdata)
453 {
454 if (!mdata->hcache)
455 return NULL;
456
457 char *seqset = NULL;
458 size_t dlen = 0;
459 char *hc_seqset = mutt_hcache_fetch_raw(mdata->hcache, "/UIDSEQSET", 10, &dlen);
460 if (hc_seqset)
461 {
462 seqset = mutt_strn_dup(hc_seqset, dlen);
463 mutt_hcache_free_raw(mdata->hcache, (void **) &hc_seqset);
464 }
465 mutt_debug(LL_DEBUG3, "Retrieved /UIDSEQSET %s\n", NONULL(seqset));
466
467 return seqset;
468 }
469 #endif
470
471 /**
472 * imap_parse_path - Parse an IMAP mailbox name into ConnAccount, name
473 * @param path Mailbox path to parse
474 * @param cac Account credentials
475 * @param mailbox Buffer for mailbox name
476 * @param mailboxlen Length of buffer
477 * @retval 0 Success
478 * @retval -1 Failure
479 *
480 * Given an IMAP mailbox name, return host, port and a path IMAP servers will
481 * recognize.
482 */
imap_parse_path(const char * path,struct ConnAccount * cac,char * mailbox,size_t mailboxlen)483 int imap_parse_path(const char *path, struct ConnAccount *cac, char *mailbox, size_t mailboxlen)
484 {
485 static unsigned short ImapPort = 0;
486 static unsigned short ImapsPort = 0;
487
488 if (ImapPort == 0)
489 {
490 struct servent *service = getservbyname("imap", "tcp");
491 if (service)
492 ImapPort = ntohs(service->s_port);
493 else
494 ImapPort = IMAP_PORT;
495 mutt_debug(LL_DEBUG3, "Using default IMAP port %d\n", ImapPort);
496 }
497
498 if (ImapsPort == 0)
499 {
500 struct servent *service = getservbyname("imaps", "tcp");
501 if (service)
502 ImapsPort = ntohs(service->s_port);
503 else
504 ImapsPort = IMAP_SSL_PORT;
505 mutt_debug(LL_DEBUG3, "Using default IMAPS port %d\n", ImapsPort);
506 }
507
508 /* Defaults */
509 cac->port = ImapPort;
510 cac->type = MUTT_ACCT_TYPE_IMAP;
511 cac->service = "imap";
512 cac->get_field = imap_get_field;
513
514 struct Url *url = url_parse(path);
515 if (!url)
516 return -1;
517
518 if ((url->scheme != U_IMAP) && (url->scheme != U_IMAPS))
519 {
520 url_free(&url);
521 return -1;
522 }
523
524 if ((mutt_account_fromurl(cac, url) < 0) || (cac->host[0] == '\0'))
525 {
526 url_free(&url);
527 return -1;
528 }
529
530 if (url->scheme == U_IMAPS)
531 cac->flags |= MUTT_ACCT_SSL;
532
533 mutt_str_copy(mailbox, url->path, mailboxlen);
534
535 url_free(&url);
536
537 if ((cac->flags & MUTT_ACCT_SSL) && !(cac->flags & MUTT_ACCT_PORT))
538 cac->port = ImapsPort;
539
540 return 0;
541 }
542
543 /**
544 * imap_mxcmp - Compare mailbox names, giving priority to INBOX
545 * @param mx1 First mailbox name
546 * @param mx2 Second mailbox name
547 * @retval <0 First mailbox precedes Second mailbox
548 * @retval 0 Mailboxes are the same
549 * @retval >0 Second mailbox precedes First mailbox
550 *
551 * Like a normal sort function except that "INBOX" will be sorted to the
552 * beginning of the list.
553 */
imap_mxcmp(const char * mx1,const char * mx2)554 int imap_mxcmp(const char *mx1, const char *mx2)
555 {
556 char *b1 = NULL;
557 char *b2 = NULL;
558 int rc;
559
560 if (!mx1 || (*mx1 == '\0'))
561 mx1 = "INBOX";
562 if (!mx2 || (*mx2 == '\0'))
563 mx2 = "INBOX";
564 if (mutt_istr_equal(mx1, "INBOX") && mutt_istr_equal(mx2, "INBOX"))
565 {
566 return 0;
567 }
568
569 b1 = mutt_mem_malloc(strlen(mx1) + 1);
570 b2 = mutt_mem_malloc(strlen(mx2) + 1);
571
572 imap_fix_path('\0', mx1, b1, strlen(mx1) + 1);
573 imap_fix_path('\0', mx2, b2, strlen(mx2) + 1);
574
575 rc = mutt_str_cmp(b1, b2);
576 FREE(&b1);
577 FREE(&b2);
578
579 return rc;
580 }
581
582 /**
583 * imap_pretty_mailbox - Prettify an IMAP mailbox name
584 * @param path Mailbox name to be tidied
585 * @param pathlen Length of path
586 * @param folder Path to use for '+' abbreviations
587 *
588 * Called by mutt_pretty_mailbox() to make IMAP paths look nice.
589 */
imap_pretty_mailbox(char * path,size_t pathlen,const char * folder)590 void imap_pretty_mailbox(char *path, size_t pathlen, const char *folder)
591 {
592 struct ConnAccount cac_target = { { 0 } };
593 struct ConnAccount cac_home = { { 0 } };
594 struct Url url = { 0 };
595 const char *delim = NULL;
596 int tlen;
597 int hlen = 0;
598 bool home_match = false;
599 char target_mailbox[1024];
600 char home_mailbox[1024];
601
602 if (imap_parse_path(path, &cac_target, target_mailbox, sizeof(target_mailbox)) < 0)
603 return;
604
605 if (imap_path_probe(folder, NULL) != MUTT_IMAP)
606 goto fallback;
607
608 if (imap_parse_path(folder, &cac_home, home_mailbox, sizeof(home_mailbox)) < 0)
609 goto fallback;
610
611 tlen = mutt_str_len(target_mailbox);
612 hlen = mutt_str_len(home_mailbox);
613
614 /* check whether we can do '+' substitution */
615 if (tlen && imap_account_match(&cac_home, &cac_target) &&
616 mutt_strn_equal(home_mailbox, target_mailbox, hlen))
617 {
618 const char *const c_imap_delim_chars =
619 cs_subset_string(NeoMutt->sub, "imap_delim_chars");
620 if (hlen == 0)
621 home_match = true;
622 else if (c_imap_delim_chars)
623 {
624 for (delim = c_imap_delim_chars; *delim != '\0'; delim++)
625 if (target_mailbox[hlen] == *delim)
626 home_match = true;
627 }
628 }
629
630 /* do the '+' substitution */
631 if (home_match)
632 {
633 *path++ = '+';
634 /* copy remaining path, skipping delimiter */
635 if (hlen == 0)
636 hlen = -1;
637 memcpy(path, target_mailbox + hlen + 1, tlen - hlen - 1);
638 path[tlen - hlen - 1] = '\0';
639 return;
640 }
641
642 fallback:
643 mutt_account_tourl(&cac_target, &url);
644 url.path = target_mailbox;
645 url_tostring(&url, path, pathlen, U_NO_FLAGS);
646 }
647
648 /**
649 * imap_continue - Display a message and ask the user if they want to go on
650 * @param msg Location of the error
651 * @param resp Message for user
652 * @retval #QuadOption Result, e.g. #MUTT_NO
653 */
imap_continue(const char * msg,const char * resp)654 enum QuadOption imap_continue(const char *msg, const char *resp)
655 {
656 imap_error(msg, resp);
657 return mutt_yesorno(_("Continue?"), MUTT_NO);
658 }
659
660 /**
661 * imap_error - Show an error and abort
662 * @param where Location of the error
663 * @param msg Message for user
664 */
imap_error(const char * where,const char * msg)665 void imap_error(const char *where, const char *msg)
666 {
667 mutt_error("%s [%s]", where, msg);
668 }
669
670 /**
671 * imap_fix_path - Fix up the imap path
672 * @param delim Delimiter specified by the server, '\0' for `$imap_delim_chars`
673 * @param mailbox Mailbox path
674 * @param path Buffer for the result
675 * @param plen Length of buffer
676 * @retval ptr Fixed-up path
677 *
678 * @note if delim is '\0', the first character in mailbox matching any of the
679 * characters in `$imap_delim_chars` is used as a delimiter.
680 *
681 * This is necessary because the rest of neomutt assumes a hierarchy delimiter of
682 * '/', which is not necessarily true in IMAP. Additionally, the filesystem
683 * converts multiple hierarchy delimiters into a single one, ie "///" is equal
684 * to "/". IMAP servers are not required to do this.
685 * Moreover, IMAP servers may dislike the path ending with the delimiter.
686 */
imap_fix_path(char delim,const char * mailbox,char * path,size_t plen)687 char *imap_fix_path(char delim, const char *mailbox, char *path, size_t plen)
688 {
689 int i = 0;
690 for (; mailbox && *mailbox && (i < plen - 1); i++)
691 {
692 const char *const c_imap_delim_chars =
693 cs_subset_string(NeoMutt->sub, "imap_delim_chars");
694 if (*mailbox == delim || (!delim && strchr(NONULL(c_imap_delim_chars), *mailbox)))
695 {
696 delim = *mailbox;
697 /* Skip multiple occurrences of delim */
698 while (*mailbox && *(mailbox + 1) == delim)
699 mailbox++;
700 }
701 path[i] = *mailbox++;
702 }
703
704 /* Do not terminate with a delimiter */
705 if (i && path[i - 1] == delim)
706 i--;
707
708 /* Ensure null termination */
709 path[i] = '\0';
710 return path;
711 }
712
713 /**
714 * imap_cachepath - Generate a cache path for a mailbox
715 * @param delim Imap server delimiter
716 * @param mailbox Mailbox name
717 * @param dest Buffer to store cache path
718 */
imap_cachepath(char delim,const char * mailbox,struct Buffer * dest)719 void imap_cachepath(char delim, const char *mailbox, struct Buffer *dest)
720 {
721 const char *p = mailbox;
722 mutt_buffer_reset(dest);
723 if (!p)
724 return;
725
726 while (*p)
727 {
728 if (p[0] == delim)
729 {
730 mutt_buffer_addch(dest, '/');
731 /* simple way to avoid collisions with UIDs */
732 if ((p[1] >= '0') && (p[1] <= '9'))
733 mutt_buffer_addch(dest, '_');
734 }
735 else
736 mutt_buffer_addch(dest, *p);
737 p++;
738 }
739 }
740
741 /**
742 * imap_get_literal_count - Write number of bytes in an IMAP literal into bytes
743 * @param[in] buf Number as a string
744 * @param[out] bytes Resulting number
745 * @retval 0 Success
746 * @retval -1 Failure
747 */
imap_get_literal_count(const char * buf,unsigned int * bytes)748 int imap_get_literal_count(const char *buf, unsigned int *bytes)
749 {
750 char *pc = NULL;
751 char *pn = NULL;
752
753 if (!buf || !(pc = strchr(buf, '{')))
754 return -1;
755
756 pc++;
757 pn = pc;
758 while (isdigit((unsigned char) *pc))
759 pc++;
760 *pc = '\0';
761 if (mutt_str_atoui(pn, bytes) < 0)
762 return -1;
763
764 return 0;
765 }
766
767 /**
768 * imap_get_qualifier - Get the qualifier from a tagged response
769 * @param buf Command string to process
770 * @retval ptr Start of the qualifier
771 *
772 * In a tagged response, skip tag and status for the qualifier message.
773 * Used by imap_copy_message for TRYCREATE
774 */
imap_get_qualifier(char * buf)775 char *imap_get_qualifier(char *buf)
776 {
777 char *s = buf;
778
779 /* skip tag */
780 s = imap_next_word(s);
781 /* skip OK/NO/BAD response */
782 s = imap_next_word(s);
783
784 return s;
785 }
786
787 /**
788 * imap_next_word - Find where the next IMAP word begins
789 * @param s Command string to process
790 * @retval ptr Next IMAP word
791 */
imap_next_word(char * s)792 char *imap_next_word(char *s)
793 {
794 bool quoted = false;
795
796 while (*s)
797 {
798 if (*s == '\\')
799 {
800 s++;
801 if (*s)
802 s++;
803 continue;
804 }
805 if (*s == '\"')
806 quoted = !quoted;
807 if (!quoted && IS_SPACE(*s))
808 break;
809 s++;
810 }
811
812 SKIPWS(s);
813 return s;
814 }
815
816 /**
817 * imap_qualify_path - Make an absolute IMAP folder target
818 * @param buf Buffer for the result
819 * @param buflen Length of buffer
820 * @param cac ConnAccount of the account
821 * @param path Path relative to the mailbox
822 */
imap_qualify_path(char * buf,size_t buflen,struct ConnAccount * cac,char * path)823 void imap_qualify_path(char *buf, size_t buflen, struct ConnAccount *cac, char *path)
824 {
825 struct Url url = { 0 };
826 mutt_account_tourl(cac, &url);
827 url.path = path;
828 url_tostring(&url, buf, buflen, U_NO_FLAGS);
829 }
830
831 /**
832 * imap_quote_string - Quote string according to IMAP rules
833 * @param dest Buffer for the result
834 * @param dlen Length of the buffer
835 * @param src String to be quoted
836 * @param quote_backtick If true, quote backticks too
837 *
838 * Surround string with quotes, escape " and \ with backslash
839 */
imap_quote_string(char * dest,size_t dlen,const char * src,bool quote_backtick)840 void imap_quote_string(char *dest, size_t dlen, const char *src, bool quote_backtick)
841 {
842 const char *quote = "`\"\\";
843 if (!quote_backtick)
844 quote++;
845
846 char *pt = dest;
847 const char *s = src;
848
849 *pt++ = '"';
850 /* save room for quote-chars */
851 dlen -= 3;
852
853 for (; *s && dlen; s++)
854 {
855 if (strchr(quote, *s))
856 {
857 if (dlen < 2)
858 break;
859 dlen -= 2;
860 *pt++ = '\\';
861 *pt++ = *s;
862 }
863 else
864 {
865 *pt++ = *s;
866 dlen--;
867 }
868 }
869 *pt++ = '"';
870 *pt = '\0';
871 }
872
873 /**
874 * imap_unquote_string - Equally stupid unquoting routine
875 * @param s String to be unquoted
876 */
imap_unquote_string(char * s)877 void imap_unquote_string(char *s)
878 {
879 char *d = s;
880
881 if (*s == '\"')
882 s++;
883 else
884 return;
885
886 while (*s)
887 {
888 if (*s == '\"')
889 {
890 *d = '\0';
891 return;
892 }
893 if (*s == '\\')
894 {
895 s++;
896 }
897 if (*s)
898 {
899 *d = *s;
900 d++;
901 s++;
902 }
903 }
904 *d = '\0';
905 }
906
907 /**
908 * imap_munge_mbox_name - Quote awkward characters in a mailbox name
909 * @param unicode true if Unicode is allowed
910 * @param dest Buffer to store safe mailbox name
911 * @param dlen Length of buffer
912 * @param src Mailbox name
913 */
imap_munge_mbox_name(bool unicode,char * dest,size_t dlen,const char * src)914 void imap_munge_mbox_name(bool unicode, char *dest, size_t dlen, const char *src)
915 {
916 char *buf = mutt_str_dup(src);
917 imap_utf_encode(unicode, &buf);
918
919 imap_quote_string(dest, dlen, buf, false);
920
921 FREE(&buf);
922 }
923
924 /**
925 * imap_unmunge_mbox_name - Remove quoting from a mailbox name
926 * @param unicode true if Unicode is allowed
927 * @param s Mailbox name
928 *
929 * The string will be altered in-place.
930 */
imap_unmunge_mbox_name(bool unicode,char * s)931 void imap_unmunge_mbox_name(bool unicode, char *s)
932 {
933 imap_unquote_string(s);
934
935 char *buf = mutt_str_dup(s);
936 if (buf)
937 {
938 imap_utf_decode(unicode, &buf);
939 strncpy(s, buf, strlen(s));
940 }
941
942 FREE(&buf);
943 }
944
945 /**
946 * imap_keepalive - Poll the current folder to keep the connection alive
947 */
imap_keepalive(void)948 void imap_keepalive(void)
949 {
950 time_t now = mutt_date_epoch();
951 struct Account *np = NULL;
952 TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
953 {
954 if (np->type != MUTT_IMAP)
955 continue;
956
957 struct ImapAccountData *adata = np->adata;
958 if (!adata || !adata->mailbox)
959 continue;
960
961 const short c_imap_keepalive =
962 cs_subset_number(NeoMutt->sub, "imap_keepalive");
963 if ((adata->state >= IMAP_AUTHENTICATED) && (now >= (adata->lastread + c_imap_keepalive)))
964 imap_check_mailbox(adata->mailbox, true);
965 }
966 }
967
968 /**
969 * imap_wait_keepalive - Wait for a process to change state
970 * @param pid Process ID to listen to
971 * @retval num 'wstatus' from waitpid()
972 */
imap_wait_keepalive(pid_t pid)973 int imap_wait_keepalive(pid_t pid)
974 {
975 struct sigaction oldalrm;
976 struct sigaction act;
977 sigset_t oldmask;
978 int rc;
979
980 const bool c_imap_passive = cs_subset_bool(NeoMutt->sub, "imap_passive");
981 cs_subset_str_native_set(NeoMutt->sub, "imap_passive", true, NULL);
982 OptKeepQuiet = true;
983
984 sigprocmask(SIG_SETMASK, NULL, &oldmask);
985
986 sigemptyset(&act.sa_mask);
987 act.sa_handler = mutt_sig_empty_handler;
988 #ifdef SA_INTERRUPT
989 act.sa_flags = SA_INTERRUPT;
990 #else
991 act.sa_flags = 0;
992 #endif
993
994 sigaction(SIGALRM, &act, &oldalrm);
995
996 const short c_imap_keepalive =
997 cs_subset_number(NeoMutt->sub, "imap_keepalive");
998 alarm(c_imap_keepalive);
999 while ((waitpid(pid, &rc, 0) < 0) && (errno == EINTR))
1000 {
1001 alarm(0); /* cancel a possibly pending alarm */
1002 imap_keepalive();
1003 alarm(c_imap_keepalive);
1004 }
1005
1006 alarm(0); /* cancel a possibly pending alarm */
1007
1008 sigaction(SIGALRM, &oldalrm, NULL);
1009 sigprocmask(SIG_SETMASK, &oldmask, NULL);
1010
1011 OptKeepQuiet = false;
1012 cs_subset_str_native_set(NeoMutt->sub, "imap_passive", c_imap_passive, NULL);
1013
1014 return rc;
1015 }
1016
1017 /**
1018 * imap_allow_reopen - Allow re-opening a folder upon expunge
1019 * @param m Mailbox
1020 */
imap_allow_reopen(struct Mailbox * m)1021 void imap_allow_reopen(struct Mailbox *m)
1022 {
1023 struct ImapAccountData *adata = imap_adata_get(m);
1024 struct ImapMboxData *mdata = imap_mdata_get(m);
1025 if (!adata || !adata->mailbox || (adata->mailbox != m) || !mdata)
1026 return;
1027 mdata->reopen |= IMAP_REOPEN_ALLOW;
1028 }
1029
1030 /**
1031 * imap_disallow_reopen - Disallow re-opening a folder upon expunge
1032 * @param m Mailbox
1033 */
imap_disallow_reopen(struct Mailbox * m)1034 void imap_disallow_reopen(struct Mailbox *m)
1035 {
1036 struct ImapAccountData *adata = imap_adata_get(m);
1037 struct ImapMboxData *mdata = imap_mdata_get(m);
1038 if (!adata || !adata->mailbox || (adata->mailbox != m) || !mdata)
1039 return;
1040 mdata->reopen &= ~IMAP_REOPEN_ALLOW;
1041 }
1042
1043 /**
1044 * imap_account_match - Compare two Accounts
1045 * @param a1 First ConnAccount
1046 * @param a2 Second ConnAccount
1047 * @retval true Accounts match
1048 */
imap_account_match(const struct ConnAccount * a1,const struct ConnAccount * a2)1049 bool imap_account_match(const struct ConnAccount *a1, const struct ConnAccount *a2)
1050 {
1051 if (!a1 || !a2)
1052 return false;
1053 if (a1->type != a2->type)
1054 return false;
1055 if (!mutt_istr_equal(a1->host, a2->host))
1056 return false;
1057 if ((a1->port != 0) && (a2->port != 0) && (a1->port != a2->port))
1058 return false;
1059 if (a1->flags & a2->flags & MUTT_ACCT_USER)
1060 return strcmp(a1->user, a2->user) == 0;
1061
1062 const char *user = NONULL(Username);
1063
1064 const char *const c_imap_user = cs_subset_string(NeoMutt->sub, "imap_user");
1065 if ((a1->type == MUTT_ACCT_TYPE_IMAP) && c_imap_user)
1066 user = c_imap_user;
1067
1068 if (a1->flags & MUTT_ACCT_USER)
1069 return strcmp(a1->user, user) == 0;
1070 if (a2->flags & MUTT_ACCT_USER)
1071 return strcmp(a2->user, user) == 0;
1072
1073 return true;
1074 }
1075
1076 /**
1077 * mutt_seqset_iterator_new - Create a new Sequence Set Iterator
1078 * @param seqset Source Sequence Set
1079 * @retval ptr Newly allocated Sequence Set Iterator
1080 */
mutt_seqset_iterator_new(const char * seqset)1081 struct SeqsetIterator *mutt_seqset_iterator_new(const char *seqset)
1082 {
1083 if (!seqset || (*seqset == '\0'))
1084 return NULL;
1085
1086 struct SeqsetIterator *iter = mutt_mem_calloc(1, sizeof(struct SeqsetIterator));
1087 iter->full_seqset = mutt_str_dup(seqset);
1088 iter->eostr = strchr(iter->full_seqset, '\0');
1089 iter->substr_cur = iter->substr_end = iter->full_seqset;
1090
1091 return iter;
1092 }
1093
1094 /**
1095 * mutt_seqset_iterator_next - Get the next UID from a Sequence Set
1096 * @param[in] iter Sequence Set Iterator
1097 * @param[out] next Next UID in set
1098 * @retval 0 Next sequence is generated
1099 * @retval 1 Iterator is finished
1100 * @retval -1 error
1101 */
mutt_seqset_iterator_next(struct SeqsetIterator * iter,unsigned int * next)1102 int mutt_seqset_iterator_next(struct SeqsetIterator *iter, unsigned int *next)
1103 {
1104 if (!iter || !next)
1105 return -1;
1106
1107 if (iter->in_range)
1108 {
1109 if ((iter->down && (iter->range_cur == (iter->range_end - 1))) ||
1110 (!iter->down && (iter->range_cur == (iter->range_end + 1))))
1111 {
1112 iter->in_range = 0;
1113 }
1114 }
1115
1116 if (!iter->in_range)
1117 {
1118 iter->substr_cur = iter->substr_end;
1119 if (iter->substr_cur == iter->eostr)
1120 return 1;
1121
1122 iter->substr_end = strchr(iter->substr_cur, ',');
1123 if (!iter->substr_end)
1124 iter->substr_end = iter->eostr;
1125 else
1126 *(iter->substr_end++) = '\0';
1127
1128 char *range_sep = strchr(iter->substr_cur, ':');
1129 if (range_sep)
1130 *range_sep++ = '\0';
1131
1132 if (mutt_str_atoui(iter->substr_cur, &iter->range_cur) != 0)
1133 return -1;
1134 if (range_sep)
1135 {
1136 if (mutt_str_atoui(range_sep, &iter->range_end) != 0)
1137 return -1;
1138 }
1139 else
1140 iter->range_end = iter->range_cur;
1141
1142 iter->down = (iter->range_end < iter->range_cur);
1143 iter->in_range = 1;
1144 }
1145
1146 *next = iter->range_cur;
1147 if (iter->down)
1148 iter->range_cur--;
1149 else
1150 iter->range_cur++;
1151
1152 return 0;
1153 }
1154
1155 /**
1156 * mutt_seqset_iterator_free - Free a Sequence Set Iterator
1157 * @param[out] ptr Iterator to free
1158 */
mutt_seqset_iterator_free(struct SeqsetIterator ** ptr)1159 void mutt_seqset_iterator_free(struct SeqsetIterator **ptr)
1160 {
1161 if (!ptr || !*ptr)
1162 return;
1163
1164 struct SeqsetIterator *iter = *ptr;
1165 FREE(&iter->full_seqset);
1166 FREE(ptr);
1167 }
1168