Migrate managesieve to passwordstore
[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), SESSION_MSG_NORMAL, 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), SESSION_MSG_NORMAL,
275                                          "\"*\"") < 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), SESSION_MSG_NORMAL,
311                                          response) < 0) {
312                                 g_free(response);
313                                 return SE_ERROR;
314                         }
315                         log_print(LOG_PROTOCOL, "Sieve> %s\n", response);
316                         g_free(response);
317                 } else {
318                         /* Server rejects AUTH */
319                         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
320                                          "\"*\"") < 0)
321                                 return SE_ERROR;
322                         log_print(LOG_PROTOCOL, "Sieve> *\n");
323                 }
324                 break;
325         default:
326                 /* stop sieve_auth when no correct authtype */
327                 if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "*") < 0)
328                         return SE_ERROR;
329                 log_print(LOG_PROTOCOL, "Sieve> *\n");
330                 break;
331         }
332
333         return SE_OK;
334 }
335
336 static gint sieve_auth_login_user_recv(SieveSession *session, const gchar *msg)
337 {
338         gchar *tmp, *tmp2;
339
340         session->state = SIEVE_AUTH_LOGIN_PASS;
341         
342         if (strstr(msg, "UGFzc3dvcmQ6")) {
343                 tmp2 = g_base64_encode(session->pass, strlen(session->pass));
344                 tmp = g_strdup_printf("\"%s\"", tmp2);
345                 g_free(tmp2);
346         } else {
347                 /* Server rejects AUTH */
348                 tmp = g_strdup("\"*\"");
349         }
350
351         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, tmp) < 0) {
352                 g_free(tmp);
353                 return SE_ERROR;
354         }
355         g_free(tmp);
356
357         log_print(LOG_PROTOCOL, "Sieve> [PASSWORD]\n");
358
359         return SE_OK;
360 }
361
362
363 static gint sieve_auth_cram_md5(SieveSession *session)
364 {
365         session->state = SIEVE_AUTH;
366         session->auth_type = SIEVEAUTH_CRAM_MD5;
367
368         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
369                                 "Authenticate \"CRAM-MD5\"") < 0)
370                 return SE_ERROR;
371         log_print(LOG_PROTOCOL, "Sieve> Authenticate CRAM-MD5\n");
372
373         return SE_OK;
374 }
375
376 static gint sieve_auth_plain(SieveSession *session)
377 {
378         gchar buf[MESSAGEBUFSIZE], *b64buf, *out;
379         gint len;
380
381         session->state = SIEVE_AUTH_PLAIN;
382         session->auth_type = SIEVEAUTH_PLAIN;
383
384         memset(buf, 0, sizeof buf);
385
386         /* "\0user\0password" */
387         len = sprintf(buf, "%c%s%c%s", '\0', session->user, '\0', session->pass);
388         b64buf = g_base64_encode(buf, len);
389         out = g_strconcat("Authenticate \"PLAIN\" \"", b64buf, "\"", NULL);
390         g_free(b64buf);
391
392         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, out) < 0) {
393                 g_free(out);
394                 return SE_ERROR;
395         }
396
397         g_free(out);
398
399         log_print(LOG_PROTOCOL, "Sieve> [Authenticate PLAIN]\n");
400
401         return SE_OK;
402 }
403
404 static gint sieve_auth_login(SieveSession *session)
405 {
406         session->state = SIEVE_AUTH;
407         session->auth_type = SIEVEAUTH_LOGIN;
408
409         if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
410                                 "Authenticate \"LOGIN\"") < 0)
411                 return SE_ERROR;
412         log_print(LOG_PROTOCOL, "Sieve> Authenticate LOGIN\n");
413
414         return SE_OK;
415 }
416
417 static gint sieve_auth(SieveSession *session)
418 {
419         SieveAuthType forced_auth_type = session->forced_auth_type;
420
421         if (!session->use_auth) {
422                 session->state = SIEVE_READY;
423                 sieve_connected(session, TRUE);
424                 return SE_OK;
425         }
426
427         session->state = SIEVE_AUTH;
428         sieve_error(session, _("Authenticating..."));
429
430         if ((forced_auth_type == SIEVEAUTH_CRAM_MD5 || forced_auth_type == 0) &&
431              (session->avail_auth_type & SIEVEAUTH_CRAM_MD5) != 0)
432                 return sieve_auth_cram_md5(session);
433         else if ((forced_auth_type == SIEVEAUTH_LOGIN || forced_auth_type == 0) &&
434                   (session->avail_auth_type & SIEVEAUTH_LOGIN) != 0)
435                 return sieve_auth_login(session);
436         else if ((forced_auth_type == SIEVEAUTH_PLAIN || forced_auth_type == 0) &&
437                   (session->avail_auth_type & SIEVEAUTH_PLAIN) != 0)
438                 return sieve_auth_plain(session);
439         else if (forced_auth_type == 0) {
440                 log_warning(LOG_PROTOCOL, _("No Sieve auth method available\n"));
441                 session->state = SIEVE_RETRY_AUTH;
442                 return SE_AUTHFAIL;
443         } else {
444                 log_warning(LOG_PROTOCOL, _("Selected Sieve auth method not available\n"));
445                 session->state = SIEVE_RETRY_AUTH;
446                 return SE_AUTHFAIL;
447         }
448
449         return SE_OK;
450 }
451
452 static void sieve_session_putscript_cb(SieveSession *session, SieveResult *result)
453 {
454         /* Remove script name from the beginning the response,
455          * which is added by Dovecot/Pigeonhole */
456         gchar *start, *desc = result->description;
457         gchar *end = NULL;
458         if (!desc) {
459                 /* callback just for the status */
460                 command_cb(session->current_cmd, result);
461         }
462         while (desc && desc[0]) {
463                 if ((end = strchr(desc, '\r')) ||
464                     (end = strchr(desc, '\n')))
465                         while (*end == '\n' || *end == '\r')
466                                 *end++ = '\0';
467                 if (g_str_has_prefix(desc, "NULL_") && (start = strchr(desc+5, ':'))) {
468                         desc = start+1;
469                         while (*desc == ' ')
470                                 desc++;
471                 /* TODO: match against known script name, in case it contains
472                  * weird text like ": line " */
473                 } else if ((start = strstr(desc, ": line ")) ||
474                                 (start = strstr(desc, ": error"))) {
475                         desc = start+2;
476                 }
477                 result->description = desc;
478                 command_cb(session->current_cmd, result);
479                 desc = end;
480         }
481 }
482
483 static inline gboolean response_is_ok(const char *msg)
484 {
485         return !strncmp(msg, "OK", 2) && (!msg[2] || msg[2] == ' ');
486 }
487
488 static inline gboolean response_is_no(const char *msg)
489 {
490         return !strncmp(msg, "NO", 2) && (!msg[2] || msg[2] == ' ');
491 }
492
493 static inline gboolean response_is_bye(const char *msg)
494 {
495         return !strncmp(msg, "BYE", 3) && (!msg[3] || msg[3] == ' ');
496 }
497
498 static void sieve_got_capability(SieveSession *session, gchar *cap_name,
499                 gchar *cap_value)
500 {
501         if (strcmp(cap_name, "SASL") == 0) {
502                 SieveAuthType auth_type = 0;
503                 gchar *auth, *end;
504                 for (auth = cap_value; auth && auth[0]; auth = end) {
505                         if ((end = strchr(auth, ' ')))
506                                 *end++ = '\0';
507                         if (strcmp(auth, "PLAIN") == 0) {
508                                 auth_type |= SIEVEAUTH_PLAIN;
509                         } else if (strcmp(auth, "CRAM-MD5") == 0) {
510                                 auth_type |= SIEVEAUTH_CRAM_MD5;
511                         } else if (strcmp(auth, "LOGIN") == 0) {
512                                 auth_type |= SIEVEAUTH_LOGIN;
513                         }
514                 }
515                 session->avail_auth_type = auth_type;
516
517         } else if (strcmp(cap_name, "STARTTLS") == 0) {
518                 session->capability.starttls = TRUE;
519         }
520 }
521
522 static void log_send(SieveSession *session, SieveCommand *cmd)
523 {
524         gchar *end, *msg = cmd->msg;
525         if (cmd->next_state == SIEVE_PUTSCRIPT && (end = strchr(msg, '\n'))) {
526                 /* Don't log the script data */
527                 msg = g_strndup(msg, end - msg);
528                 log_print(LOG_PROTOCOL, "Sieve> %s\n", msg);
529                 g_free(msg);
530                 msg = "[Data]";
531         }
532         log_print(LOG_PROTOCOL, "Sieve> %s\n", msg);
533 }
534
535 static gint sieve_pop_send_queue(SieveSession *session)
536 {
537         SieveCommand *cmd;
538         GSList *send_queue = session->send_queue;
539
540         if (session->current_cmd) {
541                 command_free(session->current_cmd);
542                 session->current_cmd = NULL;
543         }
544
545         if (!send_queue)
546                 return SE_OK;
547
548         cmd = (SieveCommand *)send_queue->data;
549         session->send_queue = g_slist_next(send_queue);
550         g_slist_free_1(send_queue);
551
552         log_send(session, cmd);
553         session->state = cmd->next_state;
554         session->current_cmd = cmd;
555         if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0)
556                 return SE_ERROR;
557
558         return SE_OK;
559 }
560
561 static void parse_split(gchar *line, gchar **first_word, gchar **second_word)
562 {
563         gchar *first = line;
564         gchar *second;
565         gchar *end;
566
567         /* get first */
568         if (line[0] == '"' && ((second = strchr(line + 1, '"')))) {
569                 *second++ = '\0';
570                 first++;
571                 if (second[0] == ' ')
572                         second++;
573         } else if ((second = strchr(line, ' '))) {
574                 *second++ = '\0';
575         }
576
577         /* unquote second */
578         if (second && second[0] == '"' &&
579                         ((end = strchr(second + 1, '"')))) {
580                 second++;
581                 *end = '\0';
582         }
583
584         *first_word = first;
585         *second_word = second;
586 }
587
588 static void unquote_inplace(gchar *str)
589 {
590         gchar *src, *dest;
591         if (*str != '"')
592                 return;
593         for (src = str+1, dest = str; src && *src && *src != '"'; src++) {
594                 if (*src == '\\') {
595                         src++;
596                 }
597                 *dest++ = *src;
598         }
599         *dest = '\0';
600 }
601
602 static void parse_response(gchar *msg, SieveResult *result)
603 {
604         gchar *end;
605
606         cm_return_if_fail(msg != NULL);
607
608         /* response status */
609         if (isalpha(msg[0])) {
610                 end = strchr(msg, ' ');
611                 if (end) {
612                         *end++ = '\0';
613                         while (*end == ' ')
614                                 end++;
615                 }
616                 result->success = strcmp(msg, "OK") == 0;
617                 result->has_status = TRUE;
618                 msg = end;
619         } else {
620                 result->has_status = FALSE;
621         }
622
623         /* response code */
624         if (msg && msg[0] == '(' && (end = strchr(msg, ')'))) {
625                 msg++;
626                 *end++ = '\0';
627                 result->code =
628                         strcmp(msg, "WARNINGS") == 0 ? SIEVE_CODE_WARNINGS :
629                         strcmp(msg, "TRYLATER") == 0 ? SIEVE_CODE_TRYLATER :
630                         SIEVE_CODE_UNKNOWN;
631                 while (*end == ' ')
632                         end++;
633                 msg = end;
634         } else {
635                 result->code = SIEVE_CODE_NONE;
636         }
637
638         /* s2c octets */
639         if (msg && msg[0] == '{' && (end = strchr(msg, '}'))) {
640                 msg++;
641                 *end++ = '\0';
642                 if (msg[0] == '0' && msg+1 == end) {
643                         result->has_octets = TRUE;
644                         result->octets = 0;
645                 } else {
646                         result->has_octets =
647                                 (result->octets = g_ascii_strtoll(msg, NULL, 10)) != 0;
648                 }
649                 while (*end == ' ')
650                         end++;
651                 msg = end;
652         } else {
653                 result->has_octets = FALSE;
654                 result->octets = 0;
655         }
656
657         /* text */
658         if (msg && *msg) {
659                 unquote_inplace(msg);
660                 result->description = msg;
661         } else {
662                 result->description = NULL;
663         }
664 }
665
666 static gint sieve_session_recv_msg(Session *session, const gchar *msg)
667 {
668         SieveSession *sieve_session = SIEVE_SESSION(session);
669         SieveResult result;
670         gint ret = SE_OK;
671
672         log_print(LOG_PROTOCOL, "Sieve< %s\n", msg);
673         if (response_is_bye(msg)) {
674                 gchar *status;
675                 parse_response((gchar *)msg, &result);
676                 if (!result.description)
677                         status = g_strdup(_("Disconnected"));
678                 else if (g_str_has_prefix(result.description, "Disconnected"))
679                         status = g_strdup(result.description);
680                 else
681                         status = g_strdup_printf(_("Disconnected: %s"), result.description);
682                 sieve_session->error = SE_ERROR;
683                 sieve_error(sieve_session, status);
684                 sieve_session->state = SIEVE_DISCONNECTED;
685                 g_free(status);
686                 return -1;
687         }
688
689         switch (sieve_session->state) {
690         case SIEVE_CAPABILITIES:
691                 if (response_is_ok(msg)) {
692                         /* capabilities list done */
693
694 #ifdef USE_GNUTLS
695                         if (sieve_session->tls_init_done == FALSE &&
696                                         sieve_session->config->tls_type != SIEVE_TLS_NO) {
697                                 if (sieve_session->capability.starttls) {
698                                         log_print(LOG_PROTOCOL, "Sieve> STARTTLS\n");
699                                         session_send_msg(session, SESSION_SEND, "STARTTLS");
700                                         sieve_session->state = SIEVE_STARTTLS;
701                                 } else if (sieve_session->config->tls_type == SIEVE_TLS_YES) {
702                                         log_warning(LOG_PROTOCOL, "Sieve: does not support STARTTLS\n");
703                                         sieve_session->state = SIEVE_ERROR;
704                                 } else {
705                                         log_warning(LOG_PROTOCOL, "Sieve: continuing without TLS\n");
706                                         sieve_session->state = SIEVE_READY;
707                                 }
708                                 break;
709                         }
710 #endif
711                         /* authenticate after getting capabilities */
712                         if (!sieve_session->authenticated) {
713                                 ret = sieve_auth(sieve_session);
714                         } else {
715                                 sieve_session->state = SIEVE_READY;
716                                 sieve_connected(sieve_session, TRUE);
717                         }
718                 } else {
719                         /* got a capability */
720                         gchar *cap_name, *cap_value;
721                         parse_split((gchar *)msg, &cap_name, &cap_value);
722                         sieve_got_capability(sieve_session, cap_name, cap_value);
723                 }
724                 break;
725         case SIEVE_READY:
726                 if (!msg[0])
727                         break;
728                 log_warning(LOG_PROTOCOL,
729                                 _("unhandled message on Sieve session: %s\n"), msg);
730                 break;
731         case SIEVE_STARTTLS:
732 #ifdef USE_GNUTLS
733                 if (session_start_tls(session) < 0) {
734                         sieve_session->state = SIEVE_ERROR;
735                         sieve_session->error = SE_ERROR;
736                         sieve_error(sieve_session, _("TLS failed"));
737                         return -1;
738                 }
739                 sieve_session->tls_init_done = TRUE;
740                 sieve_session->state = SIEVE_CAPABILITIES;
741 #endif
742                 break;
743         case SIEVE_AUTH:
744                 ret = sieve_auth_recv(sieve_session, msg);
745                 break;
746         case SIEVE_AUTH_LOGIN_USER:
747                 ret = sieve_auth_login_user_recv(sieve_session, msg);
748                 break;
749         case SIEVE_AUTH_PLAIN:
750         case SIEVE_AUTH_LOGIN_PASS:
751         case SIEVE_AUTH_CRAM_MD5:
752                 if (response_is_no(msg)) {
753                         log_print(LOG_PROTOCOL, "Sieve auth failed\n");
754                         session->state = SIEVE_RETRY_AUTH;
755                         ret = SE_AUTHFAIL;
756                 } else if (response_is_ok(msg)) {
757                         log_print(LOG_PROTOCOL, "Sieve auth completed\n");
758                         sieve_error(sieve_session, "");
759                         sieve_session->authenticated = TRUE;
760                         sieve_session->state = SIEVE_READY;
761                         sieve_connected(sieve_session, TRUE);
762                 }
763                 break;
764         case SIEVE_NOOP:
765                 if (!response_is_ok(msg)) {
766                         sieve_session->state = SIEVE_ERROR;
767                 }
768                 sieve_session->state = SIEVE_READY;
769                 break;
770         case SIEVE_LISTSCRIPTS:
771                 if (response_is_no(msg)) {
772                         /* got an error. probably not authenticated. */
773                         command_cb(sieve_session->current_cmd, NULL);
774                         sieve_session->state = SIEVE_READY;
775                 } else if (response_is_ok(msg)) {
776                         /* end of list */
777                         sieve_session->state = SIEVE_READY;
778                         sieve_session->error = SE_OK;
779                         command_cb(sieve_session->current_cmd,
780                                         (gpointer)&(SieveScript){0});
781                 } else {
782                         /* got a script name */
783                         SieveScript script;
784                         gchar *script_status;
785
786                         parse_split((gchar *)msg, &script.name, &script_status);
787                         script.active = (script_status &&
788                                         strcasecmp(script_status, "active") == 0);
789
790                         command_cb(sieve_session->current_cmd,
791                                         (gpointer)&script);
792                 }
793                 break;
794         case SIEVE_RENAMESCRIPT:
795                 if (response_is_no(msg)) {
796                         /* error */
797                         command_cb(sieve_session->current_cmd, NULL);
798                 } else if (response_is_ok(msg)) {
799                         command_cb(sieve_session->current_cmd, (void*)TRUE);
800                 } else {
801                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
802                 }
803                 sieve_session->state = SIEVE_READY;
804                 break;
805         case SIEVE_SETACTIVE:
806                 parse_response((gchar *)msg, &result);
807                 if (result.success) {
808                         /* clear status possibly set when setting another
809                          * script active. TODO: give textual feedback */
810                         sieve_error(sieve_session, "");
811
812                         command_cb(sieve_session->current_cmd, NULL);
813                 } else if (result.description) {
814                         command_cb(sieve_session->current_cmd,
815                                         result.description);
816                 } else {
817                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
818                 }
819                 if (result.has_octets) {
820                         return sieve_session_recv_chunk(sieve_session,
821                                         result.octets);
822                 } else {
823                         sieve_session->state = SIEVE_READY;
824                 }
825                 break;
826         case SIEVE_GETSCRIPT:
827                 if (response_is_no(msg)) {
828                         command_cb(sieve_session->current_cmd, (void *)-1);
829                         sieve_session->state = SIEVE_READY;
830                 } else {
831                         parse_response((gchar *)msg, &result);
832                         sieve_session->state = SIEVE_GETSCRIPT_DATA;
833                         return sieve_session_recv_chunk(sieve_session,
834                                         result.octets);
835                 }
836                 break;
837         case SIEVE_GETSCRIPT_DATA:
838                 if (!msg[0])
839                         break;
840                 sieve_session->state = SIEVE_READY;
841                 if (response_is_ok(msg)) {
842                         command_cb(sieve_session->current_cmd, NULL);
843                 } else if (msg[0]) {
844                         log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
845                 }
846                 break;
847         case SIEVE_PUTSCRIPT:
848                 if (!msg[0])
849                         break;
850                 parse_response((gchar *)msg, &result);
851                 sieve_session_putscript_cb(sieve_session, &result);
852                 if (result.has_octets) {
853                         return sieve_session_recv_chunk(sieve_session,
854                                         result.octets);
855                 } else {
856                         sieve_session->state = SIEVE_READY;
857                 }
858                 break;
859         case SIEVE_DELETESCRIPT:
860                 parse_response((gchar *)msg, &result);
861                 if (!result.success) {
862                         command_cb(sieve_session->current_cmd,
863                                         result.description);
864                 } else {
865                         command_cb(sieve_session->current_cmd, NULL);
866                 }
867                 sieve_session->state = SIEVE_READY;
868                 break;
869         case SIEVE_ERROR:
870                 log_warning(LOG_PROTOCOL, _("error occurred on Sieve session. data: %s\n"), msg);
871                 sieve_session->error = SE_ERROR;
872                 break;
873         case SIEVE_RETRY_AUTH:
874                 log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %s\n"),
875                                         msg);
876                 ret = sieve_auth(sieve_session);
877                 break;
878         default:
879                 log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %d\n"),
880                                         sieve_session->state);
881                 sieve_session->error = SE_ERROR;
882                 return -1;
883         }
884
885         if (ret == SE_OK && sieve_session->state == SIEVE_READY)
886                 ret = sieve_pop_send_queue(sieve_session);
887
888         if (ret == SE_OK) {
889                 return session_recv_msg(session);
890         } else if (ret == SE_AUTHFAIL) {
891                 sieve_error(sieve_session, _("Auth failed"));
892                 sieve_session->state = SIEVE_ERROR;
893                 sieve_session->error = SE_ERROR;
894         }
895
896         return 0;
897 }
898
899 static gint sieve_recv_message(Session *session, const gchar *msg,
900                 gpointer user_data)
901 {
902         return 0;
903 }
904
905 static void sieve_read_chunk(SieveSession *session, gchar *data, guint len)
906 {
907         log_print(LOG_PROTOCOL, "Sieve< [%u bytes]\n", len);
908
909         switch (session->state) {
910         case SIEVE_GETSCRIPT_DATA:
911                 command_cb(session->current_cmd, (gchar *)data);
912                 break;
913         case SIEVE_SETACTIVE:
914                 /* Dovecot shows a script's warnings when making it active */
915                 /* TODO: append message in case it is very long*/
916                 strretchomp(data);
917                 sieve_error(session, data);
918                 break;
919         case SIEVE_PUTSCRIPT: {
920                 SieveResult result = {.description = (gchar *)data};
921                 sieve_session_putscript_cb(session, &result);
922                 break;
923         }
924         default:
925                 log_warning(LOG_PROTOCOL,
926                                 _("error occurred on SIEVE session\n"));
927         }
928 }
929
930 static gint sieve_read_chunk_done(SieveSession *session)
931 {
932         gint ret = SE_OK;
933
934         switch (session->state) {
935         case SIEVE_GETSCRIPT_DATA:
936                 /* wait for ending "OK" response */
937                 break;
938         case SIEVE_SETACTIVE:
939         case SIEVE_PUTSCRIPT:
940                 session->state = SIEVE_READY;
941                 break;
942         default:
943                 log_warning(LOG_PROTOCOL,
944                                 _("error occurred on SIEVE session\n"));
945         }
946
947         if (ret == SE_OK && session->state == SIEVE_READY)
948                 ret = sieve_pop_send_queue(session);
949
950         if (ret == SE_OK)
951                 return session_recv_msg(SESSION(session));
952
953         return 0;
954 }
955
956 static gint sieve_cmd_noop(SieveSession *session)
957 {
958         log_print(LOG_PROTOCOL, "Sieve> NOOP\n");
959         session->state = SIEVE_NOOP;
960         if (session_send_msg(SESSION(session), SESSION_SEND, "NOOP") < 0) {
961                 session->state = SIEVE_ERROR;
962                 session->error = SE_ERROR;
963                 return 1;
964         }
965         return 0;
966 }
967
968 static gboolean sieve_ping(gpointer data)
969 {
970         Session *session = SESSION(data);
971         SieveSession *sieve_session = SIEVE_SESSION(session);
972
973         if (sieve_session->state == SIEVE_ERROR || session->state == SESSION_ERROR)
974                 return FALSE;
975         if (sieve_session->state != SIEVE_READY)
976                 return TRUE;
977
978         return sieve_cmd_noop(sieve_session) == 0;
979 }
980
981 static void sieve_session_destroy(Session *session)
982 {
983         SieveSession *sieve_session = SIEVE_SESSION(session);
984         g_free(sieve_session->pass);
985         if (sieve_session->current_cmd)
986                 command_abort(sieve_session->current_cmd);
987         sessions = g_slist_remove(sessions, (gconstpointer)session);
988         g_slist_free_full(sieve_session->send_queue,
989                         (GDestroyNotify)command_abort);
990 }
991
992 static void sieve_connect_finished(Session *session, gboolean success)
993 {
994         if (!success) {
995                 sieve_connected(SIEVE_SESSION(session), FALSE);
996         }
997 }
998
999 static gint sieve_session_connect(SieveSession *session)
1000 {
1001         session->state = SIEVE_CAPABILITIES;
1002         session->authenticated = FALSE;
1003 #ifdef USE_GNUTLS
1004         session->tls_init_done = FALSE;
1005 #endif
1006         return session_connect(SESSION(session), session->host,
1007                         session->port);
1008 }
1009
1010 static SieveSession *sieve_session_new(PrefsAccount *account)
1011 {
1012         SieveSession *session;
1013         session = g_new0(SieveSession, 1);
1014         session_init(SESSION(session), account, FALSE);
1015
1016         session->account = account;
1017
1018         SESSION(session)->recv_msg = sieve_session_recv_msg;
1019         SESSION(session)->destroy = sieve_session_destroy;
1020         SESSION(session)->connect_finished = sieve_connect_finished;
1021         session_set_recv_message_notify(SESSION(session), sieve_recv_message, NULL);
1022
1023         sieve_session_reset(session);
1024         return session;
1025 }
1026
1027 static void sieve_session_reset(SieveSession *session)
1028 {
1029         PrefsAccount *account = session->account;
1030         SieveAccountConfig *config = sieve_prefs_account_get_config(account);
1031         gboolean reuse_auth = (config->auth == SIEVEAUTH_REUSE);
1032
1033         g_slist_free_full(session->send_queue, (GDestroyNotify)command_abort);
1034
1035         session_disconnect(SESSION(session));
1036
1037         SESSION(session)->ssl_cert_auto_accept = account->ssl_certs_auto_accept;
1038         SESSION(session)->nonblocking = account->use_nonblocking_ssl;
1039         session->authenticated = FALSE;
1040         session->current_cmd = NULL;
1041         session->send_queue = NULL;
1042         session->state = SIEVE_CAPABILITIES;
1043 #ifdef USE_GNUTLS
1044         session->tls_init_done = FALSE;
1045 #endif
1046         session->avail_auth_type = 0;
1047         session->auth_type = 0;
1048         session->config = config;
1049         session->host = config->use_host ? config->host : account->recv_server;
1050         session->port = config->use_port ? config->port : SIEVE_PORT;
1051         session->user = reuse_auth ? account->userid : session->config->userid;
1052         session->forced_auth_type = config->auth_type;
1053         session_register_ping(SESSION(session), sieve_ping);
1054
1055         if (session->pass)
1056                 g_free(session->pass);
1057         if (config->auth == SIEVEAUTH_NONE) {
1058                 session->pass = NULL;
1059         } else if (reuse_auth && (session->pass = passwd_store_get(PWS_ACCOUNT,
1060                                 account->account_name, PWS_ACCOUNT_RECV))) {
1061         } else if ((session->pass = passwd_store_get(PWS_ACCOUNT,
1062                                 account->account_name, "sieve"))) {
1063         } else if (password_get(session->user, session->host, "sieve",
1064                                 session->port, &session->pass)) {
1065         } else {
1066                 session->pass = input_dialog_query_password_keep(session->host,
1067                                 session->user, &(session->pass));
1068         }
1069         if (!session->pass) {
1070                 session->pass = g_strdup("");
1071                 session->use_auth = FALSE;
1072         } else {
1073                 session->use_auth = TRUE;
1074         }
1075
1076 #ifdef USE_GNUTLS
1077         SESSION(session)->ssl_type =
1078                 (config->tls_type == SIEVE_TLS_NO) ? SSL_NONE : SSL_STARTTLS;
1079 #endif
1080 }
1081
1082 /* When an account config is changed, reset associated sessions. */
1083 void sieve_account_prefs_updated(PrefsAccount *account)
1084 {
1085         GSList *item;
1086         SieveSession *session;
1087
1088         for (item = sessions; item; item = item->next) {
1089                 session = (SieveSession *)item->data;
1090                 if (session->account == account) {
1091                         log_print(LOG_PROTOCOL, "Sieve: resetting session\n");
1092                         sieve_session_reset(session);
1093                 }
1094         }
1095 }
1096
1097 SieveSession *sieve_session_get_for_account(PrefsAccount *account)
1098 {
1099         SieveSession *session;
1100         GSList *item;
1101
1102         /* find existing */
1103         for (item = sessions; item; item = item->next) {
1104                 session = (SieveSession *)item->data;
1105                 if (session->account == account) {
1106                         return session;
1107                 }
1108         }
1109
1110         /* create new */
1111         session = sieve_session_new(account);
1112         sessions = g_slist_prepend(sessions, session);
1113
1114         return session;
1115 }
1116
1117 static void sieve_queue_send(SieveSession *session, SieveState next_state,
1118                 gchar *msg, sieve_session_data_cb_fn cb, gpointer data)
1119 {
1120         gboolean queue = FALSE;
1121         SieveCommand *cmd = g_new0(SieveCommand, 1);
1122         cmd->session = session;
1123         cmd->next_state = next_state;
1124         cmd->msg = msg;
1125         cmd->data = data;
1126         cmd->cb = cb;
1127
1128         if (!session_is_connected(SESSION(session))) {
1129                 log_print(LOG_PROTOCOL, "Sieve: connecting to %s:%hu\n",
1130                                 session->host, session->port);
1131                 if (sieve_session_connect(session) < 0) {
1132                         sieve_connect_finished(SESSION(session), FALSE);
1133                 }
1134                 queue = TRUE;
1135         } else if (session->state == SIEVE_RETRY_AUTH) {
1136                 log_print(LOG_PROTOCOL, _("Sieve: retrying auth\n"));
1137                 if (sieve_auth(session) == SE_AUTHFAIL)
1138                         sieve_error(session, _("Auth method not available"));
1139                 queue = TRUE;
1140         } else if (session->state != SIEVE_READY) {
1141                 log_print(LOG_PROTOCOL, "Sieve: in state %d\n", session->state);
1142                 queue = TRUE;
1143         }
1144
1145         if (queue) {
1146                 session->send_queue = g_slist_prepend(session->send_queue, cmd);
1147         } else {
1148                 if (session->current_cmd)
1149                         command_free(session->current_cmd);
1150                 session->current_cmd = cmd;
1151                 session->state = next_state;
1152                 log_send(session, cmd);
1153                 if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0) {
1154                         /* error */
1155                 }
1156         }
1157 }
1158
1159 void sieve_session_list_scripts(SieveSession *session,
1160                 sieve_session_data_cb_fn cb, gpointer data)
1161 {
1162         gchar *msg = g_strdup("LISTSCRIPTS");
1163         sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
1164 }
1165
1166 void sieve_session_set_active_script(SieveSession *session,
1167                 const gchar *filter_name,
1168                 sieve_session_data_cb_fn cb, gpointer data)
1169 {
1170         gchar *msg = g_strdup_printf("SETACTIVE \"%s\"",
1171                         filter_name ? filter_name : "");
1172         if (!msg) {
1173                 cb(session, FALSE, (void*)FALSE, data);
1174                 return;
1175         }
1176
1177         sieve_queue_send(session, SIEVE_SETACTIVE, msg, cb, data);
1178 }
1179
1180 void sieve_session_rename_script(SieveSession *session,
1181                 const gchar *name_old, const char *name_new,
1182                 sieve_session_data_cb_fn cb, gpointer data)
1183 {
1184         gchar *msg = g_strdup_printf("RENAMESCRIPT \"%s\" \"%s\"",
1185                         name_old, name_new);
1186
1187         sieve_queue_send(session, SIEVE_RENAMESCRIPT, msg, cb, data);
1188 }
1189
1190 void sieve_session_get_script(SieveSession *session, const gchar *filter_name,
1191                 sieve_session_data_cb_fn cb, gpointer data)
1192 {
1193         gchar *msg = g_strdup_printf("GETSCRIPT \"%s\"",
1194                         filter_name);
1195
1196         sieve_queue_send(session, SIEVE_GETSCRIPT, msg, cb, data);
1197 }
1198
1199 void sieve_session_put_script(SieveSession *session, const gchar *filter_name,
1200                 gint len, const gchar *script_contents,
1201                 sieve_session_data_cb_fn cb, gpointer data)
1202 {
1203         /* TODO: refactor so don't have to copy the whole script here */
1204         gchar *msg = g_strdup_printf("PUTSCRIPT \"%s\" {%u+}%s%s",
1205                         filter_name, len, len > 0 ? "\r\n" : "",
1206                         script_contents);
1207
1208         sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
1209 }
1210
1211 void sieve_session_check_script(SieveSession *session,
1212                 gint len, const gchar *script_contents,
1213                 sieve_session_data_cb_fn cb, gpointer data)
1214 {
1215         gchar *msg = g_strdup_printf("CHECKSCRIPT {%u+}%s%s",
1216                         len, len > 0 ? "\r\n" : "", script_contents);
1217
1218         sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
1219 }
1220
1221 void sieve_session_delete_script(SieveSession *session,
1222                 const gchar *filter_name,
1223                 sieve_session_data_cb_fn cb, gpointer data)
1224 {
1225         gchar *msg = g_strdup_printf("DELETESCRIPT \"%s\"",
1226                         filter_name);
1227
1228         sieve_queue_send(session, SIEVE_DELETESCRIPT, msg, cb, data);
1229 }