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

hold.c

/*
 *    Copyright 1990 by Rayan S. Zachariassen, all rights reserved.
 *    This will be free software, but only when it is finished.
 *    Copyright 1992-2003 Matti Aarnio -- MIME processing et.al.
 */

#include "mailer.h"

#ifdef linux
#define __USE_BSD 1
#endif
#include <ctype.h>
#include <pwd.h>
#include <sysexits.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <errno.h>

#include "zmsignal.h"
#include "zmalloc.h"
#include "zsyslog.h"

#include "ta.h"
#include "libz.h"
#include "libc.h"

#include "shmmib.h"

#ifdef      HAVE_RESOLVER
#include "netdb.h"
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#include "dnsgetrr.h"

#if   defined(TRY_AGAIN) && defined(HAVE_RESOLVER)
#define     BIND        /* Want BIND (named) nameserver support enabled */
#endif      /* TRY_AGAIN */
#ifdef      BIND
#undef NOERROR /* Solaris  <sys/socket.h>  has  NOERROR too.. */
#include <arpa/nameser.h>
#include <resolv.h>

#ifndef     BIND_VER
#ifdef      GETLONG
/* 4.7.3 introduced the {GET,PUT}{LONG,SHORT} macros in nameser.h */
#define     BIND_VER    473
#else /* !GETLONG */
#define     BIND_VER    472
#endif      /* GETLONG */
#endif      /* !BIND_VER */
#endif      /* BIND */

#if   defined(BIND_VER) && (BIND_VER >= 473)
typedef u_char msgdata;
#else /* !defined(BIND_VER) || (BIND_VER < 473) */
typedef char msgdata;
#endif      /* defined(BIND_VER) && (BIND_VER >= 473) */


/* Define all those things which exist on newer BINDs, and which may
   get returned to us, when we make a query with  T_ANY ... */

#ifndef     T_TXT
# define T_TXT 16
#endif
#ifndef T_RP
# define T_RP 17
#endif
#ifndef T_AFSDB
# define T_AFSDB 18
#endif
#ifndef T_NSAP
# define T_NSAP 22
#endif
#ifndef T_AAAA
# define T_AAAA 28      /* IPv6 Address */
#endif
#ifndef T_NSAP_PTR
# define T_NSAP_PTR 23
#endif
#ifndef     T_UINFO
# define T_UINFO 100
#endif
#ifndef T_UID
# define T_UID 101
#endif
#ifndef T_GID
# define T_GID 102
#endif
#ifndef T_UNSPEC
# define T_UNSPEC 103
#endif
#ifndef T_SA
# define T_SA 200
#endif


#include "mail.h"
#include "splay.h"

#if HAVE_SYS_WAIT_H /* POSIX-thing ?  If not, declare it so.. */
# include <sys/wait.h>
#endif
#ifndef WEXITSTATUS
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif

#ifndef     SEEK_SET
#define     SEEK_SET 0
#endif      /* SEEK_SET */

#define     PROGNAME    "hold"      /* for logging */
#define     CHANNEL           "hold"      /* the default channel name we look at */

char errormsg[BUFSIZ];
const char *progname;
char *cmdline, *eocmdline;
int pid;

#ifdef MALLOC_TRACE
#define     MEM_MALLOC 1000
int   stickymem = MEM_MALLOC; /* for strnsave() */
struct conshell *envarlist = NULL;
#endif
int   D_alloc = 0;

extern char *optarg;
extern int optind;
extern void process __((struct ctldesc *));
extern int  hold __((const char *, char **));
extern char **environ;

#ifndef strchr
extern char *strchr(), *strrchr();
#endif

#ifdef      lint
#undef      putc
#define     putc  fputc
#endif      /* lint */



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

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


FILE *verboselog = NULL;

static char filename[MAXPATHLEN+8000];

int
main(argc, argv)
      int argc;
      char *argv[];
{
      const char *channel, *host;
      int errflg, c, i;
      struct ctldesc *dp;
      RETSIGTYPE (*oldsig) __((int));

      pid = getpid();
      cmdline = &argv[0][0];
      eocmdline = cmdline;
      for (i = 0; i < argc; ++i)
        eocmdline += strlen(argv[i]) + 1;

      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);

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


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

      MIBMtaEntry->tahold.TaProcessStarts += 1;
      MIBMtaEntry->tahold.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("hold", LOG_PID, LOG_MAIL);

      while (!getout) {
        char *s;
        /* Input:
             spool/file/name [ \t host.info ] \n
         */

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

        MIBMtaEntry->tahold.TaMessages += 1;


        s = strchr(filename,'\t');
        host = NULL;
        if (s != NULL) {
          /* Ignore the host part */
          *s++ = 0;
          host = s;
        }

        dp = ctlopen(filename, channel, host, &getout, NULL, NULL);
        if (dp != NULL) {
          process(dp);
          ctlclose(dp);
        } else {
          printf("#resync %s\n",filename);
          fflush(stdout);
        }
      }
      exit(0);
      /* NOTREACHED */
      return 0;
}

/*
 * process - resubmit the message if the hold condition has disappeared.
 */

void
process(dp)
      struct ctldesc *dp;
{
      FILE *mfp;
      int n, sawok, code;
      struct rcpt *rp;
      const char *cp;
      char buf[BUFSIZ];

      MIBMtaEntry->tahold.TaDeliveryStarts += 1;

      sawok = 0;
      for (rp = dp->recipients; rp != NULL; rp = rp->next) {
        cp = rp->addr->user;
        rp->status = hold(rp->addr->host, (char**)&(rp->addr->user));
        if (rp->status == EX_OK) {
          rp->addr->user = cp;
          sawok = 1;
        } else {
          const char *action = NULL;
          const char *status = NULL;
          const char *diagnostics = NULL;
          switch (rp->status) {
          case EX_PROTOCOL:
          case EX_SOFTWARE:
            action = "failed";
            status = "5.0.0 (ZMailer internal protocol error)";
            diagnostics = "x-local; 500 (Protocol failure inside ZMAILER! AARGH!)";
            break;
          case EX_TEMPFAIL:
            action = "delayed";
            status = "4.4.3 (Temporary routing lookup failure)";
            diagnostics = "x-local; 466 (Temporary routing lookup failure)";
            break;
          default:
            action = "failed";
            status = "4.4.0 (Unknown ZMailer HOLD status ** CAN'T HAPPEN)";
            diagnostics = "x-local; 500 (Unknown HOLD return code **CAN'T HAPPEN**)";
            break;
          }
          notaryreport(rp->addr->user,action,status,diagnostics);
          diagnostic(verboselog, rp, rp->status, 0, "%s", rp->addr->user);
          SHM_MIB_diag(rp->status);
        }
      }

      if (!sawok)
        return;

      if (lseek(dp->msgfd, (off_t)(dp->msgbodyoffset), SEEK_SET) < 0L)
        warning("Cannot seek to message body! (%m)", (char *)NULL);

      SETEUID(atoi(dp->senders->misc));

      mfp = mail_open(MSG_RFC822);
      if (mfp == NULL) {
        for (rp = dp->recipients; rp != NULL; rp = rp->next)
          if (rp->status == EX_OK) {
            notaryreport(rp->addr->user,"delayed",
                     "4.3.1 (System spool full?)",
                     "x-local; 400 (Cannot resubmit anything, out of spool space?)");
            diagnostic(verboselog, rp, EX_TEMPFAIL, 0,
                   "cannot resubmit anything!");
            SHM_MIB_diag(EX_TEMPFAIL);
          }
        SETEUID(getuid());
        return;
      }
      SETEUID(getuid());

      fprintf(mfp, "via suspension\n");
      if (STREQ(dp->senders->channel,"error"))
        fprintf(mfp, "channel error\n");
      else
        fprintf(mfp, "from <%s>\n", dp->senders->user);
      if (dp->envid != NULL)
        fprintf(mfp, "envid %s\n", dp->envid);
      if (dp->dsnretmode)
        fprintf(mfp, "notaryret %s\n", dp->dsnretmode);

      for (rp = dp->recipients; rp != NULL; rp = rp->next)
        if (rp->status == EX_OK) {
          if ( rp->notify || rp->orcpt  ||
             rp->inrcpt || rp->infrom ||
             rp->deliverby                 ) {
            
            fputs("todsn",mfp);
            if (rp->orcpt != NULL)
            fprintf(mfp," ORCPT=%s",rp->orcpt);

            if (rp->inrcpt != NULL)
            fprintf(mfp," INRCPT=%s",rp->inrcpt);

            if (rp->infrom != NULL)
            fprintf(mfp," INFROM=%s",rp->infrom);

            if (rp->ezmlm != NULL)
            fprintf(mfp," EZMLM=%s",rp->ezmlm);

            if (rp->notify)
            fprintf(mfp," NOTIFY=%s", rp->notify);

            if (rp->deliverby) {
            fprintf(mfp," BY=%ld;", (long)rp->deliverby);
            if (rp->deliverbyflgs & _DELIVERBY_R) fputc('R',mfp);
            if (rp->deliverbyflgs & _DELIVERBY_N) fputc('N',mfp);
            if (rp->deliverbyflgs & _DELIVERBY_T) fputc('T',mfp);
            }

            putc('\n',mfp);
          }
          fprintf(mfp, "to <%s>\n", rp->addr->user);
        }
      fprintf(mfp,"env-end\n");

      fwriteheaders(dp->recipients,mfp,"\n",0,0,NULL);
      fprintf(mfp,"\n");

      /* append message body itself */
      while ((n = read(dp->msgfd, buf, sizeof buf)) > 0)
        fwrite(buf, sizeof buf[0], n, mfp);

      if (ferror(mfp)) {
        mail_abort(mfp);
        code = EX_TEMPFAIL;
        cp = "write error during resubmission";
      } else if (mail_close(mfp) == EOF) {
        code = EX_TEMPFAIL;
        cp = "message not resubmitted";
      } else {
        code = EX_OK;
        cp = NULL;
      }

      for (rp = dp->recipients; rp != NULL; rp = rp->next)
        if (rp->status == EX_OK) {
          notaryreport(rp->addr->user,"relayed",
                   "2.2.0 (Relayed via deferral channel)",
                   "x-local; 250 (Relayed via deferral channel)");
          diagnostic(verboselog, rp, code, 0, cp);
          SHM_MIB_diag(code);
        }
}


/*
 * The hostname for the hold channel describes a wait condition that can
 * be tested (here) before the message can be resubmitted.  The condition
 * string is canonicalized and parsed hierarchically.
 */

extern int hold_ns      __(( const char* ));
extern int hold_timeout __(( const char* ));
extern int hold_io      __(( const char* ));
extern int hold_script  __(( const char* ));
extern int hold_home    __(( const char* ));

struct holds_info {
      const char  *type;
      int   (*f) __(( const char * ));
} holds[] = {
      {     "ns",       hold_ns           },
      {     "timeout",  hold_timeout      },
      {     "io",       hold_io           },
      {     "script",   hold_script },
      {     "home",           hold_home   },
      {     NULL,       NULL        },
};


int
hold(s, errmsgp)
      const char *s;
      char **errmsgp;
{
      char *cp, *colon;
      struct holds_info *hip;
      static struct sptree *spt_hash = NULL;
      struct spblk *spl;
      int v;
      spkey_t symid;

      colon = NULL;
      for (cp = (char*)s; *cp != '\0'; ++cp) {
        unsigned char c = *cp;
        if (isascii(c) && isupper(c))
          *cp = tolower(c);
        else if (c == ':')
          colon = cp;
      }

      if (colon == NULL)
        return EX_PROTOCOL;   /* invalid hold condition */

      symid = symbol((void*)s);
      if (spt_hash == NULL)
        spt_hash = sp_init();
      if ((spl = sp_lookup(symid, spt_hash)) != NULL) {
        *errmsgp = (char *)spl->data;
        return spl->mark;
      }

      *colon++ = '\0';
      for (hip = &holds[0]; hip->type != NULL ; ++hip)
        if (strcmp(s, hip->type) == 0)
          break;
      if (hip->type == NULL)
        return EX_SOFTWARE;   /* unsupported hold condition */

      errormsg[0] = '\0';

      if ((hip->f)(colon))
        v = EX_OK;            /* resubmit the message address */
      else
        v = EX_TEMPFAIL;      /* defer resubmission */

      if (errormsg[0] != '\0') {
        cp = emalloc((u_int)strlen(errormsg)+1);
        strcpy(cp, errormsg);
      } else
        cp = "deferred";
      sp_install(symid, (const void *)cp, v, spt_hash);
      *errmsgp = cp;

      return v;
}

/* return true if the nameserver lookup of (name,type) succeeds */

#ifdef      BIND
extern int  h_errno;

struct qtypes {
      const char  *typename;
      int          value;
} qt[] = {
      {     "cname",    T_CNAME           },
      {     "mx",       T_MX        },
      {     "a",        T_A         },
      {     "aaaa",           T_AAAA            },
#ifdef  T_ANY
      {     "any",            T_ANY       },
#endif
#ifdef      T_MP
      {     "mp",       T_MP        },
#endif      /* T_MP */
#ifdef      T_UNAME
      {     "uname",    T_UNAME           },
#endif      /* T_UNAME */
#ifdef      T_TXT
      {     "txt",            T_TXT       },
#endif      /* T_TXT */
      {     "wks",            T_WKS       },
      {     "ptr",            T_PTR       },
      {     NULL,       0           }
};

int
hold_ns(s)
      const char *s;
{
      struct qtypes *qtp;
      char *cp, host[1024]; /* 256 should be enough .. */
      int ttl;

      res_init();

      if ((cp = strrchr(s, '/')) == NULL)
        return 1; /* human error, lets be nice */
      if (cp > s && *(cp-1) == '.')
        *(cp-1) = '\0';
      else
        *cp = '\0';
      ++cp;

      for (qtp = &qt[0]; qtp->typename != NULL ; ++qtp) {
        if (strcmp(qtp->typename, cp) == 0)
          break;
      }
      if (qtp->typename == NULL) {
        fprintf(stderr, "%s: unknown nameserver type '%s'\n",
              progname, cp);
        return 1;       /* inconsistency with search_res.c, yell! */
      }
      strncpy(host, s, sizeof(host));
      host[sizeof(host)-1] = 0;
      switch (getrrtype(host, &ttl, sizeof host, qtp->value, 2, NULL)) {
      case 0:
            return 1;   /* negative reply */
      case 1:
            return 1;   /* positive reply */
      case -1:
      case -2:
      case -3:
            return 0;   /* no reply */
      }
      return 0;
}
#else /* !BIND */

struct qtypes {
      char  *typename;
      u_short     value;
} qt[] = {
      {     "cname",    1           },
      {     "a",        1           },
      {     "aaaa",           1           },
      {     "ptr",            2           },
      {     NULL,       NULL        }
};

int
hold_ns(s)
      char *s;
{
      struct qtypes *qtp;
      char *cp, host[BUFSIZ];
      struct hostent *hp;
      struct in_addr ia;

      if ((cp = strrchr(s, '/')) == NULL)
        return 1; /* human error, lets be nice */
      if (cp > s && *(cp-1) == '.')
        *(cp-1) = '\0';
      else
        *cp = '\0';
      ++cp;

      for (qtp = &qt[0]; qtp->typename != NULL ; ++qtp) {
        if (strcmp(qtp->typename, cp) == 0)
          break;
      }
      if (qtp->typename == NULL) {
        fprintf(stderr, "%s: unknown hosts file lookup '%s'\n",
              progname, cp);
        return 1;       /* inconsistency with search_res.c, yell! */
      }
      strcpy(host, s);
      hp = NULL;
      switch (qtp->value) {
      case 1:
        /* XXX: getaddrinfo() */
        hp = gethostbyname(host);
        break;
      case 2:
/* XXX: use  inet_pton() here ?! */
        ia.s_addr = inet_addr(host);
        hp = gethostbyaddr(&ia, sizeof ia.s_addr, AF_INET);
        break;
      }
      return hp != NULL;
}
#endif      /* !BIND */

/* return true if the seconds-since-epoch in argument has passed */

int
hold_timeout(s)
      const char *s;
{
      time_t now, then;

      time(&now);
      then = (time_t)atol(s);
      return now >= then;
}

/* return true if we should retry the I/O operation causing the error */

int
hold_io(s)
      const char *s;
{
      return ranny(9) == 0;   /* 10% of the time */
}

/* based on tuple "hold" "script:command/$user" "$address"
   return exit status of "$MAILBIN/bin/command $user" command */

int
hold_script(command)
      const char *command;
{
      int i, in[2];
      const char *env[20], *s;
      char buf[8192], *cp, *arg;
      FILE *errfp;
      int status;

      arg = strchr(command, '/');
      if (arg != NULL)
        *arg++ = '\0';
      else
        arg = NULL;
      i = 0;
      env[i++] = "SHELL=/bin/sh";
      cp = buf;
      s = getzenv("PATH");
      if (s == NULL)
        env[i++] = "PATH=/usr/ucb:/usr/bin:/bin";
      else {
        sprintf(cp, "PATH=%s", s);
        env[i++] = cp;
        cp += strlen(cp) + 1;
      }
      env[i++] = "HOME=/tmp";
      env[i++] = "USER=anonymous";
      s = getzenv("ZCONFIG");
      if (s == NULL)
        s = ZMAILER_ENV_FILE;
      sprintf(cp, "ZCONFIG=%s", s);
      env[i++] = cp;
      s = getzenv("MAILSHARE");
      if (s == NULL)
        s = MAILBIN;
      cp += strlen(cp) + 1;
      sprintf(cp, "MAILSHARE=%s", s);
      env[i++] = cp;
      s = getzenv("MAILBIN");
      if (s == NULL)
        s = MAILSHARE;
      cp += strlen(cp) + 1;
      sprintf(cp, "MAILBIN=%s", s);
      env[i++] = cp;
      env[i] = NULL;

      /* now we can fork off and run the command... */
      if (pipe(in) < 0) {
        sprintf(errormsg,
              "cannot create pipe from \"%s\"", command);
        return 0;
      }
      cp += strlen(cp) + 1;
      sprintf(cp, "%s/bin/%s", s, command);

      if ((pid = fork()) == 0) { /* child */
        environ = (char**) env;
        dup2(in[1],1);
        dup2(in[1],2);
        close(0);
        if (in[0] != 1 && in[0] != 2)
          close(in[0]);
        if (in[1] != 1 && in[1] != 2)
          close(in[1]);
        SIGNAL_IGNORE(SIGINT);
        SIGNAL_IGNORE(SIGHUP);
        SIGNAL_HANDLE(SIGTERM, SIG_DFL);
        /*
         * Note that argv[0] is set to the command we are running.
         * That way, we should get some better error messages, at
         * least more understandable in rejection messages.
         * Some bourne shells may go into restricted mode if the
         * stuff to run contains an 'r'. XX: investigate.
         */
        execl("/bin/sh", "sh", cp, arg, (char *)NULL);
        execl("/sbin/sh", "sh", cp, arg, (char *)NULL);
        write(2, "Cannot exec /bin/sh\n", 20);
        _exit(128);
      } else if (pid < 0) {   /* fork failed */
        sprintf(errormsg, "cannot fork");
        return 0;
      } /* parent */
      close(in[1]);
      errfp = fdopen(in[0], "r");
      /* read any messages from its stdout/err on in[0] */
      cp = errormsg;
      if (fgets(errormsg, sizeof errormsg, errfp) == NULL)
            errormsg[0] = '\0';
      else if ((cp = strchr(errormsg, '\n')) != NULL)
            *cp = '\0';
      wait(&status);
      fclose(errfp);
      close(in[0]);

        if (status & 0177) { /* any signals ? */
        if (cp != errormsg)
          *cp++ = ' ';
        sprintf(cp, "[signal %d", status & 0177);
        if (status & 0200)
          strcat(cp, " (Core dumped)");
        strcat(cp, "]");
        return 0;
        } else if (WEXITSTATUS(status) != 0) {
        if (cp != errormsg)
          *cp++ = ' ';
        sprintf(cp, "[exit status %d]", WEXITSTATUS(status));
        return 0;
        }
      return 1;
}


/* The transient condition is the user's home directory is not available.
   So if the user doesn't exist any more, or if his home directory is
   null, then we "succeed", to try to redeliver the mail. */

int
hold_home(user)
const char *user;
{
      struct Zpasswd *pw;
      struct stat st;

      pw = zgetpwnam(user);
      if (!pw)
        pw = zgetpwnam(user);
      if (!pw) {
        if (errno == 0)      return 1;
        return ranny(2) == 0; /* 30% of the time */
      }
      if (pw->pw_dir == NULL || pw->pw_dir[0] == '\0') return 1;
      if (stat(pw->pw_dir, &st) == 0 &&
          S_ISDIR(st.st_mode)) return 1;
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index