simplify charset and things in the sockets API.
[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(idata->conn, out);
190   p_delete(&out);
191
192   if (rc < 0) {
193     cmd_handle_fatal (idata);
194     return -1;
195   }
196
197   do
198     rc = imap_cmd_step (idata);
199   while (rc == IMAP_CMD_CONTINUE);
200
201   if (rc == IMAP_CMD_BAD) {
202     if (imap_reconnect (idata->ctx) != 0) {
203       return -1;
204     }
205     return 0;
206   }
207
208   if (rc == IMAP_CMD_NO && (flags & IMAP_CMD_FAIL_OK))
209     return -2;
210
211   if (rc != IMAP_CMD_OK) {
212     return (flags & IMAP_CMD_FAIL_OK) ? -2 : -1;
213   }
214
215   return 0;
216 }
217
218 /* imap_cmd_running: Returns whether an IMAP command is in progress. */
219 int imap_cmd_running (IMAP_DATA * idata)
220 {
221   if (idata->cmd.state == IMAP_CMD_CONTINUE ||
222       idata->cmd.state == IMAP_CMD_RESPOND)
223     return 1;
224
225   return 0;
226 }
227
228 /* imap_cmd_finish: Attempts to perform cleanup (eg fetch new mail if
229  *   detected, do expunge). Called automatically by imap_cmd_step, but
230  *   may be called at any time. Called by imap_check_mailbox just before
231  *   the index is refreshed, for instance. */
232 void imap_cmd_finish (IMAP_DATA * idata)
233 {
234   if (idata->status == IMAP_FATAL) {
235     cmd_handle_fatal (idata);
236     return;
237   }
238
239   if (!(idata->state == IMAP_SELECTED) || idata->ctx->closing)
240     return;
241
242   if (idata->reopen & IMAP_REOPEN_ALLOW) {
243     int count = idata->newMailCount;
244
245     if (!(idata->reopen & IMAP_EXPUNGE_PENDING) &&
246         (idata->reopen & IMAP_NEWMAIL_PENDING)
247         && count > idata->ctx->msgcount) {
248       /* read new mail messages */
249       /* check_status: curs_main uses imap_check_mailbox to detect
250        *   whether the index needs updating */
251       idata->check_status = IMAP_NEWMAIL_PENDING;
252       imap_read_headers (idata, idata->ctx->msgcount, count - 1);
253     }
254     else if (idata->reopen & IMAP_EXPUNGE_PENDING) {
255       imap_expunge_mailbox (idata);
256       /* Detect whether we've gotten unexpected EXPUNGE messages */
257       if (idata->reopen & IMAP_EXPUNGE_PENDING &&
258           !(idata->reopen & IMAP_EXPUNGE_EXPECTED))
259         idata->check_status = IMAP_EXPUNGE_PENDING;
260       idata->reopen &= ~(IMAP_EXPUNGE_PENDING | IMAP_NEWMAIL_PENDING |
261                          IMAP_EXPUNGE_EXPECTED);
262     }
263   }
264
265   idata->status = 0;
266 }
267
268 /* cmd_handle_fatal: when IMAP_DATA is in fatal state, do what we can */
269 static void cmd_handle_fatal (IMAP_DATA * idata)
270 {
271   idata->status = IMAP_FATAL;
272
273   if ((idata->state == IMAP_SELECTED) &&
274       (idata->reopen & IMAP_REOPEN_ALLOW)) {
275     /* mx_fastclose_mailbox (idata->ctx); */
276     mutt_error (_("Mailbox closed"));
277     mutt_sleep (1);
278     idata->state = IMAP_DISCONNECTED;
279     if (imap_reconnect (idata->ctx) != 0)
280       mx_fastclose_mailbox (idata->ctx);
281   }
282
283   if (idata->state != IMAP_SELECTED) {
284     idata->state = IMAP_DISCONNECTED;
285     mutt_socket_close (idata->conn);
286     idata->status = 0;
287   }
288 }
289
290 /* cmd_handle_untagged: fallback parser for otherwise unhandled messages. */
291 static int cmd_handle_untagged (IMAP_DATA * idata)
292 {
293   char *s;
294   char *pn;
295   int count;
296
297   s = imap_next_word (idata->cmd.buf);
298
299   if ((idata->state == IMAP_SELECTED) && isdigit ((unsigned char) *s)) {
300     pn = s;
301     s = imap_next_word (s);
302
303     /* EXISTS and EXPUNGE are always related to the SELECTED mailbox for the
304      * connection, so update that one.
305      */
306     if (ascii_strncasecmp ("EXISTS", s, 6) == 0) {
307       /* new mail arrived */
308       count = atoi (pn);
309
310       if (!(idata->reopen & IMAP_EXPUNGE_PENDING) &&
311           count < idata->ctx->msgcount) {
312         /* something is wrong because the server reported fewer messages
313          * than we previously saw
314          */
315         mutt_error _("Fatal error.  Message count is out of sync!");
316
317         idata->status = IMAP_FATAL;
318         return -1;
319       }
320       /* at least the InterChange server sends EXISTS messages freely,
321        * even when there is no new mail */
322       else if (count == idata->ctx->msgcount)
323         ;
324       else {
325         if (!(idata->reopen & IMAP_EXPUNGE_PENDING)) {
326           idata->reopen |= IMAP_NEWMAIL_PENDING;
327         }
328         idata->newMailCount = count;
329       }
330     }
331     /* pn vs. s: need initial seqno */
332     else if (ascii_strncasecmp ("EXPUNGE", s, 7) == 0)
333       cmd_parse_expunge (idata, pn);
334     else if (ascii_strncasecmp ("FETCH", s, 5) == 0)
335       cmd_parse_fetch (idata, pn);
336   }
337   else if (ascii_strncasecmp ("CAPABILITY", s, 10) == 0)
338     cmd_parse_capabilities (idata, s);
339   else if (ascii_strncasecmp ("LSUB", s, 4) == 0)
340     cmd_parse_lsub (idata, s);
341   else if (ascii_strncasecmp ("MYRIGHTS", s, 8) == 0)
342     cmd_parse_myrights (idata, s);
343   else if (ascii_strncasecmp ("SEARCH", s, 6) == 0)
344     cmd_parse_search (idata, s);
345   else if (ascii_strncasecmp ("BYE", s, 3) == 0) {
346     /* check if we're logging out */
347     if (idata->status == IMAP_BYE)
348       return 0;
349
350     /* server shut down our connection */
351     s = vskipspaces(s + 3);
352     mutt_error ("%s", s);
353     mutt_sleep (2);
354     cmd_handle_fatal (idata);
355     return -1;
356   }
357   else if (option (OPTIMAPSERVERNOISE)
358            && (ascii_strncasecmp ("NO", s, 2) == 0)) {
359     /* Display the warning message from the server */
360     mutt_error ("%s", s + 3);
361     mutt_sleep (2);
362   }
363
364   return 0;
365 }
366
367 /* This should be optimised (eg with a tree or hash) */
368 static int uid2msgno (IMAP_DATA* idata, int uid) {
369   int i;
370
371   for (i = 0; i < idata->ctx->msgcount; i++) {
372     HEADER* h = idata->ctx->hdrs[i];
373     if (HEADER_DATA(h)->uid == uid)
374       return i;
375   }
376
377  return -1;
378 }
379
380 /* cmd_parse_search: store SEARCH response for later use */
381 static void cmd_parse_search (IMAP_DATA* idata, char* s) {
382   unsigned int uid;
383   int msgno;
384
385   while ((s = imap_next_word (s)) && *s != '\0') {
386     uid = atoi (s);
387     msgno = uid2msgno (idata, uid);
388
389     if (msgno >= 0)
390       idata->ctx->hdrs[uid2msgno (idata, uid)]->matched = 1;
391   }
392 }
393
394 /* cmd_make_sequence: make a tag suitable for starting an IMAP command */
395 static void cmd_make_sequence (IMAP_DATA * idata)
396 {
397   snprintf (idata->cmd.seq, sizeof (idata->cmd.seq), "a%04u", idata->seqno++);
398
399   if (idata->seqno > 9999)
400     idata->seqno = 0;
401 }
402
403 /* cmd_parse_capabilities: set capability bits according to CAPABILITY
404  *   response */
405 static void cmd_parse_capabilities (IMAP_DATA * idata, char *s)
406 {
407   int x;
408
409   s = imap_next_word (s);
410   p_delete(&idata->capstr);
411   idata->capstr = m_strdup(s);
412
413   p_clear(idata->capabilities, 1);
414
415   while (*s) {
416     for (x = 0; x < CAPMAX; x++)
417       if (imap_wordcasecmp (Capabilities[x], s) == 0) {
418         mutt_bit_set (idata->capabilities, x);
419         break;
420       }
421     s = imap_next_word (s);
422   }
423 }
424
425 /* cmd_parse_expunge: mark headers with new sequence ID and mark idata to
426  *   be reopened at our earliest convenience */
427 static void cmd_parse_expunge (IMAP_DATA * idata, const char *s)
428 {
429   int expno, cur;
430   HEADER *h;
431
432   expno = atoi (s);
433
434   /* walk headers, zero seqno of expunged message, decrement seqno of those
435    * above. Possibly we could avoid walking the whole list by resorting
436    * and guessing a good starting point, but I'm guessing the resort would
437    * nullify the gains */
438   for (cur = 0; cur < idata->ctx->msgcount; cur++) {
439     h = idata->ctx->hdrs[cur];
440
441     if (h->index + 1 == expno)
442       h->index = -1;
443     else if (h->index + 1 > expno)
444       h->index--;
445   }
446
447   idata->reopen |= IMAP_EXPUNGE_PENDING;
448 }
449
450 /* cmd_parse_fetch: Load fetch response into IMAP_DATA. Currently only
451  *   handles unanticipated FETCH responses, and only FLAGS data. We get
452  *   these if another client has changed flags for a mailbox we've selected.
453  *   Of course, a lot of code here duplicates code in message.c. */
454 static void cmd_parse_fetch (IMAP_DATA * idata, char *s)
455 {
456   int msgno, cur;
457   HEADER *h = NULL;
458
459   msgno = atoi (s);
460
461   if (msgno <= idata->ctx->msgcount)
462     /* see cmd_parse_expunge */
463     for (cur = 0; cur < idata->ctx->msgcount; cur++) {
464       h = idata->ctx->hdrs[cur];
465
466       if (!h)
467         break;
468
469       if (h->active && h->index + 1 == msgno) {
470         break;
471       }
472
473       h = NULL;
474     }
475
476   if (!h) {
477     return;
478   }
479
480   /* skip FETCH */
481   s = imap_next_word (s);
482   s = imap_next_word (s);
483
484   if (*s != '(') {
485     return;
486   }
487   s++;
488
489   if (ascii_strncasecmp ("FLAGS", s, 5) != 0) {
490     return;
491   }
492
493   /* If server flags could conflict with mutt's flags, reopen the mailbox. */
494   if (h->changed)
495     idata->reopen |= IMAP_EXPUNGE_PENDING;
496   else {
497     imap_set_flags (idata, h, s);
498     idata->check_status = IMAP_FLAGS_PENDING;
499   }
500 }
501
502 static void cmd_parse_lsub (IMAP_DATA* idata, char* s) {
503   char buf[STRING];
504   char errstr[STRING];
505   BUFFER err, token;
506   ciss_url_t url;
507   char *ep;
508
509   if (!option (OPTIMAPCHECKSUBSCRIBED))
510     return;
511
512   s = imap_next_word (s); /* flags */
513
514   if (*s != '(') {
515     return;
516   }
517
518   s++;
519   ep = s;
520   for (ep = s; *ep && *ep != ')'; ep++);
521   do {
522     if (!ascii_strncasecmp (s, "\\NoSelect", 9))
523       return;
524     while (s < ep && *s != ' ' && *s != ')')
525       s++;
526     if (*s == ' ')
527       s++;
528   } while (s != ep);
529
530   s = imap_next_word (s); /* delim */
531   s = imap_next_word (s); /* name */
532
533   if (s) {
534     imap_unmunge_mbox_name (s);
535
536     m_strcpy(buf, sizeof(buf), "mailboxes \"");
537     mutt_account_tourl (&idata->conn->account, &url);
538     url.path = s;
539     if (!m_strcmp(url.user, ImapUser))
540       url.user = NULL;
541     url_ciss_tostring (&url, buf + 11, sizeof (buf) - 10, 0);
542     m_strcat(buf, sizeof(buf), "\"");
543     p_clear(&token, 1);
544     err.data = errstr;
545     err.dsize = sizeof (errstr);
546     mutt_parse_rc_line (buf, &token, &err);
547     p_delete(&token.data);
548   }
549 }
550
551 /* cmd_parse_myrights: set rights bits according to MYRIGHTS response */
552 static void cmd_parse_myrights (IMAP_DATA * idata, char *s)
553 {
554   s = imap_next_word (s);
555   s = imap_next_word (s);
556
557   /* zero out current rights set */
558   p_clear(idata->rights, 1);
559
560   while (*s && !isspace ((unsigned char) *s)) {
561     switch (*s) {
562     case 'l':
563       mutt_bit_set (idata->rights, ACL_LOOKUP);
564       break;
565     case 'r':
566       mutt_bit_set (idata->rights, ACL_READ);
567       break;
568     case 's':
569       mutt_bit_set (idata->rights, ACL_SEEN);
570       break;
571     case 'w':
572       mutt_bit_set (idata->rights, ACL_WRITE);
573       break;
574     case 'i':
575       mutt_bit_set (idata->rights, ACL_INSERT);
576       break;
577     case 'p':
578       mutt_bit_set (idata->rights, ACL_POST);
579       break;
580     case 'c':
581       mutt_bit_set (idata->rights, ACL_CREATE);
582       break;
583     case 'd':
584       mutt_bit_set (idata->rights, ACL_DELETE);
585       break;
586     case 'a':
587       mutt_bit_set (idata->rights, ACL_ADMIN);
588       break;
589     }
590     s++;
591   }
592 }