/*
* Copyright 1990 by Rayan S. Zachariassen, all rights reserved.
* This will be free software, but only when it is finished.
*/
/*
* A lot of changes all around over the years by Matti Aarnio
* <mea@nic.funet.fi>, copyright 1992-2003
*/
/*
* Common routine to produce a diagnostic message for the scheduler to read
*/
#include "hostenv.h"
#include <stdio.h>
#include <sysexits.h>
#ifdef HAVE_STDARG_H
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include "ta.h"
#include "mail.h"
#include "zmalloc.h"
#include "libz.h"
#include "libc.h"
/*
If WE log the diagnostic status information into the transport
specification file, the protocol changes a bit... We don't
report quite THAT much to the scheduler then, just that things happen,
and that we did log it...
This way the errors reported by the TAs will get logged properly,
and the scheduler has a bit less to do, and no need to fuss with
all that logging...
This integer controls wether we log things ourselves, or (when zero)
we let the scheduler do it all -- like in original system.
*/
int ta_logs_diagnostics = 1;
/*
If we encounter malloc failure, we diagnose everything grimly as
EX_TEMPFAIL - always.. and hope the TA caller understands to exit..
*/
int zmalloc_failure; /* For 0/NULL values: let BSS handle it */
static char *notarybuf;
time_t retryat_time; /* Used at SMTP to avoid trying same host too soon.. */
const char *
notaryacct(rc,okstr)
int rc;
const char *okstr;
{
static char msgbuf[20];
switch (rc) {
case EX_OK:
return okstr; /* "delivered" or "relayed" .. Depends.. */
case EX_TEMPFAIL:
case EX_IOERR:
case EX_OSERR:
case EX_CANTCREAT:
case EX_SOFTWARE:
return "delayed";
case EX_NOUSER:
case EX_NOHOST:
case EX_UNAVAILABLE:
return "failed";
case EX_NOPERM:
case EX_PROTOCOL:
case EX_USAGE:
default:
sprintf(msgbuf,"*SW-ERROR*not-act-rc=%d*",rc);
return msgbuf;
}
}
static char *wtthost; /* NOTARY wtthost: 'While-Talking-To -host' */
static char *wttip;
static char *wtttaid;
int wtttaidpid = -1;
CONVERTMODE wttcvtmode = _CONVERT_UNKNOWN;
void notary_setwtt(host)
const char *host;
{
if (wtthost) free(wtthost);
if (host)
wtthost = strdup(host);
else
wtthost = NULL;
}
void notary_settaid(progname,pid)
const char *progname;
int pid;
{
if (wtttaid) free(wtttaid);
if (progname)
wtttaid = strdup(progname);
else
wtttaid = NULL;
wtttaidpid = pid;
}
void notary_setcvtmode(mode)
CONVERTMODE mode;
{
wttcvtmode = mode;
}
void notary_setwttip(ip)
const char *ip;
{
if (wttip) free(wttip);
if (ip)
wttip = strdup(ip);
else
wttip = NULL;
}
static int xdelay;
void notary_setxdelay(xdly)
int xdly;
{
xdelay = xdly;
}
/*
a1: address: host/file/something
a2: action: notary-keyword
a3: statis: extended status code
a4: diagnostics: ordinary smtp-like status code
*/
static char *A1, *A2, *A3, *A4;
void
notaryreport(arg1,arg2,arg3,arg4)
const char *arg1, *arg2, *arg3, *arg4;
{
int len;
if (arg1) {
if (A1) free(A1);
A1 = strdup(arg1);
}
if (arg2) {
if (A2) free(A2);
A2 = strdup(arg2);
}
if (arg3) {
if (A3) free(A3);
A3 = strdup(arg3);
}
if (arg4) {
if (A4) free(A4);
A4 = strdup(arg4);
}
len = 5; /* "\001\001\001\001" */
if (A1) len += strlen(A1);
if (A2) len += strlen(A2);
if (A3) len += strlen(A3);
if (A4) len += strlen(A4);
if (wtthost) len += strlen(wtthost);
if (wttip) len += strlen(wttip)+5;
if (wtttaid) len += strlen(wtttaid)+9;
notarybuf = (char*) realloc(notarybuf,len);
if (! notarybuf) {
zmalloc_failure = 1;
return;
}
sprintf(notarybuf, "%s\001%s\001%s\001%s\001%s",
A1?A1:"", A2?A2:"", A3?A3:"", A4?A4:"",
wtthost?wtthost:"");
if (wttip) {
sprintf(notarybuf + strlen(notarybuf), " (%s)", wttip);
}
if (wtttaid) {
sprintf(notarybuf + strlen(notarybuf), "\001%s[%d]",
wtttaid, wtttaidpid);
}
}
void
notaryflush __((void))
{
if (A1) free(A1);
if (A2) free(A2);
if (A3) free(A3);
if (A4) free(A4);
A1 = NULL;
A2 = NULL;
A3 = NULL;
A4 = NULL;
}
#ifdef HAVE_STDARG_H
#ifdef __STDC__
void
diagnostic(FILE *verboselog, struct rcpt *rp, int rc, int timeout, const char *fmt, ...)
#else /* Not ANSI-C */
void
diagnostic(verboselog, rp, rc, timeout, fmt) /* (rp, rc, timeout, "fmtstr", remotemsg) */
FILE *verboselog;
struct rcpt *rp;
int rc, timeout;
const char *fmt;
#endif
#else
/*VARARGS*/
void
diagnostic(verboselog, rp, rc, timeout, fmt, va_alist) /* (verboselog, rp, rc, timeout, "fmtstr", remotemsg) */
FILE *verboselog;
struct rcpt *rp;
int rc, timeout;
const char *fmt;
va_dcl
#endif
{
char message[8192];
char statmsgbuf[32+16];
const char * statmsg;
const char * syslogmsg = NULL;
char mark;
register char *s, *es, *s2;
va_list ap;
int report_notary = 1;
int logreport = 0;
int no_notary = 0;
int lockoffset = rp->lockoffset;
/* Nothing to do ?? */
if (lockoffset == 0 && !verboselog) return;
/* Ok, we either release the lock, and do diagnostics,
or we do verbose logging... or both. */
rp->status = rc;
#ifdef HAVE_STDARG_H
va_start(ap,fmt);
#else
va_start(ap);
#endif
es = &message[sizeof message - 30];
*message = 0;
for (s = message; fmt != NULL && *fmt != '\0'; ++fmt) {
if (s >= es)
break;
if (*fmt != '%') {
*s++ = *fmt;
continue;
}
switch (*++fmt) {
case 's': /* string */
s2 = va_arg(ap, char *);
for ( ; *s2 != 0; ++s2) {
*s++ = *s2;
if (s >= es)
break;
}
break;
case 'd': /* integer */
if (s >= es - 10)
break;
sprintf(s, "%d", va_arg(ap, int));
while (*s != '\0') ++s;
break;
case '%': /* percent */
*s++ = '%';
break;
case '\0':
--fmt; /* exit asap! */
break;
}
}
*s = '\0';
va_end(ap);
/* This MUST be non-void value! */
if (A2 == NULL) {
notaryreport( NULL,
notaryacct(rp->status, "delivered"),
NULL, NULL );
}
/* If we had ZMALLOC_FAILURE -> ABORT! */
if (zmalloc_failure && !(rp->notifyflgs & _DSN__DIAGDELAYMODE)) {
if (!rp->lockoffset) return; /* Don't re-report... */
fprintf(stdout,"%d/%d\t%s\t%s %s\n",
rp->desc->ctlid, rp->id,
(notarybuf && report_notary) ? notarybuf : "",
"deferred", "MALLOC FAILURE!");
fflush(stdout);
if (!lockaddr(rp->desc->ctlfd, rp->desc->ctlmap,
rp->lockoffset, _CFTAG_LOCK, _CFTAG_DEFER,
(char*)rp->desc->msgfile, rp->addr->host, getpid())) {
/* something went wrong in unlocking it, concurrency problem? */
}
rp->lockoffset = 0; /* mark this recipient unlocked */
tasyslog(rp, xdelay, wtthost, wttip, "deferred", "MALLOC FAILURE!");
return;
}
retryat_time = 0;
/* Optimize the common case -- less stuff into back-report
pipe per message.. */
if (rp->status == EX_OK &&
!(rp->notifyflgs & _DSN_NOTIFY_SUCCESS))
report_notary = 0;
switch (rp->status) {
case EX_OK:
if (ta_logs_diagnostics) {
if (!(rp->notifyflgs & _DSN_NOTIFY_SUCCESS))
statmsg = "ok3";
else
statmsg = "ok2";
} else
statmsg = "ok";
mark = _CFTAG_OK;
logreport = ta_logs_diagnostics && report_notary;
break;
case EX_TEMPFAIL:
case EX_IOERR:
case EX_OSERR:
case EX_CANTCREAT:
case EX_SOFTWARE:
if (timeout > 0) {
sprintf(statmsgbuf,"retryat +%d",timeout);
statmsg = statmsgbuf;
time(&retryat_time);
retryat_time += timeout;
}
else
statmsg = "deferred";
mark = _CFTAG_DEFER;
break;
case EX_DEFERALL:
statmsg = "deferall";
mark = _CFTAG_DEFER;
break;
case EX_NOPERM:
case EX_PROTOCOL:
case EX_USAGE:
strcat(message,
" (this is abnormal, investigate!)");
s += strlen(s);
/* fall through */
case EX_NOUSER:
case EX_NOHOST:
case EX_UNAVAILABLE:
if (ta_logs_diagnostics)
statmsg = "error2";
else
statmsg = "error";
mark = _CFTAG_NOTOK;
logreport = ta_logs_diagnostics;
break;
default:
sprintf(statmsgbuf,"error Unknown sysexits error code %d!",
rp->status);
statmsg = statmsgbuf;
mark = _CFTAG_NOTOK;
break;
}
/* If there are newlines in them for some weird reason... */
s = notarybuf; while (s && (s = strchr(s,'\n'))) *s = '\r';
s = message; while (s && (s = strchr(s,'\n'))) *s = '\r';
if (logreport && rp->lockoffset) {
/* Right, we have a honour to append our diagnostics to the
transport specification file ourselves */
int oldfl = fcntl(rp->desc->ctlfd, F_GETFL);
int newfl;
int len = 80 + strlen(notarybuf ? notarybuf : "") + strlen(message);
int rc2;
off_t ctlsize;
long oldalarm;
char *sbuf;
/* Set the APPEND-mode on.. We need it now !
(and make sure the non-blocking mode is NOT on!) */
newfl = (oldfl & ~O_NONBLOCK) | O_APPEND;
if (oldfl != newfl)
fcntl(rp->desc->ctlfd, F_SETFL, newfl);
#ifdef HAVE_ALLOCA
sbuf = alloca(len);
#else
sbuf = malloc(len);
if (!sbuf)
zmalloc_failure = 1;
#endif
if (sbuf) {
/* Log the diagnostic string to the file */
int oldflg;
#ifndef SPRINTF_CHAR
len =
#endif
sprintf(sbuf, "%c%c%d:%d:%d::%ld\t%s\t%s\n",
_CF_DIAGNOSTIC, _CFTAG_NORMAL,
rp->id, rp->headeroffset, rp->drptoffset,
(long)time(NULL), notarybuf ? notarybuf : "", message);
#ifdef SPRINTF_CHAR
len = strlen(sbuf);
#endif
oldalarm = alarm(0); /* We do NOT want to be alarmed while
writing to the log! */
ctlsize = lseek(rp->desc->ctlfd, 0, SEEK_END);
oldflg = fcntl(rp->desc->ctlfd, F_GETFL, 0);
fcntl(rp->desc->ctlfd, F_SETFL, oldflg | O_APPEND);
rc2 = write(rp->desc->ctlfd, sbuf, len);
fcntl(rp->desc->ctlfd, F_SETFL, oldflg);
if (rc2 != len || rc2 < 0 || len < 0) {
/* UAARGH! -- write failed, must have disk full! */
#ifdef HAVE_FTRUNCATE
while (ftruncate(rp->desc->ctlfd, ctlsize) < 0) /* Sigh.. */
if (errno != EINTR && errno != EAGAIN)
break;
#endif /* HAVE_FTRUNCATE */
fprintf(stdout,"#HELP! diagnostic writeout with bad results!: len=%d, rc=%d\n", len, rc2);
fflush(stdout);
exit(EX_DATAERR);
}
#ifdef HAVE_FSYNC
while (fsync(rp->desc->ctlfd) < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
break;
}
#endif
if (oldalarm) /* Restore it, if it was ticking. */
alarm(oldalarm);
#ifndef HAVE_ALLOCA
free(sbuf);
#endif
}
/* If we had to set the APPEND mode previously, clear it now! */
if (oldfl != newfl)
fcntl(rp->desc->ctlfd, F_SETFL, oldfl);
/* Now we have no reason to send also the NOTARY report up.. */
no_notary = 1;
}
/* Do the verbose logging BEFORE actual diagnostics output.
That way the "scheduler done processing" will always be
the last -- presuming the verboselog does not throw in
things AFTER the final diagnostic() call... */
if (verboselog) {
fprintf(verboselog,
"DIAG: C='%s' H='%s' U='%s' P='%s' ID=%d/%d L=%d -- stat='%s' notary='%s' ",
rp->addr->channel, rp->addr->host, rp->addr->user,
rp->addr->misc,
rp->desc->ctlid, rp->id, lockoffset,
statmsg, (notarybuf ? notarybuf : ""));
if (wtthost)
fprintf(verboselog, "WTT='%s' ", wtthost);
fprintf(verboselog, " MSG='%s'\n", message);
fflush(verboselog);
}
/* "Delay" the diagnostics from mailbox sieve subprocessing.
Actually DON'T do then at all! */
if (rp->lockoffset && (!(rp->notifyflgs & _DSN__DIAGDELAYMODE))) {
fprintf(stdout,"%d/%d\t%s\t%s %s\n",
rp->desc->ctlid, rp->id,
(!no_notary && notarybuf && report_notary) ? notarybuf : "",
statmsg, message);
fflush(stdout);
switch(rp->status) {
case EX_IOERR:
case EX_TEMPFAIL:
if (rp->notifyflgs & _DSN__TEMPFAIL_NO_UNLOCK)
break;
default:
if (!lockaddr(rp->desc->ctlfd, rp->desc->ctlmap,
rp->lockoffset, _CFTAG_LOCK, mark,
(char*)rp->desc->msgfile, rp->addr->host,
getpid())) {
/* FIXME: something went wrong in unlocking it,
FIXME: concurrency problem? */
}
rp->lockoffset = 0; /* mark this recipient unlocked */
}
syslogmsg = strrchr(message, '\r');
if (!syslogmsg) syslogmsg = message;
else syslogmsg++; /* Skip the last \r ... */
tasyslog(rp, xdelay, wtthost, wttip, statmsg, syslogmsg);
}
}
syntax highlighted by Code2HTML, v. 0.9.1