1 /*
2   filter.c
3 
4   Article filtering.
5 
6   $Id: filter.c,v 1.8 2003/04/03 17:21:22 bears Exp $
7 */
8 
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12 
13 #include <ctype.h>
14 #include "common.h"
15 #include "configfile.h"
16 #include "itemlist.h"
17 #include "log.h"
18 #include "wildmat.h"
19 #include "group.h"
20 #include "util.h"
21 #include "filter.h"
22 
23 struct
24 {
25     int nFilters;
26     int maxFilters;
27     const Filter **filters;
28     Bool needGroups;
29 } filter = { 0, 0, NULL, FALSE };
30 
31 static unsigned long
countGroups(const char * grps)32 countGroups( const char *grps )
33 {
34     unsigned long res;
35 
36     res = 1;
37     while ( *grps != '\0' )
38     {
39 	if ( *grps == ',' )
40 	    res++;
41 	grps++;
42     }
43 
44     return res;
45 }
46 static unsigned long
countRefs(const char * refs)47 countRefs( const char *refs )
48 {
49     unsigned long res;
50     Bool inRef;
51 
52     res = 0;
53     inRef = FALSE;
54 
55     while ( *refs != '\0' )
56     {
57 	if ( inRef )
58 	{
59 	    if ( *refs == '>' )
60 	    {
61 		inRef = FALSE;
62 		res++;
63 	    }
64 	}
65 	else if ( *refs == '<' )
66 	    inRef = TRUE;
67 	refs++;
68     }
69 
70     return res;
71 }
72 
73 /* Check a single rule to see if it passes. */
74 static Bool
checkRule(const char * thisGrp,const char * newsgroups,const Over * ov,const FilterRule * r)75 checkRule( const char *thisGrp, const char *newsgroups,
76 	   const Over *ov, const FilterRule *r )
77 {
78     unsigned long ul;
79     ItemList *grps;
80     const char *p;
81     time_t articletime;
82 
83     switch( r->type )
84     {
85     case RULE_NEWSGROUP:
86 	if ( Wld_match( thisGrp, r->data.grp ) )
87 	    return TRUE;
88 	if ( newsgroups != NULL )
89 	{
90 	    grps = new_Itl( newsgroups, " ,\t" );
91 	    for ( p = Itl_first( grps ); p != NULL; p = Itl_next( grps ) )
92 		if ( Wld_match( p, r->data.grp ) )
93 		     return TRUE;
94 	    del_Itl( grps );
95 	}
96 	return FALSE;
97 
98     case RULE_SUBJECT:
99 	return ( regexec( &r->data.regex, Ov_subj( ov ), 0, NULL, 0 ) == 0 );
100 
101     case RULE_REFERENCE:        /* kill thread by Msg-Id in References: */
102 	return ( regexec( &r->data.regex, Ov_ref( ov ), 0, NULL, 0 ) == 0 );
103 
104     case RULE_FROM:
105 	return ( regexec( &r->data.regex, Ov_from( ov ), 0, NULL, 0 ) == 0 );
106 
107     case RULE_BYTES_LT:
108 	return ( Ov_bytes( ov ) < r->data.amount );
109 
110     case RULE_BYTES_EQ:
111 	return ( Ov_bytes( ov ) == r->data.amount );
112 
113     case RULE_BYTES_GT:
114 	return ( Ov_bytes( ov ) > r->data.amount );
115 
116     case RULE_LINES_LT:
117 	return ( Ov_lines( ov ) < r->data.amount );
118 
119     case RULE_LINES_EQ:
120 	return ( Ov_lines( ov ) == r->data.amount );
121 
122     case RULE_LINES_GT:
123 	return ( Ov_lines( ov ) > r->data.amount );
124 
125     case RULE_MSGID:
126 	return ( regexec( &r->data.regex, Ov_msgId( ov ), 0, NULL, 0 ) == 0 );
127 
128     case RULE_DATE_LT:
129         /* Utl_parseNewsDate() is quite picky. I'm not entirely happy
130            about this, but I won't implement a relaxed date parser. */
131 	articletime = Utl_parseNewsDate( Ov_date( ov ) );
132         if ( articletime == (time_t) -1 )
133             return FALSE;
134         return ( articletime < r->data.reftime.calctime );
135 
136     case RULE_DATE_EQ:
137 	articletime = Utl_parseNewsDate( Ov_date( ov ) );
138         if ( ( articletime == (time_t) -1)
139             && ( r->data.reftime.vartime == INVALID ))
140                 return TRUE;
141         if ( ( articletime == (time_t) -1)
142             != ( r->data.reftime.vartime == INVALID ))
143                 return FALSE;
144         return ( ( articletime <= r->data.reftime.calctime
145                                     + RULE_DATE_EQ_PRECISION )
146               && ( articletime >= r->data.reftime.calctime
147                                     - RULE_DATE_EQ_PRECISION )  );
148 
149     case RULE_DATE_GT:
150 	articletime = Utl_parseNewsDate( Ov_date( ov ) );
151         if ( articletime == (time_t) -1 )
152             return FALSE;
153 	return ( articletime > r->data.reftime.calctime );
154 
155     case RULE_NOREFS_LT:
156 	ul = countRefs( Ov_ref( ov ) );
157 	return ( ul < r->data.amount );
158 
159     case RULE_NOREFS_EQ:
160 	ul = countRefs( Ov_ref( ov ) );
161 	return ( ul == r->data.amount );
162 
163     case RULE_NOREFS_GT:
164 	ul = countRefs( Ov_ref( ov ) );
165 	return ( ul > r->data.amount );
166 
167     case RULE_XPOSTS_LT:
168 	if ( newsgroups == NULL )
169 	    return FALSE;
170 	ul = countGroups( newsgroups );
171 	return ( ul < r->data.amount );
172 
173     case RULE_XPOSTS_EQ:
174 	if ( newsgroups == NULL )
175 	    return FALSE;
176 	ul = countGroups( newsgroups );
177 	return ( ul == r->data.amount );
178 
179     case RULE_XPOSTS_GT:
180 	if ( newsgroups == NULL )
181 	    return FALSE;
182 	ul = countGroups( newsgroups );
183 	return ( ul > r->data.amount );
184 
185     case RULE_POST_STATUS:
186 	if ( Grp_postAllow( thisGrp ) == r->data.postAllow )
187 	    return TRUE;
188         return FALSE;
189 
190     }
191 
192     ASSERT( FALSE );	/* Shouldn't get here */
193     return 0;		/* Keep compiler quiet */
194 }
195 
196 /* Check a single filter to see if it fires. */
197 static Bool
checkFilter(const char * thisGrp,const char * newsgroups,const Over * ov,const Filter * f)198 checkFilter( const char *thisGrp, const char *newsgroups,
199 	     const Over *ov, const Filter *f )
200 {
201     int i;
202 
203     for ( i = 0; i < f->nRules; i++ )
204 	if ( ! checkRule( thisGrp, newsgroups, ov, &f->rules[i] ) )
205 	     return FALSE;
206 
207     return TRUE;
208 }
209 
210 /* Add a filter to the list of filters. */
211 void
Flt_addFilter(const Filter * f)212 Flt_addFilter( const Filter *f )
213 {
214     ASSERT( f != NULL );
215 
216     if ( ( filter.nFilters + 1 ) > filter.maxFilters )
217     {
218 	filter.filters =
219 	    ( const Filter ** ) realloc( filter.filters,
220 					 ( filter.maxFilters + 5 )
221 					 * sizeof( Filter * ) );
222 	if ( filter.filters == NULL )
223 	    Log_fatal( "Could not realloc filter list" );
224 	filter.maxFilters += 5;
225     }
226     filter.filters[ filter.nFilters++ ] = f;
227 }
228 
229 
230 /*
231  * Called by Fetch_init().
232  * Must be called before
233  * Fetch_getNewGrps(), Client_getNewgrps(), client.c:processGrps()
234  * because processGrps() sets the stampfile needed by lastupdate.
235  */
236 void
Flt_init(const char * server)237 Flt_init( const char * server )
238 {
239     int index1, index2;
240     time_t now, lastupdate;
241     FilterRule * thisRule ;
242     Str filename;
243 
244     time ( &now );
245     lastupdate = (time_t) 0;    /* defaults to start of epoch */
246 
247     snprintf( filename, MAXCHAR, "%s/lastupdate.%s",
248               Cfg_spoolDir(), server );
249     if ( !Utl_getStamp( &lastupdate , filename ) )
250         /* There's no stamp file if server has never been queried.
251          *
252          */
253         Log_dbg( LOG_DBG_FILTER,
254             "Filter unable to get stamp file %s . Please query server.", filename );
255 
256     /* traverse all rules of all filters */
257 
258     for ( index1 = 0; index1 < filter.nFilters; index1++ )
259     {
260         for ( index2 = 0; index2 < filter.filters[ index1 ] -> nRules; index2++ )
261         {
262             thisRule = & ( filter.filters[ index1 ] -> rules[ index2 ] );
263             switch ( thisRule -> type )
264             {
265             /* evaluate variable date specs */
266                 case RULE_DATE_LT:
267                 case RULE_DATE_EQ:
268                 case RULE_DATE_GT:
269                     thisRule -> data.reftime.calctime =
270                        thisRule ->data.reftime.timeoffset;
271                     switch ( thisRule ->data.reftime.vartime )
272                     {
273                         case NOW:
274                             thisRule -> data.reftime.calctime += now;
275                             break;
276                         case LASTUPDATE:
277                             thisRule -> data.reftime.calctime += lastupdate;
278                             break;
279                         default:
280                             break;
281                     } /* end switch( ... vartime) */
282 
283                     /* Silently fix absolute dates before the epoch.
284                      * This is not the place to mock about strange dates.
285                      */
286                     if ( thisRule -> data.reftime.calctime < (time_t) 0 )
287                         thisRule -> data.reftime.calctime = (time_t) 0 ;
288 
289 #if 0
290                     Log_dbg( LOG_DBG_FILTER, "%d: %dl = %dl + %d",
291                              thisRule -> type,
292                              (long) thisRule -> data.reftime.calctime,
293                              (long) thisRule ->data.reftime.timeoffset,
294                              (int) thisRule ->data.reftime.vartime == NOW
295 				   ? now
296 				   : thisRule ->data.reftime.vartime == LASTUPDATE
297 			               ? lastupdate
298 			               : thisRule ->data.reftime.vartime );
299 #endif
300                     break;
301                 default:
302                     break;
303             } /* end switch( ... -> type) */
304         } /* end for() */
305     } /* end for() */
306     return ;
307 }
308 
309 /*
310  * Run the rules over the supplied overview. If a specific rule fires,
311  * returns its action. If no rule fires, or a rule specifying the default
312  * action fires, return the default read mode.
313  */
314 FilterAction
Flt_checkFilters(const char * thisGrp,const char * newsgroups,const Over * ov,FetchMode mode)315 Flt_checkFilters( const char *thisGrp, const char *newsgroups,
316 		  const Over *ov, FetchMode mode )
317 {
318     int i;
319 
320     for ( i = 0; i < filter.nFilters; i++ )
321 	if ( checkFilter( thisGrp, newsgroups, ov, filter.filters[ i ] ) )
322 	{
323 	    FilterAction action = filter.filters[ i ]->action;
324 
325 	    Log_dbg( LOG_DBG_FILTER,
326 		     "Filter %d fired on message %s",
327 		     i, Ov_msgId( ov ) );
328 	    if ( action == FILTER_DEFAULT )
329 		break;
330 	    else
331 		return action;
332 	}
333 
334     switch( mode )
335     {
336     case FULL:		return FILTER_FULL;
337     case THREAD:	return FILTER_THREAD;
338     case OVER:		return FILTER_XOVER;
339     }
340 
341     ASSERT( FALSE );	/* Shouldn't get here */
342     return FILTER_FULL;	/* Keep compiler quiet */
343 }
344 
345 Filter *
new_Filter(void)346 new_Filter( void )
347 {
348     Filter *f;
349 
350     if ( ! ( f = ( Filter * ) malloc( sizeof( Filter ) ) ) )
351         Log_fatal( "Cannot allocate Filter" );
352     f->nRules = 0;
353     f->maxRules = 0;
354     f->rules = NULL;
355     f->action = FILTER_DEFAULT;
356     return f;
357 }
358 
359 void
del_Filter(Filter * f)360 del_Filter( Filter *f )
361 {
362     if ( f == NULL )
363 	return;
364 
365     if ( f->rules != NULL )
366 	free( f->rules );
367     free( f );
368 }
369 
370 FilterAction
Flt_action(const Filter * f)371 Flt_action( const Filter *f )
372 {
373     return f->action;
374 }
375 
376 int
Flt_nRules(const Filter * f)377 Flt_nRules( const Filter *f )
378 {
379     return f->nRules;
380 }
381 
382 /*
383  * Do we have a rule requiring us to fetch the Newsgroups: headers of
384  * articles?
385  */
386 Bool
Flt_getNewsgroups(void)387 Flt_getNewsgroups( void )
388 {
389     return filter.needGroups;
390 }
391 
392 FilterRule
Flt_rule(const Filter * f,int ruleNo)393 Flt_rule( const Filter *f, int ruleNo )
394 {
395     ASSERT( ruleNo < f->nRules );
396     return f->rules[ ruleNo ];
397 }
398 
399 void
Flt_setAction(Filter * f,FilterAction action)400 Flt_setAction( Filter *f, FilterAction action )
401 {
402     f->action = action;
403 }
404 
405 void
Flt_addRule(Filter * f,FilterRule rule)406 Flt_addRule( Filter *f, FilterRule rule )
407 {
408     /* Does the rule require Newsgroups: headers to be fetched? */
409     if ( rule.type == RULE_NEWSGROUP ||
410 	 ( rule.type >= RULE_XPOSTS_LT && rule.type <= RULE_XPOSTS_GT ) )
411 	filter.needGroups = TRUE;
412 
413     if ( f->nRules + 1 > f->maxRules )
414     {
415 	f->rules =
416 	    ( FilterRule * ) realloc( f->rules,
417 				      ( f->maxRules + 5 )
418 				      * sizeof( FilterRule ) );
419 
420 	if ( f->rules == NULL )
421 	    Log_fatal( "Could not realloc rule list" );
422 	f->maxRules += 5;
423     }
424     f->rules[ f->nRules++ ] = rule;
425 }
426 
427 
428