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