/*
 *	"Expirer", copyright Matti Aarnio 1997.
 *
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 *	Copyright 1992-2003 Matti Aarnio.
 */

/*
 * This program is based on 'mailbox', but the usage semantics are special
 * and hard-coded inside the scheduler system.
 *
 * This implements target recipient manually ordered expiry.
 *
 * This has unusual parametrization convention, as each input line this
 * program receives can have up to 3 TAB separated fields:
 *  - spoolfilename
 *  - optional host selector (empty if third parameter is given)
 *  - optional explanatory string (up to newline)
 *
 * The scheduler will supply '-c' option to drive the channel selection.
 *
 */

#include "hostenv.h"
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include <sysexits.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* F_LOCK is there at some systems.. */
#endif
#include <string.h>

#include "ta.h"

#include "mail.h"
#include "zsyslog.h"
#include "zmsignal.h"

#include "zmalloc.h"
#include "libz.h"
#include "libc.h"

#include "shmmib.h"


const char *defcharset;
const char *progname;
const char *channel;
const char *logfile;
FILE *logfp = NULL;

extern RETSIGTYPE wantout __((int));
extern int optind;
extern char *optarg;
extern void prversion __((const char *));
extern void process __((struct ctldesc *dp, const char *optmsg, const int, FILE *verboselog));
extern void deliver __((struct ctldesc *dp, struct rcpt *rp));

extern void hp_init();
extern char **hp_getaddr();
extern int errno;
#ifndef strchr
extern char *strchr(), *strrchr();
#endif
extern int  lstat __((const char *, struct stat *)); /* remaps to  stat() if USE_LSTAT is not defined.. */
extern FILE *fdopen();
#ifndef MALLOC_TRACE
extern void * emalloc __((size_t));
#else
struct conshell *envarlist = NULL;
#endif
extern int stickymem;	/* for strsave() */
int	D_alloc = 0;

static void sig_alrm __((int));
static void sig_alrm(sig)
int sig;
{
	/* Sigh, actually dummy routine.. */
}

static void MIBcountCleanup __((void))
{
	MIBMtaEntry->taexpi.TaProcCountG -= 1;
}

static void SHM_MIB_diag(rc)
     const int rc;
{
  switch (rc) {
  case EX_OK:
    /* OK */
    MIBMtaEntry->taexpi.TaRcptsOk ++;
    break;
  case EX_TEMPFAIL:
  case EX_IOERR:
  case EX_OSERR:
  case EX_CANTCREAT:
  case EX_SOFTWARE:
  case EX_DEFERALL:
    /* DEFER */
    MIBMtaEntry->taexpi.TaRcptsRetry ++;
    break;
  case EX_NOPERM:
  case EX_PROTOCOL:
  case EX_USAGE:
  case EX_NOUSER:
  case EX_NOHOST:
  case EX_UNAVAILABLE:
  default:
    /* FAIL */
    MIBMtaEntry->taexpi.TaRcptsFail ++;
    break;
  }
}



#ifndef	MAXPATHLEN
#define	MAXPATHLEN 1024
#endif

int
main(argc, argv)
	int argc;
	const char *argv[];
{
	char file[2048];
	char *s;
	char *optmsg = NULL;
	int c, errflg, fd;
	int silent = 0;
	char *host = NULL;	/* .. and what is my host ? */
	int matchhost = 0;
	struct ctldesc *dp;
	FILE *verboselog = NULL;

	RETSIGTYPE (*oldsig) __((int));

	SIGNAL_HANDLESAVE(SIGINT, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGINT, wantout);
	SIGNAL_HANDLESAVE(SIGTERM, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGTERM, wantout);
	SIGNAL_HANDLESAVE(SIGQUIT, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGQUIT, wantout);
	SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGHUP, wantout);
	SIGNAL_HANDLE(SIGALRM, sig_alrm); /* Actually ignored, but
					     fcntl() will break ? */

	SIGNAL_IGNORE(SIGPIPE);

	if (getenv("ZCONFIG")) readzenv(getenv("ZCONFIG"));


	Z_SHM_MIB_Attach(1); /* we don't care if it succeeds or fails.. */

	MIBMtaEntry->taexpi.TaProcessStarts += 1;
	MIBMtaEntry->taexpi.TaProcCountG    += 1;

	atexit(MIBcountCleanup);


	progname = strrchr(argv[0], '/');
	if (progname == NULL)
	  progname = argv[0];
	else
	  ++progname;

	errflg = 0;
	logfile = NULL;
	channel = "";
	while (1) {
	  c = getopt(argc, (char*const*)argv, "?c:Vh:m:l:s");
	  if (c == EOF)
	    break;
	  switch (c) {
	  case 'c':		/* specify channel scanned for */
	    channel = optarg;
	    break;
	  case 'V':
	    prversion("expirer");
	    exit(EX_OK);
	    break;
	  case 'h':
	    host = strdup(optarg);
	    matchhost = 1;
	    break;
	  case 'l':		/* log file */
	    logfile = optarg;
	    break;
	  case 'm':
	    optmsg = optarg;
	    break;
	  case 's':
	    silent = 1;
	    break;
	  default:
	    ++errflg;
	    break;
	  }
	}
	if (errflg || optind != argc) {
	  fprintf(stderr, "Usage: %s [-s] [-V] [-l logfile] [-c channel] [-h host] [-m msgstr]\n",
		  argv[0]);
	  exit(EX_USAGE);
	}

	if (geteuid() != 0 || getuid() != 0) {
	  fprintf(stderr, "%s: not running as root!\n", progname);
	  exit(EX_NOPERM);
	}

	SETUID(0);		/* make us root all over */
	SETEUID(0);		/* make us root all over */


	logfp = NULL;
	if (logfile != NULL) {
	  fd = open(logfile, O_CREAT|O_APPEND|O_WRONLY, 0644);
	  if (fd < 0)
	    fprintf(stderr,
		    "%s: open(\"%s\") failed: %s\n",
		    progname, logfile, strerror(errno));
	  else {
	    logfp = fdopen(fd, "a");
	    fcntl(fd, F_SETFD, 1);
	  }
	}

	/* We need this later on .. */
	zopenlog("expirer", LOG_PID, LOG_MAIL);

	getnobody();

	while (!getout) {

	  /* Input:
	       spool/file/name [ \t host.info [ \t explanation ]] \n
	   */

	  printf("#hungry\n");
	  fflush(stdout);

	  if (fgets(file, sizeof file, stdin) == NULL)
	    break;
	  if (strchr(file, '\n') == NULL) break; /* No ending '\n' !  Must
						    have been partial input! */
	  if (strcmp(file, "#idle\n") == 0) {
	    MIBMtaEntry->taexpi.TaIdleStates += 1;
	    continue; /* Ah well, we can stay idle.. */
	  }
	  if (emptyline(file, sizeof file))
	    break;

	  MIBMtaEntry->taexpi.TaMessages += 1;


	  s = strchr(file,'\t');
	  if (s != NULL) {
	    if (host) free(host);
	    host = strdup(s+1);
	    *s = 0;
	  }

	  SETUID(0); /* We begin as roots..  process() may change us */
	  SETEUID(0); /* We begin as roots..  process() may change us */

	  notary_setxdelay(0); /* Our initial speed estimate is
				  overtly optimistic.. */

	  dp = ctlopen(file, channel, host, &getout, NULL, NULL);
	  if (dp == NULL) {
	    printf("#resync %s\n",file);
	    fflush(stdout);
	    continue;
	  }
	  if (verboselog) {
	    fclose(verboselog);
	    verboselog = NULL;
	  }
	  if (dp->verbose) {
	    verboselog = (FILE*)fopen(dp->verbose,"a");
	    if (verboselog) {
	      setbuf(verboselog,NULL);
	      fcntl(FILENO(verboselog), F_SETFD, 1);
	    }
	  }
	  process(dp, optmsg, silent, verboselog);
	  ctlclose(dp);

	}
	exit(EX_OK);
	/* NOTREACHED */
	return 0;
}


void
process(dp, optmsg, silent, verboselog)
	struct ctldesc *dp;
	const char *optmsg;
	const int silent;
	FILE *verboselog;
{
	struct rcpt *rp;

	if (optmsg == NULL || *optmsg == 0)
	  optmsg = "x-local; 500 (Administrative message deletion from delivery queue)";

	MIBMtaEntry->taexpi.TaDeliveryStarts += 1;


	for (rp = dp->recipients; rp != NULL; rp = rp->next) {

	  notary_setxdelay(0);
	  if (silent) {
	    notaryreport(rp->addr->user, "delivered", "2.0.0 (Silent ok)", "");
	    diagnostic(verboselog, rp, EX_OK, 0, "");
	    SHM_MIB_diag(EX_OK);
	  } else {
	    notaryreport(rp->addr->user, "failed", "5.7.0 (Administrative deletion command)", optmsg);
	    diagnostic(verboselog, rp, EX_UNAVAILABLE, 0, "%s", optmsg);
	    SHM_MIB_diag(EX_UNAVAILABLE);
	  }
	}
}


syntax highlighted by Code2HTML, v. 0.9.1