/*
 * mxallowd
 * (c) 2007-2009 Michael Stapelberg
 * 
 * See mxallowd.c for description, website and license information
 *
 */
#include <netdb.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <string.h>

#include "mxallowd.h"
#include "whitelist.h"
#include "log.h"

/*
 * Resolves the given IP-address using gethostbyaddr() and returns the hostname
 * or NULL if the IP-address could not be resolved
 *
 */
char *rdns_for_ip(char *ip_address) {
	struct in_addr address4;
	struct hostent *lookup = NULL;

	/* Try resolving the address using IPv4, if that fails, it's probably IPv6 */
	if (inet_pton(AF_INET, ip_address, &address4) > 0)
		lookup = gethostbyaddr((const char *)&address4, sizeof(struct in_addr), AF_INET);
#ifdef IPV6
	else {
		struct in6_addr address6;
		(void)inet_pton(AF_INET6, ip_address, &address6);
		lookup = gethostbyaddr((const char *)&address6, sizeof(struct in6_addr), AF_INET6);
	}
#endif

	if (lookup == NULL) {
		if (h_errno != HOST_NOT_FOUND && h_errno != NO_ADDRESS && h_errno != NO_DATA) {
			switch (h_errno) {
				case NO_RECOVERY:
					fprintf(stderr, "Non-recoverable error during gethostbyaddr()\n");
					break;
				case TRY_AGAIN:
					fprintf(stderr, "Temporary error resolving using gethostbyaddr()\n");
					break;
				default:
					fprintf(stderr, "Unknown error (code %d) during gethostbyaddr()\n", h_errno);
					break;
			}
		}
		return NULL;
	}
	return lookup->h_name;
}

/*
 * This thread does all the resolving in order not to block the main thread which should
 * respond as fast as possible to netfilter_queue-requests.
 *
 */
void *resolve_thread(void *data) {
	struct whitelist_entry *cur;
	char *rdns;
	char address[INET6_ADDRSTRLEN+1];

	while (1) {
		pthread_mutex_lock(&resolv_thread_mutex);
		pthread_cond_wait(&resolv_new_cond, &resolv_thread_mutex);
		pthread_mutex_unlock(&resolv_thread_mutex);

		cur = root;
		do {
			if (cur->ip_address == NULL || cur->rdns_tried)
				continue;
			rdns = rdns_for_ip(cur->ip_address);
			slog("Resolved %s to %s\n", cur->ip_address, rdns);
			cur->rdns_tried = true;
			if (rdns == NULL)
				continue;
			rdns = strdup(rdns);
			cur->rdns = rdns;
			if (!rdns_whitelist)
				continue;

			/* Resolve the hostname to get further IP addresses */
			struct addrinfo hints, *res, *res0;
			int error;

			memset(&hints, 0, sizeof(hints));
#ifndef IPV6
			hints.ai_family = AF_INET;
#endif
			hints.ai_socktype = SOCK_STREAM;
			if ((error = getaddrinfo(rdns, "smtp", &hints, &res0)) != 0) {
				fprintf(stderr, "Could not resolve \"%s\":smtp: %s\n", rdns, gai_strerror(error));
				continue;
			}

			for (res = res0; res; res = res->ai_next) {
				(void)memset(address, '\0', INET6_ADDRSTRLEN+1);
				getnameinfo(res->ai_addr, res->ai_addrlen, address, sizeof(address), NULL, 0, NI_NUMERICHOST);
				if (strcmp(address, cur->ip_address) != 0)
					add_to_whitelist(address, rdns, true, res->ai_family, res->ai_addr);
			}

			/* Avoid unequal cache times for the new entries and the original one */
			cur->added = time(NULL);
		} while ((cur = cur->next) != NULL);
	}
}
