2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2005-2022 the Claws Mail team and DINH Viet Hoa
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.
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.
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/>.
21 #include "claws-features.h"
27 #include <glib/gi18n.h>
28 #include "nntp-thread.h"
30 #include <sys/types.h>
32 #if (defined(__DragonFly__) || defined (__NetBSD__) || defined (__FreeBSD__) || defined (__OpenBSD__) || defined (__CYGWIN__))
33 #include <sys/socket.h>
42 #include "etpan-thread-manager.h"
43 #include "etpan-ssl.h"
45 #include "mainwindow.h"
46 #include "ssl_certificate.h"
48 #include "remotefolder.h"
51 #include "statusbar.h"
53 #define DISABLE_LOG_DURING_LOGIN
55 #define NNTP_BATCH_SIZE 5000
57 static struct etpan_thread_manager
* thread_manager
= NULL
;
58 static chash
* nntp_hash
= NULL
;
59 static chash
* session_hash
= NULL
;
60 static guint thread_manager_signal
= 0;
61 static GIOChannel
* io_channel
= NULL
;
63 static int do_newsnntp_socket_connect(newsnntp
* imap
, const char * server
,
64 gushort port
, ProxyInfo
* proxy_info
)
70 return newsnntp_socket_connect(imap
, server
, port
);
75 sock
= sock_connect(proxy_info
->proxy_host
, proxy_info
->proxy_port
);
78 return NEWSNNTP_ERROR_CONNECTION_REFUSED
;
80 if (proxy_connect(sock
, server
, port
, proxy_info
) < 0) {
81 sock_close(sock
, TRUE
);
82 return NEWSNNTP_ERROR_CONNECTION_REFUSED
;
85 stream
= mailstream_socket_open_timeout(sock
->sock
,
88 sock_close(sock
, TRUE
);
89 return NEWSNNTP_ERROR_MEMORY
;
92 /* Libetpan now has the socket fd, and we're not interested in
93 * rest of the SockInfo struct. Let's free it, while not touching
94 * the socket itself. */
95 sock_close(sock
, FALSE
);
97 return newsnntp_connect(imap
, stream
);
101 static int do_newsnntp_ssl_connect_with_callback(newsnntp
* imap
, const char * server
,
103 void (* callback
)(struct mailstream_ssl_context
* ssl_context
, void * data
),
105 ProxyInfo
*proxy_info
)
111 return newsnntp_ssl_connect_with_callback(imap
, server
,
112 port
, callback
, data
);
117 sock
= sock_connect(proxy_info
->proxy_host
, proxy_info
->proxy_port
);
120 return NEWSNNTP_ERROR_CONNECTION_REFUSED
;
122 if (proxy_connect(sock
, server
, port
, proxy_info
) < 0) {
123 sock_close(sock
, TRUE
);
124 return NEWSNNTP_ERROR_CONNECTION_REFUSED
;
127 stream
= mailstream_ssl_open_with_callback_timeout(sock
->sock
,
128 imap
->nntp_timeout
, callback
, data
);
129 if (stream
== NULL
) {
130 sock_close(sock
, TRUE
);
131 return NEWSNNTP_ERROR_SSL
;
134 /* Libetpan now has the socket fd, and we're not interested in
135 * rest of the SockInfo struct. Let's free it, while not touching
136 * the socket itself. */
137 sock_close(sock
, FALSE
);
139 return newsnntp_connect(imap
, stream
);
143 static void nntp_logger(int direction
, const char * str
, size_t size
)
150 log_print(LOG_PROTOCOL
, "NNTP%c [data - %"G_GSIZE_FORMAT
" bytes]\n", direction
?'>':'<', size
);
153 buf
= malloc(size
+1);
154 memset(buf
, 0, size
+1);
155 strncpy(buf
, str
, size
);
158 if (!strncmp(buf
, "<<<<<<<", 7)
159 || !strncmp(buf
, ">>>>>>>", 7)) {
163 while (strstr(buf
, "\r"))
164 *strstr(buf
, "\r") = ' ';
165 while (strlen(buf
) > 0 && buf
[strlen(buf
)-1] == '\n')
166 buf
[strlen(buf
)-1] = '\0';
168 lines
= g_strsplit(buf
, "\n", -1);
170 while (lines
[i
] && *lines
[i
]) {
171 log_print(LOG_PROTOCOL
, "NNTP%c %s\n", direction
?'>':'<', lines
[i
]);
178 static void delete_nntp(Folder
*folder
, newsnntp
*nntp
)
183 key
.len
= sizeof(folder
);
184 chash_delete(session_hash
, &key
, NULL
);
187 key
.len
= sizeof(nntp
);
188 if (nntp
&& nntp
->nntp_stream
) {
189 /* we don't want libetpan to logout */
190 mailstream_close(nntp
->nntp_stream
);
191 nntp
->nntp_stream
= NULL
;
193 debug_print("removing newsnntp %p\n", nntp
);
197 static gboolean
thread_manager_event(GIOChannel
* source
,
198 GIOCondition condition
,
205 if (condition
& G_IO_IN
)
206 g_io_channel_read_chars(source
, &ch
, 1, &bytes_read
, NULL
);
208 etpan_thread_manager_loop(thread_manager
);
213 #define ETPAN_DEFAULT_NETWORK_TIMEOUT 60
214 extern gboolean etpan_skip_ssl_cert_check
;
216 void nntp_main_init(gboolean skip_ssl_cert_check
)
218 int fd_thread_manager
;
220 etpan_skip_ssl_cert_check
= skip_ssl_cert_check
;
222 nntp_hash
= chash_new(CHASH_COPYKEY
, CHASH_DEFAULTSIZE
);
223 session_hash
= chash_new(CHASH_COPYKEY
, CHASH_DEFAULTSIZE
);
225 thread_manager
= etpan_thread_manager_new();
227 fd_thread_manager
= etpan_thread_manager_get_fd(thread_manager
);
230 io_channel
= g_io_channel_unix_new(fd_thread_manager
);
232 io_channel
= g_io_channel_win32_new_fd(fd_thread_manager
);
235 thread_manager_signal
= g_io_add_watch_full(io_channel
, 0, G_IO_IN
,
236 thread_manager_event
,
241 void nntp_main_done(gboolean have_connectivity
)
243 nntp_disconnect_all(have_connectivity
);
244 etpan_thread_manager_stop(thread_manager
);
245 #if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__)
248 etpan_thread_manager_join(thread_manager
);
250 g_source_remove(thread_manager_signal
);
251 g_io_channel_unref(io_channel
);
253 etpan_thread_manager_free(thread_manager
);
255 chash_free(session_hash
);
256 chash_free(nntp_hash
);
259 void nntp_init(Folder
* folder
)
261 struct etpan_thread
* thread
;
265 thread
= etpan_thread_manager_get_thread(thread_manager
);
268 key
.len
= sizeof(folder
);
272 chash_set(nntp_hash
, &key
, &value
, NULL
);
275 void nntp_done(Folder
* folder
)
277 struct etpan_thread
* thread
;
283 key
.len
= sizeof(folder
);
285 r
= chash_get(nntp_hash
, &key
, &value
);
291 etpan_thread_unbind(thread
);
293 chash_delete(nntp_hash
, &key
, NULL
);
295 debug_print("remove thread\n");
298 static struct etpan_thread
* get_thread(Folder
* folder
)
300 struct etpan_thread
* thread
;
306 key
.len
= sizeof(folder
);
308 r
= chash_get(nntp_hash
, &key
, &value
);
317 static newsnntp
* get_nntp(Folder
* folder
)
325 key
.len
= sizeof(folder
);
327 r
= chash_get(session_hash
, &key
, &value
);
332 debug_print("found nntp %p\n", nntp
);
337 static void generic_cb(int cancelled
, void * result
, void * callback_data
)
339 struct etpan_thread_op
* op
;
341 op
= (struct etpan_thread_op
*) callback_data
;
343 debug_print("generic_cb\n");
347 static void threaded_run(Folder
* folder
, void * param
, void * result
,
348 void (* func
)(struct etpan_thread_op
* ))
350 struct etpan_thread_op
* op
;
351 struct etpan_thread
* thread
;
352 void (*previous_stream_logger
)(int direction
,
353 const char * str
, size_t size
);
355 nntp_folder_ref(folder
);
357 op
= etpan_thread_op_new();
359 op
->nntp
= get_nntp(folder
);
364 op
->callback
= generic_cb
;
365 op
->callback_data
= op
;
367 previous_stream_logger
= mailstream_logger
;
368 mailstream_logger
= nntp_logger
;
370 thread
= get_thread(folder
);
371 etpan_thread_op_schedule(thread
, op
);
373 while (!op
->finished
) {
374 gtk_main_iteration();
377 mailstream_logger
= previous_stream_logger
;
379 etpan_thread_op_free(op
);
381 nntp_folder_unref(folder
);
387 struct connect_param
{
389 PrefsAccount
*account
;
392 ProxyInfo
* proxy_info
;
395 struct connect_result
{
399 #define CHECK_NNTP() { \
400 if (!param->nntp) { \
401 result->error = NEWSNNTP_ERROR_BAD_STATE; \
406 static void connect_run(struct etpan_thread_op
* op
)
409 struct connect_param
* param
;
410 struct connect_result
* result
;
417 r
= do_newsnntp_socket_connect(param
->nntp
,
418 param
->server
, param
->port
,
425 int nntp_threaded_connect(Folder
* folder
, const char * server
, int port
, ProxyInfo
*proxy_info
)
427 struct connect_param param
;
428 struct connect_result result
;
431 newsnntp
* nntp
, * oldnntp
;
433 oldnntp
= get_nntp(folder
);
435 nntp
= newsnntp_new(0, NULL
);
438 debug_print("deleting old nntp %p\n", oldnntp
);
439 delete_nntp(folder
, oldnntp
);
443 key
.len
= sizeof(folder
);
446 chash_set(session_hash
, &key
, &value
, NULL
);
449 param
.server
= server
;
451 param
.proxy_info
= proxy_info
;
454 threaded_run(folder
, ¶m
, &result
, connect_run
);
456 debug_print("connect ok %i with nntp %p\n", result
.error
, nntp
);
461 static void connect_ssl_run(struct etpan_thread_op
* op
)
464 struct connect_param
* param
;
465 struct connect_result
* result
;
472 r
= do_newsnntp_ssl_connect_with_callback(param
->nntp
,
473 param
->server
, param
->port
,
474 etpan_connect_ssl_context_cb
, param
->account
,
479 int nntp_threaded_connect_ssl(Folder
* folder
, const char * server
, int port
, ProxyInfo
*proxy_info
)
481 struct connect_param param
;
482 struct connect_result result
;
485 newsnntp
* nntp
, * oldnntp
;
486 gboolean accept_if_valid
= FALSE
;
488 oldnntp
= get_nntp(folder
);
490 nntp
= newsnntp_new(0, NULL
);
493 debug_print("deleting old nntp %p\n", oldnntp
);
494 delete_nntp(folder
, oldnntp
);
498 key
.len
= sizeof(folder
);
501 chash_set(session_hash
, &key
, &value
, NULL
);
504 param
.server
= server
;
506 param
.account
= folder
->account
;
507 param
.proxy_info
= proxy_info
;
510 accept_if_valid
= folder
->account
->ssl_certs_auto_accept
;
513 threaded_run(folder
, ¶m
, &result
, connect_ssl_run
);
515 if (result
.error
== NEWSNNTP_NO_ERROR
&& !etpan_skip_ssl_cert_check
) {
516 if (etpan_certificate_check(nntp
->nntp_stream
, server
, port
,
517 accept_if_valid
) != TRUE
)
520 debug_print("connect %d with nntp %p\n", result
.error
, nntp
);
526 void nntp_threaded_disconnect(Folder
* folder
)
530 nntp
= get_nntp(folder
);
532 debug_print("was disconnected\n");
536 debug_print("deleting old nntp %p\n", nntp
);
537 delete_nntp(folder
, nntp
);
539 debug_print("disconnect ok\n");
542 void nntp_threaded_cancel(Folder
* folder
)
546 nntp
= get_nntp(folder
);
547 if (nntp
->nntp_stream
!= NULL
)
548 mailstream_cancel(nntp
->nntp_stream
);
555 const char * password
;
558 struct login_result
{
562 static void login_run(struct etpan_thread_op
* op
)
564 struct login_param
* param
;
565 struct login_result
* result
;
567 #ifdef DISABLE_LOG_DURING_LOGIN
576 #ifdef DISABLE_LOG_DURING_LOGIN
577 old_debug
= mailstream_debug
;
578 mailstream_debug
= 0;
581 r
= newsnntp_authinfo_username(param
->nntp
, param
->login
);
582 /* libetpan returning NO_ERROR means it received resp.code 281:
583 in this case auth. is already successful, no password is needed. */
584 if (r
== NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD
) {
585 r
= newsnntp_authinfo_password(param
->nntp
, param
->password
);
590 #ifdef DISABLE_LOG_DURING_LOGIN
591 mailstream_debug
= old_debug
;
595 if (param
->nntp
->nntp_response
)
596 nntp_logger(0, param
->nntp
->nntp_response
, strlen(param
->nntp
->nntp_response
));
598 debug_print("nntp login run - end %i\n", r
);
601 int nntp_threaded_login(Folder
* folder
, const char * login
, const char * password
)
603 struct login_param param
;
604 struct login_result result
;
606 debug_print("nntp login - begin\n");
608 param
.nntp
= get_nntp(folder
);
610 param
.password
= password
;
612 threaded_run(folder
, ¶m
, &result
, login_run
);
614 debug_print("nntp login - end\n");
628 static void date_run(struct etpan_thread_op
* op
)
630 struct date_param
* param
;
631 struct date_result
* result
;
639 r
= newsnntp_date(param
->nntp
, param
->lt
);
642 debug_print("nntp date run - end %i\n", r
);
645 int nntp_threaded_date(Folder
* folder
, struct tm
*lt
)
647 struct date_param param
;
648 struct date_result result
;
650 debug_print("nntp date - begin\n");
652 param
.nntp
= get_nntp(folder
);
655 threaded_run(folder
, ¶m
, &result
, date_run
);
657 debug_print("nntp date - end\n");
671 static void list_run(struct etpan_thread_op
* op
)
673 struct list_param
* param
;
674 struct list_result
* result
;
682 r
= newsnntp_list(param
->nntp
, param
->grouplist
);
685 debug_print("nntp list run - end %i\n", r
);
688 int nntp_threaded_list(Folder
* folder
, clist
**grouplist
)
690 struct list_param param
;
691 struct list_result result
;
693 debug_print("nntp list - begin\n");
695 param
.nntp
= get_nntp(folder
);
696 param
.grouplist
= grouplist
;
698 threaded_run(folder
, ¶m
, &result
, list_run
);
700 debug_print("nntp list - end\n");
715 static void post_run(struct etpan_thread_op
* op
)
717 struct post_param
* param
;
718 struct post_result
* result
;
726 r
= newsnntp_post(param
->nntp
, param
->contents
, param
->len
);
729 debug_print("nntp post run - end %i\n", r
);
732 int nntp_threaded_post(Folder
* folder
, char *contents
, size_t len
)
734 struct post_param param
;
735 struct post_result result
;
737 debug_print("nntp post - begin\n");
739 param
.nntp
= get_nntp(folder
);
740 param
.contents
= contents
;
743 threaded_run(folder
, ¶m
, &result
, post_run
);
745 debug_print("nntp post - end\n");
750 struct article_param
{
757 struct article_result
{
761 static void article_run(struct etpan_thread_op
* op
)
763 struct article_param
* param
;
764 struct article_result
* result
;
772 r
= newsnntp_article(param
->nntp
, param
->num
, param
->contents
, param
->len
);
775 debug_print("nntp article run - end %i\n", r
);
778 int nntp_threaded_article(Folder
* folder
, guint32 num
, char **contents
, size_t *len
)
780 struct article_param param
;
781 struct article_result result
;
783 debug_print("nntp article - begin\n");
785 param
.nntp
= get_nntp(folder
);
787 param
.contents
= contents
;
790 threaded_run(folder
, ¶m
, &result
, article_run
);
792 debug_print("nntp article - end\n");
800 struct newsnntp_group_info
**info
;
803 struct group_result
{
807 static void group_run(struct etpan_thread_op
* op
)
809 struct group_param
* param
;
810 struct group_result
* result
;
818 r
= newsnntp_group(param
->nntp
, param
->group
, param
->info
);
821 debug_print("nntp group run - end %i\n", r
);
824 int nntp_threaded_group(Folder
* folder
, const char *group
, struct newsnntp_group_info
**info
)
826 struct group_param param
;
827 struct group_result result
;
829 debug_print("nntp group - begin\n");
831 param
.nntp
= get_nntp(folder
);
835 threaded_run(folder
, ¶m
, &result
, group_run
);
837 debug_print("nntp group - end\n");
842 struct mode_reader_param
{
846 struct mode_reader_result
{
850 static void mode_reader_run(struct etpan_thread_op
* op
)
852 struct mode_reader_param
* param
;
853 struct mode_reader_result
* result
;
861 r
= newsnntp_mode_reader(param
->nntp
);
864 debug_print("nntp mode_reader run - end %i\n", r
);
867 int nntp_threaded_mode_reader(Folder
* folder
)
869 struct mode_reader_param param
;
870 struct mode_reader_result result
;
872 debug_print("nntp mode_reader - begin\n");
874 param
.nntp
= get_nntp(folder
);
876 threaded_run(folder
, ¶m
, &result
, mode_reader_run
);
878 debug_print("nntp mode_reader - end\n");
887 struct newsnntp_xover_resp_item
**result
;
891 struct xover_result
{
895 static void xover_run(struct etpan_thread_op
* op
)
897 struct xover_param
* param
;
898 struct xover_result
* result
;
907 r
= newsnntp_xover_single(param
->nntp
, param
->beg
, param
->result
);
909 r
= newsnntp_xover_range(param
->nntp
, param
->beg
, param
->end
, param
->msglist
);
913 debug_print("nntp xover run %d-%d - end %i\n",
914 param
->beg
, param
->end
, r
);
917 int nntp_threaded_xover(Folder
* folder
, guint32 beg
, guint32 end
, struct newsnntp_xover_resp_item
**single_result
, clist
**multiple_result
)
919 struct xover_param param
;
920 struct xover_result result
;
921 clist
*l
= NULL
, *h
= NULL
;
922 guint32 cbeg
= 0, cend
= 0;
924 debug_print("nntp xover - begin (%d-%d)\n", beg
, end
);
928 /* Request the overview in batches of NNTP_BATCH_SIZE, to prevent
929 * long stalls or libetpan choking on too large server response,
930 * and to allow updating any progress indicators while we work. */
932 while (cbeg
<= end
&& cend
<= end
) {
933 cend
= cbeg
+ (NNTP_BATCH_SIZE
- 1);
937 statusbar_progress_all(cbeg
- beg
, end
- beg
, 1);
940 param
.nntp
= get_nntp(folder
);
943 param
.result
= single_result
;
946 threaded_run(folder
, ¶m
, &result
, xover_run
);
949 if (result
.error
!= NEWSNNTP_NO_ERROR
) {
950 log_warning(LOG_PROTOCOL
, _("couldn't get xover range\n"));
951 debug_print("couldn't get xover for %d-%d\n", cbeg
, cend
);
953 newsnntp_xover_resp_list_free(l
);
954 newsnntp_xover_resp_list_free(h
);
955 statusbar_progress_all(0, 0, 0);
959 /* Append the new data (l) to list of results (h). */
961 debug_print("total items so far %d, items this batch %d\n",
962 clist_count(h
), clist_count(l
));
968 cbeg
+= NNTP_BATCH_SIZE
;
971 statusbar_progress_all(0, 0, 0);
973 debug_print("nntp xover - end\n");
975 *multiple_result
= h
;
992 static void xhdr_run(struct etpan_thread_op
* op
)
994 struct xhdr_param
* param
;
995 struct xhdr_result
* result
;
1003 if (param
->beg
== param
->end
) {
1004 r
= newsnntp_xhdr_single(param
->nntp
, param
->header
, param
->beg
, param
->hdrlist
);
1006 r
= newsnntp_xhdr_range(param
->nntp
, param
->header
, param
->beg
, param
->end
, param
->hdrlist
);
1010 debug_print("nntp xhdr '%s %d-%d' run - end %i\n",
1011 param
->header
, param
->beg
, param
->end
, r
);
1014 int nntp_threaded_xhdr(Folder
* folder
, const char *header
, guint32 beg
, guint32 end
, clist
**hdrlist
)
1016 struct xhdr_param param
;
1017 struct xhdr_result result
;
1019 clist
*h
= *hdrlist
;
1020 guint32 cbeg
= 0, cend
= 0;
1022 debug_print("nntp xhdr %s - begin (%d-%d)\n", header
, beg
, end
);
1027 /* Request the headers in batches of NNTP_BATCH_SIZE, to prevent
1028 * long stalls or libetpan choking on too large server response,
1029 * and to allow updating any progress indicators while we work. */
1031 while (cbeg
<= end
&& cend
<= end
) {
1032 cend
= cbeg
+ NNTP_BATCH_SIZE
- 1;
1036 statusbar_progress_all(cbeg
- beg
, end
- beg
, 1);
1039 param
.nntp
= get_nntp(folder
);
1040 param
.header
= header
;
1045 threaded_run(folder
, ¶m
, &result
, xhdr_run
);
1048 if (result
.error
!= NEWSNNTP_NO_ERROR
) {
1049 log_warning(LOG_PROTOCOL
, _("couldn't get xhdr range\n"));
1050 debug_print("couldn't get xhdr %s %d-%d\n", header
, cbeg
, cend
);
1052 newsnntp_xhdr_free(l
);
1053 newsnntp_xhdr_free(h
);
1054 statusbar_progress_all(0, 0, 0);
1055 return result
.error
;
1058 /* Append the new data (l) to list of results (h). */
1060 debug_print("total items so far %d, items this batch %d\n",
1061 clist_count(h
), clist_count(l
));
1067 cbeg
+= NNTP_BATCH_SIZE
;
1070 statusbar_progress_all(0, 0, 0);
1072 debug_print("nntp xhdr %s - end (%d-%d)\n", header
, beg
, end
);
1076 return result
.error
;
1079 void nntp_main_set_timeout(int sec
)
1081 mailstream_network_delay
.tv_sec
= sec
;
1082 mailstream_network_delay
.tv_usec
= 0;
1087 void nntp_main_init(void)
1090 void nntp_main_done(gboolean have_connectivity
)
1093 void nntp_main_set_timeout(int sec
)
1097 void nntp_threaded_cancel(Folder
* folder
);