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