1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24 
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdarg.h>
30 #include <ctype.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <errno.h>
34 
35 #include "pop.h"
36 #include "md5.h"
37 #include "prefs_account.h"
38 #include "utils.h"
39 #include "recv.h"
40 #include "partial_download.h"
41 #include "log.h"
42 #include "hooks.h"
43 #include "file-utils.h"
44 
45 static gint pop3_greeting_recv		(Pop3Session *session,
46 					 const gchar *msg);
47 static gint pop3_getauth_user_send	(Pop3Session *session);
48 static gint pop3_getauth_pass_send	(Pop3Session *session);
49 static gint pop3_getauth_apop_send	(Pop3Session *session);
50 #ifdef USE_GNUTLS
51 static gint pop3_stls_send		(Pop3Session *session);
52 static gint pop3_stls_recv		(Pop3Session *session);
53 #endif
54 static gint pop3_getrange_stat_send	(Pop3Session *session);
55 static gint pop3_getrange_stat_recv	(Pop3Session *session,
56 					 const gchar *msg);
57 static gint pop3_getrange_last_send	(Pop3Session *session);
58 static gint pop3_getrange_last_recv	(Pop3Session *session,
59 					 const gchar *msg);
60 static gint pop3_getrange_uidl_send	(Pop3Session *session);
61 static gint pop3_getrange_uidl_recv	(Pop3Session *session,
62 					 const gchar *data,
63 					 guint        len);
64 static gint pop3_getsize_list_send	(Pop3Session *session);
65 static gint pop3_getsize_list_recv	(Pop3Session *session,
66 					 const gchar *data,
67 					 guint        len);
68 static gint pop3_retr_send		(Pop3Session *session);
69 static gint pop3_retr_recv		(Pop3Session *session,
70 					 const gchar *data,
71 					 guint        len);
72 static gint pop3_delete_send		(Pop3Session *session);
73 static gint pop3_delete_recv		(Pop3Session *session);
74 static gint pop3_logout_send		(Pop3Session *session);
75 
76 static void pop3_gen_send		(Pop3Session	*session,
77 					 const gchar	*format, ...);
78 
79 static void pop3_session_destroy	(Session	*session);
80 
81 static gint pop3_write_msg_to_file	(const gchar	*file,
82 					 const gchar	*data,
83 					 guint		 len,
84 					 const gchar 	*prefix);
85 
86 static Pop3State pop3_lookup_next	(Pop3Session	*session);
87 static Pop3ErrorValue pop3_ok		(Pop3Session	*session,
88 					 const gchar	*msg);
89 
90 static gint pop3_session_recv_msg		(Session	*session,
91 						 const gchar	*msg);
92 static gint pop3_session_recv_data_finished	(Session	*session,
93 						 guchar		*data,
94 						 guint		 len);
95 static void pop3_get_uidl_table(PrefsAccount *ac_prefs, Pop3Session *session);
96 
pop3_greeting_recv(Pop3Session * session,const gchar * msg)97 static gint pop3_greeting_recv(Pop3Session *session, const gchar *msg)
98 {
99 	session->state = POP3_GREETING;
100 
101 	session->greeting = g_strdup(msg);
102 	return PS_SUCCESS;
103 }
104 
105 #ifdef USE_GNUTLS
pop3_stls_send(Pop3Session * session)106 static gint pop3_stls_send(Pop3Session *session)
107 {
108 	session->state = POP3_STLS;
109 	pop3_gen_send(session, "STLS");
110 	return PS_SUCCESS;
111 }
112 
pop3_stls_recv(Pop3Session * session)113 static gint pop3_stls_recv(Pop3Session *session)
114 {
115 	if (session_start_tls(SESSION(session)) < 0) {
116 		session->error_val = PS_SOCKET;
117 		return -1;
118 	}
119 	return PS_SUCCESS;
120 }
121 #endif /* USE_GNUTLS */
122 
pop3_getauth_user_send(Pop3Session * session)123 static gint pop3_getauth_user_send(Pop3Session *session)
124 {
125 	cm_return_val_if_fail(session->user != NULL, -1);
126 
127 	session->state = POP3_GETAUTH_USER;
128 	pop3_gen_send(session, "USER %s", session->user);
129 	return PS_SUCCESS;
130 }
131 
pop3_getauth_pass_send(Pop3Session * session)132 static gint pop3_getauth_pass_send(Pop3Session *session)
133 {
134 	cm_return_val_if_fail(session->pass != NULL, -1);
135 
136 	session->state = POP3_GETAUTH_PASS;
137 	pop3_gen_send(session, "PASS %s", session->pass);
138 	return PS_SUCCESS;
139 }
140 
pop3_getauth_apop_send(Pop3Session * session)141 static gint pop3_getauth_apop_send(Pop3Session *session)
142 {
143 	gchar *start, *end;
144 	gchar *apop_str;
145 	gchar md5sum[33];
146 
147 	cm_return_val_if_fail(session->user != NULL, -1);
148 	cm_return_val_if_fail(session->pass != NULL, -1);
149 
150 	session->state = POP3_GETAUTH_APOP;
151 
152 	if ((start = strchr(session->greeting, '<')) == NULL) {
153 		log_error(LOG_PROTOCOL, _("Required APOP timestamp not found "
154 			    "in greeting\n"));
155 		session->error_val = PS_PROTOCOL;
156 		return -1;
157 	}
158 
159 	if ((end = strchr(start, '>')) == NULL || end == start + 1) {
160 		log_error(LOG_PROTOCOL, _("Timestamp syntax error in greeting\n"));
161 		session->error_val = PS_PROTOCOL;
162 		return -1;
163 	}
164 	*(end + 1) = '\0';
165 
166 	if (!is_ascii_str(start)) {
167 		log_error(LOG_PROTOCOL, _("Timestamp syntax error in greeting (not ASCII)\n"));
168 		session->error_val = PS_PROTOCOL;
169 		return -1;
170 	}
171 
172 	apop_str = g_strconcat(start, session->pass, NULL);
173 	md5_hex_digest(md5sum, apop_str);
174 	g_free(apop_str);
175 
176 	pop3_gen_send(session, "APOP %s %s", session->user, md5sum);
177 
178 	return PS_SUCCESS;
179 }
180 
pop3_getauth_oauth2_send(Pop3Session * session)181 static gint pop3_getauth_oauth2_send(Pop3Session *session)
182 {
183 	gchar buf[MESSAGEBUFSIZE], *b64buf, *out;
184 	gint len;
185 
186 	cm_return_val_if_fail(session->user != NULL, -1);
187 	cm_return_val_if_fail(session->pass != NULL, -1);
188 
189 	session->state = POP3_GETAUTH_OAUTH2;
190 	memset(buf, 0, sizeof buf);
191 
192 	/* "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"*/
193        /* session->pass contains the OAUTH2 Access Token*/
194 	len = sprintf(buf, "user=%s\1auth=Bearer %s\1\1", session->user, session->pass);
195 	b64buf = g_base64_encode(buf, len);
196 	out = g_strconcat("AUTH XOAUTH2 ", b64buf, NULL);
197 	g_free(b64buf);
198 
199 	pop3_gen_send(session, "%s", out);
200         /* Any error response contains base64({JSON-Body}) containing three values: status, schemes, and scope */
201         /* This could be dealt with but is currently written to the log in a fairly graceful fail - not crucial */
202 	g_free(out);
203 	return PS_SUCCESS;
204 }
205 
pop3_getrange_stat_send(Pop3Session * session)206 static gint pop3_getrange_stat_send(Pop3Session *session)
207 {
208 	session->state = POP3_GETRANGE_STAT;
209 	pop3_gen_send(session, "STAT");
210 	return PS_SUCCESS;
211 }
212 
pop3_getrange_stat_recv(Pop3Session * session,const gchar * msg)213 static gint pop3_getrange_stat_recv(Pop3Session *session, const gchar *msg)
214 {
215 	if (sscanf(msg, "%d %d", &session->count, &session->total_bytes) != 2) {
216 		log_error(LOG_PROTOCOL, _("POP protocol error\n"));
217 		session->error_val = PS_PROTOCOL;
218 		return -1;
219 	} else {
220 		if (session->count == 0) {
221 			session->uidl_is_valid = TRUE;
222 		} else {
223 			session->msg = g_new0(Pop3MsgInfo, session->count + 1);
224 			session->cur_msg = 1;
225 		}
226 	}
227 
228 	return PS_SUCCESS;
229 }
230 
pop3_getrange_last_send(Pop3Session * session)231 static gint pop3_getrange_last_send(Pop3Session *session)
232 {
233 	session->state = POP3_GETRANGE_LAST;
234 	pop3_gen_send(session, "LAST");
235 	return PS_SUCCESS;
236 }
237 
pop3_getrange_last_recv(Pop3Session * session,const gchar * msg)238 static gint pop3_getrange_last_recv(Pop3Session *session, const gchar *msg)
239 {
240 	gint last;
241 
242 	if (sscanf(msg, "%d", &last) == 0) {
243 		log_warning(LOG_PROTOCOL, _("POP protocol error\n"));
244 		session->error_val = PS_PROTOCOL;
245 		return -1;
246 	} else {
247 		if (session->count > last) {
248 			session->new_msg_exist = TRUE;
249 			session->cur_msg = last + 1;
250 		} else
251 			session->cur_msg = 0;
252 	}
253 
254 	return PS_SUCCESS;
255 }
256 
pop3_getrange_uidl_send(Pop3Session * session)257 static gint pop3_getrange_uidl_send(Pop3Session *session)
258 {
259 	session->state = POP3_GETRANGE_UIDL;
260 	pop3_gen_send(session, "UIDL");
261 	return PS_SUCCESS;
262 }
263 
pop3_getrange_uidl_recv(Pop3Session * session,const gchar * data,guint len)264 static gint pop3_getrange_uidl_recv(Pop3Session *session, const gchar *data,
265 				    guint len)
266 {
267 	gchar id[IDLEN + 1];
268 	gchar buf[POPBUFSIZE];
269 	gint buf_len;
270 	guint32 num;
271 	time_t recv_time;
272 	gint partial_recv;
273 	const gchar *p = data;
274 	const gchar *lastp = data + len;
275 	const gchar *newline;
276 
277 	while (p < lastp) {
278 		if ((newline = memchr(p, '\r', lastp - p)) == NULL)
279 			return -1;
280 		buf_len = MIN(newline - p, sizeof(buf) - 1);
281 		memcpy(buf, p, buf_len);
282 		buf[buf_len] = '\0';
283 
284 		p = newline + 1;
285 		if (p < lastp && *p == '\n') p++;
286 
287 		if (sscanf(buf, "%d %" Xstr(IDLEN) "s", &num, id) != 2 ||
288 		    num <= 0 || num > session->count) {
289 			log_warning(LOG_PROTOCOL, _("invalid UIDL response: %s\n"), buf);
290 			continue;
291 		}
292 
293 		session->msg[num].uidl = g_strdup(id);
294 
295 		recv_time = (time_t)(GPOINTER_TO_INT(g_hash_table_lookup(
296 			   		session->uidl_table, id)));
297 		session->msg[num].recv_time = recv_time;
298 
299 		if (recv_time != RECV_TIME_NONE) {
300 			debug_print("num %d uidl %s: already got it\n", num, id);
301 		} else {
302 			debug_print("num %d uidl %s: unknown\n", num, id);
303 		}
304 
305 		partial_recv = (gint)(GPOINTER_TO_INT(g_hash_table_lookup(
306 					session->partial_recv_table, id)));
307 
308 		if (recv_time != RECV_TIME_NONE
309 		|| partial_recv != POP3_TOTALLY_RECEIVED) {
310 			session->msg[num].received =
311 				(partial_recv != POP3_MUST_COMPLETE_RECV);
312 			session->msg[num].partial_recv = partial_recv;
313 			if (partial_recv == POP3_MUST_COMPLETE_RECV)
314 				session->new_msg_exist = TRUE;
315 		}
316 		if (!session->new_msg_exist &&
317 		    (recv_time == RECV_TIME_NONE ||
318 		     session->ac_prefs->rmmail)) {
319 			session->cur_msg = num;
320 			session->new_msg_exist = TRUE;
321 		}
322 	}
323 
324 	session->uidl_is_valid = TRUE;
325 	return PS_SUCCESS;
326 }
327 
pop3_getsize_list_send(Pop3Session * session)328 static gint pop3_getsize_list_send(Pop3Session *session)
329 {
330 	session->state = POP3_GETSIZE_LIST;
331 	pop3_gen_send(session, "LIST");
332 	return PS_SUCCESS;
333 }
334 
pop3_getsize_list_recv(Pop3Session * session,const gchar * data,guint len)335 static gint pop3_getsize_list_recv(Pop3Session *session, const gchar *data,
336 				   guint len)
337 {
338 	gchar buf[POPBUFSIZE];
339 	gint buf_len;
340 	guint num, size;
341 	const gchar *p = data;
342 	const gchar *lastp = data + len;
343 	const gchar *newline;
344 
345 	while (p < lastp) {
346 		if ((newline = memchr(p, '\r', lastp - p)) == NULL)
347 			return -1;
348 		buf_len = MIN(newline - p, sizeof(buf) - 1);
349 		memcpy(buf, p, buf_len);
350 		buf[buf_len] = '\0';
351 
352 		p = newline + 1;
353 		if (p < lastp && *p == '\n') p++;
354 
355 		if (sscanf(buf, "%u %u", &num, &size) != 2) {
356 			session->error_val = PS_PROTOCOL;
357 			return -1;
358 		}
359 
360 		if (num > 0 && num <= session->count)
361 			session->msg[num].size = size;
362 		if (num > 0 && num < session->cur_msg)
363 			session->cur_total_bytes += size;
364 	}
365 
366 	return PS_SUCCESS;
367 }
368 
pop3_retr_send(Pop3Session * session)369 static gint pop3_retr_send(Pop3Session *session)
370 {
371 	session->state = POP3_RETR;
372 	debug_print("retrieving %d [%s]\n", session->cur_msg,
373 		session->msg[session->cur_msg].uidl ?
374 		 session->msg[session->cur_msg].uidl:" ");
375 	pop3_gen_send(session, "RETR %d", session->cur_msg);
376 	return PS_SUCCESS;
377 }
378 
pop3_retr_recv(Pop3Session * session,const gchar * data,guint len)379 static gint pop3_retr_recv(Pop3Session *session, const gchar *data, guint len)
380 {
381 	gchar *file;
382 	gint drop_ok;
383 	MailReceiveData mail_receive_data;
384 
385 	/* NOTE: we allocate a slightly larger buffer with a zero terminator
386 	 * because some plugins may think that it has a C string. */
387 	mail_receive_data.session  = session;
388 	mail_receive_data.data     = g_new0(gchar, len + 1);
389 	mail_receive_data.data_len = len;
390 	memcpy(mail_receive_data.data, data, len);
391 
392 	hooks_invoke(MAIL_RECEIVE_HOOKLIST, &mail_receive_data);
393 
394 	file = get_tmp_file();
395 	if (pop3_write_msg_to_file(file, mail_receive_data.data,
396 				   mail_receive_data.data_len, NULL) < 0) {
397 		g_free(file);
398 		g_free(mail_receive_data.data);
399 		session->error_val = PS_IOERR;
400 		return -1;
401 	}
402 	g_free(mail_receive_data.data);
403 
404 	if (session->msg[session->cur_msg].partial_recv
405 	    == POP3_MUST_COMPLETE_RECV) {
406 		gchar *old_file = partial_get_filename(
407 				session->ac_prefs->recv_server,
408 				session->ac_prefs->userid,
409 				session->msg[session->cur_msg].uidl);
410 
411 		if (old_file) {
412 			partial_delete_old(old_file);
413 			g_free(old_file);
414 		}
415 	}
416 
417 	/* drop_ok: 0: success 1: don't receive -1: error */
418 	drop_ok = session->drop_message(session, file);
419 
420 	g_free(file);
421 	if (drop_ok < 0) {
422 		session->error_val = PS_IOERR;
423 		return -1;
424 	}
425 
426 	session->cur_total_bytes += session->msg[session->cur_msg].size;
427 	session->cur_total_recv_bytes += session->msg[session->cur_msg].size;
428 	session->cur_total_num++;
429 
430 	session->msg[session->cur_msg].received = TRUE;
431 	session->msg[session->cur_msg].partial_recv = POP3_TOTALLY_RECEIVED;
432 
433 	session->msg[session->cur_msg].recv_time =
434 		drop_ok == 1 ? RECV_TIME_KEEP : session->current_time;
435 
436 	return PS_SUCCESS;
437 }
438 
pop3_top_send(Pop3Session * session,gint max_size)439 static gint pop3_top_send(Pop3Session *session, gint max_size)
440 {
441 	gint num_lines = (max_size*1024)/82; /* consider lines to be 80 chars */
442 	session->state = POP3_TOP;
443 	pop3_gen_send(session, "TOP %d %d", session->cur_msg, num_lines);
444 	return PS_SUCCESS;
445 }
446 
pop3_top_recv(Pop3Session * session,const gchar * data,guint len)447 static gint pop3_top_recv(Pop3Session *session, const gchar *data, guint len)
448 {
449 	gchar *file;
450 	gint drop_ok;
451 	MailReceiveData mail_receive_data;
452 	gchar *partial_notice = NULL;
453 
454 	/* NOTE: we allocate a slightly larger buffer with a zero terminator
455 	 * because some plugins may think that it has a C string. */
456 	mail_receive_data.session  = session;
457 	mail_receive_data.data     = g_new0(gchar, len + 1);
458 	mail_receive_data.data_len = len;
459 	memcpy(mail_receive_data.data, data, len);
460 
461 	hooks_invoke(MAIL_RECEIVE_HOOKLIST, &mail_receive_data);
462 
463 	partial_notice = g_strdup_printf("SC-Marked-For-Download: 0\n"
464 					 "SC-Partially-Retrieved: %s\n"
465 					 "SC-Account-Server: %s\n"
466 					 "SC-Account-Login: %s\n"
467 					 "SC-Message-Size: %d",
468 					 session->msg[session->cur_msg].uidl,
469 					 session->ac_prefs->recv_server,
470 			   		 session->ac_prefs->userid,
471 					 session->msg[session->cur_msg].size);
472 	file = get_tmp_file();
473 	if (pop3_write_msg_to_file(file, mail_receive_data.data,
474 				   mail_receive_data.data_len,
475 				   partial_notice) < 0) {
476 		g_free(file);
477 		g_free(mail_receive_data.data);
478 		session->error_val = PS_IOERR;
479 		g_free(partial_notice);
480 		return -1;
481 	}
482 	g_free(mail_receive_data.data);
483 	g_free(partial_notice);
484 
485 	/* drop_ok: 0: success 1: don't receive -1: error */
486 	drop_ok = session->drop_message(session, file);
487 	g_free(file);
488 	if (drop_ok < 0) {
489 		session->error_val = PS_IOERR;
490 		return -1;
491 	}
492 
493 	session->cur_total_bytes += session->msg[session->cur_msg].size;
494 	session->cur_total_recv_bytes += session->msg[session->cur_msg].size;
495 	session->cur_total_num++;
496 
497 	session->msg[session->cur_msg].received = TRUE;
498 	session->msg[session->cur_msg].partial_recv = POP3_PARTIALLY_RECEIVED;
499 	session->msg[session->cur_msg].recv_time =
500 		drop_ok == 1 ? RECV_TIME_KEEP : session->current_time;
501 
502 	return PS_SUCCESS;
503 }
504 
pop3_delete_send(Pop3Session * session)505 static gint pop3_delete_send(Pop3Session *session)
506 {
507 	session->state = POP3_DELETE;
508 	pop3_gen_send(session, "DELE %d", session->cur_msg);
509 	return PS_SUCCESS;
510 }
511 
pop3_delete_recv(Pop3Session * session)512 static gint pop3_delete_recv(Pop3Session *session)
513 {
514 	session->msg[session->cur_msg].deleted = TRUE;
515 	return PS_SUCCESS;
516 }
517 
pop3_logout_send(Pop3Session * session)518 static gint pop3_logout_send(Pop3Session *session)
519 {
520 	session->state = POP3_LOGOUT;
521 	pop3_gen_send(session, "QUIT");
522 	return PS_SUCCESS;
523 }
524 
pop3_gen_send(Pop3Session * session,const gchar * format,...)525 static void pop3_gen_send(Pop3Session *session, const gchar *format, ...)
526 {
527 	gchar buf[POPBUFSIZE + 1];
528 	va_list args;
529 
530 	va_start(args, format);
531 	g_vsnprintf(buf, sizeof(buf) - 2, format, args);
532 	va_end(args);
533 
534 	if (!g_ascii_strncasecmp(buf, "PASS ", 5))
535 		log_print(LOG_PROTOCOL, "POP> PASS ********\n");
536         else if  (!g_ascii_strncasecmp(buf, "AUTH XOAUTH2 ", 13))
537 		log_print(LOG_PROTOCOL, "POP> AUTH XOAUTH2  ********\n");
538 	else
539 		log_print(LOG_PROTOCOL, "POP> %s\n", buf);
540 
541 	session_send_msg(SESSION(session), buf);
542 }
543 
pop3_session_new(PrefsAccount * account)544 Session *pop3_session_new(PrefsAccount *account)
545 {
546 	Pop3Session *session;
547 
548 	cm_return_val_if_fail(account != NULL, NULL);
549 
550 	account->receive_in_progress = TRUE;
551 
552 	session = g_new0(Pop3Session, 1);
553 
554 	session_init(SESSION(session), account, FALSE);
555 
556 	SESSION(session)->type = SESSION_POP3;
557 
558 	SESSION(session)->recv_msg = pop3_session_recv_msg;
559 	SESSION(session)->recv_data_finished = pop3_session_recv_data_finished;
560 	SESSION(session)->send_data_finished = NULL;
561 	SESSION(session)->ssl_cert_auto_accept = account->ssl_certs_auto_accept;
562 	SESSION(session)->destroy = pop3_session_destroy;
563 
564 #ifdef USE_GNUTLS
565 	if (account->set_gnutls_priority && account->gnutls_priority &&
566 			strlen(account->gnutls_priority) != 0)
567 		SESSION(session)->gnutls_priority = g_strdup(account->gnutls_priority);
568 	SESSION(session)->use_tls_sni = account->use_tls_sni;
569 #endif
570 
571 	session->state = POP3_READY;
572 	session->ac_prefs = account;
573 	session->pop_before_smtp = FALSE;
574 	pop3_get_uidl_table(account, session);
575 	session->current_time = time(NULL);
576 	session->error_val = PS_SUCCESS;
577 	session->error_msg = NULL;
578 
579 	return SESSION(session);
580 }
581 
pop3_session_destroy(Session * session)582 static void pop3_session_destroy(Session *session)
583 {
584 	Pop3Session *pop3_session = POP3_SESSION(session);
585 	gint n;
586 
587 	cm_return_if_fail(session != NULL);
588 
589 	for (n = 1; n <= pop3_session->count; n++)
590 		g_free(pop3_session->msg[n].uidl);
591 	g_free(pop3_session->msg);
592 
593 	if (pop3_session->uidl_table) {
594 		hash_free_strings(pop3_session->uidl_table);
595 		g_hash_table_destroy(pop3_session->uidl_table);
596 	}
597 
598 	if (pop3_session->partial_recv_table) {
599 		hash_free_strings(pop3_session->partial_recv_table);
600 		g_hash_table_destroy(pop3_session->partial_recv_table);
601 	}
602 
603 	g_free(pop3_session->greeting);
604 	g_free(pop3_session->user);
605 	g_free(pop3_session->pass);
606 	g_free(pop3_session->error_msg);
607 
608 	pop3_session->ac_prefs->receive_in_progress = FALSE;
609 }
610 
pop3_get_uidl_table(PrefsAccount * ac_prefs,Pop3Session * session)611 static void pop3_get_uidl_table(PrefsAccount *ac_prefs, Pop3Session *session)
612 {
613 	GHashTable *table;
614 	GHashTable *partial_recv_table;
615 	gchar *path;
616 	FILE *fp;
617 	gchar buf[POPBUFSIZE];
618 	gchar uidl[POPBUFSIZE];
619 	time_t recv_time;
620 	time_t now;
621 	gint partial_recv;
622 	gchar *sanitized_uid = g_strdup(ac_prefs->userid);
623 
624 	subst_for_filename(sanitized_uid);
625 
626 	table = g_hash_table_new(g_str_hash, g_str_equal);
627 	partial_recv_table = g_hash_table_new(g_str_hash, g_str_equal);
628 
629 	path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
630 			   "uidl", G_DIR_SEPARATOR_S, ac_prefs->recv_server,
631 			   "-", sanitized_uid, NULL);
632 
633 	g_free(sanitized_uid);
634 	if ((fp = claws_fopen(path, "rb")) == NULL) {
635 		if (ENOENT != errno) FILE_OP_ERROR(path, "claws_fopen");
636 		g_free(path);
637 		path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
638 				   "uidl-", ac_prefs->recv_server,
639 				   "-", ac_prefs->userid, NULL);
640 		if ((fp = claws_fopen(path, "rb")) == NULL) {
641 			if (ENOENT != errno) FILE_OP_ERROR(path, "claws_fopen");
642 			g_free(path);
643 			session->uidl_table = table;
644 			session->partial_recv_table = partial_recv_table;
645 			return;
646 		}
647 	}
648 	g_free(path);
649 
650 	now = time(NULL);
651 
652 	while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
653 		gchar tmp[POPBUFSIZE];
654 		strretchomp(buf);
655 		recv_time = RECV_TIME_NONE;
656 		partial_recv = POP3_TOTALLY_RECEIVED;
657 
658 		if (sscanf(buf, "%s\t%ld\t%s", uidl, (long int *) &recv_time, tmp) < 3) {
659 			if (sscanf(buf, "%s\t%ld", uidl, (long int *) &recv_time) != 2) {
660 				if (sscanf(buf, "%s", uidl) != 1)
661 					continue;
662 				else {
663 					recv_time = now;
664 					strcpy(tmp, "0");
665 				}
666 			} else {
667 				strcpy(tmp, "0");
668 			}
669 		}
670 
671 		if (recv_time == RECV_TIME_NONE)
672 			recv_time = RECV_TIME_RECEIVED;
673 		g_hash_table_insert(table, g_strdup(uidl),
674 				    GINT_TO_POINTER(recv_time));
675 		if (strlen(tmp) == 1)
676 			partial_recv = atoi(tmp); /* totally received ?*/
677 		else
678 			partial_recv = POP3_MUST_COMPLETE_RECV;
679 
680 		g_hash_table_insert(partial_recv_table, g_strdup(uidl),
681 				    GINT_TO_POINTER(partial_recv));
682 	}
683 
684 	claws_fclose(fp);
685 	session->uidl_table = table;
686 	session->partial_recv_table = partial_recv_table;
687 
688 	return;
689 }
690 
691 #define TRY(func) \
692 if (!(func)) \
693 { \
694 	g_warning("failed to write"); \
695 	goto err_write;			\
696 } \
697 
pop3_write_uidl_list(Pop3Session * session)698 gint pop3_write_uidl_list(Pop3Session *session)
699 {
700 	gchar *path, *tmp_path;
701 	FILE *fp;
702 	Pop3MsgInfo *msg;
703 	gint n;
704 	gchar *sanitized_uid = g_strdup(session->ac_prefs->userid);
705 
706 	subst_for_filename(sanitized_uid);
707 
708 	if (!session->uidl_is_valid) {
709 		g_free(sanitized_uid);
710 		return 0;
711 	}
712 
713 	path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
714 			   "uidl", G_DIR_SEPARATOR_S,
715 			   session->ac_prefs->recv_server,
716 			   "-", sanitized_uid, NULL);
717 	tmp_path = g_strconcat(path, ".tmp", NULL);
718 
719 	g_free(sanitized_uid);
720 
721 	if ((fp = claws_fopen(tmp_path, "wb")) == NULL) {
722 		FILE_OP_ERROR(tmp_path, "claws_fopen");
723 		goto err_write;
724 	}
725 
726 	for (n = 1; n <= session->count; n++) {
727 		msg = &session->msg[n];
728 		if (msg->uidl && msg->received &&
729 		    (!msg->deleted || session->state != POP3_DONE))
730 			TRY(fprintf(fp, "%s\t%ld\t%d\n",
731 				msg->uidl, (long int)
732 				msg->recv_time,
733 				msg->partial_recv)
734 			    > 0);
735 	}
736 
737 	if (claws_safe_fclose(fp) == EOF) {
738 		FILE_OP_ERROR(tmp_path, "claws_fclose");
739 		fp = NULL;
740 		goto err_write;
741 	}
742 	fp = NULL;
743 #ifdef G_OS_WIN32
744 	claws_unlink(path);
745 #endif
746 	if (g_rename(tmp_path, path) < 0) {
747 		FILE_OP_ERROR(path, "rename");
748 		goto err_write;
749 	}
750 	g_free(path);
751 	g_free(tmp_path);
752 	return 0;
753 err_write:
754 	if (fp)
755 		claws_fclose(fp);
756 	g_free(path);
757 	g_free(tmp_path);
758 	return -1;
759 }
760 
761 #undef TRY
762 
pop3_write_msg_to_file(const gchar * file,const gchar * data,guint len,const gchar * prefix)763 static gint pop3_write_msg_to_file(const gchar *file, const gchar *data,
764 				   guint len, const gchar *prefix)
765 {
766 	FILE *fp;
767 	const gchar *prev, *cur;
768 
769 	cm_return_val_if_fail(file != NULL, -1);
770 
771 	if ((fp = claws_fopen(file, "wb")) == NULL) {
772 		FILE_OP_ERROR(file, "claws_fopen");
773 		return -1;
774 	}
775 
776 	if (change_file_mode_rw(fp, file) < 0)
777 		FILE_OP_ERROR(file, "chmod");
778 
779 	if (prefix != NULL) {
780 		if (fprintf(fp, "%s\n", prefix) < 0) {
781 			FILE_OP_ERROR(file, "fprintf");
782 			claws_fclose(fp);
783 			claws_unlink(file);
784 			return -1;
785 		}
786 	}
787 
788 	/* +------------------+----------------+--------------------------+ *
789 	 * ^data              ^prev            ^cur             data+len-1^ */
790 
791 	prev = data;
792 	while ((cur = (gchar *)my_memmem(prev, len - (prev - data), "\r\n", 2))
793 	       != NULL) {
794 		if ((cur > prev && claws_fwrite(prev, 1, cur - prev, fp) < 1) ||
795 		    claws_fputc('\n', fp) == EOF) {
796 			FILE_OP_ERROR(file, "claws_fwrite");
797 			g_warning("can't write to file: %s", file);
798 			claws_fclose(fp);
799 			claws_unlink(file);
800 			return -1;
801 		}
802 
803 		if (cur == data + len - 1) {
804 			prev = cur + 1;
805 			break;
806 		}
807 
808 		if (*(cur + 1) == '\n')
809 			prev = cur + 2;
810 		else
811 			prev = cur + 1;
812 
813 		if (prev - data < len - 1 && *prev == '.' && *(prev + 1) == '.')
814 			prev++;
815 
816 		if (prev - data >= len)
817 			break;
818 	}
819 
820 	if (prev - data < len &&
821 	    claws_fwrite(prev, 1, len - (prev - data), fp) < 1) {
822 		FILE_OP_ERROR(file, "claws_fwrite");
823 		g_warning("can't write to file: %s", file);
824 		claws_fclose(fp);
825 		claws_unlink(file);
826 		return -1;
827 	}
828 	if (data[len - 1] != '\r' && data[len - 1] != '\n') {
829 		if (claws_fputc('\n', fp) == EOF) {
830 			FILE_OP_ERROR(file, "claws_fputc");
831 			g_warning("can't write to file: %s", file);
832 			claws_fclose(fp);
833 			claws_unlink(file);
834 			return -1;
835 		}
836 	}
837 
838 	if (claws_safe_fclose(fp) == EOF) {
839 		FILE_OP_ERROR(file, "claws_fclose");
840 		claws_unlink(file);
841 		return -1;
842 	}
843 
844 	return 0;
845 }
846 
pop3_lookup_next(Pop3Session * session)847 static Pop3State pop3_lookup_next(Pop3Session *session)
848 {
849 	Pop3MsgInfo *msg;
850 	PrefsAccount *ac = session->ac_prefs;
851 	gint size;
852 	gboolean size_limit_over;
853 
854 	for (;;) {
855 		msg = &session->msg[session->cur_msg];
856 		size = msg->size;
857 		size_limit_over =
858 		    (ac->enable_size_limit &&
859 		     ac->size_limit > 0 &&
860 		     size > ac->size_limit * 1024);
861 
862 		if (ac->rmmail &&
863 		    msg->recv_time != RECV_TIME_NONE &&
864 		    msg->recv_time != RECV_TIME_KEEP &&
865 		    msg->partial_recv == POP3_TOTALLY_RECEIVED &&
866 		    session->current_time - msg->recv_time >=
867                     ((ac->msg_leave_time * 24 * 60 * 60) +
868                      (ac->msg_leave_hour * 60 * 60))) {
869 			log_message(LOG_PROTOCOL,
870 					_("POP: Deleting expired message %d [%s]\n"),
871 					session->cur_msg, msg->uidl?msg->uidl:" ");
872 			session->cur_total_bytes += size;
873 			pop3_delete_send(session);
874 			return POP3_DELETE;
875 		}
876 
877 		if (size_limit_over) {
878 			if (!msg->received && msg->partial_recv !=
879 			    POP3_MUST_COMPLETE_RECV) {
880 				pop3_top_send(session, ac->size_limit);
881 				return POP3_TOP;
882 			} else if (msg->partial_recv == POP3_MUST_COMPLETE_RECV)
883 				break;
884 
885 			log_message(LOG_PROTOCOL,
886 					_("POP: Skipping message %d [%s] (%d bytes)\n"),
887 					session->cur_msg, msg->uidl?msg->uidl:" ", size);
888 		}
889 
890 		if (size == 0 || msg->received || size_limit_over) {
891 			session->cur_total_bytes += size;
892 			if (session->cur_msg == session->count) {
893 				pop3_logout_send(session);
894 				return POP3_LOGOUT;
895 			} else
896 				session->cur_msg++;
897 		} else
898 			break;
899 	}
900 
901 	pop3_retr_send(session);
902 	return POP3_RETR;
903 }
904 
pop3_ok(Pop3Session * session,const gchar * msg)905 static Pop3ErrorValue pop3_ok(Pop3Session *session, const gchar *msg)
906 {
907 	Pop3ErrorValue ok;
908 
909 	log_print(LOG_PROTOCOL, "POP< %s\n", msg);
910 
911 	if (!strncmp(msg, "+OK", 3))
912 		ok = PS_SUCCESS;
913 	else if (!strncmp(msg, "-ERR", 4)) {
914 		if (strstr(msg + 4, "lock") ||
915 		    strstr(msg + 4, "Lock") ||
916 		    strstr(msg + 4, "LOCK") ||
917 		    strstr(msg + 4, "wait")) {
918 			log_error(LOG_PROTOCOL, _("mailbox is locked\n"));
919 			ok = PS_LOCKBUSY;
920 		} else if (strcasestr(msg + 4, "timeout")) {
921 			log_error(LOG_PROTOCOL, _("Session timeout\n"));
922 			ok = PS_ERROR;
923 		} else {
924 			switch (session->state) {
925 #ifdef USE_GNUTLS
926 			case POP3_STLS:
927 				log_error(LOG_PROTOCOL, _("couldn't start STARTTLS session\n"));
928 				ok = PS_ERROR;
929 				break;
930 #endif
931 			case POP3_GETAUTH_USER:
932 			case POP3_GETAUTH_PASS:
933 			case POP3_GETAUTH_APOP:
934 				log_error(LOG_PROTOCOL, _("error occurred on authentication\n"));
935 				ok = PS_AUTHFAIL;
936 				break;
937 			case POP3_GETRANGE_LAST:
938 			case POP3_GETRANGE_UIDL:
939 			case POP3_TOP:
940 				log_warning(LOG_PROTOCOL, _("command not supported\n"));
941 				ok = PS_NOTSUPPORTED;
942 				break;
943 
944 			default:
945 				log_error(LOG_PROTOCOL, _("error occurred on POP session\n"));
946 				ok = PS_ERROR;
947 			}
948 		}
949 
950 		g_free(session->error_msg);
951 		session->error_msg = g_strdup(msg);
952 		g_printerr("POP: %s\n", msg);
953 	} else
954 		ok = PS_PROTOCOL;
955 
956 	session->error_val = ok;
957 	return ok;
958 }
959 
pop3_session_recv_msg(Session * session,const gchar * msg)960 static gint pop3_session_recv_msg(Session *session, const gchar *msg)
961 {
962 	Pop3Session *pop3_session = POP3_SESSION(session);
963 	Pop3ErrorValue val = PS_SUCCESS;
964 	const gchar *body;
965 
966 	body = msg;
967 	if (pop3_session->state != POP3_GETRANGE_UIDL_RECV &&
968 	    pop3_session->state != POP3_GETSIZE_LIST_RECV) {
969 		val = pop3_ok(pop3_session, msg);
970 		if (val != PS_SUCCESS) {
971 			if (val != PS_NOTSUPPORTED) {
972 				pop3_session->state = POP3_ERROR;
973 				return -1;
974 			}
975 		}
976 
977 		if (*body == '+' || *body == '-')
978 			body++;
979 		while (g_ascii_isalpha(*body))
980 			body++;
981 		while (g_ascii_isspace(*body))
982 			body++;
983 	}
984 
985 	switch (pop3_session->state) {
986 	case POP3_READY:
987 	case POP3_GREETING:
988 		pop3_greeting_recv(pop3_session, body);
989 #ifdef USE_GNUTLS
990 		if (pop3_session->ac_prefs->ssl_pop == SSL_STARTTLS)
991 			val = pop3_stls_send(pop3_session);
992 		else
993 #endif
994 		if (pop3_session->ac_prefs->use_pop_auth && pop3_session->ac_prefs->pop_auth_type == POPAUTH_APOP)
995 			val = pop3_getauth_apop_send(pop3_session);
996                 else if (pop3_session->ac_prefs->use_pop_auth && pop3_session->ac_prefs->pop_auth_type == POPAUTH_OAUTH2)
997 			val = pop3_getauth_oauth2_send(pop3_session);
998 		else
999 			val = pop3_getauth_user_send(pop3_session);
1000 		break;
1001 #ifdef USE_GNUTLS
1002 	case POP3_STLS:
1003 		if (pop3_stls_recv(pop3_session) != PS_SUCCESS)
1004 			return -1;
1005 		if (pop3_session->ac_prefs->use_pop_auth && pop3_session->ac_prefs->pop_auth_type == POPAUTH_APOP)
1006 			val = pop3_getauth_apop_send(pop3_session);
1007                 else if (pop3_session->ac_prefs->use_pop_auth && pop3_session->ac_prefs->pop_auth_type == POPAUTH_OAUTH2)
1008 			val = pop3_getauth_oauth2_send(pop3_session);
1009 		else
1010 			val = pop3_getauth_user_send(pop3_session);
1011 		break;
1012 #endif
1013 	case POP3_GETAUTH_USER:
1014 		val = pop3_getauth_pass_send(pop3_session);
1015 		break;
1016 	case POP3_GETAUTH_PASS:
1017 	case POP3_GETAUTH_APOP:
1018         case POP3_GETAUTH_OAUTH2:
1019 		if (!pop3_session->pop_before_smtp)
1020 			val = pop3_getrange_stat_send(pop3_session);
1021 		else
1022 			val = pop3_logout_send(pop3_session);
1023 		break;
1024 	case POP3_GETRANGE_STAT:
1025 		if (pop3_getrange_stat_recv(pop3_session, body) < 0)
1026 			return -1;
1027 		if (pop3_session->count > 0)
1028 			val = pop3_getrange_uidl_send(pop3_session);
1029 		else
1030 			val = pop3_logout_send(pop3_session);
1031 		break;
1032 	case POP3_GETRANGE_LAST:
1033 		if (val == PS_NOTSUPPORTED)
1034 			pop3_session->error_val = PS_SUCCESS;
1035 		else if (pop3_getrange_last_recv(pop3_session, body) < 0)
1036 			return -1;
1037 		if (pop3_session->cur_msg > 0)
1038 			val = pop3_getsize_list_send(pop3_session);
1039 		else
1040 			val = pop3_logout_send(pop3_session);
1041 		break;
1042 	case POP3_GETRANGE_UIDL:
1043 		if (val == PS_NOTSUPPORTED) {
1044 			pop3_session->error_val = PS_SUCCESS;
1045 			val = pop3_getrange_last_send(pop3_session);
1046 		} else {
1047 			pop3_session->state = POP3_GETRANGE_UIDL_RECV;
1048 			session_recv_data(session, 0, ".\r\n");
1049 		}
1050 		break;
1051 	case POP3_GETSIZE_LIST:
1052 		pop3_session->state = POP3_GETSIZE_LIST_RECV;
1053 		session_recv_data(session, 0, ".\r\n");
1054 		break;
1055 	case POP3_RETR:
1056 		pop3_session->state = POP3_RETR_RECV;
1057 		session_recv_data(session, 0, ".\r\n");
1058 		break;
1059 	case POP3_TOP:
1060 		if (val == PS_NOTSUPPORTED) {
1061 			pop3_session->error_val = PS_SUCCESS;
1062 		} else {
1063 			pop3_session->state = POP3_TOP_RECV;
1064 			session_recv_data(session, 0, ".\r\n");
1065 		}
1066 		break;
1067 	case POP3_DELETE:
1068 		pop3_delete_recv(pop3_session);
1069 		if (pop3_session->cur_msg == pop3_session->count)
1070 			val = pop3_logout_send(pop3_session);
1071 		else {
1072 			pop3_session->cur_msg++;
1073 			if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1074 				return -1;
1075 		}
1076 		break;
1077 	case POP3_LOGOUT:
1078 		pop3_session->state = POP3_DONE;
1079 		session_disconnect(session);
1080 		break;
1081 	case POP3_ERROR:
1082 	default:
1083 		return -1;
1084 	}
1085 
1086 	return val == PS_SUCCESS?0:-1;
1087 }
1088 
pop3_session_recv_data_finished(Session * session,guchar * data,guint len)1089 static gint pop3_session_recv_data_finished(Session *session, guchar *data,
1090 					    guint len)
1091 {
1092 	Pop3Session *pop3_session = POP3_SESSION(session);
1093 	Pop3ErrorValue val = PS_SUCCESS;
1094 
1095 	switch (pop3_session->state) {
1096 	case POP3_GETRANGE_UIDL_RECV:
1097 		val = pop3_getrange_uidl_recv(pop3_session, data, len);
1098 		if (val == PS_SUCCESS) {
1099 			if (pop3_session->new_msg_exist)
1100 				pop3_getsize_list_send(pop3_session);
1101 			else
1102 				pop3_logout_send(pop3_session);
1103 		} else
1104 			return -1;
1105 		break;
1106 	case POP3_GETSIZE_LIST_RECV:
1107 		val = pop3_getsize_list_recv(pop3_session, data, len);
1108 		if (val == PS_SUCCESS) {
1109 			if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1110 				return -1;
1111 		} else
1112 			return -1;
1113 		break;
1114 	case POP3_RETR_RECV:
1115 		if (pop3_retr_recv(pop3_session, data, len) < 0)
1116 			return -1;
1117 
1118 		if (pop3_session->ac_prefs->rmmail &&
1119 		    pop3_session->ac_prefs->msg_leave_time == 0 &&
1120 		    pop3_session->ac_prefs->msg_leave_hour == 0 &&
1121 		    pop3_session->msg[pop3_session->cur_msg].recv_time
1122 		    != RECV_TIME_KEEP)
1123 			pop3_delete_send(pop3_session);
1124 		else if (pop3_session->cur_msg == pop3_session->count)
1125 			pop3_logout_send(pop3_session);
1126 		else {
1127 			pop3_session->cur_msg++;
1128 			if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1129 				return -1;
1130 		}
1131 		break;
1132 	case POP3_TOP_RECV:
1133 		if (pop3_top_recv(pop3_session, data, len) < 0)
1134 			return -1;
1135 
1136 		if (pop3_session->cur_msg == pop3_session->count)
1137 			pop3_logout_send(pop3_session);
1138 		else {
1139 			pop3_session->cur_msg++;
1140 			if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1141 				return -1;
1142 		}
1143 		break;
1144 	case POP3_TOP:
1145 		log_warning(LOG_PROTOCOL, _("TOP command unsupported\n"));
1146 		if (pop3_session->cur_msg == pop3_session->count)
1147 			pop3_logout_send(pop3_session);
1148 		else {
1149 			pop3_session->cur_msg++;
1150 			if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1151 				return -1;
1152 		}
1153 		break;
1154 	case POP3_ERROR:
1155 	default:
1156 		return -1;
1157 	}
1158 
1159 	return 0;
1160 }
1161