/*
dbase.c - MessageWall database definitions
Copyright (C) 2002 Ian Gulliver

This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <firestring.h>
#include <firedns.h>
#include "messagewall.h"
#include "dnsdcc.h"
#include "virus.h"
#include "dbase.h"

static const char tagstring[] = "$Id: dbase.c,v 1.36.2.5 2002/10/17 19:04:06 ian Exp $";

const char *dbase_cidr_subnet[] = {
	"0.0.0.0",
	"128.0.0.0",
	"192.0.0.0",
	"224.0.0.0",
	"240.0.0.0",
	"248.0.0.0",
	"252.0.0.0",
	"254.0.0.0",
	"255.0.0.0",
	"255.128.0.0",
	"255.192.0.0",
	"255.224.0.0",
	"255.240.0.0",
	"255.248.0.0",
	"255.252.0.0",
	"255.254.0.0",
	"255.255.0.0",
	"255.255.128.0",
	"255.255.192.0",
	"255.255.224.0",
	"255.255.240.0",
	"255.255.248.0",
	"255.255.252.0",
	"255.255.254.0",
	"255.255.255.0",
	"255.255.255.128",
	"255.255.255.192",
	"255.255.255.224",
	"255.255.255.240",
	"255.255.255.248",
	"255.255.255.252",
	"255.255.255.254",
	"255.255.255.255"
};

void dbase_clear() { /* release local_domains, relay_ips and special_users lists from memory */
	int i;
	struct messagewall_local_domain_t *local_domain1, *local_domain2;
	struct messagewall_relay_ip_t *relay_ip1, *relay_ip2;
	struct messagewall_special_user_t *special_user1, *special_user2;

	for (i = 0; i < HASH_SIZE; i++) {
		local_domain1 = local_domain_hash[i];
		while (local_domain1 != NULL) {
			local_domain2 = local_domain1;
			local_domain1 = local_domain1->next;
			firestring_estr_free(&local_domain2->domain);
			free(local_domain2);
		}
		local_domain_hash[i] = NULL;

		relay_ip1 = relay_ip_hash[i];
		while (relay_ip1 != NULL) {
			relay_ip2 = relay_ip1;
			relay_ip1 = relay_ip1->next;
			free(relay_ip2);
		}
		relay_ip_hash[i] = NULL;

		special_user1 = special_user_hash[i];
		while (special_user1 != NULL) {
			special_user2 = special_user1;
			special_user1 = special_user1->next;
			firestring_estr_free(&special_user2->address);
			free(special_user2);
		}
		special_user_hash[i] = NULL;
	}
}

void dbase_load_local_domains() { /* populate local_domains list + hash table from local_domains config file */
	struct messagewall_local_domain_t *local_domain1;
	char line[257];
	unsigned int u;
	FILE *f;

	if (local_domains == NULL)
		return;

	/*
	 * we're inside the chroot; everything should be root controlled and fopen shouldn't be a problem
	 * SECURITY: do not let users play with files inside the chroot
	 */
	f = fopen(local_domains,"r"); /* ITS4: ignore fopen */
	if (f == NULL) {
		perror("Unable to open local_domains file");
		exit(100);
	}
	/*
	 * line really does have 257 bytes of space
	 */
	while (fgets(line,257,f) != NULL) { /* ITS4: ignore fgets */
		/*
		 * check for comment
		 */
		if (line[0] == '#')
			continue;

		firestring_chomp(line);

		/* 
		 * check for blank line
		 */
		if (line[0] == '\0')
			continue;

		/*
		 * build new object to insert
		 */
		local_domain1 = messagewall_malloc(struct messagewall_local_domain_t);
		firestring_estr_alloc(&local_domain1->domain,strlen(line));
		firestring_estr_strcpy(&local_domain1->domain,line);

		/* 
		 * calculate hash
		 */
		u = dbase_text_hash(&local_domain1->domain,0);

		/*
		 * insert into table
		 */
		local_domain1->next = local_domain_hash[u];
		local_domain_hash[u] = local_domain1;
	}
	fclose(f);
}

void dbase_load_relay_ips() { /* populate relay_ips list + hash table from relay_ips config file */
	struct messagewall_relay_ip_t *relay_ip1;
	struct in_addr *ip;
	char *slash;
	char line[257];
	unsigned int u;
	FILE *f;
	int i;

	if (relay_ips == NULL)
		return;

	/*
	 * we're inside the chroot; everything should be root controlled and fopen shouldn't be a problem
	 */
	f = fopen(relay_ips,"r"); /* ITS4: ignore fopen */
	if (f == NULL) {
		perror("Unable to open relay_ips file");
		exit(100);
	}
	/*
	 * line really does have 257 bytes of space
	 */
	while (fgets(line,257,f) != NULL) { /* ITS4: ignore fgets */
		/*
		 * check for comment
		 */
		if (line[0] == '#')
			continue;

		firestring_chomp(line);

		/* 
		 * line must contain a slash
		 */
		slash = strchr(line,'/');
		if (slash == NULL)
			continue;
		slash[0] = '\0';
		slash++;

		/*
		 * convert IP to binary form
		 */
		ip = firedns_aton4(line);
		if (ip == NULL)
			/*
			 * invalid ip
			 */
			continue;
		
		/*
		 * build new object to insert
		 */
		relay_ip1 = messagewall_malloc(struct messagewall_relay_ip_t);
		/*
		 * memcpy uses sizeof destination, is safe
		 */
		memcpy(relay_ip1->ip,ip,sizeof(relay_ip1->ip)); /* ITS4: ignore memcpy */

		/*
		 * try dotted quad mask notation
		 */
		ip = firedns_aton4_s(slash,(struct in_addr *)&relay_ip1->mask);
		if (ip == NULL) {
			i = atoi(slash);
			if (i >= 16 && i <= 32)
				ip = firedns_aton4_s(dbase_cidr_subnet[i],(struct in_addr *)&relay_ip1->mask);
			if (ip == NULL) {
				/*
				 * invalid mask
				 */
				fprintf(stderr,"STARTUP/WARNING: Invalid subnet mask: %s\n",slash);
				free(relay_ip1);
				continue;
			}
		}
		if (relay_ip1->mask[0] != 255 || relay_ip1->mask[1] != 255) {
			/*
			 * mask larger than /16
			 */
			fprintf(stderr,"STARTUP/WARNING: Subnet mask too large: %s\n",slash);
			free(relay_ip1);
			continue;
		}
		relay_ip1->ip[2] &= relay_ip1->mask[2];
		relay_ip1->ip[3] &= relay_ip1->mask[3];

		/* 
		 * calculate hash
		 */
		u = dbase_ip_hash(relay_ip1->ip);

		/*
		 * insert into table
		 */
		relay_ip1->next = relay_ip_hash[u];
		relay_ip_hash[u] = relay_ip1;
	}
	fclose(f);
}

void dbase_load_special_users() { /* populate special_users list + hash table from special_users config file */
	struct messagewall_special_user_t *special_user1;
	char *slash;
	char line[1025];
	unsigned int u;
	FILE *f;

	if (special_users == NULL)
		return;

	/*
	 * we're inside the chroot; everything should be root controlled and fopen shouldn't be a problem
	 */
	f = fopen(special_users,"r"); /* ITS4: ignore fopen */
	if (f == NULL) {
		perror("Unable to open special_users file");
		exit(100);
	}
	/*
	 * line really does have 1025 bytes of space
	 */
	while (fgets(line,1025,f) != NULL) { /* ITS4: ignore fgets */
		/*
		 * check for comment
		 */
		if (line[0] == '#')
			continue;

		firestring_chomp(line);

		/* 
		 * check for blank line
		 */
		if (line[0] == '\0')
			continue;

		/*
		 * make sure it's delimited
		 */
		slash = strchr(line,':');
		if (slash == NULL)
			continue;

		*(slash++) = '\0';

		/*
		 * build new object to insert
		 */
		special_user1 = messagewall_malloc(struct messagewall_special_user_t);
		firestring_estr_alloc(&special_user1->address,strlen(line));
		firestring_estr_strcpy(&special_user1->address,line);
		special_user1->profile = dbase_get_profile(slash);

		/* 
		 * calculate hash
		 */
		u = dbase_text_hash(&special_user1->address,0);

		/*
		 * insert into table
		 */
		special_user1->next = special_user_hash[u];
		special_user_hash[u] = special_user1;
	}
	fclose(f);
}

void dbase_load() { /* reload databases from config files */
	dbase_clear();
	dbase_load_local_domains();
	dbase_load_relay_ips();
	dbase_load_special_users();
}

unsigned int dbase_binary_hash(struct firestring_estr_t *domain, int start) {
	static char s[4];

	memset(s,0,4);
	/*
	 * copies a maximum of 8 bytes by virtue of min macro, is safe
	 */
	memcpy(s,&domain->s[start],min(4,domain->l - start)); /* ITS4: ignore memcpy */

	return (
		( (s[0] & 0x0f) << 4 ) |
		( (s[1] & 0x0f) )
	       ) * 256 + (
		( (s[2] & 0x0f) << 4 ) |
		( (s[3] & 0x0f) )
	       );
}

unsigned int dbase_text_hash(struct firestring_estr_t *address, int start) { /* hash value= 16bit from given position in string */
	if (address->l - start >= 2)
		return tolower(address->s[start]) * 256 + tolower(address->s[start + 1]);
	else
		return 0;
}

unsigned int dbase_ip_hash(unsigned char *ip) { /* hash value= most significant 16bit from IP address */
	return ip[0] * 256 + ip[1];
}

int dbase_domain_is_local(struct firestring_estr_t *domain) {
	struct messagewall_local_domain_t *iter;
	unsigned int u;

	u = dbase_text_hash(domain,0);

	iter = local_domain_hash[u];
	while (iter != NULL) {
		if (firestring_estr_estrcasecmp(domain,&iter->domain,0) == 0)
			return 0;
		iter = iter->next;
	}

	return 1;
}

int dbase_ip_can_relay(unsigned char *ip) {
	struct messagewall_relay_ip_t *iter;
	unsigned int u;

	u = dbase_ip_hash(ip);

	iter = relay_ip_hash[u];
	while (iter != NULL) {
		if (((ip[2] & iter->mask[2]) == iter->ip[2])
				&& ((ip[3] & iter->mask[3]) == iter->ip[3]))
			return 0;
		iter = iter->next;
	}

	return 1;
}

struct messagewall_profile_t *dbase_get_profile(const char *name) { /* find in list or insert newly-allocated profile struct, return pointer to it */
	struct messagewall_profile_t *iter;

	iter = profile_head;
	while (iter != NULL) {
		if (strcmp(iter->name,name) == 0)
			return iter;
		iter = iter->next;
	}

	iter = profile_head;
	profile_head = messagewall_malloc(struct messagewall_profile_t);
	profile_head->next = iter;
	profile_head->name = firestring_strdup(name);
	/*
	 * defaults get set here
	 */
	profile_head->to_cc_check = 0;
	profile_head->from_check = 0;
	profile_head->realname_check = 0;
	profile_head->rdns_required = 0;
	profile_head->rmx_required = 0;
	profile_head->reject = 0;
	profile_head->reject_score = 1;
	profile_head->header_reject = NULL;
	profile_head->header_rejecti = NULL;
	profile_head->body_reject = NULL;
	profile_head->body_rejecti = NULL;
	profile_head->filename_reject = NULL;
	profile_head->mime_strip = NULL;
	profile_head->mime_allow = NULL;
	profile_head->mime_reject = NULL;
	profile_head->dnsbl = NULL;
	profile_head->dnsbl_domain = NULL;
	profile_head->dnsdcc = NULL;
	profile_head->virus_scan = NULL;
	return profile_head;
}

struct messagewall_dnsbl_global_t *dbase_get_dnsbl(const char *dnsbl, struct messagewall_dnsbl_global_t **global_head) {
	struct messagewall_dnsbl_global_t *iter;
	int i;

	iter = *global_head;
	while (iter != NULL) {
		if (firestring_estr_strcasecmp(&iter->dnsbl,dnsbl) == 0)
			return iter;
		iter = iter->next;
	}

	iter = *global_head;
	if (iter == NULL)
		i = 0;
	else
		i = iter->i + 1;
	*global_head = messagewall_malloc(struct messagewall_dnsbl_global_t);
	(*global_head)->next = iter;
	(*global_head)->i = i;
	firestring_estr_alloc(&(*global_head)->dnsbl,strlen(dnsbl));
	firestring_estr_strcpy(&(*global_head)->dnsbl,dnsbl);
	firestring_estr_0(&(*global_head)->dnsbl);
	return dnsbl_head;
}

struct messagewall_dnsdcc_global_t *dbase_get_dnsdcc(const char *dnsdcc) {
	struct messagewall_dnsdcc_global_t *iter;
	char * (*check)(struct firestring_estr_t *);
	int i;

	iter = dnsdcc_head;
	while (iter != NULL) {
		if (firestring_estr_strcasecmp(&iter->dnsdcc,dnsdcc) == 0)
			return iter;
		iter = iter->next;
	}

	if (firestring_strncasecmp(dnsdcc,"aa.",3) == 0)
		check = dnsdcc_checksum_aa;
	else if (firestring_strncasecmp(dnsdcc,"ba.",3) == 0)
		check = dnsdcc_checksum_ba;
	else {
		fprintf(stderr,"STARTUP/FATAL: unrecognized DNS DCC checksum protocol\n");
		exit(100);
	}

	iter = dnsdcc_head;
	if (iter == NULL)
		i = 0;
	else
		i = iter->i + 1;
	dnsdcc_head = messagewall_malloc(struct messagewall_dnsdcc_global_t);
	dnsdcc_head->next = iter;
	dnsdcc_head->i = i;
	dnsdcc_head->check = check;
	firestring_estr_alloc(&dnsdcc_head->dnsdcc,strlen(dnsdcc));
	firestring_estr_strcpy(&dnsdcc_head->dnsdcc,dnsdcc);
	firestring_estr_0(&dnsdcc_head->dnsdcc);
	return dnsdcc_head;
}

static int dbase_get_score(const char **text) {
	const char *tempstr;
	int i;
	tempstr = strchr(*text,',');
	
	/*
	 * return default score
	 */
	if (tempstr == NULL)
		return 1;

	i = atoi(*text);
	*text = tempstr + 1;
	return i;
}

void dbase_load_estr_ll(struct firestring_conf_t *config, struct messagewall_estr_ll_t **head, const char *name) {
	struct messagewall_estr_ll_t *estr_ll;
	const char *tempstr;

	tempstr = NULL;
	while ((tempstr = firestring_conf_find_next(config,name,tempstr)) != NULL) {
		estr_ll = *head;
		*head = messagewall_malloc(struct messagewall_estr_ll_t);
		(*head)->next = estr_ll;
		firestring_estr_alloc(&(*head)->string,strlen(tempstr));
		firestring_estr_strcpy(&(*head)->string,tempstr);
	}
}

void dbase_load_estr_score_ll(struct firestring_conf_t *config, struct messagewall_estr_score_ll_t **head, const char *name) {
	struct messagewall_estr_score_ll_t *estr_ll;
	const char *tempstr;
	const char *tempstr2;

	tempstr2 = NULL;
	while ((tempstr2 = firestring_conf_find_next(config,name,tempstr2)) != NULL) {
		tempstr = tempstr2;
		estr_ll = *head;
		*head = messagewall_malloc(struct messagewall_estr_score_ll_t);
		(*head)->next = estr_ll;
		(*head)->score = dbase_get_score(&tempstr);
		firestring_estr_alloc(&(*head)->string,strlen(tempstr));
		firestring_estr_strcpy(&(*head)->string,tempstr);
	}
}

void dbase_load_header_reject(struct firestring_conf_t *config, struct messagewall_header_reject_t **head, const char *name) {
	const char *tempstr, *tempstr2, *tempstr3;
	struct messagewall_header_reject_t *header_reject;
	int s;

	tempstr3 = NULL;
	while ((tempstr3 = firestring_conf_find_next(config,name,tempstr3)) != NULL) {
		tempstr = tempstr3;
		s = dbase_get_score(&tempstr);
		tempstr2 = strchr(tempstr,':');
		if (tempstr2 == NULL)
			continue;
		tempstr2++;
		header_reject = *head;
		*head = messagewall_malloc(struct messagewall_header_reject_t);
		(*head)->next = header_reject;
		(*head)->score = s;
		firestring_estr_alloc(&(*head)->header,tempstr2 - tempstr);
		/*
		 * we just allocated this amount of space (estr does nil space automatically), this is safe
		 */
		memcpy((*head)->header.s,tempstr,tempstr2 - tempstr); /* ITS4: ignore memcpy */
		(*head)->header.l = tempstr2 - tempstr;
		firestring_estr_alloc(&(*head)->content,strlen(tempstr2));
		firestring_estr_strcpy(&(*head)->content,tempstr2);
	}
}

void dbase_load_int(struct firestring_conf_t *config, int *value, const char *name) {
	const char *tempstr;

	tempstr = firestring_conf_find(config,name);
	if (tempstr == NULL)
		return;
	*value = atoi(tempstr);
}

void dbase_load_int_score(struct firestring_conf_t *config, int *value, int *score, const char *name) {
	const char *tempstr;

	tempstr = firestring_conf_find(config,name);
	if (tempstr == NULL) {
		*score = 1;
		return;
	}
	*score = dbase_get_score(&tempstr);
	*value = atoi(tempstr);
}

void dbase_load_profiles(const char *profile_dir) { /* populate list of profile structs from files in profile dir */
	struct messagewall_profile_t *profile;
	struct messagewall_dnsbl_profile_t *dnsbl;
	struct messagewall_dnsdcc_profile_t *dnsdcc;
	struct messagewall_virus_database_t *virus_database;
	struct messagewall_virus_profile_t *virus_profile;
	struct firestring_conf_t *config;
	struct dirent *e;
	DIR *d;
	struct stat s;
	const char *tempstr, *tempstr3;
	char *tempstr2;
	int i;

	d = opendir(profile_dir); /* ITS4: ignore opendir */
	if (d == NULL) {
		perror("opendir(profile_dir)");
		exit(100);
	}
	/*
	 * profile dir should be in root controlled location
         * i don't see how this can be a problem
	 * SECURITY: do not put profile dir where users can move it out of the way or write inside of it
         */
	if (chdir(profile_dir) != 0) { /* ITS4: ignore chdir */
		perror("chdir(profile_dir)");
		exit(100);
	}

	while ((e = readdir(d)) != NULL) {
		if (stat(e->d_name,&s) != 0) { /* ITS4: ignore stat */
			fprintf(stderr,"STARTUP/STATUS: skipping profile '%s'\n",e->d_name);
			continue;
		}
		if (S_ISREG(s.st_mode) == 0)
			continue;
		fprintf(stderr,"STARTUP/STATUS: loaded profile %s\n",e->d_name);
		profile = dbase_get_profile(e->d_name);
		config = firestring_conf_parse(e->d_name);
		if (config == NULL) {
			fprintf(stderr,"STARTUP/FATAL: failed to load profile\n");
			exit(100);
		}

		dbase_load_int_score(config,&profile->to_cc_check,&profile->to_cc_score,"to_cc_check");
		dbase_load_int_score(config,&profile->from_check,&profile->from_score,"from_check");
		dbase_load_int_score(config,&profile->realname_check,&profile->realname_score,"realname_check");
		dbase_load_int_score(config,&profile->rdns_required,&profile->rdns_score,"rdns_required");
		dbase_load_int_score(config,&profile->rmx_required,&profile->rmx_score,"rmx_required");
		dbase_load_header_reject(config,&profile->header_reject,"header_reject");
		dbase_load_header_reject(config,&profile->header_rejecti,"header_rejecti");
		dbase_load_estr_score_ll(config,&profile->body_reject,"body_reject");
		dbase_load_estr_score_ll(config,&profile->body_rejecti,"body_rejecti");
		dbase_load_estr_score_ll(config,&profile->filename_reject,"filename_reject");
		dbase_load_estr_score_ll(config,&profile->mime_reject,"mime_reject");

		dbase_load_estr_ll(config,&profile->mime_strip,"mime_strip");
		dbase_load_estr_ll(config,&profile->mime_allow,"mime_allow");
		dbase_load_int(config,&profile->reject,"reject");
		dbase_load_int(config,&profile->reject_score,"reject_score");

		tempstr3 = NULL;
		while ((tempstr3 = firestring_conf_find_next(config,"dnsbl",tempstr3)) != NULL) {
			tempstr = tempstr3;
			dnsbl = profile->dnsbl;
			profile->dnsbl = messagewall_malloc(struct messagewall_dnsbl_profile_t);
			profile->dnsbl->next = dnsbl;
			profile->dnsbl->score = dbase_get_score(&tempstr);
			tempstr2 = strchr(tempstr,'/');
			if (tempstr2 == NULL)
				profile->dnsbl->have_response_ip = 0;
			else {
				*(tempstr2++) = 0;
				if (firedns_aton4_s(tempstr2,&profile->dnsbl->response_ip) == NULL)
					profile->dnsbl->have_response_ip = 0;
				else
					profile->dnsbl->have_response_ip = 1;
			}
			profile->dnsbl->global = dbase_get_dnsbl(tempstr,&dnsbl_head);
		}
		tempstr3 = NULL;
		while ((tempstr3 = firestring_conf_find_next(config,"dnsbl_domain",tempstr3)) != NULL) {
			tempstr = tempstr3;
			dnsbl = profile->dnsbl_domain;
			profile->dnsbl_domain = messagewall_malloc(struct messagewall_dnsbl_profile_t);
			profile->dnsbl_domain->next = dnsbl;
			profile->dnsbl_domain->score = dbase_get_score(&tempstr);
			tempstr2 = strchr(tempstr,'/');
			if (tempstr2 == NULL)
				profile->dnsbl_domain->have_response_ip = 0;
			else {
				*(tempstr2++) = 0;
				if (firedns_aton4_s(tempstr2,&profile->dnsbl_domain->response_ip) == NULL)
					profile->dnsbl_domain->have_response_ip = 0;
				else
					profile->dnsbl_domain->have_response_ip = 1;
			}
			profile->dnsbl_domain->global = dbase_get_dnsbl(tempstr,&dnsbl_domain_head);
		}
		tempstr3 = NULL;
		while ((tempstr3 = firestring_conf_find_next(config,"dnsdcc",tempstr3)) != NULL) {
			tempstr = tempstr3;
			dnsdcc = profile->dnsdcc;
			profile->dnsdcc = messagewall_malloc(struct messagewall_dnsdcc_profile_t);
			profile->dnsdcc->next = dnsdcc;
			profile->dnsdcc->score = dbase_get_score(&tempstr);
			profile->dnsdcc->global = dbase_get_dnsdcc(tempstr);
		}
		tempstr3 = NULL;
		while ((tempstr3 = firestring_conf_find_next(config,"virus_scan",tempstr3)) != NULL) {
			tempstr = tempstr3;
			i = dbase_get_score(&tempstr);
			virus_database = virus_get_database(tempstr);
			if (virus_database == NULL)
				continue;
			virus_profile = profile->virus_scan;
			profile->virus_scan = messagewall_malloc(struct messagewall_virus_profile_t);
			profile->virus_scan->next = virus_profile;
			profile->virus_scan->global = virus_database;
			profile->virus_scan->score = i;
		}
	}

	closedir(d);
}


syntax highlighted by Code2HTML, v. 0.9.1