#include "mfapi.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h> /* for Solaris */
#include <sys/types.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <unistd.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#ifdef __FreeBSD__
#include <sys/wait.h>
#else
#include <wait.h>
#endif
#include <errno.h>
#include <sys/param.h>
#include <sys/time.h>
#include <dirent.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <sys/resource.h>


static char *rcsid[]={
  "$Author: wcolburn $",
  "$Date: 2003/06/30 16:15:42 $",
  "$Revision: 3.30 $",
  "$Source: /system/milter/RCS/antivirus.c,v $",
  '\0'
};
static char *versionid="$Id: antivirus.c,v 3.30 2003/06/30 16:15:42 wcolburn Exp $";

static char *webpage="http://www.nmt.edu/~wcolburn/antivirus/";

static char *copyright="Copyright (c) 2001 New Mexico Institute of Mining and Technology. All rights reserved.";

/*
** I *DESPERATLY* need to keep track of my contributors!
**
** William D. Colburn (original author)
** Thomas Lussnig (also peeled off his own version)
** James R. Stahr
** Tim Toolan
** Steve Barber
** Sebastien VAUTHEROT 
** Pierre DAVID
** Emmanuel Collignon
*/

/*
** compilestamp and sendmailstamp come from the Makefile
*/
extern char *compilestamp;
extern char *sendmailstamp;

/*
** errno comes from errno.h and libc
*/
extern int errno;

/*
** these can be changed from the configure file
*/
static char *HOMEDIR=NULL;
static char *BASEDIR=NULL;
static char *AVSCANDIR =NULL;
static char *QUARANTINE=NULL;
static char *AVSCANNER=NULL;
static char *AVSCANARGS=NULL;
static char *UNPACKER=NULL;
static char *SAFEUSER=NULL;
static char *SAFEGROUP=NULL;
static char *PIDFILE=NULL;
static char *SOCKETNAME=NULL;
static char *TIMEOUT=NULL;
static char *AVPRODUCT=NULL;
static char *AVFAILACTION=NULL;
static char *VIRUSACTION=NULL;

static char *FORMAT=NULL;
static sfsistat avfailcode=0;
static int purgevirus=0;
static char *avargs[]={NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};

/*
** You might want to change these
*/
static char *badext[]={".com",".scr",".vbs",".pif"};

/*
** these are the default values
*/
#define CONF_HOMEDIR      "/system/av"
#define CONF_BASEDIR      "/system/av/abattoir"
#define CONF_AVSCANDIR    "/system/av/decullotage"
#define CONF_QUARANTINE   "/system/av/oubliette"
#define CONF_AVSCANNER    "/system/av/decullotage/uvscan"
#define CONF_AVSCANARGS   ""
#define CONF_UNPACKER     "/system/bin/ripmime"
#define CONF_SAFEUSER     "nobody"
#define CONF_SAFEGROUP    "nobody"
#define CONF_PIDFILE      "/system/av/etc/antivirus.pid"
#define CONF_SOCKETNAME   "local:/system/av/etc/antivirus.sock"
#define CONF_TIMEOUT      ""
#define CONF_AVPRODUCT    "mcafee"
#define CONF_AVFAILACTION "tempfail"
#define CONF_VIRUSACTION  "quarantine"

/*
** This one is for Mcafee
*/
#define FORMAT_MCAFEE "        Found the %s virus !!!"
#define SCANARGS_MCAFEE "--unzip"

/*
** This one is for FSAV (f-prot)
*/
#define FORMAT_FSAV "%*s infection: %s"

/*
** This one is for Sophos
*/
#define FORMAT_SOPHOS ">>> Virus '%[^']s' found in file %*s"

/*
** this can be given on the command line
*/
static char *configfile=NULL;
#define DEFAULTCONFIGFILE "/etc/mail/antivirus.conf"

struct mlfiPriv
{
  char *qid;
  char *datafile;
  FILE *datafp;
  char *workdir;
  int headers;
  int bodyblocks;
  int bodysize;
  int hasvirus;
  char *viruses;
  int childpid;
  int childstatus;
  FILE *childfp;
  DIR *dir;
  char *scripts;
#ifdef ANTIVIRUSTIMING
  struct timeval start;
#endif /* ANTIVIRUSTIMING */
};

/*
** this is a handy shortcut
*/
#define MLFIPRIV        ((struct mlfiPriv *) smfi_getpriv(ctx))

/*
** debugging sucks
*/
static int debug=0;

/*
** forward definitions
*/
void cleanup(SMFICTX *);

sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv)
{
  struct mlfiPriv *priv = MLFIPRIV;  
  char *qid;
  char buf[512];

  if (debug)
    syslog(LOG_ERR,"mlfi_envfrom(0x%x, %s)",(int)ctx,argv[0]);

  if (priv)
    syslog(LOG_ERR,"mlfi_envfrom(0x%x, %s) given priv 0x%x",
	   (int)ctx,argv[0],(int)priv);

  priv = malloc(sizeof *priv);
  if (priv == NULL)
    {
      syslog(LOG_CRIT,"malloc(): %s",strerror(errno));
      return SMFIS_TEMPFAIL;
    }
  memset(priv, '\0', sizeof *priv);
  smfi_setpriv(ctx, priv);

  qid=smfi_getsymval(ctx,"i");
  if (qid==NULL)
    {
      syslog(LOG_CRIT,"could not get $i from sendmail");
      return(SMFIS_TEMPFAIL);
    }

  priv->qid=strdup(qid);
  if ((priv->qid)==NULL)
    {
      syslog(LOG_CRIT,"strdup(): %s",strerror(errno));
      return(SMFIS_TEMPFAIL);
    }

  if (debug)
    syslog(LOG_ERR,"mlfi_envfrom(0x%x, %s) processing qid %s",
	   (int)ctx,argv[0],qid);

  if (((priv->headers)!=0) || ((priv->bodyblocks)!=0))
    {
      syslog(LOG_ERR,"mlfi_envfrom() found headers and bodyblocks > 0");
    }

  snprintf(buf,sizeof(buf),"%s/%s",BASEDIR,priv->qid);
  priv->workdir=strdup(buf);
  if ((priv->workdir)==NULL)
    {
      syslog(LOG_CRIT,"strdup(): %s",strerror(errno));
      return(SMFIS_TEMPFAIL);
    }

  snprintf(buf,sizeof(buf),"%s/%s",priv->workdir,qid);
  priv->datafile=strdup(buf);
  if ((priv->datafile)==NULL)
    {
      syslog(LOG_CRIT,"strdup(): %s",strerror(errno));
      return(SMFIS_TEMPFAIL);
    }

  if (mkdir(priv->workdir,0700)!=0)
    {
      syslog(LOG_CRIT,"mkdir(%s): %s",priv->workdir,strerror(errno));
      return(SMFIS_TEMPFAIL);
    }

  /*
  ** FIXME use open() instead because Solaris is stupid
  */
  priv->datafp=fopen(priv->datafile,"w");
  if ((priv->datafp)==NULL)
    {
      syslog(LOG_CRIT,"fopen(%s): %s",priv->datafile,strerror(errno));
      return(SMFIS_TEMPFAIL);
    }

  if (debug)
    syslog(LOG_ERR,"mlfi_envfrom(0x%x, %s) => SMFIS_CONTINUE",
	   (int)ctx,argv[0]);
  return(SMFIS_CONTINUE);
}

sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv)
{
  char *rcptaddr = smfi_getsymval(ctx, "{rcpt_addr}");

  if (debug)
    syslog(LOG_ERR,"mlfi_envrcpt(0x%x, %s)",(int)ctx,argv[0]);

  if (rcptaddr && (strcasecmp(rcptaddr,"postmaster")==0))
    {
      /*
      ** always accept postmaster mail
      **
      ** any mail that includes postmaster as a recipent in the
      ** SMTP transaction will be accepted without any processing
      **
      ** DANGER => people can use this against us by including
      ** postmaster in mail with viruses.  I don't consider this
      ** dangerous enough to implement a solution, so unless it
      ** starts happening I will ignore it.  Problem noted by
      ** Thomas Lussnig.
      */

      /*
      ** James R. Stahr <stahr@binc.net discovered that the lack of a
      ** cleanup(ctx) here causes some queue directories not to be
      ** cleaned up.  I had noticed them appearing, but had never
      ** worried enough about where there were coming from.
      */
      cleanup(ctx);
      if (debug)
	syslog(LOG_ERR,"mlfi_envrcpt(0x%x, %s) => SMFIS_ACCEPT",
	       (int)ctx,argv[0]);
      return SMFIS_ACCEPT;
    }

  /* continue processing */
  if (debug)
    syslog(LOG_ERR,"mlfi_envrcpt(0x%x, %s) => SMFIS_CONTINUE",
	   (int)ctx,argv[0]);
  return SMFIS_CONTINUE;
}

sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv)
{
  struct mlfiPriv *priv = MLFIPRIV;


  if (debug)
    syslog(LOG_ERR,"smfi_header(0x%x, %s, <omitted>)",(int)ctx,headerf);

  /*
  ** FIXME use write() instead because Solaris is stupid
  */
  if (fprintf(priv->datafp, "%s: %s\r\n", headerf, headerv)<=0)
    return(SMFIS_TEMPFAIL);

  (priv->headers)++;
  if (debug)
    syslog(LOG_ERR,"smfi_header(0x%x, %s, <omitted>) => SMFIS_CONTINUE",
	   (int)ctx,headerf);
  return SMFIS_CONTINUE;
}

sfsistat mlfi_eoh(SMFICTX *ctx)
{
  struct mlfiPriv *priv = MLFIPRIV;

  if (debug)
    syslog(LOG_ERR,"smfi_eoh(0x%x)",(int)ctx);

  /*
  ** FIXME use write() instead because Solaris is stupid
  */
  if (fprintf(priv->datafp, "\r\n")<=0)
    {
      if (debug)
	syslog(LOG_ERR,"smfi_eoh(0x%x) => SMFIS_TEMPFAIL",(int)ctx);
      return(SMFIS_TEMPFAIL);
    }
  
  if (debug)
    syslog(LOG_ERR,"smfi_eoh(0x%x) => SMFIS_CONTINUE",(int)ctx);

  return SMFIS_CONTINUE;
}

sfsistat mlfi_body(SMFICTX *ctx, unsigned char *bodyp, size_t bodylen)
{
  int nwritten;
  struct mlfiPriv *priv = MLFIPRIV;
  
  if (debug)
    syslog(LOG_ERR,"smfi_body(0x%x, 0x%x, %d)",(int)ctx,(int)bodyp,bodylen);

  (priv->bodyblocks)++;
  (priv->bodysize)+=bodylen;

  /*
  ** FIXME use write() instead because Solaris is stupid
  */
  if ((nwritten = fwrite(bodyp, bodylen, 1, priv->datafp)) != 1)
    {
      syslog(LOG_CRIT,"mlfi_body(): fwrite(): %s",strerror(errno));
      (void) cleanup(ctx);
      return SMFIS_TEMPFAIL;
    }
  
  if (debug)
    syslog(LOG_ERR,"smfi_body(0x%x, 0x%x, %d) => SMFIS_CONTINUE",
	   (int)ctx,(int)bodyp,bodylen);
  return SMFIS_CONTINUE;
}

int externalcommand(SMFICTX *ctx, char *workdir, int fd, char *p, char **av)
{
  struct mlfiPriv *priv = MLFIPRIV;
  char *ev[]={"PATH=/bin:/usr/bin",NULL};
  int i;
  int openmax=getdtablesize();

  if (debug)
    syslog(LOG_ERR,"externalcommand(0x%x, ..., %s, ...)",(int)ctx,p);

  if ((priv->childpid)!=0)
    {
      syslog(LOG_CRIT,"externalcommand() called and priv->childpid is %d",
	     priv->childpid);
    }

  priv->childstatus=0;
  priv->childpid=fork();
  switch(priv->childpid)
    {
    case -1: /* error */
      syslog(LOG_CRIT,"fork(): %s",strerror(errno));
      priv->childpid=0;
      return(-1);
      break;
    case 0: /* child */
      if (workdir!=NULL)
	{
	  if (chdir(workdir)!=0)
	    {
	      syslog(LOG_CRIT,"chdir(%s): %s",workdir,strerror(errno));
	    }
	}
      if (fd==EOF)
	{
	  close(0);
	  close(1);
	  close(2);
	  open("/dev/null",O_RDONLY);
	  open("/dev/null",O_WRONLY);
	  open("/dev/null",O_WRONLY);
	}
      else
	{
	  close(0);
	  open("/dev/null",O_RDONLY);
	  dup2(fd,1);
	  dup2(fd,2);
	}

      for (i=3;i<openmax;i++)
	close(i);

      execve(p, av, ev);
      syslog(LOG_CRIT,"externalcommand() failed to execve() %s",p);
      exit(1);
      break;
    default: /* parent */
      waitpid(priv->childpid,&(priv->childstatus),0);
      priv->childpid=0;
      if (WIFEXITED(priv->childstatus))
	{
	  if (debug)
	    syslog(LOG_ERR,"externalcommand(0x%x, ..., %s, ...) => %d",
		   (int)ctx,p,WEXITSTATUS(priv->childstatus));
	  return(WEXITSTATUS(priv->childstatus));
	}
      else
	{
	  if (debug)
	    syslog(LOG_ERR,"externalcommand(0x%x, ..., %s, ...) => -1",
		   (int)ctx,p);
	  return(-1);
	}
      break;
    }
  if (debug)
    syslog(LOG_ERR,"externalcommand(0x%x, ..., %s, ...) => -1",(int)ctx,p);
  return(-1);
}

int unpack(SMFICTX *ctx, char *workdir, char *datafile)
{
  struct mlfiPriv *priv = MLFIPRIV;
  char *p=UNPACKER;
  char *av[]={NULL,"--unique_names","-i",NULL,NULL};
  union
  {
    struct dirent d;
    char s[(sizeof(struct dirent)+MAXPATHLEN+1)];
  } dirent;
  struct dirent *direntp;
  int count=0;

  av[0]=UNPACKER;
  av[3]=datafile;
  
  externalcommand(ctx, workdir, EOF, p, av);

  /*
  ** On Solaris you need to compile with:
  **   -D_POSIX_PTHREAD_SEMANTICS -D_REENTRANT
  */
  priv->dir=opendir(priv->workdir);
  while((readdir_r(priv->dir,&(dirent.d),&direntp)==0) && (direntp !=NULL))
    {
      count++;
    }
  closedir(priv->dir);
  priv->dir=NULL;

  if (debug)
    syslog(LOG_ERR,"unpack() found %d files, returning %d",count,(count>3));

  return(count>3);
}

int rmrf(SMFICTX *ctx)
{
  struct mlfiPriv *priv = MLFIPRIV;
  char *p="/bin/rm";
  char *av[]={"rm","-rf",NULL,NULL};

  av[2]=priv->workdir;

  return(externalcommand(ctx, NULL, EOF, p, av));
}

sfsistat virusscan(SMFICTX *ctx, char *path, char *format)
{
  struct mlfiPriv *priv = MLFIPRIV;
  char buf[512];
  char viruses[512];
  char tmp[512];
  int retval;
  int fd;
  int i;
  char *p=NULL;
  char *av[]={NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};

  /*
  ** the size of av here is dependant on the sizeof avargs elsewhere
  */
  if ((sizeof(av)/sizeof(char *)+3)>(sizeof(avargs)/sizeof(char)))
    {
      syslog(LOG_ERR,"virusscan() detected that char *avargs[] is larger than char *av[]!\n");
      syslog(LOG_ERR,"virusscan() not scanning anything!\n");
      syslog(LOG_ERR,"virusscan() DANGER! DANGER! DANGER!\n");
      return(SMFIS_CONTINUE);
    }

  if (debug)
    syslog(LOG_ERR,"virusscan(0x%x, ...)",(int)ctx);
  
  snprintf(buf,sizeof(buf),"%s/tmp.XXXXXX",priv->workdir);
  fd=mkstemp(buf);
  if (fd==EOF)
    {
      syslog(LOG_CRIT,"mkstemp(): %s",strerror(errno));
      return(SMFIS_TEMPFAIL);
    }

  priv->childfp=fdopen(fd,"rw");
  if ((priv->childfp)==NULL)
    {
      syslog(LOG_CRIT,"fdopen(): %s",strerror(errno));
      close(fd);
      return(SMFIS_TEMPFAIL);
    }

  p=path;
  av[0]=path;
  for (i=0;i<(sizeof(avargs)/sizeof(char *))&&avargs[i];i++)
    {
      av[i+1]=avargs[i];
    }
  av[i+1]=priv->workdir;

  retval=externalcommand(ctx,AVSCANDIR,fileno(priv->childfp),p,av);

  if (debug)
    {
      syslog(LOG_ERR,"virusscan(0x%x, ...)"
	     "externalcommand() returned %d on %s",
	     (int)ctx, retval,priv->workdir);
    }

  if (retval>0)
    {
      fseek(priv->childfp,0,SEEK_SET);
      memset(viruses,0,sizeof(buf));
      while (fgets(buf,sizeof(buf),priv->childfp)!=NULL)
	{
	  if (sscanf(buf,format,tmp)==1)
	    {
	      if (viruses[0])
		strncat(viruses," ",sizeof(viruses));
	      strncat(viruses,tmp,sizeof(viruses));
	    }
	}

      fclose(priv->childfp);

      priv->childfp=NULL;
      if (viruses[0])
	priv->viruses=strdup(viruses);
      else
	priv->viruses=strdup("please contact postmaster");

      if ((priv->viruses)==NULL)
	{
	  return(avfailcode);
	}
      return(SMFIS_REJECT);
    }

  if (retval < 0)
    {
      syslog(LOG_CRIT,"externalcommand() returned less than zero");
      return(avfailcode);
    }
  if (debug)
    syslog(LOG_ERR,"virusscan(0x%x, ...) => SMFIS_CONTINUE",(int)ctx);
  return(SMFIS_CONTINUE);
}

/*
** only scan for known bad extensions
** this is wrong but less likely to generate false positives...
*/
static int isbadfilename(char *name)
{
  static int nbad=(sizeof(badext)/sizeof(char *));
  int i;
  size_t flen;
  size_t elen;

  flen=strlen(name);

  for (i=0;i<nbad;i++)
    {
      elen=strlen(badext[i]);
      if (flen > elen)
	{
	  if (strcasecmp((name+(flen-elen)),badext[i])==0)
	    {
	      return(1);
	    }
	}
    }
  return(0);
}

sfsistat extscan(SMFICTX *ctx)
{
  struct mlfiPriv *priv = MLFIPRIV;
  union
  {
    struct dirent d;
    char s[(sizeof(struct dirent)+MAXPATHLEN+1)];
  } dirent;
  struct dirent *direntp;
  char buf[1024];

  memset(buf,0,sizeof(buf));

  priv->dir=opendir(priv->workdir);

  while((readdir_r(priv->dir,&(dirent.d),&direntp)==0) && (direntp !=NULL))
    {
      if ((direntp->d_name[0] == '.')
	  && ((direntp->d_name[1] == 0)
	      ||((direntp->d_name[1] == '.') && (direntp->d_name[2] == 0))
	      )
	  )
 	continue;

      if (isbadfilename(direntp->d_name))
	{
	  if (buf[0])
	    strncat(buf," ",sizeof(buf));
	  strncat(buf,direntp->d_name,sizeof(buf));
	}
    }

  closedir(priv->dir);
  priv->dir=NULL;

  if (buf[0])
    {
      priv->scripts=strdup(buf);
      if ((priv->scripts)==NULL)
	{
	  syslog(LOG_CRIT,"strdup(): %s",strerror(errno));
	  return(SMFIS_TEMPFAIL);
	}
      return(SMFIS_REJECT);
    }
  return(SMFIS_CONTINUE);
}

sfsistat mlfi_eom(SMFICTX *ctx)
{
  struct mlfiPriv *priv = MLFIPRIV;
  char message[2048];
  sfsistat vstat;
#ifdef ANTIVIRUSTIMING
  struct timeval stop;
  struct timeval total;
#endif /* ANTIVIRUSTIMING */

  if (debug)
    syslog(LOG_ERR,"smfi_eom(0x%x)",(int)ctx);

  if (priv==NULL)
    {

      syslog(LOG_ERR,"mlfi_eom() priv is NULL in mlfi_eom?");
      return(SMFIS_TEMPFAIL);
    }

  if ((priv->datafp)!=NULL)
    {

      fclose(priv->datafp);
      (priv->datafp)=NULL;
    }
  else
    {
      syslog(LOG_ERR,"mlfi_eom() priv->datafp is NULL?");
      return(SMFIS_TEMPFAIL);
    }


  if (((priv->headers)!=0) 
      && ((priv->bodyblocks)!=0) 
      && ((priv->bodysize)>100))
    {

      if ((priv->workdir)==NULL)
	  syslog(LOG_ERR,"mlfi_eom() priv->workdir is NULL");
      if ((priv->datafile)==NULL)
	  syslog(LOG_ERR,"mlfi_eom() priv->datafile is NULL");

#ifdef ANTIVIRUSTIMING
	  if (gettimeofday(&(priv->start),NULL)!=0)
	    syslog(LOG_ERR,"gettimeofday(): %s",strerror(errno));
#endif /* ANTIVIRUSTIMING */
      if (unpack(ctx,priv->workdir, priv->datafile))
	{
	  vstat=virusscan(ctx,AVSCANNER,FORMAT);

#ifdef ANTIVIRUSTIMING
	  if (gettimeofday(&stop,NULL)!=0)
	    syslog(LOG_ERR,"cleanup(0x%x): gettimeofday(): %s",
		   (int)ctx,strerror(errno));
	  
	  total.tv_sec=(stop.tv_sec-(priv->start).tv_sec);
	  if (stop.tv_usec > (priv->start.tv_usec))
	    {
	      total.tv_usec=stop.tv_usec-(priv->start).tv_usec;
	    }
	  else
	    {
	      total.tv_sec--;
	      total.tv_usec=(1000000 + stop.tv_usec) - (priv->start).tv_usec;
	    }
	  syslog(LOG_ERR,"timing to unpack/virusscan %s: %d.%06d",
		 priv->qid,(int)total.tv_sec,(int)total.tv_usec);
#endif /* ANTIVIRUSTIMING */

	  switch(vstat)
	    {
	    case SMFIS_REJECT:

	      priv->hasvirus=1;
	      snprintf(message,sizeof(message),
		       "virus alert: %s",priv->viruses);
 	      smfi_setreply(ctx, "550", "5.7.1", message);
	      cleanup(ctx);
	      if (debug)
		syslog(LOG_ERR,"smfi_eom(0x%x) => SMFIS_REJECT",(int)ctx);
	      return(SMFIS_REJECT);
	      break;
	    case SMFIS_CONTINUE:
	      break;
	    case SMFIS_TEMPFAIL:
	    default:

	      cleanup(ctx);
	      if (debug)
		syslog(LOG_ERR,"smfi_eom(0x%x) => SMFIS_TEMPFAIL",(int)ctx);
	      return(SMFIS_TEMPFAIL);
	      break;
	    }
	  
	  vstat=extscan(ctx);
	  switch(vstat)
	    {
	    case SMFIS_REJECT:
	      priv->hasvirus=1;
	      snprintf(message,sizeof(message),
		       "illegal extension: %s",priv->scripts);
 	      smfi_setreply(ctx, "550", "5.7.1", message);
	      cleanup(ctx);
	      if (debug)
		syslog(LOG_ERR,"smfi_eom(0x%x) => SMFIS_REJECT",(int)ctx);
	      return(SMFIS_REJECT);
	      break;
	    case SMFIS_CONTINUE:
	      break;
	    case SMFIS_TEMPFAIL:
	    default:
	      cleanup(ctx);
	      if (debug)
		syslog(LOG_ERR,"smfi_eom(0x%x) => SMFIS_TEMPFAIL",(int)ctx);
	      return(SMFIS_TEMPFAIL);
	      break;
	    }
	}
    }
  
  cleanup(ctx);
  if (debug)
    syslog(LOG_ERR,"smfi_eom(0x%x) => SMFIS_CONTINUE",(int)ctx);
  return(SMFIS_CONTINUE);
}

sfsistat mlfi_abort(SMFICTX *ctx)
{
  if (debug)
    syslog(LOG_ERR,"smfi_abort(0x%x)",(int)ctx);
  cleanup(ctx);
  if (debug)
    syslog(LOG_ERR,"smfi_abort(0x%x) => SMFIS_CONTINUE",(int)ctx);
  return(SMFIS_CONTINUE);
}

void cleanup(SMFICTX *ctx)
{
  struct mlfiPriv *priv = MLFIPRIV;
  char buf[1024];

  if (debug)
    syslog(LOG_ERR,"cleanup(0x%x)",(int)ctx);

  if (priv != NULL)
    {
      if ((priv->childpid)>0)
	{
	  syslog(LOG_ERR,"cleanup() killing %d\n",priv->childpid);
	  kill(priv->childpid,SIGABRT);
	  waitpid(priv->childpid,NULL,0);
	  priv->childpid=0;
	}

      if ((priv->childfp)!=NULL)
	{
	  fclose(priv->childfp);
	  priv->childfp=NULL;
	}

      if ((priv->datafp)!=NULL)
	{
	  fclose(priv->datafp);
	  priv->datafp=NULL;
	}
      
      if (priv->dir)
	{
	  closedir(priv->dir);
	  priv->dir=NULL;
	}

      if(priv->workdir)
	{
	  if ((priv->hasvirus) && (!purgevirus))
	    {
	      snprintf(buf,sizeof(buf),"%s/%s",QUARANTINE,priv->qid);
	      /*
	      ** If your abbatoir and your oubliette are on different
	      ** file systems then you need to do more work in
	      ** order to quarantine things.  Compile with -DCROSSFX
	      **
	      ** Theoretically, we don't leak anything from externalcommand
	      ** so it is safe to call this after we cleaned up from
	      ** it already.
	      */
#ifdef CROSSFS
	      {
		char *p="/bin/mv";
		char *av[]={"mv",NULL,NULL,NULL};
		
		av[1]=priv->workdir;
		av[2]=buf;
		
		externalcommand(ctx, NULL, EOF, p, av);
	      }
#else /* CROSSFS */
	      rename(priv->workdir,buf);
#endif /* CROSSFS */
	    }
	  else
	    {
	      rmrf(ctx);
	    }
	  priv->hasvirus=0;
	  free(priv->workdir);
	  priv->workdir=NULL;
	}

      (priv->headers)=0;
      (priv->bodyblocks)=0;
      (priv->bodysize)=0;
      
      if(priv->datafile)
	{
	  free(priv->datafile);
	  priv->datafile=NULL;
	}
      
      if (priv->viruses)
	{
	  free(priv->viruses);
	  priv->viruses=NULL;
	}

      if (priv->scripts)
	{
	  free(priv->scripts);
	  priv->scripts=NULL;
	}

      if(priv->qid)
	{
	  free(priv->qid);
	  priv->qid=NULL;
	}
    }

  free(priv);
  smfi_setpriv(ctx, NULL);

  if (debug)
    syslog(LOG_ERR,"cleanup(0x%x) => (void)",(int)ctx);
}

sfsistat mlfi_close(SMFICTX *ctx)
{
  struct mlfiPriv *priv = MLFIPRIV;

  if (debug)
    syslog(LOG_ERR,"smfi_close(0x%x)",(int)ctx);

  if (priv!=NULL)
    {
      cleanup(ctx);
    }
  if (debug)
    syslog(LOG_ERR,"smfi_close(0x%x) => SMFIS_CONTINUE",(int)ctx);
  return SMFIS_CONTINUE;
}

struct smfiDesc smfilter =
{
    "antivirus",    /* filter name */
    SMFI_VERSION,   /* version code -- do not change */
    0,              /* flags */
    NULL,           /* connection info filter */
    NULL,           /* SMTP HELO command filter */
    mlfi_envfrom,   /* envelope sender filter */
    mlfi_envrcpt,   /* envelope recipient filter */
    mlfi_header,    /* header filter */
    mlfi_eoh,       /* end of header */
    mlfi_body,      /* body block filter */
    mlfi_eom,       /* end of message */
    mlfi_abort,     /* message aborted */
    mlfi_close,     /* connection cleanup */
};

static void
usage(void)
{
    fprintf(stderr,"Usage: antivirus [-C <configfile>] [-v] [-V] [-n]\n");
    fprintf(stderr,"  -v: version\n");
    fprintf(stderr,"  -V: verbose version\n");
    fprintf(stderr,"  -n: do not use pid file\n");
}

/*
** inspired by Thomas Lussnig <thomas@bewegungsmelder.de>
*/
int droproot(void)
{
  uid_t myuid;
  uid_t myeuid;
  struct passwd *pwent;
  struct group *grent;

  myuid=getuid();
  myeuid=geteuid();

  if ((myuid!=0) && (myeuid!=0))
    return(0);

  grent=getgrnam(SAFEGROUP);
  if (grent==NULL)
    {
      fprintf(stderr,"droproot(): getgrnam(%s): %s\n",
	      SAFEGROUP,strerror(errno));
      return(-1);
    }
  if (setregid(grent->gr_gid,grent->gr_gid)!=0)
    {
      fprintf(stderr,"droproot(): setregid(%d,%d): %s\n",
	      (int)grent->gr_gid,(int)grent->gr_gid,strerror(errno));
      return(-1);
    }
  if (initgroups(SAFEUSER,grent->gr_gid))
    {
      fprintf(stderr,"droproot(): initgroups(%s,%d): %s\n",
              SAFEUSER,(int)(grent->gr_gid),strerror(errno));
      return(-1);
    }

  pwent=getpwnam(SAFEUSER);
  if (pwent==NULL)
    {
      fprintf(stderr,"droproot(): getpwnam(%s): %s\n",
	      SAFEUSER,strerror(errno));
      return(-1);
    }
  if (setreuid(pwent->pw_uid,pwent->pw_uid)!=0)
    {
      fprintf(stderr,"droproot(): setreuid(%d,%d): %s\n",
	      (int)pwent->pw_uid,(int)pwent->pw_uid,strerror(errno));
      return(-1);
    }
  
  myuid=getuid();
  myeuid=geteuid();

  if ((myuid==0) || (myeuid==0))
    {
      fprintf(stderr,"droproot(): still root after setreuid()?\n");
      return(-1);
    }

  return(0);
}

static int readconfig(void)
{
  FILE *fp;
  char buf[512];
  char fmt[64];
  char token[64];
  char value[256];
  char *ptr;
  int scanned;
  int set=0;
  int line=0;
  int i;

  fflush(stdout);

  if (configfile==NULL)
    {
      fp=fopen(DEFAULTCONFIGFILE,"r");
      if (fp==NULL)
	{
	  if (errno==ENOENT)
	    return(0);
	  snprintf(buf,sizeof(buf),"fopen(%s)",configfile);
	  perror(buf);
	  return(-1);
	}
    }
  else
    {
      fp=fopen(configfile,"r");
      if (fp==NULL)
	{
	  snprintf(buf,sizeof(buf),"fopen(%s)",configfile);
	  perror(buf);
	  return(-1);
	}
    }
      
  while (fgets(buf,sizeof(buf),fp)!=NULL)
    {
      line++;
      ptr=index(buf,'#');
      if (ptr)
	*ptr='\0';

      snprintf(fmt,sizeof(fmt),"%%%ds %%%dc",sizeof(token),sizeof(value));
      bzero(token,sizeof(token));
      bzero(value,sizeof(value));
      scanned=sscanf(buf,fmt,token,value);
      /*
      ** nuke the trailing whitespace here
      */
      i=0;
      while (value[i]) i++;
      i--;
      while ((i>0) && isspace((int)value[i]))
	{
	  value[i]='\0';
	  i--;
	}

      if (scanned==1)
	{
	  fprintf(stderr,"Error on line %d in configure file: %s\n",line,buf);
	  fclose(fp);
	  return(-1);
	}
      if (scanned==2)
	{
#define DOTOKEN(X,Y) if(strcasecmp(token,(X))==0){if((Y)!=NULL){fprintf(stderr,"readconfig(): %s already defined\n",(X));return(-1);}(Y)=strdup(value);if ((Y)==NULL){perror("malloc()");return(-1);}set++;continue;}
	  DOTOKEN("HOMEDIR",HOMEDIR);
	  DOTOKEN("BASEDIR",BASEDIR);
	  DOTOKEN("AVSCANDIR",AVSCANDIR);
	  DOTOKEN("QUARANTINE",QUARANTINE);
	  DOTOKEN("AVSCANNER",AVSCANNER);
	  DOTOKEN("AVSCANARGS",AVSCANARGS);
	  DOTOKEN("UNPACKER",UNPACKER);
	  DOTOKEN("SAFEUSER",SAFEUSER);
	  DOTOKEN("SAFEGROUP",SAFEGROUP);
	  DOTOKEN("PIDFILE",PIDFILE);
	  DOTOKEN("SOCKETNAME",SOCKETNAME);
	  DOTOKEN("TIMEOUT",TIMEOUT);
	  DOTOKEN("AVPRODUCT",AVPRODUCT);  
	  DOTOKEN("AVFAILACTION",AVFAILACTION);
	  DOTOKEN("VIRUSACTION",VIRUSACTION);
	  fprintf(stderr,"Line %d unrecognized: %s\n",line,buf);
	  fclose(fp);
	  return(-1);
	}
    }

  if (set==0)
    {
      fprintf(stderr,"Warning: %s contained no valid lines\n",configfile);
    }

  fclose(fp);
  return(0);
}


int buildavargs(void)
{
  int i=0;
  int n=0;
  int c=0;
  char buf[512];

  /*
  ** put strings into *avargs[] for later use
  */

  bzero(buf,sizeof(buf));

  if (AVSCANARGS==NULL)
    {
      fprintf(stderr,"buildavargs(): AVSCANARGS is NULL?\n");
      return(-1);
    }

  if (strlen(AVSCANARGS)==0)
    {
      /*
      ** nothing to do
      */
      return(0);
    }

  for (i=0,n=0,c=0;(i<sizeof(buf))&&(AVSCANARGS[i]);i++)
    {

      if (n==(sizeof(avargs)/sizeof(char *)))
	{
	  fprintf(stderr,"buildavargs(): too many args in AVSCANARGS!\n");
	  return(-1);
	}

      if (AVSCANARGS[i]==' ')
	{
	  if (c>0)
	    {
	      printf("Found end of word %d\n",n);
	      avargs[n]=(char *)strdup(buf);
	      if (avargs[n]==NULL)
		{
		  perror("buildavargs(): strdup()");
		  return(-1);
		}
	      bzero(buf,sizeof(buf));
	      n++;
	      c=0;
	    }
	}
      else
	{
	  buf[c]=AVSCANARGS[i];
	  c++;
	}
    }
  /*
  ** consume the last word
  */
  if (c>0)
    {
      avargs[n]=(char *)strdup(buf);
      if (avargs[n]==NULL)
	{
	  perror("buildavargs(): strdup()");
	  return(-1);
	}
    }
  return(0);
}

int init(void)
{
  if (readconfig()<0)
    return(-1);

  if (HOMEDIR==NULL) HOMEDIR=CONF_HOMEDIR;
  if (BASEDIR==NULL) BASEDIR=CONF_BASEDIR;
  if (AVSCANDIR==NULL) AVSCANDIR=CONF_AVSCANDIR;
  if (QUARANTINE==NULL) QUARANTINE=CONF_QUARANTINE;
  if (AVSCANNER==NULL) AVSCANNER=CONF_AVSCANNER;
  if (AVSCANARGS==NULL) AVSCANARGS=CONF_AVSCANARGS;
  if (UNPACKER==NULL) UNPACKER=CONF_UNPACKER;
  if (SAFEUSER==NULL) SAFEUSER=CONF_SAFEUSER;
  if (SAFEGROUP==NULL) SAFEGROUP=CONF_SAFEGROUP;
  if (PIDFILE==NULL) PIDFILE=CONF_PIDFILE;
  if (SOCKETNAME==NULL) SOCKETNAME=CONF_SOCKETNAME;
  if (TIMEOUT==NULL) TIMEOUT=CONF_TIMEOUT;
  if (AVPRODUCT==NULL) AVPRODUCT=CONF_AVPRODUCT;
  if (AVFAILACTION==NULL) AVFAILACTION=CONF_AVFAILACTION;
  if (VIRUSACTION==NULL) VIRUSACTION=CONF_VIRUSACTION;

  if (strcasecmp(AVPRODUCT,"mcafee")==0)
    {
      FORMAT=FORMAT_MCAFEE;
      AVSCANARGS=SCANARGS_MCAFEE;
    }
  else if (strcasecmp(AVPRODUCT,"sophos")==0)
    {
      FORMAT=FORMAT_SOPHOS;
    }
  else if ((strcasecmp(AVPRODUCT,"fsav")==0)
	   || (strcasecmp(AVPRODUCT,"f-prot")==0))
    {
      FORMAT=FORMAT_FSAV;
    }
  else
    {
      fprintf(stderr,"init(): unrecognized AVPRODUCT %s\n",AVPRODUCT);
      fprintf(stderr,"init(): valid values are: mcafee, fsva, sophos\n");
      return(-1);
    }

  if (strcasecmp(AVFAILACTION,"continue")==0)
    {
      avfailcode=SMFIS_CONTINUE;
    }
  else   if (strcasecmp(AVFAILACTION,"tempfail")==0)
    {
      avfailcode=SMFIS_TEMPFAIL;
    }
  else   if (strcasecmp(AVFAILACTION,"reject")==0)
    {
      avfailcode=SMFIS_REJECT;
    }
  else
    {
      fprintf(stderr,"init(): unrecognized AVFAILACTION %s\n",AVFAILACTION);
      fprintf(stderr,"init(): valid values are: continue, tempfail, reject\n");
      return(-1);
    }

  if ((strcasecmp(VIRUSACTION,"quarantine")==0)
      ||(strcasecmp(VIRUSACTION,"save")==0))
    {
      purgevirus=0;
    }
  else if ((strcasecmp(VIRUSACTION,"purge")==0)
	   || (strcasecmp(VIRUSACTION,"delete")==0))
    {
      purgevirus=1;
    }
  else 
    {
      fprintf(stderr,"init(): unrecognized VIRUSACTION %s\n",VIRUSACTION);
      fprintf(stderr,"init(): valid values are: quarantine or purge\n");
      return(-1);
    }

  /*
  ** build up the *avargs[] array
  */
  if (buildavargs()==(-1))
    {
      fprintf(stderr,"init(): buildavargs() failed\n");
      return(-1);
    }
  


  return(0);
}

/*
** this is sometimes used in testing
*/
/*
void dumpconfig(void)
{
  printf("HOMEDIR    %s\n",HOMEDIR);
  printf("BASEDIR    %s\n",BASEDIR);
  printf("AVSCANDIR  %s\n",AVSCANDIR);
  printf("QUARANTINE %s\n",QUARANTINE);
  printf("AVSCANNER  %s\n",AVSCANNER);
  printf("UNPACKER   %s\n",UNPACKER);
  printf("SAFEUSER   %s\n",SAFEUSER);
  printf("SAFEGROUP  %s\n",SAFEGROUP);
  printf("PIDFILE    %s\n",PIDFILE);
  printf("SOCKETNAME %s\n",SOCKETNAME);
  printf("TIMEOUT    %s\n",TIMEOUT);
}
*/

/*
** Original version by Thomas Lussnig <thomas@bewegungsmelder.de>
** Modified by me (wcolburn@nmt.edu)
*/
void versioninfo(int longversion)
{
  int i;

  printf("\n");
  printf("%s\n",copyright);
  printf("%s\n",versionid);
  printf("\n");
  printf("%s\n",webpage);
  if (longversion)
    {
      printf("\n");
      for (i=0;rcsid[i];i++)
	printf("%s\n",rcsid[i]);

      printf("%s\n",compilestamp);
      printf("%s\n",sendmailstamp);
    }
  printf("\n");
}

int main(int argc, char **argv, char **envp)
{
  int retval;
  char c;
  FILE *fp;
  const char *args = "p:C:hnvVdD";
  extern char *optarg;
  char *socketoverride=NULL;
  int usepidfile=1;
  int daemonize=1;
#ifdef USESETRLIMIT
  struct rlimit rlim;
#endif /* USESETRLIMIT */

  while ((c = getopt(argc, argv, args)) != (char)EOF)
    {
      switch (c)
        {
	case 'd':
	  debug++;
	  break;
	case 'D':
	  daemonize=!daemonize;
	  break;
	case 'p':
	  if (socketoverride!=NULL)
	    {
	      fprintf(stderr,"Socket already set\n");
	      exit(EX_SOFTWARE);
	    }
	  socketoverride=strdup(optarg);
	  if (socketoverride==NULL)
	    {
	      perror("malloc()");
	      exit(EX_SOFTWARE);
	    }
	  break;
	case 'C':
	  /*
	  ** configure file
	  */
	  if ((optarg==NULL) || (*optarg=='\0'))
	    {
	      fprintf(stderr,"Illegal configure file\n");
	      exit(EX_SOFTWARE);
	    }
	  if (configfile != NULL)
	    {
	      fprintf(stderr,"Configure file already set\n");
	      exit(EX_SOFTWARE);
	    }
	  configfile=strdup(optarg);
	  if (configfile==NULL)
	    {
	      perror("malloc()");
	      exit(EX_SOFTWARE);
	    }
	  break;
	case 'n':
	  usepidfile=0;
	  break;
	case 'v':
	  versioninfo(0);
	  exit(0);
	  break;
	case 'V':
	  versioninfo(1);
	  exit(0);
	  break;
        case 'h':
        default:
	  usage();
	  exit(0);
	  break;
        }
    }

  if (init()!=0)
    {
      fprintf(stderr,"init() failed\n");
      exit(EX_SOFTWARE);
    }


#ifdef USESETRLIMIT
  /*
  ** Some people need more file descriptors
  */
  if (getrlimit(RLIMIT_NOFILE, &rlim)<0)
    {
      fprintf(stderr,"main(): getrlimit(RLIMIT_NOFILE, ...) failed\n");
    }
  else
    {
      /*
      ** the value 1024 should probably be in the configuration file
      */
      if (rlim.rlim_cur<1024)
	{
	  rlim.rlim_cur=1024;
	  rlim.rlim_max=1024;
	  if (setrlimit(RLIMIT_NOFILE, &rlim)<0)
	    {
	      fprintf(stderr,"main(): setrlimit(RLIMIT_NOFILE, {%d,%d}) failed\n",
		      (int)rlim.rlim_cur,(int)rlim.rlim_max);
	    }
	}
    }
#endif /* USESETRLIMIT */


  /*
  ** allow -p to override the default and the config file
  */
  if (socketoverride != NULL)
    SOCKETNAME=socketoverride;

  if (droproot()!=0)
    {
      fprintf(stderr,"droproot() failed\n");
      exit(EX_SOFTWARE);
    }
  
  openlog("antivirus",LOG_PID,LOG_MAIL);

  if (mkdir(HOMEDIR,0700)!=0)
    {
      if (errno != EEXIST)
	{
	  syslog(LOG_CRIT,"mkdir(%s): %s",HOMEDIR,strerror(errno));
	  fprintf(stderr,"mkdir(%s): %s",HOMEDIR,strerror(errno));
	  exit(EX_SOFTWARE);
	}
    }

  if (chdir(HOMEDIR)!=0)
    {
      syslog(LOG_CRIT,"chdir(%s): %s",HOMEDIR,strerror(errno));
      fprintf(stderr,"chdir(%s): %s",HOMEDIR,strerror(errno));
      exit(EX_SOFTWARE);
    }

  if (mkdir(BASEDIR,0700)!=0)
    {
      if (errno != EEXIST)
	{
	  syslog(LOG_CRIT,"mkdir(%s): %s",BASEDIR,strerror(errno));
	  fprintf(stderr,"mkdir(%s): %s",BASEDIR,strerror(errno));
	  exit(EX_SOFTWARE);
	}
    }

  if (mkdir(QUARANTINE,0700)!=0)
    {
      if (errno != EEXIST)
	{
	  syslog(LOG_CRIT,"mkdir(%s): %s",QUARANTINE,strerror(errno));
	  fprintf(stderr,"mkdir(%s): %s",QUARANTINE,strerror(errno));
	  exit(EX_SOFTWARE);
	}
    }

  /*
  ** do the socket
  */
  if(smfi_setconn(SOCKETNAME) == MI_FAILURE)
    {
      fprintf(stderr,"smfi_setconn(%s) failed",SOCKETNAME);
      syslog(LOG_CRIT,"smfi_setconn(%s) failed",SOCKETNAME);
      exit(EX_SOFTWARE);
    }
  /*
  ** If we're using a local socket, make sure it doesn't
  ** already exist.
  **
  ** Why do they do this after the smfi_setconn() call?
  */
  if(strncmp(SOCKETNAME, "unix:", 5) == 0)
    {
      if (unlink(SOCKETNAME + 5)<0)
	{
	  if (errno!=ENOENT)
	    perror("unlink()");
	}
    }
  else if(strncmp(SOCKETNAME, "local:", 6) == 0)
    {
      if (unlink(SOCKETNAME + 6)<0)
	{
	  if (errno!=ENOENT)
	    perror("unlink()");
	}
    }

  /*
  ** do the timeout
  */
  if (TIMEOUT && (*TIMEOUT != '\0'))
    {
      if(smfi_settimeout(atoi(TIMEOUT)) == MI_FAILURE)
	{
	  fprintf(stderr,"smfi_settimeout(%s) failed",TIMEOUT);
	  syslog(LOG_CRIT,"smfi_settimeout(%s) failed",TIMEOUT);
	  exit(EX_SOFTWARE);
	}
    }

  /*
  ** background the process
  */
  if (daemonize)
    {
      switch(fork())
	{
	case -1:
	  perror("fork()");
	  exit(1);
	  break;
	case 0: /* child */
	  close(0);
	  close(1);
	  close(2);
	  open("/dev/null",O_RDONLY);
	  open("/dev/null",O_WRONLY);
	  open("/dev/null",O_WRONLY);
	  setsid();
	  break;
	default: /* parent */
	  exit(0);
	  break;
	}
    }
      
  if (usepidfile)
    {
      fp=fopen(PIDFILE,"w");
      if (fp==NULL)
	{
	  fprintf(stderr,"Could not open %s\n",PIDFILE);
	  perror("fopen()");
	}
      else
	{
	  fprintf(fp,"%d\n",(int)getpid());
	  fclose(fp);
	}
    }

  if (smfi_register(smfilter) == MI_FAILURE)
    {
      fprintf(stderr, "smfi_register failed\n");
      exit(EX_UNAVAILABLE);
    }
  syslog(LOG_CRIT,"ready to answer queries");
  retval = smfi_main();
  if (retval != 0)
    {
      fprintf(stderr,"smfi_main() => %d\n",retval);
    }
  if (usepidfile)
    {
      if (truncate(PIDFILE,0)!=0)
	perror("truncate()");
    }
  return(abs(retval));
}

/*
 * $Log: antivirus.c,v $
 * Revision 3.30  2003/06/30 16:15:42  wcolburn
 * (int)value[i] inside isspace()
 *
 * Revision 3.29  2003/06/27 16:36:45  wcolburn
 * took out trailing whitespace because I'm an idiot
 *
 * Revision 3.28  2003/06/26 19:23:22  wcolburn
 * changed the parser to apparantly pickup tokens and multiword values now!
 *
 * Revision 3.27  2003/06/26 19:17:41  wcolburn
 * also check length of string at beginning, just in case! (AVSCANARGS)
 *
 * Revision 3.26  2003/06/26 19:16:05  wcolburn
 * Fix case of AVSCANARGS == ""
 *
 * Revision 3.25  2003/06/26 18:54:48  wcolburn
 * rearranged how --unzip is set for AVSCANARGS
 *
 * Revision 3.24  2003/06/26 18:48:56  wcolburn
 * char *AVSCANARGS, char *avargs[], and the buildavargs() calls so that I
 * can pass aribtary arguments to the scanner.  Primitive, but it should be
 * sufficient.
 *
 * Revision 3.23  2003/06/25 19:12:56  wcolburn
 * took out .zi
 *
 * Revision 3.22  2003/06/25 17:47:00  wcolburn
 * added ".zi" as a temporary AV measure.
 *
 * Revision 3.21  2002/11/25 16:21:30  wcolburn
 * do not scan mesasges less than 100 bytes.
 *
 * Revision 3.20  2002/05/17 14:03:03  wcolburn
 * \r\n in eoh
 *
 * Revision 3.19  2002/05/17 14:02:47  wcolburn
 * \r\n in headers
 *
 * Revision 3.18  2002/04/23 14:44:02  wcolburn
 * some tweaks I forgot, and added --unique-names from Emmanuel Collignon
 *
 * Revision 3.17  2002/04/19 14:18:33  wcolburn
 * USESETULIMIT
 *
 * Revision 3.16  2002/04/15 22:23:04  wcolburn
 * fixed a typo in an error message
 *
 * Revision 3.15  2002/04/15 22:19:00  wcolburn
 * rlimit in main()
 *
 * Revision 3.14  2002/04/12 16:52:07  wcolburn
 * VIRUSACTION to purge or quarantine viruses
 *
 * Revision 3.13  2002/04/12 16:06:55  wcolburn
 * added default in case AVFAILACTION is not set
 *
 * Revision 3.12  2002/04/12 15:59:44  wcolburn
 * AVFAILACTION
 *
 * Revision 3.11  2002/04/12 15:49:07  wcolburn
 * syslog after execve in externalcommand()
 *
 * Revision 3.10  2002/04/12 15:43:10  wcolburn
 * error checking and return value and error message for AVPRODUCT
 *
 * Revision 3.9  2002/04/12 15:41:16  wcolburn
 * AVPRODUCT works correctly now.  :)
 *
 * Revision 3.8  2002/04/12 15:31:34  wcolburn
 * AVPRODUCT to select what kind of format string to use.  This
 * should/could select the name of the scanner, but I don't know any other
 * scanner names.
 *
 * Revision 3.7  2002/04/10 21:20:58  wcolburn
 * added another comment
 *
 * Revision 3.6  2002/04/10 20:44:39  wcolburn
 * updated a comment
 *
 * Revision 3.5  2002/04/10 20:42:50  wcolburn
 * "ready to answer queries" syslog message
 *
 * Revision 3.4  2002/04/10 20:41:56  wcolburn
 * daemonize option, suggested by Sebastien VAUTHEROT
 *
 * Revision 3.3  2002/04/10 20:40:21  wcolburn
 * same but for an extention
 *
 * Revision 3.2  2002/04/10 20:39:46  wcolburn
 * correct reply for finding a virus from Sebastien VAUTHEROT
 *
 * Revision 3.1  2002/04/10 20:37:20  wcolburn
 * FREEBSD wait include from Sebastien VAUTHEROT
 *
 * Revision 3.0  2002/04/10 18:20:37  wcolburn
 * The dawn of a new world
 *
 * Revision 2.136  2002/04/10 14:30:30  wcolburn
 * ok, now the dirent and direntp stuff is all "correct" as per the manual
 * page.  Both of them even, not just one of them!
 *
 * Revision 2.135  2002/04/10 14:27:22  wcolburn
 * not quite
 *
 * Revision 2.134  2002/04/10 13:55:23  wcolburn
 * oops?  I need to look back into this dirent thing in extscan.
 *
 * Revision 2.133  2002/04/09 16:58:34  wcolburn
 * save a few cycles checking for bad file names, from Steve Barber
 *
 * Revision 2.132  2002/04/09 14:56:45  wcolburn
 * Added in a call to /bin/mv for Steve Barber and his tmpfs.
 *
 * Revision 2.131  2002/04/09 14:47:10  wcolburn
 * make a union for dirent as in the diffs Steve Barber sent me
 * This is mostly stylisting, avoids casting a character array to
 * structure.
 *
 * Revision 2.130  2002/04/09 14:32:14  wcolburn
 * Ok, Steve Barber sent me a huge diff file.  The rename (last revision
 * I checked in) and the Sophos stuff are from him.  Actually, he used
 * REGEX for sophos, but it doesn't seem to need it.
 *
 * Revision 2.129  2002/04/09 14:23:23  wcolburn
 * more generic AVSANNER and AVSCANDIR
 *
 * Revision 2.128  2002/04/01 19:54:19  wcolburn
 * moved the location of the timing
 *
 * Revision 2.127  2002/04/01 17:53:00  wcolburn
 * moved final timing to have access to qid
 *
 * Revision 2.126  2002/04/01 17:49:18  wcolburn
 * oops, now it is right
 *
 * Revision 2.125  2002/04/01 17:48:07  wcolburn
 * ANTIVIRUSTIMING
 *
 * Revision 2.124  2002/03/26 21:02:08  wcolburn
 * openmax and a few cosmetic things
 *
 * Revision 2.123  2002/03/26 20:45:59  wcolburn
 * took out a comment
 *
 * Revision 2.122  2002/03/26 17:05:35  wcolburn
 * rearranged fd handling in virusscan()
 *
 * Revision 2.121  2002/03/26 16:55:14  wcolburn
 * added Toolan's initgroups() code to droproot()
 *
 * Revision 2.120  2002/03/26 16:52:35  wcolburn
 * took out mlfi_connect() in hopes to make the error-out go away
 *
 * Revision 2.119  2002/03/26 16:12:46  wcolburn
 * close files 3..NOFILE in externalcommand before exec
 *
 * Revision 2.118  2002/03/26 16:07:04  wcolburn
 * took out a misleading newline
 *
 * Revision 2.117  2002/03/25 22:42:54  wcolburn
 * *** empty log message ***
 *
 * Revision 2.116  2002/03/25 22:06:40  wcolburn
 * close fp in readconfig()
 *
 * Revision 2.115  2002/03/25 22:05:04  wcolburn
 * setsid()
 *
 * Revision 2.114  2002/03/18 16:08:04  wcolburn
 * cleaned up code a little for solaris
 *
 * Revision 2.113  2002/03/04 18:50:32  wcolburn
 * fork at beginning to dettach terminal
 *
 * Revision 2.112  2002/02/28 21:11:16  wcolburn
 * added and removed some signal code for children
 *
 * Revision 2.111  2002/02/28 20:40:37  wcolburn
 * fixed comment about f-prot/FSAV
 *
 * Revision 2.110  2002/02/28 20:38:59  wcolburn
 * took out inuse flag
 *
 * Revision 2.109  2002/02/28 20:36:39  wcolburn
 * added an inuse to the private data
 *
 * Revision 2.108  2002/02/22 20:41:57  wcolburn
 * took out a lot of debugging
 *
 * Revision 2.107  2002/02/22 20:30:37  wcolburn
 * more debugging, and changed the fd closures in externalcommand
 *
 * Revision 2.106  2002/02/22 19:51:50  wcolburn
 * corrected spelling in some debug statements
 *
 * Revision 2.105  2002/02/22 19:44:11  wcolburn
 * no chdir() when rmrf()ing
 *
 * Revision 2.104  2002/02/22 19:31:37  wcolburn
 * tried some things but failed
 *
 * Revision 2.103  2002/02/22 19:15:45  wcolburn
 * syslog kill() in cleanup()
 *
 * Revision 2.102  2002/02/22 19:09:49  wcolburn
 * rearranged cleanup() for more sensical ordering?
 *
 * Revision 2.101  2002/02/22 19:01:35  wcolburn
 * messed up the chdir thing because I forgot how it worked.  :)
 *
 * Revision 2.100  2002/02/22 18:55:42  wcolburn
 * moved the rmrf() in cleanup() to the top
 *
 * Revision 2.99  2002/02/22 18:44:53  wcolburn
 * took out priv from rmrf() because I don't use it anymore
 *
 * Revision 2.98  2002/02/22 18:44:31  wcolburn
 * chdir to BASEDIR in rmrf()
 *
 * Revision 2.97  2002/02/22 18:39:55  wcolburn
 * priv->childpid=0 in fork() error
 *
 * Revision 2.96  2002/02/22 18:32:32  wcolburn
 * moved the closedir ahead of the rmrf()
 *
 * Revision 2.95  2002/02/22 18:30:37  wcolburn
 * lots of debugging code
 *
 * Revision 2.94  2002/02/22 17:01:48  wcolburn
 * added debug and cl option -d
 *
 * Revision 2.93  2002/02/21 21:40:14  wcolburn
 * took out those cleanup(ctx) calls because every message is failing
 *
 * Revision 2.92  2002/02/21 21:34:40  wcolburn
 * added cleanup(ctx) in a couple of places inside them eom() function
 *
 * Revision 2.91  2002/02/15 16:22:50  wcolburn
 * added a comment
 *
 * Revision 2.90  2002/02/15 16:21:51  wcolburn
 * fix for solaris compatability
 *
 * Revision 2.89  2002/01/30 15:24:08  wcolburn
 * took out double extension scan
 *
 * Revision 2.88  2002/01/30 15:00:10  wcolburn
 * .txt and .tar.bz
 *
 * Revision 2.87  2002/01/30 14:42:49  wcolburn
 * .tar.gz is ok
 *
 * Revision 2.86  2002/01/29 20:14:03  wcolburn
 * spelled extension correctly
 *
 * Revision 2.85  2002/01/29 20:07:05  wcolburn
 * added a directory counter to unpack because my load is too high
 *
 * Revision 2.84  2002/01/29 19:35:20  wcolburn
 * removed unused variable
 *
 * Revision 2.83  2002/01/29 19:34:54  wcolburn
 * always unpack() and always virusscan() because it is just safer.
 *
 * Revision 2.82  2002/01/29 16:03:43  wcolburn
 * oops, "." and ".." are special
 *
 * Revision 2.81  2002/01/29 16:01:20  wcolburn
 * start of sweeping changes to isbadfilename
 *
 * Revision 2.80  2002/01/29 15:51:00  wcolburn
 * rearranged things a little, prevent strdup()ing a NULL in virusscan
 *
 * Revision 2.79  2002/01/29 15:32:17  wcolburn
 * dehacked the uudecode thing, and added a TODO item from Lussnig so that
 * I stop losing track of all the things he keeps suggesting.
 *
 * Revision 2.78  2002/01/28 22:35:52  wcolburn
 * --output-file=
 *
 * Revision 2.77  2002/01/28 22:10:16  wcolburn
 * DOH!  My extra.dat wasn't readable by my SAFEUSER, so it was
 * being ignored and the virus wasn't being caught.
 *
 * Revision 2.76  2002/01/28 21:59:28  wcolburn
 * oops
 *
 * Revision 2.75  2002/01/28 21:58:41  wcolburn
 * rewrite from 2.64
 *
 * Revision 2.66  2002/01/28 20:48:56  wcolburn
 * changed message for extentions
 *
 * Revision 2.65  2002/01/28 20:47:00  wcolburn
 * took out .exe again
 *
 * Revision 2.64  2002/01/25 19:56:37  wcolburn
 * more dynamic extention scanning
 *
 * Revision 2.63  2002/01/24 17:08:31  wcolburn
 * added cleanup(ctx) for postmaster mail
 *
 * Revision 2.62  2002/01/16 15:46:38  wcolburn
 * added pgp to the list of allowed extentions.  I am sure it used to be
 * there!
 *
 * Revision 2.61  2001/12/19 20:58:43  wcolburn
 * cleaned up, mostly happy with it
 *
 * Revision 2.60  2001/12/19 18:50:40  wcolburn
 * minor tweaks for good error reporting
 *
 * Revision 2.59  2001/12/19 18:43:21  wcolburn
 * fixed silly mistake in socket unlink()
 *
 * Revision 2.58  2001/12/19 18:41:27  wcolburn
 * unlink() wasn't working right?
 *
 * Revision 2.57  2001/12/19 18:37:56  wcolburn
 * possibly a fully working version with new config file handling
 *
 * Revision 2.56  2001/12/19 17:34:43  wcolburn
 * redid the config file reader to make it shorter and simpler
 * as per suggestion/example from Thomas Lussnig.
 *
 * Revision 2.55  2001/12/19 15:57:59  wcolburn
 * argv[0] doesn't work, you need smfi_getsymval() to find "postmaster"
 *
 * Revision 2.54  2001/12/19 15:55:05  wcolburn
 * testing out Thomas Lussnig's usage of argv[0] instead of my sm_getsym()
 * in mlfi_envrcpt()
 *
 * Revision 2.53  2001/12/19 15:43:32  wcolburn
 * added warning about blindly accepting postmaster mail from Thomas
 * Lussnig
 *
 * Revision 2.52  2001/12/18 16:41:55  wcolburn
 * fixed the dollar Log dollar thing to not be wonky
 *
 * Revision 2.51  2001/12/18 16:40:46  wcolburn
 * changed Copyright notice
 *
 * Revision 2.50  2001/12/18 15:58:33  wcolburn
 * added in a dollar Log dollar directive
 *
 */
