/*
 * proxy list & assoc. functions
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio 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
 *
 * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*!
 * \file
 * \brief Kamailio core :: proxy list & assoc. functions
 * \ingroup core
 * Module: \ref core
 */



#include "config.h"
#include "proxy.h"
#include "error.h"
#include "dprint.h"
#include "mem/mem.h"
#include "mem/shm_mem.h"

#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>

#ifdef DNS_IP_HACK
#include "ut.h"
#endif

#include "resolve.h"
#include "ip_addr.h"
#include "globals.h"


struct proxy_l* proxies=0;



/* searches for the proxy named 'name', on port 'port' with
   proto 'proto'; if proto==0 => proto wildcard (will match any proto)
   returns: pointer to proxy_l on success or 0 if not found */
static struct proxy_l* find_proxy(str *name, unsigned short port, int proto)
{
	struct proxy_l* t;
	for(t=proxies; t; t=t->next)
		if (((t->name.len == name->len) &&
			 ((proto==PROTO_NONE)||(t->proto==proto))&&
			(strncasecmp(t->name.s, name->s, name->len)==0)) &&
				(t->port==port))
			break;
	return t;
}


#define HOSTENT_CPY(dst, src, he_malloc, he_free)						\
	do {																\
		unsigned len,len2;												\
		int r,i;														\
																		\
		/* start copying the host entry.. */							\
		/* copy h_name */												\
		len=strlen(src->h_name)+1;										\
		dst->h_name=(char*)he_malloc(sizeof(char) * len);				\
		if (dst->h_name) strncpy(dst->h_name,src->h_name, len);			\
		else{															\
			ser_error=ret=E_OUT_OF_MEM;									\
			goto error;													\
		}																\
																		\
		/* copy h_aliases */											\
		len=0;															\
		if (src->h_aliases)												\
			for (;src->h_aliases[len];len++);							\
		dst->h_aliases=(char**)he_malloc(sizeof(char*)*(len+1));		\
		if (dst->h_aliases==0){											\
			ser_error=ret=E_OUT_OF_MEM;									\
			he_free(dst->h_name);										\
			goto error;													\
		}																\
		memset((void*)dst->h_aliases, 0, sizeof(char*) * (len+1) );		\
		for (i=0;i<len;i++){											\
			len2=strlen(src->h_aliases[i])+1;							\
			dst->h_aliases[i]=(char*)he_malloc(sizeof(char)*len2);		\
			if (dst->h_aliases==0){										\
				ser_error=ret=E_OUT_OF_MEM;								\
				he_free(dst->h_name);									\
				for(r=0; r<i; r++)	he_free(dst->h_aliases[r]);			\
				he_free(dst->h_aliases);								\
				goto error;												\
			}															\
			strncpy(dst->h_aliases[i], src->h_aliases[i], len2);		\
		}																\
		/* copy h_addr_list */											\
		len=0;															\
		if (src->h_addr_list)											\
			for (;src->h_addr_list[len];len++);							\
		dst->h_addr_list=(char**)he_malloc(sizeof(char*)*(len+1));		\
		if (dst->h_addr_list==0){										\
			ser_error=ret=E_OUT_OF_MEM;									\
			he_free(dst->h_name);										\
			for(r=0; dst->h_aliases[r]; r++)							\
				he_free(dst->h_aliases[r]);								\
			he_free(dst->h_aliases[r]);									\
			he_free(dst->h_aliases);									\
			goto error;													\
		}																\
		memset((void*)dst->h_addr_list, 0, sizeof(char*) * (len+1) );	\
		for (i=0;i<len;i++){											\
			dst->h_addr_list[i]=										\
				(char*)he_malloc(sizeof(char)*src->h_length);			\
			if (dst->h_addr_list[i]==0){								\
				ser_error=ret=E_OUT_OF_MEM;								\
				he_free(dst->h_name);									\
				for(r=0; dst->h_aliases[r]; r++)						\
					he_free(dst->h_aliases[r]);							\
				he_free(dst->h_aliases[r]);								\
				he_free(dst->h_aliases);								\
				for (r=0; r<i;r++) he_free(dst->h_addr_list[r]);		\
				he_free(dst->h_addr_list);								\
				goto error;												\
			}															\
			memcpy(dst->h_addr_list[i], src->h_addr_list[i],			\
				   src->h_length);										\
		}																\
																		\
		/* copy h_addr_type & length */									\
		dst->h_addrtype=src->h_addrtype;								\
		dst->h_length=src->h_length;									\
		/*finished hostent copy */										\
																		\
		return 0;														\
	} while(0)


#define FREE_HOSTENT(dst, he_free)						\
	do {												\
		int r;											\
		if (dst->h_name) he_free(dst->h_name);			\
		if (dst->h_aliases){							\
			for(r=0; dst->h_aliases[r]; r++) {			\
				he_free(dst->h_aliases[r]);				\
			}											\
			he_free(dst->h_aliases);					\
		}												\
		if (dst->h_addr_list){							\
			for (r=0; dst->h_addr_list[r];r++) {		\
				he_free(dst->h_addr_list[r]);			\
			}											\
			he_free(dst->h_addr_list);					\
		}												\
	} while(0)



/* copies a hostent structure*, returns 0 on success, <0 on error*/
static int hostent_cpy(struct hostent *dst, struct hostent* src)
{
	int ret;
	HOSTENT_CPY(dst, src, pkg_malloc, pkg_free);
error:
	PKG_MEM_CRITICAL;
	return ret;
}


static int hostent_shm_cpy(struct hostent *dst, struct hostent* src)
{
	int ret;
	HOSTENT_CPY(dst, src, shm_malloc, shm_free);
error:
	SHM_MEM_CRITICAL;
	return ret;
}


void free_hostent(struct hostent* dst)
{
	FREE_HOSTENT(dst, pkg_free);
}

void free_shm_hostent(struct hostent* dst)
{
	FREE_HOSTENT(dst, shm_free);
}



struct proxy_l* add_proxy(str* name, unsigned short port, int proto)
{
	struct proxy_l* p;

	if ((p=find_proxy(name, port, proto))!=0) return p;
	if ((p=mk_proxy(name, port, proto))==0) goto error;
	/* add p to the proxy list */
	p->next=proxies;
	proxies=p;
	return p;

error:
	return 0;
}

/* FIXME - we should use the same error logic as in HOSTENT_CPY */
#define MK_PROXY(name, port, protocol, p_malloc, p_free, he_cpy)		\
	do {																\
		struct proxy_l* p;												\
		struct hostent* he;												\
		char proto;														\
																		\
		p=(struct proxy_l*) p_malloc(sizeof(struct proxy_l));			\
		if (p==0){														\
			ser_error=E_OUT_OF_MEM;										\
			LM_ERR("memory allocation failure\n");						\
			goto error;													\
		}																\
		memset(p,0,sizeof(struct proxy_l));								\
		p->name=*name;													\
		p->port=port;													\
																		\
		LM_DBG("doing DNS lookup...\n");								\
		proto=protocol;													\
		he=sip_resolvehost(name, &(p->port), &proto);					\
		if (he==0){														\
			ser_error=E_BAD_ADDRESS;									\
			LM_CRIT("could not resolve hostname:"						\
				" \"%.*s\"\n", name->len, name->s);						\
			p_free(p);													\
			goto error;													\
		}																\
		if (he_cpy(&(p->host), he)!=0){									\
			p_free(p);													\
			goto error;													\
		}																\
		p->proto=proto;													\
		p->ok=1;														\
		return p;														\
	error:																\
		return 0;														\
																		\
	} while(0)

/* same as add_proxy, but it doesn't add the proxy to the list
 * uses also SRV if possible & port==0 (quick hack) */

struct proxy_l* mk_proxy(str* name, unsigned short port, int protocol)
{
	MK_PROXY(name, port, protocol, pkg_malloc, pkg_free, hostent_cpy);
}


struct proxy_l* mk_shm_proxy(str* name, unsigned short port, int protocol)
{
	MK_PROXY(name, port, protocol, shm_malloc, shm_free, hostent_shm_cpy);
}



/* same as mk_proxy, but get the host as an ip*/
struct proxy_l* mk_proxy_from_ip(struct ip_addr* ip, unsigned short port,
									int proto)
{
	struct proxy_l* p;

	p=(struct proxy_l*) pkg_malloc(sizeof(struct proxy_l));
	if (p==0){
		PKG_MEM_CRITICAL;
		goto error;
	}
	memset(p,0,sizeof(struct proxy_l));

	p->port=port;
	p->proto=proto;
	p->host.h_addrtype=ip->af;
	p->host.h_length=ip->len;
	p->host.h_addr_list=pkg_malloc(2*sizeof(char*));
	if (p->host.h_addr_list==0) {
		PKG_MEM_ERROR;
		pkg_free(p);
		goto error;
	}
	p->host.h_addr_list[1]=0;
	p->host.h_addr_list[0]=pkg_malloc(ip->len+1);
	if (p->host.h_addr_list[0]==0){
		PKG_MEM_ERROR;
		pkg_free(p->host.h_addr_list);
		pkg_free(p);
		goto error;
	}

	memcpy(p->host.h_addr_list[0], ip->u.addr, ip->len);
	p->host.h_addr_list[0][ip->len]=0;

	return p;

error:
	return 0;
}




void free_proxy(struct proxy_l* p)
{
	if (p) free_hostent(&p->host);
}


void free_shm_proxy(struct proxy_l* p)
{
	if (p) free_shm_hostent(&p->host);
}
