addrutil: Re-indent.
[wk-misc.git] / vegetarise.c
1 /* vegetarise.c - A spam filter based on Paul Graham's idea
2  *    Copyright (C) 2002  Werner Koch
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
17  *
18  * $Id: vegetarise.c,v 1.6 2004/09/11 14:51:51 werner Exp $
19  */
20
21 /* How to use:
22  *
23  *  # Put this at the top of your ~/.procmailrc:
24  *  #
25  *  VEGETARISE_SOCKET=$HOME/.vegetarise-socket
26  *  # After basic filtering, e.g. throwing away all chinese stuff and the
27  *  # worm of the day, add a rule like:
28  *  :0
29  *  * ? vegetarise -s $HOME/Mail/words
30  *  spamfolder2/
31  *
32  *
33  *  To intialize vegetarise you need to have collections of spam and
34  *  vegetarian mails.  For example, if you have sorted them into two
35  *  mbox files:
36  *
37  *     vegetarise -l veg.mbox spam.mbox >words
38  *
39  *  To add new stuff to an esisting word list, use this:
40  *
41  *     vegetarise -l veg.mbox spam.mbox oldwords >words
42  *
43  *  If you don't have mbox files but two files each with a list of mails
44  *  (MH or Maildir format), you can use this:
45  *
46  *     vegetarise -L veg-files.list spam-files.list oldwords >words
47  *
48  *  It can either be run standalone (usually slow) or in auto server
49  *  mode (using option -s).
50  **/
51
52
53
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <stddef.h>
57 #include <string.h>
58 #include <stdarg.h>
59 #include <ctype.h>
60 #include <errno.h>
61 #include <assert.h>
62 #ifdef HAVE_PTH /* In theory we could use sockets without Pth but it
63                    does not make much sense to we require it. */
64 #include <signal.h>
65 #include <unistd.h>
66 #include <sys/socket.h>
67 #include <sys/un.h>
68 #include <pth.h>
69 #endif /*HAVE_PTH*/
70
71 #define PGMNAME "vegetarise"
72
73 #define MAX_WORDLENGTH 50 /* max. length of a word */
74 #define MAX_WORDS 15      /* max. number of words to look at. */
75
76
77 /* A list of token characters.  There is explicit code for 8bit
78    characters. */
79 #define TOKENCHARS "abcdefghijklmnopqrstuvwxyz" \
80                    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
81                    "0123456789-_'$"
82
83 #ifdef __GNUC__
84 #define inline __inline__
85 #else
86 #define inline
87 #endif
88 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)
89 #define ATTR_PRINTF(a,b)    __attribute__ ((format (printf,a,b)))
90 #define ATTR_NR_PRINTF(a,b) __attribute__ ((noreturn,format (printf,a,b)))
91 #else
92 #define ATTR_PRINTF(a,b)    
93 #define ATTR_NR_PRINTF(a,b)
94 #endif
95
96 #define DIM(v)               (sizeof(v)/sizeof((v)[0]))
97 #define DIMof(type,member)   DIM(((type *)0)->member)
98
99
100
101 #ifdef HAVE_PTH
102 # define my_read(a,b,c) pth_read ((a),(b),(c))
103 # define my_write(a,b,c) pth_write ((a),(b),(c))
104 # define YIELD  do { if (running_as_server) pth_yield (NULL); } while (0)
105 #else
106 # define my_read(a,b,c) read ((a),(b),(c))
107 # define my_write(a,b,c) write ((a),(b),(c))
108 # define YIELD  do { ; } while (0)
109 #endif
110
111 #define xtoi_1(a)   ((a) <= '9'? ((a)- '0'): \
112                      (a) <= 'F'? ((a)-'A'+10):((a)-'a'+10))
113 #define xtoi_2(a,b) ((xtoi_1(a) * 16) + xtoi_1(b))
114
115
116 struct pushback_s {
117   int buflen;
118   char buf[100];
119   int nl_seen;
120   int state;
121   int base64_nl;
122   int base64_val;
123   int qp1;
124 };
125 typedef struct pushback_s PUSHBACK;
126
127
128 struct hash_entry_s {
129   struct hash_entry_s *next;
130   unsigned int veg_count; 
131   unsigned int spam_count; 
132   unsigned int hit_ref; /* reference to the hit table. */
133   char prob; /* range is 1 to 99 or 0 for not calculated */
134   char word [1];
135 };
136 typedef struct hash_entry_s *HASH_ENTRY;
137
138 struct hit_array_s {
139   size_t size; /* Allocated size. */
140   unsigned int *hits;  
141 };
142 typedef struct hit_array_s *HIT_ARRAY;
143
144
145 /* Option flags. */
146 static int verbose;
147 static int name_only;
148
149 /* Flag to indicate that we are running as a server. */
150 static int running_as_server;
151
152 /* Keep track of memory used for debugging. */
153 static size_t total_memory_used;
154
155
156 /* The global table with the words and its size. */ 
157 static int hash_table_size; 
158 static HASH_ENTRY *word_table;
159
160 /* When storing a new word, we assign a hit reference id to it, so
161    that we can index a hit table.  The variable keeps tracks of the
162    used reference numbers. */
163 static unsigned int next_hit_ref;
164
165 /* Number of good and bad messages the word table is made up. */
166 static unsigned int srvr_veg_count, srvr_spam_count;
167
168
169
170 /* Base64 conversion tables. */
171 static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
172                                   "abcdefghijklmnopqrstuvwxyz"
173                                   "0123456789+/";
174 static unsigned char asctobin[256]; /* runtime initialized */
175
176
177
178 /* Prototypes. */
179
180 static void die (const char *format, ...)   ATTR_NR_PRINTF(1,2);
181 static void error (const char *format, ...) ATTR_PRINTF(1,2);
182 static void info (const char *format, ...)  ATTR_PRINTF(1,2);
183
184
185 \f
186 /* 
187     Helper
188
189 */
190
191 static void
192 die (const char *fmt, ...)
193 {
194   va_list arg_ptr;
195
196   va_start (arg_ptr, fmt);
197   fputs (PGMNAME": fatal error: ", stderr);
198   vfprintf (stderr, fmt, arg_ptr);
199   va_end (arg_ptr);
200   exit (1);
201 }
202
203 static void
204 error (const char *fmt, ...)
205 {
206   va_list arg_ptr;
207
208   va_start (arg_ptr, fmt);
209   fputs (PGMNAME": error: ", stderr);
210   vfprintf (stderr, fmt, arg_ptr);
211   va_end (arg_ptr);
212 }
213
214 static void
215 info (const char *fmt, ...)
216 {
217   va_list arg_ptr;
218
219   va_start (arg_ptr, fmt);
220   fputs (PGMNAME": ", stderr);
221   vfprintf (stderr, fmt, arg_ptr);
222   va_end (arg_ptr);
223 }
224
225 static void *
226 xmalloc (size_t n)
227 {
228   void *p = malloc (n);
229   if (!p)
230     die ("out of core\n");
231   total_memory_used += n;
232   return p;
233 }
234
235 static void *
236 xcalloc (size_t n, size_t k)
237 {
238   void *p = calloc (n, k);
239   if (!p)
240     die ("out of core\n");
241   total_memory_used += n * k;
242   return p;
243 }
244
245
246 static inline unsigned int
247 hash_string (const char *s)
248 {
249   unsigned int h = 0;
250   unsigned int g;
251   
252   while (*s)
253     {
254       h = (h << 4) + *s++;
255       if ((g = (h & (unsigned int) 0xf0000000)))
256         h = (h ^ (g >> 24)) ^ g;
257     }
258
259   return (h % hash_table_size);
260 }
261
262
263 static void
264 pushback (PUSHBACK *pb, int c)
265 {
266   if (pb->buflen < sizeof pb->buf)
267       pb->buf[pb->buflen++] = c;
268   else
269     error ("comment parsing problem\n");
270 }
271
272
273 static inline int 
274 basic_next_char (FILE *fp, PUSHBACK *pb)
275 {
276   int c;
277
278   if (pb->buflen)
279     {
280       c = *pb->buf;
281       if (--pb->buflen)
282         memmove (pb->buf, pb->buf+1, pb->buflen);
283     }
284   else
285     {
286       c = getc (fp);
287       if (c == EOF)
288         return c;
289     }
290
291   /* check for HTML comment - we only use the limited syntax:
292      "<!--" ... "-->" */
293   while (c == '<')
294     {
295       if ((c=getc (fp)) == EOF)
296           return c; 
297       pushback (pb, c);
298       if ( c != '!' )
299         return '<';
300       if ((c=getc (fp)) == EOF)
301         {
302           pb->buflen = 0;;
303           return EOF; /* This misses the last chars but who cares. */
304         }
305       pushback (pb, c);
306       if ( c != '-' )
307         return '<';
308       if ((c=getc (fp)) == EOF)
309         {
310           pb->buflen = 0;
311           return EOF; /* This misses the last chars but who cares. */
312         }
313       pushback (pb, c);
314       if ( c != '-' )
315         return '<';
316       pb->buflen = 0;
317       /* found html comment - skip to end */
318       do
319         {
320           while ( (c = getc (fp)) != '-')
321             {
322               if (c == EOF)
323                 return EOF;
324             }
325           if ( (c=getc (fp)) == EOF)
326             return EOF;
327         }
328       while ( c != '-' ); 
329       
330       while ( (c = getc (fp)) != '>')
331         {
332           if (c == EOF)
333             return EOF;
334         }
335       c = getc (fp);
336     }
337   return c;
338 }
339
340
341 static inline int 
342 next_char (FILE *fp, PUSHBACK *pb)
343 {
344   int c, c2;
345
346  next:
347   if ((c=basic_next_char (fp, pb)) == EOF)
348     return c;
349   switch (pb->state)
350     {
351     case 0: break;
352     case 1: 
353       if (pb->nl_seen && (c == '\r' || c == '\n'))
354         {
355           pb->state = 2;
356           goto next;
357         }
358       break;
359     case 2:
360       if (!strchr (bintoasc, c))
361         break;
362       pb->nl_seen = 0;
363       pb->base64_nl = 0;
364       pb->state = 3;
365       /* fall through */
366     case 3: /* 1. base64 byte */
367     case 4: /* 2. base64 byte */
368     case 5: /* 3. base64 byte */
369     case 6: /* 4. base64 byte */
370       if (c == '\n')
371         {
372           pb->base64_nl = 1;
373           goto next;
374         }
375       if (pb->base64_nl && c == '-')
376         {
377           pb->state = 0; /* end of mime part */
378           c = ' '; /* make sure that last token gets processed. */
379           break;
380         }
381       pb->base64_nl = 0;
382       if (c == ' ' || c == '\r' || c == '\t')
383         goto next; /* skip all other kind of white space */
384       if (c == '=' ) 
385         goto next;   /* we ignore the stop character and rely on
386                         the MIME boundary */
387       if ((c = asctobin[c]) == 255 )
388         goto next;  /* invalid base64 character */
389         
390       switch (pb->state)
391         {
392         case 3: 
393           pb->base64_val = c << 2;
394           pb->state = 4;
395           goto next;
396         case 4:
397           pb->base64_val |= (c>>4)&3;
398           c2 = pb->base64_val;
399           pb->base64_val = (c<<4)&0xf0;
400           c = c2;
401           pb->state = 5;
402           break; /* deliver C */
403         case 5:
404           pb->base64_val |= (c>>2)&15;
405           c2 = pb->base64_val;
406           pb->base64_val = (c<<6)&0xc0;
407           c = c2;
408           pb->state = 6;
409           break; /* deliver C */
410         case 6:
411           pb->base64_val |= c&0x3f;
412           c = pb->base64_val;
413           pb->state = 3;
414           break; /* deliver C */
415         }
416       break;
417     case 101: /* quoted-printable */
418       if (pb->nl_seen && (c == '\r' || c == '\n'))
419         {
420           pb->state = 102;
421           goto next;
422         }
423       break;
424     case 102:
425       if (pb->nl_seen && (c == '-'))
426         {
427           pb->state = 105;
428           goto next;
429         }
430       else if ( c == '=' )
431         {
432           pb->state = 103;
433           goto next;
434         }
435       break;
436     case 103:
437       if ( isxdigit (c) )
438         {
439           pb->qp1 = c;
440           pb->state = 104;
441           goto next;
442         }
443       pb->state = 102;
444       break;
445     case 104:
446       if ( isxdigit (c) )
447         c = xtoi_2 (pb->qp1, c);
448       pb->state = 102;
449       break;
450     case 105:
451       if (c == '-')
452         {
453           pb->state = 106;
454           goto next;
455         }
456       pb->state = 102;
457       break; /* we drop one, but that's okat for this application */
458     case 106:
459       if ( !isspace (c) == ' ')
460         {
461           pb->state = 0; /* assume end of mime part */
462           c = ' '; /* make sure that last token gets processed. */
463         }
464       else
465         pb->state = 102; 
466       break;
467       
468     }
469   return c;
470 }
471
472 static void
473 enlarge_hit_array (HIT_ARRAY ha)
474 {
475   unsigned int *arr;
476   size_t i, n;
477
478   n = ha->size + 100;
479   arr = xmalloc (n * sizeof *arr);
480   for (i=0; i < ha->size; i++)
481     arr[i] = ha->hits[i];
482   for (; i < n; i++)
483     arr[i] = 0;
484   free (ha->hits);
485   ha->hits = arr;
486   ha->size = n;
487 }
488
489 static HIT_ARRAY
490 new_hit_array (void)
491 {
492   HIT_ARRAY ha = xmalloc (sizeof *ha);
493   /* Create the array with space for extra 1000 words */
494   ha->size = next_hit_ref + 1000;
495   ha->hits = xcalloc (ha->size, sizeof *ha->hits);
496   return ha;
497 }
498
499 static void
500 release_hit_array (HIT_ARRAY ha)
501 {
502   if (ha)
503     {
504       free (ha->hits);
505       free (ha);
506     }
507 }
508
509 \f
510 /* 
511    real processing stuff
512 */
513
514 static HASH_ENTRY
515 store_word (const char *word, int *is_new)
516 {
517   unsigned int hash = hash_string (word);
518   HASH_ENTRY entry;
519
520   if (is_new)
521     *is_new = 0;
522   for (entry = word_table[hash];
523        entry && strcmp (entry->word, word); entry = entry->next)
524     ;
525   if (!entry)
526     {
527       size_t n = sizeof *entry + strlen (word);
528       entry = xmalloc (n);
529
530       strcpy (entry->word, word);
531       entry->veg_count = 0;
532       entry->spam_count = 0;
533       entry->hit_ref = next_hit_ref++;
534       entry->prob = 0;
535       entry->next = word_table[hash];
536       word_table[hash] = entry;
537       if (is_new)
538         *is_new = 1;
539     }
540   return entry;
541 }
542
543
544 static void
545 check_one_word ( const char *word, int left_anchored, int is_spam,
546                  HIT_ARRAY ha)
547 {
548   size_t wordlen = strlen (word);
549   const char *p;
550   int n0, n1, n2, n3, n4, n5;
551
552 /*    fprintf (stderr, "token `%s'\n", word); */
553
554   for (p=word; isdigit (*p); p++)
555     ;
556   if (!*p || wordlen < 3) 
557     return; /* only digits or less than 3 chars */
558   if (wordlen == 16 && word[6] == '-' && word[13] == '-')
559     return; /* very likely a message-id formatted like 16cpeB-0004HM-00 */
560
561   if (wordlen > 25 )
562     return; /* words longer than that are rare */ 
563
564   for (n0=n1=n2=n3=n4=n5=0, p=word; *p; p++)
565     {
566       if ( *p & 0x80)
567         n0++;
568       else if ( isupper (*p) )
569         n1++;
570       else if ( islower (*p) )
571         n2++;
572       else if ( isdigit (*p) )
573         n3++;
574       else if ( *p == '-' )
575         n4++;
576       else if ( *p == '.' )
577         n5++;
578     }
579   if ( n4 == wordlen)
580     return; /* Only dashes. */
581
582   /* try to figure meaningless identifiers */
583   if (n0)
584     ; /* 8bit chars in name are okay */
585   else if ( n3 && n3 + n5 == wordlen && n5 == 3 )
586     ; /* allow IP addresses */
587   else if ( !n5 && n1 > 3 && (n2 > 3 || n3 > 3) )
588     return; /* No dots, mixed uppercase with digits or lowercase. */
589   else if ( !n5 && n2 > 3 && (n1 > 3 || n3 > 3) )
590     return; /* No dots, mixed lowercase with digits or uppercase. */
591   else if ( wordlen > 8 && (3*n3) > (n1+n2))
592     return; /* long word with 3 times more digits than letters. */
593
594   if (ha)
595     { /* we are in checking mode */
596       int is_new;
597       HASH_ENTRY e = store_word (word, &is_new);
598       if (ha->size <= e->hit_ref)
599         {
600           assert (e->hit_ref < next_hit_ref);
601           enlarge_hit_array (ha);
602         }
603       ha->hits[e->hit_ref]++;
604     }
605   else if (is_spam)
606     store_word (word, NULL)->spam_count++;
607   else
608     store_word (word, NULL)->veg_count++;
609 }
610
611 /* Parse a message and return the number of messages in case it is an
612    mbox message as indicated by IS_MBOX passed as true. */
613 static unsigned int
614 parse_message (const char *fname, FILE *fp, int is_spam, int is_mbox,
615                HIT_ARRAY ha)
616 {
617   int c;
618   char aword[MAX_WORDLENGTH+1];
619   int idx = 0;
620   int in_token = 0;
621   int left_anchored = 0;
622   int maybe_base64 = 0;
623   PUSHBACK pbbuf;
624   unsigned int msgcount = 0;
625   int count = 0;
626
627   memset (&pbbuf, 0, sizeof pbbuf);
628   while ( (c=next_char (fp, &pbbuf)) != EOF)
629     {
630       if ( ++count > 20000 )
631         {
632           count = 0;
633           YIELD;
634         }
635     again:
636       if (in_token)
637         {
638           if ((c & 0x80) || strchr (TOKENCHARS, c))
639             {
640               if (idx < MAX_WORDLENGTH)
641                 aword[idx++] = c;
642               /* truncate a word and ignore truncated characters */
643             }
644           else
645             { /* got a delimiter */
646               in_token = 0;
647               aword[idx] = 0;
648               if (maybe_base64)
649                 {
650                   if ( !strcasecmp (aword, "base64") )
651                     pbbuf.state = 1;
652                   else if ( !strcasecmp (aword, "quoted-printable") )
653                     pbbuf.state = 101;
654                   else
655                     pbbuf.state = 0;
656                   maybe_base64 = 0; 
657                 }
658               else if (is_mbox && left_anchored
659                        && !pbbuf.state && !strcmp (aword, "From"))
660                 {
661                   if (c != ' ')
662                     {
663                       pbbuf.nl_seen = (c == '\n');
664                       goto again;
665                     }
666                   msgcount++;
667                 }
668               else if (left_anchored
669                   && (!strcasecmp (aword, "Received")
670                       || !strcasecmp (aword, "Date")))
671                 {
672                   if (c != ':')
673                     {
674                       pbbuf.nl_seen = (c == '\n');
675                       goto again;
676                     }
677
678                   do
679                     {
680                       while ( (c = next_char (fp, &pbbuf)) != '\n')
681                         {
682                           if (c == EOF)
683                             goto leave;
684                         }
685                     }
686                   while ( c == ' ' || c == '\t');
687                   pbbuf.nl_seen = 1;
688                   goto again;
689                 }
690               else if (left_anchored
691                        && !strcasecmp (aword, "Content-Transfer-Encoding"))
692                 {
693                   if (c != ':')
694                     {
695                       pbbuf.nl_seen = (c == '\n');
696                       goto again;
697                     }
698                   maybe_base64 = 1;
699                 }
700               else if (c == '.' && idx && !(aword[idx-1] & 0x80)
701                        && isalnum (aword[idx-1]) )
702                 {
703                   /* Assume an IP address or a hostname if a dot is
704                      followed by a letter or digit. */
705                   c = next_char (fp, &pbbuf);
706                   if ( !(c & 0x80) && isalnum (c) && idx < MAX_WORDLENGTH)
707                     {
708                       aword[idx++] = '.';
709                       in_token = 1;
710                     }
711                   else
712                     check_one_word (aword, left_anchored, is_spam, ha);
713                   pbbuf.nl_seen = (c == '\n');
714                   goto again;
715                 }
716 #if 0
717               else if (c == '=')
718                 {
719                   /* Assume an QP encoded character if followed by an
720                      hexdigit */
721                   c = next_char (fp, &pbbuf);
722                   if ( !(c & 0x80) && (isxdigit (c)) && idx < MAX_WORDLENGTH)
723                     {
724                       aword[idx++] = '=';
725                       in_token = 1;
726                     }
727                   else
728                     check_one_word (aword, left_anchored, is_spam, ha);
729                   pbbuf.nl_seen = (c == '\n');
730                   goto again;
731                 }
732 #endif
733               else
734                 check_one_word (aword, left_anchored, is_spam, ha);
735             }
736         }
737       else if ( (c & 0x80) || strchr (TOKENCHARS, c))
738         {
739           in_token = 1;
740           idx = 0;
741           aword[idx++] = c;
742           left_anchored = pbbuf.nl_seen;
743         }
744       pbbuf.nl_seen = (c == '\n');
745     }
746  leave:
747   if (ferror (fp))
748       die ("error reading `%s': %s\n", fname, strerror (errno));
749
750   msgcount++;
751   return msgcount;
752 }
753
754
755 static unsigned int
756 calc_prob (unsigned int g, unsigned int b,
757            unsigned int ngood, unsigned int nbad)
758 {
759    double prob_g, prob_b, prob;
760
761 /*      (max .01 */
762 /*            (min .99 (float (/ (min 1 (/ b nbad)) */
763 /*                               (+ (min 1 (/ g ngood)) */
764 /*                                  (min 1 (/ b nbad))))))))) */
765
766    prob_g = (double)g / ngood;
767    if (prob_g > 1)
768      prob_g = 1;
769    prob_b = (double)b / ngood;
770    if (prob_b > 1)
771      prob_b = 1;
772
773    prob = prob_b / (prob_g + prob_b);
774    if (prob < .01)
775      prob = .01;
776    else if (prob > .99)
777      prob = .99;
778   
779    return (unsigned int) (prob * 100);
780 }
781
782
783 static void
784 calc_probability (unsigned int ngood, unsigned int nbad)
785 {
786   int n;
787   HASH_ENTRY entry;
788   unsigned int g, b; 
789
790   if (!ngood)
791     die ("no vegetarian mails available - stop\n");
792   if (!nbad)
793     die ("no spam mails available - stop\n");
794
795   for (n=0; n < hash_table_size; n++)
796     {
797       for (entry = word_table[n]; entry; entry = entry->next)
798         {
799           g = entry->veg_count * 2;
800           b = entry->spam_count;
801           if (g + b >= 5)
802             entry->prob = calc_prob (g, b, ngood, nbad);
803         }
804     }
805 }
806
807
808 static unsigned int
809 check_spam (unsigned int ngood, unsigned int nbad, HIT_ARRAY ha)
810 {
811   unsigned int n;
812   HASH_ENTRY entry;
813   unsigned int dist, min_dist;
814   struct {
815     HASH_ENTRY e;
816     unsigned int d;
817     double prob;
818   } st[MAX_WORDS];
819   int nst = 0;
820   int i;
821   double prod, inv_prod, taste;
822
823   for (i=0; i < MAX_WORDS; i++)
824     {
825       st[i].e = NULL;
826       st[i].d = 0;
827     }
828
829   min_dist = 100;
830   for (n=0; n < hash_table_size; n++)
831     {
832       for (entry = word_table[n]; entry; entry = entry->next)
833         {
834           if (entry->hit_ref && ha->hits[entry->hit_ref])
835             {
836               if (!entry->prob)
837                 dist = 10; /* 50 - 40 */
838               else
839                 dist = entry->prob < 50? (50 - entry->prob):(entry->prob - 50);
840               if (nst < MAX_WORDS)
841                 {
842                   st[nst].e = entry;
843                   st[nst].d = dist;
844                   st[nst].prob = entry->prob? (double)entry->prob/100 : 0.4;
845                   if (dist < min_dist)
846                     min_dist = dist;
847                   nst++;
848                 }
849               else if (dist > min_dist)
850                 { 
851                   unsigned int tmp = 100;
852                   int tmpidx = -1;
853                   
854                   for (i=0; i < MAX_WORDS; i++)
855                     {
856                       if (st[i].d < tmp)
857                         {
858                           tmp = st[i].d;
859                           tmpidx = i;
860                         }
861                     }
862                   assert (tmpidx != -1);
863                   st[tmpidx].e = entry;
864                   st[tmpidx].d = dist;
865                   st[tmpidx].prob = entry->prob? (double)entry->prob/100 : 0.4;
866                   
867                   min_dist = 100;
868                   for (i=0; i < MAX_WORDS; i++)
869                     {
870                       if (st[i].d < min_dist)
871                         min_dist = dist;
872                     }
873                 }
874             }
875         }
876     }
877
878   /* ST has now the NST most intersting words */
879   if (!nst)
880     {
881       info ("not enough words - assuming goodness\n");
882       return 100;
883     }
884
885   if (verbose > 1)
886     {
887       for (i=0; i < nst; i++)
888         info ("prob %3.2f dist %3d for `%s'\n",
889               st[i].prob, st[i].d, st[i].e->word);
890     }
891
892   prod = 1;
893   for (i=0; i < nst; i++)
894     prod *= st[i].prob;
895
896   inv_prod = 1;
897   for (i=0; i < nst; i++)
898     inv_prod *= 1.0 - st[i].prob;
899
900   taste = prod / (prod + inv_prod);
901   if (verbose > 1)
902     info ("taste -> %u\n\n", (unsigned int)(taste * 100));
903   return (unsigned int)(taste * 100);
904 }
905
906
907
908 static void
909 reset_hits (HIT_ARRAY ha)
910 {
911   int i;
912
913   for (i=0; i < ha->size; i++)
914     ha->hits[i] = 0;
915 }
916
917 static void
918 write_table (unsigned int ngood, unsigned int nbad)
919 {
920   int n;
921   HASH_ENTRY entry;
922
923   printf ("#\t0\t0\t0\t%u\t%u\n", ngood, nbad);
924   for (n=0; n < hash_table_size; n++)
925     {
926       for (entry = word_table[n]; entry; entry = entry->next)
927         if (entry->prob)
928           printf ("%s\t%d\t%u\t%u\n", entry->word, entry->prob,
929                   entry->veg_count, entry->spam_count);
930     }
931 }
932
933 static void
934 read_table (const char *fname,
935             unsigned int *ngood, unsigned int *nbad, unsigned int *nwords)
936 {
937   FILE *fp;
938   char line[MAX_WORDLENGTH + 100]; 
939   unsigned int lineno = 0;
940   char *p;
941
942   *nwords = 0;
943   fp = fopen (fname, "r");
944   if (!fp)
945     die ("can't open wordlist `%s': %s\n", fname, strerror (errno));
946
947   while ( fgets (line, sizeof line, fp) )
948     {
949       lineno++;
950       if (!*line) /* last line w/o LF? */
951         die ("incomplete line %u in `%s'\n", lineno, fname);
952
953       if (line[strlen (line)-1] != '\n')
954         die ("line %u in `%s' too long\n", lineno, fname);
955
956       line[strlen (line)-1] = 0;
957       if (!*line)
958         goto invalid_line;
959       p = strchr (line, '\t');
960       if (!p || p == line || (p-line) > MAX_WORDLENGTH )
961         goto invalid_line;
962       *p++ = 0;
963       if (lineno == 1)
964         {
965           if (sscanf (p, "%*u %*u %*u %u %u", ngood, nbad) < 2)
966             goto invalid_line;
967         }
968       else
969         {
970           HASH_ENTRY e;
971           unsigned int prob, g, b;
972           int is_new;
973
974           if (sscanf (p, "%u %u %u", &prob, &g, &b) < 3)
975             goto invalid_line;
976           if (prob > 99)
977             goto invalid_line;
978           e = store_word (line, &is_new);
979           if (!is_new)
980             die ("duplicate entry at line %u in `%s'\n", lineno, fname);
981
982           e->prob = prob? prob : 1;
983           e->veg_count = g;
984           e->spam_count = b;
985           ++*nwords;
986         }
987
988     }
989   if (ferror (fp))
990     die ("error reading wordlist `%s' at line %u: %s\n",
991          fname, lineno, strerror (errno));
992   fclose (fp);
993   return;
994
995  invalid_line:
996   die ("invalid line %u in `%s'\n", lineno, fname);
997 }
998
999
1000
1001
1002 static FILE *
1003 open_next_file (FILE *listfp, char *fname, size_t fnamelen)
1004 {
1005   FILE *fp;
1006   char line[2000];
1007
1008   while ( fgets (line, sizeof line, listfp) )
1009     {
1010       if (!*line) 
1011         { /* last line w/o LF? */
1012           if (fgetc (listfp) != EOF)
1013             error ("weird problem reading file list\n");
1014           break;
1015         }
1016       if (line[strlen (line)-1] != '\n')
1017         {
1018           error ("filename too long - skipping\n");
1019           while (getc (listfp) != '\n')
1020             ;
1021           continue;
1022         }
1023       line[strlen (line)-1] = 0;
1024       if (!*line)
1025         continue; /* skip empty lines */
1026
1027       fp = fopen (line, "rb");
1028       if (fp)
1029         {
1030           if (fname && fnamelen > 1)
1031             {
1032               strncpy (fname, line, fnamelen-1);
1033               fname[fnamelen-1] = 0;
1034             }
1035           return fp;
1036         }
1037       error ("can't open `%s': %s - skipped\n", line, strerror (errno));
1038     }
1039   if (ferror (listfp))
1040     error ("error reading file list: %s\n",  strerror (errno));
1041   return NULL;
1042 }
1043
1044
1045 static void
1046 check_and_print (unsigned int veg_count, unsigned int spam_count,
1047                  const char *filename, HIT_ARRAY ha)
1048 {
1049   int spamicity = check_spam (veg_count, spam_count, ha);
1050   if (name_only > 0 && spamicity > 90)
1051     puts (filename); /* contains spam */
1052   else if (name_only < 0 && spamicity <= 90)
1053     puts (filename); /* contains valuable blurbs */
1054   else if (!name_only)
1055     printf ("%s: %2u\n", filename, spamicity);
1056   reset_hits (ha);
1057 }
1058
1059
1060 \f
1061 /*
1062    Server code and startup 
1063 */
1064
1065 #ifdef HAVE_PTH
1066
1067 /* Write NBYTES of BUF to file descriptor FD. */
1068 static int
1069 writen (int fd, const void *buf, size_t nbytes)
1070 {
1071   size_t nleft = nbytes;
1072   int nwritten;
1073   
1074   while (nleft > 0)
1075     {
1076       nwritten = my_write( fd, buf, nleft );
1077       if (nwritten < 0)
1078         {
1079           if (errno == EINTR)
1080             nwritten = 0;
1081           else
1082             {
1083               error ("write failed: %s\n", strerror (errno));
1084               return -1;
1085             }
1086         }
1087       nleft -= nwritten;
1088       buf = (const char*)buf + nwritten;
1089     }
1090   
1091   return 0;
1092 }
1093
1094
1095 /* Read an entire line and return number of bytes read. */
1096 static int
1097 readline (int fd, char *buf, size_t buflen)
1098 {
1099   size_t nleft = buflen;
1100   char *p;
1101   int nread = 0;
1102
1103   while (nleft > 0)
1104     {
1105       int n = my_read (fd, buf, nleft);
1106       if (n < 0)
1107         {
1108           if (errno == EINTR)
1109             continue;
1110           return -1;
1111         }
1112       else if (!n)
1113         return -1; /* incomplete line */
1114
1115       p = buf;
1116       nleft -= n;
1117       buf += n;
1118       nread += n;
1119       
1120       for (; n && *p != '\n'; n--, p++)
1121         ;
1122       if (n)
1123         {
1124           break; /* at least one full line available - that's enough.
1125                     This function is just a simple implementation, so
1126                     it is okay to forget about pending bytes */
1127         }
1128     }
1129   
1130   return nread; 
1131 }
1132
1133
1134
1135 static int
1136 create_socket (const char *name, struct sockaddr_un *addr, size_t *len)
1137 {
1138   int fd;
1139
1140   if (strlen (name)+1 >= sizeof addr->sun_path) 
1141     die ("oops\n");
1142
1143   if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) 
1144     die ("can't create socket: %s\n", strerror(errno));
1145     
1146   memset (addr, 0, sizeof *addr);
1147   addr->sun_family = AF_UNIX;
1148   strcpy (addr->sun_path, name);
1149   *len = (offsetof (struct sockaddr_un, sun_path)
1150           + strlen (addr->sun_path) + 1);
1151   return fd;
1152 }
1153     
1154
1155 /* Try to connect to the socket NAME and return the file descriptor. */
1156 static int
1157 find_socket (const char *name)
1158 {
1159   int fd;
1160   struct sockaddr_un addr;
1161   size_t len;
1162
1163   fd = create_socket (name, &addr, &len);
1164   if (connect (fd, (struct sockaddr*)&addr, len) == -1)
1165     {
1166       if (errno != ECONNREFUSED)
1167         error ("can't connect to `%s': %s\n", name, strerror (errno));
1168       close (fd);
1169       return -1;
1170     }
1171
1172   return fd;
1173 }
1174
1175 static void
1176 handle_signal (int signo)
1177 {
1178   switch (signo)
1179     {
1180     case SIGHUP:
1181       info ("SIGHUP received - re-reading configuration\n");
1182       break;
1183       
1184     case SIGUSR1:
1185       if (verbose < 5)
1186         verbose++;
1187       info ("SIGUSR1 received - verbosity set to %d\n", verbose);
1188       break;
1189
1190     case SIGUSR2:
1191       if (verbose)
1192         verbose--;
1193       info ("SIGUSR2 received - verbosity set to %d\n", verbose );
1194       break;
1195
1196     case SIGTERM:
1197       info ("SIGTERM received - shutting down ...\n");
1198       exit (0);
1199       break;
1200         
1201     case SIGINT:
1202       info ("SIGINT received - immediate shutdown\n");
1203       exit (0);
1204       break;
1205
1206     default:
1207       info ("signal %d received - no action defined\n", signo);
1208     }
1209 }
1210
1211
1212 /* Handle a request */
1213 static void *
1214 handle_request (void *arg)
1215 {
1216   static HIT_ARRAY hit_arrays[10];
1217   HIT_ARRAY ha;
1218   int fd = (int)arg;
1219   int i;
1220   FILE *fp;
1221   char *p, buf[100];
1222   
1223   if (verbose > 1)
1224     info ("handler for fd %d started\n", fd);
1225   /* See whether we can use a hit_array from our attic */
1226   for (ha=NULL, i=0; i < DIM (hit_arrays); i++ )
1227     {
1228       if (hit_arrays[i])
1229         {
1230           ha = hit_arrays[i];
1231           hit_arrays[i] = NULL;
1232           break;
1233         }
1234     }
1235   if (!ha) /* no - create a new one */
1236      ha = new_hit_array ();
1237
1238   fp = fdopen (fd, "rw");
1239   if (!fp)
1240     {
1241       p = "0 fd_open_failed\n";
1242       writen (fd, p, strlen (p));
1243     }
1244   else
1245     {
1246       parse_message ("[net]", fp, -1, 0, ha);
1247       sprintf (buf, "%u\n", check_spam (srvr_veg_count, srvr_spam_count, ha));
1248       p = buf;
1249     }
1250   writen (fd, p, strlen (p));
1251
1252   if (fp)
1253     fclose (fp);
1254   else
1255     close (fd);
1256   reset_hits (ha);
1257   /* See whether we can put the array back into our attic */
1258   for (i=0; i < DIM (hit_arrays); i++ )
1259     {
1260       if (!hit_arrays[i])
1261         {
1262           hit_arrays[i] = ha;
1263           ha = NULL;
1264           break;
1265         }
1266     }
1267   if (ha)
1268     release_hit_array (ha);
1269
1270   if (verbose > 1)
1271     info ("handler for fd %d terminated\n", fd);
1272   return NULL;
1273 }
1274
1275 /* Send a request to check for spam to the server process.  Return the
1276    spam level. */
1277 static unsigned int
1278 transact_request (int fd, const char *fname, FILE *fp)
1279 {
1280   char buf[4096];
1281   size_t n;
1282
1283   do
1284     {
1285       n = fread (buf, 1, sizeof buf, fp);
1286       writen (fd, buf, n);
1287     }
1288   while ( n == sizeof buf);
1289   if (ferror (fp))
1290     die ("input read error\n");
1291   shutdown (fd, 1);
1292   if (readline (fd, buf, sizeof buf -1) == -1)
1293     die ("error reading from server: %s\n", strerror (errno));
1294   return atoi (buf);
1295 }
1296
1297
1298 /* Start a server process to listen on socket NAME. */
1299 static void
1300 start_server (const char *name)
1301 {
1302   int srvr_fd;
1303   struct sockaddr_un srvr_addr;
1304   size_t len;
1305   struct sigaction sa;
1306   pid_t pid;
1307   pth_attr_t tattr;
1308   pth_event_t ev;
1309   sigset_t sigs;
1310   int signo;
1311
1312   fflush (NULL);
1313   pid = fork ();
1314   if (pid == (pid_t)-1) 
1315     die ("fork failed: %s\n", strerror (errno));
1316
1317   if (pid) 
1318     {
1319       /* FIXME: we should use a pipe to wait forthe server to get ready */
1320       return; /* we are the parent */
1321     }
1322   /* this is the child */
1323   sa.sa_handler = SIG_IGN;
1324   sigemptyset (&sa.sa_mask);
1325   sa.sa_flags = 0;
1326   sigaction (SIGPIPE, &sa, NULL);
1327
1328   srvr_fd = create_socket (name, &srvr_addr, &len);
1329   if (bind (srvr_fd, (struct sockaddr*)&srvr_addr, len) == -1)
1330     {
1331       /* This might happen when server has been started in the meantime. */
1332       die ("error binding socket to `%s': %s\n", name, strerror (errno));
1333     }
1334   
1335   if (listen (srvr_fd, 5 ) == -1)
1336     die ("listen on `%s' failed: %s\n", name, strerror (errno));
1337   if (verbose)
1338     info ("listening on socket `%s'\n", name );
1339
1340   if (!pth_init ())
1341     die ("failed to initialize the Pth library\n");
1342
1343   tattr = pth_attr_new ();
1344   pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
1345   pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 32*1024);
1346   pth_attr_set (tattr, PTH_ATTR_NAME, PGMNAME);
1347
1348   sigemptyset (&sigs );
1349   sigaddset (&sigs, SIGHUP);
1350   sigaddset (&sigs, SIGUSR1);
1351   sigaddset (&sigs, SIGUSR2);
1352   sigaddset (&sigs, SIGINT);
1353   sigaddset (&sigs, SIGTERM);
1354   ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
1355
1356   running_as_server = 1; /* enable pth_yield(). */
1357   for (;;)
1358     {
1359       int fd;
1360       struct sockaddr_un paddr;
1361       socklen_t plen = sizeof (paddr);
1362
1363       fd = pth_accept_ev (srvr_fd, (struct sockaddr *)&paddr, &plen, ev);
1364       if (fd == -1)
1365         {
1366           if (pth_event_occurred (ev))
1367             {
1368               handle_signal (signo);
1369               continue;
1370             }
1371           error ("accept failed: %s - waiting 1s\n", strerror (errno));
1372           pth_sleep(1);
1373           continue;
1374         }
1375
1376       if (!pth_spawn (tattr, handle_request, (void*)fd))
1377         {
1378           error ("error spawning connection handler: %s\n", strerror (errno));
1379           close (fd);
1380         }
1381     }
1382   /*NOTREACHED*/
1383 }
1384
1385 #endif /*HAVE_PTH*/
1386
1387 static void
1388 usage (void)
1389 {
1390   fputs (
1391    "usage: " PGMNAME " [-t] wordlist [messages]\n"
1392    "       " PGMNAME "  -T  wordlist [messages-file-list]\n"
1393    "       " PGMNAME "  -s  wordlist [message]\n"
1394    "       " PGMNAME "  -l  veg.mbox spam.mbox [initial-wordlist]\n"
1395    "       " PGMNAME "  -L  veg-file-list spam-file-list [initial-wordlist]\n"
1396          "\n"
1397    "  -v      be more verbose\n"
1398    "  -l      learn mode (mbox)\n"
1399    "  -L      learn mode (one file per message)\n"
1400    "  -n      print only the names of spam files\n"
1401    "  -N      print only the names of vegetarian files\n"
1402    "  -s      auto server mode\n"
1403    , stderr );
1404   exit (1);
1405 }
1406
1407
1408 int
1409 main (int argc, char **argv)
1410 {
1411   int i;
1412   unsigned char *s;
1413   int skip = 0;
1414   int learn = 0;
1415   int indirect = 0;
1416   int server = 0;
1417   unsigned int veg_count=0, spam_count=0;
1418   FILE *fp;
1419   char fnamebuf[1000];
1420   int server_fd = -1;
1421   HIT_ARRAY ha = NULL;
1422
1423   /* Build the helptable for radix64 to bin conversion. */
1424   for (i=0; i < 256; i++ )
1425     asctobin[i] = 255; /* used to detect invalid characters */
1426   for (s=bintoasc, i=0; *s; s++, i++)
1427     asctobin[*s] = i;
1428
1429   if (argc < 1)
1430     usage ();  /* Hey, read how to use exec*(2) */
1431   argv++; argc--;
1432   for (; argc; argc--, argv++) 
1433     {
1434       const char *s = *argv;
1435       if (!skip && *s == '-')
1436         {
1437           s++;
1438           if( *s == '-' && !s[1] ) {
1439             skip = 1;
1440             continue;
1441           }
1442           if (*s == '-' || !*s) 
1443             usage();
1444
1445           while (*s)
1446             {
1447               if (*s=='v')
1448                 {
1449                   verbose++;
1450                   s++;
1451                 }
1452               else if (*s=='t')
1453                 {
1454                   learn = 0;
1455                   indirect = 0;
1456                   s++;
1457                 }
1458               else if (*s=='T')
1459                 {
1460                   learn = 0;
1461                   indirect = 1;
1462                   s++;
1463                 }
1464               else if (*s=='l')
1465                 {
1466                   learn = 1;
1467                   indirect = 0;
1468                   s++;
1469                 }
1470               else if (*s=='L')
1471                 {
1472                   learn = 1;
1473                   indirect = 1;
1474                   s++;
1475                 }
1476               else if (*s=='n')
1477                 {
1478                   name_only = 1;
1479                   s++;
1480                 }
1481               else if (*s=='N')
1482                 {
1483                   name_only = -1;
1484                   s++;
1485                 }
1486               else if (*s=='s')
1487                 {
1488                   server = 1;
1489                   s++;
1490                 }
1491               else if (*s)
1492                 usage();
1493             }
1494           continue;
1495         }
1496       else 
1497         break;
1498     }
1499
1500   if (server)
1501     {
1502       char namebuf[80];
1503       const char *name;
1504
1505       if (learn)
1506         die ("learn mode can't be combined with server mode\n");
1507 #ifndef HAVE_PTH
1508       die ("not compiled with GNU Pth support - can't run in server mode\n");
1509 #else
1510
1511       if (argc < 1)
1512         usage ();
1513
1514       /* Well, what name should we use format_ the socket.  The best
1515          thing would be to create it in the home directory, but this
1516          will be problematic for NFS mounted homes.  So for now we use
1517          a constant name under tmp.  Check whether we can use
1518          something under /var/run */
1519       name = getenv ("VEGETARISE_SOCKET");
1520       if (!name || !*name)
1521         {
1522           sprintf (namebuf, "/tmp/vegetarise-%lu/VEG_SOCK",
1523                    (unsigned long)getuid ());
1524           name = namebuf;
1525         }
1526
1527       server_fd = find_socket (name);
1528       if (server_fd == -1)
1529         {
1530           int tries;
1531           unsigned int nwords;
1532
1533           hash_table_size = 4999;
1534           word_table = xcalloc (hash_table_size, sizeof *word_table);
1535
1536           read_table (argv[0], &veg_count, &spam_count, &nwords);
1537           info ("starting server with "
1538                 "%u vegetarian, %u spam, %u words, %lu kb memory\n",
1539                 veg_count, spam_count, nwords,
1540                 (unsigned long int)total_memory_used/1024);
1541           srvr_veg_count = veg_count;
1542           srvr_spam_count = spam_count;
1543
1544           /* fixme: don't use sleep */
1545           start_server (name);
1546           for (tries=0; (tries < 10
1547                          && (server_fd=find_socket (name))==-1); tries++)
1548             sleep (1);
1549         }
1550       if (server_fd == -1)
1551         {
1552           error ("failed to start server - disabling server mode\n");
1553           server = 0;
1554         }
1555 #endif /*HAVE_PTH*/
1556     }
1557
1558   
1559   if (learn && !server)
1560     {
1561       FILE *veg_fp = NULL, *spam_fp = NULL;
1562       unsigned int nwords;
1563
1564       if (argc != 2 && argc != 3)
1565         usage ();
1566
1567       hash_table_size = 4999;
1568       word_table = xcalloc (hash_table_size, sizeof *word_table);
1569
1570       if ( strcmp (argv[0], "-") )
1571         {
1572           veg_fp = fopen (argv[0], "r");
1573           if (!veg_fp)
1574             die ("can't open `%s': %s\n", argv[0], strerror (errno));
1575         }
1576       if ( strcmp (argv[1], "-") )
1577         {
1578           spam_fp = fopen (argv[1], "r");
1579           if (!spam_fp)
1580             die ("can't open `%s': %s\n", argv[1], strerror (errno));
1581         }
1582
1583       if (argc == 3)
1584         { 
1585           info ("loading initial wordlist\n");
1586           read_table (argv[2], &veg_count, &spam_count, &nwords);
1587           info ("%u vegetarian, %u spam, %u words, %lu kb memory used\n",
1588                 veg_count, spam_count, nwords,
1589                 (unsigned long int)total_memory_used/1024);
1590         }
1591
1592       if (veg_fp)
1593         {
1594           info ("scanning vegetarian mail\n");
1595           if (indirect)
1596             {
1597               while ((fp = open_next_file (veg_fp, fnamebuf, sizeof fnamebuf)))
1598                 {
1599                   veg_count += parse_message (fnamebuf, fp, 0, 0, ha);
1600                   fclose (fp);
1601                 }
1602             }
1603           else
1604             veg_count += parse_message (argv[0], veg_fp, 0, 1, ha);
1605           fclose (veg_fp);
1606         }
1607
1608       if (spam_fp)
1609         {
1610           info ("scanning spam mail\n");
1611           if (indirect)
1612             {
1613               while ((fp = open_next_file (spam_fp, fnamebuf, sizeof fnamebuf)))
1614                 {
1615                   spam_count += parse_message (fnamebuf, fp, 1, 0, ha);
1616                   fclose (fp);
1617                 }
1618             }
1619           else
1620             spam_count += parse_message (argv[1], spam_fp, 1, 1, ha);
1621           fclose (spam_fp);
1622         }
1623       info ("computing probabilities\n");
1624       calc_probability (veg_count, spam_count);
1625       
1626       write_table (veg_count, spam_count);
1627
1628       info ("%u vegetarian, %u spam, %lu kb memory used\n",
1629             veg_count, spam_count,
1630             (unsigned long int)total_memory_used/1024);
1631     }
1632 #ifdef HAVE_PTH
1633   else if (server_fd != -1)
1634     { /* server mode */
1635
1636       argc--; argv++; /* ignore the wordlist */
1637       
1638       if (argc > 1)
1639         usage ();
1640                     
1641       fp = argc? fopen (argv[0], "r") : stdin;
1642       if (!fp)
1643         die ("can't open `%s': %s\n", argv[0], strerror (errno));
1644       if (transact_request (server_fd, argc? argv[0]:"-", fp) > 90)
1645         {
1646           close (server_fd);
1647           if (verbose)
1648             puts ("spam\n");
1649           exit (0); 
1650         }
1651       close (server_fd);
1652       exit (1); /* Non-Spam but we use false so that a
1653                    system error does not lead false
1654                    positives */
1655     }
1656 #endif /*HAVE_PTH*/
1657   else
1658     {
1659       unsigned int nwords;
1660
1661       if (argc < 1)
1662         usage ();
1663
1664       hash_table_size = 4999;
1665       word_table = xcalloc (hash_table_size, sizeof *word_table);
1666
1667       read_table (argv[0], &veg_count, &spam_count, &nwords);
1668       argc--; argv++;
1669       if (verbose)
1670         info ("%u vegetarian, %u spam, %u words, %lu kb memory used\n",
1671               veg_count, spam_count, nwords,
1672               (unsigned long int)total_memory_used/1024);
1673       
1674       ha = new_hit_array ();
1675
1676       if (!argc)
1677         {
1678           if (indirect)
1679             {
1680               while ((fp = open_next_file (stdin, fnamebuf, sizeof fnamebuf)))
1681                 {
1682                   parse_message (fnamebuf, fp, 0, 0, ha);
1683                   fclose (fp);
1684                   check_and_print (veg_count, spam_count, fnamebuf, ha);
1685                 }
1686             }
1687           else
1688             {
1689               parse_message ("-", stdin, -1, 0, ha);
1690               if ( check_spam (veg_count, spam_count, ha) > 90)
1691                 {
1692                   if (verbose)
1693                     puts ("spam\n");
1694                   exit (0);
1695                 }
1696               else
1697                 exit (1); /* Non-Spam but we use false so that a
1698                              system error does not lead false
1699                              positives */
1700             }
1701         }
1702       else
1703         {
1704           for (; argc; argc--, argv++)
1705             {
1706               FILE *fp = fopen (argv[0], "r");
1707               if (!fp)
1708                 {
1709                   error ("can't open `%s': %s\n", argv[0], strerror (errno));
1710                   continue;
1711                 }
1712               if (indirect)
1713                 {
1714                   FILE *fp2;
1715                   while ((fp2 = open_next_file (fp,fnamebuf, sizeof fnamebuf)))
1716                     {
1717                       parse_message (fnamebuf, fp2, 0, 0, ha);
1718                       fclose (fp2);
1719                       check_and_print (veg_count, spam_count, fnamebuf, ha);
1720                     }
1721                 }
1722               else
1723                 {
1724                   parse_message (argv[0], fp, -1, 0, ha);
1725                   check_and_print (veg_count, spam_count, argv[0], ha);
1726                 }
1727               fclose (fp);
1728             }
1729         }
1730     }
1731
1732   return 0;
1733 }
1734
1735 /*
1736 Local Variables:
1737 compile-command: "gcc -Wall -g -DHAVE_PTH -o vegetarise vegetarise.c -lpth"
1738 End:
1739 */