/*
 * rls module - resource list server
 *
 * Copyright (C) 2007 Voice Sistem S.R.L.
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "../../core/ut.h"
#include "../../core/str.h"
#include "../../core/dprint.h"
#include "../../core/trim.h"
#include "../../core/data_lump_rpl.h"
#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_event.h"
#include "../../core/parser/parse_expires.h"
#include "../../core/parser/parse_cseq.h"
#include "../../core/parser/contact/parse_contact.h"
#include "../../core/parser/parse_rr.h"
#include "../../core/rand/kam_rand.h"
#include "../../modules/tm/dlg.h"
#include "../presence/utils_func.h"
#include "../presence/hash.h"
#include "../../core/hashes.h"
#include "rls.h"
#include "notify.h"
#include "utils.h"
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

static str *multipart_body = NULL;
static int multipart_body_size = 0;

typedef struct res_param
{
	struct uri_link **next;
} res_param_t;

typedef struct uri_link
{
	char *uri;
	struct uri_link *next;
} uri_link_t;

int resource_uri_col = 0, content_type_col, pres_state_col = 0,
	auth_state_col = 0, reason_col = 0;

xmlDocPtr constr_rlmi_doc(db1_res_t *result, str *rl_uri, int version,
		xmlNodePtr rl_node, char ***cid_array, str username, str domain);
void constr_multipart_body(const str *const content_type, const str *const body,
		str *cid, int boundary_len, char *boundary_string);

dlg_t *rls_notify_dlg(subs_t *subs);

void rls_notify_callback(struct cell *t, int type, struct tmcb_params *ps);

int parse_xcap_uri(char *uri, str *host, unsigned short *port, str *path);
int rls_get_resource_list(str *rl_uri, str *username, str *domain,
		xmlNodePtr *rl_node, xmlDocPtr *xmldoc);
int add_resource_to_list(char *uri, void *param);
int add_resource(char *uri, xmlNodePtr list_node, char *boundary_string,
		db1_res_t *result, int *len_est);

char *instance_id = "Scf8UhwQ";

int send_full_notify(
		subs_t *subs, xmlNodePtr rl_node, str *rl_uri, unsigned int hash_code)
{
	xmlDocPtr rlmi_body = NULL;
	xmlNodePtr list_node = NULL;
	db_key_t query_cols[1], update_cols[1], result_cols[5];
	db_val_t query_vals[1], update_vals[1];
	db1_res_t *result = NULL;
	int n_result_cols = 0;
	char *boundary_string;
	str rlsubs_did = {0, 0};
	str *rlmi_cont = NULL;
	uri_link_t *uri_list_head = NULL;
	int len_est;
	res_param_t param;
	int resource_added = 0; /* Flag to indicate that we have added
							 * at least one resource */
	multipart_body = NULL;
	db_query_f query_fn =
			rlpres_dbf.query_lock ? rlpres_dbf.query_lock : rlpres_dbf.query;

	LM_DBG("start\n");

	if(CONSTR_RLSUBS_DID(subs, &rlsubs_did) < 0) {
		LM_ERR("cannot build rls subs did\n");
		goto error;
	}

	query_cols[0] = &str_rlsubs_did_col;
	query_vals[0].type = DB1_STR;
	query_vals[0].nul = 0;
	query_vals[0].val.str_val = rlsubs_did;

	result_cols[resource_uri_col = n_result_cols++] = &str_resource_uri_col;
	result_cols[content_type_col = n_result_cols++] = &str_content_type_col;
	result_cols[pres_state_col = n_result_cols++] = &str_presence_state_col;
	result_cols[auth_state_col = n_result_cols++] = &str_auth_state_col;
	result_cols[reason_col = n_result_cols++] = &str_reason_col;

	update_cols[0] = &str_updated_col;
	update_vals[0].type = DB1_INT;
	update_vals[0].nul = 0;
	update_vals[0].val.int_val = NO_UPDATE_TYPE;

	if(rlpres_dbf.use_table(rlpres_db, &rlpres_table) < 0) {
		LM_ERR("in use_table\n");
		goto error;
	}

	if(dbmode == RLS_DB_ONLY && rlpres_dbf.start_transaction) {
		if(rlpres_dbf.start_transaction(rlpres_db, DB_LOCKING_WRITE) < 0) {
			LM_ERR("in start_transaction\n");
			goto error;
		}
	}

	if(query_fn(rlpres_db, query_cols, 0, query_vals, result_cols, 1,
			   n_result_cols, NULL, &result)
			< 0) {
		LM_ERR("in sql query\n");
		goto error;
	}
	if(result == NULL) {
		LM_ERR("bad result\n");
		goto error;
	}

	if(result->n > 0) {
		if(rlpres_dbf.update(rlpres_db, query_cols, 0, query_vals, update_cols,
				   update_vals, 1, 1)
				< 0) {
			LM_ERR("in sql update\n");
			goto error;
		}
	}

	if(dbmode == RLS_DB_ONLY && rlpres_dbf.end_transaction) {
		if(rlpres_dbf.end_transaction(rlpres_db) < 0) {
			LM_ERR("in end_transaction\n");
			goto error;
		}
	}

	/* Allocate an initial buffer for the multipart body.
	 * This buffer will be reallocated if necessary */
	multipart_body = (str *)pkg_malloc(sizeof(str));
	if(multipart_body == NULL) {
		ERR_MEM(PKG_MEM_STR);
	}

	multipart_body_size = BUF_REALLOC_SIZE;
	multipart_body->s = (char *)pkg_malloc(multipart_body_size);

	if(multipart_body->s == NULL) {
		ERR_MEM(PKG_MEM_STR);
	}

	multipart_body->len = 0;

	/* Create an empty rlmi document */
	len_est = create_empty_rlmi_doc(
			&rlmi_body, &list_node, rl_uri, subs->version, 1);
	xmlDocSetRootElement(rlmi_body, list_node);

	/* Find all the uri's to which we are subscribed */
	param.next = &uri_list_head;
	if(process_list_and_exec(rl_node, subs->watcher_user, subs->watcher_domain,
			   add_resource_to_list, (void *)(&param))
			< 0) {
		LM_ERR("in process_list_and_exec function\n");
		goto error;
	}

	boundary_string = generate_string(BOUNDARY_STRING_LEN);

	while(uri_list_head) {
		uri_link_t *last = uri_list_head;
		if(add_resource(uri_list_head->uri, list_node, boundary_string, result,
				   &len_est)
				> 0) {
			if(resource_added == 0) {
				/* We have exceeded our length estimate without adding any resource.
				 * We cannot send this resource, move on. */
				LM_ERR("Failed to add a single resource %d vs %d\n", len_est,
						rls_max_notify_body_len);
				uri_list_head = uri_list_head->next;
				if(last->uri)
					pkg_free(last->uri);
				pkg_free(last);
			} else {
				LM_DBG("send_full_notify estimate exceeded %d vs %d\n", len_est,
						rls_max_notify_body_len);
				/* If add_resource returns > 0 the resource did not fit
				 * in our size limit */
				rlmi_cont = (str *)pkg_malloc(sizeof(str));
				if(rlmi_cont == NULL) {
					ERR_MEM(PKG_MEM_STR);
				}
				/* Where we are worried about length we won't use padding */
				xmlDocDumpFormatMemory(rlmi_body,
						(xmlChar **)(void *)&rlmi_cont->s, &rlmi_cont->len, 0);
				xmlFreeDoc(rlmi_body);

				if(agg_body_sendn_update(rl_uri, boundary_string, rlmi_cont,
						   multipart_body, subs, hash_code)
						< 0) {
					LM_ERR("in function agg_body_sendn_update\n");
					goto error;
				}

				/* Create a new rlmi body, but not a full_state one this time */
				len_est = create_empty_rlmi_doc(
						&rlmi_body, &list_node, rl_uri, subs->version, 0);
				xmlDocSetRootElement(rlmi_body, list_node);
				multipart_body->len = 0;
				resource_added = 0;
			}
		} else {
			resource_added = 1;
			uri_list_head = uri_list_head->next;
			if(last->uri)
				pkg_free(last->uri);
			pkg_free(last);
		}
	}

	rlmi_cont = (str *)pkg_malloc(sizeof(str));
	if(rlmi_cont == NULL) {
		ERR_MEM(PKG_MEM_STR);
	}
	xmlDocDumpFormatMemory(rlmi_body, (xmlChar **)(void *)&rlmi_cont->s,
			&rlmi_cont->len, (rls_max_notify_body_len == 0));
	xmlFreeDoc(rlmi_body);

	if(agg_body_sendn_update(rl_uri, boundary_string, rlmi_cont, multipart_body,
			   subs, hash_code)
			< 0) {
		LM_ERR("in function agg_body_sendn_update\n");
		goto error;
	}

	xmlFree(rlmi_cont->s);
	pkg_free(rlmi_cont);

	if(multipart_body) {
		if(multipart_body->s)
			pkg_free(multipart_body->s);
		pkg_free(multipart_body);
	}
	multipart_body_size = 0;
	pkg_free(rlsubs_did.s);
	rlpres_dbf.free_result(rlpres_db, result);

	return 0;
error:
	if(rlmi_cont) {
		if(rlmi_cont->s)
			xmlFree(rlmi_cont->s);
		pkg_free(rlmi_cont);
	}
	if(multipart_body) {
		if(multipart_body->s)
			pkg_free(multipart_body->s);
		pkg_free(multipart_body);
	}
	multipart_body_size = 0;

	if(result)
		rlpres_dbf.free_result(rlpres_db, result);
	if(rlsubs_did.s)
		pkg_free(rlsubs_did.s);

	if(dbmode == RLS_DB_ONLY && rlpres_dbf.abort_transaction) {
		if(rlpres_dbf.abort_transaction(rlpres_db) < 0)
			LM_ERR("in abort_transaction");
	}
	return -1;
}

int agg_body_sendn_update(str *rl_uri, char *boundary_string, str *rlmi_body,
		str *multipart_body, subs_t *subs, unsigned int hash_code)
{
	char *cid;
	int len;
	str body = {0, 0};
	int init_len;

	cid = generate_cid(rl_uri->s, rl_uri->len);

	len = 2 * strlen(boundary_string) + 4 + 102 + strlen(cid) + 2
		  + rlmi_body->len + 50;
	if(multipart_body)
		len += multipart_body->len;

	init_len = len;

	body.s = (char *)pkg_malloc((len + 1) * sizeof(char));
	if(body.s == NULL) {
		ERR_MEM(PKG_MEM_STR);
	}
	len = sprintf(body.s, "--%s\r\n", boundary_string);
	len += sprintf(body.s + len, "Content-Transfer-Encoding: binary\r\n");
	len += sprintf(body.s + len, "Content-ID: <%s>\r\n", cid);
	len += sprintf(body.s + len,
			"Content-Type: application/rlmi+xml;charset=\"UTF-8\"\r\n");
	len += sprintf(body.s + len, "\r\n"); /*blank line*/
	memcpy(body.s + len, rlmi_body->s, rlmi_body->len);
	len += rlmi_body->len;
	len += sprintf(body.s + len, "\r\n"); /*blank line*/

	if(multipart_body) {
		memcpy(body.s + len, multipart_body->s, multipart_body->len);
		len += multipart_body->len;
	}
	len += sprintf(body.s + len, "--%s--\r\n", boundary_string);

	if(init_len < len) {
		LM_ERR("buffer size overflow init_size= %d\tlen= %d\n", init_len, len);
		goto error;
	}
	body.s[len] = '\0';
	body.len = len;

	/* send Notify */
	if(rls_send_notify(subs, &body, cid, boundary_string) < 0) {
		LM_ERR("when sending Notify\n");
		goto error;
	}
	/* update local_cseq in cache list watchers table */
	pkg_free(body.s);
	body.s = NULL;

	if(dbmode == RLS_DB_ONLY) {
		if(update_dialog_notify_rlsdb(subs) < 0) {
			LM_ERR("updating DB\n");
			goto error;
		}
	} else {
		if(pres_update_shtable(rls_table, hash_code, subs, LOCAL_TYPE) < 0) {
			LM_ERR("updating in hash table\n");
			goto error;
		}
	}

	return 0;

error:
	if(body.s)
		pkg_free(body.s);

	return -1;
}


int add_resource_instance(char *uri, xmlNodePtr resource_node,
		db1_res_t *result, char *boundary_string, int *len_est)
{
	xmlNodePtr instance_node = NULL;
	db_row_t *row;
	db_val_t *row_vals;
	int i, cmp_code;
	char *auth_state = NULL;
	int auth_state_flag;
	int boundary_len = strlen(boundary_string);
	str cid;
	str content_type = {0, 0};
	str body = {0, 0};

	for(i = 0; i < result->n; i++) {
		row = &result->rows[i];
		row_vals = ROW_VALUES(row);

		cmp_code = strncmp(
				row_vals[resource_uri_col].val.string_val, uri, strlen(uri));
		if(cmp_code > 0)
			break;

		if(cmp_code == 0) {
			auth_state_flag = row_vals[auth_state_col].val.int_val;
			auth_state = get_auth_string(auth_state_flag);
			if(auth_state == NULL) {
				LM_ERR("bad authorization status flag\n");
				goto error;
			}
			*len_est +=
					strlen(auth_state)
					+ 38; /* <instance id="12345678" state="[auth_state]" />r/n */

			if(auth_state_flag & ACTIVE_STATE) {
				cid.s = generate_cid(uri, strlen(uri));
				cid.len = strlen(cid.s);
				body.s = (char *)row_vals[pres_state_col].val.string_val;
				body.len = strlen(body.s);
				trim(&body);

				*len_est += cid.len + 8; /* cid="[cid]" */
				content_type.s =
						(char *)row_vals[content_type_col].val.string_val;
				content_type.len = strlen(content_type.s);
				*len_est += 4 + boundary_len + 35 + 16 + cid.len + 18
							+ content_type.len + 4 + body.len + 8;
			} else if(auth_state_flag & TERMINATED_STATE) {
				*len_est += strlen(row_vals[resource_uri_col].val.string_val)
							+ 10; /* reason="[resaon]" */
			}
			if(rls_max_notify_body_len > 0
					&& *len_est > rls_max_notify_body_len) {
				/* We have a limit on body length set, and we were about to exceed it */
				return *len_est;
			}
			instance_node =
					xmlNewChild(resource_node, NULL, BAD_CAST "instance", NULL);
			if(instance_node == NULL) {
				LM_ERR("while adding instance child\n");
				goto error;
			}

			/* OK, we are happy this will fit */
			/* Instance ID should be unique for each instance node
			 * within a resource node.  The same instance ID can be
			 * used in different resource nodes.  Instance ID needs
			 * to remain the same for each resource instance in
			 * future updates.  We can just use a common string
			 * here because you will only get multiple instances
			 * for a resource when the back-end SUBSCRIBE is forked
			 * and pua does not support this.  If/when pua supports
			 * forking of the SUBSCRIBEs it sends this will need to
			 * be fixed properly. */
			xmlNewProp(instance_node, BAD_CAST "id", BAD_CAST instance_id);
			xmlNewProp(instance_node, BAD_CAST "state", BAD_CAST auth_state);

			if(auth_state_flag & ACTIVE_STATE) {
				constr_multipart_body(&content_type, &body, &cid, boundary_len,
						boundary_string);
				xmlNewProp(instance_node, BAD_CAST "cid", BAD_CAST cid.s);
			} else if(auth_state_flag & TERMINATED_STATE) {
				xmlNewProp(instance_node, BAD_CAST "reason",
						BAD_CAST row_vals[reason_col].val.string_val);
			}
		}
	}

	/* if record not found should not add an instance node */
	return 0;
error:
	return -1;
}

int add_resource(char *uri, xmlNodePtr list_node, char *boundary_string,
		db1_res_t *result, int *len_est)
{
	xmlNodePtr resource_node = NULL;
	int res;

	if(rls_max_notify_body_len > 0) {
		*len_est +=
				strlen(uri) + 35; /* <resource uri="[uri]"></resource>/r/n */
		if(*len_est > rls_max_notify_body_len) {
			return *len_est;
		}
	}
	resource_node = xmlNewChild(list_node, NULL, BAD_CAST "resource", NULL);
	if(resource_node == NULL) {
		goto error;
	}
	xmlNewProp(resource_node, BAD_CAST "uri", BAD_CAST uri);

	res = add_resource_instance(
			uri, resource_node, result, boundary_string, len_est);
	if(res < 0) {
		LM_ERR("while adding resource instance node\n");
		goto error;
	}

	return res;
error:
	return -1;
}

int add_resource_to_list(char *uri, void *param)
{
	struct uri_link **next = ((res_param_t *)param)->next;
	*next = pkg_malloc(sizeof(uri_link_t));
	if(*next == NULL) {
		LM_ERR("while creating linked list element\n");
		goto error;
	}

	(*next)->next = NULL;
	(*next)->uri = pkg_malloc(strlen(uri) + 1);
	if((*next)->uri == NULL) {
		LM_ERR("while creating uri store\n");
		pkg_free(*next);
		*next = NULL;
		goto error;
	}
	strcpy((*next)->uri, uri);
	((res_param_t *)param)->next = &(*next)->next;

	return 0;
error:
	return -1;
}

int create_empty_rlmi_doc(xmlDocPtr *rlmi_doc, xmlNodePtr *list_node, str *uri,
		int version, int full_state)
{
	/* length is a pessimitic estimate of the size of an empty document
	 * We calculate it once for performance reasons.
	 * We add in the uri length each time as this varies, and it is cheap to add */
	static int length = 0;
	char *rl_uri = NULL;
	int len;

	/* make new rlmi and multipart documents */
	*rlmi_doc = xmlNewDoc(BAD_CAST "1.0");
	if(*rlmi_doc == NULL) {
		LM_ERR("when creating new xml doc\n");
		return 0;
	}
	*list_node = xmlNewNode(NULL, BAD_CAST "list");
	if(*list_node == NULL) {
		LM_ERR("while creating new xml node\n");
		return 0;
	}
	rl_uri = (char *)pkg_malloc((uri->len + 1) * sizeof(char));
	if(rl_uri == NULL) {
		ERR_MEM(PKG_MEM_STR);
	}
	memcpy(rl_uri, uri->s, uri->len);
	rl_uri[uri->len] = '\0';

	xmlNewProp(*list_node, BAD_CAST "uri", BAD_CAST rl_uri);
	xmlNewProp(*list_node, BAD_CAST "xmlns",
			BAD_CAST "urn:ietf:params:xml:ns:rlmi");
	xmlNewProp(*list_node, BAD_CAST "version", BAD_CAST int2str(version, &len));
	if(full_state)
		xmlNewProp(*list_node, BAD_CAST "fullState", BAD_CAST "true");
	else
		xmlNewProp(*list_node, BAD_CAST "fullState", BAD_CAST "false");

	xmlDocSetRootElement(*rlmi_doc, *list_node);
	pkg_free(rl_uri); /* xmlNewProp takes a copy, so we can free this now */

	if(length == 0) {
		/* We haven't found out how big an empty doc is
		 * Let's find out now ! */
		xmlChar *dumped_document;
		/* Where we are worried about length we won't use padding */
		xmlDocDumpFormatMemory(*rlmi_doc, &dumped_document, &length, 0);
		xmlFree(dumped_document);
		length -= uri->len; /* The uri varies, so we will add it each time */
	}
	return length + uri->len;
error:
	return 0;
}


void constr_multipart_body(const str *const content_type, const str *const body,
		str *cid, int boundary_len, char *boundary_string)
{
	char *buf = multipart_body->s;
	int length = multipart_body->len;
	int chunk_len;

	LM_DBG("start\n");

	chunk_len = 4 + boundary_len + 35 + 16 + cid->len + 18 + content_type->len
				+ 4 + body->len + 8;
	while(length + chunk_len >= multipart_body_size) {
		multipart_body_size += BUF_REALLOC_SIZE;
		multipart_body->s =
				(char *)pkg_reallocxf(multipart_body->s, multipart_body_size);
		if(multipart_body->s == NULL) {
			ERR_MEM("constr_multipart_body");
		}
	}
	buf = multipart_body->s;

	length +=
			sprintf(buf + length, "--%.*s\r\n", boundary_len, boundary_string);
	length += sprintf(buf + length, "Content-Transfer-Encoding: binary\r\n");
	length += sprintf(buf + length, "Content-ID: <%.*s>\r\n", cid->len, cid->s);
	length += sprintf(buf + length, "Content-Type: %.*s\r\n\r\n",
			content_type->len, content_type->s);
	length += sprintf(buf + length, "%.*s\r\n\r\n", body->len, body->s);

	multipart_body->len = length;

error:

	return;
}

str *rls_notify_extra_hdr(subs_t *subs, char *start_cid, char *boundary_string)
{
	str *str_hdr = NULL;
	int len = 0, expires;

	str_hdr = (str *)pkg_malloc(sizeof(str));
	if(str_hdr == NULL) {
		ERR_MEM(PKG_MEM_STR);
	}
	memset(str_hdr, 0, sizeof(str));

	str_hdr->s = (char *)pkg_malloc(RLS_HDR_LEN * sizeof(char));
	if(str_hdr->s == NULL) {
		ERR_MEM(PKG_MEM_STR);
	}
	memcpy(str_hdr->s, "Max-Forwards: ", 14);
	str_hdr->len = 14;
	len = sprintf(str_hdr->s + str_hdr->len, "%d", MAX_FORWARD);
	if(len <= 0) {
		LM_ERR("while printing in string\n");
		goto error;
	}
	str_hdr->len += len;
	memcpy(str_hdr->s + str_hdr->len, CRLF, CRLF_LEN);
	str_hdr->len += CRLF_LEN;

	memcpy(str_hdr->s + str_hdr->len, "Event: ", 7);
	str_hdr->len += 7;
	memcpy(str_hdr->s + str_hdr->len, subs->event->name.s,
			subs->event->name.len);
	str_hdr->len += subs->event->name.len;
	memcpy(str_hdr->s + str_hdr->len, CRLF, CRLF_LEN);
	str_hdr->len += CRLF_LEN;

	memcpy(str_hdr->s + str_hdr->len, "Contact: <", 10);
	str_hdr->len += 10;
	memcpy(str_hdr->s + str_hdr->len, subs->local_contact.s,
			subs->local_contact.len);
	str_hdr->len += subs->local_contact.len;
	memcpy(str_hdr->s + str_hdr->len, ">", 1);
	str_hdr->len += 1;
	memcpy(str_hdr->s + str_hdr->len, CRLF, CRLF_LEN);
	str_hdr->len += CRLF_LEN;

	expires = subs->expires;

	if(expires > 0)
		str_hdr->len += sprintf(str_hdr->s + str_hdr->len,
				"Subscription-State: active;expires=%d\r\n", expires);
	else
		str_hdr->len += sprintf(str_hdr->s + str_hdr->len,
				"Subscription-State: terminated;reason=timeout\r\n");

	str_hdr->len +=
			sprintf(str_hdr->s + str_hdr->len, "Require: eventlist\r\n");

	if(start_cid && boundary_string) {
		str_hdr->len += sprintf(str_hdr->s + str_hdr->len,
				"Content-Type: "
				"multipart/related;type=\"application/rlmi+xml\"");
		str_hdr->len += sprintf(str_hdr->s + str_hdr->len,
				";start=\"<%s>\";boundary=\"%s\"\r\n", start_cid,
				boundary_string);
	}
	if(str_hdr->len > RLS_HDR_LEN) {
		LM_ERR("buffer size overflow\n");
		goto error;
	}
	str_hdr->s[str_hdr->len] = '\0';

	return str_hdr;

error:
	if(str_hdr) {
		if(str_hdr->s)
			pkg_free(str_hdr->s);
		pkg_free(str_hdr);
	}
	return NULL;
}

void rls_free_td(dlg_t *td)
{
	if(td) {
		if(td->loc_uri.s)
			pkg_free(td->loc_uri.s);

		if(td->rem_uri.s)
			pkg_free(td->rem_uri.s);

		if(td->route_set)
			free_rr(&td->route_set);

		pkg_free(td);
	}
}

int rls_send_notify(
		subs_t *subs, str *body, char *start_cid, char *boundary_string)
{
	dlg_t *td = NULL;
	str met = {"NOTIFY", 6};
	str *str_hdr = NULL;
	dialog_id_t *cb_param = NULL;
	int size;
	int rt;
	uac_req_t uac_r;

	LM_DBG("start\n");
	td = rls_notify_dlg(subs);
	if(td == NULL) {
		LM_ERR("while building dlg_t structure\n");
		goto error;
	}

	LM_DBG("constructed dlg_t struct\n");
	size = sizeof(dialog_id_t)
		   + (subs->to_tag.len + subs->callid.len + subs->from_tag.len)
					 * sizeof(char);

	cb_param = (dialog_id_t *)shm_malloc(size);
	if(cb_param == NULL) {
		ERR_MEM(SHARE_MEM);
	}
	size = sizeof(dialog_id_t);

	cb_param->callid.s = (char *)cb_param + size;
	memcpy(cb_param->callid.s, subs->callid.s, subs->callid.len);
	cb_param->callid.len = subs->callid.len;
	size += subs->callid.len;

	cb_param->to_tag.s = (char *)cb_param + size;
	memcpy(cb_param->to_tag.s, subs->to_tag.s, subs->to_tag.len);
	cb_param->to_tag.len = subs->to_tag.len;
	size += subs->to_tag.len;

	cb_param->from_tag.s = (char *)cb_param + size;
	memcpy(cb_param->from_tag.s, subs->from_tag.s, subs->from_tag.len);
	cb_param->from_tag.len = subs->from_tag.len;

	LM_DBG("constructed cb_param\n");

	str_hdr = rls_notify_extra_hdr(subs, start_cid, boundary_string);
	if(str_hdr == NULL || str_hdr->s == NULL) {
		LM_ERR("while building extra headers\n");
		goto error;
	}
	LM_DBG("str_hdr= %.*s\n", str_hdr->len, str_hdr->s);

	set_uac_req(&uac_r, &met, str_hdr, body, td, TMCB_LOCAL_COMPLETED,
			rls_notify_callback, (void *)cb_param);

	rt = tmb.t_request_within(&uac_r);
	if(rt < 0) {
		LM_ERR("in function tmb.t_request_within\n");
		goto error;
	}

	pkg_free(str_hdr->s);
	pkg_free(str_hdr);
	rls_free_td(td);
	return 0;

error:
	if(td)
		rls_free_td(td);
	if(cb_param)
		shm_free(cb_param);
	if(str_hdr) {
		if(str_hdr->s)
			pkg_free(str_hdr->s);
		pkg_free(str_hdr);
	}
	return -1;
}

dlg_t *rls_notify_dlg(subs_t *subs)
{
	dlg_t *td = NULL;

	td = (dlg_t *)pkg_malloc(sizeof(dlg_t));
	if(td == NULL) {
		ERR_MEM(PKG_MEM_STR);
	}

	memset(td, 0, sizeof(dlg_t));
	td->loc_seq.value = subs->local_cseq;
	td->loc_seq.is_set = 1;

	td->id.call_id = subs->callid;
	td->id.rem_tag = subs->from_tag;
	td->id.loc_tag = subs->to_tag;
	if(uandd_to_uri(subs->to_user, subs->to_domain, &td->loc_uri) < 0) {
		LM_ERR("while constructing uri from user and domain\n");
		goto error;
	}

	if(uandd_to_uri(subs->from_user, subs->from_domain, &td->rem_uri) < 0) {
		LM_ERR("while constructing uri from user and domain\n");
		goto error;
	}

	if(subs->contact.len == 0 || subs->contact.s == NULL) {
		LM_DBG("BAD BAD contact NULL\n");
		td->rem_target = td->rem_uri;
	} else
		td->rem_target = subs->contact;

	if(subs->record_route.s && subs->record_route.len) {
		if(parse_rr_body(
				   subs->record_route.s, subs->record_route.len, &td->route_set)
				< 0) {
			LM_ERR("in function parse_rr_body\n");
			goto error;
		}
	}
	td->state = DLG_CONFIRMED;

	if(subs->sockinfo_str.len) {
		int port, proto;
		str host;
		char *tmp;
		if((tmp = as_asciiz(&subs->sockinfo_str)) == NULL) {
			LM_ERR("no pkg mem left\n");
			goto error;
		}
		if(parse_phostport(tmp, &host.s, &host.len, &port, &proto)) {
			LM_ERR("bad sockinfo string\n");
			pkg_free(tmp);
			goto error;
		}
		pkg_free(tmp);
		td->send_sock = grep_sock_info(
				&host, (unsigned short)port, (unsigned short)proto);
	}

	return td;

error:
	if(td)
		rls_free_td(td);

	return NULL;
}
void rls_notify_callback(struct cell *t, int type, struct tmcb_params *ps)
{
	if(ps->param == NULL || *ps->param == NULL
			|| ((dialog_id_t *)(*ps->param)) == NULL) {
		LM_DBG("message id not received\n");
		return;
	}

	LM_DBG("completed with status %d [to_tag:"
		   "%.*s]\n",
			ps->code, ((dialog_id_t *)(*ps->param))->to_tag.len,
			((dialog_id_t *)(*ps->param))->to_tag.s);

	if(ps->code >= 300) {
		db_key_t db_keys[2];
		db_val_t db_vals[2];
		unsigned int hash_code;
		subs_t subs;

		memset(&subs, 0, sizeof(subs_t));

		subs.to_tag = ((dialog_id_t *)(*ps->param))->to_tag;
		subs.from_tag = ((dialog_id_t *)(*ps->param))->from_tag;
		subs.callid = ((dialog_id_t *)(*ps->param))->callid;

		if(dbmode != RLS_DB_ONLY) {
			/* delete from cache table */
			hash_code = core_hash(&subs.callid, &subs.to_tag, hash_size);

			if(pres_delete_shtable(rls_table, hash_code, &subs) < 0) {
				LM_ERR("record not found in hash table\n");
			}

			/* delete from database table */
			if(rls_dbf.use_table(rls_db, &rlsubs_table) < 0) {
				LM_ERR("in use_table\n");
				goto done;
			}

			db_keys[0] = &str_to_tag_col;
			db_vals[0].type = DB1_STR;
			db_vals[0].nul = 0;
			db_vals[0].val.str_val = subs.to_tag;

			db_keys[1] = &str_callid_col;
			db_vals[1].type = DB1_STR;
			db_vals[1].nul = 0;
			db_vals[1].val.str_val = subs.callid;


			if(rls_dbf.delete(rls_db, db_keys, 0, db_vals, 2) < 0)
				LM_ERR("cleaning expired messages\n");
		} else {
			if(delete_rlsdb(&subs.callid, &subs.to_tag, NULL) < 0) {
				LM_ERR("unable to delete record from DB\n");
			}
		}
	}

done:
	if(*ps->param != NULL)
		shm_free(*ps->param);
	return;
}

int process_list_and_exec(xmlNodePtr list_node, str username, str domain,
		list_func_t function, void *param)
{
	xmlNodePtr node;
	str uri;
	int res = 0;

	for(node = list_node->children; node; node = node->next) {
		if(xmlStrcasecmp(node->name, (unsigned char *)"resource-list") == 0) {
			str hostname, rl_uri;
			unsigned short port = 0;
			xmlNodePtr rl_node = NULL;
			xmlDocPtr rl_doc = NULL;
			str unescaped_uri;
			char buf[MAX_URI_SIZE];


			uri.s = XMLNodeGetNodeContentByName(node, "resource-list", NULL);
			if(uri.s == NULL) {
				LM_ERR("when extracting URI from node\n");
				return -1;
			}
			uri.len = strlen(uri.s);
			if(uri.len > MAX_URI_SIZE - 1) {
				LM_ERR("XCAP URI is too long\n");
				xmlFree(uri.s);
				return -1;
			}
			LM_DBG("got resource-list uri <%.*s>\n", uri.len, uri.s);

			unescaped_uri.s = buf;
			unescaped_uri.len = 0;
			if(un_escape(&uri, &unescaped_uri) < 0) {
				LM_ERR("Error un-escaping XCAP URI\n");
				xmlFree(uri.s);
				return -1;
			}
			unescaped_uri.s[unescaped_uri.len] = 0;
			LM_DBG("got unescaped uri <%s>\n", unescaped_uri.s);

			if(parse_xcap_uri(unescaped_uri.s, &hostname, &port, &rl_uri) > 0) {
				if(rls_integrated_xcap_server == 1
						&& (hostname.len == 0
								|| check_self(&hostname, 0, PROTO_NONE) == 1)) {
					LM_DBG("fetching local <resource-list - %.*s>\n", uri.len,
							uri.s);
					if(rls_get_resource_list(
							   &rl_uri, &username, &domain, &rl_node, &rl_doc)
							> 0) {
						LM_DBG("calling myself for rl_node\n");
						res = process_list_and_exec(
								rl_node, username, domain, function, param);
						xmlFree(uri.s);
						xmlFreeDoc(rl_doc);
					} else {
						LM_ERR("<resource-list - %.*s> not found\n", uri.len,
								uri.s);
						xmlFree(uri.s);
						return -1;
					}

				} else {
					LM_ERR("<resource-list - %.*s> is not local - unsupported "
						   "at this time\n",
							uri.len, uri.s);
					xmlFree(uri.s);
					return -1;
				}
			} else {
				LM_ERR("unable to parse URI for <resource-list - %.*s>\n",
						uri.len, uri.s);
				xmlFree(uri.s);
				return -1;
			}
		} else if(xmlStrcasecmp(node->name, (unsigned char *)"entry") == 0) {
			uri.s = XMLNodeGetAttrContentByName(node, "uri");
			if(uri.s == NULL) {
				LM_ERR("when extracting entry uri attribute\n");
				return -1;
			}
			LM_DBG("uri= %s\n", uri.s);
			if(function(uri.s, param) < 0) {
				LM_ERR("in function given as a parameter\n");
				xmlFree(uri.s);
				return -1;
			}
			xmlFree(uri.s);
		} else if(xmlStrcasecmp(node->name, (unsigned char *)"list") == 0)
			res = process_list_and_exec(
					node, username, domain, function, param);
	}
	return res;
}

char *generate_string(int length)
{
	static char buf[128];
	int r, i;

	if(length >= 128) {
		LM_ERR("requested length exceeds buffer size\n");
		return NULL;
	}

	for(i = 0; i < length; i++) {
		r = kam_rand() % ('z' - 'A') + 'A';
		if(r > 'Z' && r < 'a')
			r = '0' + (r - 'Z');

		sprintf(buf + i, "%c", r);
	}
	buf[length] = '\0';

	return buf;
}

char *generate_cid(char *uri, int uri_len)
{
	static char cid[512];
	int len;

	len = sprintf(cid, "%d.%.*s.%d", (int)time(NULL), uri_len, uri, kam_rand());
	cid[len] = '\0';

	return cid;
}

char *get_auth_string(int flag)
{
	switch(flag) {
		case ACTIVE_STATE:
			return "active";
		case PENDING_STATE:
			return "pending";
		case TERMINATED_STATE:
			return "terminated";
	}
	return NULL;
}

#define HTTP_PREFIX "http://"
#define HTTP_PREFIX_LEN 7
#define HTTP_PORT_DEFAULT 80
#define HTTPS_PREFIX "https://"
#define HTTPS_PREFIX_LEN 8
#define HTTPS_PORT_DEFAULT 443
#define LOCAL_PREFIX "/"
#define LOCAL_PREFIX_LEN 1
#define CHAR_PORT_LEN 5

int parse_xcap_uri(char *uri, str *host, unsigned short *port, str *path)
{
	host->s = NULL;
	host->len = 0;
	*port = 0;
	path->s = NULL;
	path->len = 0;

	if(strncmp(uri, HTTP_PREFIX, HTTP_PREFIX_LEN) == 0) {
		host->s = &uri[HTTP_PREFIX_LEN];
		*port = HTTP_PORT_DEFAULT;
		LM_DBG("resource list is on http server\n");
	} else if(strncmp(uri, HTTPS_PREFIX, HTTPS_PREFIX_LEN) == 0) {
		host->s = &uri[HTTPS_PREFIX_LEN];
		*port = HTTPS_PORT_DEFAULT;
		LM_DBG("resource list is on https server\n");
	} else if(strncmp(uri, LOCAL_PREFIX, LOCAL_PREFIX_LEN) == 0) {
		path->s = &uri[0];
		LM_DBG("resource list is local\n");
	} else {
		LM_ERR("resource list is unidentifiable\n");
		return -1;
	}

	if(host->s != NULL) {
		while(host->s[host->len] != '\0' && host->s[host->len] != ':'
				&& host->s[host->len] != '/')
			host->len++;

		if(host->s[host->len] == ':') {
			char char_port[CHAR_PORT_LEN + 1];
			unsigned cur_pos = host->len + 1;
			memset(char_port, '\0', CHAR_PORT_LEN + 1);

			while(host->s[cur_pos] != '\0' && host->s[cur_pos] != '/')
				cur_pos++;
			strncpy(char_port, &host->s[host->len + 1],
					MIN(cur_pos - host->len - 1, CHAR_PORT_LEN));
			*port = atoi(char_port);

			path->s = &host->s[cur_pos];
		} else {
			path->s = &host->s[host->len];
		}
	}

	while(path->s != NULL && path->s[path->len] != '\0')
		path->len++;

	return 1;
}

#define MAX_PATH_LEN 127
int rls_get_resource_list(str *rl_uri, str *username, str *domain,
		xmlNodePtr *rl_node, xmlDocPtr *xmldoc)
{
	db_key_t query_cols[4];
	db_val_t query_vals[4];
	int n_query_cols = 0;
	db_key_t result_cols[1];
	int n_result_cols = 0;
	db1_res_t *result = 0;
	db_row_t *row;
	db_val_t *row_vals;
	int xcap_col;
	str body;
	int checked = 0;
	str root, path = {0, 0};
	char path_str[MAX_PATH_LEN + 1];
	xmlXPathContextPtr xpathCtx = NULL;
	xmlXPathObjectPtr xpathObj = NULL;


	if(rl_uri == NULL || username == NULL || domain == NULL) {
		LM_ERR("invalid parameters\n");
		return -1;
	}

	LM_DBG("rl_uri: %.*s", rl_uri->len, rl_uri->s);

	root.s = rl_uri->s;
	root.len = rl_uri->len;
	while(checked < rl_uri->len) {
		if(checked < rl_uri->len - 3
				&& strncmp(rl_uri->s + checked, "/~~", 3) == 0) {
			root.len = checked;
			checked += 3;
			break;
		}
		checked++;
	}
	LM_DBG("doc: %.*s", root.len, root.s);

	memset(path_str, '\0', MAX_PATH_LEN + 1);
	path.s = path_str;
	path.len = 0;
	while(checked < rl_uri->len && path.len <= MAX_PATH_LEN) {
		if(rl_uri->s[checked] == '/') {
			strcat(path.s, "/xmlns:");
			path.len += 7;
			checked++;
		} else {
			path.s[path.len++] = rl_uri->s[checked];
			checked++;
		}
	}
	LM_DBG("path: %.*s", path.len, path.s);

	query_cols[n_query_cols] = &str_username_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = *username;
	n_query_cols++;

	query_cols[n_query_cols] = &str_domain_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = *domain;
	n_query_cols++;

	query_cols[n_query_cols] = &str_doc_type_col;
	query_vals[n_query_cols].type = DB1_INT;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.int_val = RESOURCE_LIST;
	n_query_cols++;

	query_cols[n_query_cols] = &str_doc_uri_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = root;
	n_query_cols++;

	if(rls_xcap_dbf.use_table(rls_xcap_db, &rls_xcap_table) < 0) {
		LM_ERR("in use_table-[table]=%.*s\n", rls_xcap_table.len,
				rls_xcap_table.s);
		return -1;
	}

	result_cols[xcap_col = n_result_cols++] = &str_doc_col;

	if(rls_xcap_dbf.query(rls_xcap_db, query_cols, 0, query_vals, result_cols,
			   n_query_cols, n_result_cols, 0, &result)
			< 0) {
		LM_ERR("failed querying table xcap for document: %.*s\n", root.len,
				root.s);
		if(result)
			rls_xcap_dbf.free_result(rls_xcap_db, result);
		return -1;
	}

	if(result == NULL) {
		LM_ERR("bad result\n");
		return -1;
	}

	if(result->n <= 0) {
		LM_DBG("No rl document found\n");
		rls_xcap_dbf.free_result(rls_xcap_db, result);
		return -1;
	}

	row = &result->rows[0];
	row_vals = ROW_VALUES(row);

	body.s = (char *)row_vals[xcap_col].val.string_val;
	if(body.s == NULL) {
		LM_ERR("xcap doc is null\n");
		goto error;
	}
	body.len = strlen(body.s);
	if(body.len == 0) {
		LM_ERR("xcap doc is empty\n");
		goto error;
	}

	LM_DBG("rl document:\n%.*s", body.len, body.s);
	*xmldoc = xmlParseMemory(body.s, body.len);
	if(*xmldoc == NULL) {
		LM_ERR("while parsing XML memory. len = %d\n", body.len);
		goto error;
	}

	if(path.len == 0) {
		/* No path specified - use all resource-lists. */
		*rl_node = XMLDocGetNodeByName(*xmldoc, "resource-lists", NULL);
		if(*rl_node == NULL) {
			LM_ERR("no resource-lists node in XML document\n");
			goto error;
		}
	} else if(path.s != NULL) {
		xpathCtx = xmlXPathNewContext(*xmldoc);
		if(xpathCtx == NULL) {
			LM_ERR("unable to create new XPath context");
			goto error;
		}

		if(xmlXPathRegisterNs(xpathCtx, BAD_CAST "xmlns",
				   BAD_CAST "urn:ietf:params:xml:ns:resource-lists")
				!= 0) {
			LM_ERR("unable to register xmlns\n");
			goto error;
		}

		xpathObj = xmlXPathEvalExpression(BAD_CAST path.s, xpathCtx);
		if(xpathObj == NULL) {
			LM_ERR("unable to evaluate path\n");
			goto error;
		}

		if(xpathObj->nodesetval == NULL || xpathObj->nodesetval->nodeNr <= 0) {
			LM_ERR("no nodes found\n");
			goto error;
		}
		if(xpathObj->nodesetval->nodeTab[0] != NULL
				&& xpathObj->nodesetval->nodeTab[0]->type != XML_ELEMENT_NODE) {
			LM_ERR("no nodes of the correct type found\n");
			goto error;
		}

		*rl_node = xpathObj->nodesetval->nodeTab[0];

		xmlXPathFreeObject(xpathObj);
		xmlXPathFreeContext(xpathCtx);
	}

	rls_xcap_dbf.free_result(rls_xcap_db, result);
	return 1;

error:
	if(result != NULL)
		rls_xcap_dbf.free_result(rls_xcap_db, result);
	if(xpathObj != NULL)
		xmlXPathFreeObject(xpathObj);

	if(xpathCtx != NULL)
		xmlXPathFreeContext(xpathCtx);
	if(xmldoc != NULL)
		xmlFreeDoc(*xmldoc);

	return -1;
}
