src/mn-client-session.c (41721B) - raw
1 /* 2 * mn-client-session.c - a state machine for handling POP3 and IMAP 3 * client sessions. 4 * 5 * The MNClientSession interface provides an abstract POP3 and IMAP 6 * protocol client. The module handles the low-level client 7 * functionality, such as connecting to a server, setting up SSL/TLS, 8 * reading and writing data, and conducting a SASL authentication 9 * exchange. 10 * 11 * MNClientSession contains no code which is specific to either POP3 12 * or IMAP. It is the responsability of the caller to manage the POP3 13 * or IMAP session, by parsing responses and switching to the 14 * appropriate state depending on the context. 15 * 16 * 17 * 18 * Mail Notification 19 * Copyright (C) 2003-2008 Jean-Yves Lefort <jylefort@brutele.be> 20 * 21 * This program is free software; you can redistribute it and/or modify 22 * it under the terms of the GNU General Public License as published by 23 * the Free Software Foundation; either version 3 of the License, or 24 * (at your option) any later version. 25 * 26 * This program is distributed in the hope that it will be useful, 27 * but WITHOUT ANY WARRANTY; without even the implied warranty of 28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 29 * GNU General Public License for more details. 30 * 31 * You should have received a copy of the GNU General Public License along 32 * with this program; if not, write to the Free Software Foundation, Inc., 33 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 34 */ 35 36 #include <stdio.h> /* required by stdlib.h on Darwin */ 37 #include <stdlib.h> /* required by sys/socket.h on Darwin */ 38 #include <stdarg.h> 39 #include <string.h> 40 #include <sys/types.h> 41 #include <sys/uio.h> 42 #include <unistd.h> 43 #include <sys/socket.h> 44 #include <netdb.h> 45 #include <netinet/in.h> 46 #include <arpa/inet.h> 47 #include <errno.h> 48 #include <glib.h> 49 #include <glib/gi18n.h> 50 #if WITH_SSL 51 #include <openssl/err.h> 52 #include <openssl/x509v3.h> 53 #include "mn-ssl.h" 54 #endif /* WITH_SSL */ 55 #if WITH_SASL 56 #include <sasl/sasl.h> 57 #include <sasl/saslutil.h> 58 #include "mn-sasl.h" 59 #endif /* WITH_SASL */ 60 #include "mn-util.h" 61 #include "mn-client-session.h" 62 63 #define READ_BUFSIZE 2048 64 65 struct _MNClientSession 66 { 67 const MNClientSessionState *states; 68 const MNClientSessionCallbacks *callbacks; 69 const char *server; 70 int port; 71 int s; 72 const MNClientSessionState *state; 73 GError *error; 74 MNClientSessionPrivate *private; 75 GByteArray *input_buffer; 76 unsigned int bytes_to_remove; 77 78 #if WITH_SSL 79 SSL *ssl; 80 #endif 81 82 #if WITH_SASL 83 sasl_conn_t *sasl_conn; 84 sasl_ssf_t sasl_ssf; 85 unsigned int sasl_maxoutbuf; 86 #endif /* WITH_SASL */ 87 }; 88 89 #if WITH_SASL 90 static sasl_callback_t sasl_callbacks[] = { 91 { SASL_CB_USER, NULL, NULL }, 92 { SASL_CB_AUTHNAME, NULL, NULL }, 93 { SASL_CB_PASS, NULL, NULL }, 94 95 { SASL_CB_LIST_END, NULL, NULL } 96 }; 97 #endif /* WITH_SASL */ 98 99 #ifndef HAVE_REENTRANT_RESOLVER 100 G_LOCK_DEFINE_STATIC(resolver); 101 #endif 102 103 static int 104 mn_connect (int fd, const struct sockaddr *addr, socklen_t addrlen) 105 { 106 int status; 107 108 again: 109 status = connect(fd, addr, addrlen); 110 if (status < 0) 111 switch (errno) 112 { 113 case EINTR: 114 goto again; 115 116 case EISCONN: 117 return 0; 118 } 119 120 return status; 121 } 122 123 static ssize_t 124 mn_read (int fd, void *buf, size_t count) 125 { 126 ssize_t bytes_read; 127 128 do 129 bytes_read = read(fd, buf, count); 130 while (bytes_read < 0 && errno == EINTR); 131 132 return bytes_read; 133 } 134 135 static ssize_t 136 mn_write (int fd, const void *buf, size_t count) 137 { 138 ssize_t bytes_written; 139 140 do 141 bytes_written = write(fd, buf, count); 142 while (bytes_written < 0 && errno == EINTR); 143 144 return bytes_written; 145 } 146 147 static int 148 mn_close (int fd) 149 { 150 int status; 151 152 do 153 status = close(fd); 154 while (status < 0 && errno == EINTR); 155 156 return status; 157 } 158 159 static struct addrinfo * 160 resolve (MNClientSession *session) 161 { 162 char *servname; 163 struct addrinfo hints; 164 struct addrinfo *addrinfo; 165 int status; 166 167 g_return_val_if_fail(session != NULL, NULL); 168 169 memset(&hints, 0, sizeof(hints)); 170 #if WITH_IPV6 171 hints.ai_family = PF_UNSPEC; 172 #else 173 hints.ai_family = PF_INET; 174 #endif /* WITH_IPV6 */ 175 hints.ai_socktype = SOCK_STREAM; 176 177 mn_client_session_notice(session, _("resolving %s"), session->server); 178 179 servname = g_strdup_printf("%i", session->port); 180 #ifndef HAVE_REENTRANT_RESOLVER 181 G_LOCK(resolver); 182 #endif 183 status = getaddrinfo(session->server, servname, &hints, &addrinfo); 184 #ifndef HAVE_REENTRANT_RESOLVER 185 G_UNLOCK(resolver); 186 #endif 187 g_free(servname); 188 189 if (status == 0) 190 return addrinfo; 191 else 192 { 193 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to resolve %s: %s"), session->server, gai_strerror(status)); 194 return NULL; 195 } 196 } 197 198 static int 199 client_session_connect (MNClientSession *session, struct addrinfo *addrinfo) 200 { 201 struct addrinfo *a; 202 int n; 203 204 g_return_val_if_fail(session != NULL, -1); 205 g_return_val_if_fail(addrinfo != NULL, -1); 206 207 /* iterate over addrinfo to find a working address (RFC 3484) */ 208 for (a = addrinfo, n = 1; a; a = a->ai_next, n++) 209 { 210 int status; 211 int s; 212 char buf[NI_MAXHOST]; 213 char *fail_str = NULL; 214 const char *ip; 215 216 #ifndef HAVE_REENTRANT_RESOLVER 217 G_LOCK(resolver); 218 #endif 219 status = getnameinfo(a->ai_addr, 220 a->ai_addrlen, 221 buf, 222 sizeof(buf), 223 NULL, 224 0, 225 NI_NUMERICHOST); 226 #ifndef HAVE_REENTRANT_RESOLVER 227 G_UNLOCK(resolver); 228 #endif 229 230 if (status == 0) 231 ip = buf; 232 else 233 { 234 fail_str = g_strdup_printf(_("network address #%i"), n); 235 ip = fail_str; 236 237 mn_client_session_warning(session, _("unable to convert network address #%i into textual form: %s"), n, gai_strerror(status)); 238 } 239 240 if (a->ai_family == AF_INET) 241 ((struct sockaddr_in *) a->ai_addr)->sin_port = g_htons(session->port); 242 #if WITH_IPV6 243 else if (a->ai_family == AF_INET6) 244 ((struct sockaddr_in6 *) a->ai_addr)->sin6_port = g_htons(session->port); 245 #endif /* WITH_IPV6 */ 246 else 247 { 248 mn_client_session_notice(session, _("%s: unsupported address family"), ip); 249 goto failure; 250 } 251 252 s = socket(a->ai_family, SOCK_STREAM, 0); 253 if (s < 0) 254 { 255 mn_client_session_notice(session, _("%s: unable to create socket: %s"), ip, g_strerror(errno)); 256 goto failure; 257 } 258 259 mn_client_session_notice(session, _("connecting to %s (%s) port %i"), session->server, ip, session->port); 260 if (mn_connect(s, a->ai_addr, a->ai_addrlen) < 0) 261 { 262 mn_client_session_notice(session, _("unable to connect: %s"), g_strerror(errno)); 263 mn_close(s); 264 } 265 else 266 { 267 mn_client_session_notice(session, _("connected successfully")); 268 goto success; 269 } 270 271 failure: 272 g_free(fail_str); 273 continue; 274 275 success: 276 g_free(fail_str); 277 return s; 278 } 279 280 /* if reached, we couldn't find a working address */ 281 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to connect to %s"), session->server); 282 return -1; 283 } 284 285 static int 286 enter_state (MNClientSession *session, int id) 287 { 288 int i; 289 290 g_return_val_if_fail(session != NULL, 0); 291 292 for (i = 0; session->states[i].id; i++) 293 if (session->states[i].id == id) 294 { 295 session->state = &session->states[i]; 296 return session->state->enter_cb 297 ? session->state->enter_cb(session, session->private) 298 : MN_CLIENT_SESSION_RESULT_CONTINUE; 299 } 300 301 g_assert_not_reached(); 302 return 0; 303 } 304 305 static gboolean 306 handle_input (MNClientSession *session, const char *input) 307 { 308 MNClientSessionResponse *response; 309 gboolean cont = TRUE; 310 311 g_return_val_if_fail(session != NULL, FALSE); 312 g_return_val_if_fail(input != NULL, FALSE); 313 314 response = session->callbacks->response_new(session, input, session->private); 315 if (response) 316 { 317 int result; 318 319 g_assert(session->state->handle_cb != NULL); 320 result = session->state->handle_cb(session, response, session->private); 321 322 loop: 323 switch (result) 324 { 325 case MN_CLIENT_SESSION_RESULT_CONTINUE: 326 break; 327 328 case MN_CLIENT_SESSION_RESULT_BAD_RESPONSE_FOR_CONTEXT: 329 { 330 char *escaped; 331 332 escaped = mn_utf8_escape(input); 333 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("response \"%s\" is not valid in current context"), escaped); 334 g_free(escaped); 335 336 cont = FALSE; 337 } 338 break; 339 340 case MN_CLIENT_SESSION_RESULT_DISCONNECT: 341 cont = FALSE; 342 break; 343 344 case 0: /* assertion failed somewhere */ 345 g_assert_not_reached(); 346 break; 347 348 default: 349 g_assert(result > 0); 350 result = enter_state(session, result); 351 goto loop; 352 } 353 354 if (session->callbacks->response_free) 355 session->callbacks->response_free(session, response, session->private); 356 } 357 else 358 { 359 char *escaped; 360 361 escaped = mn_utf8_escape(input); 362 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to parse response \"%s\""), escaped); 363 g_free(escaped); 364 365 cont = FALSE; 366 } 367 368 return cont; 369 } 370 371 /** 372 * mn_client_session_run: 373 * @states: a %MN_CLIENT_SESSION_STATES_END-terminated array of 374 * %MNClientSessionState structures. One of the states must 375 * have the %MN_CLIENT_SESSION_INITIAL_STATE id. 376 * @callbacks: a pointer to a %MNClientSessionCallbacks structure 377 * @use_ssl: whether to establish a SSL/TLS connection or not 378 * @server: the hostname, IPv4 address or IPv6 address to connect to 379 * @port: the port to connect to 380 * @private: an opaque pointer which will be passed to callbacks, or %NULL 381 * @err: a location to report errors, or %NULL 382 * 383 * Runs the client session. After connecting to the server, the 384 * %MN_CLIENT_SESSION_INITIAL_STATE state is entered. 385 * 386 * Return value: %TRUE on success, or %FALSE on failure (in such case 387 * @err is set) 388 **/ 389 gboolean 390 mn_client_session_run (const MNClientSessionState *states, 391 const MNClientSessionCallbacks *callbacks, 392 #if WITH_SSL 393 gboolean use_ssl, 394 #endif 395 const char *server, 396 int port, 397 MNClientSessionPrivate *private, 398 GError **err) 399 { 400 MNClientSession session; 401 struct addrinfo *addrinfo; 402 const char *line; 403 404 g_return_val_if_fail(states != NULL, FALSE); 405 g_return_val_if_fail(callbacks != NULL, FALSE); 406 g_return_val_if_fail(callbacks->response_new != NULL, FALSE); 407 #if WITH_SSL 408 g_return_val_if_fail(callbacks->ssl_trust_server != NULL, FALSE); 409 #endif 410 g_return_val_if_fail(server != NULL, FALSE); 411 412 memset(&session, 0, sizeof(session)); 413 session.states = states; 414 session.callbacks = callbacks; 415 session.server = server; 416 session.port = port; 417 session.private = private; 418 419 addrinfo = resolve(&session); 420 if (! addrinfo) 421 goto end; 422 423 session.s = client_session_connect(&session, addrinfo); 424 freeaddrinfo(addrinfo); 425 if (session.s < 0) 426 goto end; 427 428 #if WITH_SSL 429 if (use_ssl) 430 { 431 if (! mn_client_session_enable_ssl(&session)) 432 goto end; 433 } 434 #endif /* WITH_SSL */ 435 436 enter_state(&session, MN_CLIENT_SESSION_INITIAL_STATE); 437 438 session.input_buffer = g_byte_array_new(); 439 while ((line = mn_client_session_read_line(&session))) 440 if (! handle_input(&session, line)) 441 break; 442 g_byte_array_free(session.input_buffer, TRUE); 443 444 end: 445 if (session.s >= 0) 446 mn_close(session.s); 447 #if WITH_SSL 448 if (session.ssl) 449 SSL_free(session.ssl); 450 #endif /* WITH_SSL */ 451 #if WITH_SASL 452 if (session.sasl_conn) 453 sasl_dispose(&session.sasl_conn); 454 #endif /* WITH_SASL */ 455 if (session.error) 456 { 457 g_propagate_error(err, session.error); 458 return FALSE; 459 } 460 else 461 return TRUE; 462 } 463 464 #if WITH_SSL 465 /** 466 * mn_client_session_ssl_get_certificate_servers(): 467 * @cert: the server certificate 468 * 469 * Returns the list of server names (commonName and subjectAltName) 470 * contained in @cert. 471 * 472 * Return value: a newly-allocated list of UTF-8 server names. When no 473 * longer used, the list must be freed with mn_g_slist_free_deep(). 474 **/ 475 static GSList * 476 get_ssl_certificate_servers (X509 *cert) 477 { 478 GSList *servers = NULL; 479 X509_NAME *subject; 480 void *ext; 481 482 g_return_val_if_fail(cert != NULL, NULL); 483 484 /* append the commonName entries */ 485 486 subject = X509_get_subject_name(cert); 487 if (subject) 488 { 489 int pos = -1; 490 491 while (TRUE) 492 { 493 X509_NAME_ENTRY *entry; 494 ASN1_STRING *data; 495 int len; 496 unsigned char *str; 497 498 pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos); 499 if (pos == -1) 500 break; 501 502 entry = X509_NAME_get_entry(subject, pos); 503 if (! entry) 504 continue; 505 506 data = X509_NAME_ENTRY_get_data(entry); 507 if (! data) 508 continue; 509 510 len = ASN1_STRING_to_UTF8(&str, data); 511 if (len < 0) 512 continue; 513 514 g_assert(g_utf8_validate(str, len, NULL)); 515 516 servers = g_slist_append(servers, g_strndup(str, len)); 517 OPENSSL_free(str); 518 } 519 } 520 521 /* 522 * RFC 3501 11.1: "If a subjectAltName extension of type dNSName is 523 * present in the certificate, it SHOULD be used as the source of 524 * the server's identity." 525 */ 526 527 ext = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); 528 if (ext) 529 { 530 int count; 531 int i; 532 533 count = sk_GENERAL_NAME_num(ext); 534 for (i = 0; i < count; i++) 535 { 536 GENERAL_NAME *name; 537 538 name = sk_GENERAL_NAME_value(ext, i); 539 if (name 540 && name->type == GEN_DNS 541 && name->d.ia5->data 542 && g_utf8_validate(name->d.ia5->data, -1, NULL)) 543 servers = g_slist_append(servers, g_strdup(name->d.ia5->data)); 544 } 545 } 546 547 return servers; 548 } 549 550 static gboolean 551 check_ssl_server_name (const char *user_name, const char *cert_name) 552 { 553 g_return_val_if_fail(user_name != NULL, FALSE); 554 g_return_val_if_fail(cert_name != NULL, FALSE); 555 556 /* 557 * RFC 3501 11.1: "A "*" wildcard character MAY be used as the 558 * left-most name component in the certificate. For example, 559 * *.example.com would match a.example.com, foo.example.com, 560 * etc. but would not match example.com." 561 */ 562 563 if (g_str_has_prefix(cert_name, "*.")) 564 { 565 const char *domain = cert_name + 1; 566 567 return mn_utf8_str_case_has_suffix(user_name, domain) && strlen(user_name) > strlen(domain); 568 } 569 else 570 return ! mn_utf8_strcasecmp(user_name, cert_name); 571 } 572 573 static gboolean 574 check_ssl_server_name_from_list (const char *user_name, GSList *cert_names) 575 { 576 GSList *l; 577 578 g_return_val_if_fail(user_name != NULL, FALSE); 579 580 MN_LIST_FOREACH(l, cert_names) 581 { 582 const char *cert_name = l->data; 583 584 if (check_ssl_server_name(user_name, cert_name)) 585 return TRUE; 586 } 587 588 return FALSE; 589 } 590 591 static char * 592 get_ssl_verify_error (MNClientSession *session, X509 *cert) 593 { 594 long verify_result; 595 GSList *servers; 596 char *error = NULL; 597 598 g_return_val_if_fail(session != NULL, NULL); 599 g_return_val_if_fail(session->ssl != NULL, NULL); 600 g_return_val_if_fail(cert != NULL, NULL); 601 602 /* check the result of the OpenSSL verification */ 603 604 verify_result = SSL_get_verify_result(session->ssl); 605 if (verify_result != X509_V_OK) 606 { 607 /* 608 * X509_verify_cert_error_string() is thread-unsafe (it can 609 * return a pointer to a temporary static buffer). 610 */ 611 G_LOCK(mn_ssl); 612 error = g_strdup(X509_verify_cert_error_string(verify_result)); 613 G_UNLOCK(mn_ssl); 614 615 return error; 616 } 617 618 /* 619 * Check if the user-provided server name matches one of the 620 * certificate-provided server names. This is required for IMAP (RFC 621 * 3501 11.1) and cannot hurt for POP3. 622 */ 623 624 servers = get_ssl_certificate_servers(cert); 625 if (! servers) 626 return g_strdup(_("server name not found in certificate")); 627 628 if (! check_ssl_server_name_from_list(session->server, servers)) 629 { 630 if (g_slist_length(servers) == 1) 631 error = g_strdup_printf(_("user-provided server name \"%s\" does not match certificate-provided server name \"%s\""), 632 session->server, (char *) servers->data); 633 else 634 { 635 GString *servers_comma_list; 636 GSList *l; 637 638 servers_comma_list = g_string_new(NULL); 639 640 MN_LIST_FOREACH(l, servers) 641 { 642 char *server = l->data; 643 644 if (l->next) 645 g_string_append_printf(servers_comma_list, _("\"%s\", "), server); 646 else 647 g_string_append_printf(servers_comma_list, _("\"%s\""), server); 648 } 649 650 error = g_strdup_printf(_("user-provided server name \"%s\" matches none of the certificate-provided server names %s"), 651 session->server, servers_comma_list->str); 652 653 g_string_free(servers_comma_list, TRUE); 654 } 655 } 656 657 mn_g_slist_free_deep(servers); 658 659 return error; 660 } 661 662 static gboolean 663 verify_ssl_certificate (MNClientSession *session) 664 { 665 X509 *cert; 666 667 g_return_val_if_fail(session != NULL, FALSE); 668 g_return_val_if_fail(session->ssl != NULL, FALSE); 669 670 cert = SSL_get_peer_certificate(session->ssl); 671 if (cert) 672 { 673 char *error; 674 gboolean status = FALSE; 675 676 error = get_ssl_verify_error(session, cert); 677 if (! error) 678 status = TRUE; 679 else 680 { 681 unsigned char md5sum[16]; 682 unsigned char fingerprint[40]; 683 int md5len; 684 int i; 685 unsigned char *f; 686 687 /* calculate the MD5 hash of the raw certificate */ 688 md5len = sizeof(md5sum); 689 X509_digest(cert, EVP_md5(), md5sum, &md5len); 690 for (i = 0, f = fingerprint; i < 16; i++, f += 3) 691 sprintf(f, "%.2x%c", md5sum[i], i != 15 ? ':' : '\0'); 692 693 if (session->callbacks->ssl_trust_server(session, 694 session->server, 695 session->port, 696 fingerprint, 697 error, 698 session->private)) 699 status = TRUE; 700 701 g_free(error); 702 } 703 704 X509_free(cert); 705 706 return status; 707 } 708 else 709 return session->callbacks->ssl_trust_server(session, 710 session->server, 711 session->port, 712 NULL, 713 NULL, 714 session->private); 715 } 716 717 /** 718 * mn_client_session_enable_ssl: 719 * @session: a #MNClientSession 720 * 721 * Enables in-band SSL/TLS. Must not be used if the @use_ssl 722 * mn_client_session_run() argument was %TRUE. If an error occurs, 723 * mn_client_session_set_error() will be called on @session. 724 * 725 * Return value: %TRUE on success 726 **/ 727 gboolean 728 mn_client_session_enable_ssl (MNClientSession *session) 729 { 730 SSL_CTX *ctx; 731 GError *err = NULL; 732 int ret; 733 734 g_return_val_if_fail(session != NULL, FALSE); 735 g_return_val_if_fail(session->ssl == NULL, FALSE); 736 737 ctx = mn_ssl_init(&err); 738 if (! ctx) 739 { 740 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to initialize the OpenSSL library: %s"), err->message); 741 g_error_free(err); 742 return FALSE; 743 } 744 745 session->ssl = SSL_new(ctx); 746 if (! session->ssl) 747 { 748 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to create a SSL/TLS object: %s"), mn_ssl_get_error()); 749 return FALSE; 750 } 751 752 if (! SSL_set_fd(session->ssl, session->s)) 753 { 754 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to set the SSL/TLS file descriptor: %s"), mn_ssl_get_error()); 755 return FALSE; 756 } 757 758 ret = SSL_connect(session->ssl); 759 if (ret != 1) 760 { 761 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to perform the SSL/TLS handshake: %s"), mn_ssl_get_io_error(session->ssl, ret)); 762 return FALSE; 763 } 764 765 if (! verify_ssl_certificate(session)) 766 { 767 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("untrusted server")); 768 return FALSE; 769 } 770 771 mn_client_session_notice(session, _("a SSL/TLS layer is now active (%s, %s %i-bit)"), 772 SSL_get_version(session->ssl), 773 SSL_get_cipher(session->ssl), 774 SSL_get_cipher_bits(session->ssl, NULL)); 775 776 return TRUE; 777 } 778 #endif /* WITH_SSL */ 779 780 static void 781 prepare_input_buffer (MNClientSession *session) 782 { 783 g_return_if_fail(session != NULL); 784 785 if (session->bytes_to_remove) 786 { 787 g_byte_array_remove_range(session->input_buffer, 0, session->bytes_to_remove); 788 session->bytes_to_remove = 0; 789 } 790 } 791 792 static gboolean 793 mn_client_session_fill_input_buffer (MNClientSession *session) 794 { 795 char buf[READ_BUFSIZE]; 796 ssize_t bytes_read; 797 const char *in = NULL; 798 unsigned int inlen; 799 800 g_return_val_if_fail(session != NULL, FALSE); 801 802 if (session->callbacks->pre_read) 803 session->callbacks->pre_read(session, session->private); 804 805 #if WITH_SSL 806 if (session->ssl) 807 bytes_read = SSL_read(session->ssl, buf, sizeof(buf)); 808 else 809 #endif /* WITH_SSL */ 810 bytes_read = mn_read(session->s, buf, sizeof(buf)); 811 812 if (session->callbacks->post_read) 813 session->callbacks->post_read(session, session->private); 814 815 if (bytes_read <= 0) 816 { 817 #if WITH_SSL 818 if (session->ssl) 819 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_CONNECTION_LOST, _("unable to read from server: %s"), mn_ssl_get_io_error(session->ssl, bytes_read)); 820 else 821 #endif /* WITH_SSL */ 822 { 823 if (bytes_read == 0) 824 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_CONNECTION_LOST, _("unable to read from server: EOF")); 825 else 826 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_CONNECTION_LOST, _("unable to read from server: %s"), g_strerror(errno)); 827 } 828 return FALSE; 829 } 830 831 #if WITH_SASL 832 if (session->sasl_ssf) 833 { 834 if (sasl_decode(session->sasl_conn, buf, bytes_read, &in, &inlen) != SASL_OK) 835 { 836 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to decode data using SASL: %s"), sasl_errdetail(session->sasl_conn)); 837 return FALSE; 838 } 839 } 840 #endif /* WITH_SASL */ 841 842 if (! in) 843 { 844 in = buf; 845 inlen = bytes_read; 846 } 847 848 g_byte_array_append(session->input_buffer, in, inlen); 849 return TRUE; 850 } 851 852 /** 853 * mn_client_session_read: 854 * @session: a #MNClientSession object to read from 855 * @nbytes: the number of bytes to read 856 * 857 * Reads exactly @nbytes from @session. If an error occurs, 858 * mn_client_session_set_error() will be called on @session. 859 * 860 * Return value: a pointer to a buffer containing @nbytes on success, 861 * %NULL on failure. The pointer will be valid until the next call to 862 * mn_client_session_read() or mn_client_session_read_line(). 863 **/ 864 gconstpointer 865 mn_client_session_read (MNClientSession *session, unsigned int nbytes) 866 { 867 char *str; 868 869 g_return_val_if_fail(session != NULL, FALSE); 870 g_return_val_if_fail(session->input_buffer != NULL, FALSE); 871 g_return_val_if_fail(nbytes >= 0, FALSE); 872 873 prepare_input_buffer(session); 874 875 while (session->input_buffer->len < nbytes) 876 if (! mn_client_session_fill_input_buffer(session)) 877 return FALSE; 878 879 session->bytes_to_remove = nbytes; 880 881 str = g_strndup(session->input_buffer->data, nbytes); 882 /* g_log() escapes unsafe and non UTF-8 characters, so this is safe */ 883 mn_client_session_notice(session, "< %s", str); 884 g_free(str); 885 886 return session->input_buffer->data; 887 } 888 889 /** 890 * mn_client_session_read_line: 891 * @session: a #MNClientSession object to read from 892 * 893 * Reads a crlf-terminated line from @session. If an error occurs, 894 * mn_client_session_set_error() will be called on @session. 895 * 896 * Return value: the line read on success, %NULL on failure. The 897 * pointer will be valid until the next call to 898 * mn_client_session_read() or mn_client_session_read_line(). 899 **/ 900 const char * 901 mn_client_session_read_line (MNClientSession *session) 902 { 903 char *terminator; 904 const char *line; 905 906 g_return_val_if_fail(session != NULL, NULL); 907 g_return_val_if_fail(session->input_buffer != NULL, NULL); 908 909 prepare_input_buffer(session); 910 911 while (! (session->input_buffer->data 912 && (terminator = g_strstr_len(session->input_buffer->data, 913 session->input_buffer->len, 914 "\r\n")))) 915 if (! mn_client_session_fill_input_buffer(session)) 916 return NULL; 917 918 *terminator = 0; 919 session->bytes_to_remove = terminator - (char *) session->input_buffer->data + 2; 920 921 line = session->input_buffer->data; 922 923 /* g_log() escapes unsafe and non UTF-8 characters, so this is safe */ 924 mn_client_session_notice(session, "< %s", line); 925 926 return line; 927 } 928 929 /** 930 * mn_client_session_write: 931 * @session: a #MNClientSession object to write to 932 * @format: a printf() format string 933 * @...: the arguments to the format string 934 * 935 * Writes a formatted crlf-terminated line to @session. If an error 936 * occurs, mn_client_session_set_error() will be called on @session. 937 * 938 * Return value: %MN_CLIENT_SESSION_RESULT_CONTINUE on success, or the 939 * return value of mn_client_session_set_error() on failure 940 **/ 941 int 942 mn_client_session_write (MNClientSession *session, 943 const char *format, 944 ...) 945 { 946 char *str; 947 char *full; 948 unsigned int len; 949 GByteArray *array = NULL; 950 ssize_t bytes_written; 951 int result = MN_CLIENT_SESSION_RESULT_CONTINUE; 952 953 g_return_val_if_fail(session != NULL, 0); 954 g_return_val_if_fail(format != NULL, 0); 955 956 MN_STRDUP_VPRINTF(str, format); 957 958 mn_client_session_notice(session, "> %s", str); 959 960 full = g_strconcat(str, "\r\n", NULL); 961 g_free(str); 962 963 len = strlen(full); 964 965 #if WITH_SASL 966 if (session->sasl_ssf) 967 { 968 unsigned int start = 0; 969 970 array = g_byte_array_new(); 971 while (len > 0) 972 { 973 unsigned int chunk_len; 974 const char *out; 975 unsigned int outlen; 976 977 chunk_len = MIN(len, session->sasl_maxoutbuf); 978 if (sasl_encode(session->sasl_conn, full + start, chunk_len, &out, &outlen) != SASL_OK) 979 { 980 result = mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to encode data using SASL: %s"), sasl_errdetail(session->sasl_conn)); 981 goto end; 982 } 983 984 g_byte_array_append(array, out, outlen); 985 986 start += chunk_len; 987 len -= chunk_len; 988 } 989 } 990 #endif /* WITH_SASL */ 991 992 if (! array) 993 { 994 array = g_byte_array_sized_new(len); 995 g_byte_array_append(array, full, len); 996 } 997 998 #if WITH_SSL 999 if (session->ssl) 1000 bytes_written = SSL_write(session->ssl, array->data, array->len); 1001 else 1002 #endif /* WITH_SSL */ 1003 bytes_written = mn_write(session->s, array->data, array->len); 1004 1005 if (bytes_written <= 0) 1006 { 1007 #if WITH_SSL 1008 if (session->ssl) 1009 result = mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_CONNECTION_LOST, _("unable to write to server: %s"), mn_ssl_get_io_error(session->ssl, bytes_written)); 1010 else 1011 #endif /* WITH_SSL */ 1012 { 1013 if (bytes_written == 0) 1014 result = mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_CONNECTION_LOST, _("unable to write to server: EOF")); 1015 else 1016 result = mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_CONNECTION_LOST, _("unable to write to server: %s"), g_strerror(errno)); 1017 } 1018 } 1019 1020 #if WITH_SASL 1021 end: 1022 #endif 1023 g_free(full); 1024 g_byte_array_free(array, TRUE); 1025 1026 return result; 1027 } 1028 1029 #if WITH_SASL 1030 static int 1031 write_base64 (MNClientSession *session, const char *buf, unsigned int len) 1032 { 1033 char buf64[len * 2 + 1]; /* Base64 is 33% larger than the data it encodes */ 1034 unsigned int outlen; 1035 int result; 1036 char *str; 1037 1038 g_return_val_if_fail(session != NULL, 0); 1039 g_return_val_if_fail(buf != NULL, 0); 1040 1041 result = sasl_encode64(buf, len, buf64, sizeof(buf64), &outlen); 1042 if (result != SASL_OK) 1043 return mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to encode Base64: %s"), sasl_errstring(result, NULL, NULL)); 1044 1045 str = g_strndup(buf64, outlen); 1046 result = mn_client_session_write(session, "%s", str); 1047 g_free(str); 1048 1049 return result; 1050 } 1051 1052 static gboolean 1053 fill_sasl_interact (MNClientSession *session, 1054 sasl_interact_t *interact, 1055 const char *unknown_warning) 1056 { 1057 sasl_interact_t *i; 1058 gboolean need_username = FALSE; 1059 gboolean need_password = FALSE; 1060 const char *username = NULL; 1061 const char *password = NULL; 1062 1063 g_return_val_if_fail(session != NULL, FALSE); 1064 g_return_val_if_fail(interact != NULL, FALSE); 1065 1066 for (i = interact; i->id; i++) 1067 switch (i->id) 1068 { 1069 case SASL_CB_USER: 1070 case SASL_CB_AUTHNAME: 1071 need_username = TRUE; 1072 break; 1073 1074 case SASL_CB_PASS: 1075 need_password = TRUE; 1076 break; 1077 1078 default: 1079 mn_client_session_warning(session, "%s", unknown_warning); 1080 return FALSE; 1081 }; 1082 1083 if (need_username || need_password) 1084 { 1085 if (! session->callbacks->sasl_get_credentials(session, 1086 session->private, 1087 need_username ? &username : NULL, 1088 need_password ? &password : NULL)) 1089 return FALSE; 1090 } 1091 1092 for (i = interact; i->id; i++) 1093 { 1094 const char *data; 1095 1096 switch (i->id) 1097 { 1098 case SASL_CB_USER: 1099 case SASL_CB_AUTHNAME: 1100 data = username; 1101 break; 1102 1103 case SASL_CB_PASS: 1104 data = password; 1105 break; 1106 1107 default: 1108 g_assert_not_reached(); 1109 break; 1110 }; 1111 1112 g_assert(data != NULL); 1113 1114 i->result = data; 1115 i->len = strlen(data); 1116 } 1117 1118 return TRUE; 1119 } 1120 1121 static char * 1122 get_sasl_ip_port (const struct sockaddr *addr) 1123 { 1124 #if WITH_IPV6 1125 char buf[INET6_ADDRSTRLEN]; 1126 #else 1127 char buf[INET_ADDRSTRLEN]; 1128 #endif /* WITH_IPV6 */ 1129 int port; 1130 1131 g_return_val_if_fail(addr != NULL, NULL); 1132 1133 if (addr->sa_family == AF_INET) 1134 { 1135 struct sockaddr_in *in = (struct sockaddr_in *) addr; 1136 1137 if (! inet_ntop(addr->sa_family, &in->sin_addr, buf, sizeof(buf))) 1138 return NULL; 1139 port = g_ntohs(in->sin_port); 1140 } 1141 #if WITH_IPV6 1142 else if (addr->sa_family == AF_INET6) 1143 { 1144 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *) addr; 1145 1146 if (! inet_ntop(addr->sa_family, &in6->sin6_addr, buf, sizeof(buf))) 1147 return NULL; 1148 port = g_ntohs(in6->sin6_port); 1149 } 1150 #endif 1151 else 1152 return NULL; 1153 1154 return g_strdup_printf("%s;%i", buf, port); 1155 } 1156 1157 /** 1158 * mn_client_session_sasl_authentication_start: 1159 * @session: a #MNClientSession 1160 * @service: the SASL service identifier (normally "pop" or "imap") 1161 * @mechanisms: the list of available mechanisms, or %NULL 1162 * @forced_mechanism: a mechanism to force usage of, or %NULL 1163 * @used_mechanism: a location to store the name of the mechanism that was 1164 * selected by the SASL library 1165 * @initial_clientout: a location to store the initial client response, 1166 * or %NULL 1167 * @initial_clientoutlen: a location to store the length of the initial 1168 * client response, or %NULL 1169 * 1170 * Starts a SASL authentication exchange. @initial_clientout and 1171 * @initial_clientoutlen must be both set or both %NULL. 1172 * 1173 * If @forced_mechanism is provided, authentication is attempted using 1174 * that mechanism only. Otherwise, @mechanisms must point to a 1175 * non-empty list of available mechanism names, and the SASL library 1176 * will select an appropriate mechanism automatically. 1177 * 1178 * On success, the selected mechanism is stored at @used_mechanism. 1179 * 1180 * On failure, if a mechanism could be selected, it is stored at 1181 * @used_mechanism (the caller might want to remove that mechanism 1182 * from the list and try again). Otherwise, %NULL is stored at 1183 * @used_mechanism. 1184 * 1185 * On success, if @initial_clientout and @initial_clientoutlen were 1186 * set, they point to the initial client response (which is not 1187 * necessarily NUL-terminated) and its length, respectively. If there 1188 * is no initial client response, they point to %NULL and 0, 1189 * respectively. 1190 * 1191 * The function may be called multiple times. 1192 * 1193 * Return value: %TRUE on success 1194 **/ 1195 gboolean 1196 mn_client_session_sasl_authentication_start (MNClientSession *session, 1197 const char *service, 1198 GSList *mechanisms, 1199 const char *forced_mechanism, 1200 const char **used_mechanism, 1201 const char **initial_clientout, 1202 unsigned int *initial_clientoutlen) 1203 { 1204 GError *err = NULL; 1205 int result; 1206 struct sockaddr name; 1207 socklen_t namelen; 1208 char *local_ip_port = NULL; 1209 char *remote_ip_port = NULL; 1210 1211 g_return_val_if_fail(session != NULL, FALSE); 1212 g_return_val_if_fail(session->callbacks->sasl_get_credentials != NULL, FALSE); 1213 g_return_val_if_fail(service != NULL, FALSE); 1214 g_return_val_if_fail(mechanisms != NULL || forced_mechanism != NULL, FALSE); 1215 g_return_val_if_fail((initial_clientout == NULL && initial_clientoutlen == NULL) 1216 || (initial_clientout != NULL && initial_clientoutlen != NULL), FALSE); 1217 1218 if (! mn_sasl_init(&err)) 1219 { 1220 mn_client_session_warning(session, _("unable to initialize the SASL library: %s"), err->message); 1221 g_error_free(err); 1222 return FALSE; 1223 } 1224 1225 /* make sure we do not leak the previous sasl_conn if any */ 1226 mn_client_session_sasl_dispose(session); 1227 1228 namelen = sizeof(name); 1229 if (getsockname(session->s, &name, &namelen) >= 0) 1230 local_ip_port = get_sasl_ip_port(&name); 1231 else 1232 mn_client_session_warning(session, _("unable to retrieve local address of socket: %s"), g_strerror(errno)); 1233 1234 namelen = sizeof(name); 1235 if (getpeername(session->s, &name, &namelen) >= 0) 1236 remote_ip_port = get_sasl_ip_port(&name); 1237 else 1238 mn_client_session_warning(session, _("unable to retrieve remote address of socket: %s"), g_strerror(errno)); 1239 1240 result = sasl_client_new(service, 1241 session->server, 1242 local_ip_port, 1243 remote_ip_port, 1244 sasl_callbacks, 1245 0, 1246 &session->sasl_conn); 1247 1248 g_free(local_ip_port); 1249 g_free(remote_ip_port); 1250 1251 if (result == SASL_OK) 1252 { 1253 sasl_security_properties_t security; 1254 sasl_interact_t *interact = NULL; 1255 GString *mechanisms_string; 1256 GSList *l; 1257 1258 security.min_ssf = 0; 1259 security.max_ssf = 256; 1260 security.maxbufsize = READ_BUFSIZE; 1261 /* only permit plaintext mechanisms if SSL is in use */ 1262 #if WITH_SSL 1263 if (session->ssl) 1264 security.security_flags = 0; 1265 else 1266 #endif /* WITH_SSL */ 1267 security.security_flags = SASL_SEC_NOPLAINTEXT; 1268 security.property_names = NULL; 1269 security.property_values = NULL; 1270 1271 if (sasl_setprop(session->sasl_conn, SASL_SEC_PROPS, &security) != SASL_OK) 1272 mn_client_session_warning(session, _("unable to set SASL security properties: %s"), sasl_errdetail(session->sasl_conn)); 1273 1274 mechanisms_string = g_string_new(NULL); 1275 if (forced_mechanism) 1276 g_string_append(mechanisms_string, forced_mechanism); 1277 else 1278 MN_LIST_FOREACH(l, mechanisms) 1279 { 1280 if (*mechanisms_string->str) 1281 g_string_append_c(mechanisms_string, ' '); 1282 g_string_append(mechanisms_string, l->data); 1283 } 1284 1285 do 1286 { 1287 result = sasl_client_start(session->sasl_conn, 1288 mechanisms_string->str, 1289 &interact, 1290 initial_clientout, 1291 initial_clientoutlen, 1292 used_mechanism); 1293 1294 if (result == SASL_INTERACT) 1295 { 1296 if (! fill_sasl_interact(session, interact, _("unable to start SASL authentication: SASL asked for something we did not know"))) 1297 break; 1298 } 1299 } 1300 while (result == SASL_INTERACT); 1301 1302 g_string_free(mechanisms_string, TRUE); 1303 1304 switch (result) 1305 { 1306 case SASL_OK: 1307 case SASL_CONTINUE: 1308 return TRUE; 1309 1310 case SASL_INTERACT: 1311 /* could not fill interaction, nop */ 1312 break; 1313 1314 default: 1315 mn_client_session_warning(session, _("unable to start SASL authentication: %s"), sasl_errdetail(session->sasl_conn)); 1316 } 1317 } 1318 else 1319 mn_client_session_warning(session, _("unable to create a SASL connection: %s"), sasl_errdetail(session->sasl_conn)); 1320 1321 return FALSE; 1322 } 1323 1324 /** 1325 * mn_client_session_sasl_authentication_step: 1326 * @session: a #MNClientSession 1327 * @input: the last server challenge received 1328 * 1329 * Continues a SASL authentication exchange successfully initiated 1330 * with mn_client_session_sasl_authentication_start(). 1331 * 1332 * Return value: the state to switch to 1333 **/ 1334 int 1335 mn_client_session_sasl_authentication_step (MNClientSession *session, 1336 const char *input) 1337 { 1338 g_return_val_if_fail(session != NULL, 0); 1339 g_return_val_if_fail(session->sasl_conn != NULL, 0); 1340 g_return_val_if_fail(input != NULL, 0); 1341 1342 { 1343 unsigned int inlen = strlen(input); 1344 char buf[inlen]; 1345 unsigned int outlen; 1346 int result; 1347 1348 result = sasl_decode64(input, inlen, buf, inlen, &outlen); 1349 if (result == SASL_OK) 1350 { 1351 sasl_interact_t *interact = NULL; 1352 const char *clientout; 1353 unsigned int clientoutlen; 1354 1355 do 1356 { 1357 result = sasl_client_step(session->sasl_conn, 1358 buf, 1359 outlen, 1360 &interact, 1361 &clientout, 1362 &clientoutlen); 1363 1364 if (result == SASL_INTERACT) 1365 { 1366 if (! fill_sasl_interact(session, interact, _("SASL asked for something we did not know, aborting SASL authentication"))) 1367 break; 1368 } 1369 } 1370 while (result == SASL_INTERACT); 1371 1372 switch (result) 1373 { 1374 case SASL_OK: 1375 case SASL_CONTINUE: 1376 return write_base64(session, clientout, clientoutlen); 1377 1378 case SASL_INTERACT: 1379 /* could not fill interaction, abort */ 1380 return mn_client_session_write(session, "*"); 1381 1382 default: 1383 mn_client_session_warning(session, _("%s, aborting SASL authentication"), sasl_errdetail(session->sasl_conn)); 1384 return mn_client_session_write(session, "*"); 1385 } 1386 } 1387 else /* compliance error */ 1388 return mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to decode Base64 input from server: %s"), sasl_errstring(result, NULL, NULL)); 1389 } 1390 } 1391 1392 /** 1393 * mn_client_session_sasl_authentication_done: 1394 * @session: a #MNClientSession 1395 * 1396 * Completes a successful SASL authentication exchange. Must only be 1397 * used if the server has terminated the exchange with a positive 1398 * response. 1399 * 1400 * Return value: %TRUE on success 1401 **/ 1402 gboolean 1403 mn_client_session_sasl_authentication_done (MNClientSession *session) 1404 { 1405 gconstpointer ptr; 1406 1407 g_return_val_if_fail(session != NULL, FALSE); 1408 g_return_val_if_fail(session->sasl_conn != NULL, 0); 1409 1410 if (sasl_getprop(session->sasl_conn, SASL_SSF, &ptr) == SASL_OK) 1411 { 1412 const sasl_ssf_t *ssf = ptr; 1413 1414 if (*ssf) 1415 { 1416 if (sasl_getprop(session->sasl_conn, SASL_MAXOUTBUF, &ptr) == SASL_OK) 1417 { 1418 const unsigned int *maxoutbuf = ptr; 1419 1420 session->sasl_ssf = *ssf; 1421 session->sasl_maxoutbuf = *maxoutbuf; 1422 1423 if (session->sasl_ssf) 1424 mn_client_session_notice(session, _("a SASL security layer of strength factor %i is now active"), session->sasl_ssf); 1425 } 1426 else 1427 { 1428 /* a security layer is active but we can't retrieve maxoutbuf -> fatal */ 1429 mn_client_session_set_error(session, MN_CLIENT_SESSION_ERROR_OTHER, _("unable to get SASL_MAXOUTBUF property: %s"), sasl_errdetail(session->sasl_conn)); 1430 return FALSE; 1431 } 1432 } 1433 } 1434 else 1435 mn_client_session_warning(session, _("warning: unable to get SASL_SSF property: %s"), sasl_errdetail(session->sasl_conn)); 1436 1437 return TRUE; 1438 } 1439 1440 /** 1441 * mn_client_session_sasl_dispose: 1442 * @session: a #MNClientSession 1443 * 1444 * Destroys the SASL connection of @session, or, if no SASL connection 1445 * is active, does nothing. 1446 * 1447 * Since the SASL connection is always destroyed before 1448 * mn_client_session_run() returns, omitting to call this function 1449 * will not leak the object away. However, in some situations (eg. if 1450 * SASL authentication fails but the session continues nevertheless) 1451 * it might be desirable to get rid of the object, in order to free 1452 * resources that would otherwise remain in use for the rest of the 1453 * session. 1454 **/ 1455 void 1456 mn_client_session_sasl_dispose (MNClientSession *session) 1457 { 1458 g_return_if_fail(session != NULL); 1459 1460 if (session->sasl_conn) 1461 { 1462 sasl_dispose(&session->sasl_conn); 1463 session->sasl_conn = NULL; 1464 } 1465 } 1466 1467 /** 1468 * mn_client_session_sasl_get_ssf: 1469 * @session: a #MNClientSession 1470 * 1471 * Gets the SASL security strength factor. Must not be used unless 1472 * mn_client_session_sasl_authentication_done() has returned %TRUE. 1473 * 1474 * Return value: 0 if no security layer is active, or an approximation 1475 * of the encryption key length otherwise 1476 **/ 1477 sasl_ssf_t 1478 mn_client_session_sasl_get_ssf (MNClientSession *session) 1479 { 1480 g_return_val_if_fail(session != NULL, 0); 1481 g_return_val_if_fail(session->sasl_conn != NULL, 0); 1482 1483 return session->sasl_ssf; 1484 } 1485 #endif /* WITH_SASL */ 1486 1487 /** 1488 * mn_client_session_notice: 1489 * @session: a #MNClientSession 1490 * @format: a printf() format string 1491 * @...: the arguments to the format string 1492 * 1493 * If the notice callback of @session is defined, calls it with the 1494 * given message as argument. Otherwise, does nothing. 1495 **/ 1496 void 1497 mn_client_session_notice (MNClientSession *session, 1498 const char *format, 1499 ...) 1500 { 1501 g_return_if_fail(session != NULL); 1502 g_return_if_fail(format != NULL); 1503 1504 if (session->callbacks->notice) 1505 { 1506 char *message; 1507 1508 MN_STRDUP_VPRINTF(message, format); 1509 session->callbacks->notice(session, message, session->private); 1510 g_free(message); 1511 } 1512 } 1513 1514 /** 1515 * mn_client_session_warning: 1516 * @session: a #MNClientSession 1517 * @format: a printf() format string 1518 * @...: the arguments to the format string 1519 * 1520 * If the warning callback of @session is defined, calls it with the 1521 * given message as argument. Otherwise, does nothing. 1522 **/ 1523 void 1524 mn_client_session_warning (MNClientSession *session, 1525 const char *format, 1526 ...) 1527 { 1528 g_return_if_fail(session != NULL); 1529 g_return_if_fail(format != NULL); 1530 1531 if (session->callbacks->warning) 1532 { 1533 char *message; 1534 1535 MN_STRDUP_VPRINTF(message, format); 1536 session->callbacks->warning(session, message, session->private); 1537 g_free(message); 1538 } 1539 } 1540 1541 /** 1542 * mn_client_session_set_error: 1543 * @session: a #MNClientSession 1544 * @code: a #MNClientSessionError code 1545 * @format: a printf() format string 1546 * @...: the arguments to the format string 1547 * 1548 * If @session has no error yet, sets the given error. Otherwise, does 1549 * nothing. 1550 * 1551 * Return value: %MN_CLIENT_SESSION_RESULT_DISCONNECT 1552 **/ 1553 int 1554 mn_client_session_set_error (MNClientSession *session, 1555 int code, 1556 const char *format, 1557 ...) 1558 { 1559 g_return_val_if_fail(session != NULL, 0); 1560 g_return_val_if_fail(format != NULL, 0); 1561 1562 if (! session->error) 1563 { 1564 char *message; 1565 1566 MN_STRDUP_VPRINTF(message, format); 1567 session->error = g_error_new_literal(MN_CLIENT_SESSION_ERROR, code, message); 1568 g_free(message); 1569 } 1570 1571 return MN_CLIENT_SESSION_RESULT_DISCONNECT; 1572 } 1573 1574 int 1575 mn_client_session_set_error_from_response (MNClientSession *session, 1576 int code, 1577 const char *response) 1578 { 1579 g_return_val_if_fail(session != NULL, 0); 1580 1581 return response 1582 ? mn_client_session_set_error(session, code, _("\"%s\""), response) 1583 : mn_client_session_set_error(session, code, _("unknown server error")); 1584 } 1585 1586 GQuark 1587 mn_client_session_error_quark (void) 1588 { 1589 return g_quark_from_static_string("mn-client-session-error"); 1590 }