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