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-imap-mailbox.gob (50589B) - raw

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