Andreas Krennmair:
[apps/madmutt.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-2002 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., 59 Temple Place - Suite 330, Boston, MA  02111, 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 #include "mutt_ssl.h"
30
31 #include <stdlib.h>
32 #include <ctype.h>
33
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <signal.h>
37 #include <netdb.h>
38 #include <netinet/in.h>
39
40 #include <errno.h>
41
42 /* -- public functions -- */
43
44 /* imap_expand_path: IMAP implementation of mutt_expand_path. Rewrite
45  *   an IMAP path in canonical and absolute form.
46  * Inputs: a buffer containing an IMAP path, and the number of bytes in
47  *   that buffer.
48  * Outputs: The buffer is rewritten in place with the canonical IMAP path.
49  * Returns 0 on success, or -1 if imap_parse_path chokes or url_ciss_tostring
50  *   fails, which it might if there isn't enough room in the buffer. */
51 int imap_expand_path (char* path, size_t len)
52 {
53   IMAP_MBOX mx;
54   ciss_url_t url;
55   int rc;
56
57   if (imap_parse_path (path, &mx) < 0)
58     return -1;
59
60   mutt_account_tourl (&mx.account, &url);
61   url.path = mx.mbox;
62
63   rc = url_ciss_tostring (&url, path, len, U_DECODE_PASSWD);
64   FREE (&mx.mbox);
65
66   return rc;
67 }
68
69 /* imap_parse_path: given an IMAP mailbox name, return host, port
70  *   and a path IMAP servers will recognise.
71  * mx.mbox is malloc'd, caller must free it */
72 int imap_parse_path (const char* path, IMAP_MBOX* mx)
73 {
74   static unsigned short ImapPort = 0;
75   static unsigned short ImapsPort = 0;
76   struct servent* service;
77   char tmp[128];
78   ciss_url_t url;
79   char *c;
80   int n;
81
82   if (!ImapPort)
83   {
84     service = getservbyname ("imap", "tcp");
85     if (service)
86       ImapPort = ntohs (service->s_port);
87     else
88       ImapPort = IMAP_PORT;
89     dprint (3, (debugfile, "Using default IMAP port %d\n", ImapPort));
90   }
91   if (!ImapsPort)
92   {
93     service = getservbyname ("imaps", "tcp");
94     if (service)
95       ImapsPort = ntohs (service->s_port);
96     else
97       ImapsPort = IMAP_SSL_PORT;
98     dprint (3, (debugfile, "Using default IMAPS port %d\n", ImapsPort));
99   }
100
101   /* Defaults */
102   mx->account.flags = 0;
103   mx->account.port = ImapPort;
104   mx->account.type = M_ACCT_TYPE_IMAP;
105
106   c = safe_strdup (path);
107   url_parse_ciss (&url, c);
108   if (url.scheme == U_IMAP || url.scheme == U_IMAPS)
109   {
110     if (mutt_account_fromurl (&mx->account, &url) < 0)
111     {
112       FREE (&c);
113       return -1;
114     }
115
116     mx->mbox = safe_strdup (url.path);
117
118     if (url.scheme == U_IMAPS)
119       mx->account.flags |= M_ACCT_SSL;
120
121     FREE (&c);
122   }
123   /* old PINE-compatibility code */
124   else
125   {
126     FREE (&c);
127     if (sscanf (path, "{%127[^}]}", tmp) != 1)
128       return -1;
129
130     c = strchr (path, '}');
131     if (!c)
132       return -1;
133     else
134       /* walk past closing '}' */
135       mx->mbox = safe_strdup (c+1);
136   
137     if ((c = strrchr (tmp, '@')))
138     {
139       *c = '\0';
140       strfcpy (mx->account.user, tmp, sizeof (mx->account.user));
141       strfcpy (tmp, c+1, sizeof (tmp));
142       mx->account.flags |= M_ACCT_USER;
143     }
144   
145     if ((n = sscanf (tmp, "%127[^:/]%127s", mx->account.host, tmp)) < 1)
146     {
147       dprint (1, (debugfile, "imap_parse_path: NULL host in %s\n", path));
148       FREE (&mx->mbox);
149       return -1;
150     }
151   
152     if (n > 1) {
153       if (sscanf (tmp, ":%hu%127s", &(mx->account.port), tmp) >= 1)
154         mx->account.flags |= M_ACCT_PORT;
155       if (sscanf (tmp, "/%s", tmp) == 1)
156       {
157         if (!ascii_strncmp (tmp, "ssl", 3))
158           mx->account.flags |= M_ACCT_SSL;
159         else
160         {
161           dprint (1, (debugfile, "imap_parse_path: Unknown connection type in %s\n", path));
162           FREE (&mx->mbox);
163           return -1;
164         }
165       }
166     }
167   }
168   
169 #if defined(USE_SSL) || defined(USE_GNUTLS)
170   if (option (OPTIMAPFORCESSL))
171     mx->account.flags |= M_ACCT_SSL;
172 #endif
173
174   if ((mx->account.flags & M_ACCT_SSL) && !(mx->account.flags & M_ACCT_PORT))
175     mx->account.port = ImapsPort;
176
177   return 0;
178 }
179
180 /* imap_pretty_mailbox: called by mutt_pretty_mailbox to make IMAP paths
181  *   look nice. */
182 void imap_pretty_mailbox (char* path)
183 {
184   IMAP_MBOX home, target;
185   ciss_url_t url;
186   char* delim;
187   int tlen;
188   int hlen = 0;
189   char home_match = 0;
190
191   if (imap_parse_path (path, &target) < 0)
192     return;
193
194   tlen = mutt_strlen (target.mbox);
195   /* check whether we can do '=' substitution */
196   if (mx_is_imap(Maildir) && !imap_parse_path (Maildir, &home))
197   {
198     hlen = mutt_strlen (home.mbox);
199     if (tlen && mutt_account_match (&home.account, &target.account) &&
200         !mutt_strncmp (home.mbox, target.mbox, hlen))
201     {
202       if (! hlen)
203         home_match = 1;
204       else
205         for (delim = ImapDelimChars; *delim != '\0'; delim++)
206           if (target.mbox[hlen] == *delim)
207             home_match = 1;
208     }
209     FREE (&home.mbox);
210   }
211
212   /* do the '=' substitution */
213   if (home_match) {
214     *path++ = '=';
215     /* copy remaining path, skipping delimiter */
216     if (! hlen)
217       hlen = -1;
218     memcpy (path, target.mbox + hlen + 1, tlen - hlen - 1);
219     path[tlen - hlen - 1] = '\0';
220   }
221   else
222   {
223     mutt_account_tourl (&target.account, &url);
224     url.path = target.mbox;
225     /* FIXME: That hard-coded constant is bogus. But we need the actual
226      *   size of the buffer from mutt_pretty_mailbox. And these pretty
227      *   operations usually shrink the result. Still... */
228     url_ciss_tostring (&url, path, 1024, 0);
229   }
230
231   FREE (&target.mbox);
232 }
233
234 /* -- library functions -- */
235
236 /* imap_continue: display a message and ask the user if she wants to
237  *   go on. */
238 int imap_continue (const char* msg, const char* resp)
239 {
240   imap_error (msg, resp);
241   return mutt_yesorno (_("Continue?"), 0);
242 }
243
244 /* imap_error: show an error and abort */
245 void imap_error (const char *where, const char *msg)
246 {
247   mutt_error ("%s [%s]\n", where, msg);
248   mutt_sleep (2);
249 }
250
251 /* imap_new_idata: Allocate and initialise a new IMAP_DATA structure.
252  *   Returns NULL on failure (no mem) */
253 IMAP_DATA* imap_new_idata (void) {
254   return safe_calloc (1, sizeof (IMAP_DATA));
255 }
256
257 /* imap_free_idata: Release and clear storage in an IMAP_DATA structure. */
258 void imap_free_idata (IMAP_DATA** idata) {
259   if (!idata)
260     return;
261
262   FREE (&(*idata)->capstr);
263   mutt_free_list (&(*idata)->flags);
264   FREE (&((*idata)->cmd.buf));
265   FREE (idata);
266 }
267
268 /*
269  * Fix up the imap path.  This is necessary because the rest of mutt
270  * assumes a hierarchy delimiter of '/', which is not necessarily true
271  * in IMAP.  Additionally, the filesystem converts multiple hierarchy
272  * delimiters into a single one, ie "///" is equal to "/".  IMAP servers
273  * are not required to do this.
274  * Moreover, IMAP servers may dislike the path ending with the delimiter.
275  */
276 char *imap_fix_path (IMAP_DATA *idata, char *mailbox, char *path, 
277     size_t plen)
278 {
279   int x = 0;
280
281   if (!mailbox || !*mailbox)
282   {
283     strfcpy (path, "INBOX", plen);
284     return path;
285   }
286
287   while (mailbox && *mailbox && (x < (plen - 1)))
288   {
289     if ((*mailbox == '/') || (*mailbox == idata->delim))
290     {
291       while ((*mailbox == '/') || (*mailbox == idata->delim)) mailbox++;
292       path[x] = idata->delim;
293     }
294     else
295     {
296       path[x] = *mailbox;
297       mailbox++;
298     }
299     x++;
300   }
301   if (x && path[--x] != idata->delim)
302     x++;
303   path[x] = '\0';
304   return path;
305 }
306
307 /* imap_get_literal_count: write number of bytes in an IMAP literal into
308  *   bytes, return 0 on success, -1 on failure. */
309 int imap_get_literal_count(const char *buf, long *bytes)
310 {
311   char *pc;
312   char *pn;
313
314   if (!(pc = strchr (buf, '{')))
315     return (-1);
316   pc++;
317   pn = pc;
318   while (isdigit ((unsigned char) *pc))
319     pc++;
320   *pc = 0;
321   *bytes = atoi(pn);
322   return (0);
323 }
324
325 /* imap_get_qualifier: in a tagged response, skip tag and status for
326  *   the qualifier message. Used by imap_copy_message for TRYCREATE */
327 char* imap_get_qualifier (char* buf)
328 {
329   char *s = buf;
330
331   /* skip tag */
332   s = imap_next_word (s);
333   /* skip OK/NO/BAD response */
334   s = imap_next_word (s);
335
336   return s;
337 }
338
339 /* imap_next_word: return index into string where next IMAP word begins */
340 char *imap_next_word (char *s)
341 {
342   int quoted = 0;
343
344   while (*s) {
345     if (*s == '\\') {
346       s++;
347       if (*s)
348         s++;
349       continue;
350     }
351     if (*s == '\"')
352       quoted = quoted ? 0 : 1;
353     if (!quoted && ISSPACE (*s))
354       break;
355     s++;
356   }
357
358   SKIPWS (s);
359   return s;
360 }
361
362 /* imap_parse_date: date is of the form: DD-MMM-YYYY HH:MM:SS +ZZzz */
363 time_t imap_parse_date (char *s)
364 {
365   struct tm t;
366   time_t tz;
367
368   t.tm_mday = (s[0] == ' '? s[1] - '0' : (s[0] - '0') * 10 + (s[1] - '0'));  
369   s += 2;
370   if (*s != '-')
371     return 0;
372   s++;
373   t.tm_mon = mutt_check_month (s);
374   s += 3;
375   if (*s != '-')
376     return 0;
377   s++;
378   t.tm_year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0') - 1900;
379   s += 4;
380   if (*s != ' ')
381     return 0;
382   s++;
383
384   /* time */
385   t.tm_hour = (s[0] - '0') * 10 + (s[1] - '0');
386   s += 2;
387   if (*s != ':')
388     return 0;
389   s++;
390   t.tm_min = (s[0] - '0') * 10 + (s[1] - '0');
391   s += 2;
392   if (*s != ':')
393     return 0;
394   s++;
395   t.tm_sec = (s[0] - '0') * 10 + (s[1] - '0');
396   s += 2;
397   if (*s != ' ')
398     return 0;
399   s++;
400
401   /* timezone */
402   tz = ((s[1] - '0') * 10 + (s[2] - '0')) * 3600 +
403     ((s[3] - '0') * 10 + (s[4] - '0')) * 60;
404   if (s[0] == '+')
405     tz = -tz;
406
407   return (mutt_mktime (&t, 0) + tz);
408 }
409
410 /* imap_qualify_path: make an absolute IMAP folder target, given IMAP_MBOX
411  *   and relative path. */
412 void imap_qualify_path (char *dest, size_t len, IMAP_MBOX *mx, char* path)
413 {
414   ciss_url_t url;
415
416   mutt_account_tourl (&mx->account, &url);
417   url.path = path;
418
419   url_ciss_tostring (&url, dest, len, 0);
420 }
421
422
423 /* imap_quote_string: quote string according to IMAP rules:
424  *   surround string with quotes, escape " and \ with \ */
425 void imap_quote_string (char *dest, size_t dlen, const char *src)
426 {
427   char quote[] = "\"\\", *pt;
428   const char *s;
429
430   pt = dest;
431   s  = src;
432
433   *pt++ = '"';
434   /* save room for trailing quote-char */
435   dlen -= 2;
436   
437   for (; *s && dlen; s++)
438   {
439     if (strchr (quote, *s))
440     {
441       dlen -= 2;
442       if (!dlen)
443         break;
444       *pt++ = '\\';
445       *pt++ = *s;
446     }
447     else
448     {
449       *pt++ = *s;
450       dlen--;
451     }
452   }
453   *pt++ = '"';
454   *pt = 0;
455 }
456
457 /* imap_unquote_string: equally stupid unquoting routine */
458 void imap_unquote_string (char *s)
459 {
460   char *d = s;
461
462   if (*s == '\"')
463     s++;
464   else
465     return;
466
467   while (*s)
468   {
469     if (*s == '\"')
470     {
471       *d = '\0';
472       return;
473     }
474     if (*s == '\\')
475     {
476       s++;
477     }
478     if (*s)
479     {
480       *d = *s;
481       d++;
482       s++;
483     }
484   }
485   *d = '\0';
486 }
487
488 /*
489  * Quoting and UTF-7 conversion
490  */
491
492 void imap_munge_mbox_name (char *dest, size_t dlen, const char *src)
493 {
494   char *buf;
495
496   buf = safe_strdup (src);
497   imap_utf7_encode (&buf);
498
499   imap_quote_string (dest, dlen, buf);
500
501   FREE (&buf);
502 }
503
504 void imap_unmunge_mbox_name (char *s)
505 {
506   char *buf;
507
508   imap_unquote_string(s);
509
510   buf = safe_strdup (s);
511   if (buf)
512   {
513     imap_utf7_decode (&buf);
514     strncpy (s, buf, strlen (s));
515   }
516   
517   FREE (&buf);
518 }
519
520 /* imap_wordcasecmp: find word a in word list b */
521 int imap_wordcasecmp(const char *a, const char *b)
522 {
523   char tmp[SHORT_STRING];
524   char *s = (char *)b;
525   int i;
526
527   tmp[SHORT_STRING-1] = 0;
528   for(i=0;i < SHORT_STRING-2;i++,s++)
529   {
530     if (!*s || ISSPACE(*s))
531     {
532       tmp[i] = 0;
533       break;
534     }
535     tmp[i] = *s;
536   }
537   tmp[i+1] = 0;
538
539   return ascii_strcasecmp(a, tmp);
540 }
541
542 /* 
543  * Imap keepalive: poll the current folder to keep the
544  * connection alive.
545  * 
546  */
547
548 static RETSIGTYPE alrm_handler (int sig)
549 {
550   /* empty */
551 }
552
553 void imap_keepalive (void)
554 {
555   CONNECTION *conn;
556   CONTEXT *ctx = NULL;
557   IMAP_DATA *idata;
558
559   conn = mutt_socket_head ();
560   while (conn)
561   {
562     if (conn->account.type == M_ACCT_TYPE_IMAP)
563     {
564       idata = (IMAP_DATA*) conn->data;
565
566       if (idata->state >= IMAP_AUTHENTICATED
567           && time(NULL) >= idata->lastread + ImapKeepalive)
568       {
569         if (idata->ctx)
570           ctx = idata->ctx;
571         else
572         {
573           ctx = safe_calloc (1, sizeof (CONTEXT));
574           ctx->data = idata;
575         }
576         imap_check_mailbox (ctx, NULL, 1);
577         if (!idata->ctx)
578           FREE (&ctx);
579       }
580     }
581
582     conn = conn->next;
583   }
584 }
585
586 int imap_wait_keepalive (pid_t pid)
587 {
588   struct sigaction oldalrm;
589   struct sigaction act;
590   sigset_t oldmask;
591   int rc;
592
593   short imap_passive = option (OPTIMAPPASSIVE);
594   
595   set_option (OPTIMAPPASSIVE);
596   set_option (OPTKEEPQUIET);
597
598   sigprocmask (SIG_SETMASK, NULL, &oldmask);
599
600   sigemptyset (&act.sa_mask);
601   act.sa_handler = alrm_handler;
602 #ifdef SA_INTERRUPT
603   act.sa_flags = SA_INTERRUPT;
604 #else
605   act.sa_flags = 0;
606 #endif
607
608   sigaction (SIGALRM, &act, &oldalrm);
609
610   alarm (ImapKeepalive);
611   while (waitpid (pid, &rc, 0) < 0 && errno == EINTR)
612   {
613     alarm (0); /* cancel a possibly pending alarm */
614     imap_keepalive ();
615     alarm (ImapKeepalive);
616   }
617
618   alarm (0);    /* cancel a possibly pending alarm */
619   
620   sigaction (SIGALRM, &oldalrm, NULL);
621   sigprocmask (SIG_SETMASK, &oldmask, NULL);
622
623   unset_option (OPTKEEPQUIET);
624   if (!imap_passive)
625     unset_option (OPTIMAPPASSIVE);
626
627   return rc;
628 }
629
630 /* Allow/disallow re-opening a folder upon expunge. */
631
632 void imap_allow_reopen (CONTEXT *ctx)
633 {
634   if (ctx && ctx->magic == M_IMAP && CTX_DATA->ctx == ctx)
635     CTX_DATA->reopen |= IMAP_REOPEN_ALLOW;
636 }
637
638 void imap_disallow_reopen (CONTEXT *ctx)
639 {
640   if (ctx && ctx->magic == M_IMAP && CTX_DATA->ctx == ctx)
641     CTX_DATA->reopen &= ~IMAP_REOPEN_ALLOW;
642 }