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