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