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