Rocco Rutte:
[apps/madmutt.git] / imap / command.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-2005 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 /* command.c: routines for sending commands to an IMAP server and parsing
13  *  responses */
14
15 #if HAVE_CONFIG_H
16 # include "config.h"
17 #endif
18
19 #include "mutt.h"
20 #include "imap_private.h"
21 #include "message.h"
22 #include "mx.h"
23
24 #include "lib/mem.h"
25 #include "lib/intl.h"
26 #include "lib/debug.h"
27
28 #include <ctype.h>
29 #include <stdlib.h>
30
31 #define IMAP_CMD_BUFSIZE 512
32
33 /* forward declarations */
34 static void cmd_handle_fatal (IMAP_DATA * idata);
35 static int cmd_handle_untagged (IMAP_DATA * idata);
36 static void cmd_make_sequence (IMAP_DATA * idata);
37 static void cmd_parse_capabilities (IMAP_DATA * idata, char *s);
38 static void cmd_parse_expunge (IMAP_DATA * idata, const char *s);
39 static void cmd_parse_lsub (IMAP_DATA* idata, char* s);
40 static void cmd_parse_fetch (IMAP_DATA * idata, char *s);
41 static void cmd_parse_myrights (IMAP_DATA * idata, char *s);
42
43 static char *Capabilities[] = {
44   "IMAP4",
45   "IMAP4rev1",
46   "STATUS",
47   "ACL",
48   "NAMESPACE",
49   "AUTH=CRAM-MD5",
50   "AUTH=GSSAPI",
51   "AUTH=ANONYMOUS",
52   "STARTTLS",
53   "LOGINDISABLED",
54
55   NULL
56 };
57
58 /* imap_cmd_start: Given an IMAP command, send it to the server.
59  *   Currently a minor convenience, but helps to route all IMAP commands
60  *   through a single interface. */
61 int imap_cmd_start (IMAP_DATA * idata, const char *cmd)
62 {
63   char *out;
64   int outlen;
65   int rc;
66
67   if (idata->status == IMAP_FATAL) {
68     cmd_handle_fatal (idata);
69     return IMAP_CMD_BAD;
70   }
71
72   cmd_make_sequence (idata);
73   /* seq, space, cmd, \r\n\0 */
74   outlen = str_len (idata->cmd.seq) + str_len (cmd) + 4;
75   out = (char *) mem_malloc (outlen);
76   snprintf (out, outlen, "%s %s\r\n", idata->cmd.seq, cmd);
77
78   rc = mutt_socket_write (idata->conn, out);
79
80   mem_free (&out);
81
82   return (rc < 0) ? IMAP_CMD_BAD : 0;
83 }
84
85 /* imap_cmd_step: Reads server responses from an IMAP command, detects
86  *   tagged completion response, handles untagged messages, can read
87  *   arbitrarily large strings (using malloc, so don't make it _too_
88  *   large!). */
89 int imap_cmd_step (IMAP_DATA * idata)
90 {
91   IMAP_COMMAND *cmd = &idata->cmd;
92   unsigned int len = 0;
93   int c;
94
95   if (idata->status == IMAP_FATAL) {
96     cmd_handle_fatal (idata);
97     return IMAP_CMD_BAD;
98   }
99
100   /* read into buffer, expanding buffer as necessary until we have a full
101    * line */
102   do {
103     if (len == cmd->blen) {
104       mem_realloc (&cmd->buf, cmd->blen + IMAP_CMD_BUFSIZE);
105       cmd->blen = cmd->blen + IMAP_CMD_BUFSIZE;
106       debug_print (3, ("grew buffer to %u bytes\n", cmd->blen));
107     }
108
109     c = mutt_socket_readln (cmd->buf + len, cmd->blen - len, idata->conn);
110     if (c <= 0) {
111       debug_print (1, ("Error reading server response.\n"));
112       /* cmd_handle_fatal (idata); */
113       return IMAP_CMD_BAD;
114     }
115
116     len += c;
117   }
118   /* if we've read all the way to the end of the buffer, we haven't read a
119    * full line (mutt_socket_readln strips the \r, so we always have at least
120    * one character free when we've read a full line) */
121   while (len == cmd->blen);
122
123   /* don't let one large string make cmd->buf hog memory forever */
124   if ((cmd->blen > IMAP_CMD_BUFSIZE) && (len <= IMAP_CMD_BUFSIZE)) {
125     mem_realloc (&cmd->buf, IMAP_CMD_BUFSIZE);
126     cmd->blen = IMAP_CMD_BUFSIZE;
127     debug_print (3, ("shrank buffer to %u bytes\n", cmd->blen));
128   }
129
130   idata->lastread = time (NULL);
131
132   /* handle untagged messages. The caller still gets its shot afterwards. */
133   if (!ascii_strncmp (cmd->buf, "* ", 2) && cmd_handle_untagged (idata))
134     return IMAP_CMD_BAD;
135
136   /* server demands a continuation response from us */
137   if (!ascii_strncmp (cmd->buf, "+ ", 2)) {
138     return IMAP_CMD_RESPOND;
139   }
140
141   /* tagged completion code */
142   if (!ascii_strncmp (cmd->buf, cmd->seq, SEQLEN)) {
143     imap_cmd_finish (idata);
144     return imap_code (cmd->buf) ? IMAP_CMD_OK : IMAP_CMD_NO;
145   }
146
147   return IMAP_CMD_CONTINUE;
148 }
149
150 /* imap_code: returns 1 if the command result was OK, or 0 if NO or BAD */
151 int imap_code (const char *s)
152 {
153   s += SEQLEN;
154   SKIPWS (s);
155   return (ascii_strncasecmp ("OK", s, 2) == 0);
156 }
157
158 /* imap_exec: execute a command, and wait for the response from the server.
159  * Also, handle untagged responses.
160  * Flags:
161  *   IMAP_CMD_FAIL_OK: the calling procedure can handle failure. This is used
162  *     for checking for a mailbox on append and login
163  *   IMAP_CMD_PASS: command contains a password. Suppress logging.
164  * Return 0 on success, -1 on Failure, -2 on OK Failure
165  */
166 int imap_exec (IMAP_DATA * idata, const char *cmd, int flags)
167 {
168   char *out;
169   int outlen;
170   int rc;
171
172   if (!idata) {
173     mutt_error (_("No mailbox is open."));
174     mutt_sleep (1);
175     return (-1);
176   }
177
178   if (idata->status == IMAP_FATAL) {
179     cmd_handle_fatal (idata);
180     return -1;
181   }
182
183   /* create sequence for command */
184   cmd_make_sequence (idata);
185   /* seq, space, cmd, \r\n\0 */
186   outlen = str_len (idata->cmd.seq) + str_len (cmd) + 4;
187   out = (char *) mem_malloc (outlen);
188   snprintf (out, outlen, "%s %s\r\n", idata->cmd.seq, cmd);
189
190   rc = mutt_socket_write_d (idata->conn, out,
191                             flags & IMAP_CMD_PASS ? IMAP_LOG_PASS :
192                             IMAP_LOG_CMD);
193   mem_free (&out);
194
195   if (rc < 0) {
196     cmd_handle_fatal (idata);
197     return -1;
198   }
199
200   do
201     rc = imap_cmd_step (idata);
202   while (rc == IMAP_CMD_CONTINUE);
203
204   if (rc == IMAP_CMD_BAD) {
205     if (imap_reconnect (idata->ctx) != 0) {
206       return -1;
207     }
208     return 0;
209   }
210
211   if (rc == IMAP_CMD_NO && (flags & IMAP_CMD_FAIL_OK))
212     return -2;
213
214   if (rc != IMAP_CMD_OK) {
215     if (flags & IMAP_CMD_FAIL_OK)
216       return -2;
217
218     debug_print (1, ("command failed: %s\n", idata->cmd.buf));
219     return -1;
220   }
221
222   return 0;
223 }
224
225 /* imap_cmd_running: Returns whether an IMAP command is in progress. */
226 int imap_cmd_running (IMAP_DATA * idata)
227 {
228   if (idata->cmd.state == IMAP_CMD_CONTINUE ||
229       idata->cmd.state == IMAP_CMD_RESPOND)
230     return 1;
231
232   return 0;
233 }
234
235 /* imap_cmd_finish: Attempts to perform cleanup (eg fetch new mail if
236  *   detected, do expunge). Called automatically by imap_cmd_step, but
237  *   may be called at any time. Called by imap_check_mailbox just before
238  *   the index is refreshed, for instance. */
239 void imap_cmd_finish (IMAP_DATA * idata)
240 {
241   if (idata->status == IMAP_FATAL) {
242     cmd_handle_fatal (idata);
243     return;
244   }
245
246   if (!(idata->state == IMAP_SELECTED) || idata->ctx->closing)
247     return;
248
249   if (idata->reopen & IMAP_REOPEN_ALLOW) {
250     int count = idata->newMailCount;
251
252     if (!(idata->reopen & IMAP_EXPUNGE_PENDING) &&
253         (idata->reopen & IMAP_NEWMAIL_PENDING)
254         && count > idata->ctx->msgcount) {
255       /* read new mail messages */
256       debug_print (2, ("Fetching new mail\n"));
257       /* check_status: curs_main uses imap_check_mailbox to detect
258        *   whether the index needs updating */
259       idata->check_status = IMAP_NEWMAIL_PENDING;
260       imap_read_headers (idata, idata->ctx->msgcount, count - 1);
261     }
262     else if (idata->reopen & IMAP_EXPUNGE_PENDING) {
263       debug_print (2, ("Expunging mailbox\n"));
264       imap_expunge_mailbox (idata);
265       /* Detect whether we've gotten unexpected EXPUNGE messages */
266       if (idata->reopen & IMAP_EXPUNGE_PENDING &&
267           !(idata->reopen & IMAP_EXPUNGE_EXPECTED))
268         idata->check_status = IMAP_EXPUNGE_PENDING;
269       idata->reopen &= ~(IMAP_EXPUNGE_PENDING | IMAP_NEWMAIL_PENDING |
270                          IMAP_EXPUNGE_EXPECTED);
271     }
272   }
273
274   idata->status = 0;
275 }
276
277 /* cmd_handle_fatal: when IMAP_DATA is in fatal state, do what we can */
278 static void cmd_handle_fatal (IMAP_DATA * idata)
279 {
280   idata->status = IMAP_FATAL;
281
282   if ((idata->state == IMAP_SELECTED) &&
283       (idata->reopen & IMAP_REOPEN_ALLOW)) {
284     /* mx_fastclose_mailbox (idata->ctx); */
285     mutt_error (_("Mailbox closed"));
286     mutt_sleep (1);
287     idata->state = IMAP_DISCONNECTED;
288     if (imap_reconnect (idata->ctx) != 0)
289       mx_fastclose_mailbox (idata->ctx);
290   }
291
292   if (idata->state != IMAP_SELECTED) {
293     idata->state = IMAP_DISCONNECTED;
294     mutt_socket_close (idata->conn);
295     idata->status = 0;
296   }
297 }
298
299 /* cmd_handle_untagged: fallback parser for otherwise unhandled messages. */
300 static int cmd_handle_untagged (IMAP_DATA * idata)
301 {
302   char *s;
303   char *pn;
304   int count;
305
306   s = imap_next_word (idata->cmd.buf);
307
308   if ((idata->state == IMAP_SELECTED) && isdigit ((unsigned char) *s)) {
309     pn = s;
310     s = imap_next_word (s);
311
312     /* EXISTS and EXPUNGE are always related to the SELECTED mailbox for the
313      * connection, so update that one.
314      */
315     if (ascii_strncasecmp ("EXISTS", s, 6) == 0) {
316       debug_print (2, ("Handling EXISTS\n"));
317
318       /* new mail arrived */
319       count = atoi (pn);
320
321       if (!(idata->reopen & IMAP_EXPUNGE_PENDING) &&
322           count < idata->ctx->msgcount) {
323         /* something is wrong because the server reported fewer messages
324          * than we previously saw
325          */
326         mutt_error _("Fatal error.  Message count is out of sync!");
327
328         idata->status = IMAP_FATAL;
329         return -1;
330       }
331       /* at least the InterChange server sends EXISTS messages freely,
332        * even when there is no new mail */
333       else if (count == idata->ctx->msgcount)
334         debug_print (3, ("superfluous EXISTS message.\n"));
335       else {
336         if (!(idata->reopen & IMAP_EXPUNGE_PENDING)) {
337           debug_print (2, ("New mail in %s - %d messages total.\n", idata->mailbox, count));
338           idata->reopen |= IMAP_NEWMAIL_PENDING;
339         }
340         idata->newMailCount = count;
341       }
342     }
343     /* pn vs. s: need initial seqno */
344     else if (ascii_strncasecmp ("EXPUNGE", s, 7) == 0)
345       cmd_parse_expunge (idata, pn);
346     else if (ascii_strncasecmp ("FETCH", s, 5) == 0)
347       cmd_parse_fetch (idata, pn);
348   }
349   else if (ascii_strncasecmp ("CAPABILITY", s, 10) == 0)
350     cmd_parse_capabilities (idata, s);
351   else if (ascii_strncasecmp ("LSUB", s, 4) == 0)
352     cmd_parse_lsub (idata, s);
353   else if (ascii_strncasecmp ("MYRIGHTS", s, 8) == 0)
354     cmd_parse_myrights (idata, s);
355   else if (ascii_strncasecmp ("BYE", s, 3) == 0) {
356     debug_print (2, ("Handling BYE\n"));
357
358     /* check if we're logging out */
359     if (idata->status == IMAP_BYE)
360       return 0;
361
362     /* server shut down our connection */
363     s += 3;
364     SKIPWS (s);
365     mutt_error ("%s", s);
366     mutt_sleep (2);
367     cmd_handle_fatal (idata);
368     return -1;
369   }
370   else if (option (OPTIMAPSERVERNOISE)
371            && (ascii_strncasecmp ("NO", s, 2) == 0)) {
372     debug_print (2, ("Handling untagged NO\n"));
373
374     /* Display the warning message from the server */
375     mutt_error ("%s", s + 3);
376     mutt_sleep (2);
377   }
378
379   return 0;
380 }
381
382 /* cmd_make_sequence: make a tag suitable for starting an IMAP command */
383 static void cmd_make_sequence (IMAP_DATA * idata)
384 {
385   snprintf (idata->cmd.seq, sizeof (idata->cmd.seq), "a%04u", idata->seqno++);
386
387   if (idata->seqno > 9999)
388     idata->seqno = 0;
389 }
390
391 /* cmd_parse_capabilities: set capability bits according to CAPABILITY
392  *   response */
393 static void cmd_parse_capabilities (IMAP_DATA * idata, char *s)
394 {
395   int x;
396
397   debug_print (2, ("Handling CAPABILITY\n"));
398
399   s = imap_next_word (s);
400   mem_free (&idata->capstr);
401   idata->capstr = str_dup (s);
402
403   memset (idata->capabilities, 0, sizeof (idata->capabilities));
404
405   while (*s) {
406     for (x = 0; x < CAPMAX; x++)
407       if (imap_wordcasecmp (Capabilities[x], s) == 0) {
408         mutt_bit_set (idata->capabilities, x);
409         break;
410       }
411     s = imap_next_word (s);
412   }
413 }
414
415 /* cmd_parse_expunge: mark headers with new sequence ID and mark idata to
416  *   be reopened at our earliest convenience */
417 static void cmd_parse_expunge (IMAP_DATA * idata, const char *s)
418 {
419   int expno, cur;
420   HEADER *h;
421
422   debug_print (2, ("Handling EXPUNGE\n"));
423
424   expno = atoi (s);
425
426   /* walk headers, zero seqno of expunged message, decrement seqno of those
427    * above. Possibly we could avoid walking the whole list by resorting
428    * and guessing a good starting point, but I'm guessing the resort would
429    * nullify the gains */
430   for (cur = 0; cur < idata->ctx->msgcount; cur++) {
431     h = idata->ctx->hdrs[cur];
432
433     if (h->index + 1 == expno)
434       h->index = -1;
435     else if (h->index + 1 > expno)
436       h->index--;
437   }
438
439   idata->reopen |= IMAP_EXPUNGE_PENDING;
440 }
441
442 /* cmd_parse_fetch: Load fetch response into IMAP_DATA. Currently only
443  *   handles unanticipated FETCH responses, and only FLAGS data. We get
444  *   these if another client has changed flags for a mailbox we've selected.
445  *   Of course, a lot of code here duplicates code in message.c. */
446 static void cmd_parse_fetch (IMAP_DATA * idata, char *s)
447 {
448   int msgno, cur;
449   HEADER *h = NULL;
450
451   debug_print (2, ("Handling FETCH\n"));
452
453   msgno = atoi (s);
454
455   if (msgno <= idata->ctx->msgcount)
456     /* see cmd_parse_expunge */
457     for (cur = 0; cur < idata->ctx->msgcount; cur++) {
458       h = idata->ctx->hdrs[cur];
459
460       if (h->active && h->index + 1 == msgno) {
461         debug_print (2, ("Message UID %d updated\n", HEADER_DATA (h)->uid));
462         break;
463       }
464
465       h = NULL;
466     }
467
468   if (!h) {
469     debug_print (1, ("FETCH response ignored for this message\n"));
470     return;
471   }
472
473   /* skip FETCH */
474   s = imap_next_word (s);
475   s = imap_next_word (s);
476
477   if (*s != '(') {
478     debug_print (1, ("Malformed FETCH response\n"));
479     return;
480   }
481   s++;
482
483   if (ascii_strncasecmp ("FLAGS", s, 5) != 0) {
484     debug_print (2, ("Only handle FLAGS updates\n"));
485     return;
486   }
487
488   /* If server flags could conflict with mutt's flags, reopen the mailbox. */
489   if (h->changed)
490     idata->reopen |= IMAP_EXPUNGE_PENDING;
491   else {
492     imap_set_flags (idata, h, s);
493     idata->check_status = IMAP_FLAGS_PENDING;
494   }
495 }
496
497 static void cmd_parse_lsub (IMAP_DATA* idata, char* s) {
498   char buf[STRING];
499   char errstr[STRING];
500   BUFFER err, token;
501   ciss_url_t url;
502   char *ep;
503
504   if (!option (OPTIMAPCHECKSUBSCRIBED))
505     return;
506
507   s = imap_next_word (s); /* flags */
508
509   if (*s != '(') {
510     debug_print (1, ("Bad LSUB response\n"));
511     return;
512   }
513
514   s++;
515   ep = s;
516   for (ep = s; *ep && *ep != ')'; ep++);
517   do {
518     if (!ascii_strncasecmp (s, "\\NoSelect", 9))
519       return;
520     while (s < ep && *s != ' ' && *s != ')')
521       s++;
522     if (*s == ' ')
523       s++;
524   } while (s != ep);
525
526   s = imap_next_word (s); /* delim */
527   s = imap_next_word (s); /* name */
528
529   if (s) {
530     imap_unmunge_mbox_name (s);
531     debug_print (2, ("Subscribing to %s\n", s));
532
533     strfcpy (buf, "mailboxes \"", sizeof (buf));
534     mutt_account_tourl (&idata->conn->account, &url);
535     url.path = s;
536     if (!str_cmp (url.user, ImapUser))
537       url.user = NULL;
538     url_ciss_tostring (&url, buf + 11, sizeof (buf) - 10, 0);
539     str_cat (buf, sizeof (buf), "\"");
540     memset (&token, 0, sizeof (token));
541     err.data = errstr;
542     err.dsize = sizeof (errstr);
543     if (mutt_parse_rc_line (buf, &token, &err))
544       debug_print (1, ("Error adding subscribed mailbox: %s\n", errstr));
545     mem_free (&token.data);
546   }
547   else
548     debug_print (1, ("Bad LSUB response\n"));
549 }
550
551 /* cmd_parse_myrights: set rights bits according to MYRIGHTS response */
552 static void cmd_parse_myrights (IMAP_DATA * idata, char *s)
553 {
554   debug_print (2, ("Handling MYRIGHTS\n"));
555
556   s = imap_next_word (s);
557   s = imap_next_word (s);
558
559   /* zero out current rights set */
560   memset (idata->rights, 0, sizeof (idata->rights));
561
562   while (*s && !isspace ((unsigned char) *s)) {
563     switch (*s) {
564     case 'l':
565       mutt_bit_set (idata->rights, ACL_LOOKUP);
566       break;
567     case 'r':
568       mutt_bit_set (idata->rights, ACL_READ);
569       break;
570     case 's':
571       mutt_bit_set (idata->rights, ACL_SEEN);
572       break;
573     case 'w':
574       mutt_bit_set (idata->rights, ACL_WRITE);
575       break;
576     case 'i':
577       mutt_bit_set (idata->rights, ACL_INSERT);
578       break;
579     case 'p':
580       mutt_bit_set (idata->rights, ACL_POST);
581       break;
582     case 'c':
583       mutt_bit_set (idata->rights, ACL_CREATE);
584       break;
585     case 'd':
586       mutt_bit_set (idata->rights, ACL_DELETE);
587       break;
588     case 'a':
589       mutt_bit_set (idata->rights, ACL_ADMIN);
590       break;
591     }
592     s++;
593   }
594 }