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