Improve some sieve-related translations
[claws.git] / src / plugins / managesieve / managesieve.c
blob265307cbdb49ae2e1e97691c25dce9a78b0e423c
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2015 the Claws Mail Team
4 * Copyright (C) 2014-2015 Charles Lehner
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "config.h"
23 #include <glib.h>
24 #include <glib/gi18n.h>
25 #include <ctype.h>
26 #include <errno.h>
28 #include "claws.h"
29 #include "account.h"
30 #include "passwordstore.h"
31 #include "gtk/inputdialog.h"
32 #include "md5.h"
33 #include "utils.h"
34 #include "log.h"
35 #include "session.h"
36 #include "prefs_common.h"
38 #include "managesieve.h"
39 #include "sieve_editor.h"
40 #include "sieve_prefs.h"
42 GSList *sessions = NULL;
44 static void sieve_session_destroy(Session *session);
45 static gint sieve_pop_send_queue(SieveSession *session);
46 static void sieve_session_reset(SieveSession *session);
47 static void command_free(SieveCommand *cmd);
48 static void command_abort(SieveCommand *cmd);
49 static void command_cb(SieveCommand *cmd, gpointer result);
50 static gint sieve_session_recv_chunk(SieveSession *, guint len);
51 static void sieve_read_chunk(SieveSession *, gchar *data, guint len);
52 static gint sieve_read_chunk_done(SieveSession *session);
54 void sieve_sessions_close()
56 if (sessions) {
57 GSList *list = sessions;
58 sessions = NULL;
59 g_slist_free_full(list, (GDestroyNotify)session_destroy);
63 /* remove all command callbacks with a given data pointer */
64 void sieve_sessions_discard_callbacks(gpointer user_data)
66 GSList *item;
67 GSList *queue;
68 GSList *prev = NULL;
69 SieveSession *session;
70 SieveCommand *cmd;
72 for (item = sessions; item; item = item->next) {
73 session = (SieveSession *)item->data;
74 cmd = session->current_cmd;
75 /* abort current command handler */
76 if (cmd && cmd->data == user_data) {
77 command_abort(cmd);
78 session->current_cmd = NULL;
80 /* abort queued command handlers */
81 for (queue = session->send_queue; queue; queue = queue->next) {
82 cmd = (SieveCommand *)queue->data;
83 if (cmd && cmd->data == user_data) {
84 if (prev)
85 prev->next = queue->next;
86 else
87 session->send_queue = NULL;
88 command_abort(cmd);
89 g_slist_free_1(queue);
90 } else {
91 prev = queue;
97 static void command_cb(SieveCommand *cmd, gpointer result)
99 if (cmd)
100 cmd->cb(cmd->session, FALSE, result, cmd->data);
103 static void command_abort(SieveCommand *cmd)
105 cmd->cb(cmd->session, TRUE, NULL, cmd->data);
106 g_free(cmd->msg);
107 g_free(cmd);
110 static void command_free(SieveCommand *cmd)
112 g_free(cmd->msg);
113 g_free(cmd);
116 void sieve_session_handle_status(SieveSession *session,
117 sieve_session_error_cb_fn on_error,
118 sieve_session_connected_cb_fn on_connected,
119 gpointer data)
121 session->on_error = on_error;
122 session->on_connected = on_connected;
123 session->cb_data = data;
126 static void sieve_error(SieveSession *session, const gchar *msg)
128 if (session->on_error)
129 session->on_error(session, msg, session->cb_data);
132 static void sieve_connected(SieveSession *session, gboolean connected)
134 if (session->on_connected)
135 session->on_connected(session, connected, session->cb_data);
138 static gboolean sieve_read_chunk_cb(SockInfo *source,
139 GIOCondition condition, gpointer data)
141 SieveSession *sieve_session = SIEVE_SESSION(data);
142 Session *session = &sieve_session->session;
143 gint data_len;
144 gint ret;
146 cm_return_val_if_fail(condition == G_IO_IN, FALSE);
148 session_set_timeout(session, session->timeout_interval);
150 if (session->read_buf_len == 0) {
151 gint read_len = -1;
153 if (session->sock)
154 read_len = sock_read(session->sock,
155 session->read_buf,
156 SESSION_BUFFSIZE - 1);
158 if (read_len == -1 &&
159 session->state == SESSION_DISCONNECTED) {
160 g_warning("sock_read: session disconnected");
161 if (session->io_tag > 0) {
162 g_source_remove(session->io_tag);
163 session->io_tag = 0;
165 return FALSE;
168 if (read_len == 0) {
169 g_warning("sock_read: received EOF");
170 session->state = SESSION_EOF;
171 return FALSE;
174 if (read_len < 0) {
175 switch (errno) {
176 case EAGAIN:
177 return TRUE;
178 default:
179 g_warning("sock_read: %s",
180 g_strerror(errno));
181 session->state = SESSION_ERROR;
182 return FALSE;
186 session->read_buf_len = read_len;
189 data_len = MIN(session->read_buf_len,
190 sieve_session->octets_remaining);
191 sieve_session->octets_remaining -= data_len;
192 session->read_buf_len -= data_len;
193 session->read_buf_p[data_len] = '\0';
195 /* progress callback */
196 sieve_read_chunk(sieve_session, session->read_buf_p, data_len);
198 if (session->read_buf_len == 0) {
199 session->read_buf_p = session->read_buf;
200 } else {
201 session->read_buf_p += data_len;
204 /* incomplete read */
205 if (sieve_session->octets_remaining > 0)
206 return TRUE;
208 /* complete */
209 if (session->io_tag > 0) {
210 g_source_remove(session->io_tag);
211 session->io_tag = 0;
214 /* completion callback */
215 ret = sieve_read_chunk_done(sieve_session);
217 if (ret < 0)
218 session->state = SESSION_ERROR;
220 return FALSE;
223 static gboolean sieve_read_chunk_idle_cb(gpointer data)
225 Session *session = SESSION(data);
226 gboolean ret;
228 ret = sieve_read_chunk_cb(session->sock, G_IO_IN, session);
230 if (ret == TRUE)
231 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
232 sieve_read_chunk_cb, session);
234 return FALSE;
237 /* Get data of specified length.
238 * If needed elsewhere, this should be put in session.c */
239 static gint sieve_session_recv_chunk(SieveSession *sieve_session,
240 guint bytes)
242 Session *session = &sieve_session->session;
243 cm_return_val_if_fail(session->read_msg_buf->len == 0, -1);
245 session->state = SESSION_RECV;
246 sieve_session->octets_remaining = bytes;
248 if (session->read_buf_len > 0)
249 g_idle_add(sieve_read_chunk_idle_cb, session);
250 else
251 session->io_tag = sock_add_watch(session->sock, G_IO_IN,
252 sieve_read_chunk_cb, session);
253 return 0;
257 static gint sieve_auth_recv(SieveSession *session, const gchar *msg)
259 gchar buf[MESSAGEBUFSIZE], *tmp;
261 switch (session->auth_type) {
262 case SIEVEAUTH_LOGIN:
263 session->state = SIEVE_AUTH_LOGIN_USER;
265 if (strstr(msg, "VXNlcm5hbWU6")) {
266 tmp = g_base64_encode(session->user, strlen(session->user));
267 g_snprintf(buf, sizeof(buf), "\"%s\"", tmp);
269 if (session_send_msg(SESSION(session), buf) < 0) {
270 g_free(tmp);
271 return SE_ERROR;
273 g_free(tmp);
274 log_print(LOG_PROTOCOL, "Sieve> [USERID]\n");
275 } else {
276 /* Server rejects AUTH */
277 if (session_send_msg(SESSION(session), "\"*\"") < 0)
278 return SE_ERROR;
279 log_print(LOG_PROTOCOL, "Sieve> *\n");
281 break;
282 case SIEVEAUTH_CRAM_MD5:
283 session->state = SIEVE_AUTH_CRAM_MD5;
285 if (msg[0] == '"') {
286 gchar *response;
287 gchar *response64;
288 gchar *challenge, *tmp;
289 gsize challengelen;
290 guchar hexdigest[33];
292 tmp = g_base64_decode(msg + 1, &challengelen);
293 challenge = g_strndup(tmp, challengelen);
294 g_free(tmp);
295 log_print(LOG_PROTOCOL, "Sieve< [Decoded: %s]\n", challenge);
297 g_snprintf(buf, sizeof(buf), "%s", session->pass);
298 md5_hex_hmac(hexdigest, challenge, challengelen,
299 buf, strlen(session->pass));
300 g_free(challenge);
302 response = g_strdup_printf
303 ("%s %s", session->user, hexdigest);
304 log_print(LOG_PROTOCOL, "Sieve> [Encoded: %s]\n", response);
306 response64 = g_base64_encode(response, strlen(response));
307 g_free(response);
309 response = g_strdup_printf("\"%s\"", response64);
310 g_free(response64);
312 if (session_send_msg(SESSION(session), response) < 0) {
313 g_free(response);
314 return SE_ERROR;
316 log_print(LOG_PROTOCOL, "Sieve> %s\n", response);
317 g_free(response);
318 } else {
319 /* Server rejects AUTH */
320 if (session_send_msg(SESSION(session), "\"*\"") < 0)
321 return SE_ERROR;
322 log_print(LOG_PROTOCOL, "Sieve> *\n");
324 break;
325 default:
326 /* stop sieve_auth when no correct authtype */
327 if (session_send_msg(SESSION(session), "*") < 0)
328 return SE_ERROR;
329 log_print(LOG_PROTOCOL, "Sieve> *\n");
330 break;
333 return SE_OK;
336 static gint sieve_auth_login_user_recv(SieveSession *session, const gchar *msg)
338 gchar *tmp, *tmp2;
340 session->state = SIEVE_AUTH_LOGIN_PASS;
342 if (strstr(msg, "UGFzc3dvcmQ6")) {
343 tmp2 = g_base64_encode(session->pass, strlen(session->pass));
344 tmp = g_strdup_printf("\"%s\"", tmp2);
345 g_free(tmp2);
346 } else {
347 /* Server rejects AUTH */
348 tmp = g_strdup("\"*\"");
351 if (session_send_msg(SESSION(session), tmp) < 0) {
352 g_free(tmp);
353 return SE_ERROR;
355 g_free(tmp);
357 log_print(LOG_PROTOCOL, "Sieve> [PASSWORD]\n");
359 return SE_OK;
363 static gint sieve_auth_cram_md5(SieveSession *session)
365 session->state = SIEVE_AUTH;
366 session->auth_type = SIEVEAUTH_CRAM_MD5;
368 if (session_send_msg(SESSION(session), "Authenticate \"CRAM-MD5\"") < 0)
369 return SE_ERROR;
370 log_print(LOG_PROTOCOL, "Sieve> Authenticate CRAM-MD5\n");
372 return SE_OK;
375 static gint sieve_auth_plain(SieveSession *session)
377 gchar buf[MESSAGEBUFSIZE], *b64buf, *out;
378 gint len;
380 session->state = SIEVE_AUTH_PLAIN;
381 session->auth_type = SIEVEAUTH_PLAIN;
383 memset(buf, 0, sizeof buf);
385 /* "\0user\0password" */
386 len = sprintf(buf, "%c%s%c%s", '\0', session->user, '\0', session->pass);
387 b64buf = g_base64_encode(buf, len);
388 out = g_strconcat("Authenticate \"PLAIN\" \"", b64buf, "\"", NULL);
389 g_free(b64buf);
391 if (session_send_msg(SESSION(session), out) < 0) {
392 g_free(out);
393 return SE_ERROR;
396 g_free(out);
398 log_print(LOG_PROTOCOL, "Sieve> [Authenticate PLAIN]\n");
400 return SE_OK;
403 static gint sieve_auth_login(SieveSession *session)
405 session->state = SIEVE_AUTH;
406 session->auth_type = SIEVEAUTH_LOGIN;
408 if (session_send_msg(SESSION(session), "Authenticate \"LOGIN\"") < 0)
409 return SE_ERROR;
410 log_print(LOG_PROTOCOL, "Sieve> Authenticate LOGIN\n");
412 return SE_OK;
415 static gint sieve_auth(SieveSession *session)
417 SieveAuthType forced_auth_type = session->forced_auth_type;
419 if (!session->use_auth) {
420 session->state = SIEVE_READY;
421 sieve_connected(session, TRUE);
422 return SE_OK;
425 session->state = SIEVE_AUTH;
426 sieve_error(session, _("Authenticating..."));
428 if ((forced_auth_type == SIEVEAUTH_CRAM_MD5 || forced_auth_type == 0) &&
429 (session->avail_auth_type & SIEVEAUTH_CRAM_MD5) != 0)
430 return sieve_auth_cram_md5(session);
431 else if ((forced_auth_type == SIEVEAUTH_LOGIN || forced_auth_type == 0) &&
432 (session->avail_auth_type & SIEVEAUTH_LOGIN) != 0)
433 return sieve_auth_login(session);
434 else if ((forced_auth_type == SIEVEAUTH_PLAIN || forced_auth_type == 0) &&
435 (session->avail_auth_type & SIEVEAUTH_PLAIN) != 0)
436 return sieve_auth_plain(session);
437 else if (forced_auth_type == 0) {
438 log_warning(LOG_PROTOCOL, _("No Sieve auth method available\n"));
439 session->state = SIEVE_RETRY_AUTH;
440 return SE_AUTHFAIL;
441 } else {
442 log_warning(LOG_PROTOCOL, _("Selected Sieve auth method not available\n"));
443 session->state = SIEVE_RETRY_AUTH;
444 return SE_AUTHFAIL;
447 return SE_OK;
450 static void sieve_session_putscript_cb(SieveSession *session, SieveResult *result)
452 /* Remove script name from the beginning the response,
453 * which is added by Dovecot/Pigeonhole */
454 gchar *start, *desc = result->description;
455 gchar *end = NULL;
456 if (!desc) {
457 /* callback just for the status */
458 command_cb(session->current_cmd, result);
460 while (desc && desc[0]) {
461 if ((end = strchr(desc, '\r')) ||
462 (end = strchr(desc, '\n')))
463 while (*end == '\n' || *end == '\r')
464 *end++ = '\0';
465 if (g_str_has_prefix(desc, "NULL_") && (start = strchr(desc+5, ':'))) {
466 desc = start+1;
467 while (*desc == ' ')
468 desc++;
469 /* TODO: match against known script name, in case it contains
470 * weird text like ": line " */
471 } else if ((start = strstr(desc, ": line ")) ||
472 (start = strstr(desc, ": error"))) {
473 desc = start+2;
475 result->description = desc;
476 command_cb(session->current_cmd, result);
477 desc = end;
481 static inline gboolean response_is_ok(const char *msg)
483 return !strncmp(msg, "OK", 2) && (!msg[2] || msg[2] == ' ');
486 static inline gboolean response_is_no(const char *msg)
488 return !strncmp(msg, "NO", 2) && (!msg[2] || msg[2] == ' ');
491 static inline gboolean response_is_bye(const char *msg)
493 return !strncmp(msg, "BYE", 3) && (!msg[3] || msg[3] == ' ');
496 static void sieve_got_capability(SieveSession *session, gchar *cap_name,
497 gchar *cap_value)
499 if (strcmp(cap_name, "SASL") == 0) {
500 SieveAuthType auth_type = 0;
501 gchar *auth, *end;
502 for (auth = cap_value; auth && auth[0]; auth = end) {
503 if ((end = strchr(auth, ' ')))
504 *end++ = '\0';
505 if (strcmp(auth, "PLAIN") == 0) {
506 auth_type |= SIEVEAUTH_PLAIN;
507 } else if (strcmp(auth, "CRAM-MD5") == 0) {
508 auth_type |= SIEVEAUTH_CRAM_MD5;
509 } else if (strcmp(auth, "LOGIN") == 0) {
510 auth_type |= SIEVEAUTH_LOGIN;
513 session->avail_auth_type = auth_type;
515 } else if (strcmp(cap_name, "STARTTLS") == 0) {
516 session->capability.starttls = TRUE;
520 static void log_send(SieveSession *session, SieveCommand *cmd)
522 gchar *end, *msg = cmd->msg;
523 if (cmd->next_state == SIEVE_PUTSCRIPT && (end = strchr(msg, '\n'))) {
524 /* Don't log the script data */
525 msg = g_strndup(msg, end - msg);
526 log_print(LOG_PROTOCOL, "Sieve> %s\n", msg);
527 g_free(msg);
528 msg = "[Data]";
530 log_print(LOG_PROTOCOL, "Sieve> %s\n", msg);
533 static gint sieve_pop_send_queue(SieveSession *session)
535 SieveCommand *cmd;
536 GSList *send_queue = session->send_queue;
538 if (session->current_cmd) {
539 command_free(session->current_cmd);
540 session->current_cmd = NULL;
543 if (!send_queue)
544 return SE_OK;
546 cmd = (SieveCommand *)send_queue->data;
547 session->send_queue = g_slist_next(send_queue);
548 g_slist_free_1(send_queue);
550 log_send(session, cmd);
551 session->state = cmd->next_state;
552 session->current_cmd = cmd;
553 if (session_send_msg(SESSION(session), cmd->msg) < 0)
554 return SE_ERROR;
556 return SE_OK;
559 static void parse_split(gchar *line, gchar **first_word, gchar **second_word)
561 gchar *first = line;
562 gchar *second;
563 gchar *end;
565 /* get first */
566 if (line[0] == '"' && ((second = strchr(line + 1, '"')))) {
567 *second++ = '\0';
568 first++;
569 if (second[0] == ' ')
570 second++;
571 } else if ((second = strchr(line, ' '))) {
572 *second++ = '\0';
575 /* unquote second */
576 if (second && second[0] == '"' &&
577 ((end = strchr(second + 1, '"')))) {
578 second++;
579 *end = '\0';
582 *first_word = first;
583 *second_word = second;
586 static void unquote_inplace(gchar *str)
588 gchar *src, *dest;
589 if (*str != '"')
590 return;
591 for (src = str+1, dest = str; src && *src && *src != '"'; src++) {
592 if (*src == '\\') {
593 src++;
595 *dest++ = *src;
597 *dest = '\0';
600 static void parse_response(gchar *msg, SieveResult *result)
602 gchar *end;
604 cm_return_if_fail(msg != NULL);
606 /* response status */
607 if (isalpha(msg[0])) {
608 end = strchr(msg, ' ');
609 if (end) {
610 *end++ = '\0';
611 while (*end == ' ')
612 end++;
614 result->success = strcmp(msg, "OK") == 0;
615 result->has_status = TRUE;
616 msg = end;
617 } else {
618 result->has_status = FALSE;
621 /* response code */
622 if (msg && msg[0] == '(' && (end = strchr(msg, ')'))) {
623 msg++;
624 *end++ = '\0';
625 result->code =
626 strcmp(msg, "WARNINGS") == 0 ? SIEVE_CODE_WARNINGS :
627 strcmp(msg, "TRYLATER") == 0 ? SIEVE_CODE_TRYLATER :
628 SIEVE_CODE_UNKNOWN;
629 while (*end == ' ')
630 end++;
631 msg = end;
632 } else {
633 result->code = SIEVE_CODE_NONE;
636 /* s2c octets */
637 if (msg && msg[0] == '{' && (end = strchr(msg, '}'))) {
638 msg++;
639 *end++ = '\0';
640 if (msg[0] == '0' && msg+1 == end) {
641 result->has_octets = TRUE;
642 result->octets = 0;
643 } else {
644 result->has_octets =
645 (result->octets = g_ascii_strtoll(msg, NULL, 10)) != 0;
647 while (*end == ' ')
648 end++;
649 msg = end;
650 } else {
651 result->has_octets = FALSE;
652 result->octets = 0;
655 /* text */
656 if (msg && *msg) {
657 unquote_inplace(msg);
658 result->description = msg;
659 } else {
660 result->description = NULL;
664 static gint sieve_session_recv_msg(Session *session, const gchar *msg)
666 SieveSession *sieve_session = SIEVE_SESSION(session);
667 SieveResult result;
668 gint ret = SE_OK;
670 log_print(LOG_PROTOCOL, "Sieve< %s\n", msg);
671 if (response_is_bye(msg)) {
672 gchar *status;
673 parse_response((gchar *)msg, &result);
674 if (!result.description)
675 status = g_strdup(_("Disconnected"));
676 else if (g_str_has_prefix(result.description, "Disconnected"))
677 status = g_strdup(result.description);
678 else
679 status = g_strdup_printf(_("Disconnected: %s"), result.description);
680 sieve_session->error = SE_ERROR;
681 sieve_error(sieve_session, status);
682 sieve_session->state = SIEVE_DISCONNECTED;
683 g_free(status);
684 return -1;
687 switch (sieve_session->state) {
688 case SIEVE_CAPABILITIES:
689 if (response_is_ok(msg)) {
690 /* capabilities list done */
692 #ifdef USE_GNUTLS
693 if (sieve_session->tls_init_done == FALSE &&
694 sieve_session->config->tls_type != SIEVE_TLS_NO) {
695 if (sieve_session->capability.starttls) {
696 if (session_send_msg(session, "STARTTLS") < 0)
697 sieve_session->state = SIEVE_ERROR;
698 else
699 sieve_session->state = SIEVE_STARTTLS;
700 } else if (sieve_session->config->tls_type == SIEVE_TLS_YES) {
701 log_warning(LOG_PROTOCOL, "Sieve: does not support STARTTLS\n");
702 sieve_session->state = SIEVE_ERROR;
703 } else {
704 log_warning(LOG_PROTOCOL, "Sieve: continuing unencrypted\n");
705 sieve_session->state = SIEVE_READY;
707 break;
709 #endif
710 /* authenticate after getting capabilities */
711 if (!sieve_session->authenticated) {
712 ret = sieve_auth(sieve_session);
713 } else {
714 sieve_session->state = SIEVE_READY;
715 sieve_connected(sieve_session, TRUE);
717 } else {
718 /* got a capability */
719 gchar *cap_name, *cap_value;
720 parse_split((gchar *)msg, &cap_name, &cap_value);
721 sieve_got_capability(sieve_session, cap_name, cap_value);
723 break;
724 case SIEVE_READY:
725 if (!msg[0])
726 break;
727 log_warning(LOG_PROTOCOL,
728 _("unhandled message on Sieve session: %s\n"), msg);
729 break;
730 case SIEVE_STARTTLS:
731 #ifdef USE_GNUTLS
732 if (session_start_tls(session) < 0) {
733 sieve_session->state = SIEVE_ERROR;
734 sieve_session->error = SE_ERROR;
735 sieve_error(sieve_session, _("STARTTLS failed"));
736 return -1;
738 sieve_session->tls_init_done = TRUE;
739 sieve_session->state = SIEVE_CAPABILITIES;
740 #endif
741 break;
742 case SIEVE_AUTH:
743 ret = sieve_auth_recv(sieve_session, msg);
744 break;
745 case SIEVE_AUTH_LOGIN_USER:
746 ret = sieve_auth_login_user_recv(sieve_session, msg);
747 break;
748 case SIEVE_AUTH_PLAIN:
749 case SIEVE_AUTH_LOGIN_PASS:
750 case SIEVE_AUTH_CRAM_MD5:
751 if (response_is_no(msg)) {
752 log_print(LOG_PROTOCOL, "Sieve auth failed\n");
753 sieve_session->state = SIEVE_RETRY_AUTH;
754 ret = SE_AUTHFAIL;
755 } else if (response_is_ok(msg)) {
756 log_print(LOG_PROTOCOL, "Sieve auth completed\n");
757 sieve_error(sieve_session, "");
758 sieve_session->authenticated = TRUE;
759 sieve_session->state = SIEVE_READY;
760 sieve_connected(sieve_session, TRUE);
762 break;
763 case SIEVE_NOOP:
764 if (!response_is_ok(msg)) {
765 sieve_session->state = SIEVE_ERROR;
767 sieve_session->state = SIEVE_READY;
768 break;
769 case SIEVE_LISTSCRIPTS:
770 if (response_is_no(msg)) {
771 /* got an error. probably not authenticated. */
772 command_cb(sieve_session->current_cmd, NULL);
773 sieve_session->state = SIEVE_READY;
774 } else if (response_is_ok(msg)) {
775 /* end of list */
776 sieve_session->state = SIEVE_READY;
777 sieve_session->error = SE_OK;
778 command_cb(sieve_session->current_cmd,
779 (gpointer)&(SieveScript){0});
780 } else {
781 /* got a script name */
782 SieveScript script;
783 gchar *script_status;
785 parse_split((gchar *)msg, &script.name, &script_status);
786 script.active = (script_status &&
787 strcasecmp(script_status, "active") == 0);
789 command_cb(sieve_session->current_cmd,
790 (gpointer)&script);
792 break;
793 case SIEVE_RENAMESCRIPT:
794 if (response_is_no(msg)) {
795 /* error */
796 command_cb(sieve_session->current_cmd, NULL);
797 } else if (response_is_ok(msg)) {
798 command_cb(sieve_session->current_cmd, (void*)TRUE);
799 } else {
800 log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
802 sieve_session->state = SIEVE_READY;
803 break;
804 case SIEVE_SETACTIVE:
805 parse_response((gchar *)msg, &result);
806 if (result.success) {
807 /* clear status possibly set when setting another
808 * script active. TODO: give textual feedback */
809 sieve_error(sieve_session, "");
811 command_cb(sieve_session->current_cmd, NULL);
812 } else if (result.description) {
813 command_cb(sieve_session->current_cmd,
814 result.description);
815 } else {
816 log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
818 if (result.has_octets) {
819 return sieve_session_recv_chunk(sieve_session,
820 result.octets);
821 } else {
822 sieve_session->state = SIEVE_READY;
824 break;
825 case SIEVE_GETSCRIPT:
826 if (response_is_no(msg)) {
827 command_cb(sieve_session->current_cmd, (void *)-1);
828 sieve_session->state = SIEVE_READY;
829 } else {
830 parse_response((gchar *)msg, &result);
831 sieve_session->state = SIEVE_GETSCRIPT_DATA;
832 return sieve_session_recv_chunk(sieve_session,
833 result.octets);
835 break;
836 case SIEVE_GETSCRIPT_DATA:
837 if (!msg[0])
838 break;
839 sieve_session->state = SIEVE_READY;
840 if (response_is_ok(msg)) {
841 command_cb(sieve_session->current_cmd, NULL);
842 } else if (msg[0]) {
843 log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
845 break;
846 case SIEVE_PUTSCRIPT:
847 if (!msg[0])
848 break;
849 parse_response((gchar *)msg, &result);
850 sieve_session_putscript_cb(sieve_session, &result);
851 if (result.has_octets) {
852 return sieve_session_recv_chunk(sieve_session,
853 result.octets);
854 } else {
855 sieve_session->state = SIEVE_READY;
857 break;
858 case SIEVE_DELETESCRIPT:
859 parse_response((gchar *)msg, &result);
860 if (!result.success) {
861 command_cb(sieve_session->current_cmd,
862 result.description);
863 } else {
864 command_cb(sieve_session->current_cmd, NULL);
866 sieve_session->state = SIEVE_READY;
867 break;
868 case SIEVE_ERROR:
869 log_warning(LOG_PROTOCOL, _("error occurred on Sieve session. data: %s\n"), msg);
870 sieve_session->error = SE_ERROR;
871 break;
872 case SIEVE_RETRY_AUTH:
873 log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %s\n"),
874 msg);
875 ret = sieve_auth(sieve_session);
876 break;
877 default:
878 log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %d\n"),
879 sieve_session->state);
880 sieve_session->error = SE_ERROR;
881 return -1;
884 if (ret == SE_OK && sieve_session->state == SIEVE_READY)
885 ret = sieve_pop_send_queue(sieve_session);
887 if (ret == SE_OK) {
888 return session_recv_msg(session);
889 } else if (ret == SE_AUTHFAIL) {
890 sieve_error(sieve_session, _("Auth failed"));
891 sieve_session->state = SIEVE_ERROR;
892 sieve_session->error = SE_ERROR;
895 return 0;
898 static gint sieve_recv_message(Session *session, const gchar *msg,
899 gpointer user_data)
901 return 0;
904 static void sieve_read_chunk(SieveSession *session, gchar *data, guint len)
906 log_print(LOG_PROTOCOL, "Sieve< [%u bytes]\n", len);
908 switch (session->state) {
909 case SIEVE_GETSCRIPT_DATA:
910 command_cb(session->current_cmd, (gchar *)data);
911 break;
912 case SIEVE_SETACTIVE:
913 /* Dovecot shows a script's warnings when making it active */
914 /* TODO: append message in case it is very long*/
915 strretchomp(data);
916 sieve_error(session, data);
917 break;
918 case SIEVE_PUTSCRIPT: {
919 SieveResult result = {.description = (gchar *)data};
920 sieve_session_putscript_cb(session, &result);
921 break;
923 default:
924 log_warning(LOG_PROTOCOL,
925 _("error occurred on SIEVE session\n"));
929 static gint sieve_read_chunk_done(SieveSession *session)
931 gint ret = SE_OK;
933 switch (session->state) {
934 case SIEVE_GETSCRIPT_DATA:
935 /* wait for ending "OK" response */
936 break;
937 case SIEVE_SETACTIVE:
938 case SIEVE_PUTSCRIPT:
939 session->state = SIEVE_READY;
940 break;
941 default:
942 log_warning(LOG_PROTOCOL,
943 _("error occurred on SIEVE session\n"));
946 if (ret == SE_OK && session->state == SIEVE_READY)
947 ret = sieve_pop_send_queue(session);
949 if (ret == SE_OK)
950 return session_recv_msg(SESSION(session));
952 return 0;
955 static gint sieve_cmd_noop(SieveSession *session)
957 log_print(LOG_PROTOCOL, "Sieve> NOOP\n");
958 session->state = SIEVE_NOOP;
959 if (session_send_msg(SESSION(session), "NOOP") < 0) {
960 session->state = SIEVE_ERROR;
961 session->error = SE_ERROR;
962 return 1;
964 return 0;
967 static gboolean sieve_ping(gpointer data)
969 Session *session = SESSION(data);
970 SieveSession *sieve_session = SIEVE_SESSION(session);
972 if (sieve_session->state == SIEVE_ERROR || session->state == SESSION_ERROR)
973 return FALSE;
974 if (sieve_session->state != SIEVE_READY)
975 return TRUE;
977 return sieve_cmd_noop(sieve_session) == 0;
980 static void sieve_session_destroy(Session *session)
982 SieveSession *sieve_session = SIEVE_SESSION(session);
983 g_free(sieve_session->pass);
984 if (sieve_session->current_cmd)
985 command_abort(sieve_session->current_cmd);
986 sessions = g_slist_remove(sessions, (gconstpointer)session);
987 g_slist_free_full(sieve_session->send_queue,
988 (GDestroyNotify)command_abort);
989 if (sieve_session->config)
990 sieve_prefs_account_free_config(sieve_session->config);
993 static void sieve_connect_finished(Session *session, gboolean success)
995 if (!success) {
996 sieve_connected(SIEVE_SESSION(session), FALSE);
1000 static gint sieve_session_connect(SieveSession *session)
1002 PrefsAccount *ac = session->account;
1003 ProxyInfo *proxy_info = NULL;
1005 session->state = SIEVE_CAPABILITIES;
1006 session->authenticated = FALSE;
1007 #ifdef USE_GNUTLS
1008 session->tls_init_done = FALSE;
1009 #endif
1011 if (ac->use_proxy) {
1012 if (ac->use_default_proxy) {
1013 proxy_info = (ProxyInfo *)&(prefs_common_get_prefs()->proxy_info);
1014 if (proxy_info->use_proxy_auth)
1015 proxy_info->proxy_pass = passwd_store_get(PWS_CORE, PWS_CORE_PROXY,
1016 PWS_CORE_PROXY_PASS);
1017 } else {
1018 proxy_info = (ProxyInfo *)&(ac->proxy_info);
1019 if (proxy_info->use_proxy_auth)
1020 proxy_info->proxy_pass = passwd_store_get_account(ac->account_id,
1021 PWS_ACCOUNT_PROXY_PASS);
1024 SESSION(session)->proxy_info = proxy_info;
1026 return session_connect(SESSION(session), session->host,
1027 session->port);
1030 static SieveSession *sieve_session_new(PrefsAccount *account)
1032 SieveSession *session;
1033 session = g_new0(SieveSession, 1);
1034 session_init(SESSION(session), account, FALSE);
1036 session->account = account;
1038 SESSION(session)->recv_msg = sieve_session_recv_msg;
1039 SESSION(session)->destroy = sieve_session_destroy;
1040 SESSION(session)->connect_finished = sieve_connect_finished;
1041 session_set_recv_message_notify(SESSION(session), sieve_recv_message, NULL);
1043 session->config = NULL;
1044 sieve_session_reset(session);
1045 return session;
1048 static void sieve_session_reset(SieveSession *session)
1050 PrefsAccount *account = session->account;
1051 SieveAccountConfig *config = sieve_prefs_account_get_config(account);
1052 gboolean reuse_auth = (config->auth == SIEVEAUTH_REUSE);
1054 g_slist_free_full(session->send_queue, (GDestroyNotify)command_abort);
1056 session_disconnect(SESSION(session));
1058 SESSION(session)->ssl_cert_auto_accept = account->ssl_certs_auto_accept;
1059 SESSION(session)->nonblocking = account->use_nonblocking_ssl;
1060 session->authenticated = FALSE;
1061 session->current_cmd = NULL;
1062 session->send_queue = NULL;
1063 session->state = SIEVE_CAPABILITIES;
1064 #ifdef USE_GNUTLS
1065 session->tls_init_done = FALSE;
1066 SESSION(session)->use_tls_sni = account->use_tls_sni;
1067 #endif
1068 session->avail_auth_type = 0;
1069 session->auth_type = 0;
1070 if (session->config)
1071 sieve_prefs_account_free_config(session->config);
1072 session->config = config;
1073 session->host = config->use_host ? config->host : account->recv_server;
1074 session->port = config->use_port ? config->port : SIEVE_PORT;
1075 session->user = reuse_auth ? account->userid : session->config->userid;
1076 session->forced_auth_type = config->auth_type;
1077 session_register_ping(SESSION(session), sieve_ping);
1079 if (session->pass)
1080 g_free(session->pass);
1081 if (config->auth == SIEVEAUTH_NONE) {
1082 session->pass = NULL;
1083 } else if (reuse_auth && (session->pass = passwd_store_get_account(
1084 account->account_id, PWS_ACCOUNT_RECV))) {
1085 } else if ((session->pass = passwd_store_get_account(
1086 account->account_id, "sieve"))) {
1087 } else if (password_get(session->user, session->host, "sieve",
1088 session->port, &session->pass)) {
1089 } else {
1090 session->pass = input_dialog_query_password_keep(session->host,
1091 session->user, &(session->pass));
1093 if (!session->pass) {
1094 session->pass = g_strdup("");
1095 session->use_auth = FALSE;
1096 } else {
1097 session->use_auth = TRUE;
1100 #ifdef USE_GNUTLS
1101 SESSION(session)->ssl_type =
1102 (config->tls_type == SIEVE_TLS_NO) ? SSL_NONE : SSL_STARTTLS;
1103 #endif
1106 /* When an account config is changed, reset associated sessions. */
1107 void sieve_account_prefs_updated(PrefsAccount *account)
1109 GSList *item;
1110 SieveSession *session;
1112 for (item = sessions; item; item = item->next) {
1113 session = (SieveSession *)item->data;
1114 if (session->account == account) {
1115 log_print(LOG_PROTOCOL, "Sieve: resetting session\n");
1116 sieve_session_reset(session);
1121 SieveSession *sieve_session_get_for_account(PrefsAccount *account)
1123 SieveSession *session;
1124 GSList *item;
1126 /* find existing */
1127 for (item = sessions; item; item = item->next) {
1128 session = (SieveSession *)item->data;
1129 if (session->account == account) {
1130 return session;
1134 /* create new */
1135 session = sieve_session_new(account);
1136 sessions = g_slist_prepend(sessions, session);
1138 return session;
1141 static void sieve_queue_send(SieveSession *session, SieveState next_state,
1142 gchar *msg, sieve_session_data_cb_fn cb, gpointer data)
1144 gboolean queue = FALSE;
1145 SieveCommand *cmd = g_new0(SieveCommand, 1);
1146 cmd->session = session;
1147 cmd->next_state = next_state;
1148 cmd->msg = msg;
1149 cmd->data = data;
1150 cmd->cb = cb;
1152 if (!session_is_connected(SESSION(session))) {
1153 log_print(LOG_PROTOCOL, "Sieve: connecting to %s:%hu\n",
1154 session->host, session->port);
1155 if (sieve_session_connect(session) < 0) {
1156 sieve_connect_finished(SESSION(session), FALSE);
1158 queue = TRUE;
1159 } else if (session->state == SIEVE_RETRY_AUTH) {
1160 log_print(LOG_PROTOCOL, _("Sieve: retrying auth\n"));
1161 if (sieve_auth(session) == SE_AUTHFAIL)
1162 sieve_error(session, _("Auth method not available"));
1163 queue = TRUE;
1164 } else if (session->state != SIEVE_READY) {
1165 log_print(LOG_PROTOCOL, "Sieve: in state %d\n", session->state);
1166 queue = TRUE;
1169 if (queue) {
1170 session->send_queue = g_slist_prepend(session->send_queue, cmd);
1171 } else {
1172 if (session->current_cmd)
1173 command_free(session->current_cmd);
1174 session->current_cmd = cmd;
1175 session->state = next_state;
1176 log_send(session, cmd);
1177 if (session_send_msg(SESSION(session), cmd->msg) < 0) {
1178 log_warning(LOG_PROTOCOL,
1179 _("sending error on Sieve session: %s\n"), cmd->msg);
1184 void sieve_session_list_scripts(SieveSession *session,
1185 sieve_session_data_cb_fn cb, gpointer data)
1187 gchar *msg = g_strdup("LISTSCRIPTS");
1188 sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
1191 void sieve_session_set_active_script(SieveSession *session,
1192 const gchar *filter_name,
1193 sieve_session_data_cb_fn cb, gpointer data)
1195 gchar *msg = g_strdup_printf("SETACTIVE \"%s\"",
1196 filter_name ? filter_name : "");
1197 if (!msg) {
1198 cb(session, FALSE, (void*)FALSE, data);
1199 return;
1202 sieve_queue_send(session, SIEVE_SETACTIVE, msg, cb, data);
1205 void sieve_session_rename_script(SieveSession *session,
1206 const gchar *name_old, const char *name_new,
1207 sieve_session_data_cb_fn cb, gpointer data)
1209 gchar *msg = g_strdup_printf("RENAMESCRIPT \"%s\" \"%s\"",
1210 name_old, name_new);
1212 sieve_queue_send(session, SIEVE_RENAMESCRIPT, msg, cb, data);
1215 void sieve_session_get_script(SieveSession *session, const gchar *filter_name,
1216 sieve_session_data_cb_fn cb, gpointer data)
1218 gchar *msg = g_strdup_printf("GETSCRIPT \"%s\"",
1219 filter_name);
1221 sieve_queue_send(session, SIEVE_GETSCRIPT, msg, cb, data);
1224 void sieve_session_put_script(SieveSession *session, const gchar *filter_name,
1225 gint len, const gchar *script_contents,
1226 sieve_session_data_cb_fn cb, gpointer data)
1228 /* TODO: refactor so don't have to copy the whole script here */
1229 gchar *msg = g_strdup_printf("PUTSCRIPT \"%s\" {%u+}%s%s",
1230 filter_name, len, len > 0 ? "\r\n" : "",
1231 script_contents);
1233 sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
1236 void sieve_session_check_script(SieveSession *session,
1237 gint len, const gchar *script_contents,
1238 sieve_session_data_cb_fn cb, gpointer data)
1240 gchar *msg = g_strdup_printf("CHECKSCRIPT {%u+}%s%s",
1241 len, len > 0 ? "\r\n" : "", script_contents);
1243 sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
1246 void sieve_session_delete_script(SieveSession *session,
1247 const gchar *filter_name,
1248 sieve_session_data_cb_fn cb, gpointer data)
1250 gchar *msg = g_strdup_printf("DELETESCRIPT \"%s\"",
1251 filter_name);
1253 sieve_queue_send(session, SIEVE_DELETESCRIPT, msg, cb, data);