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