talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

pop.c (26432B)


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