/***************************************************************************
 *
 * Copyright (c) 2001 BalaBit IT Ltd.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Inspired by nsyslog, originally written by Darren Reed.
 *
 * $Id: nscache.c,v 1.5 2002/09/04 14:52:25 bazsi Exp $
 *
 ***************************************************************************/
 
/*
 * Copyright (c) 2001 Gaël Roualland <gael.roualland@iname.com>
 * 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. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `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 OR CONTRIBUTORS 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 <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include "nscache.h"

/* Simple NS cache, using a flat hash table (NOT thread safe).
 * This is designed to be used with a fixed set of ip adresses.
 * Inspired from similar caches in ippl, iplog...
 *
 * The size of the cache must be a primary number for good
 * performance and somewhat larger than the maximum number
 * of estimated entries. If more entries than available in the
 * cache are used at the same time it will perform very poorly.
 */

struct nsentry {
	struct in_addr addr;
	char * name;
	time_t used;
	time_t expire;
};

struct nscache {
	int size;
	int expire;
	int expire_failed;
	struct nsentry * entries;
#ifdef NSCACHE_DEBUG
	int hits, misses;
#endif	
};

struct nscache *nscache_new(int size, int expire, int expire_failed) 
{
	struct nscache * cache;
	int i;

	if ((cache = malloc(sizeof(struct nscache))) == NULL)
		return NULL;
	
	/* check args for reasonnable values
	   0 for expire_failed means not to cache failures */
	cache->size = (size < 10) ? 10 : size;
	cache->expire = (expire < 10) ? 10 : expire;
	cache->expire_failed = (expire_failed < 0) ? 0 : expire_failed;

	if ((cache->entries =
	     malloc(cache->size * sizeof(struct nsentry))) == NULL) {
		free(cache);
		return NULL;
	}

	for (i = 0; i < cache->size; i++) {
		cache->entries[i].addr.s_addr = 0;
		cache->entries[i].name = NULL;
		cache->entries[i].expire = 0;
		cache->entries[i].used = 0;
	}

#ifdef NSCACHE_DEBUG
	fprintf(stderr, "initialising nscache: %d entries, %d/%d expiration.\n",
		cache->size, cache->expire, cache->expire_failed);
	cache->hits = cache->misses = 0;
#endif

	return cache;
}

void nscache_free(struct nscache * cache) 
{
	int i;

	if (cache == NULL)
		return;

	if (cache->entries == NULL) {
		free(cache);
		return;
	}

#ifdef NSCACHE_DEBUG
	fprintf(stderr, "destroying nscache: %d entries, %d hits, %d misses.\n",
		cache->size, cache->hits, cache->misses);
#endif
	
	for (i = 0; i < cache->size; i++) {
		if (cache->entries[i].name != NULL)
			free(cache->entries[i].name);
	}

	free(cache->entries);
	free(cache);
}

static char *nsresolve(struct in_addr addr) 
{
	struct hostent *host;

	if ((host = gethostbyaddr((char *) &addr,
				  sizeof(struct in_addr),
				  AF_INET)) != NULL &&
	    host->h_name != NULL)
		return host->h_name;
	return NULL;
}

char *nscache_lookup(struct nscache * cache, struct in_addr in) 
{
	int h, k, old, new;
#ifdef NSCACHE_DEBUG
	int iter = 1;
#endif  
	time_t now = time(NULL);
	time_t oldest = now + 1;
	char *host;

	if (cache == NULL || cache->entries == NULL) /* be safe.. */
		return nsresolve(in);

	k = h = ntohl(in.s_addr) % (cache->size - 1) + 1;
	old = new = -1;
  
	do {
		if (cache->entries[k].expire > 0 &&
		    cache->entries[k].addr.s_addr == in.s_addr) { /* found it! */
			if (now > cache->entries[k].expire) {
				/* entry has expired, update it */
#ifdef NSCACHE_DEBUG
				fprintf(stderr, "%s... updating entry %d (%d > %d).\n",
				        inet_ntoa(in), k, (int) now,
					(int) cache->entries[k].expire);
#endif
				new = k;
				break;
			}
#ifdef NSCACHE_DEBUG
			fprintf(stderr,	"%s... found on %d: %s (%d iters, %d/%d)\n",
				inet_ntoa(in), k, cache->entries[k].name,
				iter, (int) now, (int) cache->entries[k].expire);
			cache->hits++;
#endif  
			cache->entries[k].used = now;
			return cache->entries[k].name;
		}
		if (new < 0) { /* no empty entries found yet */
			if (cache->entries[k].expire < now) {
				new = k; /* use this to save new entry if not found */
				if (cache->entries[k].expire < 1)
					break; /* never used */
			} else 
				if (cache->entries[k].used < oldest) {
					old = k;
					oldest = cache->entries[k].used;
				}
		}
		k = (h + k) % cache->size;
#ifdef NSCACHE_DEBUG
		iter++;
#endif  
	} while (k > 0);
	
	if (new < 0) { /* no place to save ? use oldest. */
		if (old < 0)
			old = 0; /* shouldn't happen.. */
#ifdef NSCACHE_DEBUG
		fprintf(stderr, "%s... old entry %d will be replaced.\n",
			inet_ntoa(cache->entries[old].addr), old);
#endif  
		new = old;
	}
#ifdef NSCACHE_DEBUG
	fprintf(stderr, "%s... adding as %d (%d iters).\n",
		inet_ntoa(in), new, iter);
	cache->misses++;
#endif  
	if (cache->entries[new].name != NULL)
		free (cache->entries[new].name);
	if ((host = nsresolve(in)) != NULL) {
		cache->entries[new].name = strdup(host);
		cache->entries[new].expire =
			now + cache->expire;
	} else {
		cache->entries[new].name = NULL;
		cache->entries[new].expire =
			now + cache->expire_failed;
	}
	cache->entries[new].addr.s_addr = in.s_addr;
	return cache->entries[new].name;
}

#ifdef NSCACHE_DEBUG
void nscache_dump(struct nscache * cache) 
{
	int i;
	
	fprintf(stderr,	"nscache contents:\n");
	for (i = 0; i < cache->size; i++) {
		if (cache->entries[i].used > 0)
			fprintf(stderr, "  %5d  %-16s %09d/%09d  %s\n",
				i, inet_ntoa(cache->entries[i].addr),
				(int) cache->entries[i].used,
				(int) cache->entries[i].expire,
				cache->entries[i].name);
	}
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1