Logo Search packages:      
Sourcecode: batv-milter version File versions  Download package

batv-filter.c

/*
**  Copyright (c) 2007, 2008 Sendmail, Inc. and its suppliers.
**    All rights reserved.
**  Sendmail, Inc. Confidential
**
**  $Id: batv-filter.c,v 1.32 2008/09/04 20:17:50 msk Exp $
*/

#ifndef lint
static char batv_filter_c_id[] = "@(#)$Id: batv-filter.c,v 1.32 2008/09/04 20:17:50 msk Exp $";
#endif /* !lint */

/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <syslog.h>
#include <errno.h>
#include <sysexits.h>
#include <fcntl.h>
#include <stdio.h>
#include <assert.h>
#include <regex.h>
#include <ctype.h>
#include <pwd.h>
#ifdef DEBUG
# include <netdb.h>
#endif /* DEBUG */
#ifndef SOLARIS
# include <paths.h>
#else /* SOLARIS */
# if (SOLARIS >= 21000)
#  include <fenv.h>
# else /* (SOLARIS >= 21000) */
#  include <floatingpoint.h>
# endif /* (SOLARIS >= 21000) */
# include <iso/limits_iso.h>
#endif /* ! SOLARIS */

/* openssl includes */
#include <openssl/sha.h>

/* libsm includes */
#include <sm/string.h>

/* libmilter includes */
#ifndef DEBUG
#include "libmilter/mfapi.h"
#endif /* !DEBUG */

/* batv-filter includes */
#include "batv-filter.h"

/* macros */
#ifndef MIN
# define MIN(x,y) ((x) < (y) ? (x) : (y))
#endif /* ! MIN */

#ifdef SOLARIS
# ifndef INADDR_NONE
#  define INADDR_NONE   (in_addr_t)(-1)
# endif /* INADDR_NONE */
#endif /* SOLARIS */

#ifdef DEBUG
/* DEBUGGING STUFF */
# define MI_SUCCESS     1
# define MI_FAILURE     (-1)
# define SMFIS_CONTINUE 0
# define SMFIS_ACCEPT   1
# define SMFIS_REJECT   2
# define SMFIS_DISCARD  3
# define SMFIS_TEMPFAIL 4
# define sfsistat int
# define SMFICTX  void
# define _SOCK_ADDR     struct sockaddr

int smfi_addheader __P((void *, char *, char *));
int smfi_chgfrom __P((void *, char *, char *));
void *smfi_getpriv __P((void *));
char *smfi_getsymval __P((void *, char *));
int smfi_opensocket __P((int));
void smfi_setconn __P((char *));
void smfi_setdbg __P((int));
void smfi_setpriv __P((void *, void *));
void smfi_setreply __P((void *, char *, char *, char *));

char *smfis_ret[] = {
      "SMFIS_CONTINUE",
      "SMFIS_ACCEPT",
      "SMFIS_REJECT",
      "SMFIS_DISCARD",
      "SMFIS_TEMPFAIL",
};

typedef int bool;

static void *fakepriv;                    /* fake private space */
#endif /* DEBUG */

/*
**  RCPTLIST -- recipient list (old and new)
*/

struct batv_rcptlist
{
      char *            rcpt_orig;        /* original recipient */
      char *            rcpt_new;         /* replacement */
      struct batv_rcptlist * rcpt_next;   /* next node */
};
typedef struct batv_rcptlist * RCPTLIST;

/*
**  CONTEXT -- thread context
*/

struct batv_context
{
      bool        ctx_authed;       /* internal host */
      bool        ctx_internal;           /* internal host */
      char *            ctx_hostname;           /* client hostname */
      char *            ctx_jobid;        /* job ID */
      char *            ctx_sender;       /* envelope sender */
      char *            ctx_newsender;          /* new envelope sender */
      char *            ctx_esmtp;        /* ESMTP stuff */
      RCPTLIST    ctx_rcpts;        /* recipients */
      _SOCK_ADDR  ctx_addr;         /* client address */
};
typedef struct batv_context * CONTEXT;

/*
**  RELIST -- regular expression list
*/

struct batv_relist
{
      regex_t           re_re;                  /* regex_t */
      struct batv_relist * re_next;       /* next node */
};
typedef struct batv_relist * RELIST;

/*
**  HOSTLIST -- hostname/CIDR list
*/

struct batv_hostlist
{
      char *            host_name;        /* regex_t */
      struct in_addr    host_addr;        /* address */
      struct in_addr    host_mask;        /* mask */
      struct batv_hostlist * host_next;   /* next node */
};
typedef struct batv_hostlist * HOSTLIST;

/*
**  DOMLIST -- domains to sign/verify
*/

struct batv_domlist
{
      char *            dom_name;         /* name */
      struct batv_domlist * dom_next;           /* next node */
};
typedef struct batv_domlist * DOMLIST;

/* globals */
bool dolog;
bool addxhdr;
bool notreally;
bool bounceonly;
bool skipauth;
bool setreply;
unsigned int version;
char *progname;
char *key;
char **macros;
char **values;
char hostname[MAXHOSTNAMELEN + 1];
RELIST allow;
HOSTLIST signlist;
DOMLIST domains;

/*
**  ============================= LOCAL STUFF =============================
*/

/*
**  BATV_INITCONTEXT -- initialize a context
**
**  Parameters:
**    None.
**
**  Return value:
**    A new CONTEXT handle, or NULL on error.
*/

static CONTEXT
batv_initcontext(void)
{
      CONTEXT new;

      new = (CONTEXT) malloc(sizeof *new);
      if (new == NULL)
            return NULL;

      memset(new, '\0', sizeof *new);

      return new;
}

#define     CLOBBER(x)  if ((x) != NULL) { \
                        free((x)); \
                        (x) = NULL; \
                  }

/*
**  BATV_CLEANUP -- clean up a filter context
**
**  Parameters:
**    cfc -- CONTEXT handle to be cleaned up
**    msg -- message stuff only, or the whole shebang?
**
**  Return value:
**    None.
*/

static void
batv_cleanup(CONTEXT bfc, bool msg)
{
      RCPTLIST rcpt;
      RCPTLIST next;

      assert(bfc != NULL);

      if (!msg)
            CLOBBER(bfc->ctx_hostname);

      CLOBBER(bfc->ctx_jobid);
      CLOBBER(bfc->ctx_sender);
      CLOBBER(bfc->ctx_newsender);
      CLOBBER(bfc->ctx_esmtp);

      bfc->ctx_authed = FALSE;

      rcpt = bfc->ctx_rcpts;
      while (rcpt != NULL)
      {
            next = rcpt->rcpt_next;

            CLOBBER(rcpt->rcpt_orig);
            CLOBBER(rcpt->rcpt_new);

            free(rcpt);

            rcpt = next;
      }
      bfc->ctx_rcpts = NULL;
}

/*
**  BATV_STRIPBRACKETS -- remove angle brackets from the sender address
**
**  Parameters:
**    addr -- address to be processed
**
**  Return value:
**    None.
*/

static void
batv_stripbrackets(char *addr)
{
      char *p, *q;

      assert(addr != NULL);

      p = addr;
      q = addr + strlen(addr) - 1;

      while (*p == '<' && *q == '>')
      {
            p++;
            *q-- = '\0';
      }

      if (p != addr)
      {
            for (q = addr; *p != '\0'; p++, q++)
                  *q = *p;
            *q = '\0';
      }
}

/*
**  BATV_MKREGEXP -- make a regular expression
**
**  Parameters:
**    str -- input string
**    re -- destination string
**
**  Return value:
**    None.
*/

static void
batv_mkregexp(str, addr, len)
      char *str;
      char *addr;
      size_t len;
{
      char *p;
      char *q;
      char *end;

      assert(str != NULL);
      assert(addr != NULL);

      memset(addr, '\0', len);

      addr[0] = '^';

      end = addr + len - 1;

      for (p = str, q = addr + 1; *p != '\0' && q <= end; p++)
      {
            switch (*p)
            {
              case '*':
                  if (q >= end - 2)
                        return;
                  *q++ = '.';
                  *q++ = '*';
                  break;

              case '.':
                  if (q >= end - 2)
                        return;
                  *q++ = '\\';
                  *q++ = '.';
                  break;

              default:
                  *q++ = *p;
                  break;
            }
      }

      if (q < end)
            *q = '$';
}

/*
**  BATV_ISBLANK -- blank line?
**
**  Parameters:
**    str -- string
**
**  Return value:
**    TRUE iff "str" is either zero-length or contains only spaces.
*/

static bool
batv_isblank(char *str)
{
      char *p;

      assert(str != NULL);

      for (p = str; *p != '\0'; p++)
      {
            if (!isascii(*p) || !isspace(*p))
                  return FALSE;
      }

      return TRUE;
}

/*
**  ============================= DEBUG STUFF =============================
*/

#ifdef DEBUG
int
smfi_chgfrom(void *ctx, char *from, char *esmtp)
{
      printf("smfi_chgfrom(<ctx>, `%s', `%s')\n", from,
             esmtp == NULL ? "(null)" : esmtp);
      return MI_SUCCESS;
}

int
smfi_addheader(void *ctx, char *hdr, char *val)
{
      printf("smfi_addheader(<ctx>, `%s', `%s')\n", hdr, val);
      return MI_SUCCESS;
}

int
smfi_addrcpt(void *ctx, char *rcpt)
{
      printf("smfi_addrcpt(<ctx>, `%s')\n", rcpt);
      return MI_SUCCESS;
}

int
smfi_delrcpt(void *ctx, char *rcpt)
{
      printf("smfi_delrcpt(<ctx>, `%s')\n", rcpt);
      return MI_SUCCESS;
}

int
smfi_opensocket(int param)
{
      printf("smfi_opensocket(%d)\n", param);

      return MI_SUCCESS;
}

void
smfi_setdbg(int param)
{
      printf("smfi_setdbg(%d)\n", param);
}

void
smfi_setconn(char *file)
{
      printf("smfi_setconn(`%s')\n", file);
}

void
smfi_setpriv(void *ctx, void *priv)
{
      fakepriv = priv;
}

void *
smfi_getpriv(void *ctx)
{
      return fakepriv;
}

char *
smfi_getsymval(void *ctx, char *sym)
{
      char *ret;
      size_t l;
      CONTEXT cc;

      l = strlen(sym) + 6 + 1;
      cc = fakepriv;

      printf("smfi_getsymval(<ctx>, `%s')\n", sym);
      ret = malloc(l);
      snprintf(ret, l, "DEBUG-%s", sym);
      return ret;
}
#endif /* DEBUG */

/*
**  ============================= MILTER STUFF =============================
*/

/*
**  MLFI_NEGOTIATE -- option negotiation; for use with milter v2
**
**  Parameters:
**    ctx -- milter context
**
**  Return value:
**    SMFIS_CONTINUE
*/

sfsistat
mlfi_negotiate(SMFICTX *ctx, unsigned long f0, unsigned long f1,
               unsigned long f2, unsigned long f3, unsigned long *pf0,
               unsigned long *pf1, unsigned long *pf2,
               unsigned long *pf3)
{
#ifndef DEBUG
      *pf0 = SMFIF_CHGFROM | SMFIF_ADDHDRS | SMFIF_DELRCPT | SMFIF_ADDRCPT;
      *pf1 = SMFIP_NOHELO | SMFIP_NOHDRS | SMFIP_NOEOH | SMFIP_NOUNKNOWN | 
             SMFIP_NODATA | SMFIP_NOBODY;
#endif /* ! DEBUG */
      return SMFIS_CONTINUE;
}

/*
**  MLFI_CONNECT -- handle a connection announcement
**
**  Parameters:
**    ctx -- milter context
**    hostname -- hostname of the client
**    addr -- address information for the client
**
**  Return value:
**    An SMFIS_* constant.
*/

sfsistat
mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *addr)
{
      HOSTLIST hc;
      CONTEXT bfc;
      char *dot;

      bfc = batv_initcontext();
      if (bfc == NULL)
      {
            if (dolog)
                  syslog(LOG_ERR, "malloc(): %s", strerror(errno));

            return SMFIS_TEMPFAIL;
      }

      bfc->ctx_hostname = strdup(hostname);
      memcpy(&bfc->ctx_addr, addr, sizeof bfc->ctx_addr);

      /* is the client on the "internal" (sign) list? */
      for (hc = signlist; hc != NULL; hc = hc->host_next)
      {
            if (hc->host_name != NULL)
            {
                  /* full hostname */
                  if (strcasecmp(hc->host_name, bfc->ctx_hostname) == 0)
                  {
                        bfc->ctx_internal = TRUE;
                        break;
                  }

                  for (dot = strchr(bfc->ctx_hostname, '.');
                       dot != NULL;
                       dot = strchr(dot + 1, '.'))
                  {
                        if (strcasecmp(hc->host_name, dot) == 0)
                        {
                              bfc->ctx_internal = TRUE;
                              break;
                        }
                  }

                  if (bfc->ctx_internal)
                        break;
            }
            else if (hc->host_addr.s_addr != 0 &&
                     hc->host_mask.s_addr != 0 &&
                     bfc->ctx_addr.sa_family == AF_INET)
            {
                  struct sockaddr_in *sin;

                  sin = (struct sockaddr_in *) &bfc->ctx_addr;

                  if ((sin->sin_addr.s_addr & hc->host_mask.s_addr) ==
                      (hc->host_addr.s_addr & hc->host_mask.s_addr))
                  {
                        bfc->ctx_internal = TRUE;
                        break;
                  }
            }
      }

      (void) smfi_setpriv(ctx, bfc);

      return SMFIS_CONTINUE;
}

/*
**  MLFI_ENVFROM -- process envelope sender
**
**  Parameters:
**    ctx -- milter context
**    args -- MAIL FROM argument vector
**
**  Return value:
**    An SMFIS_* constant.
*/
        
sfsistat
mlfi_envfrom(SMFICTX *ctx, char **args)
{
      bool sign = FALSE;
      int status;
      CONTEXT bfc;
      char *jobid;
      char *domain;
      char *dot;
      RELIST ac;
      DOMLIST dc;

      bfc = smfi_getpriv(ctx);

      if (bfc == NULL)
      {
            if (dolog)
            {
                  syslog(LOG_ERR,
                         "envelope data received before connection information; temp-failing");
            }

            return SMFIS_TEMPFAIL;
      }
      
      batv_cleanup(bfc, TRUE);

      jobid = smfi_getsymval(ctx, "i");
      if (jobid != NULL)
      {
            bfc->ctx_jobid = strdup(jobid);
            if (bfc->ctx_jobid == NULL)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR, "strdup(): %s",
                               strerror(errno));
                  }

                  return SMFIS_TEMPFAIL;
            }
      }
      else
      {
            bfc->ctx_jobid = strdup(JOBIDUNKNOWN);
      }

      /* arrange to skip verification of authenticated senders */
      if (skipauth && smfi_getsymval(ctx, "{auth_authen}") != NULL)
            bfc->ctx_authed = TRUE;

      /* copy the envelope sender and clean it */
      bfc->ctx_sender = strdup(args[0]);
      if (bfc->ctx_sender == NULL)
      {
            if (dolog)
            {
                  syslog(LOG_ERR, "strdup(): %s",
                         strerror(errno));
            }

            return SMFIS_TEMPFAIL;
      }
      batv_stripbrackets(bfc->ctx_sender);
      domain = strchr(bfc->ctx_sender, '@');

      if (domain == NULL)
            return SMFIS_CONTINUE;
      else
            domain++;

      /* apply allow list to envelope sender */
      for (ac = allow; ac != NULL; ac = ac->re_next)
      {
            status = regexec(&ac->re_re, bfc->ctx_sender, 0, NULL, 0);
            if (status == 0)
            {
                  return SMFIS_CONTINUE;
            }
            else if (status != REG_NOMATCH)
            {
                  if (dolog)
                  {
                        char errbuf[BUFRSZ + 1];

                        memset(errbuf, '\0', sizeof errbuf);
                        (void) regerror(status, &ac->re_re, errbuf,
                                        BUFRSZ);
                        syslog(LOG_ERR, "%s: regexec(): %s",
                               bfc->ctx_jobid, errbuf);
                  }

                  return SMFIS_TEMPFAIL;
            }
      }

      /* is it a domain we care about? */
      if (domains == NULL)
      {
            sign = TRUE;
      }
      else
      {
            sign = FALSE;
            for (dc = domains; dc != NULL; dc = dc->dom_next)
            {
                  /* full hostname */
                  if (strcasecmp(dc->dom_name, domain) == 0)
                  {
                        sign = TRUE;
                        break;
                  }

                  for (dot = strchr(domain, '.');
                       dot != NULL;
                       dot = strchr(dot + 1, '.'))
                  {
                        if (strcasecmp(dc->dom_name, dot) == 0)
                        {
                              sign = TRUE;
                              break;
                        }
                  }

                  if (sign)
                        break;
            }
      }

      /* now test macros, if any */
      if (macros != NULL && !sign)
      {
            bool done = FALSE;
            int n;
            char *val;
            char *p;
            char name[BUFRSZ + 1];
            char vals[BUFRSZ + 1];

            for (n = 0; !done && macros[n] != NULL; n++)
            {
                  /* retrieve the macro */
                  snprintf(name, sizeof name, "{%s}", macros[n]);
                  val = smfi_getsymval(ctx, name);

                  /* short-circuit if the macro's not set */
                  if (val == NULL)
                        continue;

                  /* macro set and we don't care about the value */
                  if (val != NULL && values[n] == NULL)
                  {
                        sign = TRUE;
                        break;
                  }

                  sm_strlcpy(vals, values[n], sizeof vals);

                  for (p = strtok(vals, "|");
                       !done && p != NULL;
                       p = strtok(NULL, "|"))
                  {
                        if (strcasecmp(val, p) == 0)
                        {
                              sign = TRUE;
                              done = TRUE;
                        }
                  }
            }
      }

      /* nope, just continue */
      if (!sign)
            return SMFIS_CONTINUE;

      /* sign for internal/authenticated clients */
      if (bfc->ctx_internal || bfc->ctx_authed)
      {
            int expire;
            time_t now;
            SHA_CTX sha1;
            char buf[BUFRSZ + 1];
            unsigned char digest[SHA1_DIGEST_SIZE];
            char sender[BUFRSZ + 1];

            /* continue if already signed */
            if (strncmp(bfc->ctx_sender, "prvs=", 5) == 0)
                  return SMFIS_CONTINUE;

            (void) time(&now);
            expire = ((now / 86400) + SIGLIFETIME) % 1000;
            
            snprintf(buf, sizeof buf, "%1d%03d%s%s", version, expire,
                     bfc->ctx_sender, key);

            SHA1_Init(&sha1);
            SHA1_Update(&sha1, buf, strlen(buf));
            SHA1_Final(digest, &sha1);

            snprintf(sender, sizeof sender, "prvs=%1d%03d%02X%02X%02X=%s",
                     version, expire, digest[0], digest[1], digest[2],
                     bfc->ctx_sender);

            bfc->ctx_newsender = strdup(sender);
            if (bfc->ctx_newsender == NULL)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR, "%s: strdup(): %s",
                               bfc->ctx_jobid, strerror(errno));
                  }

                  return SMFIS_TEMPFAIL;
            }
      }

      /* if we have a new sender, we have to replace the ESMTP stuff later */
      if (bfc->ctx_newsender != NULL)
      {
            int c;
            char esmtp[BUFRSZ + 1];

            memset(esmtp, '\0', sizeof esmtp);

            for (c = 1; args[c] != NULL; c++)
            {
                  if (c > 1)
                        sm_strlcat(esmtp, " ", sizeof esmtp);

                  sm_strlcat(esmtp, args[c], sizeof esmtp);
            }

            bfc->ctx_esmtp = strdup(esmtp);
            if (bfc->ctx_esmtp == NULL)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR, "%s: strdup(): %s",
                               bfc->ctx_jobid, strerror(errno));
                  }

                  return SMFIS_TEMPFAIL;
            }
      }
      
      return SMFIS_CONTINUE;
}

/*
**  MLFI_ENVRCPT -- envelope recipient
**
**  Parameters:
**    ctx -- milter context
**    args -- RCPT TO argument vector
**
**  Return value:
**    An SMFIS_* constant.
*/

sfsistat
mlfi_envrcpt(SMFICTX *ctx, char **args)
{
      bool verify;
      int status;
      CONTEXT bfc;
      char *domain;
      char *dot;
      DOMLIST dc;
      RELIST ac;
      char addr[BUFRSZ + 1];

      bfc = smfi_getpriv(ctx);

      if (bfc == NULL)
      {
            if (dolog)
            {
                  syslog(LOG_ERR,
                         "envelope data received before connection information; temp-failing");
            }

            return SMFIS_TEMPFAIL;
      }

      sm_strlcpy(addr, args[0], sizeof addr);
      batv_stripbrackets(addr);
      domain = strchr(addr, '@');

      if (domain == NULL)
            return SMFIS_CONTINUE;
      else
            domain++;
      
      /* apply allow list to envelope recipient */
      for (ac = allow; ac != NULL; ac = ac->re_next)
      {
            status = regexec(&ac->re_re, addr, 0, NULL, 0);
            if (status == 0)
            {
                  return SMFIS_CONTINUE;
            }
            else if (status != REG_NOMATCH)
            {
                  if (dolog)
                  {
                        char errbuf[BUFRSZ + 1];

                        memset(errbuf, '\0', sizeof errbuf);
                        (void) regerror(status, &ac->re_re, errbuf,
                                        BUFRSZ);
                        syslog(LOG_ERR, "%s: regexec(): %s",
                               bfc->ctx_jobid, errbuf);
                  }

                  return SMFIS_TEMPFAIL;
            }
      }

      /* is it a domain we care about? */
      if (domains == NULL)
      {
            verify = TRUE;
      }
      else
      {
            verify = FALSE;
            for (dc = domains; dc != NULL; dc = dc->dom_next)
            {
                  /* full hostname */
                  if (strcasecmp(dc->dom_name, domain) == 0)
                  {
                        verify = TRUE;
                        break;
                  }

                  for (dot = strchr(domain, '.');
                       dot != NULL;
                       dot = strchr(dot + 1, '.'))
                  {
                        if (strcasecmp(dc->dom_name, dot) == 0)
                        {
                              verify = TRUE;
                              break;
                        }
                  }

                  if (verify)
                        break;
            }
      }

      /* now test macros, if any */
      if (macros != NULL && !verify)
      {
            bool done = FALSE;
            int n;
            char *val;
            char *p;
            char name[BUFRSZ + 1];
            char vals[BUFRSZ + 1];

            for (n = 0; !done && macros[n] != NULL; n++)
            {
                  /* retrieve the macro */
                  snprintf(name, sizeof name, "{%s}", macros[n]);
                  val = smfi_getsymval(ctx, name);

                  /* short-circuit if the macro's not set */
                  if (val == NULL)
                        continue;

                  /* macro set and we don't care about the value */
                  if (val != NULL && values[n] == NULL)
                  {
                        verify = TRUE;
                        break;
                  }

                  sm_strlcpy(vals, values[n], sizeof vals);

                  for (p = strtok(vals, "|");
                       !done && p != NULL;
                       p = strtok(NULL, "|"))
                  {
                        if (strcasecmp(val, p) == 0)
                        {
                              verify = TRUE;
                              done = TRUE;
                        }
                  }
            }
      }

      /* nope, just continue */
      if (!verify)
            return SMFIS_CONTINUE;

      /* skip mail from internal hosts and auth'd clients */
      if (bfc->ctx_internal || bfc->ctx_authed)
            return SMFIS_CONTINUE;

      /* if it's unsigned, complain */
      if (strncmp(addr, "prvs=", 5) != 0)
      {
            /* only complain if it's a bounce, or we're being fascist */
            if (strlen(bfc->ctx_sender) == 0 || !bounceonly)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s: BATV signature not present in `%s'",
                               bfc->ctx_jobid, addr);
                  }

                  if (!notreally)
                  {
                        if (setreply &&
                            smfi_setreply(ctx, BATV_REJECTSMTP,
                                          BATV_REJECTESC,
                                          "BATV signature missing") == MI_FAILURE)
                        {
                              if (dolog)
                              {
                                    syslog(LOG_ERR,
                                           "%s: smfi_setreply() failed",
                                           bfc->ctx_jobid);
                              }
                        }

                        return SMFIS_REJECT;
                  }
            }
      }
      /* if it's signed, verify */
      else
      {
            int daynum;
            int sigexpire;
            int sigversion;
            time_t now;
            char *sig = &addr[5];
            char *eq;
            RCPTLIST rcpt;
            SHA_CTX sha1;
            char orcpt[BUFRSZ + 1];
            char tmp[BUFRSZ + 1];
            char buf[BUFRSZ + 1];
            unsigned char digest[SHA1_DIGEST_SIZE];

            rcpt = (RCPTLIST) malloc(sizeof *rcpt);
            if (rcpt == NULL)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR, "malloc(): %s",
                               strerror(errno));
                  }

                  return SMFIS_TEMPFAIL;
            }

            rcpt->rcpt_orig = strdup(addr);
            if (rcpt->rcpt_orig == NULL)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR, "strdup(): %s",
                               strerror(errno));
                  }

                  free(rcpt);

                  return SMFIS_TEMPFAIL;
            }

            eq = strchr(sig, '=');
            if (eq == NULL)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s: BATV syntax error in `%s'",
                               bfc->ctx_jobid, addr);
                  }

                  free(rcpt->rcpt_orig);
                  free(rcpt);

                  return SMFIS_TEMPFAIL;
            }

            sm_strlcpy(orcpt, eq + 1, sizeof orcpt);

            if (!isdigit(sig[0]) ||
                !isdigit(sig[1]) ||
                !isdigit(sig[2]) ||
                !isdigit(sig[3]) ||
                !isxdigit(sig[4]) ||
                !isxdigit(sig[5]) ||
                !isxdigit(sig[6]) ||
                !isxdigit(sig[7]) ||
                !isxdigit(sig[8]) ||
                !isxdigit(sig[9]))
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s: BATV syntax error in `%s'",
                               bfc->ctx_jobid, addr);
                  }

                  free(rcpt->rcpt_orig);
                  free(rcpt);

                  return SMFIS_TEMPFAIL;
            }

            sigversion = sig[0] - '0';
            if (sigversion != version)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s: BATV version mismatch in `%s'",
                               bfc->ctx_jobid, addr);
                  }

                  free(rcpt->rcpt_orig);
                  free(rcpt);

                  return SMFIS_TEMPFAIL;
            }

            sigexpire = (sig[1] - '0') * 100 +
                        (sig[2] - '0') * 10 +
                        (sig[3] - '0');

            memset(buf, '\0', sizeof buf);
            buf[0] = sig[0];
            buf[1] = sig[1];
            buf[2] = sig[2];
            buf[3] = sig[3];
            sm_strlcat(buf, orcpt, sizeof buf);
            sm_strlcat(buf, key, sizeof buf);

            SHA1_Init(&sha1);
            SHA1_Update(&sha1, buf, strlen(buf));
            SHA1_Final(digest, &sha1);

            snprintf(tmp, sizeof tmp,
                     "prvs=%1d%03d%02X%02X%02X=%s",
                     sigversion, sigexpire, digest[0], digest[1],
                     digest[2], orcpt);

            if (strcmp(tmp, addr) != 0)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s: BATV validation failed in `%s'",
                               bfc->ctx_jobid, addr);
                  }

                  if (!notreally)
                  {
                        free(rcpt->rcpt_orig);
                        free(rcpt);

                        if (setreply &&
                            smfi_setreply(ctx, BATV_REJECTSMTP,
                                          BATV_REJECTESC,
                                          "BATV signature validation failed") == MI_FAILURE)
                        {
                              if (dolog)
                              {
                                    syslog(LOG_ERR,
                                           "%s: smfi_setreply() failed",
                                           bfc->ctx_jobid);
                              }
                        }

                        return SMFIS_REJECT;
                  }
            }

            (void) time(&now);
            daynum = (now / 86400) % 1000;

            /* check expiration */
            if (sigexpire < SIGLIFETIME && daynum >= 1000 - SIGLIFETIME)
                  daynum -= 1000;
            if (daynum > sigexpire)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s: BATV signature expired in `%s'",
                               bfc->ctx_jobid, addr);
                  }

                  if (!notreally)
                  {
                        free(rcpt->rcpt_orig);
                        free(rcpt);

                        if (setreply &&
                            smfi_setreply(ctx, BATV_REJECTSMTP,
                                          BATV_REJECTESC,
                                          "BATV signature expired") == MI_FAILURE)
                        {
                              if (dolog)
                              {
                                    syslog(LOG_ERR,
                                           "%s: smfi_setreply() failed",
                                           bfc->ctx_jobid);
                              }
                        }

                        return SMFIS_REJECT;
                  }
            }

            /* new recipient is the original recipient */
            rcpt->rcpt_new = strdup(orcpt);
            if (rcpt->rcpt_new == NULL)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR, "%s: strdup(): %s",
                              bfc->ctx_jobid, strerror(errno));
                  }

                  free(rcpt->rcpt_orig);
                  free(rcpt);

                  return SMFIS_TEMPFAIL;
            }

            rcpt->rcpt_next = bfc->ctx_rcpts;
            bfc->ctx_rcpts = rcpt;
      }

      return SMFIS_CONTINUE;
}

/*
**  MLFI_EOM -- end of message
**
**  Parameters:
**    ctx -- milter context
**
**  Return value:
**    SMFIS_CONTINUE
*/

sfsistat
mlfi_eom(SMFICTX *ctx)
{
      CONTEXT bfc;
      RCPTLIST rcpt;

      bfc = smfi_getpriv(ctx);

      if (bfc->ctx_newsender != NULL)
      {
            if (notreally)
            {
                  syslog(LOG_INFO,
                         "%s suppressed sender change from `%s' to `%s'",
                         bfc->ctx_jobid, bfc->ctx_sender,
                         bfc->ctx_newsender);
            }
            else if (smfi_chgfrom(ctx, bfc->ctx_newsender,
                                  bfc->ctx_esmtp) != MI_SUCCESS)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s sender change to `%s' failed",
                               bfc->ctx_jobid, bfc->ctx_newsender);
                  }

                  return SMFIS_TEMPFAIL;
            }
      }

      for (rcpt = bfc->ctx_rcpts; rcpt != NULL; rcpt = rcpt->rcpt_next)
      {
            assert(rcpt->rcpt_orig != NULL);
            assert(rcpt->rcpt_new != NULL);

            if (notreally)
            {
                  syslog(LOG_INFO,
                         "%s suppressed recipient change from `%s' to `%s'",
                         bfc->ctx_jobid, rcpt->rcpt_orig,
                         rcpt->rcpt_new);
                  continue;
            }

            if (smfi_delrcpt(ctx, rcpt->rcpt_orig) != MI_SUCCESS)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s recipient delete of `%s' failed",
                               bfc->ctx_jobid, rcpt->rcpt_orig);
                  }

                  return SMFIS_TEMPFAIL;
            }

            if (smfi_addrcpt(ctx, rcpt->rcpt_new) != MI_SUCCESS)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR,
                               "%s recipient add of `%s' failed",
                               bfc->ctx_jobid, rcpt->rcpt_new);
                  }

                  return SMFIS_TEMPFAIL;
            }
      }

      if (addxhdr)
      {
            char xfhdr[MAXHEADER + 1];

            memset(xfhdr, '\0', sizeof xfhdr);

            snprintf(xfhdr, MAXHEADER, "%s v%s %s %s", BATV_PRODUCT,
                     BATV_VERSION, hostname,
                     bfc->ctx_jobid != NULL ? bfc->ctx_jobid
                                            : JOBIDUNKNOWN);

            if (smfi_addheader(ctx, XHEADERNAME, xfhdr) != MI_SUCCESS)
            {
                  if (dolog)
                  {
                        syslog(LOG_ERR, "%s \"%s\" header add failed",
                               bfc->ctx_jobid, XHEADERNAME);
                  }

                  return SMFIS_TEMPFAIL;
            }
      }

      return SMFIS_CONTINUE;
}

/*
**  MLFI_CLOSE -- shut down a connection
**
**  Parameters:
**    ctx -- milter context
**
**  Return value:
**    SMFIS_CONTINUE
*/

sfsistat
mlfi_close(SMFICTX *ctx)
{
      CONTEXT bfc;

      bfc = smfi_getpriv(ctx);

      if (bfc != NULL)
      {
            batv_cleanup(bfc, FALSE);

            free(bfc);

            smfi_setpriv(ctx, NULL);
      }

      return SMFIS_CONTINUE;
}

/*
**  ============================= GENERAL STUFF =============================
*/

/*
**  BATV_DEBUG -- debugging code; simulates libmilter calls
**
**  Parameters:
**    None.
**
**  Return value:
**    None.
*/

#ifdef DEBUG
int
batv_debug(void)
{
      bool done;
      int status;
      size_t len;
      time_t now;
      char *p;
      char *env[2];
      char tmpblock[4096];
      char data[513];
      char block[4096];

      time(&now);
      srandom(now);

      memset(data, '\0', sizeof data);
      memset(tmpblock, '\0', sizeof tmpblock);

      for (;;)
      {
            if (fgets(data, 512, stdin) == NULL)
                  return 1;

            for (p = data; *p != '\0'; p++)
            {
                  if (*p == '\r' || *p == '\n')
                  {
                        *p = '\0';
                        break;
                  }
            }

            if (strcmp(data, ".") == 0)
                  break;

            env[0] = &data[1];
            env[1] = NULL;

            if (data[0] == 'C')
            {
                  struct hostent *h;
                  struct sockaddr_in sin;

                  h = gethostbyname(&data[1]);
                  if (h == NULL)
                  {
                        printf("gethostbyname(\"%s\") failed\n",
                               &data[1]);
                        return 1;
                  }
                  sin.sin_family = AF_INET;
                  sin.sin_port = htons(time(NULL) % 65536);
                  memcpy(&sin.sin_addr.s_addr, h->h_addr,
                         sizeof sin.sin_addr.s_addr);

                  status = mlfi_connect(NULL, &data[1],
                                        (_SOCK_ADDR *) &sin);
                  printf("mlfi_connect(NULL, `%s', `%s') returns %s\n",
                         &data[1], inet_ntoa(sin.sin_addr),
                         smfis_ret[status]);
            }
            else if (data[0] == 'F')
            {
                  status = mlfi_envfrom(NULL, env);
                  printf("mlfi_envfrom(NULL, `%s') returns %s\n", env[0],
                               smfis_ret[status]);
            }
            else if (data[0] == 'R')
            {
                  status = mlfi_envrcpt(NULL, env);
                  printf("mlfi_envrcpt(NULL, `%s') returns %s\n", env[0],
                               smfis_ret[status]);
            }
            else
            {
                  printf("?\n");
                  continue;
            }
            if (status != SMFIS_CONTINUE)
                  return 0;
      }

      done = FALSE;
      while (!done)
      {
            len = fread(block, 1, sizeof block, stdin);

            if (len < sizeof block)
                  done = TRUE;
      }

      status = mlfi_eom(NULL);
      printf("mlfi_eom(NULL) returns %s\n", smfis_ret[status]);
      return 0;
}
#endif /* DEBUG */

/*
**  USAGE -- usage message
**
**  Parameters:
**    None.
**
**  Return value:
**    EX_USAGE
*/

int
usage(void)
{
      fprintf(stderr, "%s: usage: %s -p socketfile -k key [options]\n"
                      "\t-a allowaddrs  \tfile containing addresses to pass\n"
                      "\t-A             \tskip mail from authenticated clients\n"
                      "\t-d domainlist  \tdomain(s) to verify\n"
                      "\t-D debuglevel  \tset milter debug level\n"
                      "\t-f             \tdon't fork-and-exit\n"
                      "\t-i internallist\tdomain(s) to verify\n"
                      "\t-l             \tlog activity to system log\n"
                      "\t-n             \tdon't actually do any filtering\n"
                      "\t-P pidfile     \tfile to which to write pid\n"
                      "\t-S             \trequest meaningful SMTP replies when rejecting\n"
                  "\t-u userid      \tchange to specified userid\n"
                      "\t-V             \tprint version and terminate\n",
              progname, progname);
      return EX_USAGE;
}

#ifndef DEBUG
/*
**  smfilter -- the milter module description
*/

struct smfiDesc smfilter =
{
      "Sendmail BATV Filter", /* filter name */
      SMFI_VERSION,           /* version code -- do not change */
      SMFIF_ADDHDRS|SMFIF_CHGFROM|SMFIF_DELRCPT|SMFIF_ADDRCPT, /* flags */
      mlfi_connect,           /* connection info filter */
      NULL,             /* SMTP HELO command filter */
      mlfi_envfrom,           /* envelope sender filter */
      mlfi_envrcpt,           /* envelope recipient filter */
      NULL,             /* header filter */
      NULL,             /* end of headers */
      NULL,             /* body block filter */
      mlfi_eom,         /* end of message */
      NULL,             /* message aborted */
      mlfi_close,       /* shutdown */
      NULL,             /* unknown commands */
      NULL,             /* DATA command */
      mlfi_negotiate          /* option negotiation */
};
#endif /* ! DEBUG */

/*
**  MAIN -- program mainline
**
**  Parameters:
**    The usual.
**
**  Return value:
**    Exit status.
*/

int
main(int argc, char **argv)
{
      bool dofork = TRUE;
      int c;
      int dbglevel;
#ifndef DEBUG
      int devnull;
#endif /* ! DEBUG */
      size_t rlen;
      FILE *f;
      char *p;
      char *conn = NULL;
      char *user = NULL;
      char *domainlist = NULL;
      char *ilist = NULL;
      char *pidfile = NULL;
      char *allowlist = NULL;
      char *macrolist = NULL;
      char *keyfile = NULL;
      struct stat s;

      progname = (p = strrchr(argv[0], '/')) == NULL ? argv[0] : p + 1;

      addxhdr = FALSE;
      notreally = FALSE;
      bounceonly = TRUE;
      skipauth = FALSE;
      setreply = FALSE;
      key = NULL;
      allow = NULL;
      signlist = NULL;
      macros = NULL;
      values = NULL;
      version = 0;

      memset(hostname, '\0', sizeof hostname);
      gethostname(hostname, MAXHOSTNAMELEN);

      /* Process command line options */
      while ((c = getopt(argc, argv, CMDLINEOPTS)) != -1)
      {
            switch (c)
            {
              case 'a':
                  if (allowlist != NULL)
                  {
                        fprintf(stderr,
                                "%s: multiple use of -%c not allowed\n",
                                progname, c);
                        return EX_USAGE;
                  }
                  allowlist = optarg;
                  break;

              case 'A':
                  skipauth = TRUE;
                  break;

              case 'b':
                  bounceonly = FALSE;
                  break;

              case 'd':
                  if (domainlist != NULL)
                  {
                        fprintf(stderr,
                                "%s: multiple use of -%c not allowed\n",
                                progname, c);
                        return EX_USAGE;
                  }
                  domainlist = optarg;
                  break;

              case 'D':
                  dbglevel = (int) strtol(optarg, &p, 10);
                  if (*p != '\0')
                  {
                        fprintf(stderr,
                                "%s: invalid debug level `%s'\n",
                                progname, optarg);
                        return EX_USAGE;
                  }
                  smfi_setdbg(dbglevel);
                  break;

              case 'f':
                  dofork = FALSE;
                  break;

              case 'h':
                  addxhdr = TRUE;
                  break;

              case 'i':
                  if (ilist != NULL)
                  {
                        fprintf(stderr,
                                "%s: multiple use of -%c not allowed\n",
                                progname, c);
                        return EX_USAGE;
                  }
                  ilist = optarg;
                  break;

              case 'k':
                  if (keyfile != NULL)
                  {
                        fprintf(stderr,
                                "%s: multiple use of -%c not allowed\n",
                                progname, c);
                        return EX_USAGE;
                  }
                  keyfile = optarg;
                  break;

              case 'l':
                  dolog = TRUE;
                  break;

              case 'M':
                  macrolist = optarg;
                  break;

              case 'n':
                  notreally = TRUE;
                  break;

              case 'p':
                  if (conn != NULL)
                  {
                        fprintf(stderr,
                                "%s: multiple use of -%c not allowed\n",
                                progname, c);
                        return EX_USAGE;
                  }
                  conn = optarg;
                  break;

              case 'P':
                  if (pidfile != NULL)
                  {
                        fprintf(stderr,
                                "%s: multiple use of -%c not allowed\n",
                                progname, c);
                        return EX_USAGE;
                  }
                  pidfile = optarg;
                  break;

              case 'S':
                  setreply = TRUE;
                  break;

              case 'u':
                  if (user != NULL)
                  {
                        fprintf(stderr,
                                "%s: multiple use of -%c not allowed\n",
                                progname, c);
                        return EX_USAGE;
                  }
                  user = optarg;
                  break;

              case 'V':
                  fprintf(stdout, "%s: %s v%s\n", progname,
                          BATV_PRODUCT, BATV_VERSION);
                  return 0;

              default:
                  return usage();
            }
      }

      /* conn is required */
      if (conn == NULL || keyfile == NULL)
            return usage();

      /* load the key */
      f = fopen(keyfile, "r");
      if (f == NULL)
      {
            fprintf(stderr, "%s: %s: fopen(): %s\n", progname, keyfile,
                    strerror(errno));
            return EX_UNAVAILABLE;
      }
      else if (fstat(fileno(f), &s) == -1)
      {
            fprintf(stderr, "%s: %s: fstat(): %s\n", progname, keyfile,
                    strerror(errno));
            fclose(f);
            return EX_UNAVAILABLE;
      }
      else if (s.st_size == 0)
      {
            fprintf(stderr, "%s: %s: empty file\n", progname, keyfile);
            fclose(f);
            return EX_UNAVAILABLE;
      }

      key = (char *) malloc(s.st_size);
      if (key == NULL)
      {
            fprintf(stderr, "%s: malloc(): %s\n", progname,
                    strerror(errno));
            fclose(f);
            return EX_UNAVAILABLE;
      }
      rlen = fread(key, 1, s.st_size, f);
      if (rlen < s.st_size)
      {
            if (ferror(f))
            {
                  fprintf(stderr, "%s: %s: fread(): %s\n", progname,
                          keyfile, strerror(errno));
            }
            else if (feof(f))
            {
                  fprintf(stderr,
                          "%s: %s: fread(): Unexpected end-of-file\n",
                          progname, keyfile);
            }

            fclose(f);
            return EX_UNAVAILABLE;
      }

      /* chop the key at the first newline found */
      for (p = key, c = 0; c < s.st_size; p++, c++)
      {
            if (*p == '\n')
            {
                  *p = '\0';
                  break;
            }
      }

      fclose(f);

      /* load allow list */
      if (allowlist != NULL)
      {
            int status;
            RELIST new;
            char buf[BUFRSZ + 1];
            char restr[BUFRSZ + 1];

            f = fopen(allowlist, "r");
            if (f == NULL)
            {
                  fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
                          allowlist, strerror(errno));
                  free(key);
                  return EX_UNAVAILABLE;
            }

            memset(buf, '\0', sizeof buf);
            while (fgets(buf, sizeof buf - 1 ,f) != NULL)
            {
                  for (p = buf; *p != '\0'; p++)
                  {
                        if (*p == '\n' || *p == '#')
                        {
                              *p = '\0';
                              break;
                        }
                  }

                  if (batv_isblank(buf))
                        continue;

                  batv_mkregexp(buf, restr, sizeof restr);

                  new = (RELIST) malloc(sizeof *new);
                  if (new == NULL)
                  {
                        fprintf(stderr, "%s: malloc(): %s\n",
                                progname, strerror(errno));
                        fclose(f);
                        return EX_UNAVAILABLE;
                  }

                  status = regcomp(&new->re_re, restr, REG_ICASE);
                  if (status != 0)
                  {
                        char errbuf[BUFRSZ + 1];

                        memset(errbuf, '\0', sizeof errbuf);
                        (void) regerror(status, &new->re_re, errbuf,
                                        sizeof errbuf);

                        fprintf(stderr, "%s: regcomp(): %s\n",
                                progname, errbuf);
                        fclose(f);
                        return EX_UNAVAILABLE;
                  }

                  new->re_next = allow;
                  allow = new;
            }

            if (ferror(f))
            {
                  fprintf(stderr, "%s: %s: fgets(): %s\n", progname,
                          allowlist, strerror(errno));
                  free(key);
                  fclose(f);
                  return EX_UNAVAILABLE;
            }

            fclose(f);
      }

      /* load sign list */
      if (ilist != NULL)
      {
            HOSTLIST new;
            struct in_addr addr;
            char *slash;
            char buf[BUFRSZ + 1];

            f = fopen(ilist, "r");
            if (f == NULL)
            {
                  fprintf(stderr, "%s: %s: fopen(): %s\n", progname,
                          ilist, strerror(errno));
                  free(key);
                  return EX_UNAVAILABLE;
            }

            memset(buf, '\0', sizeof buf);
            while (fgets(buf, sizeof buf - 1 ,f) != NULL)
            {
                  for (p = buf; *p != '\0'; p++)
                  {
                        if (*p == '\n' || *p == '#')
                        {
                              *p = '\0';
                              break;
                        }
                  }

                  if (batv_isblank(buf))
                        continue;

                  new = (HOSTLIST) malloc(sizeof *new);
                  if (new == NULL)
                  {
                        fprintf(stderr, "%s: malloc(): %s\n",
                                progname, strerror(errno));
                        fclose(f);
                        return EX_UNAVAILABLE;
                  }
                  memset(new, '\0', sizeof *new);

                  slash = strchr(buf, '/');
                  if (slash != NULL)
                        *slash = '\0';

                  addr.s_addr = inet_addr(buf);
                  if (addr.s_addr == INADDR_NONE)
                  {
                        new->host_name = strdup(buf);
                  }
                  else
                  {
                        if (slash == NULL)
                        {
                              new->host_addr.s_addr = addr.s_addr;
                              new->host_mask.s_addr = INADDR_BROADCAST;
                        }
                        else
                        {
                              int bits;
                              char *q;
                              struct in_addr mask;

                              mask.s_addr = 0;

                              bits = strtoul(slash + 1, &q, 10);
                              if (*q != '\0')
                              {
                                    mask.s_addr = inet_addr(slash + 1);
                                    if (mask.s_addr == INADDR_NONE)
                                    {
                                          fprintf(stderr,
                                                  "%s: %s: invalid CIDR specification `%s/%s'\n",
                                                  progname,
                                                  ilist, buf,
                                                  slash + 1);
                                    }
                                    fclose(f);
                                    return EX_UNAVAILABLE;
                              }
                              else
                              {
                                    int n;

                                    for (n = 31;
                                         bits > 0;
                                         bits--, n--)
                                          mask.s_addr |= htonl(1 << n);
                              }

                              new->host_addr.s_addr = addr.s_addr;
                              new->host_mask.s_addr = mask.s_addr;
                        }
                  }

                  new->host_next = signlist;
                  signlist = new;
            }

            if (ferror(f))
            {
                  fprintf(stderr, "%s: %s: fgets(): %s\n", progname,
                          ilist, strerror(errno));
                  free(key);
                  fclose(f);
                  return EX_UNAVAILABLE;
            }

            fclose(f);
      }
      else
      {
            HOSTLIST new;

            new = (HOSTLIST) malloc(sizeof *new);
            if (new == NULL)
            {
                  fprintf(stderr, "%s: malloc(): %s\n",
                          progname, strerror(errno));
                  fclose(f);
                  return EX_UNAVAILABLE;
            }

            new->host_name = strdup(LOCALHOST);
            new->host_addr.s_addr = htonl(INADDR_LOOPBACK);
            new->host_mask.s_addr = htonl(INADDR_BROADCAST);
            new->host_next = NULL;

            signlist = new;
      }

      /* store domain list */
      if (domainlist != NULL)
      {
            DOMLIST new;

            for (p = strtok(domainlist, ",");
                 p != NULL;
                 p = strtok(NULL, ","))
            {
                  new = (DOMLIST) malloc(sizeof *new);
                  if (new == NULL)
                  {
                        fprintf(stderr, "%s: malloc(): %s\n",
                                progname, strerror(errno));
                        fclose(f);
                        return EX_UNAVAILABLE;
                  }

                  new->dom_name = p;
                  new->dom_next = domains;
                  domains = new;
            }
      }

      /* store macro list */
      if (macrolist != NULL)
      {
            int n = 1;
            char *p;

            for (p = macrolist; *p != '\0'; p++)
            {
                  if (*p == ',')
                        n++;
            }

            macros = (char **) malloc((n + 1) * sizeof(char *));
            values = (char **) malloc((n + 1) * sizeof(char *));

            if (macros == NULL || values == NULL)
            {
                  fprintf(stderr, "%s: malloc(): %s\n",
                          progname, strerror(errno));
                  return EX_UNAVAILABLE;
            }

            n = 0;
            for (p = strtok(macrolist, ",");
                 p != NULL;
                 p = strtok(NULL, ","))
            {
                  macros[n] = p;
                  values[n] = strchr(p, '=');
                  if (values[n] != NULL)
                  {
                        *(values[n]) = '\0';
                        values[n] += 1;
                  }
                  n++;
            }
            macros[n] = NULL;
            values[n] = NULL;
      }

      /* Change user if appropriate */
      if (user != NULL)
      {
            struct passwd* pw;

            pw = getpwnam(user);
            if (pw == NULL)
            {
                  uid_t uid;

                  uid = atoi(user);
                  if (uid != 0 && uid != LONG_MIN && uid != LONG_MAX)
                        pw = getpwuid(uid);

                  if (pw == NULL)
                  {
                        fprintf(stderr, "%s: no such user `%s'\n",
                              progname, user);
                        return EX_DATAERR;
                  }
            }

            (void) endpwent();

            if (setuid(pw->pw_uid) != 0)
            {
                  fprintf(stderr, "%s: setuid(): %s\n", progname,
                        strerror(errno));
                  return EX_NOPERM;
            }
      }
 
      /* Activate logging */
      if (dolog)
#ifdef LOG_MAIL
            openlog(progname, LOG_PID, LOG_MAIL);
#else /* LOG_MAIL */
            openlog(progname, LOG_PID);
#endif /* LOG_MAIL */

      (void) smfi_setconn(conn);

#ifndef DEBUG
      /* register with the milter interface */
      if (smfi_register(smfilter) == MI_FAILURE)
      {
            if (dolog)
                  syslog(LOG_ERR, "smfi_register() failed");

            fprintf(stderr, "%s: smfi_register() failed\n", progname);

            return EX_UNAVAILABLE;
      }
#endif /* ! DEBUG */

      /* try to establish the socket */
      if (smfi_opensocket(TRUE) == MI_FAILURE)
      {
            if (dolog)
                  syslog(LOG_ERR, "smfi_opensocket() failed");

            fprintf(stderr, "%s: smfi_opensocket() failed\n", progname);

            return EX_UNAVAILABLE;
      }

      if (dofork)
      {
            int save_errno;
            pid_t pid;

            pid = fork();
            switch (pid)
            {
              case -1:
                  save_errno = errno;
                  (void) fprintf(stderr, "%s: fork(): %s\n",
                               progname, strerror(save_errno));
                  if (dolog)
                  {
                        errno = save_errno;
                        syslog(LOG_ERR, "fork(): %s", strerror(errno));
                  }
                  return EX_OSERR;

              case 0:
                  break;

              default:
                  return EX_OK;
            }
      }

#ifndef DEBUG
      /*
      **  setsid() works on POSIX only.  Fortunately, that's
      **  all we need for now.  Check sendmail/conf.c for a
      **  portable version if needed.
      */

      (void) setsid();

      if (!dofork)
      {
            int status;

            /* redirect stdin, stdout, stderr to /dev/null */
            devnull = open(_PATH_DEVNULL, O_RDWR, 0);
            if (devnull < 0)
            {
                  fprintf(stderr, "%s: %s: open(): %s\n", progname,
                          _PATH_DEVNULL, strerror(errno));
            }

            status = dup2(devnull, 0);
            if (status != -1)
                  status = dup2(devnull, 1);
            if (status != -1)
                  status = dup2(devnull, 2);
            if (status == -1)
            {
                  fprintf(stderr, "%s: %s: dup2(): %s\n",
                          progname, _PATH_DEVNULL,
                          strerror(errno));
            }

            close(devnull);
      }

      /* write out the pid */
      if (pidfile != NULL)
      {
            FILE *f;

            f = fopen(pidfile, "w");
            if (f != NULL)
            {
                  fprintf(f, "%u\n", getpid());
                  (void) fclose(f);
            }
            else if (dolog)
            {
                  syslog(LOG_ERR, "can't write pid to %s: %s",
                         pidfile, strerror(errno));
            }
      }
#endif /* ! DEBUG */

      /* call the milter mainline */
#ifdef DEBUG
      return batv_debug();
#else /* DEBUG */
      if (dolog)
      {
            int status;
            size_t n;
            char argstr[MAXARGV];
            char *end;

            memset(argstr, '\0', sizeof argstr);
            end = &argstr[sizeof argstr - 1];
            n = sizeof argstr;
            for (c = 1, p = argstr; c < argc && p < end; c++)
            {
                  if (strchr(argv[c], ' ') != NULL)
                  {
                        status = snprintf(p, n, "%s \"%s\"",
                                          c == 1 ? "args:" : "",
                                          argv[c]);
                  }
                  else
                  {
                        status = snprintf(p, n, "%s %s",
                                          c == 1 ? "args:" : "",
                                          argv[c]);
                  }

                  p += status;
                  n -= status;
            }

            syslog(LOG_INFO, "%s v%s starting (%s)", BATV_PRODUCT,
                   BATV_VERSION, argstr);
      }

      return smfi_main();
#endif /* DEBUG */
}

Generated by  Doxygen 1.6.0   Back to index