Index: trunk/include/sh_filter.h
===================================================================
--- trunk/include/sh_filter.h	(revision 232)
+++ trunk/include/sh_filter.h	(revision 232)
@@ -0,0 +1,35 @@
+#ifndef SH_FILTER_H
+#define SH_FILTER_H
+
+/* Filtering
+ */
+
+#define SH_FILT_NUM 32
+#define SH_FILT_OR  0
+#define SH_FILT_AND 1
+#define SH_FILT_NOT 2
+#define SH_FILT_INIT { 0, { NULL }, 0, { NULL }, 0, { NULL }}
+
+/* Pattern storage is of type void since it may be a char*
+ * or a regex_t*
+ */
+typedef struct _sh_filter_type
+{
+  int      for_c;
+  void   * for_v[SH_FILT_NUM];
+  int      fand_c;
+  void   * fand_v[SH_FILT_NUM];
+  int      fnot_c;
+  void   * fnot_v[SH_FILT_NUM];
+
+} sh_filter_type;
+
+int  sh_filter_add (const char * str, sh_filter_type * filter, int type);
+
+void sh_filter_free (sh_filter_type * filter);
+
+int  sh_filter_filter (const char * message, sh_filter_type * filter);
+
+sh_filter_type * sh_filter_alloc(void);
+
+#endif
Index: trunk/include/sh_mail_int.h
===================================================================
--- trunk/include/sh_mail_int.h	(revision 232)
+++ trunk/include/sh_mail_int.h	(revision 232)
@@ -0,0 +1,47 @@
+#ifndef SH_MAIL_INT_H
+#define SH_MAIL_INT_H
+
+extern int sh_mail_all_in_one;
+
+/* MX Resolver Struct
+ */
+typedef struct mx_ {
+  int    pref;
+  char * address;
+} mx;
+
+typedef struct dnsrep_ {
+  int    count;
+  mx   * reply;
+} dnsrep;
+
+int free_mx (dnsrep * answers);
+
+/* adress struct
+ */
+struct alias {
+  sh_string        * recipient;
+  struct alias     * recipient_list;
+  dnsrep           * mx_list;
+  int                severity;
+  int                send_mail;
+  sh_filter_type   * mail_filter;
+  struct alias     * next;
+  struct alias     * all_next;
+};
+
+extern struct alias * all_recipients;
+
+int sh_mail_msg (const char * message);
+
+/* Per recipient mail key
+ */
+
+int sh_nmail_get_mailkey (const char * alias, char * buf, size_t bufsiz,
+			  time_t * id_audit);
+
+SH_MUTEX_EXTERN(mutex_listall);
+SH_MUTEX_EXTERN(mutex_fifo_mail);
+extern SH_FIFO * fifo_mail;
+
+#endif
Index: trunk/include/sh_nmail.h
===================================================================
--- trunk/include/sh_nmail.h	(revision 232)
+++ trunk/include/sh_nmail.h	(revision 232)
@@ -0,0 +1,20 @@
+#ifndef SH_NMAIL_H
+#define SH_NMAIL_H
+
+int sh_nmail_pushstack (int level, const char * message, 
+			const char * alias);
+int sh_nmail_msg (int level, const char * message, 
+		  const char * alias);
+int sh_nmail_flush ();
+void sh_nmail_free();
+
+int sh_nmail_set_severity (const char * str);
+int sh_nmail_add_not (const char * str);
+int sh_nmail_add_and (const char * str);
+int sh_nmail_add_or  (const char * str);
+
+int sh_nmail_close_recipient(const char * str);
+int sh_nmail_add_compiled_recipient(const char * str);
+int sh_nmail_add_recipient(const char * str);
+int sh_nmail_add_alias(const char * str);
+#endif
Index: trunk/src/sh_filter.c
===================================================================
--- trunk/src/sh_filter.c	(revision 232)
+++ trunk/src/sh_filter.c	(revision 232)
@@ -0,0 +1,300 @@
+/* SAMHAIN file system integrity testing                                   */
+/* Copyright (C) 2009 Rainer Wichmann                                      */
+/*                                                                         */
+/*  This program is free software; you can redistribute it                 */
+/*  and/or modify                                                          */
+/*  it under the terms of the GNU General Public License as                */
+/*  published by                                                           */
+/*  the Free Software Foundation; either version 2 of the License, or      */
+/*  (at your option) any later version.                                    */
+/*                                                                         */
+/*  This program is distributed in the hope that it will be useful,        */
+/*  but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*  GNU General Public License for more details.                           */
+/*                                                                         */
+/*  You should have received a copy of the GNU General Public License      */
+/*  along with this program; if not, write to the Free Software            */
+/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.              */
+
+
+#include "config_xor.h"
+
+#include <string.h>
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
+
+#include "samhain.h"
+#include "sh_utils.h"
+#include "sh_mem.h"
+#include "sh_filter.h"
+
+#undef  FIL__
+#define FIL__  _("sh_filter.c")
+
+
+void sh_filter_free (sh_filter_type * filter)
+{
+  int i;
+
+  if (filter)
+    {
+      for (i = 0; i < filter->for_c; ++i) {
+#ifdef HAVE_REGEX_H
+	if (filter->for_v[i])
+	  regfree(filter->for_v[i]);
+#else
+	if (filter->for_v[i])
+	  SH_FREE(filter->for_v[i]);
+#endif
+	filter->for_v[i] = NULL; 
+      }
+      filter->for_c = 0;
+
+      for (i = 0; i < filter->fand_c; ++i) {
+#ifdef HAVE_REGEX_H
+	if (filter->fand_v[i])
+	  regfree(filter->fand_v[i]);
+#else
+	if (filter->fand_v[i])
+	  SH_FREE(filter->fand_v[i]);
+#endif
+	filter->fand_v[i] = NULL; 
+      }
+      filter->fand_c = 0;
+
+      for (i = 0; i < filter->fnot_c; ++i) {
+#ifdef HAVE_REGEX_H
+	if (filter->fnot_v[i])
+	  regfree(filter->fnot_v[i]);
+#else
+	if (filter->fnot_v[i])
+	  SH_FREE(filter->fnot_v[i]);
+#endif
+	filter->fnot_v[i] = NULL; 
+      }
+      filter->fnot_c = 0;
+    }
+}
+
+
+int sh_filter_add (const char * str, sh_filter_type * filter, int type)
+{
+  int     i = 0;
+  int     flag = 0;
+  size_t  s;
+
+  char  * dupp;
+  char  * p;
+  char  * end;
+  int   * ntok;
+  void ** stok;
+
+  SL_ENTER(_("sh_filter_filteradd"));
+
+  if (NULL == str || NULL == filter)
+    {
+      SL_RETURN((-1), _("sh_filter_filteradd")); 
+    }
+
+  if (type == SH_FILT_OR) {
+    ntok = &(filter->for_c);
+    stok = filter->for_v;
+  }
+  else if (type == SH_FILT_AND) {
+    ntok = &(filter->fand_c);
+    stok = filter->fand_v;
+  }
+  else if (type == SH_FILT_NOT) {
+    ntok = &(filter->fnot_c);
+    stok = filter->fnot_v;
+  }
+  else {
+    SL_RETURN((-1), _("sh_filter_filteradd")); 
+  }
+
+  i = *ntok;
+  if (i == SH_FILT_NUM) {
+    SL_RETURN((-1), _("sh_filter_filteradd")); 
+  }
+
+  dupp = sh_util_strdup(str);
+  p   = dupp;
+
+  do
+    {
+      while (*p == ',' || *p == ' ' || *p == '\t')
+	++p;
+      if (*p == '\0')
+	break;
+
+      end = p; ++end;
+      if (*end == '\0')
+	break;
+
+      if (*p == '\'')
+	{
+	  ++p; end = p; if (*end != '\'') ++end;
+	  if (*p == '\0' || *end == '\0')
+	    break;
+	  while (*end != '\0' && *end != '\'')
+	    ++end;
+	}
+      else if (*p == '"')
+	{
+	  ++p; end = p; if (*end != '"') ++end;
+	  if (*p == '\0' || *end == '\0')
+	    break;
+	  while (*end != '\0' && *end != '"')
+	    ++end;
+	}
+      else
+	{
+	  while (*end != '\0' && *end != ',' && *end != ' ' && *end != '\t')
+	    ++end;
+	}
+      if (*end == '\0')
+	flag = 1;
+      else
+	*end = '\0';
+
+      s = strlen(p);
+      if (s > 0) 
+	{
+	  ++s;
+#ifdef HAVE_REGEX_H
+	  if (stok[i] != NULL)
+	    regfree((regex_t *) stok[i]);
+	  {
+	    int status;
+
+	    stok[i] = SH_ALLOC(sizeof(regex_t));
+
+	    status = regcomp((regex_t *) stok[i], p, 
+				 REG_NOSUB|REG_EXTENDED);
+	    if (status != 0) 
+	      {
+		char * errbuf = SH_ALLOC(BUFSIZ);
+		(void) regerror(status, (regex_t *) stok[i], 
+				errbuf, BUFSIZ); 
+		errbuf[BUFSIZ-1] = '\0';
+		sh_error_handle ((-1), FIL__, __LINE__, status, MSG_E_REGEX,
+				 errbuf, p);
+		SH_FREE(errbuf);
+	      }
+	  }
+#else
+	  if (stok[i] != NULL)
+	    SH_FREE(stok[i]);
+
+	  stok[i] = SH_ALLOC(s);
+	  (void) sl_strlcpy((char *) stok[i], p, s);
+#endif
+	  ++i;
+	}
+
+      p = end; ++p;
+
+      if (i == SH_FILT_NUM)
+	break;
+    }
+  while (p != NULL && *p != '\0' && flag == 0);
+
+  *ntok = i;
+  SH_FREE(dupp);
+
+  SL_RETURN (0, _("sh_filter_filteradd"));
+}
+
+#ifdef HAVE_REGEX_H
+static int sh_filter_cmp(const char * message, void * pattern)
+{
+  int result;
+
+  result = regexec((regex_t *)pattern, message, 0, NULL, 0);
+
+  if (result != 0)
+    return -1;
+
+  /* Successful match. */
+  return 0;
+}
+#else
+static int sh_filter_cmp(const char * message, void * pattern)
+{
+  if (NULL == sl_strstr(message, (char *)pattern))
+    return -1;
+
+  /* Successful match. */
+  return 0;
+}
+#endif
+
+/*
+ * -- Check filters. Returns 0 if message passes.
+ */ 
+int sh_filter_filter (const char * message, sh_filter_type * filter)
+{
+  int i;
+
+  SL_ENTER(_("sh_filter_filter"));
+
+  if (filter)
+    {
+
+      /* Presence of any of these keywords prevents execution.
+       */
+      if (filter->fnot_c > 0)
+	{
+	  for (i = 0; i < filter->fnot_c; ++i)
+	    {
+	      if (0 == sh_filter_cmp(message, filter->fnot_v[i]))
+		{
+		  SL_RETURN ((-1), _("sh_filter_filter"));
+		}
+	    }
+	}
+      
+      /* Presence of all of these keywords is required for execution.
+       */
+      if (filter->fand_c > 0)
+	{
+	  for (i = 0; i < filter->fand_c; ++i)
+	    {
+	      if (0 != sh_filter_cmp(message, filter->fand_v[i]))
+		{
+		  SL_RETURN ((-1), _("sh_filter_filter"));
+		}
+	    }
+	}
+      
+      /* Presence of at least one of these keywords is required for execution.
+       */
+      if (filter->for_c > 0)
+	{
+	  for (i = 0; i < filter->for_c; ++i)
+	    {
+	      if (0 == sh_filter_cmp(message, filter->for_v[i]))
+		{
+		  goto isok;
+		}
+	    }
+	  SL_RETURN ((-1), _("sh_filter_filter"));
+	}
+    }
+
+ isok:
+  SL_RETURN ((0), _("sh_filter_filter"));
+}
+
+sh_filter_type * sh_filter_alloc(void)
+{
+  sh_filter_type * filter = SH_ALLOC(sizeof(sh_filter_type));
+
+  memset(filter, '\0', sizeof(sh_filter_type));
+  filter->for_c  = 0; 
+  filter->fand_c = 0; 
+  filter->fnot_c = 0;
+  return filter;
+}
Index: trunk/src/sh_nmail.c
===================================================================
--- trunk/src/sh_nmail.c	(revision 232)
+++ trunk/src/sh_nmail.c	(revision 232)
@@ -0,0 +1,921 @@
+/* SAMHAIN file system integrity testing                                   */
+/* Copyright (C) 2008 Rainer Wichmann                                      */
+/*                                                                         */
+/*  This program is free software; you can redistribute it                 */
+/*  and/or modify                                                          */
+/*  it under the terms of the GNU General Public License as                */
+/*  published by                                                           */
+/*  the Free Software Foundation; either version 2 of the License, or      */
+/*  (at your option) any later version.                                    */
+/*                                                                         */
+/*  This program is distributed in the hope that it will be useful,        */
+/*  but WITHOUT ANY WARRANTY; without even the implied warranty of         */
+/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
+/*  GNU General Public License for more details.                           */
+/*                                                                         */
+/*  You should have received a copy of the GNU General Public License      */
+/*  along with this program; if not, write to the Free Software            */
+/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.              */
+
+#include "config_xor.h"
+
+#if defined(HAVE_PTHREAD_MUTEX_RECURSIVE)
+#define _XOPEN_SOURCE 500
+#endif
+
+#include <string.h>
+#include <time.h>
+
+#if defined(SH_WITH_MAIL)
+
+#undef  FIL__
+#define FIL__  _("sh_nmail.c")
+
+#include "samhain.h"
+#include "sh_pthread.h"
+#include "sh_mem.h"
+#include "sh_mail.h"
+#include "sh_tiger.h"
+#include "sh_string.h"
+#include "sh_utils.h"
+#include "sh_fifo.h"
+#include "sh_filter.h"
+#include "sh_mail_int.h"
+
+SH_MUTEX_INIT(mutex_listall, PTHREAD_MUTEX_INITIALIZER);
+
+/* Pointer to last address */
+
+static struct alias * last = NULL;
+
+/* List of mail recipients */
+
+static struct alias * recipient_list = NULL;
+
+static struct alias * compiled_recipient_list = NULL;
+static sh_filter_type compiled_mail_filter = SH_FILT_INIT;
+
+/* List of mail aliases */
+
+static struct alias * alias_list = NULL;
+
+/* List of all recipients */
+
+struct alias * all_recipients = NULL;
+
+int check_double (const char * str, struct alias * list)
+{
+  if (str && list)
+    {
+      struct alias * item = list;
+
+      while (item)
+	{
+	  if (0 == strcmp(sh_string_str(item->recipient), str))
+	    return -1;
+	  item = item->next;
+	}
+    }
+  return 0;
+}
+
+struct alias * add_recipient_intern(const char * str, 
+				    struct alias * list)
+{
+  if (str)
+    {
+      struct alias * new  = SH_ALLOC(sizeof(struct alias));
+      new->next           = list;
+      new->mx_list        = NULL;
+      new->mail_filter    = NULL;
+      new->recipient_list = NULL;
+      new->severity       = (-1);
+      new->send_mail      = 0;
+      new->recipient      = sh_string_new_from_lchar(str, strlen(str));
+      list                = new;
+
+      SH_MUTEX_LOCK_UNSAFE(mutex_listall);
+      new->all_next       = all_recipients;
+      all_recipients      = new;
+      SH_MUTEX_UNLOCK_UNSAFE(mutex_listall);
+    }
+  return list;
+}
+
+int sh_nmail_close_recipient(const char * str)
+{
+  (void) str;
+
+  if (last)
+    {
+      last = NULL;
+      return 0;
+    }
+  return -1;
+}
+
+/* Add a single recipient
+ */
+int sh_nmail_add_recipient(const char * str)
+{
+  if (0 == check_double(str,  recipient_list))
+    {
+      recipient_list = add_recipient_intern(str, recipient_list);
+      last           = recipient_list;
+      return 0;
+    }
+  return -1;
+}
+
+/* Add a compiled-in address. These share the compiled_mail_filter
+ */
+int sh_nmail_add_compiled_recipient(const char * str)
+{
+  if (0 == check_double(str,  compiled_recipient_list))
+    {
+      compiled_recipient_list = 
+	add_recipient_intern(str, compiled_recipient_list);
+      if (compiled_recipient_list)
+	compiled_recipient_list->mail_filter = &compiled_mail_filter;
+      last           = compiled_recipient_list;
+      return 0;
+    }
+  return -1;
+}
+
+/* Add an alias; format is name ":" comma-delimited_list_of_recipients
+ */
+int sh_nmail_add_alias(const char * str)
+{
+#define SH_ALIASES_RECP_NUM 256
+  size_t lengths[SH_ALIASES_RECP_NUM];
+  unsigned int    nfields = SH_ALIASES_RECP_NUM;
+  char * new = sh_util_strdup(str);
+  char * p   = strchr(new, ':');
+  char * q;
+
+  if (p && strlen(p) > 1)
+    {
+      unsigned int     i;
+      char ** array;
+
+      *p = '\0'; q = p; ++p;
+      if (strlen(new) > 0)
+	{
+	  --q; while ((q != new) && *q == ' ') { *q = '\0'; --q; }
+	}
+      else
+	{
+	  goto err;
+	}
+
+      if (0 == check_double(new, alias_list))
+	{
+
+	  array = split_array_list(p, &nfields, lengths);
+
+	  if (array && nfields > 0)
+	    {
+	      struct alias * newalias  = SH_ALLOC(sizeof(struct alias));
+	      newalias->recipient_list = NULL;
+	      newalias->mail_filter    = NULL;
+	      newalias->mx_list        = NULL;
+	      newalias->severity       = (-1);
+	      /* This is the alias */
+	      newalias->recipient = sh_string_new_from_lchar(new, strlen(new));
+
+	      for (i = 0; i < nfields; ++i)
+		{
+		  if (lengths[i] > 0 && 
+		      0 == check_double(array[i], newalias->recipient_list))
+		    {
+		      newalias->recipient_list = 
+			add_recipient_intern(array[i],newalias->recipient_list);
+		    }
+		}
+
+	      SH_FREE(array);
+
+	      if (newalias->recipient_list == NULL)
+		{
+		  SH_FREE(newalias);
+		  goto err;
+		}
+      
+	      newalias->next = alias_list;
+	      alias_list     = newalias;
+	      last           = alias_list;
+
+	      SH_FREE(new);
+	      return 0;
+	    }
+	}
+    }
+ err:
+  SH_FREE(new);
+  return -1;
+}
+
+
+/* <<<<<<<<<<<<<<< Recipient List >>>>>>>>>>>>>>>>>>>>>> */
+
+static struct alias * find_list (const char * alias, int * single)
+{
+  struct alias * list   = NULL;
+
+  *single = 0;
+
+  if (!alias)
+    {
+      list = all_recipients;
+    }
+  else
+    {
+      struct alias * test = alias_list;
+      
+      while (test)
+	{
+	  if (0 == strcmp(alias, sh_string_str(test->recipient)))
+	    {
+	      list = test->recipient_list;
+	      break;
+	    }
+	  test = test->next;
+	}
+      
+      if (!list)
+	{
+	  test = recipient_list;
+	  while (test)
+	    {
+	      if (0 == strcmp(alias, sh_string_str(test->recipient)))
+		{
+		  list   = test;
+		  *single = 1;
+		  break;
+		}
+	      test = test->next;
+	    }
+	}
+      
+      if (!list)
+	{
+	  test = compiled_recipient_list;
+	  while (test)
+	    {
+	      if (0 == strcmp(alias, sh_string_str(test->recipient)))
+		{
+		  list   = test;
+		  *single = 1;
+		  break;
+		}
+	      test = test->next;
+	    }
+	}
+    }
+  return list;
+}
+
+/* Returns zero (no) or one (yes). Used to tag messages that are
+ * valid for a given recipient (or mailing list alias).
+ */
+int sh_nmail_valid_message_for_alias (int level, 
+				      const char * message, 
+				      const char * alias, 
+				      const void * rcv_info)
+{
+  struct alias * rcv = (struct alias *) rcv_info;
+
+  if (!alias || 0 == strcmp(alias, sh_string_str(rcv->recipient)))
+    {
+      if ((level & rcv->severity) == 0)
+	{
+	  return 0;
+	}
+
+      if (rcv->mail_filter)
+	{
+	  if (0 != sh_filter_filter(message, rcv->mail_filter))
+	    {
+	      return 0;
+	    }
+	}
+    }
+
+  return 1;
+}
+
+/* Returns number of recipients */
+
+static
+int sh_nmail_compute_recipients (int level, const char * message, 
+				 const char * alias, int flagit)
+{
+  struct alias * list   = NULL;
+  int            single = 0;
+  int            retval = 0;
+
+  if (flagit)
+    {
+      list = all_recipients;
+      while (list)
+	{
+	  list->send_mail = 0;
+	  list = list->all_next;
+	}
+      list = NULL;
+    }
+
+  if (message)
+    {
+      int flag = 0;
+
+      list = find_list (alias, &single);
+      if (list == all_recipients)
+	flag = 1;
+
+      while (list)
+	{
+	  /* Check severity 
+	   */
+	  if ((list->severity & level) == 0)
+	    {
+	      if (single) break;
+	      if (flag)
+		list = list->all_next;
+	      else
+		list = list->next;
+	      continue;
+	    }
+
+	  /* Check filter
+	   */
+	  if (list->mail_filter &&
+	      0 != sh_filter_filter(message, list->mail_filter))
+	    {
+	      if (single) break;
+	      if (flag)
+		list = list->all_next;
+	      else
+		list = list->next;
+	      continue;
+	    }
+	  
+	  /* Mark the entry
+	   */
+	  if (flagit)
+	    list->send_mail = 1;
+	  if (flag)
+	    list = list->all_next;
+	  else
+	    list = list->next;
+	  ++retval;
+	}
+    }
+  return retval;
+}
+
+static
+int sh_nmail_flag_recipients (int level, const char * message, 
+			      const char * alias)
+{
+  int retval = 0;
+
+  if (message)
+    {
+      SH_MUTEX_LOCK_UNSAFE(mutex_listall);
+      retval = sh_nmail_compute_recipients (level, message, alias, 1);
+      SH_MUTEX_UNLOCK_UNSAFE(mutex_listall);
+    }
+  return retval;
+}
+
+static
+int sh_nmail_test_recipients (int level, const char * message, 
+			      const char * alias)
+{
+  int retval = 0;
+
+  if (message)
+    {
+      SH_MUTEX_LOCK_UNSAFE(mutex_listall);
+      retval = sh_nmail_compute_recipients (level, message, alias, 0);
+      SH_MUTEX_UNLOCK_UNSAFE(mutex_listall);
+    }
+  return retval;
+}
+
+/* <<<<<<<<<<<<<<<<<<<  Mail the message  >>>>>>>>>>>>>>>>>>>>>> */
+
+SH_MUTEX_RECURSIVE(mutex_nmail_msg);
+SH_MUTEX_STATIC(nmail_lock, PTHREAD_MUTEX_INITIALIZER);
+
+/*
+ * First test list of recipients, then call sh_mail_pushstack().
+ */
+int sh_nmail_pushstack (int level, const char * message, 
+			const char * alias)
+{
+  int retval = 0;
+
+  if (0 != sh_nmail_test_recipients (level, message, alias))
+    {
+      retval = sh_mail_pushstack(level, message, alias);
+    }
+  return retval;
+}
+
+/*
+ * First mark list of recipients, then call sh_mail_msg().
+ */
+int sh_nmail_msg (int level, const char * message, 
+		  const char * alias)
+{
+  volatile int retval = 0;
+  static int count = 0;
+
+  /* Need to:
+   *   -- wait if different thread, and
+   *   -- fail if same thread. */
+  SH_MUTEX_RECURSIVE_INIT(mutex_nmail_msg);
+  SH_MUTEX_RECURSIVE_LOCK(mutex_nmail_msg);
+
+  /* Only same thread beyond this point. We fail
+   * if count > 0 already. */
+  if (0 == SH_MUTEX_TRYLOCK_UNSAFE(nmail_lock))
+    {
+      ++count;
+      if (count != 1)
+	{
+	  --count;
+	  SH_MUTEX_UNLOCK_UNSAFE(nmail_lock);
+	  goto cleanup;
+	}
+      SH_MUTEX_UNLOCK_UNSAFE(nmail_lock);
+
+      if (0 != sh_nmail_flag_recipients (level, message, alias))
+	{
+	  /* Need to keep info for sh_nmail_pushstack() 
+	   */
+	  SH_MUTEX_LOCK(mutex_listall);
+	  retval = sh_mail_msg(message);
+	  SH_MUTEX_UNLOCK(mutex_listall);
+
+	  if (retval != 0)
+	    {
+	      sh_mail_pushstack(level, message, alias);
+	    }
+	}
+      SH_MUTEX_LOCK_UNSAFE(nmail_lock);
+      --count;
+      SH_MUTEX_UNLOCK_UNSAFE(nmail_lock);
+    }
+ cleanup:
+  ; /* label at end of compound statement */
+  SH_MUTEX_RECURSIVE_UNLOCK(mutex_nmail_msg);
+  return retval;
+}
+
+/*
+ * Loop over all recipients in stack. 
+ * For each distinct one, mark all messages for sending. 
+ * Then call sh_mail_msg().
+ */
+
+int sh_nmail_flush ()
+{
+  int                retval = 0;
+  sh_string *        msg    = NULL;
+  sh_string *        smsg   = NULL;
+  struct alias     * list;
+  struct alias     * dlist;
+
+  SH_MUTEX_LOCK(mutex_listall);
+
+  /* Reset recipient list
+   */
+  list = all_recipients;
+  while (list)
+    {
+      list->send_mail = 0;
+      list = list->all_next;
+    }
+
+  /* Check (i) compiled recipients, (b) aliases, (c) single recipients.
+   * For each, tag all messages, then call sh_mail_msg with
+   *  appropriate address list.
+   */
+
+  reset_list(fifo_mail);
+
+  /* Compiled recipients
+   */
+  list  = compiled_recipient_list;
+
+  if (list)
+    {
+      msg   = tag_list(fifo_mail, sh_string_str(list->recipient),
+		       sh_nmail_valid_message_for_alias, list);
+    }
+
+  if (msg)
+    {
+      while (list)
+	{
+	  list->send_mail = 1;
+	  list = list->next;
+	}
+      
+      list = compiled_recipient_list;
+      
+      (void) sh_mail_msg(sh_string_str(msg));
+      sh_string_destroy(&msg);
+      
+      list = compiled_recipient_list;
+      while (list)
+	{
+	  list->send_mail = 0;
+	  list = list->next;
+	}
+    }
+
+  /* Aliases
+   */
+
+  list  = alias_list;
+
+  while (list) {
+
+    /* Work through the recipient list. As smsg stores last msg,
+     * we send a batch whenever msg != smsg, and continue from
+     * that point in the recipient list.
+     */
+    struct alias     * lnew;
+
+    while (list)
+      {
+	msg   = tag_list(fifo_mail, sh_string_str(list->recipient),
+			 sh_nmail_valid_message_for_alias, list);
+
+	if (msg)
+	  {
+	    if (!smsg) /* init */
+	      {
+		smsg      = sh_string_copy(msg);
+	      }
+	    else
+	      {
+		if (0 != strcmp(sh_string_str(smsg), sh_string_str(msg)))
+		  {
+		    /*
+		     * Don't set list = list->next here, since we want
+		     * to continue with this recipient in the next batch.
+		     */
+		    sh_string_destroy(&msg);
+		    break;
+		  }
+	      }
+	    lnew = list->recipient_list;
+	    while (lnew)
+	      {
+		lnew->send_mail = 1;
+		lnew= lnew->next;
+	      }
+	    sh_string_destroy(&msg);
+	  }
+	list      = list->next;
+      }
+
+    /* Continue here if smsg != msg */
+
+    if (smsg)
+      {
+	(void) sh_mail_msg(sh_string_str(smsg));
+	sh_string_destroy(&smsg);
+      }
+
+    /* Reset old list of recipients (up to current point in list)
+     * and then continue with list from current point on.
+     */
+    dlist  = alias_list;
+    while (dlist)
+      {
+	lnew = dlist->recipient_list;
+	while (lnew)
+	  {
+	    lnew->send_mail = 0;
+	    lnew = lnew->next;
+	  }
+	dlist = dlist->next;
+      }
+  }
+
+
+  /* Single recipients
+   */
+  list  = recipient_list;
+
+  while (list) {
+
+    /* Work through the recipient list. As smsg stores last msg,
+     * we send a batch whenever msg != smsg, and continue from
+     * that point in the recipient list.
+     */
+
+    while (list)
+      {
+	msg   = tag_list(fifo_mail, sh_string_str(list->recipient),
+		       sh_nmail_valid_message_for_alias, list);
+
+	if (msg)
+	  {
+	    if (!smsg) /* init */
+	      {
+		smsg = sh_string_copy(msg);
+	      }
+	    else
+	      {
+		if (0 != strcmp(sh_string_str(smsg), sh_string_str(msg)))
+		  {
+		    /*
+		     * Don't set list = list->next here, since we want
+		     * to continue with this recipient in the next batch.
+		     */
+		    sh_string_destroy(&msg);
+		    break;
+		  }
+	      }
+	    list->send_mail = 1;
+	    sh_string_destroy(&msg);
+	  }
+	list = list->next;
+      }
+
+    /* Continue here if smsg != msg */
+
+    if (smsg)
+      {
+	(void) sh_mail_msg(sh_string_str(smsg));
+	sh_string_destroy(&smsg);
+      }
+
+    /* Reset old list of recipients (up to current point in list)
+     * and then continue with list from current point on.
+     */
+    dlist  = recipient_list;
+    while (dlist)
+      {
+	dlist->send_mail = 0;
+	dlist = dlist->next;
+      }
+  }
+
+  /* Remove all mails for which no recipient failed
+   */
+
+  sh.mailNum.alarm_last -= commit_list(fifo_mail);
+  SH_MUTEX_UNLOCK(mutex_listall);
+
+  return retval;
+}
+
+
+
+/* <<<<<<<<<<<<<<<<<<<  Severity  >>>>>>>>>>>>>>>>>>>>>> */
+
+/* 
+ * -- set severity threshold for recipient or alias
+ */
+int sh_nmail_set_severity (const char * str)
+{
+  if (last == recipient_list || last == alias_list)
+    {
+      if (0 == sh_error_set_level(str, &(last->severity)))
+	{
+	  /* All recipients in alias share the severity
+	   */
+	  if (last == alias_list)
+	    {
+	      struct alias * ptr = last->recipient_list;
+
+	      while (ptr)
+		{
+		  ptr->severity = last->severity;
+		  ptr = ptr->next;
+		}
+	    }
+	  return 0;
+	}
+    }
+  return (-1);
+}
+
+/* <<<<<<<<<<<<<<<<<<<  Filters >>>>>>>>>>>>>>>>>>>>>> */
+
+
+int sh_nmail_add_generic (const char * str, int flag)
+{
+  if (last)
+    {
+      if (NULL == last->mail_filter)
+	last->mail_filter = sh_filter_alloc();
+
+      /* All recipients in alias share the mail filter
+       */
+      if (last == alias_list)
+	{
+	  struct alias * ptr = last->recipient_list;
+	  
+	  while (ptr)
+	    {
+	      ptr->mail_filter = last->mail_filter;
+	      ptr = ptr->next;
+	    }
+	}
+
+      return (sh_filter_add (str, last->mail_filter, flag));
+    }
+  return (-1);
+}
+
+/*
+ * -- add keywords to the OR filter
+ */
+int sh_nmail_add_or (const char * str)
+{
+  return sh_nmail_add_generic(str, SH_FILT_OR);
+}
+
+/*
+ * -- add keywords to the AND filter
+ */
+int sh_nmail_add_and (const char * str)
+{
+  return sh_nmail_add_generic(str, SH_FILT_AND);
+}
+
+/*
+ * -- add keywords to the NOT filter
+ */
+int sh_nmail_add_not (const char * str)
+{
+  return sh_nmail_add_generic(str, SH_FILT_NOT);
+}
+
+
+/* <<<<<<<<<<<<<<<<<<<  Mailkey per Alias >>>>>>>>>>>>>>>>>>>>>>>>> */
+
+#if defined(HAVE_MLOCK) && !defined(HAVE_BROKEN_MLOCK)
+#include <sys/mman.h>
+#endif
+
+#include "zAVLTree.h"
+
+zAVLTree        * mailkeys = NULL;
+
+struct alias_mailkey {
+  char * alias;
+  unsigned int mailcount;
+  time_t       id_audit;
+  char   mailkey_old[KEY_LEN+1];
+  char   mailkey_new[KEY_LEN+1];
+};
+
+static zAVLKey sh_nmail_getkey(void const *item)
+{
+  const struct alias_mailkey * t = (const struct alias_mailkey *) item;
+  return (zAVLKey) t->alias;
+}
+
+/* Return mailkey for alias. If there's no key yet, create it and
+ * store it in the AVL tree.
+ * This is called from sh_mail_msg, 
+ *    which is called from sh_nmail_msg,
+ *        which is protected by a mutex.
+ */
+int sh_nmail_get_mailkey (const char * alias, char * buf, size_t bufsiz,
+			  time_t * id_audit)
+{
+  char hashbuf[KEYBUF_SIZE];
+
+ start:
+
+  if (mailkeys)
+    {
+      struct alias_mailkey * t;
+
+      if (!alias)
+	t = (struct alias_mailkey *) zAVLSearch (mailkeys, _("(null)"));
+      else
+	t = (struct alias_mailkey *) zAVLSearch (mailkeys, alias);
+
+      if (t)
+	{
+	  /* iterate the key
+	   */
+	  (void) sl_strlcpy(t->mailkey_new,
+			    sh_tiger_hash (t->mailkey_old, TIGER_DATA, KEY_LEN,
+					   hashbuf, sizeof(hashbuf)),
+			    KEY_LEN+1);
+	  (void) sl_strlcpy(buf, t->mailkey_new, bufsiz);
+	  ++(t->mailcount);
+	}
+      else
+	{
+	  t = SH_ALLOC(sizeof(struct alias_mailkey));
+
+	  MLOCK(t, sizeof(struct alias_mailkey));
+
+	  if (!alias)
+	    t->alias = sh_util_strdup(_("(null)"));
+	  else
+	    t->alias = sh_util_strdup(alias);
+
+	  t->mailcount = 0;
+	  t->id_audit  = time(NULL);
+
+	  BREAKEXIT(sh_util_keyinit);
+	  (void) sh_util_keyinit (t->mailkey_old, KEY_LEN+1);
+
+	  /* iterate the key
+	   */
+	  (void) sl_strlcpy(t->mailkey_new,
+			    sh_tiger_hash (t->mailkey_old, TIGER_DATA, KEY_LEN,
+					   hashbuf, sizeof(hashbuf)),
+			    KEY_LEN+1);
+	  (void) sl_strlcpy(buf, t->mailkey_new, bufsiz);
+	  (void) zAVLInsert(mailkeys, t);
+	}
+
+      /* X(n) -> X(n-1)
+       */
+      (void) sl_strlcpy (t->mailkey_old, t->mailkey_new, KEY_LEN+1);
+
+      *id_audit = t->id_audit;
+
+      return (t->mailcount);
+    }
+
+  mailkeys = zAVLAllocTree (sh_nmail_getkey);
+  goto start;
+}
+
+/* <<<<<<<<<<<<<<<<<<<  Free for Reconfigure >>>>>>>>>>>>>>>>>>>>>> */
+
+
+static void free_recipient_list(struct alias * list)
+{
+  struct alias * new;
+
+  while (list)
+    {
+      new  = list;
+      list = new->next;
+      if (new->mx_list)
+	free_mx(new->mx_list);
+      if (new->mail_filter)
+	{
+	  sh_filter_free(new->mail_filter);
+	  SH_FREE(new->mail_filter);
+	}
+      sh_string_destroy(&(new->recipient));
+      SH_FREE(new);
+    }
+}
+
+/* Free everything to prepare for reconfigure
+ */
+void sh_nmail_free()
+{
+  SH_MUTEX_LOCK_UNSAFE(mutex_listall);
+  all_recipients = NULL;
+  SH_MUTEX_UNLOCK_UNSAFE(mutex_listall);
+
+  free_recipient_list(recipient_list);
+  recipient_list = NULL;
+
+  sh_filter_free(&compiled_mail_filter);
+
+  while (alias_list) 
+    {
+      struct alias * item = alias_list;
+
+      alias_list = item->next;
+
+      sh_string_destroy(&(item->recipient));
+      free_recipient_list(item->recipient_list);
+      if (item->mail_filter)
+	{
+	  sh_filter_free(item->mail_filter);
+	  SH_FREE(item->mail_filter);
+	}
+      SH_FREE(item);
+    }
+  alias_list = NULL;
+
+  last = compiled_recipient_list;
+  return;
+}
+
+/* defined(SH_WITH_MAIL) */
+#endif
