/*
** iplog_dns.c - iplog DNS cache routines.
** Copyright (C) 1999-2001 Ryan McCabe <odin@numb.org>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License, version 2,
** 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
**
** $Id: iplog_dns.c,v 1.21 2001/01/01 16:02:14 odin Exp $
*/

#include <config.h>

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <ctype.h>
#include <netdb.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>

#include <iplog.h>
#include <iplog_dns.h>
#include <iplog_options.h>

static u_char *add_to_cache(ipaddr_t addr, u_long hash, u_char *, size_t);

static size_t dt_size;
static struct dns_entry *dns_table;

/*
** Initializes a hash table for the DNS cache with "tlen" buckets.
*/

void init_dns_table(size_t tlen) {
	size_t i;

	dt_size = tlen;
	dns_table = xcalloc(dt_size, sizeof(struct dns_entry));

	for (i = 0 ; i < dt_size ; i++)
		pthread_mutex_init(&dns_table[i].lock, NULL);
}

/*
** Scans the DNS hash table, removing any entries that have expired.
*/

void expire_dns(void) {
	size_t i;
	struct dns_data *cur;

	for (i = 0 ; i < dt_size ; i++) {
		pthread_mutex_lock(&dns_table[i].lock);
		cur = dns_table[i].head;

		while (cur != NULL) {
			if (time(NULL) >= cur->expire) {
				free(cur->host);
				cur = dlist_delete(cur, &dns_table[i].head);
				--dns_table[i].cnt;
			} else
				cur = cur->next;
		}

		pthread_mutex_unlock(&dns_table[i].lock);
	}
}

#ifdef HAVE_PTHREAD_CANCEL

/*
** Cleanup routine called when the DNS hash table is destroyed.
*/

static void dns_cleanup(void *data) {
	struct dns_data *cur = data;

	free(cur->host);
}

/*
** Destroys the DNS hash table.
*/

void destroy_dns_cache(void) {
	size_t i;

	/*
	** All the threads have been canceled, and the locks could be in any
	** state, just zero the whole table out
	*/
	for (i = 0 ; i < dt_size ; i++) {
		if (dns_table[i].head != NULL)
			dlist_destroy(dns_table[i].head, dns_cleanup);
		memset(&dns_table[i], 0, sizeof(struct dns_entry));
		pthread_mutex_init(&dns_table[i].lock, NULL);
	}
}
#endif

/*
** Search for the host specified by "addr" in the DNS hash table.
** If it's not found, resolve the hostname and add it to the cache.
** Copy the first len - 1 bytes of the result into "buf," nul-terminate buf.
*/

u_char *get_dns_cache(ipaddr_t addr, u_char *buf, size_t len) {
	u_long hash = DNS_HASH(addr, dt_size);
	bool found = false;
	struct dns_data *cur;

	pthread_mutex_lock(&dns_table[hash].lock);
	for (cur = dns_table[hash].head ; cur != NULL ; cur = cur->next) {
		if (cur->addr == addr) {
			xstrncpy(buf, cur->host, len);
			found = true;
			break;
		}
	}
	pthread_mutex_unlock(&dns_table[hash].lock);

	if (found == false)
		return (add_to_cache(addr, hash, buf, len));

	return (buf);
}

/*
** Add an entry for host "addr" to the DNS hash table.
*/

static u_char *add_to_cache(ipaddr_t addr, u_long hash, u_char *buf, size_t len)
{
	struct dns_data *new_entry = xmalloc(sizeof(struct dns_data));
	struct in_addr in;

	in.s_addr = addr;
	_host_lookup(&in, buf, len);

	new_entry->addr = addr;
	new_entry->expire = time(NULL) + DNS_TIMEOUT;

	if (opt_enabled(LOG_IP) && !isdigit(buf[strlen(buf) - 1])) {
		size_t mlen = strlen(buf) + MAX_IPLEN + 4;
		u_char ibuf[MAX_IPLEN];

		new_entry->host = xmalloc(mlen);
		inet_ntoa_r(&in, ibuf, sizeof(ibuf));
		snprintf(new_entry->host, mlen, "%s (%s)", buf, ibuf);

		if (mlen <= len)
			xstrncpy(buf, new_entry->host, len);
		else
			xstrncpy(buf, ibuf, len);
	} else
		new_entry->host = xstrdup(buf);

	pthread_mutex_lock(&dns_table[hash].lock);

	if (dns_table[hash].cnt >= DNS_MAX_ENT) {
		/*
		** This bucket is full.  Evict the oldest entry.
		*/
		u_long old_t = ~0;
		struct dns_data *cur, *oldest = NULL;

		for (cur = dns_table[hash].head ; cur != NULL ; cur = cur->next) {
			if ((u_long) cur->expire <= old_t) {
				old_t = cur->expire;
				oldest = cur;
			}
		}

		dlist_delete(oldest, &dns_table[hash].head);
		--dns_table[hash].cnt;
	}

	dlist_prepend(new_entry, &dns_table[hash].head);
	++dns_table[hash].cnt;
	pthread_mutex_unlock(&dns_table[hash].lock);

	return (buf);
}

/* vim:ts=4:sw=8:tw=0 */


syntax highlighted by Code2HTML, v. 0.9.1