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