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