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