* Addition of a link to the Apache License 2.0. This is available from
[alpine.git] / pith / news.c
bloba829c51e4c4ec75a938516f8c76e26fd443f10c6
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: news.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2007 University of Washington
8 * Copyright 2013-2020 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;
32 * Internal prototypes
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
43 Result:
44 ----*/
45 int
46 ns_test(char *mailbox, char *namespace)
48 if(mailbox){
49 switch(*mailbox){
50 case '#' :
51 return(!struncmp(mailbox + 1, namespace, strlen(namespace)));
53 case '{' :
55 NETMBX mbx;
57 if(mail_valid_net_parse(mailbox, &mbx))
58 return(ns_test(mbx.mailbox, namespace));
61 break;
63 default :
64 break;
68 return(0);
72 int
73 news_in_folders(struct variable *var)
75 int i, found_news = 0;
76 CONTEXT_S *tc;
78 if(!(var && var->current_val.l))
79 return(found_news);
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)
84 found_news++;
86 free_context(&tc);
90 return(found_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.
112 ----*/
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,
118 ng_ref[MAILTMPLEN];
119 int expanded_len = 0, num_in_error = 0, cnt_errs;
121 MAILSTREAM *stream = NULL;
122 struct ng_list {
123 char *groupname;
124 NgCacheReturns found;
125 struct ng_list *next;
126 }*nglist = NULL, **ntmpp, *ntmp;
127 #ifdef SENDNEWS
128 static int no_servers = 0;
129 #endif
131 dprint((5,
132 "- news_build - (%s)\n", given_group ? given_group : "nul"));
134 if(error)
135 *error = NULL;
137 ng_ref[0] = '\0';
139 /*------ parse given entries into a list ----*/
140 ntmpp = &nglist;
141 for(name = given_group; *name; name = end){
143 /* find start of next group name */
144 while(*name && (isspace((unsigned char)*name) || *name == ','))
145 name++;
147 /* find end of group name */
148 end = name;
149 while(*end && !isspace((unsigned char)*end) && *end != ',')
150 end++;
152 if(end != name){
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;
160 if(!expanded_group)
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.
169 if(fccptr){
170 if((ps_global->fcc_rule == FCC_RULE_RECIP ||
171 ps_global->fcc_rule == FCC_RULE_NICK_RECIP) &&
172 (nglist && nglist->groupname)){
173 if(*fccptr)
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);
182 if(!nglist){
183 if(expanded_group)
184 *expanded_group = cpystr("");
185 return 0;
188 if(!expanded_group)
189 return 0;
191 #ifdef DEBUG
192 for(ntmp = nglist; debug >= 9 && ntmp; ntmp = ntmp->next)
193 dprint((9, "Parsed group: --[%s]--\n",
194 ntmp->groupname ? ntmp->groupname : "?"));
195 #endif
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){
213 #ifdef SENDNEWS
214 if(no_servers == 0)
215 #endif
216 if(delay_warning)
217 (*delay_warning)();
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;
224 server++){ /* MAILTMPLEN = sizeof(ng_ref) */
225 snprintf(ng_ref, sizeof(ng_ref), "{%.*s/nntp}#news.",
226 MAILTMPLEN-30, *server);
227 if((stream = pine_mail_open(stream, ng_ref,
228 OP_HALFOPEN|SP_USEPOOL|SP_TEMPUSE,
229 NULL)) != NULL)
230 break;
232 if(!server || !stream){
233 if(error)
234 #ifdef SENDNEWS
236 /* don't say this over and over */
237 if(no_servers == 0){
238 if(!server || !*server || !**server)
239 no_servers++;
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"));
248 #else
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"));
253 #endif
254 *expanded_group = cpystr(given_group);
255 goto done;
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()
267 * interface...
269 for(ntmp = nglist; ntmp; ntmp = ntmp->next){
270 if(ntmp->found == NotInCache){ /* MAILTMPLEN = sizeof(ng_ref) */
271 snprintf(ng_ref, sizeof(ng_ref), "{%.*s/nntp}#news.%.*s",
272 MAILTMPLEN/2 - 10, *server,
273 MAILTMPLEN/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,
277 NULL);
278 ps_global->noshow_error = 0;
279 if(stream)
280 add_newsgrp_cache(ntmp->groupname, ntmp->found = Found);
285 if(stream){
286 pine_mail_close(stream);
287 stream = NULL;
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;
298 else{
299 num_in_error++;
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)){
318 while(*p2)
319 *p1++ = *p2++;
321 if(ntmp->next){
322 *p1++ = ',';
323 *p1++ = ' ';
326 else if (error){
327 while(*p2 && (ep - ng_error < sizeof(ng_error)-1))
328 *ep++ = *p2++;
330 if(--cnt_errs > 0 && (ep - ng_error < sizeof(ng_error)-3)){
331 strncpy(ep, ", ", sizeof(ng_error)-(ep-ng_error));
332 ep += 2;
337 *p1 = '\0';
339 if(error && num_in_error)
340 *error = cpystr(ng_error);
342 done:
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 {
354 char *name;
355 NgCacheReturns val;
356 }NgCache;
358 static NgCache *ng_cache_ptr;
359 #if defined(DOS) && !defined(_WINDOWS)
360 #define MAX_NGCACHE_ENTRIES 15
361 #else
362 #define MAX_NGCACHE_ENTRIES 40
363 #endif
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
367 * the results.
369 NgCacheReturns
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)
376 return(ngp->val);
379 return NotInCache;
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.
389 void
390 add_newsgrp_cache(char *group, NgCacheReturns result)
392 register NgCache *ngp;
393 NgCache save_ngp;
395 /* first call, initialize cache */
396 if(!ng_cache_ptr){
397 int i;
399 ng_cache_ptr =
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++)
411 ;/* do nothing */
412 if(ngp->val == End){
414 * Cache is full, throw away top entry, move everything up,
415 * and put new entry on the bottom.
417 ngp = ng_cache_ptr;
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);
427 ngp->val = result;
429 else{
431 * Move this entry from current location to last to preserve
432 * LRU order.
434 for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++){
435 if(strcmp(group, ngp->name) == 0) /* found it */
436 break;
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;
450 void
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);
457 if(ng_cache_ptr)
458 fs_give((void **)&ng_cache_ptr);