/*
 * pua_dialoginfo module - publish dialog-info from dialo module
 *
 * Copyright (C) 2006 Voice Sistem S.R.L.
 * Copyright (C) 2007-2008 Dan Pascu
 * Copyright (C) 2008 Klaus Darilion IPCom
 *
 * 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
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#include <time.h>

#include "../../core/script_cb.h"
#include "../../core/sr_module.h"
#include "../../core/parser/parse_expires.h"
#include "../../core/dprint.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_to.h"
#include "../../core/parser/contact/parse_contact.h"
#include "../../core/str.h"
#include "../../core/str_list.h"
#include "../../core/mem/mem.h"
#include "../../core/pt.h"
#include "../../core/utils/sruid.h"
#include "../dialog/dlg_load.h"
#include "../dialog/dlg_hash.h"
#include "../pua/pua_bind.h"
#include "pua_dialoginfo.h"

MODULE_VERSION

/* Default module parameter values */
#define DEF_INCLUDE_CALLID 1
#define DEF_INCLUDE_LOCALREMOTE 1
#define DEF_INCLUDE_TAGS 1
#define DEF_OVERRIDE_LIFETIME 0
#define DEF_CALLER_ALWAYS_CONFIRMED 0
#define DEF_INCLUDE_REQ_URI 0
#define DEF_OVERRIDE_LIFETIME 0
#define DEF_SEND_PUBLISH_FLAG -1
#define DEF_USE_PUBRURI_AVPS 0
#define DEF_PUBRURI_CALLER_AVP 0
#define DEF_PUBRURI_CALLEE_AVP 0
#define DEF_CALLEE_TRYING 0
#define DEF_DISABLE_CALLER_PUBLISH_FLAG -1
#define DEF_DISABLE_CALLEE_PUBLISH_FLAG -1
#define DEF_PUBLISH_DIALOG_REQ_WITHIN 1

/* define PUA_DIALOGINFO_DEBUG to activate more verbose
 * logging and dialog info callback debugging
 */
/* #define PUA_DIALOGINFO_DEBUG 1 */

pua_api_t pua;

struct dlg_binds dlg_api;

unsigned short pubruri_caller_avp_type;
int_str pubruri_caller_avp_name;
unsigned short pubruri_callee_avp_type;
int_str pubruri_callee_avp_name;
sruid_t _puadi_sruid;

static str caller_dlg_var = {0, 0}; /* pubruri_caller */
static str callee_dlg_var = {0, 0}; /* pubruri_callee */
static str caller_entity_when_publish_disabled = {0, 0}; /* pubruri_caller */
static str callee_entity_when_publish_disabled = {0, 0}; /* pubruri_callee */

/* Module parameter variables */
int include_callid         = DEF_INCLUDE_CALLID;
int include_localremote    = DEF_INCLUDE_LOCALREMOTE;
int include_tags           = DEF_INCLUDE_TAGS;
int override_lifetime      = DEF_OVERRIDE_LIFETIME;
int caller_confirmed       = DEF_CALLER_ALWAYS_CONFIRMED;
int include_req_uri        = DEF_INCLUDE_REQ_URI;
int send_publish_flag      = DEF_SEND_PUBLISH_FLAG;
int use_pubruri_avps       = DEF_USE_PUBRURI_AVPS;
int callee_trying          = DEF_CALLEE_TRYING;
int disable_caller_publish_flag = DEF_DISABLE_CALLER_PUBLISH_FLAG;
int disable_callee_publish_flag = DEF_DISABLE_CALLEE_PUBLISH_FLAG;
char * pubruri_caller_avp  = DEF_PUBRURI_CALLER_AVP;
char * pubruri_callee_avp  = DEF_PUBRURI_CALLEE_AVP;
int publish_dialog_req_within = DEF_PUBLISH_DIALOG_REQ_WITHIN;
int dialog_event_types = DLGCB_FAILED| DLGCB_CONFIRMED_NA | DLGCB_TERMINATED | DLGCB_EXPIRED | DLGCB_EARLY;

int puadinfo_attribute_display = 0;

send_publish_t pua_send_publish;
/** module functions */

static int mod_init(void);
static int child_init(int rank);

static cmd_export_t cmds[]={
	{0, 0, 0, 0, 0, 0}
};

static param_export_t params[]={
	{"include_callid",      INT_PARAM, &include_callid },
	{"include_localremote", INT_PARAM, &include_localremote },
	{"include_tags",        INT_PARAM, &include_tags },
	{"override_lifetime",   INT_PARAM, &override_lifetime },
	{"caller_confirmed",    INT_PARAM, &caller_confirmed },
	{"include_req_uri",     INT_PARAM, &include_req_uri },
	{"send_publish_flag",   INT_PARAM, &send_publish_flag },
	{"use_pubruri_avps",    INT_PARAM, &use_pubruri_avps },
	{"pubruri_caller_avp",  PARAM_STRING, &pubruri_caller_avp },
	{"pubruri_callee_avp",  PARAM_STRING, &pubruri_callee_avp },
	{"pubruri_caller_dlg_var",  PARAM_STR, &caller_dlg_var },
	{"pubruri_callee_dlg_var",  PARAM_STR, &callee_dlg_var },
	{"callee_trying",       INT_PARAM, &callee_trying },
	{"disable_caller_publish_flag",   INT_PARAM, &disable_caller_publish_flag },
	{"disable_callee_publish_flag",   INT_PARAM, &disable_callee_publish_flag },
	{"caller_entity_when_publish_disabled",  PARAM_STR, &caller_entity_when_publish_disabled },
	{"callee_entity_when_publish_disabled",  PARAM_STR, &callee_entity_when_publish_disabled },
	{"publish_dialog_req_within",      INT_PARAM, &publish_dialog_req_within },
	{"attribute_display",   PARAM_INT, &puadinfo_attribute_display },
	{0, 0, 0 }
};

struct module_exports exports= {
	"pua_dialoginfo",	/* module name */
	DEFAULT_DLFLAGS,	/* dlopen flags */
	cmds,				/* exported functions */
	params,				/* exported parameters */
	0,					/* RPC method exports */
	0,					/* exported pseudo-variables */
	0,					/* response handling function */
	mod_init,			/* module initialization function */
	child_init,		/* per-child init function */
	0					/* module destroy function */
};


#ifdef PUA_DIALOGINFO_DEBUG
static void
__dialog_cbtest(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
{
	str tag;
	struct sip_msg *msg;
	LM_ERR("dialog callback received, from=%.*s, to=%.*s\n",
			dlg->from_uri.len, dlg->from_uri.s, dlg->to_uri.len,
			dlg->to_uri.s);
	if (dlg->tag[0].len && dlg->tag[0].s ) {
		LM_ERR("dialog callback: tag[0] = %.*s",
				dlg->tag[0].len, dlg->tag[0].s);
	}
	if (dlg->tag[0].len && dlg->tag[1].s ) {
		LM_ERR("dialog callback: tag[1] = %.*s",
				dlg->tag[1].len, dlg->tag[1].s);
	}

	if (type != DLGCB_DESTROY) {
		msg = dlg_get_valid_msg(_params);
		if (!msg) {
			LM_ERR("no SIP message available in callback parameters\n");
			return;
		}

		/* get to tag*/
		if ( !msg->to) {
			// to header not defined, parse to header
			LM_ERR("to header not defined, parse to header\n");
			if (parse_headers(msg, HDR_TO_F,0)<0) {
				//parser error
				LM_ERR("parsing of to-header failed\n");
				tag.s = 0;
				tag.len = 0;
			} else if (!msg->to) {
				// to header still not defined
				LM_ERR("bad reply or missing TO header\n");
				tag.s = 0;
				tag.len = 0;
			} else {
				tag = get_to(msg)->tag_value;
			}
		} else {
			tag = get_to(msg)->tag_value;
			if (tag.s==0 || tag.len==0) {
				LM_ERR("missing TAG param in TO hdr :-/\n");
				tag.s = 0;
				tag.len = 0;
			}
		}
		if (tag.s) {
			LM_ERR("dialog callback: msg->to->parsed->tag_value = %.*s",
					tag.len, tag.s);
		}
	}

	switch (type) {
		case DLGCB_FAILED:
			LM_ERR("dialog callback type 'DLGCB_FAILED' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_CONFIRMED_NA:
			LM_ERR("dialog callback type 'DLGCB_CONFIRMED_NA' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_CONFIRMED:
			LM_ERR("dialog callback type 'DLGCB_CONFIRMED' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_REQ_WITHIN:
			LM_ERR("dialog callback type 'DLGCB_REQ_WITHIN' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_TERMINATED:
			LM_ERR("dialog callback type 'DLGCB_TERMINATED' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_EXPIRED:
			LM_ERR("dialog callback type 'DLGCB_EXPIRED' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_EARLY:
			LM_ERR("dialog callback type 'DLGCB_EARLY' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_RESPONSE_FWDED:
			LM_ERR("dialog callback type 'DLGCB_RESPONSE_FWDED' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_RESPONSE_WITHIN:
			LM_ERR("dialog callback type 'DLGCB_RESPONSE_WITHIN' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		case DLGCB_DESTROY:
			LM_ERR("dialog callback type 'DLGCB_DESTROY' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
			break;
		default:
			LM_ERR("dialog callback type 'unknown' received, from=%.*s\n",
					dlg->from_uri.len, dlg->from_uri.s);
	}
}
#endif

static void
__dialog_sendpublish(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
{
	str tag = {0,0};
	str uri = {0,0};
	str identity_local = {0,0};
	str target = {0,0};
	struct dlginfo_cell *dlginfo = NULL;

	struct sip_msg *request = _params->req;
	dlginfo = (struct dlginfo_cell*)*_params->param;

	if(dlg==NULL || dlginfo==NULL) {
		LM_WARN("execution with null parameters - type %d, dlg %p, info %p\n",
				type, dlg, dlginfo);
		return;
	}

	/* skip requests that do not control call state */
	if(request && (request->REQ_METHOD)&(METHOD_PRACK|METHOD_UPDATE)) {
		return;
	}
	if(include_req_uri) {
		uri = dlginfo->req_uri;
	} else {
		uri = dlginfo->to_uri;
	}

	if (dlginfo->disable_caller_publish) {
		identity_local=caller_entity_when_publish_disabled;
	} else {
		identity_local=dlginfo->from_uri;
	}

	if (dlginfo->disable_callee_publish) {
		uri=callee_entity_when_publish_disabled;
	}

	switch (type) {
		case DLGCB_FAILED:
		case DLGCB_TERMINATED:
		case DLGCB_EXPIRED:
			LM_DBG("dialog over, from=%.*s\n", dlginfo->from_uri.len,
					dlginfo->from_uri.s);
			if ((!dlginfo->disable_caller_publish) && (disable_caller_publish_flag == -1 || !(request
						&& (request->flags & (1<<disable_caller_publish_flag))))) {
				dialog_publish_multi("terminated", dlginfo->pubruris_caller,
						&identity_local, &uri, &(dlginfo->callid), 1,
						10, 0, 0, &(dlginfo->from_contact),
						&target, send_publish_flag==-1?1:0,&(dlginfo->uuid));
			}
			if ((!dlginfo->disable_callee_publish) && (disable_callee_publish_flag == -1 || !(request
						&& (request->flags & (1<<disable_callee_publish_flag))))) {
				dialog_publish_multi("terminated", dlginfo->pubruris_callee,
						&uri, &identity_local, &(dlginfo->callid), 0,
						10, 0, 0, &target, &(dlginfo->from_contact),
						send_publish_flag==-1?1:0,&(dlginfo->uuid));
			}
			break;
		case DLGCB_CONFIRMED:
		case DLGCB_REQ_WITHIN:
		case DLGCB_CONFIRMED_NA:
			LM_DBG("dialog confirmed, from=%.*s\n", dlginfo->from_uri.len,
					dlginfo->from_uri.s);
			if ((!dlginfo->disable_caller_publish) && (disable_caller_publish_flag == -1 || !(request
						&& (request->flags & (1<<disable_caller_publish_flag))))) {
				dialog_publish_multi("confirmed", dlginfo->pubruris_caller,
						&identity_local, &uri, &(dlginfo->callid), 1,
						dlginfo->lifetime, 0, 0, &(dlginfo->from_contact), &target,
						send_publish_flag==-1?1:0,&(dlginfo->uuid));
			}
			if ((!dlginfo->disable_callee_publish) && (disable_callee_publish_flag == -1 || !(request
						&& (request->flags & (1<<disable_callee_publish_flag))))) {
				dialog_publish_multi("confirmed", dlginfo->pubruris_callee, &uri,
						&identity_local, &(dlginfo->callid), 0,
						dlginfo->lifetime, 0, 0, &target, &(dlginfo->from_contact),
						send_publish_flag==-1?1:0,&(dlginfo->uuid));
			}
			break;
		case DLGCB_EARLY:
			LM_DBG("dialog is early, from=%.*s\n", dlginfo->from_uri.len,
					dlginfo->from_uri.s);
			if (include_tags) {
				/* get remotetarget */
				if ( !_params->rpl->contact && ((parse_headers(_params->rpl,
									HDR_CONTACT_F,0)<0) || !_params->rpl->contact) ) {
					LM_ERR("bad reply or missing CONTACT hdr\n");
				} else {
					if ( parse_contact(_params->rpl->contact)<0 ||
							((contact_body_t *)_params->rpl->contact->parsed)->contacts==NULL ||
							((contact_body_t *)_params->rpl->contact->parsed)->contacts->next!=NULL ) {
						LM_ERR("Malformed CONTACT hdr\n");
					} else {
						target = ((contact_body_t *)_params->rpl->contact->parsed)->contacts->uri;
					}
				}
				/* get to tag*/
				if ( !_params->rpl->to && ((parse_headers(_params->rpl, HDR_TO_F,0)<0)
							|| !_params->rpl->to) ) {
					LM_ERR("bad reply or missing TO hdr :-/\n");
					tag.s = 0;
					tag.len = 0;
				} else {
					tag = get_to(_params->rpl)->tag_value;
					if (tag.s==0 || tag.len==0) {
						LM_ERR("missing TAG param in TO hdr :-/\n");
						tag.s = 0;
						tag.len = 0;
					}
				}
				if ((!dlginfo->disable_caller_publish) && (disable_caller_publish_flag == -1 || !(request
							&& (request->flags & (1<<disable_caller_publish_flag))))) {
					if (caller_confirmed) {
						dialog_publish_multi("confirmed", dlginfo->pubruris_caller,
								&identity_local, &uri, &(dlginfo->callid), 1,
								dlginfo->lifetime, &(dlginfo->from_tag), &tag,
								&(dlginfo->from_contact), &target,
								send_publish_flag==-1?1:0,&(dlginfo->uuid));
					} else {
						dialog_publish_multi("early", dlginfo->pubruris_caller,
								&identity_local, &uri, &(dlginfo->callid), 1,
								dlginfo->lifetime, &(dlginfo->from_tag), &tag,
								&(dlginfo->from_contact), &target,
								send_publish_flag==-1?1:0,&(dlginfo->uuid));
					}
				}
				if ((!dlginfo->disable_callee_publish) && (disable_callee_publish_flag == -1 || !(request
							&& (request->flags & (1<<disable_callee_publish_flag))))) {
					dialog_publish_multi("early", dlginfo->pubruris_callee, &uri,
							&identity_local, &(dlginfo->callid), 0,
							dlginfo->lifetime, &tag, &(dlginfo->from_tag), &target,
							&(dlginfo->from_contact), send_publish_flag==-1?1:0,&(dlginfo->uuid));
				}

			} else {
				if ((!dlginfo->disable_caller_publish) && (disable_caller_publish_flag == -1 || !(request
					&& (request->flags & (1<<disable_caller_publish_flag))))) {
					if (caller_confirmed) {
						dialog_publish_multi("confirmed", dlginfo->pubruris_caller,
								&identity_local, &uri, &(dlginfo->callid), 1,
								dlginfo->lifetime, 0, 0, &(dlginfo->from_contact),
								&target, send_publish_flag==-1?1:0,&(dlginfo->uuid));

					} else {
						dialog_publish_multi("early", dlginfo->pubruris_caller,
								&identity_local, &uri, &(dlginfo->callid), 1,
								dlginfo->lifetime, 0, 0, &(dlginfo->from_contact),
								&target, send_publish_flag==-1?1:0,&(dlginfo->uuid));
					}
				}
				if ((!dlginfo->disable_callee_publish) && (disable_callee_publish_flag == -1 || !(request
					&& (request->flags & (1<<disable_callee_publish_flag)))))
				{
					dialog_publish_multi("early", dlginfo->pubruris_callee, &uri,
							&identity_local, &(dlginfo->callid), 0,
							dlginfo->lifetime, 0, 0, &target,
							&(dlginfo->from_contact), send_publish_flag==-1?1:0,&(dlginfo->uuid));
				}
			}
			break;
		default:
			LM_ERR("unhandled dialog callback type %d received, from=%.*s\n",
					type, dlginfo->from_uri.len, dlginfo->from_uri.s);
			if ((!dlginfo->disable_caller_publish) && (disable_caller_publish_flag == -1 || !(request
					&& (request->flags & (1<<disable_caller_publish_flag))))) {
				dialog_publish_multi("terminated", dlginfo->pubruris_caller,
						&identity_local, &uri, &(dlginfo->callid), 1,
						10, 0, 0, &(dlginfo->from_contact), &target,
						send_publish_flag==-1?1:0,&(dlginfo->uuid));
			}
			if ((!dlginfo->disable_callee_publish) && (disable_callee_publish_flag == -1 || !(request
					&& (request->flags & (1<<disable_callee_publish_flag))))) {
				dialog_publish_multi("terminated", dlginfo->pubruris_callee, &uri,
						&identity_local, &(dlginfo->callid), 0,
						10, 0, 0, &target, &(dlginfo->from_contact),
						send_publish_flag==-1?1:0,&(dlginfo->uuid));
			}
	}
}

/*
 *  Writes all avps with name avp_name to new str_list (shm mem)
 *  Be careful: returns NULL pointer if no avp present!
 *
 */
struct str_list* get_str_list(unsigned short avp_flags, int_str avp_name) {

	int_str avp_value;
	unsigned int len;
	struct str_list* list_first = 0;
	struct str_list* list_current = 0	;
	struct search_state st;

	if(!search_first_avp(avp_flags, avp_name, &avp_value, &st)) {
		return NULL;
	}

	do {

		LM_DBG("AVP found '%.*s'\n", avp_value.s.len, avp_value.s.s);

		len = sizeof(struct str_list) + avp_value.s.len;

		if(list_current) {
			list_current->next = (struct str_list*) shm_malloc( len);
			list_current=list_current->next;
		} else {
			list_current=list_first= (struct str_list*) shm_malloc( len);
		}

		if (list_current==0) {
			SHM_MEM_ERROR;
			return 0;
		}

		memset( list_current, 0, len);

		list_current->s.s = (char*)list_current + sizeof(struct str_list);
		list_current->s.len = avp_value.s.len;
		memcpy(list_current->s.s,avp_value.s.s,avp_value.s.len);



	} while(search_next_avp(&st, &avp_value));

	return list_first;

}

struct dlginfo_cell* get_dialog_data(struct dlg_cell *dlg, int type, int disable_caller_publish, int disable_callee_publish)
{
	struct dlginfo_cell *dlginfo;
	int len;
	str* s=NULL;

	// generate new random uuid
	if(sruid_next_safe(&_puadi_sruid) < 0) {
		return NULL;
	}
	LM_DBG("uuid generated: '%.*s'\n",
		_puadi_sruid.uid.len, _puadi_sruid.uid.s);

	/* create dlginfo structure to store important data inside the module*/
	len = sizeof(struct dlginfo_cell)
		+ dlg->from_uri.len
		+ dlg->to_uri.len
		+ dlg->callid.len
		+ dlg->tag[0].len
		+ dlg->req_uri.len
		+ dlg->contact[0].len
		+ _puadi_sruid.uid.len;

	dlginfo = (struct dlginfo_cell*)shm_malloc( len );
	if (dlginfo==0) {
		SHM_MEM_ERROR;
		return NULL;
	}
	memset( dlginfo, 0, len);

	/* copy from dlg structure to dlginfo structure */
	dlginfo->lifetime     = override_lifetime ? override_lifetime : dlg->lifetime;
	dlginfo->disable_caller_publish     = disable_caller_publish;
	dlginfo->disable_callee_publish     = disable_callee_publish;
	dlginfo->from_uri.s   = (char*)dlginfo + sizeof(struct dlginfo_cell);
	dlginfo->from_uri.len = dlg->from_uri.len;
	dlginfo->to_uri.s     = dlginfo->from_uri.s + dlg->from_uri.len;
	dlginfo->to_uri.len   = dlg->to_uri.len;
	dlginfo->callid.s     = dlginfo->to_uri.s + dlg->to_uri.len;
	dlginfo->callid.len   = dlg->callid.len;
	dlginfo->from_tag.s   = dlginfo->callid.s + dlg->callid.len;
	dlginfo->from_tag.len = dlg->tag[0].len;
	dlginfo->req_uri.s    = dlginfo->from_tag.s + dlginfo->from_tag.len;
	dlginfo->req_uri.len  = dlg->req_uri.len;
	dlginfo->from_contact.s   = dlginfo->req_uri.s + dlginfo->req_uri.len;
	dlginfo->from_contact.len = dlg->contact[0].len;
	dlginfo->uuid.s = dlginfo->from_contact.s + dlginfo->from_contact.len;
	dlginfo->uuid.len = _puadi_sruid.uid.len;

	memcpy(dlginfo->from_uri.s, dlg->from_uri.s, dlg->from_uri.len);
	memcpy(dlginfo->to_uri.s, dlg->to_uri.s, dlg->to_uri.len);
	memcpy(dlginfo->callid.s, dlg->callid.s, dlg->callid.len);
	memcpy(dlginfo->from_tag.s, dlg->tag[0].s, dlg->tag[0].len);
	memcpy(dlginfo->req_uri.s, dlg->req_uri.s, dlg->req_uri.len);
	memcpy(dlginfo->from_contact.s, dlg->contact[0].s, dlg->contact[0].len);
	memcpy(dlginfo->uuid.s, _puadi_sruid.uid.s, _puadi_sruid.uid.len);

	if (use_pubruri_avps) {
		if(type==DLGCB_CREATED) {
			dlginfo->pubruris_caller = get_str_list(pubruri_caller_avp_type,
					pubruri_caller_avp_name);
			dlginfo->pubruris_callee = get_str_list(pubruri_callee_avp_type,
					pubruri_callee_avp_name);

			if(dlginfo->pubruris_callee!=NULL && callee_dlg_var.len>0)
				dlg_api.set_dlg_var(dlg, &callee_dlg_var,
						&dlginfo->pubruris_callee->s);

			if(dlginfo->pubruris_caller!=NULL && caller_dlg_var.len>0)
				dlg_api.set_dlg_var(dlg, &caller_dlg_var,
						&dlginfo->pubruris_caller->s);

		} else {
			if(caller_dlg_var.len>0
					&& (s = dlg_api.get_dlg_varref(dlg, &caller_dlg_var))!=0) {
				dlginfo->pubruris_caller =
					(struct str_list*)shm_malloc(sizeof(struct str_list) + s->len + 1);
				if (dlginfo->pubruris_caller==0) {
					SHM_MEM_ERROR;
					free_dlginfo_cell(dlginfo);
					return NULL;
				}
				memset(dlginfo->pubruris_caller, 0, sizeof(struct str_list));
				dlginfo->pubruris_caller->s.s = (char*)dlginfo->pubruris_caller
					+ sizeof(sizeof(struct str_list));
				memcpy(dlginfo->pubruris_caller->s.s, s->s, s->len);
				dlginfo->pubruris_caller->s.s[s->len] = '\0';
				dlginfo->pubruris_caller->s.len = s->len;
				LM_DBG("Found pubruris_caller in dialog '%.*s'\n",
						dlginfo->pubruris_caller->s.len, dlginfo->pubruris_caller->s.s);
			}

			if(callee_dlg_var.len>0
					&& (s = dlg_api.get_dlg_varref(dlg, &callee_dlg_var))!=0) {
				dlginfo->pubruris_callee =
					(struct str_list*)shm_malloc(sizeof(struct str_list) + s->len + 1);
				if (dlginfo->pubruris_callee==0) {
					SHM_MEM_ERROR;
					free_dlginfo_cell(dlginfo);
					return NULL;
				}
				memset(dlginfo->pubruris_callee, 0, sizeof(struct str_list));
				dlginfo->pubruris_callee->s.s = (char*)dlginfo->pubruris_callee
					+ sizeof(sizeof(struct str_list));
				memcpy(dlginfo->pubruris_callee->s.s, s->s, s->len);
				dlginfo->pubruris_callee->s.s[s->len] = '\0';
				dlginfo->pubruris_callee->s.len = s->len;
				LM_DBG("Found pubruris_callee in dialog '%.*s'\n",
						dlginfo->pubruris_callee->s.len, dlginfo->pubruris_callee->s.s);
			}
		}

		if(dlginfo->pubruris_caller == 0 && dlginfo->pubruris_callee == 0 ) {
			/* No reason to save dlginfo, we have nobody to publish to */
			LM_DBG("Neither pubruris_caller nor pubruris_callee found.\n");
			free_dlginfo_cell(dlginfo);
			return NULL;
		}
	} else {
		dlginfo->pubruris_caller =
			(struct str_list*)shm_malloc( sizeof(struct str_list) );
		if (dlginfo->pubruris_caller==0) {
			SHM_MEM_ERROR;
			free_dlginfo_cell(dlginfo);
			return NULL;
		}
		memset( dlginfo->pubruris_caller, 0, sizeof(struct str_list));
		dlginfo->pubruris_caller->s=dlginfo->from_uri;

		dlginfo->pubruris_callee =
			(struct str_list*)shm_malloc( sizeof(struct str_list) );
		if (dlginfo->pubruris_callee==0) {
			SHM_MEM_ERROR;
			free_dlginfo_cell(dlginfo);
			return NULL;
		}
		memset( dlginfo->pubruris_callee, 0, sizeof(struct str_list));

		if(include_req_uri) {
			dlginfo->pubruris_callee->s = dlginfo->req_uri;
		} else {
			dlginfo->pubruris_callee->s = dlginfo->to_uri;
		}
	}

	/* register dialog callbacks which triggers sending PUBLISH */
	if (dlg_api.register_dlgcb(dlg, dialog_event_types,
			__dialog_sendpublish, dlginfo, free_dlginfo_cell) != 0) {
		LM_ERR("cannot register callback for interesting dialog types\n");
		free_dlginfo_cell(dlginfo);
		return NULL;
	}

#ifdef PUA_DIALOGINFO_DEBUG
	/* dialog callback testing (registered last to be executed first) */
	if (dlg_api.register_dlgcb(dlg,
				DLGCB_FAILED| DLGCB_CONFIRMED_NA | DLGCB_CONFIRMED
				| DLGCB_REQ_WITHIN | DLGCB_TERMINATED | DLGCB_EXPIRED
				| DLGCB_EARLY | DLGCB_RESPONSE_FWDED | DLGCB_RESPONSE_WITHIN
				| DLGCB_DESTROY,
				__dialog_cbtest, NULL, NULL) != 0) {
		LM_ERR("cannot register callback for all dialog types\n");
		free_dlginfo_cell(dlginfo);
		return NULL;
	}
#endif

	return(dlginfo);
}

	static void
__dialog_created(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
{
	struct sip_msg *request = _params->req;
	struct dlginfo_cell *dlginfo;
	str identity_remote = {0,0};
	str identity_local = {0,0};
	int disable_caller_publish = 0;
	int disable_callee_publish = 0;

	if (request==NULL || request->REQ_METHOD != METHOD_INVITE)
		return;

	if(send_publish_flag > -1 && !(request->flags & (1<<send_publish_flag)))
		return;

	LM_DBG("new INVITE dialog created: from=%.*s\n",
			dlg->from_uri.len, dlg->from_uri.s);

	if (disable_caller_publish_flag != -1 && caller_entity_when_publish_disabled.len > 0 &&
			(request && (request->flags & (1<<disable_caller_publish_flag)))) {
		disable_caller_publish = 1;
	}

	if (disable_callee_publish_flag != -1 && callee_entity_when_publish_disabled.len > 0 &&
			(request && (request->flags & (1<<disable_callee_publish_flag)))) {
		disable_callee_publish=1;
	}

	dlginfo=get_dialog_data(dlg, type, disable_caller_publish, disable_callee_publish);
	if(dlginfo==NULL)
		return;

	if (disable_caller_publish) {
		identity_local=caller_entity_when_publish_disabled;
	} else {
		identity_local=dlginfo->from_uri;
	}

	if (disable_callee_publish) {
		identity_remote=callee_entity_when_publish_disabled;
	} else {
		identity_remote=(include_req_uri)?dlg->req_uri:dlg->to_uri;
	}

	if ((!disable_caller_publish) && (disable_caller_publish_flag == -1 || !(request
		&& (request->flags & (1<<disable_caller_publish_flag))))) {
		dialog_publish_multi("Trying", dlginfo->pubruris_caller,
				&identity_local,
				&identity_remote,
				&(dlg->callid), 1, dlginfo->lifetime,
				0, 0, 0, 0, (send_publish_flag==-1)?1:0,&(dlginfo->uuid));
	}

	if (callee_trying && ((!disable_callee_publish) && (disable_callee_publish_flag == -1 || !(request
			&& (request->flags & (1<<disable_callee_publish_flag)))))) {
		dialog_publish_multi("Trying", dlginfo->pubruris_callee,
				&identity_remote,
				&identity_local,
				&(dlg->callid), 0, dlginfo->lifetime,
				0, 0, 0, 0, (send_publish_flag==-1)?1:0,&(dlginfo->uuid));
	}
}

	static void
__dialog_loaded(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
{
	struct dlginfo_cell *dlginfo;

	LM_DBG("INVITE dialog loaded: from=%.*s\n",
			dlg->from_uri.len, dlg->from_uri.s);

	dlginfo=get_dialog_data(dlg, type, 0, 0);
	if(dlginfo!=NULL) {
		LM_DBG("dialog info initialized (from=%.*s)\n",
				dlg->from_uri.len, dlg->from_uri.s);
		/* free_dlginfo_cell(dlginfo); */
	}
}


/**
 * init module function
 */
static int mod_init(void)
{
	bind_pua_t bind_pua;

	str s;
	pv_spec_t avp_spec;
        struct sip_uri ruri_uri;

	if(sruid_init(&_puadi_sruid, (char)'-', "padi", SRUID_INC)<0) {
		return -1;
	}

	if(caller_dlg_var.len<=0) {
		LM_WARN("pubruri_caller_dlg_var is not set"
				" - restore on restart disabled\n");
	}

	if(callee_dlg_var.len<=0) {
		LM_WARN("pubruri_callee_dlg_var is not set"
				" - restore on restart disabled\n");
	}

	if((caller_entity_when_publish_disabled.len > 0) && (disable_caller_publish_flag == -1)) {
		LM_WARN("caller_entity_when_publish_disabled is set but disable_caller_publish_flag is not defined"
			" - caller_entity_when_publish_disabled cannot be used \n");
	}

	if((callee_entity_when_publish_disabled.len > 0) && (disable_callee_publish_flag == -1)) {
		LM_WARN("callee_entity_when_publish_disabled is set but disable_callee_publish_flag is not defined"
			" - callee_entity_when_publish_disabled cannot be used \n");
	}

	if ((caller_entity_when_publish_disabled.len > 0) &&
		(parse_uri(caller_entity_when_publish_disabled.s, caller_entity_when_publish_disabled.len, &ruri_uri) < 0)) {
		LM_ERR("caller_entity_when_publish_disabled is not a valid SIP uri\n");
		return -1;
	}

	if ((callee_entity_when_publish_disabled.len > 0) &&
		(parse_uri(callee_entity_when_publish_disabled.s, callee_entity_when_publish_disabled.len, &ruri_uri) < 0)) {
		LM_ERR("callee_entity_when_publish_disabled is not a valid SIP uri\n");
		return -1;
	}

	bind_pua= (bind_pua_t)find_export("bind_pua", 1,0);
	if (!bind_pua)
	{
		LM_ERR("Can't bind pua\n");
		return -1;
	}

	if (bind_pua(&pua) < 0)
	{
		LM_ERR("Can't bind pua\n");
		return -1;
	}
	if(pua.send_publish == NULL)
	{
		LM_ERR("Could not import send_publish\n");
		return -1;
	}
	pua_send_publish= pua.send_publish;

	/* bind to the dialog API */
	if (load_dlg_api(&dlg_api)!=0) {
		LM_ERR("failed to find dialog API - is dialog module loaded?\n");
		return -1;
	}
	/* register dialog creation callback */
	if (dlg_api.register_dlgcb(NULL, DLGCB_CREATED, __dialog_created, NULL, NULL) != 0) {
		LM_ERR("cannot register callback for dialog creation\n");
		return -1;
	}
	/* register dialog loaded callback */
	if (dlg_api.register_dlgcb(NULL, DLGCB_LOADED, __dialog_loaded, NULL, NULL) != 0) {
		LM_ERR("cannot register callback for dialog loaded\n");
		return -1;
	}

	if(use_pubruri_avps) {
		LM_DBG("configured to use avps for uri values\n");
		if((pubruri_caller_avp==NULL || *pubruri_caller_avp==0)
				|| (pubruri_callee_avp==NULL || *pubruri_callee_avp==0)) {
			LM_ERR("pubruri_caller_avp and pubruri_callee_avp must be set,"
					" if use_pubruri_avps is enabled\n");
			return -1;
		}

		s.s = pubruri_caller_avp; s.len = strlen(s.s);
		if (pv_parse_spec(&s, &avp_spec)==0	|| avp_spec.type!=PVT_AVP) {
			LM_ERR("malformed or non AVP %s AVP definition\n", pubruri_caller_avp);
			return -1;
		}
		if(pv_get_avp_name(0, &avp_spec.pvp, &pubruri_caller_avp_name,
					&pubruri_caller_avp_type)!=0) {
			LM_ERR("[%s]- invalid AVP definition\n", pubruri_caller_avp);
			return -1;
		}

		s.s = pubruri_callee_avp; s.len = strlen(s.s);
		if (pv_parse_spec(&s, &avp_spec)==0	|| avp_spec.type!=PVT_AVP) {
			LM_ERR("malformed or non AVP %s AVP definition\n", pubruri_callee_avp);
			return -1;
		}
		if(pv_get_avp_name(0, &avp_spec.pvp, &pubruri_callee_avp_name,
					&pubruri_callee_avp_type)!=0) {
			LM_ERR("[%s]- invalid AVP definition\n", pubruri_callee_avp);
			return -1;
		}
	} else {
		LM_DBG("configured to use headers for uri values\n");
	}

	if(publish_dialog_req_within)
		dialog_event_types |= DLGCB_REQ_WITHIN;

	return 0;
}

/**
 * @brief Initialize module children
 */
static int child_init(int rank)
{
	if(sruid_init(&_puadi_sruid, (char)'-', "padi", SRUID_INC)<0) {
		return -1;
	}

	if(rank != PROC_MAIN) {
		return 0;
	}

	return 0;
}

void free_dlginfo_cell(void *param) {

	struct dlginfo_cell *cell = NULL;

	if(param==NULL)
		return;

	cell = param;
	free_str_list_all(cell->pubruris_caller);
	free_str_list_all(cell->pubruris_callee);

	/*if (cell->to_tag) {
		shm_free(cell->to_tag);
	}*/
	shm_free(param);
}


void free_str_list_all(struct str_list * del_current) {

	struct str_list* del_next;

	while(del_current) {

		del_next = del_current->next;
		shm_free(del_current);

		del_current=del_next;
	}

}
