home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / nn.tar / nn-6.5.1 / kill.c < prev    next >
C/C++ Source or Header  |  1995-04-29  |  24KB  |  1,053 lines

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