Implemented a server mode
authorwerner <werner>
Sat, 19 Oct 2002 15:07:12 +0000 (15:07 +0000)
committerwerner <werner>
Sat, 19 Oct 2002 15:07:12 +0000 (15:07 +0000)
vegetarise.c

index c19ee77..ebbd77d 100644 (file)
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  *
- * $Id: vegetarise.c,v 1.1 2002-10-13 21:22:05 werner Exp $
+ * $Id: vegetarise.c,v 1.2 2002-10-19 15:07:12 werner Exp $
  */
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stddef.h>
 #include <string.h>
+#include <stdarg.h>
 #include <ctype.h>
 #include <errno.h>
 #include <assert.h>
-
-#ifdef __GNUC__
-#define inline __inline__
-#else
-#define inline
-#endif
-
-#define min(a,b) ((a) < (b)? (a): (b))
-#define max(a,b) ((a) > (b)? (a): (b))
+#ifdef HAVE_PTH /* In theory we could use sockets without Pth but it
+                   does not make much sense to we require it. */
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <pth.h>
+#endif /*HAVE_PTH*/
 
 #define PGMNAME "vegetarise"
 
 #define MAX_WORDLENGTH 50 /* max. length of a word */
 #define MAX_WORDS 15      /* max. number of words to look at. */
 
+
 /* A list of token characters.  There is explicit code for 8bit
    characters. */
 #define TOKENCHARS "abcdefghijklmnopqrstuvwxyz" \
                    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
                    "0123456789-_'$"
 
+#ifdef __GNUC__
+#define inline __inline__
+#else
+#define inline
+#endif
+
+#define xtoi_1(a)   ((a) <= '9'? ((a)- '0'): \
+                     (a) <= 'F'? ((a)-'A'+10):((a)-'a'+10))
+#define xtoi_2(a,b) ((xtoi_1(a) * 16) + xtoi_1(b))
+
+
 struct pushback_s {
   int buflen;
   char buf[100];
   int nl_seen;
-  int base64_state;
+  int state;
   int base64_nl;
   int base64_val;
+  int qp1;
 };
 typedef struct pushback_s PUSHBACK;
 
@@ -66,14 +79,16 @@ struct hash_entry_s {
 };
 typedef struct hash_entry_s *HASH_ENTRY;
 
-static int silent;
 static int verbose;
 static int learning;
+static int name_only;
 
 static size_t total_memory_used;
 
 static int hash_table_size; 
 static HASH_ENTRY *word_table;
+static unsigned int srvr_veg_count, srvr_spam_count;
+
 
 /* Base64 conversion tables. */
 static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -82,6 +97,81 @@ static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 static unsigned char asctobin[256]; /* runtime initialized */
 
 
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)
+#define ATTR_PRINTF(a,b)    __attribute__ ((format (printf,a,b)))
+#define ATTR_NR_PRINTF(a,b) __attribute__ ((noreturn,format (printf,a,b)))
+#else
+#define ATTR_PRINTF(a,b)    
+#define ATTR_NR_PRINTF(a,b)
+#endif
+
+
+static void die (const char *format, ...)   ATTR_NR_PRINTF(1,2);
+static void error (const char *format, ...) ATTR_PRINTF(1,2);
+static void info (const char *format, ...)  ATTR_PRINTF(1,2);
+
+
+\f
+/* 
+    Helper
+
+*/
+
+static void
+die (const char *fmt, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, fmt);
+  fputs (PGMNAME": fatal error: ", stderr);
+  vfprintf (stderr, fmt, arg_ptr);
+  va_end (arg_ptr);
+  exit (1);
+}
+
+static void
+error (const char *fmt, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, fmt);
+  fputs (PGMNAME": error: ", stderr);
+  vfprintf (stderr, fmt, arg_ptr);
+  va_end (arg_ptr);
+}
+
+static void
+info (const char *fmt, ...)
+{
+  va_list arg_ptr;
+
+  va_start (arg_ptr, fmt);
+  fputs (PGMNAME": ", stderr);
+  vfprintf (stderr, fmt, arg_ptr);
+  va_end (arg_ptr);
+}
+
+static void *
+xmalloc (size_t n)
+{
+  void *p = malloc (n);
+  if (!p)
+    die ("out of core\n");
+  total_memory_used += n;
+  return p;
+}
+
+static void *
+xcalloc (size_t n, size_t k)
+{
+  void *p = calloc (n, k);
+  if (!p)
+    die ("out of core\n");
+  total_memory_used += n * k;
+  return p;
+}
+
+
 static inline unsigned int
 hash_string (const char *s)
 {
@@ -105,7 +195,7 @@ pushback (PUSHBACK *pb, int c)
   if (pb->buflen < sizeof pb->buf)
       pb->buf[pb->buflen++] = c;
   else
-    fprintf (stderr, PGMNAME": comment parsing problem\n");
+    error ("comment parsing problem\n");
 }
 
 
@@ -185,13 +275,13 @@ next_char (FILE *fp, PUSHBACK *pb)
  next:
   if ((c=basic_next_char (fp, pb)) == EOF)
     return c;
-  switch (pb->base64_state)
+  switch (pb->state)
     {
     case 0: break;
     case 1: 
       if (pb->nl_seen && (c == '\r' || c == '\n'))
         {
-          pb->base64_state = 2;
+          pb->state = 2;
           goto next;
         }
       break;
@@ -200,7 +290,7 @@ next_char (FILE *fp, PUSHBACK *pb)
         break;
       pb->nl_seen = 0;
       pb->base64_nl = 0;
-      pb->base64_state = 3;
+      pb->state = 3;
       /* fall through */
     case 3: /* 1. base64 byte */
     case 4: /* 2. base64 byte */
@@ -213,7 +303,7 @@ next_char (FILE *fp, PUSHBACK *pb)
         }
       if (pb->base64_nl && c == '-')
         {
-          pb->base64_state = 0; /* end of mime part */
+          pb->state = 0; /* end of mime part */
           c = ' '; /* make sure that last token gets processed. */
           break;
         }
@@ -226,37 +316,94 @@ next_char (FILE *fp, PUSHBACK *pb)
       if ((c = asctobin[c]) == 255 )
         goto next;  /* invalid base64 character */
         
-      switch (pb->base64_state)
+      switch (pb->state)
         {
         case 3: 
           pb->base64_val = c << 2;
-          pb->base64_state = 4;
+          pb->state = 4;
           goto next;
         case 4:
           pb->base64_val |= (c>>4)&3;
           c2 = pb->base64_val;
           pb->base64_val = (c<<4)&0xf0;
           c = c2;
-          pb->base64_state = 5;
+          pb->state = 5;
           break; /* deliver C */
         case 5:
           pb->base64_val |= (c>>2)&15;
           c2 = pb->base64_val;
           pb->base64_val = (c<<6)&0xc0;
           c = c2;
-          pb->base64_state = 6;
+          pb->state = 6;
           break; /* deliver C */
         case 6:
           pb->base64_val |= c&0x3f;
           c = pb->base64_val;
-          pb->base64_state = 3;
+          pb->state = 3;
           break; /* deliver C */
         }
+      break;
+    case 101: /* quoted-printable */
+      if (pb->nl_seen && (c == '\r' || c == '\n'))
+        {
+          pb->state = 102;
+          goto next;
+        }
+      break;
+    case 102:
+      if (pb->nl_seen && (c == '-'))
+        {
+          pb->state = 105;
+          goto next;
+        }
+      else if ( c == '=' )
+        {
+          pb->state = 103;
+          goto next;
+        }
+      break;
+    case 103:
+      if ( isxdigit (c) )
+        {
+          pb->qp1 = c;
+          pb->state = 104;
+          goto next;
+        }
+      pb->state = 102;
+      break;
+    case 104:
+      if ( isxdigit (c) )
+        c = xtoi_2 (pb->qp1, c);
+      pb->state = 102;
+      break;
+    case 105:
+      if (c == '-')
+        {
+          pb->state = 106;
+          goto next;
+        }
+      pb->state = 102;
+      break; /* we drop one, but that's okat for this application */
+    case 106:
+      if ( !isspace (c) == ' ')
+        {
+          pb->state = 0; /* assume end of mime part */
+          c = ' '; /* make sure that last token gets processed. */
+        }
+      else
+        pb->state = 102; 
+      break;
+      
     }
   return c;
 }
 
 
+\f
+/* 
+   real processing stuff
+*/
+
 static HASH_ENTRY
 store_word (const char *word, int *is_new)
 {
@@ -271,13 +418,7 @@ store_word (const char *word, int *is_new)
   if (!entry)
     {
       size_t n = sizeof *entry + strlen (word);
-      entry = malloc (n);
-      if (!entry)
-        {
-          fprintf (stderr, PGMNAME": out of core\n");
-          exit (1);
-        }
-      total_memory_used += n;
+      entry = xmalloc (n);
 
       strcpy (entry->word, word);
       entry->veg_count = 0;
@@ -300,6 +441,8 @@ check_one_word ( const char *word, int left_anchored, int is_spam)
   const char *p;
   int n0, n1, n2, n3, n4, n5;
 
+/*    fprintf (stderr, "token `%s'\n", word); */
+
   for (p=word; isdigit (*p); p++)
     ;
   if (!*p || wordlen < 3) 
@@ -352,9 +495,10 @@ check_one_word ( const char *word, int left_anchored, int is_spam)
     store_word (word, NULL)->veg_count++;
 }
 
-
-static void
-parse_words (const char *fname, FILE *fp, int is_spam)
+/* Parse a message and return the number of messages in case it is an
+   mbox message as indicated by IS_MBOX passed as true. */
+static unsigned int
+parse_message (const char *fname, FILE *fp, int is_spam, int is_mbox)
 {
   int c;
   char aword[MAX_WORDLENGTH+1];
@@ -363,6 +507,7 @@ parse_words (const char *fname, FILE *fp, int is_spam)
   int left_anchored = 0;
   int maybe_base64 = 0;
   PUSHBACK pbbuf;
+  unsigned int msgcount = 0;
 
   memset (&pbbuf, 0, sizeof pbbuf);
   while ( (c=next_char (fp, &pbbuf)) != EOF)
@@ -382,9 +527,24 @@ parse_words (const char *fname, FILE *fp, int is_spam)
               aword[idx] = 0;
               if (maybe_base64)
                 {
-                  pbbuf.base64_state = !strcasecmp (aword, "base64");
+                  if ( !strcasecmp (aword, "base64") )
+                    pbbuf.state = 1;
+                  else if ( !strcasecmp (aword, "quoted-printable") )
+                    pbbuf.state = 101;
+                  else
+                    pbbuf.state = 0;
                   maybe_base64 = 0; 
                 }
+              else if (is_mbox && left_anchored
+                       && !pbbuf.state && !strcmp (aword, "From"))
+                {
+                  if (c != ' ')
+                    {
+                      pbbuf.nl_seen = (c == '\n');
+                      goto again;
+                    }
+                  msgcount++;
+                }
               else if (left_anchored
                   && (!strcasecmp (aword, "Received")
                       || !strcasecmp (aword, "Date")))
@@ -433,6 +593,7 @@ parse_words (const char *fname, FILE *fp, int is_spam)
                   pbbuf.nl_seen = (c == '\n');
                   goto again;
                 }
+#if 0
               else if (c == '=')
                 {
                   /* Assume an QP encoded character if followed by an
@@ -448,6 +609,7 @@ parse_words (const char *fname, FILE *fp, int is_spam)
                   pbbuf.nl_seen = (c == '\n');
                   goto again;
                 }
+#endif
               else
                 check_one_word (aword, left_anchored, is_spam);
             }
@@ -463,11 +625,10 @@ parse_words (const char *fname, FILE *fp, int is_spam)
     }
  leave:
   if (ferror (fp))
-    {
-      fprintf (stderr, PGMNAME": error reading `%s': %s\n",
-               fname, strerror (errno));
-      exit (1);
-    }
+      die ("error reading `%s': %s\n", fname, strerror (errno));
+
+  msgcount++;
+  return msgcount;
 }
 
 
@@ -507,16 +668,9 @@ calc_probability (unsigned int ngood, unsigned int nbad)
   unsigned int g, b; 
 
   if (!ngood)
-    {
-      fprintf (stderr, PGMNAME": no vegetarian mails available - stop\n");
-      exit (1);
-    }
+    die ("no vegetarian mails available - stop\n");
   if (!nbad)
-    {
-      fprintf (stderr, PGMNAME": no spam mails available - stop\n");
-      exit (1);
-    }
-    
+    die ("no spam mails available - stop\n");
 
   for (n=0; n < hash_table_size; n++)
     {
@@ -604,15 +758,15 @@ check_spam (unsigned int ngood, unsigned int nbad)
   /* ST has now the NST most intersting words */
   if (!nst)
     {
-      fprintf (stderr, PGMNAME": not enough words - assuming good\n");
+      info ("not enough words - assuming goodness\n");
       return 100;
     }
 
   if (verbose > 1)
     {
       for (i=0; i < nst; i++)
-        fprintf (stderr, PGMNAME": prob %3.2f dist %3d for `%s'\n",
-                 st[i].prob, st[i].d, st[i].e->word);
+        info ("prob %3.2f dist %3d for `%s'\n",
+              st[i].prob, st[i].d, st[i].e->word);
     }
 
   prod = 1;
@@ -670,26 +824,17 @@ read_table (const char *fname,
   *nwords = 0;
   fp = fopen (fname, "r");
   if (!fp)
-    {
-      fprintf (stderr, PGMNAME": can't open wordlist `%s': %s\n",
-               fname, strerror (errno));
-      exit (1);
-    }
+    die ("can't open wordlist `%s': %s\n", fname, strerror (errno));
+
   while ( fgets (line, sizeof line, fp) )
     {
       lineno++;
-      if (!*line)
-        { /* last line w/o LF? */
-          fprintf (stderr, PGMNAME": incomplete line %u in `%s'\n",
-                   lineno, fname);
-          exit (1);
-        }
+      if (!*line) /* last line w/o LF? */
+        die ("incomplete line %u in `%s'\n", lineno, fname);
+
       if (line[strlen (line)-1] != '\n')
-        {
-          fprintf (stderr, PGMNAME": line %u in `%s' too long\n",
-                   lineno, fname);
-          exit (1);
-        }
+        die ("line %u in `%s' too long\n", lineno, fname);
+
       line[strlen (line)-1] = 0;
       if (!*line)
         goto invalid_line;
@@ -714,11 +859,8 @@ read_table (const char *fname,
             goto invalid_line;
           e = store_word (line, &is_new);
           if (!is_new)
-            {
-              fprintf (stderr, PGMNAME": duplicate entry at line %u in `%s'\n",
-                       lineno, fname);
-              exit (1);
-            }
+            die ("duplicate entry at line %u in `%s'\n", lineno, fname);
+
           e->prob = prob? prob : 1;
           e->veg_count = g;
           e->spam_count = b;
@@ -727,39 +869,35 @@ read_table (const char *fname,
 
     }
   if (ferror (fp))
-    {
-      fprintf (stderr, PGMNAME": error reading wordlist `%s' at line %u: %s\n",
-               fname, lineno, strerror (errno));
-      exit (1);
-    }
+    die ("error reading wordlist `%s' at line %u: %s\n",
+         fname, lineno, strerror (errno));
   fclose (fp);
   return;
 
  invalid_line:
-  fprintf (stderr, PGMNAME": invalid line %u in `%s'\n", lineno, fname);
-  exit (1);
+  die ("invalid line %u in `%s'\n", lineno, fname);
 }
 
 
 
 
 static FILE *
-open_next_file (FILE *listfp)
+open_next_file (FILE *listfp, char *fname, size_t fnamelen)
 {
   FILE *fp;
   char line[2000];
 
   while ( fgets (line, sizeof line, listfp) )
     {
-      if (!*line)
+      if (!*line) 
         { /* last line w/o LF? */
           if (fgetc (listfp) != EOF)
-            fprintf (stderr, PGMNAME": weird problem reading file list\n");
+            error ("weird problem reading file list\n");
           break;
         }
       if (line[strlen (line)-1] != '\n')
         {
-          fprintf (stderr, PGMNAME": filename too long - skipping\n");
+          error ("filename too long - skipping\n");
           while (getc (listfp) != '\n')
             ;
           continue;
@@ -770,25 +908,352 @@ open_next_file (FILE *listfp)
 
       fp = fopen (line, "rb");
       if (fp)
-        return fp;
-      fprintf (stderr, PGMNAME": skipping `%s': %s\n",
-               line, strerror (errno));
+        {
+          if (fname && fnamelen > 1)
+            {
+              strncpy (fname, line, fnamelen-1);
+              fname[fnamelen-1] = 0;
+            }
+          return fp;
+        }
+      error ("can't open `%s': %s - skipped\n", line, strerror (errno));
     }
   if (ferror (listfp))
-    fprintf (stderr, PGMNAME": error reading file list: %s\n",
-             strerror (errno));
+    error ("error reading file list: %s\n",  strerror (errno));
   return NULL;
 }
 
+
+static void
+check_and_print (unsigned int veg_count, unsigned int spam_count,
+                 const char *filename)
+{
+  int spamicity = check_spam (veg_count, spam_count);
+  if (name_only > 0 && spamicity > 90)
+    puts (filename); /* contains spam */
+  else if (name_only < 0 && spamicity <= 90)
+    puts (filename); /* contains valuable blurbs */
+  else if (!name_only)
+    printf ("%s: %2u\n", filename, spamicity);
+  reset_hits ();
+}
+
+
+\f
+/*
+   Server code and startup 
+*/
+
+#ifdef HAVE_PTH
+
+/* Write NBYTES of BUF to file descriptor FD. */
+static int
+writen (int fd, const void *buf, size_t nbytes)
+{
+  size_t nleft = nbytes;
+  int nwritten;
+  
+  while (nleft > 0)
+    {
+      nwritten = write( fd, buf, nleft );
+      if (nwritten < 0)
+        {
+          if (errno == EINTR)
+            nwritten = 0;
+          else
+            {
+              error ("write failed: %s\n", strerror (errno));
+              return -1;
+            }
+        }
+      nleft -= nwritten;
+      buf = (const char*)buf + nwritten;
+    }
+  
+  return 0;
+}
+
+
+/* Read an entire line and return number of bytes read. */
+static int
+readline (int fd, char *buf, size_t buflen)
+{
+  size_t nleft = buflen;
+  char *p;
+  int nread = 0;
+
+  while (nleft > 0)
+    {
+      int n = read (fd, buf, nleft);
+      if (n < 0)
+        {
+          if (errno == EINTR)
+            continue;
+          return -1;
+        }
+      else if (!n)
+        return -1; /* incomplete line */
+
+      p = buf;
+      nleft -= n;
+      buf += n;
+      nread += n;
+      
+      for (; n && *p != '\n'; n--, p++)
+        ;
+      if (n)
+        {
+          break; /* at least one full line available - that's enough.
+                    This function is just a simple implementation, so
+                    it is okay to forget about pending bytes */
+        }
+    }
+  
+  return nread; 
+}
+
+
+
+static int
+create_socket (const char *name, struct sockaddr_un *addr, size_t *len)
+{
+  int fd;
+
+  if (strlen (name)+1 >= sizeof addr->sun_path) 
+    die ("oops\n");
+
+  if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) 
+    die ("can't create socket: %s\n", strerror(errno));
+    
+  memset (addr, 0, sizeof *addr);
+  addr->sun_family = AF_UNIX;
+  strcpy (addr->sun_path, name);
+  *len = (offsetof (struct sockaddr_un, sun_path)
+          + strlen (addr->sun_path) + 1);
+  return fd;
+}
+    
+
+/* Try to connect to the socket NAME and return the file descriptor. */
+static int
+find_socket (const char *name)
+{
+  int fd;
+  struct sockaddr_un addr;
+  size_t len;
+
+  fd = create_socket (name, &addr, &len);
+  if (connect (fd, (struct sockaddr*)&addr, len) == -1)
+    {
+      if (errno != ECONNREFUSED)
+        error ("can't connect to `%s': %s\n", name, strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  return fd;
+}
+
+static void
+handle_signal (int signo)
+{
+  switch (signo)
+    {
+    case SIGHUP:
+      info ("SIGHUP received - re-reading configuration\n");
+      break;
+      
+    case SIGUSR1:
+      if (verbose < 5)
+        verbose++;
+      info ("SIGUSR1 received - verbosity set to %d\n", verbose);
+      break;
+
+    case SIGUSR2:
+      if (verbose)
+        verbose--;
+      info ("SIGUSR2 received - verbosity set to %d\n", verbose );
+      break;
+
+    case SIGTERM:
+      info ("SIGTERM received - shutting down ...\n");
+      exit (0);
+      break;
+        
+    case SIGINT:
+      info ("SIGINT received - immediate shutdown\n");
+      exit (0);
+      break;
+
+    default:
+      info ("signal %d received - no action defined\n", signo);
+    }
+}
+
+
+/* Handle a request */
+static void *
+handle_request (void *arg)
+{
+  int fd = (int)arg;
+  FILE *fp;
+  char *p, buf[100];
+
+
+  if (verbose > 1)
+    info ("handler for fd %d started\n", fd);
+  info ("handling request ...\n");
+
+  fp = fdopen (fd, "rw");
+  if (!fp)
+    {
+      p = "0 fd_open_failed\n";
+      writen (fd, p, strlen (p));
+    }
+  else
+    {
+      parse_message ("[net]", fp, -1, 0);
+      sprintf (buf, "%u\n", check_spam (srvr_veg_count, srvr_spam_count));
+      p = buf;
+    }
+  writen (fd, p, strlen (p));
+
+  if (fp)
+    fclose (fp);
+  else
+    close (fd);
+  reset_hits (); /* FIXME: Need a hit table per connection */
+  if (verbose > 1)
+    info ("handler for fd %d terminated\n", fd);
+  return NULL;
+}
+
+/* Send a request to check for spam to the server process.  Return the
+   spam level. */
+static unsigned int
+transact_request (int fd, const char *fname, FILE *fp)
+{
+  char buf[4096];
+  size_t n;
+
+  do
+    {
+      n = fread (buf, 1, sizeof buf, fp);
+      writen (fd, buf, n);
+    }
+  while ( n == sizeof buf);
+  if (ferror (fp))
+    die ("input read error\n");
+  shutdown (fd, 1);
+  if (readline (fd, buf, sizeof buf -1) == -1)
+    die ("error reading from server: %s\n", strerror (errno));
+  return atoi (buf);
+}
+
+
+/* Start a server process to listen on socket NAME. */
+static void
+start_server (const char *name)
+{
+  int srvr_fd;
+  struct sockaddr_un srvr_addr;
+  size_t len;
+  struct sigaction sa;
+  pid_t pid;
+  pth_attr_t tattr;
+  pth_event_t ev;
+  sigset_t sigs;
+  int signo;
+
+  fflush (NULL);
+  pid = fork ();
+  if (pid == (pid_t)-1) 
+    die ("fork failed: %s\n", strerror (errno));
+
+  if (pid) 
+    return; /* we are the parent */
+
+  /* this is the child */
+  sa.sa_handler = SIG_IGN;
+  sigemptyset (&sa.sa_mask);
+  sa.sa_flags = 0;
+  sigaction (SIGPIPE, &sa, NULL);
+
+  srvr_fd = create_socket (name, &srvr_addr, &len);
+  if (bind (srvr_fd, (struct sockaddr*)&srvr_addr, len) == -1)
+    {
+      /* This might happen when server has been started in the meantime. */
+      die ("error binding socket to `%s': %s\n", name, strerror (errno));
+    }
+  
+  if (listen (srvr_fd, 5 ) == -1)
+    die ("listen on `%s' failed: %s\n", name, strerror (errno));
+  if (verbose)
+    info ("listening on socket `%s'\n", name );
+
+  if (!pth_init ())
+    die ("failed to initialize the Pth library\n");
+
+  tattr = pth_attr_new ();
+  pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+  pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 32*1024);
+  pth_attr_set (tattr, PTH_ATTR_NAME, PGMNAME);
+
+  sigemptyset (&sigs );
+  sigaddset (&sigs, SIGHUP);
+  sigaddset (&sigs, SIGUSR1);
+  sigaddset (&sigs, SIGUSR2);
+  sigaddset (&sigs, SIGINT);
+  sigaddset (&sigs, SIGTERM);
+  ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+
+  for (;;)
+    {
+      int fd;
+      struct sockaddr_un paddr;
+      socklen_t plen = sizeof (paddr);
+
+      fd = pth_accept_ev (srvr_fd, (struct sockaddr *)&paddr, &plen, ev);
+      if (fd == -1)
+        {
+          if (pth_event_occurred (ev))
+            {
+              handle_signal (signo);
+              continue;
+           }
+          error ("accept failed: %s - waiting 1s\n", strerror (errno));
+          pth_sleep(1);
+          continue;
+       }
+
+      if (!pth_spawn (tattr, handle_request, (void*)fd))
+        {
+          error ("error spawning connection handler: %s\n", strerror (errno));
+          close (fd);
+       }
+    }
+  /*NOTREACHED*/
+}
+
+#endif /*HAVE_PTH*/
+
 static void
 usage (void)
 {
-  fputs ("usage: " PGMNAME " [options] [--] [veg-file-list spam-file-list]\n"
+  fputs (
+   "usage: " PGMNAME " [-t] wordlist [messages]\n"
+   "       " PGMNAME "  -T  wordlist [messages-file-list]\n"
+   "       " PGMNAME "  -s  wordlist [message]\n"
+   "       " PGMNAME "  -l  veg.mbox spam.mbox\n"
+   "       " PGMNAME "  -L  veg-file-list spam-file-list\n"
          "\n"
-         "  -q   be silent\n"
-         "  -v   be more verbose\n"
-         "  -L   learn mode\n"
-         , stderr );
+   "  -v      be more verbose\n"
+   "  -l      learn mode (mbox)\n"
+   "  -L      learn mode (one file per message)\n"
+   "  -n      print only the names of spam files\n"
+   "  -N      print only the names of vegetarian files\n"
+   "  -s      auto server mode\n"
+   , stderr );
   exit (1);
 }
 
@@ -800,7 +1265,12 @@ main (int argc, char **argv)
   unsigned char *s;
   int skip = 0;
   int learn = 0;
+  int indirect = 0;
+  int server = 0;
   unsigned int veg_count=0, spam_count=0;
+  FILE *fp;
+  char fnamebuf[1000];
+  int server_fd = -1;
 
   /* Build the helptable for radix64 to bin conversion. */
   for (i=0; i < 256; i++ )
@@ -827,19 +1297,48 @@ main (int argc, char **argv)
 
           while (*s)
             {
-              if (*s=='q')
+              if (*s=='v')
+                {
+                  verbose++;
+                  s++;
+                }
+              else if (*s=='t')
                 {
-                  silent=1;
+                  learn = 0;
+                  indirect = 0;
                   s++;
                 }
-              else if (*s=='v')
+              else if (*s=='T')
                 {
-                  verbose++;
+                  learn = 0;
+                  indirect = 1;
+                  s++;
+                }
+              else if (*s=='l')
+                {
+                  learn = 1;
+                  indirect = 0;
                   s++;
                 }
               else if (*s=='L')
                 {
                   learn = 1;
+                  indirect = 1;
+                  s++;
+                }
+              else if (*s=='n')
+                {
+                  name_only = 1;
+                  s++;
+                }
+              else if (*s=='N')
+                {
+                  name_only = -1;
+                  s++;
+                }
+              else if (*s=='s')
+                {
+                  server = 1;
                   s++;
                 }
               else if (*s)
@@ -851,91 +1350,178 @@ main (int argc, char **argv)
         break;
     }
 
-  hash_table_size = 4999;
-  word_table = calloc (hash_table_size, sizeof *word_table);
-  if (!word_table)
+  if (server)
     {
-      fprintf (stderr, PGMNAME": out of core\n");
-      exit (1);
+      char name[80];
+
+      if (learn)
+        die ("learn mode can't be combined with server mode\n");
+#ifndef HAVE_PTH
+      die ("not compiled with GNU Pth support - can't run in server mode\n");
+#else
+
+      if (argc < 1)
+        usage ();
+
+      /* Well, what name should we use format_ the socket.  The best
+         thing would be to create it in the home directory, but this
+         will be problematic for NFS mounted homes.  So for now we use
+         a constant name under tmp.  Check whether we can use
+         something under /var/run */
+      sprintf (name, "/tmp/vegetarise-%lu/VEG_SOCK", (unsigned long)getuid ());
+
+      server_fd = find_socket (name);
+      if (server_fd == -1)
+        {
+          int tries;
+          unsigned int nwords;
+
+          hash_table_size = 4999;
+          word_table = xcalloc (hash_table_size, sizeof *word_table);
+
+          read_table (argv[0], &veg_count, &spam_count, &nwords);
+          info ("starting server with "
+                "%u vegetarian, %u spam, %u words, %lu kb memory\n",
+                veg_count, spam_count, nwords,
+                (unsigned long int)total_memory_used/1024);
+          srvr_veg_count = veg_count;
+          srvr_spam_count = spam_count;
+
+          /* fixme: don't use sleep */
+          start_server (name);
+          for (tries=0; (tries < 10
+                         && (server_fd=find_socket (name))==-1); tries++)
+            sleep (1);
+        }
+      if (server_fd == -1)
+        {
+          error ("failed to start server - disabling server mode\n");
+          server = 0;
+        }
+#endif /*HAVE_PTH*/
     }
-  total_memory_used = hash_table_size * sizeof *word_table;
+
   
   if (learn)
     {
-      FILE *veg_fp, *spam_fp, *fp;
+      FILE *veg_fp = NULL, *spam_fp = NULL;
 
       if (argc != 2)
         usage ();
 
+      hash_table_size = 4999;
+      word_table = xcalloc (hash_table_size, sizeof *word_table);
+
       learning = 1;
       veg_fp = fopen (argv[0], "r");
       if (!veg_fp)
-        {
-          fprintf (stderr, PGMNAME": can't open `%s': %s\n",
-                   argv[0], strerror (errno));
-          exit (1);
-        }
+          die ("can't open `%s': %s\n", argv[0], strerror (errno));
       spam_fp = fopen (argv[1], "r");
       if (!spam_fp)
-        {
-          fprintf (stderr, PGMNAME": can't open `%s': %s\n",
-                   argv[1], strerror (errno));
-          exit (1);
-        }
-      
+        die ("can't open `%s': %s\n", argv[1], strerror (errno));
+
       if (verbose)
-        fprintf (stderr, PGMNAME": scanning vegetarian mail\n");
-      while ((fp = open_next_file (veg_fp)))
+        info ("scanning vegetarian mail\n");
+
+      if (indirect)
         {
-          parse_words (*argv, fp, 0);
-          veg_count++;
-          fclose (fp);
+          while ((fp = open_next_file (veg_fp, fnamebuf, sizeof fnamebuf)))
+            {
+              veg_count += parse_message (fnamebuf, fp, 0, 0);
+              fclose (fp);
+            }
         }
+      else
+        veg_count += parse_message (argv[0], veg_fp, 0, 1);
       fclose (veg_fp);
-
+        
       if (verbose)
-        fprintf (stderr, PGMNAME": scanning spam mail\n");
-      while ((fp = open_next_file (spam_fp)))
+        info ("scanning spam mail\n");
+      if (indirect)
         {
-          parse_words (*argv, fp, 1);
-          spam_count++;
-          fclose (fp);
+          while ((fp = open_next_file (spam_fp, fnamebuf, sizeof fnamebuf)))
+            {
+              spam_count += parse_message (fnamebuf, fp, 1, 0);
+              fclose (fp);
+            }
         }
+      else
+        spam_count += parse_message (argv[1], spam_fp, 0, 1);
       fclose (spam_fp);
-
+          
       if (verbose)
-        fprintf (stderr, PGMNAME": computing probabilities\n");
+        info ("computing probabilities\n");
       calc_probability (veg_count, spam_count);
       
       if (verbose)
-        fprintf (stderr, PGMNAME": writing table\n");
+        info ("writing table\n");
       write_table (veg_count, spam_count);
 
       if (verbose)
-        fprintf (stderr, PGMNAME
-                 ": %u vegetarian, %u spam, %lu kb memory used\n",
-                 veg_count, spam_count,
-                 (unsigned long int)total_memory_used/1024);
+        info ("%u vegetarian, %u spam, %lu kb memory used\n",
+              veg_count, spam_count,
+              (unsigned long int)total_memory_used/1024);
+    }
+#ifdef HAVE_PTH
+  else if (server_fd != -1)
+    { /* server mode */
+
+      argc--; argv++; /* ignore the wordlist */
+      
+      if (argc > 1)
+        usage ();
+                    
+      fp = argc? fopen (argv[0], "r") : stdin;
+      if (!fp)
+        die ("can't open `%s': %s\n", argv[0], strerror (errno));
+      if (transact_request (server_fd, argc? argv[0]:"-", fp) > 90)
+        {
+          if (verbose)
+            puts ("spam\n");
+          exit (1); /* FIXME: maybe we should invert the return value
+                       to avoid marking messages as spam in case of
+                       system errors. */
+        }
+      close (server_fd);
     }
+#endif /*HAVE_PTH*/
   else
     {
       unsigned int nwords;
-      unsigned int spamicity;
 
       if (argc < 1)
         usage ();
+
+      hash_table_size = 4999;
+      word_table = xcalloc (hash_table_size, sizeof *word_table);
+
       read_table (argv[0], &veg_count, &spam_count, &nwords);
       argc--; argv++;
       if (verbose)
-        fprintf (stderr, PGMNAME
-                 ": %u vegetarian, %u spam, %u words, %lu kb memory used\n",
-                 veg_count, spam_count, nwords,
-                 (unsigned long int)total_memory_used/1024);
+        info ("%u vegetarian, %u spam, %u words, %lu kb memory used\n",
+              veg_count, spam_count, nwords,
+              (unsigned long int)total_memory_used/1024);
       if (!argc)
         {
-          parse_words ("-", stdin, -1);
-          spamicity = check_spam (veg_count, spam_count);
-          printf ("%2u\n", spamicity);
+          if (indirect)
+            {
+              while ((fp = open_next_file (stdin, fnamebuf, sizeof fnamebuf)))
+                {
+                  parse_message (fnamebuf, fp, 0, 0);
+                  fclose (fp);
+                  check_and_print (veg_count, spam_count, fnamebuf);
+                }
+            }
+          else
+            {
+              parse_message ("-", stdin, -1, 0);
+              if ( check_spam (veg_count, spam_count) > 90)
+                {
+                  if (verbose)
+                    puts ("spam\n");
+                  exit (1);
+                }
+            }
         }
       else
         {
@@ -944,15 +1530,25 @@ main (int argc, char **argv)
               FILE *fp = fopen (argv[0], "r");
               if (!fp)
                 {
-                  fprintf (stderr, PGMNAME": can't open `%s': %s\n",
-                           argv[0], strerror (errno));
+                  error ("can't open `%s': %s\n", argv[0], strerror (errno));
                   continue;
                 }
-              parse_words (argv[0], fp, -1);
+              if (indirect)
+                {
+                  FILE *fp2;
+                  while ((fp2 = open_next_file (fp,fnamebuf, sizeof fnamebuf)))
+                    {
+                      parse_message (fnamebuf, fp2, 0, 0);
+                      fclose (fp2);
+                      check_and_print (veg_count, spam_count, fnamebuf);
+                    }
+                }
+              else
+                {
+                  parse_message (argv[0], fp, -1, 0);
+                  check_and_print (veg_count, spam_count, argv[0]);
+                }
               fclose (fp);
-              spamicity = check_spam (veg_count, spam_count);
-              printf ("%s: %2u\n", argv[0], spamicity);
-              reset_hits ();
             }
         }
     }
@@ -962,6 +1558,6 @@ main (int argc, char **argv)
 
 /*
 Local Variables:
-compile-command: "gcc -Wall -g -o vegetarise vegetarise.c"
+compile-command: "gcc -Wall -g -DHAVE_PTH -o vegetarise vegetarise.c -lpth"
 End:
 */