/*
 * mxallowd
 * (c) 2007-2009 Michael Stapelberg
 * 
 * See mxallowd.c for description, website and license information
 *
 */
#include <stdbool.h>
#include <time.h>
#include <malloc.h>
#include <string.h>
#include <pthread.h>

#ifdef PF
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_pflog.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>
#include <net/pfvar.h>
#include <net/pfvar.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#include "mxallowd.h"
#include "log.h"
#define _WHITELIST_INTERNAL
#include "whitelist.h"
#undef _WHITELIST_INTERNAL

bool is_whitelisted(char *ip_address, bool useIt) {
	if (root == NULL)
		return false;

	struct whitelist_entry *cur = root;
	do {
		if (cur->ip_address != NULL && strcmp(cur->ip_address, ip_address) == 0) {
			if (useIt)
				cur->used = true;
			return true;
		}
	} while ((cur = cur->next) != NULL);
	return false;
}

/* 
 * Doesn't free() old entries but invalidates them, they'll get reused 
 *
 * */
void cleanup_whitelist() {
	if (root != NULL) {
		time_t current_time = time(NULL);
		struct whitelist_entry *cur = root;
		do {
			if (cur->ip_address != NULL && (current_time - cur->added) > allow_time) {
				/* If the entry is too old, invalidate it by freeing ip_address */
				slog("Cleaning %s (RDNS: %s) from whitelist (timeout)\n",
					cur->ip_address, (cur->rdns != NULL ? cur->rdns : "unresolvable"));
				if (!cur->is_child) {
					if (cur->used)
						successful_connects++;
					else direct_to_fake++;
				}

				free(cur->ip_address);
				cur->ip_address = NULL;
				if (cur->rdns != NULL) {
					free(cur->rdns);
					cur->rdns = NULL;
				}
			}
		} while ((cur = cur->next) != NULL);
	}
}

/*
 * Adds the given ip address (with rdns if not NULL) to whitelist
 * is_child = flag whether the entry has to be counted as one mailserver
 * or whether it's just another IP of this mailserver
 *
 */
void add_to_whitelist(char *ip_address, char *rdns, bool is_child, int af, const void *source_addr) {
	if (is_whitelisted(ip_address, false))
		return;

	struct whitelist_entry *new_entry = malloc(sizeof(struct whitelist_entry));
	new_entry->next = NULL;
	new_entry->ip_address = strdup(ip_address);
	new_entry->rdns = (rdns != NULL ? strdup(rdns) : NULL);
	new_entry->rdns_tried = (rdns != NULL);
	new_entry->added = time(NULL);
	new_entry->is_child = is_child;
	new_entry->used = false;

	if (root == NULL)
		root = new_entry;
	else {
		struct whitelist_entry *cur = root;
		while (cur->next != NULL && cur->ip_address != NULL)
			cur = cur->next;
		if (cur->ip_address == NULL) {
			/* This is a cleaned up entry, overwrite values */
			cur->ip_address = new_entry->ip_address;
			cur->rdns = new_entry->rdns;
			cur->rdns_tried = new_entry->rdns_tried;
			cur->added = new_entry->added;
			cur->is_child = is_child;
			cur->used = false;
			free(new_entry);
		} else cur->next = new_entry;
	}

#ifdef PF
	struct pfioc_table table;
	struct pfr_addr addr;
	memset(&table, '\0', sizeof(struct pfioc_table));
	memset(&addr, '\0', sizeof(struct pfr_addr));
	table.pfrio_buffer = &addr;
	table.pfrio_esize = sizeof(struct pfr_addr);
	table.pfrio_size = 1;
	strcpy(table.pfrio_table.pfrt_name, "mx-white");
	addr.pfra_af = af;
	addr.pfra_net = (af == AF_INET ? 32 : 128);
	if (af == AF_INET)
		memcpy(&(addr.pfra_ip4addr), ((struct sockaddr_in*)source_addr)->sin_addr, sizeof(struct in_addr));
	else memcpy(&addr.pfra_ip6addr, ((struct sockaddr_in6*)source_addr)->sin6_addr, sizeof(struct in6_addr));

	if (ioctl(pffd, DIOCRADDADDRS, &table) == -1)
		slogerror("Couldn't ioctl() on /dev/pf\n");
	if (table.pfrio_nadd != 1)
		slog("Adding IP to table failed, it probably is already whitelisted\n");
#endif

	if (rdns_whitelist) {
		/* Inform the resolver-thread of the new entry to resolve */
		pthread_mutex_lock(&resolv_thread_mutex);
		pthread_cond_broadcast(&resolv_new_cond);
		pthread_mutex_unlock(&resolv_thread_mutex);
	}
}
