/* SAMHAIN file system integrity testing                                   */
/* Copyright (C) 1999, 2000 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>

#if defined(SH_WITH_MAIL)

#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif


#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif

#include "samhain.h"
#include "sh_error.h"
#include "sh_unix.h"
#include "sh_tiger.h"
#include "sh_mail.h"
#include "sh_utils.h"
#include "sh_fifo.h"
#include "sh_tools.h"
#include "sh_pthread.h"

#undef  FIL__
#define FIL__  _("sh_mail.c")
#undef  GOOD
#undef  BAD

static int failedMail = SL_FALSE;

/* MX Resolver Struct
 */
typedef struct mx_ {
  int    pref;
  char * address;
} mx;

typedef struct dnsrep_ {
  int    count;
  mx   * reply;
} dnsrep;

static int free_mx (dnsrep * answers);
static dnsrep * return_mx (char *domain);

/*********************************************
 *  utility function for verifying mails
 *********************************************/

typedef struct mail_trail_struct {
  char                     trail_id[2*SH_MINIBUF];
  char                     trail_key[KEY_LEN+1];
  struct mail_trail_struct * next;
} mail_trail_type;

static mail_trail_type * mail_trail = NULL;

int sh_mail_sigverify (const char * s)
{
  SL_TICKET  fd;
  long   i;
  char * buf;
  char * bufc;
  char   key[81];
  char   number[2*SH_MINIBUF];
  char   audit_id[2 * SH_MINIBUF];
  long   numsig;
  char   key2[KEY_LEN+1];

  char * theSig;

  mail_trail_type * mail_trail_ptr = NULL;

  sh_error_logoff();

  ASSERT((s != NULL && sl_strlen(s) < PATH_MAX), 
	 _("(s != NULL && sl_strlen(s) < PATH_MAX)"));

  if (s == NULL || sl_strlen(s) >= PATH_MAX) 
    _exit (EXIT_FAILURE);

  /* open the file, then check it 
   */
  if (0 != sl_is_suid())
    {
      fprintf(stderr, _("Cannot open file %s in suid mode\n"), s);
      _exit (EXIT_FAILURE);
    }
  if ( SL_ISERROR(fd = sl_open_read (s, SL_NOPRIV)))
    {
      fprintf(stderr, _("Could not open file %s\n"), s);
      _exit (EXIT_FAILURE);
    }

  buf     = SH_ALLOC( (size_t)(SH_MSG_BUF+SH_BUFSIZE+1));
  bufc    = SH_ALLOC( (size_t)(SH_MSG_BUF+SH_MAXBUF+1));

  while (1 == 1)
    {
      buf[0]  = '\0';
      bufc[0] = '\0';

      /* find start of next message
       */
      while (0 != sl_strncmp(buf, _("-----BEGIN MESSAGE-----"),
			     sizeof("-----BEGIN MESSAGE-----")-1)) 
	{
	  (void) sh_unix_getline (fd, buf, SH_MSG_BUF+SH_BUFSIZE);
	  if (buf[0] == '\0')
	    {
	      /* End of mailbox reached, exit. 
	       */
	      (void) fflush(stdout);
	      _exit (EXIT_SUCCESS);

	      /* Fix for AIX cc complaint. 
	       */
	      /*@notreached@*/
	      return 0; 
	    }
	}
      
      /* Read message, compress into bufc.
       */
      while (1 == 1)
	{
	  (void) sh_unix_getline (fd, buf, SH_MSG_BUF+SH_BUFSIZE);
	  if (0 == sl_strncmp(buf, _("-----BEGIN SIGNATURE-----"),
			      sizeof("-----BEGIN SIGNATURE-----")-1))
	    break;
	  if (buf[0] == '\0') 
	    _exit (EXIT_FAILURE);
	  (void) sh_util_compress(bufc, buf, SH_MSG_BUF+SH_MAXBUF-KEY_LEN);
	}
      
      /* get signature and number 
       */
      (void) sh_unix_getline (fd, key, (int)sizeof(key));
      key[KEY_LEN] = '\0';

      (void) sh_unix_getline (fd, number, (int)sizeof(number));
      number[(2*SH_MINIBUF) - 2]   = '\0';
      numsig = atol (number);
      (void) sl_strlcpy (audit_id, &number[7], 2*SH_MINIBUF);
      
      fprintf(stderr, _("Message %06ld  Trail %s\n"), 
	      numsig, /*@-usedef@*/ audit_id /*@+usedef@*/);

      mail_trail_ptr = mail_trail;
      while (mail_trail_ptr)
	{
	  if (0 == sl_strcmp(mail_trail_ptr->trail_id, audit_id))
	    break;
	  mail_trail_ptr = mail_trail_ptr->next;
	}

      if (!mail_trail_ptr)
	{
	  if (numsig > 0)
	    {
	      fprintf (stderr, _("ERROR (no key -- cannot check)\n"));
	      continue;
	    }
	  else
	    {
	      mail_trail_ptr = SH_ALLOC (sizeof(mail_trail_type));
	      mail_trail_ptr->next = mail_trail;
	      mail_trail = mail_trail_ptr;
	      (void) sl_strlcpy (mail_trail_ptr->trail_id,  
				 audit_id, 2*SH_MINIBUF);
	    }
	}
      else if (numsig == 0)
	{
	  fprintf (stderr, _("ERROR (repeated audit trail)\n"));
	  continue;
	}
	

      if (numsig == 0)
	{
	  sh_util_encode(key, bufc, 1, 'A');
	  (void) sl_strlcpy (mail_trail_ptr->trail_key, key, KEY_LEN+1);
	  fprintf (stderr, _("(unchecked)\n"));
	}
      else
	{
	  char sigbuf[KEYBUF_SIZE];

	  /* iterate key
	   */
	  (void) sl_strlcpy(key2, mail_trail_ptr->trail_key, KEY_LEN+1); 
	  for (i = 0; i < numsig; ++i) 
	    {
	      char hashbuf[KEYBUF_SIZE];
	      (void) sl_strlcpy (key2, 
				 sh_tiger_hash (key2, TIGER_DATA, KEY_LEN,
						hashbuf, sizeof(hashbuf)), 
				 KEY_LEN+1);
	    }
	  

	  theSig = sh_util_siggen (key2, bufc, sl_strlen(bufc), sigbuf, sizeof(sigbuf));
	  if (sl_strncmp (key, 
			  theSig,
			  KEY_LEN) != 0) 
	    {
	      fprintf (stderr, _("(FAILED)\n"));
	    } 
	  else 
	    { 
	      fprintf (stderr, _("(passed)\n"));
	    }

	}

    } /* end scan mailbox */

  /*@notreached@*/
}

#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 }}

typedef struct _sh_filter_type
{
  int      for_c;
  char   * for_v[SH_FILT_NUM];
  int      fand_c;
  char   * fand_v[SH_FILT_NUM];
  int      fnot_c;
  char   * fnot_v[SH_FILT_NUM];

} sh_filter_type;

static
int sh_filter_filteradd (const char * argstring, 
			 sh_filter_type * filter, int ftype)
{
  int     i = 0;
  int     flag = 0;
  size_t  s;

  char  * dupp;
  char  * p;
  char  * end;
  int   * ntok;
  char ** stok;

  SL_ENTER(_("sh_filter_filteradd"));

  if (NULL == argstring)
    {
      SL_RETURN((-1), _("sh_filter_filteradd")); 
    }

  if (ftype == SH_FILT_OR) {
    ntok = &(filter->for_c);
    stok = filter->for_v;
  }
  else if (ftype == SH_FILT_AND) {
    ntok = &(filter->fand_c);
    stok = filter->fand_v;
  }
  else if (ftype == SH_FILT_NOT) {
    ntok = &(filter->fnot_c);
    stok = filter->fnot_v;
  }
  else {
    SL_RETURN((-1), _("sh_filter_filteradd")); 
  }

  *ntok = 0;

  dupp = sh_util_strdup(argstring);
  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; ++end;
	  if (*p == '\0' || *end == '\0')
	    break;
	  while (*end != '\0' && *end != '\'')
	    ++end;
	}
      else if (*p == '"')
	{
	  ++p; end = p; ++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) + 1;
      if (stok[i] != NULL)
	SH_FREE(stok[i]);
      stok[i] = SH_ALLOC(s);
      (void) sl_strlcpy(stok[i], p, s);

      p = end; ++p;

      ++i;
      if (i == SH_FILT_NUM)
	break;
    }
  while (p != NULL && *p != '\0' && flag == 0);

  *ntok = i;
  SH_FREE(dupp);

  SL_RETURN (0, _("sh_filter_filteradd"));
}

/*
 * -- check filters
 */
static 
int sh_filter_filter (const char * message, sh_filter_type * filter)
{
  int i;
  int j = 0;

  SL_ENTER(_("sh_mail_filter"));

  /* Presence of any of these keywords prevents execution.
   */
  if (filter->fnot_c > 0)
    {
      for (i = 0; i < filter->fnot_c; ++i)
	{
	  if (NULL != sl_strstr(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)
    {
      j = 0;

      for (i = 0; i < filter->fand_c; ++i)
	if (NULL != sl_strstr(message, filter->fand_v[i]))
	  ++j;

      if (j != filter->fand_c)
	{
	  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 (NULL != sl_strstr(message, filter->for_v[i]))
	    {
	      goto isok;
	    }
	}
      SL_RETURN ((-1), _("sh_filter_filter"));
    }

 isok:
  SL_RETURN ((0), _("sh_filter_filter"));
}


static sh_filter_type mail_filter = SH_FILT_INIT;

/*
 * -- add keywords to the OR filter
 */
int sh_mail_add_or (const char * str)
{
  return (sh_filter_filteradd (str, &(mail_filter), SH_FILT_OR));
}

/*
 * -- add keywords to the AND filter
 */
int sh_mail_add_and (const char * str)
{
  return (sh_filter_filteradd (str, &(mail_filter), SH_FILT_AND));
}

/*
 * -- add keywords to the NOT filter
 */
int sh_mail_add_not (const char * str)
{
  return (sh_filter_filteradd (str, &(mail_filter), SH_FILT_NOT));
}


static char * address_list[8] = { 
  NULL, NULL, NULL, NULL, 
  NULL, NULL, NULL, NULL 
};

static   int   address_num = 0;
static   int   address_num_compiled = 0;
static   int   setaddress_compiled = S_FALSE;

void reset_count_dev_mail(void)
{
  /* if not, then we still have the compiled-in address (if any), so we
   * don' touch them
   */
  if (address_num_compiled == -99)
    address_num = 0;
  return;
}

int sh_mail_setaddress (const char * address)
{
  char     *     p;

  SL_ENTER(_("sh_mail_setaddress"));
  
  if (0 == strcmp(address, _("NULL")))
    SL_RETURN ( (0), _("sh_mail_setaddress"));
    
  if (address != NULL && address_num < (2 * SH_PATHBUF / 64 )) 
    {
      if (address_num < (SH_PATHBUF / 64 ))
	p = &sh.srvmail.name[address_num*64];
      else
	p = &sh.srvmail.alt[address_num*64];

      (void) sl_strlcpy (p, address, 64);
      
      if ((p == NULL) || ( sl_strlen(address) != sl_strlen(p)))
	{
	  memset(p, (int)'\0', 64);
	  SL_RETURN ( (-1), _("sh_mail_setaddress"));
	}
      address_list[address_num] = p;
#if 0
      if (!sl_is_suid())
	{
	  TPT(( 0, FIL__, __LINE__, _("msg=<address_list[%d] = %s>\n"), 
		address_num, address_list[address_num]));
	}
#endif
      if (setaddress_compiled == S_TRUE)
	{
	  ++address_num;
	  ++address_num_compiled;
	}
      else
	{
	  if (address_num == address_num_compiled)
	    {
	      address_num = 0;
	      address_num_compiled = -99;
	    }
	  ++address_num;
	}
      SL_RETURN ( (0), _("sh_mail_setaddress"));
    }
  SL_RETURN ( (-1), _("sh_mail_setaddress"));
}

int sh_mail_setaddress_int (const char * address)
{
  int i;
  SL_ENTER(_("sh_mail_setaddress_int"));
  setaddress_compiled = S_TRUE;
  i = sh_mail_setaddress(address);
  setaddress_compiled = S_FALSE;
  SL_RETURN(i, _("sh_mail_setaddress_int"));
}

int sh_mail_setNum (const char * str)
{
  int i = atoi (str);

  SL_ENTER(_("sh_mail_setNum"));

  if (i >= 0 && i < SH_FIFO_MAX) 
    sh.mailNum.alarm_interval = (time_t) i;
  else 
    SL_RETURN ((-1), _("sh_mail_setNum"));
  SL_RETURN( (0), _("sh_mail_setNum"));
}


static int all_in_one = S_FALSE;

int sh_mail_setFlag (const char * str)
{
  int i;
  SL_ENTER(_("sh_mail_setFlag"));
  i = sh_util_flagval(str, &all_in_one);
  SL_RETURN(i, _("sh_mail_setFlag"));
}

static char * mail_subject = NULL;

int set_mail_subject (const char * str)
{
  SL_ENTER(_("set_mail_subject"));
  if (!str)
    SL_RETURN( (-1), _("set_mail_subject"));

  if (mail_subject != NULL)
    SH_FREE(mail_subject);

  if (0 == sl_strncmp(str, _("NULL"), 4))
    {
      mail_subject = NULL;
      SL_RETURN( 0, _("set_mail_subject"));
    }

  mail_subject = sh_util_strdup(str);
  SL_RETURN( (0), _("set_mail_subject"));
}


static SH_FIFO * fifo_mail = NULL;

static
void sh_mail_emptystack (void)
{
  char * msg;
  size_t len;

  SL_ENTER(_("sh_mail_emptystack"));

  if (fifo_mail == NULL)
    SL_RET0(_("sh_mail_emptystack"));

  while (NULL != (msg = pop_list(fifo_mail)))
    {
      len = sl_strlen(msg);
      memset(msg, 0, len);
      SH_FREE(msg);
    }

  SL_RET0(_("sh_mail_emptystack"));
}

/* insert "\r\n" after each 998 char
 */
static char * split_string(char * str);

int sh_mail_pushstack (/*@null@*/char * msg)
{
  char * p;
  int    retval = 0;
  int    status;

  SL_ENTER(_("sh_mail_pushstack"));

  if (msg == NULL || failedMail == SL_TRUE || sh.srvmail.name[0] == '\0') 
    SL_RETURN((0), (_("sh_mail_pushstack")));

  if (0 != sh_filter_filter(msg, &mail_filter))
    SL_RETURN((0), (_("sh_mail_pushstack")));

#if 0
  if (msg != NULL && sl_strlen(msg) > 998)  /* RFC 2822 */
    msg[998] = '\0';
#endif

  p = split_string(msg);

  if (fifo_mail == NULL)
    {
      fifo_mail = SH_ALLOC(sizeof(SH_FIFO));
      fifo_init(fifo_mail);
    }

  status = push_list (fifo_mail, p);
  if (status >= 0)
    ++sh.mailNum.alarm_last;

  SH_FREE(p);

  if (sh.mailNum.alarm_last >= sh.mailNum.alarm_interval)
    {
      BREAKEXIT(sh_mail_msg);
      retval = sh_mail_msg (NULL);
    }

  if (status == SH_FIFO_MAX)
    retval = -2;
  SL_RETURN(retval, (_("sh_mail_pushstack")));
}


/* The mailer.
 */
static int sh_mail_end_conn (FILE * connfile, int fd);
static FILE * sh_mail_start_conn (int aFlag, int * fd);

static
void sh_mail_get_subject(char * message,
			 char * mheader, size_t len)
{
  st_format rep_serv_tab[] = {
    { 'T', S_FMT_TIME,    0, 0, NULL},
    { 'H', S_FMT_STRING,  0, 0, NULL},
    { 'M', S_FMT_STRING,  0, 0, NULL},
    { 'S', S_FMT_STRING,  0, 0, NULL},
    {'\0', S_FMT_ULONG,   0, 0, NULL},
  };

  char * p;
  char * mptr;
  char   sev[8];

  SL_ENTER(_("sh_mail_get_subject"));

  (void) sl_strlcpy(mheader, _("Subject: "), len);
  if (NULL == strchr(mail_subject, '%'))
    {
      (void) sl_strlcat(mheader, mail_subject, len);
      SL_RET0(_("sh_mail_get_subject"));
    }


  rep_serv_tab[0].data_ulong = (unsigned long) time(NULL);
  rep_serv_tab[1].data_str   = sh.host.name;

  /* fast forward to the important part
   */
  mptr = (char*)sl_strstr(message, _("msg="));
  if (mptr)
    {
      mptr += 4;
      rep_serv_tab[2].data_str   = mptr;
    }
  else
    rep_serv_tab[2].data_str   = message;

  mptr = (char*)sl_strstr(message, _("sev="));
  if (mptr)
    {
      mptr += 5;
      sev[0] = *mptr; ++mptr;
      sev[1] = *mptr; ++mptr;
      sev[2] = *mptr; ++mptr;
      sev[3] = *mptr; ++mptr;
      sev[4] = '\0';
    }
  else
    {
      mptr = message;
      sev[0] = *mptr; ++mptr;
      sev[1] = *mptr; ++mptr;
      sev[2] = *mptr; ++mptr;
      sev[3] = *mptr; ++mptr;
      if (*mptr == ' ') {
	sev[4] = '\0';
      } else {
	sev[4] = *mptr; ++mptr;
	if (*mptr == ' ') {
	  sev[5] = '\0';
	} else {
	  sev[5] = *mptr;
	  sev[6] = '\0';
	}
      }
    }
  rep_serv_tab[3].data_str   = sev;


  p = sh_util_formatted(mail_subject, rep_serv_tab);
  (void) sl_strlcat(mheader, p, len);
  SH_FREE(p);
  SL_RET0(_("sh_mail_get_subject"));
}


#if defined(HAVE_MLOCK) && !defined(HAVE_BROKEN_MLOCK)
#include <sys/mman.h>
#endif

static char * sh_mail_realloc (char * inbuf, size_t * insize, size_t increase)
{
  size_t newsize;
  char * outbuf = inbuf;

  SL_ENTER(_("sh_mail_realloc"));

  if (sl_ok_adds((*insize), 1))
    {
      newsize = (*insize) + 1;

      if (sl_ok_adds(newsize, increase))
	{
	  newsize += increase;

	  outbuf = SH_ALLOC(newsize);
	  MLOCK(outbuf, newsize);
	  (void) sl_strlcpy(outbuf, inbuf, newsize);

	  memset (inbuf, 0, (*insize));
	  MUNLOCK(inbuf, (*insize));
	  SH_FREE(inbuf);

	  *insize = newsize;
	}
    }

  SL_RETURN( (outbuf), _("sh_mail_realloc"));
}

int sh_mail_msg (/*@null@*/char * message)
{
    char         subject[32+32+SH_MINIBUF+2+3+SH_PATHBUF];
    char         mheader[32+32+SH_MINIBUF+2+3];

    char       * mailMsg;
    char       * popMsg;
    int          status = 0, errcount;
    size_t       wrlen;
    int          i;
    int          num_popped = 0;
    int          retval = -1;  

    char       * bufcompress;
    static int   failcount = 0;
    static int   isfirst   = 1;
    static int   mailcount = 0;
    FILE       * connfile  = NULL;

    struct  sigaction  old_act;
    struct  sigaction  new_act;

    static  time_t id_audit  = 0;
    static  time_t fail_time = 0;
    static  time_t success_time = 0;

    static  int ma_block = 0;

    int       ma_socket = -1;

    SH_FIFO * fifo_temp = NULL;

    char    * theSig;
    char    * theMsg = NULL;

    /* #define SH_MAILBUF (256)    */
#define SH_MAILBUF (8*4096) 

    size_t    msgbufsize = SH_MAILBUF;
    size_t    combufsize = SH_MAILBUF;
    char      timebuf[81];
    char hashbuf[KEYBUF_SIZE];

    SL_ENTER(_("sh_mail_msg"));

    if (ma_block == 1)
      SL_RETURN( (0), _("sh_mail_msg"));

    /* Return if we cannot mail.
     */
    if (failedMail == SL_TRUE) 
      SL_RETURN((-1), _("sh_mail_msg"));

    if (failedMail == SL_FALSE && address_list[0] == NULL)
      {
	TPT((0, FIL__, __LINE__, 
	     _("msg=<Mail error: no recipient address.>\n")));
	failedMail = SL_TRUE;
	SL_RETURN((-1), _("sh_mail_msg"));
      }

    if ( (success_time > 0) && (fail_time > 0) &&
	 (time(NULL) - success_time) > 3600*SH_MAX_FAIL)
      {
	ma_block = 1;
	sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_SRV_FAIL,
			 _("mail"), address_list[0]);
	ma_block = 0;
	sh_mail_emptystack();
	sh.mailNum.alarm_last = 0;
	failedMail = SL_TRUE;
	SL_RETURN((-1), _("sh_mail_msg"));
      }

    /* Try at most each hour.
     */
    if ((fail_time > 0) && (time(NULL) - fail_time) < 3/*600*/)
      {
	if (failcount > 3)
	  {
	    /* -- Save for later. -- 
	     */
	    sh_mail_pushstack (message);
	    ++failcount;
	    
	    SL_RETURN((-1), _("sh_mail_msg"));
	  }
	else
	  {
	    (void) retry_msleep(2, 0);
	    ++failcount;
	  }
      }

    /* -- Reset time of last failure. --
     */
    fail_time = 0;

    /* -- Polling, empty queue. --
     */
    if (message == NULL && sh.mailNum.alarm_last == 0)
      SL_RETURN((-1), _("sh_mail_msg"));

    /* -- Filtered. --
     */
    if (message != NULL && 0 != sh_filter_filter(message, &mail_filter))
      SL_RETURN((-1), (_("sh_mail_msg")));



    /* ---------  Build complete message. ------------------------ */

    theMsg = split_string(message);

    /* ---------- Header  ---------------------------------------- */

    if (mail_subject == NULL)
      {
	(void) sl_strlcpy(mheader, _("Subject: "),       sizeof(mheader)-5);
	(void) sl_strlcat(mheader, sh_unix_time (0, timebuf, sizeof(timebuf)),
			  sizeof(mheader)-5);
	(void) sl_strlcat(mheader, " ",                  sizeof(mheader)-5);
	(void) sl_strlcat(mheader, sh.host.name,         sizeof(mheader)-5);
      }
    else
      {
	
	if (message == NULL)
	  {
	    theMsg  = pop_list(fifo_mail);
	    message = theMsg;
	    if (message)
	      --sh.mailNum.alarm_last;
	  }
	
	if (message)
	  {
	    sh_mail_get_subject(message, mheader, sizeof(mheader)-5);
	  }
	else
	  {
	    (void) sl_strlcpy(mheader, _("Subject: "),     sizeof(mheader)-5);
	    (void) sl_strlcat(mheader, sh_unix_time (0, timebuf, sizeof(timebuf)),
			      sizeof(mheader)-5);
	    (void) sl_strlcat(mheader, " ",                sizeof(mheader)-5);
	    (void) sl_strlcat(mheader, sh.host.name,       sizeof(mheader)-5);
	  }
      }

    /* RFC 821: Header is terminated by an empty line
     */
    (void) sl_strlcat(mheader, "\015\012\015\012",        sizeof(mheader));

    /* ---------- Message  --------------------------------------- */

    (void) sl_strlcpy(subject, sh_unix_time (0, timebuf, sizeof(timebuf)),
		      sizeof(subject));
    (void) sl_strlcat(subject, " ",                       sizeof(subject));
    (void) sl_strlcat(subject, sh.host.name,              sizeof(subject));
    (void) sl_strlcat(subject, "\r\n",                    sizeof(subject));


    mailMsg     = (char *) SH_ALLOC (msgbufsize);
    bufcompress = (char *) SH_ALLOC (combufsize);

    MLOCK(mailMsg     , msgbufsize);
    MLOCK(bufcompress , combufsize);

    (void) sl_strlcpy(mailMsg, mheader, msgbufsize);
    bufcompress[0] = '\0';

    (void) sl_strlcat(mailMsg, _("-----BEGIN MESSAGE-----\r\n"), msgbufsize);
    (void) sl_strlcat(mailMsg, subject, msgbufsize);
    (void) sh_util_compress (bufcompress, subject, 
			     (combufsize - KEY_LEN - 1));
    if (message != NULL)
      {
	if ((sl_strlen(theMsg) + sl_strlen(mailMsg) + 1) > 
	    (msgbufsize-(4*KEY_LEN)))
	  {
	    mailMsg     = sh_mail_realloc(mailMsg,     
					  &msgbufsize, sl_strlen(theMsg)+2);
	    bufcompress = sh_mail_realloc(bufcompress, 
					  &combufsize, sl_strlen(theMsg));
	  }
	(void) sl_strlcat(mailMsg,  theMsg, msgbufsize-(4*KEY_LEN));
	(void) sl_strlcat(mailMsg,  "\r\n", msgbufsize-(4*KEY_LEN));

	(void) sh_util_compress (bufcompress,  theMsg, combufsize-KEY_LEN-1);
      }

    if (sh.mailNum.alarm_last > 0)
      {
	fifo_temp = SH_ALLOC (sizeof(SH_FIFO));
	fifo_init (fifo_temp);

	while ( NULL != (popMsg = pop_list(fifo_mail)) )
	  {
	    (void) push_list (fifo_temp, popMsg);

	    if ((sl_strlen(popMsg) + sl_strlen(mailMsg) + 1) > 
		(msgbufsize-(4*KEY_LEN)))
	      {
		mailMsg     = sh_mail_realloc(mailMsg,     
					      &msgbufsize, 
					      sl_strlen(popMsg)+2);
		bufcompress = sh_mail_realloc(bufcompress, 
					      &combufsize, 
					      sl_strlen(popMsg));
	      }

	    (void) sl_strlcat(mailMsg, popMsg, msgbufsize-(4*KEY_LEN));
	    (void) sl_strlcat(mailMsg, "\r\n", msgbufsize-(4*KEY_LEN));
	    (void) sh_util_compress(bufcompress, popMsg, combufsize-KEY_LEN-1);
	    SH_FREE(popMsg);
	    --sh.mailNum.alarm_last;
	    ++num_popped;
	  }
      }

    /* ------ signature block ------------------------------------ */
    
    (void) sl_strlcat(mailMsg, _("-----BEGIN SIGNATURE-----\r\n"), msgbufsize);

    /* Generate new signature key.
     */
    if (isfirst == 1)
      {
	BREAKEXIT(sh_util_keyinit);
	(void) sh_util_keyinit (skey->mailkey_old, KEY_LEN+1);
      }

    /* iterate the key
     */
    (void) sl_strlcpy(skey->mailkey_new,
		      sh_tiger_hash (skey->mailkey_old, TIGER_DATA, KEY_LEN,
				     hashbuf, sizeof(hashbuf)),
		      KEY_LEN+1);

    if (isfirst == 0)
      {
	char sigbuf[KEYBUF_SIZE];

        /* Sign the message with the signature key.
         */
	theSig = sh_util_siggen (skey->mailkey_new, 
				 bufcompress, sl_strlen(bufcompress),
				 sigbuf, sizeof(sigbuf));
	(void) sl_strlcat (mailMsg, 
			   theSig,
			   msgbufsize);
      }
    else
      {
        id_audit = time (NULL);

        /* reveal first signature key
         */
	/* flawfinder: ignore */
        (void) sl_strlcpy(skey->crypt, skey->mailkey_new, KEY_LEN+1); 

	BREAKEXIT(sh_util_encode);
	/* flawfinder: ignore */
        sh_util_encode(skey->crypt, bufcompress, 0, 'A');

	/* flawfinder: ignore */
        (void) sl_strlcat (mailMsg, skey->crypt, msgbufsize);
	/* flawfinder: ignore */
        memset (skey->crypt, 0, KEY_LEN);
        isfirst = 0;
      }
    (void) sl_strlcat (mailMsg, "\r\n", msgbufsize);

    /* X(n) -> X(n-1)
     */
    (void) sl_strlcpy (skey->mailkey_old, skey->mailkey_new, KEY_LEN+1);

    sl_snprintf(subject, sizeof(subject), _("%06d %010ld::%s\r\n"),
		mailcount, (long) id_audit, sh.host.name);

    (void) sl_strlcat (mailMsg, subject, msgbufsize);
    ++mailcount;

    (void) sl_strlcat (mailMsg, _("-----END MESSAGE-----"), msgbufsize);



    /* ---------- Connect ---------------------------------------- */



    /* -- Catch (ignore) 'broken pipe'.
     */
    new_act.sa_handler = SIG_IGN;
    sigemptyset( &new_act.sa_mask );         /* set an empty mask       */
    new_act.sa_flags = 0;                    /* init sa_flags           */

    (void) sigaction (SIGPIPE, &new_act, &old_act);

    i        = 0;
    errcount = 0;

    if (all_in_one == S_FALSE)
      {
	while (address_list[i] != NULL && i < address_num)
	  {
	    connfile = sh_mail_start_conn (i, &ma_socket);
	    
	    if (NULL != connfile)
	      {
		wrlen = fwrite (mailMsg, 1, sl_strlen(mailMsg), 
				 connfile);
		wrlen -= sl_strlen(mailMsg);
		if (wrlen == 0) 
		  status = sh_mail_end_conn (connfile, ma_socket);
		else
		  status = -1;
	      }
	    if (NULL == connfile ||  status != 0)
	      {
		ma_block = 1;
		sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_SRV_FAIL,
				 _("mail"), address_list[i]);
		ma_block = 0;
		++errcount;
		++sh.statistics.mail_failed;
	      }
	    else
	      {
		++sh.statistics.mail_success;
	      }

	    if (connfile != NULL)
	      {
		(void) fclose (connfile);
		connfile = NULL;
	      }
	    ++i;
	  }
      }
    else
      {
	connfile = sh_mail_start_conn ( -9 , &ma_socket);
	
	if (NULL != connfile)
	  {
	    wrlen = fwrite (mailMsg, 1, sl_strlen(mailMsg), connfile);
	    wrlen -= sl_strlen(mailMsg);
	    if (wrlen == 0)
	      status = sh_mail_end_conn (connfile, ma_socket);
	    else
	      status = -1;
	  }
	if (NULL == connfile ||  status != 0)
	  {
	    ma_block = 1;
	    sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_SRV_FAIL,
			     _("mail"), address_list[0]);
	    ma_block = 0;
	    errcount = address_num;
	    ++sh.statistics.mail_failed;
	  }
	else
	  {
	    ++sh.statistics.mail_success;
	  }

	if (connfile != NULL)
	  {
	    (void) fclose (connfile);
	    connfile = NULL;
	  }
      }

    
    memset (bufcompress, 0, combufsize);
    MUNLOCK(bufcompress , combufsize);
    SH_FREE(bufcompress);

    memset (mailMsg, 0, msgbufsize);
    MUNLOCK(mailMsg , msgbufsize);
    SH_FREE(mailMsg);

    /* --- Stay responsible for delivery in case of failure --- */

    if (errcount == address_num && fifo_temp != NULL)
      {
        while ( (NULL != (popMsg = pop_list(fifo_temp))) )
          {
            if (push_list (fifo_mail, popMsg) >= 0)
	      ++sh.mailNum.alarm_last;
            SH_FREE(popMsg);
          }
	if (message != NULL)
	  {
	    if (fifo_mail == NULL)
	      {
		fifo_mail = SH_ALLOC(sizeof(SH_FIFO));
		fifo_init(fifo_mail);
	      }
	    retval = push_list (fifo_mail,  theMsg);
	    if (retval >= 0) 
	      ++sh.mailNum.alarm_last;
	    if (retval == SH_FIFO_MAX)
	      retval = -2;
	    else
	      retval = -1;
	  }
      }
    else if (fifo_temp != NULL)
      {
        while ( (NULL != (popMsg = pop_list(fifo_temp))) )
          {
            SH_FREE(popMsg);
          }
      }
    if (fifo_temp != NULL)
      SH_FREE(fifo_temp);

    /*
    if (connfile != NULL) 
      fclose (connfile);
    */

    if (theMsg != NULL)
      SH_FREE(theMsg);

    /* --- Reset signal. ---
     */
    (void) sigaction (SIGPIPE, &old_act, NULL);

    if (errcount == address_num)
      {
	fail_time = time(NULL);
	SL_RETURN((retval), _("sh_mail_msg"));
      }
    success_time = time(NULL);
    failcount = 0;

    SL_RETURN((0), _("sh_mail_msg"));
}


/*
 *
 * SMTP CODE BELOW
 *
 *
 */

#include <ctype.h>
#ifdef  HOST_IS_HPUX
#define _XOPEN_SOURCE_EXTENDED
#endif
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifndef S_SPLINT_S
#include <arpa/inet.h>
#else
#define AF_INET 2
#endif

#define SH_NEED_GETHOSTBYXXX
#include "sh_static.h"

/* missing on HP-UX 10.20 */
#ifndef IPPORT_SMTP
#define IPPORT_SMTP 25
#endif

static int sh_mail_wait(int code, int ma_socket);

static char * relay_host = NULL;

int sh_mail_set_relay (const char * str_s)
{
  SL_ENTER(_("sh_mail_set_relay"));

  if (str_s == NULL)
    SL_RETURN( -1, _("sh_mail_set_relay"));

  if (relay_host != NULL)
    {
      SH_FREE (relay_host);
      relay_host = NULL;
    }

  if (0 == sl_strncmp(str_s, _("NULL"), 4))
    {
      SL_RETURN( 0, _("sh_mail_set_relay"));
    }

  relay_host = sh_util_strdup(str_s);

  SL_RETURN( 0, _("sh_mail_set_relay"));
}

static char * mail_sender = NULL;

int sh_mail_set_sender (const char *str)
{
  if (mail_sender != NULL) 
    {
      SH_FREE (mail_sender);
      mail_sender = NULL;
    }
  if (str != NULL)
    {
      mail_sender = sh_util_strdup (str);
    }
  if (mail_sender == NULL)
    {
      return -1;
    }
  return 0;
}


/*************************
 *
 * start connection
 * for details on SMTP, see RFC 821 
 */

static time_t time_wait = 300;

static FILE * sh_mail_start_conn (int aFlag, int * ma_socket)
{
  char       * address;

  int          ecount;

  char         this_address[256];
  char         ma_machine[256];
  char         ma_user[256];
  char         error_msg[256];
  char         error_call[SH_MINIBUF];
  int          error_num = 0;
  register int i, j, k;
  FILE       * connFile = NULL;
  struct tm  * my_tm;
#if defined(HAVE_PTHREAD) && defined (_POSIX_THREAD_SAFE_FUNCTIONS) && defined(HAVE_LOCALTIME_R)
  struct tm    time_tm;
#endif
  time_t       my_time;
  char         my_tbuf[128];

  int          fd;

  dnsrep     * answers;
  mx         * result;

  SL_ENTER(_("sh_mail_start_conn"));

  *ma_socket = -1;
  time_wait  = 300;

  if (aFlag >= 0)
    address = address_list[aFlag];
  else
    address = address_list[0];

  TPT(( 0, FIL__, __LINE__, _("msg=<aFlag %d address %s>\n"), 
	aFlag, address)); 

  /* -------   split adress ------------------  */

  if (0 == strcmp(address, _("NULL")))
    {
      SL_RETURN( NULL, _("sh_mail_start_conn"));
    } 
    
  if (strchr (address, '@') == NULL) {
    (void) sl_strlcpy(ma_user,    address,     256);
    (void) sl_strlcpy(ma_machine, _("localhost"), 256);
  } else {
    i = 0;
    while (i < 255 && address[i] != '@') {
      ma_user[i] = address[i];
      ++i;
    }

    /* adress[i] = '@' 
     */
    ma_user[i] = '\0';
    j = i + 1; k = i; i = 0;
    while (i < 255 && address[i+j] != '\0') {
      ma_machine[i] = address[i+j];
      ++i;
    }
    ma_machine[i] = '\0';
    if (address[k] != '@' || address[k+i+1] != '\0') 
      {
	SL_RETURN( NULL, _("sh_mail_start_conn"));
      } 
  }


  if (relay_host != NULL) 
    {
      (void) sl_strlcpy (ma_machine, relay_host, sizeof(ma_machine));
      TPT((0, FIL__, __LINE__, _("msg=<user %s machine %s>\n"), 
	   ma_user, ma_machine)); 
      fd = connect_port (ma_machine, IPPORT_SMTP, 
			 error_call, &error_num, error_msg, 256);
    }
  else
    {
      answers = return_mx (ma_machine);
      if (answers)
	{
	  result = answers->reply;
	  fd     = -1;
 	  for (i = 0; i < answers->count; ++i)
	    {
	      (void) sl_strlcpy(ma_machine, result[i].address, 
				sizeof(ma_machine));
	      TPT((0, FIL__, __LINE__, 
		   _("msg=<user %s mx %s pref %d>\n"), 
		   ma_user, ma_machine, result[i].pref));
	      fd = connect_port (ma_machine, IPPORT_SMTP, 
				 error_call, &error_num, error_msg, 256);
	      if (fd >= 0)
		break;
	    }
	  (void) free_mx(answers);
	}
      else
	{
	  (void) sl_strlcpy(error_call, _("return_mx"), SH_MINIBUF);
	  (void) sl_strlcpy(error_msg, _("The specified host is unknown: "), 
			    256);
	  (void) sl_strlcat(error_msg, ma_machine, 256); 
	  fd = -1;
	}
    }

  
  if (fd < 0)
    {
      sh_error_handle ((-1), FIL__, __LINE__, error_num, 
		       MSG_E_NET, error_msg, error_call,
		       _("email"), ma_machine);
      SL_RETURN( NULL, _("sh_mail_start_conn"));
    }

  /* associate a FILE structure with it
   */
  connFile = fdopen (fd, "r+");
  if (connFile == NULL) 
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<fdopen() failed>\n")));
      (void) close(fd);
      SL_RETURN( NULL, _("sh_mail_start_conn"));
    }


  /* say HELO to the other socket
   */
  if (0 == sh_mail_wait (220, fd)) 
    {
      sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_NET, 
		      _("Timeout on SMTP session init"), 
		      _("sh_mail_start_conn"), 
		      _("mail"), sh.host.name);
      TPT(( 0, FIL__, __LINE__, _("msg=<Timeout>\n")));
      (void) fclose(connFile);
      SL_RETURN( NULL, _("sh_mail_start_conn"));
    }

  (void) fflush(connFile);

  if (0 != is_numeric(sh.host.name))
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<HELO [%s]>%c%c"), 
	    sh.host.name, 13, 10));
    }
  else
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<HELO %s>%c%c"), 
	    sh.host.name, 13, 10));
    }
  if (0 != is_numeric(sh.host.name))
    fprintf(connFile, _("HELO [%s]%c%c"), sh.host.name, 13, 10);
  else
    fprintf(connFile, _("HELO %s%c%c"), sh.host.name, 13, 10);

  (void) fflush(connFile);

  if (0 == sh_mail_wait(250, fd)) 
    {
      sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_NET, 
		      _("HELO failed"), _("sh_mail_start_conn"), 
		      _("mail"), sh.host.name);

      TPT(( 0, FIL__, __LINE__, _("msg=<Timeout.>\n")));
      (void) fclose(connFile);
      SL_RETURN( NULL, _("sh_mail_start_conn"));
    }

  /* tell them who we are
   */
  (void) sl_strlcpy (this_address, 
		     mail_sender ? mail_sender : DEFAULT_SENDER, 256);
  if (NULL == strchr(this_address, '@'))
    {
      (void) sl_strlcat (this_address, "@", 256);
      if (0 != is_numeric(sh.host.name))
	(void) sl_strlcat (this_address, _("example.com"), 256);
      else
	(void) sl_strlcat (this_address, sh.host.name, 256);
    }

  TPT(( 0, FIL__, __LINE__,  _("msg=<MAIL FROM:<%s>>%c%c"), 
	this_address, 13, 10));

  (void) fflush(connFile);
  /*@-usedef@*/
  fprintf(connFile, _("MAIL FROM:<%s>%c%c"), this_address, 13, 10);
  /*@+usedef@*/
  (void) fflush(connFile);

  if (0 == sh_mail_wait(250, fd)) 
    {
      sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_NET, 
		      _("MAIL FROM failed"), _("sh_mail_start_conn"), 
		      _("mail"), this_address);
      TPT(( 0, FIL__, __LINE__, _("msg=<Timeout.>\n")));
      (void) fclose(connFile);
      SL_RETURN( NULL, _("sh_mail_start_conn"));
    }

  /* tell them who to send mail to
   */
  if (aFlag >= 0)
    {
      TPT(( 0, FIL__, __LINE__,  _("msg=<RCPT TO:<%s>>%c%c"), 
	    address, 13, 10)); 

      (void) fflush(connFile);
      fprintf(connFile, _("RCPT TO:<%s>%c%c"), address, 13, 10); 
      (void) fflush(connFile);

      if (0 == sh_mail_wait(250, fd)) 
	{
	  sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_NET, 
			  _("RCPT TO failed"), _("sh_mail_start_conn"), 
			  _("mail"), address);
	  TPT(( 0, FIL__, __LINE__, _("msg=<Timeout.>\n")));
	  (void) fclose(connFile);
	  SL_RETURN( NULL, _("sh_mail_start_conn"));
	}
    }
  else
    {
      ecount = 0;
      for (i = 0; i < address_num; ++i)
	{
	  if (address_list[i] == NULL)  /* paranoia */
	    break;
	  TPT(( 0, FIL__, __LINE__,  _("msg=<RCPT TO:<%s>>%c%c"), 
		address_list[i], 13, 10)); 
	  
	  (void) fflush(connFile);
	  fprintf(connFile, _("RCPT TO:<%s>%c%c"), address_list[i], 13, 10); 
	  (void) fflush(connFile);
	  
	  if (0 == sh_mail_wait(250, fd)) 
	    {
	      sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_NET, 
			      _("RCPT TO failed"), _("sh_mail_start_conn"), 
			      _("mail"), address_list[i]);

	      TPT(( 0, FIL__, __LINE__, _("msg=<Timeout.>\n")));
	      ++ecount;
	    }
	}
      if (ecount == address_num)
	{
	  (void) fclose(connFile);
	  SL_RETURN( NULL, _("sh_mail_start_conn"));
	}
    }

  /* Send the message 
   */
  TPT(( 0, FIL__, __LINE__,  _("msg=<DATA>%c%c"), 13, 10)); 

  (void) fflush(connFile);
  fprintf(connFile, _("DATA%c%c"), 13, 10);      
  (void) fflush(connFile);

  if (0 == sh_mail_wait(354, fd)) 
    {
      sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_NET, 
		      _("DATA failed"), _("sh_mail_start_conn"), 
		      _("mail"), address);
      TPT(( 0, FIL__, __LINE__, _("msg=<Timeout.>\n")));
      (void) fclose(connFile);
      SL_RETURN( NULL, _("sh_mail_start_conn"));
    }


  my_time = time(NULL);
#if defined(HAVE_PTHREAD) && defined (_POSIX_THREAD_SAFE_FUNCTIONS) && defined(HAVE_LOCALTIME_R)
  my_tm   = localtime_r(&my_time, &time_tm);
#else
  my_tm   = localtime(&my_time);
#endif
  (void)    strftime(my_tbuf, 127, _("%a, %d %b %Y %H:%M:%S %Z"), my_tm);

  TPT(( 0, FIL__, __LINE__,  _("msg=<From: <%s>%c%cTo: <%s>%c%cDate: %s>%c%c"),
	this_address, 13, 10, address, 13, 10, my_tbuf, 13, 10));

  (void) fflush(connFile);
  fprintf(connFile,
	  _("From: <%s>%c%c"\
	    "To: <%s>%c%c"\
	    "Date: %s%c%c"),
	  this_address, 13, 10,
	  address, 13, 10,
	  my_tbuf, 13, 10);

  *ma_socket = fd;
  SL_RETURN( connFile, _("sh_mail_start_conn"));
}

/*************************
 *
 * end connection
 *
 */

static int sh_mail_end_conn (FILE * connFile, int fd)
{
  SL_ENTER(_("sh_mail_end_conn"));

  time_wait = 300;

  (void) fflush(connFile);
  fprintf(connFile, _("%c%c.%c%c"), 13, 10, 13, 10);   
  (void) fflush(connFile);

  TPT(( 0, FIL__, __LINE__, _("msg=<message end written>\n")));

  if (0 != sh_mail_wait(250, fd))
    {  
      (void) fflush(connFile);
      fprintf(connFile, _("QUIT%c%c"), 13, 10);
      (void) fflush(connFile);
      TPT(( 0, FIL__, __LINE__, _("msg=<exit>\n")));

      SL_RETURN (0, _("sh_mail_end_conn"));
    }
    
  sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_NET, 
		  _("QUIT failed"), _("sh_mail_end_conn"), 
		  _("mail"), _("SMTP server"));

  TPT(( 0, FIL__, __LINE__, _("msg=<abnormal exit>\n")));

  SL_RETURN ((-1), _("sh_mail_end_conn"));
}



/****************************
 *
 * Handle server replies
 *
 *
 */

static int sh_mail_wait(int code, int ma_socket)
{
  int rcode, g;

  char c;

  char errmsg[128];

  enum { 
    WAIT_CODE_START, 
    WAIT_CODE, 
    WAIT_NL, 
    WAIT_NL_CONT 
  } state;

  time_t waited_time;

  SL_ENTER(_("mail_wait"));
  
  waited_time = time(NULL);

  /* timeout after 5 minutes
   */

  rcode = 0;
  state = WAIT_CODE_START;

  while (sl_read_timeout_fd (ma_socket, &c, 1, time_wait, SL_FALSE) > 0) {

    g = (int) c;

    /*
    if (g == EOF)
      {
	TPT((0, FIL__, __LINE__, _("msg=<mail_wait: EOF>\n"))); 
	SL_RETURN( 0, _("mail_wait")); 
      }
    */

    switch(state) {

      /* wait for start of a numerical code
       */
    case WAIT_CODE_START:
      if (0 != isspace(g))
	break;             /* Skip white space                    */
      if (0 == isdigit(g))
	return 0;          /* No leading number                   */
      rcode = g-(int)'0';  /* convert to number                   */
      state = WAIT_CODE;
      break;
      
      /* wait for completion of numerical code
       */
    case WAIT_CODE:
      if (0 != isdigit(g)) {
	rcode = rcode * 10 + (g-(int)'0'); /* next digit          */
	break;
      }
      /*@+charintliteral@*/
      state = ((g == '-') ?  WAIT_NL_CONT :  WAIT_NL); 
      /*@-charintliteral@*/
      break;
      
      /* wait for newline, then return with status code
       */
    case WAIT_NL:
      /*@+charintliteral@*/
      if (g != '\n')
	break;
      /*@-charintliteral@*/

      TPT((0, FIL__, __LINE__, 
	   _("msg=<mail_wait: OK got %d (%d) need %d (%d)>\n"),
	   rcode, (int)(rcode/100), code, (int)(code/100) ));
      g = ((int)(rcode/100) == (int)(code/100)) ? 1 : 0;
      if (g != 1)
	{
          sl_snprintf(errmsg, sizeof(errmsg),
		      _("Bad response (%d), expected %d"), rcode, code);

	  sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_NET, 
			  errmsg, _("sh_mail_wait"), 
			  _("mail"), _("SMTP server"));
	}
      waited_time = time(NULL) - waited_time;
      time_wait -= waited_time;
      TPT((0, FIL__, __LINE__, 
	   _("msg=<mail_wait: time_wait reduced to %d sec>\n"),
	   (int) time_wait));
      SL_RETURN( (g), _("mail_wait")) ;

      /* wait for continuation line
       */
      /*@fallthrough@*//* no, but splint doesn't understand */
    case WAIT_NL_CONT:
      /*@+charintliteral@*/
      if (g == '\n')
	state = WAIT_CODE_START;  /* There is a continuation line */
      /*@-charintliteral@*/
      break; 
      
    default:

      TPT((0, FIL__, __LINE__, _("msg=<mail_wait: bad>\n"))); 
      SL_RETURN( 0, _("mail_wait")); 
      
    }
  }

  TPT((0, FIL__, __LINE__, _("msg=<mail_wait: failed>\n"))); 

  /* Failed, EOF or error on socket */
  SL_RETURN( 0, _("mail_wait")); 
}

/* -- function to insert "\r\n" after each 998 chars --
 */

#define SPLIT_AT 998

static char * split_string(char * str)
{
  size_t size;
  size_t blocks;
  int    count = 0;

  char * p, * p0;
  char * q;

  if (!str)
    return NULL;

  size   = strlen(str) + 1;
  blocks = 1 + (size / SPLIT_AT);
  
  if (sl_ok_muls(2, blocks) && sl_ok_adds(size, (2*blocks)))
    {
      size   = size + (2*blocks);
    }
  else
    {
      /* integer overflow, do not split */
      p = sh_util_strdup(str);
      return p;
    }

  p = SH_ALLOC(size);
  memset(p, 0, size);

  p0 = p;

  q = str;
  while (*q != '\0') {
    *p = *q;
    ++p;
    ++q;
    ++count;
    if (0 == (count % SPLIT_AT)) {
      count = 0;
      *p = '\r';
      ++p;
      *p = '\n';
      ++p;
    }
  }
  /* fprintf(stderr, "used = %d\n", strlen(p0)); */

  return p0;
}



/*****************************************************************
 *
 * MX Resolver Routines
 *
 *****************************************************************/

#if defined(HAVE_ARPA_NAMESER_H)

#include <netinet/in.h>
#ifdef __APPLE__
#define BIND_8_COMPAT 1
#endif
#ifndef S_SPLINT_S
#include <arpa/nameser.h>
#include <resolv.h>
#endif
#include <netdb.h>
#include <sys/socket.h>
#ifndef S_SPLINT_S
#include <arpa/inet.h>
#endif

#include "sh_tools.h"

#ifndef HFIXEDSZ
#define HFIXEDSZ 12
#endif
#ifndef QFIXEDSZ
#define QFIXEDSZ  4
#endif

/*@unused@*//* used in get_mx() which is not parsed by splint */
static unsigned int get_short (unsigned char * loc)
{
  unsigned int retval = 0;
  if (loc)
    {
      /* byte order: MSB first
       */
      /*@+charint@*/
      retval = (((unsigned char) * loc) * 256) | ((unsigned char) * (loc + 1));
      /*@-charint@*/
    }
  return (retval);
}

/* parser errors with splint */
#ifndef S_SPLINT_S
static dnsrep * get_mx (char *hostname)
{
  int  ret, length, status;
  mx * result;
  size_t len;

  typedef union
  {
    HEADER head;
    unsigned char buffer[4096];
  } querybuf;

  querybuf * reply;
  char expanded[1024];
  unsigned char * comp_dn, * eom;
  HEADER * header;
  int      type, rdlength, pref;
  unsigned int count, theindex;
  dnsrep * retval;

  SL_ENTER(_("get_mx"));

  if (0 != res_init ())
    SL_RETURN (NULL, _("get_mx"));

  reply = SH_ALLOC(sizeof(querybuf));

  errno = 0;
  length = res_query (hostname, C_IN, T_MX, 
		      (unsigned char *) reply, 4095);
  if (length < 1)
    {
      char errbuf[SH_ERRBUF_SIZE];

      /* error handling
       */
      if (length == -1)
	{
	  if (errno == ECONNREFUSED)
	    status = ECONNREFUSED;
	  else
	    status = h_errno;

#ifdef FIL__
	  sh_error_handle (SH_ERR_ALL, FIL__, __LINE__, status, MSG_E_SUBGEN,
			   (errno == ECONNREFUSED) ? 
			   sh_error_message (status, errbuf, sizeof(errbuf)) : 
			   sh_tools_errmessage(status, errbuf, sizeof(errbuf)),
			   _("res_query"));
#else
	  if (errno == ECONNREFUSED)
	    fprintf(stderr, " ERROR: %s: \n", strerror(errno)); /* TESTONLY */
	  else
	    fprintf(stderr, "HERROR: %s\n", hstrerror(h_errno));/* TESTONLY */
#endif
	}
      SH_FREE(reply);
      SL_RETURN (NULL, _("get_mx"));
    }

  ret = 0;
  header  = (HEADER *) reply;

  /* start of data section
   */
  comp_dn = (unsigned char *) reply + HFIXEDSZ;

  /* end-of-message
   */
  eom     = (unsigned char *) reply + length;

  /* HEADER NAME  -- must be skipped or decompressed
   * TYPE         -- type of data we got back, 16 bit integer
   * CLASS        -- class we got back, also a 16 bit integer 
   * TTL          -- 32 bit time-to-live. just skip this 
   * RDLENGTH     -- length of the data to follow 
   * RDATA        -- the data:
   *                 PREF  -- 16 bit preference 
   *                 MX    -- name of mail exchanger, must be decompressed
   */

  /* Skip the query data. 
   * QDCOUNT is the number of entries (unsigned 16 bit int). 
   */
  count = ntohs (header->qdcount); 
  for (theindex = 0; theindex < count; ++theindex)
    {
      ret = dn_skipname (comp_dn, eom);
      comp_dn += ret + QFIXEDSZ;
      if (ret < 1 || comp_dn >= eom)
	{
	  SH_FREE(reply);
	  SL_RETURN (NULL, _("get_mx"));
	}
    }
  count         = ntohs (header->ancount);
  if (count < 1)
    {
      SH_FREE(reply);
      SL_RETURN (NULL, _("get_mx"));
    }

  retval        = SH_ALLOC (sizeof (dnsrep));
  if (!retval)
    {
      SH_FREE(reply);
      SL_RETURN (NULL, _("get_mx"));
    }

  retval->count = count;

  /* allocate space for the results */

  if (!sl_ok_muls(count, sizeof (mx)))
    {
      SH_FREE(reply);
      SH_FREE   (retval);
      SL_RETURN (NULL, _("get_mx"));
    }

  result        = SH_ALLOC (count * sizeof (mx));
  
  if (!result)
    {
      SH_FREE(reply);
      SH_FREE   (retval);
      SL_RETURN (NULL, _("get_mx"));
    }
  retval->reply = result;

  do
    {
      /* HEADER NAME 
       */
      ret = dn_expand ((unsigned char *) &reply, eom, comp_dn, 
		       (char *) expanded, 1023);
      comp_dn += ret;
      if (ret < 1 || comp_dn >= eom)
	{
	  SH_FREE(reply);
	  SH_FREE (result);
	  SH_FREE (retval);
	  SL_RETURN (NULL, _("get_mx"));
	}

      /* TYPE
       */
      type = get_short (comp_dn);
      comp_dn += 2;
      if (type != T_MX || comp_dn >= eom)
	{
	  SH_FREE(reply);
	  SH_FREE (result);
	  SH_FREE (retval);
	  SL_RETURN (NULL, _("get_mx"));
	}

      /* CLASS (re-use 'type' var)
       */
      type = get_short (comp_dn);
      comp_dn += 2;
      if (comp_dn >= eom)
	{
	  SH_FREE(reply);
	  SH_FREE (result);
	  SH_FREE (retval);
	  SL_RETURN (NULL, _("get_mx"));
	}

      /* TTL
       */
      comp_dn += 4;
      if (comp_dn >= eom)
	{
	  SH_FREE(reply);
	  SH_FREE (result);
	  SH_FREE (retval);
	  SL_RETURN (NULL, _("get_mx"));
	}

      /* RDLENGTH
       */
      rdlength = get_short (comp_dn);
      comp_dn += 2;
      if (rdlength < 1 || comp_dn >= eom)
	{
	  SH_FREE(reply);
	  SH_FREE (result);
	  SH_FREE (retval);
	  SL_RETURN (NULL, _("get_mx"));
	}

      /* RDATA
       */
      pref = get_short (comp_dn);
      comp_dn += 2;
      if (comp_dn >= eom)
	{
	  SH_FREE(reply);
	  SH_FREE (result);
	  SH_FREE (retval);
	  SL_RETURN (NULL, _("get_mx"));
	}

      ret = dn_expand ((unsigned char *) &reply, eom, comp_dn, 
		       (char *) expanded, 1023);
      comp_dn += ret;
      if (ret < 1)
	{
	  SH_FREE(reply);
	  SH_FREE (result);
	  SH_FREE (retval);
	  SL_RETURN (NULL, _("get_mx"));
	}
      count--;

      /* fill in the struct 
       */
      result[count].pref = pref;
      len = strlen (expanded) + 1;
      result[count].address = SH_ALLOC (len);
      sl_strlcpy (result[count].address, expanded, len);
    }
  while (ret > 0 && comp_dn < eom && count);

  SH_FREE(reply);
  SL_RETURN (retval, _("get_mx"));
}
/* ifndef S_SPLINT_S */
#endif

/* #if defined(HAVE_ARPA_NAMESER_H) */
#endif


static int comp_mx_pref (const void * a, const void * b)
{
  const mx * ax = (const mx *) a;
  const mx * bx = (const mx *) b;
  
  if      (ax->pref > bx->pref)
    return 1;
  else if (ax->pref < bx->pref)
    return -1;
  else
    return 0;
}

/*
 * return_mx returns a list of valid mail exchangers for domain
 */
static dnsrep * return_mx (char *domain)
{
  struct hostent *host;
  dnsrep * answers = NULL;
  mx     * result;
  dnsrep * retval;
  char     errmsg[128];
  size_t   len;

  SL_ENTER(_("return_mx"));

#if defined(HAVE_ARPA_NAMESER_H)
  if (domain != NULL)
    answers = /*@-unrecog@*/get_mx (domain)/*@+unrecog@*/;
#endif

  if (answers != NULL && answers->count > 0)
    {
      qsort(answers->reply, (size_t) answers->count, sizeof(mx),
            comp_mx_pref);
      SL_RETURN (answers, _("return_mx"));
    }
  else
    {
      if (domain != NULL)
	{
#if defined(HAVE_ARPA_NAMESER_H)
#ifdef FIL__
	  (void) sl_strlcpy (errmsg, _("No MX record for domain "), 127);
	  (void) sl_strlcat (errmsg, domain, 127);
	  sh_error_handle (SH_ERR_ALL, FIL__, __LINE__, 0, MSG_E_SUBGEN,
			   errmsg,
			   _("get_mx"));
#else
	  /* flawfinder: ignore *//* test code only */
	  strcpy  (errmsg,                               /* known to fit  */
		   _("No MX record for domain "));
	  strncat (errmsg, domain, 100);
	  errmsg[122] = '\0';
	  fprintf(stderr, "Warning: %s\n", errmsg);
#endif
#endif
	}

      SH_MUTEX_LOCK(mutex_resolv);

      host   = NULL;
      retval = NULL;

      if (domain != NULL)
	host = /*@-unrecog@*/sh_gethostbyname (domain)/*@+unrecog@*/;

      if (host)
	{
	  result       = SH_ALLOC (sizeof (mx));
	  retval       = SH_ALLOC (sizeof (dnsrep));
	  retval->reply = result;
	  retval->count = 1;
	  result->pref  = 0;
	  /*@-type@*/
	  len = strlen (host->h_name) + 1;
	  result->address = SH_ALLOC (len);
	  sl_strlcpy (result->address, host->h_name, len);
	  /*@+type@*/
	}
      SH_MUTEX_UNLOCK(mutex_resolv);

      if (!host)
	{
#ifdef FIL__
	  (void) sl_strlcpy (errmsg, _("Unknown host "), 127);
	  (void) sl_strlcat (errmsg, domain, 127);
	  sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
			   errmsg,
			   _("return_mx"));
#endif
	  SL_RETURN (NULL, _("return_mx"));
	}

      SL_RETURN (retval, _("return_mx"));
    }
}

static int free_mx (dnsrep * answers)
{
  mx     * result;
  int      i;

  SL_ENTER(_("free_mx"));
  if (!answers)
    SL_RETURN (0, _("return_mx"));

  result = answers->reply;  
  for (i = 0;  i < answers->count; ++i)
    {
      SH_FREE (result[i].address);
    }
  SH_FREE(result);
  SH_FREE(answers);
  SL_RETURN (0, _("return_mx"));
}

#ifdef TEST_ONLY
int main(int argc, char * argv[])
{
  int      i;
  dnsrep * answers;
  mx     * result;

  if (argc < 2)
    {
      fprintf(stderr, "Usage: dns <hostname>\n");
      return -1;
    }
  answers = return_mx(argv[1]);

  if (!answers)
    {
      fprintf(stderr, "No answer\n");
      return -1;
    }

  if (answers->count > 0)
    {
      result = answers->reply;
      for (i = 0; i < answers->count; ++i)
	{
	  fprintf(stderr, "Record %3d: [%3d] %s\n", i, 
		  result[i].pref, result[i].address);
	}	  
    }
  else
    {
      fprintf(stderr, "No answer\n");
      free_mx(answers);
      return -1;
    }
  free_mx(answers);
  return (0);
}
#endif

  

/* if defined(SH_WITH_MAIL) */
#endif



