src/mn-pop3-mailbox.gob (32909B) - raw
1 /* 2 * mn-pop3-mailbox.gob - POP3 support for Mail Notification 3 * 4 * Compliance: 5 * 6 * - RFC 1939 7 * - RFC 2449 8 * - RFC 1734 9 * - RFC 2595 10 * - RFC 2384 11 * 12 * Mail Notification 13 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be> 14 * 15 * This program is free software; you can redistribute it and/or modify 16 * it under the terms of the GNU General Public License as published by 17 * the Free Software Foundation; either version 3 of the License, or 18 * (at your option) any later version. 19 * 20 * This program is distributed in the hope that it will be useful, 21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 * GNU General Public License for more details. 24 * 25 * You should have received a copy of the GNU General Public License along 26 * with this program; if not, write to the Free Software Foundation, Inc., 27 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 28 */ 29 30 %headertop{ 31 #include "mn-pi-mailbox.h" 32 %} 33 34 %h{ 35 extern int mn_pop3_mailbox_default_ports[MN_PI_MAILBOX_N_CONNECTION_TYPES]; 36 %} 37 38 %{ 39 #include <stdio.h> 40 #include <stdarg.h> 41 #include <string.h> 42 #include <stdlib.h> 43 #include <unistd.h> 44 #include <glib/gi18n.h> 45 #include <libgnomevfs/gnome-vfs-utils.h> 46 #if WITH_SASL 47 #include <sasl/saslutil.h> 48 #endif /* WITH_SASL */ 49 #include "mn-mailbox-private.h" 50 #include "mn-authenticated-mailbox-private.h" 51 #include "mn-pi-mailbox-private.h" 52 #include "mn-client-session.h" 53 #include "mn-util.h" 54 #include "mn-md5.h" 55 #include "mn-message-mime.h" 56 57 enum 58 { 59 STATE_GREETING = MN_CLIENT_SESSION_INITIAL_STATE, 60 STATE_CAPA, 61 #if WITH_SSL 62 STATE_STLS, 63 #endif 64 #if WITH_SASL 65 STATE_AUTH, 66 #endif 67 STATE_APOP, 68 STATE_USER, 69 STATE_PASS, 70 STATE_LIST, 71 STATE_UIDL, 72 STATE_RETR_TOP, 73 STATE_QUIT 74 }; 75 76 struct _MNClientSessionPrivate 77 { 78 MN_PI_MAILBOX_SESSION_PRIVATE; 79 MNPOP3Mailbox *self; 80 81 gboolean in_list; 82 gboolean in_retr_top; 83 84 char *apop_timestamp; 85 GSList *auth_mechanisms; 86 87 gboolean top_supported; 88 gboolean uidl_supported; 89 90 #if WITH_SSL 91 gboolean stls_supported; 92 gboolean stls_completed; 93 #endif 94 95 #if WITH_SASL 96 GSList *sasl_remaining_mechanisms; 97 const char *sasl_mechanism; 98 gboolean sasl_had_clientout; 99 #endif 100 101 gboolean authenticated; 102 103 gboolean login_delay_user; 104 105 int num_errors; 106 GSList *messages; 107 GSList *message_iter; 108 GString *message_buffer; 109 }; 110 111 typedef enum 112 { 113 RESPONSE_OK, 114 RESPONSE_ERR, 115 RESPONSE_LIST_ITEM, 116 RESPONSE_LIST_END, 117 RESPONSE_CONTINUATION 118 } ResponseType; 119 120 struct _MNClientSessionResponse 121 { 122 ResponseType type; 123 char *arguments; 124 }; 125 126 typedef struct 127 { 128 MNMessage *message; 129 char *mid; 130 int number; 131 } MessageInfo; 132 133 int mn_pop3_mailbox_default_ports[MN_PI_MAILBOX_N_CONNECTION_TYPES] = { 110, 110, 995 }; 134 %} 135 136 class MN:POP3:Mailbox from MN:PI:Mailbox 137 { 138 private int login_delay; 139 private GTimer *authentication_timer destroywith g_timer_destroy; 140 141 class_init (class) 142 { 143 MN_MAILBOX_CLASS(class)->type = "pop3"; 144 MN_PI_MAILBOX_CLASS(class)->default_ports = mn_pop3_mailbox_default_ports; 145 } 146 147 init (self) 148 { 149 mn_mailbox_set_format(MN_MAILBOX(self), "POP3"); 150 } 151 152 override (MN:Mailbox) void 153 seal (MNMailbox *mailbox) 154 { 155 MNAuthenticatedMailbox *auth_mailbox = MN_AUTHENTICATED_MAILBOX(mailbox); 156 157 PARENT_HANDLER(mailbox); 158 159 if (! mailbox->runtime_name) 160 mailbox->runtime_name = self_build_name(MN_AUTHENTICATED_MAILBOX(mailbox)->username, 161 MN_PI_MAILBOX(mailbox)->hostname); 162 163 #if WITH_SSL 164 if (MN_PI_MAILBOX(mailbox)->connection_type == MN_PI_MAILBOX_CONNECTION_TYPE_SSL) 165 auth_mailbox->keyring_protocol = g_strdup("pop3s"); 166 else 167 #endif 168 auth_mailbox->keyring_protocol = g_strdup("pop3"); 169 } 170 171 /* 172 * Parses a RFC 2384 POP URL. 173 */ 174 override (MN:Mailbox) MNMailbox * 175 parse_uri (MNMailbox *dummy, const char *uri) 176 { 177 int len; 178 int buflen; 179 char *username = NULL; 180 char *authmech = NULL; 181 char *hostname; 182 int port; 183 MNMailbox *mailbox; 184 185 len = strlen(uri); 186 buflen = len + 1; 187 188 { 189 char scheme_buf[buflen]; 190 char auth_buf[buflen]; 191 char location_buf[buflen]; 192 char username_buf[buflen]; 193 char authmech_buf[buflen]; 194 char hostname_buf[buflen]; 195 gboolean has_authmech = FALSE; 196 197 if (! mn_pi_mailbox_split_uri(uri, len, scheme_buf, auth_buf, location_buf)) 198 return NULL; 199 200 if (strcmp(scheme_buf, "pop")) 201 return NULL; 202 203 if (! mn_pi_mailbox_split_uri_auth(auth_buf, len, username_buf, authmech_buf, &has_authmech)) 204 return NULL; 205 206 if (has_authmech && ! strcmp(authmech_buf, "*")) 207 has_authmech = FALSE; 208 209 mn_pi_mailbox_split_uri_hostport(location_buf, len, hostname_buf, &port); 210 211 username = gnome_vfs_unescape_string(username_buf, NULL); 212 if (has_authmech) 213 authmech = gnome_vfs_unescape_string(authmech_buf, NULL); 214 hostname = gnome_vfs_unescape_string(hostname_buf, NULL); 215 } 216 217 mailbox = mn_mailbox_new("pop3", 218 "username", username, 219 "authmech", authmech, 220 "hostname", hostname, 221 "port", port, 222 NULL); 223 224 g_free(username); 225 g_free(authmech); 226 g_free(hostname); 227 228 return mailbox; 229 } 230 231 private int 232 handle_greeting_cb (MNClientSession *session, 233 MNClientSessionResponse *response, 234 MNClientSessionPrivate *priv) 235 { 236 priv->session = session; 237 238 switch (response->type) 239 { 240 case RESPONSE_OK: 241 if (response->arguments) 242 { 243 char *timestamp; 244 245 timestamp = strchr(response->arguments, '<'); 246 if (timestamp) 247 { 248 char *timestamp_end; 249 250 timestamp_end = strchr(timestamp, '>'); 251 if (timestamp_end) 252 priv->apop_timestamp = g_strndup(timestamp, timestamp_end - timestamp + 1); 253 } 254 } 255 return STATE_CAPA; 256 257 case RESPONSE_ERR: 258 return mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 259 260 default: 261 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 262 } 263 } 264 265 private int 266 enter_capa_cb (MNClientSession *session, 267 MNClientSessionPrivate *priv) 268 { 269 return mn_client_session_write(session, "CAPA"); 270 } 271 272 private int 273 handle_capa_cb (MNClientSession *session, 274 MNClientSessionResponse *response, 275 MNClientSessionPrivate *priv) 276 { 277 self_session_handle_list_response(priv, response, FALSE); 278 279 switch (response->type) 280 { 281 case RESPONSE_OK: 282 mn_g_slist_clear_deep(&priv->auth_mechanisms); 283 #if WITH_SASL 284 mn_g_slist_clear(&priv->sasl_remaining_mechanisms); 285 #endif 286 priv->top_supported = FALSE; 287 priv->uidl_supported = FALSE; 288 priv->self->_priv->login_delay = 0; 289 priv->login_delay_user = FALSE; 290 #if WITH_SSL 291 priv->stls_supported = FALSE; 292 #endif 293 294 return MN_CLIENT_SESSION_RESULT_CONTINUE; 295 296 case RESPONSE_ERR: 297 case RESPONSE_LIST_END: 298 if (priv->authenticated) 299 return self_session_enter_list_or_uidl(priv); 300 301 #if WITH_SSL 302 if (priv->pi_mailbox->connection_type == MN_PI_MAILBOX_CONNECTION_TYPE_INBAND_SSL 303 && ! priv->stls_completed) 304 { 305 if (priv->stls_supported) 306 return STATE_STLS; 307 else 308 { 309 mn_client_session_set_error(priv->session, MN_CLIENT_SESSION_ERROR_OTHER, _("server does not support in-band SSL/TLS")); 310 return STATE_QUIT; 311 } 312 } 313 else 314 #endif /* WITH_SSL */ 315 return self_session_authenticate(priv); 316 317 case RESPONSE_LIST_ITEM: 318 { 319 char **tokens; 320 gboolean login_delay_error = FALSE; 321 322 tokens = g_strsplit(response->arguments, " ", 0); 323 if (tokens[0]) 324 { 325 if (! strcmp(tokens[0], "SASL")) 326 { 327 int i; 328 329 for (i = 1; tokens[i]; i++) 330 priv->auth_mechanisms = g_slist_append(priv->auth_mechanisms, g_strdup(tokens[i])); 331 } 332 else if (! strcmp(tokens[0], "TOP")) 333 priv->top_supported = TRUE; 334 else if (! strcmp(tokens[0], "UIDL")) 335 priv->uidl_supported = TRUE; 336 #if WITH_SSL 337 else if (! strcmp(tokens[0], "STLS")) 338 priv->stls_supported = TRUE; 339 #endif /* WITH_SSL */ 340 else if (! strcmp(tokens[0], "LOGIN-DELAY")) 341 { 342 if (tokens[1] && mn_str_isnumeric(tokens[1]) 343 && (! tokens[2] 344 || (! tokens[3] 345 && ! priv->authenticated 346 && ! strcmp(tokens[2], "USER")))) 347 { 348 priv->self->_priv->login_delay = atoi(tokens[1]); 349 if (tokens[2]) 350 priv->login_delay_user = TRUE; 351 } 352 else 353 login_delay_error = TRUE; 354 } 355 } 356 g_strfreev(tokens); 357 358 if (login_delay_error) 359 return mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("invalid arguments for the LOGIN-DELAY capability")); 360 } 361 return MN_CLIENT_SESSION_RESULT_CONTINUE; 362 363 default: 364 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 365 } 366 } 367 368 private int 369 enter_stls_cb (MNClientSession *session, 370 MNClientSessionPrivate *priv) 371 { 372 #if WITH_SSL 373 return mn_client_session_write(session, "STLS"); 374 #else 375 g_assert_not_reached(); 376 return 0; 377 #endif /* WITH_SSL */ 378 } 379 380 private int 381 handle_stls_cb (MNClientSession *session, 382 MNClientSessionResponse *response, 383 MNClientSessionPrivate *priv) 384 { 385 #if WITH_SSL 386 switch (response->type) 387 { 388 case RESPONSE_OK: 389 priv->stls_completed = TRUE; 390 return mn_client_session_enable_ssl(session) 391 ? STATE_CAPA /* [1] */ 392 : MN_CLIENT_SESSION_RESULT_DISCONNECT; 393 394 /* 395 * [1] RFC 2595 4: 396 * 397 * "Once TLS has been started, the client MUST discard cached 398 * information about server capabilities and SHOULD re-issue 399 * the CAPA command." 400 */ 401 402 case RESPONSE_ERR: 403 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 404 return STATE_QUIT; 405 406 default: 407 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 408 } 409 #else 410 g_assert_not_reached(); 411 return 0; 412 #endif /* WITH_SSL */ 413 } 414 415 private int 416 enter_auth_cb (MNClientSession *session, 417 MNClientSessionPrivate *priv) 418 { 419 #if WITH_SASL 420 /* 421 * RFC 2449 6.3 specifies that POP3 supports the initial client 422 * response feature of SASL. 423 */ 424 const char *initial_clientout = NULL; 425 unsigned int initial_clientoutlen = 0; 426 427 priv->sasl_mechanism = NULL; 428 429 if (mn_client_session_sasl_authentication_start(priv->session, 430 "pop", 431 priv->sasl_remaining_mechanisms, 432 priv->pi_mailbox->authmech, 433 &priv->sasl_mechanism, 434 priv->sasl_had_clientout ? NULL : &initial_clientout, 435 priv->sasl_had_clientout ? NULL : &initial_clientoutlen)) 436 { 437 g_assert(priv->sasl_mechanism != NULL); 438 439 if (initial_clientoutlen > 0) 440 { 441 char buf64[initial_clientoutlen * 2 + 1]; /* Base64 is 33% larger than the data it encodes */ 442 unsigned int outlen; 443 int result; 444 char *str; 445 446 result = sasl_encode64(initial_clientout, initial_clientoutlen, buf64, sizeof(buf64), &outlen); 447 if (result != SASL_OK) 448 return mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to encode Base64: %s"), sasl_errstring(result, NULL, NULL)); 449 450 str = g_strndup(buf64, outlen); 451 result = mn_client_session_write(session, "AUTH %s %s", priv->sasl_mechanism, str); 452 g_free(str); 453 454 priv->sasl_had_clientout = TRUE; 455 return result; 456 } 457 else 458 { 459 priv->sasl_had_clientout = FALSE; 460 return mn_client_session_write(session, "AUTH %s", priv->sasl_mechanism); 461 } 462 } 463 else 464 return priv->auth_mailbox->auth_cancelled 465 ? STATE_QUIT 466 : self_session_authenticate_fallback(priv, FALSE, FALSE); 467 #else 468 g_assert_not_reached(); 469 return 0; 470 #endif /* WITH_SASL */ 471 } 472 473 private int 474 handle_auth_cb (MNClientSession *session, 475 MNClientSessionResponse *response, 476 MNClientSessionPrivate *priv) 477 { 478 #if WITH_SASL 479 switch (response->type) 480 { 481 case RESPONSE_OK: 482 return mn_client_session_sasl_authentication_done(session) 483 ? self_session_authenticated(priv) 484 : MN_CLIENT_SESSION_RESULT_DISCONNECT; 485 486 case RESPONSE_ERR: 487 if (priv->auth_mailbox->auth_cancelled) 488 return STATE_QUIT; 489 else 490 { 491 if (priv->sasl_had_clientout) 492 { 493 /* 494 * Some servers violate RFC 2449 by not supporting a 495 * second argument to the AUTH command. Support these 496 * servers nevertheless, by retrying without the SASL 497 * initial client response. 498 */ 499 500 mn_client_session_notice(session, _("SASL authentication with initial client response failed, retrying without initial client response")); 501 return STATE_AUTH; 502 } 503 else 504 return self_session_authenticate_fallback(priv, FALSE, FALSE); 505 } 506 507 case RESPONSE_CONTINUATION: 508 return mn_client_session_sasl_authentication_step(session, response->arguments); 509 510 default: 511 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 512 } 513 #else 514 g_assert_not_reached(); 515 return 0; 516 #endif /* WITH_SASL */ 517 } 518 519 private int 520 enter_apop_cb (MNClientSession *session, 521 MNClientSessionPrivate *priv) 522 { 523 MNMD5Context context; 524 char buf[16]; 525 char hexbuf[33]; 526 527 g_assert(priv->apop_timestamp != NULL); 528 529 if (! mn_authenticated_mailbox_fill_password(priv->auth_mailbox, TRUE)) 530 return STATE_QUIT; 531 532 mn_md5_init_ctx(&context); 533 mn_md5_process_bytes(&context, priv->apop_timestamp, strlen(priv->apop_timestamp)); 534 mn_md5_process_bytes(&context, priv->auth_mailbox->runtime_password, strlen(priv->auth_mailbox->runtime_password)); 535 mn_md5_finish_ctx(&context, buf); 536 mn_md5_to_hex(buf, hexbuf); 537 538 return mn_client_session_write(session, "APOP %s %s", priv->auth_mailbox->username, hexbuf); 539 } 540 541 private int 542 handle_apop_cb (MNClientSession *session (check null), 543 MNClientSessionResponse *response (check null), 544 MNClientSessionPrivate *priv (check null)) 545 { 546 switch (response->type) 547 { 548 case RESPONSE_OK: 549 return self_session_authenticated(priv); 550 551 case RESPONSE_ERR: 552 return self_session_authenticate_fallback(priv, TRUE, FALSE); 553 554 default: 555 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 556 } 557 } 558 559 private int 560 enter_user_cb (MNClientSession *session (check null), 561 MNClientSessionPrivate *priv (check null)) 562 { 563 return mn_client_session_write(session, "USER %s", priv->auth_mailbox->username); 564 } 565 566 private int 567 handle_user_cb (MNClientSession *session, 568 MNClientSessionResponse *response, 569 MNClientSessionPrivate *priv) 570 { 571 switch (response->type) 572 { 573 case RESPONSE_OK: 574 return STATE_PASS; 575 576 case RESPONSE_ERR: 577 mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 578 return STATE_QUIT; 579 580 default: 581 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 582 } 583 } 584 585 private int 586 enter_pass_cb (MNClientSession *session, 587 MNClientSessionPrivate *priv) 588 { 589 if (! mn_authenticated_mailbox_fill_password(priv->auth_mailbox, TRUE)) 590 return STATE_QUIT; 591 592 return mn_client_session_write(session, "PASS %s", priv->auth_mailbox->runtime_password); 593 } 594 595 private int 596 handle_pass_cb (MNClientSession *session, 597 MNClientSessionResponse *response, 598 MNClientSessionPrivate *priv) 599 { 600 switch (response->type) 601 { 602 case RESPONSE_OK: 603 return self_session_authenticated(priv); 604 605 case RESPONSE_ERR: 606 return self_session_authenticate_fallback(priv, TRUE, TRUE); 607 608 default: 609 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 610 } 611 } 612 613 private int 614 enter_list_cb (MNClientSession *session, 615 MNClientSessionPrivate *priv) 616 { 617 return mn_client_session_write(session, "LIST"); 618 } 619 620 private int 621 handle_list_cb (MNClientSession *session, 622 MNClientSessionResponse *response, 623 MNClientSessionPrivate *priv) 624 { 625 self_session_handle_list_response(priv, response, FALSE); 626 627 switch (response->type) 628 { 629 case RESPONSE_OK: 630 return MN_CLIENT_SESSION_RESULT_CONTINUE; 631 632 case RESPONSE_LIST_END: 633 return self_session_enter_retr_top(priv); 634 635 case RESPONSE_ERR: 636 return mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 637 638 case RESPONSE_LIST_ITEM: 639 { 640 int num; 641 int size; 642 643 if (sscanf(response->arguments, "%d %d", &num, &size) == 2) 644 priv->messages = g_slist_prepend(priv->messages, self_message_info_new(num)); 645 else /* compliance error */ 646 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 647 } 648 return MN_CLIENT_SESSION_RESULT_CONTINUE; 649 650 default: 651 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 652 } 653 } 654 655 private int 656 enter_uidl_cb (MNClientSession *session, 657 MNClientSessionPrivate *priv) 658 { 659 return mn_client_session_write(session, "UIDL"); 660 } 661 662 private int 663 handle_uidl_cb (MNClientSession *session, 664 MNClientSessionResponse *response, 665 MNClientSessionPrivate *priv) 666 { 667 self_session_handle_list_response(priv, response, FALSE); 668 669 switch (response->type) 670 { 671 case RESPONSE_OK: 672 return MN_CLIENT_SESSION_RESULT_CONTINUE; 673 674 case RESPONSE_LIST_END: 675 return self_session_enter_retr_top(priv); 676 677 case RESPONSE_ERR: 678 /* 679 * The server advertised UIDL but does not support it, 680 * fallback to LIST. 681 */ 682 return STATE_LIST; 683 684 case RESPONSE_LIST_ITEM: 685 { 686 int num; 687 char *uid; 688 689 if (self_parse_uidl_list_item(response->arguments, &num, &uid)) 690 { 691 MessageInfo *info; 692 MNMessage *message; 693 694 info = self_message_info_new(num); 695 info->mid = uid; 696 697 message = mn_mailbox_get_message_from_mid(priv->mailbox, info->mid); 698 if (message) 699 info->message = g_object_ref(message); 700 701 priv->messages = g_slist_prepend(priv->messages, info); 702 703 return MN_CLIENT_SESSION_RESULT_CONTINUE; 704 } 705 else 706 /* compliance error */ 707 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 708 } 709 710 default: 711 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 712 } 713 } 714 715 private int 716 enter_retr_top_cb (MNClientSession *session, 717 MNClientSessionPrivate *priv) 718 { 719 MessageInfo *info; 720 721 info = self_session_message_iter_get_message_info(priv); 722 723 if (info) 724 return mn_client_session_write(session, priv->top_supported ? "TOP %i 0" : "RETR %i", info->number); 725 else 726 { 727 GSList *messages = NULL; 728 GSList *l; 729 730 MN_LIST_FOREACH(l, priv->messages) 731 { 732 info = l->data; 733 734 if (info->message) 735 messages = g_slist_prepend(messages, info->message); 736 } 737 738 GDK_THREADS_ENTER(); 739 740 mn_mailbox_set_messages(priv->mailbox, messages); 741 742 if (priv->num_errors != 0) 743 mn_mailbox_set_error(priv->mailbox, 744 ngettext("cannot retrieve %i message", 745 "cannot retrieve %i messages", 746 priv->num_errors), 747 priv->num_errors); 748 749 /* 750 * In authenticated_check(), we do not hold the GDK lock while 751 * unreffing the messages. We have just exposed the messages 752 * to other threads through our mn_mailbox_set_messages() 753 * call, so unref them here, while we hold the GDK lock. 754 */ 755 mn_g_slist_clear_deep_custom(&priv->messages, (GFunc) self_message_info_free, NULL); 756 757 gdk_flush(); 758 GDK_THREADS_LEAVE(); 759 760 g_slist_free(messages); 761 762 return STATE_QUIT; 763 } 764 } 765 766 private int 767 handle_retr_top_cb (MNClientSession *session, 768 MNClientSessionResponse *response, 769 MNClientSessionPrivate *priv) 770 { 771 self_session_handle_list_response(priv, response, TRUE); 772 773 switch (response->type) 774 { 775 case RESPONSE_OK: 776 if (priv->message_buffer) 777 g_string_free(priv->message_buffer, TRUE); 778 priv->message_buffer = g_string_new(NULL); 779 return MN_CLIENT_SESSION_RESULT_CONTINUE; 780 781 case RESPONSE_LIST_END: 782 { 783 MessageInfo *info = priv->message_iter->data; 784 GError *err = NULL; 785 786 g_assert(info->message == NULL); 787 788 info->message = mn_message_new_from_buffer(priv->mailbox, 789 priv->message_buffer->str, 790 priv->message_buffer->len, 791 info->mid, 792 0, 793 TRUE, 794 &err); 795 if (err) 796 { 797 mn_client_session_warning(session, 798 "cannot read message %i: %s", 799 info->number, 800 err->message); 801 g_error_free(err); 802 803 priv->num_errors++; 804 } 805 806 priv->message_iter = priv->message_iter->next; 807 return STATE_RETR_TOP; 808 } 809 810 case RESPONSE_ERR: 811 { 812 MessageInfo *info = priv->message_iter->data; 813 814 g_assert(info->message == NULL); 815 816 mn_client_session_warning(session, 817 "cannot retrieve message %i: %s", 818 info->number, 819 response->arguments ? response->arguments : "unknown error"); 820 priv->num_errors++; 821 822 priv->message_iter = priv->message_iter->next; 823 return STATE_RETR_TOP; 824 } 825 826 case RESPONSE_LIST_ITEM: 827 g_string_append_printf(priv->message_buffer, "%s\n", response->arguments); 828 return MN_CLIENT_SESSION_RESULT_CONTINUE; 829 830 default: 831 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 832 } 833 } 834 835 private int 836 enter_quit_cb (MNClientSession *session, 837 MNClientSessionPrivate *priv) 838 { 839 return mn_client_session_write(session, "QUIT"); 840 } 841 842 private int 843 handle_quit_cb (MNClientSession *session, 844 MNClientSessionResponse *response, 845 MNClientSessionPrivate *priv) 846 { 847 switch (response->type) 848 { 849 case RESPONSE_OK: 850 return MN_CLIENT_SESSION_RESULT_DISCONNECT; 851 852 case RESPONSE_ERR: 853 return mn_client_session_set_error_from_response(session, MN_CLIENT_SESSION_ERROR_OTHER, response->arguments); 854 855 default: 856 return MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT; 857 } 858 } 859 860 override (MN:Authenticated:Mailbox) void 861 authenticated_check (MNAuthenticatedMailbox *mailbox) 862 { 863 Self *self = SELF(mailbox); 864 static const MNClientSessionState states[] = { 865 { STATE_GREETING, NULL, self_handle_greeting_cb }, 866 { STATE_CAPA, self_enter_capa_cb, self_handle_capa_cb }, 867 #if WITH_SSL 868 { STATE_STLS, self_enter_stls_cb, self_handle_stls_cb }, 869 #endif 870 #if WITH_SASL 871 { STATE_AUTH, self_enter_auth_cb, self_handle_auth_cb }, 872 #endif 873 { STATE_APOP, self_enter_apop_cb, self_handle_apop_cb }, 874 { STATE_USER, self_enter_user_cb, self_handle_user_cb }, 875 { STATE_PASS, self_enter_pass_cb, self_handle_pass_cb }, 876 { STATE_LIST, self_enter_list_cb, self_handle_list_cb }, 877 { STATE_UIDL, self_enter_uidl_cb, self_handle_uidl_cb }, 878 { STATE_RETR_TOP, self_enter_retr_top_cb, self_handle_retr_top_cb }, 879 { STATE_QUIT, self_enter_quit_cb, self_handle_quit_cb }, 880 881 MN_CLIENT_SESSION_STATES_END 882 }; 883 static const MNClientSessionCallbacks callbacks = { 884 mn_pi_mailbox_notice_cb, 885 mn_pi_mailbox_warning_cb, 886 self_response_new_cb, 887 self_response_free_cb, 888 NULL, /* pre_read */ 889 NULL, /* post_read */ 890 #if WITH_SASL 891 mn_pi_mailbox_sasl_get_credentials_cb, 892 #endif 893 #if WITH_SSL 894 mn_pi_mailbox_ssl_trust_server_cb, 895 #endif 896 }; 897 MNClientSessionPrivate priv; 898 gboolean status; 899 GError *err = NULL; 900 901 PARENT_HANDLER(mailbox); 902 903 /* check if the parent handler has disabled the mailbox */ 904 if (! mn_mailbox_get_poll(MN_MAILBOX(self))) 905 return; 906 907 if (selfp->login_delay && selfp->authentication_timer) 908 { 909 double elapsed; 910 911 g_timer_stop(selfp->authentication_timer); 912 elapsed = g_timer_elapsed(selfp->authentication_timer, NULL); 913 914 if (elapsed < selfp->login_delay) 915 { 916 int sleeptime; 917 918 sleeptime = selfp->login_delay - elapsed; 919 mn_mailbox_notice(MN_MAILBOX(self), 920 ngettext("honouring LOGIN-DELAY, sleeping for %i second", 921 "honouring LOGIN-DELAY, sleeping for %i seconds", 922 sleeptime), 923 sleeptime); 924 sleep(sleeptime); 925 } 926 } 927 928 memset(&priv, 0, sizeof(priv)); 929 mn_pi_mailbox_session_private_init(MN_PI_MAILBOX(self), &priv); 930 priv.self = self; 931 932 status = mn_client_session_run(states, 933 &callbacks, 934 #if WITH_SSL 935 priv.pi_mailbox->connection_type == MN_PI_MAILBOX_CONNECTION_TYPE_SSL, 936 #endif 937 priv.pi_mailbox->hostname, 938 priv.pi_mailbox->runtime_port, 939 &priv, 940 &err); 941 942 if (! status) 943 { 944 char *escaped; 945 946 escaped = mn_utf8_escape(err->message); 947 g_error_free(err); 948 949 GDK_THREADS_ENTER(); 950 951 mn_mailbox_set_error(MN_MAILBOX(self), "%s", escaped); 952 953 gdk_flush(); 954 GDK_THREADS_LEAVE(); 955 956 g_free(escaped); 957 } 958 959 g_free(priv.apop_timestamp); 960 mn_g_slist_free_deep(priv.auth_mechanisms); 961 962 #if WITH_SASL 963 g_slist_free(priv.sasl_remaining_mechanisms); 964 #endif 965 966 mn_g_slist_free_deep_custom(priv.messages, (GFunc) self_message_info_free, NULL); 967 968 if (priv.message_buffer) 969 g_string_free(priv.message_buffer, TRUE); 970 } 971 972 private MNClientSessionResponse * 973 response_new_cb (MNClientSession *session, 974 const char *input, 975 MNClientSessionPrivate *priv) 976 { 977 MNClientSessionResponse *response = NULL; 978 979 /* 980 * About character set handling: 981 * 982 * RFC 1939 3 specifies that "keywords and arguments consist of 983 * printable ASCII characters", however that refers to 984 * commands. Nothing is said about responses. We therefore do not 985 * check the encoding of responses. 986 * 987 * Safety: 988 * - we ensure that GTK+ will only receive UTF-8 data by 989 * escaping error messages in authenticated_check() 990 * - responses ending up in mn_mailbox_notice() need not be 991 * valid UTF-8, since g_log() escapes unsafe and non UTF-8 992 * characters 993 * - RETR/TOP multiline responses are passed to our 994 * mn-message-mime implementation, which handles character set 995 * conversions 996 */ 997 998 if (priv->in_list) 999 { 1000 response = g_new0(MNClientSessionResponse, 1); 1001 if (! strcmp(input, ".")) 1002 response->type = RESPONSE_LIST_END; 1003 else if (g_str_has_prefix(input, "..")) 1004 { 1005 response->type = RESPONSE_LIST_ITEM; 1006 response->arguments = g_strdup(input + 1); /* skip the initial dot */ 1007 } 1008 else 1009 { 1010 response->type = RESPONSE_LIST_ITEM; 1011 response->arguments = g_strdup(input); 1012 } 1013 } 1014 else 1015 { 1016 if (g_str_has_prefix(input, "+ ")) 1017 { 1018 response = g_new0(MNClientSessionResponse, 1); 1019 response->type = RESPONSE_CONTINUATION; 1020 response->arguments = g_strdup(input + 2); 1021 } 1022 else 1023 { 1024 ResponseType type; 1025 int after_status = 0; 1026 1027 if (g_str_has_prefix(input, "+OK")) 1028 { 1029 type = RESPONSE_OK; 1030 after_status = 3; 1031 } 1032 else if (g_str_has_prefix(input, "-ERR")) 1033 { 1034 type = RESPONSE_ERR; 1035 after_status = 4; 1036 } 1037 1038 if (after_status) 1039 { 1040 char c = input[after_status]; 1041 1042 if (c == ' ' || c == ',') /* skip commonly used separators */ 1043 after_status++; 1044 1045 response = g_new0(MNClientSessionResponse, 1); 1046 response->type = type; 1047 if (input[after_status] != 0) 1048 response->arguments = g_strdup(input + after_status); 1049 } 1050 } 1051 } 1052 1053 return response; 1054 } 1055 1056 private void 1057 response_free_cb (MNClientSession *session, 1058 MNClientSessionResponse *response, 1059 MNClientSessionPrivate *priv) 1060 { 1061 g_free(response->arguments); 1062 g_free(response); 1063 } 1064 1065 private int 1066 session_authenticate (MNClientSessionPrivate *priv (check null)) 1067 { 1068 #if WITH_SASL 1069 g_slist_free(priv->sasl_remaining_mechanisms); 1070 priv->sasl_remaining_mechanisms = g_slist_copy(priv->auth_mechanisms); 1071 #endif /* WITH_SASL */ 1072 1073 if (priv->pi_mailbox->authmech) 1074 { 1075 if (*priv->pi_mailbox->authmech != '+') 1076 { 1077 #if WITH_SASL 1078 return STATE_AUTH; 1079 #else 1080 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")); 1081 return STATE_QUIT; 1082 #endif /* WITH_SASL */ 1083 } 1084 else 1085 { 1086 if (! strcmp(priv->pi_mailbox->authmech, "+APOP")) 1087 { 1088 if (priv->apop_timestamp) 1089 return STATE_APOP; 1090 else 1091 { 1092 mn_client_session_set_error(priv->session, MN_CLIENT_SESSION_ERROR_OTHER, _("server does not support APOP authentication")); 1093 return STATE_QUIT; 1094 } 1095 } 1096 else if (! strcmp(priv->pi_mailbox->authmech, "+USERPASS")) 1097 return STATE_USER; 1098 else 1099 { 1100 mn_client_session_set_error(priv->session, MN_CLIENT_SESSION_ERROR_OTHER, _("unknown authentication mechanism \"%s\""), priv->pi_mailbox->authmech); 1101 return STATE_QUIT; 1102 } 1103 } 1104 } 1105 else 1106 { 1107 #if WITH_SASL 1108 if (priv->sasl_remaining_mechanisms) 1109 return STATE_AUTH; 1110 #endif /* WITH_SASL */ 1111 if (priv->apop_timestamp) 1112 return STATE_APOP; 1113 else 1114 return STATE_USER; 1115 } 1116 } 1117 1118 private int 1119 session_authenticate_fallback (MNClientSessionPrivate *priv (check null), 1120 gboolean tried_apop, 1121 gboolean tried_pass) 1122 { 1123 if (! priv->pi_mailbox->authmech) 1124 { 1125 #if WITH_SASL 1126 if (priv->sasl_mechanism) 1127 { 1128 GSList *elem; 1129 1130 elem = mn_g_str_slist_find(priv->sasl_remaining_mechanisms, priv->sasl_mechanism); 1131 if (elem) 1132 { 1133 priv->sasl_remaining_mechanisms = g_slist_delete_link(priv->sasl_remaining_mechanisms, elem); 1134 if (priv->sasl_remaining_mechanisms) 1135 { 1136 mn_client_session_notice(priv->session, _("disabling mechanism \"%s\" and retrying SASL authentication"), priv->sasl_mechanism); 1137 return STATE_AUTH; 1138 } 1139 } 1140 } 1141 1142 /* SASL is not needed anymore, save some memory */ 1143 mn_client_session_sasl_dispose(priv->session); 1144 #endif /* WITH_SASL */ 1145 1146 if (! tried_apop && priv->apop_timestamp) 1147 { 1148 mn_client_session_notice(priv->session, _("falling back to APOP authentication")); 1149 return STATE_APOP; 1150 } 1151 else if (! tried_pass) 1152 { 1153 mn_client_session_notice(priv->session, _("falling back to USER/PASS authentication")); 1154 return STATE_USER; 1155 } 1156 } 1157 1158 if (priv->auth_mailbox->auth_prompted) 1159 { 1160 mn_authenticated_mailbox_auth_failed(priv->auth_mailbox); 1161 return self_session_authenticate(priv); 1162 } 1163 else 1164 { 1165 mn_client_session_set_error(priv->session, MN_CLIENT_SESSION_ERROR_OTHER, _("authentication failed")); 1166 return STATE_QUIT; 1167 } 1168 } 1169 1170 private int 1171 session_authenticated (MNClientSessionPrivate *priv (check null)) 1172 { 1173 priv->authenticated = TRUE; 1174 if (priv->self->_priv->login_delay) 1175 { 1176 if (priv->self->_priv->authentication_timer) 1177 g_timer_start(priv->self->_priv->authentication_timer); 1178 else 1179 priv->self->_priv->authentication_timer = g_timer_new(); 1180 } 1181 1182 /* 1183 * We are now in transaction state. We must re-issue CAPA if: 1184 * 1185 * - the LOGIN-DELAY capability announced in the authorization 1186 * state contained the USER argument (priv->login_delay_user is 1187 * true) 1188 * - the TOP capability was not announced in the authorization 1189 * state: although RFC 2449 states that "capabilities available 1190 * in the AUTHORIZATION state MUST be announced in both states", 1191 * some servers (for instance, pop.gmail.com) violate the RFC 1192 * and only announce TOP in the transaction state. 1193 */ 1194 return priv->login_delay_user || ! priv->top_supported 1195 ? STATE_CAPA 1196 : self_session_enter_list_or_uidl(priv); 1197 } 1198 1199 private void 1200 session_handle_list_response (MNClientSessionPrivate *priv (check null), 1201 MNClientSessionResponse *response (check null), 1202 gboolean in_retr_top) 1203 { 1204 switch (response->type) 1205 { 1206 case RESPONSE_OK: 1207 priv->in_list = TRUE; 1208 priv->in_retr_top = in_retr_top; 1209 break; 1210 1211 case RESPONSE_LIST_ITEM: /* nop */ 1212 break; 1213 1214 default: 1215 priv->in_list = FALSE; 1216 priv->in_retr_top = FALSE; 1217 break; 1218 } 1219 } 1220 1221 private int 1222 session_enter_list_or_uidl (MNClientSessionPrivate *priv (check null)) 1223 { 1224 if (priv->uidl_supported) 1225 return STATE_UIDL; 1226 else 1227 return STATE_LIST; 1228 } 1229 1230 private int 1231 session_enter_retr_top (MNClientSessionPrivate *priv (check null)) 1232 { 1233 priv->message_iter = priv->messages; 1234 1235 return STATE_RETR_TOP; 1236 } 1237 1238 private MessageInfo * 1239 session_message_iter_get_message_info (MNClientSessionPrivate *priv (check null)) 1240 { 1241 for (; priv->message_iter; priv->message_iter = priv->message_iter->next) 1242 { 1243 MessageInfo *info = priv->message_iter->data; 1244 1245 if (! info->message) 1246 return info; 1247 } 1248 1249 return NULL; 1250 } 1251 1252 private gboolean 1253 parse_uidl_list_item (const char *item (check null), 1254 int *number (check null), 1255 char **uid (check null)) 1256 { 1257 char **fields; 1258 gboolean status = FALSE; 1259 1260 fields = g_strsplit(item, " ", 2); 1261 if (g_strv_length(fields) == 2 && mn_str_isnumeric(fields[0])) 1262 { 1263 *number = atoi(fields[0]); 1264 *uid = g_strdup(fields[1]); 1265 status = TRUE; 1266 } 1267 g_strfreev(fields); 1268 1269 return status; 1270 } 1271 1272 private MessageInfo * 1273 message_info_new (int number) 1274 { 1275 MessageInfo *info; 1276 1277 info = g_new0(MessageInfo, 1); 1278 info->number = number; 1279 1280 return info; 1281 } 1282 1283 private void 1284 message_info_free (MessageInfo *info (check null)) 1285 { 1286 if (info->message) 1287 g_object_unref(info->message); 1288 g_free(info->mid); 1289 g_free(info); 1290 } 1291 1292 public char * 1293 build_name (const char *username (check null), 1294 const char *server (check null)) 1295 { 1296 return g_strdup_printf("%s@%s", username, server); 1297 } 1298 }