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>
39 #include "etpan-thread-manager.h"
40 #include "etpan-ssl.h"
42 #include "mainwindow.h"
43 #include "ssl_certificate.h"
45 #include "remotefolder.h"
48 #include "statusbar.h"
50 #define DISABLE_LOG_DURING_LOGIN
52 #define NNTP_BATCH_SIZE 5000
54 static struct etpan_thread_manager
* thread_manager
= NULL
;
55 static chash
* nntp_hash
= NULL
;
56 static chash
* session_hash
= NULL
;
57 static guint thread_manager_signal
= 0;
58 static GIOChannel
* io_channel
= NULL
;
60 static int do_newsnntp_socket_connect(newsnntp
* imap
, const char * server
,
61 gushort port
, ProxyInfo
* proxy_info
)
67 return newsnntp_socket_connect(imap
, server
, port
);
72 sock
= sock_connect(proxy_info
->proxy_host
, proxy_info
->proxy_port
);
75 return NEWSNNTP_ERROR_CONNECTION_REFUSED
;
77 if (proxy_connect(sock
, server
, port
, proxy_info
) < 0) {
78 sock_close(sock
, TRUE
);
79 return NEWSNNTP_ERROR_CONNECTION_REFUSED
;
82 stream
= mailstream_socket_open_timeout(sock
->sock
,
85 sock_close(sock
, TRUE
);
86 return NEWSNNTP_ERROR_MEMORY
;
89 /* Libetpan now has the socket fd, and we're not interested in
90 * rest of the SockInfo struct. Let's free it, while not touching
91 * the socket itself. */
92 sock_close(sock
, FALSE
);
94 return newsnntp_connect(imap
, stream
);
98 static int do_newsnntp_ssl_connect_with_callback(newsnntp
* imap
, const char * server
,
100 void (* callback
)(struct mailstream_ssl_context
* ssl_context
, void * data
),
102 ProxyInfo
*proxy_info
)
108 return newsnntp_ssl_connect_with_callback(imap
, server
,
109 port
, callback
, data
);
114 sock
= sock_connect(proxy_info
->proxy_host
, proxy_info
->proxy_port
);
117 return NEWSNNTP_ERROR_CONNECTION_REFUSED
;
119 if (proxy_connect(sock
, server
, port
, proxy_info
) < 0) {
120 sock_close(sock
, TRUE
);
121 return NEWSNNTP_ERROR_CONNECTION_REFUSED
;
124 stream
= mailstream_ssl_open_with_callback_timeout(sock
->sock
,
125 imap
->nntp_timeout
, callback
, data
);
126 if (stream
== NULL
) {
127 sock_close(sock
, TRUE
);
128 return NEWSNNTP_ERROR_SSL
;
131 /* Libetpan now has the socket fd, and we're not interested in
132 * rest of the SockInfo struct. Let's free it, while not touching
133 * the socket itself. */
134 sock_close(sock
, FALSE
);
136 return newsnntp_connect(imap
, stream
);
140 static void nntp_logger(int direction
, const char * str
, size_t size
)
147 log_print(LOG_PROTOCOL
, "NNTP%c [data - %"G_GSIZE_FORMAT
" bytes]\n", direction
?'>':'<', size
);
150 buf
= malloc(size
+1);
151 memset(buf
, 0, size
+1);
152 strncpy(buf
, str
, size
);
155 if (!strncmp(buf
, "<<<<<<<", 7)
156 || !strncmp(buf
, ">>>>>>>", 7)) {
160 while (strstr(buf
, "\r"))
161 *strstr(buf
, "\r") = ' ';
162 while (strlen(buf
) > 0 && buf
[strlen(buf
)-1] == '\n')
163 buf
[strlen(buf
)-1] = '\0';
165 lines
= g_strsplit(buf
, "\n", -1);
167 while (lines
[i
] && *lines
[i
]) {
168 log_print(LOG_PROTOCOL
, "NNTP%c %s\n", direction
?'>':'<', lines
[i
]);
175 static void delete_nntp(Folder
*folder
, newsnntp
*nntp
)
180 key
.len
= sizeof(folder
);
181 chash_delete(session_hash
, &key
, NULL
);
184 key
.len
= sizeof(nntp
);
185 if (nntp
&& nntp
->nntp_stream
) {
186 /* we don't want libetpan to logout */
187 mailstream_close(nntp
->nntp_stream
);
188 nntp
->nntp_stream
= NULL
;
190 debug_print("removing newsnntp %p\n", nntp
);
194 static gboolean
thread_manager_event(GIOChannel
* source
,
195 GIOCondition condition
,
202 if (condition
& G_IO_IN
)
203 g_io_channel_read_chars(source
, &ch
, 1, &bytes_read
, NULL
);
205 etpan_thread_manager_loop(thread_manager
);
210 #define ETPAN_DEFAULT_NETWORK_TIMEOUT 60
211 extern gboolean etpan_skip_ssl_cert_check
;
213 void nntp_main_init(gboolean skip_ssl_cert_check
)
215 int fd_thread_manager
;
217 etpan_skip_ssl_cert_check
= skip_ssl_cert_check
;
219 nntp_hash
= chash_new(CHASH_COPYKEY
, CHASH_DEFAULTSIZE
);
220 session_hash
= chash_new(CHASH_COPYKEY
, CHASH_DEFAULTSIZE
);
222 thread_manager
= etpan_thread_manager_new();
224 fd_thread_manager
= etpan_thread_manager_get_fd(thread_manager
);
227 io_channel
= g_io_channel_unix_new(fd_thread_manager
);
229 io_channel
= g_io_channel_win32_new_fd(fd_thread_manager
);
232 thread_manager_signal
= g_io_add_watch_full(io_channel
, 0, G_IO_IN
,
233 thread_manager_event
,
238 void nntp_main_done(gboolean have_connectivity
)
240 nntp_disconnect_all(have_connectivity
);
241 etpan_thread_manager_stop(thread_manager
);
242 #if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__)
245 etpan_thread_manager_join(thread_manager
);
247 g_source_remove(thread_manager_signal
);
248 g_io_channel_unref(io_channel
);
250 etpan_thread_manager_free(thread_manager
);
252 chash_free(session_hash
);
253 chash_free(nntp_hash
);
256 void nntp_init(Folder
* folder
)
258 struct etpan_thread
* thread
;
262 thread
= etpan_thread_manager_get_thread(thread_manager
);
265 key
.len
= sizeof(folder
);
269 chash_set(nntp_hash
, &key
, &value
, NULL
);
272 void nntp_done(Folder
* folder
)
274 struct etpan_thread
* thread
;
280 key
.len
= sizeof(folder
);
282 r
= chash_get(nntp_hash
, &key
, &value
);
288 etpan_thread_unbind(thread
);
290 chash_delete(nntp_hash
, &key
, NULL
);
292 debug_print("remove thread\n");
295 static struct etpan_thread
* get_thread(Folder
* folder
)
297 struct etpan_thread
* thread
;
303 key
.len
= sizeof(folder
);
305 r
= chash_get(nntp_hash
, &key
, &value
);
314 static newsnntp
* get_nntp(Folder
* folder
)
322 key
.len
= sizeof(folder
);
324 r
= chash_get(session_hash
, &key
, &value
);
329 debug_print("found nntp %p\n", nntp
);
334 static void generic_cb(int cancelled
, void * result
, void * callback_data
)
336 struct etpan_thread_op
* op
;
338 op
= (struct etpan_thread_op
*) callback_data
;
340 debug_print("generic_cb\n");
344 static void threaded_run(Folder
* folder
, void * param
, void * result
,
345 void (* func
)(struct etpan_thread_op
* ))
347 struct etpan_thread_op
* op
;
348 struct etpan_thread
* thread
;
349 void (*previous_stream_logger
)(int direction
,
350 const char * str
, size_t size
);
352 nntp_folder_ref(folder
);
354 op
= etpan_thread_op_new();
356 op
->nntp
= get_nntp(folder
);
361 op
->callback
= generic_cb
;
362 op
->callback_data
= op
;
364 previous_stream_logger
= mailstream_logger
;
365 mailstream_logger
= nntp_logger
;
367 thread
= get_thread(folder
);
368 etpan_thread_op_schedule(thread
, op
);
370 while (!op
->finished
) {
371 gtk_main_iteration();
374 mailstream_logger
= previous_stream_logger
;
376 etpan_thread_op_free(op
);
378 nntp_folder_unref(folder
);
384 struct connect_param
{
386 PrefsAccount
*account
;
389 ProxyInfo
* proxy_info
;
392 struct connect_result
{
396 #define CHECK_NNTP() { \
397 if (!param->nntp) { \
398 result->error = NEWSNNTP_ERROR_BAD_STATE; \
403 static void connect_run(struct etpan_thread_op
* op
)
406 struct connect_param
* param
;
407 struct connect_result
* result
;
414 r
= do_newsnntp_socket_connect(param
->nntp
,
415 param
->server
, param
->port
,
422 int nntp_threaded_connect(Folder
* folder
, const char * server
, int port
, ProxyInfo
*proxy_info
)
424 struct connect_param param
;
425 struct connect_result result
;
428 newsnntp
* nntp
, * oldnntp
;
430 oldnntp
= get_nntp(folder
);
432 nntp
= newsnntp_new(0, NULL
);
435 debug_print("deleting old nntp %p\n", oldnntp
);
436 delete_nntp(folder
, oldnntp
);
440 key
.len
= sizeof(folder
);
443 chash_set(session_hash
, &key
, &value
, NULL
);
446 param
.server
= server
;
448 param
.proxy_info
= proxy_info
;
451 threaded_run(folder
, ¶m
, &result
, connect_run
);
453 debug_print("connect ok %i with nntp %p\n", result
.error
, nntp
);
458 static void connect_ssl_run(struct etpan_thread_op
* op
)
461 struct connect_param
* param
;
462 struct connect_result
* result
;
469 r
= do_newsnntp_ssl_connect_with_callback(param
->nntp
,
470 param
->server
, param
->port
,
471 etpan_connect_ssl_context_cb
, param
->account
,
476 int nntp_threaded_connect_ssl(Folder
* folder
, const char * server
, int port
, ProxyInfo
*proxy_info
)
478 struct connect_param param
;
479 struct connect_result result
;
482 newsnntp
* nntp
, * oldnntp
;
483 gboolean accept_if_valid
= FALSE
;
485 oldnntp
= get_nntp(folder
);
487 nntp
= newsnntp_new(0, NULL
);
490 debug_print("deleting old nntp %p\n", oldnntp
);
491 delete_nntp(folder
, oldnntp
);
495 key
.len
= sizeof(folder
);
498 chash_set(session_hash
, &key
, &value
, NULL
);
501 param
.server
= server
;
503 param
.account
= folder
->account
;
504 param
.proxy_info
= proxy_info
;
507 accept_if_valid
= folder
->account
->ssl_certs_auto_accept
;
510 threaded_run(folder
, ¶m
, &result
, connect_ssl_run
);
512 if (result
.error
== NEWSNNTP_NO_ERROR
&& !etpan_skip_ssl_cert_check
) {
513 if (etpan_certificate_check(nntp
->nntp_stream
, server
, port
,
514 accept_if_valid
) != TRUE
)
517 debug_print("connect %d with nntp %p\n", result
.error
, nntp
);
523 void nntp_threaded_disconnect(Folder
* folder
)
527 nntp
= get_nntp(folder
);
529 debug_print("was disconnected\n");
533 debug_print("deleting old nntp %p\n", nntp
);
534 delete_nntp(folder
, nntp
);
536 debug_print("disconnect ok\n");
539 void nntp_threaded_cancel(Folder
* folder
)
543 nntp
= get_nntp(folder
);
544 if (nntp
->nntp_stream
!= NULL
)
545 mailstream_cancel(nntp
->nntp_stream
);
552 const char * password
;
555 struct login_result
{
559 static void login_run(struct etpan_thread_op
* op
)
561 struct login_param
* param
;
562 struct login_result
* result
;
564 #ifdef DISABLE_LOG_DURING_LOGIN
573 #ifdef DISABLE_LOG_DURING_LOGIN
574 old_debug
= mailstream_debug
;
575 mailstream_debug
= 0;
578 r
= newsnntp_authinfo_username(param
->nntp
, param
->login
);
579 /* libetpan returning NO_ERROR means it received resp.code 281:
580 in this case auth. is already successful, no password is needed. */
581 if (r
== NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD
) {
582 r
= newsnntp_authinfo_password(param
->nntp
, param
->password
);
587 #ifdef DISABLE_LOG_DURING_LOGIN
588 mailstream_debug
= old_debug
;
592 if (param
->nntp
->nntp_response
)
593 nntp_logger(0, param
->nntp
->nntp_response
, strlen(param
->nntp
->nntp_response
));
595 debug_print("nntp login run - end %i\n", r
);
598 int nntp_threaded_login(Folder
* folder
, const char * login
, const char * password
)
600 struct login_param param
;
601 struct login_result result
;
603 debug_print("nntp login - begin\n");
605 param
.nntp
= get_nntp(folder
);
607 param
.password
= password
;
609 threaded_run(folder
, ¶m
, &result
, login_run
);
611 debug_print("nntp login - end\n");
625 static void date_run(struct etpan_thread_op
* op
)
627 struct date_param
* param
;
628 struct date_result
* result
;
636 r
= newsnntp_date(param
->nntp
, param
->lt
);
639 debug_print("nntp date run - end %i\n", r
);
642 int nntp_threaded_date(Folder
* folder
, struct tm
*lt
)
644 struct date_param param
;
645 struct date_result result
;
647 debug_print("nntp date - begin\n");
649 param
.nntp
= get_nntp(folder
);
652 threaded_run(folder
, ¶m
, &result
, date_run
);
654 debug_print("nntp date - end\n");
668 static void list_run(struct etpan_thread_op
* op
)
670 struct list_param
* param
;
671 struct list_result
* result
;
679 r
= newsnntp_list(param
->nntp
, param
->grouplist
);
682 debug_print("nntp list run - end %i\n", r
);
685 int nntp_threaded_list(Folder
* folder
, clist
**grouplist
)
687 struct list_param param
;
688 struct list_result result
;
690 debug_print("nntp list - begin\n");
692 param
.nntp
= get_nntp(folder
);
693 param
.grouplist
= grouplist
;
695 threaded_run(folder
, ¶m
, &result
, list_run
);
697 debug_print("nntp list - end\n");
712 static void post_run(struct etpan_thread_op
* op
)
714 struct post_param
* param
;
715 struct post_result
* result
;
723 r
= newsnntp_post(param
->nntp
, param
->contents
, param
->len
);
726 debug_print("nntp post run - end %i\n", r
);
729 int nntp_threaded_post(Folder
* folder
, char *contents
, size_t len
)
731 struct post_param param
;
732 struct post_result result
;
734 debug_print("nntp post - begin\n");
736 param
.nntp
= get_nntp(folder
);
737 param
.contents
= contents
;
740 threaded_run(folder
, ¶m
, &result
, post_run
);
742 debug_print("nntp post - end\n");
747 struct article_param
{
754 struct article_result
{
758 static void article_run(struct etpan_thread_op
* op
)
760 struct article_param
* param
;
761 struct article_result
* result
;
769 r
= newsnntp_article(param
->nntp
, param
->num
, param
->contents
, param
->len
);
772 debug_print("nntp article run - end %i\n", r
);
775 int nntp_threaded_article(Folder
* folder
, guint32 num
, char **contents
, size_t *len
)
777 struct article_param param
;
778 struct article_result result
;
780 debug_print("nntp article - begin\n");
782 param
.nntp
= get_nntp(folder
);
784 param
.contents
= contents
;
787 threaded_run(folder
, ¶m
, &result
, article_run
);
789 debug_print("nntp article - end\n");
797 struct newsnntp_group_info
**info
;
800 struct group_result
{
804 static void group_run(struct etpan_thread_op
* op
)
806 struct group_param
* param
;
807 struct group_result
* result
;
815 r
= newsnntp_group(param
->nntp
, param
->group
, param
->info
);
818 debug_print("nntp group run - end %i\n", r
);
821 int nntp_threaded_group(Folder
* folder
, const char *group
, struct newsnntp_group_info
**info
)
823 struct group_param param
;
824 struct group_result result
;
826 debug_print("nntp group - begin\n");
828 param
.nntp
= get_nntp(folder
);
832 threaded_run(folder
, ¶m
, &result
, group_run
);
834 debug_print("nntp group - end\n");
839 struct mode_reader_param
{
843 struct mode_reader_result
{
847 static void mode_reader_run(struct etpan_thread_op
* op
)
849 struct mode_reader_param
* param
;
850 struct mode_reader_result
* result
;
858 r
= newsnntp_mode_reader(param
->nntp
);
861 debug_print("nntp mode_reader run - end %i\n", r
);
864 int nntp_threaded_mode_reader(Folder
* folder
)
866 struct mode_reader_param param
;
867 struct mode_reader_result result
;
869 debug_print("nntp mode_reader - begin\n");
871 param
.nntp
= get_nntp(folder
);
873 threaded_run(folder
, ¶m
, &result
, mode_reader_run
);
875 debug_print("nntp mode_reader - end\n");
884 struct newsnntp_xover_resp_item
**result
;
888 struct xover_result
{
892 static void xover_run(struct etpan_thread_op
* op
)
894 struct xover_param
* param
;
895 struct xover_result
* result
;
904 r
= newsnntp_xover_single(param
->nntp
, param
->beg
, param
->result
);
906 r
= newsnntp_xover_range(param
->nntp
, param
->beg
, param
->end
, param
->msglist
);
910 debug_print("nntp xover run %d-%d - end %i\n",
911 param
->beg
, param
->end
, r
);
914 int nntp_threaded_xover(Folder
* folder
, guint32 beg
, guint32 end
, struct newsnntp_xover_resp_item
**single_result
, clist
**multiple_result
)
916 struct xover_param param
;
917 struct xover_result result
;
918 clist
*l
= NULL
, *h
= NULL
;
919 guint32 cbeg
= 0, cend
= 0;
921 debug_print("nntp xover - begin (%d-%d)\n", beg
, end
);
925 /* Request the overview in batches of NNTP_BATCH_SIZE, to prevent
926 * long stalls or libetpan choking on too large server response,
927 * and to allow updating any progress indicators while we work. */
929 while (cbeg
<= end
&& cend
<= end
) {
930 cend
= cbeg
+ (NNTP_BATCH_SIZE
- 1);
934 statusbar_progress_all(cbeg
- beg
, end
- beg
, 1);
937 param
.nntp
= get_nntp(folder
);
940 param
.result
= single_result
;
943 threaded_run(folder
, ¶m
, &result
, xover_run
);
946 if (result
.error
!= NEWSNNTP_NO_ERROR
) {
947 log_warning(LOG_PROTOCOL
, _("couldn't get xover range\n"));
948 debug_print("couldn't get xover for %d-%d\n", cbeg
, cend
);
950 newsnntp_xover_resp_list_free(l
);
951 newsnntp_xover_resp_list_free(h
);
952 statusbar_progress_all(0, 0, 0);
956 /* Append the new data (l) to list of results (h). */
958 debug_print("total items so far %d, items this batch %d\n",
959 clist_count(h
), clist_count(l
));
965 cbeg
+= NNTP_BATCH_SIZE
;
968 statusbar_progress_all(0, 0, 0);
970 debug_print("nntp xover - end\n");
972 *multiple_result
= h
;
989 static void xhdr_run(struct etpan_thread_op
* op
)
991 struct xhdr_param
* param
;
992 struct xhdr_result
* result
;
1000 if (param
->beg
== param
->end
) {
1001 r
= newsnntp_xhdr_single(param
->nntp
, param
->header
, param
->beg
, param
->hdrlist
);
1003 r
= newsnntp_xhdr_range(param
->nntp
, param
->header
, param
->beg
, param
->end
, param
->hdrlist
);
1007 debug_print("nntp xhdr '%s %d-%d' run - end %i\n",
1008 param
->header
, param
->beg
, param
->end
, r
);
1011 int nntp_threaded_xhdr(Folder
* folder
, const char *header
, guint32 beg
, guint32 end
, clist
**hdrlist
)
1013 struct xhdr_param param
;
1014 struct xhdr_result result
;
1016 clist
*h
= *hdrlist
;
1017 guint32 cbeg
= 0, cend
= 0;
1019 debug_print("nntp xhdr %s - begin (%d-%d)\n", header
, beg
, end
);
1024 /* Request the headers in batches of NNTP_BATCH_SIZE, to prevent
1025 * long stalls or libetpan choking on too large server response,
1026 * and to allow updating any progress indicators while we work. */
1028 while (cbeg
<= end
&& cend
<= end
) {
1029 cend
= cbeg
+ NNTP_BATCH_SIZE
- 1;
1033 statusbar_progress_all(cbeg
- beg
, end
- beg
, 1);
1036 param
.nntp
= get_nntp(folder
);
1037 param
.header
= header
;
1042 threaded_run(folder
, ¶m
, &result
, xhdr_run
);
1045 if (result
.error
!= NEWSNNTP_NO_ERROR
) {
1046 log_warning(LOG_PROTOCOL
, _("couldn't get xhdr range\n"));
1047 debug_print("couldn't get xhdr %s %d-%d\n", header
, cbeg
, cend
);
1049 newsnntp_xhdr_free(l
);
1050 newsnntp_xhdr_free(h
);
1051 statusbar_progress_all(0, 0, 0);
1052 return result
.error
;
1055 /* Append the new data (l) to list of results (h). */
1057 debug_print("total items so far %d, items this batch %d\n",
1058 clist_count(h
), clist_count(l
));
1064 cbeg
+= NNTP_BATCH_SIZE
;
1067 statusbar_progress_all(0, 0, 0);
1069 debug_print("nntp xhdr %s - end (%d-%d)\n", header
, beg
, end
);
1073 return result
.error
;
1076 void nntp_main_set_timeout(int sec
)
1078 mailstream_network_delay
.tv_sec
= sec
;
1079 mailstream_network_delay
.tv_usec
= 0;
1084 void nntp_main_init(void)
1087 void nntp_main_done(gboolean have_connectivity
)
1090 void nntp_main_set_timeout(int sec
)
1094 void nntp_threaded_cancel(Folder
* folder
);