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 }