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