1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: news.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2006-2007 University of Washington
8 * Copyright 2013-2015 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/news.h"
21 #include "../pith/state.h"
22 #include "../pith/conf.h"
23 #include "../pith/context.h"
24 #include "../pith/stream.h"
25 #include "../pith/util.h"
28 typedef enum {NotChecked
, NotInCache
, Found
, Missing
, End
} NgCacheReturns
;
34 NgCacheReturns
chk_newsgrp_cache(char *);
35 void add_newsgrp_cache(char *, NgCacheReturns
);
38 /*----------------------------------------------------------------------
39 Function to see if a given MAILSTREAM mailbox is in the news namespace
41 Input: stream -- mail stream to test
46 ns_test(char *mailbox
, char *namespace)
51 return(!struncmp(mailbox
+ 1, namespace, strlen(namespace)));
57 if(mail_valid_net_parse(mailbox
, &mbx
))
58 return(ns_test(mbx
.mailbox
, namespace));
73 news_in_folders(struct variable
*var
)
75 int i
, found_news
= 0;
78 if(!(var
&& var
->current_val
.l
))
81 for(i
=0; !found_news
&& var
->current_val
.l
[i
]; i
++){
82 if((tc
= new_context(var
->current_val
.l
[i
], NULL
)) != NULL
){
83 if(tc
->use
& CNTXT_NEWS
)
94 /*----------------------------------------------------------------------
95 Verify and canonicalize news groups names.
96 Called from the message composer
98 Args: given_group -- List of groups typed by user
99 expanded_group -- pointer to point to expanded list, which will be
100 allocated here and freed in caller. If this is
101 NULL, don't attempt to validate.
102 error -- pointer to store error message
103 fcc -- pointer to point to fcc, which will be
104 allocated here and freed in caller
106 Returns: 0 if all is OK
107 -1 if addresses weren't valid
109 Test the given list of newstroups against those recognized by our nntp
110 servers. Testing by actually trying to open the list is much cheaper, both
111 in bandwidth and memory, than yanking the whole list across the wire.
114 news_grouper(char *given_group
, char **expanded_group
, char **error
,
115 char **fccptr
, void (*delay_warning
)(void))
117 char ng_error
[90], *p1
, *p2
, *name
, *end
, *ep
, **server
,
119 int expanded_len
= 0, num_in_error
= 0, cnt_errs
;
121 MAILSTREAM
*stream
= NULL
;
124 NgCacheReturns found
;
125 struct ng_list
*next
;
126 }*nglist
= NULL
, **ntmpp
, *ntmp
;
128 static int no_servers
= 0;
132 "- news_build - (%s)\n", given_group
? given_group
: "nul"));
139 /*------ parse given entries into a list ----*/
141 for(name
= given_group
; *name
; name
= end
){
143 /* find start of next group name */
144 while(*name
&& (isspace((unsigned char)*name
) || *name
== ','))
147 /* find end of group name */
149 while(*end
&& !isspace((unsigned char)*end
) && *end
!= ',')
153 *ntmpp
= (struct ng_list
*)fs_get(sizeof(struct ng_list
));
154 (*ntmpp
)->next
= NULL
;
155 (*ntmpp
)->found
= NotChecked
;
156 (*ntmpp
)->groupname
= fs_get(end
- name
+ 1);
157 strncpy((*ntmpp
)->groupname
, name
, end
- name
);
158 (*ntmpp
)->groupname
[end
- name
] = '\0';
159 ntmpp
= &(*ntmpp
)->next
;
161 break; /* no need to continue if just doing fcc */
166 * If fcc is not set or is set to default, then replace it if
167 * one of the recipient rules is in effect.
170 if((ps_global
->fcc_rule
== FCC_RULE_RECIP
||
171 ps_global
->fcc_rule
== FCC_RULE_NICK_RECIP
) &&
172 (nglist
&& nglist
->groupname
)){
174 fs_give((void **) fccptr
);
176 *fccptr
= cpystr(nglist
->groupname
);
178 else if(!*fccptr
) /* already default otherwise */
179 *fccptr
= cpystr(ps_global
->VAR_DEFAULT_FCC
);
184 *expanded_group
= cpystr("");
192 for(ntmp
= nglist
; debug
>= 9 && ntmp
; ntmp
= ntmp
->next
)
193 dprint((9, "Parsed group: --[%s]--\n",
194 ntmp
->groupname
? ntmp
->groupname
: "?"));
197 /* If we are doing validation */
198 if(F_OFF(F_NO_NEWS_VALIDATION
, ps_global
)){
199 int need_to_talk_to_server
= 0;
202 * First check our cache of validated newsgroups to see if we even
203 * have to open a stream.
205 for(ntmp
= nglist
; ntmp
; ntmp
= ntmp
->next
){
206 ntmp
->found
= chk_newsgrp_cache(ntmp
->groupname
);
207 if(ntmp
->found
== NotInCache
)
208 need_to_talk_to_server
++;
211 if(need_to_talk_to_server
){
220 * Build a stream to the first server that'll talk to us...
222 for(server
= ps_global
->VAR_NNTP_SERVER
;
223 server
&& *server
&& **server
;
225 snprintf(ng_ref
, sizeof(ng_ref
), "{%.*s/nntp}#news.",
226 sizeof(ng_ref
)-30, *server
);
227 if((stream
= pine_mail_open(stream
, ng_ref
,
228 OP_HALFOPEN
|SP_USEPOOL
|SP_TEMPUSE
,
232 if(!server
|| !stream
){
236 /* don't say this over and over */
238 if(!server
|| !*server
|| !**server
)
241 *error
= cpystr(no_servers
242 /* TRANSLATORS: groups refers to news groups */
243 ? _("Can't validate groups. No servers defined")
244 /* TRANSLATORS: groups refers to news groups */
245 : _("Can't validate groups. No servers responding"));
249 *error
= cpystr((!server
|| !*server
|| !**server
)
250 ? _("No servers defined for posting to newsgroups")
251 /* TRANSLATORS: groups refers to news groups */
252 : _("Can't validate groups. No servers responding"));
254 *expanded_group
= cpystr(given_group
);
260 * Now, go thru the list, making sure we can at least open each one...
262 for(server
= ps_global
->VAR_NNTP_SERVER
;
263 server
&& *server
&& **server
; server
++){
265 * It's faster and easier right now just to open the stream and
266 * do our own finds than to use the current folder_exists()
269 for(ntmp
= nglist
; ntmp
; ntmp
= ntmp
->next
){
270 if(ntmp
->found
== NotInCache
){
271 snprintf(ng_ref
, sizeof(ng_ref
), "{%.*s/nntp}#news.%.*s",
272 sizeof(ng_ref
)/2 - 10, *server
,
273 sizeof(ng_ref
)/2 - 10, ntmp
->groupname
);
274 ps_global
->noshow_error
= 1;
275 stream
= pine_mail_open(stream
, ng_ref
,
276 OP_SILENT
|SP_USEPOOL
|SP_TEMPUSE
,
278 ps_global
->noshow_error
= 0;
280 add_newsgrp_cache(ntmp
->groupname
, ntmp
->found
= Found
);
286 pine_mail_close(stream
);
294 /* figure length of string for matching groups */
295 for(ntmp
= nglist
; ntmp
; ntmp
= ntmp
->next
){
296 if(ntmp
->found
== Found
|| F_ON(F_NO_NEWS_VALIDATION
, ps_global
))
297 expanded_len
+= strlen(ntmp
->groupname
) + 2;
300 if(ntmp
->found
== NotInCache
)
301 add_newsgrp_cache(ntmp
->groupname
, ntmp
->found
= Missing
);
306 * allocate and write the allowed, and error lists...
308 p1
= *expanded_group
= fs_get((expanded_len
+ 1) * sizeof(char));
309 if(error
&& num_in_error
){
310 cnt_errs
= num_in_error
;
311 memset((void *)ng_error
, 0, sizeof(ng_error
));
312 snprintf(ng_error
, sizeof(ng_error
), "Unknown news group%s: ", plural(num_in_error
));
313 ep
= ng_error
+ strlen(ng_error
);
315 for(ntmp
= nglist
; ntmp
; ntmp
= ntmp
->next
){
316 p2
= ntmp
->groupname
;
317 if(ntmp
->found
== Found
|| F_ON(F_NO_NEWS_VALIDATION
, ps_global
)){
327 while(*p2
&& (ep
- ng_error
< sizeof(ng_error
)-1))
330 if(--cnt_errs
> 0 && (ep
- ng_error
< sizeof(ng_error
)-3)){
331 strncpy(ep
, ", ", sizeof(ng_error
)-(ep
-ng_error
));
339 if(error
&& num_in_error
)
340 *error
= cpystr(ng_error
);
343 while((ntmp
= nglist
) != NULL
){
344 nglist
= nglist
->next
;
345 fs_give((void **)&ntmp
->groupname
);
346 fs_give((void **)&ntmp
);
349 return(num_in_error
? -1 : 0);
353 typedef struct ng_cache
{
358 static NgCache
*ng_cache_ptr
;
359 #if defined(DOS) && !defined(_WINDOWS)
360 #define MAX_NGCACHE_ENTRIES 15
362 #define MAX_NGCACHE_ENTRIES 40
365 * Simple newsgroup validity cache. Opening a newsgroup to see if it
366 * exists can be very slow on a heavily loaded NNTP server, so we cache
370 chk_newsgrp_cache(char *group
)
372 register NgCache
*ngp
;
374 for(ngp
= ng_cache_ptr
; ngp
&& ngp
->name
; ngp
++){
375 if(strcmp(group
, ngp
->name
) == 0)
384 * Add an entry to the newsgroup validity cache.
386 * LRU entry is the one on the bottom, oldest on the top.
387 * A slot has an entry in it if name is not NULL.
390 add_newsgrp_cache(char *group
, NgCacheReturns result
)
392 register NgCache
*ngp
;
395 /* first call, initialize cache */
400 (NgCache
*)fs_get((MAX_NGCACHE_ENTRIES
+1)*sizeof(NgCache
));
401 for(i
= 0; i
<= MAX_NGCACHE_ENTRIES
; i
++){
402 ng_cache_ptr
[i
].name
= NULL
;
403 ng_cache_ptr
[i
].val
= NotInCache
;
405 ng_cache_ptr
[MAX_NGCACHE_ENTRIES
].val
= End
;
408 if(chk_newsgrp_cache(group
) == NotInCache
){
409 /* find first empty slot or End */
410 for(ngp
= ng_cache_ptr
; ngp
->name
; ngp
++)
414 * Cache is full, throw away top entry, move everything up,
415 * and put new entry on the bottom.
418 if(ngp
->name
) /* just making sure */
419 fs_give((void **)&ngp
->name
);
421 for(; (ngp
+1)->name
; ngp
++){
422 ngp
->name
= (ngp
+1)->name
;
423 ngp
->val
= (ngp
+1)->val
;
426 ngp
->name
= cpystr(group
);
431 * Move this entry from current location to last to preserve
434 for(ngp
= ng_cache_ptr
; ngp
&& ngp
->name
; ngp
++){
435 if(strcmp(group
, ngp
->name
) == 0) /* found it */
438 save_ngp
.name
= ngp
->name
;
439 save_ngp
.val
= ngp
->val
;
440 for(; (ngp
+1)->name
; ngp
++){
441 ngp
->name
= (ngp
+1)->name
;
442 ngp
->val
= (ngp
+1)->val
;
444 ngp
->name
= save_ngp
.name
;
445 ngp
->val
= save_ngp
.val
;
451 free_newsgrp_cache(void)
453 register NgCache
*ngp
;
455 for(ngp
= ng_cache_ptr
; ngp
&& ngp
->name
; ngp
++)
456 fs_give((void **)&ngp
->name
);
458 fs_give((void **)&ng_cache_ptr
);