2007-04-18 [paul] 2.9.0cvs8
[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 2 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, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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)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)g_hash_table_lookup(
278                                         session->partial_recv_table, id);
279
280                 if ((!session->ac_prefs->getall && 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                     (session->ac_prefs->getall || 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                 fprintf(fp, "%s", prefix);
738                 fprintf(fp, "\n");
739         }
740         
741         /* +------------------+----------------+--------------------------+ *
742          * ^data              ^prev            ^cur             data+len-1^ */
743
744         prev = data;
745         while ((cur = (gchar *)my_memmem(prev, len - (prev - data), "\r\n", 2))
746                != NULL) {
747                 if ((cur > prev && fwrite(prev, 1, cur - prev, fp) < 1) ||
748                     fputc('\n', fp) == EOF) {
749                         FILE_OP_ERROR(file, "fwrite");
750                         g_warning("can't write to file: %s\n", file);
751                         fclose(fp);
752                         g_unlink(file);
753                         return -1;
754                 }
755
756                 if (cur == data + len - 1) {
757                         prev = cur + 1;
758                         break;
759                 }
760
761                 if (*(cur + 1) == '\n')
762                         prev = cur + 2;
763                 else
764                         prev = cur + 1;
765
766                 if (prev - data < len - 1 && *prev == '.' && *(prev + 1) == '.')
767                         prev++;
768
769                 if (prev - data >= len)
770                         break;
771         }
772
773         if (prev - data < len &&
774             fwrite(prev, 1, len - (prev - data), fp) < 1) {
775                 FILE_OP_ERROR(file, "fwrite");
776                 g_warning("can't write to file: %s\n", file);
777                 fclose(fp);
778                 g_unlink(file);
779                 return -1;
780         }
781         if (data[len - 1] != '\r' && data[len - 1] != '\n') {
782                 if (fputc('\n', fp) == EOF) {
783                         FILE_OP_ERROR(file, "fputc");
784                         g_warning("can't write to file: %s\n", file);
785                         fclose(fp);
786                         g_unlink(file);
787                         return -1;
788                 }
789         }
790
791         if (fclose(fp) == EOF) {
792                 FILE_OP_ERROR(file, "fclose");
793                 g_unlink(file);
794                 return -1;
795         }
796
797         return 0;
798 }
799
800 static Pop3State pop3_lookup_next(Pop3Session *session)
801 {
802         Pop3MsgInfo *msg;
803         PrefsAccount *ac = session->ac_prefs;
804         gint size;
805         gboolean size_limit_over;
806
807         for (;;) {
808                 msg = &session->msg[session->cur_msg];
809                 size = msg->size;
810                 size_limit_over =
811                     (ac->enable_size_limit &&
812                      ac->size_limit > 0 &&
813                      size > ac->size_limit * 1024);
814
815                 if (ac->rmmail &&
816                     msg->recv_time != RECV_TIME_NONE &&
817                     msg->recv_time != RECV_TIME_KEEP &&
818                     msg->partial_recv == POP3_TOTALLY_RECEIVED &&
819                     session->current_time - msg->recv_time >=
820                     ac->msg_leave_time * 24 * 60 * 60) {
821                         log_message(LOG_PROTOCOL, 
822                                         _("POP3: Deleting expired message %d [%s]\n"),
823                                         session->cur_msg, msg->uidl?msg->uidl:" ");
824                         session->cur_total_bytes += size;
825                         pop3_delete_send(session);
826                         return POP3_DELETE;
827                 }
828
829                 if (size_limit_over) {
830                         if (!msg->received && msg->partial_recv != 
831                             POP3_MUST_COMPLETE_RECV) {
832                                 pop3_top_send(session, ac->size_limit);
833                                 return POP3_TOP;
834                         } else if (msg->partial_recv == POP3_MUST_COMPLETE_RECV)
835                                 break;
836
837                         log_message(LOG_PROTOCOL, 
838                                         _("POP3: Skipping message %d [%s] (%d bytes)\n"),
839                                         session->cur_msg, msg->uidl?msg->uidl:" ", size);
840                 }
841                 
842                 if (size == 0 || msg->received || size_limit_over) {
843                         session->cur_total_bytes += size;
844                         if (session->cur_msg == session->count) {
845                                 pop3_logout_send(session);
846                                 return POP3_LOGOUT;
847                         } else
848                                 session->cur_msg++;
849                 } else
850                         break;
851         }
852
853         pop3_retr_send(session);
854         return POP3_RETR;
855 }
856
857 static Pop3ErrorValue pop3_ok(Pop3Session *session, const gchar *msg)
858 {
859         Pop3ErrorValue ok;
860
861         log_print(LOG_PROTOCOL, "POP3< %s\n", msg);
862
863         if (!strncmp(msg, "+OK", 3))
864                 ok = PS_SUCCESS;
865         else if (!strncmp(msg, "-ERR", 4)) {
866                 if (strstr(msg + 4, "lock") ||
867                     strstr(msg + 4, "Lock") ||
868                     strstr(msg + 4, "LOCK") ||
869                     strstr(msg + 4, "wait")) {
870                         log_error(LOG_PROTOCOL, _("mailbox is locked\n"));
871                         ok = PS_LOCKBUSY;
872                 } else if (strcasestr(msg + 4, "timeout")) {
873                         log_error(LOG_PROTOCOL, _("Session timeout\n"));
874                         ok = PS_ERROR;
875                 } else {
876                         switch (session->state) {
877 #if USE_OPENSSL
878                         case POP3_STLS:
879                                 log_error(LOG_PROTOCOL, _("couldn't start TLS session\n"));
880                                 ok = PS_ERROR;
881                                 break;
882 #endif
883                         case POP3_GETAUTH_USER:
884                         case POP3_GETAUTH_PASS:
885                         case POP3_GETAUTH_APOP:
886                                 log_error(LOG_PROTOCOL, _("error occurred on authentication\n"));
887                                 ok = PS_AUTHFAIL;
888                                 break;
889                         case POP3_GETRANGE_LAST:
890                         case POP3_GETRANGE_UIDL:
891                         case POP3_TOP:
892                                 log_warning(LOG_PROTOCOL, _("command not supported\n"));
893                                 ok = PS_NOTSUPPORTED;
894                                 break;
895                                 
896                         default:
897                                 log_error(LOG_PROTOCOL, _("error occurred on POP3 session\n"));
898                                 ok = PS_ERROR;
899                         }
900                 }
901
902                 g_free(session->error_msg);
903                 session->error_msg = g_strdup(msg);
904                 fprintf(stderr, "POP3: %s\n", msg);
905         } else
906                 ok = PS_PROTOCOL;
907
908         session->error_val = ok;
909         return ok;
910 }
911
912 static gint pop3_session_recv_msg(Session *session, const gchar *msg)
913 {
914         Pop3Session *pop3_session = POP3_SESSION(session);
915         Pop3ErrorValue val = PS_SUCCESS;
916         const gchar *body;
917
918         body = msg;
919         if (pop3_session->state != POP3_GETRANGE_UIDL_RECV &&
920             pop3_session->state != POP3_GETSIZE_LIST_RECV) {
921                 val = pop3_ok(pop3_session, msg);
922                 if (val != PS_SUCCESS) {
923                         if (val != PS_NOTSUPPORTED) {
924                                 pop3_session->state = POP3_ERROR;
925                                 return -1;
926                         }
927                 }
928
929                 if (*body == '+' || *body == '-')
930                         body++;
931                 while (g_ascii_isalpha(*body))
932                         body++;
933                 while (g_ascii_isspace(*body))
934                         body++;
935         }
936
937         switch (pop3_session->state) {
938         case POP3_READY:
939         case POP3_GREETING:
940                 pop3_greeting_recv(pop3_session, body);
941 #if USE_OPENSSL
942                 if (pop3_session->ac_prefs->ssl_pop == SSL_STARTTLS)
943                         val = pop3_stls_send(pop3_session);
944                 else
945 #endif
946                 if (pop3_session->ac_prefs->use_apop_auth)
947                         val = pop3_getauth_apop_send(pop3_session);
948                 else
949                         val = pop3_getauth_user_send(pop3_session);
950                 break;
951 #if USE_OPENSSL
952         case POP3_STLS:
953                 if (pop3_stls_recv(pop3_session) != PS_SUCCESS)
954                         return -1;
955                 if (pop3_session->ac_prefs->use_apop_auth)
956                         val = pop3_getauth_apop_send(pop3_session);
957                 else
958                         val = pop3_getauth_user_send(pop3_session);
959                 break;
960 #endif
961         case POP3_GETAUTH_USER:
962                 val = pop3_getauth_pass_send(pop3_session);
963                 break;
964         case POP3_GETAUTH_PASS:
965         case POP3_GETAUTH_APOP:
966                 if (!pop3_session->pop_before_smtp)
967                         val = pop3_getrange_stat_send(pop3_session);
968                 else
969                         val = pop3_logout_send(pop3_session);
970                 break;
971         case POP3_GETRANGE_STAT:
972                 if (pop3_getrange_stat_recv(pop3_session, body) < 0)
973                         return -1;
974                 if (pop3_session->count > 0)
975                         val = pop3_getrange_uidl_send(pop3_session);
976                 else
977                         val = pop3_logout_send(pop3_session);
978                 break;
979         case POP3_GETRANGE_LAST:
980                 if (val == PS_NOTSUPPORTED)
981                         pop3_session->error_val = PS_SUCCESS;
982                 else if (pop3_getrange_last_recv(pop3_session, body) < 0)
983                         return -1;
984                 if (pop3_session->cur_msg > 0)
985                         val = pop3_getsize_list_send(pop3_session);
986                 else
987                         val = pop3_logout_send(pop3_session);
988                 break;
989         case POP3_GETRANGE_UIDL:
990                 if (val == PS_NOTSUPPORTED) {
991                         pop3_session->error_val = PS_SUCCESS;
992                         val = pop3_getrange_last_send(pop3_session);
993                 } else {
994                         pop3_session->state = POP3_GETRANGE_UIDL_RECV;
995                         session_recv_data(session, 0, ".\r\n");
996                 }
997                 break;
998         case POP3_GETSIZE_LIST:
999                 pop3_session->state = POP3_GETSIZE_LIST_RECV;
1000                 session_recv_data(session, 0, ".\r\n");
1001                 break;
1002         case POP3_RETR:
1003                 pop3_session->state = POP3_RETR_RECV;
1004                 session_recv_data(session, 0, ".\r\n");
1005                 break;
1006         case POP3_TOP:
1007                 if (val == PS_NOTSUPPORTED) {
1008                         pop3_session->error_val = PS_SUCCESS;
1009                 } else {
1010                         pop3_session->state = POP3_TOP_RECV;
1011                         session_recv_data(session, 0, ".\r\n");
1012                 }
1013                 break;
1014         case POP3_DELETE:
1015                 pop3_delete_recv(pop3_session);
1016                 if (pop3_session->cur_msg == pop3_session->count)
1017                         val = pop3_logout_send(pop3_session);
1018                 else {
1019                         pop3_session->cur_msg++;
1020                         if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1021                                 return -1;
1022                 }
1023                 break;
1024         case POP3_LOGOUT:
1025                 pop3_session->state = POP3_DONE;
1026                 session_disconnect(session);
1027                 break;
1028         case POP3_ERROR:
1029         default:
1030                 return -1;
1031         }
1032
1033         return val == PS_SUCCESS?0:-1;
1034 }
1035
1036 static gint pop3_session_recv_data_finished(Session *session, guchar *data,
1037                                             guint len)
1038 {
1039         Pop3Session *pop3_session = POP3_SESSION(session);
1040         Pop3ErrorValue val = PS_SUCCESS;
1041
1042         switch (pop3_session->state) {
1043         case POP3_GETRANGE_UIDL_RECV:
1044                 val = pop3_getrange_uidl_recv(pop3_session, data, len);
1045                 if (val == PS_SUCCESS) {
1046                         if (pop3_session->new_msg_exist)
1047                                 pop3_getsize_list_send(pop3_session);
1048                         else
1049                                 pop3_logout_send(pop3_session);
1050                 } else
1051                         return -1;
1052                 break;
1053         case POP3_GETSIZE_LIST_RECV:
1054                 val = pop3_getsize_list_recv(pop3_session, data, len);
1055                 if (val == PS_SUCCESS) {
1056                         if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1057                                 return -1;
1058                 } else
1059                         return -1;
1060                 break;
1061         case POP3_RETR_RECV:
1062                 if (pop3_retr_recv(pop3_session, data, len) < 0)
1063                         return -1;
1064
1065                 if (pop3_session->ac_prefs->rmmail &&
1066                     pop3_session->ac_prefs->msg_leave_time == 0 &&
1067                     pop3_session->msg[pop3_session->cur_msg].recv_time
1068                     != RECV_TIME_KEEP)
1069                         pop3_delete_send(pop3_session);
1070                 else if (pop3_session->cur_msg == pop3_session->count)
1071                         pop3_logout_send(pop3_session);
1072                 else {
1073                         pop3_session->cur_msg++;
1074                         if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1075                                 return -1;
1076                 }
1077                 break;
1078         case POP3_TOP_RECV:
1079                 if (pop3_top_recv(pop3_session, data, len) < 0)
1080                         return -1;
1081
1082                 if (pop3_session->cur_msg == pop3_session->count)
1083                         pop3_logout_send(pop3_session);
1084                 else {
1085                         pop3_session->cur_msg++;
1086                         if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1087                                 return -1;
1088                 }
1089                 break;
1090         case POP3_TOP:
1091                 log_warning(LOG_PROTOCOL, _("TOP command unsupported\n"));
1092                 if (pop3_session->cur_msg == pop3_session->count)
1093                         pop3_logout_send(pop3_session);
1094                 else {
1095                         pop3_session->cur_msg++;
1096                         if (pop3_lookup_next(pop3_session) == POP3_ERROR)
1097                                 return -1;
1098                 }
1099                 break;
1100         case POP3_ERROR:
1101         default:
1102                 return -1;
1103         }
1104
1105         return 0;
1106 }