/*
* 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 & that smaller, and
* larger changes...
*/
#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include "zmalloc.h"
#include <pwd.h>
#include <sysexits.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include "ta.h"
#include "mail.h"
#include "zmsignal.h"
#include "zsyslog.h"
#include "libz.h"
#include "libc.h"
#include "shmmib.h"
#define PROGNAME "errormail"
#define CHANNEL "error" /* the default channel name we deliver for */
const char *dfltform[] = {
"From: The Post Office <postmaster>",
"Subject: email delivery error",
"Cc: The Postmaster <postmaster>",
"MIME-Version: 1.0",
"Precedence: junk", /* BSD sendmail */
"Content-Type: multipart/report; report-type=delivery-status;",
"",
"Processing your mail message caused the following errors:",
NULL
};
const char *progname;
extern char *optarg;
extern int optind;
extern void process __((struct ctldesc *));
FILE *verboselog = NULL;
#ifndef strchr
extern char *strrchr(), *strchr();
#endif
#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif /* MAXPATHLEN */
int D_alloc = 0; /* For tmalloc() from libz.a ... */
static void MIBcountCleanup __((void))
{
MIBMtaEntry->taerrm.TaProcCountG -= 1;
}
static void SHM_MIB_diag(rc)
const int rc;
{
switch (rc) {
case EX_OK:
/* OK */
MIBMtaEntry->taerrm.TaRcptsOk ++;
break;
case EX_TEMPFAIL:
case EX_IOERR:
case EX_OSERR:
case EX_CANTCREAT:
case EX_SOFTWARE:
case EX_DEFERALL:
/* DEFER */
MIBMtaEntry->taerrm.TaRcptsRetry ++;
break;
case EX_NOPERM:
case EX_PROTOCOL:
case EX_USAGE:
case EX_NOUSER:
case EX_NOHOST:
case EX_UNAVAILABLE:
default:
/* FAIL */
MIBMtaEntry->taerrm.TaRcptsFail ++;
break;
}
}
int
main(argc, argv)
int argc;
char *argv[];
{
const char *channel;
char msgfilename[MAXPATHLEN+1];
int errflg, c;
struct ctldesc *dp;
RETSIGTYPE (*oldsig) __((int));
SIGNAL_HANDLESAVE(SIGINT, SIG_IGN, oldsig);
if (oldsig != SIG_IGN)
SIGNAL_HANDLE(SIGINT, wantout);
SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig);
if (oldsig != SIG_IGN)
SIGNAL_HANDLE(SIGHUP, 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_IGNORE(SIGPIPE);
if (getenv("ZCONFIG")) readzenv(getenv("ZCONFIG"));
Z_SHM_MIB_Attach(1); /* we don't care if it succeeds or fails.. */
MIBMtaEntry->taerrm.TaProcessStarts += 1;
MIBMtaEntry->taerrm.TaProcCountG += 1;
atexit(MIBcountCleanup);
if ((progname = strrchr(argv[0], '/')) == NULL)
progname = argv[0];
else
++progname;
errflg = 0;
channel = CHANNEL;
while (1) {
c = getopt(argc, argv, "c:V");
if (c == EOF)
break;
switch (c) {
case 'c': /* specify channel scanned for */
channel = optarg;
break;
case 'V':
prversion(PROGNAME);
exit(EX_OK);
break;
default:
++errflg;
break;
}
}
if (errflg || optind != argc) {
fprintf(stderr, "Usage: %s [-V] [-c channel]\n",
argv[0]);
exit(EX_USAGE);
}
/* We need this later on .. */
zopenlog("errormail", LOG_PID, LOG_MAIL);
while (!getout) {
char *host;
printf("#hungry\n");
fflush(stdout);
if (fgets(msgfilename, sizeof msgfilename, stdin) == NULL) break;
if (strchr(msgfilename, '\n') == NULL) break; /* No ending '\n' !
Must have been
partial input! */
if (strcmp(msgfilename, "#idle\n") == 0) {
MIBMtaEntry->taerrm.TaIdleStates += 1;
continue; /* Ah well, we can stay idle.. */
}
/* Input:
spool/file/name [ \t host.info ] \n
*/
if (emptyline(msgfilename, sizeof msgfilename))
break;
MIBMtaEntry->taerrm.TaMessages += 1;
host = strchr(msgfilename,'\t');
if (host != NULL)
*host++ = 0;
dp = ctlopen(msgfilename, channel, host, &getout, ctlsticky, NULL);
if (dp != NULL) {
process(dp);
ctlclose(dp);
} else {
printf("#resync %s\n",msgfilename);
fflush(stdout);
}
}
return 0;
}
/*
#ifndef MALLOC_TRACE
univptr_t
tmalloc(n)
size_t n;
{
return emalloc((u_int)n);
}
#endif
*/
void encodeXtext __((Sfio_t *, const char *, int));
void encodeXtext(mfp,s,len)
Sfio_t *mfp;
const char *s;
int len;
{
while (*s && len > 0) {
int c = (*s) & 0xFF;
if ('!' <= c && c <= '~' && c != '+' && c != '=')
sfputc(mfp,c);
else
sfprintf(mfp,"+%02X",c);
++s;
--len;
}
}
static void decodeXtext __((Sfio_t *, const char *));
static void
decodeXtext(mfp,xtext)
Sfio_t *mfp;
const char *xtext;
{
for (;*xtext;++xtext) {
if (*xtext == '+') {
int c = '?';
sscanf(xtext+1,"%02X",&c);
sfputc(mfp,c);
if (*xtext) ++xtext;
if (*xtext) ++xtext;
} else
sfputc(mfp,*xtext);
}
}
/* Pick recipient address from the input line.
EXTREMELY Simple minded parsing.. */
static void pick_env_addr __((const char *buf, Sfio_t *mfp));
static void pick_env_addr(buf,mfp)
const char *buf;
Sfio_t *mfp;
{
const 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,'<');
if (s != NULL) {
/* Cc: The Postoffice managers <postoffice> */
buf = ++s;
s = strrchr(buf,'>');
if (s == NULL)
return; /* No trailing '>' ? BAD BAD! */
sfprintf(mfp,"todsn NOTIFY=NEVER ORCPT=rfc822;");
encodeXtext(mfp, buf, (int)(s - buf));
sfprintf(mfp,"\nto <%.*s>\n",(int)(s - buf), buf);
} else {
/* Cc: some-address */
sfprintf(mfp,"todsn NOTIFY=NEVER ORCPT=rfc822;");
s = buf + strlen(buf) - 1;
encodeXtext(mfp,buf,strlen(buf));
sfprintf(mfp,"\nto <%s>\n",buf);
}
}
void
process(dp)
struct ctldesc *dp;
{
char buf[BUFSIZ];
const char **cpp, *mailshare, *mfpath;
Sfio_t *mfp, *efp;
int n;
struct rcpt *rp;
char boundarystr[400];
char lastchar;
int reportcnt = 0;
struct stat stbuf;
long inum;
time_t mtime;
if (fstat(dp->msgfd, &stbuf) != 0)
abort(); /* This is a "CAN'T FAIL" case.. */
MIBMtaEntry->taerrm.TaDeliveryStarts += 1;
/* recipient host field is the error message file name in FORMSDIR */
/* recipient user field is the address causing the error */
if (dp->senders == NULL) {
/*
* If there was no error return address
* it might be because this message was
* an error message being bounced back.
* We do NOT want to bounce this, but
* instead just drop it on the floor.
*/
for (rp = dp->recipients; rp != NULL; rp = rp->next) {
diagnostic(verboselog, rp, EX_OK, 0, "error bounce dropped");
SHM_MIB_diag(EX_OK);
}
return;
}
if ((mfp = sfmail_open(MSG_RFC822)) == NULL) {
for (rp = dp->recipients; rp != NULL; rp = rp->next) {
diagnostic(verboselog, rp, EX_TEMPFAIL, 0, "sfmail_open failure");
SHM_MIB_diag(EX_TEMPFAIL);
}
warning("Cannot open mail file!");
return;
}
{
char *dom = mydomain(); /* transports/libta/buildbndry.c */
struct stat stbuf;
fstat(sffileno(mfp),&stbuf);
taspoolid(boundarystr, stbuf.st_ctime, (long)stbuf.st_ino);
strcat(boundarystr, "=_/errmail/");
strcat(boundarystr, dom);
}
sfprintf(mfp, "channel error\n");
sfprintf(mfp, "errormsg\n");
rp = dp->recipients;
sfprintf(mfp, "todsn NOTIFY=NEVER ORCPT=RFC822;");
encodeXtext(mfp, rp->addr->link->user, strlen(rp->addr->link->user));
if (STREQ(rp->addr->link->channel,"error"))
rp->addr->link->user = "postmaster";
sfprintf(mfp, "\nto <%s>\n",rp->addr->link->user);
/* copy error message file itself */
mailshare = getzenv("MAILSHARE");
if (mailshare == NULL)
mailshare = MAILSHARE;
mfpath = emalloc(3 + strlen(mailshare) + strlen(FORMSDIR) +
strlen(rp->addr->host));
sprintf((char*)mfpath, "%s/%s/%s", mailshare, FORMSDIR, rp->addr->host);
efp = sfopen(NULL, mfpath, "r");
if (efp != NULL) {
int inhdr = 1;
buf[sizeof(buf)-1] = 0;
while (csfgets(buf,sizeof(buf)-1,efp) >= 0) {
if (strncmp(buf,"HDR",3)==0)
continue;
else if (strncmp(buf,"ADR",3)==0)
pick_env_addr(buf+4,mfp);
else if (strncmp(buf,"SUB",3)==0)
continue;
else
break;
}
sfseek(efp,(Sfoff_t)0,0); /* Rewind! */
sfprintf(mfp,"env-end\n");
/* copy To: from error return address */
sfprintf(mfp, "To: <%s>\n", rp->addr->link->user);
while (csfgets(buf,sizeof(buf)-1,efp) >= 0) {
if (strncmp(buf,"HDR",3)==0) {
sfprintf(mfp, "%s", buf+4);
} else if (strncmp(buf,"ADR",3)==0) {
sfprintf(mfp, "%s", buf+4);
} else if (strncmp(buf,"SUB",3)==0) {
sfprintf(mfp, "%s", buf+4);
} else {
if (inhdr) {
inhdr = 0;
sfprintf(mfp,"MIME-Version: 1.0\n");
sfprintf(mfp,"Content-Type: multipart/report; report-type=delivery-status;\n");
sfprintf(mfp,"\tboundary=\"%s\"\n\n\n",boundarystr);
sfprintf(mfp, "--%s\n", boundarystr);
sfprintf(mfp, "Content-Type: text/plain\n");
}
sfprintf(mfp, "%s", buf);
}
} /* ... while() ends.. */
sfclose(efp);
} else {
sfprintf(mfp,"to <postmaster>\n"); /* Default-form Cc: ... */
sfprintf(mfp,"env-end\n");
for (cpp = &dfltform[0]; *cpp != NULL; ++cpp)
if (**cpp == '\0') {
sfprintf(mfp, "\tboundary=\"%s\"\n\n\n", boundarystr);
sfprintf(mfp, "--%s\n", boundarystr);
sfprintf(mfp, "Content-Type: text/plain\n");
} else
sfprintf(mfp, "%s\n", *cpp);
}
/* print out errors in standard format */
sfputc(mfp, '\n');
for (rp = dp->recipients; rp != NULL; rp = rp->next) {
/* If not prohibited, print it! */
if ( rp->notifyflgs & _DSN_NOTIFY_FAILURE ) {
sfprintf(mfp, "error: %s: %s\n", rp->addr->host, rp->addr->user);
++reportcnt;
}
}
/* Did we report anything ? */
if (reportcnt == 0){
/* No, throw it away and ack success. */
sfmail_abort(mfp);
for (rp = dp->recipients; rp != NULL; rp = rp->next)
if (!(rp->notifyflgs & _DSN_NOTIFY_FAILURE)) {
diagnostic(verboselog, rp, EX_OK, 0, "discarded, reportcnt=0");
SHM_MIB_diag(EX_OK);
}
return;
}
sfprintf(mfp, "\n--%s\n", boundarystr);
sfprintf(mfp, "Content-Type: message/delivery-status\n\n");
/* Print out errors in IETF-NOTARY format as well! */
if (mydomain() != NULL) {
sfprintf(mfp, "Reporting-MTA: dns; %s\n", mydomain() );
} else {
sfprintf(mfp, "Reporting-MTA: x-local-hostname; -unknown-\n");
}
if (dp->envid != NULL) {
sfprintf(mfp, "Original-Envelope-Id: ");
decodeXtext(mfp,dp->envid);
sfputc(mfp,'\n');
}
/* rfc822date() returns a string with trailing newline! */
sfprintf(mfp, "Arrival-Date: %s", rfc822date(&stbuf.st_ctime));
sfprintf(mfp, "\n");
for (rp = dp->recipients; rp != NULL; rp = rp->next) {
/* If not prohibited, print it! */
const char *typetag;
const char *rcpt;
static const char *type_rfc = "RFC822";
static const char *type_local = "X-LOCAL";
if (rp->notify != NULL && CISTREQN(rp->notify,"NEVER",5))
continue;
rcpt = rp->addr->user;
if (strchr(rcpt,'@') != NULL) {
typetag = type_rfc;
if (strncmp(rcpt,"ns:",3)==0) /* 'hold'-channel stuff */
typetag = type_local;
} else
typetag = type_local;
if (rp->orcpt != NULL) {
sfprintf(mfp, "Original-Recipient: ");
decodeXtext(mfp,rp->orcpt);
sfputc(mfp,'\n');
}
sfprintf(mfp, "Final-Recipient: %s; %s\n", typetag, rcpt);
sfprintf(mfp, "Action: failed\n");
sfprintf(mfp, "Status: 5.0.0\n");
sfprintf(mfp, "Diagnostic-Code: X-LOCAL; 500 (%s)\n", rp->addr->host );
sfprintf(mfp, "\n");
}
sfprintf(mfp, "--%s\n", boundarystr);
sfprintf(mfp, "Content-Type: message/rfc822\n\n");
/* Skip over the delivery envelope lines! */
rp = dp->recipients;
/* copy original message file */
/* seek to message body -- try it anyway */
lseek(dp->msgfd, (off_t)(dp->msgbodyoffset), SEEK_SET);
/* write the (new) headers with local "Received:"-line.. */
header_received_for_clause(rp, 0, verboselog);
swriteheaders(rp, mfp, "\n", 0, 0, NULL);
sfprintf(mfp,"\n");
/* If the DSN RET=HDRS is in effect, don't copy the msg body! */
if (!dp->dsnretmode || CISTREQN(dp->dsnretmode,"FULL",4)) {
/* Copy out the rest with somewhat more efficient method */
lastchar = 0;
while ((n = read(dp->msgfd, buf, sizeof(buf))) > 0) {
sfwrite(mfp, buf, n);
lastchar = buf[n-1];
}
if (lastchar != '\n')
sfputc(mfp, '\n');
}
sfprintf(mfp, "--%s--\n", boundarystr);
if (sferror(mfp)) {
sfmail_abort(mfp);
n = EX_IOERR;
} else if (_sfmail_close_(mfp, &inum, &mtime) == EOF)
n = EX_IOERR;
else
n = EX_OK;
{
/* Ok, build response with proper "spoolid" */
char taspid[30];
taspoolid(taspid, mtime, inum);
for (rp = dp->recipients; rp != NULL; rp = rp->next) {
diagnostic(verboselog, rp, n, 0, taspid);
SHM_MIB_diag(n);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1