mail-notification

Fork of Jean-Yves Lefort's mail-notification, a tray icon to notify of new mail
git clone https://code.djc.id.au/git/mail-notification/

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 }