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