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-pop3-mailbox.gob (32909B) - raw

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