Logo Search packages:      
Sourcecode: zmailer version File versions  Download package

diagnostic.c

/*
 *    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);
      }
}

Generated by  Doxygen 1.6.0   Back to index