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