mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-1998,2010,2012 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 1996-1999 Brandon Long <blong@fiction.net>
4 * Copyright (C) 1999-2009,2011 Brendan Cully <brendan@kublai.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21/* command.c: routines for sending commands to an IMAP server and parsing
22 * responses */
23
24#if HAVE_CONFIG_H
25# include "config.h"
26#endif
27
28#include "mutt.h"
29#include "mutt_menu.h"
30#include "imap_private.h"
31#include "mx.h"
32#include "buffy.h"
33
34#include <ctype.h>
35#include <stdlib.h>
36#include <errno.h>
37
38#define IMAP_CMD_BUFSIZE 512
39
40/* forward declarations */
41static int cmd_start (IMAP_DATA* idata, const char* cmdstr, int flags);
42static int cmd_queue_full (IMAP_DATA* idata);
43static int cmd_queue (IMAP_DATA* idata, const char* cmdstr, int flags);
44static IMAP_COMMAND* cmd_new (IMAP_DATA* idata);
45static int cmd_status (const char *s);
46static void cmd_handle_fatal (IMAP_DATA* idata);
47static int cmd_handle_untagged (IMAP_DATA* idata);
48static void cmd_parse_capability (IMAP_DATA* idata, char* s);
49static void cmd_parse_vanished (IMAP_DATA* idata, char* s);
50static void cmd_parse_expunge (IMAP_DATA* idata, const char* s);
51static void cmd_parse_list (IMAP_DATA* idata, char* s);
52static void cmd_parse_lsub (IMAP_DATA* idata, char* s);
53static void cmd_parse_fetch (IMAP_DATA* idata, char* s);
54static void cmd_parse_myrights (IMAP_DATA* idata, const char* s);
55static void cmd_parse_search (IMAP_DATA* idata, const char* s);
56static void cmd_parse_status (IMAP_DATA* idata, char* s);
57static void cmd_parse_enabled (IMAP_DATA* idata, const char* s);
58
59static const char * const Capabilities[] = {
60 "IMAP4",
61 "IMAP4rev1",
62 "STATUS",
63 "ACL",
64 "NAMESPACE",
65 "AUTH=CRAM-MD5",
66 "AUTH=GSSAPI",
67 "AUTH=ANONYMOUS",
68 "AUTH=OAUTHBEARER",
69 "STARTTLS",
70 "LOGINDISABLED",
71 "IDLE",
72 "SASL-IR",
73 "ENABLE",
74 "CONDSTORE",
75 "QRESYNC",
76 "LIST-EXTENDED",
77
78 NULL
79};
80
81/* imap_cmd_start: Given an IMAP command, send it to the server.
82 * If cmdstr is NULL, sends queued commands. */
83int imap_cmd_start (IMAP_DATA* idata, const char* cmdstr)
84{
85 return cmd_start (idata, cmdstr, 0);
86}
87
88/* imap_cmd_step: Reads server responses from an IMAP command, detects
89 * tagged completion response, handles untagged messages, can read
90 * arbitrarily large strings (using malloc, so don't make it _too_
91 * large!). */
92int imap_cmd_step (IMAP_DATA* idata)
93{
94 size_t len = 0;
95 int c;
96 int rc;
97 int stillrunning = 0;
98 IMAP_COMMAND* cmd;
99
100 if (idata->status == IMAP_FATAL)
101 {
102 cmd_handle_fatal (idata);
103 return IMAP_CMD_BAD;
104 }
105
106 /* read into buffer, expanding buffer as necessary until we have a full
107 * line */
108 do
109 {
110 if (len == idata->blen)
111 {
112 safe_realloc (&idata->buf, idata->blen + IMAP_CMD_BUFSIZE);
113 idata->blen = idata->blen + IMAP_CMD_BUFSIZE;
114 dprint (3, (debugfile, "imap_cmd_step: grew buffer to %u bytes\n",
115 idata->blen));
116 }
117
118 /* back up over '\0' */
119 if (len)
120 len--;
121 c = mutt_socket_readln (idata->buf + len, idata->blen - len, idata->conn);
122 if (c <= 0)
123 {
124 dprint (1, (debugfile, "imap_cmd_step: Error reading server response.\n"));
125 cmd_handle_fatal (idata);
126 return IMAP_CMD_BAD;
127 }
128
129 len += c;
130 }
131 /* if we've read all the way to the end of the buffer, we haven't read a
132 * full line (mutt_socket_readln strips the \r, so we always have at least
133 * one character free when we've read a full line) */
134 while (len == idata->blen);
135
136 /* don't let one large string make cmd->buf hog memory forever */
137 if ((idata->blen > IMAP_CMD_BUFSIZE) && (len <= IMAP_CMD_BUFSIZE))
138 {
139 safe_realloc (&idata->buf, IMAP_CMD_BUFSIZE);
140 idata->blen = IMAP_CMD_BUFSIZE;
141 dprint (3, (debugfile, "imap_cmd_step: shrank buffer to %u bytes\n", idata->blen));
142 }
143
144 idata->lastread = time (NULL);
145
146 /* handle untagged messages. The caller still gets its shot afterwards. */
147 if ((!ascii_strncmp (idata->buf, "* ", 2)
148 || !ascii_strncmp (imap_next_word (idata->buf), "OK [", 4))
149 && cmd_handle_untagged (idata))
150 return IMAP_CMD_BAD;
151
152 /* server demands a continuation response from us */
153 if (idata->buf[0] == '+')
154 return IMAP_CMD_RESPOND;
155
156 /* Look for tagged command completions.
157 *
158 * Some response handlers can end up recursively calling
159 * imap_cmd_step() and end up handling all tagged command
160 * completions.
161 * (e.g. FETCH->set_flag->set_header_color->~h pattern match.)
162 *
163 * Other callers don't even create an idata->cmds entry.
164 *
165 * For both these cases, we default to returning OK */
166 rc = IMAP_CMD_OK;
167 c = idata->lastcmd;
168 do
169 {
170 cmd = &idata->cmds[c];
171 if (cmd->state == IMAP_CMD_NEW)
172 {
173 if (!ascii_strncmp (idata->buf, cmd->seq, SEQLEN))
174 {
175 if (!stillrunning)
176 {
177 /* first command in queue has finished - move queue pointer up */
178 idata->lastcmd = (idata->lastcmd + 1) % idata->cmdslots;
179 }
180 cmd->state = cmd_status (idata->buf);
181 /* bogus - we don't know which command result to return here. Caller
182 * should provide a tag. */
183 rc = cmd->state;
184 }
185 else
186 stillrunning++;
187 }
188
189 c = (c + 1) % idata->cmdslots;
190 }
191 while (c != idata->nextcmd);
192
193 if (stillrunning)
194 rc = IMAP_CMD_CONTINUE;
195 else
196 {
197 dprint (3, (debugfile, "IMAP queue drained\n"));
198 imap_cmd_finish (idata);
199 }
200
201
202 return rc;
203}
204
205/* imap_code: returns 1 if the command result was OK, or 0 if NO or BAD */
206int imap_code (const char *s)
207{
208 return cmd_status (s) == IMAP_CMD_OK;
209}
210
211/* imap_cmd_trailer: extra information after tagged command response if any */
212const char* imap_cmd_trailer (IMAP_DATA* idata)
213{
214 static const char* notrailer = "";
215 const char* s = idata->buf;
216
217 if (!s)
218 {
219 dprint (2, (debugfile, "imap_cmd_trailer: not a tagged response"));
220 return notrailer;
221 }
222
223 s = imap_next_word ((char *)s);
224 if (!s || (ascii_strncasecmp (s, "OK", 2) &&
225 ascii_strncasecmp (s, "NO", 2) &&
226 ascii_strncasecmp (s, "BAD", 3)))
227 {
228 dprint (2, (debugfile, "imap_cmd_trailer: not a command completion: %s",
229 idata->buf));
230 return notrailer;
231 }
232
233 s = imap_next_word ((char *)s);
234 if (!s)
235 return notrailer;
236
237 return s;
238}
239
240/* imap_exec: execute a command, and wait for the response from the server.
241 * Also, handle untagged responses.
242 * Flags:
243 * IMAP_CMD_FAIL_OK: the calling procedure can handle failure. This is used
244 * for checking for a mailbox on append and login
245 * IMAP_CMD_PASS: command contains a password. Suppress logging.
246 * IMAP_CMD_QUEUE: only queue command, do not execute.
247 * IMAP_CMD_POLL: poll the socket for a response before running imap_cmd_step.
248 * Return 0 on success, -1 on Failure, -2 on OK Failure
249 */
250int imap_exec (IMAP_DATA* idata, const char* cmdstr, int flags)
251{
252 int rc;
253
254 if ((rc = cmd_start (idata, cmdstr, flags)) < 0)
255 {
256 cmd_handle_fatal (idata);
257 return -1;
258 }
259
260 if (flags & IMAP_CMD_QUEUE)
261 return 0;
262
263 if ((flags & IMAP_CMD_POLL) &&
264 (ImapPollTimeout > 0) &&
265 (mutt_socket_poll (idata->conn, ImapPollTimeout)) == 0)
266 {
267 mutt_error (_("Connection to %s timed out"), idata->conn->account.host);
268 mutt_sleep (2);
269 cmd_handle_fatal (idata);
270 return -1;
271 }
272
273 do
274 rc = imap_cmd_step (idata);
275 while (rc == IMAP_CMD_CONTINUE);
276
277 if (rc == IMAP_CMD_NO && (flags & IMAP_CMD_FAIL_OK))
278 return -2;
279
280 if (rc != IMAP_CMD_OK)
281 {
282 if ((flags & IMAP_CMD_FAIL_OK) && idata->status != IMAP_FATAL)
283 return -2;
284
285 dprint (1, (debugfile, "imap_exec: command failed: %s\n", idata->buf));
286 return -1;
287 }
288
289 return 0;
290}
291
292/* imap_cmd_finish
293 *
294 * If a reopen is allowed, it attempts to perform cleanup (eg fetch new
295 * mail if detected, do expunge). Called automatically by
296 * imap_cmd_step(), but may be called at any time.
297 *
298 * idata->check_status is set and will be used later by
299 * imap_check_mailbox().
300 */
301void imap_cmd_finish (IMAP_DATA* idata)
302{
303 if (idata->status == IMAP_FATAL)
304 {
305 cmd_handle_fatal (idata);
306 return;
307 }
308
309 if (!(idata->state >= IMAP_SELECTED) || idata->ctx->closing)
310 return;
311
312 if (idata->reopen & IMAP_REOPEN_ALLOW)
313 {
314 if (idata->reopen & IMAP_EXPUNGE_PENDING)
315 {
316 dprint (2, (debugfile, "imap_cmd_finish: Expunging mailbox\n"));
317 imap_expunge_mailbox (idata);
318 /* Detect whether we've gotten unexpected EXPUNGE messages */
319 if (!(idata->reopen & IMAP_EXPUNGE_EXPECTED))
320 idata->check_status |= IMAP_EXPUNGE_PENDING;
321 idata->reopen &= ~(IMAP_EXPUNGE_PENDING | IMAP_EXPUNGE_EXPECTED);
322 }
323 if (idata->reopen & IMAP_NEWMAIL_PENDING)
324 {
325 dprint (2, (debugfile, "imap_cmd_finish: Fetching new mail\n"));
326 imap_read_headers (idata, idata->max_msn+1, idata->newMailCount, 0);
327 idata->check_status |= IMAP_NEWMAIL_PENDING;
328 }
329 }
330
331 idata->status = 0;
332}
333
334/* imap_cmd_idle: Enter the IDLE state. */
335int imap_cmd_idle (IMAP_DATA* idata)
336{
337 int rc;
338
339 if (cmd_start (idata, "IDLE", IMAP_CMD_POLL) < 0)
340 {
341 cmd_handle_fatal (idata);
342 return -1;
343 }
344
345 if ((ImapPollTimeout > 0) &&
346 (mutt_socket_poll (idata->conn, ImapPollTimeout)) == 0)
347 {
348 mutt_error (_("Connection to %s timed out"), idata->conn->account.host);
349 mutt_sleep (2);
350 cmd_handle_fatal (idata);
351 return -1;
352 }
353
354 do
355 rc = imap_cmd_step (idata);
356 while (rc == IMAP_CMD_CONTINUE);
357
358 if (rc == IMAP_CMD_RESPOND)
359 {
360 /* successfully entered IDLE state */
361 idata->state = IMAP_IDLE;
362 /* queue automatic exit when next command is issued */
363 mutt_buffer_addstr (idata->cmdbuf, "DONE\r\n");
364 rc = IMAP_CMD_OK;
365 }
366 if (rc != IMAP_CMD_OK)
367 {
368 dprint (1, (debugfile, "imap_cmd_idle: error starting IDLE\n"));
369 return -1;
370 }
371
372 return 0;
373}
374
375static int cmd_queue_full (IMAP_DATA* idata)
376{
377 if ((idata->nextcmd + 1) % idata->cmdslots == idata->lastcmd)
378 return 1;
379
380 return 0;
381}
382
383/* sets up a new command control block and adds it to the queue.
384 * Returns NULL if the pipeline is full. */
385static IMAP_COMMAND* cmd_new (IMAP_DATA* idata)
386{
387 IMAP_COMMAND* cmd;
388
389 if (cmd_queue_full (idata))
390 {
391 dprint (3, (debugfile, "cmd_new: IMAP command queue full\n"));
392 return NULL;
393 }
394
395 cmd = idata->cmds + idata->nextcmd;
396 idata->nextcmd = (idata->nextcmd + 1) % idata->cmdslots;
397
398 snprintf (cmd->seq, sizeof (cmd->seq), "a%04u", idata->seqno++);
399 if (idata->seqno > 9999)
400 idata->seqno = 0;
401
402 cmd->state = IMAP_CMD_NEW;
403
404 return cmd;
405}
406
407/* queues command. If the queue is full, attempts to drain it. */
408static int cmd_queue (IMAP_DATA* idata, const char* cmdstr, int flags)
409{
410 IMAP_COMMAND* cmd;
411 int rc;
412
413 if (cmd_queue_full (idata))
414 {
415 dprint (3, (debugfile, "Draining IMAP command pipeline\n"));
416
417 rc = imap_exec (idata, NULL, IMAP_CMD_FAIL_OK | (flags & IMAP_CMD_POLL));
418
419 if (rc < 0 && rc != -2)
420 return rc;
421 }
422
423 if (!(cmd = cmd_new (idata)))
424 return IMAP_CMD_BAD;
425
426 if (mutt_buffer_add_printf (idata->cmdbuf, "%s %s\r\n", cmd->seq, cmdstr) < 0)
427 return IMAP_CMD_BAD;
428
429 return 0;
430}
431
432static int cmd_start (IMAP_DATA* idata, const char* cmdstr, int flags)
433{
434 int rc;
435
436 if (idata->status == IMAP_FATAL)
437 {
438 cmd_handle_fatal (idata);
439 return -1;
440 }
441
442 if (cmdstr && ((rc = cmd_queue (idata, cmdstr, flags)) < 0))
443 return rc;
444
445 if (flags & IMAP_CMD_QUEUE)
446 return 0;
447
448 if (mutt_buffer_len (idata->cmdbuf) == 0)
449 return IMAP_CMD_BAD;
450
451 rc = mutt_socket_write_d (idata->conn, idata->cmdbuf->data, -1,
452 flags & IMAP_CMD_PASS ? IMAP_LOG_PASS : IMAP_LOG_CMD);
453 mutt_buffer_clear (idata->cmdbuf);
454
455 /* unidle when command queue is flushed */
456 if (idata->state == IMAP_IDLE)
457 idata->state = IMAP_SELECTED;
458
459 return (rc < 0) ? IMAP_CMD_BAD : 0;
460}
461
462/* parse response line for tagged OK/NO/BAD */
463static int cmd_status (const char *s)
464{
465 s = imap_next_word((char*)s);
466
467 if (!ascii_strncasecmp("OK", s, 2))
468 return IMAP_CMD_OK;
469 if (!ascii_strncasecmp("NO", s, 2))
470 return IMAP_CMD_NO;
471
472 return IMAP_CMD_BAD;
473}
474
475/* cmd_handle_fatal: when IMAP_DATA is in fatal state, do what we can */
476static void cmd_handle_fatal (IMAP_DATA* idata)
477{
478 idata->status = IMAP_FATAL;
479
480 if ((idata->state >= IMAP_SELECTED) &&
481 (idata->reopen & IMAP_REOPEN_ALLOW))
482 {
483 mx_fastclose_mailbox (idata->ctx);
484 mutt_socket_close (idata->conn);
485 mutt_error (_("Mailbox %s@%s closed"),
486 idata->conn->account.login, idata->conn->account.host);
487 mutt_sleep (1);
488 idata->state = IMAP_DISCONNECTED;
489 }
490
491 if (idata->state < IMAP_SELECTED)
492 imap_close_connection (idata);
493}
494
495/* cmd_handle_untagged: fallback parser for otherwise unhandled messages. */
496static int cmd_handle_untagged (IMAP_DATA* idata)
497{
498 char* s;
499 char* pn;
500 unsigned int count;
501
502 s = imap_next_word (idata->buf);
503 pn = imap_next_word (s);
504
505 if ((idata->state >= IMAP_SELECTED) && isdigit ((unsigned char) *s))
506 {
507 pn = s;
508 s = imap_next_word (s);
509
510 /* EXISTS and EXPUNGE are always related to the SELECTED mailbox for the
511 * connection, so update that one.
512 */
513 if (ascii_strncasecmp ("EXISTS", s, 6) == 0)
514 {
515 dprint (2, (debugfile, "Handling EXISTS\n"));
516
517 /* new mail arrived */
518 mutt_atoui (pn, &count);
519
520 if (count < idata->max_msn)
521 {
522 /* Notes 6.0.3 has a tendency to report fewer messages exist than
523 * it should. */
524 dprint (1, (debugfile, "Message count is out of sync"));
525 return 0;
526 }
527 /* at least the InterChange server sends EXISTS messages freely,
528 * even when there is no new mail */
529 else if (count == idata->max_msn)
530 dprint (3, (debugfile,
531 "cmd_handle_untagged: superfluous EXISTS message.\n"));
532 else
533 {
534 dprint (2, (debugfile,
535 "cmd_handle_untagged: New mail in %s - %d messages total.\n",
536 idata->mailbox, count));
537 idata->reopen |= IMAP_NEWMAIL_PENDING;
538 idata->newMailCount = count;
539 }
540 }
541 /* pn vs. s: need initial seqno */
542 else if (ascii_strncasecmp ("EXPUNGE", s, 7) == 0)
543 cmd_parse_expunge (idata, pn);
544 else if (ascii_strncasecmp ("FETCH", s, 5) == 0)
545 cmd_parse_fetch (idata, pn);
546 }
547 else if ((idata->state >= IMAP_SELECTED) &&
548 ascii_strncasecmp ("VANISHED", s, 8) == 0)
549 cmd_parse_vanished (idata, pn);
550 else if (ascii_strncasecmp ("CAPABILITY", s, 10) == 0)
551 cmd_parse_capability (idata, s);
552 else if (!ascii_strncasecmp ("OK [CAPABILITY", s, 14))
553 cmd_parse_capability (idata, pn);
554 else if (!ascii_strncasecmp ("OK [CAPABILITY", pn, 14))
555 cmd_parse_capability (idata, imap_next_word (pn));
556 else if (ascii_strncasecmp ("LIST", s, 4) == 0)
557 cmd_parse_list (idata, s);
558 else if (ascii_strncasecmp ("LSUB", s, 4) == 0)
559 cmd_parse_lsub (idata, s);
560 else if (ascii_strncasecmp ("MYRIGHTS", s, 8) == 0)
561 cmd_parse_myrights (idata, s);
562 else if (ascii_strncasecmp ("SEARCH", s, 6) == 0)
563 cmd_parse_search (idata, s);
564 else if (ascii_strncasecmp ("STATUS", s, 6) == 0)
565 cmd_parse_status (idata, s);
566 else if (ascii_strncasecmp ("ENABLED", s, 7) == 0)
567 cmd_parse_enabled (idata, s);
568 else if (ascii_strncasecmp ("BYE", s, 3) == 0)
569 {
570 dprint (2, (debugfile, "Handling BYE\n"));
571
572 /* check if we're logging out */
573 if (idata->status == IMAP_BYE)
574 return 0;
575
576 /* server shut down our connection */
577 s += 3;
578 SKIPWS (s);
579 mutt_error ("%s", s);
580 mutt_sleep (2);
581 cmd_handle_fatal (idata);
582
583 return -1;
584 }
585 else if (option (OPTIMAPSERVERNOISE) && (ascii_strncasecmp ("NO", s, 2) == 0))
586 {
587 dprint (2, (debugfile, "Handling untagged NO\n"));
588
589 /* Display the warning message from the server */
590 mutt_error ("%s", s+2);
591 mutt_sleep (2);
592 }
593
594 return 0;
595}
596
597/* cmd_parse_capabilities: set capability bits according to CAPABILITY
598 * response */
599static void cmd_parse_capability (IMAP_DATA* idata, char* s)
600{
601 int x;
602 char* bracket;
603
604 dprint (3, (debugfile, "Handling CAPABILITY\n"));
605
606 s = imap_next_word (s);
607 if ((bracket = strchr (s, ']')))
608 *bracket = '\0';
609 FREE(&idata->capstr);
610 idata->capstr = safe_strdup (s);
611
612 memset (idata->capabilities, 0, sizeof (idata->capabilities));
613
614 while (*s)
615 {
616 for (x = 0; x < CAPMAX; x++)
617 if (imap_wordcasecmp(Capabilities[x], s) == 0)
618 {
619 mutt_bit_set (idata->capabilities, x);
620 break;
621 }
622 s = imap_next_word (s);
623 }
624}
625
626/* cmd_parse_expunge: mark headers with new sequence ID and mark idata to
627 * be reopened at our earliest convenience */
628static void cmd_parse_expunge (IMAP_DATA* idata, const char* s)
629{
630 unsigned int exp_msn, cur;
631 HEADER* h;
632
633 dprint (2, (debugfile, "Handling EXPUNGE\n"));
634
635 if (mutt_atoui (s, &exp_msn) < 0 ||
636 exp_msn < 1 || exp_msn > idata->max_msn)
637 return;
638
639 h = idata->msn_index[exp_msn - 1];
640 if (h)
641 {
642 /* imap_expunge_mailbox() will rewrite h->index.
643 * It needs to resort using SORT_ORDER anyway, so setting to INT_MAX
644 * makes the code simpler and possibly more efficient. */
645 h->index = INT_MAX;
646 HEADER_DATA(h)->msn = 0;
647 }
648
649 /* decrement seqno of those above. */
650 for (cur = exp_msn; cur < idata->max_msn; cur++)
651 {
652 h = idata->msn_index[cur];
653 if (h)
654 HEADER_DATA(h)->msn--;
655 idata->msn_index[cur - 1] = h;
656 }
657
658 idata->msn_index[idata->max_msn - 1] = NULL;
659 idata->max_msn--;
660
661 idata->reopen |= IMAP_EXPUNGE_PENDING;
662}
663
664/* cmd_parse_vanished: handles VANISHED (RFC 7162), which is like
665 * expunge, but passes a seqset of UIDs. An optional (EARLIER) argument
666 * specifies not to decrement subsequent MSNs. */
667static void cmd_parse_vanished (IMAP_DATA* idata, char* s)
668{
669 int earlier = 0, rc;
670 char *end_of_seqset;
671 SEQSET_ITERATOR *iter;
672 unsigned int uid, exp_msn, cur;
673 HEADER* h;
674
675 dprint (2, (debugfile, "Handling VANISHED\n"));
676
677 if (ascii_strncasecmp ("(EARLIER)", s, 9) == 0)
678 {
679 /* The RFC says we should not decrement msns with the VANISHED EARLIER tag.
680 * My experimentation says that's crap. */
681 /* earlier = 1; */
682 s = imap_next_word (s);
683 }
684
685 end_of_seqset = s;
686 while (*end_of_seqset)
687 {
688 if (!strchr ("0123456789:,", *end_of_seqset))
689 *end_of_seqset = '\0';
690 else
691 end_of_seqset++;
692 }
693
694 iter = mutt_seqset_iterator_new (s);
695 if (!iter)
696 {
697 dprint (2, (debugfile, "VANISHED: empty seqset [%s]?\n", s));
698 return;
699 }
700
701 while ((rc = mutt_seqset_iterator_next (iter, &uid)) == 0)
702 {
703 h = (HEADER *)int_hash_find (idata->uid_hash, uid);
704 if (!h)
705 continue;
706
707 exp_msn = HEADER_DATA(h)->msn;
708
709 /* imap_expunge_mailbox() will rewrite h->index.
710 * It needs to resort using SORT_ORDER anyway, so setting to INT_MAX
711 * makes the code simpler and possibly more efficient. */
712 h->index = INT_MAX;
713 HEADER_DATA(h)->msn = 0;
714
715 if (exp_msn < 1 || exp_msn > idata->max_msn)
716 {
717 dprint (1, (debugfile,
718 "VANISHED: msn for UID %u is incorrect.\n", uid));
719 continue;
720 }
721 if (idata->msn_index[exp_msn - 1] != h)
722 {
723 dprint (1, (debugfile,
724 "VANISHED: msn_index for UID %u is incorrect.\n", uid));
725 continue;
726 }
727
728 idata->msn_index[exp_msn - 1] = NULL;
729
730 if (!earlier)
731 {
732 /* decrement seqno of those above. */
733 for (cur = exp_msn; cur < idata->max_msn; cur++)
734 {
735 h = idata->msn_index[cur];
736 if (h)
737 HEADER_DATA(h)->msn--;
738 idata->msn_index[cur - 1] = h;
739 }
740
741 idata->msn_index[idata->max_msn - 1] = NULL;
742 idata->max_msn--;
743 }
744 }
745
746 if (rc < 0)
747 dprint (1, (debugfile, "VANISHED: illegal seqset %s\n", s));
748
749 idata->reopen |= IMAP_EXPUNGE_PENDING;
750
751 mutt_seqset_iterator_free (&iter);
752}
753
754/* cmd_parse_fetch: Load fetch response into IMAP_DATA. Currently only
755 * handles unanticipated FETCH responses, and only FLAGS data. We get
756 * these if another client has changed flags for a mailbox we've selected.
757 * Of course, a lot of code here duplicates code in message.c. */
758static void cmd_parse_fetch (IMAP_DATA* idata, char* s)
759{
760 unsigned int msn, uid;
761 HEADER *h;
762 char *flags = NULL;
763 int uid_checked = 0;
764 int server_changes = 0;
765
766 dprint (3, (debugfile, "Handling FETCH\n"));
767
768 if (mutt_atoui (s, &msn) < 0)
769 {
770 dprint (3, (debugfile, "cmd_parse_fetch: Skipping FETCH response - illegal MSN\n"));
771 return;
772 }
773
774 if (msn < 1 || msn > idata->max_msn)
775 {
776 dprint (3, (debugfile,
777 "cmd_parse_fetch: Skipping FETCH response - MSN %u out of range\n",
778 msn));
779 return;
780 }
781
782 h = idata->msn_index[msn - 1];
783 if (!h || !h->active)
784 {
785 dprint (3, (debugfile,
786 "cmd_parse_fetch: Skipping FETCH response - MSN %u not in msn_index\n",
787 msn));
788 return;
789 }
790
791 dprint (2, (debugfile, "Message UID %u updated\n", HEADER_DATA(h)->uid));
792 /* skip FETCH */
793 s = imap_next_word (s);
794 s = imap_next_word (s);
795
796 if (*s != '(')
797 {
798 dprint (1, (debugfile, "Malformed FETCH response"));
799 return;
800 }
801 s++;
802
803 while (*s)
804 {
805 SKIPWS (s);
806
807 if (ascii_strncasecmp ("FLAGS", s, 5) == 0)
808 {
809 flags = s;
810 if (uid_checked)
811 break;
812
813 s += 5;
814 SKIPWS(s);
815 if (*s != '(')
816 {
817 dprint (1, (debugfile, "cmd_parse_fetch: bogus FLAGS response: %s\n",
818 s));
819 return;
820 }
821 s++;
822 while (*s && *s != ')')
823 s++;
824 if (*s == ')')
825 s++;
826 else
827 {
828 dprint (1, (debugfile,
829 "cmd_parse_fetch: Unterminated FLAGS response: %s\n", s));
830 return;
831 }
832 }
833 else if (ascii_strncasecmp ("UID", s, 3) == 0)
834 {
835 s += 3;
836 SKIPWS (s);
837 if (mutt_atoui (s, &uid) < 0)
838 {
839 dprint (1, (debugfile, "cmd_parse_fetch: Illegal UID. Skipping update.\n"));
840 return;
841 }
842 if (uid != HEADER_DATA(h)->uid)
843 {
844 dprint (1, (debugfile, "cmd_parse_fetch: UID vs MSN mismatch. Skipping update.\n"));
845 return;
846 }
847 uid_checked = 1;
848 if (flags)
849 break;
850 s = imap_next_word (s);
851 }
852 else if (ascii_strncasecmp ("MODSEQ", s, 6) == 0)
853 {
854 s += 6;
855 SKIPWS(s);
856 if (*s != '(')
857 {
858 dprint (1, (debugfile, "cmd_parse_fetch: bogus MODSEQ response: %s\n",
859 s));
860 return;
861 }
862 s++;
863 while (*s && *s != ')')
864 s++;
865 if (*s == ')')
866 s++;
867 else
868 {
869 dprint (1, (debugfile,
870 "cmd_parse_fetch: Unterminated MODSEQ response: %s\n", s));
871 return;
872 }
873 }
874 else if (*s == ')')
875 break; /* end of request */
876 else if (*s)
877 {
878 dprint (2, (debugfile, "Only handle FLAGS updates\n"));
879 break;
880 }
881 }
882
883 if (flags)
884 {
885 imap_set_flags (idata, h, flags, &server_changes);
886 if (server_changes)
887 {
888 /* If server flags could conflict with mutt's flags, reopen the mailbox. */
889 if (h->changed)
890 idata->reopen |= IMAP_EXPUNGE_PENDING;
891 else
892 idata->check_status |= IMAP_FLAGS_PENDING;
893 }
894 }
895}
896
897static void cmd_parse_list (IMAP_DATA* idata, char* s)
898{
899 IMAP_LIST* list;
900 IMAP_LIST lb;
901 char delimbuf[5]; /* worst case: "\\"\0 */
902 unsigned int litlen;
903
904 if (idata->cmddata && idata->cmdtype == IMAP_CT_LIST)
905 list = (IMAP_LIST*)idata->cmddata;
906 else
907 list = &lb;
908
909 memset (list, 0, sizeof (IMAP_LIST));
910
911 /* flags */
912 s = imap_next_word (s);
913 if (*s != '(')
914 {
915 dprint (1, (debugfile, "Bad LIST response\n"));
916 return;
917 }
918 s++;
919 while (*s)
920 {
921 if (!ascii_strncasecmp (s, "\\NoSelect", 9))
922 list->noselect = 1;
923 else if (!ascii_strncasecmp (s, "\\NonExistent", 12)) /* rfc5258 */
924 list->noselect = 1;
925 else if (!ascii_strncasecmp (s, "\\NoInferiors", 12))
926 list->noinferiors = 1;
927 else if (!ascii_strncasecmp (s, "\\HasNoChildren", 14)) /* rfc5258*/
928 list->noinferiors = 1;
929
930 s = imap_next_word (s);
931 if (*(s - 2) == ')')
932 break;
933 }
934
935 /* Delimiter */
936 if (ascii_strncasecmp (s, "NIL", 3))
937 {
938 delimbuf[0] = '\0';
939 safe_strcat (delimbuf, 5, s);
940 imap_unquote_string (delimbuf);
941 list->delim = delimbuf[0];
942 }
943
944 /* Name */
945 s = imap_next_word (s);
946 /* Notes often responds with literals here. We need a real tokenizer. */
947 if (!imap_get_literal_count (s, &litlen))
948 {
949 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
950 {
951 idata->status = IMAP_FATAL;
952 return;
953 }
954
955 if (strlen(idata->buf) < litlen)
956 {
957 dprint (1, (debugfile, "Error parsing LIST mailbox\n"));
958 return;
959 }
960
961 list->name = idata->buf;
962 s = list->name + litlen;
963 if (*s)
964 {
965 *s = '\0';
966 s++;
967 SKIPWS(s);
968 }
969 }
970 else
971 {
972 list->name = s;
973 /* Exclude rfc5258 RECURSIVEMATCH CHILDINFO suffix */
974 s = imap_next_word (s);
975 if (*s)
976 *(s - 1) = '\0';
977 imap_unmunge_mbox_name (idata, list->name);
978 }
979
980 if (list->name[0] == '\0')
981 {
982 idata->delim = list->delim;
983 dprint (3, (debugfile, "Root delimiter: %c\n", idata->delim));
984 }
985}
986
987static void cmd_parse_lsub (IMAP_DATA* idata, char* s)
988{
989 char buf[STRING];
990 char errstr[STRING];
991 BUFFER err, token;
992 ciss_url_t url;
993 IMAP_LIST list;
994
995 if (idata->cmddata && idata->cmdtype == IMAP_CT_LIST)
996 {
997 /* caller will handle response itself */
998 cmd_parse_list (idata, s);
999 return;
1000 }
1001
1002 if (!option (OPTIMAPCHECKSUBSCRIBED))
1003 return;
1004
1005 idata->cmdtype = IMAP_CT_LIST;
1006 idata->cmddata = &list;
1007 cmd_parse_list (idata, s);
1008 idata->cmddata = NULL;
1009 /* noselect is for a gmail quirk (#3445) */
1010 if (!list.name || list.noselect)
1011 return;
1012
1013 dprint (3, (debugfile, "Subscribing to %s\n", list.name));
1014
1015 strfcpy (buf, "mailboxes \"", sizeof (buf));
1016 mutt_account_tourl (&idata->conn->account, &url);
1017 /* escape \ and ". Also escape ` because the resulting
1018 * string will be passed to mutt_parse_rc_line. */
1019 imap_quote_string_and_backquotes (errstr, sizeof (errstr), list.name);
1020 url.path = errstr + 1;
1021 url.path[strlen(url.path) - 1] = '\0';
1022 if (!mutt_strcmp (url.user, ImapUser))
1023 url.user = NULL;
1024 url_ciss_tostring (&url, buf + 11, sizeof (buf) - 11, 0);
1025 safe_strcat (buf, sizeof (buf), "\"");
1026 mutt_buffer_init (&token);
1027 mutt_buffer_init (&err);
1028 err.data = errstr;
1029 err.dsize = sizeof (errstr);
1030 if (mutt_parse_rc_line (buf, &token, &err))
1031 dprint (1, (debugfile, "Error adding subscribed mailbox: %s\n", errstr));
1032 FREE (&token.data);
1033}
1034
1035/* cmd_parse_myrights: set rights bits according to MYRIGHTS response */
1036static void cmd_parse_myrights (IMAP_DATA* idata, const char* s)
1037{
1038 dprint (2, (debugfile, "Handling MYRIGHTS\n"));
1039
1040 s = imap_next_word ((char*)s);
1041 s = imap_next_word ((char*)s);
1042
1043 /* zero out current rights set */
1044 memset (idata->ctx->rights, 0, sizeof (idata->ctx->rights));
1045
1046 while (*s && !isspace((unsigned char) *s))
1047 {
1048 switch (*s)
1049 {
1050 case 'l':
1051 mutt_bit_set (idata->ctx->rights, MUTT_ACL_LOOKUP);
1052 break;
1053 case 'r':
1054 mutt_bit_set (idata->ctx->rights, MUTT_ACL_READ);
1055 break;
1056 case 's':
1057 mutt_bit_set (idata->ctx->rights, MUTT_ACL_SEEN);
1058 break;
1059 case 'w':
1060 mutt_bit_set (idata->ctx->rights, MUTT_ACL_WRITE);
1061 break;
1062 case 'i':
1063 mutt_bit_set (idata->ctx->rights, MUTT_ACL_INSERT);
1064 break;
1065 case 'p':
1066 mutt_bit_set (idata->ctx->rights, MUTT_ACL_POST);
1067 break;
1068 case 'a':
1069 mutt_bit_set (idata->ctx->rights, MUTT_ACL_ADMIN);
1070 break;
1071 case 'k':
1072 mutt_bit_set (idata->ctx->rights, MUTT_ACL_CREATE);
1073 break;
1074 case 'x':
1075 mutt_bit_set (idata->ctx->rights, MUTT_ACL_DELMX);
1076 break;
1077 case 't':
1078 mutt_bit_set (idata->ctx->rights, MUTT_ACL_DELETE);
1079 break;
1080 case 'e':
1081 mutt_bit_set (idata->ctx->rights, MUTT_ACL_EXPUNGE);
1082 break;
1083
1084 /* obsolete rights */
1085 case 'c':
1086 mutt_bit_set (idata->ctx->rights, MUTT_ACL_CREATE);
1087 mutt_bit_set (idata->ctx->rights, MUTT_ACL_DELMX);
1088 break;
1089 case 'd':
1090 mutt_bit_set (idata->ctx->rights, MUTT_ACL_DELETE);
1091 mutt_bit_set (idata->ctx->rights, MUTT_ACL_EXPUNGE);
1092 break;
1093 default:
1094 dprint(1, (debugfile, "Unknown right: %c\n", *s));
1095 }
1096 s++;
1097 }
1098}
1099
1100/* cmd_parse_search: store SEARCH response for later use */
1101static void cmd_parse_search (IMAP_DATA* idata, const char* s)
1102{
1103 unsigned int uid;
1104 HEADER *h;
1105
1106 dprint (2, (debugfile, "Handling SEARCH\n"));
1107
1108 while ((s = imap_next_word ((char*)s)) && *s != '\0')
1109 {
1110 if (mutt_atoui (s, &uid) < 0)
1111 continue;
1112 h = (HEADER *)int_hash_find (idata->uid_hash, uid);
1113 if (h)
1114 h->matched = 1;
1115 }
1116}
1117
1118/* first cut: just do buffy update. Later we may wish to cache all
1119 * mailbox information, even that not desired by buffy */
1120static void cmd_parse_status (IMAP_DATA* idata, char* s)
1121{
1122 char* mailbox;
1123 char* value;
1124 BUFFY* inc;
1125 IMAP_MBOX mx;
1126 unsigned long ulcount;
1127 unsigned int count;
1128 IMAP_STATUS *status;
1129 unsigned int olduv, oldun;
1130 unsigned int litlen;
1131 short new = 0;
1132 short new_msg_count = 0;
1133
1134 mailbox = imap_next_word (s);
1135
1136 /* We need a real tokenizer. */
1137 if (!imap_get_literal_count (mailbox, &litlen))
1138 {
1139 if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
1140 {
1141 idata->status = IMAP_FATAL;
1142 return;
1143 }
1144
1145 if (strlen(idata->buf) < litlen)
1146 {
1147 dprint (1, (debugfile, "Error parsing STATUS mailbox\n"));
1148 return;
1149 }
1150
1151 mailbox = idata->buf;
1152 s = mailbox + litlen;
1153 *s = '\0';
1154 s++;
1155 SKIPWS(s);
1156 }
1157 else
1158 {
1159 s = imap_next_word (mailbox);
1160 *(s - 1) = '\0';
1161 imap_unmunge_mbox_name (idata, mailbox);
1162 }
1163
1164 status = imap_mboxcache_get (idata, mailbox, 1);
1165 olduv = status->uidvalidity;
1166 oldun = status->uidnext;
1167
1168 if (*s++ != '(')
1169 {
1170 dprint (1, (debugfile, "Error parsing STATUS\n"));
1171 return;
1172 }
1173 while (*s && *s != ')')
1174 {
1175 value = imap_next_word (s);
1176
1177 errno = 0;
1178 ulcount = strtoul (value, &value, 10);
1179 if ((errno == ERANGE && ulcount == ULONG_MAX) ||
1180 ((unsigned int) ulcount != ulcount))
1181 {
1182 dprint (1, (debugfile, "Error parsing STATUS number\n"));
1183 return;
1184 }
1185 count = (unsigned int) ulcount;
1186
1187 if (!ascii_strncmp ("MESSAGES", s, 8))
1188 {
1189 status->messages = count;
1190 new_msg_count = 1;
1191 }
1192 else if (!ascii_strncmp ("RECENT", s, 6))
1193 status->recent = count;
1194 else if (!ascii_strncmp ("UIDNEXT", s, 7))
1195 status->uidnext = count;
1196 else if (!ascii_strncmp ("UIDVALIDITY", s, 11))
1197 status->uidvalidity = count;
1198 else if (!ascii_strncmp ("UNSEEN", s, 6))
1199 status->unseen = count;
1200
1201 s = value;
1202 if (*s && *s != ')')
1203 s = imap_next_word (s);
1204 }
1205 dprint (3, (debugfile, "%s (UIDVALIDITY: %u, UIDNEXT: %u) %d messages, %d recent, %d unseen\n",
1206 status->name, status->uidvalidity, status->uidnext,
1207 status->messages, status->recent, status->unseen));
1208
1209 /* caller is prepared to handle the result herself */
1210 if (idata->cmddata && idata->cmdtype == IMAP_CT_STATUS)
1211 {
1212 memcpy (idata->cmddata, status, sizeof (IMAP_STATUS));
1213 return;
1214 }
1215
1216 dprint (3, (debugfile, "Running default STATUS handler\n"));
1217
1218 /* should perhaps move this code back to imap_buffy_check */
1219 for (inc = Incoming; inc; inc = inc->next)
1220 {
1221 if (inc->magic != MUTT_IMAP)
1222 continue;
1223
1224 if (imap_parse_path (mutt_b2s (inc->pathbuf), &mx) < 0)
1225 {
1226 dprint (1, (debugfile, "Error parsing mailbox %s, skipping\n", mutt_b2s (inc->pathbuf)));
1227 continue;
1228 }
1229 /* dprint (2, (debugfile, "Buffy entry: [%s] mbox: [%s]\n", inc->path, NONULL(mx.mbox))); */
1230
1231 if (imap_account_match (&idata->conn->account, &mx.account))
1232 {
1233 if (mx.mbox)
1234 {
1235 value = safe_strdup (mx.mbox);
1236 imap_fix_path (idata, mx.mbox, value, mutt_strlen (value) + 1);
1237 FREE (&mx.mbox);
1238 }
1239 else
1240 value = safe_strdup ("INBOX");
1241
1242 if (value && !imap_mxcmp (mailbox, value))
1243 {
1244 dprint (3, (debugfile, "Found %s in buffy list (OV: %u ON: %u U: %d)\n",
1245 mailbox, olduv, oldun, status->unseen));
1246
1247 if (option(OPTMAILCHECKRECENT))
1248 {
1249 if (olduv && olduv == status->uidvalidity)
1250 {
1251 if (oldun < status->uidnext)
1252 new = (status->unseen > 0);
1253 }
1254 else if (!olduv && !oldun)
1255 /* first check per session, use recent. might need a flag for this. */
1256 new = (status->recent > 0);
1257 else
1258 new = (status->unseen > 0);
1259 }
1260 else
1261 new = (status->unseen > 0);
1262
1263#ifdef USE_SIDEBAR
1264 if ((inc->new != new) ||
1265 (inc->msg_count != status->messages) ||
1266 (inc->msg_unread != status->unseen))
1267 mutt_set_current_menu_redraw (REDRAW_SIDEBAR);
1268#endif
1269 inc->new = new;
1270 if (new_msg_count)
1271 inc->msg_count = status->messages;
1272 inc->msg_unread = status->unseen;
1273
1274 if (inc->new)
1275 /* force back to keep detecting new mail until the mailbox is
1276 opened */
1277 status->uidnext = oldun;
1278
1279 FREE (&value);
1280 return;
1281 }
1282
1283 FREE (&value);
1284 }
1285
1286 FREE (&mx.mbox);
1287 }
1288}
1289
1290/* cmd_parse_enabled: record what the server has enabled */
1291static void cmd_parse_enabled (IMAP_DATA* idata, const char* s)
1292{
1293 dprint (2, (debugfile, "Handling ENABLED\n"));
1294
1295 while ((s = imap_next_word ((char*)s)) && *s != '\0')
1296 {
1297 if (ascii_strncasecmp(s, "UTF8=ACCEPT", 11) == 0 ||
1298 ascii_strncasecmp(s, "UTF8=ONLY", 9) == 0)
1299 idata->unicode = 1;
1300 if (ascii_strncasecmp(s, "QRESYNC", 7) == 0)
1301 idata->qresync = 1;
1302 }
1303}