/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	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.

**********************************************************************/




#include	"task.h"
#include	"lock_level.h"
#include	"netutils.h"
#include	"memory_debug.h"

SEM	resolve_lock;
HOST_ENTRY * resolv_cache;
HOST_ENTRY ** prev_cache;

HOST_ENTRY ** del_list;
int del_ptr;

#define DEL_SIZE	100
#define RESOLV_TICK	3600
#define RESOLV_TIMEOUT		600

void resolv_tick();

void
init_resolve()
{
int i;
	resolve_lock = new_lock(LL_P_RESOLV);
	del_list = d_alloc(sizeof(HOST_ENTRY*)*DEL_SIZE);
	for ( i = 0 ; i < DEL_SIZE ; i ++ )
		del_list[i] = 0;
	del_ptr = 0;
	new_tick(resolv_tick,RESOLV_TICK,0);
}


HOST_ENTRY *
_search_host_entry_byname(char * name)
{
HOST_ENTRY * ret;
int i;
	prev_cache = &resolv_cache;
	for ( ret = resolv_cache ; ret ; 
			prev_cache = &ret->next,
			ret = ret->next ) {
		for ( i = 0 ; ret->names[i] ; i ++ )
			if ( strcmp(ret->names[i],name) == 0 )
				return ret;
	}
	return 0;
}

HOST_ENTRY *
_search_host_entry_byaddr(HOST_ADDR ip)
{
HOST_ENTRY * ret;
int i;
	prev_cache = &resolv_cache;
	for ( ret = resolv_cache ; ret ;
			prev_cache = &ret->next,
			ret = ret->next ) {
		for ( i = 0 ; i < ret->ips_length ; i ++ ) {
			if ( ret->ips[i].size != ip.size )
				continue;
			if ( memcmp(&ret->ips[i].d,&ip.d,ip.size) )
				continue;
			return ret;
		}
	}
	return 0;
}

HOST_ENTRY *
new_host_entry(int names_size,int ips_size)
{
HOST_ENTRY * ret;
	ret = d_alloc(sizeof(*ret));
	ret->flags = 0;
	ret->names = d_alloc((names_size+1)*sizeof(char*));
	ret->ips = d_alloc(ips_size*sizeof(HOST_ADDR));
	ret->ips_length = ips_size;
	ret->timer = 0;
	ret->canonical = 0;
	return ret;
}

void
free_host_entry(HOST_ENTRY * he)
{
char ** q;
	for ( q = he->names ; *q ; q ++ )
		d_f_ree(*q);
	d_f_ree(he->names);
	d_f_ree(he->ips);
	d_f_ree(he);
}

void
_delete_host_entry(HOST_ENTRY * he)
{
HOST_ENTRY ** hep;
	if ( prev_cache && *prev_cache == he ) {
		hep = prev_cache;
		goto del;
	}
	for ( hep = &resolv_cache ; *hep ; hep = &(*hep)->next )
		if ( *hep == he )
			goto del;
	return;
del:
	*hep = he->next;
	prev_cache = 0;

	if ( del_list[del_ptr] )
		free_host_entry(del_list[del_ptr]);
	del_list[del_ptr++] = he;
	if ( del_ptr >= DEL_SIZE )
		del_ptr = 0;
	return;
}


void
_insert_host_entry(HOST_ENTRY * he)
{
	he->next = resolv_cache;
	resolv_cache = he;
	prev_cache = 0;
}


int
_cmp_he_name(char * name,HOST_ENTRY * he)
{
char ** q;
	for ( q = he->names ; *q ; q ++ )
		if ( strcmp(*q,name) == 0 )
			return 0;
	return -1;
}

int
_cmp_he_addr(HOST_ADDR a,HOST_ENTRY * he)
{
int i;
HOST_ADDR * ap;
	for ( i = 0 ; i < he->ips_length ; i ++ ) {
		ap = &he->ips[i];
		if ( a.type != ap->type )
			continue;
		if ( a.size != ap->size )
			continue;
		if ( memcmp(&a.d,&ap->d,a.size) )
			continue;
		return 0;
	}
	return -1;
}

int
_cmp_host_entry(HOST_ENTRY * he1,HOST_ENTRY * he2)
{
char ** q, ** r;
int i;
	if ( he1->ips_length != he2->ips_length )
		return -1;
	for ( q = he1->names, r = he2->names ; *q && *r ; q ++ , r ++ );
	if ( *q != *r )
		return -1;
	for ( q = he1->names ; *q ; q ++ )
		if ( _cmp_he_name(*q,he2) < 0 )
			return -1;
	for ( i = 0 ; i < he1->ips_length ; i ++ )
		if ( _cmp_he_addr(he1->ips[i],he2) < 0 )
			return -1;
	return 0;
}

HOST_ENTRY * 
_replace_host_entry(HOST_ENTRY * he)
{
int i;
HOST_ENTRY * he1;
	he1 = _search_host_entry_byname(he->names[0]);
	if ( he1 && _cmp_host_entry(he1,he) == 0 )
		return he1;
	for ( i = 0 ; he->names[i] ; i ++ ) {
		he1 = _search_host_entry_byname(he->names[i]);
		if ( he1 == 0 )
			continue;
		_delete_host_entry(he1);
	}
	for ( i = 0 ; i  < he->ips_length ; i ++ ) {
		he1 = _search_host_entry_byaddr(he->ips[i]);
		if ( he1 == 0 )
			continue;
		_delete_host_entry(he1);
	}
	_insert_host_entry(he);
	return he;
}


void
_host_entry_regulation(HOST_ENTRY * he)
{
char * dom;
char ** q;
char * p;
int len1,len_dom;
char * target;
	dom = get_localdomainname();
	if ( dom == 0 )
		return;
	len_dom = strlen(dom);
	for ( q = he->names ; *q ; q ++ ) {
		if ( strcmp(*q,"localhost") == 0 )
			continue;
		p = *q;
		for ( ; *p && *p != '.' ; p ++ );
		if ( *p == '.' ) {
			len1 = strlen(&p[1]);
			if ( memcmp(&p[1],dom,len1) == 0 ) {
				target = d_alloc(strlen(*q)+len_dom+1);
				*p = 0;
				sprintf(target,"%s.%s",*q,dom);
				d_f_ree(*q);
				*q = target;
			}
		}
		else {
			target = d_alloc(strlen(*q)+len_dom+2);
			sprintf(target,"%s.%s",*q,dom);
			d_f_ree(*q);
			*q = target;
		}
	}
	he->canonical = he->names[0];
}


HOST_ENTRY *
r_gethostbyaddr(HOST_ADDR a)
{
HOST_ENTRY * ret, *new_ret;
unsigned int t;
	ret = intr_gethostbyaddr_rr(a);
	t = get_xltime();
	lock_task(resolve_lock);
	if ( ret ) {
		_host_entry_regulation(ret);
		new_ret = _replace_host_entry(ret);
		if ( new_ret != ret ) {
			free_host_entry(ret);
			ret = new_ret;
		}
		ret->timer = 0;
	}
	else {
		ret = _search_host_entry_byaddr(a);
		if ( ret && !(ret->flags & HEF_LOGHOST) ) {
			if ( ret->timer == 0 )
				ret->timer = t;
			else if ( t - ret->timer > RESOLV_TIMEOUT ) {
				_delete_host_entry(ret);
				ret = 0;
			}
		}
	}
	if ( ret )
		ret->access = t;
	unlock_task(resolve_lock,"r_gethostbyaddr");
	return ret;
}

HOST_ENTRY *
r_gethostbyname(char * name)
{
HOST_ENTRY * ret, * new_ret;
unsigned int t;


	ret = intr_gethostbyname_rr(name);
	t = get_xltime();
	lock_task(resolve_lock);
	if ( ret ) {
		_host_entry_regulation(ret);
		new_ret = _replace_host_entry(ret);
		if ( new_ret != ret ) {
			free_host_entry(ret);
			ret = new_ret;
		}
		ret->timer = 0;
	}
	else {
		ret = _search_host_entry_byname(name);
		if ( ret && !(ret->flags & HEF_LOGHOST) ) {
			if ( ret->timer == 0 )
				ret->timer = t;
			else if ( t - ret->timer > RESOLV_TIMEOUT ) {
				_delete_host_entry(ret);
				ret = 0;
			}
		}
	}
	if ( ret )
		ret->access = t;
	unlock_task(resolve_lock,"r_gethostbyname");
	return ret;
}

void
resolv_tick()
{
HOST_ENTRY * he;
unsigned int t;
	t = get_xltime();
	lock_task(resolve_lock);
retry:
	for ( he = resolv_cache ; he ; he = he->next ) {
		if ( he->flags & HEF_LOGHOST )
			continue;
		if ( t - he->access > RESOLV_TICK ) {
			_delete_host_entry(he);
			goto retry;
		}
	}
	unlock_task(resolve_lock,"resolv_tick");
}

int
cmp_HA(HOST_ADDR a,HOST_ADDR b)
{
	if ( a.type != b.type )
		return -1;
	if ( a.size != b.size )
		return -1;
	if ( memcmp(&a.d,&b.d,a.size) == 0 )
		return 0;
	return -1;
}

