2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2007 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/news.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/context.h"
20 #include "../pith/stream.h"
21 #include "../pith/util.h"
24 typedef enum {NotChecked
, NotInCache
, Found
, Missing
, End
} NgCacheReturns
;
30 NgCacheReturns
chk_newsgrp_cache(char *);
31 void add_newsgrp_cache(char *, NgCacheReturns
);
34 /*----------------------------------------------------------------------
35 Function to see if a given MAILSTREAM mailbox is in the news namespace
37 Input: stream -- mail stream to test
42 ns_test(char *mailbox
, char *namespace)
47 return(!struncmp(mailbox
+ 1, namespace, strlen(namespace)));
53 if(mail_valid_net_parse(mailbox
, &mbx
))
54 return(ns_test(mbx
.mailbox
, namespace));
69 news_in_folders(struct variable
*var
)
71 int i
, found_news
= 0;
74 if(!(var
&& var
->current_val
.l
))
77 for(i
=0; !found_news
&& var
->current_val
.l
[i
]; i
++){
78 if((tc
= new_context(var
->current_val
.l
[i
], NULL
)) != NULL
){
79 if(tc
->use
& CNTXT_NEWS
)
90 /*----------------------------------------------------------------------
91 Verify and canonicalize news groups names.
92 Called from the message composer
94 Args: given_group -- List of groups typed by user
95 expanded_group -- pointer to point to expanded list, which will be
96 allocated here and freed in caller. If this is
97 NULL, don't attempt to validate.
98 error -- pointer to store error message
99 fcc -- pointer to point to fcc, which will be
100 allocated here and freed in caller
102 Returns: 0 if all is OK
103 -1 if addresses weren't valid
105 Test the given list of newstroups against those recognized by our nntp
106 servers. Testing by actually trying to open the list is much cheaper, both
107 in bandwidth and memory, than yanking the whole list across the wire.
110 news_grouper(char *given_group
, char **expanded_group
, char **error
,
111 char **fccptr
, void (*delay_warning
)(void))
113 char ng_error
[90], *p1
, *p2
, *name
, *end
, *ep
, **server
,
115 int expanded_len
= 0, num_in_error
= 0, cnt_errs
= 0;
117 MAILSTREAM
*stream
= NULL
;
120 NgCacheReturns found
;
121 struct ng_list
*next
;
122 }*nglist
= NULL
, **ntmpp
, *ntmp
;
124 static int no_servers
= 0;
128 "- news_build - (%s)\n", given_group
? given_group
: "nul"));
135 /*------ parse given entries into a list ----*/
137 for(name
= given_group
; *name
; name
= end
){
139 /* find start of next group name */
140 while(*name
&& (isspace((unsigned char)*name
) || *name
== ','))
143 /* find end of group name */
145 while(*end
&& !isspace((unsigned char)*end
) && *end
!= ',')
149 *ntmpp
= (struct ng_list
*)fs_get(sizeof(struct ng_list
));
150 (*ntmpp
)->next
= NULL
;
151 (*ntmpp
)->found
= NotChecked
;
152 (*ntmpp
)->groupname
= fs_get(end
- name
+ 1);
153 strncpy((*ntmpp
)->groupname
, name
, end
- name
);
154 (*ntmpp
)->groupname
[end
- name
] = '\0';
155 ntmpp
= &(*ntmpp
)->next
;
157 break; /* no need to continue if just doing fcc */
162 * If fcc is not set or is set to default, then replace it if
163 * one of the recipient rules is in effect.
166 if((ps_global
->fcc_rule
== FCC_RULE_RECIP
||
167 ps_global
->fcc_rule
== FCC_RULE_NICK_RECIP
) &&
168 (nglist
&& nglist
->groupname
)){
170 fs_give((void **) fccptr
);
172 *fccptr
= cpystr(nglist
->groupname
);
174 else if(!*fccptr
) /* already default otherwise */
175 *fccptr
= cpystr(ps_global
->VAR_DEFAULT_FCC
);
180 *expanded_group
= cpystr("");
188 for(ntmp
= nglist
; debug
>= 9 && ntmp
; ntmp
= ntmp
->next
)
189 dprint((9, "Parsed group: --[%s]--\n",
190 ntmp
->groupname
? ntmp
->groupname
: "?"));
193 /* If we are doing validation */
194 if(F_OFF(F_NO_NEWS_VALIDATION
, ps_global
)){
195 int need_to_talk_to_server
= 0;
198 * First check our cache of validated newsgroups to see if we even
199 * have to open a stream.
201 for(ntmp
= nglist
; ntmp
; ntmp
= ntmp
->next
){
202 ntmp
->found
= chk_newsgrp_cache(ntmp
->groupname
);
203 if(ntmp
->found
== NotInCache
)
204 need_to_talk_to_server
++;
207 if(need_to_talk_to_server
){
216 * Build a stream to the first server that'll talk to us...
218 for(server
= ps_global
->VAR_NNTP_SERVER
;
219 server
&& *server
&& **server
;
220 server
++){ /* MAILTMPLEN = sizeof(ng_ref) */
221 snprintf(ng_ref
, sizeof(ng_ref
), "{%.*s/nntp}#news.",
222 MAILTMPLEN
-30, *server
);
223 if((stream
= pine_mail_open(stream
, ng_ref
,
224 OP_HALFOPEN
|SP_USEPOOL
|SP_TEMPUSE
,
228 if(!server
|| !stream
){
232 /* don't say this over and over */
234 if(!server
|| !*server
|| !**server
)
237 *error
= cpystr(no_servers
238 /* TRANSLATORS: groups refers to news groups */
239 ? _("Can't validate groups. No servers defined")
240 /* TRANSLATORS: groups refers to news groups */
241 : _("Can't validate groups. No servers responding"));
245 *error
= cpystr((!server
|| !*server
|| !**server
)
246 ? _("No servers defined for posting to newsgroups")
247 /* TRANSLATORS: groups refers to news groups */
248 : _("Can't validate groups. No servers responding"));
250 *expanded_group
= cpystr(given_group
);
256 * Now, go thru the list, making sure we can at least open each one...
258 for(server
= ps_global
->VAR_NNTP_SERVER
;
259 server
&& *server
&& **server
; server
++){
261 * It's faster and easier right now just to open the stream and
262 * do our own finds than to use the current folder_exists()
265 for(ntmp
= nglist
; ntmp
; ntmp
= ntmp
->next
){
266 if(ntmp
->found
== NotInCache
){ /* MAILTMPLEN = sizeof(ng_ref) */
267 snprintf(ng_ref
, sizeof(ng_ref
), "{%.*s/nntp}#news.%.*s",
268 MAILTMPLEN
/2 - 10, *server
,
269 MAILTMPLEN
/2 - 10, ntmp
->groupname
);
270 ps_global
->noshow_error
= 1;
271 stream
= pine_mail_open(stream
, ng_ref
,
272 OP_SILENT
|SP_USEPOOL
|SP_TEMPUSE
,
274 ps_global
->noshow_error
= 0;
276 add_newsgrp_cache(ntmp
->groupname
, ntmp
->found
= Found
);
282 pine_mail_close(stream
);
290 /* figure length of string for matching groups */
291 for(ntmp
= nglist
; ntmp
; ntmp
= ntmp
->next
){
292 if(ntmp
->found
== Found
|| F_ON(F_NO_NEWS_VALIDATION
, ps_global
))
293 expanded_len
+= strlen(ntmp
->groupname
) + 2;
296 if(ntmp
->found
== NotInCache
)
297 add_newsgrp_cache(ntmp
->groupname
, ntmp
->found
= Missing
);
302 * allocate and write the allowed, and error lists...
304 p1
= *expanded_group
= fs_get((expanded_len
+ 1) * sizeof(char));
305 if(error
&& num_in_error
){
306 cnt_errs
= num_in_error
;
307 memset((void *)ng_error
, 0, sizeof(ng_error
));
308 snprintf(ng_error
, sizeof(ng_error
), "Unknown news group%s: ", plural(num_in_error
));
309 ep
= ng_error
+ strlen(ng_error
);
311 for(ntmp
= nglist
; ntmp
; ntmp
= ntmp
->next
){
312 p2
= ntmp
->groupname
;
313 if(ntmp
->found
== Found
|| F_ON(F_NO_NEWS_VALIDATION
, ps_global
)){
323 while(*p2
&& (ep
- ng_error
< sizeof(ng_error
)-1))
326 if(--cnt_errs
> 0 && (ep
- ng_error
< sizeof(ng_error
)-3)){
327 strncpy(ep
, ", ", sizeof(ng_error
)-(ep
-ng_error
));
335 if(error
&& num_in_error
)
336 *error
= cpystr(ng_error
);
339 while((ntmp
= nglist
) != NULL
){
340 nglist
= nglist
->next
;
341 fs_give((void **)&ntmp
->groupname
);
342 fs_give((void **)&ntmp
);
345 return(num_in_error
? -1 : 0);
349 typedef struct ng_cache
{
354 static NgCache
*ng_cache_ptr
;
355 #if defined(DOS) && !defined(_WINDOWS)
356 #define MAX_NGCACHE_ENTRIES 15
358 #define MAX_NGCACHE_ENTRIES 40
361 * Simple newsgroup validity cache. Opening a newsgroup to see if it
362 * exists can be very slow on a heavily loaded NNTP server, so we cache
366 chk_newsgrp_cache(char *group
)
368 register NgCache
*ngp
;
370 for(ngp
= ng_cache_ptr
; ngp
&& ngp
->name
; ngp
++){
371 if(strcmp(group
, ngp
->name
) == 0)
380 * Add an entry to the newsgroup validity cache.
382 * LRU entry is the one on the bottom, oldest on the top.
383 * A slot has an entry in it if name is not NULL.
386 add_newsgrp_cache(char *group
, NgCacheReturns result
)
388 register NgCache
*ngp
;
391 /* first call, initialize cache */
396 (NgCache
*)fs_get((MAX_NGCACHE_ENTRIES
+1)*sizeof(NgCache
));
397 for(i
= 0; i
<= MAX_NGCACHE_ENTRIES
; i
++){
398 ng_cache_ptr
[i
].name
= NULL
;
399 ng_cache_ptr
[i
].val
= NotInCache
;
401 ng_cache_ptr
[MAX_NGCACHE_ENTRIES
].val
= End
;
404 if(chk_newsgrp_cache(group
) == NotInCache
){
405 /* find first empty slot or End */
406 for(ngp
= ng_cache_ptr
; ngp
->name
; ngp
++)
410 * Cache is full, throw away top entry, move everything up,
411 * and put new entry on the bottom.
414 if(ngp
->name
) /* just making sure */
415 fs_give((void **)&ngp
->name
);
417 for(; (ngp
+1)->name
; ngp
++){
418 ngp
->name
= (ngp
+1)->name
;
419 ngp
->val
= (ngp
+1)->val
;
422 ngp
->name
= cpystr(group
);
427 * Move this entry from current location to last to preserve
430 for(ngp
= ng_cache_ptr
; ngp
&& ngp
->name
; ngp
++){
431 if(strcmp(group
, ngp
->name
) == 0) /* found it */
434 save_ngp
.name
= ngp
->name
;
435 save_ngp
.val
= ngp
->val
;
436 for(; (ngp
+1)->name
; ngp
++){
437 ngp
->name
= (ngp
+1)->name
;
438 ngp
->val
= (ngp
+1)->val
;
440 ngp
->name
= save_ngp
.name
;
441 ngp
->val
= save_ngp
.val
;
447 free_newsgrp_cache(void)
449 register NgCache
*ngp
;
451 for(ngp
= ng_cache_ptr
; ngp
&& ngp
->name
; ngp
++)
452 fs_give((void **)&ngp
->name
);
454 fs_give((void **)&ng_cache_ptr
);