src/mn-imap-mailbox.gob (50589B) - raw
1 /* 2 * mn-imap-mailbox.gob - IMAP 4rev1 support for Mail Notification 3 * 4 * Compliance: 5 * 6 * - RFC 3501 7 * - RFC 2177 8 * - RFC 2192 (subset) 9 * 10 * Mail Notification 11 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be> 12 * 13 * This program is free software; you can redistribute it and/or modify 14 * it under the terms of the GNU General Public License as published by 15 * the Free Software Foundation; either version 3 of the License, or 16 * (at your option) any later version. 17 * 18 * This program is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU General Public License for more details. 22 * 23 * You should have received a copy of the GNU General Public License along 24 * with this program; if not, write to the Free Software Foundation, Inc., 25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 26 */ 27 28 %headertop{ 29 #include "mn-pi-mailbox.h" 30 %} 31 32 %h{ 33 #define MN_IMAP_MAILBOX_N_USE_IDLE 3 34 35 extern int mn_imap_mailbox_default_ports[MN_PI_MAILBOX_N_CONNECTION_TYPES]; 36 %} 37 38 %privateheader{ 39 #include "mn-client-session.h" 40 %} 41 42 %{ 43 #include <stdio.h> 44 #include <string.h> 45 #include <stdarg.h> 46 #include <stdlib.h> 47 #include <glib/gi18n.h> 48 #include <libgnomevfs/gnome-vfs-utils.h> 49 #include "mn-mailbox-private.h" 50 #include "mn-authenticated-mailbox-private.h" 51 #include "mn-pi-mailbox-private.h" 52 #include "mn-util.h" 53 #include "mn-message-mime.h" 54 55 #define HAS_CURRENT_TAG(response, priv) (! strcmp((response)->tag, (priv)->tag)) 56 #define IS(response, token) (! g_ascii_strcasecmp((response)->response, (token))) 57 #define IS_OK(response) IS(response, "OK") 58 #define IS_NO(response) IS(response, "NO") 59 #define IS_BAD(response) IS(response, "BAD") 60 #define IS_BYE(response) IS(response, "BYE") 61 62 enum 63 { 64 STATE_GREETING = MN_CLIENT_SESSION_INITIAL_STATE, 65 STATE_CAPABILITY, 66 #if WITH_SSL 67 STATE_STARTTLS, 68 #endif 69 #if WITH_SASL 70 STATE_AUTHENTICATE, 71 #endif 72 STATE_LOGIN, 73 STATE_EXAMINE, 74 STATE_SEARCH_UNSEEN, 75 STATE_SEARCH_RECENT, 76 STATE_FETCH_UID, 77 STATE_FETCH, 78 STATE_IDLE, 79 STATE_LOGOUT 80 }; 81 82 typedef enum 83 { 84 IDLE_STATE_PRE_IDLE, 85 IDLE_STATE_IDLE, 86 IDLE_STATE_POST_IDLE 87 } IdleState; 88 89 struct _MNClientSessionPrivate 90 { 91 MN_PI_MAILBOX_SESSION_PRIVATE; 92 MNIMAPMailbox *self; 93 94 const char *server_software; 95 gboolean server_software_supports_idle; 96 97 int numeric_tag; 98 char tag[5]; 99 100 char **capabilities; 101 GSList *auth_mechanisms; 102 gboolean authenticated; 103 104 /* 105 * RFC 3501 specifies that UIDVALIDITY is a 32-bit number, but we do 106 * not need it to be one. Use a string for interoperability purposes 107 * (in case some server vendors did not read the RFC properly). 108 */ 109 char *uidvalidity; 110 111 int num_errors; 112 GHashTable *messages; 113 114 #if WITH_SSL 115 gboolean starttls_completed; 116 #endif 117 #if WITH_SASL 118 GSList *sasl_remaining_mechanisms; 119 const char *sasl_mechanism; 120 #endif 121 122 IdleState idle_state; 123 unsigned int idle_inactivity_timeout_id; 124 gboolean idle_inactivity; 125 gboolean could_idle; /* could idle at least once */ 126 }; 127 128 struct _MNClientSessionResponse 129 { 130 char *continuation; 131 char *tag; 132 char *response; 133 char *code; 134 char *arguments; 135 }; 136 137 typedef struct 138 { 139 MNMessage *message; 140 char *mid; 141 int number; 142 MNMessageFlags flags; 143 } MessageInfo; 144 145 int mn_imap_mailbox_default_ports[MN_PI_MAILBOX_N_CONNECTION_TYPES] = { 143, 143, 993 }; 146 147 /* variable taken from Evolution (camel-utf8.c) */ 148 static const char *utf7_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; 149 %} 150 151 enum MN_IMAP_MAILBOX_USE_IDLE 152 { 153 NEVER, 154 AUTODETECT, 155 ALWAYS 156 } MN:IMAP:Mailbox:Use:IDLE; 157 158 class MN:IMAP:Mailbox from MN:PI:Mailbox 159 { 160 private GMutex *mutex = {g_mutex_new()} destroywith g_mutex_free; 161 162 private MNClientSessionPrivate *idle_session; 163 164 public char *mailbox destroywith g_free; 165 property STRING mailbox (link, 166 flags = CONSTRUCT 167 | MN_MAILBOX_PARAM_LOAD_SAVE 168 | MN_MAILBOX_PARAM_REQUIRED 169 | MN_MAILBOX_PARAM_IGNORE_CASE, 170 default_value = "INBOX"); 171 172 public MNIMAPMailboxUseIDLE use_idle_extension; 173 property ENUM use_idle_extension (link, 174 enum_type = MN:IMAP:Mailbox:Use:IDLE, 175 flags = CONSTRUCT 176 | MN_MAILBOX_PARAM_LOAD_SAVE, 177 default_value = MN_IMAP_MAILBOX_USE_IDLE_AUTODETECT); 178 179 class_init (class) 180 { 181 MN_MAILBOX_CLASS(class)->type = "imap"; 182 MN_PI_MAILBOX_CLASS(class)->default_ports = mn_imap_mailbox_default_ports; 183 } 184 185 init (self) 186 { 187 mn_mailbox_set_format(MN_MAILBOX(self), "IMAP"); 188 } 189 190 override (MN:Mailbox) void 191 seal (MNMailbox *mailbox) 192 { 193 MNAuthenticatedMailbox *auth_mailbox = MN_AUTHENTICATED_MAILBOX(mailbox); 194 Self *self = SELF(mailbox); 195 196 PARENT_HANDLER(mailbox); 197 198 if (! mailbox->runtime_name) 199 mailbox->runtime_name = self_build_name(MN_AUTHENTICATED_MAILBOX(mailbox)->username, 200 MN_PI_MAILBOX(mailbox)->hostname, 201 self->mailbox); 202 203 #if WITH_SSL 204 if (MN_PI_MAILBOX(self)->connection_type == MN_PI_MAILBOX_CONNECTION_TYPE_SSL) 205 auth_mailbox->keyring_protocol = g_strdup("imaps"); 206 else 207 #endif 208 auth_mailbox->keyring_protocol = g_strdup("imap"); 209 } 210 211 /* 212 * Parses a RFC 2192 IMAP URL. 213 * 214 * Note: we only handle a subset of the RFC 2192 specification, 215 * since mailbox lists, message lists and message parts have no 216 * meaning in Mail Notification. Furthermore, Mail Notification 217 * requires an username. 218 */ 219 override (MN:Mailbox) MNMailbox * 220 parse_uri (MNMailbox *dummy, const char *uri) 221 { 222 int len; 223 int buflen; 224 char *username = NULL; 225 char *authmech = NULL; 226 char *hostname; 227 char *path = NULL; 228 int port; 229 MNMailbox *mailbox; 230 231 len = strlen(uri); 232 buflen = len + 1; 233 234 { 235 char scheme_buf[buflen]; 236 char auth_buf[buflen]; 237 char location_buf[buflen]; 238 char hostport_buf[buflen]; 239 char path_buf[buflen]; 240 char username_buf[buflen]; 241 char authmech_buf[buflen]; 242 char hostname_buf[buflen]; 243 gboolean has_path; 244 gboolean has_authmech = FALSE; 245 246 if (! mn_pi_mailbox_split_uri(uri, len, scheme_buf, auth_buf, location_buf)) 247 return NULL; 248 249 if (strcmp(scheme_buf, "imap")) 250 return NULL; 251 252 if (! self_split_uri_location(location_buf, len, hostport_buf, path_buf, &has_path)) 253 return NULL; 254 255 if (! mn_pi_mailbox_split_uri_auth(auth_buf, len, username_buf, authmech_buf, &has_authmech)) 256 return NULL; 257 258 if (has_authmech && ! strcmp(authmech_buf, "*")) 259 has_authmech = FALSE; 260 261 mn_pi_mailbox_split_uri_hostport(hostport_buf, len, hostname_buf, &port); 262 263 username = gnome_vfs_unescape_string(username_buf, NULL); 264 if (has_authmech) 265 authmech = gnome_vfs_unescape_string(authmech_buf, NULL); 266 hostname = gnome_vfs_unescape_string(hostname_buf, NULL); 267 if (has_path) 268 path = gnome_vfs_unescape_string(path_buf, NULL); 269 } 270 271 mailbox = mn_mailbox_new("imap", 272 "username", username, 273 "authmech", authmech, 274 "hostname", hostname, 275 "port", port, 276 NULL); 277 278 if (path) 279 g_object_set(mailbox, MN_IMAP_MAILBOX_PROP_MAILBOX(path), NULL); 280 281 g_free(username); 282 g_free(authmech); 283 g_free(hostname); 284 g_free(path); 285 286 return mailbox; 287 } 288 289 private gboolean 290 split_uri_location (const char *location (check null), 291 int maxlen, 292 char *hostport (check null), 293 char *path (check null), 294 gboolean *has_path (check null)) 295 { 296 char *pat; 297 int n; 298 299 pat = g_strdup_printf("%%%i[^/]/%%%is", maxlen, maxlen); 300 n = sscanf(location, pat, hostport, path); 301 g_free(pat); 302 303 g_return_val_if_fail(n >= 1, FALSE); 304 305 *has_path = n == 2; 306 if (*has_path) 307 { 308 /* we only handle enc_mailbox (see the RFC 2192 ABNF) */ 309 if (strspn(path, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$-_.+!*'(),%&=~:@/") != strlen(path)) 310 return FALSE; /* more than enc_mailbox, unhandled */ 311 } 312 313 return TRUE; 314 } 315 316 override (MN:Mailbox) void 317 removed (MNMailbox *mailbox) 318 { 319 Self *self = SELF(mailbox); 320 321 PARENT_HANDLER(mailbox); 322 323 self_lock(self); 324 if (selfp->idle_session) 325 { 326 mn_client_session_write(selfp->idle_session->session, "DONE"); 327 selfp->idle_session->idle_state = IDLE_STATE_POST_IDLE; 328 } 329 self_unlock(self); 330 } 331 332 private int 333 handle_greeting_cb (MNClientSession *session, 334 MNClientSessionResponse *response, 335 MNClientSessionPrivate *priv) 336 { 337 priv->session = session; 338 339 if (response->continuation) 340 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 341 else if (! response->tag && IS_OK(response)) 342 { 343 self_session_detect_imapd(session, response, priv); 344 return self_session_handle_capability_code(priv, response) 345 ? self_session_after_capability(priv) 346 : STATE_CAPABILITY; 347 } 348 else if (! response->tag && IS(response, "PREAUTH")) 349 { 350 priv->authenticated = TRUE; 351 self_session_detect_imapd(session, response, priv); 352 return self_session_handle_capability_code(priv, response) 353 ? self_session_after_capability(priv) 354 : STATE_CAPABILITY; 355 } 356 else if (! response->tag && IS_BYE(response)) 357 return mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 358 else 359 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 360 } 361 362 private int 363 enter_capability_cb (MNClientSession *session, 364 MNClientSessionPrivate *priv) 365 { 366 return self_session_write(priv, "CAPABILITY"); 367 } 368 369 private int 370 handle_capability_cb (MNClientSession *session, 371 MNClientSessionResponse *response, 372 MNClientSessionPrivate *priv) 373 { 374 if (response->continuation) 375 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 376 else if (response->tag) 377 { 378 if (HAS_CURRENT_TAG(response, priv)) 379 { 380 if (IS_OK(response)) 381 { 382 return priv->capabilities 383 ? self_session_after_capability(priv) 384 : mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("server did not send capabilities")); 385 } 386 else if (IS_BAD(response)) 387 { 388 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 389 return STATE_LOGOUT; 390 } 391 else 392 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 393 } 394 } 395 else if (IS(response, "CAPABILITY")) 396 { 397 self_session_parse_capabilities(priv, response->arguments); 398 return MN_CLIENT_SESSION_RESULT_CONTINUE; 399 } 400 401 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 402 } 403 404 private int 405 enter_starttls_cb (MNClientSession *session, 406 MNClientSessionPrivate *priv) 407 { 408 #if WITH_SSL 409 return self_session_write(priv, "STARTTLS"); 410 #else 411 g_assert_not_reached(); 412 return 0; 413 #endif /* WITH_SSL */ 414 } 415 416 private int 417 handle_starttls_cb (MNClientSession *session, 418 MNClientSessionResponse *response, 419 MNClientSessionPrivate *priv) 420 { 421 #if WITH_SSL 422 if (response->continuation) 423 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 424 else if (response->tag) 425 { 426 if (HAS_CURRENT_TAG(response, priv)) 427 { 428 if (IS_OK(response)) 429 { 430 priv->starttls_completed = TRUE; 431 return mn_client_session_enable_ssl(session) 432 ? STATE_CAPABILITY 433 : MN_CLIENT_SESSION_RESULT_DISCONNECT; 434 } 435 else if (IS_BAD(response)) 436 { 437 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 438 return STATE_LOGOUT; 439 } 440 else 441 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 442 } 443 } 444 445 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 446 #else 447 g_assert_not_reached(); 448 return 0; 449 #endif /* WITH_SSL */ 450 } 451 452 private int 453 enter_authenticate_cb (MNClientSession *session, 454 MNClientSessionPrivate *priv) 455 { 456 #if WITH_SASL 457 priv->sasl_mechanism = NULL; 458 459 if (mn_client_session_sasl_authentication_start(priv->session, 460 "imap", 461 priv->sasl_remaining_mechanisms, 462 priv->pi_mailbox->authmech, 463 &priv->sasl_mechanism, 464 NULL, /* [1] */ 465 NULL)) /* [1] */ 466 { 467 g_assert(priv->sasl_mechanism != NULL); 468 return self_session_write(priv, "AUTHENTICATE %s", priv->sasl_mechanism); 469 } 470 else 471 return priv->auth_mailbox->auth_cancelled 472 ? STATE_LOGOUT 473 : self_session_authenticate_fallback(priv, FALSE); 474 475 /* 476 * [1] RFC 3501 6.2.2 specifies that the IMAP protocol does not 477 * support the initial client response feature of SASL. 478 */ 479 #else 480 g_assert_not_reached(); 481 return 0; 482 #endif /* WITH_SASL */ 483 } 484 485 private int 486 handle_authenticate_cb (MNClientSession *session, 487 MNClientSessionResponse *response, 488 MNClientSessionPrivate *priv) 489 { 490 #if WITH_SASL 491 if (response->tag) 492 { 493 if (HAS_CURRENT_TAG(response, priv)) 494 { 495 if (IS_OK(response)) 496 { 497 if (mn_client_session_sasl_authentication_done(session)) 498 { 499 priv->authenticated = TRUE; 500 501 /* 502 * RFC 3501 2.2.2: 503 * 504 * A server MAY include a CAPABILITY response code 505 * in the tagged OK response of a successful 506 * AUTHENTICATE command in order to send 507 * capabilities automatically. It is unnecessary 508 * for a client to send a separate CAPABILITY 509 * command if it recognizes these automatic 510 * capabilities. This should only be done if a 511 * security layer was not negotiated by the 512 * AUTHENTICATE command, because the tagged OK 513 * response as part of an AUTHENTICATE command is 514 * not protected by encryption/integrity checking. 515 * [SASL] requires the client to re-issue a 516 * CAPABILITY command in this case. 517 */ 518 return self_session_handle_capability_code(priv, response) && mn_client_session_sasl_get_ssf(session) 519 ? STATE_EXAMINE 520 : STATE_CAPABILITY; 521 } 522 else 523 return MN_CLIENT_SESSION_RESULT_DISCONNECT; 524 } 525 else if (IS_NO(response) || IS_BAD(response)) 526 { 527 return priv->auth_mailbox->auth_cancelled 528 ? STATE_LOGOUT 529 : self_session_authenticate_fallback(priv, FALSE); 530 } 531 else 532 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 533 } 534 } 535 else if (response->continuation) 536 return mn_client_session_sasl_authentication_step(session, response->continuation); 537 538 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 539 #else 540 g_assert_not_reached(); 541 return 0; 542 #endif /* WITH_SASL */ 543 } 544 545 private int 546 enter_login_cb (MNClientSession *session, 547 MNClientSessionPrivate *priv) 548 { 549 if (self_session_has_capability(priv, "LOGINDISABLED")) 550 { 551 mn_client_session_notice(session, _("server advertised LOGINDISABLED, not using LOGIN authentication")); 552 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to login")); 553 return STATE_LOGOUT; 554 } 555 else 556 { 557 char *quoted_username; 558 char *quoted_password; 559 int result; 560 561 if (! mn_authenticated_mailbox_fill_password(priv->auth_mailbox, TRUE)) 562 return STATE_LOGOUT; 563 564 quoted_username = self_quote(priv->auth_mailbox->username); 565 quoted_password = self_quote(priv->auth_mailbox->runtime_password); 566 result = self_session_write(priv, "LOGIN %s %s", quoted_username, quoted_password); 567 g_free(quoted_username); 568 g_free(quoted_password); 569 570 return result; 571 } 572 } 573 574 private int 575 handle_login_cb (MNClientSession *session, 576 MNClientSessionResponse *response, 577 MNClientSessionPrivate *priv) 578 { 579 if (response->continuation) 580 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 581 else if (response->tag) 582 { 583 if (HAS_CURRENT_TAG(response, priv)) 584 { 585 if (IS_OK(response)) 586 { 587 priv->authenticated = TRUE; 588 return self_session_handle_capability_code(priv, response) 589 ? STATE_EXAMINE 590 : STATE_CAPABILITY; 591 } 592 else if (IS_NO(response) || IS_BAD(response)) 593 return self_session_authenticate_fallback(priv, TRUE); 594 else 595 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 596 } 597 } 598 599 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 600 } 601 602 private int 603 enter_examine_cb (MNClientSession *session, 604 MNClientSessionPrivate *priv) 605 { 606 char *utf7_mailbox; 607 char *quoted_mailbox; 608 int result; 609 610 utf7_mailbox = self_utf8_to_imap_utf7(priv->self->mailbox); 611 quoted_mailbox = self_quote(utf7_mailbox); 612 g_free(utf7_mailbox); 613 614 result = self_session_write(priv, "EXAMINE %s", quoted_mailbox); 615 g_free(quoted_mailbox); 616 617 return result; 618 } 619 620 private int 621 handle_examine_cb (MNClientSession *session, 622 MNClientSessionResponse *response, 623 MNClientSessionPrivate *priv) 624 { 625 if (response->continuation) 626 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 627 else if (response->tag) 628 { 629 if (HAS_CURRENT_TAG(response, priv)) 630 { 631 if (IS_OK(response)) 632 return STATE_SEARCH_UNSEEN; 633 else if (IS_NO(response) || IS_BAD(response)) 634 { 635 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 636 return STATE_LOGOUT; 637 } 638 else 639 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 640 } 641 } 642 else if (IS(response, "OK")) 643 { 644 if (response->code) 645 { 646 char **fields; 647 648 fields = g_strsplit(response->code, " ", 0); 649 if (g_strv_length(fields) == 2 650 && ! g_ascii_strcasecmp(fields[0], "UIDVALIDITY")) 651 { 652 g_free(priv->uidvalidity); 653 priv->uidvalidity = g_strdup(fields[1]); 654 } 655 g_strfreev(fields); 656 } 657 } 658 659 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 660 } 661 662 private int 663 enter_search_unseen_cb (MNClientSession *session, 664 MNClientSessionPrivate *priv) 665 { 666 priv->num_errors = 0; 667 668 if (priv->messages) 669 g_hash_table_remove_all(priv->messages); 670 else 671 priv->messages = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) self_message_info_free); 672 673 return self_session_write(priv, "SEARCH UNSEEN"); 674 } 675 676 private int 677 handle_search_unseen_cb (MNClientSession *session, 678 MNClientSessionResponse *response, 679 MNClientSessionPrivate *priv) 680 { 681 if (response->continuation) 682 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 683 else if (response->tag) 684 { 685 if (HAS_CURRENT_TAG(response, priv)) 686 { 687 if (IS_OK(response)) 688 { 689 /* 690 * Some uncompliant IMAP server implementations do not 691 * send an untagged SEARCH response when there are no 692 * search results (#18876). We are tolerant. 693 */ 694 if (g_hash_table_size(priv->messages) != 0) 695 return STATE_SEARCH_RECENT; 696 else 697 return self_session_got_messages(priv); 698 } 699 else if (IS_NO(response) || IS_BAD(response)) 700 { 701 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 702 return STATE_LOGOUT; 703 } 704 else 705 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 706 } 707 } 708 else if (IS(response, "SEARCH")) 709 { 710 if (response->arguments) 711 { 712 char **strings; 713 int i; 714 715 strings = g_strsplit(response->arguments, " ", 0); 716 717 for (i = 0; strings[i]; i++) 718 if (mn_str_isnumeric(strings[i])) 719 { 720 int n = atoi(strings[i]); 721 self_session_ensure_message_info(priv, n); 722 } 723 724 g_strfreev(strings); 725 } 726 727 return MN_CLIENT_SESSION_RESULT_CONTINUE; 728 } 729 730 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 731 } 732 733 private int 734 enter_search_recent_cb (MNClientSession *session, 735 MNClientSessionPrivate *priv) 736 { 737 return self_session_write(priv, "SEARCH RECENT"); 738 } 739 740 private int 741 handle_search_recent_cb (MNClientSession *session, 742 MNClientSessionResponse *response, 743 MNClientSessionPrivate *priv) 744 { 745 if (response->continuation) 746 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 747 else if (response->tag) 748 { 749 if (HAS_CURRENT_TAG(response, priv)) 750 { 751 if (IS_OK(response)) 752 { 753 if (priv->uidvalidity) 754 return STATE_FETCH_UID; 755 else 756 return STATE_FETCH; 757 } 758 else if (IS_NO(response) || IS_BAD(response)) 759 { 760 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 761 return STATE_LOGOUT; 762 } 763 else 764 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 765 } 766 } 767 else if (IS(response, "SEARCH")) 768 { 769 if (response->arguments) 770 { 771 char **strings; 772 int i; 773 774 strings = g_strsplit(response->arguments, " ", 0); 775 776 for (i = 0; strings[i]; i++) 777 if (mn_str_isnumeric(strings[i])) 778 { 779 int n = atoi(strings[i]); 780 MessageInfo *info; 781 782 info = self_session_get_message_info(priv, n); 783 if (info) 784 info->flags |= MN_MESSAGE_NEW; 785 /* 786 * Otherwise the message is recent but not unseen, 787 * and we must ignore it (fixes #230425). 788 */ 789 } 790 791 g_strfreev(strings); 792 } 793 794 return MN_CLIENT_SESSION_RESULT_CONTINUE; 795 } 796 797 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 798 } 799 800 private int 801 enter_fetch_uid_cb (MNClientSession *session, MNClientSessionPrivate *priv) 802 { 803 GString *set; 804 int result; 805 806 g_assert(priv->messages != NULL); 807 g_assert(g_hash_table_size(priv->messages) > 0); 808 809 set = g_string_new(NULL); 810 811 g_hash_table_foreach(priv->messages, self_build_fetch_uid_set_cb, set); 812 813 result = self_session_write(priv, "FETCH %s UID", set->str); 814 815 g_string_free(set, TRUE); 816 817 return result; 818 } 819 820 private void 821 build_fetch_uid_set_cb (gpointer key, gpointer value, gpointer user_data) 822 { 823 MessageInfo *info = value; 824 GString *set = user_data; 825 826 if (*set->str) 827 g_string_append_c(set, ','); 828 829 g_string_append_printf(set, "%i", info->number); 830 } 831 832 private char * 833 parse_fetch_uid_response (const char *response (check null)) 834 { 835 char *start; 836 char *end; 837 838 start = mn_ascii_strcasestr_span(response, "UID "); 839 if (! start) 840 return NULL; 841 842 /* 843 * The UID should be the only list element, but we are tolerant 844 * and allow a space in case other elements are present, eg: 845 * 846 * * 1 FETCH (UID 17 OTHER_ELEMENT) 847 */ 848 end = strpbrk(start, " )"); 849 if (! end || end == start) 850 return NULL; 851 852 return g_strndup(start, end - start); 853 } 854 855 private int 856 handle_fetch_uid_cb (MNClientSession *session, 857 MNClientSessionResponse *response, 858 MNClientSessionPrivate *priv) 859 { 860 if (response->continuation) 861 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 862 else if (response->tag) 863 { 864 if (HAS_CURRENT_TAG(response, priv)) 865 { 866 /* a failure (NO or BAD) is not fatal */ 867 if (IS_OK(response) || IS_NO(response) || IS_BAD(response)) 868 return STATE_FETCH; 869 else 870 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 871 } 872 } 873 else if (mn_str_isnumeric(response->response) 874 && response->arguments 875 && mn_ascii_str_case_has_prefix(response->arguments, "FETCH ")) 876 { 877 char *uid; 878 879 uid = self_parse_fetch_uid_response(response->arguments); 880 if (uid) 881 { 882 int n; 883 MessageInfo *info; 884 885 n = atoi(response->response); 886 887 info = self_session_get_message_info(priv, n); 888 if (info && ! info->mid) 889 { 890 MNMessage *message; 891 892 g_assert(info->message == NULL); 893 894 /* 895 * RFC 3501 specifies that UID is a 32-bit number, 896 * but we do not need it to be one. Use a string 897 * for interoperability purposes (in case some 898 * server vendors did not read the RFC properly). 899 */ 900 info->mid = g_strdup_printf("%s:%s", priv->uidvalidity, uid); 901 902 message = mn_mailbox_get_message_from_mid(priv->mailbox, info->mid); 903 if (message) 904 /* 905 * We create a new instance rather than reusing 906 * the existing one since the flags might be 907 * different and a MNMessage is immutable (so we 908 * cannot change the flags of the existing 909 * message). 910 */ 911 info->message = mn_g_object_clone(message, 912 MN_MESSAGE_PROP_FLAGS(info->flags), 913 NULL); 914 } 915 916 g_free(uid); 917 } 918 919 return MN_CLIENT_SESSION_RESULT_CONTINUE; 920 } 921 922 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 923 } 924 925 private int 926 enter_fetch_cb (MNClientSession *session, MNClientSessionPrivate *priv) 927 { 928 GString *set; 929 int result; 930 931 g_assert(priv->messages != NULL); 932 g_assert(g_hash_table_size(priv->messages) > 0); 933 934 set = g_string_new(NULL); 935 936 g_hash_table_foreach(priv->messages, self_build_fetch_set_cb, set); 937 938 if (*set->str) 939 result = self_session_write(priv, "FETCH %s BODY.PEEK[HEADER]", set->str); 940 else 941 /* all the unseen messages were cached, no message to fetch */ 942 result = self_session_got_messages(priv); 943 944 g_string_free(set, TRUE); 945 946 return result; 947 } 948 949 private void 950 build_fetch_set_cb (gpointer key, gpointer value, gpointer user_data) 951 { 952 MessageInfo *info = value; 953 GString *set = user_data; 954 955 if (! info->message) 956 { 957 if (*set->str) 958 g_string_append_c(set, ','); 959 960 g_string_append_printf(set, "%i", info->number); 961 } 962 } 963 964 private int 965 handle_fetch_cb (MNClientSession *session, 966 MNClientSessionResponse *response, 967 MNClientSessionPrivate *priv) 968 { 969 if (response->continuation) 970 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 971 else if (response->tag) 972 { 973 if (HAS_CURRENT_TAG(response, priv)) 974 { 975 if (IS_OK(response)) 976 { 977 /* 978 * Note that in previous versions we required the 979 * fetch results to include all the unseen messages, 980 * but it caused problems in some cases (#20132). 981 */ 982 983 return self_session_got_messages(priv); 984 } 985 else if (IS_NO(response) || IS_BAD(response)) 986 { 987 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 988 return STATE_LOGOUT; 989 } 990 else 991 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 992 } 993 } 994 else if (mn_str_isnumeric(response->response) 995 && response->arguments 996 && mn_ascii_str_case_has_prefix(response->arguments, "FETCH ") 997 && (mn_ascii_strcasestr(response->arguments, "BODY[HEADER]") 998 || mn_ascii_strcasestr(response->arguments, "BODY[HEADER "))) /* [1] */ 999 /* 1000 * [1] Non-compliant response (see RFC 3501 BNF), sent by Binc 1001 * IMAP and maybe others. We are tolerant. 1002 */ 1003 { 1004 int n; 1005 MessageInfo *info; 1006 1007 n = atoi(response->response); 1008 1009 info = self_session_get_message_info(priv, n); 1010 if (info && ! info->message) 1011 { 1012 char *p; 1013 int len; 1014 1015 /* we assume the header string will be in literal form */ 1016 1017 p = strrchr(response->arguments, '{'); 1018 if (p && sscanf(p, "{%d}", &len) == 1 && len >= 0) 1019 { 1020 gconstpointer buf; 1021 GError *err = NULL; 1022 1023 buf = mn_client_session_read(session, len); 1024 if (! buf) 1025 return MN_CLIENT_SESSION_RESULT_DISCONNECT; 1026 1027 info->message = mn_message_new_from_buffer(priv->mailbox, 1028 buf, 1029 len, 1030 info->mid, 1031 info->flags, 1032 FALSE, 1033 &err); 1034 1035 if (err) 1036 { 1037 mn_client_session_warning(session, "cannot read message %i: %s", n, err->message); 1038 g_error_free(err); 1039 1040 priv->num_errors++; 1041 } 1042 1043 /* read end of line (after literal) */ 1044 if (! mn_client_session_read_line(session)) 1045 return MN_CLIENT_SESSION_RESULT_DISCONNECT; 1046 } 1047 else 1048 { 1049 mn_client_session_warning(session, "cannot retrieve message %i", n); 1050 priv->num_errors++; 1051 } 1052 1053 return MN_CLIENT_SESSION_RESULT_CONTINUE; 1054 } 1055 } 1056 1057 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 1058 } 1059 1060 private int 1061 enter_idle_cb (MNClientSession *session, 1062 MNClientSessionPrivate *priv) 1063 { 1064 if (mn_mailbox_get_active(priv->mailbox)) 1065 { 1066 if (priv->self->use_idle_extension == MN_IMAP_MAILBOX_USE_IDLE_NEVER) 1067 mn_client_session_notice(session, _("\"Use the IDLE extension\" set to \"never\" in the mailbox properties, logging out")); 1068 else 1069 { 1070 if (self_session_has_capability(priv, "IDLE")) 1071 { 1072 switch (priv->self->use_idle_extension) 1073 { 1074 case MN_IMAP_MAILBOX_USE_IDLE_AUTODETECT: 1075 if (priv->server_software_supports_idle) 1076 goto idle; 1077 else 1078 mn_client_session_notice(session, _("the remote server runs %s, not using the IDLE extension"), priv->server_software); 1079 break; 1080 1081 case MN_IMAP_MAILBOX_USE_IDLE_ALWAYS: 1082 if (! priv->server_software_supports_idle) 1083 mn_client_session_warning(session, _("the remote server runs %s, the IDLE extension might not function properly"), priv->server_software); 1084 goto idle; 1085 break; 1086 1087 default: 1088 g_assert_not_reached(); 1089 } 1090 } 1091 else 1092 mn_client_session_notice(session, _("the remote server does not support the IDLE extension, logging out")); 1093 } 1094 } 1095 1096 return STATE_LOGOUT; 1097 1098 idle: 1099 priv->idle_state = IDLE_STATE_PRE_IDLE; 1100 return self_session_write(priv, "IDLE"); 1101 } 1102 1103 private int 1104 handle_idle_cb (MNClientSession *session, 1105 MNClientSessionResponse *response, 1106 MNClientSessionPrivate *priv) 1107 { 1108 switch (priv->idle_state) 1109 { 1110 case IDLE_STATE_PRE_IDLE: 1111 if (response->tag) 1112 { 1113 if (HAS_CURRENT_TAG(response, priv)) 1114 { 1115 if (IS_NO(response) || IS_BAD(response)) 1116 /* 1117 * The server advertised IDLE but does not actually 1118 * support it. 1119 * 1120 * Although strictly speaking this can be considered a 1121 * compliance fault, we'll be tolerant and just logout 1122 * without setting an error. 1123 */ 1124 return STATE_LOGOUT; 1125 else 1126 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 1127 } 1128 } 1129 else if (response->continuation) 1130 { 1131 /* we're now in the idle loop */ 1132 priv->idle_state = IDLE_STATE_IDLE; 1133 priv->idle_inactivity = FALSE; 1134 priv->could_idle = TRUE; 1135 1136 GDK_THREADS_ENTER(); 1137 mn_mailbox_set_poll(priv->mailbox, FALSE); 1138 gdk_flush(); 1139 GDK_THREADS_LEAVE(); 1140 1141 return MN_CLIENT_SESSION_RESULT_CONTINUE; 1142 } 1143 break; 1144 1145 case IDLE_STATE_IDLE: 1146 if (response->continuation || response->tag) 1147 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 1148 1149 if (IS_BYE(response)) 1150 { 1151 priv->idle_state = IDLE_STATE_POST_IDLE; 1152 return MN_CLIENT_SESSION_RESULT_DISCONNECT; /* we'll reconnect */ 1153 } 1154 else if (response->arguments 1155 && mn_str_isnumeric(response->response) 1156 && (! g_ascii_strcasecmp(response->arguments, "EXISTS") 1157 || ! g_ascii_strcasecmp(response->arguments, "RECENT") 1158 || ! g_ascii_strcasecmp(response->arguments, "EXPUNGE") 1159 || mn_ascii_str_case_has_prefix(response->arguments, "FETCH "))) 1160 { 1161 priv->idle_state = IDLE_STATE_POST_IDLE; 1162 return mn_client_session_write(session, "DONE"); /* wake up */ 1163 } 1164 break; 1165 1166 case IDLE_STATE_POST_IDLE: 1167 if (response->tag) 1168 { 1169 if (HAS_CURRENT_TAG(response, priv)) 1170 { 1171 if (IS_OK(response)) 1172 { 1173 if (! mn_mailbox_get_active(priv->mailbox)) 1174 return STATE_LOGOUT; 1175 else if (priv->idle_inactivity) 1176 return STATE_IDLE; /* anti-inactivity, re-enter */ 1177 else 1178 return STATE_SEARCH_UNSEEN; 1179 } 1180 else if (IS_NO(response) || IS_BAD(response)) 1181 { 1182 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 1183 return STATE_LOGOUT; 1184 } 1185 else 1186 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 1187 } 1188 } 1189 else if (response->continuation) 1190 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 1191 break; 1192 1193 default: 1194 g_assert_not_reached(); 1195 break; 1196 } 1197 1198 return self_default_handler(response, priv, MN_CLIENT_SESSION_ERROR_OTHER); 1199 } 1200 1201 private int 1202 enter_logout_cb (MNClientSession *session, 1203 MNClientSessionPrivate *priv) 1204 { 1205 return self_session_write(priv, "LOGOUT"); 1206 } 1207 1208 private int 1209 handle_logout_cb (MNClientSession *session, 1210 MNClientSessionResponse *response, 1211 MNClientSessionPrivate *priv) 1212 { 1213 if (response->continuation) 1214 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 1215 else if (response->tag && HAS_CURRENT_TAG(response, priv)) 1216 { 1217 if (IS_OK(response)) 1218 return MN_CLIENT_SESSION_RESULT_DISCONNECT; 1219 else if (IS_BAD(response)) 1220 return mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 1221 else 1222 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 1223 } 1224 else 1225 return MN_CLIENT_SESSION_RESULT_CONTINUE; 1226 } 1227 1228 override (MN:Authenticated:Mailbox) void 1229 authenticated_check (MNAuthenticatedMailbox *mailbox) 1230 { 1231 Self *self = SELF(mailbox); 1232 static const MNClientSessionState states[] = { 1233 { STATE_GREETING, NULL, self_handle_greeting_cb }, 1234 { STATE_CAPABILITY, self_enter_capability_cb, self_handle_capability_cb }, 1235 #if WITH_SSL 1236 { STATE_STARTTLS, self_enter_starttls_cb, self_handle_starttls_cb }, 1237 #endif 1238 #if WITH_SASL 1239 { STATE_AUTHENTICATE, self_enter_authenticate_cb, self_handle_authenticate_cb }, 1240 #endif 1241 { STATE_LOGIN, self_enter_login_cb, self_handle_login_cb }, 1242 { STATE_EXAMINE, self_enter_examine_cb, self_handle_examine_cb }, 1243 { STATE_SEARCH_UNSEEN, self_enter_search_unseen_cb, self_handle_search_unseen_cb }, 1244 { STATE_SEARCH_RECENT, self_enter_search_recent_cb, self_handle_search_recent_cb }, 1245 { STATE_FETCH_UID, self_enter_fetch_uid_cb, self_handle_fetch_uid_cb }, 1246 { STATE_FETCH, self_enter_fetch_cb, self_handle_fetch_cb }, 1247 { STATE_IDLE, self_enter_idle_cb, self_handle_idle_cb }, 1248 { STATE_LOGOUT, self_enter_logout_cb, self_handle_logout_cb }, 1249 1250 MN_CLIENT_SESSION_STATES_END 1251 }; 1252 static const MNClientSessionCallbacks callbacks = { 1253 mn_pi_mailbox_notice_cb, 1254 mn_pi_mailbox_warning_cb, 1255 self_response_new_cb, 1256 self_response_free_cb, 1257 self_pre_read_cb, 1258 self_post_read_cb, 1259 #if WITH_SASL 1260 mn_pi_mailbox_sasl_get_credentials_cb, 1261 #endif 1262 #if WITH_SSL 1263 mn_pi_mailbox_ssl_trust_server_cb, 1264 #endif 1265 }; 1266 MNClientSessionPrivate priv; 1267 gboolean status; 1268 GError *err = NULL; 1269 1270 PARENT_HANDLER(mailbox); 1271 1272 /* check if the parent handler has disabled the mailbox */ 1273 if (! mn_mailbox_get_poll(MN_MAILBOX(self))) 1274 return; 1275 1276 again: 1277 g_clear_error(&err); 1278 1279 memset(&priv, 0, sizeof(priv)); 1280 mn_pi_mailbox_session_private_init(MN_PI_MAILBOX(self), &priv); 1281 priv.self = self; 1282 priv.server_software_supports_idle = TRUE; /* assume it does */ 1283 1284 status = mn_client_session_run(states, 1285 &callbacks, 1286 #if WITH_SSL 1287 priv.pi_mailbox->connection_type == MN_PI_MAILBOX_CONNECTION_TYPE_SSL, 1288 #endif 1289 priv.pi_mailbox->hostname, 1290 priv.pi_mailbox->runtime_port, 1291 &priv, 1292 &err); 1293 1294 g_strfreev(priv.capabilities); 1295 mn_g_slist_free_deep(priv.auth_mechanisms); 1296 1297 g_free(priv.uidvalidity); 1298 1299 if (priv.messages) 1300 g_hash_table_destroy(priv.messages); 1301 1302 #if WITH_SASL 1303 g_slist_free(priv.sasl_remaining_mechanisms); 1304 #endif 1305 1306 if (priv.could_idle && mn_mailbox_get_active(priv.mailbox)) 1307 { 1308 if (status) 1309 goto again; 1310 /* some servers abruptly disconnect for inactivity */ 1311 else if (g_error_matches(err, MN_CLIENT_SESSION_ERROR, MN_CLIENT_SESSION_ERROR_CONNECTION_LOST)) 1312 { 1313 /* g_log() escapes unsafe and non UTF-8 characters, so this is safe */ 1314 mn_mailbox_notice(priv.mailbox, "%s", err->message); 1315 goto again; 1316 } 1317 } 1318 1319 GDK_THREADS_ENTER(); 1320 1321 mn_mailbox_set_poll(MN_MAILBOX(self), TRUE); 1322 if (! status) 1323 { 1324 char *escaped; 1325 1326 escaped = mn_utf8_escape(err->message); 1327 g_error_free(err); 1328 1329 mn_mailbox_set_error(MN_MAILBOX(self), "%s", escaped); 1330 g_free(escaped); 1331 } 1332 1333 gdk_flush(); 1334 GDK_THREADS_LEAVE(); 1335 } 1336 1337 private MNClientSessionResponse * 1338 response_new_cb (MNClientSession *session, 1339 const char *input, 1340 MNClientSessionPrivate *priv) 1341 { 1342 MNClientSessionResponse *response = NULL; 1343 1344 /* 1345 * About character set handling: 1346 * 1347 * RFC 3501 section 1.2 specifies that "characters are 7-bit 1348 * US-ASCII unless otherwise specified", and the ABNF formal 1349 * syntax found in section 9 confirms it. However, we follow the 1350 * RFC 793 robustness principle ("be liberal in what you accept") 1351 * and do not require responses to be valid 7-bit US-ASCII. 1352 * 1353 * Safety: 1354 * - we ensure that GTK+ will only receive UTF-8 data by 1355 * escaping error messages in authenticated_check() 1356 * - responses ending up in mn_mailbox_notice() need not be 1357 * valid UTF-8, since g_log() escapes unsafe and non UTF-8 1358 * characters 1359 * - messages do not go through this function; they are handled 1360 * in handle_fetch_cb() and passed to our mn-message-mime 1361 * implementation, which handles character set conversions 1362 */ 1363 1364 if (! strcmp(input, "+")) 1365 { 1366 response = g_new0(MNClientSessionResponse, 1); 1367 response->continuation = g_strdup(""); 1368 } 1369 else if (g_str_has_prefix(input, "+ ")) 1370 { 1371 response = g_new0(MNClientSessionResponse, 1); 1372 response->continuation = g_strdup(input + 2); 1373 } 1374 else 1375 { 1376 char **tokens; 1377 1378 tokens = g_strsplit(input, " ", 3); 1379 if (tokens[0] && tokens[1]) 1380 { 1381 if (tokens[2] && tokens[2][0] == '[') 1382 { 1383 char *code_start; 1384 char *code_end; 1385 1386 code_start = tokens[2] + 1; 1387 code_end = strchr(code_start, ']'); 1388 if (code_end) 1389 { 1390 response = g_new0(MNClientSessionResponse, 1); 1391 response->code = g_strndup(code_start, code_end - code_start); 1392 response->arguments = code_end[1] ? g_strdup(code_end + 2) : NULL; 1393 } 1394 } 1395 else 1396 { 1397 response = g_new0(MNClientSessionResponse, 1); 1398 response->arguments = g_strdup(tokens[2]); 1399 } 1400 1401 if (response) 1402 { 1403 response->tag = ! strcmp(tokens[0], "*") ? NULL : g_strdup(tokens[0]); 1404 response->response = g_strdup(tokens[1]); 1405 } 1406 } 1407 g_strfreev(tokens); 1408 } 1409 1410 return response; 1411 } 1412 1413 private void 1414 response_free_cb (MNClientSession *session, 1415 MNClientSessionResponse *response, 1416 MNClientSessionPrivate *priv) 1417 { 1418 g_free(response->continuation); 1419 g_free(response->tag); 1420 g_free(response->response); 1421 g_free(response->code); 1422 g_free(response->arguments); 1423 g_free(response); 1424 } 1425 1426 private int 1427 default_handler (MNClientSessionResponse *response (check null), 1428 MNClientSessionPrivate *priv (check null), 1429 int error_code_when_bye) 1430 { 1431 if (! response->tag && IS_BYE(response)) 1432 return mn_client_session_set_error_from_response(priv->session, error_code_when_bye, response->arguments); 1433 else 1434 return MN_CLIENT_SESSION_RESULT_CONTINUE; 1435 } 1436 1437 private void 1438 pre_read_cb (MNClientSession *session, 1439 MNClientSessionPrivate *priv) 1440 { 1441 if (priv->idle_state == IDLE_STATE_IDLE) 1442 { 1443 Self *self = priv->self; 1444 1445 self_lock(self); 1446 1447 selfp->idle_session = priv; 1448 1449 /* cycle IDLE in 29 minutes, as advised by RFC 2177 */ 1450 1451 g_assert(priv->idle_inactivity_timeout_id == 0); 1452 priv->idle_inactivity_timeout_id = g_timeout_add(60 * 29 * 1000, self_idle_inactivity_timeout_cb, self); 1453 1454 self_unlock(self); 1455 } 1456 } 1457 1458 private void 1459 post_read_cb (MNClientSession *session, 1460 MNClientSessionPrivate *priv) 1461 { 1462 Self *self = priv->self; 1463 1464 self_lock(self); 1465 1466 if (priv->idle_state >= IDLE_STATE_IDLE) 1467 { 1468 selfp->idle_session = NULL; 1469 mn_source_clear(&priv->idle_inactivity_timeout_id); 1470 } 1471 1472 self_unlock(self); 1473 } 1474 1475 private gboolean 1476 idle_inactivity_timeout_cb (gpointer data) 1477 { 1478 Self *self = data; 1479 1480 self_lock(self); 1481 1482 if (selfp->idle_session) 1483 { 1484 mn_client_session_write(selfp->idle_session->session, "DONE"); 1485 selfp->idle_session->idle_state = IDLE_STATE_POST_IDLE; 1486 selfp->idle_session->idle_inactivity = TRUE; 1487 selfp->idle_session->idle_inactivity_timeout_id = 0; 1488 } 1489 1490 self_unlock(self); 1491 1492 return FALSE; 1493 } 1494 1495 private int 1496 session_write (MNClientSessionPrivate *priv (check null), 1497 const char *format (check null), 1498 ...) 1499 attr {G_GNUC_PRINTF(2, 3)} 1500 { 1501 char *command; 1502 int result; 1503 1504 MN_STRDUP_VPRINTF(command, format); 1505 1506 if (priv->numeric_tag == 1000) 1507 priv->numeric_tag = 0; 1508 sprintf(priv->tag, "a%03i", priv->numeric_tag++); 1509 1510 result = mn_client_session_write(priv->session, "%s %s", priv->tag, command); 1511 g_free(command); 1512 1513 return result; 1514 } 1515 1516 private gboolean 1517 session_handle_capability_code (MNClientSessionPrivate *priv (check null), 1518 MNClientSessionResponse *response (check null)) 1519 { 1520 if (response->code) 1521 { 1522 if (! g_ascii_strcasecmp(response->code, "CAPABILITY")) 1523 { 1524 self_session_parse_capabilities(priv, NULL); 1525 return TRUE; 1526 } 1527 else if (mn_ascii_str_case_has_prefix(response->code, "CAPABILITY ")) 1528 { 1529 self_session_parse_capabilities(priv, response->code + 11); 1530 return TRUE; 1531 } 1532 } 1533 1534 return FALSE; 1535 } 1536 1537 private void 1538 session_parse_capabilities (MNClientSessionPrivate *priv (check null), 1539 const char *capabilities) 1540 { 1541 g_strfreev(priv->capabilities); 1542 priv->capabilities = NULL; 1543 1544 mn_g_slist_clear_deep(&priv->auth_mechanisms); 1545 1546 #if WITH_SASL 1547 mn_g_slist_clear(&priv->sasl_remaining_mechanisms); 1548 #endif 1549 1550 if (capabilities) 1551 { 1552 int i; 1553 1554 priv->capabilities = g_strsplit(capabilities, " ", 0); 1555 1556 for (i = 0; priv->capabilities[i]; i++) 1557 if (g_str_has_prefix(priv->capabilities[i], "AUTH=")) 1558 priv->auth_mechanisms = g_slist_append(priv->auth_mechanisms, g_strdup(priv->capabilities[i] + 5)); 1559 } 1560 else 1561 priv->capabilities = g_new0(char *, 1); 1562 } 1563 1564 private gboolean 1565 session_has_capability (MNClientSessionPrivate *priv (check null), 1566 const char *capability (check null)) 1567 { 1568 int i; 1569 1570 g_return_val_if_fail(priv->capabilities != NULL, FALSE); 1571 1572 for (i = 0; priv->capabilities[i]; i++) 1573 if (! g_ascii_strcasecmp(priv->capabilities[i], capability)) 1574 return TRUE; 1575 1576 return FALSE; 1577 } 1578 1579 private int 1580 session_after_capability (MNClientSessionPrivate *priv (check null)) 1581 { 1582 if (priv->authenticated) 1583 return STATE_EXAMINE; 1584 else 1585 { 1586 #if WITH_SSL 1587 if (priv->pi_mailbox->connection_type == MN_PI_MAILBOX_CONNECTION_TYPE_INBAND_SSL 1588 && ! priv->starttls_completed) 1589 { 1590 if (self_session_has_capability(priv, "STARTTLS")) 1591 return STATE_STARTTLS; 1592 else 1593 { 1594 mn_client_session_set_error(priv->session, MN_CLIENT_SESSION_ERROR_OTHER, _("server does not support in-band SSL/TLS")); 1595 return STATE_LOGOUT; 1596 } 1597 } 1598 #endif /* WITH_SSL */ 1599 return self_session_authenticate(priv); 1600 } 1601 } 1602 1603 private int 1604 session_authenticate (MNClientSessionPrivate *priv (check null)) 1605 { 1606 #if WITH_SASL 1607 g_slist_free(priv->sasl_remaining_mechanisms); 1608 priv->sasl_remaining_mechanisms = g_slist_copy(priv->auth_mechanisms); 1609 #endif /* WITH_SASL */ 1610 1611 if (priv->pi_mailbox->authmech) 1612 { 1613 if (*priv->pi_mailbox->authmech != '+') 1614 { 1615 #if WITH_SASL 1616 return STATE_AUTHENTICATE; 1617 #else 1618 mn_client_session_set_error(priv->session, MN_CLIENT_SESSION_ERROR_OTHER, _("a SASL authentication mechanism was selected but SASL support has not been compiled in")); 1619 return STATE_LOGOUT; 1620 #endif /* WITH_SASL */ 1621 } 1622 else 1623 { 1624 if (! strcmp(priv->pi_mailbox->authmech, "+LOGIN")) 1625 return STATE_LOGIN; 1626 else 1627 { 1628 mn_client_session_set_error(priv->session, MN_CLIENT_SESSION_ERROR_OTHER, _("unknown authentication mechanism \"%s\""), priv->pi_mailbox->authmech); 1629 return STATE_LOGOUT; 1630 } 1631 } 1632 } 1633 else 1634 { 1635 #if WITH_SASL 1636 if (priv->sasl_remaining_mechanisms) 1637 return STATE_AUTHENTICATE; 1638 #endif /* WITH_SASL */ 1639 return STATE_LOGIN; 1640 } 1641 } 1642 1643 private int 1644 session_authenticate_fallback (MNClientSessionPrivate *priv (check null), 1645 gboolean tried_login) 1646 { 1647 if (! priv->pi_mailbox->authmech) 1648 { 1649 #if WITH_SASL 1650 if (priv->sasl_mechanism) 1651 { 1652 GSList *elem; 1653 1654 elem = mn_g_str_slist_find(priv->sasl_remaining_mechanisms, priv->sasl_mechanism); 1655 if (elem) 1656 { 1657 priv->sasl_remaining_mechanisms = g_slist_delete_link(priv->sasl_remaining_mechanisms, elem); 1658 if (priv->sasl_remaining_mechanisms) 1659 { 1660 mn_client_session_notice(priv->session, _("disabling mechanism \"%s\" and retrying SASL authentication"), priv->sasl_mechanism); 1661 return STATE_AUTHENTICATE; 1662 } 1663 } 1664 } 1665 1666 /* SASL is not needed anymore, save some memory */ 1667 mn_client_session_sasl_dispose(priv->session); 1668 #endif /* WITH_SASL */ 1669 1670 if (! tried_login) 1671 { 1672 mn_client_session_notice(priv->session, _("falling back to IMAP LOGIN authentication")); 1673 return STATE_LOGIN; 1674 } 1675 } 1676 1677 if (priv->auth_mailbox->auth_prompted) 1678 { 1679 mn_authenticated_mailbox_auth_failed(priv->auth_mailbox); 1680 return self_session_authenticate(priv); 1681 } 1682 else 1683 { 1684 mn_client_session_set_error(priv->session, MN_CLIENT_SESSION_ERROR_OTHER, _("authentication failed")); 1685 return STATE_LOGOUT; 1686 } 1687 } 1688 1689 private void 1690 session_detect_imapd (MNClientSession *session (check null), 1691 MNClientSessionResponse *greeting_response (check null), 1692 MNClientSessionPrivate *priv (check null)) 1693 { 1694 if (greeting_response->arguments) 1695 { 1696 char *str; 1697 1698 /* 1699 * Try to detect UW imapd by looking for " IMAP4rev1 YEAR." 1700 */ 1701 1702 if ((str = mn_strstr_span(greeting_response->arguments, " IMAP4rev1 ")) 1703 && strspn(str, "0123456789") == 4 1704 && str[4] == '.') 1705 { 1706 priv->server_software = "UW imapd"; 1707 priv->server_software_supports_idle = FALSE; 1708 return; 1709 } 1710 } 1711 } 1712 1713 private int 1714 session_got_messages (MNClientSessionPrivate *priv (check null)) 1715 { 1716 GSList *messages = NULL; 1717 1718 g_hash_table_foreach(priv->messages, self_get_messages_list_cb, &messages); 1719 1720 GDK_THREADS_ENTER(); 1721 1722 mn_mailbox_set_messages(priv->mailbox, messages); 1723 1724 if (priv->num_errors == 0) 1725 mn_mailbox_set_error(priv->mailbox, NULL); 1726 else 1727 mn_mailbox_set_error(priv->mailbox, 1728 ngettext("cannot retrieve %i message", 1729 "cannot retrieve %i messages", 1730 priv->num_errors), 1731 priv->num_errors); 1732 1733 /* 1734 * In authenticated_check(), we do not hold the GDK lock while 1735 * destroying the messages hash table. We have just exposed the 1736 * messages to other threads through our mn_mailbox_set_messages() 1737 * call, so destroy the hash table here, while we hold the GDK 1738 * lock. 1739 */ 1740 g_hash_table_destroy(priv->messages); 1741 priv->messages = NULL; 1742 1743 gdk_flush(); 1744 GDK_THREADS_LEAVE(); 1745 1746 g_slist_free(messages); 1747 1748 return STATE_IDLE; 1749 } 1750 1751 private void 1752 get_messages_list_cb (gpointer key, gpointer value, gpointer user_data) 1753 { 1754 MessageInfo *info = value; 1755 GSList **list = user_data; 1756 1757 if (info->message) 1758 *list = g_slist_prepend(*list, info->message); 1759 } 1760 1761 private MessageInfo * 1762 session_get_message_info (MNClientSessionPrivate *priv (check null), 1763 int number) 1764 { 1765 return g_hash_table_lookup(priv->messages, GINT_TO_POINTER(number)); 1766 } 1767 1768 private MessageInfo * 1769 session_ensure_message_info (MNClientSessionPrivate *priv (check null), 1770 int number) 1771 { 1772 MessageInfo *info; 1773 1774 info = self_session_get_message_info(priv, number); 1775 if (! info) 1776 { 1777 info = self_message_info_new(number); 1778 g_hash_table_insert(priv->messages, GINT_TO_POINTER(number), info); 1779 } 1780 1781 return info; 1782 } 1783 1784 private MessageInfo * 1785 message_info_new (int number) 1786 { 1787 MessageInfo *info; 1788 1789 info = g_new0(MessageInfo, 1); 1790 info->number = number; 1791 1792 return info; 1793 } 1794 1795 private void 1796 message_info_free (MessageInfo *info (check null)) 1797 { 1798 if (info->message) 1799 g_object_unref(info->message); 1800 g_free(info->mid); 1801 g_free(info); 1802 } 1803 1804 /** 1805 * quote: 1806 * @str: the string to quote 1807 * 1808 * Quotes a string using RFC 3501 BNF rules. 1809 * 1810 * Return value: the quoted string. 1811 **/ 1812 private char * 1813 quote (const char *str (check null)) 1814 { 1815 GString *quoted; 1816 int i; 1817 1818 quoted = g_string_new("\""); 1819 for (i = 0; str[i]; i++) 1820 if (str[i] == '"' || str[i] == '\\') /* quoted-specials in BNF */ 1821 g_string_append_printf(quoted, "\\%c", str[i]); 1822 else 1823 g_string_append_c(quoted, str[i]); 1824 g_string_append_c(quoted, '"'); 1825 1826 return g_string_free(quoted, FALSE); 1827 } 1828 1829 /** 1830 * utf8_to_imap_utf7: 1831 * @str: the UTF-8 string to convert to modified UTF-7 1832 * 1833 * Converts a string from UTF-8 to modified UTF-7 as defined by RFC 3501. 1834 * 1835 * Return value: the string converted to modified UTF-7. 1836 **/ 1837 private char * 1838 utf8_to_imap_utf7 (const char *str (check null)) 1839 { 1840 gunichar c; 1841 guint32 x, v = 0; 1842 int state = 0; 1843 GString *out; 1844 int i = 0; 1845 1846 /* 1847 * Taken from the Ximian Evolution sources (camel-utf8.c) and 1848 * edited for style. 1849 */ 1850 1851 out = g_string_new(NULL); 1852 1853 while ((c = g_utf8_get_char(str))) 1854 { 1855 if (c >= 0x20 && c <= 0x7e) 1856 { 1857 if (state == 1) 1858 { 1859 self_imap_utf7_closeb64(out, v, i); 1860 state = 0; 1861 i = 0; 1862 } 1863 if (c == '&') 1864 g_string_append(out, "&-"); 1865 else 1866 g_string_append_c(out, c); 1867 } 1868 else 1869 { 1870 if (state == 0) 1871 { 1872 g_string_append_c(out, '&'); 1873 state = 1; 1874 } 1875 1876 v = (v << 16) | c; 1877 i += 16; 1878 1879 while (i >= 6) 1880 { 1881 x = (v >> (i - 6)) & 0x3f; 1882 g_string_append_c(out, utf7_alphabet[x]); 1883 i -= 6; 1884 } 1885 } 1886 1887 str = g_utf8_next_char(str); 1888 } 1889 1890 if (state == 1) 1891 self_imap_utf7_closeb64(out, v, i); 1892 1893 return g_string_free(out, FALSE); 1894 } 1895 1896 private void 1897 imap_utf7_closeb64 (GString *out (check null), guint32 v, guint32 i) 1898 { 1899 /* 1900 * Taken from the Ximian Evolution sources (camel-utf8.c) and 1901 * edited for style. 1902 */ 1903 1904 if (i > 0) 1905 { 1906 guint32 x; 1907 1908 x = (v << (6 - i)) & 0x3f; 1909 g_string_append_c(out, utf7_alphabet[x]); 1910 } 1911 1912 g_string_append_c(out, '-'); 1913 } 1914 1915 public char * 1916 build_name (const char *username (check null), 1917 const char *server (check null), 1918 const char *mailbox) 1919 { 1920 GString *name; 1921 1922 name = g_string_new(NULL); 1923 1924 g_string_append_printf(name, "%s@%s", username, server); 1925 1926 if (mailbox && mn_utf8_strcasecmp(mailbox, "INBOX")) 1927 g_string_append_printf(name, "/%s", mailbox); 1928 1929 return g_string_free(name, FALSE); 1930 } 1931 1932 private void 1933 lock (self) 1934 { 1935 g_mutex_lock(selfp->mutex); 1936 } 1937 1938 private void 1939 unlock (self) 1940 { 1941 g_mutex_unlock(selfp->mutex); 1942 } 1943 }