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 }