9170c05df27323460eb8b2b93bdea16e7e2fefd8
[claws.git] / src / plugins / managesieve / managesieve.c
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
5  *
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.
10  *
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.
15  *
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/>.
18  * 
19  */
20
21 #include <glib.h>
22 #include <glib/gi18n.h>
23 #include <ctype.h>
24
25 #include "claws.h"
26 #include "account.h"
27 #include "gtk/inputdialog.h"
28 #include "md5.h"
29 #include "utils.h"
30 #include "log.h"
31 #include "session.h"
32
33 #include "managesieve.h"
34 #include "sieve_editor.h"
35 #include "sieve_prefs.h"
36
37 GSList *sessions = NULL;
38
39 static void sieve_session_destroy(Session *session);
40 static gint sieve_pop_send_queue(SieveSession *session);
41 static void sieve_session_reset(SieveSession *session);
42 static void command_free(SieveCommand *cmd);
43 static void command_abort(SieveCommand *cmd);
44 static void command_cb(SieveCommand *cmd, gpointer result);
45
46 void sieve_sessions_close()
47 {
48         if (sessions) {
49                 g_slist_free_full(sessions, (GDestroyNotify)session_destroy);
50                 sessions = NULL;
51         }
52 }
53
54 /* remove all command callbacks with a given data pointer */
55 void sieve_sessions_discard_callbacks(gpointer user_data)
56 {
57         GSList *item;
58         GSList *queue;
59         GSList *prev = NULL;
60         SieveSession *session;
61         SieveCommand *cmd;
62
63         for (item = sessions; item; item = item->next) {
64                 session = (SieveSession *)item->data;
65                 cmd = session->current_cmd;
66                 /* abort current command handler */
67                 if (cmd && cmd->data == user_data) {
68                         command_abort(cmd);
69                         session->current_cmd = NULL;
70                 }
71                 /* abort queued command handlers */
72                 for (queue = session->send_queue; queue; queue = queue->next) {
73                         cmd = (SieveCommand *)queue->data;
74                         if (cmd && cmd->data == user_data) {
75                                 if (prev)
76                                         prev->next = queue->next;
77                                 else
78                                         session->send_queue = NULL;
79                                 command_abort(cmd);
80                                 g_slist_free_1(queue);
81                         } else {
82                                 prev = queue;
83                         }
84                 }
85         }
86 }
87
88 static void command_cb(SieveCommand *cmd, gpointer result)
89 {
90         if (cmd)
91                 cmd->cb(cmd->session, FALSE, result, cmd->data);
92 }
93
94 static void command_abort(SieveCommand *cmd)
95 {
96         cmd->cb(cmd->session, TRUE, NULL, cmd->data);
97         g_free(cmd->msg);
98         g_free(cmd);
99 }
100
101 static void command_free(SieveCommand *cmd)
102 {
103         g_free(cmd->msg);
104         g_free(cmd);
105 }
106
107 void sieve_session_handle_status(SieveSession *session,
108                 sieve_session_error_cb_fn on_error,
109                 sieve_session_connected_cb_fn on_connected,
110                 gpointer data)
111 {
112         session->on_error = on_error;
113         session->on_connected = on_connected;
114         session->cb_data = data;
115 }
116
117 static void sieve_error(SieveSession *session, const gchar *msg)
118 {
119         if (session->on_error)
120                 session->on_error(session, msg, session->cb_data);
121 }
122
123 static void sieve_connected(SieveSession *session, gboolean connected)
124 {
125         if (session->on_connected)
126                 session->on_connected(session, connected, session->cb_data);
127 }
128
129 static gint sieve_auth_recv(SieveSession *session, const gchar *msg)
130 {
131         gchar buf[MESSAGEBUFSIZE], *tmp;
132
133         switch (session->auth_type) {
134         case SIEVEAUTH_LOGIN:
135                 session->state = SIEVE_AUTH_LOGIN_USER;
136
137                 if (strstr(msg, "VXNlcm5hbWU6")) {
138                         tmp = g_base64_encode(session->user, strlen(session->user));
139                         g_snprintf(buf, sizeof(buf), "\"%s\"", tmp);
140
141                         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf) < 0) {
142                                 g_free(tmp);
143                                 return SE_ERROR;
144                         }
145                         g_free(tmp);
146                         log_print(LOG_PROTOCOL, "Sieve> [USERID]\n");
147                 } else {
148                         /* Server rejects AUTH */
149                         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
150                                          "\"*\"") < 0)
151                                 return SE_ERROR;
152                         log_print(LOG_PROTOCOL, "Sieve> *\n");
153                 }
154                 break;
155         case SIEVEAUTH_CRAM_MD5:
156                 session->state = SIEVE_AUTH_CRAM_MD5;
157
158                 if (msg[0] == '"') {
159                         gchar *response;
160                         gchar *response64;
161                         gchar *challenge, *tmp;
162                         gsize challengelen;
163                         guchar hexdigest[33];
164
165                         tmp = g_base64_decode(msg + 1, &challengelen);
166                         challenge = g_strndup(tmp, challengelen);
167                         g_free(tmp);
168                         log_print(LOG_PROTOCOL, "Sieve< [Decoded: %s]\n", challenge);
169
170                         g_snprintf(buf, sizeof(buf), "%s", session->pass);
171                         md5_hex_hmac(hexdigest, challenge, challengelen,
172                                      buf, strlen(session->pass));
173                         g_free(challenge);
174
175                         response = g_strdup_printf
176                                 ("%s %s", session->user, hexdigest);
177                         log_print(LOG_PROTOCOL, "Sieve> [Encoded: %s]\n", response);
178
179                         response64 = g_base64_encode(response, strlen(response));
180                         g_free(response);
181
182                         response = g_strdup_printf("\"%s\"", response64);
183                         g_free(response64);
184
185                         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
186                                          response) < 0) {
187                                 g_free(response);
188                                 return SE_ERROR;
189                         }
190                         log_print(LOG_PROTOCOL, "Sieve> %s\n", response);
191                         g_free(response);
192                 } else {
193                         /* Server rejects AUTH */
194                         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
195                                          "\"*\"") < 0)
196                                 return SE_ERROR;
197                         log_print(LOG_PROTOCOL, "Sieve> *\n");
198                 }
199                 break;
200         default:
201                 /* stop sieve_auth when no correct authtype */
202                 if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "*") < 0)
203                         return SE_ERROR;
204                 log_print(LOG_PROTOCOL, "Sieve> *\n");
205                 break;
206         }
207
208         return SE_OK;
209 }
210
211 static gint sieve_auth_login_user_recv(SieveSession *session, const gchar *msg)
212 {
213         gchar *tmp, *tmp2;
214
215         session->state = SIEVE_AUTH_LOGIN_PASS;
216         
217         if (strstr(msg, "UGFzc3dvcmQ6")) {
218                 tmp2 = g_base64_encode(session->pass, strlen(session->pass));
219                 tmp = g_strdup_printf("\"%s\"", tmp2);
220                 g_free(tmp2);
221         } else {
222                 /* Server rejects AUTH */
223                 tmp = g_strdup("\"*\"");
224         }
225
226         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, tmp) < 0) {
227                 g_free(tmp);
228                 return SE_ERROR;
229         }
230         g_free(tmp);
231
232         log_print(LOG_PROTOCOL, "Sieve> [PASSWORD]\n");
233
234         return SE_OK;
235 }
236
237
238 static gint sieve_auth_cram_md5(SieveSession *session)
239 {
240         session->state = SIEVE_AUTH;
241         session->auth_type = SIEVEAUTH_CRAM_MD5;
242
243         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
244                                 "Authenticate \"CRAM-MD5\"") < 0)
245                 return SE_ERROR;
246         log_print(LOG_PROTOCOL, "Sieve> Authenticate CRAM-MD5\n");
247
248         return SE_OK;
249 }
250
251 static gint sieve_auth_plain(SieveSession *session)
252 {
253         gchar buf[MESSAGEBUFSIZE], *b64buf, *out;
254         gint len;
255
256         session->state = SIEVE_AUTH_PLAIN;
257         session->auth_type = SIEVEAUTH_PLAIN;
258
259         memset(buf, 0, sizeof buf);
260
261         /* "\0user\0password" */
262         len = sprintf(buf, "%c%s%c%s", '\0', session->user, '\0', session->pass);
263         b64buf = g_base64_encode(buf, len);
264         out = g_strconcat("Authenticate \"PLAIN\" \"", b64buf, "\"", NULL);
265         g_free(b64buf);
266
267         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, out) < 0) {
268                 g_free(out);
269                 return SE_ERROR;
270         }
271
272         g_free(out);
273
274         log_print(LOG_PROTOCOL, "Sieve> [Authenticate PLAIN]\n");
275
276         return SE_OK;
277 }
278
279 static gint sieve_auth_login(SieveSession *session)
280 {
281         session->state = SIEVE_AUTH;
282         session->auth_type = SIEVEAUTH_LOGIN;
283
284         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
285                                 "Authenticate \"LOGIN\"") < 0)
286                 return SE_ERROR;
287         log_print(LOG_PROTOCOL, "Sieve> Authenticate LOGIN\n");
288
289         return SE_OK;
290 }
291
292 static gint sieve_auth(SieveSession *session)
293 {
294         SieveAuthType forced_auth_type = session->forced_auth_type;
295
296         if (!session->use_auth) {
297                 session->state = SIEVE_READY;
298                 sieve_connected(session, TRUE);
299                 return SE_OK;
300         }
301
302         session->state = SIEVE_AUTH;
303         sieve_error(session, _("Authenticating..."));
304
305         if ((forced_auth_type == SIEVEAUTH_CRAM_MD5 || forced_auth_type == 0) &&
306              (session->avail_auth_type & SIEVEAUTH_CRAM_MD5) != 0)
307                 return sieve_auth_cram_md5(session);
308         else if ((forced_auth_type == SIEVEAUTH_LOGIN || forced_auth_type == 0) &&
309                   (session->avail_auth_type & SIEVEAUTH_LOGIN) != 0)
310                 return sieve_auth_login(session);
311         else if ((forced_auth_type == SIEVEAUTH_PLAIN || forced_auth_type == 0) &&
312                   (session->avail_auth_type & SIEVEAUTH_PLAIN) != 0)
313                 return sieve_auth_plain(session);
314         else if (forced_auth_type == 0) {
315                 log_warning(LOG_PROTOCOL, _("No Sieve auth method available\n"));
316                 session->state = SIEVE_RETRY_AUTH;
317                 return SE_AUTHFAIL;
318         } else {
319                 log_warning(LOG_PROTOCOL, _("Selected Sieve auth method not available\n"));
320                 session->state = SIEVE_RETRY_AUTH;
321                 return SE_AUTHFAIL;
322         }
323
324         return SE_OK;
325 }
326
327 static void sieve_session_putscript_cb(SieveSession *session, SieveResult *result)
328 {
329         /* Remove script name from the beginning the response,
330          * which are added by Dovecot/Pigeonhole */
331         gchar *start, *desc = result->description;
332         if (desc) {
333                 if (g_str_has_prefix(desc, "NULL_") && (start = strchr(desc+5, ':'))) {
334                         desc = start+1;
335                         while (*desc == ' ')
336                                 desc++;
337                 /* TODO: match against known script name, in case it contains
338                  * weird text like ": line " */
339                 } else if ((start = strstr(desc, ": line ")) ||
340                                 (start = strstr(desc, ": error"))) {
341                         desc = start+2;
342                 }
343                 result->description = desc;
344         }
345         /* pass along the callback */
346         command_cb(session->current_cmd, result);
347 }
348
349 static inline gboolean response_is_ok(const char *msg)
350 {
351         return !strncmp(msg, "OK", 2) && (!msg[2] || msg[2] == ' ');
352 }
353
354 static inline gboolean response_is_no(const char *msg)
355 {
356         return !strncmp(msg, "NO", 2) && (!msg[2] || msg[2] == ' ');
357 }
358
359 static inline gboolean response_is_bye(const char *msg)
360 {
361         return !strncmp(msg, "BYE", 3) && (!msg[3] || msg[3] == ' ');
362 }
363
364 static void sieve_got_capability(SieveSession *session, gchar *cap_name,
365                 gchar *cap_value)
366 {
367         if (strcmp(cap_name, "SASL") == 0) {
368                 SieveAuthType auth_type = 0;
369                 gchar *auth, *end;
370                 for (auth = cap_value; auth && auth[0]; auth = end) {
371                         if ((end = strchr(auth, ' ')))
372                                 *end++ = '\0';
373                         if (strcmp(auth, "PLAIN") == 0) {
374                                 auth_type |= SIEVEAUTH_PLAIN;
375                         } else if (strcmp(auth, "CRAM-MD5") == 0) {
376                                 auth_type |= SIEVEAUTH_CRAM_MD5;
377                         } else if (strcmp(auth, "LOGIN") == 0) {
378                                 auth_type |= SIEVEAUTH_LOGIN;
379                         }
380                 }
381                 session->avail_auth_type = auth_type;
382
383         } else if (strcmp(cap_name, "STARTTLS") == 0) {
384                 session->capability.starttls = TRUE;
385         }
386 }
387
388 static void log_send(SieveSession *session, SieveCommand *cmd)
389 {
390         gchar *end, *msg = cmd->msg;
391         if (cmd->next_state == SIEVE_PUTSCRIPT && (end = strchr(msg, '\n'))) {
392                 /* Don't log the script data */
393                 msg = g_strndup(msg, end - msg);
394                 log_print(LOG_PROTOCOL, "Sieve> %s\n", msg);
395                 g_free(msg);
396                 msg = "[Data]";
397         }
398         log_print(LOG_PROTOCOL, "Sieve> %s\n", msg);
399 }
400
401 static gint sieve_pop_send_queue(SieveSession *session)
402 {
403         SieveCommand *cmd;
404         GSList *send_queue = session->send_queue;
405
406         if (session->current_cmd) {
407                 command_free(session->current_cmd);
408                 session->current_cmd = NULL;
409         }
410
411         if (!send_queue)
412                 return SE_OK;
413
414         cmd = (SieveCommand *)send_queue->data;
415         session->send_queue = g_slist_next(send_queue);
416         g_slist_free_1(send_queue);
417
418         log_send(session, cmd);
419         session->state = cmd->next_state;
420         session->current_cmd = cmd;
421         if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0)
422                 return SE_ERROR;
423
424         return SE_OK;
425 }
426
427 static void parse_split(gchar *line, gchar **first_word, gchar **second_word)
428 {
429         gchar *first = line;
430         gchar *second;
431         gchar *end;
432
433         /* get first */
434         if (line[0] == '"' && ((second = strchr(line + 1, '"')))) {
435                 *second++ = '\0';
436                 first++;
437                 if (second[0] == ' ')
438                         second++;
439         } else if ((second = strchr(line, ' '))) {
440                 *second++ = '\0';
441         }
442
443         /* unquote second */
444         if (second && second[0] == '"' &&
445                         ((end = strchr(second + 1, '"')))) {
446                 second++;
447                 *end = '\0';
448         }
449
450         *first_word = first;
451         *second_word = second;
452 }
453
454 static void unquote_inplace(gchar *str)
455 {
456         gchar *src, *dest;
457         if (*str != '"')
458                 return;
459         for (src = str+1, dest = str; src && *src && *src != '"'; src++) {
460                 if (*src == '\\') {
461                         src++;
462                 }
463                 *dest++ = *src;
464         }
465         *dest = '\0';
466 }
467
468 static void parse_response(gchar *msg, SieveResult *result)
469 {
470         gchar *end;
471
472         /* response status */
473         if (isalpha(msg[0])) {
474                 end = strchr(msg, ' ');
475                 if (end) {
476                         *end++ = '\0';
477                         while (*end == ' ')
478                                 end++;
479                 }
480                 result->success = strcmp(msg, "OK") == 0;
481                 result->has_status = TRUE;
482                 msg = end;
483         } else {
484                 result->has_status = FALSE;
485         }
486
487         /* response code */
488         if (msg[0] == '(' && (end = strchr(msg, ')'))) {
489                 msg++;
490                 *end++ = '\0';
491                 result->code =
492                         strcmp(msg, "WARNINGS") == 0 ? SIEVE_CODE_WARNINGS :
493                         strcmp(msg, "TRYLATER") == 0 ? SIEVE_CODE_TRYLATER :
494                         SIEVE_CODE_UNKNOWN;
495                 while (*end == ' ')
496                         end++;
497                 msg = end;
498         } else {
499                 result->code = SIEVE_CODE_NONE;
500         }
501
502         /* s2c octets */
503         if (msg[0] == '{' && (end = strchr(msg, '}'))) {
504                 msg++;
505                 *end++ = '\0';
506                 if (msg[0] == '0' && msg+1 == end) {
507                         result->has_octets = TRUE;
508                         result->octets = 0;
509                 } else {
510                         result->has_octets =
511                                 (result->octets = g_ascii_strtoll(msg, NULL, 10)) != 0;
512                 }
513                 while (*end == ' ')
514                         end++;
515                 msg = end;
516         } else {
517                 result->has_octets = FALSE;
518                 result->octets = 0;
519         }
520
521         /* text */
522         if (*msg) {
523                 unquote_inplace(msg);
524                 result->description = msg;
525         } else {
526                 result->description = NULL;
527         }
528 }
529
530 static gint sieve_session_recv_msg(Session *session, const gchar *msg)
531 {
532         SieveSession *sieve_session = SIEVE_SESSION(session);
533         SieveResult result;
534         gint ret = SE_OK;
535
536         switch (sieve_session->state) {
537         case SIEVE_GETSCRIPT_DATA:
538                 log_print(LOG_PROTOCOL, "Sieve< [GETSCRIPT data]\n");
539                 break;
540         default:
541                 log_print(LOG_PROTOCOL, "Sieve< %s\n", msg);
542                 if (response_is_bye(msg)) {
543                         gchar *status;
544                         parse_response((gchar *)msg, &result);
545                         if (!result.description)
546                                 status = g_strdup(_("Disconnected"));
547                         else if (g_str_has_prefix(result.description, "Disconnected"))
548                                 status = g_strdup(result.description);
549                         else
550                                 status = g_strdup_printf(_("Disconnected: %s"), result.description);
551                         sieve_session->error = SE_ERROR;
552                         sieve_error(sieve_session, status);
553                         sieve_session->state = SIEVE_DISCONNECTED;
554                         g_free(status);
555                         return -1;
556                 }
557         }
558
559         switch (sieve_session->state) {
560         case SIEVE_CAPABILITIES:
561                 if (response_is_ok(msg)) {
562                         /* capabilities list done */
563
564 #ifdef USE_GNUTLS
565                         if (sieve_session->tls_init_done == FALSE &&
566                                         sieve_session->config->tls_type != SIEVE_TLS_NO) {
567                                 if (sieve_session->capability.starttls) {
568                                         log_print(LOG_PROTOCOL, "Sieve> STARTTLS\n");
569                                         session_send_msg(session, SESSION_SEND, "STARTTLS");
570                                         sieve_session->state = SIEVE_STARTTLS;
571                                 } else if (sieve_session->config->tls_type == SIEVE_TLS_YES) {
572                                         log_warning(LOG_PROTOCOL, "Sieve: does not support STARTTLS\n");
573                                         sieve_session->state = SIEVE_ERROR;
574                                 } else {
575                                         log_warning(LOG_PROTOCOL, "Sieve: continuing without TLS\n");
576                                         sieve_session->state = SIEVE_READY;
577                                 }
578                                 break;
579                         }
580 #endif
581                         /* authenticate after getting capabilities */
582                         if (!sieve_session->authenticated) {
583                                 ret = sieve_auth(sieve_session);
584                         } else {
585                                 sieve_session->state = SIEVE_READY;
586                                 sieve_connected(sieve_session, TRUE);
587                         }
588                 } else {
589                         /* got a capability */
590                         gchar *cap_name, *cap_value;
591                         parse_split((gchar *)msg, &cap_name, &cap_value);
592                         sieve_got_capability(sieve_session, cap_name, cap_value);
593                 }
594                 break;
595         case SIEVE_READY:
596                 log_warning(LOG_PROTOCOL,
597                                 _("unhandled message on Sieve session: %s\n"), msg);
598                 break;
599         case SIEVE_STARTTLS:
600 #ifdef USE_GNUTLS
601                 if (session_start_tls(session) < 0) {
602                         sieve_session->state = SIEVE_ERROR;
603                         sieve_session->error = SE_ERROR;
604                         sieve_error(sieve_session, _("TLS failed"));
605                         return -1;
606                 }
607                 sieve_session->tls_init_done = TRUE;
608                 sieve_session->state = SIEVE_CAPABILITIES;
609 #endif
610                 break;
611         case SIEVE_AUTH:
612                 ret = sieve_auth_recv(sieve_session, msg);
613                 break;
614         case SIEVE_AUTH_LOGIN_USER:
615                 ret = sieve_auth_login_user_recv(sieve_session, msg);
616                 break;
617         case SIEVE_AUTH_PLAIN:
618         case SIEVE_AUTH_LOGIN_PASS:
619         case SIEVE_AUTH_CRAM_MD5:
620                 if (response_is_no(msg)) {
621                         log_print(LOG_PROTOCOL, "Sieve auth failed\n");
622                         session->state = SIEVE_RETRY_AUTH;
623                         ret = SE_AUTHFAIL;
624                 } else if (response_is_ok(msg)) {
625                         log_print(LOG_PROTOCOL, "Sieve auth completed\n");
626                         sieve_error(sieve_session, "");
627                         sieve_session->authenticated = TRUE;
628                         sieve_session->state = SIEVE_READY;
629                         sieve_connected(sieve_session, TRUE);
630                 }
631                 break;
632         case SIEVE_NOOP:
633                 if (!response_is_ok(msg)) {
634                         sieve_session->state = SIEVE_ERROR;
635                 }
636                 sieve_session->state = SIEVE_READY;
637                 break;
638         case SIEVE_LISTSCRIPTS:
639                 if (response_is_no(msg)) {
640                         /* got an error. probably not authenticated. */
641                         command_cb(sieve_session->current_cmd, NULL);
642                         sieve_session->state = SIEVE_READY;
643                 } else if (response_is_ok(msg)) {
644                         /* end of list */
645                         sieve_session->state = SIEVE_READY;
646                         sieve_session->error = SE_OK;
647                         command_cb(sieve_session->current_cmd,
648                                         (gpointer)&(SieveScript){0});
649                 } else {
650                         /* got a script name */
651                         SieveScript script;
652                         gchar *script_status;
653
654                         parse_split((gchar *)msg, &script.name, &script_status);
655                         script.active = (script_status &&
656                                         strcasecmp(script_status, "active") == 0);
657
658                         command_cb(sieve_session->current_cmd,
659                                         (gpointer)&script);
660                 }
661                 break;
662         case SIEVE_RENAMESCRIPT:
663                 if (response_is_no(msg)) {
664                         /* error */
665                         command_cb(sieve_session->current_cmd, NULL);
666                 } else if (response_is_ok(msg)) {
667                         command_cb(sieve_session->current_cmd, (void*)TRUE);
668                 } else {
669                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
670                 }
671                 sieve_session->state = SIEVE_READY;
672                 break;
673         case SIEVE_SETACTIVE:
674                 if (response_is_no(msg)) {
675                         /* error */
676                         command_cb(sieve_session->current_cmd, NULL);
677                 } else if (response_is_ok(msg)) {
678                         command_cb(sieve_session->current_cmd, (void*)TRUE);
679                 } else {
680                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
681                 }
682                 sieve_session->state = SIEVE_READY;
683                 break;
684         case SIEVE_GETSCRIPT:
685                 if (response_is_no(msg)) {
686                         command_cb(sieve_session->current_cmd, (void *)-1);
687                         sieve_session->state = SIEVE_READY;
688                 } else {
689                         parse_response((gchar *)msg, &result);
690                         sieve_session->state = SIEVE_GETSCRIPT_DATA;
691                         /* account for newline */
692                         sieve_session->octets_remaining = result.octets + 1;
693                 }
694                 break;
695         case SIEVE_GETSCRIPT_DATA:
696                 if (sieve_session->octets_remaining > 0) {
697                         command_cb(sieve_session->current_cmd, (gchar *)msg);
698                         sieve_session->octets_remaining -= strlen(msg) + 1;
699                 } else if (response_is_ok(msg)) {
700                         sieve_session->state = SIEVE_READY;
701                         command_cb(sieve_session->current_cmd, NULL);
702                 } else {
703                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
704                 }
705                 break;
706         case SIEVE_PUTSCRIPT:
707                 parse_response((gchar *)msg, &result);
708                 if (result.has_octets) {
709                         sieve_session->state = SIEVE_PUTSCRIPT_DATA;
710                 } else {
711                         sieve_session->state = SIEVE_READY;
712                 }
713                 sieve_session_putscript_cb(sieve_session, &result);
714                 break;
715         case SIEVE_PUTSCRIPT_DATA:
716                 if (!msg[0]) {
717                         sieve_session->state = SIEVE_READY;
718                 } else {
719                         result.has_status = FALSE;
720                         result.has_octets = FALSE;
721                         result.success = -1;
722                         result.code = SIEVE_CODE_NONE;
723                         result.description = (gchar *)msg;
724                         sieve_session_putscript_cb(sieve_session, &result);
725                 }
726                 break;
727         case SIEVE_DELETESCRIPT:
728                 parse_response((gchar *)msg, &result);
729                 if (!result.success) {
730                         command_cb(sieve_session->current_cmd,
731                                         result.description);
732                 } else {
733                         command_cb(sieve_session->current_cmd, NULL);
734                 }
735                 sieve_session->state = SIEVE_READY;
736                 break;
737         case SIEVE_ERROR:
738                 log_warning(LOG_PROTOCOL, _("error occurred on Sieve session. data: %s\n"), msg);
739                 sieve_session->error = SE_ERROR;
740                 break;
741         case SIEVE_RETRY_AUTH:
742                 log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %s\n"),
743                                         msg);
744                 ret = sieve_auth(sieve_session);
745                 break;
746         default:
747                 log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %d\n"),
748                                         sieve_session->state);
749                 sieve_session->error = SE_ERROR;
750                 return -1;
751         }
752
753         if (ret == SE_OK && sieve_session->state == SIEVE_READY)
754                 ret = sieve_pop_send_queue(sieve_session);
755
756         if (ret == SE_OK) {
757                 return session_recv_msg(session);
758         } else if (ret == SE_AUTHFAIL) {
759                 sieve_error(sieve_session, _("Auth failed"));
760                 sieve_session->state = SIEVE_ERROR;
761                 sieve_session->error = SE_ERROR;
762         }
763
764         return 0;
765 }
766
767 static gint sieve_recv_message(Session *session, const gchar *msg,
768                 gpointer user_data)
769 {
770         return 0;
771 }
772
773 static gint sieve_cmd_noop(SieveSession *session)
774 {
775         log_print(LOG_PROTOCOL, "Sieve> NOOP\n");
776         session->state = SIEVE_NOOP;
777         if (session_send_msg(SESSION(session), SESSION_SEND, "NOOP") < 0) {
778                 session->state = SIEVE_ERROR;
779                 session->error = SE_ERROR;
780                 return 1;
781         }
782         return 0;
783 }
784
785 static gboolean sieve_ping(gpointer data)
786 {
787         Session *session = SESSION(data);
788         SieveSession *sieve_session = SIEVE_SESSION(session);
789
790         if (sieve_session->state == SIEVE_ERROR || session->state == SESSION_ERROR)
791                 return FALSE;
792         if (sieve_session->state != SIEVE_READY)
793                 return TRUE;
794
795         return sieve_cmd_noop(sieve_session) == 0;
796 }
797
798 static void sieve_session_destroy(Session *session)
799 {
800         SieveSession *sieve_session = SIEVE_SESSION(session);
801         g_free(sieve_session->pass);
802         if (sieve_session->current_cmd)
803                 command_abort(sieve_session->current_cmd);
804         sessions = g_slist_remove(sessions, (gconstpointer)session);
805         g_slist_free_full(sieve_session->send_queue,
806                         (GDestroyNotify)command_abort);
807 }
808
809 static void sieve_connect_finished(Session *session, gboolean success)
810 {
811         if (!success) {
812                 sieve_connected(SIEVE_SESSION(session), FALSE);
813         }
814 }
815
816 static gint sieve_session_connect(SieveSession *session)
817 {
818         session->state = SIEVE_CAPABILITIES;
819         session->authenticated = FALSE;
820         session->tls_init_done = FALSE;
821         return session_connect(SESSION(session), session->host,
822                         session->port);
823 }
824
825 static SieveSession *sieve_session_new(PrefsAccount *account)
826 {
827         SieveSession *session;
828         session = g_new0(SieveSession, 1);
829         session_init(SESSION(session), account, FALSE);
830
831         session->account = account;
832
833         SESSION(session)->recv_msg = sieve_session_recv_msg;
834         SESSION(session)->destroy = sieve_session_destroy;
835         SESSION(session)->connect_finished = sieve_connect_finished;
836         session_set_recv_message_notify(SESSION(session), sieve_recv_message, NULL);
837
838         sieve_session_reset(session);
839         return session;
840 }
841
842 static void sieve_session_reset(SieveSession *session)
843 {
844         PrefsAccount *account = session->account;
845         SieveAccountConfig *config = sieve_prefs_account_get_config(account);
846         gboolean reuse_auth = (config->auth == SIEVEAUTH_REUSE);
847
848         g_slist_free_full(session->send_queue, (GDestroyNotify)command_abort);
849
850         session_disconnect(SESSION(session));
851
852         SESSION(session)->ssl_cert_auto_accept = account->ssl_certs_auto_accept;
853         SESSION(session)->nonblocking = account->use_nonblocking_ssl;
854         session->authenticated = FALSE;
855         session->current_cmd = NULL;
856         session->send_queue = NULL;
857         session->state = SIEVE_CAPABILITIES;
858         session->tls_init_done = FALSE;
859         session->avail_auth_type = 0;
860         session->auth_type = 0;
861         session->config = config;
862         session->host = config->use_host ? config->host : account->recv_server;
863         session->port = config->use_port ? config->port : SIEVE_PORT;
864         session->user = reuse_auth ? account->userid : session->config->userid;
865         session->forced_auth_type = config->auth_type;
866         session_register_ping(SESSION(session), sieve_ping);
867
868         if (session->pass)
869                 g_free(session->pass);
870         if (config->auth == SIEVEAUTH_NONE) {
871                 session->pass = NULL;
872         } else if (reuse_auth && account->passwd) {
873                 session->pass = g_strdup(account->passwd);
874         } else if (config->passwd && config->passwd[0]) {
875                 session->pass = g_strdup(config->passwd);
876         } else if (password_get(session->user, session->host, "sieve",
877                                 session->port, &session->pass)) {
878         } else {
879                 session->pass = input_dialog_query_password_keep(session->host,
880                                 session->user, &(session->pass));
881         }
882         if (!session->pass) {
883                 session->pass = g_strdup("");
884                 session->use_auth = FALSE;
885         } else {
886                 session->use_auth = TRUE;
887         }
888
889 #ifdef USE_GNUTLS
890         SESSION(session)->ssl_type =
891                 (config->tls_type == SIEVE_TLS_NO) ? SSL_NONE : SSL_STARTTLS;
892 #endif
893 }
894
895 /* When an account config is changed, reset associated sessions. */
896 void sieve_account_prefs_updated(PrefsAccount *account)
897 {
898         GSList *item;
899         SieveSession *session;
900
901         for (item = sessions; item; item = item->next) {
902                 session = (SieveSession *)item->data;
903                 if (session->account == account) {
904                         log_print(LOG_PROTOCOL, "Sieve: resetting session\n");
905                         sieve_session_reset(session);
906                 }
907         }
908 }
909
910 SieveSession *sieve_session_get_for_account(PrefsAccount *account)
911 {
912         SieveSession *session;
913         GSList *item;
914
915         /* find existing */
916         for (item = sessions; item; item = item->next) {
917                 session = (SieveSession *)item->data;
918                 if (session->account == account) {
919                         return session;
920                 }
921         }
922
923         /* create new */
924         session = sieve_session_new(account);
925         sessions = g_slist_prepend(sessions, session);
926
927         return session;
928 }
929
930 static void sieve_queue_send(SieveSession *session, SieveState next_state,
931                 gchar *msg, sieve_session_data_cb_fn cb, gpointer data)
932 {
933         gboolean queue = FALSE;
934         SieveCommand *cmd = g_new0(SieveCommand, 1);
935         cmd->session = session;
936         cmd->next_state = next_state;
937         cmd->msg = msg;
938         cmd->data = data;
939         cmd->cb = cb;
940
941         if (!session_is_connected(SESSION(session))) {
942                 log_print(LOG_PROTOCOL, "Sieve: connecting to %s:%hu\n",
943                                 session->host, session->port);
944                 if (sieve_session_connect(session) < 0) {
945                         sieve_connect_finished(SESSION(session), FALSE);
946                 }
947                 queue = TRUE;
948         } else if (session->state == SIEVE_RETRY_AUTH) {
949                 log_print(LOG_PROTOCOL, _("Sieve: retrying auth\n"));
950                 if (sieve_auth(session) == SE_AUTHFAIL)
951                         sieve_error(session, _("Auth method not available"));
952                 queue = TRUE;
953         } else if (session->state != SIEVE_READY) {
954                 log_print(LOG_PROTOCOL, "Sieve: in state %d\n", session->state);
955                 queue = TRUE;
956         }
957
958         if (queue) {
959                 session->send_queue = g_slist_prepend(session->send_queue, cmd);
960         } else {
961                 if (session->current_cmd)
962                         command_free(session->current_cmd);
963                 session->current_cmd = cmd;
964                 session->state = next_state;
965                 log_send(session, cmd);
966                 if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0) {
967                         /* error */
968                 }
969         }
970 }
971
972 void sieve_session_list_scripts(SieveSession *session,
973                 sieve_session_data_cb_fn cb, gpointer data)
974 {
975         gchar *msg = g_strdup("LISTSCRIPTS");
976         sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
977 }
978
979 void sieve_session_set_active_script(SieveSession *session,
980                 const gchar *filter_name,
981                 sieve_session_data_cb_fn cb, gpointer data)
982 {
983         gchar *msg = g_strdup_printf("SETACTIVE \"%s\"",
984                         filter_name ? filter_name : "");
985         if (!msg) {
986                 cb(session, FALSE, (void*)FALSE, data);
987                 return;
988         }
989
990         sieve_queue_send(session, SIEVE_SETACTIVE, msg, cb, data);
991 }
992
993 void sieve_session_rename_script(SieveSession *session,
994                 const gchar *name_old, const char *name_new,
995                 sieve_session_data_cb_fn cb, gpointer data)
996 {
997         gchar *msg = g_strdup_printf("RENAMESCRIPT \"%s\" \"%s\"",
998                         name_old, name_new);
999
1000         sieve_queue_send(session, SIEVE_RENAMESCRIPT, msg, cb, data);
1001 }
1002
1003 void sieve_session_get_script(SieveSession *session, const gchar *filter_name,
1004                 sieve_session_data_cb_fn cb, gpointer data)
1005 {
1006         gchar *msg = g_strdup_printf("GETSCRIPT \"%s\"",
1007                         filter_name);
1008
1009         sieve_queue_send(session, SIEVE_GETSCRIPT, msg, cb, data);
1010 }
1011
1012 void sieve_session_put_script(SieveSession *session, const gchar *filter_name,
1013                 gint len, const gchar *script_contents,
1014                 sieve_session_data_cb_fn cb, gpointer data)
1015 {
1016         /* TODO: refactor so don't have to copy the whole script here */
1017         gchar *msg = g_strdup_printf("PUTSCRIPT \"%s\" {%u+}\r\n%s",
1018                         filter_name, len, script_contents);
1019
1020         sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
1021 }
1022
1023 void sieve_session_check_script(SieveSession *session,
1024                 gint len, const gchar *script_contents,
1025                 sieve_session_data_cb_fn cb, gpointer data)
1026 {
1027         gchar *msg = g_strdup_printf("CHECKSCRIPT {%u+}\r\n%s",
1028                         len, script_contents);
1029
1030         sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
1031 }
1032
1033 void sieve_session_delete_script(SieveSession *session,
1034                 const gchar *filter_name,
1035                 sieve_session_data_cb_fn cb, gpointer data)
1036 {
1037         gchar *msg = g_strdup_printf("DELETESCRIPT \"%s\"",
1038                         filter_name);
1039
1040         sieve_queue_send(session, SIEVE_DELETESCRIPT, msg, cb, data);
1041 }