/* $Id: acl.c,v 1.33.2.3 2006/10/08 13:21:13 manu Exp $ */

/*
 * Copyright (c) 2004 Emmanuel Dreyfus
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by Emmanuel Dreyfus
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,  
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#ifdef HAVE_SYS_CDEFS_H
#include <sys/cdefs.h>
#ifdef __RCSID
__RCSID("$Id: acl.c,v 1.33.2.3 2006/10/08 13:21:13 manu Exp $");
#endif
#endif

#ifdef HAVE_OLD_QUEUE_H
#include "queue.h"
#else 
#include <sys/queue.h>
#endif

#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <pthread.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <regex.h>

#include "acl.h"
#include "conf.h"
#include "sync.h"
#include "list.h"
#ifdef USE_DNSRBL
#include "dnsrbl.h"
#endif
#include "macro.h"
#include "milter-greylist.h"

struct acllist acl_head;
pthread_rwlock_t acl_lock;

static struct acl_entry gacl;

static void
acl_init_entry (void) {
	memset (&gacl, 0, sizeof (gacl));
	gacl.a_delay = -1;
	gacl.a_autowhite = -1;
}

void
acl_init(void) {
	int error;

	TAILQ_INIT(&acl_head);
	if ((error = pthread_rwlock_init(&acl_lock, NULL)) != 0) {
		mg_log(LOG_ERR, "pthread_rwlock_init failed: %s", 
		    strerror(error));
		exit(EX_OSERR);
	}
	acl_init_entry();

	return;
}

void
acl_add_flushaddr(void) {
	gacl.a_flags |= A_FLUSHADDR;
	return;	
}

void
acl_add_netblock(sa, salen, cidr)
	struct sockaddr *sa;
	socklen_t salen;
	int cidr;
{
	ipaddr mask;
	char addrstr[IPADDRSTRLEN];
	char maskstr[IPADDRSTRLEN];
	int maxcidr, masklen;
#ifdef AF_INET6
	int i;
#endif

	if (gacl.a_addr != NULL) {
		mg_log(LOG_ERR,
		    "addr specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	switch (sa->sa_family) {
	case AF_INET:
		maxcidr = 32;
		masklen = sizeof(mask.in4);
		break;
#ifdef AF_INET6
	case AF_INET6:
		maxcidr = 128;
		masklen = sizeof(mask.in6);
		break;
#endif
	default:
		mg_log(LOG_ERR,
		    "bad address family in acl list line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	if (cidr > maxcidr || cidr < 0) {
		mg_log(LOG_ERR, "bad mask in acl list line %d", 
		    conf_line);
		exit(EX_DATAERR);
	}

	switch (sa->sa_family) {
	case AF_INET:
		prefix2mask4(cidr, &mask.in4);
		SADDR4(sa)->s_addr &= mask.in4.s_addr;
		break;
#ifdef AF_INET6
	case AF_INET6:
		prefix2mask6(cidr, &mask.in6);
		for (i = 0; i < 16; i += 4)
			*(uint32_t *)&SADDR6(sa)->s6_addr[i] &=
			    *(uint32_t *)&mask.in6.s6_addr[i];
		break;
#endif
	}

	if ((gacl.a_addr = malloc(salen)) == NULL ||
	    (gacl.a_mask = malloc(masklen)) == NULL) {
		mg_log(LOG_ERR, "acl malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
		
	gacl.a_addrlen = salen;
	memcpy(gacl.a_addr, sa, salen);
	memcpy(gacl.a_mask, &mask, masklen);

	if (conf.c_debug || conf.c_acldebug) {
		iptostring(gacl.a_addr, gacl.a_addrlen, addrstr,
		    sizeof(addrstr));
		inet_ntop(gacl.a_addr->sa_family, gacl.a_mask, maskstr,
		    sizeof(maskstr));
		mg_log(LOG_DEBUG, "load acl net %s/%s", addrstr, maskstr);
	}

	return;
}

void
acl_add_from(email)
	char *email;
{
	if (gacl.a_from != NULL || 
	    gacl.a_from_re != NULL ||
	    gacl.a_fromlist != NULL ) {
		mg_log(LOG_ERR,
		    "from specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	if ((gacl.a_from = strdup(email)) == NULL) {
		mg_log(LOG_ERR, "acl malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl from %s", email);

	return;
}

#ifdef USE_DNSRBL
void
acl_add_dnsrbl(dnsrbl)
	char *dnsrbl;
{
	if (gacl.a_dnsrbl != NULL ||
	    gacl.a_dnsrbllist != NULL) {
		mg_log(LOG_ERR,
		    "dnsrbl specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	if ((gacl.a_dnsrbl = dnsrbl_byname(dnsrbl)) == NULL) {
		mg_log(LOG_ERR, "unknown DNSRBL \"%s\"", dnsrbl);
		exit(EX_DATAERR);
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl dnsrbl %s", dnsrbl);

	return;
}
#endif

void
acl_add_macro(macro)
	char *macro;
{
	if (gacl.a_macro != NULL ||
	    gacl.a_macrolist != NULL) {
		mg_log(LOG_ERR,
		    "sm_macro specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	if ((gacl.a_macro = macro_byname(macro)) == NULL) {
		mg_log(LOG_ERR, "unknown sm_macro \"%s\"", macro);
		exit(EX_DATAERR);
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl sm_macro %s", macro);

	return;
}

void
acl_add_rcpt(email)
	char *email;
{
	if (gacl.a_rcpt != NULL || 
	    gacl.a_rcpt_re != NULL ||
	    gacl.a_rcptlist != NULL) {
		mg_log(LOG_ERR,
		    "rcpt specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	if ((gacl.a_rcpt = strdup(email)) == NULL) {
		mg_log(LOG_ERR, "acl malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl rcpt %s", email);

	return;
}

void
acl_add_domain(domain)
	char *domain;
{
	if (gacl.a_domain != NULL || 
	    gacl.a_domain_re != NULL ||
	    gacl.a_domainlist != NULL) {
		mg_log(LOG_ERR,
		    "domain specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	if ((gacl.a_domain = strdup(domain)) == NULL) {
		mg_log(LOG_ERR, "acl malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl domain %s", domain);

	return;
}

#define ERRLEN 1024
void
acl_add_from_regex(regexstr)
	char *regexstr;
{
	size_t len;
	int error;
	char errstr[ERRLEN + 1];

	if (gacl.a_from != NULL || 
	    gacl.a_from_re != NULL ||
	    gacl.a_fromlist != NULL) {
		mg_log(LOG_ERR,
		    "from specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	/* 
	 * Strip leading and trailing slashes
	 */
	len = strlen(regexstr);
	if (len > 0)
		regexstr[len - 1] = '\0';
	regexstr++;

	if ((gacl.a_from_re = malloc(sizeof(*gacl.a_from_re))) == NULL) {
		mg_log(LOG_ERR, "acl malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
	if ((error = regcomp(gacl.a_from_re, regexstr, 
	    (conf.c_extendedregex ? REG_EXTENDED : 0) | REG_ICASE)) != 0) {
		regerror(error, gacl.a_from_re, errstr, ERRLEN);
		mg_log(LOG_ERR, "bad regular expression \"%s\": %s", 
		    regexstr, errstr);
		exit(EX_OSERR);
	}

	if ((gacl.a_from_re_copy = strdup(regexstr)) == NULL) {
		mg_log(LOG_ERR, "acl strdup failed: %s", strerror(errno));
		exit(EX_OSERR);
	}

	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl from regex %s", regexstr);

	return;
}

void
acl_add_rcpt_regex(regexstr)
	char *regexstr;
{
	size_t len;
	int error;
	char errstr[ERRLEN + 1];

	if (gacl.a_rcpt != NULL || 
	    gacl.a_rcpt_re != NULL ||
	    gacl.a_rcptlist != NULL) {
		mg_log(LOG_ERR,
		    "rcpt specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	/* 
	 * Strip leading and trailing slashes
	 */
	len = strlen(regexstr);
	if (len > 0)
		regexstr[len - 1] = '\0';
	regexstr++;

	if ((gacl.a_rcpt_re = malloc(sizeof(*gacl.a_rcpt_re))) == NULL) {
		mg_log(LOG_ERR, "acl malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
	if ((error = regcomp(gacl.a_rcpt_re, regexstr,
	    (conf.c_extendedregex ? REG_EXTENDED : 0) | REG_ICASE)) != 0) {
		regerror(error, gacl.a_rcpt_re, errstr, ERRLEN);
		mg_log(LOG_ERR, "bad regular expression \"%s\": %s", 
		    regexstr, errstr);
		exit(EX_OSERR);
	}

	if ((gacl.a_rcpt_re_copy = strdup(regexstr)) == NULL) {
		mg_log(LOG_ERR, "acl strdup failed: %s", strerror(errno));
		exit(EX_OSERR);
	}

	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl rcpt regex %s", regexstr);

	return;
}

void
acl_add_domain_regex(regexstr)
	char *regexstr;
{
	size_t len;
	int error;
	char errstr[ERRLEN + 1];

	if (gacl.a_domain != NULL || 
	    gacl.a_domain_re != NULL ||
	    gacl.a_domainlist != NULL) {
		mg_log(LOG_ERR,
		    "domain specified twice in ACL line %d",
		    conf_line);
		exit(EX_DATAERR);
	}
	/* 
	 * Strip leading and trailing slashes
	 */
	len = strlen(regexstr);
	if (len > 0)
		regexstr[len - 1] = '\0';
	regexstr++;

	if ((gacl.a_domain_re = malloc(sizeof(*gacl.a_domain_re))) == NULL) {
		mg_log(LOG_ERR, "acl malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
	if ((error = regcomp(gacl.a_domain_re, regexstr,
	    (conf.c_extendedregex ? REG_EXTENDED : 0) | REG_ICASE)) != 0) {
		regerror(error, gacl.a_domain_re, errstr, ERRLEN);
		mg_log(LOG_ERR, "bad regular expression \"%s\": %s", 
		    regexstr, errstr);
		exit(EX_OSERR);
	}

	if ((gacl.a_domain_re_copy = strdup(regexstr)) == NULL) {
		mg_log(LOG_ERR, "acl strdup failed: %s", strerror(errno));
		exit(EX_OSERR);
	}

	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl domain regex %s", regexstr);

	return;
}

struct acl_entry *
acl_register_entry_first(acl_type)	/* acllist must be write-locked */
	acl_type_t acl_type;
{
	struct acl_entry *acl;

	if ((acl = malloc(sizeof(*acl))) == NULL) {
		mg_log(LOG_ERR, "ACL malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
	*acl = gacl;
	acl->a_type = acl_type;
	acl->a_line = conf_line - 1;
	TAILQ_INSERT_HEAD(&acl_head, acl, a_list);
	acl_init_entry ();

	if (conf.c_debug || conf.c_acldebug) {
		switch(acl_type) {
		case A_GREYLIST:
			mg_log(LOG_DEBUG, "register acl first GREYLIST");
			break;
		case A_WHITELIST:
			mg_log(LOG_DEBUG, "register acl first WHITELIST");
			break;
		case A_BLACKLIST:
			mg_log(LOG_DEBUG, "register acl first BLACKLIST");
			break;
		default:
			mg_log(LOG_ERR, "unecpected acl_type %d", acl_type);
			exit(EX_SOFTWARE);
			break;
		}
	}

	return acl;
}

struct acl_entry *
acl_register_entry_last(acl_type)	/* acllist must be write-locked */
	acl_type_t acl_type;
{
	struct acl_entry *acl;

	if ((acl = malloc(sizeof(*acl))) == NULL) {
		mg_log(LOG_ERR, "ACL malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
	*acl = gacl;
	acl->a_type = acl_type;
	acl->a_line = conf_line - 1;
	TAILQ_INSERT_TAIL(&acl_head, acl, a_list);
	acl_init_entry ();

	if (conf.c_debug || conf.c_acldebug) {
		switch(acl_type) {
		case A_GREYLIST:
			mg_log(LOG_DEBUG, "register acl last GREYLIST");
			break;
		case A_WHITELIST:
			mg_log(LOG_DEBUG, "register acl last WHITELIST");
			break;
		case A_BLACKLIST:
			mg_log(LOG_DEBUG, "register acl last BLACKLIST");
			break;
		default:
			mg_log(LOG_ERR, "unecpected acl_type %d", acl_type);
			exit(EX_SOFTWARE);
			break;
		}
	}

	return acl;
}

int 
acl_filter(ctx, priv, rcpt)
	SMFICTX *ctx;
	struct mlfi_priv *priv;
	char *rcpt;
{
	struct sockaddr *sa;
	socklen_t salen;
	char *hostname;
	char *from;
	char *queueid;
	struct acl_entry *acl;
	char addrstr[IPADDRSTRLEN];
	char whystr[HDRLEN];
	char tmpstr[HDRLEN];
	int retval;
	int testmode = conf.c_testmode;

	sa = SA(&priv->priv_addr);
	salen = priv->priv_addrlen;
	hostname = priv->priv_hostname;
	from = priv->priv_from;
	queueid = priv->priv_queueid;

	ACL_RDLOCK;

	TAILQ_FOREACH(acl, &acl_head, a_list) {
		retval = 0;

		if (acl->a_addrlist != NULL) {
			if (list_addr_filter(acl->a_addrlist, sa)) {
				retval |= EXF_ADDR;
			} else {
				continue;
			}
		}

		if (acl->a_addr != NULL) {
			if (ip_match(sa, acl->a_addr, acl->a_mask)) {
				retval |= EXF_ADDR;
			} else  {
				continue;
			}
		}

		if (acl->a_domainlist != NULL) {
			if (list_domain_filter(acl->a_domainlist, hostname)) {
				retval |= EXF_DOMAIN;
			} else {
				continue;
			}
		}

		if (acl->a_domain != NULL) {
			if (domaincmp(hostname, acl->a_domain)) {
				retval |= EXF_DOMAIN;
			} else {
				continue;
			}
		}

		if (acl->a_domain_re != NULL) {
			if (regexec(acl->a_domain_re,
			    hostname, 0, NULL, 0) == 0) {
				retval |= EXF_DOMAIN;
			} else {
				continue;
			}
		}

		if (acl->a_fromlist != NULL) {
			if (list_from_filter(acl->a_fromlist, from)) {
				retval |= EXF_FROM;
			} else {
				continue;
			}
		}

		if (acl->a_from != NULL) {
			if (emailcmp(from, acl->a_from) == 0) {
				retval |= EXF_FROM;
			} else {
				continue;
			}
		}

		if (acl->a_from_re != NULL) {
			if (regexec(acl->a_from_re, from, 0, NULL, 0) == 0) {
				retval |= EXF_FROM;
			} else {
				continue;
			}
		}

		if (acl->a_rcptlist != NULL) {
			if (list_rcpt_filter(acl->a_rcptlist, rcpt)) {
				retval |= EXF_RCPT;
			} else {
				continue;
			}
		}

		if (acl->a_rcpt != NULL) {
			if (emailcmp(rcpt, acl->a_rcpt) == 0) {
			retval |= EXF_RCPT;
			} else {
				continue;
			}
		}

		if (acl->a_rcpt_re != NULL) {
			if (regexec(acl->a_rcpt_re, rcpt, 0, NULL, 0) == 0) {
				retval |= EXF_RCPT;
			} else {
				continue;
			}
		}
#ifdef USE_DNSRBL
		if (acl->a_dnsrbllist != NULL) {
			if (list_dnsrbl_filter(acl->a_dnsrbllist, salen, sa)) {
				retval |= EXF_DNSRBL;
			} else {
				continue;
			}
		}

		if (acl->a_dnsrbl != NULL) {
			if (dnsrbl_check_source(sa, 
			    salen, acl->a_dnsrbl) == 1) {
				retval |= EXF_DNSRBL;
			} else {
				continue;
			}
		}
#endif
		if (acl->a_macrolist != NULL) {
			if (list_macro_filter(acl->a_macrolist, ctx)) {
				retval |= EXF_MACRO;
			} else {
				continue;
			}
		}
		if (acl->a_macro != NULL) {
			if (macro_check(ctx, acl->a_macro) == 0) {
				retval |= EXF_MACRO;
			} else {
				continue;
			}
		}
		/*
		 * We found an entry that matches, exit the evaluation
		 * loop
		 */
		break;
	}

	if (acl) {
		if (retval == 0)
			retval = EXF_DEFAULT;
		switch (acl->a_type) {
		case A_GREYLIST:
			retval |= EXF_GREYLIST;
			break;
		case A_WHITELIST:
			retval |= EXF_WHITELIST;
			break;
		case A_BLACKLIST:
			retval |= EXF_BLACKLIST;
			break;
		default:
			mg_log(LOG_ERR, "corrupted acl list");
			exit(EX_SOFTWARE);
			break;
		}

		priv->priv_acl_line = acl->a_line;

		priv->priv_delay =
		    (acl->a_delay != -1) ? acl->a_delay : conf.c_delay;
		priv->priv_autowhite =
		    (acl->a_autowhite != -1) ? 
		    acl->a_autowhite : conf.c_autowhite_validity;

		if (acl->a_code) {
			if ((priv->priv_code = strdup(acl->a_code)) == NULL) {
				mg_log(LOG_ERR, "strdup failed: %s", 
				    strerror(errno));
				exit(EX_OSERR);
			}
		}
		if (acl->a_ecode) {
			if ((priv->priv_ecode = strdup(acl->a_ecode)) == NULL) {
				mg_log(LOG_ERR, "strdup failed: %s", 
				    strerror(errno));
				exit(EX_OSERR);
			}
		}
		if (acl->a_msg) {
			if ((priv->priv_msg = strdup(acl->a_msg)) == NULL) {
				mg_log(LOG_ERR, "strdup failed: %s", 
				    strerror(errno));
				exit(EX_OSERR);
			}
		}
			

		if (acl->a_flags & A_FLUSHADDR)
			pending_del_addr(sa, salen, queueid, acl->a_line);

		if (conf.c_debug || conf.c_acldebug) {
			iptostring(sa, salen, addrstr, sizeof(addrstr));
			mg_log(LOG_DEBUG, "Mail from=%s, rcpt=%s, addr=%s[%s] "
			    "is matched by entry %s", from, rcpt, 
			    hostname, addrstr, acl_entry(acl));
		}
	} else {
		/*
		 * No match: use the default action
		 */
		if (testmode)
			retval = EXF_WHITELIST;
		else
			retval = EXF_GREYLIST;
		retval |= EXF_DEFAULT;

		priv->priv_delay = conf.c_delay;
		priv->priv_autowhite = conf.c_autowhite_validity;
	}

	if (retval & EXF_WHITELIST) {
		whystr[0] = '\0';
		if (retval & EXF_ADDR) {
			iptostring(sa, salen, addrstr, sizeof(addrstr));
			snprintf(tmpstr, sizeof(tmpstr),
			     "address %s is whitelisted", addrstr);
			ADD_REASON(whystr, tmpstr);
		}
		if (retval & EXF_DNSRBL) {
			iptostring(sa, salen, addrstr, sizeof(addrstr));
			snprintf(tmpstr, sizeof(tmpstr),
			    "address %s is whitelisted by DNSRBL", addrstr);
			ADD_REASON(whystr, tmpstr);
		}
		if (retval & EXF_DOMAIN) {
			snprintf(tmpstr, sizeof(tmpstr),
			     "sender DNS name %s is whitelisted", hostname);
			ADD_REASON(whystr, tmpstr);
		}
		if (retval & EXF_FROM) {
			snprintf(tmpstr, sizeof(tmpstr),
			     "sender %s is whitelisted", from);
			ADD_REASON(whystr, tmpstr);
		}
		if (retval & EXF_RCPT) {
			snprintf(tmpstr, sizeof(tmpstr),
			     "recipient %s is whitelisted", rcpt);
			ADD_REASON(whystr, tmpstr);
		}
		if (retval & EXF_MACRO) {
			snprintf(tmpstr, sizeof(tmpstr),
			     "macro rule is satisfied");
			ADD_REASON(whystr, tmpstr);
		}
		if (retval & EXF_DEFAULT) {
			ADD_REASON(whystr, "this is the default action");
		}
		iptostring(sa, salen, addrstr, sizeof(addrstr));
		snprintf(tmpstr, sizeof(tmpstr),
		    "(from=%s, rcpt=%s, addr=%s[%s])", from, rcpt, hostname, addrstr);
		ADD_REASON(whystr, tmpstr);

		mg_log(LOG_INFO, "%s: skipping greylist because %s",
		    queueid, whystr);
	}
	ACL_UNLOCK;
	return retval;
}


int
domaincmp(host, domain)
	char *host;
	char *domain;
{
	int hidx, didx;

	if ((host[0] == '\0') && domain[0] == '\0')
		return 1;

	if ((host[0] == '\0') || domain[0] == '\0') 
		return 0;

	hidx = strlen(host) - 1;
	didx = strlen(domain) - 1;

	while ((hidx >= 0) && (didx >= 0)) {
		if (tolower((int)host[hidx]) != tolower((int)domain[didx])) {
			return (0);
		}
		hidx--;
		didx--;
	}

	if (didx >= 0)
		return (0);

	return (1);
}

int 
emailcmp(big, little)
	char *big;
	char *little;
{
	int i;
	int retval = -1;
	char *cbig;
	char *clittle;
	char *ocbig;
	char *oclittle;

	if ((cbig = malloc(strlen(big) + 1)) == NULL) {
		mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
	ocbig = cbig;
	strcpy(cbig, big);

	if ((clittle = malloc(strlen(little) + 1)) == NULL) {
		mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}
	oclittle = clittle;
	strcpy(clittle, little);

	/* Strip leading <, tabs and spaces */
	while (strchr("< \t", cbig[0]) != NULL)
		cbig++;
	while (strchr("< \t", clittle[0]) != NULL)
		clittle++;

	/* Strip trailing >, tabs and spaces */
	i = strlen(cbig) - 1;
	while ((i >= 0) && (strchr("> \t", cbig[i]) != NULL))
		cbig[i--] = '\0';
	i = strlen(clittle) - 1;
	while ((i >= 0) && (strchr("> \t", clittle[i]) != NULL))
		clittle[i--] = '\0';

	while (cbig[0] && clittle[0]) {
		if (tolower((int)cbig[0]) != tolower((int)clittle[0]))
			break;
		cbig++;
		clittle++;
	}
		
	if (cbig[0] || clittle[0])
		retval = -1;
	else
		retval = 0;

	free(ocbig);
	free(oclittle);

	return retval;
}

void
acl_clear(void) {	/* acllist must be write locked */
	struct acl_entry *acl;

	while (!TAILQ_EMPTY(&acl_head)) {
		acl = TAILQ_FIRST(&acl_head);
		TAILQ_REMOVE(&acl_head, acl, a_list);

		if (acl->a_addr != NULL) {
			free(acl->a_addr);
			free(acl->a_mask);
		}
		if (acl->a_from != NULL)
			free(acl->a_from);
		if (acl->a_rcpt != NULL)
			free(acl->a_rcpt);
		if (acl->a_domain != NULL)
			free(acl->a_domain);
		if (acl->a_from_re != NULL)
			regfree(acl->a_from_re);
		if (acl->a_from_re_copy != NULL)
			free(acl->a_from_re_copy);
		if (acl->a_rcpt_re != NULL)
			regfree(acl->a_rcpt_re);
		if (acl->a_rcpt_re_copy != NULL)
			free(acl->a_rcpt_re_copy);
		if (acl->a_domain_re != NULL)
			regfree(acl->a_domain_re);
		if (acl->a_domain_re_copy != NULL)
			free(acl->a_domain_re_copy);
		if (acl->a_code != NULL)
			free(acl->a_code);
		if (acl->a_ecode != NULL)
			free(acl->a_ecode);
		if (acl->a_msg != NULL)
			free(acl->a_msg);
		free(acl);
	}

	TAILQ_INIT(&acl_head);
	acl_init_entry();

	return;
}

char *
acl_entry(acl)
	struct acl_entry *acl;
{
	static char entrystr[HDRLEN];
	char tempstr[HDRLEN];
	char addrstr[IPADDRSTRLEN];
	char maskstr[IPADDRSTRLEN];
	int def = 1;

	snprintf(entrystr, HDRLEN, "acl %d ", acl->a_line);

	switch (acl->a_type) {
	case A_GREYLIST:
		mystrlcat(entrystr, "greylist ", sizeof(entrystr));
		break;
	case A_WHITELIST:
		mystrlcat(entrystr, "whitelist ", sizeof(entrystr));
		break;
	case A_BLACKLIST:
		mystrlcat(entrystr, "blacklist ", sizeof(entrystr));
		break;
	default:
		mg_log(LOG_ERR, "corrupted acl list");
		exit(EX_SOFTWARE);
		break;
	}

	if (acl->a_addrlist != NULL) {
		snprintf(tempstr, sizeof(tempstr), "addr list \"%s\" ", 
		    acl->a_addrlist->al_name);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_addr != NULL) {
		iptostring(acl->a_addr, acl->a_addrlen, addrstr,
		    sizeof(addrstr));
		inet_ntop(acl->a_addr->sa_family, acl->a_mask, maskstr,
		    sizeof(maskstr));
		snprintf(tempstr, sizeof(tempstr), "addr %s/%s ", addrstr, maskstr);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_fromlist != NULL) {
		snprintf(tempstr, sizeof(tempstr), "from list \"%s\" ", 
		    acl->a_fromlist->al_name);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_from != NULL) {
		snprintf(tempstr, sizeof(tempstr), "from %s ", acl->a_from);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_from_re != NULL) {
		snprintf(tempstr, sizeof(tempstr), "from /%s/ ",
		    acl->a_from_re_copy);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_rcptlist != NULL) {
		snprintf(tempstr, sizeof(tempstr), "rcpt list \"%s\" ", 
		    acl->a_rcptlist->al_name);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_rcpt != NULL) {
		snprintf(tempstr, sizeof(tempstr), "rcpt %s ", acl->a_rcpt);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_rcpt_re != NULL) {
		snprintf(tempstr, sizeof(tempstr), "rcpt /%s/ ",
		    acl->a_rcpt_re_copy);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_domainlist != NULL) {
		snprintf(tempstr, sizeof(tempstr), "domainlist \"%s\" ", 
		    acl->a_domainlist->al_name);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_domain != NULL) {
		snprintf(tempstr, sizeof(tempstr), "domain %s ", acl->a_domain);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_domain_re != NULL) {
		snprintf(tempstr, sizeof(tempstr), "domain /%s/ ",
		    acl->a_domain_re_copy);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
#if USE_DNSRBL
	if (acl->a_dnsrbllist != NULL) {
		snprintf(tempstr, sizeof(tempstr), "dnsrbllist \"%s\" ", 
		    acl->a_dnsrbllist->al_name);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_dnsrbl != NULL) {
		snprintf(tempstr, sizeof(tempstr), "dnsrbl \"%s\" ",
		    acl->a_dnsrbl->d_name);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
#endif
	if (acl->a_macrolist != NULL) {
		snprintf(tempstr, sizeof(tempstr), "sm_macrolist \"%s\" ", 
		    acl->a_macrolist->al_name);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_macro != NULL) {
		snprintf(tempstr, sizeof(tempstr), "sm_macro \"%s\" ",
		    acl->a_macro->m_name);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
		def = 0;
	}
	if (acl->a_delay != -1) {
		snprintf(tempstr, sizeof(tempstr), 
		    "[delay %ld] ", (long)acl->a_delay);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
	}

	if (acl->a_autowhite != -1) {
		snprintf(tempstr, sizeof(tempstr), 
		    "[aw %ld] ", (long)acl->a_autowhite);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
	}

	if (acl->a_flags & A_FLUSHADDR) {
		snprintf(tempstr, sizeof(tempstr), "[flushaddr] ");
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
	}

	if (acl->a_code) {
		snprintf(tempstr, sizeof(tempstr), 
		    "[code \"%s\"] ", acl->a_code);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
	}

	if (acl->a_ecode) {
		snprintf(tempstr, sizeof(tempstr), 
		    "[ecode \"%s\"] ", acl->a_ecode);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
	}

	if (acl->a_msg) {
		snprintf(tempstr, sizeof(tempstr), 
		    "[msg \"%s\"] ", acl->a_msg);
		mystrlcat(entrystr, tempstr, sizeof(entrystr));
	}

	if (def)
		mystrlcat(entrystr, "default", sizeof(entrystr));
	return entrystr;
}

void
acl_dump (void) {	/* acllist must be write locked */
	struct acl_entry *acl;
	char *entry;
	FILE *debug = NULL;

	/*
	 * We log the ACL to syslogd
	 * We can also write the ACL in a file because syslogd seems to lose
	 * some debugging messages on FreeBSD 4.10 :-(
	 * XXX This is disabled by default (#if 0 above) since it creates
	 * security hazards: /tmp/access-list.debug could already exist and
	 * be a link to some system file which would be overwritten.
	 * Enable it if you need it, but you may be better changing the path
	 */
#if 0
	debug = fopen("/tmp/access-list.debug", "w");
#endif
	ACL_RDLOCK;
	mg_log(LOG_INFO, "Access list dump:");
	TAILQ_FOREACH(acl, &acl_head, a_list) {
		entry = acl_entry(acl);
		mg_log(LOG_INFO, "%s", entry);
		if (debug != NULL)
			fprintf(debug, "%s", entry);
	}
	ACL_UNLOCK;
	if (debug != NULL)
		fclose(debug);
}

void 
acl_add_delay(delay)
	time_t delay;
{
	if (gacl.a_delay != -1) {
		mg_log(LOG_ERR,
		    "delay specified twice in ACL line %d", conf_line);
		exit(EX_DATAERR);
	}

	gacl.a_delay = delay;
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl delay %ld", (long)delay);

	return;
}

void
acl_add_autowhite(delay)
	time_t delay;
{
	if (gacl.a_autowhite != -1) {
		mg_log(LOG_ERR,
		    "autowhite specified twice in ACL line %d", conf_line);
		exit(EX_DATAERR);
	}

	gacl.a_autowhite = delay;
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl delay %ld", (long)delay);

	return;
}

void
acl_add_list(list)
	char *list;
{
	struct all_list_entry *ale;

	if ((ale = all_list_byname(list)) == NULL) {
		mg_log(LOG_ERR, "inexistent list \"%s\" line %d",
		    list, conf_line);
		exit(EX_DATAERR);
	}

	switch (ale->al_type) {
	case LT_FROM:
		if (gacl.a_from != NULL || 
		    gacl.a_from_re != NULL ||
		    gacl.a_fromlist != NULL) {
			mg_log(LOG_ERR,
			    "muliple from statement (list \"%s\", line %d)",
			    list, conf_line);
			exit(EX_DATAERR);
		}
		gacl.a_fromlist = ale;
		break;

	case LT_RCPT:
		if (gacl.a_rcpt != NULL ||
		    gacl.a_rcpt_re != NULL ||
		    gacl.a_rcptlist != NULL) {
			mg_log(LOG_ERR,
			    "muliple rcpt statement (list \"%s\", line %d)",
			    list, conf_line);
			exit(EX_DATAERR);
		}
		gacl.a_rcptlist = ale;
		break;

	case LT_DOMAIN:
		if (gacl.a_domain != NULL ||
		    gacl.a_domain_re != NULL ||
		    gacl.a_domainlist != NULL) {
			mg_log(LOG_ERR,
			    "muliple domain statement (list \"%s\", line %d)",
			    list, conf_line);
			exit(EX_DATAERR);
		}
		gacl.a_domainlist = ale;
		break;

#if USE_DNSRBL
	case LT_DNSRBL:
		if (gacl.a_dnsrbl != NULL ||
		    gacl.a_dnsrbllist != NULL) {
			mg_log(LOG_ERR,
			    "muliple dnsrbl statement (list \"%s\", line %d)",
			    list, conf_line);
			exit(EX_DATAERR);
		}
		gacl.a_dnsrbllist = ale;
		break;
#endif
	case LT_MACRO:
		if (gacl.a_macro != NULL ||
		    gacl.a_macrolist != NULL) {
			mg_log(LOG_ERR,
			    "muliple sm_macro statement (list \"%s\", line %d)",
			    list, conf_line);
			exit(EX_DATAERR);
		}
		gacl.a_macrolist = ale;
		break;

	case LT_ADDR:
		if (gacl.a_addr != NULL ||
		    gacl.a_addrlist != NULL) {
			mg_log(LOG_ERR,
			    "muliple addr statement (list \"%s\", line %d)",
			    list, conf_line);
			exit(EX_DATAERR);
		}
		gacl.a_addrlist = ale;
		break;

	default:
		mg_log(LOG_ERR, "unexpected al_type %d line %d", 
		    ale->al_type, conf_line);
		exit(EX_DATAERR);
		break;
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl list \"%s\"", list);

	return;
}

void 
acl_add_code(code)
	char *code;
{
	if (gacl.a_code) {
		mg_log(LOG_ERR,
		    "code specified twice in ACL line %d", conf_line);
		exit(EX_DATAERR);
	}

	if ((gacl.a_code = strdup(code)) == NULL) {
		mg_log(LOG_ERR,
		    "malloc failed in ACL line %d", conf_line);
		exit(EX_OSERR);
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl code \"%s\"", code);

	return;
}

void 
acl_add_ecode(ecode)
	char *ecode;
{
	if (gacl.a_ecode) {
		mg_log(LOG_ERR,
		    "ecode specified twice in ACL line %d", conf_line);
		exit(EX_DATAERR);
	}

	if ((gacl.a_ecode = strdup(ecode)) == NULL) {
		mg_log(LOG_ERR,
		    "malloc failed in ACL line %d", conf_line);
		exit(EX_OSERR);
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl ecode \"%s\"", ecode);

	return;
}

void 
acl_add_msg(msg)
	char *msg;
{
	if (gacl.a_msg) {
		mg_log(LOG_ERR,
		    "msg specified twice in ACL line %d", conf_line);
		exit(EX_DATAERR);
	}

	if ((gacl.a_msg = strdup(msg)) == NULL) {
		mg_log(LOG_ERR,
		    "malloc failed in ACL line %d", conf_line);
		exit(EX_OSERR);
	}
		
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load acl msg \"%s\"", msg);

	return;
}


syntax highlighted by Code2HTML, v. 0.9.1