/*
 *  relaytest.c -- module for ZMailer's smtpserver
 *  By Matti Aarnio <mea@nic.funet.fi>
 *
 */

/*
 * TODO:
 *  - Attribute 'request' initializations for resolving
 *  - 'freeze' attribute processing
 *  - Addresses in form  <@foo:uu@dd>, <host!user>, <host!user@domain>
 */

#include "hostenv.h"
#include "mailer.h"

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <fcntl.h>
#ifdef HAVE_DB_H
#include <db.h>
#endif
#ifdef HAVE_NDBM_H
#define datum Ndatum
#include <ndbm.h>
#undef datum
#endif
#ifdef HAVE_GDBM_H
#define datum Gdatum
#include <gdbm.h>
#undef datum
#endif

#ifdef	HAVE_SYS_SOCKET_H
#include <sys/socket.h>

#include <netdb.h>

#include <netinet/in.h>
#ifdef HAVE_NETINET_IN6_H
#include <netinet/in6.h>
#endif
#ifdef HAVE_NETINET6_IN6_H
#include <netinet6/in6.h>
#endif
#ifdef HAVE_LINUX_IN6_H
#include <linux/in6.h>
#endif

#endif

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

#define _POLICYTEST_INTERNAL_
#include "policytest.h"
#include "policy.h"

extern int debug;

static int resolveattributes __((struct policytest *, int, struct policystate *, const char *, int));
static void check_domain __((struct policytest *, struct policystate *, const char *, int));
static int  check_user __((struct policytest *, struct policystate *, const char *, int));
static int  checkaddr  __((struct policytest *, struct policystate *, const char *));

#if defined(AF_INET6) && defined(INET6)
extern const struct in6_addr zv4mapprefix;
#endif

static void mask_ip_bits __((unsigned char *, int, int));
static void mask_ip_bits(ipnum, width, maxwidth)
unsigned char *ipnum;
int width, maxwidth;
{
    int i, bytewidth, bytemaxwidth;

    bytemaxwidth = maxwidth >> 3;	/* multiple of 8 */
    bytewidth = (width + 7) >> 3;

    /* All full zero bytes... */
    for (i = bytewidth; i < bytemaxwidth; ++i)
	ipnum[i] = 0;

    /* Now the remaining byte */
    i = 8 - (width & 7);	/* Modulo 8 */

    bytewidth = width >> 3;
    if (i != 8) {
	/* Not exactly multiple-of-byte-width operand to be masked    */
	/* For 'width=31' we get now 'bytewidth=3', and 'i=1'         */
	/* For 'width=25' we get now 'bytewidth=3', and 'i=7'         */
	ipnum[bytewidth] &= (0xFF << i);
    }
}


void policydefine(relp, dbtype, dbpath)
struct policytest **relp;
const char *dbtype, *dbpath;
{
    struct policytest *rel = (void *) emalloc(sizeof(*rel));
    int openok;
    char *dbname;

    *relp = rel;
    memset(rel, 0, sizeof(*rel));
    rel->dbtype = strdup(dbtype);
    rel->dbpath = strdup(dbpath);
    rel->dbt = _dbt_none;
#ifdef HAVE_NDBM_H
    if (cistrcmp(dbtype, "ndbm") == 0)
	rel->dbt = _dbt_ndbm;
#endif
#ifdef HAVE_GDBM_H
    if (cistrcmp(dbtype, "gdbm") == 0)
	rel->dbt = _dbt_ndbm;
#endif
#ifdef HAVE_DB_H
    if (cistrcmp(dbtype, "btree") == 0)
	rel->dbt = _dbt_btree;
#endif
    if (rel->dbt == _dbt_none) {
	/* XX: ERROR! Unknown/unsupported dbtype! */
	return;
    }
    openok = 0;
#ifdef HAVE_ALLOCA
    dbname = alloca(strlen(dbpath) + 8);
#else
    dbname = emalloc(strlen(dbpath) + 8);
#endif
    switch (rel->dbt) {
#ifdef HAVE_NDBM_H
    case _dbt_ndbm:
	/*
	   rel->ndbm = dbm_open((char*)dbpath, O_RDWR|O_CREAT|O_TRUNC, 0644);
	 */
	rel->ndbm = dbm_open((char *) dbpath, O_RDWR, 0644);
	openok = (rel->ndbm != NULL);
	break;
#endif
#ifdef HAVE_GDBM_H
    case _dbt_gdbm:
	/* Append '.gdbm' to the name */
	sprintf(dbname, "%s.gdbm", dbpath);
	rel->gdbm = gdbm_open(dbname, 0, GDBM_READER, 0644, NULL);
	openok = (rel->gdbm != NULL);
	break;
#endif
#ifdef HAVE_DB_H
    case _dbt_btree:
	/* Append '.db' to the name */
	sprintf(dbname, "%s.db", dbpath);
	rel->btree = dbopen(dbname, O_RDONLY, 0644, DB_BTREE, NULL);
	openok = (rel->btree != NULL);
	break;
#endif
    default:
	break;
    }
#ifndef HAVE_ALLOCA
    free(dbname);
#endif
    if (!openok) {
	/* ERROR!  Could not open the database! */
      if (debug)
	printf("ERROR!  Could not open the database!\n");
      return;
    }
#ifdef HAVE_NDBM_H
#endif
#ifdef HAVE_GDBM_H
#endif
#ifdef HAVE_DB_H
#endif
}

/* Do the actual query - return pointer to the result record */
static void *dbquery __((struct policytest *, const void *, const int, int *));

static void *dbquery(rel, qptr, qlen, rlenp)
struct policytest *rel;
const void *qptr;
const int qlen;
int *rlenp;			/* result length ptr ! */
{
    char *buffer;
#ifdef HAVE_NDBM_H
    Ndatum Nkey, Nresult;
#endif
#ifdef HAVE_GDBM_H
    Gdatum Gkey, Gresult;
#endif
#ifdef HAVE_DB_H
    DBT Bkey, Bresult;
    int rc;
#endif


    switch (rel->dbt) {
#ifdef HAVE_NDBM_H
    case _dbt_ndbm:

	Nkey.dptr = (char *) qptr;
	Nkey.dsize = qlen;

	Nresult = dbm_fetch(rel->ndbm, Nkey);
	if (Nresult.dptr == NULL)
	    return NULL;

	buffer = (char *) emalloc(Nresult.dsize);
	memcpy(buffer, Nresult.dptr, Nresult.dsize);

	*rlenp = Nresult.dsize;
	return buffer;

	break;
#endif
#ifdef HAVE_GDBM_H
    case _dbt_gdbm:

	Gkey.dptr = (void *) qptr;
	Gkey.dsize = qlen;

	Gresult = gdbm_fetch(rel->gdbm, Gkey);

	/* gdbm_fetch allocates memory for return data: Gresult.dptr
	   Must be freed later. */

	*rlenp = Gresult.dsize;
	return Gresult.dptr;

	break;
#endif
#ifdef HAVE_DB_H
    case _dbt_btree:


	Bkey.data = (void *) qptr;
	Bkey.size = qlen;

	rc = (rel->btree->get) (rel->btree, &Bkey, &Bresult, 0);
	if (rc != 0)
	    return NULL;

	buffer = (char *) emalloc(Bresult.size);
	memcpy(buffer, Bresult.data, Bresult.size);

	*rlenp = Bresult.size;
	return buffer;

	break;
#endif
    default:
	break;
    }
    return NULL;
}



/******************************************************************************
 * Function: resolveattributes()
 *
 *       recursions - Max recursive calls for parsing aliases.
 *    state.request - These bit flags say which attributes are checked.
 * state.values[]   - Attribute values are stored here. Both are indexed
 *                    according to the attribute constants. (P_A_...)
 *              key - Pointer to search record. Binary address, ascii alias or
 *                    ascii domain name.
 *             init - Init flag. Give value 1. Value 0 when called recursively
 *                    by itself.
 * -------------
 * Returns 0, when it has found something
 *****************************************************************************/

static int resolveattributes(rel, recursions, state, key, init)
struct policytest *rel;
int recursions;
struct policystate *state;
const char *key;
int init;
{
  char *str, *str_base, pbuf[256];
    int rlen, result, interest, len;

    if (init) {
	/* First call of this function. Not called recursively. */
	/* Zero return value array. */
	memset(state->values, 0, sizeof(state->values));
    }
    --recursions;

    if (key[1] != P_K_IPv4 && key[1] != P_K_IPv6) {
	if (debug)
	  printf("Key: %d/%d/%s\n", key[0],key[1],key+2);
    } else
      if (debug)
	printf("Key: %u.%u.%u.%u\n", key[2] & 0xff, key[3] & 0xff, 
	       key[4] & 0xff, key[5] & 0xff);

    str_base = str = (char *) dbquery(rel, &key[0], key[0], &rlen);

    /* str[0]    - attribute list lenght
       str[1]    - attribute numeric value
       str[2...] - attribute flag string    */

    if (str == NULL) {
      if (debug)
	printf("Query failed: key='%d/%d/%s'\n", key[0], key[1], key+2);
      return -1;
    }
    /* Scan trough attribute list. Call resolveattributes recursively
       if aliases is found */

    while (rlen > 3) {

	/* Alias */
	if (str[1] == P_A_ALIAS) {
	    /* Do not continue if max recursions reached. */
	    if (recursions < 0) {
	      if (debug)
		printf("Max recursions reached.\n");
	    } else {
	      if (debug)
		printf("Alias-recursion: %d\n", recursions);

	      strcpy(pbuf+2,str+2);
	      pbuf[0] = strlen(str+2) + 3;
	      pbuf[1] = P_K_TAG;
	      result = resolveattributes(rel, recursions, state, pbuf, 0);
	    }
	    rlen -= str[0];
	    str += str[0];
	    continue;
	}

	/* Attribute */
	if (debug)
	  printf("Attribute: %d/%d/%s\n", (int) str[0] & 0xFF, (int) str[1] & 0xff, str + 2);

	interest = 1 << str[1];	/* Convert attrib. num. value into flag bit */
	if ((interest & state->request) == 0) {
	    /* Not interested in this attribute, skip into next. */
	    rlen -= str[0];
	    str  += str[0];
	    if (debug)
	      printf("Not interested. Skipping over.\n");
	    continue;
	} else {
	    /* Mask it off. */
	    state->request &= ~interest;
	}

	if (str[2] != '+' && str[2] != '-') {
	  if (debug)
	    printf("Unknown flag: %s\n", &str[2]);
	  continue;
	}
	/* Store valid attribute.
	   str[1] is attributes id constant, str[2] attribute flag. */

	if (P_A_FirstAttr <= str[1] && str[1] <= P_A_LastAttr) {
	    if (state->values[str[1] & 0xFF] == 0)
		state->values[str[1] & 0xFF] = str[2];
	} else {
	  if (debug)
	    printf("Unknown attribute, number: %d\n", str[1] & 0xFF);
	}

	rlen -= str[0];
	str += str[0];

	/* If all requests are done, exit. */
	if (!state->request) {
	  if (debug)
	    printf("Every request found. Finishing search.\n");
	  break;
	}
    }				/* End of while. */

    /* Free memory from attribute list. Allocated in dbquery. */
    if (str_base)
	free(str_base);

    return 0;
}


/* Return 0, when found something */
static int checkaddr(rel, state, pbuf)
struct policytest *rel;
struct policystate *state;
const char *pbuf;
{
    int result, count, countmax;
    int maxrecursions;


    maxrecursions = 5;

    if (pbuf[1] == P_K_DOMAIN) {
	if (debug)
	  printf("checkaddr(): domain of '%s'\n",pbuf+2);
	result = resolveattributes(rel, maxrecursions, state, pbuf, 1);
	if (debug) {
	  int i;
	  printf("Results: ");
	  for (i = 0; i < sizeof(state->values); i++)
	    printf(":%c", state->values[i] ? state->values[i] : '0');
	  printf("\n");
	}
	return (result);
    }
    if (pbuf[1] == P_K_USER) {
	if (debug)
	  printf("checkaddr(): user of '%s'\n",pbuf+2);
	result = resolveattributes(rel, maxrecursions, state, pbuf, 1);
	if (debug) {
	  int i;
	  printf("Results: ");
	  for (i = 0; i < sizeof(state->values); i++)
	    printf(":%c", state->values[i] ? state->values[i] : '0');
	  printf("\n");
	}
	return (result);
    } else if (pbuf[1] == P_K_IPv4)
	countmax = 32;
    else
	countmax = 128;

    result = 1;
    count = 1;

    while (result != 0 && count <= countmax) {

	/* Search database */
	result = resolveattributes(rel, maxrecursions, state, pbuf, 1);
	if (result == 0)	/* Found. */
	    break;

	mask_ip_bits((char*)&pbuf[2], countmax - count, countmax);

	if (pbuf[1] == P_K_IPv4)
	    ((char*)pbuf)[6] = 32 - count;	/* Width */
#if defined(AF_INET6) && defined(INET6)
	else			/* AF_INET6 */
	  ((char*)pbuf)[18] = 128 - count;
#endif
	count++;
    }

    
#if defined(AF_INET6) && defined(INET6)
    /* Umm.. looked for IPv6 address ?  And nothing found ?
       Try then to look for the wild-card [0.0.0.0]/0 entry. */
    if (result != 0 && pbuf[1] == P_K_IPv6) {
      memset((char*)pbuf,0,6);
      ((char*)pbuf)[0] = 7;
      ((char*)pbuf)[1] = P_K_IPv4;
      ((char*)pbuf)[6] = 0;
      /* Search database */
      result = resolveattributes(rel, maxrecursions, state, pbuf, 1);
    }
#endif

    if (result != 0) {
      if (debug)
	printf("Address not found.\n");
      return -1;
    } else
      if (debug) {
	int i;
	for (i = 0; i < 32; i++)
	  printf(":%c", state->values[i] ? state->values[i] : '0');
	printf(":  Count: %d\n", count - 1);
      }
    return 0;
}


void policyinit(rel, state)
struct policytest *rel;
struct policystate *state;
{
  memset(state, 0, sizeof(*state));
}



int policytestaddr(rel, state, what, raddr)
struct policytest *rel;
struct policystate *state;
PolicyTest what;
struct sockaddr *raddr;
{
    char pbuf[64]; /* Not THAT much space needed.. */

    struct sockaddr_in *si4;
#if defined(AF_INET6) && defined(INET6)
    struct sockaddr_in6 *si6;
#endif


    if (what != POLICY_SOURCEADDR)
	abort();		/* Urgle..! Code mismatch! */

    if (rel == NULL)
      return 0;

    /* Find address match -- IPv4 mapped into IPv6 space too! */

    if (raddr->sa_family == 0)
      return 0; /* Interactive testing... */

    if (raddr->sa_family == AF_INET) {
	si4 = (struct sockaddr_in *) raddr;
	pbuf[0] = 7;
	pbuf[1] = P_K_IPv4;
	memcpy(&pbuf[2], (char *) &si4->sin_addr.s_addr, 4);
	pbuf[6] = 32;		/* 32 bits */
    } else
#if defined(AF_INET6) && defined(INET6)
    if (raddr->sa_family == AF_INET6) {
	si6 = (struct sockaddr_in6 *) raddr;
	if (memcmp((void *)&si6->sin6_addr, &zv4mapprefix, 12) == 0) {
	  /* This is IPv4 address mapped into IPv6 */
	  pbuf[0] = 7;
	  pbuf[1] = P_K_IPv4;
	  memcpy(pbuf+2, ((char *) &si6->sin6_addr) + 12, 4);
	  pbuf[6] = 32;			/*  32 bits */
	} else {
	  pbuf[0] = 19;
	  pbuf[1] = P_K_IPv6;
	  memcpy(pbuf+2, ((char *) &si6->sin6_addr), 16);
	  pbuf[18] = 128;		/* 128 bits */
	}
    } else
#endif
    {
      printf("Unknown address format; sa_family = %d\n", raddr->sa_family);
      return -2;
    }


    /* state->request initialization !! */

    state->request = ( 1 << P_A_REJECTNET    |
		       1 << P_A_FREEZENET    |
		       1 << P_A_RELAYCUSTNET |
		       1 << P_A_REJECTSOURCE |
		       1 << P_A_FREEZESOURCE   );

    if (checkaddr(rel, state, pbuf) != 0)
      return 0; /* Nothing found */

#if 0
/* if (IP address of SMTP client has 'rejectnet +' attribute) then
    any further conversation refused
    [state->always_reject = 1; return -1;]
    ...
   if (IP address of SMTP client has 'freezenet +' attribute) then
    we present happy face, but always put the messages into a freezer..
    [state->always_freeze = 1; return -1;]
   if (IP address of SMTP client has 'relaycustnet +' attribute) then
    sender accepted, recipients not checked
    [state->always_accept = 1; return 0;]
    ...
   Except that:
   if (HELO-name of SMTP client has 'rejectnet +' attribute) then
    any further conversation refused
    [state->always_reject = 1; return -1;]
   else if (sender's domain has 'rejectsource +' attribute) then
    sender rejected, any further conversation refused
    [state->sender_reject = 1; return -1;]
 */
#endif
    if (state->values[P_A_REJECTNET] == '+') {
      if (debug)
	printf("policytestaddr: 'rejectnet +' found\n");
	state->always_reject = 1;
	return -1;
    }
    if (state->values[P_A_FREEZENET] == '+') {
      if (debug)
	printf("policytestaddr: 'freezenet +' found\n");
	state->always_freeze = 1;
	return  1;
    }
    if (state->values[P_A_RELAYCUSTNET] == '+') {
      if (debug)
	printf("policytestaddr: 'relaycustnet +' found\n");
	state->always_accept = 1;
	return  0;
    }
    if (state->values[P_A_REJECTSOURCE] == '+') {
      if (debug)
	printf("policytestaddr: 'rejectsource +' found\n");
	state->sender_reject = 1;
	return -1;
    }
    if (state->values[P_A_FREEZESOURCE] == '+') {
      if (debug)
	printf("policytestaddr: 'freezesource +' found\n");
	state->sender_freeze = 1;
	return -1;
    }

    return 0;
}


static void check_domain(rel, state, input, inlen)
struct policytest *rel;
struct policystate *state;
const char *input;
int inlen;
{
    char *ptr, *ptr2, pbuf[256];
    int addr_len, i, plen, result;


#if 0
    /* Get address after @ */
    ptr = strchr(input, '@');
    if (ptr == NULL) {
	printf("Invalid address. @ not found!\n");
	exit(0);
    }
    ptr++;
    addr_len = inlen - (ptr - input);
#else
    ptr = (char*)input;
    addr_len = inlen;
#endif

    /* Convert to lower case. */
    if (addr_len > 254)
	addr_len = 254;
    strncpy(pbuf+2, ptr, addr_len);
    pbuf[2+addr_len] = 0;
    strlower(pbuf+2);

    plen = addr_len;
    /* '\0' not included in inlen... */
    plen += 1 + 2;

    pbuf[0] = plen;
    pbuf[1] = P_K_DOMAIN;

    result = 1;

    while (result != 0) {
	if (debug)
	  printf("DEBUG: %d/%d/%s\n", pbuf[0], pbuf[1], pbuf+2);
	result = checkaddr(rel, state, pbuf);

	if (result == 0) /* Found! */
	    break;

	if (pbuf[2] != '.') {
	    /* Put '.' in the beginning */
	    for (i = pbuf[0]; i >= 2; --i) {
		pbuf[i + 1] = pbuf[i];
	    }
	    pbuf[2] = '.';
	    pbuf[0] += 1;
	} else {
	    /* Test with shorter address. */
	    ptr = &pbuf[3];
	    while (*ptr != 0 && *ptr != '.')
		ptr++;
	    if (*ptr == '\0') {
		/* Quit the loop if everything is examined. */
		if (pbuf[2] == '.' && pbuf[3] == '\0')
		    break;
		pbuf[2] = '.';
		pbuf[3] = '\0';
	    } else {
		ptr++;
		ptr2 = &pbuf[3];
		while (*ptr != '\0') {
		    *ptr2++ = *ptr++;
		}
		*ptr2++ = *ptr++;
	    }
	    pbuf[0] = strlen(&pbuf[2]) + 1 + 2;
	}
    }
}


/* Return 0, when found something */
static int check_user(rel, state, input, inlen)
struct policytest *rel;
struct policystate *state;
const char *input;
int inlen;
{
    char pbuf[512], *at;
    int i, result;

    if (inlen > (sizeof(pbuf) - 3))
      inlen = sizeof(pbuf) - 3;

    /* Store the MAIL FROM:<user@domain> into a temporary buffer, and
       lowercasify it   */
    strncpy(pbuf+2, input, inlen);
    pbuf[2+inlen] = 0;
    strlower(pbuf + 2);

    at = memchr(pbuf + 2, '@', inlen);
    if (!at) return 0;

    pbuf[inlen + 2] = '\0';
    pbuf[0] = inlen + 1 + 2;
    pbuf[1] = P_K_USER;

    result = checkaddr(rel, state, pbuf);
    if (result == 0) /* Found! */
      return result;

    /* 'user@' */
    inlen = (at+1 - pbuf) - 2;
    pbuf[inlen + 2] = '\0';
    pbuf[0] = inlen + 1 + 2;
    pbuf[1] = P_K_USER;

    result = checkaddr(rel, state, pbuf);
    return result;
}


/*
   static int pt_heloname __((struct policytest *, struct policystate *, const char *, const int));

   static int pt_mailfrom __((struct policytest *, struct policystate *, const char *, const int));
 */
static int pt_rcptto __((struct policytest *, struct policystate *, const char *, const int));

int pt_heloname(rel, state, str, len)
struct policytest *rel;
struct policystate *state;
const char *str;
const int len;
{
    if (state->always_reject)
	return -1;
    if (state->always_freeze)
	return 1;
    if (state->always_accept)
	return 0;

    /* state->request initialization !! */
    state->request = ( 1 << P_A_REJECTNET |
		       1 << P_A_FREEZENET  );

    check_domain(rel, state, str, len);

/*
   # if (name of SMTP client has 'rejectnet +' attribute) then
   #    any further conversation refused
   #      [state->always_reject = 1; return -1;]
 */
    if (state->values[P_A_REJECTNET] == '+') {
	state->always_reject = 1;
	return -1;
    }
    if (state->values[P_A_FREEZENET] == '+') {
	state->always_freeze = 1;
	return  1;
    }
    return 0;
}

int pt_mailfrom(rel, state, str, len)
struct policytest *rel;
struct policystate *state;
const char *str;
const int len;
{
	char *at;
    state->rcpt_nocheck  = 0;
    state->sender_reject = 0;
    state->sender_freeze = 0;

    if (state->always_reject)
	return -1;
    if (state->always_freeze)
	return 1;
    if (state->always_accept)
	return 0;

    if (len == 0) /* MAIL FROM:<> -- error message ? */
      return 0;   /* We accept it, sigh.. */

    /* state->request initialization !! */
    state->request = ( 1 << P_A_REJECTSOURCE |
		       1 << P_A_FREEZESOURCE   );

    /* XX: How about  <@foo:user@domain> ??? */

    /* Check source user */
    if (check_user(rel, state, str, len) == 0) {
      if (state->values[P_A_FREEZESOURCE] == '+') {
	if (debug)
	  printf("mailfrom: 'freezesource +'\n");
	state->sender_freeze = 1;
	return 1;
      }
      if (state->values[P_A_REJECTSOURCE] == '+') {
	if (debug)
	  printf("mailfrom: 'rejectsource +'\n");
	state->sender_reject = 1;
	return -1;
      }
    }

    state->request = ( 1 << P_A_REJECTSOURCE  |
		       1 << P_A_FREEZESOURCE  |
		       1 << P_A_RELAYCUSTNET  |
		       1 << P_A_RELAYCUSTOMER   );

    at = memchr(str, '@', len);
    if (at != NULL)
      check_domain(rel, state, at+1, len - (1 + at - str));
    else {
      /* Doh ??  Not  <user@domain> ??? */
      return -1;
    }

/*
   # else if (sender's domain has 'rejectsource +' attribute) then
   #    sender rejected, any further conversation refused
   #        [state->sender_reject = 1; return -1;]
   # else if (sender's domain has 'freezesource +' attribute) then
   #    sender marks freeze, and we present happy accepting face..
   #        [state->sender_freeze = 1; return -1;]
   # else if (IP address of SMTP client has 'relaycustnet +' attribute) then
   #           [state->relaycustnet]
   #    sender accepted, recipients not checked
   #        [state->rcpt_nocheck = 1; return 0;]
   # else if (HELO-name of SMTP client has 'relaycustnet +' attribute) then
   #    sender accepted, recipients not checked
   #        [state->rcpt_nocheck = 1; return 0;]
   --- danger! danger!
   # else if (sender's domain has 'relaycustomer +' attribute) then
   #    sender accepted, recipients not checked
   #        [state->rcpt_nocheck = 1; return 0;]
 */

    if (state->values[P_A_REJECTSOURCE] == '+') {
	if (debug)
	  printf("mailfrom: 'rejectsource +'\n");
	state->sender_reject = 1;
	return -1;
    }
    if (state->values[P_A_FREEZESOURCE] == '+') {
	if (debug)
	  printf("mailfrom: 'freezesource +'\n");
	state->sender_freeze = 1;
	return -1;
    }
    /* IP address of SMTP client */
    if (state->values[P_A_RELAYCUSTNET] == '+') {
	if (debug)
	  printf("mailfrom: 'relaycustnet +'\n");
	state->relaycustnet = 1;
	state->rcpt_nocheck = 1;
	return  0;
    }
    if (state->values[P_A_RELAYCUSTOMER] == '+') {
	if (debug)
	  printf("mailfrom: 'relaycustomer +'\n");
	state->rcpt_nocheck = 1;
	return  0;
    }
#if 0 /* Umm.. no.. */
    if (state->values[P_A_RELAYCUSTOMER] == '-') {
	if (debug)
	  printf("mailfrom: 'relaycustomer -'\n");
	state->sender_reject = 1;
	return -1;
    }
#endif
    return 0;
}

static int pt_rcptto(rel, state, str, len)
struct policytest *rel;
struct policystate *state;
const char *str;
const int len;
{
	char *at;

    if (state->always_reject)
	return -1;
    if (state->sender_reject)
	return -2;
    if (state->always_freeze)
	return 1;
    if (state->sender_freeze)
	return 1;
    if (state->always_accept)
	return 0;

    if (state->rcpt_nocheck)
	return 0;
    /* rcptfreeze even for 'rcpt-nocheck' ? */

    /* state->request initialization !! */
    state->request = ( 1 << P_A_RELAYTARGET     |
		       1 << P_A_ACCEPTbutFREEZE );

    at = memchr(str, '@', len);
    if (at != NULL)
      check_domain(rel, state, at+1, len - (1 + at - str));
    else {
      /* Doh ??  Not  <user@domain> ??? */
      return -1;
    }

/*
   # else if (recipient's domain has 'relaytarget +' attribute) then
   #    recipient accepted
   #      [return  0;]
   # else if (recipient's domain has 'freeze +' attribute) then
   #    the MESSAGE is accepted into a freezer..
   #      [state->sender_freeze = 1; return -1;]
   # else
   #    this recipient refused
   #      [return -1;]
 */

    if (state->values[P_A_RELAYTARGET] == '+') {
	return  0;
    }
    if (state->values[P_A_RELAYTARGET] == '-') {
	return -1;
    }
    if (state->values[P_A_ACCEPTbutFREEZE] == '+') {
	state->sender_freeze = 1;
	return  1;
    }
    /*      return 0; */
    return 0;
}

int policytest(rel, state, what, str, len)
struct policytest *rel;
struct policystate *state;
PolicyTest what;
const char *str;
const int len;
{
    if (rel == NULL)
      return 0;

    if (what == POLICY_HELONAME)
	return pt_heloname(rel, state, str, len);
    if (what == POLICY_MAILFROM)
	return pt_mailfrom(rel, state, str, len);
    if (what == POLICY_RCPTTO)
	return pt_rcptto(rel, state, str, len);

    abort();			/* Code error! Bad policy ! */
}
