/* $Id: list.c,v 1.9.2.2 2006/10/02 17:02:42 manu Exp $ */

/*
 * Copyright (c) 2006 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: list.c,v 1.9.2.2 2006/10/02 17:02:42 manu Exp $");
#endif
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <errno.h>
#include <sysexits.h>
#include <sys/types.h>
#include <regex.h>

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

#include <netinet/in.h>

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

struct all_list all_list_head;
struct all_list_entry *glist;


void
all_list_init(void)
{
	LIST_INIT(&all_list_head);

	glist_init();
	return;
}

void
all_list_clear(void)	/* acllist must be write locked */
{
	struct all_list_entry *ale;

	while(!LIST_EMPTY(&all_list_head)) {
		ale = LIST_FIRST(&all_list_head);
		LIST_REMOVE(ale, al_list);

		all_list_put(ale);	

		free(ale);
	}

	all_list_init();

	return;
}

struct all_list_entry *
all_list_get(type, name)
	int type;
	char *name;
{
	struct all_list_entry *ale;

	if ((ale = malloc(sizeof(*ale))) == NULL) {
		mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}

	ale->al_type = type;
	strncpy(ale->al_name, name, sizeof(ale->al_name));
	ale->al_name[sizeof(ale->al_name) - 1] = '\0';
	LIST_INIT(&ale->al_head);

	LIST_INSERT_HEAD(&all_list_head, ale, al_list);

	return ale;
}

void
all_list_put(ale)
	struct all_list_entry *ale;
{
	struct list_entry *le;

	while(!LIST_EMPTY(&ale->al_head)) {
		le = LIST_FIRST(&ale->al_head);
		LIST_REMOVE(le, l_list);

		switch(le->l_type) {
		case L_STRING:
			free(le->l_data.string);
			break;

		case L_REGEX:
			regfree(le->l_data.regex);
			break;

#ifdef USE_DNSRBL
		case L_DNSRBL:
			/* Nothing to do, it is free'ed with dnsrbl_list */
			break;
#endif

		case L_ADDR:
			free(le->l_data.netblock.nb_addr);
			free(le->l_data.netblock.nb_mask);
			break;
		default:
			mg_log(LOG_ERR, "unexpected type %d", ale->al_type);
			exit(EX_SOFTWARE);
			break;
		}

		free(le);
	}

	return;
}

void
list_add(ale, type, data)
	struct all_list_entry *ale;
	enum item_type type;
	void *data;
{
	struct list_entry *le;

	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load list item %s", (char *)data);

	if ((le = malloc(sizeof(*le))) == NULL) {
		mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}

	le->l_type = type;

	switch(type) {
	case L_STRING:
		if ((le->l_data.string = strdup(data)) == NULL) {
			mg_log(LOG_ERR, "strdup failed: %s", strerror(errno));
			exit(EX_OSERR);
		}
		break;

#define ERRLEN 1024
	case L_REGEX: {
		size_t len;
		char *str = (char *)data;
		regex_t *re;
		int error;
		int extended;
		char errstr[ERRLEN + 1];

		/* Strip leading and trailing slashes */
		len = strlen(str);
		if (len > 0)
			str[len - 1] = '\0';
		str++;

		if ((re = malloc(sizeof(*re))) == NULL) {
			mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
			exit(EX_OSERR);
		}

		extended = (conf.c_extendedregex ? REG_EXTENDED : 0);
		if ((error = regcomp(re, str, extended | REG_ICASE)) != 0) {
			regerror(error, re, errstr, ERRLEN);
			mg_log(LOG_ERR, "bad regular expression \"%s\": %s",
			    str, errstr);
			exit(EX_DATAERR);
		}

		le->l_data.regex = re;
		break;
	}
		
	case L_ADDR:
		/* Not done here */
		/* FALLTHROUGH */
	default:
		mg_log(LOG_ERR, "unexpected l_type %d", type);
		exit(EX_OSERR);
	}

	LIST_INSERT_HEAD(&ale->al_head, le, l_list);
}

/* Lot of code duplicate with acl_add_netblock() ... */
void
list_add_netblock(ale, sa, salen, cidr)
	struct all_list_entry *ale;
	struct sockaddr *sa;
	socklen_t salen;
	int cidr;
{
	struct list_entry *le;
	ipaddr mask;
	int maxcidr, masklen;
#ifdef AF_INET
	int i;
#endif
	if (conf.c_debug || conf.c_acldebug) { 
		char addrstr[IPADDRSTRLEN];

		iptostring(sa, salen, addrstr, sizeof(addrstr));
                mg_log(LOG_DEBUG, "load list item %s/%d", addrstr, cidr);
	}

	if ((le = malloc(sizeof(*le))) == NULL) {
		mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}

	le->l_type = L_ADDR;

	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 line %d", conf_line);
		exit(EX_DATAERR);
		break;
	}

	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
	default:
		break;
	}

	if (((le->l_data.netblock.nb_addr = malloc(salen)) == NULL) ||
	    ((le->l_data.netblock.nb_mask = malloc(masklen)) == NULL)) {
		mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
		exit(EX_OSERR);
	}

	le->l_data.netblock.nb_addrlen = salen;
	memcpy(le->l_data.netblock.nb_addr, sa, salen);
	memcpy(le->l_data.netblock.nb_mask, &mask, masklen);

	LIST_INSERT_HEAD(&ale->al_head, le, l_list);

	return;
}

#define DEBUGSTR 1024
void
all_list_settype(ale, type)
	struct all_list_entry *ale;
	enum list_type type;
{
	ale->al_type = type;

	if (conf.c_debug || conf.c_acldebug) { 
		char debugstr[DEBUGSTR + 1];

		snprintf(debugstr, DEBUGSTR, "load list type ");
		switch(type) {
		case LT_FROM:
			strncat(debugstr, "from ", DEBUGSTR);
			break;
		case LT_RCPT:
			strncat(debugstr, "rcpt ", DEBUGSTR);
			break;
		case LT_DOMAIN:
			strncat(debugstr, "domain ", DEBUGSTR);
			break;
#ifdef USE_DNSRBL
		case LT_DNSRBL:
			strncat(debugstr, "dnsrbl ", DEBUGSTR);
			break;
#endif
		case LT_ADDR:
			strncat(debugstr, "addr ", DEBUGSTR);
			break;
		default:
			mg_log(LOG_ERR, "unexpected al_type %d", 
			    type);
			break;
		}
		mg_log(LOG_DEBUG, debugstr);
	}

#if USE_DNSRBL
	/* Lookup the DNSRBL */
	if (type == LT_DNSRBL) {
		struct list_entry *le;

		LIST_FOREACH(le, &ale->al_head, l_list) {
			struct dnsrbl_entry *de;

			if (le->l_type != L_STRING) {
				mg_log(LOG_ERR, "inconsistent list line %d",
				    conf_line);
				exit(EX_SOFTWARE);
			}

			if ((de = dnsrbl_byname(le->l_data.string)) == NULL) {
				mg_log(LOG_ERR, "Inexistent DNSRBL \"%s\" "
				    "at line %d", le->l_data.string, conf_line);
				exit(EX_DATAERR);
			}

			le->l_data.dnsrbl = de;
			le->l_type = L_DNSRBL;
		}

	}
#endif /* USE_DNSRBL */
	return;
}

void
all_list_setname(ale, name)
	struct all_list_entry *ale;
	char *name;
{
	if (conf.c_debug || conf.c_acldebug)
		mg_log(LOG_DEBUG, "load list name \"%s\"", name);

	strncpy(ale->al_name, name, sizeof(ale->al_name));
	ale->al_name[sizeof(ale->al_name) - 1] = '\0';
	return;
}

void
glist_init(void)
{
	glist = all_list_get(LT_UNKNOWN, "");
	return;
}


struct all_list_entry *
all_list_byname(name)
	char *name;
{
	struct all_list_entry *ale;

	LIST_FOREACH(ale, &all_list_head, al_list) {
		if (strcmp(ale->al_name, name) == 0)
			break;
	}

	return ale;
}

int
list_addr_filter(list, sa)
	struct all_list_entry *list;
	struct sockaddr *sa;
{
	struct list_entry *le;

	LIST_FOREACH(le, &list->al_head, l_list) {
		if (ip_match(sa, 
		    le->l_data.netblock.nb_addr, 
		    le->l_data.netblock.nb_mask))
			break;
	}

	return (le != NULL);
}

#if USE_DNSRBL
int
list_dnsrbl_filter(list,salen, sa)
	struct all_list_entry *list;
	socklen_t salen;
	struct sockaddr *sa;
{
	struct list_entry *le;

	LIST_FOREACH(le, &list->al_head, l_list) {
		if (dnsrbl_check_source(sa, salen, le->l_data.dnsrbl) == 1)
			break;
	}

	return (le != NULL);
}
#endif

int
list_macro_filter(list, ctx)
	struct all_list_entry *list;
	SMFICTX *ctx;
{
	struct list_entry *le;

	LIST_FOREACH(le, &list->al_head, l_list) {
		if (macro_check(ctx, le->l_data.macro) == 0)
			break;
	}

	return (le != NULL);
}

int
list_from_filter(list, from)
	struct all_list_entry *list;
	char *from;
{
	struct list_entry *le;

	LIST_FOREACH(le, &list->al_head, l_list) {
		switch(le->l_type) {
		case L_STRING:
			if (emailcmp(from, le->l_data.string) == 0)
				goto from_out;
			break;
		case L_REGEX:
			if (regexec(le->l_data.regex, 
			    from, 0, NULL, 0) == 0)
				goto from_out;
			break;
		default:
			mg_log(LOG_ERR, "corrupted list");
			exit(EX_SOFTWARE);
			break;
		}
	}
from_out:
	return (le != NULL);
}

int
list_rcpt_filter(list, rcpt)
	struct all_list_entry *list;
	char *rcpt;
{
	struct list_entry *le;

	LIST_FOREACH(le, &list->al_head, l_list) {
		switch(le->l_type) {
		case L_STRING:
			if (emailcmp(rcpt, le->l_data.string) == 0)
				goto rcpt_out;
			break;
		case L_REGEX:
			if (regexec(le->l_data.regex, 
			    rcpt, 0, NULL, 0) == 0)
				goto rcpt_out;
			break;
		default:
			mg_log(LOG_ERR, "corrupted list");
			exit(EX_SOFTWARE);
			break;
		}
	}
rcpt_out:
	return (le != NULL);
}

int
list_domain_filter(list, domain)
	struct all_list_entry *list;
	char *domain;
{
	struct list_entry *le;

	LIST_FOREACH(le, &list->al_head, l_list) {
		switch(le->l_type) {
		case L_STRING:
			if (domaincmp(domain, le->l_data.string))
				goto domain_out;
			break;
		case L_REGEX:
			if (regexec(le->l_data.regex, 
			    domain, 0, NULL, 0) == 0)
				goto domain_out;
			break;
		default:
			mg_log(LOG_ERR, "corrupted list");
			exit(EX_SOFTWARE);
			break;
		}
	}
domain_out:
	return (le != NULL);
}




syntax highlighted by Code2HTML, v. 0.9.1