mutt stable branch with some hacks
at jcs 1303 lines 34 kB view raw
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}