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