/*
 * Registrar module interface
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * 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
 *
 */

/*!
 * \defgroup registrar Registrar :: SIP Registrar support
 * The module contains REGISTER processing logic.
 */

/*!
 * \file
 * \brief SIP registrar module - interface
 * \ingroup registrar
 *
 * - Module: \ref registrar
 */

#include <stdio.h>
#include "../../core/sr_module.h"
#include "../../core/timer.h"
#include "../../core/dprint.h"
#include "../../core/error.h"
#include "../../core/socket_info.h"
#include "../../core/pvar.h"
#include "../../core/dset.h"
#include "../../modules/usrloc/usrloc.h"
#include "../../core/counters.h"
#include "../../core/utils/sruid.h"
#include "../../modules/sl/sl.h"
#include "../../core/mod_fix.h"
#include "../../core/kemi.h"

#include "save.h"
#include "api.h"
#include "lookup.h"
#include "regpv.h"
#include "reply.h"
#include "registrar.h"
#include "config.h"

MODULE_VERSION

usrloc_api_t _reg_ul; /*!< Structure containing pointers to usrloc functions*/

/*! \brief Module init & destroy function */
static int mod_init(void);
static int child_init(int);
static void mod_destroy(void);
static int w_save2(struct sip_msg *_m, char *_d, char *_cflags);
static int w_save3(struct sip_msg *_m, char *_d, char *_cflags, char *_uri);
static int w_lookup(struct sip_msg *_m, char *_d, char *_p2);
static int w_lookup_to_dset(struct sip_msg *_m, char *_d, char *_p2);
static int w_lookup_branches(struct sip_msg *_m, char *_d, char *_p2);
static int w_lookup_xavp(sip_msg_t *_m, char *_ptname, char *_puri,
		char *prxname, char *pcxname);
static int w_registered(struct sip_msg *_m, char *_d, char *_uri);
static int w_unregister(struct sip_msg *_m, char *_d, char *_uri);
static int w_unregister2(struct sip_msg *_m, char *_d, char *_uri, char *_ruid);
static int w_registered3(
		struct sip_msg *_m, char *_d, char *_uri, char *_flags);
static int w_registered4(struct sip_msg *_m, char *_d, char *_uri, char *_flags,
		char *_actionflags);
static int w_reg_send_reply(sip_msg_t *_m, char *_p1, char *_p2);
static int w_reg_from_user(
		sip_msg_t *_m, char *_ptname, char *_puri, char *_pmode);

/*! \brief Fixup functions */
static int domain_fixup(void **param, int param_no);
static int domain_uri_fixup(void **param, int param_no);
static int save_fixup(void **param, int param_no);
static int unreg_fixup(void **param, int param_no);
static int fetchc_fixup(void **param, int param_no);
static int registered_fixup(void **param, int param_no);
/*! \brief Functions */
static int w_add_sock_hdr(struct sip_msg *msg, char *str, char *foo);

/*!< if the TCP connection should be kept open */
int tcp_persistent_flag = -1;
/*!< if the looked up contacts should be filtered based on supported methods */
int method_filtering = 0;
/*!< if the Path HF should be handled */
int path_enabled = 0;
/*!< if the Path HF should be inserted in the reply.
	*   - STRICT (2): always insert, error if no support indicated in request
	*   - LAZY   (1): insert only if support indicated in request
	*   - OFF    (0): never insert */
int path_mode = PATH_MODE_STRICT;

/*!< if the received- and nat-parameters of last Path uri should be used
	* to determine if UAC is nat'ed */
int path_use_params = 0;

int path_check_local = 0;

/* sruid to get internal uid */
sruid_t _reg_sruid;

int reg_gruu_enabled = 1;
int reg_outbound_mode = 0;
int reg_regid_mode = 0;
int reg_flow_timer = 0;

int contact_max_size = 512; /* max size of contact URIs */

str match_callid_name = str_init("match_callid");
str match_received_name = str_init("match_received");
str match_contact_name = str_init("match_contact");

char *rcv_avp_param = 0;
avp_flags_t rcv_avp_type = 0;
avp_name_t rcv_avp_name;

str reg_xavp_cfg = {0};
str reg_xavp_rcd = {0};
int reg_xavp_rcd_mask = 0;
int reg_use_domain = 0;

int sock_flag = -1;
str sock_hdr_name = {0, 0};

int reg_sock_mode = 0;

/* where to go for event route ("usrloc:contact-expired") */
int reg_expire_event_rt = -1; /* default disabled */
str reg_event_callback = STR_NULL;

int reg_lookup_filter_mode = 0;
int reg_min_expires_mode = 0;

sr_kemi_eng_t *keng = NULL;

#define RCV_NAME "received"
str rcv_param = str_init(RCV_NAME);

stat_var *accepted_registrations;
stat_var *rejected_registrations;
stat_var *max_expires_stat;
stat_var *max_contacts_stat;
stat_var *default_expire_stat;
stat_var *default_expire_range_stat;
stat_var *expire_range_stat;
/** SL API structure */
sl_api_t _reg_slb;

/* clang-format off */
/*! \brief
 * Exported PV
 */
static pv_export_t mod_pvs[] = {
	{{"ulc", sizeof("ulc") - 1}, PVT_OTHER, pv_get_ulc, pv_set_ulc,
		pv_parse_ulc_name, pv_parse_index, 0, 0},
	{{0, 0}, 0, 0, 0, 0, 0, 0, 0}
};

/*! \brief
 * Exported functions
 */
static cmd_export_t cmds[] = {
	{"save", (cmd_function)w_save2, 1,
		save_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE},
	{"save", (cmd_function)w_save2, 2,
		save_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE},
	{"save", (cmd_function)w_save3, 3,
		save_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE},
	{"lookup", (cmd_function)w_lookup, 1,
		domain_uri_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"lookup", (cmd_function)w_lookup, 2,
		domain_uri_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"lookup_to_dset", (cmd_function)w_lookup_to_dset, 1,
		domain_uri_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"lookup_to_dset", (cmd_function)w_lookup_to_dset, 2,
		domain_uri_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"registered", (cmd_function)w_registered, 1,
		domain_uri_fixup, 0, ANY_ROUTE},
	{"registered", (cmd_function)w_registered, 2,
		domain_uri_fixup, 0, ANY_ROUTE},
	{"registered", (cmd_function)w_registered3, 3,
		registered_fixup, 0, ANY_ROUTE},
	{"registered", (cmd_function)w_registered4, 4,
		registered_fixup, 0, ANY_ROUTE},
	{"add_sock_hdr", (cmd_function)w_add_sock_hdr, 1,
		fixup_spve_null, 0, REQUEST_ROUTE},
	{"unregister", (cmd_function)w_unregister, 2,
		unreg_fixup, 0, ANY_ROUTE},
	{"unregister", (cmd_function)w_unregister2, 3,
		unreg_fixup, 0, ANY_ROUTE},
	{"reg_fetch_contacts", (cmd_function)pv_fetch_contacts, 3,
		fetchc_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"reg_free_contacts", (cmd_function)pv_free_contacts, 1,
		fixup_str_null, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"lookup_branches", (cmd_function)w_lookup_branches, 1,
		domain_uri_fixup, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"lookup_xavp", (cmd_function)w_lookup_xavp, 4,
		fixup_spve_all, fixup_free_spve_null, ANY_ROUTE},
	{"reg_from_user", (cmd_function)w_reg_from_user, 3,
		fixup_ssi, fixup_free_ssi, ANY_ROUTE},
	{"reg_send_reply", (cmd_function)w_reg_send_reply, 0,
		0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"bind_registrar", (cmd_function)bind_registrar, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0}
};

/*! \brief
 * Exported parameters
 */
static param_export_t params[] = {
	{"default_expires", PARAM_INT, &default_registrar_cfg.default_expires},
	{"default_expires_range", PARAM_INT,
			&default_registrar_cfg.default_expires_range},
	{"expires_range", PARAM_INT, &default_registrar_cfg.expires_range},
	{"default_q", PARAM_INT, &default_registrar_cfg.default_q},
	{"append_branches", PARAM_INT, &default_registrar_cfg.append_branches},
	{"case_sensitive", PARAM_INT, &default_registrar_cfg.case_sensitive},
	/*	{"tcp_persistent_flag",PARAM_INT, &tcp_persistent_flag }, */
	{"realm_prefix", PARAM_STR, &default_registrar_cfg.realm_pref},
	{"min_expires", PARAM_INT, &default_registrar_cfg.min_expires},
	{"max_expires", PARAM_INT, &default_registrar_cfg.max_expires},
	{"received_param", PARAM_STR, &rcv_param},
	{"received_avp", PARAM_STRING, &rcv_avp_param},
	{"max_contacts", PARAM_INT, &default_registrar_cfg.max_contacts},
	{"retry_after", PARAM_INT, &default_registrar_cfg.retry_after},
	{"sock_mode", PARAM_INT, &reg_sock_mode},
	{"sock_flag", PARAM_INT, &sock_flag},
	{"sock_hdr_name", PARAM_STR, &sock_hdr_name},
	{"method_filtering", PARAM_INT, &method_filtering},
	{"use_path", PARAM_INT, &path_enabled},
	{"path_mode", PARAM_INT, &path_mode},
	{"path_use_received", PARAM_INT, &path_use_params},
	{"path_check_local", PARAM_INT, &path_check_local},
	{"xavp_cfg", PARAM_STR, &reg_xavp_cfg},
	{"xavp_rcd", PARAM_STR, &reg_xavp_rcd},
	{"xavp_rcd_mask", PARAM_INT, &reg_xavp_rcd_mask},
	{"gruu_enabled", PARAM_INT, &reg_gruu_enabled},
	{"outbound_mode", PARAM_INT, &reg_outbound_mode},
	{"regid_mode", PARAM_INT, &reg_regid_mode},
	{"flow_timer", PARAM_INT, &reg_flow_timer},
	{"contact_max_size", PARAM_INT, &contact_max_size},
	{"event_callback", PARAM_STR, &reg_event_callback},
	{"lookup_filter_mode", PARAM_INT, &reg_lookup_filter_mode},
	{"min_expires_mode", PARAM_INT, &reg_min_expires_mode},
	{"use_expired_contacts", PARAM_INT,
			&default_registrar_cfg.use_expired_contacts},
	{0, 0, 0}
};

/*! \brief We expose internal variables via the statistic framework below.*/
stat_export_t mod_stats[] = {
	{"max_expires", STAT_NO_RESET, &max_expires_stat},
	{"max_contacts", STAT_NO_RESET, &max_contacts_stat},
	{"default_expire", STAT_NO_RESET, &default_expire_stat},
	{"default_expires_range", STAT_NO_RESET, &default_expire_range_stat},
	{"expires_range", STAT_NO_RESET, &expire_range_stat},
	{"accepted_regs", 0, &accepted_registrations},
	{"rejected_regs", 0, &rejected_registrations},
	{0, 0, 0}
};

/*! \brief
 * Module exports structure
 */
struct module_exports exports = {
	"registrar",     /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,            /* exported functions */
	params,          /* exported parameters */
	0,               /* RPC method exports */
	mod_pvs,         /* exported pseudo-variables */
	0,               /* response handling function */
	mod_init,        /* module initialization function */
	child_init,      /* per-child init function */
	mod_destroy      /* module destroy function */
};
/* clang-format on */

/*! \brief
 * Initialize parent
 */
static int mod_init(void)
{
	pv_spec_t avp_spec;
	str s;
	bind_usrloc_t bind_usrloc;
	qvalue_t dq;

	if(sruid_init(&_reg_sruid, '-', "uloc", SRUID_INC) < 0) {
		return -1;
	}

#ifdef STATISTICS
	/* register statistics */
	if(register_module_stats("registrar", mod_stats) != 0) {
		LM_ERR("Failed to register core statistics\n");
		return -1;
	}
#endif

	/* bind the SL API */
	if(sl_load_api(&_reg_slb) != 0) {
		LM_ERR("Cannot bind to SL API. Please load the SL module before"
			   " loading this module.\n");
		return -1;
	}

	if(cfg_declare("registrar", registrar_cfg_def, &default_registrar_cfg,
			   cfg_sizeof(registrar), &registrar_cfg)) {
		LM_ERR("Failed to declare the configuration parameters.\n");
		return -1;
	}

	if(rcv_avp_param && *rcv_avp_param) {
		s.s = rcv_avp_param;
		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", rcv_avp_param);
			return -1;
		}

		if(pv_get_avp_name(0, &avp_spec.pvp, &rcv_avp_name, &rcv_avp_type)
				!= 0) {
			LM_ERR("[%s]- invalid AVP definition\n", rcv_avp_param);
			return -1;
		}
	} else {
		rcv_avp_name.n = 0;
		rcv_avp_type = 0;
	}

	bind_usrloc = (bind_usrloc_t)find_export("ul_bind_usrloc", 1, 0);
	if(!bind_usrloc) {
		LM_ERR("Can't bind to the usrloc module."
			   "Please load it before this module.\n");
		return -1;
	}

	/* Normalize default_q parameter */
	dq = cfg_get(registrar, registrar_cfg, default_q);
	if(dq != Q_UNSPECIFIED) {
		if(dq > MAX_Q) {
			LM_DBG("default_q = %d, lowering to MAX_Q: %d\n", dq, MAX_Q);
			dq = MAX_Q;
		} else if(dq < MIN_Q) {
			LM_DBG("default_q = %d, raising to MIN_Q: %d\n", dq, MIN_Q);
			dq = MIN_Q;
		}
	}
	cfg_get(registrar, registrar_cfg, default_q) = dq;

	if(bind_usrloc(&_reg_ul) < 0) {
		return -1;
	}

	if(_reg_ul.register_ulcb != NULL) {
		if(reg_event_callback.s == NULL || reg_event_callback.len <= 0) {
			reg_expire_event_rt =
					route_lookup(&event_rt, "usrloc:contact-expired");
			if(reg_expire_event_rt >= 0
					&& event_rt.rlist[reg_expire_event_rt] == 0)
				reg_expire_event_rt = -1; /* disable */
		} else {
			keng = sr_kemi_eng_get();
			if(keng == NULL) {
				LM_DBG("event callback (%s) set, but no cfg engine\n",
						reg_event_callback.s);
			}
		}
		if(reg_expire_event_rt >= 0
				|| (reg_event_callback.s != NULL && keng != NULL)) {
			set_child_rpc_sip_mode();
			if(_reg_ul.register_ulcb(
					   UL_CONTACT_EXPIRE, reg_ul_expired_contact, 0)
					< 0) {
				LM_ERR("Can not register callback for expired contacts"
					   " (usrloc module)\n");
				return -1;
			}
		}
	}
	/*
	 * Import use_domain parameter from usrloc
	 */
	reg_use_domain = _reg_ul.use_domain;

	if(sock_hdr_name.s) {
		if(sock_hdr_name.len == 0 || sock_flag == -1) {
			LM_INFO("empty sock_hdr_name or sock_flag not set -> resetting\n");
			sock_hdr_name.len = 0;
			sock_flag = -1;
		}
	} else if(reg_xavp_cfg.s) {
		if(reg_xavp_cfg.len == 0 || sock_flag == -1) {
			LM_DBG("empty reg_xavp_cfg or sock_flag not set -> resetting\n");
			sock_flag = -1;
		}
	} else if(sock_flag != -1) {
		LM_INFO("sock_flag defined but no sock_hdr_name"
				" or no reg_xavp_cfg -> resetting flag\n");
		sock_flag = -1;
	}

	if(reg_outbound_mode < 0 || reg_outbound_mode > 2) {
		LM_ERR("outbound_mode modparam must be 0 (not supported),"
			   " 1 (supported), or 2 (supported and required)\n");
		return -1;
	}

	if(reg_regid_mode < 0 || reg_regid_mode > 1) {
		LM_ERR("regid_mode modparam must be 0 (use with outbound),"
			   " 1 (use always)\n");
		return -1;
	}

	if(reg_flow_timer < 0 || reg_flow_timer > REG_FLOW_TIMER_MAX
			|| (reg_flow_timer > 0 && reg_outbound_mode == REG_OUTBOUND_NONE)) {
		LM_ERR("bad value for flow_timer\n");
		return -1;
	}

	/* fix the flags */
	sock_flag = (sock_flag != -1) ? (1 << sock_flag) : 0;
	tcp_persistent_flag =
			(tcp_persistent_flag != -1) ? (1 << tcp_persistent_flag) : 0;

	set_aor_case_sensitive(cfg_get(registrar, registrar_cfg, case_sensitive));

	return 0;
}


static int child_init(int rank)
{
	if(sruid_init(&_reg_sruid, '-', "uloc", SRUID_INC) < 0)
		return -1;
	if(rank == 1) {
		/* init stats */
		//TODO if parameters are modified via cfg framework do i change them?
		update_stat(max_expires_stat, default_registrar_cfg.max_expires);
		update_stat(max_contacts_stat, default_registrar_cfg.max_contacts);
		update_stat(default_expire_stat, default_registrar_cfg.default_expires);
	}

	return 0;
}

/*! \brief
 * Wrapper to save(location)
 */
static int w_save2(struct sip_msg *_m, char *_d, char *_cflags)
{
	return save(_m, (udomain_t *)_d, ((int)(unsigned long)_cflags), NULL);
}

/*! \brief
 * Wrapper to save(location)
 */
static int w_save3(struct sip_msg *_m, char *_d, char *_cflags, char *_uri)
{
	str uri;
	if(fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0 || uri.len <= 0) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}

	return save(_m, (udomain_t *)_d, ((int)(unsigned long)_cflags), &uri);
}

/*! \brief
 * Wrapper to lookup(location)
 */
static int w_lookup(struct sip_msg *_m, char *_d, char *_uri)
{
	str uri = {0};
	if(_uri != NULL
			&& (fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0
					|| uri.len <= 0)) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}

	return lookup(_m, (udomain_t *)_d, (uri.len > 0) ? &uri : NULL);
}

/*! \brief
 * Wrapper to lookup_to_dset(location)
 */
static int w_lookup_to_dset(struct sip_msg *_m, char *_d, char *_uri)
{
	str uri = {0};
	if(_uri != NULL
			&& (fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0
					|| uri.len <= 0)) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}

	return lookup_to_dset(_m, (udomain_t *)_d, (uri.len > 0) ? &uri : NULL);
}

/*! \brief
 * Wrapper to lookup_branches(location)
 */
static int w_lookup_branches(sip_msg_t *_m, char *_d, char *_p2)
{
	return lookup_branches(_m, (udomain_t *)_d);
}


static int ki_lookup_branches(sip_msg_t *_m, str *_dtable)
{
	udomain_t *d;

	if(_reg_ul.get_udomain(_dtable->s, &d) < 0) {
		LM_ERR("usrloc domain [%s] not found\n", _dtable->s);
		return -1;
	}

	return lookup_branches(_m, d);
}

static int w_lookup_xavp(sip_msg_t *_m, char *_ptname, char *_puri,
		char *_prxname, char *_pcxname)
{
	str vtname = STR_NULL;
	str vuri = STR_NULL;
	str vrxname = STR_NULL;
	str vcxname = STR_NULL;

	if(fixup_get_svalue(_m, (gparam_t *)_ptname, &vtname) != 0) {
		LM_ERR("failed to get location table parameter\n");
		return -1;
	}
	if(fixup_get_svalue(_m, (gparam_t *)_puri, &vuri) != 0) {
		LM_ERR("failed to get uri parameter\n");
		return -1;
	}
	if(fixup_get_svalue(_m, (gparam_t *)_prxname, &vrxname) != 0) {
		LM_ERR("failed to get record xavp parameter\n");
		return -1;
	}
	if(fixup_get_svalue(_m, (gparam_t *)_pcxname, &vcxname) != 0) {
		LM_ERR("failed to get contact xavp parameter\n");
		return -1;
	}

	return ki_lookup_xavp(_m, &vtname, &vuri, &vrxname, &vcxname);
}

static int w_reg_from_user(
		sip_msg_t *_m, char *_ptname, char *_puri, char *_pmode)
{
	str vtname = STR_NULL;
	str vuri = STR_NULL;
	int vmode = 0;

	if(fixup_get_svalue(_m, (gparam_t *)_ptname, &vtname) != 0) {
		LM_ERR("failed to get location table parameter\n");
		return -1;
	}
	if(fixup_get_svalue(_m, (gparam_t *)_puri, &vuri) != 0) {
		LM_ERR("failed to get uri parameter\n");
		return -1;
	}
	if(fixup_get_ivalue(_m, (gparam_t *)_pmode, &vmode) != 0) {
		LM_ERR("failed to get mode parameter\n");
		return -1;
	}

	return ki_reg_from_user(_m, &vtname, &vuri, vmode);
}

static int w_registered(struct sip_msg *_m, char *_d, char *_uri)
{
	str uri = {0};
	if(_uri != NULL
			&& (fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0
					|| uri.len <= 0)) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}
	return registered(_m, (udomain_t *)_d, (uri.len > 0) ? &uri : NULL);
}

static int ki_registered_uri(sip_msg_t *_m, str *_dtable, str *_uri)
{
	udomain_t *d;

	if(_reg_ul.get_udomain(_dtable->s, &d) < 0) {
		LM_ERR("usrloc domain [%s] not found\n", _dtable->s);
		return -1;
	}

	return registered(_m, d, (_uri && _uri->len > 0) ? _uri : NULL);
}

static int w_registered3(struct sip_msg *_m, char *_d, char *_uri, char *_flags)
{
	str uri = {0};
	int flags = 0;
	if(_uri != NULL
			&& (fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0
					|| uri.len <= 0)) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}
	if(_flags != NULL
			&& (fixup_get_ivalue(_m, (fparam_t *)_flags, &flags)) < 0) {
		LM_ERR("invalid flags parameter\n");
		return -1;
	}
	return registered3(_m, (udomain_t *)_d, (uri.len > 0) ? &uri : NULL, flags);
}

static int ki_registered_flags(sip_msg_t *_m, str *_dtable, str *_uri, int _f)
{
	udomain_t *d;

	if(_reg_ul.get_udomain(_dtable->s, &d) < 0) {
		LM_ERR("usrloc domain [%s] not found\n", _dtable->s);
		return -1;
	}

	return registered3(_m, d, (_uri && _uri->len > 0) ? _uri : NULL, _f);
}

static int w_registered4(struct sip_msg *_m, char *_d, char *_uri, char *_flags,
		char *_actionflags)
{
	str uri = {0};
	int flags = 0;
	int actionflags = 0;
	if(_uri != NULL
			&& (fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0
					|| uri.len <= 0)) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}
	if(_flags != NULL
			&& (fixup_get_ivalue(_m, (fparam_t *)_flags, &flags)) < 0) {
		LM_ERR("invalid flags parameter\n");
		return -1;
	}
	if(_actionflags != NULL
			&& (fixup_get_ivalue(_m, (fparam_t *)_actionflags, &actionflags))
					   < 0) {
		LM_ERR("invalid action flag parameter\n");
		return -1;
	}
	return registered4(_m, (udomain_t *)_d, (uri.len > 0) ? &uri : NULL, flags,
			actionflags);
}

static int ki_registered_action(
		sip_msg_t *_m, str *_dtable, str *_uri, int _f, int _aflags)
{
	udomain_t *d;

	if(_reg_ul.get_udomain(_dtable->s, &d) < 0) {
		LM_ERR("usrloc domain [%s] not found\n", _dtable->s);
		return -1;
	}

	return registered4(
			_m, d, (_uri && _uri->len > 0) ? _uri : NULL, _f, _aflags);
}

static int w_unregister(struct sip_msg *_m, char *_d, char *_uri)
{
	str uri = {0};
	if(fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0 || uri.len <= 0) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}

	return unregister(_m, (udomain_t *)_d, &uri, NULL);
}

static int ki_unregister(sip_msg_t *_m, str *_dtable, str *_uri)
{
	udomain_t *d;

	if(_uri == NULL || _uri->len <= 0) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}
	if(_reg_ul.get_udomain(_dtable->s, &d) < 0) {
		LM_ERR("usrloc domain [%s] not found\n", _dtable->s);
		return -1;
	}

	return unregister(_m, d, _uri, NULL);
}

static int w_unregister2(struct sip_msg *_m, char *_d, char *_uri, char *_ruid)
{
	str uri = {0, 0};
	str ruid = {0};
	if(fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}
	if(fixup_get_svalue(_m, (gparam_p)_ruid, &ruid) != 0 || ruid.len <= 0) {
		LM_ERR("invalid ruid parameter\n");
		return -1;
	}

	return unregister(_m, (udomain_t *)_d, &uri, &ruid);
}

static int ki_unregister_ruid(
		sip_msg_t *_m, str *_dtable, str *_uri, str *_ruid)
{
	udomain_t *d;

	if(_uri == NULL || _uri->len <= 0) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}
	if(_reg_ul.get_udomain(_dtable->s, &d) < 0) {
		LM_ERR("usrloc domain [%s] not found\n", _dtable->s);
		return -1;
	}

	return unregister(_m, d, _uri, _ruid);
}

/*!
 *
 */
static int ki_reg_send_reply(sip_msg_t *_m)
{
	int ret;
	ret = reg_send_reply(_m);
	return (ret == 0) ? 1 : ret;
}

/*!
 *
 */
static int w_reg_send_reply(sip_msg_t *_m, char *_p1, char *_p2)
{
	return ki_reg_send_reply(_m);
}

/*! \brief
 * Convert char* parameter to udomain_t* pointer
 */
static int domain_fixup(void **param, int param_no)
{
	udomain_t *d;

	if(param_no == 1) {
		if(_reg_ul.register_udomain((char *)*param, &d) < 0) {
			LM_ERR("failed to register domain\n");
			return E_UNSPEC;
		}

		*param = (void *)d;
	}
	return 0;
}

/*! \brief
 * Convert char* parameter to udomain_t* pointer
 */
static int domain_uri_fixup(void **param, int param_no)
{
	if(param_no == 1) {
		return domain_fixup(param, 1);
	} else if(param_no == 2) {
		return fixup_spve_null(param, 1);
	}
	return 0;
}

static int registered_fixup(void **param, int param_no)
{
	if(param_no == 1) {
		return domain_fixup(param, 1);
	} else if(param_no == 2) {
		return fixup_spve_null(param, 1);
	} else if(param_no == 3) {
		return fixup_igp_null(param, 1);
	} else if(param_no == 4) {
		return fixup_igp_null(param, 1);
	}
	return 0;
}

/*! \brief
 * Convert char* parameter to udomain_t* pointer
 * Convert char* parameter to pv_elem_t* pointer
 */
static int unreg_fixup(void **param, int param_no)
{
	if(param_no == 1) {
		return domain_fixup(param, 1);
	} else if(param_no == 2) {
		return fixup_spve_null(param, 1);
	} else if(param_no == 3) {
		return fixup_spve_null(param, 1);
	}
	return 0;
}


/*! \brief
 * Fixup for "save" function - both domain and flags
 */
static int save_fixup(void **param, int param_no)
{
	unsigned int flags;
	str s;

	if(param_no == 1) {
		return domain_fixup(param, param_no);
	} else if(param_no == 2) {
		s.s = (char *)*param;
		s.len = strlen(s.s);
		flags = 0;
		if((strno2int(&s, &flags) < 0) || (flags > REG_SAVE_ALL_FL)) {
			LM_ERR("bad flags <%s>\n", (char *)(*param));
			return E_CFG;
		}
		if(_reg_ul.db_mode == DB_ONLY && flags & REG_SAVE_MEM_FL) {
			LM_ERR("MEM flag set while using the DB_ONLY mode in USRLOC\n");
			return E_CFG;
		}
		pkg_free(*param);
		*param = (void *)(unsigned long int)flags;
	} else if(param_no == 3) {
		return fixup_spve_null(param, 1);
	}
	return 0;
}

/*! \brief
 * Convert char* parameter to udomain_t* pointer
 * Convert char* parameter to pv_elem_t* pointer
 * Convert char* parameter to str* pointer
 */
static int fetchc_fixup(void **param, int param_no)
{
	if(param_no == 1) {
		return domain_fixup(param, 1);
	} else if(param_no == 2) {
		return fixup_spve_null(param, 1);
	} else if(param_no == 3) {
		return fixup_str_null(param, 1);
	}
	return 0;
}


static void mod_destroy(void)
{
	free_contact_buf();
}


#include "../../core/data_lump.h"
#include "../../core/ip_addr.h"
#include "../../core/ut.h"

static int ki_add_sock_hdr(sip_msg_t *msg, str *hdr_name)
{
	struct socket_info *si;
	struct lump *anchor;
	str hdr;
	char *p;

	if(hdr_name == NULL || hdr_name->s == NULL || hdr_name->len <= 0) {
		LM_ERR("invalid header name parameter\n");
		return -1;
	}
	si = msg->rcv.bind_address;

	if(parse_headers(msg, HDR_EOH_F, 0) == -1) {
		LM_ERR("failed to parse message\n");
		goto error;
	}

	anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, 0);
	if(anchor == 0) {
		LM_ERR("can't get anchor\n");
		goto error;
	}

	hdr.len = hdr_name->len + 2 + si->sock_str.len + CRLF_LEN;
	if((hdr.s = (char *)pkg_malloc(hdr.len)) == 0) {
		LM_ERR("no more pkg mem\n");
		goto error;
	}

	p = hdr.s;
	memcpy(p, hdr_name->s, hdr_name->len);
	p += hdr_name->len;
	*(p++) = ':';
	*(p++) = ' ';

	memcpy(p, si->sock_str.s, si->sock_str.len);
	p += si->sock_str.len;

	memcpy(p, CRLF, CRLF_LEN);
	p += CRLF_LEN;

	if(p - hdr.s != hdr.len) {
		LM_CRIT("buffer overflow (%d!=%d)\n", (int)(long)(p - hdr.s), hdr.len);
		goto error1;
	}

	if(insert_new_lump_before(anchor, hdr.s, hdr.len, 0) == 0) {
		LM_ERR("can't insert lump\n");
		goto error1;
	}

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

static int w_add_sock_hdr(struct sip_msg *msg, char *name, char *foo)
{
	str hdr_name;
	if(fixup_get_svalue(msg, (gparam_t *)name, &hdr_name) < 0) {
		LM_ERR("cannot get the header name\n");
		return -1;
	}
	return ki_add_sock_hdr(msg, &hdr_name);
}

void default_expires_stats_update(str *gname, str *name)
{
	update_stat(default_expire_stat,
			cfg_get(registrar, registrar_cfg, default_expires));
}

void max_expires_stats_update(str *gname, str *name)
{
	update_stat(
			max_expires_stat, cfg_get(registrar, registrar_cfg, max_expires));
}

void default_expires_range_update(str *gname, str *name)
{
	update_stat(default_expire_range_stat,
			cfg_get(registrar, registrar_cfg, default_expires_range));
}

void expires_range_update(str *gname, str *name)
{
	update_stat(expire_range_stat,
			cfg_get(registrar, registrar_cfg, expires_range));
}

/**
 *
 */
/* clang-format off */
static sr_kemi_t sr_kemi_registrar_exports[] = {
	{ str_init("registrar"), str_init("save"),
		SR_KEMIP_INT, regapi_save,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("save_uri"),
		SR_KEMIP_INT, regapi_save_uri,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("lookup"),
		SR_KEMIP_INT, regapi_lookup,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("lookup_uri"),
		SR_KEMIP_INT, regapi_lookup_uri,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("lookup_to_dset"),
		SR_KEMIP_INT, regapi_lookup_to_dset,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("lookup_branches"),
		SR_KEMIP_INT, ki_lookup_branches,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("lookup_xavp"),
		SR_KEMIP_INT, ki_lookup_xavp,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("registered"),
		SR_KEMIP_INT, regapi_registered,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("registered_uri"),
		SR_KEMIP_INT, ki_registered_uri,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("registered_flags"),
		SR_KEMIP_INT, ki_registered_flags,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_INT,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("registered_action"),
		SR_KEMIP_INT, ki_registered_action,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_INT,
			SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("set_q_override"),
		SR_KEMIP_INT, regapi_set_q_override,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("add_sock_hdr"),
		SR_KEMIP_INT, ki_add_sock_hdr,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("unregister"),
		SR_KEMIP_INT, ki_unregister,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("unregister_ruid"),
		SR_KEMIP_INT, ki_unregister_ruid,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("reg_fetch_contacts"),
		SR_KEMIP_INT, ki_reg_fetch_contacts,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("reg_free_contacts"),
		SR_KEMIP_INT, ki_reg_free_contacts,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("reg_from_user"),
		SR_KEMIP_INT, ki_reg_from_user,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_INT,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("reg_send_reply"),
		SR_KEMIP_INT, ki_reg_send_reply,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("ulc_rget"),
		SR_KEMIP_XVAL, ki_reg_ulc_rget,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("registrar"), str_init("ulc_cget"),
		SR_KEMIP_XVAL, ki_reg_ulc_cget,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_INT,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
};
/* clang-format on */

/**
 *
 */
int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
	sr_kemi_modules_add(sr_kemi_registrar_exports);
	return 0;
}
