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