managesieve: fix command aborting and discarding
[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 sieve_pop_send_queue(session);
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 (!send_queue)
407                 return SE_OK;
408
409         cmd = (SieveCommand *)send_queue->data;
410         session->send_queue = g_slist_next(send_queue);
411         g_slist_free_1(send_queue);
412
413         log_send(session, cmd);
414         session->state = cmd->next_state;
415         if (session->current_cmd)
416                 command_free(session->current_cmd);
417         session->current_cmd = cmd;
418         if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0)
419                 return SE_ERROR;
420
421         return SE_OK;
422 }
423
424 static void parse_split(gchar *line, gchar **first_word, gchar **second_word)
425 {
426         gchar *first = line;
427         gchar *second;
428         gchar *end;
429
430         /* get first */
431         if (line[0] == '"' && ((second = strchr(line + 1, '"')))) {
432                 *second++ = '\0';
433                 first++;
434                 if (second[0] == ' ')
435                         second++;
436         } else if ((second = strchr(line, ' '))) {
437                 *second++ = '\0';
438         }
439
440         /* unquote second */
441         if (second && second[0] == '"' &&
442                         ((end = strchr(second + 1, '"')))) {
443                 second++;
444                 *end = '\0';
445         }
446
447         *first_word = first;
448         *second_word = second;
449 }
450
451 static void unquote_inplace(gchar *str)
452 {
453         gchar *src, *dest;
454         if (*str != '"')
455                 return;
456         for (src = str+1, dest = str; src && *src && *src != '"'; src++) {
457                 if (*src == '\\') {
458                         src++;
459                 }
460                 *dest++ = *src;
461         }
462         *dest = '\0';
463 }
464
465 static void parse_response(gchar *msg, SieveResult *result)
466 {
467         gchar *end;
468
469         /* response status */
470         if (isalpha(msg[0])) {
471                 end = strchr(msg, ' ');
472                 if (end) {
473                         *end++ = '\0';
474                         while (*end == ' ')
475                                 end++;
476                 }
477                 result->success = strcmp(msg, "OK") == 0;
478                 result->has_status = TRUE;
479                 msg = end;
480         } else {
481                 result->has_status = FALSE;
482         }
483
484         /* response code */
485         if (msg[0] == '(' && (end = strchr(msg, ')'))) {
486                 msg++;
487                 *end++ = '\0';
488                 result->code =
489                         strcmp(msg, "WARNINGS") == 0 ? SIEVE_CODE_WARNINGS :
490                         strcmp(msg, "TRYLATER") == 0 ? SIEVE_CODE_TRYLATER :
491                         SIEVE_CODE_UNKNOWN;
492                 while (*end == ' ')
493                         end++;
494                 msg = end;
495         } else {
496                 result->code = SIEVE_CODE_NONE;
497         }
498
499         /* s2c octets */
500         if (msg[0] == '{' && (end = strchr(msg, '}'))) {
501                 msg++;
502                 *end++ = '\0';
503                 if (msg[0] == '0' && msg+1 == end) {
504                         result->has_octets = TRUE;
505                         result->octets = 0;
506                 } else {
507                         result->has_octets =
508                                 (result->octets = g_ascii_strtoll(msg, NULL, 10)) != 0;
509                 }
510                 while (*end == ' ')
511                         end++;
512                 msg = end;
513         } else {
514                 result->has_octets = FALSE;
515                 result->octets = 0;
516         }
517
518         /* text */
519         if (*msg) {
520                 unquote_inplace(msg);
521                 result->description = msg;
522         } else {
523                 result->description = NULL;
524         }
525 }
526
527 static gint sieve_session_recv_msg(Session *session, const gchar *msg)
528 {
529         SieveSession *sieve_session = SIEVE_SESSION(session);
530         SieveResult result;
531         gint ret = 0;
532
533         switch (sieve_session->state) {
534         case SIEVE_GETSCRIPT_DATA:
535                 log_print(LOG_PROTOCOL, "Sieve< [GETSCRIPT data]\n");
536                 break;
537         default:
538                 log_print(LOG_PROTOCOL, "Sieve< %s\n", msg);
539                 if (response_is_bye(msg)) {
540                         gchar *status;
541                         parse_response((gchar *)msg, &result);
542                         if (!result.description)
543                                 status = g_strdup(_("Disconnected"));
544                         else if (g_str_has_prefix(result.description, "Disconnected"))
545                                 status = g_strdup(result.description);
546                         else
547                                 status = g_strdup_printf(_("Disconnected: %s"), result.description);
548                         sieve_session->error = SE_ERROR;
549                         sieve_error(sieve_session, status);
550                         sieve_session->state = SIEVE_DISCONNECTED;
551                         g_free(status);
552                         return -1;
553                 }
554         }
555
556         switch (sieve_session->state) {
557         case SIEVE_CAPABILITIES:
558                 if (response_is_ok(msg)) {
559                         /* capabilities list done */
560
561 #ifdef USE_GNUTLS
562                         if (sieve_session->tls_init_done == FALSE &&
563                                         sieve_session->config->tls_type != SIEVE_TLS_NO) {
564                                 if (sieve_session->capability.starttls) {
565                                         log_print(LOG_PROTOCOL, "Sieve> STARTTLS\n");
566                                         session_send_msg(session, SESSION_SEND, "STARTTLS");
567                                         sieve_session->state = SIEVE_STARTTLS;
568                                 } else if (sieve_session->config->tls_type == SIEVE_TLS_YES) {
569                                         log_warning(LOG_PROTOCOL, "Sieve: does not support STARTTLS\n");
570                                         sieve_session->state = SIEVE_ERROR;
571                                 } else {
572                                         log_warning(LOG_PROTOCOL, "Sieve: continuing without TLS\n");
573                                         sieve_session->state = SIEVE_CAPABILITIES;
574                                 }
575                                 break;
576                         }
577 #endif
578                         /* authenticate after getting capabilities */
579                         if (!sieve_session->authenticated) {
580                                 ret = sieve_auth(sieve_session);
581                         } else {
582                                 sieve_session->state = SIEVE_READY;
583                                 sieve_connected(sieve_session, TRUE);
584                                 ret = sieve_pop_send_queue(sieve_session);
585                         }
586                 } else {
587                         /* got a capability */
588                         gchar *cap_name, *cap_value;
589                         parse_split((gchar *)msg, &cap_name, &cap_value);
590                         sieve_got_capability(sieve_session, cap_name, cap_value);
591                 }
592                 break;
593         case SIEVE_READY:
594                 log_warning(LOG_PROTOCOL,
595                                 _("unhandled message on Sieve session: %s\n"), msg);
596                 break;
597         case SIEVE_STARTTLS:
598 #ifdef USE_GNUTLS
599                 if (session_start_tls(session) < 0) {
600                         sieve_session->state = SIEVE_ERROR;
601                         sieve_session->error = SE_ERROR;
602                         sieve_error(sieve_session, _("TLS failed"));
603                         return -1;
604                 }
605                 sieve_session->tls_init_done = TRUE;
606                 sieve_session->state = SIEVE_CAPABILITIES;
607                 ret = SE_OK;
608 #endif
609                 break;
610         case SIEVE_AUTH:
611                 ret = sieve_auth_recv(sieve_session, msg);
612                 break;
613         case SIEVE_AUTH_LOGIN_USER:
614                 ret = sieve_auth_login_user_recv(sieve_session, msg);
615                 break;
616         case SIEVE_AUTH_PLAIN:
617         case SIEVE_AUTH_LOGIN_PASS:
618         case SIEVE_AUTH_CRAM_MD5:
619                 if (response_is_no(msg)) {
620                         log_print(LOG_PROTOCOL, "Sieve auth failed\n");
621                         session->state = SIEVE_RETRY_AUTH;
622                         ret = SE_AUTHFAIL;
623                 } else if (response_is_ok(msg)) {
624                         log_print(LOG_PROTOCOL, "Sieve auth completed\n");
625                         sieve_error(sieve_session, "");
626                         sieve_session->authenticated = TRUE;
627                         sieve_session->state = SIEVE_READY;
628                         sieve_connected(sieve_session, TRUE);
629                         ret = sieve_pop_send_queue(sieve_session);
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                         ret = sieve_pop_send_queue(sieve_session);
644                 } else if (response_is_ok(msg)) {
645                         /* end of list */
646                         sieve_session->state = SIEVE_READY;
647                         sieve_session->error = SE_OK;
648                         command_cb(sieve_session->current_cmd,
649                                         (gpointer)&(SieveScript){0});
650                         ret = sieve_pop_send_queue(sieve_session);
651                 } else {
652                         /* got a script name */
653                         SieveScript script;
654                         gchar *script_status;
655
656                         parse_split((gchar *)msg, &script.name, &script_status);
657                         script.active = (script_status &&
658                                         strcasecmp(script_status, "active") == 0);
659
660                         command_cb(sieve_session->current_cmd,
661                                         (gpointer)&script);
662                         ret = SE_OK;
663                 }
664                 break;
665         case SIEVE_RENAMESCRIPT:
666                 if (response_is_no(msg)) {
667                         /* error */
668                         command_cb(sieve_session->current_cmd, NULL);
669                 } else if (response_is_ok(msg)) {
670                         command_cb(sieve_session->current_cmd, (void*)TRUE);
671                 } else {
672                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
673                 }
674                 sieve_session->state = SIEVE_READY;
675                 break;
676         case SIEVE_SETACTIVE:
677                 if (response_is_no(msg)) {
678                         /* error */
679                         command_cb(sieve_session->current_cmd, NULL);
680                 } else if (response_is_ok(msg)) {
681                         command_cb(sieve_session->current_cmd, (void*)TRUE);
682                 } else {
683                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
684                 }
685                 sieve_session->state = SIEVE_READY;
686                 break;
687         case SIEVE_GETSCRIPT:
688                 if (response_is_no(msg)) {
689                         command_cb(sieve_session->current_cmd, (void *)-1);
690                         sieve_session->state = SIEVE_READY;
691                 } else {
692                         parse_response((gchar *)msg, &result);
693                         sieve_session->state = SIEVE_GETSCRIPT_DATA;
694                         /* account for newline */
695                         sieve_session->octets_remaining = result.octets + 1;
696                 }
697                 ret = SE_OK;
698                 break;
699         case SIEVE_GETSCRIPT_DATA:
700                 if (sieve_session->octets_remaining > 0) {
701                         command_cb(sieve_session->current_cmd, (gchar *)msg);
702                         sieve_session->octets_remaining -= strlen(msg) + 1;
703                 } else if (response_is_ok(msg)) {
704                         sieve_session->state = SIEVE_READY;
705                         command_cb(sieve_session->current_cmd, NULL);
706                 } else {
707                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
708                 }
709                 ret = SE_OK;
710                 break;
711         case SIEVE_PUTSCRIPT:
712                 parse_response((gchar *)msg, &result);
713                 if (result.has_octets) {
714                         sieve_session->state = SIEVE_PUTSCRIPT_DATA;
715                 } else {
716                         sieve_session->state = SIEVE_READY;
717                 }
718                 sieve_session_putscript_cb(sieve_session, &result);
719                 ret = SE_OK;
720                 break;
721         case SIEVE_PUTSCRIPT_DATA:
722                 if (!msg[0]) {
723                         sieve_session->state = SIEVE_READY;
724                 } else {
725                         result.has_status = FALSE;
726                         result.has_octets = FALSE;
727                         result.success = -1;
728                         result.code = SIEVE_CODE_NONE;
729                         result.description = (gchar *)msg;
730                         sieve_session_putscript_cb(sieve_session, &result);
731                 }
732                 ret = SE_OK;
733                 break;
734         case SIEVE_DELETESCRIPT:
735                 parse_response((gchar *)msg, &result);
736                 if (!result.success) {
737                         command_cb(sieve_session->current_cmd,
738                                         result.description);
739                 } else {
740                         command_cb(sieve_session->current_cmd, NULL);
741                 }
742                 sieve_session->state = SIEVE_READY;
743                 break;
744         case SIEVE_ERROR:
745                 log_warning(LOG_PROTOCOL, _("error occurred on Sieve session. data: %s\n"), msg);
746                 sieve_session->error = SE_ERROR;
747                 break;
748         case SIEVE_RETRY_AUTH:
749                 log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %s\n"),
750                                         msg);
751                 ret = sieve_auth(sieve_session);
752                 break;
753         default:
754                 log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %d\n"),
755                                         sieve_session->state);
756                 sieve_session->error = SE_ERROR;
757                 return -1;
758         }
759
760         if (ret == SE_OK)
761                 return session_recv_msg(session);
762         else if (ret == SE_AUTHFAIL) {
763                 sieve_error(sieve_session, _("Auth failed"));
764                 sieve_session->state = SIEVE_ERROR;
765                 sieve_session->error = SE_ERROR;
766         }
767
768         return 0;
769 }
770
771 static gint sieve_recv_message(Session *session, const gchar *msg,
772                 gpointer user_data)
773 {
774         return 0;
775 }
776
777 static gint sieve_cmd_noop(SieveSession *session)
778 {
779         log_print(LOG_PROTOCOL, "Sieve> NOOP\n");
780         session->state = SIEVE_NOOP;
781         if (session_send_msg(SESSION(session), SESSION_SEND, "NOOP") < 0) {
782                 session->state = SIEVE_ERROR;
783                 session->error = SE_ERROR;
784                 return 1;
785         }
786         return 0;
787 }
788
789 static gboolean sieve_ping(gpointer data)
790 {
791         Session *session = SESSION(data);
792         SieveSession *sieve_session = SIEVE_SESSION(session);
793
794         if (sieve_session->state == SIEVE_ERROR || session->state == SESSION_ERROR)
795                 return FALSE;
796         if (sieve_session->state != SIEVE_READY)
797                 return TRUE;
798
799         return sieve_cmd_noop(sieve_session) == 0;
800 }
801
802 static void sieve_session_destroy(Session *session)
803 {
804         SieveSession *sieve_session = SIEVE_SESSION(session);
805         g_free(sieve_session->pass);
806         if (sieve_session->current_cmd)
807                 command_abort(sieve_session->current_cmd);
808         sessions = g_slist_remove(sessions, (gconstpointer)session);
809         g_slist_free_full(sieve_session->send_queue,
810                         (GDestroyNotify)command_abort);
811 }
812
813 static void sieve_connect_finished(Session *session, gboolean success)
814 {
815         if (!success) {
816                 sieve_connected(SIEVE_SESSION(session), FALSE);
817         }
818 }
819
820 static gint sieve_session_connect(SieveSession *session)
821 {
822         session->state = SIEVE_CAPABILITIES;
823         session->authenticated = FALSE;
824         session->tls_init_done = FALSE;
825         return session_connect(SESSION(session), session->host,
826                         session->port);
827 }
828
829 static SieveSession *sieve_session_new(PrefsAccount *account)
830 {
831         SieveSession *session;
832         session = g_new0(SieveSession, 1);
833         session_init(SESSION(session), account, FALSE);
834
835         session->account = account;
836
837         SESSION(session)->recv_msg = sieve_session_recv_msg;
838         SESSION(session)->destroy = sieve_session_destroy;
839         SESSION(session)->connect_finished = sieve_connect_finished;
840         session_set_recv_message_notify(SESSION(session), sieve_recv_message, NULL);
841
842         sieve_session_reset(session);
843         return session;
844 }
845
846 static void sieve_session_reset(SieveSession *session)
847 {
848         PrefsAccount *account = session->account;
849         SieveAccountConfig *config = sieve_prefs_account_get_config(account);
850         gboolean reuse_auth = (config->auth == SIEVEAUTH_REUSE);
851
852         g_slist_free_full(session->send_queue, (GDestroyNotify)command_abort);
853
854         session_disconnect(SESSION(session));
855
856         SESSION(session)->ssl_cert_auto_accept = account->ssl_certs_auto_accept;
857         SESSION(session)->nonblocking = account->use_nonblocking_ssl;
858         session->authenticated = FALSE;
859         session->current_cmd = NULL;
860         session->send_queue = NULL;
861         session->state = SIEVE_CAPABILITIES;
862         session->tls_init_done = FALSE;
863         session->avail_auth_type = 0;
864         session->auth_type = 0;
865         session->config = config;
866         session->host = config->use_host ? config->host : account->recv_server;
867         session->port = config->use_port ? config->port : SIEVE_PORT;
868         session->user = reuse_auth ? account->userid : session->config->userid;
869         session->forced_auth_type = config->auth_type;
870         session_register_ping(SESSION(session), sieve_ping);
871
872         if (session->pass)
873                 g_free(session->pass);
874         if (config->auth == SIEVEAUTH_NONE) {
875                 session->pass = NULL;
876         } else if (reuse_auth && account->passwd) {
877                 session->pass = g_strdup(account->passwd);
878         } else if (config->passwd && config->passwd[0]) {
879                 session->pass = g_strdup(config->passwd);
880         } else if (password_get(session->user, session->host, "sieve",
881                                 session->port, &session->pass)) {
882         } else {
883                 session->pass = input_dialog_query_password_keep(session->host,
884                                 session->user, &(session->pass));
885         }
886         if (!session->pass) {
887                 session->pass = g_strdup("");
888                 session->use_auth = FALSE;
889         } else {
890                 session->use_auth = TRUE;
891         }
892
893 #ifdef USE_GNUTLS
894         SESSION(session)->ssl_type =
895                 (config->tls_type == SIEVE_TLS_NO) ? SSL_NONE : SSL_STARTTLS;
896 #endif
897 }
898
899 /* When an account config is changed, reset associated sessions. */
900 void sieve_account_prefs_updated(PrefsAccount *account)
901 {
902         GSList *item;
903         SieveSession *session;
904
905         for (item = sessions; item; item = item->next) {
906                 session = (SieveSession *)item->data;
907                 if (session->account == account) {
908                         log_print(LOG_PROTOCOL, "Sieve: resetting session\n");
909                         sieve_session_reset(session);
910                 }
911         }
912 }
913
914 SieveSession *sieve_session_get_for_account(PrefsAccount *account)
915 {
916         SieveSession *session;
917         GSList *item;
918
919         /* find existing */
920         for (item = sessions; item; item = item->next) {
921                 session = (SieveSession *)item->data;
922                 if (session->account == account) {
923                         return session;
924                 }
925         }
926
927         /* create new */
928         session = sieve_session_new(account);
929         sessions = g_slist_prepend(sessions, session);
930
931         return session;
932 }
933
934 static void sieve_queue_send(SieveSession *session, SieveState next_state,
935                 gchar *msg, sieve_session_data_cb_fn cb, gpointer data)
936 {
937         gboolean queue = FALSE;
938         SieveCommand *cmd = g_new0(SieveCommand, 1);
939         cmd->session = session;
940         cmd->next_state = next_state;
941         cmd->msg = msg;
942         cmd->data = data;
943         cmd->cb = cb;
944
945         if (!session_is_connected(SESSION(session))) {
946                 log_print(LOG_PROTOCOL, "Sieve: connecting to %s:%hu\n",
947                                 session->host, session->port);
948                 if (sieve_session_connect(session) < 0) {
949                         sieve_connect_finished(SESSION(session), FALSE);
950                 }
951                 queue = TRUE;
952         } else if (session->state == SIEVE_RETRY_AUTH) {
953                 log_print(LOG_PROTOCOL, _("Sieve: retrying auth\n"));
954                 if (sieve_auth(session) == SE_AUTHFAIL)
955                         sieve_error(session, _("Auth method not available"));
956                 queue = TRUE;
957         } else if (session->state != SIEVE_READY) {
958                 log_print(LOG_PROTOCOL, "Sieve: in state %d\n", session->state);
959                 queue = TRUE;
960         }
961
962         if (queue) {
963                 session->send_queue = g_slist_prepend(session->send_queue, cmd);
964         } else {
965                 if (session->current_cmd)
966                         command_free(session->current_cmd);
967                 session->current_cmd = cmd;
968                 session->state = next_state;
969                 log_send(session, cmd);
970                 if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0) {
971                         /* error */
972                 }
973         }
974 }
975
976 void sieve_session_list_scripts(SieveSession *session,
977                 sieve_session_data_cb_fn cb, gpointer data)
978 {
979         gchar *msg = g_strdup("LISTSCRIPTS");
980         sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
981 }
982
983 void sieve_session_add_script(SieveSession *session, const gchar *filter_name,
984                 sieve_session_data_cb_fn cb, gpointer data)
985 {
986 /*
987         gchar *msg = g_strdup("LISTSCRIPTS");
988         sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
989 */
990 }
991
992 void sieve_session_set_active_script(SieveSession *session,
993                 const gchar *filter_name,
994                 sieve_session_data_cb_fn cb, gpointer data)
995 {
996         gchar *msg = g_strdup_printf("SETACTIVE \"%s\"",
997                         filter_name ? filter_name : "");
998         if (!msg) {
999                 cb(session, FALSE, (void*)FALSE, data);
1000                 return;
1001         }
1002
1003         sieve_queue_send(session, SIEVE_SETACTIVE, msg, cb, data);
1004 }
1005
1006 void sieve_session_rename_script(SieveSession *session,
1007                 const gchar *name_old, const char *name_new,
1008                 sieve_session_data_cb_fn cb, gpointer data)
1009 {
1010         gchar *msg = g_strdup_printf("RENAMESCRIPT \"%s\" \"%s\"",
1011                         name_old, name_new);
1012
1013         sieve_queue_send(session, SIEVE_RENAMESCRIPT, msg, cb, data);
1014 }
1015
1016 void sieve_session_get_script(SieveSession *session, const gchar *filter_name,
1017                 sieve_session_data_cb_fn cb, gpointer data)
1018 {
1019         gchar *msg = g_strdup_printf("GETSCRIPT \"%s\"",
1020                         filter_name);
1021
1022         sieve_queue_send(session, SIEVE_GETSCRIPT, msg, cb, data);
1023 }
1024
1025 void sieve_session_put_script(SieveSession *session, const gchar *filter_name,
1026                 gint len, const gchar *script_contents,
1027                 sieve_session_data_cb_fn cb, gpointer data)
1028 {
1029         /* TODO: refactor so don't have to copy the whole script here */
1030         gchar *msg = g_strdup_printf("PUTSCRIPT \"%s\" {%u+}\r\n%s",
1031                         filter_name, len, script_contents);
1032
1033         sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
1034 }
1035
1036 void sieve_session_check_script(SieveSession *session,
1037                 gint len, const gchar *script_contents,
1038                 sieve_session_data_cb_fn cb, gpointer data)
1039 {
1040         gchar *msg = g_strdup_printf("CHECKSCRIPT {%u+}\r\n%s",
1041                         len, script_contents);
1042
1043         sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
1044 }
1045
1046 void sieve_session_delete_script(SieveSession *session,
1047                 const gchar *filter_name,
1048                 sieve_session_data_cb_fn cb, gpointer data)
1049 {
1050         gchar *msg = g_strdup_printf("DELETESCRIPT \"%s\"",
1051                         filter_name);
1052
1053         sieve_queue_send(session, SIEVE_DELETESCRIPT, msg, cb, data);
1054 }