/*
 * Copyright (C) 2005 Voice Sistem SRL
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * UAC Kamailio-module 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.
 *
 * UAC Kamailio-module 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 uac :: header replacement/retrieval functions
 * \ingroup uac
 * Module: \ref uac
 */

#include <ctype.h>

#include "../../core/parser/parse_from.h"
#include "../../core/mem/mem.h"
#include "../../core/data_lump.h"
#include "../../core/route.h"
#include "../../modules/tm/h_table.h"
#include "../../modules/tm/tm_load.h"
#include "../rr/api.h"
#include "../dialog/dlg_load.h"
#include "../dialog/dlg_hash.h"


#include "replace.h"

extern str uac_passwd;
extern int restore_mode;
extern str rr_from_param;
extern str rr_to_param;
extern struct tm_binds uac_tmb;
extern struct rr_binds uac_rrb;

extern str restore_from_avp;
extern str restore_to_avp;
extern unsigned short restore_from_avp_type;
extern int_str restore_from_avp_name;
extern unsigned short restore_to_avp_type;
extern int_str restore_to_avp_name;

struct dlg_binds dlg_api;
static str from_dlgvar[] = {str_init("_uac_fu"), str_init("_uac_funew"), str_init("_uac_fdp"), str_init("_uac_fdpnew")};
static str to_dlgvar[] = {str_init("_uac_to"), str_init("_uac_tonew"), str_init("_uac_tdp"), str_init("_uac_tdpnew")};

static char enc_table64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz0123456789+/";

static int dec_table64[256];

static void restore_uris_reply(struct cell* t, int type, struct tmcb_params *p);

static void replace_callback(struct dlg_cell *dlg, int type,
		struct dlg_cb_params *_params);

#define text3B64_len(_l)   ( ( ((_l)+2)/3 ) << 2 )

/* The reply, were the From-Line was replaced. */

void init_from_replacer(void)
{
	int i;

	for( i=0 ; i<256 ; i++)
		dec_table64[i] = -1;
	for ( i=0 ; i<64; i++)
		dec_table64[(unsigned char)enc_table64[i]] = i;
	}


static inline int encode_uri( str *src, str *dst )
{
	static char buf[text3B64_len(MAX_URI_SIZE)];
	int  idx;
	int  left;
	int  block;
	int  i,r;
	char *p;

	dst->len = text3B64_len( src->len );
	dst->s = buf;
	if (dst->len>text3B64_len(MAX_URI_SIZE))
	{
		LM_ERR("uri too long\n");
		return -1;
	}

	for ( idx=0, p=buf ; idx<src->len ; idx+=3)
	{
		left = src->len - idx -1 ;
		left = (left>1? 2 : left);

		/* Collect 1 to 3 bytes to encode */
		block = 0;
		for ( i=0,r= 16 ; i<=left ; i++,r-=8 )
		{
			block += ((unsigned char)src->s[idx+i]) << r;
		}

		/* Encode into 2-4 chars appending '=' if not enough data left.*/
		*(p++) = enc_table64[(block >> 18) & 0x3f];
		*(p++) = enc_table64[(block >> 12) & 0x3f];
		*(p++) = left > 0 ? enc_table64[(block >> 6) & 0x3f] : '-';
		*(p++) = left > 1 ? enc_table64[block & 0x3f] : '-';
	}

	return 0;
}


static inline int decode_uri( str *src , str *dst)
{
	static char buf[MAX_URI_SIZE];
	int block;
	int n;
	int idx;
	int end;
	int i,j;
	signed char c;

	/* sanity checks */
	if (!src) {
		LM_ERR("NULL src\n");
		return -1;
	}

	if (!dst) {
		LM_ERR("NULL dst\n");
		return -1;
	}

	if (!src->s || src->len == 0) {
		LM_ERR("empty src\n");
		return -1;
	}

	/* Count '-' at end and disregard them */
	for( n=0,i=src->len-1; src->s[i]=='-'; i--)
		n++;

	dst->len = ((src->len * 6) >> 3) - n;
	dst->s = buf;
	if (dst->len>MAX_URI_SIZE)
	{
		LM_ERR("uri too long\n");
		return -1;
	}

	end = src->len - n;
	for ( i=0,idx=0 ; i<end ; idx+=3 )
	{
		/* Assemble three bytes into an int from four "valid" characters */
		block = 0;
		for ( j=0; j<4 && i<end ; j++)
		{
			c = dec_table64[(unsigned char)src->s[i++]];
			if ( c<0 )
			{
				LM_ERR("invalid base64 string\"%.*s\"\n",src->len,src->s);
				return -1;
			}
			block += c << (18 - 6*j);
		}

		/* Add the bytes */
		for ( j=0,n=16 ; j<3 && idx+j< dst->len; j++,n-=8 )
			buf[idx+j] = (char) ((block >> n) & 0xff);
	}

	return 0;
}


static inline struct lump* get_display_anchor(struct sip_msg *msg,
		struct hdr_field *hdr, struct to_body *body, str *dsp)
{
	struct lump* l;
	char *p1;
	char *p2;

	/* is URI quoted or not? */
	p1 = hdr->name.s + hdr->name.len;
	for( p2=body->uri.s-1 ; p2>=p1 && *p2!='<' ; p2--);

	if (*p2=='<') {
		/* is quoted */
		l = anchor_lump( msg, p2 - msg->buf, 0, 0);
		if (l==0) {
			LM_ERR("unable to build lump anchor\n");
			return 0;
		}
		dsp->s[dsp->len++] = ' ';
		return l;
	}

	/* not quoted - more complicated....must place the closing bracket */
	l = anchor_lump( msg, (body->uri.s+body->uri.len) - msg->buf, 0, 0);
	if (l==0) {
		LM_ERR("unable to build lump anchor\n");
		return 0;
	}
	p1 = (char*)pkg_malloc(1);
	if (p1==0) {
		PKG_MEM_ERROR;
		return 0;
	}
	*p1 = '>';
	if (insert_new_lump_after( l, p1, 1, 0)==0) {
		LM_ERR("insert lump failed\n");
		pkg_free(p1);
		return 0;
	}
	/* build anchor for display */
	l = anchor_lump( msg, body->uri.s - msg->buf, 0, 0);
	if (l==0) {
		LM_ERR("unable to build lump anchor\n");
		return 0;
	}
	dsp->s[dsp->len++] = ' ';
	dsp->s[dsp->len++] = '<';
	return l;
}


/*
 * replace uri and/or display name in FROM / TO header
 */
int replace_uri( struct sip_msg *msg, str *display, str *uri,
	struct hdr_field *hdr, str *rr_param, str* restore_avp, int check_from)
{
	static char buf_s[MAX_URI_SIZE];
	struct to_body *body;
	struct lump* l;
	struct cell *Trans;
	str replace;
	char *p;
	str param;
	str buf;
	unsigned int uac_flag;
	int i;
	int_str avp_value;
	struct dlg_cell* dlg = 0;
	str * dlgvar_names;
	str display_tmp;
	str undefined_display = str_init("");

	uac_flag = (hdr==msg->from)?FL_USE_UAC_FROM:FL_USE_UAC_TO;
	if(get_route_type()==REQUEST_ROUTE) {
		if(msg->msg_flags & uac_flag)
		{
			LM_ERR("called uac_replace_%s() multiple times on the message\n",
					(hdr==msg->from)?"from":"to");
			return -1;
		}
	}

	/* consistency check! in AUTO mode, do NOT allow URI changing
	 * in sequential request */
	if (restore_mode==UAC_AUTO_RESTORE && uri && uri->len) {
		if ( msg->to==0 && (parse_headers(msg,HDR_TO_F,0)!=0 || msg->to==0) ) {
			LM_ERR("failed to parse TO hdr\n");
			goto error;
		}
		if (get_to(msg)->tag_value.len!=0) {
			LM_ERR("decline FROM replacing in sequential request in auto mode (has TO tag)\n");
			goto error;
		}
	}

	body = (struct to_body*)hdr->parsed;

	if(restore_avp->s) {
		/* backup data in avp (if avp is set) */
		/* catch whitespace characters after uri */
		for( p = body->uri.s+body->uri.len, i=0; isspace(p[i]) ; i++ );
		/* if ">" present after uri (and whitespace), catch it */
		avp_value.s.len =  p - body->body.s + ((p[i]=='>') ? (i+1) : 0) ;
		avp_value.s.s=body->body.s;

		LM_DBG("value to store is is '%.*s' and len is '%d'\n",
				avp_value.s.len, avp_value.s.s,avp_value.s.len);

		if(check_from) {
			LM_DBG("Storing in FROM-AVP (for use in reply): '%.*s' with len '%d'\n",
					avp_value.s.len, avp_value.s.s,avp_value.s.len);
			add_avp(restore_from_avp_type, restore_from_avp_name, avp_value);
		} else {
			LM_DBG("Storing in TO-AVP (for use in reply): '%.*s' with len '%d'\n",
					avp_value.s.len, avp_value.s.s,avp_value.s.len);
			add_avp(restore_to_avp_type, restore_to_avp_name, avp_value);
		}
	}

	/* first deal with display name */
	if (display)
	{
		/* must be replaced/ removed */
		l = 0;
		/* first remove the existing display */
		if ( body->display.len)
		{
			LM_DBG("removing display [%.*s]\n",
					body->display.len, body->display.s);
			/* build del lump */
			l = del_lump(msg, body->display.s-msg->buf, body->display.len, 0);
			if (l==0)
			{
				LM_ERR("display del lump failed\n");
				goto error;
			}
		}
		/* some new display to set? */
		if (display->len)
		{
			LM_DBG("adding new display [%.*s]\n", display->len, display->s);
			/* add the new display exactly over the deleted one */
			buf.s = pkg_malloc( display->len + 2 );
			if (buf.s==0)
			{
				PKG_MEM_ERROR;
				goto error;
			}
			memcpy( buf.s, display->s, display->len);
			buf.len =  display->len;
			if (l==0 && (l=get_display_anchor(msg,hdr,body,&buf))==0)
			{
				LM_ERR("failed to insert anchor\n");
				pkg_free(buf.s);
				goto error;
			}
			if (insert_new_lump_after( l, buf.s, buf.len, 0)==0)
			{
				LM_ERR("insert new display lump failed\n");
				pkg_free(buf.s);
				goto error;
			}
		}
	}

	/* now handle the URI */
	if (uri==0 || uri->len==0 )
		/* do not touch URI part */
		return 0;

	LM_DBG("uri to replace [%.*s]\n",body->uri.len, body->uri.s);
	LM_DBG("replacement uri is [%.*s]\n",uri->len, uri->s);

	/* build del/add lumps */
	if ((l=del_lump( msg, body->uri.s-msg->buf, body->uri.len, 0))==0)
	{
		LM_ERR("del lump failed\n");
		goto error;
	}
	p = pkg_malloc( uri->len);
	if (p==0)
	{
		PKG_MEM_ERROR;
		goto error;
	}
	memcpy( p, uri->s, uri->len);
	if (insert_new_lump_after( l, p, uri->len, 0)==0)
	{
		LM_ERR("insert new lump failed\n");
		pkg_free(p);
		goto error;
	}

	if (restore_mode==UAC_NO_RESTORE)
		return 0;

	/* trying to get dialog */
	if (dlg_api.get_dlg) {
		dlg = dlg_api.get_dlg(msg);
	}

	if (dlg) {
		dlgvar_names = (uac_flag==FL_USE_UAC_FROM)?from_dlgvar:to_dlgvar;
		if(dlg_api.get_dlg_var(dlg, &dlgvar_names[0])) {

			LM_INFO("Already called uac_replace for this dialog\n");
			/* delete the from_new dlg var */

			if (dlg_api.set_dlg_var(dlg, &dlgvar_names[1], 0) < 0) {
				LM_ERR("cannot store new uri value\n");
				dlg_api.release_dlg(dlg);
				goto error;
			}
			LM_INFO("Deleted <%.*s> var in dialog\n",
					dlgvar_names[1].len, dlgvar_names[1].s);
		}
		else {
			/* the first time uac_replace is called for this dialog */
			/* store old URI value */
			if (dlg_api.set_dlg_var(dlg, &dlgvar_names[0], &body->uri) < 0) {
				LM_ERR("cannot store value\n");
				dlg_api.release_dlg(dlg);
				goto error;
			}
			LM_DBG("Stored <%.*s> var in dialog with value %.*s\n",
					dlgvar_names[0].len, dlgvar_names[0].s,
					body->uri.len, body->uri.s);

			if (dlg_api.register_dlgcb(dlg,
						DLGCB_REQ_WITHIN|DLGCB_CONFIRMED|DLGCB_TERMINATED,
						(void*)(unsigned long)replace_callback,
						(void*)(unsigned long)uac_flag, 0) != 0) {
				LM_ERR("cannot register callback\n");
				dlg_api.release_dlg(dlg);
				goto error;
			}
		}
		/* store new URI value */
		if (dlg_api.set_dlg_var(dlg, &dlgvar_names[1], uri) < 0) {
			LM_ERR("cannot store new uri value\n");
			dlg_api.release_dlg(dlg);
			goto error;
		}
		LM_DBG("Stored <%.*s> var in dialog with value %.*s\n",
				dlgvar_names[1].len, dlgvar_names[1].s, uri->len, uri->s);

		/* store the display name as well */
		if (body->display.s && body->display.len >0) {
			display_tmp = body->display;
		} else {
			display_tmp = undefined_display;
		}
		if (dlg_api.set_dlg_var(dlg, &dlgvar_names[2], &display_tmp) < 0) {
			LM_ERR("cannot store display value\n");
			dlg_api.release_dlg(dlg);
			goto error;
		}
		LM_DBG("Stored <%.*s> var in dialog with value %.*s\n",
				dlgvar_names[1].len, dlgvar_names[2].s, display_tmp.len, display_tmp.s);

		if (display && display->s && display->len > 0) {
			display_tmp.s=display->s;
			display_tmp.len=display->len;
		} else {
			display_tmp = undefined_display;
		}
		if (dlg_api.set_dlg_var(dlg, &dlgvar_names[3], &display_tmp) < 0) {
			LM_ERR("cannot store new display value\n");
			dlg_api.release_dlg(dlg);
			goto error;
		}
		LM_DBG("Stored <%.*s> var in dialog with value %.*s\n",
				dlgvar_names[1].len, dlgvar_names[3].s, display_tmp.len, display_tmp.s);

		dlg_api.release_dlg(dlg);
	} else {
		if (!uac_rrb.append_fromtag) {
			LM_ERR("'append_fromtag' RR param is not enabled!"
			" - required by AUTO restore mode\n");
			goto error;
		}

		/* build RR parameter */
		buf.s = buf_s;
		if ( body->uri.len>uri->len ) {
			if (body->uri.len>MAX_URI_SIZE) {
				LM_ERR("old %.*s uri too long\n",hdr->name.len,hdr->name.s);
				goto error;
			}
			memcpy( buf.s, body->uri.s, body->uri.len);
			for( i=0 ; i<uri->len ; i++ )
				buf.s[i] ^=uri->s[i];
			buf.len = body->uri.len;
		} else {
			if (uri->len>MAX_URI_SIZE) {
				LM_ERR("new %.*s uri too long\n",hdr->name.len,hdr->name.s);
				goto error;
			}
			memcpy( buf.s, uri->s, uri->len);
			for( i=0 ; i<body->uri.len ; i++ )
				buf.s[i] ^=body->uri.s[i];
			buf.len = uri->len;
		}

		/* encrypt parameter ;) */
		if (uac_passwd.len)
			for( i=0 ; i<buf.len ; i++)
				buf.s[i] ^= uac_passwd.s[i%uac_passwd.len];

		/* encode the param */
		if (encode_uri( &buf , &replace)<0 )
		{
			LM_ERR("failed to encode uris\n");
			goto error;
		}
		LM_DBG("encode is=<%.*s> len=%d\n",replace.len,replace.s,replace.len);

		/* add RR parameter */
		param.len = 1+rr_param->len+1+replace.len;
		param.s = (char*)pkg_malloc(param.len);
		if (param.s==0)
		{
			PKG_MEM_ERROR;
			goto error;
		}
		p = param.s;
		*(p++) = ';';
		memcpy( p, rr_param->s, rr_param->len);
		p += rr_param->len;
		*(p++) = '=';
		memcpy( p, replace.s, replace.len);

		if (uac_rrb.add_rr_param( msg, &param)!=0)
		{
			LM_ERR("add_RR_param failed\n");
			goto error1;
		}
		pkg_free(param.s);
	}

	if ((msg->msg_flags&(FL_USE_UAC_FROM|FL_USE_UAC_TO))==0) {
		/* add TM callback to restore the FROM/TO hdr in reply */
		if (uac_tmb.register_tmcb( msg, 0, TMCB_RESPONSE_IN,
			restore_uris_reply,0,0)!=1) {
			LM_ERR("failed to install TM callback\n");
			goto error;
		}
	}
	msg->msg_flags |= uac_flag;

	if ( (Trans=uac_tmb.t_gett())!=NULL && Trans!=T_UNDEFINED &&
		Trans->uas.request) {
		Trans->uas.request->msg_flags |= uac_flag;
	}

	return 0;
error1:
	pkg_free(param.s);
error:
	return -1;
}


/*
 * return  0 - restored
 *        -1 - not restored or error
 */
int restore_uri( struct sip_msg *msg, str *rr_param, str* restore_avp,
		int check_from)
{
	struct lump* l;
	str param_val;
	str add_to_rr = {0, 0};
	struct to_body* old_body;
	str old_uri = {0, 0};
	str new_uri = {0, 0};
	char *p;
	int i;
	int_str avp_value;
	int flag;
	int bsize;

	/* we should process only sequential request, but since we are looking
	 * for Route param, the test is not really required -bogdan */

	LM_DBG("getting '%.*s' Route param\n",
		rr_param->len,rr_param->s);
	/* is there something to restore ? */
	if (uac_rrb.get_route_param( msg, rr_param, &param_val)!=0) {
		LM_DBG("route param '%.*s' not found\n",
			rr_param->len,rr_param->s);
		goto failed;
	}
	LM_DBG("route param is '%.*s' (len=%d)\n",
		param_val.len,param_val.s,param_val.len);

	/* decode the parameter val to a URI */
	if (decode_uri( &param_val, &new_uri)<0 ) {
		LM_ERR("failed to decode uri\n");
		goto failed;
	}

	bsize = 3+rr_param->len+param_val.len;
	add_to_rr.s = pkg_malloc(bsize);
	if ( add_to_rr.s==0 ) {
		add_to_rr.len = 0;
		PKG_MEM_ERROR;
		goto failed;
	}
	add_to_rr.len = snprintf(add_to_rr.s, bsize, ";%.*s=%.*s",
			rr_param->len, rr_param->s, param_val.len, param_val.s);

	if(add_to_rr.len<0 || add_to_rr.len>=bsize) {
		LM_ERR("printing rr param failed\n");
		goto failed;
	}
	if ( uac_rrb.add_rr_param(msg, &add_to_rr)!=0 ) {
		LM_ERR("add rr param failed\n");
		goto failed;
	}
	pkg_free(add_to_rr.s);
	add_to_rr.s = NULL;

	/* decrypt parameter */
	if (uac_passwd.len) {
		for( i=0 ; i<new_uri.len ; i++)
			new_uri.s[i] ^= uac_passwd.s[i%uac_passwd.len];
	}

	/* check the request direction */
	if ( (check_from && uac_rrb.is_direction(msg, RR_FLOW_UPSTREAM)==0)
			|| (!check_from && uac_rrb.is_direction(msg, RR_FLOW_DOWNSTREAM)==0)
				) {
		/* replace the TO URI */
		if ( msg->to==0 && (parse_headers(msg,HDR_TO_F,0)!=0 || msg->to==0) ) {
			LM_ERR("failed to parse TO hdr\n");
			goto failed;
		}
		old_body = (struct to_body*) msg->to->parsed;
		flag = FL_USE_UAC_TO;
		LM_DBG("replacing in To header\n");
	} else {
		/* replace the FROM URI */
		if ( parse_from_header(msg)<0 ) {
			LM_ERR("failed to find/parse FROM hdr\n");
			goto failed;
		}
		old_body = (struct to_body*) msg->from->parsed;
		flag = FL_USE_UAC_FROM;
		LM_DBG("replacing in From header\n");
	}

	if(restore_avp->s) {
		/* backup data to avp (if avp is set) */

		/* catch whitespace characters after uri */
		for( p = old_body->uri.s+old_body->uri.len, i=0; isspace(p[i]) ; i++ );
		/* if ">" present after uri (and whitespace), catch it */
		avp_value.s.len =  p - old_body->body.s + ((p[i]=='>') ? (i+1) : 0) ;
		avp_value.s.s=old_body->body.s;

		if(flag==FL_USE_UAC_FROM) {
			LM_DBG("Storing in FROM-AVP (for use in reply): '%.*s' with len '%d'\n",
					avp_value.s.len, avp_value.s.s,avp_value.s.len);
			add_avp(restore_from_avp_type, restore_from_avp_name, avp_value);
		} else {
			LM_DBG("Storing in TO-AVP (for use in reply): '%.*s' with len '%d'\n",
					avp_value.s.len, avp_value.s.s,avp_value.s.len);
			add_avp(restore_to_avp_type, restore_to_avp_name, avp_value);
		}
	}

	old_uri= old_body->uri;

	/* get new uri */
	if ( new_uri.len<old_uri.len ) {
		LM_ERR("new URI [%.*s] shorter than old URI [%.*s]\n",
				new_uri.len, new_uri.s, old_uri.len, old_uri.s);
		goto failed;
	}
	for( i=0 ; i<old_uri.len ; i++ ) {
		new_uri.s[i] ^= old_uri.s[i];
		if (new_uri.s[i] == 0) {
			new_uri.len = i;
			break;
		}
	}
	if (new_uri.len==0) {
		LM_ERR("new URI got 0 len\n");
		goto failed;
	}

	/* check if new uri has valid characters */
	for(i=0; i<new_uri.len; i++) {
		if(!isprint(new_uri.s[i])) {
			LM_WARN("invalid char found in the new uri at pos %d (%c) [%.*s]\n",
					i, new_uri.s[i], new_uri.len, new_uri.s);
			LM_WARN("this can happen when URI values are altered by end points"
					" - skipping the update\n");
			goto failed;
		}
	}
	LM_DBG("decoded uris are: new=[%.*s] old=[%.*s]\n",
		new_uri.len, new_uri.s, old_uri.len, old_uri.s);

	/* duplicate the decoded value */
	p = pkg_malloc( new_uri.len);
	if (p==0) {
		PKG_MEM_ERROR;
		goto failed;
	}
	memcpy( p, new_uri.s, new_uri.len);
	new_uri.s = p;

	/* build del/add lumps */
	l = del_lump( msg, old_uri.s-msg->buf, old_uri.len, 0);
	if (l==0) {
		LM_ERR("del lump failed\n");
		goto failed1;
	}

	if (insert_new_lump_after( l, new_uri.s, new_uri.len, 0)==0) {
		LM_ERR("insert new lump failed\n");
		goto failed1;
	}

	msg->msg_flags |= flag;

	return 0;
failed1:
	pkg_free(new_uri.s);
failed:
	if(add_to_rr.s)
		pkg_free(add_to_rr.s);
	return -1;
}



/************************** RRCB functions ******************************/

void rr_checker(struct sip_msg *msg, str *r_param, void *cb_param)
{
	/* check if the request contains the route param */
	if ( (restore_uri( msg, &rr_from_param, &restore_from_avp, 1/*from*/) +
			restore_uri( msg, &rr_to_param, &restore_to_avp, 0/*to*/) )!= -2 ) {
		/* restore in req performed -> replace in reply */
		/* in callback we need TO/FROM to be parsed- it's already done
		 * by restore_from_to() function */
		if ( uac_tmb.register_tmcb( msg, 0, TMCB_RESPONSE_IN,
		restore_uris_reply, 0, 0)!=1 ) {
			LM_ERR("failed to install TM callback\n");
				return;
		}
	}
}


/************************** TMCB functions ******************************/

/* replace the entire HDR with the original request */
static inline int restore_uri_reply(struct sip_msg *rpl,
		struct hdr_field *rpl_hdr, struct hdr_field *req_hdr, str* stored_value)

{
	struct lump* l;
	struct to_body *body;
	str new_val;
	int len;
	char *p;

	if(stored_value->len) {
		LM_DBG("stored AVP value is '%.*s'with len '%d'\n",
				stored_value->len, stored_value->s, stored_value->len);
		len=stored_value->len;
		p = stored_value->s;
	} else {
		/* duplicate the new hdr value */
		body = (struct to_body*)req_hdr->parsed;
		/* catch whitespace characters after uri */
		for( p = body->uri.s+body->uri.len, len=0; isspace(p[len]) ; len++ );
		/* if ">" present after uri (and whitespace), catch it */
		len =  p - body->body.s + ((p[len]=='>') ? (len+1) : 0) ;
		p = body->body.s;
	}

	new_val.s = pkg_malloc( len );
	if (new_val.s==0) {
		PKG_MEM_ERROR;
		return -1;
	}
	memcpy( new_val.s, p, len);
	new_val.len = len;

	body = (struct to_body*)rpl_hdr->parsed;

	/* catch whitespace characters after uri */
	for( p = body->uri.s+body->uri.len, len=0; isspace(p[len]) ; len++ );
	/* if ">" present after uri (and whitespace), catch it */
	len =  p - body->body.s + ((p[len]=='>') ? (len+1) : 0) ;
	LM_DBG("removing <%.*s>\n", len,body->body.s);
	l = del_lump( rpl, body->body.s-rpl->buf, len, 0);
	if (l==0) {
		LM_ERR("del lump failed\n");
		pkg_free( new_val.s );
		return -1;
	}

	LM_DBG("inserting <%.*s>\n",
			new_val.len,new_val.s);
	if (insert_new_lump_after( l, new_val.s, new_val.len, 0)==0) {
		LM_ERR("insert new lump failed\n");
		pkg_free( new_val.s );
		l->len = 0;
		return -1;
	}

	return 0;
}



/* replace the entire from HDR with the original FROM request */
void restore_uris_reply(struct cell* t, int type, struct tmcb_params *p)
{
	struct sip_msg *req;
	struct sip_msg *rpl;
	int_str avp_value;

	if ( !t || !t->uas.request || !p->rpl )
		return;

	req = t->uas.request;
	rpl = p->rpl;

	if (req->msg_flags & FL_USE_UAC_FROM ) {

		/* parse FROM in reply */
		if (parse_from_header( rpl )<0 ) {
			LM_ERR("failed to find/parse FROM hdr\n");
			return;
		}

		avp_value.s.len=0;
		if(restore_from_avp.s) {
			search_first_avp(restore_from_avp_type, restore_from_avp_name,
					&avp_value,0);
		}

		if (restore_uri_reply( rpl, rpl->from, req->from, &avp_value.s)) {
			LM_ERR("failed to restore FROM\n");
		}

	}
	if (req->msg_flags & FL_USE_UAC_TO ) {

		/* parse TO in reply */
		if ( rpl->to==0 && (parse_headers(rpl,HDR_TO_F,0)!=0 || rpl->to==0) ) {
			LM_ERR("failed to parse TO hdr\n");
			return;
		}

		avp_value.s.len=0;
		if(restore_to_avp.s) {
			search_first_avp(restore_to_avp_type, restore_to_avp_name,
					&avp_value, 0);
		}

		if (restore_uri_reply( rpl, rpl->to, req->to, &avp_value.s)) {
			LM_ERR("failed to restore TO\n");
		}

	}
}

/************************** DIALOG CB function ******************************/

static void replace_callback(struct dlg_cell *dlg, int type,
		struct dlg_cb_params *_params)
{
	struct lump* l;
	struct sip_msg *msg;
	struct hdr_field *hdr;
	struct to_body *body;
	str old_uri;
	str* new_uri;
	str* new_display;
	str buf;
	char *p;
	unsigned int uac_flag;
	int dlgvar_index = 0;
	int dlgvar_dpindex = 0;
	str* dlgvar_names;

	if (!dlg || !_params || _params->direction == DLG_DIR_NONE || !_params->req)
		return;

	uac_flag = (unsigned int)(unsigned long)*(_params->param);
	msg = _params->req;
	if(msg->msg_flags & uac_flag)
		return;

	dlgvar_names = (uac_flag==FL_USE_UAC_FROM)?from_dlgvar:to_dlgvar;

	/* check the request direction */
	if ( ((uac_flag == FL_USE_UAC_TO) && _params->direction == DLG_DIR_DOWNSTREAM)
			|| ((uac_flag != FL_USE_UAC_TO)
				&& _params->direction == DLG_DIR_UPSTREAM) ) {
		/* replace the TO URI */
		if ( msg->to==0 && (parse_headers(msg,HDR_TO_F,0)!=0 || msg->to==0) ) {
			LM_ERR("failed to parse TO hdr\n");
			return;
		}
		old_uri = ((struct to_body*)msg->to->parsed)->uri;
		hdr = (struct hdr_field*)msg->to;
		body = ((struct to_body*)msg->to->parsed);
	} else {
		/* replace the FROM URI */
		if ( parse_from_header(msg)<0 ) {
			LM_ERR("failed to find/parse FROM hdr\n");
			return;
		}
		old_uri = ((struct to_body*)msg->from->parsed)->uri;
		hdr = (struct hdr_field*)msg->from;
		body = (struct to_body*)msg->from->parsed;
	}

	if (_params->direction == DLG_DIR_DOWNSTREAM) {
		dlgvar_index = 1;
		dlgvar_dpindex = 3;
		LM_DBG("DOWNSTREAM direction detected - replacing uri"
				" with the new uri\n");
	} else {
		dlgvar_index = 0;
		dlgvar_dpindex = 2;
		LM_DBG("UPSTREAM direction detected - replacing uri"
				" with the original uri\n");
	}

	if ((new_uri = dlg_api.get_dlg_var(dlg, &dlgvar_names[dlgvar_index])) == 0) {
		LM_DBG("<%.*s> param not found\n", dlgvar_names[dlgvar_index].len,
				dlgvar_names[dlgvar_index].s);
		return;
	}
	if ((new_display = dlg_api.get_dlg_var(dlg, &dlgvar_names[dlgvar_dpindex])) == 0) {
		LM_DBG("<%.*s> param not found\n", dlgvar_names[dlgvar_dpindex].len,
				dlgvar_names[dlgvar_dpindex].s);
		return;
	}

	LM_DBG("Replace [%.*s %.*s] with [%.*s %.*s]\n", body->display.len, body->display.s,
			old_uri.len, old_uri.s, new_display->len, new_display->s,
			new_uri->len, new_uri->s);

	/* duplicate the decoded value */
	p = pkg_malloc( new_uri->len);
	if (!p) {
		PKG_MEM_ERROR;
		return;
	}
	memcpy( p, new_uri->s, new_uri->len);

	/* build del/add lumps */
	l = del_lump( msg, old_uri.s-msg->buf, old_uri.len, 0);
	if (l==0) {
		LM_ERR("del lump failed\n");
		goto free;
	}

	if (insert_new_lump_after( l, p, new_uri->len, 0)==0) {
		LM_ERR("insert new lump failed\n");
		goto free;
	}

	/* deal with display name */
	l = 0;
	/* first remove the existing display */
	if (body->display.s && body->display.len > 0) {
		LM_DBG("removing display [%.*s]\n",
				body->display.len, body->display.s);
		/* build del lump */
		l = del_lump(msg, body->display.s-msg->buf, body->display.len, 0);
		if (l==0) {
			LM_ERR("display del lump failed\n");
			goto free;
			}
	}
	if (new_display->s && new_display->len > 0) {
		LM_DBG("inserting display [%.*s]\n",
				new_display->len, new_display->s);
		/* add the new display exactly over the deleted one */
		buf.s = pkg_malloc(new_display->len + 2);
		if (buf.s==0) {
			PKG_MEM_ERROR;
			goto free;
		}
		memcpy( buf.s, new_display->s, new_display->len);
		buf.len = new_display->len;
		if (l==0 && (l=get_display_anchor(msg, hdr, body, &buf)) == 0) {
			LM_ERR("failed to insert anchor\n");
			goto free2;
		}
		if (insert_new_lump_after(l, buf.s, buf.len, 0) == 0) {
			LM_ERR("insert new display lump failed\n");
			goto free2;
		}
	}

	/* register tm callback to change replies,
	 * but only if not registered earlier */
	if (!(msg->msg_flags & (FL_USE_UAC_FROM|FL_USE_UAC_TO)) &&
			uac_tmb.register_tmcb( msg, 0, TMCB_RESPONSE_IN,
			restore_uris_reply, 0, 0) != 1 ) {
		LM_ERR("failed to install TM callback\n");
		return;
	}
	msg->msg_flags |= uac_flag;

	return;

free2:
	pkg_free(buf.s);

free:
	pkg_free(p);
}


/* helper function to avoid code duplication */
static inline int uac_load_callback_helper(struct dlg_cell* dialog, unsigned int uac_flag) {

	if( dlg_api.register_dlgcb(dialog, DLGCB_REQ_WITHIN,
			(void*)(unsigned long)replace_callback, (void*)(unsigned long)uac_flag, 0) != 0) {
		LM_ERR("can't register create dialog REQ_WITHIN callback\n");
		return -1;
	}

	if( dlg_api.register_dlgcb(dialog, DLGCB_CONFIRMED,
			(void*)(unsigned long)replace_callback, (void*)(unsigned long)uac_flag, 0) != 0) {
		LM_ERR("can't register create dialog CONFIRM callback\n");
		return -1;
	}

	if( dlg_api.register_dlgcb(dialog, DLGCB_TERMINATED,
			(void*)(unsigned long)replace_callback, (void*)(unsigned long)uac_flag, 0) != 0) {
		LM_ERR("can't register create dialog TERMINATED callback\n");
		return -1;
	}
	return 0;
}


/* callback for loading a dialog from database */
static void uac_on_load_callback(struct dlg_cell* dialog, int type, struct dlg_cb_params* params) {

	if(!dialog) {
		LM_ERR("invalid values\n!");
		return;
	}

	/* Note:
	 * We don't have a way to access the real uac flags from the uac_replace_*
	 * method call at this point in time anymore. Therefore we just install a
	 * callback for both FROM and TO replace cases. This might be a bit
	 * inefficient in cases where only one of the functions is used. But as
	 * this applies only e.g. to a proxy restart with runnning dialogs, it
	 * does not matter. The replace_callback function will just not find a
	 * an entry in the dialog variables table and log an error.
	 */
	if(uac_load_callback_helper(dialog, FL_USE_UAC_FROM) != 0) {
		LM_ERR("can't register create callbacks for UAC FROM\n");
		return;
	}
	if(uac_load_callback_helper(dialog, FL_USE_UAC_TO) != 0) {
		LM_ERR("can't register create callbacks for UAC TO\n");
		return;
	}

	LM_DBG("dialog '%p' loaded and callbacks registered\n", dialog);
}


/* initialization of all necessary callbacks to track a dialog */
int uac_init_dlg(void) {

	memset(&dlg_api, 0, sizeof(struct dlg_binds));

	if( load_dlg_api(&dlg_api) != 0) {
		LM_ERR("can't load dialog API\n");
		return -1;
	}

	if( dlg_api.register_dlgcb( 0, DLGCB_LOADED, uac_on_load_callback, 0, 0) != 0) {
		LM_ERR("can't register on load callback\n");
		return -1;
	}
	LM_DBG("loaded dialog API and registered on load callback\n");
	return 0;
}