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