/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */
/*
 *	Lots of modifications (new guts, more or less..) by
 *	Matti Aarnio <mea@nic.funet.fi>  (copyright) 1992-1996
 */

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "mail.h"
#include "scheduler.h"
#ifdef	HAVE_SYSLOG_H
#include <syslog.h>
#endif	/* HAVE_SYSLOG_H */
#include "ta.h"

#include "prototypes.h"

extern char *strchr();
extern char *strrchr();
#ifndef strnsave /* unless it is a debugging macro.. */
extern char *strnsave();
#endif
extern int errno;

/*
 * There has to be some way of collecting the error messages produced during
 * delivery, and this is it. Each message is appended to the control file.
 * Just before the control file is to be unlinked (or at other times by
 * external programs), we check to see if we had any diagnostic messages.
 * If so, we need to submit a message to the router using the standard
 * routines. This scheme is ugly in that it modifies the control file rather
 * drastically (at least its forced-appends), but elegant in most every other
 * respect (knock wood).
 */

struct not {
	char *not;
	char *dsn;
	char *rcpt;
	char *rcpntp;
};

static void	/* There is notaryreport() on transporters also.. */
scnotaryreport(errfp,notary)
	FILE *errfp;
	struct not *notary;
{
	char *rcpt, *action, *status, *diagnostic, *wtt;
	char const *typetag;
	char *channel;

	static char const *type_rfc   = "RFC822";
	static char const *type_local = "X-LOCAL";

	/* NOTARY: addres / action / status / diagnostic / wtt */

	if (notary == NULL) {
	  return; /* XX: ?? call into here should not happen.. */
	}

	rcpt     = notary->not;
	if (*rcpt == 0)
	  return;	/* XX: ?? eh, well.. we should not be called.. */

	action   = strchr(rcpt,'\001');
	*action++ = 0;
	status   = strchr(action,'\001');
	*status++ = 0;
	diagnostic = strchr(status,'\001');
	if (diagnostic == NULL) {
	  diagnostic = status;
	  status = NULL;
	  wtt = NULL;
	} else {
	  *diagnostic++ = 0;
	  wtt      = strchr(diagnostic,'\001');
	  *wtt++    = 0;
	}

	if (*rcpt == '\003' /* XX: MAGIC! */) {
	  /* Tough, internal expiry didn't know our target address!
	     Lets scan it from  rcpntp .. We can mush the string pointed
	     by the rcpntp.. */
	  char *s = notary->rcpntp;
	  char *p;
	  while (*s != ' ' && *s != '\t' && *s != 0) ++s;
	  while (*s && (*s == ' ' || *s == '\t')) ++s;
	  /* XX: If the 'host' part has spaces in it ?? */
	  while (*s != ' ' && *s != '\t' && *s != 0) ++s;
	  while (*s && (*s == ' ' || *s == '\t')) ++s;
	  p = strrchr(s,' ');
	  if (!p) p = strrchr(s,'\t');
	  if (p) *p = 0;
	  /* Ok, we have it chopped ..  Quite a butcher job, but.. */
	  rcpt = s;
	}

	if (strchr(rcpt,'@') != NULL) {
	  typetag = type_rfc;
	  if (strncmp(rcpt,"ns:",3)==0) /* 'hold'-channel stuff */
	    typetag = type_local;
	} else
	  typetag = type_local;
	fprintf(errfp, "Final-Recipient: %s; %s\n", typetag, rcpt);
	fprintf(errfp, "Action: %s\n", action);
	if (status)
	  fprintf(errfp, "Status: %s\n", status);
	fprintf(errfp, "Diagnostic-Code: %s\n", diagnostic);
	if (notary->dsn)
	  fprintf(errfp, "X-DSN: %s\n", notary->dsn);
	if (wtt && wtt[0] != 0)
	  fprintf(errfp, "Remote-MTA: %s\n", wtt);
	fprintf(errfp, "\n");
	action[-1] = '\001';
	if (status)
	  status[-1] = '\001';
	diagnostic[-1] = '\001';
	if (wtt)
	  wtt[-1]    = '\001';
}


/* deposit the error message */

void
msgerror(vp, offset, message)
	struct vertex *vp;
	long offset;
	char *message;
{
	FILE *fp;
	char *notary = "";

	if (vp->notary) notary = vp->notary;

	/* exclusive access required, but we're the only scheduler... */
	if ((fp = fopen(vp->cfp->mid, "a")) == NULL) {
	  fprintf(stderr,
		  "Cannot open control file %s to deposit", vp->cfp->mid);
	  fprintf(stderr,
		  " error message for offset %ld:\n", offset);
	  fprintf(stderr, "\t%s\n", message);
	  return;
	}
	vp->cfp->haderror = 1;
	fprintf(fp, "%c%c%ld:%ld:%ld:%ld\t%s\t%s\n",
		_CF_DIAGNOSTIC, _CFTAG_NORMAL, offset,
		vp->headeroffset, vp->drptoffset,
		vp->dnsrecipient ? (vp->dnsrecipient - vp->cfp->contents) : -1,
		notary, message);
	fclose(fp);
}

/* Pick recipient address from the input line.
   EXTREMELY Simple minded parsing.. */
static void pick_env_addr(buf,mfp)
char *buf;
FILE *mfp;
{
	char *s = buf;

	while (*s != 0 && *s != ' ' && *s != '\t' && *s != ':') ++s;
	if (*s != ':') return; /* BAD INPUT! */
	buf = ++s; /* We have skipped the initial header.. */

	s = strchr(buf,'\n');
	if (s) *s = 0;
	s = strchr(buf,'<');
	if (s != NULL) {
	  /*  Cc:  The Postoffice managers <postoffice> */
	  buf = ++s;
	  s = strrchr(buf,'>');
	  if (s) *s = 0;
	  else return; /* No trailing '>' ? BAD BAD! */
	  fprintf(mfp,"to <%s>\n",buf);
	} else {
	  /*  Cc: some-address  */
	  fprintf(mfp,"to <%s>\n",buf);
	}
}


/* called to process errors at sensible intervals */

void
reporterrs(cfpi)
	struct ctlfile *cfpi;
{
	int i, n, wroteheader, byteidx, headeridx = -1, drptidx, fd;
	long *lp;
	char path[MAXPATHLEN], buf[BUFSIZ], *midbuf, *cp, *s, *eaddr;
	char *deliveryform;
	FILE *errfp, *fp;
	char *boundarystr;
	char *notary;
	struct not *notaries = NULL;
	char *envid;
	int notarycnt = 0;
	int notaryspc = 0;
	char *dnsrecipient = NULL;
	struct ctlfile *cfp;
	char *rcpntpointer;

	if (cfpi->haderror == 0)
		return;
	if (cfpi->erroraddr == NULL)
		return;

	boundarystr = (char*)buildboundary("dlvryerr");

	eaddr = cfpi->erroraddr;
	deliveryform = cfpi->deliveryform;
	envid = cfpi->envid;
	/* re-read the control file to get some unstashed information */
	midbuf = cfpi->mid;

	/* exclusive access required, but we're the only scheduler... */
	if ((fd = open(cfpi->mid, O_RDWR, 0)) < 0 ||
	    (cfp = slurp(fd, cfpi->id)) == NULL) {
	  fprintf(stderr,
		  "%s: unexpected absence of control file %s for error processing!\n",
		  progname, cfpi->mid);
	  if (fd >= 0)
	    close(fd);
	  return;
	}
	lp = &cfp->offset[0];
	if ((errfp = mail_open(MSG_RFC822)) == NULL) {
	  fprintf(stderr,
		  "%s: cannot open output mail file to return diagnostics! errno=%d, errstr='%s'\n",
		  progname, errno, strerror(errno));
	  close(cfp->fd);
	  free_cfp_memory(cfp);
	  return;
	}
	if (cfp->mid) free(cfp->mid);
	cfp->mid = midbuf; /* This is the original one! */
	wroteheader = 0;
	for (i = 0; i < cfp->nlines; ++i, ++lp) {
	  cp = cfp->contents + *lp;

	  /* Line fmt:
	     <TAGS> offset ':' headeroffset <TAB> NOTARY-DATA <TAB> MESSAGE <NL> */

	  if (!(*cp == _CF_DIAGNOSTIC && *++cp == _CFTAG_NORMAL))
	    continue;
	  if (!wroteheader) {
	    if (*eaddr == '|' /* Nobody's address starts with a pipe ? */ ||
		(*eaddr == '/' &&
		 (strchr(eaddr,'@') == NULL ||
		  strchr(eaddr,'=') == NULL /*Smells of X.400*/)))
	      eaddr = "postmaster"; /* Paranoid, eh ? */
	    fprintf(errfp, "channel error\n");

	    fprintf(errfp, "to <%s>\n", eaddr);
	    sprintf(path, "%s/%s/%s", mailshare, FORMSDIR,
		    deliveryform ? deliveryform : "delivery");
	    if ((fp = fopen(path, "r")) != NULL) {
	      int inhdr = 1;
	      buf[sizeof(buf)-1] = 0;
	      while (fgets(buf,sizeof(buf)-1,fp) != NULL) {
		if (strncmp(buf,"HDR",3)==0)
		  continue;
		else if (strncmp(buf,"ADR",3)==0)
		  pick_env_addr(buf+4,errfp);
		else if (strncmp(buf,"SUB",3)==0)
		  continue;
		else
		  break;
	      }
	      fputs("env-end\n",errfp); /* Envelope ends */
	      fseek(fp,0,0);		/* Rewind! Start building the report */

	      fprintf(errfp, "To: %s\n", eaddr);
	      while (fgets(buf,sizeof(buf)-1,fp) != NULL) {
		if (strncmp(buf,"HDR",3)==0) {
		  fputs(buf+4,errfp);
		} else if (strncmp(buf,"ADR",3)==0) {
		  fputs(buf+4,errfp);
		} else if (strncmp(buf,"SUB",3)==0) {
		  fputs(buf+4,errfp);
		} else {
		  if (inhdr) {
		    inhdr = 0;
		    fprintf(errfp,"MIME-Version: 1.0\n");
		    fprintf(errfp,"Content-Type: multipart/report; report-type=delivery-status;\n");
		    fprintf(errfp,"\tboundary=\"%s\"\n\n",boundarystr);
		    fprintf(errfp, "--%s\n", boundarystr);
		    fprintf(errfp, "Content-Type: text/plain\n");
		  }
		  fputs(buf,errfp);
		}
	      } /* ... while() ends.. */
	      fclose(fp);
	    }
	    /* fprintf(errfp, "\n"); -- not needed ?? */
	    wroteheader = 1;
	  }
	  
	  /* Line fmt:
	     <TAGS> offset ':' headeroffset ':' drptidx <TAB> NOTARY-DATA <TAB> MESSAGE <NL> */

	  cp = cfp->contents + *lp + 2;
	  byteidx = atoi(cp);
	  while (*cp  && isascii(*cp) && isdigit(*cp))
	    ++cp;
	  headeridx = -1;
	  if (*cp == ':') {
	    ++cp;
	    headeridx = atoi(cp);
	    while (*cp && isascii(*cp) && (isdigit(*cp) || *cp == '-'))
	      ++cp;
	  }
	  drptidx = -1;
	  if (*cp == ':') {
	    ++cp;
	    drptidx = atoi(cp);
	    while (isascii(*cp) && (isdigit(*cp) || *cp == '-'))
	      ++cp;
	  }
	  if (*cp == ':') {
	    int offs = atoi(++cp);
	    if (offs > 0)
	      dnsrecipient = cfp->contents + offs;
	    while (isascii(*cp) && (isdigit(*cp) || *cp == '-'))
	      ++cp;
	  }
	  notary = NULL;
	  if (*cp == '\t') {
	    notary = ++cp;
	    while (*cp && *cp != '\t') ++cp;
	    notary = strnsave(notary, cp-notary); /* Make a copy of it */
	    ++cp;
	  }
	  rcpntpointer = cfp->contents + byteidx + 2 + _CFTAG_RCPTPIDSIZE;
	  if (notary != NULL && *notary != 0) {
	    if (!notaries) {
	      notaryspc = 8;
	      notaries = (void*)emalloc(sizeof(struct not)*(notaryspc+1));
	    }
	    if (notarycnt >= notaryspc) {
	      notaryspc += 8;
	      notaries = (void*)erealloc((void*)notaries,
					 sizeof(struct not)*(notaryspc+1));
	    }
	    notaries[notarycnt].not    = notary;
	    notaries[notarycnt].dsn    = NULL;
	    notaries[notarycnt].rcpt   = dnsrecipient;
	    notaries[notarycnt].rcpntp = rcpntpointer;
	    if (drptidx > 0) {
	      notaries[notarycnt].dsn = cfp->contents + drptidx;
	    }
	    ++notarycnt;
	    notaries[notarycnt].not = NULL;
	  }

	  /* Scan to the start of the message text */
	  while (isascii(*cp) && isspace(*cp))
	    ++cp;
	  fprintf(errfp, "<%s>: ", rcpntpointer);
	  if (strchr(cp, '\r')) {
	    fprintf(errfp, "...\\\n\t");
	    for (s = cp; *s != '\0'; ++s) {
	      if (*s == '\r')
		putc('\n', errfp), putc('\t', errfp);
	      else
		putc(*s, errfp);
	    }
	    putc('\n', errfp);
	  } else
	    fprintf(errfp, "%s\n", cp);
	  lockaddr(cfp->fd, NULL, *lp + 1, _CFTAG_NORMAL, _CFTAG_OK);
#ifdef	LOG_INFO
	  syslog(LOG_INFO, "%s: <%s>: %s",
		 cfp->mid, cfp->contents + byteidx + 2, cp);
#endif				/* LOG_INFO */
	}
	
	fprintf(errfp, "\n");
	fprintf(errfp, "--%s\n", boundarystr);
	fprintf(errfp, "Content-Type: message/delivery-status\n\n");

	if (mydomain() != NULL) {
	  fprintf(errfp, "Reporting-MTA: dns; %s\n", mydomain() );
	} else {
	  fprintf(errfp, "Reporting-MTA: x-local-hostname; -unknown-\n");
	}
	if (envid != NULL)
	  fprintf(errfp, "Original-Envelope-Id: %s\n",envid);
	/* rfc822date() returns a string with trailing newline! */
	fprintf(errfp, "Arrival-Date: %s", rfc822date(&cfpi->ctime));
	fprintf(errfp, "\n");

	/* Now scan 'em all again for IETF-NOTARY */
	for (i = 0; i < notarycnt; ++i) {
	  scnotaryreport(errfp, &notaries[i]);
	  if (notaries[i].not != NULL)
	    free(notaries[i].not);
	}
	free((void*)notaries);

	fprintf(errfp, "--%s\n", boundarystr);
	fprintf(errfp, "Content-Type: message/rfc822\n\n");

	sprintf(path, "../%s/%s", QUEUEDIR, cfp->mid);
	if ((fp = fopen(path, "r")) != NULL) {

	  if (cfp->msgbodyoffset > 0 && headeridx > 0 ) {
	    /* We have knowledge about the headers of errored email,
	       use those headers on output ! */
	    fputs(cfp->contents+headeridx, errfp);
	    putc('\n', errfp); /* Newline in between headers and the body.. */
	    fseek(fp, cfp->msgbodyoffset, 0);
	  } else {
	    /* Scan the input, and drop off the Zmailer
	       envelope headers */
	    while (fgets(buf,sizeof(buf),fp) != NULL) {
	      char *s = buf;
	      while (*s && *s != ':' && *s != ' ' && *s != '\t') ++s;
	      if (*s == ':') break;
	      *buf = 0;
	    }
	    /* We leave the first scan-phase with  buf[]  containing some
	       valid RFC-822 -style header, propably "Received:" */
	    if (*buf)
	      fputs(buf,errfp);
	    else {
	      fputs("< Eh, no content in the ORIGINAL message ???  >\n",errfp);
	      fputs("< We will dump also transporter envelope here >\n",errfp);
	      fseek(fp,0,0);
	    }
	  }
	  /* Copy out the rest with somewhat more efficient method */
	  while ((n = fread(buf, sizeof buf[0], sizeof buf, fp)) > 0)
	    fwrite(buf, sizeof buf[0], n, errfp);
	  fclose(fp);
	}
	/* And cap the tail with paired MIME boundary.. */
	fprintf(errfp, "--%s--\n", boundarystr);

	mail_close(errfp);	/* XX: check for error */
	close(cfp->fd);
	cfp->mid = NULL; /* we don't want to loose the original one! */
	free_cfp_memory(cfp);
}

char *
mail_alloc(n)
	u_int n;
{
	return emalloc(n);
}

int
mail_free(s)
	char *s;
{
	free(s);
	return 0;
}

