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