/*
 * Copyright (c) 1996-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * This is independant code branch modified to fit fwtk configuration and
 * logging style by ArkanoiD, 2001
 *
 * Original copyright info follows.
 */
    
/*
 * smtpfwdd, Obtuse SMTP forward daemon, master process watches spool
 * directory for files spooled by smtpd. On seeing one, spawns a child
 * to pick it up and invokes sendmail (or sendmail-like agent) to
 * deliver it.
 *
 * $Id: smtpfwdd.c,v 1.8 2007/09/21 18:33:17 xsaper Exp $
 * 
 * Copyright (c) 1996, 1997 Obtuse Systems Corporation. All rights
 * reserved.
 *   
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Obtuse Systems 
 *      Corporation and its contributors.
 * 4. Neither the name of the Obtuse Systems Corporation nor the names
 *    of its contributors may be used to endorse or promote products 
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS CORPORATION AND
 * CONTRIBUTORS ``AS IS''AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */
char *obtuse_copyright =
"Copyright 1996 - Obtuse Systems Corporation - All rights reserved.";

#include <stdio.h>
#include <signal.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <syslog.h>
#include <unistd.h>
#include <libgen.h>
#include <time.h>
#ifdef IRIX_BROKEN_INCLUDES
/* IRIX 5.3 defines EX_OK (see sysexits.h) as something very strange in unistd.h :-) */
#ifdef EX_OK
#undef EX_OK
#endif
#endif
#include <sysexits.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <utime.h>
#include "smtp.h"
#include "firewall.h"
#include "firewall2.h"
#include "fwfunc.h"

#ifndef MAIL_AGENT
#define MAIL_AGENT PREFIX"/bin/ssmtp"
#endif

#ifndef SENDMAIL_OITRUE
#define SENDMAIL_OITRUE 0
#endif

#ifndef MAXCHILDREN
#define MAXCHILDREN 10
#endif

#ifndef MAXARGS
#define MAXARGS 100
#endif

#if MAXARGS < 6
%%%MAXARGS must be at least 6 %%%
#endif

#ifndef POLL_TIME
#define POLL_TIME 10
#endif

#ifndef SENDMAIL_RETRY
#define SENDMAIL_RETRY 1
#endif 

/*
 * How long to wait before trying to re-process a file
 */

#ifndef RETRY_DELAY_TIME
#define RETRY_DELAY_TIME	600
#endif

/* 
 * How long can a spool file be incomplete before I start 
 * yelling about it?
 */ 
#ifndef COMPLETION_WAIT
#define COMPLETION_WAIT         86400
#endif

char *spooldir = NULL;
char *mailagent;
char *baddir = NULL;
char *badadmin = NULL;
char *pidfile = NULL;
int children = 0;
int maxchildren = MAXCHILDREN;
int poll_time = POLL_TIME;
int gc_int = COMPLETION_WAIT;
int VerboseSyslog = 1;

extern  int     proxy_uid, proxy_gid;

#if defined(linux) || defined (SOLARIS)
extern 	void	initsetproctitle(int,char**);
#endif

static char* moduleId ATTR_UNUSED = "$Id: smtpfwdd.c,v 1.8 2007/09/21 18:33:17 xsaper Exp $";

void    proxy_update_status();
void    proxy_update_operation(char*);
void    proxy_parse_options(Cfg*,fwparm*);
int     searchlist();
int     proxy_conf_int(Cfg*,const char*,int,int,int);
int     proxy_conf_groupid(Cfg*);
int     proxy_conf_userid(Cfg*);
char*   proxy_conf_string(Cfg*,const char*);
Cfg*    proxy_conf_hosts(Cfg*,char*,char*);
void	proxy_setugid();
void 	sendadmin(char*,char*);

/*
 * zap spoolfile and leave 
 */
void
fail_abort(FILE * f, char *fname, int save)
{
  char buf[MAXPATHLEN];
  if (locktest_fd(fileno(f)) == 0 &&
      lockun_fd(fileno(f)) != 0) {
    syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s using lockf after removal (%s)!", fname, strerror(errno));
    exit(EX_CONFIG);
  }
  fclose(f);
  if (save) {
  	if (badadmin)
		sendadmin(fname,badadmin);
  	if (baddir) {
		snprintf(buf,MAXPATHLEN,"%s/%s",baddir,fname);
		if (rename(fname,buf)) {
			syslog(LLEV,"fwtksyserr: cannot move to baddir %.128s, %s",buf, strerror(errno));
      			exit(EX_CONFIG);
		}
	}
  }
  if (unlink(fname) != 0) {
    /* we could be here after a sibling removed the file. If this is 
     * the case, no problem. Otherwise something's wrong with our
     * setup. 
     */
    if (errno != ENOENT) {
      syslog(LLEV, "fwtksyserr: couldn't remove spool file %s! (%s)", fname, strerror(errno));
      exit(EX_CONFIG);
    }
  }
  exit(EX_DATAERR);
}

/*
 * leave and unlock spoolfile for retry 
 */
void
fail_retry(FILE * f, char *fname)
{
  struct utimbuf utb;

  /*
   * first make sure the others x bit is on so we know this file has
   * been tried. 
   */

  if (chmod(fname, 0755) != 0) {
    syslog(LLEV, "fwtksyserr: couldn't change mode of %s for retry (%s)! abandoning message!", fname, strerror(errno));
    fail_abort(f, fname, 1);
  }
  /*
   * touch the file, so we base the time of the next retry on the
   * current time. 
   */
  utb.actime = utb.modtime = time(NULL);
  if (utime(fname, &utb) != 0) {
    syslog(LLEV, "fwtksyserr: couldn't set modification time of %s for retry (%s)! abandoning message!", fname, strerror(errno));
    fail_abort(f, fname, 1);
  }
  if (locktest_fd(fileno(f)) == 0)
    if (lockun_fd(fileno(f)) != 0) {
      syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s with lockf for retry (%s)!", fname, strerror(errno));
      exit(EX_CONFIG);
    }
  fclose(f);
  exit(EX_TEMPFAIL);
}

void
sendadmin(path, admin)
char *path, *admin;
{
        char    *faka[MAXARGS];
        int     i, pid;
        int     w;

        i = 0;
        faka[i++] = mailagent;
        faka[i++] = "-f";
        faka[i++] = "root";
        faka[i++] = admin;
        faka[i] = (char *)0;

        if ((pid = fork()) == 0) {
                int     fd;

                if ((fd = open(path,O_RDONLY)) < 0) {
                        syslog(LLEV, "fwtksyserr: cannot open %.512s: %s",path, strerror(errno));
                        exit(1);
                }
                close(0);
                if(dup(fd) < 0) {
                        syslog(LLEV, "fwtksyserr: cannot dup %.512s: %s",path, strerror(errno));
                        exit(1);
                }
                execv(faka[0], faka);
                syslog(LLEV,"fwtksyserr: cannot exec %.512s: %s",faka[0], strerror(errno));
                exit(1);
        }

        if (pid == -1) {
                syslog(LLEV, "fwtksyserr: cannot send bad msg to admin! fork failed: %s", strerror(errno));
                return;
        }
        wait(&w);
#ifdef  WTERMSIG
        if (WIFSIGNALED(w))
                w = WTERMSIG(w);
        else
#endif
#ifdef WEXITSTATUS
        if (WIFEXITED(w))
                w = WEXITSTATUS(w);
        else
                w = 0;
#endif

        if (w != 0)
                syslog(LLEV,"cannot send bad msg to admin! exit status %d",w);
}

/*
 * is spool file fname complete? it's complete if it's mode 750.
 * This doesn't mean we can lock it, but means it's ok to try. 
 */
int
smtp_spoolfile_complete(const char *fname)
{
  struct stat buf;

  if (stat(fname, &buf) != 0) {
    /*
     * If the file doesn't exist then some other child just finished
     * processing it - not a problem!
     * Anything else is a serious problem (OS is probably insane).
     */
    if (errno != ENOENT) {
      syslog(LLEV, "fwtksyserr: can't stat %s (%s) - bye!", fname, strerror(errno));
      exit(EX_CONFIG);
    }
    return (0);
  }
  if (!(S_ISREG(buf.st_mode))) {
    syslog(LLEV, "fwtksyserr: spool file %s isn't a regular file!", fname);
    exit(EX_CONFIG);
  }
  if ((buf.st_mode & 0110) != 0110) {
#ifdef VERBOSE     
     syslog(LOG_DEBUG, "%s not complete now.", fname);
#endif     
     if (gc_int && (buf.st_mtime+gc_int <= time(NULL))) {
	/* 
	 * This file has been hanging around incomplete for more than
	 * gc_int, This could be due simply to a (really) slow connection/big
	 * message tying up an smtpd process for a long time, or it could
	 * be due to something like the machine being rebooted killing off
	 * an smtpd process that had started to receive a message before
	 * it was able to finish and mark this file as complete.
	 * 
	 * Therefore we better let the appropriate authority know about this
	 * file.
	 */
	struct utimbuf utb;
	utb.actime = utb.modtime = time(NULL);
	if (utime(fname, &utb) != 0) {
	   syslog(LLEV, "fwtksyserr: utime() failed on spool file %s (%s)", fname, strerror(errno));
	}
	syslog(LLEV, "fwtksyserr: spool file %s has been incomplete since %s. Please investigate.", fname, ctime(&(buf.st_ctime)));
     }
    return (0);
  }
  if ((buf.st_mode & 0111) == 0111) {
    /*
     * if the others execute bit is ticked, then this file had been
     * previously tried, and we got a temp. sendmail failure. We
     * don't want to retry too often, so make sure the mtime is more 
     * than RETRY_DELAY_TIME seconds ago. 
     */
    if ((time(NULL) - buf.st_mtime) < RETRY_DELAY_TIME) {
#ifdef VERBOSE       
      syslog(LOG_DEBUG, "Skipping file %s, delivery last attempted at %s.", fname, ctime(&(buf.st_mtime)));
#endif
      return (0);
    }
    else { 
      syslog(LLEV, "Retrying delivery of file %s, last attempted at %s.", fname, ctime(&(buf.st_mtime)));
    }
  }
  return (1);
}

/*
 * Generate obituaries for our dead children and keep track of how many
 * of our kids are still alive.
 */

void
reap_children(void)
{
  while (1) {
    pid_t pid;
    int status;

    pid = waitpid(-1, &status, WNOHANG);

    if (pid == 0) {
      return;
    } else if (pid == -1) {
      if (errno != ECHILD) {
	syslog(LLEV, "fwtksyserr: CRITICAL - waitpid failed (%s) - aborting", strerror(errno));
	abort();
      }
      return;
    }
    children--;
    if ((!WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) {
      switch (WEXITSTATUS(status)) {
      case EX_TEMPFAIL:
	
	/*
	 * normal retry case 
	 */
	syslog(LLEV, "Child process (%d) exited indicating retry", pid);
	break;
      case EX_CONFIG:
	/*
	 * we only exit with this code if we know we've got
	 * configuration problems. If a child exits like this,
	 * we also should exit 
	 */
	syslog(LLEV, "fwtkcfgerr: child process (%d) failed due to configuration problems. Exiting", pid);
	exit(EX_CONFIG);
	break;
      default:
	/*
	 * permanent failure or something unusual 
	 */
	syslog(LLEV, "Child process (%d) failed - no retry", pid);
	break;
      }
    }
  }
}

/*
 * forward a mail message recieved by smtpd contained in file fname.
 * file is expected to be as follows:
 * -------------------
 * FROM addr
 * RCPT addr          (or SENT addr)
 * ...
 * BODY
 * message body
 * ...
 * -------------------
 *
 * The FROM line indicates who sent this message.
 * The RCPT lines each indicate an intended recipient.
 * Any SENT lines indicate recipients that this message has already been
 * delivered to (these only happen if a message is partially processed
 * before sendmail signals a temporary failure).
 *
 * Everything before "BODY" will have been sanitized by smtpd. It's up
 * to us to do anything we want to the message body, as smtpd takes that
 * in verbatim.
 *
 * A message is processed MAXARGS recipients at a time.  As each batch
 * is processed, the RCPT verbs for the batch are turned into SENT verbs.
 * This prevents the message from being sent to the same people more than
 * once if a subsequent batch fails with a retry-able error.  It also
 * limits the number of people who will get the message twice if the system
 * crashes at a bad moment.
 *
 * We call MAIL_AGENT -f fromaddr toaddr toaddr toaddr ...
 * to forward mail. I.E. MAIL_AGENT should be sendmail or something
 * else that delivers mail and will take those arguments a-la sendmail.
 * For filtering message bodies for unwanted things, one can call a filter
 * program which checks the message body as or before passing it through
 * to a delivery program. MAIL_AGENT needs to exit with sendmail-like
 * exit codes.
 *
 * We exit with
 *  EX_TEMPFAIL - Retry later for whatever reason
 *  EX_CONFIG - Something's horribly wrong, and our parent should exit
 *  EX_OK - We have removed the spoolfile after success
 *  anything else - We have removed the spoolfile after failure (no retry)
 */

void
forward(char *fname)
{
  FILE *f = NULL;
  char line[SMTP_MAX_CMD_LINE];
  char *c, *from;
  int sentout;
  off_t body;
  struct smtp_victim *victim, *victims;
  int status, pid, pidw, i, rstart;

  proxy_update_operation("FORWARDING");
  victim = (struct smtp_victim *) malloc(sizeof(struct smtp_victim));

  victim->name = NULL;
  victim->next = NULL;
  victims = victim;

  if (victims == NULL) {
    syslog(LLEV, "fwtksyserr: malloc failed, aborting delivery of %s", fname);
    fail_abort(f, fname, 1);
  }
  /*
   * Step 1 - open the file for updating. exit silently if it fails, 
   * since that is most likely due to one of our siblings having dealt
   * with it and removed it.
   */

  f = fopen(fname, "r+");
  if (f == NULL) {
    syslog(LLEV, "fwtksyserr: couldn't open spool file %s! (%s)", fname, strerror(errno));
    exit(EX_TEMPFAIL);
  }
  /*
   * Step 2 - try to get a non-blocking exclusive lock on the file.
   * Just exit (relatively) silently if it fails.  This happens for a number
   * of reasons:
   *
   *    - one of our siblings has already got the file
   *    - smtpd isn't done with it yet
   */

  if (locktest_fd(fileno(f)) != 0) {
    exit(EX_TEMPFAIL);
  }

  /*
   * Step 3 - do a basic sanity test on the file
   *
   * We do the test using the file's name instead of the just opened
   * file descriptor to avoid the following race condition:
   *
   *    - we and one of our siblings both open the file successfully above
   *    - we're suspended while our sibling completely processes the file
   *      (including unlinking the file).
   *    - we finally get around to locking the file.  Since our sibling is
   *      done, the lock attempt works.
   *    - we do the sanity test using the file descriptor (which is associated
   *      with a file that no longer has a name).
   *    - we process the file again.
   *
   * By doing the following sanity check using the file's name instead
   * of the file descriptor, we avoid the race because, if the above sequence
   * of events occurs, the file won't exist when we do the sanity test
   * (which will cause the sanity test to fail).
   *
   */

  if (!smtp_spoolfile_complete(fname)) {
    /*
     * smtpd hasn't finished with this one yet or the file is gone.
     * Bail out.  If the file still exists, it will get tried again later.
     */

    /* If we locked the file (above) and have discovered it isn't complete,
     * be sure to unlock it. Sadly, some OS's seem to think that locks
     * can stay after a process goes away. Sigh. -BB
     */

    if (locktest_fd(fileno(f)) == 0)
      if (lockun_fd(fileno(f)) != 0) {
	syslog(LLEV, "fwtksyserr: couldn't unlock incomplete spool file %s (%s)!", fname, strerror(errno));
	exit(EX_CONFIG);
      }
    exit(EX_TEMPFAIL);
  }
  /*
   * parse file 
   */

  if (fgets(line, sizeof(line), f) == NULL) {
    syslog(LLEV, "fwtksyserr: read failed on spool file %s (%s) - message not forwarded", fname, strerror(errno));
    fail_abort(f, fname, 1);
  }
  line[SMTP_MAX_CMD_LINE - 1] = '\0';

  if (strncmp(line, "FROM ", 5) != 0) {
    syslog(LLEV, "fwtksyserr: file %s corrupt (no FROM line) - message not forwarded", fname);
    fail_abort(f, fname, 1);
  }
  c = strchr(line, '\n');
  if (c == NULL) {
    syslog(LLEV, "fwtksyserr: FROM line too long in %s - message not forwarded", fname);
    fail_abort(f, fname, 1);
  }
  *c = '\0';
  from = strdup(line + 5);
  if (from == NULL) {
    syslog(LLEV, "fwtksyserr: malloc failed - retrying later");
    fail_retry(f, fname);
  }

#if STRIP_QUOTES
  /* remove <> quotes from sender, as some MTA's (like qmail) don't deal
   * with it well. 
   */
  if ((from[0]=='<') && (from[strlen(from)-1]=='>')) {
     from[strlen(from)-1]='\0';
     from++;
  }
#endif
	
  for (;;) {
    long vloc;

    vloc = ftell(f);
    if (fgets(line, sizeof(line), f) == NULL) {
      syslog(LLEV, "fwtksyserr: read failed on spool file %s (%s) - message not forwarded", fname, strerror(errno));
      fail_abort(f, fname, 1);
    }
    line[SMTP_MAX_CMD_LINE - 1] = '\0';
    if (strncmp(line, "SENT ", 5) == 0) {
      /*
       * we already sent it to this victim on a previous attempt. 
       */
      continue;
    }
    if (strncmp(line, "RCPT ", 5) != 0) {
      break;
    }
    /*
     * we have a RCPT 
     */
    if (victim->name != NULL) {
      victim->next = (struct smtp_victim *) malloc(sizeof(struct smtp_victim));

      victim = victim->next;
      victim->name = NULL;
      victim->next = NULL;
    }
    c = strchr(line, '\n');
    if (c == NULL) {
      syslog(LLEV, "securityalert: RCPT line too long in %s - message not forwarded", fname);
      fail_abort(f, fname, 1);
    }
    *c = '\0';
    if ((victim->name = strdup(line + 5)) == NULL) {
      syslog(LLEV, "fwtksyserr: malloc failed - retrying later");
      fail_retry(f, fname);
    }
#if STRIP_QUOTES
    /* again, strip <> if present in case MTA can't handle it */
    if ((victim->name[0]=='<') && (victim->name[strlen(victim->name)-1]=='>')) {
       victim->name[strlen(victim->name)-1]='\0';
       (victim->name)++;
    }
#endif
    victim->location = vloc;
  }

  c = strchr(line, '\n');
  if (c == NULL) {
    syslog(LLEV, "securityalert: BODY line too long in %s - message not forwarded", fname);
    fail_abort(f, fname, 1);
  }
  *c = '\0';
  if (strcmp(line, "BODY") != 0) {
    syslog(LLEV, "fwtksyserr: file %s corrupt (no BODY after RCPT) - message not forwarded", fname);
    fail_abort(f, fname, 1);
  }
  /*
   * We're now at the start of our message body with the list of
   * recipients in "victims" and the sender in "from". fire off our
   * mail program to send it out 
   */
  body = ftell(f);
  victim = victims;
  sentout = 0;
  if (!VerboseSyslog) {
    accumlog(LOG_INFO, "Forwading %s", fname);
  }
  while (victim != NULL) {
    struct smtp_victim *sv = victim;
    char *av[MAXARGS];

    i=0;
    av[i++] = mailagent;
#if SENDMAIL_OITRUE
    if (strstr(mailagent, "sendmail") != 0) {
      /*
       * Sendmail has a feature/bug that it will by default 
       * stop on a line with just a '.'. We need to 
       * tell sendmail to ignore a line that contains just a '.' 
       * otherwise it decides that it's the end of the message. 
       * We may not need this if "sendmail" isn't really sendmail.
       * (for example, qmail's phony "sendmail" that calls qmail-inject
       * doesn't need this).
       */
      av[i++] = "-oiTrue";
    }
#endif
    av[i++] = "-f";
    av[i++] = from;
    rstart = i;
    while (i < MAXARGS - 2) {
      if (VerboseSyslog) {      
	syslog(LLEV, "forwarding to recipient %s", victim->name);
      } else {
	accumlog(LLEV, " to=%s", victim->name);
      }
      av[i++] = victim->name;
      victim = victim->next;
      if (victim == NULL) {
	break;
      }
    }
    av[i] = NULL;

    if ((pid = fork()) == 0) {
      int xerrno;

      close(0);
      close(1);
      close(2);
      if (dup(fileno(f)) != 0) {
	syslog(LLEV, "fwtksyserr: couldn't dup open %s to stdin (%s)", fname, strerror(errno));
	exit(EX_OSERR);
      }

      fclose(f);
      if (lseek(0, body, SEEK_SET) < 0) {
	syslog(LLEV, "fwtksyserr: can't lseek spool file %s! (%s)", fname, strerror(errno));
	exit(EX_OSERR);
      }
      execv(av[0], av);
      xerrno = errno;
      errno = xerrno;
      if (errno == ENOMEM) {
	syslog(LLEV, "fwtksyserr: exec of %s failed (%s) - retrying it later", av[0] , strerror(errno));
	fail_retry(fdopen(0, "r+"), fname);
      } else {
	syslog(LLEV, "fwtksyserr: exec of %s failed! (%s)", av[0], strerror(errno));
	exit(EX_CONFIG);
      }
    } else if (pid < 0) {
      syslog(LLEV, "fwtksyserr: fork failed - retrying message later");
      fail_retry(f, fname);
    }
    do {
      pidw = wait(&status);
    }
    while ((pidw != pid) && (pidw != -1));

    if ((!WIFEXITED(status)) || (WEXITSTATUS(status) != 0)) {
      /*
       * Sendmail go boom.  boo hoo. 
       */
      switch (WEXITSTATUS(status)) {
      case EX_IOERR:	
      case EX_UNAVAILABLE:
      case EX_TEMPFAIL:
	syslog(LLEV, "Temporary sendmail failure (status %d), will retry later", status);
	fail_retry(f, fname);
	break;
#ifdef EX_NOUSER
      case EX_NOUSER:
	syslog(LLEV, "Sendmail exited indicating one or more local recipients did not exist (no retry)");
	fail_abort(f, fname, 0);  
#endif
      case EX_CONFIG:
	syslog(LLEV, "fwtkcfgerr: sendmail configuration error!");
	exit(EX_CONFIG);
      default:
	syslog(LLEV, "fwtksyserr: sendmail exited abnormally (status %d) - message not forwarded.", status);
	fail_abort(f, fname, 1);
      }
    }
    /*
     * yippee. so far so good 
     */
    sentout += (i - rstart);

    if (victim != NULL) {

      /*
       * We got more, and have to do it all again. Before we do,
       * tag the existing recipients who got sent out by changing
       * RCPT to SENT in the spoolfile. In this way we avoid 
       * delivering this again if we have a temporary sendmail
       * failure and retry after having sent it out to part of the 
       * recipients successfully. 
       */


      for (i = rstart; i < (MAXARGS - 2); i++) {
	if (fseek(f, sv->location, SEEK_SET) != 0) {
	  syslog(LLEV, "fwtksyserr: couldn't fseek %s (%s)\n - message abandoned after delivery to first %d recipients", fname, strerror(errno), sentout);
	  fail_abort(f, fname, 1);
	}
	fprintf(f, "SENT");
	fflush(f);
	sv = sv->next;
	if (sv == NULL) {
	  break;
	}
      }


      if (fseek(f, body, SEEK_SET) != 0) {
	syslog(LLEV, "fwtksyserr: couldn't fseek %s (%s)\n - message abandoned after delivery to first %d recipients", fname, strerror(errno), sentout);
	fail_abort(f, fname, 1);
      }
    }
  }

  /*
   * All seems to have worked 
   */
  syslog(LLEV,"delivered file=%.512s pid=%d code=%d recipients=%d",fname,
	      pid,WEXITSTATUS(status),sentout);

  if (unlink(fname) != 0) {
    syslog(LLEV, "fwtksyserr: couldn't remove spool file %s! (%s)", fname, strerror(errno));
    exit(EX_CONFIG);
  }
  if (locktest_fd(fileno(f)) == 0)
    if (lockun_fd(fileno(f)) != 0) {
      syslog(LLEV, "fwtksyserr: couldn't unlock spool file %s using lockf after removal (%s)!", fname, strerror(errno));
      exit(EX_CONFIG);
    }

  fclose(f);
  exit(EX_OK);
}

/*
 * The brains of this operation
 */

int
main(int argc, char **argv)
{
  int pid;

  Cfg *cfp;

#ifndef LOG_DAEMON
  openlog(basename(argv[0]),LOG_PID);
#else
  openlog(basename(argv[0]),LOG_PID|LOG_NDELAY,LFAC);
#endif

#if defined(linux) || defined (SOLARIS)
  initsetproctitle(argc,argv);
#endif

  if ((cfp = cfg_read(basename(argv[0]))) == (Cfg *)-1) {
	fprintf(stderr,"no configuration found for %s",basename(argv[0]));
	exit(EX_CONFIG);
  }

  if (!(mailagent = proxy_conf_string(cfp,"sendmail"))) 
	mailagent=MAIL_AGENT;

  spooldir = proxy_conf_string(cfp,"directory");
  maxchildren = proxy_conf_int(cfp,"maxchildren",1,1000,10);
  poll_time = proxy_conf_int(cfp,"wakeup",1,1000,30);
  baddir = proxy_conf_string(cfp,"baddir");
  badadmin = proxy_conf_string(cfp,"badadmin");
  pidfile = proxy_conf_string(cfp,"pidfile");
  proxy_conf_userid(cfp);
  proxy_conf_groupid(cfp);

  if (proxy_uid == 0) {
    fprintf(stderr, "Sorry, I don't want to run as root! It's a bad idea!\n");
    exit(EX_CONFIG);
  }
  if (proxy_gid == 0) {
    fprintf(stderr, "Sorry, I don't want to run as group 0. It's a bad idea!\n");
    exit(EX_CONFIG);
  }
  proxy_setugid();


  if (spooldir == NULL) {
    syslog(LLEV,"fwtkcfgerr: no directory specified");
    fprintf(stderr,"fwtkcfgerr: no directory specified");
    exit(EX_CONFIG);
  }
  
  /*
   * OK, we're now running as a non-root user and group, hopefully one
   * that can run sendmail -f and have it work. 
   */

  if (chdir(spooldir) != 0) {
    perror("Chdir failed!");
    fprintf(stderr, "Can't change directory to spooldir %s\n", spooldir);
    exit(EX_CONFIG);
  }
  if ((pid = fork()) != 0) {
    if (pid < 0) {
      syslog(LLEV, "fwtksyserr: fork failed (%s) while trying to become a daemon", strerror(errno));
    }
    exit(EX_OSERR);
  } else {
    DIR *dir;

    /*
     * Try to get a semaphore file. Prevents multiple instances of
     * smtpfwdd from running at once on the same spool directory. 
     */

    {
      int lfd,pfd;
      char tbuf[100];

      lfd = open(".smtpfwdd.lock", O_WRONLY | O_CREAT, 0644);
      if (lfd < 0) {
	syslog(LLEV, "fwtksyserr: can't open semaphore file in \"%s\" (%s) - bye!", spooldir, strerror(errno));
	exit(EX_CONFIG);
      }
      if (locktest_fd(lfd) != 0) {
	syslog(LLEV, "fwtkcfgerr: I'm already running in %s", spooldir);
	exit(EX_CONFIG);
      }
      snprintf(tbuf, 99, "%7d\n", (int) getpid());
      if (pidfile) {
	if ((pfd = open(pidfile, O_WRONLY | O_CREAT, 0644)) < 0) {
		syslog(LLEV, "fwtksyserr: can't open pid file \"%s\" (%s)",pidfile, strerror(errno));
		exit(EX_CONFIG);
	} else 
      		write(pfd, tbuf, strlen(tbuf));
      }

      /*
       * Done - put our pid in the semaphore file.
       * Note that we keep the semaphore file open but forget the file's fd.
       */
      write(lfd, tbuf, strlen(tbuf));
    }

    setsid();

    signal(SIGCHLD, SIG_DFL);

    dir = opendir(".");
    if (dir == NULL) {
      syslog(LLEV, "fwtksyserr: can't open directory %s (%s) - exiting",
	     spooldir, strerror(errno));
      exit(EX_CONFIG);
    }
    for (;;) {
      struct dirent *direct;
      int cpid;

      proxy_update_operation("SCAN");
      while ((direct = readdir(dir)) != NULL) {
	int groks = 0;

	reap_children();
	while (children >= maxchildren) {
	  groks++;
	  if (groks == 60) {
	    syslog(LLEV, "fwtksyserr: too many children for last minute! Please investigate!");
	    groks = 0;
	  }
	  sleep(1);
	  reap_children();
	}
 	if (!VerboseSyslog) {
	  /* should be empty - but just in case */
	  accumlog(LOG_INFO, 0);
	}
	/*
	 * If we have a file with an appropriate name and it is
	 * complete then create a child which will try to forward the
	 * message.
	 */
	if (strncmp(direct->d_name, "smtpd", 5) == 0
	    && smtp_spoolfile_complete(direct->d_name)) {
	  children++;
	  if ((cpid = fork()) == 0) {
	    forward(direct->d_name);
	    /*
	     * NOTREACHED 
	     */
	    syslog(LLEV,
		   "fwtksyserr: returned from forward()! SHOULD NOT HAPPEN!");
	    exit(EX_CONFIG);
	  }
	  if (cpid < 0) {
	    syslog(LLEV, "fwtksyserr: fork failed! (%s)", strerror(errno));
	    children--;
	  }
	}
      }
      rewinddir(dir);
      proxy_update_operation("IDLE");
      sleep(poll_time);
    }
  }
}
