]> git.llucax.com Git - software/mutt-debian.git/blob - imap/util.c
Merge commit 'upstream/1.5.20'
[software/mutt-debian.git] / imap / util.c
1 /*
2  * Copyright (C) 1996-8 Michael R. Elkins <me@mutt.org>
3  * Copyright (C) 1996-9 Brandon Long <blong@fiction.net>
4  * Copyright (C) 1999-2009 Brendan Cully <brendan@kublai.com>
5  *
6  *     This program is free software; you can redistribute it and/or modify
7  *     it under the terms of the GNU General Public License as published by
8  *     the Free Software Foundation; either version 2 of the License, or
9  *     (at your option) any later version.
10  *
11  *     This program is distributed in the hope that it will be useful,
12  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *     GNU General Public License for more details.
15  *
16  *     You should have received a copy of the GNU General Public License
17  *     along with this program; if not, write to the Free Software
18  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20
21 /* general IMAP utility functions */
22
23 #include "config.h"
24
25 #include "mutt.h"
26 #include "mx.h" /* for M_IMAP */
27 #include "url.h"
28 #include "imap_private.h"
29 #ifdef USE_HCACHE
30 #include "message.h"
31 #include "hcache.h"
32 #endif
33
34 #include <stdlib.h>
35 #include <ctype.h>
36
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <signal.h>
40 #include <netdb.h>
41 #include <netinet/in.h>
42
43 #include <errno.h>
44
45 /* -- public functions -- */
46
47 /* imap_expand_path: IMAP implementation of mutt_expand_path. Rewrite
48  *   an IMAP path in canonical and absolute form.
49  * Inputs: a buffer containing an IMAP path, and the number of bytes in
50  *   that buffer.
51  * Outputs: The buffer is rewritten in place with the canonical IMAP path.
52  * Returns 0 on success, or -1 if imap_parse_path chokes or url_ciss_tostring
53  *   fails, which it might if there isn't enough room in the buffer. */
54 int imap_expand_path (char* path, size_t len)
55 {
56   IMAP_MBOX mx;
57   IMAP_DATA* idata;
58   ciss_url_t url;
59   char fixedpath[LONG_STRING];
60   int rc;
61
62   if (imap_parse_path (path, &mx) < 0)
63     return -1;
64
65   idata = imap_conn_find (&mx.account, M_IMAP_CONN_NONEW);
66   mutt_account_tourl (&mx.account, &url);
67   imap_fix_path (idata, mx.mbox, fixedpath, sizeof (fixedpath));
68   url.path = fixedpath;
69
70   rc = url_ciss_tostring (&url, path, len, U_DECODE_PASSWD);
71   FREE (&mx.mbox);
72
73   return rc;
74 }
75
76 #ifdef USE_HCACHE
77 static int imap_hcache_namer (const char* path, char* dest, size_t dlen)
78 {
79   return snprintf (dest, dlen, "%s.hcache", path);
80 }
81
82 header_cache_t* imap_hcache_open (IMAP_DATA* idata, const char* path)
83 {
84   IMAP_MBOX mx;
85   ciss_url_t url;
86   char cachepath[LONG_STRING];
87   char mbox[LONG_STRING];
88
89   if (path)
90     imap_cachepath (idata, path, mbox, sizeof (mbox));
91   else
92   {
93     if (!idata->ctx || imap_parse_path (idata->ctx->path, &mx) < 0)
94       return NULL;
95
96     imap_cachepath (idata, mx.mbox, mbox, sizeof (mbox));
97     FREE (&mx.mbox);
98   }
99
100   mutt_account_tourl (&idata->conn->account, &url);
101   url.path = mbox;
102   url_ciss_tostring (&url, cachepath, sizeof (cachepath), U_PATH);
103
104   return mutt_hcache_open (HeaderCache, cachepath, imap_hcache_namer);
105 }
106
107 void imap_hcache_close (IMAP_DATA* idata)
108 {
109   if (!idata->hcache)
110     return;
111
112   mutt_hcache_close (idata->hcache);
113   idata->hcache = NULL;
114 }
115
116 HEADER* imap_hcache_get (IMAP_DATA* idata, unsigned int uid)
117 {
118   char key[16];
119   unsigned int* uv;
120   HEADER* h = NULL;
121
122   if (!idata->hcache)
123     return NULL;
124
125   sprintf (key, "/%u", uid);
126   uv = (unsigned int*)mutt_hcache_fetch (idata->hcache, key,
127                                          imap_hcache_keylen);
128   if (uv)
129   {
130     if (*uv == idata->uid_validity)
131       h = mutt_hcache_restore ((unsigned char*)uv, NULL);
132     FREE (&uv);
133   }
134
135   return h;
136 }
137
138 int imap_hcache_put (IMAP_DATA* idata, HEADER* h)
139 {
140   char key[16];
141
142   if (!idata->hcache)
143     return -1;
144
145   sprintf (key, "/%u", HEADER_DATA (h)->uid);
146   return mutt_hcache_store (idata->hcache, key, h, idata->uid_validity,
147                             imap_hcache_keylen);
148 }
149
150 int imap_hcache_del (IMAP_DATA* idata, unsigned int uid)
151 {
152   char key[16];
153
154   if (!idata->hcache)
155     return -1;
156
157   sprintf (key, "/%u", uid);
158   return mutt_hcache_delete (idata->hcache, key, imap_hcache_keylen);
159 }
160 #endif
161
162 /* imap_parse_path: given an IMAP mailbox name, return host, port
163  *   and a path IMAP servers will recognise.
164  * mx.mbox is malloc'd, caller must free it */
165 int imap_parse_path (const char* path, IMAP_MBOX* mx)
166 {
167   static unsigned short ImapPort = 0;
168   static unsigned short ImapsPort = 0;
169   struct servent* service;
170   char tmp[128];
171   ciss_url_t url;
172   char *c;
173   int n;
174
175   if (!ImapPort)
176   {
177     service = getservbyname ("imap", "tcp");
178     if (service)
179       ImapPort = ntohs (service->s_port);
180     else
181       ImapPort = IMAP_PORT;
182     dprint (3, (debugfile, "Using default IMAP port %d\n", ImapPort));
183   }
184   if (!ImapsPort)
185   {
186     service = getservbyname ("imaps", "tcp");
187     if (service)
188       ImapsPort = ntohs (service->s_port);
189     else
190       ImapsPort = IMAP_SSL_PORT;
191     dprint (3, (debugfile, "Using default IMAPS port %d\n", ImapsPort));
192   }
193
194   /* Defaults */
195   memset(&mx->account, 0, sizeof(mx->account));
196   mx->account.port = ImapPort;
197   mx->account.type = M_ACCT_TYPE_IMAP;
198
199   c = safe_strdup (path);
200   url_parse_ciss (&url, c);
201   if (url.scheme == U_IMAP || url.scheme == U_IMAPS)
202   {
203     if (mutt_account_fromurl (&mx->account, &url) < 0 || !*mx->account.host)
204     {
205       FREE (&c);
206       return -1;
207     }
208
209     mx->mbox = safe_strdup (url.path);
210
211     if (url.scheme == U_IMAPS)
212       mx->account.flags |= M_ACCT_SSL;
213
214     FREE (&c);
215   }
216   /* old PINE-compatibility code */
217   else
218   {
219     FREE (&c);
220     if (sscanf (path, "{%127[^}]}", tmp) != 1)
221       return -1;
222
223     c = strchr (path, '}');
224     if (!c)
225       return -1;
226     else
227       /* walk past closing '}' */
228       mx->mbox = safe_strdup (c+1);
229
230     if ((c = strrchr (tmp, '@')))
231     {
232       *c = '\0';
233       strfcpy (mx->account.user, tmp, sizeof (mx->account.user));
234       strfcpy (tmp, c+1, sizeof (tmp));
235       mx->account.flags |= M_ACCT_USER;
236     }
237
238     if ((n = sscanf (tmp, "%127[^:/]%127s", mx->account.host, tmp)) < 1)
239     {
240       dprint (1, (debugfile, "imap_parse_path: NULL host in %s\n", path));
241       FREE (&mx->mbox);
242       return -1;
243     }
244
245     if (n > 1) {
246       if (sscanf (tmp, ":%hu%127s", &(mx->account.port), tmp) >= 1)
247         mx->account.flags |= M_ACCT_PORT;
248       if (sscanf (tmp, "/%s", tmp) == 1)
249       {
250         if (!ascii_strncmp (tmp, "ssl", 3))
251           mx->account.flags |= M_ACCT_SSL;
252         else
253         {
254           dprint (1, (debugfile, "imap_parse_path: Unknown connection type in %s\n", path));
255           FREE (&mx->mbox);
256           return -1;
257         }
258       }
259     }
260   }
261
262   if ((mx->account.flags & M_ACCT_SSL) && !(mx->account.flags & M_ACCT_PORT))
263     mx->account.port = ImapsPort;
264
265   return 0;
266 }
267
268 /* silly helper for mailbox name string comparisons, because of INBOX */
269 int imap_mxcmp (const char* mx1, const char* mx2)
270 {
271   char* b1;
272   char* b2;
273   int rc;
274
275   if (!mx1 || !*mx1)
276     mx1 = "INBOX";
277   if (!mx2 || !*mx2)
278     mx2 = "INBOX";
279   if (!ascii_strcasecmp (mx1, "INBOX") && !ascii_strcasecmp (mx2, "INBOX"))
280     return 0;
281
282   b1 = safe_malloc (strlen (mx1) + 1);
283   b2 = safe_malloc (strlen (mx2) + 1);
284
285   imap_fix_path (NULL, mx1, b1, strlen (mx1) + 1);
286   imap_fix_path (NULL, mx2, b2, strlen (mx2) + 1);
287
288   rc = mutt_strcmp (b1, b2);
289   FREE (&b1);
290   FREE (&b2);
291
292   return rc;
293 }
294
295 /* imap_pretty_mailbox: called by mutt_pretty_mailbox to make IMAP paths
296  *   look nice. */
297 void imap_pretty_mailbox (char* path)
298 {
299   IMAP_MBOX home, target;
300   ciss_url_t url;
301   char* delim;
302   int tlen;
303   int hlen = 0;
304   char home_match = 0;
305
306   if (imap_parse_path (path, &target) < 0)
307     return;
308
309   tlen = mutt_strlen (target.mbox);
310   /* check whether we can do '=' substitution */
311   if (mx_is_imap(Maildir) && !imap_parse_path (Maildir, &home))
312   {
313     hlen = mutt_strlen (home.mbox);
314     if (tlen && mutt_account_match (&home.account, &target.account) &&
315         !mutt_strncmp (home.mbox, target.mbox, hlen))
316     {
317       if (! hlen)
318         home_match = 1;
319       else
320         for (delim = ImapDelimChars; *delim != '\0'; delim++)
321           if (target.mbox[hlen] == *delim)
322             home_match = 1;
323     }
324     FREE (&home.mbox);
325   }
326
327   /* do the '=' substitution */
328   if (home_match) {
329     *path++ = '=';
330     /* copy remaining path, skipping delimiter */
331     if (! hlen)
332       hlen = -1;
333     memcpy (path, target.mbox + hlen + 1, tlen - hlen - 1);
334     path[tlen - hlen - 1] = '\0';
335   }
336   else
337   {
338     mutt_account_tourl (&target.account, &url);
339     url.path = target.mbox;
340     /* FIXME: That hard-coded constant is bogus. But we need the actual
341      *   size of the buffer from mutt_pretty_mailbox. And these pretty
342      *   operations usually shrink the result. Still... */
343     url_ciss_tostring (&url, path, 1024, 0);
344   }
345
346   FREE (&target.mbox);
347 }
348
349 /* -- library functions -- */
350
351 /* imap_continue: display a message and ask the user if she wants to
352  *   go on. */
353 int imap_continue (const char* msg, const char* resp)
354 {
355   imap_error (msg, resp);
356   return mutt_yesorno (_("Continue?"), 0);
357 }
358
359 /* imap_error: show an error and abort */
360 void imap_error (const char *where, const char *msg)
361 {
362   mutt_error ("%s [%s]\n", where, msg);
363   mutt_sleep (2);
364 }
365
366 /* imap_new_idata: Allocate and initialise a new IMAP_DATA structure.
367  *   Returns NULL on failure (no mem) */
368 IMAP_DATA* imap_new_idata (void)
369 {
370   IMAP_DATA* idata = safe_calloc (1, sizeof (IMAP_DATA));
371
372   if (!idata)
373     return NULL;
374
375   if (!(idata->cmdbuf = mutt_buffer_init (NULL)))
376     FREE (&idata);
377
378   idata->cmdslots = ImapPipelineDepth + 2;
379   if (!(idata->cmds = safe_calloc(idata->cmdslots, sizeof(*idata->cmds))))
380   {
381     mutt_buffer_free(&idata->cmdbuf);
382     FREE (&idata);
383   }
384
385   return idata;
386 }
387
388 /* imap_free_idata: Release and clear storage in an IMAP_DATA structure. */
389 void imap_free_idata (IMAP_DATA** idata)
390 {
391   if (!idata)
392     return;
393
394   FREE (&(*idata)->capstr);
395   mutt_free_list (&(*idata)->flags);
396   imap_mboxcache_free (*idata);
397   mutt_buffer_free(&(*idata)->cmdbuf);
398   FREE (&(*idata)->buf);
399   mutt_bcache_close (&(*idata)->bcache);
400   FREE (&(*idata)->cmds);
401   FREE (idata);         /* __FREE_CHECKED__ */
402 }
403
404 /*
405  * Fix up the imap path.  This is necessary because the rest of mutt
406  * assumes a hierarchy delimiter of '/', which is not necessarily true
407  * in IMAP.  Additionally, the filesystem converts multiple hierarchy
408  * delimiters into a single one, ie "///" is equal to "/".  IMAP servers
409  * are not required to do this.
410  * Moreover, IMAP servers may dislike the path ending with the delimiter.
411  */
412 char *imap_fix_path (IMAP_DATA *idata, const char *mailbox, char *path,
413     size_t plen)
414 {
415   int i = 0;
416   char delim = '\0';
417
418   if (idata)
419     delim = idata->delim;
420
421   while (mailbox && *mailbox && i < plen - 1)
422   {
423     if ((ImapDelimChars && strchr(ImapDelimChars, *mailbox))
424         || *mailbox == delim)
425     {
426       /* use connection delimiter if known. Otherwise use user delimiter */
427       if (!idata)
428         delim = *mailbox;
429
430       while (*mailbox &&
431              (strchr(ImapDelimChars, *mailbox) || *mailbox == delim))
432         mailbox++;
433       path[i] = delim;
434     }
435     else
436     {
437       path[i] = *mailbox;
438       mailbox++;
439     }
440     i++;
441   }
442   if (i && path[--i] != delim)
443     i++;
444   path[i] = '\0';
445
446   return path;
447 }
448
449 void imap_cachepath(IMAP_DATA* idata, const char* mailbox, char* dest,
450                     size_t dlen)
451 {
452   char* s;
453   const char* p = mailbox;
454
455   for (s = dest; p && *p && dlen; dlen--)
456   {
457     if (*p == idata->delim)
458     {
459       *s = '/';
460       /* simple way to avoid collisions with UIDs */
461       if (*(p + 1) >= '0' && *(p + 1) <= '9')
462       {
463         if (--dlen)
464           *++s = '_';
465       }
466     }
467     else
468       *s = *p;
469     p++;
470     s++;
471   }
472   *s = '\0';
473 }
474
475 /* imap_get_literal_count: write number of bytes in an IMAP literal into
476  *   bytes, return 0 on success, -1 on failure. */
477 int imap_get_literal_count(const char *buf, long *bytes)
478 {
479   char *pc;
480   char *pn;
481
482   if (!buf || !(pc = strchr (buf, '{')))
483     return -1;
484
485   pc++;
486   pn = pc;
487   while (isdigit ((unsigned char) *pc))
488     pc++;
489   *pc = 0;
490   *bytes = atoi(pn);
491
492   return 0;
493 }
494
495 /* imap_get_qualifier: in a tagged response, skip tag and status for
496  *   the qualifier message. Used by imap_copy_message for TRYCREATE */
497 char* imap_get_qualifier (char* buf)
498 {
499   char *s = buf;
500
501   /* skip tag */
502   s = imap_next_word (s);
503   /* skip OK/NO/BAD response */
504   s = imap_next_word (s);
505
506   return s;
507 }
508
509 /* imap_next_word: return index into string where next IMAP word begins */
510 char *imap_next_word (char *s)
511 {
512   int quoted = 0;
513
514   while (*s) {
515     if (*s == '\\') {
516       s++;
517       if (*s)
518         s++;
519       continue;
520     }
521     if (*s == '\"')
522       quoted = quoted ? 0 : 1;
523     if (!quoted && ISSPACE (*s))
524       break;
525     s++;
526   }
527
528   SKIPWS (s);
529   return s;
530 }
531
532 /* imap_parse_date: date is of the form: DD-MMM-YYYY HH:MM:SS +ZZzz */
533 time_t imap_parse_date (char *s)
534 {
535   struct tm t;
536   time_t tz;
537
538   t.tm_mday = (s[0] == ' '? s[1] - '0' : (s[0] - '0') * 10 + (s[1] - '0'));
539   s += 2;
540   if (*s != '-')
541     return 0;
542   s++;
543   t.tm_mon = mutt_check_month (s);
544   s += 3;
545   if (*s != '-')
546     return 0;
547   s++;
548   t.tm_year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0') - 1900;
549   s += 4;
550   if (*s != ' ')
551     return 0;
552   s++;
553
554   /* time */
555   t.tm_hour = (s[0] - '0') * 10 + (s[1] - '0');
556   s += 2;
557   if (*s != ':')
558     return 0;
559   s++;
560   t.tm_min = (s[0] - '0') * 10 + (s[1] - '0');
561   s += 2;
562   if (*s != ':')
563     return 0;
564   s++;
565   t.tm_sec = (s[0] - '0') * 10 + (s[1] - '0');
566   s += 2;
567   if (*s != ' ')
568     return 0;
569   s++;
570
571   /* timezone */
572   tz = ((s[1] - '0') * 10 + (s[2] - '0')) * 3600 +
573     ((s[3] - '0') * 10 + (s[4] - '0')) * 60;
574   if (s[0] == '+')
575     tz = -tz;
576
577   return (mutt_mktime (&t, 0) + tz);
578 }
579
580 /* imap_qualify_path: make an absolute IMAP folder target, given IMAP_MBOX
581  *   and relative path. */
582 void imap_qualify_path (char *dest, size_t len, IMAP_MBOX *mx, char* path)
583 {
584   ciss_url_t url;
585
586   mutt_account_tourl (&mx->account, &url);
587   url.path = path;
588
589   url_ciss_tostring (&url, dest, len, 0);
590 }
591
592
593 /* imap_quote_string: quote string according to IMAP rules:
594  *   surround string with quotes, escape " and \ with \ */
595 void imap_quote_string (char *dest, size_t dlen, const char *src)
596 {
597   char quote[] = "\"\\", *pt;
598   const char *s;
599
600   pt = dest;
601   s  = src;
602
603   *pt++ = '"';
604   /* save room for trailing quote-char */
605   dlen -= 2;
606
607   for (; *s && dlen; s++)
608   {
609     if (strchr (quote, *s))
610     {
611       dlen -= 2;
612       if (!dlen)
613         break;
614       *pt++ = '\\';
615       *pt++ = *s;
616     }
617     else
618     {
619       *pt++ = *s;
620       dlen--;
621     }
622   }
623   *pt++ = '"';
624   *pt = 0;
625 }
626
627 /* imap_unquote_string: equally stupid unquoting routine */
628 void imap_unquote_string (char *s)
629 {
630   char *d = s;
631
632   if (*s == '\"')
633     s++;
634   else
635     return;
636
637   while (*s)
638   {
639     if (*s == '\"')
640     {
641       *d = '\0';
642       return;
643     }
644     if (*s == '\\')
645     {
646       s++;
647     }
648     if (*s)
649     {
650       *d = *s;
651       d++;
652       s++;
653     }
654   }
655   *d = '\0';
656 }
657
658 /*
659  * Quoting and UTF-7 conversion
660  */
661
662 void imap_munge_mbox_name (char *dest, size_t dlen, const char *src)
663 {
664   char *buf;
665
666   buf = safe_strdup (src);
667   imap_utf7_encode (&buf);
668
669   imap_quote_string (dest, dlen, buf);
670
671   FREE (&buf);
672 }
673
674 void imap_unmunge_mbox_name (char *s)
675 {
676   char *buf;
677
678   imap_unquote_string(s);
679
680   buf = safe_strdup (s);
681   if (buf)
682   {
683     imap_utf7_decode (&buf);
684     strncpy (s, buf, strlen (s));
685   }
686
687   FREE (&buf);
688 }
689
690 /* imap_wordcasecmp: find word a in word list b */
691 int imap_wordcasecmp(const char *a, const char *b)
692 {
693   char tmp[SHORT_STRING];
694   char *s = (char *)b;
695   int i;
696
697   tmp[SHORT_STRING-1] = 0;
698   for(i=0;i < SHORT_STRING-2;i++,s++)
699   {
700     if (!*s || ISSPACE(*s))
701     {
702       tmp[i] = 0;
703       break;
704     }
705     tmp[i] = *s;
706   }
707   tmp[i+1] = 0;
708
709   return ascii_strcasecmp(a, tmp);
710 }
711
712 /*
713  * Imap keepalive: poll the current folder to keep the
714  * connection alive.
715  *
716  */
717
718 static RETSIGTYPE alrm_handler (int sig)
719 {
720   /* empty */
721 }
722
723 void imap_keepalive (void)
724 {
725   CONNECTION *conn;
726   CONTEXT *ctx = NULL;
727   IMAP_DATA *idata;
728
729   conn = mutt_socket_head ();
730   while (conn)
731   {
732     if (conn->account.type == M_ACCT_TYPE_IMAP)
733     {
734       idata = (IMAP_DATA*) conn->data;
735
736       if (idata->state >= IMAP_AUTHENTICATED
737           && time(NULL) >= idata->lastread + ImapKeepalive)
738       {
739         if (idata->ctx)
740           ctx = idata->ctx;
741         else
742         {
743           ctx = safe_calloc (1, sizeof (CONTEXT));
744           ctx->data = idata;
745         }
746         imap_check_mailbox (ctx, NULL, 1);
747         if (!idata->ctx)
748           FREE (&ctx);
749       }
750     }
751
752     conn = conn->next;
753   }
754 }
755
756 int imap_wait_keepalive (pid_t pid)
757 {
758   struct sigaction oldalrm;
759   struct sigaction act;
760   sigset_t oldmask;
761   int rc;
762
763   short imap_passive = option (OPTIMAPPASSIVE);
764
765   set_option (OPTIMAPPASSIVE);
766   set_option (OPTKEEPQUIET);
767
768   sigprocmask (SIG_SETMASK, NULL, &oldmask);
769
770   sigemptyset (&act.sa_mask);
771   act.sa_handler = alrm_handler;
772 #ifdef SA_INTERRUPT
773   act.sa_flags = SA_INTERRUPT;
774 #else
775   act.sa_flags = 0;
776 #endif
777
778   sigaction (SIGALRM, &act, &oldalrm);
779
780   alarm (ImapKeepalive);
781   while (waitpid (pid, &rc, 0) < 0 && errno == EINTR)
782   {
783     alarm (0); /* cancel a possibly pending alarm */
784     imap_keepalive ();
785     alarm (ImapKeepalive);
786   }
787
788   alarm (0);    /* cancel a possibly pending alarm */
789
790   sigaction (SIGALRM, &oldalrm, NULL);
791   sigprocmask (SIG_SETMASK, &oldmask, NULL);
792
793   unset_option (OPTKEEPQUIET);
794   if (!imap_passive)
795     unset_option (OPTIMAPPASSIVE);
796
797   return rc;
798 }
799
800 /* Allow/disallow re-opening a folder upon expunge. */
801
802 void imap_allow_reopen (CONTEXT *ctx)
803 {
804   if (ctx && ctx->magic == M_IMAP && CTX_DATA->ctx == ctx)
805     CTX_DATA->reopen |= IMAP_REOPEN_ALLOW;
806 }
807
808 void imap_disallow_reopen (CONTEXT *ctx)
809 {
810   if (ctx && ctx->magic == M_IMAP && CTX_DATA->ctx == ctx)
811     CTX_DATA->reopen &= ~IMAP_REOPEN_ALLOW;
812 }
813
814 int imap_account_match (const ACCOUNT* a1, const ACCOUNT* a2)
815 {
816   IMAP_DATA* a1_idata = imap_conn_find (a1, M_IMAP_CONN_NONEW);
817   IMAP_DATA* a2_idata = imap_conn_find (a2, M_IMAP_CONN_NONEW);
818   const ACCOUNT* a1_canon = a1_idata == NULL ? a1 : &a1_idata->conn->account;
819   const ACCOUNT* a2_canon = a2_idata == NULL ? a2 : &a2_idata->conn->account;
820
821   return mutt_account_match (a1_canon, a2_canon);
822 }