1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: pattern.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
3 #endif
4 /*
5  * ========================================================================
6  * Copyright 2013-2021 Eduardo Chappa
7  * Copyright 2006-2009 University of Washington
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *     http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * ========================================================================
16  */
17 
18 #include "../pith/headers.h"
19 #include "../pith/pattern.h"
20 #include "../pith/state.h"
21 #include "../pith/conf.h"
22 #include "../pith/string.h"
23 #include "../pith/msgno.h"
24 #include "../pith/status.h"
25 #include "../pith/list.h"
26 #include "../pith/flag.h"
27 #include "../pith/tempfile.h"
28 #include "../pith/addrstring.h"
29 #include "../pith/search.h"
30 #include "../pith/mailcmd.h"
31 #include "../pith/filter.h"
32 #include "../pith/save.h"
33 #include "../pith/mimedesc.h"
34 #include "../pith/reply.h"
35 #include "../pith/folder.h"
36 #include "../pith/maillist.h"
37 #include "../pith/sort.h"
38 #include "../pith/copyaddr.h"
39 #include "../pith/pipe.h"
40 #include "../pith/list.h"
41 #include "../pith/news.h"
42 #include "../pith/util.h"
43 #include "../pith/sequence.h"
44 #include "../pith/detoken.h"
45 #include "../pith/busy.h"
46 #include "../pith/indxtype.h"
47 #include "../pith/mailindx.h"
48 #include "../pith/send.h"
49 #include "../pith/icache.h"
50 #include "../pith/ablookup.h"
51 #include "../pith/keyword.h"
52 
53 
54 /*
55  * Internal prototypes
56  */
57 void        open_any_patterns(long);
58 void        sub_open_any_patterns(long);
59 void        sub_close_patterns(long);
60 int         sub_any_patterns(long, PAT_STATE *);
61 PAT_LINE_S *parse_pat_lit(char *);
62 PAT_LINE_S *parse_pat_inherit(void);
63 PAT_S      *parse_pat(char *);
64 void        parse_patgrp_slash(char *, PATGRP_S *);
65 void        parse_action_slash(char *, ACTION_S *);
66 ARBHDR_S   *parse_arbhdr(char *);
67 char       *next_arb(char *);
68 PAT_S      *first_any_pattern(PAT_STATE *);
69 PAT_S      *last_any_pattern(PAT_STATE *);
70 PAT_S      *prev_any_pattern(PAT_STATE *);
71 PAT_S      *next_any_pattern(PAT_STATE *);
72 int         sub_write_patterns(long);
73 int         write_pattern_file(char **, PAT_LINE_S *);
74 int         write_pattern_lit(char **, PAT_LINE_S *);
75 int         write_pattern_inherit(char **, PAT_LINE_S *);
76 char       *data_for_patline(PAT_S *);
77 int         charsets_present_in_msg(MAILSTREAM *, unsigned long, STRLIST_S *);
78 void        collect_charsets_from_subj(ENVELOPE *, STRLIST_S **);
79 void        collect_charsets_from_body(BODY *, STRLIST_S **);
80 SEARCHPGM  *next_not(SEARCHPGM *);
81 SEARCHOR   *next_or(SEARCHOR **);
82 void        set_up_search_pgm(char *, PATTERN_S *, SEARCHPGM *);
83 void        add_type_to_pgm(char *, PATTERN_S *, SEARCHPGM *);
84 void        set_srch(char *, char *, SEARCHPGM *);
85 void        set_srch_hdr(char *, char *, SEARCHPGM *);
86 void        set_search_by_age(INTVL_S *, SEARCHPGM *, int);
87 void        set_search_by_size(INTVL_S *, SEARCHPGM *);
88 int	    non_eh(char *);
89 void        add_eh(char **, char **, char *, int *);
90 void        set_extra_hdrs(char *);
91 int         is_ascii_string(char *);
92 ACTION_S   *combine_inherited_role_guts(ACTION_S *);
93 int	    move_filtered_msgs(MAILSTREAM *, MSGNO_S *, char *, int, char *);
94 void        set_some_flags(MAILSTREAM *, MSGNO_S *, long, char **, char **, int, char *);
95 
96 
97 /*
98  *  optional hook for external-program filter test
99  */
100 void (*pith_opt_filter_pattern_cmd)(char **, SEARCHSET *, MAILSTREAM *, long, INTVL_S *);
101 
102 
103 void
role_process_filters(void)104 role_process_filters(void)
105 {
106     int         i;
107     MAILSTREAM *stream;
108     MSGNO_S    *msgmap;
109 
110     for(i = 0; i < ps_global->s_pool.nstream; i++){
111 	stream = ps_global->s_pool.streams[i];
112 	if(stream && pine_mail_ping(stream)){
113 	    msgmap = sp_msgmap(stream);
114 	    if(msgmap)
115 	      reprocess_filter_patterns(stream, msgmap, MI_REFILTERING);
116 	}
117     }
118 }
119 
120 
121 int
add_to_pattern(PAT_S * pat,long int rflags)122 add_to_pattern(PAT_S *pat, long int rflags)
123 {
124     PAT_LINE_S *new_patline, *patline;
125     PAT_S      *new_pat;
126     PAT_STATE   dummy;
127 
128     if(!any_patterns(rflags, &dummy))
129       return(0);
130 
131     /* need a new patline */
132     new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline));
133     memset((void *)new_patline, 0, sizeof(*new_patline));
134     new_patline->type = Literal;
135     (*cur_pat_h)->dirtypinerc = 1;
136 
137     /* and a copy of pat */
138     new_pat = copy_pat(pat);
139 
140     /* tie together */
141     new_patline->first = new_patline->last = new_pat;
142     new_pat->patline = new_patline;
143 
144 
145     /*
146      * Manipulate bits directly in pattern list.
147      * Cur_pat_h is set by any_patterns.
148      */
149 
150 
151     /* find last patline */
152     for(patline = (*cur_pat_h)->patlinehead;
153 	patline && patline->next;
154 	patline = patline->next)
155       ;
156 
157     /* add new patline to end of list */
158     if(patline){
159 	patline->next = new_patline;
160 	new_patline->prev = patline;
161     }
162     else
163       (*cur_pat_h)->patlinehead = new_patline;
164 
165     return(1);
166 }
167 
168 
169 /*
170  * Does pattern quoting. Takes the string that the user sees and converts
171  * it to the config file string.
172  *
173  * Args: src -- The source string.
174  *
175  * The last arg to add_escapes causes \, and \\ to be replaced with hex
176  * versions of comma and backslash. That's so we can embed commas in
177  * list variables without having them act as separators. If the user wants
178  * a literal comma, they type backslash comma.
179  * If /, \, or " appear (other than the special cases in previous sentence)
180  * they are backslash-escaped like \/, \\, or \".
181  *
182  * Returns: An allocated string with quoting added.
183  *
184  *   The caller is responsible for freeing the memory allocated for the answer.
185  */
186 char *
add_pat_escapes(char * src)187 add_pat_escapes(char *src)
188 {
189     return(add_escapes(src, "/\\\"", '\\', "", ",\\"));
190 }
191 
192 
193 /*
194  * Undoes the escape quoting done by add_pat_escapes.
195  *
196  * Args: src -- The source string.
197  *
198  * Returns: A string with backslash quoting removed or NULL. The string starts
199  *          at src and goes until the end of src or until a / is reached. The
200  *          / is not included in the string. /'s may be quoted by preceding
201  *          them with a backslash (\) and \'s may also be quoted by
202  *          preceding them with a \. In fact, \ quotes any character.
203  *          Not quite, \nnn is octal escape, \xXX is hex escape.
204  *          Hex escapes are undone but left with a backslash in front.
205  *
206  *   The caller is responsible for freeing the memory allocated for the answer.
207  */
208 char *
remove_pat_escapes(char * src)209 remove_pat_escapes(char *src)
210 {
211     char *ans = NULL, *q, *p;
212     int done = 0;
213 
214     if(src){
215 	p = q = (char *)fs_get(strlen(src) + 1);
216 
217 	while(!done){
218 	    switch(*src){
219 	      case '\\':
220 		src++;
221 		if(*src){
222 		    if(isdigit((unsigned char)*src)){	/* octal escape */
223 			*p++ = '\\';
224 			*p++ = (char)read_octal(&src);
225 		    }
226 		    else if((*src == 'x' || *src == 'X') &&
227 			    *(src+1) && *(src+2) && isxpair(src+1)){
228 			*p++ = '\\';
229 			*p++ = (char)read_hex(src+1);
230 			src += 3;
231 		    }
232 		    else
233 		      *p++ = *src++;
234 		}
235 
236 		break;
237 
238 	      case '\0':
239 	      case '/':
240 		done++;
241 		break;
242 
243 	      default:
244 		*p++ = *src++;
245 		break;
246 	    }
247 	}
248 
249 	*p = '\0';
250 
251 	ans = cpystr(q);
252 	fs_give((void **)&q);
253     }
254 
255     return(ans);
256 }
257 
258 
259 /*
260  * This takes envelope data and adds the backslash escapes that the user
261  * would have been responsible for adding if editing manually.
262  * It just escapes commas and backslashes.
263  *
264  * Caller must free result.
265  */
266 char *
add_roletake_escapes(char * src)267 add_roletake_escapes(char *src)
268 {
269     return(add_escapes(src, ",\\", '\\', "", ""));
270 }
271 
272 /*
273  * This function only escapes commas.
274  */
275 char *
add_comma_escapes(char * src)276 add_comma_escapes(char *src)
277 {
278     return(add_escapes(src, ",", '\\', "", ""));
279 }
280 
281 
282 /*
283  * These are the global pattern handles which all of the pattern routines
284  * use. Once we open one of these we usually leave it open until exiting
285  * pine. The _any versions are only used if we are altering our configuration,
286  * the _ne (NonEmpty) versions are used routinely. We open the patterns by
287  * calling either nonempty_patterns (normal use) or any_patterns (config).
288  *
289  * There are eight different pinerc variables which contain patterns. They are
290  * patterns-filters2, patterns-roles, patterns-scores2, patterns-indexcolors,
291  * patterns-other, and the old patterns, patterns-filters, and patterns-scores.
292  * The first five are the active patterns variables and the old variable are
293  * kept around so that we can convert old patterns to new. The reason we
294  * split it into five separate variables is so that each can independently
295  * be controlled by the main pinerc or by the exception pinerc. The reason
296  * for the change to filters2 and scores2 was so we could change the semantics
297  * of how rules work when there are pieces in the rule that we don't
298  * understand. We added a rule to detect 8bitSubjects. So a user might have
299  * a filter that deletes messages with 8bitSubjects. The problem was that
300  * that same filter in a old patterns-filters pine would match because it
301  * would ignore the 8bitSubject part of the pattern and match on the rest.
302  * So we changed the semantics so that rules with unknown pieces would be
303  * ignored instead of used. We had to change variable names at the same time
304  * because we were adding the 8bit thing and the old pines are still out
305  * there. Filters and Scores can both be dangerous. Roles, Colors, and Other
306  * seem less dangerous so not worth adding a new variable for them.
307  *
308  * Each of the eight variables has its own handle and status variables below.
309  * That means that they operate independently.
310  *
311  * Looking at just a single one of those variables, it has four possible
312  * values. In normal use, we use the current_val of the variable to set
313  * up the patterns. We do that by calling nonempty_patterns() with the
314  * appropriate rflags. When editing configurations, we have the other two
315  * variables to deal with: main_user_val  and post_user_val.
316  * We only ever deal with one of those at a time, so we re-use the variables.
317  * However, we do sometimes want to deal with one of those and at the same
318  * time refer to the current current_val. For example, if we are editing
319  * the post or main user_val for the filters variable, we still want
320  * to check for new mail. If we find new mail we'll want to call
321  * process_filter_patterns which uses the current_val for filter patterns.
322  * That means we have to provide for the case where we are using current_val
323  * at the same time as we're using one of the user_vals. That's why we have
324  * both the _ne variables (NonEmpty) and the _any variables.
325  *
326  * In any_patterns (and first_pattern...) use_flags may only be set to
327  * one value at a time, whereas rflags may be more than one value OR'd together.
328  */
329 PAT_HANDLE	       **cur_pat_h;
330 static PAT_HANDLE	*pattern_h_roles_ne,    *pattern_h_roles_any,
331 			*pattern_h_scores_ne,   *pattern_h_scores_any,
332 			*pattern_h_filts_ne,    *pattern_h_filts_any,
333 			*pattern_h_filts_cfg,
334 			*pattern_h_filts_ne,    *pattern_h_filts_any,
335 			*pattern_h_incol_ne,    *pattern_h_incol_any,
336 			*pattern_h_other_ne,    *pattern_h_other_any,
337 			*pattern_h_srch_ne,     *pattern_h_srch_any,
338 			*pattern_h_oldpat_ne,   *pattern_h_oldpat_any;
339 
340 /*
341  * These contain the PAT_OPEN_MASK open status and the PAT_USE_MASK use status.
342  */
343 static long		*cur_pat_status;
344 static long	  	 pat_status_roles_ne,    pat_status_roles_any,
345 			 pat_status_scores_ne,   pat_status_scores_any,
346 			 pat_status_filts_ne,    pat_status_filts_any,
347 			 pat_status_incol_ne,    pat_status_incol_any,
348 			 pat_status_other_ne,    pat_status_other_any,
349 			 pat_status_srch_ne,     pat_status_srch_any,
350 			 pat_status_oldpat_ne,   pat_status_oldpat_any,
351 			 pat_status_oldfilt_ne,  pat_status_oldfilt_any,
352 			 pat_status_oldscore_ne, pat_status_oldscore_any;
353 
354 #define SET_PATTYPE(rflags)						\
355     set_pathandle(rflags);						\
356     cur_pat_status =							\
357       ((rflags) & PAT_USE_CURRENT)					\
358 	? (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_ne :		\
359 	    ((rflags) & ROLE_DO_OTHER)  ? &pat_status_other_ne :	\
360 	     ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_ne :	\
361 	      ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_ne :	\
362 	       ((rflags) & ROLE_DO_ROLES)  ? &pat_status_roles_ne :	\
363 	        ((rflags) & ROLE_DO_SRCH)   ? &pat_status_srch_ne :	\
364 	         ((rflags) & ROLE_OLD_FILT)  ? &pat_status_oldfilt_ne :	\
365 	          ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_ne :\
366 					         &pat_status_oldpat_ne)	\
367 	: (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_any :	\
368 	    ((rflags) & ROLE_DO_OTHER)  ? &pat_status_other_any :	\
369 	     ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_any :	\
370 	      ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_any :	\
371 	       ((rflags) & ROLE_DO_ROLES)  ? &pat_status_roles_any :	\
372 	        ((rflags) & ROLE_DO_SRCH)   ? &pat_status_srch_any :	\
373 	         ((rflags) & ROLE_OLD_FILT)  ? &pat_status_oldfilt_any :\
374 	          ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_any:\
375 					         &pat_status_oldpat_any);
376 #define CANONICAL_RFLAGS(rflags)	\
377     ((((rflags) & (ROLE_DO_ROLES | ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE)) \
378 					? ROLE_DO_ROLES  : 0) |		   \
379      (((rflags) & (ROLE_DO_INCOLS | ROLE_INCOL))			   \
380 					? ROLE_DO_INCOLS : 0) |		   \
381      (((rflags) & (ROLE_DO_SCORES | ROLE_SCORE))			   \
382 					? ROLE_DO_SCORES : 0) |		   \
383      (((rflags) & (ROLE_DO_FILTER))					   \
384 					? ROLE_DO_FILTER : 0) |		   \
385      (((rflags) & (ROLE_DO_OTHER))					   \
386 					? ROLE_DO_OTHER  : 0) |		   \
387      (((rflags) & (ROLE_DO_SRCH))					   \
388 					? ROLE_DO_SRCH   : 0) |		   \
389      (((rflags) & (ROLE_OLD_FILT))					   \
390 					? ROLE_OLD_FILT  : 0) |		   \
391      (((rflags) & (ROLE_OLD_SCORE))					   \
392 					? ROLE_OLD_SCORE : 0) |		   \
393      (((rflags) & (ROLE_OLD_PAT))					   \
394 					? ROLE_OLD_PAT  : 0))
395 
396 #define SETPGMSTATUS(val,yes,no)	\
397     switch(val){			\
398       case PAT_STAT_YES:		\
399 	(yes) = 1;			\
400 	break;				\
401       case PAT_STAT_NO:			\
402 	(no) = 1;			\
403 	break;				\
404       case PAT_STAT_EITHER:		\
405       default:				\
406         break;				\
407     }
408 
409 #define SET_STATUS(srchin,srchfor,assignto)				\
410     {char *qq, *pp;							\
411      int   ii;								\
412      NAMEVAL_S *vv;							\
413      if((qq = srchstr(srchin, srchfor)) != NULL){			\
414 	if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){	\
415 	    for(ii = 0; (vv = role_status_types(ii)); ii++)		\
416 	      if(!strucmp(pp, vv->shortname)){				\
417 		  assignto = vv->value;					\
418 		  break;						\
419 	      }								\
420 									\
421 	    fs_give((void **)&pp);					\
422 	}								\
423      }									\
424     }
425 
426 #define SET_MSGSTATE(srchin,srchfor,assignto)				\
427     {char *qq, *pp;							\
428      int   ii;								\
429      NAMEVAL_S *vv;							\
430      if((qq = srchstr(srchin, srchfor)) != NULL){			\
431 	if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){	\
432 	    for(ii = 0; (vv = msg_state_types(ii)); ii++)		\
433 	      if(!strucmp(pp, vv->shortname)){				\
434 		  assignto = vv->value;					\
435 		  break;						\
436 	      }								\
437 									\
438 	    fs_give((void **)&pp);					\
439 	}								\
440      }									\
441     }
442 
443 #define PATTERN_N (9)
444 
445 
446 void
set_pathandle(long int rflags)447 set_pathandle(long int rflags)
448 {
449     cur_pat_h = (rflags & PAT_USE_CURRENT)
450 		? ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_ne :
451 		    (rflags & ROLE_DO_OTHER)  ? &pattern_h_other_ne :
452 		     (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_ne :
453 		      (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_ne :
454 		       (rflags & ROLE_DO_ROLES)  ? &pattern_h_roles_ne :
455 		        (rflags & ROLE_DO_SRCH)   ? &pattern_h_srch_ne :
456 					             &pattern_h_oldpat_ne)
457 		: ((rflags & PAT_USE_CHANGED)
458 		 ? &pattern_h_filts_cfg
459 	         : ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_any :
460 		     (rflags & ROLE_DO_OTHER)  ? &pattern_h_other_any :
461 		      (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_any :
462 		       (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_any :
463 		        (rflags & ROLE_DO_ROLES)  ? &pattern_h_roles_any :
464 		         (rflags & ROLE_DO_SRCH)   ? &pattern_h_srch_any :
465 					              &pattern_h_oldpat_any));
466 }
467 
468 
469 /*
470  * Rflags may be more than one pattern type OR'd together. It also contains
471  * the "use" parameter.
472  */
473 void
open_any_patterns(long int rflags)474 open_any_patterns(long int rflags)
475 {
476     long canon_rflags;
477 
478     dprint((7, "open_any_patterns(0x%x)\n", rflags));
479 
480     canon_rflags = CANONICAL_RFLAGS(rflags);
481 
482     if(canon_rflags & ROLE_DO_INCOLS)
483       sub_open_any_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
484     if(canon_rflags & ROLE_DO_FILTER)
485       sub_open_any_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
486     if(canon_rflags & ROLE_DO_OTHER)
487       sub_open_any_patterns(ROLE_DO_OTHER  | (rflags & PAT_USE_MASK));
488     if(canon_rflags & ROLE_DO_SCORES)
489       sub_open_any_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
490     if(canon_rflags & ROLE_DO_ROLES)
491       sub_open_any_patterns(ROLE_DO_ROLES  | (rflags & PAT_USE_MASK));
492     if(canon_rflags & ROLE_DO_SRCH)
493       sub_open_any_patterns(ROLE_DO_SRCH   | (rflags & PAT_USE_MASK));
494     if(canon_rflags & ROLE_OLD_FILT)
495       sub_open_any_patterns(ROLE_OLD_FILT  | (rflags & PAT_USE_MASK));
496     if(canon_rflags & ROLE_OLD_SCORE)
497       sub_open_any_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK));
498     if(canon_rflags & ROLE_OLD_PAT)
499       sub_open_any_patterns(ROLE_OLD_PAT   | (rflags & PAT_USE_MASK));
500 }
501 
502 
503 /*
504  * This should only be called with a single pattern type (plus use flags).
505  * We assume that patterns of this type are closed before this is called.
506  * This always succeeds unless we run out of memory, in which case fs_get
507  * never returns.
508  */
509 void
sub_open_any_patterns(long int rflags)510 sub_open_any_patterns(long int rflags)
511 {
512     PAT_LINE_S *patline = NULL, *pl = NULL;
513     char      **t = NULL;
514     struct variable *var = NULL;
515 
516     SET_PATTYPE(rflags);
517 
518     *cur_pat_h = (PAT_HANDLE *)fs_get(sizeof(**cur_pat_h));
519     memset((void *)*cur_pat_h, 0, sizeof(**cur_pat_h));
520 
521     if(rflags & ROLE_DO_ROLES)
522       var = &ps_global->vars[V_PAT_ROLES];
523     else if(rflags & ROLE_DO_FILTER)
524       var = &ps_global->vars[V_PAT_FILTS];
525     else if(rflags & ROLE_DO_OTHER)
526       var = &ps_global->vars[V_PAT_OTHER];
527     else if(rflags & ROLE_DO_SCORES)
528       var = &ps_global->vars[V_PAT_SCORES];
529     else if(rflags & ROLE_DO_INCOLS)
530       var = &ps_global->vars[V_PAT_INCOLS];
531     else if(rflags & ROLE_DO_SRCH)
532       var = &ps_global->vars[V_PAT_SRCH];
533     else if(rflags & ROLE_OLD_FILT)
534       var = &ps_global->vars[V_PAT_FILTS_OLD];
535     else if(rflags & ROLE_OLD_SCORE)
536       var = &ps_global->vars[V_PAT_SCORES_OLD];
537     else if(rflags & ROLE_OLD_PAT)
538       var = &ps_global->vars[V_PATTERNS];
539 
540     switch(rflags & PAT_USE_MASK){
541       case PAT_USE_CURRENT:
542 	t = var->current_val.l;
543 	break;
544       case PAT_USE_CHANGED:
545 	/*
546 	 * some trickery to only use changed if actually changed.
547 	 * otherwise, use current_val
548 	 */
549 	t = var->is_changed_val ? var->changed_val.l : var->current_val.l;
550 	break;
551       case PAT_USE_MAIN:
552 	t = var->main_user_val.l;
553 	break;
554       case PAT_USE_POST:
555 	t = var->post_user_val.l;
556 	break;
557     }
558 
559     if(t){
560 	for(; t[0] && t[0][0]; t++){
561 	    if(*t && !strncmp("LIT:", *t, 4))
562 	      patline = parse_pat_lit(*t + 4);
563 	    else if(*t && !strncmp("FILE:", *t, 5))
564 	      patline = parse_pat_file(*t + 5);
565 	    else if(rflags & (PAT_USE_MAIN | PAT_USE_POST) &&
566 		    patline == NULL && *t && !strcmp(INHERIT, *t))
567 	      patline = parse_pat_inherit();
568 	    else
569 	      patline = NULL;
570 
571 	    if(patline){
572 		if(pl){
573 		    pl->next      = patline;
574 		    patline->prev = pl;
575 		    pl = pl->next;
576 		}
577 		else{
578 		    (*cur_pat_h)->patlinehead = patline;
579 		    pl = patline;
580 		}
581 	    }
582 	    else
583 	      q_status_message1(SM_ORDER, 0, 3,
584 				"Invalid patterns line \"%.200s\"", *t);
585 	}
586     }
587 
588     *cur_pat_status = PAT_OPENED | (rflags & PAT_USE_MASK);
589 }
590 
591 
592 void
close_every_pattern(void)593 close_every_pattern(void)
594 {
595     close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES
596 		   | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH
597 		   | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT
598 		   | PAT_USE_CURRENT);
599     /*
600      * Since there is only one set of variables for the other three uses
601      * we can just close any one of them. There can only be one open at
602      * a time.
603      */
604     close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES
605 		   | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH
606 		   | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT
607 		   | PAT_USE_MAIN);
608 }
609 
610 
611 /*
612  * Can be called with more than one pattern type.
613  */
614 void
close_patterns(long int rflags)615 close_patterns(long int rflags)
616 {
617     long canon_rflags;
618 
619     dprint((7, "close_patterns(0x%x)\n", rflags));
620 
621     canon_rflags = CANONICAL_RFLAGS(rflags);
622 
623     if(canon_rflags & ROLE_DO_INCOLS)
624       sub_close_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
625     if(canon_rflags & ROLE_DO_OTHER)
626       sub_close_patterns(ROLE_DO_OTHER  | (rflags & PAT_USE_MASK));
627     if(canon_rflags & ROLE_DO_FILTER)
628       sub_close_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
629     if(canon_rflags & ROLE_DO_SCORES)
630       sub_close_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
631     if(canon_rflags & ROLE_DO_ROLES)
632       sub_close_patterns(ROLE_DO_ROLES  | (rflags & PAT_USE_MASK));
633     if(canon_rflags & ROLE_DO_SRCH)
634       sub_close_patterns(ROLE_DO_SRCH   | (rflags & PAT_USE_MASK));
635     if(canon_rflags & ROLE_OLD_FILT)
636       sub_close_patterns(ROLE_OLD_FILT  | (rflags & PAT_USE_MASK));
637     if(canon_rflags & ROLE_OLD_SCORE)
638       sub_close_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK));
639     if(canon_rflags & ROLE_OLD_PAT)
640       sub_close_patterns(ROLE_OLD_PAT   | (rflags & PAT_USE_MASK));
641 }
642 
643 
644 /*
645  * Can be called with only a single pattern type.
646  */
647 void
sub_close_patterns(long int rflags)648 sub_close_patterns(long int rflags)
649 {
650     SET_PATTYPE(rflags);
651 
652     if(*cur_pat_h != NULL){
653 	free_patline(&(*cur_pat_h)->patlinehead);
654 	fs_give((void **)cur_pat_h);
655     }
656 
657     *cur_pat_status = PAT_CLOSED;
658 
659     scores_are_used(SCOREUSE_INVALID);
660 }
661 
662 
663 /*
664  * Can be called with more than one pattern type.
665  * Nonempty always uses PAT_USE_CURRENT (the current_val).
666  */
667 int
nonempty_patterns(long int rflags,PAT_STATE * pstate)668 nonempty_patterns(long int rflags, PAT_STATE *pstate)
669 {
670     return(any_patterns((rflags & ROLE_MASK) | PAT_USE_CURRENT, pstate));
671 }
672 
673 
674 /*
675  * Initializes pstate and parses and sets up appropriate pattern variables.
676  * May be called with more than one pattern type OR'd together in rflags.
677  * Pstate will keep track of that and next_pattern et. al. will increment
678  * through all of those pattern types.
679  */
680 int
any_patterns(long int rflags,PAT_STATE * pstate)681 any_patterns(long int rflags, PAT_STATE *pstate)
682 {
683     int  ret = 0;
684     long canon_rflags;
685 
686     dprint((7, "any_patterns(0x%x)\n", rflags));
687 
688     memset((void *)pstate, 0, sizeof(*pstate));
689     pstate->rflags    = rflags;
690 
691     canon_rflags = CANONICAL_RFLAGS(pstate->rflags);
692 
693     if(canon_rflags & ROLE_DO_INCOLS)
694       ret += sub_any_patterns(ROLE_DO_INCOLS, pstate);
695     if(canon_rflags & ROLE_DO_OTHER)
696       ret += sub_any_patterns(ROLE_DO_OTHER, pstate);
697     if(canon_rflags & ROLE_DO_FILTER)
698       ret += sub_any_patterns(ROLE_DO_FILTER, pstate);
699     if(canon_rflags & ROLE_DO_SCORES)
700       ret += sub_any_patterns(ROLE_DO_SCORES, pstate);
701     if(canon_rflags & ROLE_DO_ROLES)
702       ret += sub_any_patterns(ROLE_DO_ROLES, pstate);
703     if(canon_rflags & ROLE_DO_SRCH)
704       ret += sub_any_patterns(ROLE_DO_SRCH, pstate);
705     if(canon_rflags & ROLE_OLD_FILT)
706       ret += sub_any_patterns(ROLE_OLD_FILT, pstate);
707     if(canon_rflags & ROLE_OLD_SCORE)
708       ret += sub_any_patterns(ROLE_OLD_SCORE, pstate);
709     if(canon_rflags & ROLE_OLD_PAT)
710       ret += sub_any_patterns(ROLE_OLD_PAT, pstate);
711 
712     return(ret);
713 }
714 
715 
716 int
sub_any_patterns(long int rflags,PAT_STATE * pstate)717 sub_any_patterns(long int rflags, PAT_STATE *pstate)
718 {
719     SET_PATTYPE(rflags | (pstate->rflags & PAT_USE_MASK));
720 
721     if(*cur_pat_h &&
722        (((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT &&
723 	 (*cur_pat_status & PAT_USE_MASK) != PAT_USE_CURRENT) ||
724         ((pstate->rflags & PAT_USE_MASK) != PAT_USE_CURRENT &&
725          ((*cur_pat_status & PAT_OPEN_MASK) != PAT_OPENED ||
726 	  (*cur_pat_status & PAT_USE_MASK) !=
727 	   (pstate->rflags & PAT_USE_MASK)))))
728       close_patterns(rflags | (pstate->rflags & PAT_USE_MASK));
729 
730     /* open_any always succeeds */
731     if(!*cur_pat_h && ((*cur_pat_status & PAT_OPEN_MASK) == PAT_CLOSED))
732       open_any_patterns(rflags | (pstate->rflags & PAT_USE_MASK));
733 
734     if(!*cur_pat_h){		/* impossible */
735 	*cur_pat_status = PAT_CLOSED;
736 	return(0);
737     }
738 
739     /*
740      * Opening nonempty can fail. That just means there aren't any
741      * patterns of that type.
742      */
743     if((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT &&
744        !(*cur_pat_h)->patlinehead)
745       *cur_pat_status = (PAT_OPEN_FAILED | PAT_USE_CURRENT);
746 
747     return(((*cur_pat_status & PAT_OPEN_MASK) == PAT_OPENED) ? 1 : 0);
748 }
749 
750 
751 int
edit_pattern(PAT_S * newpat,int pos,long int rflags)752 edit_pattern(PAT_S *newpat, int pos, long int rflags)
753 {
754     PAT_S *oldpat;
755     PAT_LINE_S *tpatline;
756     int i;
757     PAT_STATE pstate;
758 
759     if(!any_patterns(rflags, &pstate)) return(1);
760 
761     for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
762 	i < pos && tpatline; tpatline = tpatline->next, i++);
763     if(i != pos) return(1);
764     oldpat = tpatline->first;
765     free_pat(&oldpat);
766     tpatline->first = tpatline->last = newpat;
767     newpat->patline = tpatline;
768     tpatline->dirty = 1;
769 
770     (*cur_pat_h)->dirtypinerc = 1;
771     write_patterns(rflags);
772 
773     return(0);
774 }
775 
776 int
add_pattern(PAT_S * newpat,long int rflags)777 add_pattern(PAT_S *newpat, long int rflags)
778 {
779     PAT_LINE_S *tpatline, *newpatline;
780     PAT_STATE pstate;
781 
782     any_patterns(rflags, &pstate);
783 
784     for(tpatline = (*cur_pat_h)->patlinehead;
785 	tpatline && tpatline->next ; tpatline = tpatline->next);
786     newpatline = (PAT_LINE_S *)fs_get(sizeof(PAT_LINE_S));
787     if(tpatline)
788 	tpatline->next = newpatline;
789     else
790       (*cur_pat_h)->patlinehead = newpatline;
791     memset((void *)newpatline, 0, sizeof(PAT_LINE_S));
792     newpatline->prev = tpatline;
793     newpatline->first = newpatline->last = newpat;
794     newpatline->type = Literal;
795     newpat->patline = newpatline;
796     newpatline->dirty = 1;
797 
798     (*cur_pat_h)->dirtypinerc = 1;
799     write_patterns(rflags);
800 
801     return(0);
802 }
803 
804 int
delete_pattern(int pos,long int rflags)805 delete_pattern(int pos, long int rflags)
806 {
807     PAT_LINE_S *tpatline;
808     int i;
809     PAT_STATE pstate;
810 
811     if(!any_patterns(rflags, &pstate)) return(1);
812 
813     for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
814 	i < pos && tpatline; tpatline = tpatline->next, i++);
815     if(i != pos) return(1);
816 
817     if(tpatline == (*cur_pat_h)->patlinehead)
818       (*cur_pat_h)->patlinehead = tpatline->next;
819     if(tpatline->prev) tpatline->prev->next = tpatline->next;
820     if(tpatline->next) tpatline->next->prev = tpatline->prev;
821     tpatline->prev = NULL;
822     tpatline->next = NULL;
823 
824     free_patline(&tpatline);
825 
826     (*cur_pat_h)->dirtypinerc = 1;
827     write_patterns(rflags);
828 
829     return(0);
830 }
831 
832 int
shuffle_pattern(int pos,int up,long int rflags)833 shuffle_pattern(int pos, int up, long int rflags)
834 {
835     PAT_LINE_S *tpatline, *shufpatline;
836     int i;
837     PAT_STATE pstate;
838 
839     if(!any_patterns(rflags, &pstate)) return(1);
840 
841     for(i = 0, tpatline = (*cur_pat_h)->patlinehead;
842 	i < pos && tpatline; tpatline = tpatline->next, i++);
843     if(i != pos) return(1);
844 
845     if(up == 1){
846 	if(tpatline->prev == NULL) return(1);
847 	shufpatline = tpatline->prev;
848 	tpatline->prev = shufpatline->prev;
849 	if(shufpatline->prev)
850 	  shufpatline->prev->next = tpatline;
851 	if(tpatline->next)
852 	  tpatline->next->prev = shufpatline;
853 	shufpatline->next = tpatline->next;
854 	shufpatline->prev = tpatline;
855 	tpatline->next = shufpatline;
856 	if(shufpatline == (*cur_pat_h)->patlinehead)
857 	  (*cur_pat_h)->patlinehead = tpatline;
858     }
859     else if(up == -1){
860 	if(tpatline->next == NULL) return(1);
861 	shufpatline = tpatline->next;
862 	tpatline->next = shufpatline->next;
863 	if(shufpatline->next)
864 	  shufpatline->next->prev = tpatline;
865 	if(tpatline->prev)
866 	  tpatline->prev->next = shufpatline;
867 	shufpatline->prev = tpatline->prev;
868 	shufpatline->next = tpatline;
869 	tpatline->prev = shufpatline;
870 	if(tpatline == (*cur_pat_h)->patlinehead)
871 	  (*cur_pat_h)->patlinehead = shufpatline;
872     }
873     else return(1);
874 
875     shufpatline->dirty = 1;
876     tpatline->dirty = 1;
877 
878     (*cur_pat_h)->dirtypinerc = 1;
879     write_patterns(rflags);
880 
881     return(0);
882 }
883 
884 PAT_LINE_S *
parse_pat_lit(char * litpat)885 parse_pat_lit(char *litpat)
886 {
887     PAT_LINE_S *patline;
888     PAT_S      *pat;
889 
890     patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
891     memset((void *)patline, 0, sizeof(*patline));
892     patline->type = Literal;
893 
894 
895     if((pat = parse_pat(litpat)) != NULL){
896 	pat->patline   = patline;
897 	patline->first = pat;
898 	patline->last  = pat;
899     }
900 
901     return(patline);
902 }
903 
904 
905 /*
906  * This always returns a patline even if we can't read the file. The patline
907  * returned will say readonly in the worst case and there will be no patterns.
908  * If the file doesn't exist, this creates it if possible.
909  */
910 PAT_LINE_S *
parse_pat_file(char * filename)911 parse_pat_file(char *filename)
912 {
913 #define BUF_SIZE 5000
914     PAT_LINE_S *patline;
915     PAT_S      *pat, *p;
916     char        path[MAXPATH+1], buf[BUF_SIZE];
917     char       *dir, *q;
918     FILE       *fp;
919     int         ok = 0, some_pats = 0;
920     struct variable *vars = ps_global->vars;
921 
922     signature_path(filename, path, MAXPATH);
923 
924     if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, path)){
925 	q_status_message1(SM_ORDER | SM_DING, 3, 4,
926 			  "Can't use Roles file outside of %.200s",
927 			  VAR_OPER_DIR);
928 	return(NULL);
929     }
930 
931     patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
932     memset((void *)patline, 0, sizeof(*patline));
933     patline->type     = File;
934     patline->filename = cpystr(filename);
935     patline->filepath = cpystr(path);
936 
937     if((q = last_cmpnt(path)) != NULL){
938 	int save;
939 
940 	save = *--q;
941 	*q = '\0';
942 	dir  = cpystr(*path ? path : "/");
943 	*q = save;
944     }
945     else
946       dir = cpystr(".");
947 
948 #if	defined(DOS) || defined(OS2)
949     /*
950      * If the dir has become a drive letter and : (e.g. "c:")
951      * then append a "\".  The library function access() in the
952      * win 16 version of MSC seems to require this.
953      */
954     if(isalpha((unsigned char) *dir)
955        && *(dir+1) == ':' && *(dir+2) == '\0'){
956 	*(dir+2) = '\\';
957 	*(dir+3) = '\0';
958     }
959 #endif	/* DOS || OS2 */
960 
961     /*
962      * Even if we can edit the file itself, we aren't going
963      * to be able to change it unless we can also write in
964      * the directory that contains it (because we write into a
965      * temp file and then rename).
966      */
967     if(can_access(dir, EDIT_ACCESS) != 0)
968       patline->readonly = 1;
969 
970     if(can_access(path, EDIT_ACCESS) == 0){
971 	if(patline->readonly)
972 	  q_status_message1(SM_ORDER, 0, 3,
973 			    "Pattern file directory (%.200s) is ReadOnly", dir);
974     }
975     else if(can_access(path, READ_ACCESS) == 0)
976       patline->readonly = 1;
977 
978     if(can_access(path, ACCESS_EXISTS) == 0){
979 	if((fp = our_fopen(path, "rb")) != NULL){
980 	    /* Check to see if this is a valid patterns file */
981 	    if(fp_file_size(fp) <= 0L)
982 	      ok++;
983 	    else{
984 		size_t len;
985 
986 		len = strlen(PATTERN_MAGIC);
987 	        if(fread(buf, sizeof(char), len+3, fp) == len+3){
988 		    buf[len+3] = '\0';
989 		    buf[len] = '\0';
990 		    if(strcmp(buf, PATTERN_MAGIC) == 0){
991 			if(atoi(PATTERN_FILE_VERS) < atoi(buf + len + 1))
992 			  q_status_message1(SM_ORDER, 0, 4,
993   "Pattern file \"%.200s\" is made by newer Alpine, will try to use it anyway",
994 					    filename);
995 
996 			ok++;
997 			some_pats++;
998 			/* toss rest of first line */
999 			(void)fgets(buf, BUF_SIZE, fp);
1000 		    }
1001 		}
1002 	    }
1003 
1004 	    if(!ok){
1005 		patline->readonly = 1;
1006 		q_status_message1(SM_ORDER | SM_DING, 3, 4,
1007 				  "\"%.200s\" is not a Pattern file", path);
1008 	    }
1009 
1010 	    p = NULL;
1011 	    while(some_pats && fgets(buf, BUF_SIZE, fp) != NULL){
1012 		if((pat = parse_pat(buf)) != NULL){
1013 		    pat->patline = patline;
1014 		    if(!patline->first)
1015 		      patline->first = pat;
1016 
1017 		    patline->last  = pat;
1018 
1019 		    if(p){
1020 			p->next   = pat;
1021 			pat->prev = p;
1022 			p = p->next;
1023 		    }
1024 		    else
1025 		      p = pat;
1026 		}
1027 	    }
1028 
1029 	    (void)fclose(fp);
1030 	}
1031 	else{
1032 	    patline->readonly = 1;
1033 	    q_status_message2(SM_ORDER | SM_DING, 3, 4,
1034 			  "Error \"%.200s\" reading pattern file \"%.200s\"",
1035 			      error_description(errno), path);
1036 	}
1037     }
1038     else{		/* doesn't exist yet, try to create it */
1039 	if(patline->readonly)
1040 	  q_status_message1(SM_ORDER, 0, 3,
1041 			    "Pattern file directory (%.200s) is ReadOnly", dir);
1042 	else{
1043 	    /*
1044 	     * We try to create it by making up an empty patline and calling
1045 	     * write_pattern_file.
1046 	     */
1047 	    patline->dirty = 1;
1048 	    if(write_pattern_file(NULL, patline) != 0){
1049 		patline->readonly = 1;
1050 		patline->dirty = 0;
1051 		q_status_message1(SM_ORDER | SM_DING, 3, 4,
1052 				  "Error creating pattern file \"%.200s\"",
1053 				  path);
1054 	    }
1055 	}
1056     }
1057 
1058     if(dir)
1059       fs_give((void **)&dir);
1060 
1061     return(patline);
1062 }
1063 
1064 
1065 PAT_LINE_S *
parse_pat_inherit(void)1066 parse_pat_inherit(void)
1067 {
1068     PAT_LINE_S *patline;
1069     PAT_S      *pat;
1070 
1071     patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
1072     memset((void *)patline, 0, sizeof(*patline));
1073     patline->type = Inherit;
1074 
1075     pat = (PAT_S *)fs_get(sizeof(*pat));
1076     memset((void *)pat, 0, sizeof(*pat));
1077     pat->inherit = 1;
1078 
1079     pat->patline = patline;
1080     patline->first = pat;
1081     patline->last  = pat;
1082 
1083     return(patline);
1084 }
1085 
1086 
1087 /*
1088  * There are three forms that a PATTERN_S has at various times. There is
1089  * the actual PATTERN_S struct which is used internally and is used whenever
1090  * we are actually doing something with the pattern, like filtering or
1091  * something. There is the version that goes in the config file. And there
1092  * is the version the user edits.
1093  *
1094  * To go between these three forms we have the helper routines
1095  *
1096  *   pattern_to_config
1097  *   config_to_pattern
1098  *   pattern_to_editlist
1099  *   editlist_to_pattern
1100  *
1101  * Here's what is supposed to be happening. A PATTERN_S is a linked list
1102  * of strings with nothing escaped. That is, a backslash or a comma is
1103  * just in there as a backslash or comma.
1104  *
1105  * The version the user edits is very similar. Because we have historically
1106  * used commas as separators the user has always had to enter a \, in order
1107  * to put a real comma in one of the items. That is the only difference
1108  * between a PATTERN_S string and the editlist strings. Note that backslashes
1109  * themselves are not escaped. A backslash which is not followed by a comma
1110  * is a backslash. It doesn't escape the following character. That's a bit
1111  * odd, it is that way because most people will never know about this
1112  * backslash stuff but PC-Pine users may have backslashes in folder names.
1113  *
1114  * The version that goes in the config file has a few transformations made.
1115  *      PATTERN_S   intermediate_form   Config string
1116  *         ,              \,               \x2C
1117  *         \              \\               \x5C
1118  *         /                               \/
1119  *         "                               \"
1120  *
1121  * The commas are turned into hex commas so that we can tell the separators
1122  * in the comma-separated lists from those commas.
1123  * The backslashes are escaped because they escape commas.
1124  * The /'s are escaped because they separate pattern pieces.
1125  * The "'s are escaped because they are significant to parse_list when
1126  *   parsing the config file.
1127  *                            hubert - 2004-04-01
1128  *                                     (date is only coincidental!)
1129  *
1130  * Addendum. The not's are handled separately from all the strings. Not sure
1131  * why that is or if there is a good reason. Nevertheless, now is not the
1132  * time to figure it out so leave it that way.
1133  *                            hubert - 2004-07-14
1134  */
1135 PAT_S *
parse_pat(char * str)1136 parse_pat(char *str)
1137 {
1138     PAT_S *pat = NULL;
1139     char  *p, *q, *astr, *pstr;
1140     int    backslashed;
1141 #define PTRN "pattern="
1142 #define PTRNLEN 8
1143 #define ACTN "action="
1144 #define ACTNLEN 7
1145 
1146     if(str)
1147       removing_trailing_white_space(str);
1148 
1149     if(!str || !*str || *str == '#')
1150       return(pat);
1151 
1152     pat = (PAT_S *)fs_get(sizeof(*pat));
1153     memset((void *)pat, 0, sizeof(*pat));
1154 
1155     if((p = srchstr(str, PTRN)) != NULL){
1156 	pat->patgrp = (PATGRP_S *)fs_get(sizeof(*pat->patgrp));
1157 	memset((void *)pat->patgrp, 0, sizeof(*pat->patgrp));
1158 	pat->patgrp->fldr_type = FLDR_DEFL;
1159 	pat->patgrp->inabook = IAB_DEFL;
1160 	pat->patgrp->cat_lim   = -1L;
1161 
1162 	if((pstr = copy_quoted_string_asis(p+PTRNLEN)) != NULL){
1163 	    /* move to next slash */
1164 	    for(q=pstr, backslashed=0; *q; q++){
1165 		switch(*q){
1166 		  case '\\':
1167 		    backslashed = !backslashed;
1168 		    break;
1169 
1170 		  case '/':
1171 		    if(!backslashed){
1172 			parse_patgrp_slash(q, pat->patgrp);
1173 			if(pat->patgrp->bogus && !pat->raw)
1174 			  pat->raw = cpystr(str);
1175 		    }
1176 
1177 		  /* fall through */
1178 
1179 		  default:
1180 		    backslashed = 0;
1181 		    break;
1182 		}
1183 	    }
1184 
1185 	    /* we always force a nickname */
1186 	    if(!pat->patgrp->nick)
1187 	      pat->patgrp->nick = cpystr("Alternate Role");
1188 
1189 	    fs_give((void **)&pstr);
1190 	}
1191     }
1192 
1193     if((p = srchstr(str, ACTN)) != NULL){
1194 	pat->action = (ACTION_S *)fs_get(sizeof(*pat->action));
1195 	memset((void *)pat->action, 0, sizeof(*pat->action));
1196 	pat->action->startup_rule = IS_NOTSET;
1197 	pat->action->repl_type = ROLE_REPL_DEFL;
1198 	pat->action->forw_type = ROLE_FORW_DEFL;
1199 	pat->action->comp_type = ROLE_COMP_DEFL;
1200 	pat->action->nick = cpystr((pat->patgrp && pat->patgrp->nick
1201 				    && pat->patgrp->nick[0])
1202 				       ? pat->patgrp->nick : "Alternate Role");
1203 
1204 	if((astr = copy_quoted_string_asis(p+ACTNLEN)) != NULL){
1205 	    /* move to next slash */
1206 	    for(q=astr, backslashed=0; *q; q++){
1207 		switch(*q){
1208 		  case '\\':
1209 		    backslashed = !backslashed;
1210 		    break;
1211 
1212 		  case '/':
1213 		    if(!backslashed){
1214 			parse_action_slash(q, pat->action);
1215 			if(pat->action->bogus && !pat->raw)
1216 			  pat->raw = cpystr(str);
1217 		    }
1218 
1219 		  /* fall through */
1220 
1221 		  default:
1222 		    backslashed = 0;
1223 		    break;
1224 		}
1225 	    }
1226 
1227 	    fs_give((void **)&astr);
1228 
1229 	    if(!pat->action->is_a_score)
1230 	      pat->action->scoreval = 0L;
1231 
1232 	    if(pat->action->is_a_filter)
1233 	      pat->action->kill = (pat->action->folder
1234 				   || pat->action->kill == -1) ? 0 : 1;
1235 	    else{
1236 		if(pat->action->folder)
1237 		  free_pattern(&pat->action->folder);
1238 	    }
1239 
1240 	    if(!pat->action->is_a_role){
1241 		pat->action->repl_type = ROLE_NOTAROLE_DEFL;
1242 		pat->action->forw_type = ROLE_NOTAROLE_DEFL;
1243 		pat->action->comp_type = ROLE_NOTAROLE_DEFL;
1244 		if(pat->action->from)
1245 		  mail_free_address(&pat->action->from);
1246 		if(pat->action->replyto)
1247 		  mail_free_address(&pat->action->replyto);
1248 		if(pat->action->fcc)
1249 		  fs_give((void **)&pat->action->fcc);
1250 		if(pat->action->litsig)
1251 		  fs_give((void **)&pat->action->litsig);
1252 		if(pat->action->sig)
1253 		  fs_give((void **)&pat->action->sig);
1254 		if(pat->action->template)
1255 		  fs_give((void **)&pat->action->template);
1256 		if(pat->action->cstm)
1257 		  free_list_array(&pat->action->cstm);
1258 		if(pat->action->smtp)
1259 		  free_list_array(&pat->action->smtp);
1260 		if(pat->action->nntp)
1261 		  free_list_array(&pat->action->nntp);
1262 		if(pat->action->inherit_nick)
1263 		  fs_give((void **)&pat->action->inherit_nick);
1264 	    }
1265 
1266 	    if(!pat->action->is_a_incol){
1267 		if(pat->action->incol)
1268 		  free_color_pair(&pat->action->incol);
1269 	    }
1270 
1271 	    if(!pat->action->is_a_other){
1272 		pat->action->sort_is_set = 0;
1273 		pat->action->sortorder = 0;
1274 		pat->action->revsort = 0;
1275 		pat->action->startup_rule = IS_NOTSET;
1276 		if(pat->action->index_format)
1277 		  fs_give((void **)&pat->action->index_format);
1278 	    }
1279 	}
1280     }
1281 
1282     return(pat);
1283 }
1284 
1285 
1286 /*
1287  * Fill in one member of patgrp from str.
1288  *
1289  * The multiple constant strings are lame but it evolved this way from
1290  * previous versions and isn't worth fixing.
1291  */
1292 void
parse_patgrp_slash(char * str,PATGRP_S * patgrp)1293 parse_patgrp_slash(char *str, PATGRP_S *patgrp)
1294 {
1295     char  *p;
1296 
1297     if(!patgrp)
1298       alpine_panic("NULL patgrp to parse_patgrp_slash");
1299     else if(!(str && *str)){
1300 	alpine_panic("NULL or empty string to parse_patgrp_slash");
1301 	patgrp->bogus = 1;
1302     }
1303     else if(!strncmp(str, "/NICK=", 6))
1304       patgrp->nick = remove_pat_escapes(str+6);
1305     else if(!strncmp(str, "/COMM=", 6))
1306       patgrp->comment = remove_pat_escapes(str+6);
1307     else if(!strncmp(str, "/TO=", 4) || !strncmp(str, "/!TO=", 5))
1308       patgrp->to = parse_pattern("TO", str, 1);
1309     else if(!strncmp(str, "/CC=", 4) || !strncmp(str, "/!CC=", 5))
1310       patgrp->cc = parse_pattern("CC", str, 1);
1311     else if(!strncmp(str, "/RECIP=", 7) || !strncmp(str, "/!RECIP=", 8))
1312       patgrp->recip = parse_pattern("RECIP", str, 1);
1313     else if(!strncmp(str, "/PARTIC=", 8) || !strncmp(str, "/!PARTIC=", 9))
1314       patgrp->partic = parse_pattern("PARTIC", str, 1);
1315     else if(!strncmp(str, "/FROM=", 6) || !strncmp(str, "/!FROM=", 7))
1316       patgrp->from = parse_pattern("FROM", str, 1);
1317     else if(!strncmp(str, "/SENDER=", 8) || !strncmp(str, "/!SENDER=", 9))
1318       patgrp->sender = parse_pattern("SENDER", str, 1);
1319     else if(!strncmp(str, "/NEWS=", 6) || !strncmp(str, "/!NEWS=", 7))
1320       patgrp->news = parse_pattern("NEWS", str, 1);
1321     else if(!strncmp(str, "/SUBJ=", 6) || !strncmp(str, "/!SUBJ=", 7))
1322       patgrp->subj = parse_pattern("SUBJ", str, 1);
1323     else if(!strncmp(str, "/ALL=", 5) || !strncmp(str, "/!ALL=", 6))
1324       patgrp->alltext = parse_pattern("ALL", str, 1);
1325     else if(!strncmp(str, "/BODY=", 6) || !strncmp(str, "/!BODY=", 7))
1326       patgrp->bodytext = parse_pattern("BODY", str, 1);
1327     else if(!strncmp(str, "/KEY=", 5) || !strncmp(str, "/!KEY=", 6))
1328       patgrp->keyword = parse_pattern("KEY", str, 1);
1329     else if(!strncmp(str, "/CHAR=", 6) || !strncmp(str, "/!CHAR=", 7))
1330       patgrp->charsets = parse_pattern("CHAR", str, 1);
1331     else if(!strncmp(str, "/FOLDER=", 8) || !strncmp(str, "/!FOLDER=", 9))
1332       patgrp->folder = parse_pattern("FOLDER", str, 1);
1333     else if(!strncmp(str, "/ABOOKS=", 8) || !strncmp(str, "/!ABOOKS=", 9))
1334       patgrp->abooks = parse_pattern("ABOOKS", str, 1);
1335     /*
1336      * A problem with arbhdrs is that more than one of them can appear in
1337      * the string. We come back here the second time, but we already took
1338      * care of the whole thing on the first pass. Hence the check for
1339      * arbhdr already set.
1340      */
1341     else if(!strncmp(str, "/ARB", 4) || !strncmp(str, "/!ARB", 5)
1342 	    || !strncmp(str, "/EARB", 5) || !strncmp(str, "/!EARB", 6)){
1343 	if(!patgrp->arbhdr)
1344 	  patgrp->arbhdr = parse_arbhdr(str);
1345 	/* else do nothing */
1346     }
1347     else if(!strncmp(str, "/SENTDATE=", 10))
1348       patgrp->age_uses_sentdate = 1;
1349     else if(!strncmp(str, "/SCOREI=", 8)){
1350 	if((p = remove_pat_escapes(str+8)) != NULL){
1351 	    if((patgrp->score = parse_intvl(p)) != NULL)
1352 	      patgrp->do_score = 1;
1353 
1354 	    fs_give((void **)&p);
1355 	}
1356     }
1357     else if(!strncmp(str, "/AGE=", 5)){
1358 	if((p = remove_pat_escapes(str+5)) != NULL){
1359 	    if((patgrp->age = parse_intvl(p)) != NULL)
1360 	      patgrp->do_age  = 1;
1361 
1362 	    fs_give((void **)&p);
1363 	}
1364     }
1365     else if(!strncmp(str, "/SIZE=", 6)){
1366 	if((p = remove_pat_escapes(str+6)) != NULL){
1367 	    if((patgrp->size = parse_intvl(p)) != NULL)
1368 	      patgrp->do_size  = 1;
1369 
1370 	    fs_give((void **)&p);
1371 	}
1372     }
1373     else if(!strncmp(str, "/CATCMD=", 8)){
1374 	if((p = remove_pat_escapes(str+8)) != NULL){
1375 	    int   commas = 0;
1376 	    char *q;
1377 
1378 	    /* count elements in list */
1379 	    for(q = p; q && *q; q++)
1380 	      if(*q == ',')
1381 		commas++;
1382 
1383 	    patgrp->category_cmd = parse_list(p, commas+1, PL_REMSURRQUOT,NULL);
1384 	    fs_give((void **)&p);
1385 	}
1386     }
1387     else if(!strncmp(str, "/CATVAL=", 8)){
1388 	if((p = remove_pat_escapes(str+8)) != NULL){
1389 	    if((patgrp->cat = parse_intvl(p)) != NULL)
1390 	      patgrp->do_cat = 1;
1391 
1392 	    fs_give((void **)&p);
1393 	}
1394     }
1395     else if(!strncmp(str, "/CATLIM=", 8)){
1396 	if((p = remove_pat_escapes(str+8)) != NULL){
1397 	    long i;
1398 
1399 	    i = atol(p);
1400 	    patgrp->cat_lim = i;
1401 	    fs_give((void **)&p);
1402 	}
1403     }
1404     else if(!strncmp(str, "/FLDTYPE=", 9)){
1405 	if((p = remove_pat_escapes(str+9)) != NULL){
1406 	    int        i;
1407 	    NAMEVAL_S *v;
1408 
1409 	    for(i = 0; (v = pat_fldr_types(i)); i++)
1410 	      if(!strucmp(p, v->shortname)){
1411 		  patgrp->fldr_type = v->value;
1412 		  break;
1413 	      }
1414 
1415 	    fs_give((void **)&p);
1416 	}
1417     }
1418     else if(!strncmp(str, "/AFROM=", 7)){
1419 	if((p = remove_pat_escapes(str+7)) != NULL){
1420 	    int        i;
1421 	    NAMEVAL_S *v;
1422 
1423 	    for(i = 0; (v = inabook_fldr_types(i)); i++)
1424 	      if(!strucmp(p, v->shortname)){
1425 		  patgrp->inabook |= v->value;
1426 		  break;
1427 	      }
1428 
1429 	    /* to match old semantics */
1430 	    patgrp->inabook |= IAB_FROM;
1431 	    patgrp->inabook |= IAB_REPLYTO;
1432 
1433 	    fs_give((void **)&p);
1434 	}
1435     }
1436     else if(!strncmp(str, "/AFROMA=", 8)){
1437 	if((p = remove_pat_escapes(str+8)) != NULL){
1438 
1439 	    /* make sure AFROMA comes after AFROM in config lines */
1440 	    patgrp->inabook &= ~IAB_ADDR_MASK;
1441 
1442 	    if(strchr(p, 'F'))
1443 	      patgrp->inabook |= IAB_FROM;
1444 	    if(strchr(p, 'R'))
1445 	      patgrp->inabook |= IAB_REPLYTO;
1446 	    if(strchr(p, 'S'))
1447 	      patgrp->inabook |= IAB_SENDER;
1448 	    if(strchr(p, 'T'))
1449 	      patgrp->inabook |= IAB_TO;
1450 	    if(strchr(p, 'C'))
1451 	      patgrp->inabook |= IAB_CC;
1452 
1453 	    fs_give((void **)&p);
1454 	}
1455     }
1456     else if(!strncmp(str, "/STATN=", 7)){
1457 	SET_STATUS(str,"/STATN=",patgrp->stat_new);
1458     }
1459     else if(!strncmp(str, "/STATR=", 7)){
1460 	SET_STATUS(str,"/STATR=",patgrp->stat_rec);
1461     }
1462     else if(!strncmp(str, "/STATI=", 7)){
1463 	SET_STATUS(str,"/STATI=",patgrp->stat_imp);
1464     }
1465     else if(!strncmp(str, "/STATA=", 7)){
1466 	SET_STATUS(str,"/STATA=",patgrp->stat_ans);
1467     }
1468     else if(!strncmp(str, "/STATD=", 7)){
1469 	SET_STATUS(str,"/STATD=",patgrp->stat_del);
1470     }
1471     else if(!strncmp(str, "/8BITS=", 7)){
1472 	SET_STATUS(str,"/8BITS=",patgrp->stat_8bitsubj);
1473     }
1474     else if(!strncmp(str, "/BOM=", 5)){
1475 	SET_STATUS(str,"/BOM=",patgrp->stat_bom);
1476     }
1477     else if(!strncmp(str, "/BOY=", 5)){
1478 	SET_STATUS(str,"/BOY=",patgrp->stat_boy);
1479     }
1480     else{
1481 	char save = '\0';;
1482 
1483 	patgrp->bogus = 1;
1484 
1485 	if((p = strindex(str, '=')) != NULL){
1486 	    save = *(p+1);
1487 	    *(p+1) = '\0';
1488 	}
1489 
1490 	dprint((1,
1491 	       "parse_patgrp_slash(%.20s): unrecognized in \"%s\"\n",
1492 	       str ? str : "?",
1493 	       (patgrp && patgrp->nick) ? patgrp->nick : ""));
1494 	q_status_message4(SM_ORDER, 1, 3,
1495 	      "Warning: unrecognized pattern element \"%.20s\"%.20s%.20s%.20s",
1496 	      str, patgrp->nick ? " in rule \"" : "",
1497 	      patgrp->nick ? patgrp->nick : "", patgrp->nick ? "\"" : "");
1498 
1499 	if(p)
1500 	  *(p+1) = save;
1501     }
1502 }
1503 
1504 
1505 /*
1506  * Fill in one member of action struct from str.
1507  *
1508  * The multiple constant strings are lame but it evolved this way from
1509  * previous versions and isn't worth fixing.
1510  */
1511 void
parse_action_slash(char * str,ACTION_S * action)1512 parse_action_slash(char *str, ACTION_S *action)
1513 {
1514     char      *p;
1515     int        stateval, i;
1516     NAMEVAL_S *v;
1517 
1518     if(!action)
1519       alpine_panic("NULL action to parse_action_slash");
1520     else if(!(str && *str))
1521       alpine_panic("NULL or empty string to parse_action_slash");
1522     else if(!strncmp(str, "/ROLE=1", 7))
1523       action->is_a_role = 1;
1524     else if(!strncmp(str, "/OTHER=1", 8))
1525       action->is_a_other = 1;
1526     else if(!strncmp(str, "/ISINCOL=1", 10))
1527       action->is_a_incol = 1;
1528     else if(!strncmp(str, "/ISSRCH=1", 9))
1529       action->is_a_srch = 1;
1530     /*
1531      * This is unfortunate. If a new filter is set to only set
1532      * state bits it will be interpreted by an older pine which
1533      * doesn't have that feature like a filter that is set to Delete.
1534      * So we change the filter indicator to FILTER=2 to disable the
1535      * filter for older versions.
1536      */
1537     else if(!strncmp(str, "/FILTER=1", 9) || !strncmp(str, "/FILTER=2", 9))
1538       action->is_a_filter = 1;
1539     else if(!strncmp(str, "/ISSCORE=1", 10))
1540       action->is_a_score = 1;
1541     else if(!strncmp(str, "/SCORE=", 7)){
1542 	if((p = remove_pat_escapes(str+7)) != NULL){
1543 	    long i;
1544 
1545 	    i = atol(p);
1546 	    if(i >= SCORE_MIN && i <= SCORE_MAX)
1547 	      action->scoreval = i;
1548 
1549 	    fs_give((void **)&p);
1550 	}
1551     }
1552     else if(!strncmp(str, "/SCOREHDRTOK=", 13))
1553       action->scorevalhdrtok = config_to_hdrtok(str+13);
1554     else if(!strncmp(str, "/FOLDER=", 8))
1555       action->folder = parse_pattern("FOLDER", str, 1);
1556     else if(!strncmp(str, "/KEYSET=", 8))
1557       action->keyword_set = parse_pattern("KEYSET", str, 1);
1558     else if(!strncmp(str, "/KEYCLR=", 8))
1559       action->keyword_clr = parse_pattern("KEYCLR", str, 1);
1560     else if(!strncmp(str, "/NOKILL=", 8))
1561       action->kill = -1;
1562     else if(!strncmp(str, "/NOTDEL=", 8))
1563       action->move_only_if_not_deleted = 1;
1564     else if(!strncmp(str, "/NONTERM=", 9))
1565       action->non_terminating = 1;
1566     else if(!strncmp(str, "/STATI=", 7)){
1567 	stateval = ACT_STAT_LEAVE;
1568 	SET_MSGSTATE(str,"/STATI=",stateval);
1569 	switch(stateval){
1570 	  case ACT_STAT_LEAVE:
1571 	    break;
1572 	  case ACT_STAT_SET:
1573 	    action->state_setting_bits |= F_FLAG;
1574 	    break;
1575 	  case ACT_STAT_CLEAR:
1576 	    action->state_setting_bits |= F_UNFLAG;
1577 	    break;
1578 	}
1579     }
1580     else if(!strncmp(str, "/STATD=", 7)){
1581 	stateval = ACT_STAT_LEAVE;
1582 	SET_MSGSTATE(str,"/STATD=",stateval);
1583 	switch(stateval){
1584 	  case ACT_STAT_LEAVE:
1585 	    break;
1586 	  case ACT_STAT_SET:
1587 	    action->state_setting_bits |= F_DEL;
1588 	    break;
1589 	  case ACT_STAT_CLEAR:
1590 	    action->state_setting_bits |= F_UNDEL;
1591 	    break;
1592 	}
1593     }
1594     else if(!strncmp(str, "/STATA=", 7)){
1595 	stateval = ACT_STAT_LEAVE;
1596 	SET_MSGSTATE(str,"/STATA=",stateval);
1597 	switch(stateval){
1598 	  case ACT_STAT_LEAVE:
1599 	    break;
1600 	  case ACT_STAT_SET:
1601 	    action->state_setting_bits |= F_ANS;
1602 	    break;
1603 	  case ACT_STAT_CLEAR:
1604 	    action->state_setting_bits |= F_UNANS;
1605 	    break;
1606 	}
1607     }
1608     else if(!strncmp(str, "/STATN=", 7)){
1609 	stateval = ACT_STAT_LEAVE;
1610 	SET_MSGSTATE(str,"/STATN=",stateval);
1611 	switch(stateval){
1612 	  case ACT_STAT_LEAVE:
1613 	    break;
1614 	  case ACT_STAT_SET:
1615 	    action->state_setting_bits |= F_UNSEEN;
1616 	    break;
1617 	  case ACT_STAT_CLEAR:
1618 	    action->state_setting_bits |= F_SEEN;
1619 	    break;
1620 	}
1621     }
1622     else if(!strncmp(str, "/RTYPE=", 7)){
1623 	/* reply type */
1624 	action->repl_type = ROLE_REPL_DEFL;
1625 	if((p = remove_pat_escapes(str+7)) != NULL){
1626 	    for(i = 0; (v = role_repl_types(i)); i++)
1627 	      if(!strucmp(p, v->shortname)){
1628 		  action->repl_type = v->value;
1629 		  break;
1630 	      }
1631 
1632 	    fs_give((void **)&p);
1633 	}
1634     }
1635     else if(!strncmp(str, "/FTYPE=", 7)){
1636 	/* forward type */
1637 	action->forw_type = ROLE_FORW_DEFL;
1638 	if((p = remove_pat_escapes(str+7)) != NULL){
1639 	    for(i = 0; (v = role_forw_types(i)); i++)
1640 	      if(!strucmp(p, v->shortname)){
1641 		  action->forw_type = v->value;
1642 		  break;
1643 	      }
1644 
1645 	    fs_give((void **)&p);
1646 	}
1647     }
1648     else if(!strncmp(str, "/CTYPE=", 7)){
1649 	/* compose type */
1650 	action->comp_type = ROLE_COMP_DEFL;
1651 	if((p = remove_pat_escapes(str+7)) != NULL){
1652 	    for(i = 0; (v = role_comp_types(i)); i++)
1653 	      if(!strucmp(p, v->shortname)){
1654 		  action->comp_type = v->value;
1655 		  break;
1656 	      }
1657 
1658 	    fs_give((void **)&p);
1659 	}
1660     }
1661     else if(!strncmp(str, "/FROM=", 6)){
1662 	/* get the from */
1663 	if((p = remove_pat_escapes(str+6)) != NULL){
1664 	    rfc822_parse_adrlist(&action->from, p,
1665 				 ps_global->maildomain);
1666 	    fs_give((void **)&p);
1667 	}
1668     }
1669     else if(!strncmp(str, "/REPL=", 6)){
1670 	/* get the reply-to */
1671 	if((p = remove_pat_escapes(str+6)) != NULL){
1672 	    rfc822_parse_adrlist(&action->replyto, p,
1673 				 ps_global->maildomain);
1674 	    fs_give((void **)&p);
1675 	}
1676     }
1677     else if(!strncmp(str, "/FCC=", 5))
1678       action->fcc = remove_pat_escapes(str+5);
1679     else if(!strncmp(str, "/LSIG=", 6))
1680       action->litsig = remove_pat_escapes(str+6);
1681     else if(!strncmp(str, "/SIG=", 5))
1682       action->sig = remove_pat_escapes(str+5);
1683     else if(!strncmp(str, "/TEMPLATE=", 10))
1684       action->template = remove_pat_escapes(str+10);
1685     /* get the custom headers */
1686     else if(!strncmp(str, "/CSTM=", 6)){
1687 	if((p = remove_pat_escapes(str+6)) != NULL){
1688 	    int   commas = 0;
1689 	    char *q;
1690 
1691 	    /* count elements in list */
1692 	    for(q = p; q && *q; q++)
1693 	      if(*q == ',')
1694 		commas++;
1695 
1696 	    action->cstm = parse_list(p, commas+1, 0, NULL);
1697 	    fs_give((void **)&p);
1698 	}
1699     }
1700     else if(!strncmp(str, "/SMTP=", 6)){
1701 	if((p = remove_pat_escapes(str+6)) != NULL){
1702 	    int   commas = 0;
1703 	    char *q;
1704 
1705 	    /* count elements in list */
1706 	    for(q = p; q && *q; q++)
1707 	      if(*q == ',')
1708 		commas++;
1709 
1710 	    action->smtp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL);
1711 	    fs_give((void **)&p);
1712 	}
1713     }
1714     else if(!strncmp(str, "/NNTP=", 6)){
1715 	if((p = remove_pat_escapes(str+6)) != NULL){
1716 	    int   commas = 0;
1717 	    char *q;
1718 
1719 	    /* count elements in list */
1720 	    for(q = p; q && *q; q++)
1721 	      if(*q == ',')
1722 		commas++;
1723 
1724 	    action->nntp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL);
1725 	    fs_give((void **)&p);
1726 	}
1727     }
1728     else if(!strncmp(str, "/INICK=", 7))
1729       action->inherit_nick = remove_pat_escapes(str+7);
1730     else if(!strncmp(str, "/INCOL=", 7)){
1731 	if((p = remove_pat_escapes(str+7)) != NULL){
1732 	    char *fg = NULL, *bg = NULL, *z;
1733 
1734 	    /*
1735 	     * Color should look like
1736 	     * /FG=white/BG=red
1737 	     */
1738 	    if((z = srchstr(p, "/FG=")) != NULL)
1739 	      fg = remove_pat_escapes(z+4);
1740 	    if((z = srchstr(p, "/BG=")) != NULL)
1741 	      bg = remove_pat_escapes(z+4);
1742 
1743 	    if(fg && *fg && bg && *bg)
1744 	      action->incol = new_color_pair(fg, bg);
1745 
1746 	    if(fg)
1747 	      fs_give((void **)&fg);
1748 	    if(bg)
1749 	      fs_give((void **)&bg);
1750 	    fs_give((void **)&p);
1751 	}
1752     }
1753     /* per-folder sort */
1754     else if(!strncmp(str, "/SORT=", 6)){
1755 	if((p = remove_pat_escapes(str+6)) != NULL){
1756 	    SortOrder def_sort;
1757 	    int       def_sort_rev;
1758 
1759 	    if(decode_sort(p, &def_sort, &def_sort_rev) != -1){
1760 		action->sort_is_set = 1;
1761 		action->sortorder = def_sort;
1762 		action->revsort   = (def_sort_rev ? 1 : 0);
1763 	    }
1764 
1765 	    fs_give((void **)&p);
1766 	}
1767     }
1768     /* per-folder index-format */
1769     else if(!strncmp(str, "/IFORM=", 7))
1770       action->index_format = remove_pat_escapes(str+7);
1771     /* per-folder startup-rule */
1772     else if(!strncmp(str, "/START=", 7)){
1773 	if((p = remove_pat_escapes(str+7)) != NULL){
1774 	    for(i = 0; (v = startup_rules(i)); i++)
1775 	      if(!strucmp(p, S_OR_L(v))){
1776 		  action->startup_rule = v->value;
1777 		  break;
1778 	      }
1779 
1780 	    fs_give((void **)&p);
1781 	}
1782     }
1783     else{
1784 	char save = '\0';
1785 
1786 	action->bogus = 1;
1787 
1788 	if((p = strindex(str, '=')) != NULL){
1789 	    save = *(p+1);
1790 	    *(p+1) = '\0';
1791 	}
1792 
1793 	dprint((1,
1794 	       "parse_action_slash(%.20s): unrecognized in \"%s\"\n",
1795 	       str ? str : "?",
1796 	       (action && action->nick) ? action->nick : ""));
1797 	q_status_message4(SM_ORDER, 1, 3,
1798 	      "Warning: unrecognized pattern action \"%.20s\"%.20s%.20s%.20s",
1799 	      str, action->nick ? " in rule \"" : "",
1800 	      action->nick ? action->nick : "", action->nick ? "\"" : "");
1801 
1802 	if(p)
1803 	  *(p+1) = save;
1804     }
1805 }
1806 
1807 
1808 /*
1809  * Str looks like (min,max) or a comma-separated list of these.
1810  *
1811  * Parens are optional if unambiguous, whitespace is ignored.
1812  * If min is left out it is -INF. If max is left out it is INF.
1813  * If only one number and no comma number is min and max is INF.
1814  *
1815  * Returns the INTVL_S list.
1816  */
1817 INTVL_S *
parse_intvl(char * str)1818 parse_intvl(char *str)
1819 {
1820     char *q;
1821     long  left, right;
1822     INTVL_S *ret = NULL, **next = NULL;
1823 
1824     if(!str)
1825       return(ret);
1826 
1827     q = str;
1828 
1829     for(;;){
1830 	left = right = INTVL_UNDEF;
1831 
1832 	/* skip to first number */
1833 	while(isspace((unsigned char) *q) || *q == LPAREN)
1834 	  q++;
1835 
1836 	/* min number */
1837 	if(*q == COMMA || !struncmp(q, "-INF", 4))
1838 	  left = - INTVL_INF;
1839 	else if(*q == '-' || isdigit((unsigned char) *q))
1840 	  left = atol(q);
1841 
1842 	if(left != INTVL_UNDEF){
1843 	    /* skip to second number */
1844 	    while(*q && *q != COMMA && *q != RPAREN)
1845 	      q++;
1846 	    if(*q == COMMA)
1847 	      q++;
1848 	    while(isspace((unsigned char) *q))
1849 	      q++;
1850 
1851 	    /* max number */
1852 	    if(*q == '\0' || *q == RPAREN || !struncmp(q, "INF", 3))
1853 	      right = INTVL_INF;
1854 	    else if(*q == '-' || isdigit((unsigned char) *q))
1855 	      right = atol(q);
1856 	}
1857 
1858 	if(left == INTVL_UNDEF || right == INTVL_UNDEF
1859 	   || left > right){
1860 	    if(left != INTVL_UNDEF || right != INTVL_UNDEF || *q){
1861 		if(left != INTVL_UNDEF && right != INTVL_UNDEF
1862 		   && left > right)
1863 		  q_status_message1(SM_ORDER, 3, 5,
1864 				_("Error: Interval \"%s\", min > max"), str);
1865 		else
1866 		  q_status_message1(SM_ORDER, 3, 5,
1867 		      _("Error: Interval \"%s\": syntax is (min,max)"), str);
1868 
1869 		if(ret)
1870 		  free_intvl(&ret);
1871 
1872 		ret = NULL;
1873 	    }
1874 
1875 	    break;
1876 	}
1877 	else{
1878 	    if(!ret){
1879 		ret = (INTVL_S *) fs_get(sizeof(*ret));
1880 		memset((void *) ret, 0, sizeof(*ret));
1881 		ret->imin = left;
1882 		ret->imax = right;
1883 		next = &ret->next;
1884 	    }
1885 	    else{
1886 		*next = (INTVL_S *) fs_get(sizeof(*ret));
1887 		memset((void *) *next, 0, sizeof(*ret));
1888 		(*next)->imin = left;
1889 		(*next)->imax = right;
1890 		next = &(*next)->next;
1891 	    }
1892 
1893 	    /* skip to next interval in list */
1894 	    while(*q && *q != COMMA && *q != RPAREN)
1895 	      q++;
1896 	    if(*q == RPAREN)
1897 	      q++;
1898 	    while(*q && *q != COMMA)
1899 	      q++;
1900 	    if(*q == COMMA)
1901 	      q++;
1902 	}
1903     }
1904 
1905     return(ret);
1906 }
1907 
1908 
1909 /*
1910  * Returns string that looks like "(left,right),(left2,right2)".
1911  * Caller is responsible for freeing memory.
1912  */
1913 char *
stringform_of_intvl(INTVL_S * intvl)1914 stringform_of_intvl(INTVL_S *intvl)
1915 {
1916     char *res = NULL;
1917 
1918     if(intvl && intvl->imin != INTVL_UNDEF && intvl->imax != INTVL_UNDEF
1919        && intvl->imin <= intvl->imax){
1920 	char     lbuf[20], rbuf[20], buf[45], *p;
1921 	INTVL_S *iv;
1922 	int      count = 0;
1923 	size_t   reslen;
1924 
1925 	/* find a max size and allocate it for the result */
1926 	for(iv = intvl;
1927 	    (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF
1928 	     && iv->imin <= iv->imax);
1929 	    iv = iv->next)
1930 	  count++;
1931 
1932 	reslen = count * 50 * sizeof(char);
1933 	res = (char *) fs_get(reslen+1);
1934 	memset((void *) res, 0, reslen+1);
1935 	p = res;
1936 
1937 	for(iv = intvl;
1938 	    (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF
1939 	     && iv->imin <= iv->imax);
1940 	    iv = iv->next){
1941 
1942 	    if(iv->imin == - INTVL_INF){
1943 	      strncpy(lbuf, "-INF", sizeof(lbuf));
1944 	      lbuf[sizeof(lbuf)-1] = '\0';
1945 	    }
1946 	    else
1947 	      snprintf(lbuf, sizeof(lbuf), "%ld", iv->imin);
1948 
1949 	    if(iv->imax == INTVL_INF){
1950 	      strncpy(rbuf, "INF", sizeof(rbuf));
1951 	      rbuf[sizeof(rbuf)-1] = '\0';
1952 	    }
1953 	    else
1954 	      snprintf(rbuf, sizeof(rbuf), "%ld", iv->imax);
1955 
1956 	    snprintf(buf, sizeof(buf), "%.1s(%.20s,%.20s)", (p == res) ? "" : ",",
1957 		    lbuf, rbuf);
1958 
1959 	    sstrncpy(&p, buf, reslen+1 -(p-res));
1960 	}
1961 
1962 	res[reslen] = '\0';
1963     }
1964 
1965     return(res);
1966 }
1967 
1968 
1969 char *
hdrtok_to_stringform(HEADER_TOK_S * hdrtok)1970 hdrtok_to_stringform(HEADER_TOK_S *hdrtok)
1971 {
1972     char  buf[1024], nbuf[10];
1973     char *res = NULL;
1974     char *p, *ptr;
1975 
1976     if(hdrtok){
1977 	snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum);
1978 	ptr = buf;
1979 	sstrncpy(&ptr, hdrtok->hdrname ? hdrtok->hdrname : "", sizeof(buf)-(ptr-buf));
1980 	sstrncpy(&ptr, "(", sizeof(buf)-(ptr-buf));
1981 	sstrncpy(&ptr, nbuf, sizeof(buf)-(ptr-buf));
1982 	sstrncpy(&ptr, ",\"", sizeof(buf)-(ptr-buf));
1983 	p = hdrtok->fieldseps;
1984 	while(p && *p){
1985 	    if((*p == '\"' || *p == '\\') && ptr-buf < sizeof(buf))
1986 	      *ptr++ = '\\';
1987 
1988 	    if(ptr-buf < sizeof(buf))
1989 	      *ptr++ = *p++;
1990 	}
1991 
1992 	sstrncpy(&ptr, "\")", sizeof(buf)-(ptr-buf));
1993 
1994 	if(ptr-buf < sizeof(buf))
1995 	  *ptr = '\0';
1996 	else
1997 	  buf[sizeof(buf)-1] = '\0';
1998 
1999 	res = cpystr(buf);
2000     }
2001 
2002     return(res);
2003 }
2004 
2005 
2006 HEADER_TOK_S *
stringform_to_hdrtok(char * str)2007 stringform_to_hdrtok(char *str)
2008 {
2009     char  *p, *q, *w, hdrname[200];
2010     HEADER_TOK_S *hdrtok = NULL;
2011 
2012     if(str && *str){
2013 	p = str;
2014 	hdrname[0] = '\0';
2015 	w = hdrname;
2016 
2017 	if(*p == '\"'){				/* quoted name */
2018 	    p++;
2019 	    while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){
2020 		if(*p == '\\')
2021 		  p++;
2022 
2023 		*w++ = *p++;
2024 	    }
2025 
2026 	    *w = '\0';
2027 	    if(*p == '\"')
2028 	      p++;
2029 	}
2030 	else{
2031 	    while(w < hdrname + sizeof(hdrname)-1 &&
2032 		  !(!(*p & 0x80) && isspace((unsigned char)*p)) &&
2033 		  *p != '(')
2034 	      *w++ = *p++;
2035 
2036 	    *w = '\0';
2037 	}
2038 
2039 	if(hdrname[0])
2040 	  hdrtok = new_hdrtok(hdrname);
2041 
2042 	if(hdrtok){
2043 	    if(*p == '('){
2044 		p++;
2045 
2046 		if(*p && isdigit((unsigned char) *p)){
2047 		    q = p;
2048 		    while(*p && isdigit((unsigned char) *p))
2049 		      p++;
2050 
2051 		    hdrtok->fieldnum = atoi(q);
2052 
2053 		    if(*p == ','){
2054 			int j;
2055 
2056 			p++;
2057 			/* don't use default */
2058 			if(*p && *p != ')' && hdrtok->fieldseps){
2059 			    hdrtok->fieldseps[0] = '\0';
2060 			    hdrtok->fieldsepcnt = 0;
2061 			}
2062 
2063 			j = 0;
2064 			if(*p == '\"' && strchr(p+1, '\"')){		/* quoted */
2065 			    p++;
2066 			    while(*p && *p != '\"'){
2067 				if(hdrtok->fieldseps)
2068 				  fs_resize((void **) &hdrtok->fieldseps, j+2);
2069 
2070 				if(*p == '\\' && *(p+1))
2071 				  p++;
2072 
2073 				if(hdrtok->fieldseps){
2074 				    hdrtok->fieldseps[j++] = *p++;
2075 				    hdrtok->fieldseps[j] = '\0';
2076 				    hdrtok->fieldsepcnt = j;
2077 				}
2078 			    }
2079 			}
2080 			else{
2081 			    while(*p && *p != ')'){
2082 				if(hdrtok->fieldseps)
2083 				  fs_resize((void **) &hdrtok->fieldseps, j+2);
2084 
2085 				if(*p == '\\' && *(p+1))
2086 				  p++;
2087 
2088 				if(hdrtok->fieldseps){
2089 				    hdrtok->fieldseps[j++] = *p++;
2090 				    hdrtok->fieldseps[j] = '\0';
2091 				    hdrtok->fieldsepcnt = j;
2092 				}
2093 			    }
2094 			}
2095 		    }
2096 		    else{
2097 			q_status_message(SM_ORDER | SM_DING, 3, 3, "Missing 2nd argument should be field number, a non-negative digit");
2098 		    }
2099 		}
2100 		else{
2101 		    q_status_message(SM_ORDER | SM_DING, 3, 3, "1st argument should be field number, a non-negative digit");
2102 		}
2103 	    }
2104 	    else{
2105 		q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing left parenthesis in %s", str);
2106 	    }
2107 	}
2108 	else{
2109 	    q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing header name in %s", str);
2110 	}
2111     }
2112 
2113     return(hdrtok);
2114 }
2115 
2116 
2117 char *
hdrtok_to_config(HEADER_TOK_S * hdrtok)2118 hdrtok_to_config(HEADER_TOK_S *hdrtok)
2119 {
2120     char *ptr, buf[1024], nbuf[10], *p1, *p2, *p3;
2121     char *res = NULL;
2122 
2123     if(hdrtok){
2124 	snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum);
2125 	memset(buf, 0, sizeof(buf));
2126 	ptr = buf;
2127 	sstrncpy(&ptr, "/HN=", sizeof(buf)-(ptr-buf));
2128 	sstrncpy(&ptr, (p1=add_pat_escapes(hdrtok->hdrname ? hdrtok->hdrname : "")), sizeof(buf)-(ptr-buf));
2129 	sstrncpy(&ptr, "/FN=", sizeof(buf)-(ptr-buf));
2130 	sstrncpy(&ptr, (p2=add_pat_escapes(nbuf)), sizeof(buf)-(ptr-buf));
2131 	sstrncpy(&ptr, "/FS=", sizeof(buf)-(ptr-buf));
2132 	sstrncpy(&ptr, (p3=add_pat_escapes(hdrtok->fieldseps ? hdrtok->fieldseps : "")), sizeof(buf)-(ptr-buf));
2133 
2134 	buf[sizeof(buf)-1] = '\0';
2135 	res = add_pat_escapes(buf);
2136 
2137 	if(p1)
2138 	  fs_give((void **)&p1);
2139 
2140 	if(p2)
2141 	  fs_give((void **)&p2);
2142 
2143 	if(p3)
2144 	  fs_give((void **)&p3);
2145     }
2146 
2147     return(res);
2148 }
2149 
2150 
2151 HEADER_TOK_S *
config_to_hdrtok(char * str)2152 config_to_hdrtok(char *str)
2153 {
2154     HEADER_TOK_S *hdrtok = NULL;
2155     char *p, *q;
2156     int j;
2157 
2158     if(str && *str){
2159 	if((q = remove_pat_escapes(str)) != NULL){
2160 	    char *hn = NULL, *fn = NULL, *fs = NULL, *z;
2161 
2162 	    if((z = srchstr(q, "/HN=")) != NULL)
2163 	      hn = remove_pat_escapes(z+4);
2164 	    if((z = srchstr(q, "/FN=")) != NULL)
2165 	      fn = remove_pat_escapes(z+4);
2166 	    if((z = srchstr(q, "/FS=")) != NULL)
2167 	      fs = remove_pat_escapes(z+4);
2168 
2169 	    hdrtok = new_hdrtok(hn);
2170 	    if(fn)
2171 	      hdrtok->fieldnum = atoi(fn);
2172 
2173 	    if(fs && *fs){
2174 		if(hdrtok->fieldseps){
2175 		    hdrtok->fieldseps[0] = '\0';
2176 		    hdrtok->fieldsepcnt = 0;
2177 		}
2178 
2179 		p = fs;
2180 		j = 0;
2181 		if(*p == '\"' && strchr(p+1, '\"')){
2182 		    p++;
2183 		    while(*p && *p != '\"'){
2184 			if(hdrtok->fieldseps)
2185 			  fs_resize((void **) &hdrtok->fieldseps, j+2);
2186 
2187 			if(*p == '\\' && *(p+1))
2188 			  p++;
2189 
2190 			if(hdrtok->fieldseps){
2191 			    hdrtok->fieldseps[j++] = *p++;
2192 			    hdrtok->fieldseps[j] = '\0';
2193 			    hdrtok->fieldsepcnt = j;
2194 			}
2195 		    }
2196 		}
2197 		else{
2198 		    while(*p){
2199 			if(hdrtok->fieldseps)
2200 			  fs_resize((void **) &hdrtok->fieldseps, j+2);
2201 
2202 			if(*p == '\\' && *(p+1))
2203 			  p++;
2204 
2205 			if(hdrtok->fieldseps){
2206 			    hdrtok->fieldseps[j++] = *p++;
2207 			    hdrtok->fieldseps[j] = '\0';
2208 			    hdrtok->fieldsepcnt = j;
2209 			}
2210 		    }
2211 		}
2212 	    }
2213 
2214 	    if(hn)
2215 	      fs_give((void **)&hn);
2216 	    if(fn)
2217 	      fs_give((void **)&fn);
2218 	    if(fs)
2219 	      fs_give((void **)&fs);
2220 
2221 	    fs_give((void **)&q);
2222 	}
2223     }
2224 
2225     return(hdrtok);
2226 }
2227 
2228 
2229 /*
2230  * Args -- flags  - SCOREUSE_INVALID  Mark scores_in_use invalid so that we'll
2231  *					recalculate if we want to use it again.
2232  *		  - SCOREUSE_GET    Return whether scores are being used or not.
2233  *
2234  * Returns -- 0 - Scores not being used at all.
2235  *	     >0 - Scores are used. The return value consists of flag values
2236  *		    OR'd together. Possible values are:
2237  *
2238  *			SCOREUSE_INCOLS  - scores needed for index line colors
2239  *			SCOREUSE_ROLES   - scores needed for roles
2240  *			SCOREUSE_FILTERS - scores needed for filters
2241  *			SCOREUSE_OTHER   - scores needed for other stuff
2242  *			SCOREUSE_INDEX   - scores needed for index drawing
2243  *
2244  *			SCOREUSE_STATEDEP - scores depend on message state
2245  */
2246 int
scores_are_used(int flags)2247 scores_are_used(int flags)
2248 {
2249     static int  scores_in_use = -1;
2250     long        type1, type2;
2251     int         scores_are_defined, scores_are_used_somewhere = 0;
2252     PAT_STATE   pstate1, pstate2;
2253 
2254     if(flags & SCOREUSE_INVALID) /* mark invalid so we recalculate next time */
2255       scores_in_use = -1;
2256     else if(scores_in_use == -1){
2257 
2258 	/*
2259 	 * Check the patterns to see if scores are potentially
2260 	 * being used.
2261 	 * The first_pattern() in the if checks whether there are any
2262 	 * non-zero scorevals. The loop checks whether any patterns
2263 	 * use those non-zero scorevals.
2264 	 */
2265 	type1 = ROLE_SCORE;
2266 	type2 = (ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE |
2267 		 ROLE_INCOL | ROLE_DO_FILTER);
2268 	scores_are_defined = nonempty_patterns(type1, &pstate1)
2269 			     && first_pattern(&pstate1);
2270 	if(scores_are_defined)
2271 	  scores_are_used_somewhere =
2272 	     ((nonempty_patterns(type2, &pstate2) && first_pattern(&pstate2))
2273 	      || ps_global->a_format_contains_score
2274 	      || mn_get_sort(ps_global->msgmap) == SortScore);
2275 
2276 	if(scores_are_used_somewhere){
2277 	    PAT_S *pat;
2278 
2279 	    /*
2280 	     * Careful. nonempty_patterns() may call close_pattern()
2281 	     * which will set scores_in_use to -1! So we have to be
2282 	     * sure to reset it after we call nonempty_patterns().
2283 	     */
2284 	    scores_in_use = 0;
2285 	    if(ps_global->a_format_contains_score
2286 	       || mn_get_sort(ps_global->msgmap) == SortScore)
2287 	      scores_in_use |= SCOREUSE_INDEX;
2288 
2289 	    if(nonempty_patterns(type2, &pstate2))
2290 	      for(pat = first_pattern(&pstate2);
2291 		  pat;
2292 		  pat = next_pattern(&pstate2))
2293 	        if(pat->patgrp && !pat->patgrp->bogus && pat->patgrp->do_score){
2294 		    if(pat->action && pat->action->is_a_incol)
2295 		      scores_in_use |= SCOREUSE_INCOLS;
2296 		    if(pat->action && pat->action->is_a_role)
2297 		      scores_in_use |= SCOREUSE_ROLES;
2298 		    if(pat->action && pat->action->is_a_filter)
2299 		      scores_in_use |= SCOREUSE_FILTERS;
2300 		    if(pat->action && pat->action->is_a_other)
2301 		      scores_in_use |= SCOREUSE_OTHER;
2302 	        }
2303 
2304 	    /*
2305 	     * Note whether scores depend on message state or not.
2306 	     */
2307 	    if(scores_in_use)
2308 	      for(pat = first_pattern(&pstate1);
2309 		  pat;
2310 		  pat = next_pattern(&pstate1))
2311 		if(patgrp_depends_on_active_state(pat->patgrp)){
2312 		    scores_in_use |= SCOREUSE_STATEDEP;
2313 		    break;
2314 		}
2315 
2316 	}
2317 	else
2318 	  scores_in_use = 0;
2319     }
2320 
2321     return((scores_in_use == -1) ? 0 : scores_in_use);
2322 }
2323 
2324 
2325 int
patgrp_depends_on_state(PATGRP_S * patgrp)2326 patgrp_depends_on_state(PATGRP_S *patgrp)
2327 {
2328     return(patgrp && (patgrp_depends_on_active_state(patgrp)
2329 		      || patgrp->stat_rec != PAT_STAT_EITHER));
2330 }
2331 
2332 
2333 /*
2334  * Recent doesn't count for this function because it doesn't change while
2335  * the mailbox is open.
2336  */
2337 int
patgrp_depends_on_active_state(PATGRP_S * patgrp)2338 patgrp_depends_on_active_state(PATGRP_S *patgrp)
2339 {
2340     return(patgrp && !patgrp->bogus
2341            && (patgrp->stat_new  != PAT_STAT_EITHER ||
2342 	       patgrp->stat_del  != PAT_STAT_EITHER ||
2343 	       patgrp->stat_imp  != PAT_STAT_EITHER ||
2344 	       patgrp->stat_ans  != PAT_STAT_EITHER ||
2345 	       patgrp->keyword));
2346 }
2347 
2348 
2349 /*
2350  * Look for label in str and return a pointer to parsed string.
2351  * Actually, we look for "label=" or "!label=", the second means NOT.
2352  * Converts from string from patterns file which looks like
2353  *       /NEWS=comp.mail.,comp.mail.pine/TO=...
2354  * This is the string that came from pattern="string" with the pattern=
2355  * and outer quotes removed.
2356  * This converts the string to a PATTERN_S list and returns
2357  * an allocated copy.
2358  */
2359 PATTERN_S *
parse_pattern(char * label,char * str,int hex_to_backslashed)2360 parse_pattern(char *label, char *str, int hex_to_backslashed)
2361 {
2362     char       copy[50];	/* local copy of label */
2363     char       copynot[50];	/* local copy of label, NOT'ed */
2364     char      *q, *labeled_str;
2365     PATTERN_S *head = NULL;
2366 
2367     if(!label || !str)
2368       return(NULL);
2369 
2370     q = copy;
2371     sstrncpy(&q, "/", sizeof(copy));
2372     sstrncpy(&q, label, sizeof(copy) - (q-copy));
2373     sstrncpy(&q, "=", sizeof(copy) - (q-copy));
2374     copy[sizeof(copy)-1] = '\0';
2375     q = copynot;
2376     sstrncpy(&q, "/!", sizeof(copynot));
2377     sstrncpy(&q, label, sizeof(copynot) - (q-copynot));
2378     sstrncpy(&q, "=", sizeof(copynot) - (q-copynot));
2379     copynot[sizeof(copynot)-1] = '\0';
2380 
2381     if(hex_to_backslashed){
2382 	if((q = srchstr(str, copy)) != NULL){
2383 	    head = config_to_pattern(q+strlen(copy));
2384 	}
2385 	else if((q = srchstr(str, copynot)) != NULL){
2386 	    head = config_to_pattern(q+strlen(copynot));
2387 	    head->not = 1;
2388 	}
2389     }
2390     else{
2391 	if((q = srchstr(str, copy)) != NULL){
2392 	    if((labeled_str =
2393 			remove_backslash_escapes(q+strlen(copy))) != NULL){
2394 		head = string_to_pattern(labeled_str);
2395 		fs_give((void **)&labeled_str);
2396 	    }
2397 	}
2398 	else if((q = srchstr(str, copynot)) != NULL){
2399 	    if((labeled_str =
2400 			remove_backslash_escapes(q+strlen(copynot))) != NULL){
2401 		head = string_to_pattern(labeled_str);
2402 		head->not = 1;
2403 		fs_give((void **)&labeled_str);
2404 	    }
2405 	}
2406     }
2407 
2408     return(head);
2409 }
2410 
2411 
2412 /*
2413  * Look for /ARB's in str and return a pointer to parsed ARBHDR_S.
2414  * Actually, we look for /!ARB and /!EARB as well. Those mean NOT.
2415  * Converts from string from patterns file which looks like
2416  *       /ARB<fieldname1>=pattern/.../ARB<fieldname2>=pattern...
2417  * This is the string that came from pattern="string" with the pattern=
2418  * and outer quotes removed.
2419  * This converts the string to a ARBHDR_S list and returns
2420  * an allocated copy.
2421  */
2422 ARBHDR_S *
parse_arbhdr(char * str)2423 parse_arbhdr(char *str)
2424 {
2425     char      *q, *s, *equals, *noesc;
2426     int        not, empty, skip;
2427     ARBHDR_S  *ahdr = NULL, *a, *aa;
2428     PATTERN_S *p = NULL;
2429 
2430     if(!str)
2431       return(NULL);
2432 
2433     aa = NULL;
2434     for(s = str; (q = next_arb(s)); s = q+1){
2435 	not = (q[1] == '!') ? 1 : 0;
2436 	empty = (q[not+1] == 'E') ? 1 : 0;
2437 	skip = 4 + not + empty;
2438 	if((noesc = remove_pat_escapes(q+skip)) != NULL){
2439 	    if(*noesc != '=' && (equals = strindex(noesc, '=')) != NULL){
2440 		a = (ARBHDR_S *)fs_get(sizeof(*a));
2441 		memset((void *)a, 0, sizeof(*a));
2442 		*equals = '\0';
2443 		a->isemptyval = empty;
2444 		a->field = cpystr(noesc);
2445 		if(empty)
2446 		  a->p     = string_to_pattern("");
2447 		else if(*(equals+1) &&
2448 			(p = string_to_pattern(equals+1)) != NULL)
2449 		  a->p     = p;
2450 
2451 		if(not && a->p)
2452 		  a->p->not = 1;
2453 
2454 		/* keep them in the same order */
2455 		if(aa){
2456 		    aa->next = a;
2457 		    aa = aa->next;
2458 		}
2459 		else{
2460 		    ahdr = a;
2461 		    aa = ahdr;
2462 		}
2463 	    }
2464 
2465 	    fs_give((void **)&noesc);
2466 	}
2467     }
2468 
2469     return(ahdr);
2470 }
2471 
2472 
2473 char *
next_arb(char * start)2474 next_arb(char *start)
2475 {
2476     char *q1, *q2, *q3, *q4, *p;
2477 
2478     q1 = srchstr(start, "/ARB");
2479     q2 = srchstr(start, "/!ARB");
2480     q3 = srchstr(start, "/EARB");
2481     q4 = srchstr(start, "/!EARB");
2482 
2483     p = q1;
2484     if(!p || (q2 && q2 < p))
2485       p = q2;
2486     if(!p || (q3 && q3 < p))
2487       p = q3;
2488     if(!p || (q4 && q4 < p))
2489       p = q4;
2490 
2491     return(p);
2492 }
2493 
2494 
2495 /*
2496  * Converts a string to a PATTERN_S list and returns an
2497  * allocated copy. The source string looks like
2498  *        string1,string2,...
2499  * Commas and backslashes may be backslash-escaped in the original string
2500  * in order to include actual commas and backslashes in the pattern.
2501  * So \, is an actual comma and , is the separator character.
2502  */
2503 PATTERN_S *
string_to_pattern(char * str)2504 string_to_pattern(char *str)
2505 {
2506     char      *q, *s, *workspace;
2507     PATTERN_S *p, *head = NULL, **nextp;
2508 
2509     if(!str)
2510       return(head);
2511 
2512     /*
2513      * We want an empty string to cause an empty substring in the pattern
2514      * instead of returning a NULL pattern. That can be used as a way to
2515      * match any header. For example, if all the patterns but the news
2516      * pattern were null and the news pattern was a substring of "" then
2517      * we use that to match any message with a newsgroups header.
2518      */
2519     if(!*str){
2520 	head = (PATTERN_S *)fs_get(sizeof(*p));
2521 	memset((void *)head, 0, sizeof(*head));
2522 	head->substring = cpystr("");
2523     }
2524     else{
2525 	nextp = &head;
2526 	workspace = (char *)fs_get((strlen(str)+1) * sizeof(char));
2527 	s = workspace;
2528 	*s = '\0';
2529 	q = str;
2530 	do {
2531 	    switch(*q){
2532 	      case COMMA:
2533 	      case '\0':
2534 		*s = '\0';
2535 		removing_leading_and_trailing_white_space(workspace);
2536 		p = (PATTERN_S *)fs_get(sizeof(*p));
2537 		memset((void *)p, 0, sizeof(*p));
2538 		p->substring = cpystr(workspace);
2539 
2540 		convert_possibly_encoded_str_to_utf8(&p->substring);
2541 
2542 		*nextp = p;
2543 		nextp = &p->next;
2544 		s = workspace;
2545 		*s = '\0';
2546 		break;
2547 
2548 	      case BSLASH:
2549 		if(*(q+1) == COMMA || *(q+1) == BSLASH)
2550 		  *s++ = *(++q);
2551 		else
2552 		  *s++ = *q;
2553 
2554 		break;
2555 
2556 	      default:
2557 		*s++ = *q;
2558 		break;
2559 	    }
2560 	} while(*q++);
2561 
2562 	fs_give((void **)&workspace);
2563     }
2564 
2565     return(head);
2566 }
2567 
2568 
2569 /*
2570  * Converts a PATTERN_S list to a string.
2571  * The resulting string is allocated here and looks like
2572  *        string1,string2,...
2573  * Commas and backslashes in the original pattern
2574  * end up backslash-escaped in the string.
2575  */
2576 char *
pattern_to_string(PATTERN_S * pattern)2577 pattern_to_string(PATTERN_S *pattern)
2578 {
2579     PATTERN_S *p;
2580     char      *result = NULL, *q, *s;
2581     size_t     n;
2582 
2583     if(!pattern)
2584       return(result);
2585 
2586     /* how much space is needed? */
2587     n = 0;
2588     for(p = pattern; p; p = p->next){
2589 	n += (p == pattern) ? 0 : 1;
2590 	for(s = p->substring; s && *s; s++){
2591 	    if(*s == COMMA || *s == BSLASH)
2592 	      n++;
2593 
2594 	    n++;
2595 	}
2596     }
2597 
2598     q = result = (char *)fs_get(++n);
2599     for(p = pattern; p; p = p->next){
2600 	if(p != pattern)
2601 	  *q++ = COMMA;
2602 
2603 	for(s = p->substring; s && *s; s++){
2604 	    if(*s == COMMA || *s == BSLASH)
2605 	      *q++ = '\\';
2606 
2607 	    *q++ = *s;
2608 	}
2609     }
2610 
2611     *q = '\0';
2612 
2613     return(result);
2614 }
2615 
2616 
2617 /*
2618  * Do the escaping necessary to take a string for a pattern into a comma-
2619  * separated string with escapes suitable for the config file.
2620  * Returns an allocated copy of that string.
2621  * In particular
2622  *     ,  ->  \,  ->  \x2C
2623  *     \  ->  \\  ->  \x5C
2624  *     "       ->      \"
2625  *     /       ->      \/
2626  */
2627 char *
pattern_to_config(PATTERN_S * pat)2628 pattern_to_config(PATTERN_S *pat)
2629 {
2630     char *s, *res = NULL;
2631 
2632     s = pattern_to_string(pat);
2633     if(s){
2634 	res = add_pat_escapes(s);
2635 	fs_give((void **) &s);
2636     }
2637 
2638     return(res);
2639 }
2640 
2641 /*
2642  * Opposite of pattern_to_config.
2643  */
2644 PATTERN_S *
config_to_pattern(char * str)2645 config_to_pattern(char *str)
2646 {
2647     char      *s;
2648     PATTERN_S *pat = NULL;
2649 
2650     s = remove_pat_escapes(str);
2651     if(s){
2652 	pat = string_to_pattern(s);
2653 	fs_give((void **) &s);
2654     }
2655 
2656     return(pat);
2657 }
2658 
2659 
2660 /*
2661  * Converts an array of strings to a PATTERN_S list and returns an
2662  * allocated copy.
2663  * The list strings may not contain commas directly, because the UI turns
2664  * those into separate list members. Instead, the user types \, and
2665  * that backslash comma is converted to a comma here.
2666  * It is a bit odd. Backslash itself is not escaped. A backslash which is
2667  * not followed by a comma is a literal backslash, a backslash followed by
2668  * a comma is a comma.
2669  */
2670 PATTERN_S *
editlist_to_pattern(char ** list)2671 editlist_to_pattern(char **list)
2672 {
2673     PATTERN_S *head = NULL;
2674 
2675     if(!(list && *list))
2676       return(head);
2677 
2678     /*
2679      * We want an empty string to cause an empty substring in the pattern
2680      * instead of returning a NULL pattern. That can be used as a way to
2681      * match any header. For example, if all the patterns but the news
2682      * pattern were null and the news pattern was a substring of "" then
2683      * we use that to match any message with a newsgroups header.
2684      */
2685     if(!list[0][0]){
2686 	head = (PATTERN_S *) fs_get(sizeof(*head));
2687 	memset((void *) head, 0, sizeof(*head));
2688 	head->substring = cpystr("");
2689     }
2690     else{
2691 	char      *str, *s, *q, *workspace = NULL;
2692 	size_t     l = 0;
2693 	PATTERN_S *p, **nextp;
2694 	int        i;
2695 
2696 	nextp = &head;
2697 	for(i = 0; (str = list[i]); i++){
2698 	    if(str[0]){
2699 		if(!workspace){
2700 		    l = strlen(str) + 1;
2701 		    workspace = (char *) fs_get(l * sizeof(char));
2702 		}
2703 		else if(strlen(str) + 1 > l){
2704 		    l = strlen(str) + 1;
2705 		    fs_give((void **) &workspace);
2706 		    workspace = (char *) fs_get(l * sizeof(char));
2707 		}
2708 
2709 		s = workspace;
2710 		*s = '\0';
2711 		q = str;
2712 		do {
2713 		    switch(*q){
2714 		      case '\0':
2715 			*s = '\0';
2716 			removing_leading_and_trailing_white_space(workspace);
2717 			p = (PATTERN_S *) fs_get(sizeof(*p));
2718 			memset((void *) p, 0, sizeof(*p));
2719 			p->substring = cpystr(workspace);
2720 			*nextp = p;
2721 			nextp = &p->next;
2722 			s = workspace;
2723 			*s = '\0';
2724 			break;
2725 
2726 		      case BSLASH:
2727 			if(*(q+1) == COMMA)
2728 			  *s++ = *(++q);
2729 			else
2730 			  *s++ = *q;
2731 
2732 			break;
2733 
2734 		      default:
2735 			*s++ = *q;
2736 			break;
2737 		    }
2738 		} while(*q++);
2739 	    }
2740 	}
2741     }
2742 
2743     return(head);
2744 }
2745 
2746 
2747 /*
2748  * Converts a PATTERN_S to an array of strings and returns an allocated copy.
2749  * Commas are converted to backslash-comma, because the text_tool UI uses
2750  * commas to separate items.
2751  * It is a bit odd. Backslash itself is not escaped. A backslash which is
2752  * not followed by a comma is a literal backslash, a backslash followed by
2753  * a comma is a comma.
2754  */
2755 char **
pattern_to_editlist(PATTERN_S * pat)2756 pattern_to_editlist(PATTERN_S *pat)
2757 {
2758     int        cnt, i;
2759     PATTERN_S *p;
2760     char     **list = NULL;
2761 
2762     if(!pat)
2763       return(list);
2764 
2765     /* how many in list? */
2766     for(cnt = 0, p = pat; p; p = p->next)
2767       cnt++;
2768 
2769     list = (char **) fs_get((cnt + 1) * sizeof(*list));
2770     memset((void *) list, 0, (cnt + 1) * sizeof(*list));
2771 
2772     for(i = 0, p = pat; p; p = p->next, i++)
2773       list[i] = add_comma_escapes(p->substring);
2774 
2775     return(list);
2776 }
2777 
2778 
2779 PATGRP_S *
nick_to_patgrp(char * nick,int rflags)2780 nick_to_patgrp(char *nick, int rflags)
2781 {
2782     PAT_S     *pat;
2783     PAT_STATE  pstate;
2784     PATGRP_S  *patgrp = NULL;
2785 
2786     if(!(nick && *nick
2787 	 && nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
2788       return(patgrp);
2789 
2790     for(pat = first_pattern(&pstate);
2791 	!patgrp && pat;
2792 	pat = next_pattern(&pstate))
2793       if(pat->patgrp && pat->patgrp->nick && !strcmp(pat->patgrp->nick, nick))
2794 	patgrp = copy_patgrp(pat->patgrp);
2795 
2796     return(patgrp);
2797 }
2798 
2799 
2800 /*
2801  * Must be called with a pstate, we don't check for it.
2802  * It respects the cur_rflag_num in pstate. That is, it doesn't start over
2803  * at i=1, it starts at cur_rflag_num.
2804  */
2805 PAT_S *
first_any_pattern(PAT_STATE * pstate)2806 first_any_pattern(PAT_STATE *pstate)
2807 {
2808     PAT_LINE_S *patline = NULL;
2809     int         i;
2810     long        local_rflag;
2811 
2812     /*
2813      * The rest of pstate should be set before coming here.
2814      * In particular, the rflags should be set by a call to nonempty_patterns
2815      * or any_patterns, and cur_rflag_num should be set.
2816      */
2817     pstate->patlinecurrent = NULL;
2818     pstate->patcurrent     = NULL;
2819 
2820     /*
2821      * The order of these is important. It is the same as the order
2822      * used for next_any_pattern and opposite of the order used by
2823      * last and prev. For next_any's benefit, we allow cur_rflag_num to
2824      * start us out past the first set.
2825      */
2826     for(i = pstate->cur_rflag_num; i <= PATTERN_N; i++){
2827 
2828 	local_rflag = 0L;
2829 
2830 	switch(i){
2831 	  case 1:
2832 	    local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags);
2833 	    break;
2834 
2835 	  case 2:
2836 	    local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags);
2837 	    break;
2838 
2839 	  case 3:
2840 	    local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags);
2841 	    break;
2842 
2843 	  case 4:
2844 	    local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags);
2845 	    break;
2846 
2847 	  case 5:
2848 	    local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags);
2849 	    break;
2850 
2851 	  case 6:
2852 	    local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags);
2853 	    break;
2854 
2855 	  case 7:
2856 	    local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags);
2857 	    break;
2858 
2859 	  case 8:
2860 	    local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags);
2861 	    break;
2862 
2863 	  case PATTERN_N:
2864 	    local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags);
2865 	    break;
2866 	}
2867 
2868 	if(local_rflag){
2869 	    SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK));
2870 
2871 	    if(*cur_pat_h){
2872 		/* Find first patline with a pat */
2873 		for(patline = (*cur_pat_h)->patlinehead;
2874 		    patline && !patline->first;
2875 		    patline = patline->next)
2876 		  ;
2877 	    }
2878 
2879 	    if(patline){
2880 		pstate->cur_rflag_num  = i;
2881 		pstate->patlinecurrent = patline;
2882 		pstate->patcurrent     = patline->first;
2883 	    }
2884 	}
2885 
2886 	if(pstate->patcurrent)
2887 	  break;
2888     }
2889 
2890     return(pstate->patcurrent);
2891 }
2892 
2893 
2894 /*
2895  * Return first pattern of the specified types. These types were set by a
2896  * previous call to any_patterns or nonempty_patterns.
2897  *
2898  * Args --  pstate  pattern state. This is set here and passed back for
2899  *                  use by next_pattern. Must be non-null.
2900  *                  It must have been initialized previously by a call to
2901  *                  nonempty_patterns or any_patterns.
2902  */
2903 PAT_S *
first_pattern(PAT_STATE * pstate)2904 first_pattern(PAT_STATE *pstate)
2905 {
2906     PAT_S           *pat;
2907     long             rflags;
2908 
2909     pstate->cur_rflag_num = 1;
2910 
2911     rflags = pstate->rflags;
2912 
2913     for(pat = first_any_pattern(pstate);
2914 	pat && !((pat->action &&
2915 		  ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
2916 	           (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
2917 		    pat->action->is_a_incol) ||
2918 	           (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
2919 	           (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
2920 	           (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
2921 		   (rflags & ROLE_SCORE && (pat->action->scoreval
2922 		                            || pat->action->scorevalhdrtok)) ||
2923 		   (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
2924 	           (rflags & ROLE_REPLY &&
2925 		    (pat->action->repl_type == ROLE_REPL_YES ||
2926 		     pat->action->repl_type == ROLE_REPL_NOCONF)) ||
2927 	           (rflags & ROLE_FORWARD &&
2928 		    (pat->action->forw_type == ROLE_FORW_YES ||
2929 		     pat->action->forw_type == ROLE_FORW_NOCONF)) ||
2930 	           (rflags & ROLE_COMPOSE &&
2931 		    (pat->action->comp_type == ROLE_COMP_YES ||
2932 		     pat->action->comp_type == ROLE_COMP_NOCONF)) ||
2933 	           (rflags & ROLE_OLD_FILT) ||
2934 	           (rflags & ROLE_OLD_SCORE) ||
2935 	           (rflags & ROLE_OLD_PAT)))
2936 		||
2937 		 pat->inherit);
2938 	pat = next_any_pattern(pstate))
2939       ;
2940 
2941     return(pat);
2942 }
2943 
2944 
2945 /*
2946  * Just like first_any_pattern.
2947  */
2948 PAT_S *
last_any_pattern(PAT_STATE * pstate)2949 last_any_pattern(PAT_STATE *pstate)
2950 {
2951     PAT_LINE_S *patline = NULL;
2952     int         i;
2953     long        local_rflag;
2954 
2955     /*
2956      * The rest of pstate should be set before coming here.
2957      * In particular, the rflags should be set by a call to nonempty_patterns
2958      * or any_patterns, and cur_rflag_num should be set.
2959      */
2960     pstate->patlinecurrent = NULL;
2961     pstate->patcurrent     = NULL;
2962 
2963     for(i = pstate->cur_rflag_num; i >= 1; i--){
2964 
2965 	local_rflag = 0L;
2966 
2967 	switch(i){
2968 	  case 1:
2969 	    local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags);
2970 	    break;
2971 
2972 	  case 2:
2973 	    local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags);
2974 	    break;
2975 
2976 	  case 3:
2977 	    local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags);
2978 	    break;
2979 
2980 	  case 4:
2981 	    local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags);
2982 	    break;
2983 
2984 	  case 5:
2985 	    local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags);
2986 	    break;
2987 
2988 	  case 6:
2989 	    local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags);
2990 	    break;
2991 
2992 	  case 7:
2993 	    local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags);
2994 	    break;
2995 
2996 	  case 8:
2997 	    local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags);
2998 	    break;
2999 
3000 	  case PATTERN_N:
3001 	    local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags);
3002 	    break;
3003 	}
3004 
3005 	if(local_rflag){
3006 	    SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK));
3007 
3008 	    pstate->patlinecurrent = NULL;
3009 	    pstate->patcurrent     = NULL;
3010 
3011 	    if(*cur_pat_h){
3012 		/* Find last patline with a pat */
3013 		for(patline = (*cur_pat_h)->patlinehead;
3014 		    patline;
3015 		    patline = patline->next)
3016 		  if(patline->last)
3017 		    pstate->patlinecurrent = patline;
3018 
3019 		if(pstate->patlinecurrent)
3020 		  pstate->patcurrent = pstate->patlinecurrent->last;
3021 	    }
3022 
3023 	    if(pstate->patcurrent)
3024 	      pstate->cur_rflag_num = i;
3025 
3026 	    if(pstate->patcurrent)
3027 	      break;
3028 	}
3029     }
3030 
3031     return(pstate->patcurrent);
3032 }
3033 
3034 
3035 /*
3036  * Return last pattern of the specified types. These types were set by a
3037  * previous call to any_patterns or nonempty_patterns.
3038  *
3039  * Args --  pstate  pattern state. This is set here and passed back for
3040  *                  use by prev_pattern. Must be non-null.
3041  *                  It must have been initialized previously by a call to
3042  *                  nonempty_patterns or any_patterns.
3043  */
3044 PAT_S *
last_pattern(PAT_STATE * pstate)3045 last_pattern(PAT_STATE *pstate)
3046 {
3047     PAT_S           *pat;
3048     long             rflags;
3049 
3050     pstate->cur_rflag_num = PATTERN_N;
3051 
3052     rflags = pstate->rflags;
3053 
3054     for(pat = last_any_pattern(pstate);
3055 	pat && !((pat->action &&
3056 		  ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3057 	           (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3058 		    pat->action->is_a_incol) ||
3059 	           (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3060 	           (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3061 	           (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3062 		   (rflags & ROLE_SCORE && (pat->action->scoreval
3063 		                            || pat->action->scorevalhdrtok)) ||
3064 		   (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3065 	           (rflags & ROLE_REPLY &&
3066 		    (pat->action->repl_type == ROLE_REPL_YES ||
3067 		     pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3068 	           (rflags & ROLE_FORWARD &&
3069 		    (pat->action->forw_type == ROLE_FORW_YES ||
3070 		     pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3071 	           (rflags & ROLE_COMPOSE &&
3072 		    (pat->action->comp_type == ROLE_COMP_YES ||
3073 		     pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3074 	           (rflags & ROLE_OLD_FILT) ||
3075 	           (rflags & ROLE_OLD_SCORE) ||
3076 	           (rflags & ROLE_OLD_PAT)))
3077 		||
3078 		 pat->inherit);
3079 	pat = prev_any_pattern(pstate))
3080       ;
3081 
3082     return(pat);
3083 }
3084 
3085 
3086 /*
3087  * This assumes that pstate is valid.
3088  */
3089 PAT_S *
next_any_pattern(PAT_STATE * pstate)3090 next_any_pattern(PAT_STATE *pstate)
3091 {
3092     PAT_LINE_S *patline;
3093 
3094     if(pstate->patlinecurrent){
3095 	if(pstate->patcurrent && pstate->patcurrent->next)
3096 	  pstate->patcurrent = pstate->patcurrent->next;
3097 	else{
3098 	    /* Find next patline with a pat */
3099 	    for(patline = pstate->patlinecurrent->next;
3100 		patline && !patline->first;
3101 		patline = patline->next)
3102 	      ;
3103 
3104 	    if(patline){
3105 		pstate->patlinecurrent = patline;
3106 		pstate->patcurrent     = patline->first;
3107 	    }
3108 	    else{
3109 		pstate->patlinecurrent = NULL;
3110 		pstate->patcurrent     = NULL;
3111 	    }
3112 	}
3113     }
3114 
3115     /* we've reached the last, try the next rflag_num (the next pattern type) */
3116     if(!pstate->patcurrent){
3117 	pstate->cur_rflag_num++;
3118 	pstate->patcurrent = first_any_pattern(pstate);
3119     }
3120 
3121     return(pstate->patcurrent);
3122 }
3123 
3124 
3125 /*
3126  * Return next pattern of the specified types. These types were set by a
3127  * previous call to any_patterns or nonempty_patterns.
3128  *
3129  * Args -- pstate  pattern state. This is set by first_pattern or last_pattern.
3130  */
3131 PAT_S *
next_pattern(PAT_STATE * pstate)3132 next_pattern(PAT_STATE *pstate)
3133 {
3134     PAT_S           *pat;
3135     long             rflags;
3136 
3137     rflags = pstate->rflags;
3138 
3139     for(pat = next_any_pattern(pstate);
3140 	pat && !((pat->action &&
3141 		  ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3142 	           (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3143 		    pat->action->is_a_incol) ||
3144 	           (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3145 	           (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3146 	           (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3147 		   (rflags & ROLE_SCORE && (pat->action->scoreval
3148 		                            || pat->action->scorevalhdrtok)) ||
3149 		   (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3150 	           (rflags & ROLE_REPLY &&
3151 		    (pat->action->repl_type == ROLE_REPL_YES ||
3152 		     pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3153 	           (rflags & ROLE_FORWARD &&
3154 		    (pat->action->forw_type == ROLE_FORW_YES ||
3155 		     pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3156 	           (rflags & ROLE_COMPOSE &&
3157 		    (pat->action->comp_type == ROLE_COMP_YES ||
3158 		     pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3159 	           (rflags & ROLE_OLD_FILT) ||
3160 	           (rflags & ROLE_OLD_SCORE) ||
3161 	           (rflags & ROLE_OLD_PAT)))
3162 		||
3163 		 pat->inherit);
3164 	pat = next_any_pattern(pstate))
3165       ;
3166 
3167     return(pat);
3168 }
3169 
3170 
3171 /*
3172  * This assumes that pstate is valid.
3173  */
3174 PAT_S *
prev_any_pattern(PAT_STATE * pstate)3175 prev_any_pattern(PAT_STATE *pstate)
3176 {
3177     PAT_LINE_S *patline;
3178 
3179     if(pstate->patlinecurrent){
3180 	if(pstate->patcurrent && pstate->patcurrent->prev)
3181 	  pstate->patcurrent = pstate->patcurrent->prev;
3182 	else{
3183 	    /* Find prev patline with a pat */
3184 	    for(patline = pstate->patlinecurrent->prev;
3185 		patline && !patline->last;
3186 		patline = patline->prev)
3187 	      ;
3188 
3189 	    if(patline){
3190 		pstate->patlinecurrent = patline;
3191 		pstate->patcurrent     = patline->last;
3192 	    }
3193 	    else{
3194 		pstate->patlinecurrent = NULL;
3195 		pstate->patcurrent     = NULL;
3196 	    }
3197 	}
3198     }
3199 
3200     if(!pstate->patcurrent){
3201 	pstate->cur_rflag_num--;
3202 	pstate->patcurrent = last_any_pattern(pstate);
3203     }
3204 
3205     return(pstate->patcurrent);
3206 }
3207 
3208 
3209 /*
3210  * Return prev pattern of the specified types. These types were set by a
3211  * previous call to any_patterns or nonempty_patterns.
3212  *
3213  * Args -- pstate  pattern state. This is set by first_pattern or last_pattern.
3214  */
3215 PAT_S *
prev_pattern(PAT_STATE * pstate)3216 prev_pattern(PAT_STATE *pstate)
3217 {
3218     PAT_S           *pat;
3219     long             rflags;
3220 
3221     rflags = pstate->rflags;
3222 
3223     for(pat = prev_any_pattern(pstate);
3224 	pat && !((pat->action &&
3225 		  ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) ||
3226 	           (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) &&
3227 		    pat->action->is_a_incol) ||
3228 	           (rflags & ROLE_DO_OTHER && pat->action->is_a_other) ||
3229 	           (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) ||
3230 	           (rflags & ROLE_DO_SCORES && pat->action->is_a_score) ||
3231 		   (rflags & ROLE_SCORE && (pat->action->scoreval
3232 		                            || pat->action->scorevalhdrtok)) ||
3233 		   (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) ||
3234 	           (rflags & ROLE_REPLY &&
3235 		    (pat->action->repl_type == ROLE_REPL_YES ||
3236 		     pat->action->repl_type == ROLE_REPL_NOCONF)) ||
3237 	           (rflags & ROLE_FORWARD &&
3238 		    (pat->action->forw_type == ROLE_FORW_YES ||
3239 		     pat->action->forw_type == ROLE_FORW_NOCONF)) ||
3240 	           (rflags & ROLE_COMPOSE &&
3241 		    (pat->action->comp_type == ROLE_COMP_YES ||
3242 		     pat->action->comp_type == ROLE_COMP_NOCONF)) ||
3243 	           (rflags & ROLE_OLD_FILT) ||
3244 	           (rflags & ROLE_OLD_SCORE) ||
3245 	           (rflags & ROLE_OLD_PAT)))
3246 		||
3247 		 pat->inherit);
3248 	pat = prev_any_pattern(pstate))
3249       ;
3250 
3251     return(pat);
3252 }
3253 
3254 
3255 /*
3256  * Rflags may be more than one pattern type OR'd together.
3257  */
3258 int
write_patterns(long int rflags)3259 write_patterns(long int rflags)
3260 {
3261     int canon_rflags;
3262     int err = 0;
3263 
3264     dprint((7, "write_patterns(0x%x)\n", rflags));
3265 
3266     canon_rflags = CANONICAL_RFLAGS(rflags);
3267 
3268     if(canon_rflags & ROLE_DO_INCOLS)
3269       err += sub_write_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK));
3270     if(!err && canon_rflags & ROLE_DO_OTHER)
3271       err += sub_write_patterns(ROLE_DO_OTHER  | (rflags & PAT_USE_MASK));
3272     if(!err && canon_rflags & ROLE_DO_FILTER)
3273       err += sub_write_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK));
3274     if(!err && canon_rflags & ROLE_DO_SCORES)
3275       err += sub_write_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK));
3276     if(!err && canon_rflags & ROLE_DO_ROLES)
3277       err += sub_write_patterns(ROLE_DO_ROLES  | (rflags & PAT_USE_MASK));
3278     if(!err && canon_rflags & ROLE_DO_SRCH)
3279       err += sub_write_patterns(ROLE_DO_SRCH   | (rflags & PAT_USE_MASK));
3280 
3281     if(!err && !(rflags & PAT_USE_CHANGED))
3282       write_pinerc(ps_global, (rflags & PAT_USE_MAIN) ? Main : Post, WRP_NONE);
3283 
3284     return(err);
3285 }
3286 
3287 
3288 int
sub_write_patterns(long int rflags)3289 sub_write_patterns(long int rflags)
3290 {
3291     int            err = 0, lineno = 0;
3292     char         **lvalue = NULL;
3293     PAT_LINE_S    *patline;
3294 
3295     SET_PATTYPE(rflags);
3296 
3297     if(!(*cur_pat_h)){
3298 	q_status_message(SM_ORDER | SM_DING, 3, 4,
3299 			  "Unknown error saving patterns");
3300 	return(-1);
3301     }
3302 
3303     if((*cur_pat_h)->dirtypinerc){
3304 	/* Count how many lines will be in patterns variable */
3305 	for(patline = (*cur_pat_h)->patlinehead;
3306 	    patline;
3307 	    patline = patline->next)
3308 	  lineno++;
3309 
3310 	lvalue = (char **)fs_get((lineno+1)*sizeof(char *));
3311 	memset(lvalue, 0, (lineno+1) * sizeof(char *));
3312     }
3313 
3314     for(patline = (*cur_pat_h)->patlinehead, lineno = 0;
3315 	!err && patline;
3316 	patline = patline->next, lineno++){
3317 	if(patline->type == File)
3318 	  err = write_pattern_file((*cur_pat_h)->dirtypinerc
3319 				      ? &lvalue[lineno] : NULL, patline);
3320 	else if(patline->type == Literal && (*cur_pat_h)->dirtypinerc)
3321 	  err = write_pattern_lit(&lvalue[lineno], patline);
3322 	else if(patline->type == Inherit)
3323 	  err = write_pattern_inherit((*cur_pat_h)->dirtypinerc
3324 				      ? &lvalue[lineno] : NULL, patline);
3325     }
3326 
3327     if((*cur_pat_h)->dirtypinerc){
3328 	if(err)
3329 	  free_list_array(&lvalue);
3330 	else{
3331 	    char ***alval;
3332 	    struct variable *var = NULL;
3333 
3334 	    if(rflags & ROLE_DO_ROLES)
3335 	      var = &ps_global->vars[V_PAT_ROLES];
3336 	    else if(rflags & ROLE_DO_OTHER)
3337 	      var = &ps_global->vars[V_PAT_OTHER];
3338 	    else if(rflags & ROLE_DO_FILTER)
3339 	      var = &ps_global->vars[V_PAT_FILTS];
3340 	    else if(rflags & ROLE_DO_SCORES)
3341 	      var = &ps_global->vars[V_PAT_SCORES];
3342 	    else if(rflags & ROLE_DO_INCOLS)
3343 	      var = &ps_global->vars[V_PAT_INCOLS];
3344 	    else if(rflags & ROLE_DO_SRCH)
3345 	      var = &ps_global->vars[V_PAT_SRCH];
3346 
3347 	    alval = (rflags & PAT_USE_CHANGED) ? &(var->changed_val.l)
3348 	      : ALVAL(var, (rflags & PAT_USE_MAIN) ? Main : Post);
3349 	    if(*alval)
3350 	      free_list_array(alval);
3351 
3352 	    if(rflags & PAT_USE_CHANGED) var->is_changed_val = 1;
3353 
3354 	    *alval = lvalue;
3355 
3356 	    if(!(rflags & PAT_USE_CHANGED))
3357 	      set_current_val(var, TRUE, TRUE);
3358 	}
3359     }
3360 
3361     if(!err)
3362       (*cur_pat_h)->dirtypinerc = 0;
3363 
3364     return(err);
3365 }
3366 
3367 
3368 /*
3369  * Write pattern lines into a file.
3370  *
3371  * Args  lvalue -- Pointer to char * to fill in variable value
3372  *      patline --
3373  *
3374  * Returns  0 -- all is ok, lvalue has been filled in, file has been written
3375  *       else -- error, lvalue untouched, file not written
3376  */
3377 int
write_pattern_file(char ** lvalue,PAT_LINE_S * patline)3378 write_pattern_file(char **lvalue, PAT_LINE_S *patline)
3379 {
3380     char  *p, *tfile;
3381     int    fd = -1, err = 0;
3382     FILE  *fp_new;
3383     PAT_S *pat;
3384 
3385     dprint((7, "write_pattern_file(%s)\n",
3386 	   (patline && patline->filepath) ? patline->filepath : "?"));
3387 
3388     if(lvalue){
3389 	size_t l;
3390 
3391 	l = strlen(patline->filename) + 5;
3392 	p = (char *) fs_get((l+1) * sizeof(char));
3393 	strncpy(p, "FILE:", l+1);
3394 	p[l] = '\0';
3395 	strncat(p, patline->filename, l+1-1-strlen(p));
3396 	p[l] = '\0';
3397 	*lvalue = p;
3398     }
3399 
3400     if(patline->readonly || !patline->dirty)	/* doesn't need writing */
3401       return(err);
3402 
3403     /* Get a tempfile to write the patterns into */
3404     if(((tfile = tempfile_in_same_dir(patline->filepath, ".pt", NULL)) == NULL)
3405        || ((fd = our_open(tfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0)
3406        || ((fp_new = fdopen(fd, "w")) == NULL)){
3407 	q_status_message1(SM_ORDER | SM_DING, 3, 4,
3408 			  "Can't write in directory containing file \"%.200s\"",
3409 			  patline->filepath);
3410 	if(tfile){
3411 	    our_unlink(tfile);
3412 	    fs_give((void **)&tfile);
3413 	}
3414 
3415 	if(fd >= 0)
3416 	  close(fd);
3417 
3418 	return(-1);
3419     }
3420 
3421     dprint((9, "write_pattern_file: writing into %s\n",
3422 	   tfile ? tfile : "?"));
3423 
3424     if(fprintf(fp_new, "%s %s\n", PATTERN_MAGIC, PATTERN_FILE_VERS) == EOF)
3425       err--;
3426 
3427     for(pat = patline->first; !err && pat; pat = pat->next){
3428 	if((p = data_for_patline(pat)) != NULL){
3429 	    if(fprintf(fp_new, "%s\n", p) == EOF)
3430 	      err--;
3431 
3432 	    fs_give((void **)&p);
3433 	}
3434     }
3435 
3436     if(err || fclose(fp_new) == EOF){
3437 	if(err)
3438 	  (void)fclose(fp_new);
3439 
3440 	err--;
3441 	q_status_message2(SM_ORDER | SM_DING, 3, 4,
3442 			  "I/O error: \"%.200s\": %.200s",
3443 			  tfile, error_description(errno));
3444     }
3445 
3446     if(!err && rename_file(tfile, patline->filepath) < 0){
3447 	err--;
3448 	q_status_message3(SM_ORDER | SM_DING, 3, 4,
3449 			  _("Error renaming \"%s\" to \"%s\": %s"),
3450 			  tfile, patline->filepath, error_description(errno));
3451 	dprint((2,
3452 	       "write_pattern_file: Error renaming (%s,%s): %s\n",
3453 	       tfile ? tfile : "?",
3454 	       (patline && patline->filepath) ? patline->filepath : "?",
3455 	       error_description(errno)));
3456     }
3457 
3458     if(tfile){
3459 	our_unlink(tfile);
3460 	fs_give((void **)&tfile);
3461     }
3462 
3463     if(!err)
3464       patline->dirty = 0;
3465 
3466     return(err);
3467 }
3468 
3469 
3470 /*
3471  * Write literal pattern lines into lvalue (pinerc variable).
3472  *
3473  * Args  lvalue -- Pointer to char * to fill in variable value
3474  *      patline --
3475  *
3476  * Returns  0 -- all is ok, lvalue has been filled in, file has been written
3477  *       else -- error, lvalue untouched, file not written
3478  */
3479 int
write_pattern_lit(char ** lvalue,PAT_LINE_S * patline)3480 write_pattern_lit(char **lvalue, PAT_LINE_S *patline)
3481 {
3482     char  *p = NULL;
3483     int    err = 0;
3484     PAT_S *pat;
3485 
3486     pat = patline ? patline->first : NULL;
3487 
3488     if(pat && lvalue && (p = data_for_patline(pat)) != NULL){
3489 	size_t l;
3490 
3491 	l = strlen(p) + 4;
3492 	*lvalue = (char *) fs_get((l+1) * sizeof(char));
3493 	strncpy(*lvalue, "LIT:", l+1);
3494 	(*lvalue)[l] = '\0';
3495 	strncat(*lvalue, p, l+1-1-strlen(*lvalue));
3496 	(*lvalue)[l] = '\0';
3497     }
3498     else{
3499 	q_status_message(SM_ORDER | SM_DING, 3, 4,
3500 			 _("Unknown error saving pattern variable"));
3501 	err--;
3502     }
3503 
3504     if(p)
3505       fs_give((void **)&p);
3506 
3507     return(err);
3508 }
3509 
3510 
3511 int
write_pattern_inherit(char ** lvalue,PAT_LINE_S * patline)3512 write_pattern_inherit(char **lvalue, PAT_LINE_S *patline)
3513 {
3514     int    err = 0;
3515 
3516     if(patline && patline->type == Inherit && lvalue)
3517       *lvalue = cpystr(INHERIT);
3518     else
3519       err--;
3520 
3521     return(err);
3522 }
3523 
3524 
3525 
3526 char *
data_for_patline(PAT_S * pat)3527 data_for_patline(PAT_S *pat)
3528 {
3529     char          *p = NULL, *q, *to_pat = NULL,
3530 		  *news_pat = NULL, *from_pat = NULL,
3531 		  *sender_pat = NULL, *cc_pat = NULL, *subj_pat = NULL,
3532 		  *arb_pat = NULL, *fldr_type_pat = NULL, *fldr_pat = NULL,
3533 		  *afrom_type_pat = NULL, *abooks_pat = NULL,
3534 		  *alltext_pat = NULL, *scorei_pat = NULL, *recip_pat = NULL,
3535 		  *keyword_pat = NULL, *charset_pat = NULL,
3536 		  *bodytext_pat = NULL, *age_pat = NULL, *sentdate = NULL,
3537 		  *size_pat = NULL,
3538 		  *category_cmd = NULL, *category_pat = NULL,
3539 		  *category_lim = NULL,
3540 		  *partic_pat = NULL, *stat_new_val = NULL,
3541 		  *stat_rec_val = NULL,
3542 		  *stat_imp_val = NULL, *stat_del_val = NULL,
3543 		  *stat_ans_val = NULL, *stat_8bit_val = NULL,
3544 		  *stat_bom_val = NULL, *stat_boy_val = NULL,
3545 		  *from_act = NULL, *replyto_act = NULL, *fcc_act = NULL,
3546 		  *sig_act = NULL, *nick = NULL, *templ_act = NULL,
3547 		  *litsig_act = NULL, *cstm_act = NULL, *smtp_act = NULL,
3548                   *nntp_act = NULL, *comment = NULL,
3549 		  *repl_val = NULL, *forw_val = NULL, *comp_val = NULL,
3550 		  *incol_act = NULL, *inherit_nick = NULL,
3551 		  *score_act = NULL, *hdrtok_act = NULL,
3552 		  *sort_act = NULL, *iform_act = NULL, *start_act = NULL,
3553 		  *folder_act = NULL, *filt_ifnotdel = NULL,
3554 		  *filt_nokill = NULL, *filt_del_val = NULL,
3555 		  *filt_imp_val = NULL, *filt_ans_val = NULL,
3556 		  *filt_new_val = NULL, *filt_nonterm = NULL,
3557 		  *keyword_set = NULL, *keyword_clr = NULL;
3558     int            to_not = 0, news_not = 0, from_not = 0,
3559 		   sender_not = 0, cc_not = 0, subj_not = 0,
3560 		   partic_not = 0, recip_not = 0, alltext_not = 0, bodytext_not = 0,
3561 		   keyword_not = 0, charset_not = 0;
3562     size_t         l;
3563     ACTION_S      *action = NULL;
3564     NAMEVAL_S     *f;
3565 
3566     if(!pat)
3567       return(p);
3568 
3569     if((pat->patgrp && pat->patgrp->bogus)
3570        || (pat->action && pat->action->bogus)){
3571 	if(pat->raw)
3572 	  p = cpystr(pat->raw);
3573 
3574 	return(p);
3575     }
3576 
3577     if(pat->patgrp){
3578 	if(pat->patgrp->nick)
3579 	  if((nick = add_pat_escapes(pat->patgrp->nick)) && !*nick)
3580 	    fs_give((void **) &nick);
3581 
3582 	if(pat->patgrp->comment)
3583 	  if((comment = add_pat_escapes(pat->patgrp->comment)) && !*comment)
3584 	    fs_give((void **) &comment);
3585 
3586 	if(pat->patgrp->to){
3587 	    to_pat = pattern_to_config(pat->patgrp->to);
3588 	    to_not = pat->patgrp->to->not;
3589 	}
3590 
3591 	if(pat->patgrp->from){
3592 	    from_pat = pattern_to_config(pat->patgrp->from);
3593 	    from_not = pat->patgrp->from->not;
3594 	}
3595 
3596 	if(pat->patgrp->sender){
3597 	    sender_pat = pattern_to_config(pat->patgrp->sender);
3598 	    sender_not = pat->patgrp->sender->not;
3599 	}
3600 
3601 	if(pat->patgrp->cc){
3602 	    cc_pat = pattern_to_config(pat->patgrp->cc);
3603 	    cc_not = pat->patgrp->cc->not;
3604 	}
3605 
3606 	if(pat->patgrp->recip){
3607 	    recip_pat = pattern_to_config(pat->patgrp->recip);
3608 	    recip_not = pat->patgrp->recip->not;
3609 	}
3610 
3611 	if(pat->patgrp->partic){
3612 	    partic_pat = pattern_to_config(pat->patgrp->partic);
3613 	    partic_not = pat->patgrp->partic->not;
3614 	}
3615 
3616 	if(pat->patgrp->news){
3617 	    news_pat = pattern_to_config(pat->patgrp->news);
3618 	    news_not = pat->patgrp->news->not;
3619 	}
3620 
3621 	if(pat->patgrp->subj){
3622 	    subj_pat = pattern_to_config(pat->patgrp->subj);
3623 	    subj_not = pat->patgrp->subj->not;
3624 	}
3625 
3626 	if(pat->patgrp->alltext){
3627 	    alltext_pat = pattern_to_config(pat->patgrp->alltext);
3628 	    alltext_not = pat->patgrp->alltext->not;
3629 	}
3630 
3631 	if(pat->patgrp->bodytext){
3632 	    bodytext_pat = pattern_to_config(pat->patgrp->bodytext);
3633 	    bodytext_not = pat->patgrp->bodytext->not;
3634 	}
3635 
3636 	if(pat->patgrp->keyword){
3637 	    keyword_pat = pattern_to_config(pat->patgrp->keyword);
3638 	    keyword_not = pat->patgrp->keyword->not;
3639 	}
3640 
3641 	if(pat->patgrp->charsets){
3642 	    charset_pat = pattern_to_config(pat->patgrp->charsets);
3643 	    charset_not = pat->patgrp->charsets->not;
3644 	}
3645 
3646 	if(pat->patgrp->arbhdr){
3647 	    ARBHDR_S *a;
3648 	    char     *p1 = NULL, *p2 = NULL, *p3 = NULL, *p4 = NULL;
3649 	    int       len = 0;
3650 
3651 	    /* This is brute force dumb, but who cares? */
3652 	    for(a = pat->patgrp->arbhdr; a; a = a->next){
3653 		if(a->field && a->field[0]){
3654 		    p1 = pattern_to_string(a->p);
3655 		    p1 = p1 ? p1 : cpystr("");
3656 		    l = strlen(a->field)+strlen(p1)+1;
3657 		    p2 = (char *) fs_get((l+1) * sizeof(char));
3658 		    snprintf(p2, l+1, "%s=%s", a->field, p1);
3659 		    p3 = add_pat_escapes(p2);
3660 		    l = strlen(p3)+6;
3661 		    p4 = (char *) fs_get((l+1) * sizeof(char));
3662 		    snprintf(p4, l+1, "/%s%sARB%s",
3663 			    (a->p && a->p->not) ? "!" : "",
3664 			    a->isemptyval ? "E" : "", p3);
3665 		    len += strlen(p4);
3666 
3667 		    if(p1)
3668 		      fs_give((void **)&p1);
3669 		    if(p2)
3670 		      fs_give((void **)&p2);
3671 		    if(p3)
3672 		      fs_give((void **)&p3);
3673 		    if(p4)
3674 		      fs_give((void **)&p4);
3675 		}
3676 	    }
3677 
3678 	    p = arb_pat = (char *)fs_get((len + 1) * sizeof(char));
3679 
3680 	    for(a = pat->patgrp->arbhdr; a; a = a->next){
3681 		if(a->field && a->field[0]){
3682 		    p1 = pattern_to_string(a->p);
3683 		    p1 = p1 ? p1 : cpystr("");
3684 		    l = strlen(a->field)+strlen(p1)+1;
3685 		    p2 = (char *) fs_get((l+1) * sizeof(char));
3686 		    snprintf(p2, l+1, "%s=%s", a->field, p1);
3687 		    p3 = add_pat_escapes(p2);
3688 		    l = strlen(p3)+6;
3689 		    p4 = (char *) fs_get((l+1) * sizeof(char));
3690 		    snprintf(p4, l+1, "/%s%sARB%s",
3691 			    (a->p && a->p->not) ? "!" : "",
3692 			    a->isemptyval ? "E" : "", p3);
3693 		    sstrncpy(&p, p4, len+1-(p-arb_pat));
3694 
3695 		    if(p1)
3696 		      fs_give((void **)&p1);
3697 		    if(p2)
3698 		      fs_give((void **)&p2);
3699 		    if(p3)
3700 		      fs_give((void **)&p3);
3701 		    if(p4)
3702 		      fs_give((void **)&p4);
3703 		}
3704 	    }
3705 
3706 	    arb_pat[len] = '\0';
3707 	}
3708 
3709 	if(pat->patgrp->age_uses_sentdate)
3710 	  sentdate = cpystr("/SENTDATE=1");
3711 
3712 	if(pat->patgrp->do_score){
3713 	    p = stringform_of_intvl(pat->patgrp->score);
3714 	    if(p){
3715 		scorei_pat = add_pat_escapes(p);
3716 		fs_give((void **)&p);
3717 	    }
3718 	}
3719 
3720 	if(pat->patgrp->do_age){
3721 	    p = stringform_of_intvl(pat->patgrp->age);
3722 	    if(p){
3723 		age_pat = add_pat_escapes(p);
3724 		fs_give((void **)&p);
3725 	    }
3726 	}
3727 
3728 	if(pat->patgrp->do_size){
3729 	    p = stringform_of_intvl(pat->patgrp->size);
3730 	    if(p){
3731 		size_pat = add_pat_escapes(p);
3732 		fs_give((void **)&p);
3733 	    }
3734 	}
3735 
3736 	if(pat->patgrp->category_cmd && pat->patgrp->category_cmd[0]){
3737 	    size_t sz;
3738 	    char **l, *q;
3739 
3740 	    /* concatenate into string with commas first */
3741 	    sz = 0;
3742 	    for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++)
3743 	      sz += strlen(l[0]) + 1;
3744 
3745 	    if(sz){
3746 		char *p;
3747 		int   first_one = 1;
3748 
3749 		q = (char *)fs_get(sz);
3750 		memset(q, 0, sz);
3751 		p = q;
3752 		for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++){
3753 		    if(!first_one)
3754 		      sstrncpy(&p, ",", sz-(p-q));
3755 
3756 		    first_one = 0;
3757 		    sstrncpy(&p, l[0], sz-(p-q));
3758 		}
3759 
3760 		q[sz-1] = '\0';
3761 
3762 		category_cmd = add_pat_escapes(q);
3763 		fs_give((void **)&q);
3764 	    }
3765 	}
3766 
3767 	if(pat->patgrp->do_cat){
3768 	    p = stringform_of_intvl(pat->patgrp->cat);
3769 	    if(p){
3770 		category_pat = add_pat_escapes(p);
3771 		fs_give((void **)&p);
3772 	    }
3773 	}
3774 
3775 	if(pat->patgrp->cat_lim != -1L){
3776 	    category_lim = (char *) fs_get(20 * sizeof(char));
3777 	    snprintf(category_lim, 20, "%ld", pat->patgrp->cat_lim);
3778 	}
3779 
3780 	if((f = pat_fldr_types(pat->patgrp->fldr_type)) != NULL)
3781 	  fldr_type_pat = f->shortname;
3782 
3783 	if(pat->patgrp->folder)
3784 	  fldr_pat = pattern_to_config(pat->patgrp->folder);
3785 
3786 	if((f = inabook_fldr_types(pat->patgrp->inabook)) != NULL
3787 	   && f->value != IAB_DEFL)
3788 	  afrom_type_pat = f->shortname;
3789 
3790 	if(pat->patgrp->abooks)
3791 	  abooks_pat = pattern_to_config(pat->patgrp->abooks);
3792 
3793 	if(pat->patgrp->stat_new != PAT_STAT_EITHER &&
3794 	   (f = role_status_types(pat->patgrp->stat_new)) != NULL)
3795 	  stat_new_val = f->shortname;
3796 
3797 	if(pat->patgrp->stat_rec != PAT_STAT_EITHER &&
3798 	   (f = role_status_types(pat->patgrp->stat_rec)) != NULL)
3799 	  stat_rec_val = f->shortname;
3800 
3801 	if(pat->patgrp->stat_del != PAT_STAT_EITHER &&
3802 	   (f = role_status_types(pat->patgrp->stat_del)) != NULL)
3803 	  stat_del_val = f->shortname;
3804 
3805 	if(pat->patgrp->stat_ans != PAT_STAT_EITHER &&
3806 	   (f = role_status_types(pat->patgrp->stat_ans)) != NULL)
3807 	  stat_ans_val = f->shortname;
3808 
3809 	if(pat->patgrp->stat_imp != PAT_STAT_EITHER &&
3810 	   (f = role_status_types(pat->patgrp->stat_imp)) != NULL)
3811 	  stat_imp_val = f->shortname;
3812 
3813 	if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER &&
3814 	   (f = role_status_types(pat->patgrp->stat_8bitsubj)) != NULL)
3815 	  stat_8bit_val = f->shortname;
3816 
3817 	if(pat->patgrp->stat_bom != PAT_STAT_EITHER &&
3818 	   (f = role_status_types(pat->patgrp->stat_bom)) != NULL)
3819 	  stat_bom_val = f->shortname;
3820 
3821 	if(pat->patgrp->stat_boy != PAT_STAT_EITHER &&
3822 	   (f = role_status_types(pat->patgrp->stat_boy)) != NULL)
3823 	  stat_boy_val = f->shortname;
3824     }
3825 
3826     if(pat->action){
3827 	action = pat->action;
3828 
3829 	if(action->is_a_score){
3830 	    if(action->scoreval != 0L &&
3831 	       action->scoreval >= SCORE_MIN && action->scoreval <= SCORE_MAX){
3832 		score_act = (char *) fs_get(5 * sizeof(char));
3833 		snprintf(score_act, 5, "%ld", pat->action->scoreval);
3834 	    }
3835 
3836 	    if(action->scorevalhdrtok)
3837 	      hdrtok_act = hdrtok_to_config(action->scorevalhdrtok);
3838 	}
3839 
3840 	if(action->is_a_role){
3841 	    if(action->inherit_nick)
3842 	      inherit_nick = add_pat_escapes(action->inherit_nick);
3843 	    if(action->fcc)
3844 	      fcc_act = add_pat_escapes(action->fcc);
3845 	    if(action->litsig)
3846 	      litsig_act = add_pat_escapes(action->litsig);
3847 	    if(action->sig)
3848 	      sig_act = add_pat_escapes(action->sig);
3849 	    if(action->template)
3850 	      templ_act = add_pat_escapes(action->template);
3851 
3852 	    if(action->cstm){
3853 		size_t sz;
3854 		char **l, *q;
3855 
3856 		/* concatenate into string with commas first */
3857 		sz = 0;
3858 		for(l = action->cstm; l[0] && l[0][0]; l++)
3859 		  sz += strlen(l[0]) + 1;
3860 
3861 		if(sz){
3862 		    char *p;
3863 		    int   first_one = 1;
3864 
3865 		    q = (char *)fs_get(sz);
3866 		    memset(q, 0, sz);
3867 		    p = q;
3868 		    for(l = action->cstm; l[0] && l[0][0]; l++){
3869 			if((!struncmp(l[0], "from", 4) &&
3870 			   (l[0][4] == ':' || l[0][4] == '\0')) ||
3871 			   (!struncmp(l[0], "reply-to", 8) &&
3872 			   (l[0][8] == ':' || l[0][8] == '\0')))
3873 			  continue;
3874 
3875 			if(!first_one)
3876 			  sstrncpy(&p, ",", sz-(p-q));
3877 
3878 		        first_one = 0;
3879 			sstrncpy(&p, l[0], sz-(p-q));
3880 		    }
3881 
3882 		    q[sz-1] = '\0';
3883 
3884 		    cstm_act = add_pat_escapes(q);
3885 		    fs_give((void **)&q);
3886 		}
3887 	    }
3888 
3889 	    if(action->smtp){
3890 		size_t sz;
3891 		char **l, *q;
3892 
3893 		/* concatenate into string with commas first */
3894 		sz = 0;
3895 		for(l = action->smtp; l[0] && l[0][0]; l++)
3896 		  sz += strlen(l[0]) + 1;
3897 
3898 		if(sz){
3899 		    char *p;
3900 		    int   first_one = 1;
3901 
3902 		    q = (char *)fs_get(sz);
3903 		    memset(q, 0, sz);
3904 		    p = q;
3905 		    for(l = action->smtp; l[0] && l[0][0]; l++){
3906 			if(!first_one)
3907 			  sstrncpy(&p, ",", sz-(p-q));
3908 
3909 		        first_one = 0;
3910 			sstrncpy(&p, l[0], sz-(p-q));
3911 		    }
3912 
3913 		    q[sz-1] = '\0';
3914 
3915 		    smtp_act = add_pat_escapes(q);
3916 		    fs_give((void **)&q);
3917 		}
3918 	    }
3919 
3920 	    if(action->nntp){
3921 		size_t sz;
3922 		char **l, *q;
3923 
3924 		/* concatenate into string with commas first */
3925 		sz = 0;
3926 		for(l = action->nntp; l[0] && l[0][0]; l++)
3927 		  sz += strlen(l[0]) + 1;
3928 
3929 		if(sz){
3930 		    char *p;
3931 		    int   first_one = 1;
3932 
3933 		    q = (char *)fs_get(sz);
3934 		    memset(q, 0, sz);
3935 		    p = q;
3936 		    for(l = action->nntp; l[0] && l[0][0]; l++){
3937 			if(!first_one)
3938 			  sstrncpy(&p, ",", sz-(p-q));
3939 
3940 		        first_one = 0;
3941 			sstrncpy(&p, l[0], sz-(p-q));
3942 		    }
3943 
3944 		    q[sz-1] = '\0';
3945 
3946 		    nntp_act = add_pat_escapes(q);
3947 		    fs_give((void **)&q);
3948 		}
3949 	    }
3950 
3951 	    if((f = role_repl_types(action->repl_type)) != NULL)
3952 	      repl_val = f->shortname;
3953 
3954 	    if((f = role_forw_types(action->forw_type)) != NULL)
3955 	      forw_val = f->shortname;
3956 
3957 	    if((f = role_comp_types(action->comp_type)) != NULL)
3958 	      comp_val = f->shortname;
3959 	}
3960 
3961 	if(action->is_a_incol && action->incol){
3962 	    char *ptr, buf[256], *p1, *p2;
3963 
3964 	    ptr = buf;
3965 	    memset(buf, 0, sizeof(buf));
3966 	    sstrncpy(&ptr, "/FG=", sizeof(buf)-(ptr-buf));
3967 	    sstrncpy(&ptr, (p1=add_pat_escapes(action->incol->fg)), sizeof(buf)-(ptr-buf));
3968 	    sstrncpy(&ptr, "/BG=", sizeof(buf)-(ptr-buf));
3969 	    sstrncpy(&ptr, (p2=add_pat_escapes(action->incol->bg)), sizeof(buf)-(ptr-buf));
3970 	    buf[sizeof(buf)-1] = '\0';
3971 	    /* the colors will be doubly escaped */
3972 	    incol_act = add_pat_escapes(buf);
3973 	    if(p1)
3974 	      fs_give((void **)&p1);
3975 
3976 	    if(p2)
3977 	      fs_give((void **)&p2);
3978 	}
3979 
3980 	if(action->is_a_other){
3981 	    char buf[256];
3982 
3983 	    if(action->sort_is_set){
3984 		snprintf(buf, sizeof(buf), "%.50s%.50s",
3985 			sort_name(action->sortorder),
3986 			action->revsort ? "/Reverse" : "");
3987 		sort_act = add_pat_escapes(buf);
3988 	    }
3989 
3990 	    if(action->index_format)
3991 	      iform_act = add_pat_escapes(action->index_format);
3992 
3993 	    if(action->startup_rule != IS_NOTSET &&
3994 	       (f = startup_rules(action->startup_rule)) != NULL)
3995 	      start_act = S_OR_L(f);
3996 	}
3997 
3998 	if(action->is_a_role && action->from){
3999 	    char *bufp;
4000 	    size_t len;
4001 
4002 	    len = est_size(action->from);
4003 	    bufp = (char *) fs_get(len * sizeof(char));
4004 	    p = addr_string_mult(action->from, bufp, len);
4005 	    if(p){
4006 		from_act = add_pat_escapes(p);
4007 		fs_give((void **)&p);
4008 	    }
4009 	}
4010 
4011 	if(action->is_a_role && action->replyto){
4012 	    char *bufp;
4013 	    size_t len;
4014 
4015 	    len = est_size(action->replyto);
4016 	    bufp = (char *) fs_get(len * sizeof(char));
4017 	    p = addr_string_mult(action->replyto, bufp, len);
4018 	    if(p){
4019 		replyto_act = add_pat_escapes(p);
4020 		fs_give((void **)&p);
4021 	    }
4022 	}
4023 
4024 	if(action->is_a_filter){
4025 	    if(action->folder){
4026 		if((folder_act = pattern_to_config(action->folder)) != NULL){
4027 		    if(action->move_only_if_not_deleted)
4028 		      filt_ifnotdel = cpystr("/NOTDEL=1");
4029 		}
4030 	    }
4031 
4032 	    if(action->keyword_set)
4033 	      keyword_set = pattern_to_config(action->keyword_set);
4034 
4035 	    if(action->keyword_clr)
4036 	      keyword_clr = pattern_to_config(action->keyword_clr);
4037 
4038 	    if(!action->kill)
4039 	      filt_nokill = cpystr("/NOKILL=1");
4040 
4041 	    if(action->non_terminating)
4042 	      filt_nonterm = cpystr("/NONTERM=1");
4043 
4044 	    if(action->state_setting_bits){
4045 		char buf[256];
4046 		int  dval, nval, ival, aval;
4047 
4048 		buf[0] = '\0';
4049 		p = buf;
4050 
4051 		convert_statebits_to_vals(action->state_setting_bits,
4052 					  &dval, &aval, &ival, &nval);
4053 		if(dval != ACT_STAT_LEAVE &&
4054 		   (f = msg_state_types(dval)) != NULL)
4055 		  filt_del_val = f->shortname;
4056 
4057 		if(aval != ACT_STAT_LEAVE &&
4058 		   (f = msg_state_types(aval)) != NULL)
4059 		  filt_ans_val = f->shortname;
4060 
4061 		if(ival != ACT_STAT_LEAVE &&
4062 		   (f = msg_state_types(ival)) != NULL)
4063 		  filt_imp_val = f->shortname;
4064 
4065 		if(nval != ACT_STAT_LEAVE &&
4066 		   (f = msg_state_types(nval)) != NULL)
4067 		  filt_new_val = f->shortname;
4068 	    }
4069 	}
4070     }
4071 
4072     l = strlen(nick ? nick : "Alternate Role") +
4073 	strlen(comment ? comment : "") +
4074 	strlen(to_pat ? to_pat : "") +
4075 	strlen(from_pat ? from_pat : "") +
4076 	strlen(sender_pat ? sender_pat : "") +
4077 	strlen(cc_pat ? cc_pat : "") +
4078 	strlen(recip_pat ? recip_pat : "") +
4079 	strlen(partic_pat ? partic_pat : "") +
4080 	strlen(news_pat ? news_pat : "") +
4081 	strlen(subj_pat ? subj_pat : "") +
4082 	strlen(alltext_pat ? alltext_pat : "") +
4083 	strlen(bodytext_pat ? bodytext_pat : "") +
4084 	strlen(arb_pat ? arb_pat : "") +
4085 	strlen(scorei_pat ? scorei_pat : "") +
4086 	strlen(keyword_pat ? keyword_pat : "") +
4087 	strlen(charset_pat ? charset_pat : "") +
4088 	strlen(age_pat ? age_pat : "") +
4089 	strlen(size_pat ? size_pat : "") +
4090 	strlen(category_cmd ? category_cmd : "") +
4091 	strlen(category_pat ? category_pat : "") +
4092 	strlen(category_lim ? category_lim : "") +
4093 	strlen(fldr_pat ? fldr_pat : "") +
4094 	strlen(abooks_pat ? abooks_pat : "") +
4095 	strlen(sentdate ? sentdate : "") +
4096 	strlen(inherit_nick ? inherit_nick : "") +
4097 	strlen(score_act ? score_act : "") +
4098 	strlen(hdrtok_act ? hdrtok_act : "") +
4099 	strlen(from_act ? from_act : "") +
4100 	strlen(replyto_act ? replyto_act : "") +
4101 	strlen(fcc_act ? fcc_act : "") +
4102 	strlen(litsig_act ? litsig_act : "") +
4103 	strlen(cstm_act ? cstm_act : "") +
4104 	strlen(smtp_act ? smtp_act : "") +
4105 	strlen(nntp_act ? nntp_act : "") +
4106 	strlen(sig_act ? sig_act : "") +
4107 	strlen(incol_act ? incol_act : "") +
4108 	strlen(sort_act ? sort_act : "") +
4109 	strlen(iform_act ? iform_act : "") +
4110 	strlen(start_act ? start_act : "") +
4111 	strlen(filt_ifnotdel ? filt_ifnotdel : "") +
4112 	strlen(filt_nokill ? filt_nokill : "") +
4113 	strlen(filt_nonterm ? filt_nonterm : "") +
4114 	(folder_act ? (strlen(folder_act) + 8) : 0) +
4115 	strlen(keyword_set ? keyword_set : "") +
4116 	strlen(keyword_clr ? keyword_clr : "") +
4117 	strlen(templ_act ? templ_act : "") + 540;
4118     /*
4119      * The +540 above is larger than needed but not everything is accounted
4120      * for with the strlens.
4121      */
4122     p = (char *) fs_get(l * sizeof(char));
4123 
4124     q = p;
4125     sstrncpy(&q, "pattern=\"/NICK=", l-(q-p));
4126 
4127     if(nick){
4128 	sstrncpy(&q, nick, l-(q-p));
4129 	fs_give((void **) &nick);
4130     }
4131     else
4132       sstrncpy(&q, "Alternate Role", l-(q-p));
4133 
4134     if(comment){
4135 	sstrncpy(&q, "/", l-(q-p));
4136 	sstrncpy(&q, "COMM=", l-(q-p));
4137 	sstrncpy(&q, comment, l-(q-p));
4138 	fs_give((void **) &comment);
4139     }
4140 
4141     if(to_pat){
4142 	sstrncpy(&q, "/", l-(q-p));
4143 	if(to_not)
4144 	  sstrncpy(&q, "!", l-(q-p));
4145 
4146 	sstrncpy(&q, "TO=", l-(q-p));
4147 	sstrncpy(&q, to_pat, l-(q-p));
4148 	fs_give((void **) &to_pat);
4149     }
4150 
4151     if(from_pat){
4152 	sstrncpy(&q, "/", l-(q-p));
4153 	if(from_not)
4154 	  sstrncpy(&q, "!", l-(q-p));
4155 
4156 	sstrncpy(&q, "FROM=", l-(q-p));
4157 	sstrncpy(&q, from_pat, l-(q-p));
4158 	fs_give((void **) &from_pat);
4159     }
4160 
4161     if(sender_pat){
4162 	sstrncpy(&q, "/", l-(q-p));
4163 	if(sender_not)
4164 	  sstrncpy(&q, "!", l-(q-p));
4165 
4166 	sstrncpy(&q, "SENDER=", l-(q-p));
4167 	sstrncpy(&q, sender_pat, l-(q-p));
4168 	fs_give((void **) &sender_pat);
4169     }
4170 
4171     if(cc_pat){
4172 	sstrncpy(&q,"/", l-(q-p));
4173 	if(cc_not)
4174 	  sstrncpy(&q, "!", l-(q-p));
4175 
4176 	sstrncpy(&q,"CC=", l-(q-p));
4177 	sstrncpy(&q, cc_pat, l-(q-p));
4178 	fs_give((void **) &cc_pat);
4179     }
4180 
4181     if(recip_pat){
4182 	sstrncpy(&q, "/", l-(q-p));
4183 	if(recip_not)
4184 	  sstrncpy(&q, "!", l-(q-p));
4185 
4186 	sstrncpy(&q, "RECIP=", l-(q-p));
4187 	sstrncpy(&q, recip_pat, l-(q-p));
4188 	fs_give((void **) &recip_pat);
4189     }
4190 
4191     if(partic_pat){
4192 	sstrncpy(&q, "/", l-(q-p));
4193 	if(partic_not)
4194 	  sstrncpy(&q, "!", l-(q-p));
4195 
4196 	sstrncpy(&q, "PARTIC=", l-(q-p));
4197 	sstrncpy(&q, partic_pat, l-(q-p));
4198 	fs_give((void **) &partic_pat);
4199     }
4200 
4201     if(news_pat){
4202 	sstrncpy(&q, "/", l-(q-p));
4203 	if(news_not)
4204 	  sstrncpy(&q, "!", l-(q-p));
4205 
4206 	sstrncpy(&q, "NEWS=", l-(q-p));
4207 	sstrncpy(&q, news_pat, l-(q-p));
4208 	fs_give((void **) &news_pat);
4209     }
4210 
4211     if(subj_pat){
4212 	sstrncpy(&q, "/", l-(q-p));
4213 	if(subj_not)
4214 	  sstrncpy(&q, "!", l-(q-p));
4215 
4216 	sstrncpy(&q, "SUBJ=", l-(q-p));
4217 	sstrncpy(&q, subj_pat, l-(q-p));
4218 	fs_give((void **)&subj_pat);
4219     }
4220 
4221     if(alltext_pat){
4222 	sstrncpy(&q, "/", l-(q-p));
4223 	if(alltext_not)
4224 	  sstrncpy(&q, "!", l-(q-p));
4225 
4226 	sstrncpy(&q, "ALL=", l-(q-p));
4227 	sstrncpy(&q, alltext_pat, l-(q-p));
4228 	fs_give((void **) &alltext_pat);
4229     }
4230 
4231     if(bodytext_pat){
4232 	sstrncpy(&q, "/", l-(q-p));
4233 	if(bodytext_not)
4234 	  sstrncpy(&q, "!", l-(q-p));
4235 
4236 	sstrncpy(&q, "BODY=", l-(q-p));
4237 	sstrncpy(&q, bodytext_pat, l-(q-p));
4238 	fs_give((void **) &bodytext_pat);
4239     }
4240 
4241     if(keyword_pat){
4242 	sstrncpy(&q, "/", l-(q-p));
4243 	if(keyword_not)
4244 	  sstrncpy(&q, "!", l-(q-p));
4245 
4246 	sstrncpy(&q, "KEY=", l-(q-p));
4247 	sstrncpy(&q, keyword_pat, l-(q-p));
4248 	fs_give((void **) &keyword_pat);
4249     }
4250 
4251     if(charset_pat){
4252 	sstrncpy(&q, "/", l-(q-p));
4253 	if(charset_not)
4254 	  sstrncpy(&q, "!", l-(q-p));
4255 
4256 	sstrncpy(&q, "CHAR=", l-(q-p));
4257 	sstrncpy(&q, charset_pat, l-(q-p));
4258 	fs_give((void **) &charset_pat);
4259     }
4260 
4261     if(arb_pat){
4262 	sstrncpy(&q, arb_pat, l-(q-p));
4263 	fs_give((void **)&arb_pat);
4264     }
4265 
4266     if(scorei_pat){
4267 	sstrncpy(&q, "/SCOREI=", l-(q-p));
4268 	sstrncpy(&q, scorei_pat, l-(q-p));
4269 	fs_give((void **) &scorei_pat);
4270     }
4271 
4272     if(age_pat){
4273 	sstrncpy(&q, "/AGE=", l-(q-p));
4274 	sstrncpy(&q, age_pat, l-(q-p));
4275 	fs_give((void **) &age_pat);
4276     }
4277 
4278     if(size_pat){
4279 	sstrncpy(&q, "/SIZE=", l-(q-p));
4280 	sstrncpy(&q, size_pat, l-(q-p));
4281 	fs_give((void **) &size_pat);
4282     }
4283 
4284     if(category_cmd){
4285 	sstrncpy(&q, "/CATCMD=", l-(q-p));
4286 	sstrncpy(&q, category_cmd, l-(q-p));
4287 	fs_give((void **) &category_cmd);
4288     }
4289 
4290     if(category_pat){
4291 	sstrncpy(&q, "/CATVAL=", l-(q-p));
4292 	sstrncpy(&q, category_pat, l-(q-p));
4293 	fs_give((void **) &category_pat);
4294     }
4295 
4296     if(category_lim){
4297 	sstrncpy(&q, "/CATLIM=", l-(q-p));
4298 	sstrncpy(&q, category_lim, l-(q-p));
4299 	fs_give((void **) &category_lim);
4300     }
4301 
4302     if(sentdate){
4303 	sstrncpy(&q, sentdate, l-(q-p));
4304 	fs_give((void **) &sentdate);
4305     }
4306 
4307     if(fldr_type_pat){
4308 	sstrncpy(&q, "/FLDTYPE=", l-(q-p));
4309 	sstrncpy(&q, fldr_type_pat, l-(q-p));
4310     }
4311 
4312     if(fldr_pat){
4313 	sstrncpy(&q, "/FOLDER=", l-(q-p));
4314 	sstrncpy(&q, fldr_pat, l-(q-p));
4315 	fs_give((void **) &fldr_pat);
4316     }
4317 
4318     if(afrom_type_pat){
4319 	sstrncpy(&q, "/AFROM=", l-(q-p));
4320 	sstrncpy(&q, afrom_type_pat, l-(q-p));
4321 
4322 	/*
4323 	 * Add address types. If it is From or Reply-to
4324 	 * leave this out so it will still work with pine.
4325 	 */
4326 	if((pat->patgrp->inabook & IAB_FROM
4327 	    && pat->patgrp->inabook & IAB_REPLYTO
4328 	    && !(pat->patgrp->inabook & IAB_SENDER)
4329 	    && !(pat->patgrp->inabook & IAB_TO)
4330 	    && !(pat->patgrp->inabook & IAB_CC))
4331 	   ||
4332 	   (!(pat->patgrp->inabook & IAB_FROM)
4333 	    && !(pat->patgrp->inabook & IAB_REPLYTO)
4334 	    && !(pat->patgrp->inabook & IAB_SENDER)
4335 	    && !(pat->patgrp->inabook & IAB_TO)
4336 	    && !(pat->patgrp->inabook & IAB_CC))){
4337 	    ; /* leave it out */
4338 	}
4339 	else{
4340 	    sstrncpy(&q, "/AFROMA=", l-(q-p));
4341 	    if(pat->patgrp->inabook & IAB_FROM)
4342 	      sstrncpy(&q, "F", l-(q-p));
4343 
4344 	    if(pat->patgrp->inabook & IAB_REPLYTO)
4345 	      sstrncpy(&q, "R", l-(q-p));
4346 
4347 	    if(pat->patgrp->inabook & IAB_SENDER)
4348 	      sstrncpy(&q, "S", l-(q-p));
4349 
4350 	    if(pat->patgrp->inabook & IAB_TO)
4351 	      sstrncpy(&q, "T", l-(q-p));
4352 
4353 	    if(pat->patgrp->inabook & IAB_CC)
4354 	      sstrncpy(&q, "C", l-(q-p));
4355 	}
4356     }
4357 
4358     if(abooks_pat){
4359 	sstrncpy(&q, "/ABOOKS=", l-(q-p));
4360 	sstrncpy(&q, abooks_pat, l-(q-p));
4361 	fs_give((void **) &abooks_pat);
4362     }
4363 
4364     if(stat_new_val){
4365 	sstrncpy(&q, "/STATN=", l-(q-p));
4366 	sstrncpy(&q, stat_new_val, l-(q-p));
4367     }
4368 
4369     if(stat_rec_val){
4370 	sstrncpy(&q, "/STATR=", l-(q-p));
4371 	sstrncpy(&q, stat_rec_val, l-(q-p));
4372     }
4373 
4374     if(stat_del_val){
4375 	sstrncpy(&q, "/STATD=", l-(q-p));
4376 	sstrncpy(&q, stat_del_val, l-(q-p));
4377     }
4378 
4379     if(stat_imp_val){
4380 	sstrncpy(&q, "/STATI=", l-(q-p));
4381 	sstrncpy(&q, stat_imp_val, l-(q-p));
4382     }
4383 
4384     if(stat_ans_val){
4385 	sstrncpy(&q, "/STATA=", l-(q-p));
4386 	sstrncpy(&q, stat_ans_val, l-(q-p));
4387     }
4388 
4389     if(stat_8bit_val){
4390 	sstrncpy(&q, "/8BITS=", l-(q-p));
4391 	sstrncpy(&q, stat_8bit_val, l-(q-p));
4392     }
4393 
4394     if(stat_bom_val){
4395 	sstrncpy(&q, "/BOM=", l-(q-p));
4396 	sstrncpy(&q, stat_bom_val, l-(q-p));
4397     }
4398 
4399     if(stat_boy_val){
4400 	sstrncpy(&q, "/BOY=", l-(q-p));
4401 	sstrncpy(&q, stat_boy_val, l-(q-p));
4402     }
4403 
4404     sstrncpy(&q, "\" action=\"", l-(q-p));
4405 
4406     if(inherit_nick && *inherit_nick){
4407 	sstrncpy(&q, "/INICK=", l-(q-p));
4408 	sstrncpy(&q, inherit_nick, l-(q-p));
4409 	fs_give((void **)&inherit_nick);
4410     }
4411 
4412     if(action){
4413 	if(action->is_a_role)
4414 	  sstrncpy(&q, "/ROLE=1", l-(q-p));
4415 
4416 	if(action->is_a_incol)
4417 	  sstrncpy(&q, "/ISINCOL=1", l-(q-p));
4418 
4419 	if(action->is_a_srch)
4420 	  sstrncpy(&q, "/ISSRCH=1", l-(q-p));
4421 
4422 	if(action->is_a_score)
4423 	  sstrncpy(&q, "/ISSCORE=1", l-(q-p));
4424 
4425 	if(action->is_a_filter){
4426 	    /*
4427 	     * Older pine will interpret a filter that has no folder
4428 	     * as a Delete, even if we set it up here to be a Just Set
4429 	     * State filter. Disable the filter for older versions in that
4430 	     * case. If kill is set then Delete is what is supposed to
4431 	     * happen, so that's ok. If folder is set then Move is what is
4432 	     * supposed to happen, so ok.
4433 	     */
4434 	    if(!action->kill && !action->folder)
4435 	      sstrncpy(&q, "/FILTER=2", l-(q-p));
4436 	    else
4437 	      sstrncpy(&q, "/FILTER=1", l-(q-p));
4438 	}
4439 
4440 	if(action->is_a_other)
4441 	  sstrncpy(&q, "/OTHER=1", l-(q-p));
4442     }
4443 
4444     if(score_act){
4445 	sstrncpy(&q, "/SCORE=", l-(q-p));
4446 	sstrncpy(&q, score_act, l-(q-p));
4447 	fs_give((void **)&score_act);
4448     }
4449 
4450     if(hdrtok_act){
4451 	sstrncpy(&q, "/SCOREHDRTOK=", l-(q-p));
4452 	sstrncpy(&q, hdrtok_act, l-(q-p));
4453 	fs_give((void **)&hdrtok_act);
4454     }
4455 
4456     if(from_act){
4457 	sstrncpy(&q, "/FROM=", l-(q-p));
4458 	sstrncpy(&q, from_act, l-(q-p));
4459       fs_give((void **) &from_act);
4460     }
4461 
4462     if(replyto_act){
4463 	sstrncpy(&q, "/REPL=", l-(q-p));
4464 	sstrncpy(&q, replyto_act, l-(q-p));
4465 	fs_give((void **)&replyto_act);
4466     }
4467 
4468     if(fcc_act){
4469 	sstrncpy(&q, "/FCC=", l-(q-p));
4470 	sstrncpy(&q, fcc_act, l-(q-p));
4471 	fs_give((void **)&fcc_act);
4472     }
4473 
4474     if(litsig_act){
4475 	sstrncpy(&q, "/LSIG=", l-(q-p));
4476 	sstrncpy(&q, litsig_act, l-(q-p));
4477 	fs_give((void **)&litsig_act);
4478     }
4479 
4480     if(sig_act){
4481 	sstrncpy(&q, "/SIG=", l-(q-p));
4482 	sstrncpy(&q, sig_act, l-(q-p));
4483 	fs_give((void **)&sig_act);
4484     }
4485 
4486     if(templ_act){
4487 	sstrncpy(&q, "/TEMPLATE=", l-(q-p));
4488 	sstrncpy(&q, templ_act, l-(q-p));
4489 	fs_give((void **)&templ_act);
4490     }
4491 
4492     if(cstm_act){
4493 	sstrncpy(&q, "/CSTM=", l-(q-p));
4494 	sstrncpy(&q, cstm_act, l-(q-p));
4495 	fs_give((void **)&cstm_act);
4496     }
4497 
4498     if(smtp_act){
4499 	sstrncpy(&q, "/SMTP=", l-(q-p));
4500 	sstrncpy(&q, smtp_act, l-(q-p));
4501 	fs_give((void **)&smtp_act);
4502     }
4503 
4504     if(nntp_act){
4505 	sstrncpy(&q, "/NNTP=", l-(q-p));
4506 	sstrncpy(&q, nntp_act, l-(q-p));
4507 	fs_give((void **)&nntp_act);
4508     }
4509 
4510     if(repl_val){
4511 	sstrncpy(&q, "/RTYPE=", l-(q-p));
4512 	sstrncpy(&q, repl_val, l-(q-p));
4513     }
4514 
4515     if(forw_val){
4516 	sstrncpy(&q, "/FTYPE=", l-(q-p));
4517 	sstrncpy(&q, forw_val, l-(q-p));
4518     }
4519 
4520     if(comp_val){
4521 	sstrncpy(&q, "/CTYPE=", l-(q-p));
4522 	sstrncpy(&q, comp_val, l-(q-p));
4523     }
4524 
4525     if(incol_act){
4526 	sstrncpy(&q, "/INCOL=", l-(q-p));
4527 	sstrncpy(&q, incol_act, l-(q-p));
4528 	fs_give((void **)&incol_act);
4529     }
4530 
4531     if(sort_act){
4532 	sstrncpy(&q, "/SORT=", l-(q-p));
4533 	sstrncpy(&q, sort_act, l-(q-p));
4534 	fs_give((void **)&sort_act);
4535     }
4536 
4537     if(iform_act){
4538 	sstrncpy(&q, "/IFORM=", l-(q-p));
4539 	sstrncpy(&q, iform_act, l-(q-p));
4540 	fs_give((void **)&iform_act);
4541     }
4542 
4543     if(start_act){
4544 	sstrncpy(&q, "/START=", l-(q-p));
4545 	sstrncpy(&q, start_act, l-(q-p));
4546     }
4547 
4548     if(folder_act){
4549 	sstrncpy(&q, "/FOLDER=", l-(q-p));
4550 	sstrncpy(&q, folder_act, l-(q-p));
4551 	fs_give((void **) &folder_act);
4552     }
4553 
4554     if(filt_ifnotdel){
4555 	sstrncpy(&q, filt_ifnotdel, l-(q-p));
4556 	fs_give((void **) &filt_ifnotdel);
4557     }
4558 
4559     if(filt_nonterm){
4560 	sstrncpy(&q, filt_nonterm, l-(q-p));
4561 	fs_give((void **) &filt_nonterm);
4562     }
4563 
4564     if(filt_nokill){
4565 	sstrncpy(&q, filt_nokill, l-(q-p));
4566 	fs_give((void **) &filt_nokill);
4567     }
4568 
4569     if(filt_new_val){
4570 	sstrncpy(&q, "/STATN=", l-(q-p));
4571 	sstrncpy(&q, filt_new_val, l-(q-p));
4572     }
4573 
4574     if(filt_del_val){
4575 	sstrncpy(&q, "/STATD=", l-(q-p));
4576 	sstrncpy(&q, filt_del_val, l-(q-p));
4577     }
4578 
4579     if(filt_imp_val){
4580 	sstrncpy(&q, "/STATI=", l-(q-p));
4581 	sstrncpy(&q, filt_imp_val, l-(q-p));
4582     }
4583 
4584     if(filt_ans_val){
4585 	sstrncpy(&q, "/STATA=", l-(q-p));
4586 	sstrncpy(&q, filt_ans_val, l-(q-p));
4587     }
4588 
4589     if(keyword_set){
4590 	sstrncpy(&q, "/KEYSET=", l-(q-p));
4591 	sstrncpy(&q, keyword_set, l-(q-p));
4592 	fs_give((void **) &keyword_set);
4593     }
4594 
4595     if(keyword_clr){
4596 	sstrncpy(&q, "/KEYCLR=", l-(q-p));
4597 	sstrncpy(&q, keyword_clr, l-(q-p));
4598 	fs_give((void **) &keyword_clr);
4599     }
4600 
4601     if(q-p < l)
4602       *q++ = '\"';
4603 
4604     if(q-p < l)
4605       *q   = '\0';
4606 
4607     p[l-1] = '\0';
4608 
4609     return(p);
4610 }
4611 
4612 
4613 void
convert_statebits_to_vals(long int bits,int * dval,int * aval,int * ival,int * nval)4614 convert_statebits_to_vals(long int bits, int *dval, int *aval, int *ival, int *nval)
4615 {
4616     if(dval)
4617       *dval = ACT_STAT_LEAVE;
4618     if(aval)
4619       *aval = ACT_STAT_LEAVE;
4620     if(ival)
4621       *ival = ACT_STAT_LEAVE;
4622     if(nval)
4623       *nval = ACT_STAT_LEAVE;
4624 
4625     if(ival){
4626 	if(bits & F_FLAG)
4627 	  *ival = ACT_STAT_SET;
4628 	else if(bits & F_UNFLAG)
4629 	  *ival = ACT_STAT_CLEAR;
4630     }
4631 
4632     if(aval){
4633 	if(bits & F_ANS)
4634 	  *aval = ACT_STAT_SET;
4635 	else if(bits & F_UNANS)
4636 	  *aval = ACT_STAT_CLEAR;
4637     }
4638 
4639     if(dval){
4640 	if(bits & F_DEL)
4641 	  *dval = ACT_STAT_SET;
4642 	else if(bits & F_UNDEL)
4643 	  *dval = ACT_STAT_CLEAR;
4644     }
4645 
4646     if(nval){
4647 	if(bits & F_UNSEEN)
4648 	  *nval = ACT_STAT_SET;
4649 	else if(bits & F_SEEN)
4650 	  *nval = ACT_STAT_CLEAR;
4651     }
4652 }
4653 
4654 
4655 /*
4656  * The "searched" bit will be set for each message which matches.
4657  *
4658  * Args:   patgrp -- Pattern to search with
4659  *         stream --
4660  *      searchset -- Restrict search to this set
4661  *        section -- Searching a section of the message, not the whole thing
4662  *      get_score -- Function to return the score for a message
4663  *          flags -- Most of these are flags to mail_search_full. However, we
4664  *                   overload the flags namespace and pass some flags of our
4665  *                   own in here that we pick off before calling mail_search.
4666  *                   Danger, danger, don't overlap with flag values defined
4667  *                   for c-client (that we want to use). Flags that we will
4668  *                   use here are:
4669  *                     MP_IN_CCLIENT_CB
4670  *                       If this is set we are in a callback from c-client
4671  *                       because some imap data arrived. We don't want to
4672  *                       call c-client again because it isn't re-entrant safe.
4673  *                       This is only a problem if we need to get the text of
4674  *                       a message to do the search, the envelope is cached
4675  *                       already.
4676  *                     MP_NOT
4677  *                       We want a ! of the patgrp in the search.
4678  *                   We also throw in SE_FREE for free, since we create
4679  *                   the search program here.
4680  *
4681  * Returns:   1 if any message in the searchset matches this pattern
4682  *            0 if no matches
4683  *           -1 if couldn't perform search because of no_fetch restriction
4684  */
4685 int
match_pattern(PATGRP_S * patgrp,MAILSTREAM * stream,SEARCHSET * searchset,char * section,long int (* get_score)(MAILSTREAM *,long int),long int flags)4686 match_pattern(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset,
4687 	      char *section, long int (*get_score)(MAILSTREAM *, long int),
4688 	      long int flags)
4689 {
4690     SEARCHPGM    *pgm;
4691     SEARCHSET    *s;
4692     MESSAGECACHE *mc;
4693     long          i, msgno = 0L;
4694     int           in_client_callback = 0, not = 0;
4695 
4696     dprint((7, "match_pattern\n"));
4697 
4698     /*
4699      * Is the current folder the right type and possibly the right specific
4700      * folder for a match?
4701      */
4702     if(!(patgrp && !patgrp->bogus && match_pattern_folder(patgrp, stream)))
4703       return(0);
4704 
4705     /*
4706      * NULL searchset means that there is no message to compare against.
4707      * This is a match if the folder type matches above (that gets
4708      * us here), and there are no patterns to match against.
4709      *
4710      * It is not totally clear what should be done in the case of an empty
4711      * search set.  If there is search criteria, and someone does something
4712      * that is not specific to any messages (composing from scratch,
4713      * forwarding an attachment), then we can't be sure what a user would
4714      * expect.  The original way was to just use the role, which we'll
4715      * preserve here.
4716      */
4717     if(!searchset)
4718       return(1);
4719 
4720     /*
4721      * change by sderr : match_pattern_folder will sometimes
4722      * accept NULL streams, but if we are not in a folder-type-only
4723      * match test, we don't
4724      */
4725     if(!stream)
4726       return(0);
4727 
4728     if(flags & MP_IN_CCLIENT_CB){
4729 	in_client_callback++;
4730 	flags &= ~MP_IN_CCLIENT_CB;
4731     }
4732 
4733     if(flags & MP_NOT){
4734 	not++;
4735 	flags &= ~MP_NOT;
4736     }
4737 
4738     flags |= SE_FREE;
4739 
4740     if(patgrp->stat_bom != PAT_STAT_EITHER){
4741 	if(patgrp->stat_bom == PAT_STAT_YES){
4742 	    if(!ps_global->beginning_of_month){
4743 		return(0);
4744 	    }
4745 	}
4746 	else if(patgrp->stat_bom == PAT_STAT_NO){
4747 	    if(ps_global->beginning_of_month){
4748 		return(0);
4749 	    }
4750 	}
4751     }
4752 
4753     if(patgrp->stat_boy != PAT_STAT_EITHER){
4754 	if(patgrp->stat_boy == PAT_STAT_YES){
4755 	    if(!ps_global->beginning_of_year){
4756 		return(0);
4757 	    }
4758 	}
4759 	else if(patgrp->stat_boy == PAT_STAT_NO){
4760 	    if(ps_global->beginning_of_year){
4761 		return(0);
4762 	    }
4763 	}
4764     }
4765 
4766     if(in_client_callback && is_imap_stream(stream)
4767        && (patgrp->alltext || patgrp->bodytext
4768 	|| (patgrp->inabook != IAB_EITHER
4769 	    && any_addressbook_in_remote_stream(stream))))
4770       return(-1);
4771 
4772     pgm = match_pattern_srchpgm(patgrp, stream, searchset);
4773     if(not && !(is_imap_stream(stream) && !modern_imap_stream(stream))){
4774 	SEARCHPGM *srchpgm;
4775 
4776 	srchpgm = pgm;
4777 	pgm = mail_newsearchpgm();
4778 	pgm->not = mail_newsearchpgmlist();
4779 	pgm->not->pgm = srchpgm;
4780     }
4781 
4782     if((patgrp->alltext || patgrp->bodytext)
4783        && (!is_imap_stream(stream) || modern_imap_stream(stream)))
4784       /*
4785        * Cache isn't going to work. Search on server.
4786        * Except that is likely to not work on an old imap server because
4787        * the OR criteria won't work and we are likely to have some ORs.
4788        * So turn off the NOSERVER flag (and search on server if remote)
4789        * unless the server is an old server. It doesn't matter if we
4790        * turn if off if it's not an imap stream, but we do it anyway.
4791        */
4792       flags &= ~SE_NOSERVER;
4793 
4794     if(section){
4795 	/*
4796 	 * Mail_search_full only searches the top-level msg. We want to
4797 	 * search an attached msg instead. First do the stuff
4798 	 * that mail_search_full would have done before calling
4799 	 * mail_search_msg, then call mail_search_msg with a section number.
4800 	 * Mail_search_msg does take a section number even though
4801 	 * mail_search_full doesn't.
4802 	 */
4803 
4804 	/*
4805 	 * We'll only ever set section if the searchset is a single message.
4806 	 */
4807 	if(pgm->msgno->next == NULL && pgm->msgno->first == pgm->msgno->last)
4808 	  msgno = pgm->msgno->first;
4809 
4810 	for(i = 1L; i <= stream->nmsgs; i++)
4811 	  if((mc = mail_elt(stream, i)) != NULL)
4812 	    mc->searched = NIL;
4813 
4814 	if(mail_search_msg(stream,msgno,section,pgm)
4815 	   && msgno > 0L && msgno <= stream->nmsgs
4816 	   && (mc = mail_elt(stream, msgno)))
4817 	  mc->searched = T;
4818 
4819 	if(flags & SE_FREE)
4820 	  mail_free_searchpgm(&pgm);
4821     }
4822     else{
4823 	/*
4824 	 * Here we could be checking on the return value to see if
4825 	 * the search was "successful" or not.  It may be the case
4826 	 * that we'd want to stop trying filtering if we got some
4827 	 * sort of error, but for now we would just continue on
4828 	 * to the next filter.
4829 	 */
4830 	pine_mail_search_full(stream, "UTF-8", pgm, flags);
4831     }
4832 
4833     /* we searched without the not, reverse it */
4834     if(not && is_imap_stream(stream) && !modern_imap_stream(stream)){
4835 	for(msgno = 1L; msgno < mn_get_total(sp_msgmap(stream)); msgno++)
4836 	  if(stream && msgno && msgno <= stream->nmsgs){
4837 	     if((mc=mail_elt(stream,msgno)) != NULL){
4838 	        if(mc->searched)
4839 		   mc->searched = NIL;
4840 	        else
4841 		   mc->searched = T;
4842 	     }
4843 	  }
4844     }
4845 
4846     /* check scores */
4847     if(get_score && scores_are_used(SCOREUSE_GET) && patgrp->do_score){
4848 	char      *savebits;
4849 	SEARCHSET *ss;
4850 
4851 	/*
4852 	 * Get_score may call build_header_line recursively (we may
4853 	 * be in build_header_line now) so we have to preserve and
4854 	 * restore the sequence bits.
4855 	 */
4856 	savebits = (char *)fs_get((stream->nmsgs+1) * sizeof(char));
4857 
4858 	for(i = 1L; i <= stream->nmsgs; i++){
4859 	    if((mc = mail_elt(stream, i)) != NULL){
4860 		savebits[i] = mc->sequence;
4861 		mc->sequence = 0;
4862 	    }
4863 	}
4864 
4865 	/*
4866 	 * Build a searchset which will get all the scores that we
4867 	 * need but not more.
4868 	 */
4869 	for(s = searchset; s; s = s->next)
4870 	  for(msgno = s->first; msgno <= s->last; msgno++)
4871 	    if(msgno > 0L && msgno <= stream->nmsgs
4872 	       && (mc = mail_elt(stream, msgno)) && mc->searched
4873 	       && get_msg_score(stream, msgno) == SCORE_UNDEF)
4874 	      mc->sequence = 1;
4875 
4876 	if((ss = build_searchset(stream)) != NULL){
4877 	    (void)calculate_some_scores(stream, ss, in_client_callback);
4878 	    mail_free_searchset(&ss);
4879 	}
4880 
4881 	/*
4882 	 * Now check the scores versus the score intervals to see if
4883 	 * any of the messages which have matched up to this point can
4884 	 * be tossed because they don't match the score interval.
4885 	 */
4886 	for(s = searchset; s; s = s->next)
4887 	  for(msgno = s->first; msgno <= s->last; msgno++)
4888 	    if(msgno > 0L && msgno <= stream->nmsgs
4889 	       && (mc = mail_elt(stream, msgno)) && mc->searched){
4890 		long score;
4891 
4892 		score = (*get_score)(stream, msgno);
4893 
4894 		/*
4895 		 * If the score is outside all of the intervals,
4896 		 * turn off the searched bit.
4897 		 * So that means we check each interval and if
4898 		 * it is inside any interval we stop and leave
4899 		 * the bit set. If it is outside we keep checking.
4900 		 */
4901 		if(score != SCORE_UNDEF){
4902 		    INTVL_S *iv;
4903 
4904 		    for(iv = patgrp->score; iv; iv = iv->next)
4905 		      if(score >= iv->imin && score <= iv->imax)
4906 			break;
4907 
4908 		    if(!iv)
4909 		      mc->searched = NIL;
4910 		}
4911 	    }
4912 
4913 	for(i = 1L; i <= stream->nmsgs; i++)
4914 	  if((mc = mail_elt(stream, i)) != NULL)
4915 	    mc->sequence = savebits[i];
4916 
4917 	fs_give((void **)&savebits);
4918     }
4919 
4920     /* if there are still matches, check for 8bit subject match */
4921     if(patgrp->stat_8bitsubj != PAT_STAT_EITHER)
4922       find_8bitsubj_in_messages(stream, searchset, patgrp->stat_8bitsubj, 1);
4923 
4924     /* if there are still matches, check for charset matches */
4925     if(patgrp->charsets)
4926       find_charsets_in_messages(stream, searchset, patgrp, 1);
4927 
4928     /* Still matches, check addrbook */
4929     if(patgrp->inabook != IAB_EITHER)
4930       address_in_abook(stream, searchset, patgrp->inabook, patgrp->abooks);
4931 
4932     /* Still matches? Run the categorization command on each msg. */
4933     if(pith_opt_filter_pattern_cmd)
4934       (*pith_opt_filter_pattern_cmd)(patgrp->category_cmd, searchset, stream, patgrp->cat_lim, patgrp->cat);
4935 
4936     for(s = searchset; s; s = s->next)
4937       for(msgno = s->first; msgno > 0L && msgno <= s->last; msgno++)
4938         if(msgno > 0L && msgno <= stream->nmsgs
4939 	   && (mc = mail_elt(stream, msgno)) && mc->searched)
4940 	  return(1);
4941 
4942     return(0);
4943 }
4944 
4945 
4946 /*
4947  * Look through messages in searchset to see if they contain 8bit
4948  * characters in their subjects. All of the messages in
4949  * searchset should initially have the searched bit set. Turn off the
4950  * searched bit where appropriate.
4951  */
4952 void
find_8bitsubj_in_messages(MAILSTREAM * stream,SEARCHSET * searchset,int stat_8bitsubj,int saveseqbits)4953 find_8bitsubj_in_messages(MAILSTREAM *stream, SEARCHSET *searchset,
4954 			  int stat_8bitsubj, int saveseqbits)
4955 {
4956     char         *savebits = NULL;
4957     SEARCHSET    *s, *ss = NULL;
4958     MESSAGECACHE *mc;
4959     long          count = 0L;
4960     unsigned long msgno;
4961 
4962     /*
4963      * If we are being called while in build_header_line we may
4964      * call build_header_line recursively. So save and restore the
4965      * sequence bits.
4966      */
4967     if(saveseqbits)
4968       savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
4969 
4970     for(msgno = 1L; msgno <= stream->nmsgs; msgno++){
4971 	if((mc = mail_elt(stream, msgno)) != NULL){
4972 	    if(savebits)
4973 	      savebits[msgno] = mc->sequence;
4974 
4975 	    mc->sequence = 0;
4976 	}
4977     }
4978 
4979     /*
4980      * Build a searchset so we can look at all the envelopes
4981      * we need to look at but only those we need to look at.
4982      * Everything with the searched bit set is still a
4983      * possibility, so restrict to that set.
4984      */
4985 
4986     for(s = searchset; s; s = s->next)
4987       for(msgno = s->first; msgno <= s->last; msgno++)
4988 	if(msgno > 0L && msgno <= stream->nmsgs
4989 	   && (mc = mail_elt(stream, msgno)) && mc->searched){
4990 	    mc->sequence = 1;
4991 	    count++;
4992 	}
4993 
4994     ss = build_searchset(stream);
4995 
4996     if(count){
4997 	SEARCHSET **sset;
4998 
4999 	mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
5000 
5001 	/*
5002 	 * This causes the lookahead to fetch precisely
5003 	 * the messages we want (in the searchset) instead
5004 	 * of just fetching the next 20 sequential
5005 	 * messages. If the searching so far has caused
5006 	 * a sparse searchset in a large mailbox, the
5007 	 * difference can be substantial.
5008 	 * This resets automatically after the first fetch.
5009 	 */
5010 	sset = (SEARCHSET **) mail_parameters(stream,
5011 					      GET_FETCHLOOKAHEAD,
5012 					      (void *) stream);
5013 	if(sset)
5014 	  *sset = ss;
5015     }
5016 
5017     for(s = ss; s; s = s->next){
5018 	for(msgno = s->first; msgno <= s->last; msgno++){
5019 	    ENVELOPE   *e;
5020 
5021 	    if(!stream || msgno <= 0L || msgno > stream->nmsgs)
5022 	      continue;
5023 
5024 	    e = pine_mail_fetchenvelope(stream, msgno);
5025 	    if(stat_8bitsubj == PAT_STAT_YES){
5026 		if(e && e->subject){
5027 		    char *p;
5028 
5029 		    for(p = e->subject; *p; p++)
5030 		      if(*p & 0x80)
5031 			break;
5032 
5033 		    if(!*p && msgno > 0L && msgno <= stream->nmsgs
5034 		       && (mc = mail_elt(stream, msgno)))
5035 		      mc->searched = NIL;
5036 		}
5037 		else if(msgno > 0L && msgno <= stream->nmsgs
5038 			&& (mc = mail_elt(stream, msgno)))
5039 		  mc->searched = NIL;
5040 	    }
5041 	    else if(stat_8bitsubj == PAT_STAT_NO){
5042 		if(e && e->subject){
5043 		    char *p;
5044 
5045 		    for(p = e->subject; *p; p++)
5046 		      if(*p & 0x80)
5047 			break;
5048 
5049 		    if(*p && msgno > 0L && msgno <= stream->nmsgs
5050 		       && (mc = mail_elt(stream, msgno)))
5051 		      mc->searched = NIL;
5052 		}
5053 	    }
5054 	}
5055     }
5056 
5057     if(savebits){
5058 	for(msgno = 1L; msgno <= stream->nmsgs; msgno++)
5059 	  if((mc = mail_elt(stream, msgno)) != NULL)
5060 	    mc->sequence = savebits[msgno];
5061 
5062 	fs_give((void **) &savebits);
5063     }
5064 
5065     if(ss)
5066       mail_free_searchset(&ss);
5067 }
5068 
5069 
5070 /*
5071  * Look through messages in searchset to see if they contain any of the
5072  * charsets or scripts listed in charsets pattern. All of the messages in
5073  * searchset should initially have the searched bit set. Turn off the
5074  * searched bit where appropriate.
5075  */
5076 void
find_charsets_in_messages(MAILSTREAM * stream,SEARCHSET * searchset,PATGRP_S * patgrp,int saveseqbits)5077 find_charsets_in_messages(MAILSTREAM *stream, SEARCHSET *searchset,
5078 			  PATGRP_S *patgrp, int saveseqbits)
5079 {
5080     char         *savebits = NULL;
5081     unsigned long msgno;
5082     long          count = 0L;
5083     MESSAGECACHE *mc;
5084     SEARCHSET    *s, *ss;
5085 
5086     if(!stream || !patgrp)
5087       return;
5088 
5089     /*
5090      * When we actually want to use charsets, we convert it into a list
5091      * of charsets instead of the mixed list of scripts and charsets and
5092      * we eliminate duplicates. This is more efficient when we actually
5093      * do the lookups and compares.
5094      */
5095     if(!patgrp->charsets_list){
5096 	PATTERN_S    *cs;
5097 	const CHARSET *cset;
5098 	STRLIST_S    *sl = NULL, *newsl;
5099 	unsigned long scripts = 0L;
5100 	SCRIPT       *script;
5101 
5102 	for(cs = patgrp->charsets; cs; cs = cs->next){
5103 	    /*
5104 	     * Run through the charsets pattern looking for
5105 	     * scripts and set the corresponding script bits.
5106 	     * If it isn't a script, it is a character set.
5107 	     */
5108 	    if(cs->substring && (script = utf8_script(cs->substring)))
5109 	      scripts |= script->script;
5110 	    else{
5111 		/* add it to list as a specific character set */
5112 		newsl = new_strlist(cs->substring);
5113 		if(compare_strlists_for_match(sl, newsl))  /* already in list */
5114 		  free_strlist(&newsl);
5115 		else{
5116 		    newsl->next = sl;
5117 		    sl = newsl;
5118 		}
5119 	    }
5120 	}
5121 
5122 	/*
5123 	 * Now scripts has a bit set for each script the user
5124 	 * specified in the charsets pattern. Go through all of
5125 	 * the known charsets and include ones in these scripts.
5126 	 */
5127 	if(scripts){
5128 	    for(cset = utf8_charset(NIL); cset && cset->name; cset++){
5129 		if(cset->script & scripts){
5130 
5131 		    /* filter this out of each script, not very useful */
5132 		    if(!strucmp("ISO-2022-JP-2", cset->name)
5133 		       || !strucmp("UTF-7", cset->name)
5134 		       || !strucmp("UTF-8", cset->name))
5135 		      continue;
5136 
5137 		    /* add cset->name to the list */
5138 		    newsl = new_strlist(cset->name);
5139 		    if(compare_strlists_for_match(sl, newsl))
5140 		      free_strlist(&newsl);
5141 		    else{
5142 			newsl->next = sl;
5143 			sl = newsl;
5144 		    }
5145 		}
5146 	    }
5147 	}
5148 
5149 	patgrp->charsets_list = sl;
5150     }
5151 
5152     /*
5153      * This may call build_header_line recursively because we may be in
5154      * build_header_line now. So we have to preserve and restore the
5155      * sequence bits since we want to use them here.
5156      */
5157     if(saveseqbits)
5158       savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char));
5159 
5160     for(msgno = 1L; msgno <= stream->nmsgs; msgno++){
5161 	if((mc = mail_elt(stream, msgno)) != NULL){
5162 	    if(savebits)
5163 	      savebits[msgno] = mc->sequence;
5164 
5165 	    mc->sequence = 0;
5166 	}
5167     }
5168 
5169 
5170     /*
5171      * Build a searchset so we can look at all the bodies
5172      * we need to look at but only those we need to look at.
5173      * Everything with the searched bit set is still a
5174      * possibility, so restrict to that set.
5175      */
5176 
5177     for(s = searchset; s; s = s->next)
5178       for(msgno = s->first; msgno <= s->last; msgno++)
5179 	if(msgno > 0L && msgno <= stream->nmsgs
5180 	   && (mc = mail_elt(stream, msgno)) && mc->searched){
5181 	    mc->sequence = 1;
5182 	    count++;
5183         }
5184 
5185     ss = build_searchset(stream);
5186 
5187     if(count){
5188 	SEARCHSET **sset;
5189 
5190 	mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
5191 
5192 	/*
5193 	 * This causes the lookahead to fetch precisely
5194 	 * the messages we want (in the searchset) instead
5195 	 * of just fetching the next 20 sequential
5196 	 * messages. If the searching so far has caused
5197 	 * a sparse searchset in a large mailbox, the
5198 	 * difference can be substantial.
5199 	 * This resets automatically after the first fetch.
5200 	 */
5201 	sset = (SEARCHSET **) mail_parameters(stream,
5202 					      GET_FETCHLOOKAHEAD,
5203 					      (void *) stream);
5204 	if(sset)
5205 	  *sset = ss;
5206     }
5207 
5208     for(s = ss; s; s = s->next){
5209 	for(msgno = s->first; msgno <= s->last; msgno++){
5210 
5211 	    if(msgno <= 0L || msgno > stream->nmsgs)
5212 	      continue;
5213 
5214 	    if(patgrp->charsets_list
5215 	       && charsets_present_in_msg(stream,msgno,patgrp->charsets_list)){
5216 		if(patgrp->charsets->not){
5217 		    if((mc = mail_elt(stream, msgno)))
5218 		      mc->searched = NIL;
5219 		}
5220 		/* else leave it */
5221 	    }
5222 	    else{		/* charset isn't in message */
5223 		if(!patgrp->charsets->not){
5224 		    if((mc = mail_elt(stream, msgno)))
5225 		      mc->searched = NIL;
5226 		}
5227 		/* else leave it */
5228 	    }
5229 	}
5230     }
5231 
5232     if(savebits){
5233 	for(msgno = 1L; msgno <= stream->nmsgs; msgno++)
5234 	  if((mc = mail_elt(stream, msgno)) != NULL)
5235 	    mc->sequence = savebits[msgno];
5236 
5237 	fs_give((void **) &savebits);
5238     }
5239 
5240     if(ss)
5241       mail_free_searchset(&ss);
5242 }
5243 
5244 
5245 /*
5246  * Look for any of the charsets in this particular message.
5247  *
5248  * Returns 1 if there is a match, 0 otherwise.
5249  */
5250 int
charsets_present_in_msg(MAILSTREAM * stream,long unsigned int rawmsgno,STRLIST_S * charsets)5251 charsets_present_in_msg(MAILSTREAM *stream, long unsigned int rawmsgno, STRLIST_S *charsets)
5252 {
5253     BODY       *body = NULL;
5254     ENVELOPE   *env = NULL;
5255     STRLIST_S  *msg_charsets = NULL;
5256     int         ret = 0;
5257 
5258     if(charsets && stream && rawmsgno > 0L && rawmsgno <= stream->nmsgs){
5259 	env = pine_mail_fetchstructure(stream, rawmsgno, &body);
5260 	collect_charsets_from_subj(env, &msg_charsets);
5261 	collect_charsets_from_body(body, &msg_charsets);
5262 	if(msg_charsets){
5263 	    ret = compare_strlists_for_match(msg_charsets, charsets);
5264 	    free_strlist(&msg_charsets);
5265 	}
5266     }
5267 
5268     return(ret);
5269 }
5270 
5271 
5272 void
collect_charsets_from_subj(ENVELOPE * env,STRLIST_S ** listptr)5273 collect_charsets_from_subj(ENVELOPE *env, STRLIST_S **listptr)
5274 {
5275     STRLIST_S *newsl;
5276     char      *text, *e;
5277 
5278     if(listptr && env && env->subject){
5279 	/* find encoded word */
5280 	for(text = env->subject; *text; text++){
5281 	    if((*text == '=') && (text[1] == '?') && isalpha(text[2]) &&
5282 	       (e = strchr(text+2,'?'))){
5283 		*e = '\0';			/* tie off charset name */
5284 
5285 		newsl = new_strlist(text+2);
5286 		*e = '?';
5287 
5288 		if(compare_strlists_for_match(*listptr, newsl))
5289 		  free_strlist(&newsl);
5290 		else{
5291 		    newsl->next = *listptr;
5292 		    *listptr = newsl;
5293 		}
5294 	    }
5295 	}
5296     }
5297 }
5298 
5299 
5300 /*
5301  * Check for any of the charsets in any of the charset params in
5302  * any of the text parts of the body of a message. Put them in the list
5303  * pointed to by listptr.
5304  */
5305 void
collect_charsets_from_body(struct mail_bodystruct * body,STRLIST_S ** listptr)5306 collect_charsets_from_body(struct mail_bodystruct *body, STRLIST_S **listptr)
5307 {
5308     PART      *part;
5309     char      *cset;
5310 
5311     if(listptr && body){
5312 	switch(body->type){
5313           case TYPEMULTIPART:
5314 	    for(part = body->nested.part; part; part = part->next)
5315 	      collect_charsets_from_body(&part->body, listptr);
5316 
5317 	    break;
5318 
5319           case TYPEMESSAGE:
5320 	    if(!strucmp(body->subtype, "RFC822")){
5321 	        collect_charsets_from_subj(body->nested.msg->env, listptr);
5322 		collect_charsets_from_body(body->nested.msg->body, listptr);
5323 		break;
5324 	    }
5325 	    /* else fall through to text case */
5326 
5327 	  case TYPETEXT:
5328 	    cset = parameter_val(body->parameter, "charset");
5329 	    if(cset){
5330 		STRLIST_S *newsl;
5331 
5332 		newsl = new_strlist(cset);
5333 
5334 		if(compare_strlists_for_match(*listptr, newsl))
5335 		  free_strlist(&newsl);
5336 		else{
5337 		    newsl->next = *listptr;
5338 		    *listptr = newsl;
5339 		}
5340 
5341 	        fs_give((void **) &cset);
5342 	    }
5343 
5344 	    break;
5345 
5346 	  default:			/* non-text terminal mode */
5347 	    break;
5348 	}
5349     }
5350 }
5351 
5352 
5353 /*
5354  * If any of the names in list1 is the same as any of the names in list2
5355  * then return 1, else return 0. Comparison is case independent.
5356  */
5357 int
compare_strlists_for_match(STRLIST_S * list1,STRLIST_S * list2)5358 compare_strlists_for_match(STRLIST_S *list1, STRLIST_S *list2)
5359 {
5360     int        ret = 0;
5361     STRLIST_S *cs1, *cs2;
5362 
5363     for(cs1 = list1; !ret && cs1; cs1 = cs1->next)
5364       for(cs2 = list2; !ret && cs2; cs2 = cs2->next)
5365         if(cs1->name && cs2->name && !strucmp(cs1->name, cs2->name))
5366 	  ret = 1;
5367 
5368     return(ret);
5369 }
5370 
5371 
5372 int
match_pattern_folder(PATGRP_S * patgrp,MAILSTREAM * stream)5373 match_pattern_folder(PATGRP_S *patgrp, MAILSTREAM *stream)
5374 {
5375     int	       is_news;
5376 
5377     /* change by sderr : we match FLDR_ANY even if stream is NULL */
5378     return((patgrp->fldr_type == FLDR_ANY)
5379 	   || (stream
5380 	       && (((is_news = IS_NEWS(stream))
5381 	            && patgrp->fldr_type == FLDR_NEWS)
5382 	           || (!is_news && patgrp->fldr_type == FLDR_EMAIL)
5383 	           || (patgrp->fldr_type == FLDR_SPECIFIC
5384 		       && match_pattern_folder_specific(patgrp->folder,
5385 						       stream, FOR_PATTERN)))));
5386 }
5387 
5388 
5389 /*
5390  * Returns positive if this stream is open on one of the folders in the
5391  * folders argument, 0 otherwise.
5392  *
5393  * If FOR_PATTERN is set, this interprets simple names as nicknames in
5394  * the incoming collection, otherwise it treats simple names as being in
5395  * the primary collection.
5396  * If FOR_FILT is set, the folder names are detokenized before being used.
5397  */
5398 int
match_pattern_folder_specific(PATTERN_S * folders,MAILSTREAM * stream,int flags)5399 match_pattern_folder_specific(PATTERN_S *folders, MAILSTREAM *stream, int flags)
5400 {
5401     PATTERN_S *p;
5402     int        match = 0;
5403     char      *patfolder, *free_this = NULL;
5404 
5405     dprint((8, "match_pattern_folder_specific\n"));
5406 
5407     if(!(stream && stream->mailbox && stream->mailbox[0]))
5408       return(0);
5409 
5410     /*
5411      * For each of the folders in the pattern, see if we get
5412      * a match. We're just looking for any match. If none match,
5413      * we return 0, otherwise we fall through and check the rest
5414      * of the pattern. The fact that the string is called "substring"
5415      * is not meaningful. We're just using the convenient pattern
5416      * structure to store a list of folder names. They aren't
5417      * substrings of names, they are the whole name.
5418      */
5419     for(p = folders; !match && p; p = p->next){
5420 	free_this = NULL;
5421 	if(flags & FOR_FILTER)
5422 	  patfolder = free_this = detoken_src(p->substring, FOR_FILT, NULL,
5423 					      NULL, NULL, NULL);
5424 	else
5425 	  patfolder = p->substring;
5426 
5427 	if(patfolder
5428 	   && (!strucmp(patfolder, ps_global->inbox_name)
5429 	       || !strcmp(patfolder, ps_global->VAR_INBOX_PATH))){
5430 	    if(sp_flagged(stream, SP_INBOX))
5431 	      match++;
5432 	}
5433 	else{
5434 	    char      *fname;
5435 	    char      *t, *streamfolder;
5436 	    char       tmp1[MAILTMPLEN], tmp2[MAX(MAILTMPLEN,NETMAXMBX)];
5437 	    CONTEXT_S *cntxt = NULL;
5438 
5439 	    if(flags & FOR_PATTERN){
5440 		/*
5441 		 * See if patfolder is a nickname in the incoming collection.
5442 		 * If so, use its real name instead.
5443 		 */
5444 		if(patfolder[0] &&
5445 		   (ps_global->context_list->use & CNTXT_INCMNG) &&
5446 		   (fname = (folder_is_nick(patfolder,
5447 					    FOLDERS(ps_global->context_list),
5448 					    0))))
5449 		  patfolder = fname;
5450 	    }
5451 	    else{
5452 		char *save_ref = NULL;
5453 
5454 		/*
5455 		 * If it's an absolute pathname, we treat is as a local file
5456 		 * instead of interpreting it in the primary context.
5457 		 */
5458 		if(!is_absolute_path(patfolder)
5459 		   && !(cntxt = default_save_context(ps_global->context_list)))
5460 		  cntxt = ps_global->context_list;
5461 
5462 		/*
5463 		 * Because this check is independent of where the user is
5464 		 * in the folder hierarchy and has nothing to do with that,
5465 		 * we want to ignore the reference field built into the
5466 		 * context. Zero it out temporarily here.
5467 		 */
5468 		if(cntxt && cntxt->dir){
5469 		    save_ref = cntxt->dir->ref;
5470 		    cntxt->dir->ref = NULL;
5471 		}
5472 
5473 		patfolder = context_apply(tmp1, cntxt, patfolder, sizeof(tmp1));
5474 		if(save_ref)
5475 		  cntxt->dir->ref = save_ref;
5476 	    }
5477 
5478 	    switch(patfolder[0]){
5479 	      case '{':
5480 		if(stream->mailbox[0] == '{' &&
5481 		   same_stream(patfolder, stream) &&
5482 		   (streamfolder = strindex(&stream->mailbox[1], '}')) &&
5483 		   (t = strindex(&patfolder[1], '}')) &&
5484 		   (!strcmp(t+1, streamfolder+1) ||
5485 		    (*(t+1) == '\0' && !strcmp("INBOX", streamfolder+1))))
5486 		  match++;
5487 
5488 		break;
5489 
5490 	      case '#':
5491 	        if(!strcmp(patfolder, stream->mailbox))
5492 		  match++;
5493 
5494 		break;
5495 
5496 	      default:
5497 		t = (strlen(patfolder) < (MAILTMPLEN/2))
5498 				? mailboxfile(tmp2, patfolder) : NULL;
5499 		if(t && *t && !strcmp(t, stream->mailbox))
5500 		  match++;
5501 
5502 		break;
5503 	    }
5504 	}
5505 
5506 	if(free_this)
5507 	  fs_give((void **) &free_this);
5508     }
5509 
5510     return(match);
5511 }
5512 
5513 
5514 /*
5515  * generate a search program corresponding to the provided patgrp
5516  */
5517 SEARCHPGM *
match_pattern_srchpgm(PATGRP_S * patgrp,MAILSTREAM * stream,SEARCHSET * searchset)5518 match_pattern_srchpgm(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset)
5519 {
5520     SEARCHPGM	 *pgm, *tmppgm;
5521     SEARCHOR     *or;
5522     SEARCHSET	**sp;
5523 
5524     pgm = mail_newsearchpgm();
5525 
5526     sp = &pgm->msgno;
5527     /* copy the searchset */
5528     while(searchset){
5529 	SEARCHSET *s;
5530 
5531 	s = mail_newsearchset();
5532 	s->first = searchset->first;
5533 	s->last  = searchset->last;
5534 	searchset = searchset->next;
5535 	*sp = s;
5536 	sp = &s->next;
5537     }
5538 
5539     if(!patgrp)
5540       return(pgm);
5541 
5542     if(patgrp->subj){
5543 	if(patgrp->subj->not)
5544 	  tmppgm = next_not(pgm);
5545 	else
5546 	  tmppgm = pgm;
5547 
5548 	set_up_search_pgm("subject", patgrp->subj, tmppgm);
5549     }
5550 
5551     if(patgrp->cc){
5552 	if(patgrp->cc->not)
5553 	  tmppgm = next_not(pgm);
5554 	else
5555 	  tmppgm = pgm;
5556 
5557 	set_up_search_pgm("cc", patgrp->cc, tmppgm);
5558     }
5559 
5560     if(patgrp->from){
5561 	if(patgrp->from->not)
5562 	  tmppgm = next_not(pgm);
5563 	else
5564 	  tmppgm = pgm;
5565 
5566 	set_up_search_pgm("from", patgrp->from, tmppgm);
5567     }
5568 
5569     if(patgrp->to){
5570 	if(patgrp->to->not)
5571 	  tmppgm = next_not(pgm);
5572 	else
5573 	  tmppgm = pgm;
5574 
5575 	set_up_search_pgm("to", patgrp->to, tmppgm);
5576     }
5577 
5578     if(patgrp->sender){
5579 	if(patgrp->sender->not)
5580 	  tmppgm = next_not(pgm);
5581 	else
5582 	  tmppgm = pgm;
5583 
5584 	set_up_search_pgm("sender", patgrp->sender, tmppgm);
5585     }
5586 
5587     if(patgrp->news){
5588 	if(patgrp->news->not)
5589 	  tmppgm = next_not(pgm);
5590 	else
5591 	  tmppgm = pgm;
5592 
5593 	set_up_search_pgm("newsgroups", patgrp->news, tmppgm);
5594     }
5595 
5596     /* To OR Cc */
5597     if(patgrp->recip){
5598 	if(patgrp->recip->not)
5599 	  tmppgm = next_not(pgm);
5600 	else
5601 	  tmppgm = pgm;
5602 
5603 	or = next_or(&tmppgm->or);
5604 
5605 	set_up_search_pgm("to", patgrp->recip, or->first);
5606 	set_up_search_pgm("cc", patgrp->recip, or->second);
5607     }
5608 
5609     /* To OR Cc OR From */
5610     if(patgrp->partic){
5611 	if(patgrp->partic->not)
5612 	  tmppgm = next_not(pgm);
5613 	else
5614 	  tmppgm = pgm;
5615 
5616 	or = next_or(&tmppgm->or);
5617 
5618 	set_up_search_pgm("to", patgrp->partic, or->first);
5619 
5620 	or->second->or = mail_newsearchor();
5621 	set_up_search_pgm("cc", patgrp->partic, or->second->or->first);
5622 	set_up_search_pgm("from", patgrp->partic, or->second->or->second);
5623     }
5624 
5625     if(patgrp->arbhdr){
5626 	ARBHDR_S *a;
5627 
5628 	for(a = patgrp->arbhdr; a; a = a->next)
5629 	  if(a->field && a->field[0] && a->p){
5630 	      if(a->p->not)
5631 	        tmppgm = next_not(pgm);
5632 	      else
5633 	        tmppgm = pgm;
5634 
5635 	      set_up_search_pgm(a->field, a->p, tmppgm);
5636 	  }
5637     }
5638 
5639     if(patgrp->alltext){
5640 	if(patgrp->alltext->not)
5641 	  tmppgm = next_not(pgm);
5642 	else
5643 	  tmppgm = pgm;
5644 
5645 	set_up_search_pgm("alltext", patgrp->alltext, tmppgm);
5646     }
5647 
5648     if(patgrp->bodytext){
5649 	if(patgrp->bodytext->not)
5650 	  tmppgm = next_not(pgm);
5651 	else
5652 	  tmppgm = pgm;
5653 
5654 	set_up_search_pgm("bodytext", patgrp->bodytext, tmppgm);
5655     }
5656 
5657     if(patgrp->keyword){
5658 	PATTERN_S *p_old, *p_new, *new_pattern = NULL, **nextp;
5659 	char      *q;
5660 
5661 	if(patgrp->keyword->not)
5662 	  tmppgm = next_not(pgm);
5663 	else
5664 	  tmppgm = pgm;
5665 
5666 	/*
5667 	 * The keyword entries may be nicknames instead of the actual
5668 	 * keywords, so those need to be converted to actual keywords.
5669 	 *
5670 	 * If we search for keywords that are not defined for a folder
5671 	 * we may get error messages back that we don't want instead of
5672 	 * just no match. We will build a replacement pattern here which
5673 	 * contains only the defined subset of the keywords.
5674 	 */
5675 
5676 	nextp = &new_pattern;
5677 
5678 	for(p_old = patgrp->keyword; p_old; p_old = p_old->next){
5679 	    q = nick_to_keyword(p_old->substring);
5680 	    if(user_flag_index(stream, q) >= 0){
5681 		p_new = (PATTERN_S *) fs_get(sizeof(*p_new));
5682 		memset(p_new, 0, sizeof(*p_new));
5683 		p_new->substring = cpystr(q);
5684 		*nextp = p_new;
5685 		nextp = &p_new->next;
5686 	    }
5687 	}
5688 
5689 	/*
5690 	 * If there are some matching keywords that are defined in
5691 	 * the folder, then we are ok because we will match only if
5692 	 * we match one of those. However, if the list is empty, then
5693 	 * we can't just leave this part of the search program empty.
5694 	 * That would result in a match instead of not a match.
5695 	 * We can fake our way around the problem with NOT. If the
5696 	 * list is empty we want the opposite, so we insert a NOT in
5697 	 * front of an empty program. We may end up with NOT NOT if
5698 	 * this was already NOT'd, but that's ok, too. Alternatively,
5699 	 * we could undo the first NOT instead.
5700 	 */
5701 
5702 	if(new_pattern){
5703 	    set_up_search_pgm("keyword", new_pattern, tmppgm);
5704 	    free_pattern(&new_pattern);
5705 	}
5706 	else
5707 	  (void) next_not(tmppgm);	/* add NOT of something that matches,
5708 					   so the NOT thing doesn't match   */
5709     }
5710 
5711     if(patgrp->do_age && patgrp->age){
5712 	INTVL_S  *iv;
5713 	SEARCHOR *or;
5714 
5715 	tmppgm = pgm;
5716 
5717 	for(iv = patgrp->age; iv; iv = iv->next){
5718 	    if(iv->next){
5719 		or = next_or(&tmppgm->or);
5720 		set_search_by_age(iv, or->first, patgrp->age_uses_sentdate);
5721 		tmppgm = or->second;
5722 	    }
5723 	    else
5724 	      set_search_by_age(iv, tmppgm, patgrp->age_uses_sentdate);
5725 	}
5726     }
5727 
5728     if(patgrp->do_size && patgrp->size){
5729 	INTVL_S  *iv;
5730 	SEARCHOR *or;
5731 
5732 	tmppgm = pgm;
5733 
5734 	for(iv = patgrp->size; iv; iv = iv->next){
5735 	    if(iv->next){
5736 		or = next_or(&tmppgm->or);
5737 		set_search_by_size(iv, or->first);
5738 		tmppgm = or->second;
5739 	    }
5740 	    else
5741 	      set_search_by_size(iv, tmppgm);
5742 	}
5743     }
5744 
5745     SETPGMSTATUS(patgrp->stat_new,pgm->unseen,pgm->seen);
5746     SETPGMSTATUS(patgrp->stat_rec,pgm->recent,pgm->old);
5747     SETPGMSTATUS(patgrp->stat_del,pgm->deleted,pgm->undeleted);
5748     SETPGMSTATUS(patgrp->stat_imp,pgm->flagged,pgm->unflagged);
5749     SETPGMSTATUS(patgrp->stat_ans,pgm->answered,pgm->unanswered);
5750 
5751     return(pgm);
5752 }
5753 
5754 
5755 SEARCHPGM *
next_not(SEARCHPGM * pgm)5756 next_not(SEARCHPGM *pgm)
5757 {
5758     SEARCHPGMLIST *not, **not_ptr;
5759 
5760     if(!pgm)
5761       return(NULL);
5762 
5763     /* find next unused not slot */
5764     for(not = pgm->not; not && not->next; not = not->next)
5765       ;
5766 
5767     if(not)
5768       not_ptr = &not->next;
5769     else
5770       not_ptr = &pgm->not;
5771 
5772     /* allocate */
5773     *not_ptr = mail_newsearchpgmlist();
5774 
5775     return((*not_ptr)->pgm);
5776 }
5777 
5778 
5779 SEARCHOR *
next_or(struct search_or ** startingor)5780 next_or(struct search_or **startingor)
5781 {
5782     SEARCHOR *or, **or_ptr;
5783 
5784     /* find next unused or slot */
5785     for(or = (*startingor); or && or->next; or = or->next)
5786       ;
5787 
5788     if(or)
5789       or_ptr = &or->next;
5790     else
5791       or_ptr = startingor;
5792 
5793     /* allocate */
5794     *or_ptr = mail_newsearchor();
5795 
5796     return(*or_ptr);
5797 }
5798 
5799 
5800 void
set_up_search_pgm(char * field,PATTERN_S * pattern,SEARCHPGM * pgm)5801 set_up_search_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm)
5802 {
5803     SEARCHOR *or;
5804 
5805     if(field && pattern && pgm){
5806 
5807 	/*
5808 	 * To is special because we want to use the ReSent-To header instead
5809 	 * of the To header if it exists.  We set up something like:
5810 	 *
5811 	 * if((resent-to matches pat1 or pat2...)
5812 	 *                  OR
5813 	 *    (<resent-to doesn't exist> AND (to matches pat1 or pat2...)))
5814 	 *
5815 	 *  Some servers (Exchange, apparently) seem to have trouble with
5816 	 *  the search for the empty string to decide if the header exists
5817 	 *  or not. So, we will search for either the empty string OR the
5818 	 *  header with a SPACE in it. Some still have trouble with this
5819 	 *  so we are changing it to be off by default.
5820 	 */
5821 	if(!strucmp(field, "to") && F_ON(F_USE_RESENTTO, ps_global)){
5822 	    or = next_or(&pgm->or);
5823 
5824 	    add_type_to_pgm("resent-to", pattern, or->first);
5825 
5826 	    /* check for resent-to doesn't exist */
5827 	    or->second->not = mail_newsearchpgmlist();
5828 
5829 	    or->second->not->pgm->or = mail_newsearchor();
5830 	    set_srch("resent-to", " ", or->second->not->pgm->or->first);
5831 	    set_srch("resent-to",  "", or->second->not->pgm->or->second);
5832 
5833 	    /* now add the real To search to second */
5834 	    add_type_to_pgm(field, pattern, or->second);
5835 	}
5836 	else
5837 	  add_type_to_pgm(field, pattern, pgm);
5838     }
5839 }
5840 
5841 
5842 void
add_type_to_pgm(char * field,PATTERN_S * pattern,SEARCHPGM * pgm)5843 add_type_to_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm)
5844 {
5845     PATTERN_S *p;
5846     SEARCHOR  *or;
5847     SEARCHPGM *notpgm, *tpgm;
5848     int        cnt = 0;
5849 
5850     if(field && pattern && pgm){
5851 	/*
5852 	 * Here is a weird bit of logic. What we want here is simply
5853 	 *       A or B or C or D
5854 	 * for all of the elements of pattern. Ors are a bit complicated.
5855 	 * The list of ORs in the SEARCHPGM structure are ANDed together,
5856 	 * not ORd together. It's for things like
5857 	 *      Subject A or B  AND  From C or D
5858 	 * The Subject part would be one member of the OR list and the From
5859 	 * part would be another member of the OR list. Instead we want
5860 	 * a big OR which may have more than two members (first and second)
5861 	 * but the structure just has two members. So we have to build an
5862 	 * OR tree and we build it by going down one branch of the tree
5863 	 * instead of by balancing the branches.
5864 	 *
5865 	 *            or
5866 	 *           /  \
5867 	 *    first==A   second
5868 	 *                /  \
5869 	 *          first==B  second
5870 	 *                     /  \
5871 	 *               first==C  second==D
5872 	 *
5873 	 * There is an additional problem. Some servers don't like deeply
5874 	 * nested logic in the SEARCH command. The tree above produces a
5875 	 * fairly deeply nested command if the user wants to match on
5876 	 * several different From addresses or Subjects...
5877 	 * We use the tried and true equation
5878 	 *
5879 	 *        (A or B) == !(!A and !B)
5880 	 *
5881 	 * to change the deeply nested OR tree into ANDs which aren't nested.
5882 	 * Right now we're only doing that if the nesting is fairly deep.
5883 	 * We can think of some reasons to do that. First, we know that the
5884 	 * OR thing works, that's what we've been using for a while and the
5885 	 * only problem is the deep nesting. 2nd, it is easier to understand.
5886 	 * 3rd, it looks dumb to use  NOT NOT A   instead of  A.
5887 	 * It is probably dumb to mix the two, but what the heck.
5888 	 * Hubert 2003-04-02
5889 	 */
5890 	for(p = pattern; p; p = p->next)
5891 	  cnt++;
5892 
5893 	if(cnt < 10){				/* use ORs if count is low */
5894 	    for(p = pattern; p; p = p->next){
5895 		if(p->next){
5896 		    or = next_or(&pgm->or);
5897 
5898 		    set_srch(field, p->substring ? p->substring : "", or->first);
5899 		    pgm = or->second;
5900 		}
5901 		else
5902 		  set_srch(field, p->substring ? p->substring : "", pgm);
5903 	    }
5904 	}
5905 	else{					/* else use ANDs */
5906 	    /* ( A or B or C )  <=>  ! ( !A and !B and !C ) */
5907 
5908 	    /* first, NOT of the whole thing */
5909 	    notpgm = next_not(pgm);
5910 
5911 	    /* then the not list is ANDed together */
5912 	    for(p = pattern; p; p = p->next){
5913 		tpgm = next_not(notpgm);
5914 		set_srch(field, p->substring ? p->substring : "", tpgm);
5915 	    }
5916 	}
5917     }
5918 }
5919 
5920 
5921 void
set_srch(char * field,char * value,SEARCHPGM * pgm)5922 set_srch(char *field, char *value, SEARCHPGM *pgm)
5923 {
5924     char        *decoded;
5925     STRINGLIST **list;
5926 
5927     if(!(field && value && pgm))
5928       return;
5929 
5930     if(!strucmp(field, "subject"))
5931       list = &pgm->subject;
5932     else if(!strucmp(field, "from"))
5933       list = &pgm->from;
5934     else if(!strucmp(field, "to"))
5935       list = &pgm->to;
5936     else if(!strucmp(field, "cc"))
5937       list = &pgm->cc;
5938     else if(!strucmp(field, "sender"))
5939       list = &pgm->sender;
5940     else if(!strucmp(field, "reply-to"))
5941       list = &pgm->reply_to;
5942     else if(!strucmp(field, "in-reply-to"))
5943       list = &pgm->in_reply_to;
5944     else if(!strucmp(field, "message-id"))
5945       list = &pgm->message_id;
5946     else if(!strucmp(field, "newsgroups"))
5947       list = &pgm->newsgroups;
5948     else if(!strucmp(field, "followup-to"))
5949       list = &pgm->followup_to;
5950     else if(!strucmp(field, "alltext"))
5951       list = &pgm->text;
5952     else if(!strucmp(field, "bodytext"))
5953       list = &pgm->body;
5954     else if(!strucmp(field, "keyword"))
5955       list = &pgm->keyword;
5956     else{
5957 	set_srch_hdr(field, value, pgm);
5958 	return;
5959     }
5960 
5961     if(!list)
5962       return;
5963 
5964     *list = mail_newstringlist();
5965     decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, value);
5966 
5967     (*list)->text.data = (unsigned char *)cpystr(decoded);
5968     (*list)->text.size = strlen(decoded);
5969 }
5970 
5971 
5972 void
set_srch_hdr(char * field,char * value,SEARCHPGM * pgm)5973 set_srch_hdr(char *field, char *value, SEARCHPGM *pgm)
5974 {
5975     char *decoded;
5976     SEARCHHEADER  **hdr;
5977 
5978     if(!(field && value && pgm))
5979       return;
5980 
5981     hdr = &pgm->header;
5982     if(!hdr)
5983       return;
5984 
5985     decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5986 					     SIZEOF_20KBUF, value);
5987     while(*hdr && (*hdr)->next)
5988       *hdr = (*hdr)->next;
5989 
5990     if(*hdr)
5991       (*hdr)->next = mail_newsearchheader(field, decoded);
5992     else
5993       *hdr = mail_newsearchheader(field, decoded);
5994 }
5995 
5996 
5997 void
set_search_by_age(INTVL_S * age,SEARCHPGM * pgm,int age_uses_sentdate)5998 set_search_by_age(INTVL_S *age, SEARCHPGM *pgm, int age_uses_sentdate)
5999 {
6000     time_t         now, comparetime;
6001     struct tm     *tm;
6002     unsigned short i;
6003 
6004     if(!(age && pgm))
6005       return;
6006 
6007     now = time(0);
6008 
6009     if(age->imin >= 0L && age->imin == age->imax){
6010 	comparetime = now;
6011 	comparetime -= (age->imin * 86400L);
6012 	tm = localtime(&comparetime);
6013 	if(tm && tm->tm_year >= 70){
6014 	    i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6015 			       tm->tm_mday);
6016 	    if(age_uses_sentdate)
6017 	      pgm->senton = i;
6018 	    else
6019 	      pgm->on = i;
6020 	}
6021     }
6022     else{
6023 	/*
6024 	 * The 20000's are just protecting against overflows.
6025 	 * That's back past the start of email time, anyway.
6026 	 */
6027 	if(age->imin > 0L && age->imin < 20000L){
6028 	    comparetime = now;
6029 	    comparetime -= ((age->imin - 1L) * 86400L);
6030 	    tm = localtime(&comparetime);
6031 	    if(tm && tm->tm_year >= 70){
6032 		i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6033 				   tm->tm_mday);
6034 		if(age_uses_sentdate)
6035 		  pgm->sentbefore = i;
6036 		else
6037 		  pgm->before = i;
6038 	    }
6039 	}
6040 
6041 	if(age->imax >= 0L && age->imax < 20000L){
6042 	    comparetime = now;
6043 	    comparetime -= (age->imax * 86400L);
6044 	    tm = localtime(&comparetime);
6045 	    if(tm && tm->tm_year >= 70){
6046 		i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1,
6047 				   tm->tm_mday);
6048 		if(age_uses_sentdate)
6049 		  pgm->sentsince = i;
6050 		else
6051 		  pgm->since = i;
6052 	    }
6053 	}
6054     }
6055 }
6056 
6057 
6058 void
set_search_by_size(INTVL_S * size,SEARCHPGM * pgm)6059 set_search_by_size(INTVL_S *size, SEARCHPGM *pgm)
6060 {
6061     if(!(size && pgm))
6062       return;
6063 
6064     /*
6065      * INTVL_S intervals include the endpoints, pgm larger and smaller
6066      * do not include the endpoints.
6067      */
6068     if(size->imin != INTVL_UNDEF && size->imin > 0L)
6069       pgm->larger  = size->imin - 1L;
6070 
6071     if(size->imax != INTVL_UNDEF && size->imax >= 0L && size->imax != INTVL_INF)
6072       pgm->smaller = size->imax + 1L;
6073 }
6074 
6075 
6076 static char *extra_hdrs;
6077 
6078 /*
6079  * Run through the patterns and note which headers we'll need to ask for
6080  * which aren't normally asked for and so won't be cached.
6081  */
6082 void
calc_extra_hdrs(void)6083 calc_extra_hdrs(void)
6084 {
6085     PAT_S    *pat = NULL;
6086     int       alloced_size;
6087     long      type = (ROLE_INCOL | ROLE_SCORE);
6088     ARBHDR_S *a;
6089     PAT_STATE pstate;
6090     char     *q, *p = NULL, *hdrs[MLCMD_COUNT + 1], **pp;
6091     INDEX_COL_S *cdesc;
6092 #define INITIALSIZE 1000
6093 
6094     q = (char *)fs_get((INITIALSIZE+1) * sizeof(char));
6095     q[0] = '\0';
6096     alloced_size = INITIALSIZE;
6097     p = q;
6098 
6099     /*
6100      * *ALWAYS* make sure Resent-To is in the set of
6101      * extra headers getting fetched.
6102      *
6103      * This is because we *will* reference it when we're
6104      * building header lines and thus want it fetched with
6105      * the standard envelope data.  Worse, in the IMAP case
6106      * we're called back from c-client with the envelope data
6107      * so we can format and display the index lines as they
6108      * arrive, so we have to ensure the resent-to field
6109      * is in the cache so we don't reenter c-client
6110      * to look for it from the callback.  Yeouch.
6111      */
6112     add_eh(&q, &p, "resent-to", &alloced_size);
6113     add_eh(&q, &p, "resent-date", &alloced_size);
6114     add_eh(&q, &p, "resent-from", &alloced_size);
6115     add_eh(&q, &p, "resent-cc", &alloced_size);
6116     add_eh(&q, &p, "resent-subject", &alloced_size);
6117 
6118     /*
6119      * Sniff at viewer-hdrs too so we can include them
6120      * if there are any...
6121      */
6122     for(pp = ps_global->VAR_VIEW_HEADERS; pp && *pp; pp++)
6123       if(non_eh(*pp))
6124 	add_eh(&q, &p, *pp, &alloced_size);
6125 
6126     /*
6127      * Be sure to ask for List management headers too
6128      * since we'll offer their use in the message view
6129      */
6130     for(pp = rfc2369_hdrs(hdrs); *pp; pp++)
6131       add_eh(&q, &p, *pp, &alloced_size);
6132 
6133     if(nonempty_patterns(type, &pstate))
6134       for(pat = first_pattern(&pstate);
6135 	  pat;
6136 	  pat = next_pattern(&pstate)){
6137 	  /*
6138 	   * This section wouldn't be necessary if sender was retrieved
6139 	   * from the envelope. But if not, we do need to add it.
6140 	   */
6141 	  if(pat->patgrp && pat->patgrp->sender)
6142 	    add_eh(&q, &p, "sender", &alloced_size);
6143 
6144 	  if(pat->patgrp && pat->patgrp->arbhdr)
6145 	    for(a = pat->patgrp->arbhdr; a; a = a->next)
6146 	      if(a->field && a->field[0] && a->p && non_eh(a->field))
6147 		add_eh(&q, &p, a->field, &alloced_size);
6148       }
6149 
6150     /*
6151      * Check for use of HEADER or X-Priority in index-format.
6152      */
6153     for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++){
6154 	if(cdesc->ctype == iHeader && cdesc->hdrtok && cdesc->hdrtok->hdrname
6155            && cdesc->hdrtok->hdrname[0] && non_eh(cdesc->hdrtok->hdrname))
6156 	  add_eh(&q, &p, cdesc->hdrtok->hdrname, &alloced_size);
6157 	else if(cdesc->ctype == iPrio
6158 		|| cdesc->ctype == iPrioAlpha
6159 		|| cdesc->ctype == iPrioBang)
6160 	  add_eh(&q, &p, PRIORITYNAME, &alloced_size);
6161     }
6162 
6163     /*
6164      * Check for use of scorevalhdrtok in scoring patterns.
6165      */
6166     type = ROLE_SCORE;
6167     if(nonempty_patterns(type, &pstate))
6168       for(pat = first_pattern(&pstate);
6169 	  pat;
6170 	  pat = next_pattern(&pstate)){
6171 	  /*
6172 	   * This section wouldn't be necessary if sender was retrieved
6173 	   * from the envelope. But if not, we do need to add it.
6174 	   */
6175 	  if(pat->action && pat->action->scorevalhdrtok
6176 	     && pat->action->scorevalhdrtok->hdrname
6177 	     && pat->action->scorevalhdrtok->hdrname[0]
6178 	     && non_eh(pat->action->scorevalhdrtok->hdrname))
6179 	    add_eh(&q, &p, pat->action->scorevalhdrtok->hdrname, &alloced_size);
6180       }
6181 
6182     set_extra_hdrs(q);
6183     if(q)
6184       fs_give((void **)&q);
6185 }
6186 
6187 
6188 int
non_eh(char * field)6189 non_eh(char *field)
6190 {
6191     char **t;
6192     static char *existing[] = {"subject", "from", "to", "cc", "sender",
6193 			       "reply-to", "in-reply-to", "message-id",
6194 			       "path", "newsgroups", "followup-to",
6195 			       "references", NULL};
6196 
6197     /*
6198      * If it is one of these, we should already have it
6199      * from the envelope or from the extra headers c-client
6200      * already adds to the list (hdrheader and hdrtrailer
6201      * in imap4r1.c, Aug 99, slh).
6202      */
6203     for(t = existing; *t; t++)
6204       if(!strucmp(field, *t))
6205 	return(FALSE);
6206 
6207     return(TRUE);
6208 }
6209 
6210 
6211 /*
6212  * Add field to extra headers string if not already there.
6213  */
6214 void
add_eh(char ** start,char ** ptr,char * field,int * asize)6215 add_eh(char **start, char **ptr, char *field, int *asize)
6216 {
6217       char *s;
6218 
6219       /* already there? */
6220       for(s = *start; (s = srchstr(s, field)) != NULL; s++)
6221 	if(s[strlen(field)] == SPACE || s[strlen(field)] == '\0')
6222 	  return;
6223 
6224       /* enough space for it? */
6225       while(strlen(field) + (*ptr - *start) + 1 > *asize){
6226 	  (*asize) *= 2;
6227 	  fs_resize((void **)start, (*asize)+1);
6228 	  *ptr = *start + strlen(*start);
6229       }
6230 
6231       if(*ptr > *start)
6232 	sstrncpy(ptr, " ", *asize-(*ptr - *start));
6233 
6234       sstrncpy(ptr, field, *asize-(*ptr - *start));
6235 
6236       (*start)[*asize] = '\0';
6237 }
6238 
6239 
6240 void
set_extra_hdrs(char * hdrs)6241 set_extra_hdrs(char *hdrs)
6242 {
6243     free_extra_hdrs();
6244     if(hdrs && *hdrs)
6245       extra_hdrs = cpystr(hdrs);
6246 }
6247 
6248 
6249 char *
get_extra_hdrs(void)6250 get_extra_hdrs(void)
6251 {
6252     return(extra_hdrs);
6253 }
6254 
6255 
6256 void
free_extra_hdrs(void)6257 free_extra_hdrs(void)
6258 {
6259     if(extra_hdrs)
6260       fs_give((void **)&extra_hdrs);
6261 }
6262 
6263 
6264 int
is_ascii_string(char * str)6265 is_ascii_string(char *str)
6266 {
6267     if(!str)
6268       return(0);
6269 
6270     while(*str && isascii(*str))
6271       str++;
6272 
6273     return(*str == '\0');
6274 }
6275 
6276 
6277 void
free_patline(PAT_LINE_S ** patline)6278 free_patline(PAT_LINE_S **patline)
6279 {
6280     if(patline && *patline){
6281 	free_patline(&(*patline)->next);
6282 	if((*patline)->filename)
6283 	  fs_give((void **)&(*patline)->filename);
6284 	if((*patline)->filepath)
6285 	  fs_give((void **)&(*patline)->filepath);
6286 	free_pat(&(*patline)->first);
6287 	fs_give((void **)patline);
6288     }
6289 }
6290 
6291 
6292 void
free_pat(PAT_S ** pat)6293 free_pat(PAT_S **pat)
6294 {
6295     if(pat && *pat){
6296 	free_pat(&(*pat)->next);
6297 	free_patgrp(&(*pat)->patgrp);
6298 	free_action(&(*pat)->action);
6299 	if((*pat)->raw)
6300 	  fs_give((void **)&(*pat)->raw);
6301 
6302 	fs_give((void **)pat);
6303     }
6304 }
6305 
6306 
6307 void
free_patgrp(PATGRP_S ** patgrp)6308 free_patgrp(PATGRP_S **patgrp)
6309 {
6310     if(patgrp && *patgrp){
6311 	if((*patgrp)->nick)
6312 	  fs_give((void **) &(*patgrp)->nick);
6313 
6314 	if((*patgrp)->comment)
6315 	  fs_give((void **) &(*patgrp)->comment);
6316 
6317 	if((*patgrp)->category_cmd)
6318 	  free_list_array(&(*patgrp)->category_cmd);
6319 
6320 	if((*patgrp)->charsets_list)
6321 	  free_strlist(&(*patgrp)->charsets_list);
6322 
6323 	free_pattern(&(*patgrp)->to);
6324 	free_pattern(&(*patgrp)->cc);
6325 	free_pattern(&(*patgrp)->recip);
6326 	free_pattern(&(*patgrp)->partic);
6327 	free_pattern(&(*patgrp)->from);
6328 	free_pattern(&(*patgrp)->sender);
6329 	free_pattern(&(*patgrp)->news);
6330 	free_pattern(&(*patgrp)->subj);
6331 	free_pattern(&(*patgrp)->alltext);
6332 	free_pattern(&(*patgrp)->bodytext);
6333 	free_pattern(&(*patgrp)->keyword);
6334 	free_pattern(&(*patgrp)->charsets);
6335 	free_pattern(&(*patgrp)->folder);
6336 	free_arbhdr(&(*patgrp)->arbhdr);
6337 	free_intvl(&(*patgrp)->score);
6338 	free_intvl(&(*patgrp)->age);
6339 	fs_give((void **) patgrp);
6340     }
6341 }
6342 
6343 
6344 void
free_pattern(PATTERN_S ** pattern)6345 free_pattern(PATTERN_S **pattern)
6346 {
6347     if(pattern && *pattern){
6348 	free_pattern(&(*pattern)->next);
6349 	if((*pattern)->substring)
6350 	  fs_give((void **)&(*pattern)->substring);
6351 	fs_give((void **)pattern);
6352     }
6353 }
6354 
6355 
6356 void
free_arbhdr(ARBHDR_S ** arbhdr)6357 free_arbhdr(ARBHDR_S **arbhdr)
6358 {
6359     if(arbhdr && *arbhdr){
6360 	free_arbhdr(&(*arbhdr)->next);
6361 	if((*arbhdr)->field)
6362 	  fs_give((void **)&(*arbhdr)->field);
6363 	free_pattern(&(*arbhdr)->p);
6364 	fs_give((void **)arbhdr);
6365     }
6366 }
6367 
6368 
6369 void
free_intvl(INTVL_S ** intvl)6370 free_intvl(INTVL_S **intvl)
6371 {
6372     if(intvl && *intvl){
6373 	free_intvl(&(*intvl)->next);
6374 	fs_give((void **) intvl);
6375     }
6376 }
6377 
6378 
6379 void
free_action(ACTION_S ** action)6380 free_action(ACTION_S **action)
6381 {
6382     if(action && *action){
6383 	if((*action)->from)
6384 	  mail_free_address(&(*action)->from);
6385 	if((*action)->replyto)
6386 	  mail_free_address(&(*action)->replyto);
6387 	if((*action)->fcc)
6388 	  fs_give((void **)&(*action)->fcc);
6389 	if((*action)->litsig)
6390 	  fs_give((void **)&(*action)->litsig);
6391 	if((*action)->sig)
6392 	  fs_give((void **)&(*action)->sig);
6393 	if((*action)->template)
6394 	  fs_give((void **)&(*action)->template);
6395 	if((*action)->scorevalhdrtok)
6396 	  free_hdrtok(&(*action)->scorevalhdrtok);
6397 	if((*action)->cstm)
6398 	  free_list_array(&(*action)->cstm);
6399 	if((*action)->smtp)
6400 	  free_list_array(&(*action)->smtp);
6401 	if((*action)->nntp)
6402 	  free_list_array(&(*action)->nntp);
6403 	if((*action)->nick)
6404 	  fs_give((void **)&(*action)->nick);
6405 	if((*action)->inherit_nick)
6406 	  fs_give((void **)&(*action)->inherit_nick);
6407 	if((*action)->incol)
6408 	  free_color_pair(&(*action)->incol);
6409 	if((*action)->folder)
6410 	  free_pattern(&(*action)->folder);
6411 	if((*action)->index_format)
6412 	  fs_give((void **)&(*action)->index_format);
6413 	if((*action)->keyword_set)
6414 	  free_pattern(&(*action)->keyword_set);
6415 	if((*action)->keyword_clr)
6416 	  free_pattern(&(*action)->keyword_clr);
6417 
6418 	fs_give((void **)action);
6419     }
6420 }
6421 
6422 
6423 /*
6424  * Returns an allocated copy of the pat.
6425  *
6426  * Args   pat -- the source pat
6427  *
6428  * Returns a copy of pat.
6429  */
6430 PAT_S *
copy_pat(PAT_S * pat)6431 copy_pat(PAT_S *pat)
6432 {
6433     PAT_S *new_pat = NULL;
6434 
6435     if(pat){
6436 	new_pat = (PAT_S *)fs_get(sizeof(*new_pat));
6437 	memset((void *)new_pat, 0, sizeof(*new_pat));
6438 
6439 	new_pat->patgrp = copy_patgrp(pat->patgrp);
6440 	new_pat->action = copy_action(pat->action);
6441     }
6442 
6443     return(new_pat);
6444 }
6445 
6446 
6447 /*
6448  * Returns an allocated copy of the patgrp.
6449  *
6450  * Args   patgrp -- the source patgrp
6451  *
6452  * Returns a copy of patgrp.
6453  */
6454 PATGRP_S *
copy_patgrp(PATGRP_S * patgrp)6455 copy_patgrp(PATGRP_S *patgrp)
6456 {
6457     char     *p;
6458     PATGRP_S *new_patgrp = NULL;
6459 
6460     if(patgrp){
6461 	new_patgrp = (PATGRP_S *)fs_get(sizeof(*new_patgrp));
6462 	memset((void *)new_patgrp, 0, sizeof(*new_patgrp));
6463 
6464 	if(patgrp->nick)
6465 	  new_patgrp->nick = cpystr(patgrp->nick);
6466 
6467 	if(patgrp->comment)
6468 	  new_patgrp->comment = cpystr(patgrp->comment);
6469 
6470 	if(patgrp->to){
6471 	    p = pattern_to_string(patgrp->to);
6472 	    new_patgrp->to = string_to_pattern(p);
6473 	    fs_give((void **)&p);
6474 	    new_patgrp->to->not = patgrp->to->not;
6475 	}
6476 
6477 	if(patgrp->from){
6478 	    p = pattern_to_string(patgrp->from);
6479 	    new_patgrp->from = string_to_pattern(p);
6480 	    fs_give((void **)&p);
6481 	    new_patgrp->from->not = patgrp->from->not;
6482 	}
6483 
6484 	if(patgrp->sender){
6485 	    p = pattern_to_string(patgrp->sender);
6486 	    new_patgrp->sender = string_to_pattern(p);
6487 	    fs_give((void **)&p);
6488 	    new_patgrp->sender->not = patgrp->sender->not;
6489 	}
6490 
6491 	if(patgrp->cc){
6492 	    p = pattern_to_string(patgrp->cc);
6493 	    new_patgrp->cc = string_to_pattern(p);
6494 	    fs_give((void **)&p);
6495 	    new_patgrp->cc->not = patgrp->cc->not;
6496 	}
6497 
6498 	if(patgrp->recip){
6499 	    p = pattern_to_string(patgrp->recip);
6500 	    new_patgrp->recip = string_to_pattern(p);
6501 	    fs_give((void **)&p);
6502 	    new_patgrp->recip->not = patgrp->recip->not;
6503 	}
6504 
6505 	if(patgrp->partic){
6506 	    p = pattern_to_string(patgrp->partic);
6507 	    new_patgrp->partic = string_to_pattern(p);
6508 	    fs_give((void **)&p);
6509 	    new_patgrp->partic->not = patgrp->partic->not;
6510 	}
6511 
6512 	if(patgrp->news){
6513 	    p = pattern_to_string(patgrp->news);
6514 	    new_patgrp->news = string_to_pattern(p);
6515 	    fs_give((void **)&p);
6516 	    new_patgrp->news->not = patgrp->news->not;
6517 	}
6518 
6519 	if(patgrp->subj){
6520 	    p = pattern_to_string(patgrp->subj);
6521 	    new_patgrp->subj = string_to_pattern(p);
6522 	    fs_give((void **)&p);
6523 	    new_patgrp->subj->not = patgrp->subj->not;
6524 	}
6525 
6526 	if(patgrp->alltext){
6527 	    p = pattern_to_string(patgrp->alltext);
6528 	    new_patgrp->alltext = string_to_pattern(p);
6529 	    fs_give((void **)&p);
6530 	    new_patgrp->alltext->not = patgrp->alltext->not;
6531 	}
6532 
6533 	if(patgrp->bodytext){
6534 	    p = pattern_to_string(patgrp->bodytext);
6535 	    new_patgrp->bodytext = string_to_pattern(p);
6536 	    fs_give((void **)&p);
6537 	    new_patgrp->bodytext->not = patgrp->bodytext->not;
6538 	}
6539 
6540 	if(patgrp->keyword){
6541 	    p = pattern_to_string(patgrp->keyword);
6542 	    new_patgrp->keyword = string_to_pattern(p);
6543 	    fs_give((void **)&p);
6544 	    new_patgrp->keyword->not = patgrp->keyword->not;
6545 	}
6546 
6547 	if(patgrp->charsets){
6548 	    p = pattern_to_string(patgrp->charsets);
6549 	    new_patgrp->charsets = string_to_pattern(p);
6550 	    fs_give((void **)&p);
6551 	    new_patgrp->charsets->not = patgrp->charsets->not;
6552 	}
6553 
6554 	if(patgrp->charsets_list)
6555 	  new_patgrp->charsets_list = copy_strlist(patgrp->charsets_list);
6556 
6557 	if(patgrp->arbhdr){
6558 	    ARBHDR_S *aa, *a, *new_a;
6559 
6560 	    aa = NULL;
6561 	    for(a = patgrp->arbhdr; a; a = a->next){
6562 		new_a = (ARBHDR_S *)fs_get(sizeof(*new_a));
6563 		memset((void *)new_a, 0, sizeof(*new_a));
6564 
6565 		if(a->field)
6566 		  new_a->field = cpystr(a->field);
6567 
6568 		if(a->p){
6569 		    p = pattern_to_string(a->p);
6570 		    new_a->p = string_to_pattern(p);
6571 		    fs_give((void **)&p);
6572 		    new_a->p->not = a->p->not;
6573 		}
6574 
6575 		new_a->isemptyval = a->isemptyval;
6576 
6577 		if(aa){
6578 		    aa->next = new_a;
6579 		    aa = aa->next;
6580 		}
6581 		else{
6582 		    new_patgrp->arbhdr = new_a;
6583 		    aa = new_patgrp->arbhdr;
6584 		}
6585 	    }
6586 	}
6587 
6588 	new_patgrp->fldr_type = patgrp->fldr_type;
6589 
6590 	if(patgrp->folder){
6591 	    p = pattern_to_string(patgrp->folder);
6592 	    new_patgrp->folder = string_to_pattern(p);
6593 	    fs_give((void **)&p);
6594 	}
6595 
6596 	new_patgrp->inabook = patgrp->inabook;
6597 
6598 	if(patgrp->abooks){
6599 	    p = pattern_to_string(patgrp->abooks);
6600 	    new_patgrp->abooks = string_to_pattern(p);
6601 	    fs_give((void **)&p);
6602 	}
6603 
6604 	new_patgrp->do_score  = patgrp->do_score;
6605 	if(patgrp->score){
6606 	    INTVL_S *intvl, *iv, *new_iv;
6607 
6608 	    intvl = NULL;
6609 	    for(iv = patgrp->score; iv; iv = iv->next){
6610 		new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6611 		memset((void *) new_iv, 0, sizeof(*new_iv));
6612 
6613 		new_iv->imin = iv->imin;
6614 		new_iv->imax = iv->imax;
6615 
6616 		if(intvl){
6617 		    intvl->next = new_iv;
6618 		    intvl = intvl->next;
6619 		}
6620 		else{
6621 		    new_patgrp->score = new_iv;
6622 		    intvl = new_patgrp->score;
6623 		}
6624 	    }
6625 	}
6626 
6627 	new_patgrp->do_age    = patgrp->do_age;
6628 	if(patgrp->age){
6629 	    INTVL_S *intvl, *iv, *new_iv;
6630 
6631 	    intvl = NULL;
6632 	    for(iv = patgrp->age; iv; iv = iv->next){
6633 		new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6634 		memset((void *) new_iv, 0, sizeof(*new_iv));
6635 
6636 		new_iv->imin = iv->imin;
6637 		new_iv->imax = iv->imax;
6638 
6639 		if(intvl){
6640 		    intvl->next = new_iv;
6641 		    intvl = intvl->next;
6642 		}
6643 		else{
6644 		    new_patgrp->age = new_iv;
6645 		    intvl = new_patgrp->age;
6646 		}
6647 	    }
6648 	}
6649 
6650 	new_patgrp->age_uses_sentdate = patgrp->age_uses_sentdate;
6651 
6652 	new_patgrp->do_size    = patgrp->do_size;
6653 	if(patgrp->size){
6654 	    INTVL_S *intvl, *iv, *new_iv;
6655 
6656 	    intvl = NULL;
6657 	    for(iv = patgrp->size; iv; iv = iv->next){
6658 		new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6659 		memset((void *) new_iv, 0, sizeof(*new_iv));
6660 
6661 		new_iv->imin = iv->imin;
6662 		new_iv->imax = iv->imax;
6663 
6664 		if(intvl){
6665 		    intvl->next = new_iv;
6666 		    intvl = intvl->next;
6667 		}
6668 		else{
6669 		    new_patgrp->size = new_iv;
6670 		    intvl = new_patgrp->size;
6671 		}
6672 	    }
6673 	}
6674 
6675 	new_patgrp->stat_new  = patgrp->stat_new;
6676 	new_patgrp->stat_rec  = patgrp->stat_rec;
6677 	new_patgrp->stat_del  = patgrp->stat_del;
6678 	new_patgrp->stat_imp  = patgrp->stat_imp;
6679 	new_patgrp->stat_ans  = patgrp->stat_ans;
6680 
6681 	new_patgrp->stat_8bitsubj = patgrp->stat_8bitsubj;
6682 	new_patgrp->stat_bom  = patgrp->stat_bom;
6683 	new_patgrp->stat_boy  = patgrp->stat_boy;
6684 
6685 	new_patgrp->do_cat    = patgrp->do_cat;
6686 	if(patgrp->cat){
6687 	    INTVL_S *intvl, *iv, *new_iv;
6688 
6689 	    intvl = NULL;
6690 	    for(iv = patgrp->cat; iv; iv = iv->next){
6691 		new_iv = (INTVL_S *) fs_get(sizeof(*new_iv));
6692 		memset((void *) new_iv, 0, sizeof(*new_iv));
6693 
6694 		new_iv->imin = iv->imin;
6695 		new_iv->imax = iv->imax;
6696 
6697 		if(intvl){
6698 		    intvl->next = new_iv;
6699 		    intvl = intvl->next;
6700 		}
6701 		else{
6702 		    new_patgrp->cat = new_iv;
6703 		    intvl = new_patgrp->cat;
6704 		}
6705 	    }
6706 	}
6707 
6708 	if(patgrp->category_cmd)
6709 	  new_patgrp->category_cmd = copy_list_array(patgrp->category_cmd);
6710     }
6711 
6712     return(new_patgrp);
6713 }
6714 
6715 
6716 /*
6717  * Returns an allocated copy of the action.
6718  *
6719  * Args   action -- the source action
6720  *
6721  * Returns a copy of action.
6722  */
6723 ACTION_S *
copy_action(ACTION_S * action)6724 copy_action(ACTION_S *action)
6725 {
6726     ACTION_S *newaction = NULL;
6727     char     *p;
6728 
6729     if(action){
6730 	newaction = (ACTION_S *)fs_get(sizeof(*newaction));
6731 	memset((void *)newaction, 0, sizeof(*newaction));
6732 
6733 	newaction->is_a_role   = action->is_a_role;
6734 	newaction->is_a_incol  = action->is_a_incol;
6735 	newaction->is_a_score  = action->is_a_score;
6736 	newaction->is_a_filter = action->is_a_filter;
6737 	newaction->is_a_other  = action->is_a_other;
6738 	newaction->is_a_srch   = action->is_a_srch;
6739 	newaction->repl_type   = action->repl_type;
6740 	newaction->forw_type   = action->forw_type;
6741 	newaction->comp_type   = action->comp_type;
6742 	newaction->scoreval    = action->scoreval;
6743 	newaction->kill        = action->kill;
6744 	newaction->state_setting_bits = action->state_setting_bits;
6745 	newaction->move_only_if_not_deleted = action->move_only_if_not_deleted;
6746 	newaction->non_terminating = action->non_terminating;
6747 	newaction->sort_is_set = action->sort_is_set;
6748 	newaction->sortorder   = action->sortorder;
6749 	newaction->revsort     = action->revsort;
6750 	newaction->startup_rule = action->startup_rule;
6751 
6752 	if(action->from)
6753 	  newaction->from = copyaddrlist(action->from);
6754 	if(action->replyto)
6755 	  newaction->replyto = copyaddrlist(action->replyto);
6756 	if(action->cstm)
6757 	  newaction->cstm = copy_list_array(action->cstm);
6758 	if(action->smtp)
6759 	  newaction->smtp = copy_list_array(action->smtp);
6760 	if(action->nntp)
6761 	  newaction->nntp = copy_list_array(action->nntp);
6762 	if(action->fcc)
6763 	  newaction->fcc = cpystr(action->fcc);
6764 	if(action->litsig)
6765 	  newaction->litsig = cpystr(action->litsig);
6766 	if(action->sig)
6767 	  newaction->sig = cpystr(action->sig);
6768 	if(action->template)
6769 	  newaction->template = cpystr(action->template);
6770 	if(action->nick)
6771 	  newaction->nick = cpystr(action->nick);
6772 	if(action->inherit_nick)
6773 	  newaction->inherit_nick = cpystr(action->inherit_nick);
6774 	if(action->incol)
6775 	  newaction->incol = new_color_pair(action->incol->fg,
6776 					    action->incol->bg);
6777 	if(action->scorevalhdrtok){
6778 	    newaction->scorevalhdrtok = new_hdrtok(action->scorevalhdrtok->hdrname);
6779 	    if(action->scorevalhdrtok && action->scorevalhdrtok->fieldseps){
6780 		if(newaction->scorevalhdrtok->fieldseps)
6781 		  fs_give((void **) &newaction->scorevalhdrtok->fieldseps);
6782 
6783 		newaction->scorevalhdrtok->fieldseps = cpystr(action->scorevalhdrtok->fieldseps);
6784 	    }
6785 	}
6786 
6787 	if(action->folder){
6788 	    p = pattern_to_string(action->folder);
6789 	    newaction->folder = string_to_pattern(p);
6790 	    fs_give((void **) &p);
6791 	}
6792 
6793 	if(action->keyword_set){
6794 	    p = pattern_to_string(action->keyword_set);
6795 	    newaction->keyword_set = string_to_pattern(p);
6796 	    fs_give((void **) &p);
6797 	}
6798 
6799 	if(action->keyword_clr){
6800 	    p = pattern_to_string(action->keyword_clr);
6801 	    newaction->keyword_clr = string_to_pattern(p);
6802 	    fs_give((void **) &p);
6803 	}
6804 
6805 	if(action->index_format)
6806 	  newaction->index_format = cpystr(action->index_format);
6807     }
6808 
6809     return(newaction);
6810 }
6811 
6812 
6813 /*
6814  * Given a role, return an allocated role. If this role inherits from
6815  * another role, then do the correct inheriting so that the result is
6816  * the role we want to use. The inheriting that is done is just the set
6817  * of set- actions. This is for role stuff, no inheriting happens for scores
6818  * or for colors.
6819  *
6820  * Args   role -- The source role
6821  *
6822  * Returns a role.
6823  */
6824 ACTION_S *
combine_inherited_role(ACTION_S * role)6825 combine_inherited_role(ACTION_S *role)
6826 {
6827     PAT_STATE pstate;
6828     PAT_S    *pat;
6829 
6830     /*
6831      * Protect against loops in the role inheritance.
6832      */
6833     if(role && role->is_a_role && nonempty_patterns(ROLE_DO_ROLES, &pstate))
6834       for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
6835 	if(pat->action){
6836 	    if(pat->action == role)
6837 	      pat->action->been_here_before = 1;
6838 	    else
6839 	      pat->action->been_here_before = 0;
6840 	}
6841 
6842     return(combine_inherited_role_guts(role));
6843 }
6844 
6845 
6846 ACTION_S *
combine_inherited_role_guts(ACTION_S * role)6847 combine_inherited_role_guts(ACTION_S *role)
6848 {
6849     ACTION_S *newrole = NULL, *inherit_role = NULL;
6850     PAT_STATE pstate;
6851 
6852     if(role && role->is_a_role){
6853 	newrole = (ACTION_S *)fs_get(sizeof(*newrole));
6854 	memset((void *)newrole, 0, sizeof(*newrole));
6855 
6856 	newrole->repl_type  = role->repl_type;
6857 	newrole->forw_type  = role->forw_type;
6858 	newrole->comp_type  = role->comp_type;
6859 	newrole->is_a_role  = role->is_a_role;
6860 
6861 	if(role->inherit_nick && role->inherit_nick[0] &&
6862 	   nonempty_patterns(ROLE_DO_ROLES, &pstate)){
6863 	    PAT_S    *pat;
6864 
6865 	    /* find the inherit_nick pattern */
6866 	    for(pat = first_pattern(&pstate);
6867 		pat;
6868 		pat = next_pattern(&pstate)){
6869 		if(pat->patgrp &&
6870 		   pat->patgrp->nick &&
6871 		   !strucmp(role->inherit_nick, pat->patgrp->nick)){
6872 		    /* found it, if it has a role, use it */
6873 		    if(!pat->action->been_here_before){
6874 			pat->action->been_here_before = 1;
6875 			inherit_role = pat->action;
6876 		    }
6877 
6878 		    break;
6879 		}
6880 	    }
6881 
6882 	    /*
6883 	     * inherit_role might inherit further from other roles.
6884 	     * In any case, we copy it so that we'll consistently have
6885 	     * an allocated copy.
6886 	     */
6887 	    if(inherit_role){
6888 		if(inherit_role->inherit_nick && inherit_role->inherit_nick[0])
6889 		  inherit_role = combine_inherited_role_guts(inherit_role);
6890 		else
6891 		  inherit_role = copy_action(inherit_role);
6892 	    }
6893 	}
6894 
6895 	if(role->from)
6896 	  newrole->from = copyaddrlist(role->from);
6897 	else if(inherit_role && inherit_role->from)
6898 	  newrole->from = copyaddrlist(inherit_role->from);
6899 
6900 	if(role->replyto)
6901 	  newrole->replyto = copyaddrlist(role->replyto);
6902 	else if(inherit_role && inherit_role->replyto)
6903 	  newrole->replyto = copyaddrlist(inherit_role->replyto);
6904 
6905 	if(role->fcc)
6906 	  newrole->fcc = cpystr(role->fcc);
6907 	else if(inherit_role && inherit_role->fcc)
6908 	  newrole->fcc = cpystr(inherit_role->fcc);
6909 
6910 	if(role->litsig)
6911 	  newrole->litsig = cpystr(role->litsig);
6912 	else if(inherit_role && inherit_role->litsig)
6913 	  newrole->litsig = cpystr(inherit_role->litsig);
6914 
6915 	if(role->sig)
6916 	  newrole->sig = cpystr(role->sig);
6917 	else if(inherit_role && inherit_role->sig)
6918 	  newrole->sig = cpystr(inherit_role->sig);
6919 
6920 	if(role->template)
6921 	  newrole->template = cpystr(role->template);
6922 	else if(inherit_role && inherit_role->template)
6923 	  newrole->template = cpystr(inherit_role->template);
6924 
6925 	if(role->cstm)
6926 	  newrole->cstm = copy_list_array(role->cstm);
6927 	else if(inherit_role && inherit_role->cstm)
6928 	  newrole->cstm = copy_list_array(inherit_role->cstm);
6929 
6930 	if(role->smtp)
6931 	  newrole->smtp = copy_list_array(role->smtp);
6932 	else if(inherit_role && inherit_role->smtp)
6933 	  newrole->smtp = copy_list_array(inherit_role->smtp);
6934 
6935 	if(role->nntp)
6936 	  newrole->nntp = copy_list_array(role->nntp);
6937 	else if(inherit_role && inherit_role->nntp)
6938 	  newrole->nntp = copy_list_array(inherit_role->nntp);
6939 
6940 	if(role->nick)
6941 	  newrole->nick = cpystr(role->nick);
6942 
6943 	if(inherit_role)
6944 	  free_action(&inherit_role);
6945     }
6946 
6947     return(newrole);
6948 }
6949 
6950 
6951 void
mail_expunge_prefilter(MAILSTREAM * stream,int flags)6952 mail_expunge_prefilter(MAILSTREAM *stream, int flags)
6953 {
6954     int sfdo_state = 0,		/* Some Filter Depends On or Sets State  */
6955 	sfdo_scores = 0,	/* Some Filter Depends On Scores */
6956 	ssdo_state = 0;		/* Some Score Depends On State   */
6957 
6958     if(!stream || !sp_flagged(stream, SP_LOCKED))
6959       return;
6960 
6961     /*
6962      * An Expunge causes a re-examination of the filters to
6963      * see if any state changes have caused new matches.
6964      */
6965 
6966     sfdo_scores = (scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS);
6967     if(sfdo_scores)
6968       ssdo_state = (scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP);
6969 
6970     if(!(sfdo_scores && ssdo_state))
6971       sfdo_state = some_filter_depends_on_active_state();
6972 
6973 
6974     if(sfdo_state || (sfdo_scores && ssdo_state)){
6975 	if(sfdo_scores && ssdo_state)
6976 	  clear_folder_scores(stream);
6977 
6978 	reprocess_filter_patterns(stream, sp_msgmap(stream),
6979 				  (flags & MI_CLOSING) |
6980 				  MI_REFILTERING | MI_STATECHGONLY);
6981     }
6982 }
6983 
6984 
6985 /*----------------------------------------------------------------------
6986   Dispatch messages matching FILTER patterns.
6987 
6988   Args:
6989 	stream -- mail stream serving messages
6990 	msgmap -- sequence to msgno mapping table
6991 	recent -- number of recent messages to check (but really only its
6992 	               nonzeroness is used)
6993 
6994  When we're done, any filtered messages are filtered and the message
6995  mapping table has any filtered messages removed.
6996   ---*/
6997 void
process_filter_patterns(MAILSTREAM * stream,MSGNO_S * msgmap,long int recent)6998 process_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, long int recent)
6999 {
7000     long	  i, n, raw;
7001     imapuid_t     uid;
7002     int           we_cancel = 0, any_msgs = 0, any_to_filter = 0;
7003     int		  exbits, nt = 0, pending_actions = 0, for_debugging = 0;
7004     int           cleared_index_cache = 0;
7005     long          rflags = ROLE_DO_FILTER;
7006     char	 *nick = NULL;
7007     char          busymsg[80];
7008     MSGNO_S      *tmpmap = NULL;
7009     MESSAGECACHE *mc;
7010     PAT_S        *pat, *nextpat = NULL;
7011     SEARCHPGM	 *pgm = NULL;
7012     SEARCHSET	 *srchset = NULL;
7013     long          flags = (SE_NOPREFETCH|SE_FREE);
7014     PAT_STATE     pstate;
7015 
7016     dprint((5, "process_filter_patterns(stream=%s, recent=%ld)\n",
7017 	    !stream                             ? "<null>"                   :
7018 	     sp_flagged(stream, SP_INBOX)        ? "inbox"                   :
7019 	      stream->original_mailbox            ? stream->original_mailbox :
7020 	       stream->mailbox                     ? stream->mailbox         :
7021 				                      "?",
7022 	    recent));
7023 
7024     if(!msgmap || !stream)
7025       return;
7026 
7027     if(!recent)
7028       sp_set_flags(stream, sp_flags(stream) | SP_FILTERED);
7029 
7030     while(stream && stream->nmsgs && nonempty_patterns(rflags, &pstate)){
7031 
7032 	for_debugging++;
7033 	pending_actions = 0;
7034 	nextpat = NULL;
7035 
7036 	uid = mail_uid(stream, stream->nmsgs);
7037 
7038 	/*
7039 	 * Some of the search stuff won't work on old servers so we
7040 	 * get the data and search locally. Big performance hit.
7041 	 */
7042 	if(is_imap_stream(stream) && !modern_imap_stream(stream))
7043 	  flags |= SE_NOSERVER;
7044 
7045 	/*
7046 	 * ignore all previously filtered messages
7047 	 * and, if requested, anything not a recent
7048 	 * arrival...
7049 	 *
7050 	 * Here we're using spare6 (MN_STMP), meaning we'll only
7051 	 * search the ones with spare6 marked, new messages coming
7052 	 * in will not be considered.  There used to be orig_nmsgs,
7053 	 * which kept track of this, but if a message gets expunged,
7054 	 * then a new message could be lower than orig_nmsgs.
7055 	 */
7056 	for(i = 1; i <= stream->nmsgs; i++)
7057 	  if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7058 	      if(exbits & MSG_EX_FILTERED){
7059 		  if((mc = mail_elt(stream, i)) != NULL)
7060 		    mc->spare6 = 0;
7061 	      }
7062 	      else if(!recent || !(exbits & MSG_EX_TESTED)){
7063 		  if((mc = mail_elt(stream, i)) != NULL)
7064 		    mc->spare6 = 1;
7065 
7066 		  any_to_filter++;
7067 	      }
7068 	      else if((mc = mail_elt(stream, i)) != NULL)
7069 		mc->spare6 = 0;
7070 	  }
7071 	  else{
7072 	      if((mc = mail_elt(stream, i)) != NULL)
7073 	        mc->spare6 = !recent;
7074 
7075 	      any_to_filter += !recent;
7076 	  }
7077 
7078 	if(!any_to_filter){
7079 	  dprint((5, "No messages need filtering\n"));
7080 	}
7081 
7082 	/* Then start searching */
7083 	for(pat = first_pattern(&pstate); any_to_filter && pat; pat = nextpat){
7084 	    nextpat = next_pattern(&pstate);
7085 	    dprint((5,
7086 		"Trying filter \"%s\"\n",
7087 		(pat->patgrp && pat->patgrp->nick)
7088 		    ? pat->patgrp->nick : "?"));
7089 	    if(pat->patgrp && !pat->patgrp->bogus
7090 	       && pat->action && !pat->action->bogus
7091 	       && !trivial_patgrp(pat->patgrp)
7092 	       && match_pattern_folder(pat->patgrp, stream)
7093 	       && !match_pattern_folder_specific(pat->action->folder,
7094 						 stream, FOR_FILTER)){
7095 
7096 		/*
7097 		 * We could just keep track of spare6 accurately when
7098 		 * we change the msgno_exceptions flags, but...
7099 		 */
7100 		for(i = 1; i <= stream->nmsgs; i++){
7101 		    if((mc=mail_elt(stream, i)) && mc->spare6){
7102 			if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7103 			    if(exbits & MSG_EX_FILTERED)
7104 			      mc->sequence = 0;
7105 			    else if(!recent || !(exbits & MSG_EX_TESTED))
7106 			      mc->sequence = 1;
7107 			    else
7108 			      mc->sequence = 0;
7109 			}
7110 			else
7111 			  mc->sequence = !recent;
7112 		    }
7113 		    else
7114 		      mc->sequence = 0;
7115 		}
7116 
7117 		if(!(srchset = build_searchset(stream))){
7118 		    dprint((5, "Empty searchset\n"));
7119 		    continue;		/* nothing to search, move on */
7120 		}
7121 
7122 #ifdef DEBUG
7123 		{SEARCHSET *s;
7124 		  dprint((5, "searchset="));
7125 		  for(s = srchset; s; s = s->next){
7126 		    if(s->first == s->last || s->last == 0L){
7127 		      dprint((5, " %ld", s->first));
7128 		    }
7129 		    else{
7130 		      dprint((5, " %ld-%ld", s->first, s->last));
7131 		    }
7132 		  }
7133 		  dprint((5, "\n"));
7134 		}
7135 #endif
7136 		nick = (pat && pat->patgrp && pat->patgrp->nick
7137 			&& pat->patgrp->nick[0]) ? pat->patgrp->nick : NULL;
7138 		snprintf(busymsg, sizeof(busymsg), _("Processing filter \"%s\""),
7139 			nick ? nick : "?");
7140 
7141 		/*
7142 		 * The strange last argument is so that the busy message
7143 		 * won't come out until after a second if the user sets
7144 		 * the feature to quell "filtering done". That's because
7145 		 * they are presumably interested in the filtering actions
7146 		 * themselves more than what is happening, so they'd
7147 		 * rather see the action messages instead of the processing
7148 		 * message. That's my theory anyway.
7149 		 */
7150 		if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
7151 		  any_msgs = we_cancel = busy_cue(busymsg, NULL,
7152 				   F_ON(F_QUELL_FILTER_DONE_MSG, ps_global)
7153 					? 1 : 0);
7154 
7155 		if(pat->patgrp->stat_bom != PAT_STAT_EITHER){
7156 		    if(pat->patgrp->stat_bom == PAT_STAT_YES){
7157 			if(!ps_global->beginning_of_month){
7158 			    dprint((5,
7159 		    "Filter %s wants beginning of month and it isn't bom\n",
7160 				   nick ? nick : "?"));
7161 			    continue;
7162 			}
7163 		    }
7164 		    else if(pat->patgrp->stat_bom == PAT_STAT_NO){
7165 			if(ps_global->beginning_of_month){
7166 			    dprint((5,
7167 		   "Filter %s does not want beginning of month and it is bom\n",
7168 				   nick ? nick : "?"));
7169 			    continue;
7170 			}
7171 		    }
7172 		}
7173 
7174 		if(pat->patgrp->stat_boy != PAT_STAT_EITHER){
7175 		    if(pat->patgrp->stat_boy == PAT_STAT_YES){
7176 			if(!ps_global->beginning_of_year){
7177 			    dprint((5,
7178 		    "Filter %s wants beginning of year and it isn't boy\n",
7179 				   nick ? nick : "?"));
7180 			    continue;
7181 			}
7182 		    }
7183 		    else if(pat->patgrp->stat_boy == PAT_STAT_NO){
7184 			if(ps_global->beginning_of_year){
7185 			    dprint((5,
7186 		   "Filter %s does not want beginning of year and it is boy\n",
7187 				   nick ? nick : "?"));
7188 			    continue;
7189 			}
7190 		    }
7191 		}
7192 
7193 		pgm = match_pattern_srchpgm(pat->patgrp, stream, srchset);
7194 
7195 		pine_mail_search_full(stream, "UTF-8", pgm, flags);
7196 
7197 		/* check scores */
7198 		if(scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS &&
7199 		   pat->patgrp->do_score){
7200 		    SEARCHSET *s, *ss;
7201 
7202 		    /*
7203 		     * Build a searchset so we can get all the scores we
7204 		     * need and only the scores we need efficiently.
7205 		     */
7206 
7207 		    for(i = 1; i <= stream->nmsgs; i++)
7208 		      if((mc = mail_elt(stream, i)) != NULL)
7209 		        mc->sequence = 0;
7210 
7211 		    for(s = srchset; s; s = s->next)
7212 		      for(i = s->first; i <= s->last; i++)
7213 			if(i > 0L && stream && i <= stream->nmsgs
7214 			   && (mc=mail_elt(stream, i)) && mc->searched &&
7215 			   get_msg_score(stream, i) == SCORE_UNDEF)
7216 			  mc->sequence = 1;
7217 
7218 		    if((ss = build_searchset(stream)) != NULL){
7219 			(void)calculate_some_scores(stream, ss, 0);
7220 			mail_free_searchset(&ss);
7221 		    }
7222 
7223 		    /*
7224 		     * Now check the patterns which have matched so far
7225 		     * to see if their score is in the score interval.
7226 		     */
7227 		    for(s = srchset; s; s = s->next)
7228 		      for(i = s->first; i <= s->last; i++)
7229 			if(i > 0L && stream && i <= stream->nmsgs
7230 			   && (mc=mail_elt(stream, i)) && mc->searched){
7231 			    long score;
7232 
7233 			    score = get_msg_score(stream, i);
7234 
7235 			    /*
7236 			     * If the score is outside all of the intervals,
7237 			     * turn off the searched bit.
7238 			     * So that means we check each interval and if
7239 			     * it is inside any interval we stop and leave
7240 			     * the bit set. If it is outside we keep checking.
7241 			     */
7242 			    if(score != SCORE_UNDEF){
7243 				INTVL_S *iv;
7244 
7245 				for(iv = pat->patgrp->score; iv; iv = iv->next)
7246 				  if(score >= iv->imin && score <= iv->imax)
7247 				    break;
7248 
7249 				if(!iv)
7250 				  mc->searched = NIL;
7251 			    }
7252 			}
7253 		}
7254 
7255 		/* check for 8bit subject match or not */
7256 		if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER)
7257 		  find_8bitsubj_in_messages(stream, srchset,
7258 					    pat->patgrp->stat_8bitsubj, 0);
7259 
7260 		/* if there are still matches, check for charset matches */
7261 		if(pat->patgrp->charsets)
7262 		  find_charsets_in_messages(stream, srchset, pat->patgrp, 0);
7263 
7264 		if(pat->patgrp->inabook != IAB_EITHER)
7265 		  address_in_abook(stream, srchset, pat->patgrp->inabook, pat->patgrp->abooks);
7266 
7267 		/* Still matches? Run the categorization command on each msg. */
7268 		if(pith_opt_filter_pattern_cmd)
7269 		  (*pith_opt_filter_pattern_cmd)(pat->patgrp->category_cmd, srchset, stream, pat->patgrp->cat_lim, pat->patgrp->cat);
7270 
7271 		if(we_cancel){
7272 		    cancel_busy_cue(-1);
7273 		    we_cancel = 0;
7274 		}
7275 
7276 		nt = pat->action->non_terminating;
7277 		pending_actions = MAX(nt, pending_actions);
7278 
7279 		/*
7280 		 * Change some state bits.
7281 		 * This used to only happen if kill was not set, but
7282 		 * it can be useful to Delete a message even if killing.
7283 		 * That way, it will show up in another pine that isn't
7284 		 * running the same filter as Deleted, so the user won't
7285 		 * bother looking at it.  Hubert 2004-11-16
7286 		 */
7287 		if(pat->action->state_setting_bits
7288 		   || pat->action->keyword_set
7289 		   || pat->action->keyword_clr){
7290 		    tmpmap = NULL;
7291 		    mn_init(&tmpmap, stream->nmsgs);
7292 
7293 		    for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
7294 		      if((mc = mail_elt(stream, i)) && mc->searched
7295 			 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
7296 			      && (exbits & MSG_EX_FILTERED))){
7297 			if(!n++){
7298 			    mn_set_cur(tmpmap, i);
7299 			}
7300 			else{
7301 			    mn_add_cur(tmpmap, i);
7302 			}
7303 		      }
7304 
7305 		    if(n){
7306 			long flagbits;
7307 			char     **keywords_to_set = NULL,
7308 			         **keywords_to_clr = NULL;
7309 			PATTERN_S *pp;
7310 			int        cnt;
7311 
7312 			flagbits = pat->action->state_setting_bits;
7313 
7314 			if(pat->action->keyword_set){
7315 			    for(cnt = 0, pp = pat->action->keyword_set;
7316 				pp; pp = pp->next)
7317 			      cnt++;
7318 
7319 			    keywords_to_set = (char **) fs_get((cnt+1) *
7320 						    sizeof(*keywords_to_set));
7321 			    memset(keywords_to_set, 0,
7322 				   (cnt+1) * sizeof(*keywords_to_set));
7323 			    for(cnt = 0, pp = pat->action->keyword_set;
7324 				pp; pp = pp->next){
7325 				char *q;
7326 
7327 				q = nick_to_keyword(pp->substring);
7328 				if(q && q[0])
7329 				  keywords_to_set[cnt++] = cpystr(q);
7330 			    }
7331 
7332 			    flagbits |= F_KEYWORD;
7333 			}
7334 
7335 			if(pat->action->keyword_clr){
7336 			    for(cnt = 0, pp = pat->action->keyword_clr;
7337 				pp; pp = pp->next)
7338 			      cnt++;
7339 
7340 			    keywords_to_clr = (char **) fs_get((cnt+1) *
7341 						    sizeof(*keywords_to_clr));
7342 			    memset(keywords_to_clr, 0,
7343 				   (cnt+1) * sizeof(*keywords_to_clr));
7344 			    for(cnt = 0, pp = pat->action->keyword_clr;
7345 				pp; pp = pp->next){
7346 				char *q;
7347 
7348 				q = nick_to_keyword(pp->substring);
7349 				if(q && q[0])
7350 				  keywords_to_clr[cnt++] = cpystr(q);
7351 			    }
7352 
7353 			    flagbits |= F_UNKEYWORD;
7354 			}
7355 
7356 			set_some_flags(stream, tmpmap, flagbits,
7357 				       keywords_to_set, keywords_to_clr, 1,
7358 				       nick);
7359 		    }
7360 
7361 		    mn_give(&tmpmap);
7362 		}
7363 
7364 		/*
7365 		 * The two halves of the if-else are almost the same and
7366 		 * could probably be combined cleverly. The if clause
7367 		 * is simply setting the MSG_EX_FILTERED bit, and leaving
7368 		 * n set to zero. The msgno_exclude is not done in this case.
7369 		 * The else clause excludes each message (because it is
7370 		 * either filtered into nothing or moved to folder). The
7371 		 * exclude messes with the msgmap and that changes max_msgno,
7372 		 * so the loop control is a little tricky.
7373 		 */
7374 		if(!(pat->action->kill || pat->action->folder)){
7375 		  n = 0L;
7376 		  for(i = 1L; i <= mn_get_total(msgmap); i++)
7377 		    if((raw = mn_m2raw(msgmap, i)) > 0L
7378 		       && stream && raw <= stream->nmsgs
7379 		       && (mc = mail_elt(stream, raw)) && mc->searched){
7380 		        dprint((5,
7381 			    "FILTER matching \"%s\": msg %ld%s\n",
7382 			    nick ? nick : "unnamed",
7383 			    raw, nt ? " (dont stop)" : ""));
7384 		        if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
7385 			  exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7386 		        else
7387 			  exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7388 
7389 			/*
7390 			 * If this matched an earlier non-terminating rule
7391 			 * we've been keeping track of that so that we can
7392 			 * turn it into a permanent match at the end.
7393 			 * However, now we've matched another rule that is
7394 			 * terminating so we don't have to worry about it
7395 			 * anymore. Turn off the flag.
7396 			 */
7397 			if(!nt && exbits & MSG_EX_FILTONCE)
7398 			  exbits ^= MSG_EX_FILTONCE;
7399 
7400 			exbits &= ~MSG_EX_STATECHG;
7401 
7402 		        msgno_exceptions(stream, raw, "0", &exbits, TRUE);
7403 		    }
7404 		}
7405 		else{
7406 		  for(i = 1L, n = 0L; i <= mn_get_total(msgmap); )
7407 		    if((raw = mn_m2raw(msgmap, i))
7408 		       && raw > 0L && stream && raw <= stream->nmsgs
7409 		       && (mc = mail_elt(stream, raw)) && mc->searched){
7410 		        dprint((5,
7411 			      "FILTER matching \"%s\": msg %ld %s%s\n",
7412 			      nick ? nick : "unnamed",
7413 			      raw, pat->action->folder ? "filed" : "killed",
7414 			      nt ? " (dont stop)" : ""));
7415 			if(nt)
7416 			  i++;
7417 			else{
7418 			    if(!cleared_index_cache
7419 			       && stream == ps_global->mail_stream){
7420 				cleared_index_cache = 1;
7421 				clear_index_cache(stream, 0);
7422 			    }
7423 
7424 			    msgno_exclude(stream, msgmap, i, 1);
7425 			    /*
7426 			     * If this message is new, decrement
7427 			     * new_mail_count. Previously, the caller would
7428 			     * do this by counting MN_EXCLUDE before and after,
7429 			     * but the results weren't accurate in the case
7430 			     * where new messages arrived while filtering,
7431 			     * or the filtered message could have gotten
7432 			     * expunged.
7433 			     */
7434 			    if(msgno_exceptions(stream, raw, "0", &exbits,
7435 						FALSE)
7436 			       && (exbits & MSG_EX_RECENT)){
7437 				long l, ll;
7438 
7439 			        l = sp_new_mail_count(stream);
7440 			        ll = sp_recent_since_visited(stream);
7441 				dprint((5, "New message being filtered, decrement new_mail_count: %ld -> %ld\n", l, l-1L));
7442 			        if(l > 0L)
7443 				  sp_set_new_mail_count(stream, l-1L);
7444 			        if(ll > 0L)
7445 				  sp_set_recent_since_visited(stream, ll-1L);
7446 			    }
7447 			}
7448 
7449 		        if(msgno_exceptions(stream, raw, "0", &exbits, FALSE))
7450 			  exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7451 		        else
7452 			  exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED);
7453 
7454 			/* set pending exclusion  for later */
7455 			if(nt)
7456 			  exbits |= MSG_EX_PEND_EXLD;
7457 
7458 			/*
7459 			 * If this matched an earlier non-terminating rule
7460 			 * we've been keeping track of that so that we can
7461 			 * turn it into a permanent match at the end.
7462 			 * However, now we've matched another rule that is
7463 			 * terminating so we don't have to worry about it
7464 			 * anymore. Turn off the flags.
7465 			 */
7466 			if(!nt && exbits & MSG_EX_FILTONCE){
7467 			    exbits ^= MSG_EX_FILTONCE;
7468 
7469 			    /* we've already excluded it, too */
7470 			    if(exbits & MSG_EX_PEND_EXLD)
7471 			      exbits ^= MSG_EX_PEND_EXLD;
7472 			}
7473 
7474 			exbits &= ~MSG_EX_STATECHG;
7475 
7476 			msgno_exceptions(stream, raw, "0", &exbits, TRUE);
7477 			n++;
7478 		    }
7479 		    else
7480 		      i++;
7481 		}
7482 
7483 		if(n && pat->action->folder){
7484 		    PATTERN_S *p;
7485 		    int	       err = 0;
7486 
7487 		    tmpmap = NULL;
7488 		    mn_init(&tmpmap, stream->nmsgs);
7489 
7490 		    /*
7491 		     * For everything matching msg that hasn't
7492 		     * already been saved somewhere, do it...
7493 		     */
7494 		    for(i = 1L, n = 0L; i <= stream->nmsgs; i++)
7495 		      if((mc = mail_elt(stream, i)) && mc->searched
7496 			 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
7497 			      && (exbits & MSG_EX_FILED))){
7498 			if(!n++){
7499 			    mn_set_cur(tmpmap, i);
7500 			}
7501 			else{
7502 			    mn_add_cur(tmpmap, i);
7503 			}
7504 		      }
7505 
7506 		    /*
7507 		     * Remove already deleted messages from the tmp
7508 		     * message map.
7509 		     * There is a bug with this. If a filter moves a
7510 		     * message to another folder _and_ sets the deleted
7511 		     * status, then the setting of the deleted status
7512 		     * will already have happened above in set_some_flags.
7513 		     * So if the move_only_if_not_deleted bit is set that
7514 		     * message will never be moved. A workaround for the
7515 		     * user is to not set the move-only-if-not-deleted
7516 		     * option.
7517 		     */
7518 		    if(n && pat->action->move_only_if_not_deleted){
7519 			char         *seq;
7520 			MSGNO_S      *tmpmap2 = NULL;
7521 			long          nn = 0L;
7522 			MESSAGECACHE *mc;
7523 
7524 			mn_init(&tmpmap2, stream->nmsgs);
7525 
7526 			/*
7527 			 * First, make sure elts are valid for all the
7528 			 * interesting messages.
7529 			 */
7530 			if((seq = invalid_elt_sequence(stream, tmpmap)) != NULL){
7531 			    pine_mail_fetch_flags(stream, seq, NIL);
7532 			    fs_give((void **) &seq);
7533 			}
7534 
7535 			for(i = mn_first_cur(tmpmap); i > 0L;
7536 			    i = mn_next_cur(tmpmap)){
7537 			    mc = ((raw = mn_m2raw(tmpmap, i)) > 0L
7538 			          && stream && raw <= stream->nmsgs)
7539 				    ? mail_elt(stream, raw) : NULL;
7540 			    if(mc && !mc->deleted){
7541 				if(!nn++){
7542 				    mn_set_cur(tmpmap2, i);
7543 				}
7544 				else{
7545 				    mn_add_cur(tmpmap2, i);
7546 				}
7547 			    }
7548 			}
7549 
7550 			mn_give(&tmpmap);
7551 			tmpmap = tmpmap2;
7552 			n = nn;
7553 		    }
7554 
7555 		    if(n){
7556 			for(p = pat->action->folder; p; p = p->next){
7557 			  int dval;
7558 			  int flags_for_save;
7559 			  char *processed_name;
7560 
7561 			  /* does this filter set delete bit? ... */
7562 			  convert_statebits_to_vals(pat->action->state_setting_bits, &dval, NULL, NULL, NULL);
7563 			  /* ... if so, tell save not to fix it before copy */
7564 			  flags_for_save = SV_FOR_FILT | SV_INBOXWOCNTXT |
7565 				  (nt ? 0 : SV_DELETE) |
7566 				  ((dval != ACT_STAT_SET) ? SV_FIX_DELS : 0);
7567 			  processed_name = detoken_src(p->substring,
7568 						       FOR_FILT, NULL,
7569 						       NULL, NULL, NULL);
7570 			  if(move_filtered_msgs(stream, tmpmap,
7571 					    (processed_name && *processed_name)
7572 					      ? processed_name : p->substring,
7573 					    flags_for_save, nick)){
7574 			      /*
7575 			       * If we filtered into the current
7576 			       * folder, chuck a ping down the
7577 			       * stream so the user can notice it
7578 			       * before the next new mail check...
7579 			       */
7580 			      if(ps_global->mail_stream
7581 				 && ps_global->mail_stream != stream
7582 				 && match_pattern_folder_specific(
7583 						 pat->action->folder,
7584 						 ps_global->mail_stream,
7585 						 FOR_FILTER)){
7586 				  (void) pine_mail_ping(ps_global->mail_stream);
7587 			      }
7588 			  }
7589 			  else{
7590 			      err = 1;
7591 			      break;
7592 			  }
7593 
7594 			  if(processed_name)
7595 			    fs_give((void **) &processed_name);
7596 			}
7597 
7598 			if(!err)
7599 			  for(n = mn_first_cur(tmpmap);
7600 			      n > 0L;
7601 			      n = mn_next_cur(tmpmap)){
7602 
7603 			      if(msgno_exceptions(stream, mn_m2raw(tmpmap, n),
7604 						  "0", &exbits, FALSE))
7605 				exbits |= (nt ? MSG_EX_FILEONCE : MSG_EX_FILED);
7606 			      else
7607 				exbits = (nt ? MSG_EX_FILEONCE : MSG_EX_FILED);
7608 
7609 			      exbits &= ~MSG_EX_STATECHG;
7610 
7611 			      msgno_exceptions(stream, mn_m2raw(tmpmap, n),
7612 					       "0", &exbits, TRUE);
7613 			  }
7614 		    }
7615 
7616 		    mn_give(&tmpmap);
7617 		}
7618 
7619 		mail_free_searchset(&srchset);
7620 	    }
7621 
7622 	    /*
7623 	     * If this is the last rule,
7624 	     * we make sure we delete messages that we delayed deleting
7625 	     * in the save. We delayed so that the deletion wouldn't have
7626 	     * an effect on later rules. We convert any temporary
7627 	     * FILED (FILEONCE) and FILTERED (FILTONCE) flags
7628 	     * (which were set by an earlier non-terminating rule)
7629 	     * to permanent. We also exclude some messages from the view.
7630 	     */
7631 	    if(pending_actions && !nextpat){
7632 
7633 		pending_actions = 0;
7634 		tmpmap = NULL;
7635 		mn_init(&tmpmap, stream->nmsgs);
7636 
7637 		for(i = 1L, n = 0L; i <= mn_get_total(msgmap); i++){
7638 
7639 		    raw = mn_m2raw(msgmap, i);
7640 		    if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){
7641 			if(exbits & MSG_EX_FILEONCE){
7642 			    if(!n++){
7643 				mn_set_cur(tmpmap, raw);
7644 			    }
7645 			    else{
7646 				mn_add_cur(tmpmap, raw);
7647 			    }
7648 			}
7649 		    }
7650 		}
7651 
7652 		if(n)
7653 		  set_some_flags(stream, tmpmap, F_DEL, NULL, NULL, 0, NULL);
7654 
7655 		mn_give(&tmpmap);
7656 
7657 		for(i = 1L; i <= mn_get_total(msgmap); i++){
7658 		    raw = mn_m2raw(msgmap, i);
7659 		    if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){
7660 			if(exbits & MSG_EX_PEND_EXLD){
7661 			    if(!cleared_index_cache
7662 			       && stream == ps_global->mail_stream){
7663 				cleared_index_cache = 1;
7664 				clear_index_cache(stream, 0);
7665 			    }
7666 
7667 			    msgno_exclude(stream, msgmap, i, 1);
7668 			    if(msgno_exceptions(stream, raw, "0",
7669 						&exbits, FALSE)
7670 			       && (exbits & MSG_EX_RECENT)){
7671 				long l, ll;
7672 
7673 				/*
7674 				 * If this message is new, decrement
7675 				 * new_mail_count.  See the above
7676 				 * call to msgno_exclude.
7677 				 */
7678 				l = sp_new_mail_count(stream);
7679 				ll = sp_recent_since_visited(stream);
7680 				dprint((5, "New message being filtered. Decrement new_mail_count: %ld -> %ld\n", l, l-1L));
7681 				if(l > 0L)
7682 				  sp_set_new_mail_count(stream, l - 1L);
7683 				if(ll > 0L)
7684 				  sp_set_recent_since_visited(stream, ll - 1L);
7685 			    }
7686 
7687 			    i--;   /* to compensate for loop's i++ */
7688 			}
7689 
7690 			/* get rid of temporary flags */
7691 			if(exbits & (MSG_EX_FILTONCE | MSG_EX_FILEONCE |
7692 			             MSG_EX_PEND_EXLD)){
7693 			    if(exbits & MSG_EX_FILTONCE){
7694 				/* convert to permanent */
7695 				exbits ^= MSG_EX_FILTONCE;
7696 				exbits |= MSG_EX_FILTERED;
7697 			    }
7698 
7699 			    /* convert to permanent */
7700 			    if(exbits & MSG_EX_FILEONCE){
7701 				exbits ^= MSG_EX_FILEONCE;
7702 				exbits |= MSG_EX_FILED;
7703 			    }
7704 
7705 			    if(exbits & MSG_EX_PEND_EXLD)
7706 			      exbits ^= MSG_EX_PEND_EXLD;
7707 
7708 			    exbits &= ~MSG_EX_STATECHG;
7709 
7710 			    msgno_exceptions(stream, raw, "0", &exbits,TRUE);
7711 			}
7712 		    }
7713 		}
7714 	    }
7715 	}
7716 
7717 	/* New mail arrival means start over */
7718 	if(mail_uid(stream, stream->nmsgs) == uid)
7719 	  break;
7720 	/* else, go again */
7721 
7722 	recent = 1; /* only check recent ones now */
7723     }
7724 
7725     if(!recent){
7726 	/* clear status change flags */
7727 	for(i = 1; i <= stream->nmsgs; i++){
7728 	    if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7729 		if(exbits & MSG_EX_STATECHG){
7730 		    exbits &= ~MSG_EX_STATECHG;
7731 		    msgno_exceptions(stream, i, "0", &exbits, TRUE);
7732 		}
7733 	    }
7734 	}
7735     }
7736 
7737     /* clear any private "recent" flags and add TESTED flag */
7738     for(i = 1; i <= stream->nmsgs; i++){
7739 	if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7740 	    if(exbits & MSG_EX_RECENT
7741 	       || !(exbits & MSG_EX_TESTED)
7742 	       || (!recent && exbits & MSG_EX_STATECHG)){
7743 		exbits &= ~MSG_EX_RECENT;
7744 		exbits |= MSG_EX_TESTED;
7745 		if(!recent)
7746 		  exbits &= ~MSG_EX_STATECHG;
7747 
7748 		msgno_exceptions(stream, i, "0", &exbits, TRUE);
7749 	    }
7750 	}
7751 	else{
7752 	    exbits = MSG_EX_TESTED;
7753 	    msgno_exceptions(stream, i, "0", &exbits, TRUE);
7754 	}
7755 
7756 	/* clear any stmp flags just in case */
7757 	if((mc = mail_elt(stream, i)) != NULL)
7758 	  mc->spare6 = 0;
7759     }
7760 
7761     msgmap->flagged_stmp = 0L;
7762 
7763     if(any_msgs && F_OFF(F_QUELL_FILTER_MSGS, ps_global)
7764        && F_OFF(F_QUELL_FILTER_DONE_MSG, ps_global)){
7765 	q_status_message(SM_ORDER, 0, 1, _("filtering done"));
7766 	display_message('x');
7767     }
7768 }
7769 
7770 
7771 /*
7772  * Re-check the filters for matches because a change of message state may
7773  * have changed the results.
7774  */
7775 void
reprocess_filter_patterns(MAILSTREAM * stream,MSGNO_S * msgmap,int flags)7776 reprocess_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, int flags)
7777 {
7778     if(stream){
7779 	long i;
7780 	int  exbits;
7781 
7782 	if(msgno_include(stream, msgmap, flags)
7783 	   && stream == ps_global->mail_stream
7784 	   && !(flags & MI_CLOSING)){
7785 	    clear_index_cache(stream, 0);
7786 	    refresh_sort(stream, msgmap, SRT_NON);
7787 	    ps_global->mangled_header = 1;
7788 	}
7789 
7790 	/*
7791 	 * Passing 1 in the last argument causes it to only look at the
7792 	 * messages we included above, which should be only the ones we
7793 	 * need to look at.
7794 	 */
7795 	process_filter_patterns(stream, msgmap,
7796 				(flags & MI_STATECHGONLY) ? 1L : 0);
7797 
7798 	/* clear status change flags */
7799 	for(i = 1; i <= stream->nmsgs; i++){
7800 	    if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){
7801 		if(exbits & MSG_EX_STATECHG){
7802 		    exbits &= ~MSG_EX_STATECHG;
7803 		    msgno_exceptions(stream, i, "0", &exbits, TRUE);
7804 		}
7805 	    }
7806 	}
7807     }
7808 }
7809 
7810 
7811 /*
7812  * When killing or filtering we don't want to match by mistake. So if
7813  * a pattern has nothing set except the Current Folder Type (which is always
7814  * set to something) we'll consider it to be trivial and not a match.
7815  * match_pattern uses this to determine if there is a match, where it is
7816  * just triggered on the Current Folder Type.
7817  */
7818 int
trivial_patgrp(PATGRP_S * patgrp)7819 trivial_patgrp(PATGRP_S *patgrp)
7820 {
7821     int ret = 1;
7822 
7823     if(patgrp){
7824 	if(patgrp->subj || patgrp->cc || patgrp->from || patgrp->to ||
7825 	   patgrp->sender || patgrp->news || patgrp->recip || patgrp->partic ||
7826 	   patgrp->alltext || patgrp->bodytext)
7827 	  ret = 0;
7828 
7829 	if(ret && patgrp->do_age)
7830 	  ret = 0;
7831 
7832 	if(ret && patgrp->do_size)
7833 	  ret = 0;
7834 
7835 	if(ret && patgrp->do_score)
7836 	  ret = 0;
7837 
7838 	if(ret && patgrp->category_cmd && patgrp->category_cmd[0])
7839 	  ret = 0;
7840 
7841 	if(ret && patgrp_depends_on_state(patgrp))
7842 	  ret = 0;
7843 
7844 	if(ret && patgrp->stat_8bitsubj != PAT_STAT_EITHER)
7845 	  ret = 0;
7846 
7847 	if(ret && patgrp->charsets)
7848 	  ret = 0;
7849 
7850 	if(ret && patgrp->stat_bom != PAT_STAT_EITHER)
7851 	  ret = 0;
7852 
7853 	if(ret && patgrp->stat_boy != PAT_STAT_EITHER)
7854 	  ret = 0;
7855 
7856 	if(ret && patgrp->inabook != IAB_EITHER)
7857 	  ret = 0;
7858 
7859 	if(ret && patgrp->arbhdr){
7860 	    ARBHDR_S *a;
7861 
7862 	    for(a = patgrp->arbhdr; a && ret; a = a->next)
7863 	      if(a->field && a->field[0] && a->p)
7864 		ret = 0;
7865 	}
7866     }
7867 
7868     return(ret);
7869 }
7870 
7871 
7872 int
some_filter_depends_on_active_state(void)7873 some_filter_depends_on_active_state(void)
7874 {
7875     long          rflags = ROLE_DO_FILTER;
7876     PAT_S        *pat;
7877     PAT_STATE     pstate;
7878     int           ret = 0;
7879 
7880     if(nonempty_patterns(rflags, &pstate)){
7881 
7882 	for(pat = first_pattern(&pstate);
7883 	    pat && !ret;
7884 	    pat = next_pattern(&pstate))
7885 	  if(patgrp_depends_on_active_state(pat->patgrp))
7886 	    ret++;
7887     }
7888 
7889     return(ret);
7890 }
7891 
7892 
7893 /*----------------------------------------------------------------------
7894   Move all messages with sequence bit lit to dstfldr
7895 
7896   Args: stream -- stream to use
7897 	msgmap -- map of messages to be moved
7898 	dstfldr -- folder to receive moved messages
7899 	flags_for_save
7900 
7901   Returns: nonzero on success or on readonly stream
7902   ----*/
7903 int
move_filtered_msgs(MAILSTREAM * stream,MSGNO_S * msgmap,char * dstfldr,int flags_for_save,char * nick)7904 move_filtered_msgs(MAILSTREAM *stream, MSGNO_S *msgmap, char *dstfldr,
7905 		   int flags_for_save, char *nick)
7906 {
7907     long	  n;
7908     int           we_cancel = 0, width;
7909     CONTEXT_S	 *save_context = NULL;
7910     char	  buf[MAX_SCREEN_COLS+1], sbuf[MAX_SCREEN_COLS+1];
7911     char         *save_ref = NULL;
7912 #define	FILTMSG_MAX	30
7913 
7914     if(!stream)
7915       return 0;
7916 
7917     if(READONLY_FOLDER(stream)){
7918 	dprint((1,
7919 		"Can't delete messages in readonly folder \"%s\"\n",
7920 		STREAMNAME(stream)));
7921 	q_status_message1(SM_ORDER, 1, 3,
7922 			 _("Can't delete messages in readonly folder \"%s\""),
7923 			 STREAMNAME(stream));
7924 	return 1;
7925     }
7926 
7927     buf[0] = '\0';
7928 
7929     width = MAX(10, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80);
7930     snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"\"",
7931 	    nick ? nick : "", nick ? ": " : "",
7932 	    comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)));
7933     /* 2 is for brackets, 5 is for " DONE" in busy alarm */
7934     width -= (strlen(buf) + 2 + 5);
7935     snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"%s\"",
7936 	    nick ? nick : "", nick ? ": " : "",
7937 	    comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)),
7938 	    short_str(dstfldr, sbuf, sizeof(sbuf), width, FrontDots));
7939 
7940     dprint((5, "%s\n", buf));
7941 
7942     if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
7943       we_cancel = busy_cue(buf, NULL, 0);
7944 
7945     if(!is_absolute_path(dstfldr)
7946        && !(save_context = default_save_context(ps_global->context_list)))
7947       save_context = ps_global->context_list;
7948 
7949     /*
7950      * Because this save is happening independent of where the user is
7951      * in the folder hierarchy and has nothing to do with that, we want
7952      * to ignore the reference field built into the context. Zero it out
7953      * temporarily here so it won't affect the results of context_apply
7954      * in save.
7955      *
7956      * This might be a problem elsewhere, as well. The same thing as this
7957      * is also done in match_pattern_folder_specific, which is also only
7958      * called from within process_filter_patterns. But there could be
7959      * others. We could have a separate function, something like
7960      * copy_default_save_context(), that automatically zeroes out the
7961      * reference field in the copy. However, some of the uses of
7962      * default_save_context() require that a pointer into the actual
7963      * context list is returned, so this would have to be done carefully.
7964      * Besides, we don't know of any other problems so we'll just change
7965      * these known cases for now.
7966      */
7967     if(save_context && save_context->dir){
7968 	save_ref = save_context->dir->ref;
7969 	save_context->dir->ref = NULL;
7970     }
7971 
7972     n = save(ps_global, stream, save_context, dstfldr, msgmap, flags_for_save);
7973 
7974     if(save_ref)
7975       save_context->dir->ref = save_ref;
7976 
7977     if(n != mn_total_cur(msgmap)){
7978 	int   exbits;
7979 	long  x;
7980 
7981 	buf[0] = '\0';
7982 
7983 	/* Clear "filtered" flags for failed messages */
7984 	for(x = mn_first_cur(msgmap); x > 0L; x = mn_next_cur(msgmap))
7985 	  if(n-- <= 0 && msgno_exceptions(stream, mn_m2raw(msgmap, x),
7986 					  "0", &exbits, FALSE)){
7987 	      exbits &= ~(MSG_EX_FILTONCE | MSG_EX_FILEONCE |
7988 			  MSG_EX_FILTERED | MSG_EX_FILED);
7989 	      msgno_exceptions(stream, mn_m2raw(msgmap, x),
7990 			       "0", &exbits, TRUE);
7991 	  }
7992 
7993 	/* then re-incorporate them into folder they belong */
7994 	(void) msgno_include(stream, sp_msgmap(stream), MI_NONE);
7995 	clear_index_cache(stream, 0);
7996 	refresh_sort(stream, sp_msgmap(stream), SRT_NON);
7997 	ps_global->mangled_header = 1;
7998     }
7999     else{
8000 	snprintf(buf, sizeof(buf), _("Filtered all %s message to \"%s\""),
8001 		comatose(n), dstfldr);
8002 	dprint((5, "%s\n", buf));
8003     }
8004 
8005     if(we_cancel)
8006       cancel_busy_cue(buf[0] ? 0 : -1);
8007 
8008     return(buf[0] != '\0');
8009 }
8010 
8011 
8012 /*----------------------------------------------------------------------
8013   Move all messages with sequence bit lit to dstfldr
8014 
8015   Args: stream -- stream to use
8016 	msgmap -- which messages to set
8017 	flagbits -- which flags to set or clear
8018 	kw_on  -- keywords to set
8019 	kw_off -- keywords to clear
8020 	verbose -- 1 => busy alarm after 1 second
8021 	           2 => forced busy alarm
8022   ----*/
8023 void
set_some_flags(MAILSTREAM * stream,MSGNO_S * msgmap,long int flagbits,char ** kw_on,char ** kw_off,int verbose,char * nick)8024 set_some_flags(MAILSTREAM *stream, MSGNO_S *msgmap, long int flagbits,
8025 	       char **kw_on, char **kw_off, int verbose, char *nick)
8026 {
8027     long	  count = 0L, flipped_flags;
8028     int           we_cancel = 0;
8029     char          buf[150], *seq;
8030 
8031     if(!stream)
8032       return;
8033 
8034     if(READONLY_FOLDER(stream)){
8035 	dprint((1, "Can't set flags in readonly folder \"%s\"\n",
8036 		STREAMNAME(stream)));
8037 	q_status_message1(SM_ORDER, 1, 3,
8038 			 _("Can't set flags in readonly folder \"%s\""),
8039 			 STREAMNAME(stream));
8040 	return;
8041     }
8042 
8043     /* use this to determine if anything needs to be done */
8044     flipped_flags = ((flagbits & F_ANS)    ? F_UNANS : 0)       |
8045 		    ((flagbits & F_UNANS)  ? F_ANS : 0)         |
8046 		    ((flagbits & F_FLAG)   ? F_UNFLAG : 0)      |
8047 		    ((flagbits & F_UNFLAG) ? F_FLAG : 0)        |
8048 		    ((flagbits & F_DEL)    ? F_UNDEL : 0)       |
8049 		    ((flagbits & F_UNDEL)  ? F_DEL : 0)         |
8050 		    ((flagbits & F_SEEN)   ? F_UNSEEN : 0)      |
8051 		    ((flagbits & F_UNSEEN) ? F_SEEN : 0)        |
8052 		    ((flagbits & F_KEYWORD) ? F_UNKEYWORD : 0)  |
8053 		    ((flagbits & F_UNKEYWORD) ? F_KEYWORD : 0);
8054     if((seq = currentf_sequence(stream, msgmap, flipped_flags, &count, 0,
8055 			       kw_off, kw_on)) != NULL){
8056 	char *sets = NULL, *clears = NULL;
8057 	char *ps, *pc, **t;
8058 	size_t clen, slen;
8059 
8060 	/* allocate enough space for mail_flags arguments */
8061 	for(slen=100, t = kw_on; t && *t; t++)
8062 	  slen += (strlen(*t) + 1);
8063 
8064 	sets = (char *) fs_get(slen * sizeof(*sets));
8065 
8066 	for(clen=100, t = kw_off; t && *t; t++)
8067 	  clen += (strlen(*t) + 1);
8068 
8069 	clears = (char *) fs_get(clen * sizeof(*clears));
8070 
8071 	sets[0] = clears[0] = '\0';
8072 	ps = sets;
8073 	pc = clears;
8074 
8075 	snprintf(buf, sizeof(buf), "%.30s%.2sSetting flags in %.10s message%.10s",
8076 		nick ? nick : "", nick ? ": " : "",
8077 		comatose(count), plural(count));
8078 
8079 	if(F_OFF(F_QUELL_FILTER_MSGS, ps_global))
8080 	  we_cancel = busy_cue(buf, NULL, verbose ? 0 : 1);
8081 
8082 	/*
8083 	 * What's going on here? If we want to set more than one flag
8084 	 * we can do it with a single roundtrip by combining the arguments
8085 	 * into a single call and separating them with spaces.
8086 	 */
8087 	if(flagbits & F_ANS)
8088 	  sstrncpy(&ps, "\\ANSWERED", slen-(ps-sets));
8089 	if(flagbits & F_FLAG){
8090 	    if(ps > sets)
8091 	      sstrncpy(&ps, " ", slen-(ps-sets));
8092 
8093 	    sstrncpy(&ps, "\\FLAGGED", slen-(ps-sets));
8094 	}
8095 	if(flagbits & F_DEL){
8096 	    if(ps > sets)
8097 	      sstrncpy(&ps, " ", slen-(ps-sets));
8098 
8099 	    sstrncpy(&ps, "\\DELETED", slen-(ps-sets));
8100 	}
8101 	if(flagbits & F_SEEN){
8102 	    if(ps > sets)
8103 	      sstrncpy(&ps, " ", slen-(ps-sets));
8104 
8105 	    sstrncpy(&ps, "\\SEEN", slen-(ps-sets));
8106 	}
8107 	if(flagbits & F_KEYWORD){
8108 	    for(t = kw_on; t && *t; t++){
8109 		int i;
8110 
8111 		/*
8112 		 * We may be able to tell that this will fail before
8113 		 * we actually try it.
8114 		 */
8115 		if(stream->kwd_create ||
8116 		   (((i=user_flag_index(stream, *t)) >= 0) && i < NUSERFLAGS)){
8117 		    if(ps > sets)
8118 		      sstrncpy(&ps, " ", slen-(ps-sets));
8119 
8120 		    sstrncpy(&ps, *t, slen-(ps-sets));
8121 		}
8122 		else{
8123 		  int some_defined = 0;
8124 		  static int msg_delivered = 0;
8125 
8126 		  some_defined = some_user_flags_defined(stream);
8127 
8128 		  if(msg_delivered++ < 2){
8129 		    char b[200], c[200], *p;
8130 		    int w;
8131 
8132 		    if(some_defined){
8133 		      snprintf(b, sizeof(b), "Can't set \"%.30s\". No more keywords in ", keyword_to_nick(*t));
8134 		      w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1);
8135 		      p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots);
8136 		      q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p);
8137 		    }
8138 		    else{
8139 		      snprintf(b, sizeof(b), "Can't set \"%.30s\". Can't add keywords in ", keyword_to_nick(*t));
8140 		      w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1);
8141 		      p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots);
8142 		      q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p);
8143 		    }
8144 		  }
8145 
8146 		  if(some_defined){
8147 		    dprint((1, "Can't set keyword \"%s\". No more keywords allowed in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder"));
8148 		  }
8149 		  else{
8150 		    dprint((1, "Can't set keyword \"%s\". Can't add keywords in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder"));
8151 		  }
8152 		}
8153 	    }
8154 	}
8155 
8156 	/* need a separate call for the clears */
8157 	if(flagbits & F_UNANS)
8158 	  sstrncpy(&pc, "\\ANSWERED", clen-(pc-clears));
8159 	if(flagbits & F_UNFLAG){
8160 	    if(pc > clears)
8161 	      sstrncpy(&pc, " ", clen-(pc-clears));
8162 
8163 	    sstrncpy(&pc, "\\FLAGGED", clen-(pc-clears));
8164 	}
8165 	if(flagbits & F_UNDEL){
8166 	    if(pc > clears)
8167 	      sstrncpy(&pc, " ", clen-(pc-clears));
8168 
8169 	    sstrncpy(&pc, "\\DELETED", clen-(pc-clears));
8170 	}
8171 	if(flagbits & F_UNSEEN){
8172 	    if(pc > clears)
8173 	      sstrncpy(&pc, " ", clen-(pc-clears));
8174 
8175 	    sstrncpy(&pc, "\\SEEN", clen-(pc-clears));
8176 	}
8177 	if(flagbits & F_UNKEYWORD){
8178 	    for(t = kw_off; t && *t; t++){
8179 		if(pc > clears)
8180 		  sstrncpy(&pc, " ", clen-(pc-clears));
8181 
8182 		sstrncpy(&pc, *t, clen-(pc-clears));
8183 	    }
8184 	}
8185 
8186 
8187 	if(sets[0])
8188 	  mail_flag(stream, seq, sets, ST_SET);
8189 
8190 	if(clears[0])
8191 	  mail_flag(stream, seq, clears, 0L);
8192 
8193 	fs_give((void **) &sets);
8194 	fs_give((void **) &clears);
8195 	fs_give((void **) &seq);
8196 
8197 	if(we_cancel)
8198 	  cancel_busy_cue(buf[0] ? 0 : -1);
8199     }
8200 }
8201 
8202 
8203 /*
8204  * Delete messages which are marked FILTERED and excluded.
8205  * Messages which are FILTERED but not excluded are those that have had
8206  * their state set by a filter pattern, but are to remain in the same
8207  * folder.
8208  */
8209 void
delete_filtered_msgs(MAILSTREAM * stream)8210 delete_filtered_msgs(MAILSTREAM *stream)
8211 {
8212     int	  exbits;
8213     long  i;
8214     char *seq;
8215     MESSAGECACHE *mc;
8216 
8217     for(i = 1L; i <= stream->nmsgs; i++)
8218       if(msgno_exceptions(stream, i, "0", &exbits, FALSE)
8219 	 && (exbits & MSG_EX_FILTERED)
8220 	 && get_lflag(stream, NULL, i, MN_EXLD)){
8221 	  if((mc = mail_elt(stream, i)) != NULL)
8222 	    mc->sequence = 1;
8223       }
8224       else if((mc = mail_elt(stream, i)) != NULL)
8225 	mc->sequence = 0;
8226 
8227     if((seq = build_sequence(stream, NULL, NULL)) != NULL){
8228 	mail_flag(stream, seq, "\\DELETED", ST_SET | ST_SILENT);
8229 	fs_give((void **) &seq);
8230     }
8231 }
8232