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
4 
5 /*
6  * ========================================================================
7  * Copyright 2006-2007 University of Washington
8  * Copyright 2013-2021 Eduardo Chappa
9  *
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
13  *
14  *     http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * ========================================================================
17  */
18 
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"
26 
27 
28 typedef enum {NotChecked, NotInCache, Found, Missing, End} NgCacheReturns;
29 
30 
31 /*
32  * Internal prototypes
33  */
34 NgCacheReturns chk_newsgrp_cache(char *);
35 void           add_newsgrp_cache(char *, NgCacheReturns);
36 
37 
38 /*----------------------------------------------------------------------
39   Function to see if a given MAILSTREAM mailbox is in the news namespace
40 
41   Input: stream -- mail stream to test
42 
43  Result:
44   ----*/
45 int
ns_test(char * mailbox,char * namespace)46 ns_test(char *mailbox, char *namespace)
47 {
48     if(mailbox){
49 	switch(*mailbox){
50 	  case '#' :
51 	    return(!struncmp(mailbox + 1, namespace, strlen(namespace)));
52 
53 	  case '{' :
54 	  {
55 	      NETMBX mbx;
56 
57 	      if(mail_valid_net_parse(mailbox, &mbx))
58 		return(ns_test(mbx.mailbox, namespace));
59 	  }
60 
61 	  break;
62 
63 	  default :
64 	    break;
65 	}
66     }
67 
68     return(0);
69 }
70 
71 
72 int
news_in_folders(struct variable * var)73 news_in_folders(struct variable *var)
74 {
75     int        i, found_news = 0;
76     CONTEXT_S *tc;
77 
78     if(!(var && var->current_val.l))
79       return(found_news);
80 
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++;
85 
86 	    free_context(&tc);
87 	}
88     }
89 
90     return(found_news);
91 }
92 
93 
94 /*----------------------------------------------------------------------
95     Verify and canonicalize news groups names.
96     Called from the message composer
97 
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
105 
106 Returns:  0 if all is OK
107          -1 if addresses weren't valid
108 
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   ----*/
113 int
news_grouper(char * given_group,char ** expanded_group,char ** error,char ** fccptr,void (* delay_warning)(void))114 news_grouper(char *given_group, char **expanded_group, char **error,
115 	     char **fccptr, void (*delay_warning)(void))
116 {
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 = 0;
120 
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
130 
131     dprint((5,
132 	"- news_build - (%s)\n", given_group ? given_group : "nul"));
133 
134     if(error)
135       *error = NULL;
136 
137     ng_ref[0] = '\0';
138 
139     /*------ parse given entries into a list ----*/
140     ntmpp = &nglist;
141     for(name = given_group; *name; name = end){
142 
143 	/* find start of next group name */
144         while(*name && (isspace((unsigned char)*name) || *name == ','))
145 	  name++;
146 
147 	/* find end of group name */
148 	end = name;
149 	while(*end && !isspace((unsigned char)*end) && *end != ',')
150 	  end++;
151 
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 */
162         }
163     }
164 
165     /*
166      * If fcc is not set or is set to default, then replace it if
167      * one of the recipient rules is in effect.
168      */
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);
175 
176 	  *fccptr = cpystr(nglist->groupname);
177 	}
178 	else if(!*fccptr) /* already default otherwise */
179 	  *fccptr = cpystr(ps_global->VAR_DEFAULT_FCC);
180     }
181 
182     if(!nglist){
183 	if(expanded_group)
184 	  *expanded_group = cpystr("");
185         return 0;
186     }
187 
188     if(!expanded_group)
189       return 0;
190 
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
196 
197     /* If we are doing validation */
198     if(F_OFF(F_NO_NEWS_VALIDATION, ps_global)){
199 	int need_to_talk_to_server = 0;
200 
201 	/*
202 	 * First check our cache of validated newsgroups to see if we even
203 	 * have to open a stream.
204 	 */
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++;
209 	}
210 
211 	if(need_to_talk_to_server){
212 
213 #ifdef SENDNEWS
214 	  if(no_servers == 0)
215 #endif
216 	    if(delay_warning)
217 	      (*delay_warning)();
218 
219 	    /*
220 	     * Build a stream to the first server that'll talk to us...
221 	     */
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;
231 	    }
232 	    if(!server || !stream){
233 		if(error)
234 #ifdef SENDNEWS
235 		{
236 		 /* don't say this over and over */
237 		 if(no_servers == 0){
238 		    if(!server || !*server || !**server)
239 		      no_servers++;
240 
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"));
246 		 }
247 		}
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;
256 	    }
257 	}
258 
259 	/*
260 	 * Now, go thru the list, making sure we can at least open each one...
261 	 */
262 	for(server = ps_global->VAR_NNTP_SERVER;
263 	    server && *server && **server; server++){
264 	    /*
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...
268 	     */
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);
281 		}
282 
283 	    }
284 
285 	    if(stream){
286 		pine_mail_close(stream);
287 		stream = NULL;
288 	    }
289 
290 	}
291 
292     }
293 
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);
302       }
303     }
304 
305     /*
306      * allocate and write the allowed, and error lists...
307      */
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);
314     }
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++;
320 
321 	    if(ntmp->next){
322 		*p1++ = ',';
323 		*p1++ = ' ';
324 	    }
325 	}
326 	else if (error){
327 	    while(*p2 && (ep - ng_error < sizeof(ng_error)-1))
328 	      *ep++ = *p2++;
329 
330 	    if(--cnt_errs > 0 && (ep - ng_error < sizeof(ng_error)-3)){
331 		strncpy(ep, ", ", sizeof(ng_error)-(ep-ng_error));
332 		ep += 2;
333 	    }
334 	}
335     }
336 
337     *p1 = '\0';
338 
339     if(error && num_in_error)
340       *error = cpystr(ng_error);
341 
342 done:
343     while((ntmp = nglist) != NULL){
344 	nglist = nglist->next;
345 	fs_give((void **)&ntmp->groupname);
346 	fs_give((void **)&ntmp);
347     }
348 
349     return(num_in_error ? -1 : 0);
350 }
351 
352 
353 typedef struct ng_cache {
354     char          *name;
355     NgCacheReturns val;
356 }NgCache;
357 
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
364 /*
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.
368  */
369 NgCacheReturns
chk_newsgrp_cache(char * group)370 chk_newsgrp_cache(char *group)
371 {
372     register NgCache *ngp;
373 
374     for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++){
375 	if(strcmp(group, ngp->name) == 0)
376 	  return(ngp->val);
377     }
378 
379     return NotInCache;
380 }
381 
382 
383 /*
384  * Add an entry to the newsgroup validity cache.
385  *
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.
388  */
389 void
add_newsgrp_cache(char * group,NgCacheReturns result)390 add_newsgrp_cache(char *group, NgCacheReturns result)
391 {
392     register NgCache *ngp;
393     NgCache save_ngp;
394 
395     /* first call, initialize cache */
396     if(!ng_cache_ptr){
397 	int i;
398 
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;
404 	}
405 	ng_cache_ptr[MAX_NGCACHE_ENTRIES].val  = End;
406     }
407 
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){
413 	    /*
414 	     * Cache is full, throw away top entry, move everything up,
415 	     * and put new entry on the bottom.
416 	     */
417 	    ngp = ng_cache_ptr;
418 	    if(ngp->name) /* just making sure */
419 	      fs_give((void **)&ngp->name);
420 
421 	    for(; (ngp+1)->name; ngp++){
422 		ngp->name = (ngp+1)->name;
423 		ngp->val  = (ngp+1)->val;
424 	    }
425 	}
426 	ngp->name = cpystr(group);
427 	ngp->val  = result;
428     }
429     else{
430 	/*
431 	 * Move this entry from current location to last to preserve
432 	 * LRU order.
433 	 */
434 	for(ngp = ng_cache_ptr; ngp && ngp->name; ngp++){
435 	    if(strcmp(group, ngp->name) == 0) /* found it */
436 	      break;
437 	}
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;
443 	}
444 	ngp->name = save_ngp.name;
445 	ngp->val  = save_ngp.val;
446     }
447 }
448 
449 
450 void
free_newsgrp_cache(void)451 free_newsgrp_cache(void)
452 {
453     register NgCache *ngp;
454 
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);
459 }
460