84a2f83d761b4527174805cec6047526bb06caf0
[claws.git] / src / etpan / nntp-thread.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2005-2012 DINH Viet Hoa and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24
25 #ifdef HAVE_LIBETPAN
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include "nntp-thread.h"
30 #include "news.h"
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #if (defined(__DragonFly__) || defined (__NetBSD__) || defined (__FreeBSD__) || defined (__OpenBSD__) || defined (__CYGWIN__))
34 #include <sys/socket.h>
35 #endif
36 #include <fcntl.h>
37 #ifndef G_OS_WIN32
38 #include <sys/mman.h>
39 #include <sys/wait.h>
40 #endif
41 #include <gtk/gtk.h>
42 #include <log.h>
43 #include "etpan-thread-manager.h"
44 #include "etpan-ssl.h"
45 #include "utils.h"
46 #include "mainwindow.h"
47 #include "ssl_certificate.h"
48 #include "socket.h"
49 #include "remotefolder.h"
50 #include "main.h"
51 #include "account.h"
52
53 #define DISABLE_LOG_DURING_LOGIN
54
55 static struct etpan_thread_manager * thread_manager = NULL;
56 static chash * nntp_hash = NULL;
57 static chash * session_hash = NULL;
58 static guint thread_manager_signal = 0;
59 static GIOChannel * io_channel = NULL;
60
61 static void (*previous_stream_logger)(int direction,
62     const char * str, size_t size);
63
64 static void nntp_logger(int direction, const char * str, size_t size) 
65 {
66         gchar *buf;
67         gchar **lines;
68         int i = 0;
69
70         if (size > 256) {
71                 log_print(LOG_PROTOCOL, "NNTP%c [data - %zd bytes]\n", direction?'>':'<', size);
72                 return;
73         }
74         buf = malloc(size+1);
75         memset(buf, 0, size+1);
76         strncpy(buf, str, size);
77         buf[size] = '\0';
78
79         if (!strncmp(buf, "<<<<<<<", 7) 
80         ||  !strncmp(buf, ">>>>>>>", 7)) {
81                 free(buf);
82                 return;
83         }
84         while (strstr(buf, "\r"))
85                 *strstr(buf, "\r") = ' ';
86         while (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
87                 buf[strlen(buf)-1] = '\0';
88
89         lines = g_strsplit(buf, "\n", -1);
90
91         while (lines[i] && *lines[i]) {
92                 log_print(LOG_PROTOCOL, "NNTP%c %s\n", direction?'>':'<', lines[i]);
93                 i++;
94         }
95         g_strfreev(lines);
96         free(buf);
97 }
98
99 static void delete_nntp(Folder *folder, newsnntp *nntp)
100 {
101         chashdatum key;
102
103         key.data = &folder;
104         key.len = sizeof(folder);
105         chash_delete(session_hash, &key, NULL);
106         
107         key.data = &nntp;
108         key.len = sizeof(nntp);
109         if (nntp && nntp->nntp_stream) {
110                 /* we don't want libetpan to logout */
111                 mailstream_close(nntp->nntp_stream);
112                 nntp->nntp_stream = NULL;
113         }
114         debug_print("removing newsnntp %p\n", nntp);
115         newsnntp_free(nntp);    
116 }
117
118 static gboolean thread_manager_event(GIOChannel * source,
119     GIOCondition condition,
120     gpointer data)
121 {
122 #ifdef G_OS_WIN32
123         gsize bytes_read;
124         gchar ch;
125         
126         if (condition & G_IO_IN)
127                 g_io_channel_read_chars(source, &ch, 1, &bytes_read, NULL);
128 #endif
129         etpan_thread_manager_loop(thread_manager);
130         
131         return TRUE;
132 }
133
134 #define ETPAN_DEFAULT_NETWORK_TIMEOUT 60
135 extern gboolean etpan_skip_ssl_cert_check;
136
137 void nntp_main_init(gboolean skip_ssl_cert_check)
138 {
139         int fd_thread_manager;
140         
141         etpan_skip_ssl_cert_check = skip_ssl_cert_check;
142         
143         nntp_hash = chash_new(CHASH_COPYKEY, CHASH_DEFAULTSIZE);
144         session_hash = chash_new(CHASH_COPYKEY, CHASH_DEFAULTSIZE);
145         
146         thread_manager = etpan_thread_manager_new();
147         
148         fd_thread_manager = etpan_thread_manager_get_fd(thread_manager);
149         
150 #ifndef G_OS_WIN32
151         io_channel = g_io_channel_unix_new(fd_thread_manager);
152 #else
153         io_channel = g_io_channel_win32_new_fd(fd_thread_manager);
154 #endif
155         
156         thread_manager_signal = g_io_add_watch_full(io_channel, 0, G_IO_IN,
157                                                     thread_manager_event,
158                                                     (gpointer) NULL,
159                                                     NULL);
160 }
161
162 void nntp_main_done(gboolean have_connectivity)
163 {
164         nntp_disconnect_all(have_connectivity);
165         etpan_thread_manager_stop(thread_manager);
166 #if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__)
167         return;
168 #endif
169         etpan_thread_manager_join(thread_manager);
170         
171         g_source_remove(thread_manager_signal);
172         g_io_channel_unref(io_channel);
173         
174         etpan_thread_manager_free(thread_manager);
175         
176         chash_free(session_hash);
177         chash_free(nntp_hash);
178 }
179
180 void nntp_init(Folder * folder)
181 {
182         struct etpan_thread * thread;
183         chashdatum key;
184         chashdatum value;
185         
186         thread = etpan_thread_manager_get_thread(thread_manager);
187         
188         key.data = &folder;
189         key.len = sizeof(folder);
190         value.data = thread;
191         value.len = 0;
192         
193         chash_set(nntp_hash, &key, &value, NULL);
194 }
195
196 void nntp_done(Folder * folder)
197 {
198         struct etpan_thread * thread;
199         chashdatum key;
200         chashdatum value;
201         int r;
202         
203         key.data = &folder;
204         key.len = sizeof(folder);
205         
206         r = chash_get(nntp_hash, &key, &value);
207         if (r < 0)
208                 return;
209         
210         thread = value.data;
211         
212         etpan_thread_unbind(thread);
213         
214         chash_delete(nntp_hash, &key, NULL);
215         
216         debug_print("remove thread");
217 }
218
219 static struct etpan_thread * get_thread(Folder * folder)
220 {
221         struct etpan_thread * thread;
222         chashdatum key;
223         chashdatum value;
224         
225         key.data = &folder;
226         key.len = sizeof(folder);
227         
228         chash_get(nntp_hash, &key, &value);
229         thread = value.data;
230         
231         return thread;
232 }
233
234 static newsnntp * get_nntp(Folder * folder)
235 {
236         newsnntp * nntp;
237         chashdatum key;
238         chashdatum value;
239         int r;
240         
241         key.data = &folder;
242         key.len = sizeof(folder);
243         
244         r = chash_get(session_hash, &key, &value);
245         if (r < 0)
246                 return NULL;
247         
248         nntp = value.data;
249         debug_print("found nntp %p\n", nntp);
250         return nntp;
251 }
252
253
254 static void generic_cb(int cancelled, void * result, void * callback_data)
255 {
256         struct etpan_thread_op * op;
257         
258         op = (struct etpan_thread_op *) callback_data;
259
260         debug_print("generic_cb\n");
261         op->finished = 1;
262 }
263
264 static void threaded_run(Folder * folder, void * param, void * result,
265                          void (* func)(struct etpan_thread_op * ))
266 {
267         struct etpan_thread_op * op;
268         struct etpan_thread * thread;
269         
270         nntp_folder_ref(folder);
271
272         op = etpan_thread_op_new();
273         
274         op->nntp = get_nntp(folder);
275         op->param = param;
276         op->result = result;
277         
278         op->cancellable = 0;
279         op->run = func;
280         op->callback = generic_cb;
281         op->callback_data = op;
282         op->cleanup = NULL;
283         
284         op->finished = 0;
285         
286         previous_stream_logger = mailstream_logger;
287         mailstream_logger = nntp_logger;
288
289         thread = get_thread(folder);
290         etpan_thread_op_schedule(thread, op);
291         
292         while (!op->finished) {
293                 gtk_main_iteration();
294         }
295         
296         mailstream_logger = previous_stream_logger;
297
298         etpan_thread_op_free(op);
299
300         nntp_folder_unref(folder);
301 }
302
303
304 /* connect */
305
306 struct connect_param {
307         newsnntp * nntp;
308         PrefsAccount *account;
309         const char * server;
310         int port;
311 };
312
313 struct connect_result {
314         int error;
315 };
316
317 #define CHECK_NNTP() {                                          \
318         if (!param->nntp) {                                     \
319                 result->error = NEWSNNTP_ERROR_BAD_STATE;       \
320                 return;                                         \
321         }                                                       \
322 }
323
324 static void connect_run(struct etpan_thread_op * op)
325 {
326         int r;
327         struct connect_param * param;
328         struct connect_result * result;
329         
330         param = op->param;
331         result = op->result;
332         
333         CHECK_NNTP();
334
335         r = newsnntp_socket_connect(param->nntp,
336                                     param->server, param->port);
337         
338         result->error = r;
339 }
340
341
342 int nntp_threaded_connect(Folder * folder, const char * server, int port)
343 {
344         struct connect_param param;
345         struct connect_result result;
346         chashdatum key;
347         chashdatum value;
348         newsnntp * nntp, * oldnntp;
349         
350         oldnntp = get_nntp(folder);
351
352         nntp = newsnntp_new(0, NULL);
353         
354         if (oldnntp) {
355                 debug_print("deleting old nntp %p\n", oldnntp);
356                 delete_nntp(folder, oldnntp);
357         }
358         
359         key.data = &folder;
360         key.len = sizeof(folder);
361         value.data = nntp;
362         value.len = 0;
363         chash_set(session_hash, &key, &value, NULL);
364         
365         param.nntp = nntp;
366         param.server = server;
367         param.port = port;
368         
369         refresh_resolvers();
370         threaded_run(folder, &param, &result, connect_run);
371         
372         debug_print("connect ok %i with nntp %p\n", result.error, nntp);
373         
374         return result.error;
375 }
376
377 static void connect_ssl_run(struct etpan_thread_op * op)
378 {
379         int r;
380         struct connect_param * param;
381         struct connect_result * result;
382         
383         param = op->param;
384         result = op->result;
385         
386         CHECK_NNTP();
387
388         r = newsnntp_ssl_connect_with_callback(param->nntp,
389                                  param->server, param->port,
390                                  etpan_connect_ssl_context_cb, param->account);
391         result->error = r;
392 }
393
394 int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port)
395 {
396         struct connect_param param;
397         struct connect_result result;
398         chashdatum key;
399         chashdatum value;
400         newsnntp * nntp, * oldnntp;
401         
402         oldnntp = get_nntp(folder);
403
404         nntp = newsnntp_new(0, NULL);
405         
406         if (oldnntp) {
407                 debug_print("deleting old nntp %p\n", oldnntp);
408                 delete_nntp(folder, oldnntp);
409         }
410
411         key.data = &folder;
412         key.len = sizeof(folder);
413         value.data = nntp;
414         value.len = 0;
415         chash_set(session_hash, &key, &value, NULL);
416         
417         param.nntp = nntp;
418         param.server = server;
419         param.port = port;
420         param.account = folder->account;
421
422         refresh_resolvers();
423         threaded_run(folder, &param, &result, connect_ssl_run);
424
425         if (result.error == NEWSNNTP_NO_ERROR && !etpan_skip_ssl_cert_check) {
426                 if (etpan_certificate_check(nntp->nntp_stream, server, port) < 0)
427                         return -1;
428         }
429         debug_print("connect %d with nntp %p\n", result.error, nntp);
430         
431         return result.error;
432 }
433
434 void nntp_threaded_disconnect(Folder * folder)
435 {
436         newsnntp * nntp;
437         
438         nntp = get_nntp(folder);
439         if (nntp == NULL) {
440                 debug_print("was disconnected\n");
441                 return;
442         }
443         
444         debug_print("deleting old nntp %p\n", nntp);
445         delete_nntp(folder, nntp);
446         
447         debug_print("disconnect ok\n");
448 }
449
450 void nntp_threaded_cancel(Folder * folder)
451 {
452         newsnntp * nntp;
453         
454         nntp = get_nntp(folder);
455         if (nntp->nntp_stream != NULL)
456                 mailstream_cancel(nntp->nntp_stream);
457 }
458
459
460 struct login_param {
461         newsnntp * nntp;
462         const char * login;
463         const char * password;
464 };
465
466 struct login_result {
467         int error;
468 };
469
470 static void login_run(struct etpan_thread_op * op)
471 {
472         struct login_param * param;
473         struct login_result * result;
474         int r;
475 #ifdef DISABLE_LOG_DURING_LOGIN
476         int old_debug;
477 #endif
478         
479         param = op->param;
480         result = op->result;
481
482         CHECK_NNTP();
483
484 #ifdef DISABLE_LOG_DURING_LOGIN
485         old_debug = mailstream_debug;
486         mailstream_debug = 0;
487 #endif
488
489         r = newsnntp_authinfo_username(param->nntp, param->login);
490         /* libetpan returning NO_ERROR means it received resp.code 281:
491            in this case auth. is already successful, no password is needed. */
492         if (r == NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD) {
493                 r = newsnntp_authinfo_password(param->nntp, param->password);
494         }
495         
496
497
498 #ifdef DISABLE_LOG_DURING_LOGIN
499         mailstream_debug = old_debug;
500 #endif
501         
502         result->error = r;
503         if (param->nntp->nntp_response)
504                 nntp_logger(0, param->nntp->nntp_response, strlen(param->nntp->nntp_response));
505
506         debug_print("nntp login run - end %i\n", r);
507 }
508
509 int nntp_threaded_login(Folder * folder, const char * login, const char * password)
510 {
511         struct login_param param;
512         struct login_result result;
513         
514         debug_print("nntp login - begin\n");
515         
516         param.nntp = get_nntp(folder);
517         param.login = login;
518         param.password = password;
519
520         threaded_run(folder, &param, &result, login_run);
521         
522         debug_print("nntp login - end\n");
523         
524         return result.error;
525 }
526
527 struct date_param {
528         newsnntp * nntp;
529         struct tm * lt;
530 };
531
532 struct date_result {
533         int error;
534 };
535
536 static void date_run(struct etpan_thread_op * op)
537 {
538         struct date_param * param;
539         struct date_result * result;
540         int r;
541         
542         param = op->param;
543         result = op->result;
544
545         CHECK_NNTP();
546
547         r = newsnntp_date(param->nntp, param->lt);
548         
549         result->error = r;
550         debug_print("nntp date run - end %i\n", r);
551 }
552
553 int nntp_threaded_date(Folder * folder, struct tm *lt)
554 {
555         struct date_param param;
556         struct date_result result;
557         
558         debug_print("nntp date - begin\n");
559         
560         param.nntp = get_nntp(folder);
561         param.lt = lt;
562
563         threaded_run(folder, &param, &result, date_run);
564         
565         debug_print("nntp date - end\n");
566         
567         return result.error;
568 }
569
570 struct list_param {
571         newsnntp * nntp;
572         clist **grouplist;
573 };
574
575 struct list_result {
576         int error;
577 };
578
579 static void list_run(struct etpan_thread_op * op)
580 {
581         struct list_param * param;
582         struct list_result * result;
583         int r;
584         
585         param = op->param;
586         result = op->result;
587
588         CHECK_NNTP();
589
590         r = newsnntp_list(param->nntp, param->grouplist);
591         
592         result->error = r;
593         debug_print("nntp list run - end %i\n", r);
594 }
595
596 int nntp_threaded_list(Folder * folder, clist **grouplist)
597 {
598         struct list_param param;
599         struct list_result result;
600         
601         debug_print("nntp list - begin\n");
602         
603         param.nntp = get_nntp(folder);
604         param.grouplist = grouplist;
605
606         threaded_run(folder, &param, &result, list_run);
607         
608         debug_print("nntp list - end\n");
609         
610         return result.error;
611 }
612
613 struct post_param {
614         newsnntp * nntp;
615         char *contents;
616         size_t len;
617 };
618
619 struct post_result {
620         int error;
621 };
622
623 static void post_run(struct etpan_thread_op * op)
624 {
625         struct post_param * param;
626         struct post_result * result;
627         int r;
628         
629         param = op->param;
630         result = op->result;
631
632         CHECK_NNTP();
633
634         r = newsnntp_post(param->nntp, param->contents, param->len);
635         
636         result->error = r;
637         debug_print("nntp post run - end %i\n", r);
638 }
639
640 int nntp_threaded_post(Folder * folder, char *contents, size_t len)
641 {
642         struct post_param param;
643         struct post_result result;
644         
645         debug_print("nntp post - begin\n");
646         
647         param.nntp = get_nntp(folder);
648         param.contents = contents;
649         param.len = len;
650
651         threaded_run(folder, &param, &result, post_run);
652         
653         debug_print("nntp post - end\n");
654         
655         return result.error;
656 }
657
658 struct article_param {
659         newsnntp * nntp;
660         guint32 num;
661         char **contents;
662         size_t *len;
663 };
664
665 struct article_result {
666         int error;
667 };
668
669 static void article_run(struct etpan_thread_op * op)
670 {
671         struct article_param * param;
672         struct article_result * result;
673         int r;
674         
675         param = op->param;
676         result = op->result;
677
678         CHECK_NNTP();
679
680         r = newsnntp_article(param->nntp, param->num, param->contents, param->len);
681         
682         result->error = r;
683         debug_print("nntp article run - end %i\n", r);
684 }
685
686 int nntp_threaded_article(Folder * folder, guint32 num, char **contents, size_t *len)
687 {
688         struct article_param param;
689         struct article_result result;
690         
691         debug_print("nntp article - begin\n");
692         
693         param.nntp = get_nntp(folder);
694         param.num = num;
695         param.contents = contents;
696         param.len = len;
697
698         threaded_run(folder, &param, &result, article_run);
699         
700         debug_print("nntp article - end\n");
701         
702         return result.error;
703 }
704
705 struct group_param {
706         newsnntp * nntp;
707         const char *group;
708         struct newsnntp_group_info **info;
709 };
710
711 struct group_result {
712         int error;
713 };
714
715 static void group_run(struct etpan_thread_op * op)
716 {
717         struct group_param * param;
718         struct group_result * result;
719         int r;
720         
721         param = op->param;
722         result = op->result;
723
724         CHECK_NNTP();
725
726         r = newsnntp_group(param->nntp, param->group, param->info);
727         
728         result->error = r;
729         debug_print("nntp group run - end %i\n", r);
730 }
731
732 int nntp_threaded_group(Folder * folder, const char *group, struct newsnntp_group_info **info)
733 {
734         struct group_param param;
735         struct group_result result;
736         
737         debug_print("nntp group - begin\n");
738         
739         param.nntp = get_nntp(folder);
740         param.group = group;
741         param.info = info;
742
743         threaded_run(folder, &param, &result, group_run);
744         
745         debug_print("nntp group - end\n");
746         
747         return result.error;
748 }
749
750 struct mode_reader_param {
751         newsnntp * nntp;
752 };
753
754 struct mode_reader_result {
755         int error;
756 };
757
758 static void mode_reader_run(struct etpan_thread_op * op)
759 {
760         struct mode_reader_param * param;
761         struct mode_reader_result * result;
762         int r;
763         
764         param = op->param;
765         result = op->result;
766
767         CHECK_NNTP();
768
769         r = newsnntp_mode_reader(param->nntp);
770         
771         result->error = r;
772         debug_print("nntp mode_reader run - end %i\n", r);
773 }
774
775 int nntp_threaded_mode_reader(Folder * folder)
776 {
777         struct mode_reader_param param;
778         struct mode_reader_result result;
779         
780         debug_print("nntp mode_reader - begin\n");
781         
782         param.nntp = get_nntp(folder);
783
784         threaded_run(folder, &param, &result, mode_reader_run);
785         
786         debug_print("nntp mode_reader - end\n");
787         
788         return result.error;
789 }
790
791 struct xover_param {
792         newsnntp * nntp;
793         guint32 beg;
794         guint32 end;
795         struct newsnntp_xover_resp_item **result;
796         clist **msglist;
797 };
798
799 struct xover_result {
800         int error;
801 };
802
803 static void xover_run(struct etpan_thread_op * op)
804 {
805         struct xover_param * param;
806         struct xover_result * result;
807         int r;
808         
809         param = op->param;
810         result = op->result;
811
812         CHECK_NNTP();
813         
814         if (param->result) {
815                 r = newsnntp_xover_single(param->nntp, param->beg, param->result);
816         } else {
817                 r = newsnntp_xover_range(param->nntp, param->beg, param->end, param->msglist);
818         }
819         
820         result->error = r;
821         debug_print("nntp xover run - end %i\n", r);
822 }
823
824 int nntp_threaded_xover(Folder * folder, guint32 beg, guint32 end, struct newsnntp_xover_resp_item **single_result, clist **multiple_result)
825 {
826         struct xover_param param;
827         struct xover_result result;
828         
829         debug_print("nntp xover - begin\n");
830         
831         param.nntp = get_nntp(folder);
832         param.beg = beg;
833         param.end = end;
834         param.result = single_result;
835         param.msglist = multiple_result;
836
837         threaded_run(folder, &param, &result, xover_run);
838         
839         debug_print("nntp xover - end\n");
840         
841         return result.error;
842 }
843
844 struct xhdr_param {
845         newsnntp * nntp;
846         const char *header;
847         guint32 beg;
848         guint32 end;
849         clist **hdrlist;
850 };
851
852 struct xhdr_result {
853         int error;
854 };
855
856 static void xhdr_run(struct etpan_thread_op * op)
857 {
858         struct xhdr_param * param;
859         struct xhdr_result * result;
860         int r;
861         
862         param = op->param;
863         result = op->result;
864
865         CHECK_NNTP();
866         
867         if (param->beg == param->end) {
868                 r = newsnntp_xhdr_single(param->nntp, param->header, param->beg, param->hdrlist);
869         } else {
870                 r = newsnntp_xhdr_range(param->nntp, param->header, param->beg, param->end, param->hdrlist);
871         }
872         
873         result->error = r;
874         debug_print("nntp xhdr run - end %i\n", r);
875 }
876
877 int nntp_threaded_xhdr(Folder * folder, const char *header, guint32 beg, guint32 end, clist **hdrlist)
878 {
879         struct xhdr_param param;
880         struct xhdr_result result;
881         
882         debug_print("nntp xhdr - begin\n");
883         
884         param.nntp = get_nntp(folder);
885         param.header = header;
886         param.beg = beg;
887         param.end = end;
888         param.hdrlist = hdrlist;
889
890         threaded_run(folder, &param, &result, xhdr_run);
891         
892         debug_print("nntp xhdr - end\n");
893         
894         return result.error;
895 }
896
897 void nntp_main_set_timeout(int sec)
898 {
899         mailstream_network_delay.tv_sec = sec;
900         mailstream_network_delay.tv_usec = 0;
901 }
902
903 #else
904
905 void nntp_main_init(void)
906 {
907 }
908 void nntp_main_done(gboolean have_connectivity)
909 {
910 }
911 void nntp_main_set_timeout(int sec)
912 {
913 }
914
915 void nntp_threaded_cancel(Folder * folder);
916 {
917 }
918
919 #endif