#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "brequeued.c,v 1.1.1.1 1995/06/16 21:10:41 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * brequeued.c
 *
 * Look for new mail arriving in queue and hand off to sendmail for delivery
 */

#include "bsClient.h"

#include <dirent.h>
#include <sys/mman.h>

#include <dict.h>
#include <dll.h>


#ifndef BS_QUEUEIN
#define BS_QUEUEIN "/queue/in"
#endif
#ifndef BS_QUEUEOUT
#define BS_QUEUEOUT "/queue/out"
#endif
#ifndef BS_QUEUEBAD
#define BS_QUEUEBAD "/queue/bad"
#endif

#define BS_SCANINTR "60"

/* Magic cookies */
#define HELO "HELO: "
#define HOST "HOST: "
#define VERSION "VERSION: "
#define FROM "FROM: "
#define NUMRCPT "NUMRCPT: "
#define RCPT "RCPT: "
#define BODY "**BODY**"

sos_string eol = { "\r\n", 2 };

#define PROTO_STR	"BSMTP:"
#define PROTO_ARG	"-p"

#define VARA_ARG	"-oMA"
#define VARV_ARG	"-oMV"
#define VARI_ARG	"-oMI"

/*
 * Verify that COOKIE is the first characters of tmp.  Update tmp
 * to point to the first character after cookie.  Update next to
 * point to the return at the end of the line started by tmp
 *
 * Set err if anything is wrong
 */
#define FIND_TOK(err,tmp,next,COOKIE) do { int cooklen = strlen(COOKIE); err = 0; if (cooklen + eol.len + 1 > tmp.len) { err = 1; break; } if (strncmp(COOKIE,tmp.str,cooklen)) { err = 1; break; } tmp.str += cooklen; tmp.len -= cooklen; next.str = sos_sosstrstr(&tmp,&eol); if (!next.str) { err = 1; break; } next.len = tmp.len - (next.str - tmp.str);} while (0)

/*
 * Copy the string from tmp to next into a string allocated into new
 */
#define DUP_TOK(new,tmp,next) do { int len = tmp.len - next.len; if ((new = (char *)malloc(len+1)) == NULL) exiterror("Could not malloc",1); memcpy(new,tmp.str,len); new[len] = '\0';} while (0)

/* Advance tmp to end of line pointed to next */
#define ADVANCE(tmp,next) do { tmp.str = next.str + eol.len; tmp.len = next.len - eol.len; } while (0)

/*
 * Async delivery is the right thing to do (tm) but the problem
 * is that we don't have sufficient rate limitation.  So if we
 * have a thousand pieces of mail that were just dumped on us
 * (because our network link was down, or whatever), we will
 * run out file kernel FILE handles.  We could increase the
 * limit, but eventually we will run out of one resource or
 * another, so we should just fix the problem.
 */
#define ASYNC_DELIVERY "-odb" /* Async delivery */
/*
 * Unfortunately, interactive is not really the best way either.
 * Now we lose the benifit of parallelism and could run into
 * almost arbitrary delays (e.g. we could be delayed by an
 * almost arbitrary period of time because of network problems).
 * We are depending on sendmail to eventually exit to free us.
 */
#define INTERACTIVE_DELIVERY "-odi" /* Interactive delivery */
/*
 * QUEUE delivery should never cause problems with resources but it
 * can cause significant problems with delivery time (since messages
 * undeliverable because of network problems will be tried before all
 * of the new messages).  We will try a compromise where we start
 * queuing at QLWM and then do a fast queue run (with low network
 * timeouts) at the end.  A normal queue run will be done via sendmail
 * within the retry interval (1h default) so it will eventually get
 * tried with normal sendmail V8 timeouts
 */
#define QUEUE_DELIVERY	"-odq"	/* Try queue runs */

#define QUEUE_LWM	16	/* Queue Low Water Mark (if more than this
				 * number of entries in queue directory,
				 * start queuing
				 */
/* #define SLEEP_LWM	256	/* Sleep Low Water Mark (if more than this
				 * number of entries in queue directory,
				 * start sleeping between each file
				 */


char *delivery_mode = ASYNC_DELIVERY;
char *program;
char *myhostname;
int debug = 0;
char *mailmsg;
sos_config_t config;
int need_queue_run = 0;

void exiterror(char *msg, int wanterrno);
void exitcleanup();
void handle(char *file);
void badfile(int want_perror, char *file, char *fmt, ...);
int process(sos_string *File, char *file);
int send_mail(char *helo, char *host, char *version, char *file,
	      char *from, dict_obj rcpt, caddr_t body, int len);

static struct sigaction ignpipe = SIGACTIONDEFINE( SIG_IGN, 0, 0 );



main(int argc,char *argv[], char *envp[])
{
  int tmpvar;
  int getopterr = 0;
  extern char *optarg;
  extern int optind;
  char *tmp;
  int scanintr;
  DIR *Qdir;
  int sawdot;

  sos_initProcTitle(argc, &argv, &envp, &program);
  sos_setProcTitle("INITIALIZING: QID=%s","undetermined");

  while ((tmpvar = getopt(argc,argv, "d")) != -1)
    switch(tmpvar)
      {
      case 'd':
	debug=1;
	break;
      case '?':
	getopterr++;
      }

  if (getopterr)
    {
      fprintf(stderr,"Usage: %s [-s]\n", program);
      exit(2);
    }
  
  open_soslog(program,debug);

  if (chdir(SOS_GWD("MAIL_QUEUEOUT",BS_QUEUEOUT)) < 0)
    {
      exiterror("chdir",1);
    }

  /* Create parent to wait and worry about child who does work */
  switch(fork())
    {
    case -1:
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 1, "fork failed (parent)");
      abort();
    case 0:			/* Child */
      break;
    default:			/* Parent */
      {
	int status = 0177;

	sos_setProcTitle("Failsafe wait");

	/* Wait for real exit (not STOP or ptrace) */
	while (status == 0177)
	  wait(&status);

	/* Log a message for swatch++ to handle */
	soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, 0, "REQUEUED TERMINATED");
	exit(1);
      }
    }

  /* XXX Bad idea? */
  sigaction (SIGPIPE, (struct sigaction *)&ignpipe,(struct sigaction *)NULL);

  /* This is non-fatal */
  if ((config = sos_config_read(SOS_ENV_GWD(BS_CLIENT_CONF_ENV,BS_CLIENT_CONF))) == NULL)
    {
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT,1, "Could not read config file %s", SOS_ENV_GWD(BS_CLIENT_CONF_ENV,BS_CLIENT_CONF));
    }

  scanintr = atoi(SOS_GWD("REQUEUE_SCANINTR",BS_SCANINTR));

  if ((Qdir = opendir(".")) == NULL)
    exiterror("opendir",1);

  while (Qdir)
    {
      struct dirent *file;
      int count = 0;

      sos_setProcTitle("SCANNING: QID=%s","undetermined");

      rewinddir(Qdir);

      sawdot = 0;
      while (file = readdir(Qdir))
	{
	  if (!strcmp(file->d_name,".") || !strcmp(file->d_name,".."))
	    {
	      sawdot = 1;
	      continue;
	    }

	  if (count++ > QUEUE_LWM)
	    {
	      need_queue_run = 1;
	      delivery_mode = QUEUE_DELIVERY;

#ifdef SLEEP_LWN
	      if (count > SLEEP_LWM)
		sleep(scanintr); /* Sleep to prevent overloads */
#endif /*SLEEP_LWN*/
	    }
	  sos_setProcTitle("PROCESS: QID=%s",file->d_name);
	  handle(file->d_name);
	}

      if (need_queue_run)
	{
	  sos_setProcTitle("RUNNING SENDMAIL QUEUE: QID=%s","undetermined");
	  run_sendmail_queue();
	  need_queue_run = 0;
	  delivery_mode = ASYNC_DELIVERY;
	}

      if (!sawdot)
	exiterror("empty readdir",1);

      sos_setProcTitle("SLEEP: QID=%s","undetermined");
      sleep(scanintr);
    }
}



/*
 * Exit with a syslogged message
 */
void exiterror(char *msg, int wanterrno)
{
  char *errmsg = strerror(errno);

  alarm (0);
  soslog(SOSLOG_CRIT, SOSLOG_TYPE_EVENT, wanterrno, msg);

#ifdef DEBUG
  fprintf(stderr,"Error: %s: %s: %s\r\n",program,msg,wanterrno?errmsg:"");
#endif

  exitcleanup();
}



/*
 * Log exit messages
 */
void exitcleanup()
{
  alarm (0);

#if 0
  soslog(SOSLOG_INFO, SOSLOG_TYPE_CONDITION, 0,
	 "Exit: User :%s:, %d bytes from :%s:, %d bytes from :%s:, %d seconds",
	 username,ioinfo.bytes_from_source,sourceinfo.printhost,
	 ioinfo.bytes_from_dest,destinfo.printhost,ioinfo.endtime-ioinfo.starttime);
#endif
  exit(0);
}  



/*
 * The file is not properly formatted.  Let's move it to the
 * error directory and log this event.
 */
void badfile(int want_perror, char *file, char *fmt, ...)
{
  char newfile[64];
  va_list args;

  va_start(args, fmt);
  vsoslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, want_perror, fmt, args);
  va_end(args);

  sprintf(newfile,"%s/%s",SOS_GWD("MAIL_QUEUEBAD",BS_QUEUEBAD),file);
  if (rename(file,newfile) < 0)
    soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, 1, "Failed to rename(%s,%s)",file,newfile);
  else
    soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, 0, "MAIL DROPPED: %s", file);

  return;
}



/*
 * Handle a file (e.g. if everything is OK, give it to sendmail)
 */
void handle(char *file)
{
  struct stat buf;
  sos_string File;
  int fd;
  int isbad;

  soslog(SOSLOG_DEBUG, SOSLOG_TYPE_CONDITION, 0, "Proccessing mail file %s", file);

  if (stat(file,&buf) < 0)
    {
      badfile(1, file, "Could not stat: %s", file);
      return;
    }

  if (buf.st_size <= 0)
    {
      badfile(0, file, "Invalid size: %s", file);
      return;
    }

  if (!S_ISREG(buf.st_mode))
    {
      badfile(0, file, "Not a file: %s", file);
      return;
    }

  if ((fd = open(file,O_RDONLY)) < 0)
    {
      badfile(1, file, "Could not open: %s", file);
      return;
    }

  File.len = buf.st_size;
  if ((int)(File.str = mmap(0,File.len,PROT_READ,MAP_SHARED,fd,0)) == -1)
    {
      badfile(1, file, "Could not mmap: %s", file);
      return;
    }

  isbad = process(&File, file);

  if (munmap(File.str, File.len) < 0)
    {
      badfile(1, file, "Could not munmap: %s", file);
      return;
    }

  if (close(fd) < 0)
    {
      badfile(1, file, "Could not close: %s", file);
      return;
    }

  if (isbad)
    badfile(0, file, "Ill-formatted: %s", file);
#ifndef DEBUG2
  else
    unlink(file);
#endif /*!DEBUG2*/

  return;
}



/*
 * Process a mmapped mail file
 */
int process(sos_string *File, char *file)
{
  sos_string curaddr;
  sos_string nextaddr;
  char *helo = NULL;
  char *host = NULL;
  char *version = NULL;
  char *from = NULL;
  char *tmprcpt = NULL;
  dict_obj llcur = NULL;
  dict_obj rcpt = NULL;
  int numr, numrcpt;
  int err = 0;

  curaddr.str = File->str;
  curaddr.len = File->len;

  FIND_TOK(err,curaddr,nextaddr,HELO);
  if (err) goto cleanup;
  DUP_TOK(helo,curaddr,nextaddr);
  ADVANCE(curaddr,nextaddr);

  FIND_TOK(err,curaddr,nextaddr,HOST);
  if (err) goto cleanup;
  DUP_TOK(host,curaddr,nextaddr);
  ADVANCE(curaddr,nextaddr);

  FIND_TOK(err,curaddr,nextaddr,VERSION);
  if (err) goto cleanup;
  DUP_TOK(version,curaddr,nextaddr);
  ADVANCE(curaddr,nextaddr);

  FIND_TOK(err,curaddr,nextaddr,FROM);
  if (err) goto cleanup;
  DUP_TOK(from,curaddr,nextaddr);
  ADVANCE(curaddr,nextaddr);

  FIND_TOK(err,curaddr,nextaddr,NUMRCPT);
  if (err) goto cleanup;
  DUP_TOK(tmprcpt,curaddr,nextaddr);
  ADVANCE(curaddr,nextaddr);

  numrcpt = numr = atoi(tmprcpt);

  if ((rcpt = dll_create(NULL,NULL,DICT_UNORDERED,NULL)) == NULL)
    {
      err = 1;
      goto cleanup;
    }

  for (;numr > 0;numr--)
    {
      FIND_TOK(err,curaddr,nextaddr,RCPT);
      if (err) goto cleanup;
      DUP_TOK(tmprcpt,curaddr,nextaddr);
      ADVANCE(curaddr,nextaddr);

      if (dll_insert(rcpt,(dict_h)tmprcpt) != DICT_OK)
	{
	  err = 1;
	  goto cleanup;
	}
    }

  FIND_TOK(err,curaddr,nextaddr,BODY);
  if (err) goto cleanup;
  ADVANCE(curaddr,nextaddr);

#ifdef CHECKHEADER
  if (err = checkheader(from))
    goto cleanup;

  for(llcur = dll_maximum(rcpt);llcur != NULL;llcur = dll_predecessor(rcpt,llcur))
    {
      if (err = checkheader(llcur))
	goto cleanup;
    }
#endif /*CHECKHEADER*/

#ifdef DEBUG2
  fprintf(stderr,"Helo: %s\n",helo);
  fprintf(stderr,"Host: %s\n",host);
  fprintf(stderr,"Version: %s\n",version);
  fprintf(stderr,"From: %s\n",from);
  fprintf(stderr,"Numrcpt: %d\n",numrcpt);

  for(llcur = dll_maximum(rcpt);llcur != NULL;llcur = dll_predecessor(rcpt,llcur))
    {
      if (!fprintf(stderr,"Rcpt: %s\r\n",llcur))
	{
	  err = 1;
	  break;
	}
    }
  if (err) goto cleanup;

  fprintf(stderr,"Body:\n");
  fwrite(curaddr.str,curaddr.len,1,stderr);
#endif /*DEBUG*/

  if (send_mail(helo, host, version, file, from, rcpt, curaddr.str, curaddr.len) < 0)
    {
      err = 1;
      goto cleanup;
    }

 cleanup:
  if (helo)
    free(helo);
  if (host)
    free(host);
  if (version)
    free(version);
  if (tmprcpt)
    free(tmprcpt);
  if (from)
    free(from);

  if (rcpt)
    {
      for(llcur = dll_maximum(rcpt);llcur != NULL;llcur = dll_maximum(rcpt))
	{
	  free(llcur);
	  dll_delete(rcpt,llcur);
	}
      dll_destroy(rcpt);
    }

  return(err);
}



/*
 * Fork off sendmail
 */
int send_mail(char *helo, char *host, char *version, char *file,
	      char *from, dict_obj rcpt, caddr_t body, int len)
{
  int err = 0;
  int x;
  dict_obj llcur = NULL;
  int status;
  int fd[2];

  char *path = "/usr/lib/sendmail";
  char *scmd[_POSIX_ARG_MAX] =
    {
      "sendmail",		/* Name */
      delivery_mode,		/* Delivery mode (-odb, -odi, -odq) */
      "-oem",			/* Mail errors */
      "-os",			/* Always instantiate queue */
      "-oi",			/* Dots don't terminate */
#define CMD_PROTO_ARG	5
      NULL,			/* Protocol/hostname for Received line */
#define CMD_VARA_ARG	6
      NULL,			/* A variable for remote host in received line */
#define CMD_VARV_ARG	7
      NULL,			/* V variable for SMTP version */
#define CMD_VARI_ARG	8
      NULL,			/* I variable for BS queue name */
      "-f",			/* Set envelope from */
#define CMD_FROM_ARG	10
      NULL,			/* Envelope from */
      "--",			/* Prevent RCPT addrs from being options */
#define CMD_RCPT_ARG	12
      NULL,			/* RCPT lines...*/
    };

  if (!(scmd[CMD_PROTO_ARG] = (char *)malloc(strlen(helo)+strlen(PROTO_STR)+strlen(PROTO_ARG)+1)))
    {
      err = -1;
      goto cleanup;
    }
  scmd[CMD_PROTO_ARG][0] = '\0';
  strcat(scmd[CMD_PROTO_ARG],PROTO_ARG);
  strcat(scmd[CMD_PROTO_ARG],PROTO_STR);
  strcat(scmd[CMD_PROTO_ARG],helo);

  if (!(scmd[CMD_VARA_ARG] = (char *)malloc(strlen(host)+strlen(VARA_ARG)+1)))
    {
      err = -1;
      goto cleanup;
    }
  scmd[CMD_VARA_ARG][0] = '\0';
  strcat(scmd[CMD_VARA_ARG],VARA_ARG);
  strcat(scmd[CMD_VARA_ARG],host);

  if (!(scmd[CMD_VARV_ARG] = (char *)malloc(strlen(version)+strlen(VARV_ARG)+1)))
    {
      err = -1;
      goto cleanup;
    }
  scmd[CMD_VARV_ARG][0] = '\0';
  strcat(scmd[CMD_VARV_ARG],VARV_ARG);
  strcat(scmd[CMD_VARV_ARG],version);

  if (!(scmd[CMD_VARI_ARG] = (char *)malloc(strlen(file)+strlen(VARI_ARG)+1)))
    {
      err = -1;
      goto cleanup;
    }
  scmd[CMD_VARI_ARG][0] = '\0';
  strcat(scmd[CMD_VARI_ARG],VARI_ARG);
  strcat(scmd[CMD_VARI_ARG],file);

  scmd[CMD_FROM_ARG] = from;

  for(x=CMD_RCPT_ARG,llcur = dll_maximum(rcpt);llcur != NULL && x<_POSIX_ARG_MAX-1;
      llcur = dll_predecessor(rcpt,llcur),x++)
    {
      scmd[x] = (char *)llcur;
    }
  scmd[x] = NULL;


#ifdef DEBUG2
  printf("Exec: ");
  for(i=0;i<x;i++)
    {
      printf(" %s",scmd[i]);
    }
  printf("\n");
#endif /*DEBUG*/
#ifndef NOEXEC

  if (pipe(fd) < 0)
    {
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 1, "pipe failed");
      err = -1;
      goto cleanup;
    }

  switch(fork())
    {
    case -1:
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 1, "fork failed");
      err = -1;
      goto cleanup;
    case 0:			/* Child */
      dup2(fd[0],0);
      close(fd[1]);

      execv(path,scmd);
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 1, "exec failed of %s",path);
      exit(255);
    default:			/* Parent */

      {				/* Send the message out */
	int bytes_out = 0;
	int bytes = -1;

	sos_setProcTitle("SENDMAIL: QID=%s",file);
	close(fd[0]);
	while (bytes_out < len)
	  {
	    bytes = write(fd[1],(body+bytes_out),(len-bytes_out));
	    if (bytes <= 0)
	      break;
	    bytes_out += bytes;
	  }
	if (bytes < 0)
	  err = -1;
	close(fd[1]);
      }


      status = 0177;
      while (status == 0177)
	{
	  wait(&status);
#ifdef DEBUG2
	  fprintf(stderr,"  waited for %d\n",status);
#endif /*DEBUG*/
	}
      if (status != 0)
	{
	  soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 0, "sendmail exited with status %d",status);
	  err = -1;
	}
    }
#endif /*!NOEXEC*/

 cleanup:
  if (scmd[CMD_PROTO_ARG]) free(scmd[CMD_PROTO_ARG]);
  if (scmd[CMD_VARA_ARG]) free(scmd[CMD_VARA_ARG]);
  if (scmd[CMD_VARV_ARG]) free(scmd[CMD_VARV_ARG]);
  if (scmd[CMD_VARI_ARG]) free(scmd[CMD_VARI_ARG]);

  return(err);
}



/*
 * Sendmail queue runs
 */
int run_sendmail_queue()
{
  int err = 0;
  int status;

  char *path = "/usr/lib/sendmail";
  char *scmd[] =
    {
      "sendmail",		/* Name */
      "-oem",			/* Mail errors */
      "-q",			/* Queue run */
      "-orinitial=1m,helo=1m,mail=5m,rcpt=5m,datainit=2m,datablock=3m,datafinal=10m,command=5m,ident=5s", /* Fast queue runs wanted */
      NULL,			/* Terminating null...*/
    };

#ifndef NOEXEC
  switch(fork())
    {
    case -1:
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 1, "fork failed");
      err = -1;
      return(err);
    case 0:			/* Child */
      execv(path,scmd);
      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 1, "exec failed of %s",path);
      exit(255);
    default:			/* Parent */
      status = 0177;
      while (status == 0177)
	{
	  wait(&status);
#ifdef DEBUG2
	  fprintf(stderr,"  waited for %d\n",status);
#endif /*DEBUG*/
	}
      if (status != 0)
	{
	  soslog(SOSLOG_NOTICE, SOSLOG_TYPE_CONDITION, 0, "sendmail exited with status %d",status);
	  err = -1;
	}
    }
#endif /*!NOEXEC*/

  return(err);
}

