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