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

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

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

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>


#include "sleepycatdb.h"

#ifdef HAVE_NDBM
#define datum Ndatum
#include <ndbm.h>
#undef datum
#endif
#ifdef HAVE_GDBM
#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
#include <arpa/inet.h>

#endif

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

#define _POLICYTEST_INTERNAL_
#include "policytest.h"


/* We are not including  "smtpserver.h",  thus have to do local prototype.. */
#if defined(HAVE_STDARG_H) && defined(HAVE_VPRINTF)
extern void type __((void *, int code, const char *status, const char *fmt,...));
#else
extern void type __(( /* void *SS, int code, const char *status, const char *fmt, ... */ ));
#endif



extern int debug;
extern int percent_accept;

/* This is *NOT* the official prototype for type() !!! */
extern void type __((void *, const int code, const char *status, const char *fmt,...));

static int resolveattributes __((struct policytest *, int, struct policystate *, const char *, int));
static int  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 u_char zv4mapprefix[16];
#endif

/* KK() and KA() macroes are at "policy.h" */

static char *showkey __((const char *key));
static char *showkey(key)
const char *key;
{
    static char buf[256];

    if (key[1] != P_K_IPv4 && key[1] != P_K_IPv6) {
	if (strlen(key+2) > (sizeof(buf) - 200))
	    sprintf(buf,"%d/%s/'%s'", key[0], KK(key[1]), "<too long name>");
	else
	    sprintf(buf,"%d/%s/'%s'", key[0], KK(key[1]), key+2);
    } else
      if (key[1] == P_K_IPv4)
	sprintf(buf,"%d/%s/%u.%u.%u.%u/%d",
		key[0], KK(key[1]),
		key[2] & 0xff, key[3] & 0xff, key[4] & 0xff, key[5] & 0xff,
		key[6] & 0xff);
      else
	sprintf(buf,"%d/%s/%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x/%d",
		key[0], KK(key[1]),
		key[2] & 0xff, key[3] & 0xff, key[4] & 0xff, key[5] & 0xff,
		key[6] & 0xff, key[7] & 0xff, key[8] & 0xff, key[9] & 0xff,
		key[10] & 0xff, key[11] & 0xff, key[12] & 0xff, key[13] & 0xff,
		key[14] & 0xff, key[15] & 0xff, key[16] & 0xff, key[17] & 0xff,
		key[18] & 0xff);
    return buf;
}

static char *showattr __((const unsigned char *key));
static char *showattr(key)
const unsigned char *key;
{
    static char buf[500];
    sprintf(buf,"%d/%s/'%s'", key[0], KA(key[1]), key+2);
    return buf;
}

static int valueeq __((const char *value, const char *str));
static int valueeq(value,str)
     const char *value, *str;
{
    if (!value) return 0;
    return (strcmp(value,str) == 0);
}

static char *showresults __((struct policystate *state));
static char *showresults(state)
struct policystate *state;
{
    static char buf[2000];
    int i;
    char **values=state->values;

    buf[0] = '\0';
    for (i = P_A_FirstAttr; i <= P_A_LastAttr; ++i) {
	sprintf(buf+strlen(buf),"%s ",KA(i));
	if (i == P_A_InboundSizeLimit )
	    sprintf(buf+strlen(buf),"%li ",state->maxinsize);
	else if (i == P_A_OutboundSizeLimit )
	    sprintf(buf+strlen(buf),"%li ",state->maxoutsize);
	else if (i == P_A_MaxSameIpSource )
	    sprintf(buf+strlen(buf),"%li ",state->maxsameiplimit);
	else
	    sprintf(buf+strlen(buf),"%s ",values[i] ? values[i] : ".");
    }
    return buf;
}

static void printstate __((const struct policystate *state));
static void printstate (state)
const struct policystate *state;
{
	int i;

	type(NULL,0,NULL," always_reject=%d",state->always_reject);
	type(NULL,0,NULL," always_freeze=%d",state->always_freeze);
	type(NULL,0,NULL," always_accept=%d",state->always_accept);
	type(NULL,0,NULL," full_trust=%d",   state->full_trust);
	type(NULL,0,NULL," trust_recipients=%d",state->trust_recipients);
	type(NULL,0,NULL," sender_reject=%d",state->sender_reject);
	type(NULL,0,NULL," sender_freeze=%d",state->sender_freeze);
	type(NULL,0,NULL," sender_norelay=%d",state->sender_norelay);
	type(NULL,0,NULL," relaycustnet=%d", state->relaycustnet);
	type(NULL,0,NULL," rcpt_nocheck=%d", state->rcpt_nocheck);

	for ( i = P_A_FirstAttr; i <= P_A_LastAttr ; ++i) {
	    type(NULL,0,NULL," %s: %srequested, value=%s", KA(i),
		 (state->origrequest & (1<<i)) ? "" : "not ",
		 state->values[i]?state->values[i]:".");
	}

	type(NULL,0,NULL," maxinsize=%li", state->maxinsize);
	type(NULL,0,NULL," maxoutsize=%li", state->maxoutsize);
	type(NULL,0,NULL," maxsameiplimit=%li", state->maxsameiplimit);
}

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));
    *relp = rel;
    memset(rel, 0, sizeof(*rel));
    rel->dbtype = strdup(dbtype);
    rel->dbpath = strdup(dbpath);
    rel->dbt = _dbt_none;
}

/* 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
    Ndatum Nkey, Nresult;
#endif
#ifdef HAVE_GDBM
    Gdatum Gkey, Gresult;
#endif
#ifdef HAVE_DB
    DBT Bkey, Bresult;
    int rc;
#endif


    switch (rel->dbt) {
#ifdef HAVE_NDBM
    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; /* some compilers complain, some produce bad code
		  without this... */
#endif
#ifdef HAVE_GDBM
    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; /* some compilers complain, some produce bad code
		  without this... */
#endif
#ifdef HAVE_DB
    case _dbt_btree:


	memset(&Bkey,    0, sizeof(Bkey));
	memset(&Bresult, 0, sizeof(Bresult));

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

#ifdef DB_INIT_TXN
	rc = (rel->btree->get) (rel->btree, NULL, &Bkey, &Bresult, 0);
#else
	rc = (rel->btree->get) (rel->btree, &Bkey, &Bresult, 0);
#endif
	if (rc != 0)
	    return NULL;

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

	*rlenp = Bresult.size;
	return buffer;

	break; /* some compilers complain, some produce bad code
		  without this... */

    case _dbt_bhash:

	memset(&Bkey,    0, sizeof(Bkey));
	memset(&Bresult, 0, sizeof(Bresult));

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

#ifdef DB_INIT_TXN
	rc = (rel->bhash->get) (rel->bhash, NULL, &Bkey, &Bresult, 0);
#else
	rc = (rel->bhash->get) (rel->bhash, &Bkey, &Bresult, 0);
#endif
	if (rc != 0)
	    return NULL;

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

	*rlenp = Bresult.size;
	return buffer;

	break; /* some compilers complain, some produce bad code
		  without this... */
#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;
{
    unsigned char *str, *str_base;
    int rlen, result, interest;
    char *msgstr = NULL;

    if (init) {
	/* First call of this function. Not called recursively. */
	/* Zero return value array. */
	int i;
	for (i = 0; i <= P_A_LastAttr; ++i) {
	  if (state->values[i])   free(state->values[i]);
	  if (state->messages[i]) free(state->messages[i]);
	}
	memset(state->values, 0, sizeof(state->values));
	memset(state->messages, 0, sizeof(state->messages));

	state->origrequest = state->request;
    }
    --recursions;

    if (debug)
       type(NULL,0,NULL," Key: %s", showkey(key));
/*
    if (key[1] != P_K_IPv4 && key[1] != P_K_IPv6) {
	if (debug)
	  type(NULL,0,NULL," Key: %d/%d/%s", key[0],key[1],key+2);
    } else
      if (debug)
	type(NULL,0,NULL," Key: %u.%u.%u.%u", 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)
	type(NULL,0,NULL,"  query failed");
      return -1;
    }
    /* Scan trough attribute list. Call resolveattributes recursively
       if aliases is found */

    while (rlen > 3) {

	if (str[0] < 3) {
	  if (debug)
	    type(NULL,0,NULL," Bad length of attrbute, under 3 bytes!  %d", str[0]);
	  break; /* BAD ATTRIBUTE! */
	}

	/* Attribute */
	if (debug)
	  type(NULL,0,NULL,"   Attribute: %s", showattr(str));

	/* Alias */
	if (str[1] == P_A_ALIAS) {
	    /* Do not continue if max recursions reached. */
	    if (recursions < 0) {
	      if (debug)
		type(NULL,0,NULL," Max recursions reached.");
	    } else {
	      char pbuf[256];

	      if (debug)
		type(NULL,0,NULL," Alias-recursion: %d", recursions);

	      strncpy(pbuf+2,str+2, sizeof(pbuf)-3);
	      pbuf[ sizeof(pbuf)-1 ] = 0;

	      strlower(pbuf+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;
	}

	if (str[1] == P_A_MESSAGE) {
	  if (msgstr) free(msgstr);
	  msgstr = strdup(str+2);
	  goto nextattr;
	}

	interest = 1 << str[1];	/* Convert attrib. num. value into flag bit */
	if ((interest & state->request) == 0) {
	    /* Not interested in this attribute, skip into next. */
	    if (debug)
	      type(NULL,0,NULL,"     not interested, skipped...");

	    goto nextattr;
	} else {
	    /* Mask it off. */
	    state->request &= ~interest;
	}

	if (P_A_FirstAttr <= str[1] && str[1] <= P_A_LastAttr) {
	  /* If a message was given in previous attribute, pick it! */
	  state->messages[0xFF & (str[1])] = msgstr;
	  msgstr = NULL;
	}

	if (str[1] == P_A_InboundSizeLimit) {

	  sscanf(str+2,"%li", &state->maxinsize);
	  goto nextattr;

	} else if (str[1] == P_A_OutboundSizeLimit) {

	  sscanf(str+2,"%li", &state->maxoutsize);
	  goto nextattr;

	} else if (str[1] == P_A_MaxSameIpSource) {

	  sscanf(str+2,"%li", &state->maxsameiplimit);
	  goto nextattr;

	} else if ((str[2] != '+' && str[2] != '-') &&
		   !state->values[str[1] & 0xFF]) {

	  /* Supply suffix domain (set), e.g.:
	         RBL.MAPS.VIX.COM,DUL.MAPS.VIX.COM
	     whatever you want ... */

	  state->values[str[1] & 0xFF] = strdup(str + 2);

	} else if (str[2] != '+' && str[2] != '-') {

	  if (debug)
	    type(NULL,0,NULL," Unknown flag: %s", &str[2]);
	  goto nextattr;
	}
	/* 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])
		state->values[str[1] & 0xFF] = strdup(str + 2);
	  if (debug)
	    type(NULL,0,NULL,"     accepted!");
	} else {
	  if (debug)
	    type(NULL,0,NULL,"   Unknown attribute, number: %d", str[1]);
	}

    nextattr:

	/* If this wasn't the P_A_MESSAGE, we drop possibly
	   existing message here.. */
	if (str[1] != P_A_MESSAGE) {
	  if (msgstr) free(msgstr);
	  msgstr = NULL;
	}

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

	/* If all requests are done, exit. */
	if (!state->request) {
	  if (debug)
	    type(NULL,0,NULL," Every request found. Finishing search.");
	  break;
	}

    }				/* End of while. */

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

    if (msgstr) free(msgstr);

    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)
	  type(NULL,0,NULL," checkaddr(): domain of '%s'",pbuf+2);
	result = resolveattributes(rel, maxrecursions, state, pbuf, 1);
	if (debug) {
	  type(NULL,0,NULL," Results: %s", showresults(state));
	}
	return (result);
    }
    if (pbuf[1] == P_K_USER) {
	if (debug)
	  type(NULL,0,NULL," checkaddr(): user of '%s'",pbuf+2);
	result = resolveattributes(rel, maxrecursions, state, pbuf, 1);
	if (debug) {
	  type(NULL,0,NULL," Results: %s", showresults(state));
	}
	return (result);
    } else if (pbuf[1] == P_K_IPv4)
	countmax = 32;
    else
	countmax = 128;

    result = 1;
    count = 0;

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

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

	mask_ip_bits((u_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
    }

    
#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)
	type(NULL,0,NULL," Address not found.");
      return -1;
    } else
      if (debug) {
	type(NULL,0,NULL," Results:  %s", showresults(state));
      }
    return 0;
}


int policyinit(relp, state, whosonrc)
struct policytest **relp;
struct policystate *state;
int whosonrc;
{
    int openok;
    char *dbname;
    struct policytest *rel = *relp;

    if (rel == NULL)
      return -1;  /* Not defined! */

    memset(state, 0, sizeof(*state));

#ifdef HAVE_NDBM
    if (cistrcmp(rel->dbtype, "ndbm") == 0)
	rel->dbt = _dbt_ndbm;
#endif
#ifdef HAVE_GDBM
    if (cistrcmp(rel->dbtype, "gdbm") == 0)
	rel->dbt = _dbt_gdbm;
#endif
#ifdef HAVE_DB
    if (cistrcmp(rel->dbtype, "btree") == 0)
	rel->dbt = _dbt_btree;
    if (cistrcmp(rel->dbtype, "bhash") == 0)
	rel->dbt = _dbt_bhash;
#endif
    if (rel->dbt == _dbt_none) {
	/* XX: ERROR! Unknown/unsupported dbtype! */
      *relp = NULL;
	return 1;
    }
    openok = 0;
#ifdef HAVE_ALLOCA
    dbname = (char*)alloca(strlen(rel->dbpath) + 8);
#else
    dbname = (char*)emalloc(strlen(rel->dbpath) + 8);
#endif
    switch (rel->dbt) {
#ifdef HAVE_NDBM
    case _dbt_ndbm:
	/*
	   rel->ndbm = dbm_open((char*)rel->dbpath, O_RDWR|O_CREAT|O_TRUNC, 0644);
	 */
	strcpy(dbname, rel->dbpath);
	rel->ndbm = dbm_open(dbname, O_RDONLY, 0644);
	openok = (rel->ndbm != NULL);
	break;
#endif
#ifdef HAVE_GDBM
    case _dbt_gdbm:
	/* Append '.gdbm' to the name */
	sprintf(dbname, "%s.gdbm", rel->dbpath);
	rel->gdbm = gdbm_open(dbname, 0, GDBM_READER, 0644, NULL);
	openok = (rel->gdbm != NULL);
	break;
#endif
#ifdef HAVE_DB
    case _dbt_btree:
	/* Append '.db' to the name */
	sprintf(dbname, "%s.db", rel->dbpath);

#if defined(HAVE_DB3) || defined(HAVE_DB4)

	rel->btree = NULL;
	openok = db_create(&rel->btree, NULL, 0);
	if (openok == 0)
	  openok = rel->btree->open(rel->btree,
#if (DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR == 1)
				    NULL, /* TXN id was added at SleepyDB 4.1 */
#endif
				    dbname, NULL,  DB_BTREE,
				    DB_RDONLY, 0);
	if (debug && openok)
	  type(NULL,0,NULL," btree->open('%s',BTREE, RDONLY) ret=%d",dbname,openok);
	openok = !openok;

#else
#if defined(HAVE_DB2)

	rel->btree = NULL;
#ifndef DB_RDONLY
# define DB_RDONLY O_RDONLY
#endif
	openok = db_open(dbname, DB_BTREE, DB_RDONLY, 0644,
			 NULL, NULL, &rel->btree);
	openok = !openok;
#else /* HAVE_DB1 */
	rel->btree = dbopen(dbname, O_RDONLY, 0644, DB_BTREE, NULL);
	openok = (rel->btree != NULL);
#endif
#endif
	break;

    case _dbt_bhash:
	/* Append '.db' to the name */
	sprintf(dbname, "%s.dbh", rel->dbpath);

#if defined(HAVE_DB3) || defined(HAVE_DB4)

	rel->bhash = NULL;
	openok = db_create(&rel->bhash, NULL, 0);
	if (openok == 0)
	  openok = rel->bhash->open(rel->bhash,
#if (DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR == 1)
				    NULL, /* TXN id was added at SleepyDB 4.1 */
#endif
				    dbname, NULL, DB_HASH,
				    DB_RDONLY, 0);
	if (debug && openok)
	  type(NULL,0,NULL," bhash->open('%s',BHASH, RDONLY) ret=%d",dbname,openok);
	openok = !openok;

#else
#if defined(HAVE_DB2)

	rel->bhash = NULL;
#ifndef DB_RDONLY
# define DB_RDONLY O_RDONLY
#endif
	openok = db_open(dbname, DB_HASH, DB_RDONLY, 0644,
			 NULL, NULL, &rel->bhash);
	openok = !openok;
#else /* HAVE_DB1 */
	rel->bhash = dbopen(rel->dbpath, O_RDONLY, 0644, DB_HASH, NULL);
	openok = (rel->bhash != NULL);
#endif
#endif
	break;
#endif
    default:
	break;
    }
    if (!openok) {
	/* ERROR!  Could not open the database! */
      if (debug) {
	type(NULL,0,NULL," ERROR!  Could not open the database file '%s'; errno=%d!",
	       dbname, errno);
	fflush(stdout);
      }
      *relp = NULL;

#ifndef HAVE_ALLOCA
      free(dbname);
#endif
      return 2;
    }
#ifndef HAVE_ALLOCA
    free(dbname);
#endif

#ifdef HAVE_WHOSON_H
    state->whoson_result = whosonrc;
#endif
    state->maxsameiplimit = -1;
    return 0;
}


static int _addrtest_ __((struct policytest *rel, struct policystate *state, const char *pbuf, int sourceaddr));

static int _addrtest_(rel, state, pbuf, sourceaddr)
struct policytest *rel;
struct policystate *state;
const char *pbuf;
int sourceaddr;
{
    u_char ipaddr[16];
    int ipaf = pbuf[1];
    int myaddress, lcldom;
    Usockaddr saddr;

    /* Prepare for automatic match of the address */

    memset(&saddr, 0, sizeof(saddr));

    if (pbuf[1] == P_K_IPv4) {
      memcpy(ipaddr, pbuf+2, 4);
      memcpy(& saddr.v4.sin_addr, pbuf+2, 4);
      saddr.v4.sin_family = AF_INET;
    }
    if (pbuf[1] == P_K_IPv6) {
      memcpy(ipaddr, pbuf+2, 16);
#if defined(AF_INET6) && defined(INET6)
      memcpy(& saddr.v6.sin6_addr, pbuf+2, 4);
      saddr.v6.sin6_family = AF_INET6;
#endif
    }

    lcldom = (state->request & (1 << P_A_LocalDomain));
    myaddress = matchmyaddress(&saddr);

    if (debug)
      type(NULL,0,NULL," policytestaddr: lcldom/myaddress=%d/%d",lcldom,myaddress);

    /* state->request initialization !! */

    if (sourceaddr)
      state->request = ( 1 << P_A_REJECTNET         |
			 1 << P_A_FREEZENET         |
			 1 << P_A_RELAYCUSTNET      |
			 1 << P_A_InboundSizeLimit  |
			 1 << P_A_OutboundSizeLimit |
			 1 << P_A_FullTrustNet      |
			 1 << P_A_TrustRecipients   |
			 1 << P_A_TrustWhosOn       |
			 1 << P_A_Filtering         |
			 1 << P_A_MaxSameIpSource    );
    if (!myaddress)
      state->request |= ( 1 << P_A_TestDnsRBL       |
			  1 << P_A_RcptDnsRBL        );

    state->maxinsize  = -1;
    state->maxoutsize = -1;

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

    if (myaddress && lcldom) {

      if (state->values[P_A_LocalDomain]) free(state->values[P_A_LocalDomain]);
      state->values[P_A_LocalDomain] = strdup("+");
      if (state->values[P_A_RELAYTARGET]) free(state->values[P_A_RELAYTARGET]);
      state->values[P_A_RELAYTARGET] = strdup("+");
      if (state->values[P_A_TestDnsRBL]) free(state->values[P_A_TestDnsRBL]);
      state->values[P_A_TestDnsRBL] = NULL;
      if (state->values[P_A_RcptDnsRBL]) free(state->values[P_A_RcptDnsRBL]);
      state->values[P_A_RcptDnsRBL] = NULL;

      if (state->values[P_A_Filtering]) {
	if (debug)
	  type(NULL,0,NULL," policytestaddr: 'filter %s' found",
		 state->values[P_A_Filtering]);
	if (valueeq(state->values[P_A_Filtering], "+")) {
	  state->content_filter = 1;
	} else {
	  state->content_filter = 0;
	}
      }

      if (debug)
	type(NULL,0,NULL," Results:  %s", showresults(state));

      return 0;
    }

    if (!sourceaddr)
      goto just_rbl_checks;


#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->message != NULL)
      free(state->message);
    state->message = NULL;

#define PICK_PA_MSG(attrib)	\
	if (state->message) free(state->message);	\
	state->message = state->messages[(attrib)];	\
	state->messages[(attrib)] = NULL


    if (valueeq(state->values[P_A_REJECTNET], "+")) {
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'rejectnet +' found");
      PICK_PA_MSG(P_A_REJECTNET);
      if (state->message == NULL)
	state->message = strdup("Your network address is blackholed in our static tables");
      state->always_reject = 1;
      return -1;
    }
    if (valueeq(state->values[P_A_FREEZENET], "+")) {
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'freezenet +' found");
      PICK_PA_MSG(P_A_FREEZENET);
      if (state->message == NULL)
	state->message = strdup("Your network address is blackholed in our static tables");
      state->always_freeze = 1;
      return  1;
    }
    if (valueeq(state->values[P_A_TrustRecipients], "+")) {
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'trustrecipients +' found");
      state->trust_recipients = 1;
      PICK_PA_MSG(P_A_TrustRecipients);
    }
    if (valueeq(state->values[P_A_FullTrustNet], "+")) {
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'fulltrustnet +' found");
      state->full_trust = 1;
      PICK_PA_MSG(P_A_FullTrustNet);
    }
#ifdef HAVE_WHOSON_H
    if (valueeq(state->values[P_A_TrustWhosOn], "+")) {
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'trust-whoson +' found, accept? = %d",
	       (state->whoson_result == 0));
      if (state->whoson_result == 0)
	state->always_accept = 1;
      PICK_PA_MSG(P_A_TrustWhosOn);
    }
#endif
    if (valueeq(state->values[P_A_RELAYCUSTNET], "+")) {
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'relaycustnet +' found");
      state->always_accept = 1;
      PICK_PA_MSG(P_A_RELAYCUSTNET);
    }

    if (state->values[P_A_Filtering]) {
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'filter %s' found",
	       state->values[P_A_Filtering]);
      if (valueeq(state->values[P_A_Filtering], "+")) {
	state->content_filter = 1;
      } else {
	state->content_filter = 0;
      }
    }

    if (state->trust_recipients || state->full_trust || state->always_accept)
      return 0;

 just_rbl_checks:;

    if (state->values[P_A_Filtering]) {
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'filter %s' found",
	       state->values[P_A_Filtering]);
      if (valueeq(state->values[P_A_Filtering], "+")) {
	state->content_filter = 1;
      } else {
	state->content_filter = 0;
      }
    }

    if (state->values[P_A_TestDnsRBL] &&
	!valueeq(state->values[P_A_TestDnsRBL], "-")) {
      int rc;
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'test-dns-rbl %s' found;",
	       state->values[P_A_TestDnsRBL]);
      rc = rbl_dns_test(ipaf, ipaddr, state->values[P_A_TestDnsRBL], &state->message);
      if (!state->message){ PICK_PA_MSG(P_A_TestDnsRBL); }

      if (debug)
	type(NULL,0,NULL,"  rc=%d; msg='%s'",
	     rc, state->message ? state->message : "<nil>");

      return rc;
    }

    /* bag = Andrey Blochintsev <bag@iptelecom.net.ua>  */
    /* bag + */
    if (state->values[P_A_RcptDnsRBL] &&
	state->values[P_A_RcptDnsRBL][0] == '_') {
      int rc = 1;
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'rcpt-dns-rbl %s' found;",
	       state->values[P_A_RcptDnsRBL]);
      if (state->values[P_A_RcptDnsRBL][1] != '+') {
      	if (state->rblmsg != NULL)
	  free(state->rblmsg);
	state->rblmsg = strdup(state->values[P_A_RcptDnsRBL] + 1);
	rc = 0;
      }
      if (!state->message){ PICK_PA_MSG(P_A_RcptDnsRBL); }
      if (debug)
	type(NULL,0,NULL,"  rc=%d", rc);
      return 0; /* We report error LATER */

    }
    /* bag - */

    if (state->values[P_A_RcptDnsRBL] &&
	!valueeq(state->values[P_A_RcptDnsRBL], "-")) {
      int rc;
      if (debug)
	type(NULL,0,NULL," policytestaddr: 'rcpt-dns-rbl %s' found;",
	       state->values[P_A_RcptDnsRBL]);
      rc = rbl_dns_test(ipaf, ipaddr, state->values[P_A_RcptDnsRBL], &state->rblmsg);

      if (debug)
	type(NULL, 0, NULL, "rcpt-dns-rbl test yields: rc=%d rblmsg='%s'", rc,
	     state->rblmsg ? state->rblmsg : "<none>");

      if (!state->message){ PICK_PA_MSG(P_A_RcptDnsRBL); }
      if (debug)
	type(NULL,0,NULL,"  rc=%d", rc);
      return 0; /* We report error LATER */
    }
    return 0;
}

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

    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! */

    state->message = NULL; /* This is early initial clearing */

    if (raddr->v4.sin_family == 0){
      state->full_trust = 1;
      return 0; /* Interactive testing... */
    }

    if (raddr->v4.sin_family == AF_INET) {
      si4 = & (raddr->v4);
      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->v6.sin6_family == AF_INET6) {
      si6 = & (raddr->v6);
      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
    {
      type(NULL,0,NULL,"Unknown address format; sa_family = %d",
	   raddr->v4.sin_family);
      return -2;
    }

    state->request = 0;
    state->content_filter = -1;

    rc = _addrtest_(rel, state, pbuf, 1);
    if (debug) fflush(stdout);
    return rc;
}


static int 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 > sizeof(pbuf)-3)
	addr_len = sizeof(pbuf)-3;
    strncpy(pbuf+2, ptr, addr_len);
    pbuf[2+addr_len] = 0;
    strlower(pbuf+2);

    if (pbuf[2] == '[') {
      /* IP address literal ??? */
      if (strncmp(pbuf+2+1,"ipv6",4)==0) {
#if defined(AF_INET6) && defined(INET6)
	char *s = strchr(pbuf+3,']');
	if (s) *s = 0;
	if (inet_pton(AF_INET6, pbuf+3+5, pbuf+2) < 1) {
	  /* XX: Duh ?  Our input is syntax checked, so
	     this ERROR should not happen.. */
	}
	pbuf[0] = 19;
	pbuf[1] = P_K_IPv6;
	pbuf[18] = 128;
#else
	/* XXX: Duh ??? IPv6 not supported, how to report errs ?? */
#endif
      } else {
	char *s = strchr(pbuf+3,']');
	if (s) *s = 0;
	if (inet_pton(AF_INET, pbuf+3, (u_char *)pbuf+2) < 1) {
	  /* XX: Duh ?  Our input is syntax checked, so
	     this ERROR should not happen.. */
	}

	pbuf[0] = 7;
	pbuf[1] = P_K_IPv4;
	pbuf[6] = 32;
      }
      return _addrtest_(rel,state,pbuf, 0);
    }

    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)
	  type(NULL,0,NULL," DEBUG: %s", showkey(pbuf));
	result = checkaddr(rel, state, pbuf);

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

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

static const char * find_nonqchr __((const char *, int, int));
static const char *
find_nonqchr(input, chr, inlen)
const char *input;
int chr, inlen;
{
  int quote = 0;
  /* Find first unquoted ``chr'' character, and return a pointer to it */
  for (; inlen > 0; --inlen,++input) {
    if (*input == '"')
      quote = !quote;
    if (*input == '\\') {
      --inlen; ++input;
      continue;
    }
    if (*input == chr && !quote)
      return input;
  }
  return NULL;
}

/* 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];
    const char *at;
    int 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 = find_nonqchr(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));

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

static 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;
    if (state->full_trust)
	return 0;

    /*
     * This is somewhat controversial.
     * This exists solely to allow simplification of
     * smtpserver.conf  file by having the HELO/EHLO
     * strings to be rejected stored in the policy
     * database.
     *
     * In Sep-2001 it became apparent that very least
     * there is no point in analyzing '[...]' numeric
     * HELO parameter contained address data.
     *
     * Current code will only look for the input string
     * in the database (by domain lookup protocol), and
     * react on it by smalling the door shut (if any).
     *
     */


    if (*str != '[') { /* Don't test address literals! */

      /* 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 (valueeq(state->values[P_A_REJECTNET], "+")) {
	state->always_reject = 1;
	PICK_PA_MSG(P_A_REJECTNET);
	return -1;
      }
      if (valueeq(state->values[P_A_FREEZENET], "+")) {
	state->always_freeze = 1;
	PICK_PA_MSG(P_A_FREEZENET);
	return  1;
      }

    }

    return 0;
}

static int pt_sourcedomain(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;
    if (state->full_trust)
	return 0;

    /* state->request initialization !! */
    state->request = ( 1 << P_A_REJECTNET    |
		       1 << P_A_FREEZENET    |
		       1 << P_A_RELAYCUSTNET |
		       1 << P_A_InboundSizeLimit  |
		       1 << P_A_OutboundSizeLimit  );
    state->request |= ( 1 << P_A_RcptDnsRBL );	/* bag */

    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 (valueeq(state->values[P_A_REJECTNET], "+")) {
	state->always_reject = 1;
	PICK_PA_MSG(P_A_REJECTNET);
	return -1;
    }
    if (valueeq(state->values[P_A_FREEZENET], "+")) {
	state->always_freeze = 1;
	PICK_PA_MSG(P_A_FREEZENET);
	return  1;
    }
    if (valueeq(state->values[P_A_RELAYCUSTNET], "+")) {
      if (debug)
	type(NULL,0,NULL," pt_sourceaddr: 'relaycustnet +' found");
      state->always_accept = 1;
      PICK_PA_MSG(P_A_RELAYCUSTNET);
      return  0;
    }
    if (valueeq(state->values[P_A_FullTrustNet], "+")) {
      if (debug)
	type(NULL,0,NULL," pt_sourceaddr: 'fulltrustnet +' found");
      state->full_trust = 1;
      PICK_PA_MSG(P_A_FullTrustNet);
      return  0;
    }
    /* bag + */
    if (state->rblmsg == NULL &&	/* only if no rbl_message before (from lookup by net) */
	state->values[P_A_RcptDnsRBL] &&
	state->values[P_A_RcptDnsRBL][0] == '_') {
      if (debug)
	type(NULL,0,NULL," pt_sourceaddr: 'rcpt-dns-rbl %s' found;",
	       state->values[P_A_RcptDnsRBL]);
      if (state->values[P_A_RcptDnsRBL][1] != '+') {
	state->rblmsg = strdup(state->values[P_A_RcptDnsRBL] + 1);
      }
      free(state->values[P_A_RcptDnsRBL]);
      return 0; /* We report error LATER */

    }
    /* bag - */
    return 0;
}

static int pt_mailfrom(rel, state, str, len)
struct policytest *rel;
struct policystate *state;
const char *str;
const int len;
{
    const char *at;
    int requestmask = 0;

    state->rcpt_nocheck  = 0;
    state->sender_reject = 0;
    state->sender_freeze = 0;
    state->sender_norelay = 0;

    if (state->always_reject)
	return -1;
    if (state->always_freeze)
	return 1;
    if (state->full_trust || state->authuser)
      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> ??? */
    /* XX: With IGNORING RFC-821-source-route "@foo:" we
           don't have problems here */

    /* Check source user */
    if (check_user(rel, state, str, len) == 0) {
      if (valueeq(state->values[P_A_FREEZESOURCE], "+")) {
	if (debug)
	  type(NULL,0,NULL," mailfrom: 'freezesource +'");
	state->sender_freeze = 1;
	PICK_PA_MSG(P_A_FREEZESOURCE);
	return 1;
      }
      if (state->values[P_A_FREEZESOURCE])
	requestmask |= 1 << P_A_FREEZESOURCE;

      if (valueeq(state->values[P_A_REJECTSOURCE], "+")) {
	if (debug)
	  type(NULL,0,NULL," mailfrom: 'rejectsource +'");
	state->sender_reject = 1;
	PICK_PA_MSG(P_A_REJECTSOURCE);
	return -1;
      }
      if (state->values[P_A_REJECTSOURCE])
	requestmask |= 1 << P_A_REJECTSOURCE;
    }

    state->request = ( 1 << P_A_REJECTSOURCE  |
		       1 << P_A_FREEZESOURCE  |
#if 0
		       1 << P_A_RELAYCUSTOMER |
#endif
		       1 << P_A_SENDERNoRelay |
		       1 << P_A_SENDERokWithDNS ) & (~ requestmask);

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

    if (valueeq(state->values[P_A_SENDERNoRelay], "+")) {
      if (debug)
	type(NULL,0,NULL," mailfrom: 'sendernorelay +'");
      state->sender_norelay = 1;
      PICK_PA_MSG(P_A_SENDERNoRelay);
    }

    if (state->values[P_A_SENDERokWithDNS] && (at[1] != '[')) {
      /* Accept if found in DNS, and not an address literal! */
      int rc = sender_dns_verify(state->values[P_A_SENDERokWithDNS][0],
				 at+1, len - (1 + at - str));
      if (debug)
	type(NULL,0,NULL," ... returns: %d", rc);
      PICK_PA_MSG(P_A_SENDERokWithDNS);
      return rc;
    }

    if (valueeq(state->values[P_A_REJECTSOURCE], "+")) {
	if (debug)
	  type(NULL,0,NULL," mailfrom: 'rejectsource +'");
	state->sender_reject = 1;
	PICK_PA_MSG(P_A_REJECTSOURCE);
	return -1;
    }
    if (valueeq(state->values[P_A_FREEZESOURCE], "+")) {
	if (debug)
	  type(NULL,0,NULL," mailfrom: 'freezesource +'");
	state->sender_freeze = 1;
	PICK_PA_MSG(P_A_FREEZESOURCE);
	return -1;
    }

    if (state->always_accept && at[1] != '[') {
      /* Always accept, and not an address literal! */
      int rc = sender_dns_verify('-', at+1, len - (1 + at - str));
      if (debug)
	type(NULL,0,NULL," ... returns: %d", rc);
      return rc;
    }
#if 0 /* Eh..., NOT! */
    if (valueeq(state->values[P_A_RELAYCUSTOMER], "+")) {
	if (debug)
	  type(NULL,0,NULL," mailfrom: 'relaycustomer +'");
	state->rcpt_nocheck = 1;
	PICK_PA_MSG(P_A_RELAYCUSTOMER);
	return  0;
    }
#endif
    return 0;
}

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

    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->full_trust)    return  0;
    if (state->authuser)      return  0;
    if (state->trust_recipients) return 0;

    /* rcptfreeze even for 'rcpt-nocheck' ? */

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

    /* Test first the full address */
    if (check_user(rel, state, str, len) == 0) {
      if (valueeq(state->values[P_A_RELAYTARGET], "+")) {
	PICK_PA_MSG(P_A_RELAYTARGET);
	return  0;
      }
      if (valueeq(state->values[P_A_RELAYTARGET], "-")) {
	PICK_PA_MSG(P_A_RELAYTARGET);
	return -1;
      }
      if (valueeq(state->values[P_A_ACCEPTbutFREEZE], "+")) {
	state->sender_freeze = 1;
	PICK_PA_MSG(P_A_ACCEPTbutFREEZE);
	return  1;
      }
      if (valueeq(state->values[P_A_TestRcptDnsRBL], "+")) {

	type(NULL, 0, NULL, "test-rcpt-dns-rbl test; rblmsg='%s'",
	     state->rblmsg ? state->rblmsg : "<none>");

	if (state->rblmsg != NULL) {
	  /* Now this is cute... the source address had RBL entry,
	     and the recipient domain had a request to honour the
	     RBL data. */
	  if (state->message != NULL) free(state->message);
	  state->message = strdup(state->rblmsg);
	  if (debug)
	    type(NULL,0,NULL," ... TestRcptDnsRBL has a message: '%s'",
		   state->rblmsg);
	  return -1;
	}
      }
    }

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

    at = find_nonqchr(str, '@', len);
    if (at != NULL) {
      if (check_domain(rel, state, at+1, len - (1 + at - str)) != 0) {
	type(NULL,0,NULL,"rcptto checkdomain fails; -1");
	return -1;
      }
    } else {
      if (state->rcpt_nocheck)
	return 0;

      /* 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;]
 */

    while (((localdom = valueeq(state->values[P_A_LocalDomain], "+")) ||
	    (relayable = valueeq(state->values[P_A_RELAYTARGET], "+"))) &&
	   (percent_accept < 0)) {

      /* Ok, local domain recognized, now see if it has
	 '%'-hack at the local-part.. */

      const char *phack, *phack2;
      int llen = (at - str);

      /* How about '!' ??? */
      phack = find_nonqchr(str, '!', llen);
      if (phack != NULL && percent_accept < 0) {
	/* Bang-path from left to right... */
	/* ... each component from str to phack-1 */
	/* state->request initialization !! */
	state->request = ( 1 << P_A_RELAYTARGET     |
			   1 << P_A_ACCEPTbutFREEZE |
			   1 << P_A_ACCEPTifMX      |
			   1 << P_A_ACCEPTifDNS     |
			   1 << P_A_TestRcptDnsRBL  |
			   1 << P_A_LocalDomain );

	llen = (phack - str);
	if (check_domain(rel, state, str, llen) != 0)
	  return -1;
	
	str = phack+1;
	continue;
      }

      /* Find the LAST of unquoted '%' characters! */
      phack = find_nonqchr(str, '%', llen);
      phack2 = NULL;
      while (phack && !phack2) {
	int ll2 = at - phack -1;
	phack2 = find_nonqchr(phack+1, '%', ll2);
	if (phack2) {
	  phack = phack2;
	  phack2 = NULL;
	} else
	  break; /* Not found */
      }
      /* Now do test of the domain in there, is it ok
	 for relaying to ? */
      if (phack) {
	/* state->request initialization !! */
	state->request = ( 1 << P_A_RELAYTARGET     |
			   1 << P_A_ACCEPTbutFREEZE |
			   1 << P_A_ACCEPTifMX      |
			   1 << P_A_ACCEPTifDNS     |
			   1 << P_A_TestRcptDnsRBL  |
			   1 << P_A_LocalDomain );

	llen = (at - phack)-1;
	if (check_domain(rel, state, phack+1, llen) != 0)
	  return -1;
	at = phack;
	*((int*)&len) = (1 + at - str) + llen;
	continue;
      }

      if (phack != NULL && percent_accept < 0) {
	return -2; /* Reject the percent kludge */
      }

      break; /* Ok, could be ok, but RBL may say differently ... */
    }


    /* Do target specific rejects early */

    if (valueeq(state->values[P_A_RELAYTARGET], "-")) {
      PICK_PA_MSG(P_A_RELAYTARGET);
      return -1;
    }


    if (valueeq(state->values[P_A_ACCEPTbutFREEZE], "+")) {
	state->sender_freeze = 1;
	PICK_PA_MSG(P_A_ACCEPTbutFREEZE);
	return  1;
    }

    if (valueeq(state->values[P_A_TestRcptDnsRBL], "+")) {

      type(NULL, 0, NULL, "test-rcpt-dns-rbl test; rblmsg='%s'",
	   state->rblmsg ? state->rblmsg : "<none>");

      if (state->rblmsg != NULL) {
	/* Now this is cute... the source address had RBL entry,
	   and the recipient domain had a request to honour the
	   RBL data. */
	if (state->message != NULL) free(state->message);
	state->message = strdup(state->rblmsg);
	if (debug)
	  type(NULL,0,NULL," ... TestRcptDnsRBL has a message: '%s'",
		 state->rblmsg);
	return -1;
      }
    }

    if (valueeq(state->values[P_A_RELAYTARGET], "+")) {
	PICK_PA_MSG(P_A_RELAYTARGET);
	return  0;
    }

    if (state->rcpt_nocheck) {
      if (debug)
	type(NULL,0,NULL," ... rcpt_nocheck is on!");
      return 0;
    }

    if (state->always_accept) {
      int rc, c = '-';

      if (state->values[P_A_ACCEPTifMX]) {
	c = state->values[P_A_ACCEPTifMX][0];
      }
      rc = client_dns_verify(c, at+1, len - (1 + at - str));
      /* XX: state->message setup! */
      if (debug)
	type(NULL,0,NULL," ... returns: %d", rc);
      PICK_PA_MSG(P_A_ACCEPTifMX);
      return rc;
    }

    if (state->values[P_A_ACCEPTifMX] || state->sender_norelay != 0) {
      int c = state->values[P_A_ACCEPTifMX] ? state->values[P_A_ACCEPTifMX][0] : '.';
      int rc = mx_client_verify(c, at+1, len - (1 + at - str)); 
      /* XX: state->message setup! */
      if (debug)
	type(NULL,0,NULL," ...(mx_client_verify('%.*s')) returns: %d",
	       (int)(len - (1 + at - str)), at+1, rc);
      PICK_PA_MSG(P_A_ACCEPTifMX);
      return rc;
    }

    if (state->values[P_A_ACCEPTifDNS]) {
      int rc = client_dns_verify(state->values[P_A_ACCEPTifDNS][0],
				 at+1, len - (1 + at - str));
      /* XX: state->message setup! */
      if (debug)
	type(NULL,0,NULL," ... returns: %d", rc);
      PICK_PA_MSG(P_A_ACCEPTifDNS);
      return rc;
    }

    return 0;
}

static int pt_rcptpostmaster(rel, state, str, len)
struct policytest *rel;
struct policystate *state;
const char *str;
const int len;
{
    /* state->request initialization !! */
    state->request = ( 1 << P_A_RELAYTARGET );

    if (check_user(rel, state, str, len) == 0) {
      if (valueeq(state->values[P_A_RELAYTARGET], "+")) {
	PICK_PA_MSG(P_A_RELAYTARGET);
	return  0;
      }
    }
    return -1;
}


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

    if (state->authuser == NULL)
      state->authuser = (char*)authuser;

    if (debug) {
	type(NULL,0,NULL," policytest what=%d", what);
	printstate(state);
    }

    if (state->message != NULL)
      free(state->message);
    state->message = NULL;

    switch(what) {
    case POLICY_SOURCEDOMAIN:
      rc = pt_sourcedomain(rel, state, str, len);
      break;
    case POLICY_HELONAME:
      rc = pt_heloname(rel, state, str, len);
      break;
    case POLICY_MAILFROM:
      rc = pt_mailfrom(rel, state, str, len);
      break;
    case POLICY_RCPTTO:
      rc = pt_rcptto(rel, state, str, len);
      break;
    case POLICY_RCPTPOSTMASTER:
      rc = pt_rcptpostmaster(rel, state, str, len);
      break;
    default:
      abort();			/* Code error! Bad policy ! */
      return 9999; /* To silence most compilers.. */
    }
    if (debug) fflush(stdout);
    return rc;
}

char *
policymsg(rel, state)
struct policytest *rel;
struct policystate *state;
{
    return state->message;
}

long
policyinsizelimit(rel, state)
struct policytest *rel;
struct policystate *state;
{
    return state->maxinsize;
}

long
policysameiplimit(rel, state)
struct policytest *rel;
struct policystate *state;
{
    return state->maxsameiplimit;
}


syntax highlighted by Code2HTML, v. 0.9.1