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 }