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