1 /*
2  *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
3  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
4  *
5  *	Kill file handling
6  */
7 
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include "config.h"
13 #include "global.h"
14 #include "db.h"
15 #include "kill.h"
16 #include "match.h"
17 #include "menu.h"
18 #include "regexp.h"
19 #include "nn_term.h"
20 
21 /* prototypes further down */
22 
23 int             killed_articles;
24 int             dflt_kill_select = 30;
25 int             kill_file_loaded = 0;
26 int             kill_debug = 0;
27 int             kill_ref_count = 0;
28 
29 extern char    *temp_file;
30 extern char     delayed_msg[];
31 
32 static char     KILL_FILE[] = "kill";
33 static char     COMPILED_KILL[] = "KILL.COMP";
34 
35 #define COMP_KILL_MAGIC	0x4b694c6f	/* KiLo */
36 
37 /*
38  * kill flags
39  */
40 
41 #define COMP_KILL_ENTRY		0x80000000
42 
43 #define GROUP_REGEXP		0x01000000
44 #define GROUP_REGEXP_HDR	0x02000000
45 
46 #define AND_MATCH		0x00020000
47 #define OR_MATCH		0x00010000
48 
49 #define	KILL_CASE_MATCH		0x00000100
50 #define KILL_ON_REGEXP		0x00000200
51 #define KILL_UNLESS_MATCH	0x00000400
52 
53 #define	AUTO_KILL		0x00000001
54 #define AUTO_SELECT		0x00000002
55 #define ON_SUBJECT		0x00000004
56 #define	ON_SENDER		0x00000008
57 #define ON_FOLLOW_UP		0x00000010
58 #define ON_ANY_REFERENCES	0x00000020
59 #define ON_NOT_FOLLOW_UP	0x00000040
60 
61 /*
62  * external flag representation
63  */
64 
65 #define	EXT_AUTO_KILL		'!'
66 #define EXT_AUTO_SELECT		'+'
67 #define EXT_KILL_UNLESS_MATCH	'~'
68 #define EXT_ON_FOLLOW_UP	'>'
69 #define EXT_ON_NOT_FOLLOW_UP	'<'
70 #define EXT_ON_ANY_REFERENCES	'a'
71 #define EXT_ON_SUBJECT		's'
72 #define	EXT_ON_SENDER		'n'
73 #define	EXT_KILL_CASE_MATCH	'='
74 #define EXT_KILL_ON_REGEXP	'/'
75 #define EXT_AND_MATCH		'&'
76 #define EXT_OR_MATCH		'|'
77 
78 /*
79  * period = nnn DAYS
80  */
81 
82 #define	DAYS	* 24 * 60 * 60
83 
84 
85 /*
86  * kill_article
87  *
88  *	return 1 to kill article, 0 to include it
89  */
90 
91 typedef struct kill_list_entry {
92     flag_type       kill_flag;
93     char           *kill_pattern;
94     regexp         *kill_regexp;
95     struct kill_list_entry *next_kill;
96 }               kill_list_entry;
97 
98 static kill_list_entry *kill_tab;
99 static char    *kill_patterns;
100 
101 static kill_list_entry *global_kill_list = NULL;
102 static kill_list_entry latest_kl_entry;
103 
104 typedef struct {
105     regexp         *group_regexp;
106     kill_list_entry *kill_entry;
107 }               kill_group_regexp;
108 
109 static kill_group_regexp *group_regexp_table = NULL;
110 static int      regexp_table_size = 0;
111 static kill_list_entry *regexp_kill_list = NULL;
112 static group_header *current_kill_group = NULL;
113 
114 /* kill.c */
115 
116 static void     build_regexp_kill(void);
117 static kill_list_entry *exec_kill(register kill_list_entry * kl, register article_header * ah, int *unlessp, int do_kill, int do_select);
118 static void     fput_pattern(register char *p, register FILE * f);
119 static char    *get_pattern(register char *p, int *lenp, int more);
120 static int      compile_kill_file(void);
121 static void     free_kill_list(register kill_list_entry * kl);
122 static int      print_kill(register kill_list_entry * kl);
123 
124 /*
125  *	Build regexp_kill_list for current_group
126  */
127 
128 static void
build_regexp_kill(void)129 build_regexp_kill(void)
130 {
131     register kill_group_regexp *tb;
132     register int    n, used_last;
133     register char  *name;
134 
135     regexp_kill_list = NULL;
136     current_kill_group = current_group;
137     name = current_group->group_name;
138     used_last = 0;		/* get AND_MATCH/OR_MATCH for free */
139 
140     for (n = regexp_table_size, tb = group_regexp_table; --n >= 0; tb++) {
141 	if (tb->group_regexp != NULL) {
142 	    used_last = 0;
143 	    if (!regexec(tb->group_regexp, name))
144 		continue;
145 	} else if (!used_last)
146 	    continue;
147 
148 	tb->kill_entry->next_kill = regexp_kill_list;
149 	regexp_kill_list = tb->kill_entry;
150 	used_last = 1;
151     }
152 }
153 
154 /*
155  *	execute kill patterns on article
156  */
157 
158 static kill_list_entry *
exec_kill(register kill_list_entry * kl,register article_header * ah,int * unlessp,int do_kill,int do_select)159 exec_kill(register kill_list_entry * kl, register article_header * ah, int *unlessp, int do_kill, int do_select)
160 {
161     register flag_type flag;
162     register char  *string;
163 
164     for (; kl != NULL; kl = kl->next_kill) {
165 	flag = kl->kill_flag;
166 
167 	if (do_select && (flag & AUTO_SELECT) == 0)
168 	    goto failed;
169 	if (do_kill && (flag & AUTO_KILL) == 0)
170 	    goto failed;
171 
172 	if (kill_debug && print_kill(kl) < 0)
173 	    kill_debug = 0;
174 
175 	if (flag & KILL_UNLESS_MATCH)
176 	    *unlessp = 1;
177 
178 	if (flag & ON_ANY_REFERENCES) {
179 	    if (ah->replies & 0x7f)
180 		goto match;
181 	    goto failed;
182 	}
183 	if (flag & ON_SUBJECT) {
184 	    if (flag & ON_FOLLOW_UP) {
185 		if ((ah->replies & 0x80) == 0)
186 		    goto failed;
187 	    } else if (flag & ON_NOT_FOLLOW_UP) {
188 		if (ah->replies & 0x80)
189 		    goto failed;
190 	    }
191 	    string = ah->subject;
192 	} else
193 	    string = ah->sender;
194 
195 	if (flag & KILL_CASE_MATCH) {
196 	    if (flag & KILL_ON_REGEXP) {
197 		if (regexec(kl->kill_regexp, string))
198 		    goto match;
199 	    } else if (strcmp(kl->kill_pattern, string) == 0)
200 		goto match;
201 	} else if (flag & KILL_ON_REGEXP) {
202 	    if (regexec_fold(kl->kill_regexp, string))
203 		goto match;
204 	} else if (strmatch_fold(kl->kill_pattern, string))
205 	    goto match;
206 
207 failed:
208 	if ((flag & AND_MATCH) == 0)
209 	    continue;
210 
211 	do			/* skip next */
212 	    kl = kl->next_kill;
213 	while (kl && (kl->kill_flag & AND_MATCH));
214 	if (kl)
215 	    continue;
216 	break;
217 
218 match:
219 	if (kill_debug) {
220 	    pg_next();
221 	    tprintf("%sMATCH\n", flag & AND_MATCH ? "PARTIAL " : "");
222 	}
223 	if (flag & AND_MATCH)
224 	    continue;
225 	break;
226     }
227     return kl;
228 }
229 
230 
231 int
kill_article(article_header * ah)232 kill_article(article_header * ah)
233 {
234     register kill_list_entry *kl;
235     int             unless_match = 0;
236 
237     if (kill_debug) {
238 	clrdisp();
239 	pg_init(0, 1);
240 	pg_next();
241 	so_printf("\1KILL: %s: %s%-.40s (%d)\1",
242 		  ah->sender, ah->replies & 0x80 ? "Re: " : "",
243 		  ah->subject, ah->replies & 0x7f);
244     }
245     kl = exec_kill((kill_list_entry *) (current_group->kill_list), ah,
246 		   &unless_match, 0, 0);
247     if (kl == NULL && group_regexp_table != NULL) {
248 	if (current_kill_group != current_group)
249 	    build_regexp_kill();
250 	kl = exec_kill(regexp_kill_list, ah, &unless_match, 0, 0);
251     }
252     if (kl == NULL)
253 	kl = exec_kill(global_kill_list, ah, &unless_match, 0, 0);
254 
255     if (kl != NULL) {
256 	if (kl->kill_flag & AUTO_KILL)
257 	    goto did_kill;
258 
259 	if (kl->kill_flag & AUTO_SELECT) {
260 	    ah->attr = A_AUTO_SELECT;
261 	    goto did_select;
262 	}
263 	goto no_kill;
264     }
265     if (unless_match)
266 	goto did_kill;
267 
268 no_kill:
269     if (kill_ref_count && (int) (ah->replies & 0x7f) >= kill_ref_count) {
270 	if (kill_debug) {
271 	    pg_next();
272 	    tprintf("REFERENCE COUNT (%d) >= %d\n",
273 		    ah->replies & 0x7f, kill_ref_count);
274 	}
275 	goto did_kill;
276     }
277 did_select:
278     if (kill_debug && pg_end() < 0)
279 	kill_debug = 0;
280     return 0;
281 
282 did_kill:
283     if (kill_debug && pg_end() < 0)
284 	kill_debug = 0;
285     killed_articles++;
286     return 1;
287 }
288 
289 
290 int
auto_select_article(article_header * ah,int do_select)291 auto_select_article(article_header * ah, int do_select)
292 {
293     register kill_list_entry *kl;
294     int             dummy;
295 
296     if (do_select == 1) {
297 	kl = ah->a_group ? (kill_list_entry *) (ah->a_group->kill_list) :
298 	    (kill_list_entry *) (current_group->kill_list);
299 	kl = exec_kill(kl, ah, &dummy, !do_select, do_select);
300 	if (kl == NULL && group_regexp_table != NULL) {
301 	    if (current_kill_group != current_group)
302 		build_regexp_kill();
303 	    kl = exec_kill(regexp_kill_list, ah, &dummy, !do_select, do_select);
304 	}
305 	if (kl == NULL)
306 	    kl = exec_kill(global_kill_list, ah, &dummy, !do_select, do_select);
307     } else {
308 	kl = exec_kill(&latest_kl_entry, ah, &dummy, !do_select, do_select);
309     }
310 
311     if (kl == NULL)
312 	return 0;
313 
314     if (!do_select)
315 	killed_articles++;
316     return 1;
317 }
318 
319 
320 static void
fput_pattern(register char * p,register FILE * f)321 fput_pattern(register char *p, register FILE * f)
322 {
323     register char   c;
324 
325     while ((c = *p++)) {
326 	if (c == ':' || c == '\\')
327 	    putc('\\', f);
328 	putc(c, f);
329     }
330 }
331 
332 static char    *
get_pattern(register char * p,int * lenp,int more)333 get_pattern(register char *p, int *lenp, int more)
334 {
335     register char   c, *q, *start;
336 
337     start = q = p;
338     while ((c = *p++)) {
339 	if (c == '\\') {
340 	    c = *p++;
341 	    if (c != ':' && c != '\\')
342 		*q++ = '\\';
343 	    *q++ = c;
344 	    continue;
345 	}
346 	if (more) {
347 	    if (c == ':')
348 		break;
349 	    if (c == NL)
350 		return NULL;
351 	} else if (c == NL)
352 	    break;
353 
354 	*q++ = c;
355     }
356 
357     if (c == NUL)
358 	return NULL;
359 
360     *q++ = NUL;
361     *lenp = q - start;
362     return p;
363 }
364 
365 void
enter_kill_file(group_header * gh,char * pattern,register flag_type flag,int days)366 enter_kill_file(group_header * gh, char *pattern, register flag_type flag, int days)
367 {
368     FILE           *killf;
369     register kill_list_entry *kl;
370     regexp         *re;
371     char           *str;
372 
373     str = copy_str(pattern);
374 
375     if ((flag & KILL_CASE_MATCH) == 0)
376 	fold_string(str);
377 
378     if (flag & KILL_ON_REGEXP) {
379 	re = regcomp(pattern);
380 	if (re == NULL)
381 	    return;
382     } else
383 	re = NULL;
384 
385     killf = open_file(relative(nn_directory, KILL_FILE), OPEN_APPEND);
386     if (killf == NULL) {
387 	msg("cannot create kill file");
388 	return;
389     }
390     if (days >= 0) {
391 	if (days == 0)
392 	    days = 30;
393 	fprintf(killf, "%ld:", (long) (cur_time() + days DAYS));
394     }
395     if (gh)
396 	fputs(gh->group_name, killf);
397     fputc(':', killf);
398 
399     if (flag & KILL_UNLESS_MATCH)
400 	fputc(EXT_KILL_UNLESS_MATCH, killf);
401     if (flag & AUTO_KILL)
402 	fputc(EXT_AUTO_KILL, killf);
403     if (flag & AUTO_SELECT)
404 	fputc(EXT_AUTO_SELECT, killf);
405     if (flag & ON_FOLLOW_UP)
406 	fputc(EXT_ON_FOLLOW_UP, killf);
407     if (flag & ON_NOT_FOLLOW_UP)
408 	fputc(EXT_ON_NOT_FOLLOW_UP, killf);
409     if (flag & ON_ANY_REFERENCES)
410 	fputc(EXT_ON_ANY_REFERENCES, killf);
411     if (flag & ON_SENDER)
412 	fputc(EXT_ON_SENDER, killf);
413     if (flag & ON_SUBJECT)
414 	fputc(EXT_ON_SUBJECT, killf);
415     if (flag & KILL_CASE_MATCH)
416 	fputc(EXT_KILL_CASE_MATCH, killf);
417     if (flag & KILL_ON_REGEXP)
418 	fputc(EXT_KILL_ON_REGEXP, killf);
419     fputc(':', killf);
420 
421     fput_pattern(pattern, killf);
422     fputc(NL, killf);
423 
424     fclose(killf);
425     rm_kill_file();
426 
427     kl = newobj(kill_list_entry, 1);
428 
429     latest_kl_entry.kill_pattern = kl->kill_pattern = str;
430     latest_kl_entry.kill_regexp = kl->kill_regexp = re;
431     latest_kl_entry.kill_flag = kl->kill_flag = flag;
432     latest_kl_entry.next_kill = NULL;
433 
434     if (gh) {
435 	kl->next_kill = (kill_list_entry *) (gh->kill_list);
436 	gh->kill_list = (char *) kl;
437     } else {
438 	kl->next_kill = global_kill_list;
439 	global_kill_list = kl;
440     }
441 }
442 
443 
444 typedef struct {
445     group_number    ck_group;
446     flag_type       ck_flag;
447     long            ck_pattern_index;
448 }               comp_kill_entry;
449 
450 typedef struct {
451     long            ckh_magic;
452     time_t          ckh_db_check;
453     off_t           ckh_pattern_offset;
454     long            ckh_pattern_size;
455     long            ckh_entries;
456     long            ckh_regexp_size;
457 }               comp_kill_header;
458 
459 
460 int
kill_menu(article_header * ah)461 kill_menu(article_header * ah)
462 {
463     int             days;
464     register flag_type flag;
465     char           *mode1, *mode2;
466     char           *pattern, *dflt, *days_str, buffer[512];
467     group_header   *gh;
468 
469     days = dflt_kill_select % 100;
470     flag = (dflt_kill_select / 100) ? AUTO_SELECT : AUTO_KILL;
471     prompt("\1AUTO\1 (k)ill or (s)elect (CR => %s subject %d days) ",
472 	   flag == AUTO_KILL ? "Kill" : "Select", days);
473 
474     switch (get_c()) {
475 	case CR:
476 	case NL:
477 	    if (ah == NULL) {
478 		ah = get_menu_article();
479 		if (ah == NULL)
480 		    return -1;
481 	    }
482 	    strcpy(buffer, ah->subject);
483 	    enter_kill_file(current_group, buffer,
484 			    flag | ON_SUBJECT | KILL_CASE_MATCH, days);
485 	    msg("DONE");
486 	    return 1;
487 
488 	case 'k':
489 	case 'K':
490 	case '!':
491 	    flag = AUTO_KILL;
492 	    mode1 = "KILL";
493 	    break;
494 	case 's':
495 	case 'S':
496 	case '+':
497 	    flag = AUTO_SELECT;
498 	    mode1 = "SELECT";
499 	    break;
500 	default:
501 	    return -1;
502     }
503 
504     prompt("\1AUTO %s\1 on (s)ubject or (n)ame  (s)", mode1);
505 
506     dflt = NULL;
507     switch (get_c()) {
508 	case 'n':
509 	case 'N':
510 	    flag |= ON_SENDER;
511 	    if (ah)
512 		dflt = ah->sender;
513 	    mode2 = "Name";
514 	    break;
515 	case 's':
516 	case 'S':
517 	case SP:
518 	case CR:
519 	case NL:
520 	    flag |= ON_SUBJECT;
521 	    if (ah)
522 		dflt = ah->subject;
523 	    mode2 = "Subject";
524 	    break;
525 	default:
526 	    return -1;
527     }
528 
529     prompt("\1%s %s:\1 (%=/) ", mode1, mode2);
530 
531     pattern = get_s(dflt, NONE, "%=/", NULL_FCT);
532     if (pattern == NULL)
533 	return -1;
534     if (*pattern == NUL || *pattern == '%' || *pattern == '=') {
535 	if (dflt && *dflt)
536 	    pattern = dflt;
537 	else {
538 	    if ((ah = get_menu_article()) == NULL)
539 		return -1;
540 	    pattern = (flag & ON_SUBJECT) ? ah->subject : ah->sender;
541 	}
542 	flag |= KILL_CASE_MATCH;
543     } else if (*pattern == '/') {
544 	prompt("\1%s %s\1 (regexp): ", mode1, mode2);
545 
546 	pattern = get_s(NONE, NONE, NONE, NULL_FCT);
547 	if (pattern == NULL || *pattern == NUL)
548 	    return -1;
549 	flag |= KILL_ON_REGEXP;
550     }
551     strcpy(buffer, pattern);
552     pattern = buffer;
553 
554     prompt("\1%s\1 in (g)roup '%s' or in (a)ll groups  (g)",
555 	   mode1, current_group->group_name);
556 
557     switch (get_c()) {
558 	case 'g':
559 	case 'G':
560 	case SP:
561 	case CR:
562 	case NL:
563 	    gh = current_group;
564 	    break;
565 	case 'A':
566 	case 'a':
567 	    gh = NULL;
568 	    break;
569 	default:
570 	    return -1;
571     }
572 
573     prompt("\1Lifetime of entry in days\1 (p)ermanent  (30) ");
574     days_str = get_s(" 30 days", NONE, "pP", NULL_FCT);
575     if (days_str == NULL)
576 	return -1;
577 
578     if (*days_str == NUL) {
579 	days_str = "30 days";
580 	days = 30;
581     } else if (*days_str == 'p' || *days_str == 'P') {
582 	days_str = "perm";
583 	days = -1;
584     } else if (isdigit(*days_str)) {
585 	days = atoi(days_str);
586 	sprintf(days_str, "%d days", days);
587     } else {
588 	ding();
589 	return -1;
590     }
591 
592     prompt("\1CONFIRM\1 %s %s %s%s: %-.35s%s ",
593 	   mode1, mode2, days_str,
594 	   (flag & KILL_CASE_MATCH) ? " exact" :
595 	   (flag & KILL_ON_REGEXP) ? " regexp" : "",
596 	   pattern, (int) strlen(pattern) > 35 ? "..." : "");
597     if (yes(0) <= 0)
598 	return -1;
599 
600     enter_kill_file(gh, pattern, flag, days);
601 
602     return (flag & AUTO_KILL) ? 1 : 0;
603 }
604 
605 static int
compile_kill_file(void)606 compile_kill_file(void)
607 {
608     FILE           *killf, *compf, *patternf, *dropf;
609     comp_kill_header header;
610     comp_kill_entry entry;
611     time_t          now, age;
612     long            cur_line_start;
613     char            line[512];
614     register char  *cp, *np;
615     register int    c;
616     group_header   *gh;
617     flag_type       flag, fields[10];
618     int             any_errors, nfield, nf, len;
619 
620     any_errors = 0;
621     header.ckh_entries = header.ckh_regexp_size = 0;
622 
623     killf = open_file(relative(nn_directory, KILL_FILE),
624 		      OPEN_READ | DONT_CREATE);
625     if (killf == NULL)
626 	return 0;
627 
628     dropf = NULL;
629 
630     compf = open_file(relative(nn_directory, COMPILED_KILL), OPEN_CREATE);
631     if (compf == NULL)
632 	goto err1;
633 
634     new_temp_file();
635     if ((patternf = open_file(temp_file, OPEN_CREATE)) == NULL)
636 	goto err2;
637 
638     msg("Compiling kill file");
639 
640     fseek(compf, sizeof(header), 0);
641 
642     now = cur_time();
643 
644 next_entry:
645 
646     for (;;) {
647 	cur_line_start = ftell(killf);
648 
649 	if (fgets(line, 512, killf) == NULL)
650 	    break;
651 
652 	cp = line;
653 	while (*cp && isascii(*cp) && isspace(*cp))
654 	    cp++;
655 	if (*cp == NUL || *cp == '#' || !isascii(*cp))
656 	    continue;
657 
658 	if ((np = strchr(cp, ':')) == NULL)
659 	    goto bad_entry;
660 
661 	/* optional "age:" */
662 
663 	if (np != cp && isdigit(*cp)) {
664 	    *np++ = NUL;
665 	    age = (time_t) atol(cp);
666 	    if (age < now)
667 		goto drop_entry;
668 	    cp = np;
669 	    if ((np = strchr(cp, ':')) == NULL)
670 		goto bad_entry;
671 	}
672 	/* "group-name:"  or "/regexp:" or ":" for all groups */
673 
674 	flag = COMP_KILL_ENTRY;
675 
676 	if (np == cp) {
677 	    entry.ck_group = -1;
678 	    np++;
679 	} else {
680 	    *np++ = NUL;
681 	    if (*cp == '/') {
682 		entry.ck_group = (long) ftell(patternf);
683 		cp++;
684 		len = strlen(cp) + 1;
685 		if (fwrite(cp, sizeof(char), len, patternf) != len)
686 		    goto err3;
687 		flag |= GROUP_REGEXP | GROUP_REGEXP_HDR;
688 	    } else {
689 		if ((gh = lookup(cp)) == NULL || (gh->master_flag & M_IGNORE_GROUP)) {
690 		    tprintf("Unknown/ignored group in kill file: %s\n", cp);
691 		    any_errors++;
692 		    goto drop_entry;
693 		}
694 		entry.ck_group = gh->group_num;
695 	    }
696 	}
697 
698 	/* flags */
699 
700 	cp = np;
701 	nfield = 0;
702 
703 	for (;;) {
704 	    switch (*cp++) {
705 		case EXT_AND_MATCH:
706 		case EXT_OR_MATCH:
707 		    fields[nfield++] = flag;
708 		    flag &= ~(AND_MATCH | ON_SUBJECT | ON_SENDER |
709 			      KILL_CASE_MATCH | KILL_ON_REGEXP |
710 			      GROUP_REGEXP_HDR);
711 		    flag |= (cp[-1] == EXT_AND_MATCH) ? AND_MATCH : OR_MATCH;
712 		    continue;
713 		case EXT_AUTO_KILL:
714 		    flag |= AUTO_KILL;
715 		    continue;
716 		case EXT_AUTO_SELECT:
717 		    flag |= AUTO_SELECT;
718 		    continue;
719 		case EXT_ON_FOLLOW_UP:
720 		    flag |= ON_FOLLOW_UP;
721 		    continue;
722 		case EXT_ON_NOT_FOLLOW_UP:
723 		    flag |= ON_NOT_FOLLOW_UP;
724 		    continue;
725 		case EXT_ON_ANY_REFERENCES:
726 		    flag |= ON_ANY_REFERENCES;
727 		    continue;
728 		case EXT_ON_SUBJECT:
729 		    flag |= ON_SUBJECT;
730 		    continue;
731 		case EXT_ON_SENDER:
732 		    flag |= ON_SENDER;
733 		    continue;
734 		case EXT_KILL_CASE_MATCH:
735 		    flag |= KILL_CASE_MATCH;
736 		    continue;
737 		case EXT_KILL_UNLESS_MATCH:
738 		    flag |= KILL_UNLESS_MATCH;
739 		    continue;
740 		case EXT_KILL_ON_REGEXP:
741 		    flag |= KILL_ON_REGEXP;
742 		    continue;
743 		case ':':
744 		    break;
745 		case NL:
746 		    goto bad_entry;
747 		default:
748 		    tprintf("Ignored flag '%c' in kill file\n", cp[-1]);
749 		    any_errors++;
750 		    continue;
751 	    }
752 	    break;
753 	}
754 
755 	fields[nfield++] = flag;
756 
757 	for (nf = 0; --nfield >= 0; nf++) {
758 	    entry.ck_flag = flag = fields[nf];
759 	    np = cp;
760 	    if ((cp = get_pattern(np, &len, nfield)) == NULL)
761 		goto bad_entry;
762 
763 	    if ((flag & KILL_CASE_MATCH) == 0)
764 		fold_string(np);
765 
766 	    entry.ck_pattern_index = ftell(patternf);
767 
768 	    if (fwrite((char *) &entry, sizeof(entry), 1, compf) != 1)
769 		goto err3;
770 
771 	    if (fwrite(np, sizeof(char), len, patternf) != len)
772 		goto err3;
773 
774 	    header.ckh_entries++;
775 	    if (flag & GROUP_REGEXP)
776 		header.ckh_regexp_size++;
777 	}
778     }
779 
780     header.ckh_pattern_size = ftell(patternf);
781 
782     fclose(patternf);
783     patternf = open_file(temp_file, OPEN_READ | OPEN_UNLINK);
784     if (patternf == NULL)
785 	goto err2;
786 
787     header.ckh_pattern_offset = ftell(compf);
788 
789     while ((c = getc(patternf)) != EOF)
790 	putc(c, compf);
791 
792     fclose(patternf);
793 
794     rewind(compf);
795 
796     header.ckh_magic = COMP_KILL_MAGIC;
797     header.ckh_db_check = master.db_created;
798 
799     if (fwrite((char *) &header, sizeof(header), 1, compf) != 1)
800 	goto err2;
801 
802     fclose(compf);
803     fclose(killf);
804     if (dropf != NULL)
805 	fclose(dropf);
806 
807     if (any_errors) {
808 	tputc(NL);
809 	any_key(0);
810     }
811     return 1;
812 
813 bad_entry:
814     tprintf("Incomplete kill file entry:\n%s", line);
815     fl;
816     any_errors++;
817 
818 drop_entry:
819     if (dropf == NULL) {
820 	dropf = open_file(relative(nn_directory, KILL_FILE),
821 			  OPEN_UPDATE | DONT_CREATE);
822 	if (dropf == NULL)
823 	    goto next_entry;
824     }
825     fseek(dropf, cur_line_start, 0);
826     fwrite("# ", sizeof(char), 2, dropf);
827     goto next_entry;
828 
829 err3:
830     fclose(patternf);
831     unlink(temp_file);
832 err2:
833     fclose(compf);
834     rm_kill_file();
835 err1:
836     fclose(killf);
837     if (dropf != NULL)
838 	fclose(dropf);
839 
840     msg("cannot compile kill file");
841     return 0;
842 }
843 
844 int
init_kill(void)845 init_kill(void)
846 {
847     FILE           *killf;
848     comp_kill_header header;
849     comp_kill_entry entry;
850     register kill_list_entry *kl;
851     register kill_group_regexp *tb;
852     register group_header *gh;
853     time_t          kill_age, comp_age;
854     register long   n;
855     int             first_try = 1;
856 
857     Loop_Groups_Header(gh)
858 	gh->kill_list = NULL;
859 
860     kill_age = file_exist(relative(nn_directory, KILL_FILE), "frw");
861     if (kill_age == 0)
862 	return 0;
863 
864     comp_age = file_exist(relative(nn_directory, COMPILED_KILL), "fr");
865 again:
866     if (comp_age < kill_age && !compile_kill_file())
867 	return 0;
868 
869     kill_tab = NULL;
870     kill_patterns = NULL;
871     group_regexp_table = NULL;
872     regexp_table_size = 0;
873 
874     killf = open_file(relative(nn_directory, COMPILED_KILL), OPEN_READ);
875     if (killf == NULL)
876 	return 0;
877 
878     if (fread((char *) &header, sizeof(header), 1, killf) != 1)
879 	goto err;
880     /* MAGIC check: format changed or using different hardware */
881     if (header.ckh_magic != COMP_KILL_MAGIC)
882 	goto err;
883 
884 #ifndef NOV
885     /* DB check: if database is rebuilt, group numbers may change */
886     if (header.ckh_db_check != master.db_created)
887 	goto err;
888 #else
889     /* ugly hack for NOV as there isn't a master to check */
890     if (first_try)
891 	goto err;
892 #endif
893 
894     if (header.ckh_entries == 0) {
895 	fclose(killf);
896 	kill_file_loaded = 1;
897 	return 0;
898     }
899     if (header.ckh_pattern_size > 0) {
900 	kill_patterns = newstr(header.ckh_pattern_size);
901 	fseek(killf, header.ckh_entries * sizeof(entry), 1);
902 	if (fread(kill_patterns, sizeof(char), (int) header.ckh_pattern_size, killf)
903 	    != header.ckh_pattern_size)
904 	    goto err;
905     } else
906 	kill_patterns = newstr(1);
907 
908     kill_tab = newobj(kill_list_entry, header.ckh_entries);
909     if ((regexp_table_size = header.ckh_regexp_size))
910 	group_regexp_table = newobj(kill_group_regexp, header.ckh_regexp_size);
911 
912     tb = group_regexp_table;
913 
914     fseek(killf, sizeof(header), 0);
915     for (n = header.ckh_entries, kl = kill_tab; --n >= 0; kl++) {
916 	if (fread((char *) &entry, sizeof(entry), 1, killf) != 1)
917 	    goto err;
918 	if (header.ckh_pattern_size <= entry.ck_pattern_index ||
919 	    entry.ck_pattern_index < 0)
920 	    goto err;
921 
922 	kl->kill_pattern = kill_patterns + entry.ck_pattern_index;
923 	kl->kill_flag = entry.ck_flag;
924 
925 	if (kl->kill_flag & KILL_ON_REGEXP)
926 	    kl->kill_regexp = regcomp(kl->kill_pattern);
927 	else
928 	    kl->kill_regexp = NULL;
929 
930 	if (kl->kill_flag & GROUP_REGEXP) {
931 	    if (kl->kill_flag & GROUP_REGEXP_HDR) {
932 		if (header.ckh_pattern_size <= entry.ck_group ||
933 		    entry.ck_group < 0)
934 		    goto err;
935 		tb->group_regexp = regcomp(kill_patterns + entry.ck_group);
936 	    } else
937 		tb->group_regexp = NULL;
938 	    tb->kill_entry = kl;
939 	    tb++;
940 	} else if (entry.ck_group >= 0) {
941 	    gh = ACTIVE_GROUP(entry.ck_group);
942 	    kl->next_kill = (kill_list_entry *) (gh->kill_list);
943 	    gh->kill_list = (char *) kl;
944 	} else {
945 	    kl->next_kill = global_kill_list;
946 	    global_kill_list = kl;
947 	}
948     }
949 
950     fclose(killf);
951 
952     kill_file_loaded = 1;
953     return 1;
954 
955 err:
956     if (group_regexp_table != NULL)
957 	freeobj(group_regexp_table);
958     if (kill_patterns != NULL)
959 	freeobj(kill_patterns);
960     if (kill_tab != NULL)
961 	freeobj(kill_tab);
962 
963     fclose(killf);
964     rm_kill_file();
965     if (first_try) {
966 	first_try = 0;
967 	comp_age = 0;
968 	goto again;
969     }
970     strcpy(delayed_msg, "Error in compiled kill file (ignored)");
971 
972     Loop_Groups_Header(gh)
973 	gh->kill_list = NULL;
974 
975     global_kill_list = NULL;
976     group_regexp_table = NULL;
977 
978     return 0;
979 }
980 
981 
982 
983 void
rm_kill_file(void)984 rm_kill_file(void)
985 {
986     unlink(relative(nn_directory, COMPILED_KILL));
987 }
988 
989 
990 static void
free_kill_list(register kill_list_entry * kl)991 free_kill_list(register kill_list_entry * kl)
992 {
993     register kill_list_entry *nxt;
994     while (kl) {
995 	nxt = kl->next_kill;
996 	if (kl->kill_regexp != NULL)
997 	    freeobj(kl->kill_regexp);
998 	if ((kl->kill_flag & COMP_KILL_ENTRY) == 0) {
999 	    if (kl->kill_pattern != NULL)
1000 		freeobj(kl->kill_pattern);
1001 	    freeobj(kl);
1002 	}
1003 	kl = nxt;
1004     }
1005 }
1006 
1007 void
free_kill_entries(void)1008 free_kill_entries(void)
1009 {
1010     register group_header *gh;
1011     register kill_group_regexp *tb;
1012     register int    n;
1013 
1014     Loop_Groups_Header(gh)
1015 	if (gh->kill_list) {
1016 	free_kill_list((kill_list_entry *) (gh->kill_list));
1017 	gh->kill_list = NULL;
1018     }
1019     free_kill_list(global_kill_list);
1020     global_kill_list = NULL;
1021 
1022     if ((tb = group_regexp_table)) {
1023 	for (n = regexp_table_size; --n >= 0; tb++)
1024 	    if (tb->group_regexp != NULL)
1025 		freeobj(tb->group_regexp);
1026 
1027 	freeobj(group_regexp_table);
1028 	group_regexp_table = NULL;
1029     }
1030     if (kill_patterns != NULL)
1031 	freeobj(kill_patterns);
1032     if (kill_tab != NULL)
1033 	freeobj(kill_tab);
1034     kill_file_loaded = 0;
1035 }
1036 
1037 
1038 static flag_type pk_prev_and;
1039 
1040 static int
print_kill(register kill_list_entry * kl)1041 print_kill(register kill_list_entry * kl)
1042 {
1043     register flag_type flag = kl->kill_flag;
1044 
1045     if (pg_next() < 0)
1046 	return -1;
1047 
1048     if (pk_prev_and)
1049 	tprintf("\r    AND ");
1050     else
1051 	tprintf("\r%s%s ON ",
1052 		flag & AUTO_KILL ? "AUTO KILL" :
1053 		flag & AUTO_SELECT ? "AUTO SELECT" : "",
1054 
1055 		(flag & KILL_UNLESS_MATCH) == 0 ? "" :
1056 		flag & AUTO_KILL ? " UNLESS" :
1057 		flag & AUTO_SELECT ? "" : "KEEP");
1058 
1059     tprintf("%s '%s%.35s'%s\n",
1060 	    flag & ON_SUBJECT ? "SUBJECT" :
1061 	    flag & ON_SENDER ? "NAME" :
1062 	    flag & ON_ANY_REFERENCES ? "ANY REF" : "????",
1063 
1064 	    flag & ON_NOT_FOLLOW_UP ? "!Re: " :
1065 	    flag & ON_FOLLOW_UP ? "Re: " : "",
1066 	    kl->kill_pattern,
1067 
1068 	    flag & KILL_CASE_MATCH ?
1069 	    (flag & KILL_ON_REGEXP ? " (re exact)" : " (exact)") :
1070 	    (flag & KILL_ON_REGEXP ? " (re fold)" : ""));
1071 
1072     pk_prev_and = flag & AND_MATCH;
1073 
1074     return 0;
1075 }
1076 
1077 void
dump_kill_list(void)1078 dump_kill_list(void)
1079 {
1080     register kill_list_entry *kl;
1081 
1082     pg_init(0, 1);
1083     pg_next();
1084 
1085     kl = (kill_list_entry *) (current_group->kill_list);
1086     if (current_kill_group != current_group)
1087 	build_regexp_kill();
1088 
1089     if (kl == NULL && regexp_kill_list == NULL) {
1090 	tprintf("No kill entries for %s", current_group->group_name);
1091     } else {
1092 	so_printf("\1GROUP %s kill list entries\1", current_group->group_name);
1093 
1094 	pk_prev_and = 0;
1095 	for (; kl; kl = kl->next_kill)
1096 	    if (print_kill(kl) < 0)
1097 		goto out;
1098 
1099 	pk_prev_and = 0;
1100 	for (kl = regexp_kill_list; kl; kl = kl->next_kill)
1101 	    if (print_kill(kl) < 0)
1102 		goto out;
1103 
1104 	if (pg_next() < 0)
1105 	    goto out;
1106     }
1107 
1108     if (pg_next() < 0)
1109 	goto out;
1110     so_printf("\1GLOBAL kill list entries:\1");
1111 
1112     pk_prev_and = 0;
1113     for (kl = global_kill_list; kl != NULL; kl = kl->next_kill)
1114 	if (print_kill(kl) < 0)
1115 	    goto out;
1116 
1117 out:
1118     pg_end();
1119 }
1120