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