/*
 * TM (transaction management) module
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/** TM :: Module API (core).
 * @file
 * @ingroup tm
 */

/**
 * @defgroup tm TM :: Transaction stateful proxy support
 *
 * The TM module enables stateful processing of SIP transactions. The main use
 * of stateful logic, which is costly in terms of memory and CPU, is some
 * services that inherently need state.
 *
 * For example, transaction-based accounting (module acc) needs to process
 * transaction state as opposed to individual messages, and any kinds of
 * forking must be implemented statefully. Other use of stateful processing
 * is it trading CPU caused by retransmission processing for memory.
 * That however only makes sense if CPU consumption per request is huge.
 * For example, if you want to avoid costly DNS resolution for every
 * retransmission of a request to an unresolvable destination, use stateful
 * mode. Then, only the initial message burdens server by DNS queries,
 * subsequent retransmissions will be dropped and will not result in more
 * processes blocked by DNS resolution. The price is more memory consumption
 * and higher processing latency.
 */


#include <stdio.h>
#include <string.h>
#include <netdb.h>

#include "../../core/sr_module.h"
#include "../../core/dprint.h"
#include "../../core/error.h"
#include "../../core/ut.h"
#include "../../core/script_cb.h"
#include "../../core/usr_avp.h"
#include "../../core/mem/mem.h"
#include "../../core/route_struct.h"
#include "../../core/route.h"
#include "../../core/cfg/cfg.h"
#include "../../core/globals.h"
#include "../../core/timer_ticks.h"
#include "../../core/dset.h"
#include "../../core/mod_fix.h"
#include "../../core/kemi.h"
#include "../../core/parser/parse_from.h"

#include "config.h"
#include "sip_msg.h"
#include "h_table.h"
#include "t_hooks.h"
#include "tm_load.h"
#include "ut.h"
#include "t_reply.h"
#include "uac.h"
#include "t_fwd.h"
#include "t_lookup.h"
#include "t_stats.h"
#include "callid.h"
#include "t_cancel.h"
#include "t_fifo.h"
#include "timer.h"
#include "t_msgbuilder.h"
#include "select.h"
#include "t_serial.h"
#include "rpc_uac.h"

MODULE_VERSION

/* fixup functions */
static int fixup_hostport2proxy(void **param, int param_no);
static int fixup_proto_hostport2proxy(void **param, int param_no);
static int fixup_on_failure(void **param, int param_no);
static int fixup_on_branch_failure(void **param, int param_no);
static int fixup_on_reply(void **param, int param_no);
static int fixup_on_branch(void **param, int param_no);
static int fixup_t_reply(void **param, int param_no);
static int fixup_t_relay_to(void **param, int param_no);
static int fixup_t_is_set(void **param, int param_no);

/* init functions */
static int mod_init(void);
static int child_init(int rank);

#ifdef USE_DNS_FAILOVER
static int t_failover_parse_reply_codes();
#endif

/* exported functions */
static int w_t_check(struct sip_msg *msg, char *str, char *str2);
static int w_t_lookup_cancel(struct sip_msg *msg, char *str, char *str2);
static int w_t_reply(struct sip_msg *msg, char *str, char *str2);
static int w_t_reply_error(struct sip_msg *msg, char *str, char *str2);
static int w_t_send_reply(struct sip_msg *msg, char *p1, char *p2);
static int w_t_release(struct sip_msg *msg, char *str, char *str2);
static int w_t_retransmit_reply(struct sip_msg *p_msg, char *foo, char *bar);
static int w_t_newtran(struct sip_msg *p_msg, char *foo, char *bar);
static int w_t_relay(struct sip_msg *p_msg, char *_foo, char *_bar);
static int w_t_relay2(struct sip_msg *p_msg, char *proxy, char *);
static int w_t_relay_to_udp(struct sip_msg *p_msg, char *proxy, char *);
static int w_t_relay_to_udp_uri(struct sip_msg *p_msg, char *, char *);
#ifdef USE_TCP
static int w_t_relay_to_tcp(struct sip_msg *p_msg, char *proxy, char *);
static int w_t_relay_to_tcp_uri(struct sip_msg *p_msg, char *, char *);
#endif
#ifdef USE_TLS
static int w_t_relay_to_tls(struct sip_msg *p_msg, char *proxy, char *);
static int w_t_relay_to_tls_uri(struct sip_msg *p_msg, char *, char *);
#endif
#ifdef USE_SCTP
static int w_t_relay_to_sctp(struct sip_msg *p_msg, char *proxy, char *);
static int w_t_relay_to_sctp_uri(struct sip_msg *, char *, char *);
#endif
static int w_t_relay_to_avp(struct sip_msg *msg, char *str, char *);
static int w_t_relay_to(struct sip_msg *msg, char *str, char *);
static int w_t_replicate_uri(struct sip_msg *p_msg,
		char *uri, /* sip uri as string or variable */
		char *_foo /* nothing expected */);
static int w_t_replicate(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */);
static int w_t_replicate_udp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */);
#ifdef USE_TCP
static int w_t_replicate_tcp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */);
#endif
#ifdef USE_TLS
static int w_t_replicate_tls(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */);
#endif
#ifdef USE_SCTP
static int w_t_replicate_sctp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */);
#endif
static int w_t_replicate_to(struct sip_msg *msg, char *str, char *);
static int w_t_forward_nonack(struct sip_msg *msg, char *str, char *);
static int w_t_forward_nonack_uri(struct sip_msg *msg, char *str, char *);
static int w_t_forward_nonack_udp(struct sip_msg *msg, char *str, char *);
#ifdef USE_TCP
static int w_t_forward_nonack_tcp(struct sip_msg *, char *str, char *);
#endif
#ifdef USE_TLS
static int w_t_forward_nonack_tls(struct sip_msg *, char *str, char *);
#endif
#ifdef USE_SCTP
static int w_t_forward_nonack_sctp(struct sip_msg *, char *str, char *);
#endif
static int w_t_forward_nonack_to(struct sip_msg *msg, char *str, char *);
static int w_t_relay_cancel(struct sip_msg *p_msg, char *_foo, char *_bar);
static int w_t_on_failure(struct sip_msg *msg, char *go_to, char *foo);
static int w_t_on_branch_failure(struct sip_msg *msg, char *go_to, char *foo);
static int w_t_on_branch(struct sip_msg *msg, char *go_to, char *foo);
static int w_t_on_reply(struct sip_msg *msg, char *go_to, char *foo);
static int t_check_status(struct sip_msg *msg, char *match, char *foo);
static int t_set_fr_inv(struct sip_msg *msg, char *fr_inv, char *foo);
static int t_set_fr_all(struct sip_msg *msg, char *fr_inv, char *fr);
static int w_t_reset_fr(struct sip_msg *msg, char *foo, char *bar);
static int w_t_set_retr(struct sip_msg *msg, char *retr_t1, char *retr_t2);
static int w_t_reset_retr(struct sip_msg *msg, char *foo, char *bar);
static int w_t_set_max_lifetime(struct sip_msg *msg, char *inv, char *noninv);
static int w_t_reset_max_lifetime(struct sip_msg *msg, char *foo, char *bar);
static int w_t_set_auto_inv_100(struct sip_msg *msg, char *on_off, char *foo);
static int w_t_set_disable_6xx(struct sip_msg *msg, char *on_off, char *foo);
static int w_t_set_disable_failover(struct sip_msg *msg, char *on_off, char *f);
static int w_t_set_no_e2e_cancel_reason(
		struct sip_msg *msg, char *on_off, char *f);
static int w_t_set_disable_internal_reply(
		struct sip_msg *msg, char *on_off, char *f);
static int w_t_branch_timeout(struct sip_msg *msg, char *, char *);
static int w_t_branch_replied(struct sip_msg *msg, char *, char *);
static int w_t_any_timeout(struct sip_msg *msg, char *, char *);
static int w_t_any_replied(struct sip_msg *msg, char *, char *);
static int w_t_is_canceled(struct sip_msg *msg, char *, char *);
static int w_t_is_expired(struct sip_msg *msg, char *, char *);
static int w_t_is_retr_async_reply(struct sip_msg *msg, char *, char *);
static int w_t_grep_status(struct sip_msg *msg, char *, char *);
static int w_t_drop_replies(struct sip_msg *msg, char *foo, char *bar);
static int w_t_save_lumps(struct sip_msg *msg, char *foo, char *bar);
static int w_t_check_trans(struct sip_msg *msg, char *foo, char *bar);
static int w_t_is_set(struct sip_msg *msg, char *target, char *bar);
static int w_t_use_uac_headers(sip_msg_t *msg, char *foo, char *bar);
static int w_t_uac_send(sip_msg_t *msg, char *pmethod, char *pruri,
		char *pnexthop, char *psock, char *phdrs, char *pbody);
static int w_t_get_status_code(sip_msg_t *msg, char *p1, char *p2);

static int t_clean(struct sip_msg *msg, char *key, char *value);
static int w_t_exists(struct sip_msg *msg, char *p1, char *p2);

/* by default the fr timers avps are not set, so that the avps won't be
 * searched for nothing each time a new transaction is created */
static char *fr_timer_param = 0 /*FR_TIMER_AVP*/;
static char *fr_inv_timer_param = 0 /*FR_INV_TIMER_AVP*/;

str contacts_avp = {0, 0};
str contact_flows_avp = {0, 0};
str ulattrs_xavp_name = {NULL, 0};
str on_sl_reply_name = {NULL, 0};
int tm_remap_503_500 = 1;
str _tm_event_callback_lres_sent = {NULL, 0};

#ifdef USE_DNS_FAILOVER
str failover_reply_codes_str = {NULL, 0};
int **failover_reply_codes = NULL;
int *failover_reply_codes_cnt;
#endif

/* control if reply should be relayed
 * when transaction reply status is RPS_PUSHED_AFTER_COMPLETION */
int tm_reply_relay_mode = 1;

unsigned long tm_exec_time_check = 0; /* microseconds */
int tm_exec_time_check_param = 5000;  /* milliseconds */

int tm_failure_exec_mode = 0;

int tm_dns_reuse_rcv_socket = 0;

static rpc_export_t tm_rpc[];

str tm_event_callback = STR_NULL;

static int fixup_t_check_status(void **param, int param_no);

/* clang-format off */
static cmd_export_t cmds[] = {
	{"t_newtran", w_t_newtran, 0, 0, 0, REQUEST_ROUTE},
	{"t_lookup_request", w_t_check, 0, 0, 0, REQUEST_ROUTE},
	{"t_lookup_cancel", w_t_lookup_cancel, 0, 0, 0, REQUEST_ROUTE},
	{"t_lookup_cancel", w_t_lookup_cancel, 1, fixup_int_1, 0,
			REQUEST_ROUTE},
	{"t_reply", w_t_reply, 2, fixup_t_reply, 0,
			REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
	{"t_reply_error", w_t_reply_error, 0, 0, 0,
			REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
	{"t_send_reply", w_t_send_reply, 2, fixup_t_reply, 0,
			REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
	{"t_retransmit_reply", w_t_retransmit_reply, 0, 0, 0, REQUEST_ROUTE},
	{"t_release", w_t_release, 0, 0, 0, REQUEST_ROUTE},
	{"t_relay_to_udp", w_t_relay_to_udp, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_relay_to_udp", w_t_relay_to_udp_uri, 0, 0, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
#ifdef USE_TCP
	{"t_relay_to_tcp", w_t_relay_to_tcp, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_relay_to_tcp", w_t_relay_to_tcp_uri, 0, 0, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
#endif
#ifdef USE_TLS
	{"t_relay_to_tls", w_t_relay_to_tls, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_relay_to_tls", w_t_relay_to_tls_uri, 0, 0, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
#endif
#ifdef USE_SCTP
	{"t_relay_to_sctp", w_t_relay_to_sctp, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_relay_to_sctp", w_t_relay_to_sctp_uri, 0, 0, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
#endif
	{"t_replicate", w_t_replicate_uri, 0, 0, 0, REQUEST_ROUTE},
	{"t_replicate", w_t_replicate_uri, 1, fixup_spve_null, 0,
			REQUEST_ROUTE},
	{"t_replicate", w_t_replicate, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE},
	{"t_replicate_udp", w_t_replicate_udp, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE},
#ifdef USE_TCP
	{"t_replicate_tcp", w_t_replicate_tcp, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE},
#endif
#ifdef USE_TLS
	{"t_replicate_tls", w_t_replicate_tls, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE},
#endif
#ifdef USE_SCTP
	{"t_replicate_sctp", w_t_replicate_sctp, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE},
#endif
	{"t_replicate_to", w_t_replicate_to, 2, fixup_proto_hostport2proxy, 0,
			REQUEST_ROUTE},
	{"t_relay", w_t_relay, 0, 0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_relay", w_t_relay2, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_relay_to_avp", w_t_relay_to_avp, 2, fixup_proto_hostport2proxy, 0,
			REQUEST_ROUTE},
	{"t_relay_to", w_t_relay_to, 0, 0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_relay_to", w_t_relay_to, 1, fixup_t_relay_to, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_relay_to", w_t_relay_to, 2, fixup_t_relay_to, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_forward_nonack", w_t_forward_nonack, 2, fixup_hostport2proxy, 0,
			REQUEST_ROUTE},
	{"t_forward_nonack_uri", w_t_forward_nonack_uri, 0, 0, 0,
			REQUEST_ROUTE},
	{"t_forward_nonack_udp", w_t_forward_nonack_udp, 2,
			fixup_hostport2proxy, 0, REQUEST_ROUTE},
#ifdef USE_TCP
	{"t_forward_nonack_tcp", w_t_forward_nonack_tcp, 2,
			fixup_hostport2proxy, 0, REQUEST_ROUTE},
#endif
#ifdef USE_TLS
	{"t_forward_nonack_tls", w_t_forward_nonack_tls, 2,
			fixup_hostport2proxy, 0, REQUEST_ROUTE},
#endif
#ifdef USE_SCTP
	{"t_forward_nonack_sctp", w_t_forward_nonack_sctp, 2,
			fixup_hostport2proxy, 0, REQUEST_ROUTE},
#endif
	{"t_forward_nonack_to", w_t_forward_nonack_to, 2,
			fixup_proto_hostport2proxy, 0, REQUEST_ROUTE},
	{"t_relay_cancel", w_t_relay_cancel, 0, 0, 0, REQUEST_ROUTE},
	{"t_on_failure", w_t_on_failure, 1, fixup_on_failure, 0,
			REQUEST_ROUTE | FAILURE_ROUTE | TM_ONREPLY_ROUTE
					| BRANCH_ROUTE},
	{"t_on_branch_failure", w_t_on_branch_failure, 1,
			fixup_on_branch_failure, 0,
			REQUEST_ROUTE | FAILURE_ROUTE | TM_ONREPLY_ROUTE
					| BRANCH_ROUTE},
	{"t_on_reply", w_t_on_reply, 1, fixup_on_reply, 0,
			REQUEST_ROUTE | FAILURE_ROUTE | TM_ONREPLY_ROUTE
					| BRANCH_ROUTE},
	{"t_on_branch", w_t_on_branch, 1, fixup_on_branch, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_check_status", t_check_status, 1, fixup_t_check_status, 0,
			REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE},
	{"t_write_req", t_write_req, 2, fixup_t_write, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_write_unix", t_write_unix, 2, fixup_t_write, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_set_fr", t_set_fr_inv, 1, fixup_var_int_1, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_set_fr", t_set_fr_all, 2, fixup_var_int_12, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_reset_fr", w_t_reset_fr, 0, 0, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_set_retr", w_t_set_retr, 2, fixup_var_int_12, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_reset_retr", w_t_reset_retr, 0, 0, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_set_max_lifetime", w_t_set_max_lifetime, 2, fixup_var_int_12, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_reset_max_lifetime", w_t_reset_max_lifetime, 0, 0, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_set_auto_inv_100", w_t_set_auto_inv_100, 1, fixup_var_int_1, 0,
			REQUEST_ROUTE},
	{"t_set_disable_6xx", w_t_set_disable_6xx, 1, fixup_var_int_1, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_set_disable_failover", w_t_set_disable_failover, 1, fixup_var_int_1,
			0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_set_no_e2e_cancel_reason", w_t_set_no_e2e_cancel_reason, 1,
			fixup_var_int_1, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	/* alias for t_set_no_e2e_cancel_reason */
	{"t_disable_e2e_cancel_reason", w_t_set_no_e2e_cancel_reason, 1,
			fixup_var_int_1, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_set_disable_internal_reply", w_t_set_disable_internal_reply, 1,
			fixup_var_int_1, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_branch_timeout", w_t_branch_timeout, 0, 0, 0,
			FAILURE_ROUTE | EVENT_ROUTE},
	{"t_branch_replied", w_t_branch_replied, 0, 0, 0,
			FAILURE_ROUTE | EVENT_ROUTE},
	{"t_any_timeout", w_t_any_timeout, 0, 0, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_any_replied", w_t_any_replied, 0, 0, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_is_canceled", w_t_is_canceled, 0, 0, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_is_retr_async_reply", w_t_is_retr_async_reply, 0, 0, 0,
			TM_ONREPLY_ROUTE},
	{"t_is_expired", w_t_is_expired, 0, 0, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_grep_status", w_t_grep_status, 1, fixup_var_int_1, 0,
			REQUEST_ROUTE | TM_ONREPLY_ROUTE | FAILURE_ROUTE
					| BRANCH_ROUTE},
	{"t_drop_replies", w_t_drop_replies, 0, 0, 0, FAILURE_ROUTE},
	{"t_drop_replies", w_t_drop_replies, 1, 0, 0, FAILURE_ROUTE},
	{"t_save_lumps", w_t_save_lumps, 0, 0, 0, REQUEST_ROUTE},
	{"t_check_trans", w_t_check_trans, 0, 0, 0,
			REQUEST_ROUTE | ONREPLY_ROUTE | BRANCH_ROUTE},
	{"t_is_set", w_t_is_set, 1, fixup_t_is_set, 0, ANY_ROUTE},
	{"t_use_uac_headers", w_t_use_uac_headers, 0, 0, 0, ANY_ROUTE},
	{"t_uac_send", (cmd_function)w_t_uac_send, 6, fixup_spve_all, 0,
			ANY_ROUTE},
	{"t_get_status_code", w_t_get_status_code, 0, 0, 0,
			REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE},

	{"t_load_contacts", t_load_contacts, 0, 0, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_load_contacts", t_load_contacts, 1, fixup_var_int_1, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_next_contacts", t_next_contacts, 0, 0, 0,
			REQUEST_ROUTE | FAILURE_ROUTE},
	{"t_next_contact_flow", t_next_contact_flow, 0, 0, 0, REQUEST_ROUTE},
	{"t_clean", t_clean, 0, 0, 0, ANY_ROUTE},
	{"t_exists", w_t_exists, 0, 0, 0, ANY_ROUTE},

	/* not applicable from the script */
	{"load_tm", (cmd_function)load_tm, NO_SCRIPT, 0, 0, 0},
	{"load_xtm", (cmd_function)load_xtm, NO_SCRIPT, 0, 0, 0},

	{0, 0, 0, 0, 0, 0}
};


static param_export_t params[] = {
	{"ruri_matching", PARAM_INT, &default_tm_cfg.ruri_matching},
	{"via1_matching", PARAM_INT, &default_tm_cfg.via1_matching},
	{"callid_matching", PARAM_INT, &default_tm_cfg.callid_matching},
	{"callid_cseq_matching", PARAM_INT,
			&default_tm_cfg.callid_cseq_matching},
	{"fr_timer", PARAM_INT, &default_tm_cfg.fr_timeout},
	{"fr_inv_timer", PARAM_INT, &default_tm_cfg.fr_inv_timeout},
	{"wt_timer", PARAM_INT, &default_tm_cfg.wait_timeout},
	{"retr_timer1", PARAM_INT, &default_tm_cfg.rt_t1_timeout_ms},
	{"retr_timer2", PARAM_INT, &default_tm_cfg.rt_t2_timeout_ms},
	{"max_inv_lifetime", PARAM_INT, &default_tm_cfg.tm_max_inv_lifetime},
	{"max_noninv_lifetime", PARAM_INT,
			&default_tm_cfg.tm_max_noninv_lifetime},
	{"noisy_ctimer", PARAM_INT, &default_tm_cfg.noisy_ctimer},
	{"auto_inv_100", PARAM_INT, &default_tm_cfg.tm_auto_inv_100},
	{"auto_inv_100_reason", PARAM_STRING,
			&default_tm_cfg.tm_auto_inv_100_r},
	{"unix_tx_timeout", PARAM_INT, &default_tm_cfg.tm_unix_tx_timeout},
	{"restart_fr_on_each_reply", PARAM_INT,
			&default_tm_cfg.restart_fr_on_each_reply},
	{"fr_timer_avp", PARAM_STRING, &fr_timer_param},
	{"fr_inv_timer_avp", PARAM_STRING, &fr_inv_timer_param},
	{"tw_append", PARAM_STRING | PARAM_USE_FUNC, (void *)parse_tw_append},
	{"pass_provisional_replies", PARAM_INT,
			&default_tm_cfg.pass_provisional_replies},
	{"aggregate_challenges", PARAM_INT, &default_tm_cfg.tm_aggregate_auth},
	{"unmatched_cancel", PARAM_INT, &default_tm_cfg.unmatched_cancel},
	{"default_code", PARAM_INT, &default_tm_cfg.default_code},
	{"default_reason", PARAM_STRING, &default_tm_cfg.default_reason},
	{"reparse_invite", PARAM_INT, &default_tm_cfg.reparse_invite},
	{"ac_extra_hdrs", PARAM_STR, &default_tm_cfg.ac_extra_hdrs},
	{"blst_503", PARAM_INT, &default_tm_cfg.tm_blst_503},
	{"blst_503_def_timeout", PARAM_INT,
			&default_tm_cfg.tm_blst_503_default},
	{"blst_503_min_timeout", PARAM_INT, &default_tm_cfg.tm_blst_503_min},
	{"blst_503_max_timeout", PARAM_INT, &default_tm_cfg.tm_blst_503_max},
	{"blst_methods_add", PARAM_INT, &default_tm_cfg.tm_blst_methods_add},
	{"blst_methods_lookup", PARAM_INT,
			&default_tm_cfg.tm_blst_methods_lookup},
	{"cancel_b_method", PARAM_INT, &default_tm_cfg.cancel_b_flags},
	{"reparse_on_dns_failover", PARAM_INT,
			&default_tm_cfg.reparse_on_dns_failover},
	{"on_sl_reply", PARAM_STR, &on_sl_reply_name},
	{"contacts_avp", PARAM_STR, &contacts_avp},
	{"contact_flows_avp", PARAM_STR, &contact_flows_avp},
	{"disable_6xx_block", PARAM_INT, &default_tm_cfg.disable_6xx},
	{"local_ack_mode", PARAM_INT, &default_tm_cfg.local_ack_mode},
	{"failure_reply_mode", PARAM_INT, &failure_reply_mode},
	{"faked_reply_prio", PARAM_INT, &faked_reply_prio},
	{"remap_503_500", PARAM_INT, &tm_remap_503_500},
	{"failure_exec_mode", PARAM_INT, &tm_failure_exec_mode},
	{"dns_reuse_rcv_socket", PARAM_INT, &tm_dns_reuse_rcv_socket},
	{"local_cancel_reason", PARAM_INT, &default_tm_cfg.local_cancel_reason},
	{"e2e_cancel_reason", PARAM_INT, &default_tm_cfg.e2e_cancel_reason},
	{"xavp_contact", PARAM_STR, &ulattrs_xavp_name},
	{"event_callback", PARAM_STR, &tm_event_callback},
	{"relay_100", PARAM_INT, &default_tm_cfg.relay_100},
	{"rich_redirect", PARAM_INT, &tm_rich_redirect},
	{"event_callback_lres_sent", PARAM_STR, &_tm_event_callback_lres_sent},
	{"exec_time_check", PARAM_INT, &tm_exec_time_check_param},
	{"reply_relay_mode", PARAM_INT, &tm_reply_relay_mode},
	{"enable_uac_fr", PARAM_INT, &default_tm_cfg.enable_uac_fr},
#ifdef USE_DNS_FAILOVER
	{"failover_reply_codes", PARAM_STR, &failover_reply_codes_str},
#endif
	{0, 0, 0}
};

#ifdef STATIC_TM
struct module_exports tm_exports = {
#else
struct module_exports exports = {
#endif
	"tm",			 /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,			 /* cmd (cfg function) exports */
	params,			 /* param exports */
	tm_rpc,			 /* RPC method exports */
	0,				 /* pv exports */
	reply_received,	 /* response handling function */
	mod_init,		 /* module init function */
	child_init,		 /* per-child init function */
	tm_shutdown		 /* module destroy function */
};
/* clang-format on */

/* helper for fixup_on_* */
static int fixup_routes(char *r_type, struct route_list *rt, void **param)
{
	int i;

	i = route_get(rt, (char *)*param);
	if(i == -1) {
		LM_ERR("route_get failed\n");
		return E_UNSPEC;
	}
	if(r_type && rt->rlist[i] == 0) {
		LM_WARN("%s(\"%s\"): empty/non existing route\n", r_type,
				(char *)*param);
	}
	*param = (void *)(long)i;
	return 0;
}

static int fixup_t_reply(void **param, int param_no)
{
	if(param_no == 1) {
		if(fixup_var_int_12(param, 1) != 0)
			return -1;
	} else if(param_no == 2) {
		return fixup_var_str_12(param, 2);
	}
	return 0;
}

static int fixup_on_failure(void **param, int param_no)
{
	if(param_no == 1) {
		if(strlen((char *)*param) <= 1
				&& (*(char *)(*param) == 0 || *(char *)(*param) == '0')) {
			*param = (void *)0;
			return 0;
		}
		return fixup_routes("t_on_failure", &failure_rt, param);
	}
	return 0;
}

#define BRANCH_FAILURE_ROUTE_PREFIX "tm:branch-failure"
static int fixup_on_branch_failure(void **param, int param_no)
{
	char *full_route_name = NULL;
	int blen = 0;
	int bsize = 0;
	int ret = 0;
	if(param_no == 1) {
		bsize = strlen((char *)*param);
		if((bsize <= 1)
				&& (*(char *)(*param) == 0 || *(char *)(*param) == '0')) {
			*param = (void *)0;
			return 0;
		}
		bsize += strlen(BRANCH_FAILURE_ROUTE_PREFIX) + 2;
		if((full_route_name = pkg_malloc(bsize)) == NULL) {
			LM_ERR("No memory left in branch_failure fixup\n");
			return -1;
		}
		blen = snprintf(full_route_name, bsize, "%s:%s",
				BRANCH_FAILURE_ROUTE_PREFIX, (char *)*param);
		if(blen < 0 || blen >= bsize) {
			LM_ERR("Failure to construct route block name\n");
			pkg_free(full_route_name);
			return -1;
		}
		*param = (void *)full_route_name;
		ret = fixup_routes("t_on_branch_failure", &event_rt, param);
		pkg_free(full_route_name);
	}
	return ret;
}


static int fixup_on_reply(void **param, int param_no)
{
	if(param_no == 1) {
		if(strlen((char *)*param) <= 1
				&& (*(char *)(*param) == 0 || *(char *)(*param) == '0')) {
			*param = (void *)0;
			return 0;
		}
		return fixup_routes("t_on_reply", &onreply_rt, param);
	}
	return 0;
}


static int fixup_on_branch(void **param, int param_no)
{
	if(param_no == 1) {
		if(strlen((char *)*param) <= 1
				&& (*(char *)(*param) == 0 || *(char *)(*param) == '0')) {
			*param = (void *)0;
			return 0;
		}
		return fixup_routes("t_on_branch", &branch_rt, param);
	}
	return 0;
}


/* (char *hostname, char *port_nr) ==> (struct proxy_l *, -)  */
static int fixup_hostport2proxy(void **param, int param_no)
{
	unsigned int port;
	char *host;
	int err;
	struct proxy_l *proxy;
	action_u_t *a;
	str s;

	DBG("TM module: fixup_hostport2proxy(%s, %d)\n", (char *)*param, param_no);
	if(param_no == 1) {
		return 0;
	} else if(param_no == 2) {
		a = fixup_get_param(param, param_no, 1);
		host = a->u.string;
		port = str2s(*param, strlen(*param), &err);
		if(err != 0) {
			LM_ERR("bad port number <%s>\n", (char *)(*param));
			return E_UNSPEC;
		}
		s.s = host;
		s.len = strlen(host);
		proxy = mk_proxy(&s, port, 0);
		if(proxy == 0) {
			LM_ERR("bad host name in URI <%s>\n", host);
			return E_BAD_ADDRESS;
		}
		/* success -- fix the first parameter to proxy now ! */

		a->u.data = proxy;
		return 0;
	} else {
		LM_ERR("called with parameter number different than {1,2}\n");
		return E_BUG;
	}
}

/* (char *$proto, char *$host:port) ==> (fparam, fparam)  */
static int fixup_proto_hostport2proxy(void **param, int param_no)
{
	int ret;

	ret = fix_param(FPARAM_AVP, param);
	if(ret <= 0)
		return ret;
	if(fix_param(FPARAM_STRING, param) != 0)
		return -1;
	return 0;
}


static int fixup_t_check_status(void **param, int param_no)
{
	int ret;

	ret = fix_param(FPARAM_PVS, param);
	if(ret <= 0)
		return ret;

	ret = fix_param(FPARAM_AVP, param);
	if(ret <= 0)
		return ret;

	ret = fix_param(FPARAM_SELECT, param);
	if(ret <= 0)
		return ret;

	if(fix_param(FPARAM_REGEX, param) != 0)
		return -1;
	return 0;
}


/***************************** init functions *****************************/
static int w_t_unref(struct sip_msg *foo, unsigned int flags, void *bar)
{
	return t_unref(foo);
}


static int script_init(struct sip_msg *foo, unsigned int flags, void *bar)
{
	/* we primarily reset all private memory here to make sure
	 * private values left over from previous message will
	 * not be used again */

	/* make sure the new message will not inherit previous
	   message's t_on_failure value
	   */
	t_on_failure(0);
	t_on_branch_failure(0);
	t_on_reply(0);
	t_on_branch(0);
	/* reset the kr status */
	reset_kr();
	/* set request mode so that multiple-mode actions know
	 * how to behave */
	set_route_type(REQUEST_ROUTE);
	lumps_are_cloned = 0;
	return 1;
}


static int mod_init(void)
{
	sr_kemi_eng_t *keng = NULL;

	DBG("TM - (sizeof cell=%ld, sip_msg=%ld) initializing...\n",
			(long)sizeof(struct cell), (long)sizeof(struct sip_msg));

	if(tm_exec_time_check_param > 0) {
		tm_exec_time_check = (unsigned long)tm_exec_time_check_param * 1000;
	}

	/* checking if we have sufficient bitmap capacity for given
	 * maximum number of  branches */
	if(sr_dst_max_branches + 1 > 31) {
		LM_CRIT("Too many max UACs for UAC branch_bm_t bitmap: %d\n",
				sr_dst_max_branches);
		return -1;
	}

	if(tm_rpc_response_list_init() < 0) {
		LM_ERR("failed to init rpc\n");
		return -1;
	}

	if(on_sl_reply_name.s != NULL && on_sl_reply_name.len > 0) {
		keng = sr_kemi_eng_get();
		if(keng == NULL) {
			goto_on_sl_reply = route_get(&onreply_rt, on_sl_reply_name.s);
			if(goto_on_sl_reply == -1) {
				LM_ERR("route get failed for on_sl_reply\n");
				return -1;
			}
		}
	}
	if(init_callid() < 0) {
		LM_CRIT("Error while initializing Call-ID generator\n");
		return -1;
	}

	/* building the hash table*/
	if(!init_hash_table()) {
		LM_ERR("initializing hash_table failed\n");
		return -1;
	}

	/* init static hidden values */
	init_t();

	if(tm_init_selects() == -1) {
		LM_ERR("select init failed\n");
		return -1;
	}

	/* the default timer values must be fixed-up before
	 * declaring the configuration (Miklos) */
	if(tm_init_timers() == -1) {
		LM_ERR("timer init failed\n");
		return -1;
	}

	/* the cancel branch flags must be fixed before declaring the
	 * configuration */
	if(cancel_b_flags_get(
			   &default_tm_cfg.cancel_b_flags, default_tm_cfg.cancel_b_flags)
			< 0) {
		LM_ERR("bad cancel branch method\n");
		return -1;
	}

#ifdef USE_DNS_FAILOVER
	/* Initialize code and counter  for other failover reply codes*/
	failover_reply_codes = (int **)shm_malloc(sizeof(unsigned int *));
	*failover_reply_codes = 0;
	failover_reply_codes_cnt = (int *)shm_malloc(sizeof(int));
	*failover_reply_codes_cnt = 0;
	if(failover_reply_codes_str.s && failover_reply_codes_str.len > 0) {
		if(t_failover_parse_reply_codes() < 0) {
			LM_ERR("failed to parse failover_reply_codes\n");
			return -1;
		}
	}

	if(default_tm_cfg.reparse_on_dns_failover && mhomed) {
		LM_WARN("reparse_on_dns_failover is enabled on a"
				" multihomed host -- check the readme of tm module!\n");
	}
#endif

	/* declare the configuration */
	if(cfg_declare(
			   "tm", tm_cfg_def, &default_tm_cfg, cfg_sizeof(tm), &tm_cfg)) {
		LM_ERR("failed to declare the configuration\n");
		return -1;
	}

	/* First tm_stat initialization function only allocates the top level stat
	 * structure in shared memory, the initialization will complete in child
	 * init with init_tm_stats_child when the final value of
	 * estimated_process_count is known
	 */
	if(init_tm_stats() < 0) {
		LM_CRIT("failed to init stats\n");
		return -1;
	}

	if(uac_init() == -1) {
		LM_ERR("uac_init failed\n");
		return -1;
	}

	if(init_tmcb_lists() != 1) {
		LM_CRIT("failed to init tmcb lists\n");
		return -1;
	}

	tm_init_tags();
	init_twrite_lines();
	if(init_twrite_sock() < 0) {
		LM_ERR("Unable to create socket\n");
		return -1;
	}

	/* register post-script clean-up function */
	if(register_script_cb(w_t_unref, POST_SCRIPT_CB | REQUEST_CB, 0) < 0) {
		LM_ERR("failed to register POST request callback\n");
		return -1;
	}
	if(register_script_cb(script_init, PRE_SCRIPT_CB | REQUEST_CB, 0) < 0) {
		LM_ERR("failed to register PRE request callback\n");
		return -1;
	}

	if(init_avp_params(fr_timer_param, fr_inv_timer_param) < 0) {
		LM_ERR("failed to process AVP params\n");
		return -1;
	}
	if((contacts_avp.len > 0) && (contact_flows_avp.len == 0)) {
		LM_ERR("contact_flows_avp param has not been defined\n");
		return -1;
	}

#ifdef WITH_EVENT_LOCAL_REQUEST
	goto_on_local_req = route_lookup(&event_rt, "tm:local-request");
	if(goto_on_local_req >= 0 && event_rt.rlist[goto_on_local_req] == 0)
		goto_on_local_req = -1; /* disable */
	if(goto_on_local_req >= 0 || tm_event_callback.len > 0)
		set_child_rpc_sip_mode();
#endif /* WITH_EVENT_LOCAL_REQUEST */
	if(goto_on_sl_reply && onreply_rt.rlist[goto_on_sl_reply] == 0)
		LM_WARN("empty/non existing on_sl_reply route\n");

#ifdef WITH_TM_CTX
	tm_ctx_init();
#endif
	tm_init = 1;
	return 0;
}

static int child_init(int rank)
{
	if(rank == PROC_INIT) {
		/* we must init stats when rank==PROC_INIT: after mod_init we know
		 * the exact number of processes and we must init the shared structure
		 * before any other process is starting (or else some new process
		 * might try to use the stats before the stats array is allocated) */
		if(init_tm_stats_child() < 0) {
			LM_ERR("Error while initializing tm statistics structures\n");
			return -1;
		}
	} else if(child_init_callid(rank) < 0) {
		/* don't init callid for PROC_INIT*/
		LM_ERR("Error while initializing Call-ID generator\n");
		return -2;
	}
	return 0;
}


/**************************** wrapper functions ***************************/

static int ki_t_get_status_code(sip_msg_t *msg)
{
	int scode = -1;
	int ret;
	tm_cell_t *t = NULL;

	/* first get the transaction */
	if(t_check(msg, 0) == -1)
		return -1;
	if((t = get_t()) == 0) {
		LM_ERR("cannot check status for a reply"
			   " which has no T-state established\n");
		goto error;
	}

	switch(get_route_type()) {
		case REQUEST_ROUTE:
			/* use the status of the last sent reply */
			scode = t->uas.status;
			break;

		case TM_ONREPLY_ROUTE:
		case CORE_ONREPLY_ROUTE:
			/* use the status of the current reply */
			scode = (int)msg->first_line.u.reply.statuscode;
			break;

		case FAILURE_ROUTE:
			/* use the status of the winning reply */
			ret = t_pick_branch(-1, 0, t, &scode);
			if(ret == -1) {
				/* t_pick_branch() returns error also when there are only
				 * blind UACs. Let us give it another chance including the
				 * blind branches. */
				LM_DBG("t_pick_branch returned error,"
					   " trying t_pick_branch_blind\n");
				ret = t_pick_branch_blind(t, &scode);
			}
			if(ret < 0) {
				LM_CRIT("BUG: t_pick_branch failed to get"
						" a final response in FAILURE_ROUTE\n");
				goto error;
			}
			break;

		case BRANCH_FAILURE_ROUTE:
			scode = t->uac[get_t_branch()].last_received;
			break;

		default:
			LM_ERR("unsupported route type %d\n", get_route_type());
			goto error;
	}

	LM_DBG("t status code is <%d>\n", scode);

	return (scode != 0) ? scode : -1;

error:
	return -1;
}

static int w_t_get_status_code(sip_msg_t *msg, char *p1, char *p2)
{
	return ki_t_get_status_code(msg);
}

static int ki_t_get_branch_index(sip_msg_t *msg)
{
	tm_cell_t *t = 0;
	tm_ctx_t *tcx = 0;
	int idx = T_BR_UNDEFINED;

	if(msg == NULL) {
		return -1;
	}

	/* statefull replies have the branch_index set */
	if(msg->first_line.type == SIP_REPLY) {
		tcx = tm_ctx_get();
		if(tcx != NULL) {
			idx = tcx->branch_index;
		}
	} else
		switch(route_type) {
			case BRANCH_ROUTE:
			case BRANCH_FAILURE_ROUTE:
				/* branch and branch_failure routes have their index set */
				tcx = tm_ctx_get();
				if(tcx != NULL) {
					idx = tcx->branch_index;
				}
				break;
			case REQUEST_ROUTE:
				/* take the branch number from the number of added branches */
				idx = nr_branches;
				break;
			case FAILURE_ROUTE:
				/* first get the transaction */
				t = get_t();
				if(t == NULL || t == T_UNDEFINED) {
					return -1;
				}
				/* add the currently added branches to the number of
			 * completed branches in the transaction
			 */
				idx = t->nr_of_outgoings + nr_branches;
				break;
		}
	return idx;
}

static int t_check_status(struct sip_msg *msg, char *p1, char *foo)
{
	regmatch_t pmatch;
	struct cell *t;
	char *status, *s = NULL;
	char backup;
	int lowest_status, n, ret;
	fparam_t *fp;
	regex_t *re0 = NULL;
	regex_t *re = NULL;
	str tmp;

	fp = (fparam_t *)p1;
	t = 0;
	/* first get the transaction */
	if(t_check(msg, 0) == -1)
		return -1;
	if((t = get_t()) == 0) {
		LM_ERR("cannot check status for a reply"
			   " which has no T-state established\n");
		goto error;
	}
	backup = 0;

	switch(fp->type) {
		case FPARAM_REGEX:
			re = fp->v.regex;
			break;

		default:
			/* AVP or select, get the value and compile the regex */
			if(get_str_fparam(&tmp, msg, fp) < 0)
				goto error;
			s = pkg_malloc(tmp.len + 1);
			if(s == NULL) {
				LM_ERR("Out of memory\n");
				goto error;
			}
			memcpy(s, tmp.s, tmp.len);
			s[tmp.len] = '\0';

			if((re0 = pkg_malloc(sizeof(regex_t))) == 0) {
				LM_ERR("No memory left\n");
				goto error;
			}

			if(regcomp(re0, s, REG_EXTENDED | REG_ICASE | REG_NEWLINE)) {
				LM_ERR("Bad regular expression '%s'\n", s);
				pkg_free(re0);
				re0 = NULL;
				goto error;
			}
			re = re0;
			break;
	}

	switch(get_route_type()) {
		case REQUEST_ROUTE:
			/* use the status of the last sent reply */
			status = int2str(t->uas.status, 0);
			break;

		case TM_ONREPLY_ROUTE:
		case CORE_ONREPLY_ROUTE:
			/* use the status of the current reply */
			status = msg->first_line.u.reply.status.s;
			backup = status[msg->first_line.u.reply.status.len];
			status[msg->first_line.u.reply.status.len] = 0;
			break;

		case FAILURE_ROUTE:
			/* use the status of the winning reply */
			ret = t_pick_branch(-1, 0, t, &lowest_status);
			if(ret == -1) {
				/* t_pick_branch() returns error also when there are only
				 * blind UACs. Let us give it another chance including the
				 * blind branches. */
				LM_DBG("t_pick_branch returned error,"
					   " trying t_pick_branch_blind\n");
				ret = t_pick_branch_blind(t, &lowest_status);
			}
			if(ret < 0) {
				LM_CRIT("BUG: t_pick_branch failed to get"
						" a final response in FAILURE_ROUTE\n");
				goto error;
			}
			status = int2str(lowest_status, 0);
			break;
		case BRANCH_FAILURE_ROUTE:
			status = int2str(t->uac[get_t_branch()].last_received, 0);
			break;
		default:
			LM_ERR("unsupported route type %d\n", get_route_type());
			goto error;
	}

	LM_DBG("checked status is <%s>\n", status);
	/* do the checking */
	n = regexec(re, status, 1, &pmatch, 0);

	if(backup)
		status[msg->first_line.u.reply.status.len] = backup;
	if(s)
		pkg_free(s);
	if(re0) {
		regfree(re0);
		pkg_free(re0);
	}

	if(unlikely(t && is_route_type(CORE_ONREPLY_ROUTE))) {
		/* t_check() above has the side effect of setting T and
		 * REFerencing T => we must unref and unset it.  */
		UNREF(t);
		set_t(T_UNDEFINED, T_BR_UNDEFINED);
	}
	if(n != 0)
		return -1;
	return 1;

error:
	if(unlikely(t && is_route_type(CORE_ONREPLY_ROUTE))) {
		/* t_check() above has the side effect of setting T and
		 * REFerencing T => we must unref and unset it.  */
		UNREF(t);
		set_t(T_UNDEFINED, T_BR_UNDEFINED);
	}
	if(s)
		pkg_free(s);
	if(re0) {
		regfree(re0);
		pkg_free(re0);
	}
	return -1;
}

static int ki_t_check_status(sip_msg_t *msg, str *sexp)
{
	regmatch_t pmatch;
	struct cell *t;
	char *status = NULL;
	char backup;
	int lowest_status, n, ret;
	regex_t re;

	/* first get the transaction */
	if(t_check(msg, 0) == -1)
		return -1;

	backup = 0;
	if((t = get_t()) == 0) {
		LM_ERR("cannot check status for a reply"
			   " which has no T-state established\n");
		goto error0;
	}

	memset(&re, 0, sizeof(regex_t));
	if(regcomp(&re, sexp->s, REG_EXTENDED | REG_ICASE | REG_NEWLINE)) {
		LM_ERR("Bad regular expression '%s'\n", sexp->s);
		goto error0;
	}

	switch(get_route_type()) {
		case REQUEST_ROUTE:
			/* use the status of the last sent reply */
			status = int2str(t->uas.status, 0);
			break;

		case TM_ONREPLY_ROUTE:
		case CORE_ONREPLY_ROUTE:
			/* use the status of the current reply */
			status = msg->first_line.u.reply.status.s;
			backup = status[msg->first_line.u.reply.status.len];
			status[msg->first_line.u.reply.status.len] = 0;
			break;

		case FAILURE_ROUTE:
			/* use the status of the winning reply */
			ret = t_pick_branch(-1, 0, t, &lowest_status);
			if(ret == -1) {
				/* t_pick_branch() returns error also when there are only
				 * blind UACs. Let us give it another chance including the
				 * blind branches. */
				LM_DBG("t_pick_branch returned error,"
					   " trying t_pick_branch_blind\n");
				ret = t_pick_branch_blind(t, &lowest_status);
			}
			if(ret < 0) {
				LM_CRIT("BUG: t_pick_branch failed to get"
						" a final response in FAILURE_ROUTE\n");
				goto error;
			}
			status = int2str(lowest_status, 0);
			break;
		case BRANCH_FAILURE_ROUTE:
			status = int2str(t->uac[get_t_branch()].last_received, 0);
			break;
		default:
			LM_ERR("unsupported route type %d\n", get_route_type());
			goto error;
	}

	LM_DBG("checked status is <%s>\n", status);
	/* do the checking */
	n = regexec(&re, status, 1, &pmatch, 0);

	if(backup)
		status[msg->first_line.u.reply.status.len] = backup;
	regfree(&re);

	if(unlikely(t && is_route_type(CORE_ONREPLY_ROUTE))) {
		/* t_check() above has the side effect of setting T and
		 * REFerencing T => we must unref and unset it.  */
		UNREF(t);
		set_t(T_UNDEFINED, T_BR_UNDEFINED);
	}
	if(n != 0)
		return -1;
	return 1;

error:
	regfree(&re);
error0:
	if(unlikely(t && is_route_type(CORE_ONREPLY_ROUTE))) {
		/* t_check() above has the side effect of setting T and
		 * REFerencing T => we must unref and unset it.  */
		UNREF(t);
		set_t(T_UNDEFINED, T_BR_UNDEFINED);
	}

	return -1;
}

static int w_t_check(struct sip_msg *msg, char *str, char *str2)
{
	return (t_check_msg(msg, 0) == 1) ? 1 : -1;
}

static int ki_t_lookup_request(struct sip_msg *msg)
{
	return (t_check_msg(msg, 0) == 1) ? 1 : -1;
}

static int ki_t_lookup_cancel_flags(sip_msg_t *msg, int flags)
{
	struct cell *ret;
	if(msg->REQ_METHOD == METHOD_CANCEL) {
		ret = t_lookupOriginalT(msg);
		LM_DBG("lookup_original: t_lookupOriginalT returned: %p\n", ret);
		if(ret != T_NULL_CELL) {
			/* If the parameter is set to 1, overwrite the message flags of
			 * the CANCEL with the flags of the INVITE */
			if(flags)
				msg->flags = ret->uas.request->flags;

			/* The cell is reffed by t_lookupOriginalT, but T is not set.
			   So we must unref it before returning. */
			UNREF(ret);
			return 1;
		}
	} else {
		LM_WARN("script error - t_lookup_cancel() called for non-CANCEL "
				"request\n");
	}
	return -1;
}

static int ki_t_lookup_cancel(sip_msg_t *msg)
{
	return ki_t_lookup_cancel_flags(msg, 0);
}

static int w_t_lookup_cancel(struct sip_msg *msg, char *str, char *str2)
{
	int i = 0;

	if(str) {
		if(get_int_fparam(&i, msg, (fparam_t *)str) < 0)
			return -1;
	}
	return ki_t_lookup_cancel_flags(msg, i);
}

inline static int str2proto(char *s, int len)
{
	if(len == 3 && !strncasecmp(s, "udp", 3))
		return PROTO_UDP;
	else if(len == 3 && !strncasecmp(s, "tcp", 3)) /* tcp&tls checks will be
														passed in getproto() */
		return PROTO_TCP;
	else if(len == 3 && !strncasecmp(s, "tls", 3))
		return PROTO_TLS;
	else if(len == 4 && !strncasecmp(s, "sctp", 4))
		return PROTO_SCTP;
	else if(len == 2 && !strncasecmp(s, "ws", 2))
		return PROTO_WS;
	else if(len == 3 && !strncasecmp(s, "wss", 3)) {
		LM_WARN("\"wss\" used somewhere...\n");
		return PROTO_WS;
	} else
		return PROTO_NONE;
}

inline static struct proxy_l *t_protoaddr2proxy(char *proto_par, char *addr_par)
{
	struct proxy_l *proxy = 0;
	avp_t *avp;
	avp_value_t val;
	int proto, port, err;
	str s;
	char *c;

	switch(((fparam_t *)proto_par)->type) {
		case FPARAM_AVP:
			if(!(avp = search_first_avp(((fparam_t *)proto_par)->v.avp.flags,
						 ((fparam_t *)proto_par)->v.avp.name, &val, 0))) {
				proto = PROTO_NONE;
			} else {
				if(avp->flags & AVP_VAL_STR) {
					proto = str2proto(val.s.s, val.s.len);
				} else {
					proto = val.n;
				}
			}
			break;

		case FPARAM_INT:
			proto = ((fparam_t *)proto_par)->v.i;
			break;
		case FPARAM_STRING:
			proto = str2proto(((fparam_t *)proto_par)->v.asciiz,
					strlen(((fparam_t *)proto_par)->v.asciiz));
			break;
		default:
			LM_ERR("Invalid proto parameter value in t_protoaddr2proxy\n");
			return 0;
	}


	switch(((fparam_t *)addr_par)->type) {
		case FPARAM_AVP:
			if(!(avp = search_first_avp(((fparam_t *)addr_par)->v.avp.flags,
						 ((fparam_t *)addr_par)->v.avp.name, &val, 0))) {
				s.len = 0;
			} else {
				if((avp->flags & AVP_VAL_STR) == 0) {
					LM_ERR("avp <%.*s> value is not string\n",
							((fparam_t *)addr_par)->v.avp.name.s.len,
							((fparam_t *)addr_par)->v.avp.name.s.s);
					return 0;
				}
				s = val.s;
			}
			break;

		case FPARAM_STRING:
			s.s = ((fparam_t *)addr_par)->v.asciiz;
			s.len = strlen(s.s);
			break;

		default:
			LM_ERR("Invalid addr parameter value in t_protoaddr2proxy\n");
			return 0;
	}

	port = 5060;
	if(s.len) {
		c = memchr(s.s, ':', s.len);
		if(c) {
			port = str2s(c + 1, s.len - (c - s.s + 1), &err);
			if(err != 0) {
				LM_ERR("bad port number <%.*s>\n", s.len, s.s);
				return 0;
			}
			s.len = c - s.s;
		}
	}
	if(!s.len) {
		LM_ERR("host name is empty\n");
		return 0;
	}
	proxy = mk_proxy(&s, port, proto);
	if(proxy == 0) {
		LM_ERR("bad host name in URI <%.*s>\n", s.len, s.s);
		return 0;
	}
	return proxy;
}

static int _w_t_forward_nonack(
		struct sip_msg *msg, struct proxy_l *proxy, int proto)
{
	struct cell *t;
	if(t_check(msg, 0) == -1) {
		LM_ERR("can't forward when no transaction was set up\n");
		return -1;
	}
	t = get_t();
	if(t && t != T_UNDEFINED) {
		if(msg->REQ_METHOD == METHOD_ACK) {
			LM_WARN("you don't really want to fwd hop-by-hop ACK\n");
			return -1;
		}
		return t_forward_nonack(t, msg, proxy, proto);
	} else {
		LM_DBG("no transaction found\n");
		return -1;
	}
}


static int w_t_forward_nonack(struct sip_msg *msg, char *proxy, char *foo)
{
	return _w_t_forward_nonack(msg, (struct proxy_l *)proxy, PROTO_NONE);
}


static int w_t_forward_nonack_uri(struct sip_msg *msg, char *foo, char *bar)
{
	return _w_t_forward_nonack(msg, 0, PROTO_NONE);
}


static int w_t_forward_nonack_udp(struct sip_msg *msg, char *proxy, char *foo)
{
	return _w_t_forward_nonack(msg, (struct proxy_l *)proxy, PROTO_UDP);
}


#ifdef USE_TCP
static int w_t_forward_nonack_tcp(struct sip_msg *msg, char *proxy, char *foo)
{
	return _w_t_forward_nonack(msg, (struct proxy_l *)proxy, PROTO_TCP);
}
#endif


#ifdef USE_TLS
static int w_t_forward_nonack_tls(struct sip_msg *msg, char *proxy, char *foo)
{
	return _w_t_forward_nonack(msg, (struct proxy_l *)proxy, PROTO_TLS);
}
#endif


#ifdef USE_SCTP
static int w_t_forward_nonack_sctp(struct sip_msg *msg, char *proxy, char *foo)
{
	return _w_t_forward_nonack(msg, (struct proxy_l *)proxy, PROTO_SCTP);
}
#endif


static int w_t_forward_nonack_to(
		struct sip_msg *p_msg, char *proto_par, char *addr_par)
{
	struct proxy_l *proxy;
	int r = -1;
	proxy = t_protoaddr2proxy(proto_par, addr_par);
	if(proxy) {
		r = _w_t_forward_nonack(p_msg, proxy, proxy->proto);
		free_proxy(proxy);
		pkg_free(proxy);
	}
	return r;
}

/**
 *
 */
static int ki_t_reply(sip_msg_t *msg, int code, str *reason)
{
	tm_cell_t *t = NULL;
	int ret = -1;

	if(msg->REQ_METHOD == METHOD_ACK) {
		LM_DBG("ACKs are not replied\n");
		return -1;
	}
	if(msg->msg_flags & FL_MSG_NOREPLY) {
		LM_INFO("message marked with no-reply flag\n");
		return -2;
	}

	if(t_check(msg, 0) == -1)
		return -1;
	t = get_t();
	if(!t) {
		LM_ERR("cannot send a t_reply to a message"
			   " for which no T-state has been established\n");
		return -1;
	}

	/* if called from reply_route, make sure that unsafe version
	 * is called; we are already in a mutex and another mutex in
	 * the safe version would lead to a deadlock
	 */
	t->flags |= T_ADMIN_REPLY;
	if(is_route_type(FAILURE_ROUTE)) {
		LM_DBG("t_reply_unsafe called from w_t_reply\n");
		ret = t_reply_str_unsafe(t, msg, (unsigned int)code, reason);
	} else if(is_route_type(REQUEST_ROUTE)) {
		ret = t_reply_str(t, msg, (unsigned int)code, reason);
	} else if(is_route_type(ONREPLY_ROUTE)) {
		if(likely(t->uas.request)) {
			if(is_route_type(CORE_ONREPLY_ROUTE))
				ret = t_reply_str(
						t, t->uas.request, (unsigned int)code, reason);
			else
				ret = t_reply_str_unsafe(
						t, t->uas.request, (unsigned int)code, reason);
		} else
			ret = -1;
		/* t_check() above has the side effect of setting T and
		 * REFerencing T => we must unref and unset it.
		 * Note: this is needed only in the CORE_ONREPLY_ROUTE and not also in
		 * the TM_ONREPLY_ROUTE.
		 */
		if(is_route_type(CORE_ONREPLY_ROUTE)) {
			UNREF(t);
			set_t(T_UNDEFINED, T_BR_UNDEFINED);
		}
	} else {
		LM_CRIT("w_t_reply entered in unsupported mode\n");
		ret = -1;
	}

	return ret;
}

static int w_t_reply(struct sip_msg *msg, char *p1, char *p2)
{
	int code;
	str reason;

	if(msg->REQ_METHOD == METHOD_ACK) {
		LM_DBG("ACKs are not replied\n");
		return -1;
	}

	if(get_int_fparam(&code, msg, (fparam_t *)p1) < 0) {
		code = cfg_get(tm, tm_cfg, default_code);
	}

	if(get_str_fparam(&reason, msg, (fparam_t *)p2) < 0) {
		reason.s = cfg_get(tm, tm_cfg, default_reason);
		reason.len = strlen(reason.s);
	}

	return ki_t_reply(msg, code, &reason);
}

/* wrapper for inter-module api */
int w_t_reply_wrp(struct sip_msg *msg, unsigned int code, char *txt)
{
	str reason;

	reason.s = txt;
	reason.len = strlen(reason.s);

	return ki_t_reply(msg, code, &reason);
}

/**
 * kemi function to send reply based on internal error code
 */
int ki_t_reply_error(sip_msg_t *msg)
{
	char err_buffer[128];
	str reason;
	int sip_err;
	int ret;

	if(msg->msg_flags & FL_FINAL_REPLY) {
		LM_INFO("message marked with final-reply flag\n");
		return -2;
	}

	ret = err2reason_phrase(
			prev_ser_error, &sip_err, err_buffer, sizeof(err_buffer), "TM");
	if(ret > 0) {
		reason.s = err_buffer;
		reason.len = strlen(reason.s);
		return ki_t_reply(msg, sip_err, &reason);
	} else {
		LM_ERR("failed to get internal error reason phrase\n");
		return -1;
	}
}


/**
 * config function to send reply based on internal error code
 */
static int w_t_reply_error(sip_msg_t *msg, char *p1, char *p2)
{
	return ki_t_reply_error(msg);
}

/**
 *
 */
static int ki_t_send_reply(sip_msg_t *msg, int code, str *reason)
{
	int ret;

	ret = t_newtran(msg);
	if(ret == 0) {
		LM_NOTICE("transaction already in process %p\n", get_t());
	}

	return ki_t_reply(msg, code, reason);
}

/**
 *
 */
static int w_t_send_reply(struct sip_msg *msg, char *p1, char *p2)
{
	int code;
	str reason;

	if(msg->REQ_METHOD == METHOD_ACK) {
		LM_DBG("ACKs are not replied\n");
		return -1;
	}

	if(get_int_fparam(&code, msg, (fparam_t *)p1) < 0) {
		code = cfg_get(tm, tm_cfg, default_code);
	}

	if(get_str_fparam(&reason, msg, (fparam_t *)p2) < 0) {
		reason.s = cfg_get(tm, tm_cfg, default_reason);
		reason.len = strlen(reason.s);
	}

	return ki_t_send_reply(msg, code, &reason);
}

/**
 *
 */
static int t_release(sip_msg_t *msg)
{
	struct cell *t;
	int ret;

	if(get_route_type() != REQUEST_ROUTE) {
		LM_INFO("invalid usage - not in request route\n");
		return -1;
	}

	if(t_check(msg, 0) == -1)
		return -1;
	t = get_t();
	if(t && t != T_UNDEFINED) {
		ret = t_release_transaction(t);
		t_unref(msg);
		return ret;
	}
	return 1;
}

static int w_t_release(struct sip_msg *msg, char *str, char *str2)
{
	return t_release(msg);
}

static int ki_t_retransmit_reply(struct sip_msg *p_msg)
{
	struct cell *t;

	if(t_check(p_msg, 0) == -1)
		return 1;
	t = get_t();
	if(t) {
		if(p_msg->REQ_METHOD == METHOD_ACK) {
			LM_WARN("ACKs transmit_replies not replied\n");
			return -1;
		}
		return t_retransmit_reply(t);
	} else
		return -1;
}

static int w_t_retransmit_reply(struct sip_msg *p_msg, char *foo, char *bar)
{
	return ki_t_retransmit_reply(p_msg);
}

static int w_t_newtran(struct sip_msg *p_msg, char *foo, char *bar)
{
	/* t_newtran returns 0 on error (negative value means
	 * 'transaction exists' */
	int ret;
	ret = t_newtran(p_msg);
	if(ret == E_SCRIPT) {
		LM_NOTICE("transaction already in process %p\n", get_t());
	}
	return ret;
}


static int w_t_on_failure(struct sip_msg *msg, char *go_to, char *foo)
{
	t_on_failure((unsigned int)(long)go_to);
	return 1;
}


static int w_t_on_branch_failure(struct sip_msg *msg, char *go_to, char *foo)
{
	t_on_branch_failure((unsigned int)(long)go_to);
	return 1;
}


static int w_t_on_branch(struct sip_msg *msg, char *go_to, char *foo)
{
	t_on_branch((unsigned int)(long)go_to);
	return 1;
}


static int w_t_on_reply(struct sip_msg *msg, char *go_to, char *foo)
{
	t_on_reply((unsigned int)(long)go_to);
	return 1;
}


static int t_is_set(sip_msg_t *msg, str *target)
{
	int r;
	tm_cell_t *t = NULL;

	r = 0;
	t = get_t();
	if(t == T_UNDEFINED)
		t = NULL;

	switch(target->s[0]) {
		case 'b':
			if(t == NULL)
				r = get_on_branch();
			else
				r = t->on_branch;
			break;
		case 'f':
			if(t == NULL)
				r = get_on_failure();
			else
				r = t->on_failure;
			break;
		case 'o':
			if(t == NULL)
				r = get_on_reply();
			else
				r = t->on_reply;
			break;
	}
	if(r)
		return 1;
	return -1;
}

static int w_t_is_set(struct sip_msg *msg, char *target, char *foo)
{
	str s = STR_NULL;

	s.s = target;
	return t_is_set(msg, &s);
}

static int fixup_t_is_set(void **param, int param_no)
{
	int len;
	if(param_no == 1) {
		len = strlen((char *)*param);
		if((len == 13 && strncmp((char *)*param, "failure_route", 13) == 0)
				|| (len == 13
						&& strncmp((char *)*param, "onreply_route", 13) == 0)
				|| (len == 12
						&& strncmp((char *)*param, "branch_route", 12) == 0)) {
			return 0;
		}

		LM_ERR("invalid parameter value: %s\n", (char *)*param);
		return 1;
	}
	return 0;
}

static int _w_t_relay_to(
		struct sip_msg *p_msg, struct proxy_l *proxy, int force_proto)
{
	struct cell *t;
	int res;

	if(is_route_type(FAILURE_ROUTE | BRANCH_FAILURE_ROUTE)) {
		t = get_t();
		if(!t || t == T_UNDEFINED) {
			LM_CRIT("undefined T\n");
			return -1;
		}
		res = t_forward_nonack(t, p_msg, proxy, force_proto);
		if(res <= 0) {
			if(res != E_CFG) {
				LM_ERR("t_forward_noack failed\n");
				if(get_kr() == REQ_ERR_DELAYED) {
					p_msg->msg_flags |= FL_DELAYED_REPLY;
				}
			}
			/* let us save the error code, we might need it later
			 * when the failure_route has finished (Miklos) */
			tm_error = ser_error;
			return -1;
		}
		return 1;
	}
	if(is_route_type(REQUEST_ROUTE)) {
		res = t_relay_to(p_msg, proxy, force_proto, 0 /* no replication */);
		if(res < 0) {
			if(get_kr() == REQ_ERR_DELAYED) {
				p_msg->msg_flags |= FL_DELAYED_REPLY;
			}
		}
		return res;
	}
	LM_CRIT("unsupported route type: %d\n", get_route_type());
	return 0;
}


static int w_t_relay_to_udp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l * expected */
		char *_foo /* nothing expected */)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)proxy, PROTO_UDP);
}

/* forward to uri, but force udp as transport */
static int w_t_relay_to_udp_uri(struct sip_msg *p_msg, char *_foo, char *_bar)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)0, PROTO_UDP);
}


#ifdef USE_TCP
static int w_t_relay_to_tcp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l* */
		char *_foo /* nothing expected */)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)proxy, PROTO_TCP);
}

/* forward to uri, but force tcp as transport */
static int w_t_relay_to_tcp_uri(struct sip_msg *p_msg, char *_foo, char *_bar)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)0, PROTO_TCP);
}
#endif


#ifdef USE_TLS
static int w_t_relay_to_tls(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l* expected */
		char *_foo /* nothing expected */)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)proxy, PROTO_TLS);
}

/* forward to uri, but force tls as transport */
static int w_t_relay_to_tls_uri(struct sip_msg *p_msg, char *_foo, char *_bar)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)0, PROTO_TLS);
}
#endif


#ifdef USE_SCTP
static int w_t_relay_to_sctp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l* */
		char *_foo /* nothing expected */)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)proxy, PROTO_SCTP);
}

/* forward to uri, but force tcp as transport */
static int w_t_relay_to_sctp_uri(struct sip_msg *p_msg, char *_foo, char *_bar)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)0, PROTO_SCTP);
}
#endif


static int w_t_relay_to_avp(
		struct sip_msg *p_msg, char *proto_par, char *addr_par)
{
	struct proxy_l *proxy;
	int r = -1;

	proxy = t_protoaddr2proxy(proto_par, addr_par);
	if(proxy) {
		r = _w_t_relay_to(p_msg, proxy, PROTO_NONE);
		free_proxy(proxy);
		pkg_free(proxy);
	}
	return r;
}

int t_replicate_uri(struct sip_msg *msg, str *suri)
{
	struct proxy_l *proxy = NULL;
	struct sip_uri turi;
	int r = -1;

	if(suri != NULL && suri->s != NULL && suri->len > 0) {
		memset(&turi, 0, sizeof(struct sip_uri));
		if(parse_uri(suri->s, suri->len, &turi) != 0) {
			LM_ERR("bad replicate SIP address!\n");
			return -1;
		}

		proxy = mk_proxy(&turi.host, turi.port_no, turi.proto);
		if(proxy == 0) {
			LM_ERR("cannot create proxy from URI <%.*s>\n", suri->len, suri->s);
			return -1;
		}

		r = t_replicate(msg, proxy, proxy->proto);
		free_proxy(proxy);
		pkg_free(proxy);
	} else {
		r = t_replicate(msg, NULL, 0);
	}
	return r;
}

static int w_t_replicate_uri(struct sip_msg *msg,
		char *uri, /* sip uri as string or variable */
		char *_foo /* nothing expected */)
{
	str suri;

	if(uri == NULL)
		return t_replicate_uri(msg, NULL);

	if(fixup_get_svalue(msg, (gparam_p)uri, &suri) != 0) {
		LM_ERR("invalid replicate uri parameter\n");
		return -1;
	}
	return t_replicate_uri(msg, &suri);
}

static int w_t_replicate(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */)
{
	return t_replicate(p_msg, (struct proxy_l *)proxy, p_msg->rcv.proto);
}

static int w_t_replicate_udp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */)
{
	return t_replicate(p_msg, (struct proxy_l *)proxy, PROTO_UDP);
}


#ifdef USE_TCP
static int w_t_replicate_tcp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */)
{
	return t_replicate(p_msg, (struct proxy_l *)proxy, PROTO_TCP);
}
#endif


#ifdef USE_TLS
static int w_t_replicate_tls(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */)
{
	return t_replicate(p_msg, (struct proxy_l *)proxy, PROTO_TLS);
}
#endif


#ifdef USE_SCTP
static int w_t_replicate_sctp(struct sip_msg *p_msg,
		char *proxy, /* struct proxy_l *proxy expected */
		char *_foo /* nothing expected */)
{
	return t_replicate(p_msg, (struct proxy_l *)proxy, PROTO_SCTP);
}
#endif


static int w_t_replicate_to(
		struct sip_msg *p_msg, char *proto_par, char *addr_par)
{
	struct proxy_l *proxy;
	int r = -1;
	proxy = t_protoaddr2proxy(proto_par, addr_par);
	if(proxy) {
		r = t_replicate(p_msg, proxy, proxy->proto);
		free_proxy(proxy);
		pkg_free(proxy);
	}
	return r;
}

static int w_t_relay(struct sip_msg *p_msg, char *_foo, char *_bar)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)0, PROTO_NONE);
}


/* like t_relay but use the specified destination and port and the same proto
 * as the received msg */
static int w_t_relay2(struct sip_msg *p_msg, char *proxy, char *_foo)
{
	return _w_t_relay_to(p_msg, (struct proxy_l *)proxy, p_msg->rcv.proto);
}


/* relays CANCEL at the beginning of the script */
static int w_t_relay_cancel(struct sip_msg *p_msg, char *_foo, char *_bar)
{
	if(p_msg->REQ_METHOD != METHOD_CANCEL)
		return 1;

	/* it makes no sense to use this function without reparse_invite=1 */
	if(!cfg_get(tm, tm_cfg, reparse_invite))
		LM_WARN("probably used with wrong configuration,"
				" check the readme for details\n");

	return t_relay_cancel(p_msg);
}

/* set fr_inv_timeout & or fr_timeout; 0 means: use the default value */
static int t_set_fr_all(struct sip_msg *msg, char *p1, char *p2)
{
	int fr, fr_inv;

	if(get_int_fparam(&fr_inv, msg, (fparam_t *)p1) < 0)
		return -1;
	if(p2) {
		if(get_int_fparam(&fr, msg, (fparam_t *)p2) < 0)
			return -1;
	} else {
		fr = 0;
	}

	return t_set_fr(msg, fr_inv, fr);
}

static int t_set_fr_inv(struct sip_msg *msg, char *fr_inv, char *foo)
{
	return t_set_fr_all(msg, fr_inv, (char *)0);
}

static int ki_t_set_fr(struct sip_msg *msg, int fr_inv, int fr)
{
	return t_set_fr(msg, fr_inv, fr);
}

static int ki_t_set_fr_inv(struct sip_msg *msg, int fr_inv)
{
	return t_set_fr(msg, fr_inv, 0);
}

/* reset fr_timer and fr_inv_timer to the default values */
static int w_t_reset_fr(struct sip_msg *msg, char *foo, char *bar)
{
	return t_reset_fr();
}

static int ki_t_reset_fr(struct sip_msg *msg)
{
	return t_reset_fr();
}

static int ki_t_set_retr(sip_msg_t *msg, int t1, int t2)
{
	return t_set_retr(msg, t1, t2);
}

/* set retr. intervals per transaction; 0 means: use the default value */
static int w_t_set_retr(struct sip_msg *msg, char *p1, char *p2)
{
	int t1, t2;

	if(get_int_fparam(&t1, msg, (fparam_t *)p1) < 0)
		return -1;
	if(p2) {
		if(get_int_fparam(&t2, msg, (fparam_t *)p2) < 0)
			return -1;
	} else {
		t2 = 0;
	}
	return ki_t_set_retr(msg, t1, t2);
}

/* reset retr. t1 and t2 to the default values */
int ki_t_reset_retr(sip_msg_t *msg)
{
	return t_reset_retr();
}

int w_t_reset_retr(struct sip_msg *msg, char *foo, char *bar)
{
	return ki_t_reset_retr(msg);
}

/* set maximum transaction lifetime for inv & noninv */
static int w_t_set_max_lifetime(struct sip_msg *msg, char *p1, char *p2)
{
	int t1, t2;

	if(get_int_fparam(&t1, msg, (fparam_t *)p1) < 0)
		return -1;
	if(p2) {
		if(get_int_fparam(&t2, msg, (fparam_t *)p2) < 0)
			return -1;
	} else {
		t2 = 0;
	}
	return t_set_max_lifetime(msg, t1, t2);
}

static int ki_t_set_max_lifetime(sip_msg_t *msg, int t1, int t2)
{
	return t_set_max_lifetime(msg, t1, t2);
}

/* reset maximum invite/non-invite lifetime to the default value */
int w_t_reset_max_lifetime(struct sip_msg *msg, char *foo, char *bar)
{
	return t_reset_max_lifetime();
}

int ki_t_reset_max_lifetime(sip_msg_t *msg)
{
	return t_reset_max_lifetime();
}


/**
 * helper macro, builds a function for setting a cell flag from the script.
 * e.g. W_T_SET_FLAG_GEN_FUNC(t_set_foo, T_FOO) =>
 * static int t_set_foo(struct sip_msg* msg, char*, char* )
 * that will expect fparam as first param and will set or reset T_FOO
 * in the current or next to be created transaction. */
#define T_SET_FLAG_GEN_FUNC(fname, T_FLAG_NAME)                                     \
	static int fname(sip_msg_t *msg, int state)                                     \
	{                                                                               \
		struct cell *t;                                                             \
		unsigned int set_flags;                                                     \
		unsigned int reset_flags;                                                   \
                                                                                    \
		t = get_t();                                                                \
		/* in REPLY_ROUTE and FAILURE_ROUTE T will be set to current transaction; \
	 * in REQUEST_ROUTE T will be set only if the transaction was already  \
	 * created; if not -> use the static variables */ \
		if(!t || t == T_UNDEFINED) {                                                \
			set_flags = get_msgid_val(user_cell_set_flags, msg->id, int);           \
			reset_flags = get_msgid_val(user_cell_reset_flags, msg->id, int);       \
			if(state) {                                                             \
				/* set */                                                           \
				set_flags |= T_FLAG_NAME;                                           \
				reset_flags &= ~T_FLAG_NAME;                                        \
			} else {                                                                \
				/* reset */                                                         \
				set_flags &= ~T_FLAG_NAME;                                          \
				reset_flags |= T_FLAG_NAME;                                         \
			}                                                                       \
			set_msgid_val(user_cell_set_flags, msg->id, int, set_flags);            \
			set_msgid_val(user_cell_reset_flags, msg->id, int, reset_flags);        \
		} else {                                                                    \
			if(state)                                                               \
				t->flags |= T_FLAG_NAME;                                            \
			else                                                                    \
				t->flags &= ~T_FLAG_NAME;                                           \
		}                                                                           \
		return 1;                                                                   \
	}

#define W_T_SET_FLAG_GEN_FUNC(fname, T_FLAG_NAME)            \
	static int w_##fname(sip_msg_t *msg, char *p1, char *p2) \
	{                                                        \
		int state;                                           \
		if(get_int_fparam(&state, msg, (fparam_t *)p1) < 0)  \
			return -1;                                       \
		return fname(msg, state);                            \
	}

/* set automatically sending 100 replies on/off for the current or
 * next to be created transaction */
T_SET_FLAG_GEN_FUNC(t_set_auto_inv_100, T_AUTO_INV_100)

W_T_SET_FLAG_GEN_FUNC(t_set_auto_inv_100, T_AUTO_INV_100)


/* set 6xx handling for the current or next to be created transaction */
T_SET_FLAG_GEN_FUNC(t_set_disable_6xx, T_DISABLE_6xx)

W_T_SET_FLAG_GEN_FUNC(t_set_disable_6xx, T_DISABLE_6xx)


/* disable dns failover for the current transaction */
T_SET_FLAG_GEN_FUNC(t_set_disable_failover, T_DISABLE_FAILOVER)

W_T_SET_FLAG_GEN_FUNC(t_set_disable_failover, T_DISABLE_FAILOVER)


/* disable/enable e2e cancel reason copy for the current transaction */
T_SET_FLAG_GEN_FUNC(t_set_no_e2e_cancel_reason, T_NO_E2E_CANCEL_REASON)

W_T_SET_FLAG_GEN_FUNC(t_set_no_e2e_cancel_reason, T_NO_E2E_CANCEL_REASON)


/* disable internal negative reply for the current transaction */
T_SET_FLAG_GEN_FUNC(t_set_disable_internal_reply, T_DISABLE_INTERNAL_REPLY)

W_T_SET_FLAG_GEN_FUNC(t_set_disable_internal_reply, T_DISABLE_INTERNAL_REPLY)


/* FAILURE_ROUTE and BRANCH_FAILURE_ROUTE only,
 * returns true if the choosed "failure" branch failed because of a timeout,
 * -1 otherwise */
int t_branch_timeout(sip_msg_t *msg)
{
	switch(get_route_type()) {
		case FAILURE_ROUTE:
		case BRANCH_FAILURE_ROUTE:
			return (msg->msg_flags & FL_TIMEOUT) ? 1 : -1;
		default:
			LM_ERR("unsupported route type %d\n", get_route_type());
	}
	return -1;
}


/* script function, FAILURE_ROUTE and BRANCH_FAILURE_ROUTE only,
 * returns true if the choosed "failure" branch failed because of a timeout,
 * -1 otherwise */
int w_t_branch_timeout(sip_msg_t *msg, char *foo, char *bar)
{
	return t_branch_timeout(msg);
}

/* FAILURE_ROUTE and BRANCH_FAILURE_ROUTE only,
 * returns true if the choosed "failure" branch ever received a reply,
 * -1 otherwise */
int t_branch_replied(sip_msg_t *msg)
{
	switch(get_route_type()) {
		case FAILURE_ROUTE:
		case BRANCH_FAILURE_ROUTE:
			return (msg->msg_flags & FL_REPLIED) ? 1 : -1;
		default:
			LM_ERR("unsupported route type %d\n", get_route_type());
	}
	return -1;
}


/* script function, FAILURE_ROUTE and BRANCH_FAILURE_ROUTE only,
 * returns true if the choosed "failure" branch ever received a reply,
 * -1 otherwise */
int w_t_branch_replied(sip_msg_t *msg, char *foo, char *bar)
{
	return t_branch_replied(msg);
}

/* script function, returns: 1 if the transaction was canceled, -1 if not */
int t_is_canceled(struct sip_msg *msg)
{
	struct cell *t;
	int ret;

	if(t_check(msg, 0) == -1)
		return -1;
	t = get_t();
	if((t == 0) || (t == T_UNDEFINED)) {
		LM_ERR("cannot check a message"
			   " for which no T-state has been established\n");
		ret = -1;
	} else {
		ret = (t->flags & T_CANCELED) ? 1 : -1;
	}
	return ret;
}

static int w_t_is_canceled(sip_msg_t *msg, char *foo, char *bar)
{
	return t_is_canceled(msg);
}

/* returns: 1 if the transaction is currently suspended, -1 if not */
int t_is_retr_async_reply(sip_msg_t *msg)
{
	struct cell *t;
	int ret;

	if(t_check(msg, 0) == -1)
		return -1;
	t = get_t();
	if((t == 0) || (t == T_UNDEFINED)) {
		LM_ERR("cannot check a message"
			   " for which no T-state has been established\n");
		ret = -1;
	} else {
		LM_DBG("TRANSACTION FLAGS IS %d\n", t->flags);
		ret = (t->flags & T_ASYNC_SUSPENDED) ? 1 : -1;
	}
	return ret;
}

/* script function, returns: 1 if the transaction is currently suspended,
 * -1 if not */
static int w_t_is_retr_async_reply(sip_msg_t *msg, char *foo, char *bar)
{
	return t_is_retr_async_reply(msg);
}

/* returns: 1 if the transaction lifetime interval has already elapsed, -1 if not */
int t_is_expired(sip_msg_t *msg)
{
	struct cell *t;
	int ret;

	if(t_check(msg, 0) == -1)
		return -1;
	t = get_t();
	if((t == 0) || (t == T_UNDEFINED)) {
		LM_ERR("cannot check a message"
			   " for which no T-state has been established\n");
		ret = -1;
	} else {
		ret = (TICKS_GT(t->end_of_life, get_ticks_raw())) ? -1 : 1;
	}
	return ret;
}

/* script function, returns: 1 if the transaction lifetime interval
 * has already elapsed, -1 if not */
int w_t_is_expired(sip_msg_t *msg, char *foo, char *bar)
{
	return t_is_expired(msg);
}

/* returns: 1 if any of the branches did timeout, -1 if not */
int t_any_timeout(sip_msg_t *msg)
{
	struct cell *t;
	int r;

	if(t_check(msg, 0) == -1)
		return -1;
	t = get_t();
	if((t == 0) || (t == T_UNDEFINED)) {
		LM_ERR("cannot check a message"
			   " for which no T-state has been established\n");
		return -1;
	} else {
		for(r = 0; r < t->nr_of_outgoings; r++) {
			if(t->uac[r].request.flags & F_RB_TIMEOUT)
				return 1;
		}
	}
	return -1;
}


/* script function, returns: 1 if any of the branches did timeout, -1 if not */
int w_t_any_timeout(sip_msg_t *msg, char *foo, char *bar)
{
	return t_any_timeout(msg);
}

/* returns: 1 if any of the branches received at leat one
 * reply, -1 if not */
int t_any_replied(sip_msg_t *msg)
{
	struct cell *t;
	int r;

	if(t_check(msg, 0) == -1)
		return -1;
	t = get_t();
	if((t == 0) || (t == T_UNDEFINED)) {
		LM_ERR("cannot check a message"
			   " for which no T-state has been established\n");
		return -1;
	} else {
		for(r = 0; r < t->nr_of_outgoings; r++) {
			if(t->uac[r].request.flags & F_RB_REPLIED)
				return 1;
		}
	}
	return -1;
}


/* script function, returns: 1 if any of the branches received at leat one
 * reply, -1 if not */
int w_t_any_replied(sip_msg_t *msg, char *foo, char *bar)
{
	return t_any_replied(msg);
}


/* returns: 1 if any of the branches received the
 *  reply code "status" */
int t_grep_status(sip_msg_t *msg, int code)
{
	struct cell *t;
	int r;

	if(t_check(msg, 0) == -1)
		return -1;
	t = get_t();
	if((t == 0) || (t == T_UNDEFINED)) {
		LM_ERR("cannot check a message"
			   " for which no T-state has been established\n");
		return -1;
	} else {
		for(r = 0; r < t->nr_of_outgoings; r++) {
			if((t->uac[r].last_received == code)
					&& (t->uac[r].request.flags & F_RB_REPLIED))
				return 1;
		}
	}
	return -1;
}


/* script function, returns: 1 if any of the branches received the
 *  reply code "status" */
int w_t_grep_status(struct sip_msg *msg, char *status, char *bar)
{
	int code;

	if(get_int_fparam(&code, msg, (fparam_t *)status) < 0)
		return -1;

	return t_grep_status(msg, code);
}

/* drop all the existing replies in failure_route to make sure
 * that none of them is picked up again */
static int t_drop_replies_helper(sip_msg_t *msg, char *mode)
{
	if(mode == NULL)
		t_drop_replies(1);
	else if(*mode == 'n')
		t_drop_replies(0);
	else if(*mode == 'l')
		t_drop_replies(2);
	else
		t_drop_replies(1);
	return 1;
}

static int w_t_drop_replies(struct sip_msg *msg, char *mode, char *bar)
{
	return t_drop_replies_helper(msg, mode);
}

static int ki_t_drop_replies(sip_msg_t *msg, str *mode)
{
	return t_drop_replies_helper(msg, (mode) ? mode->s : NULL);
}

static int ki_t_drop_replies_all(sip_msg_t *msg)
{
	return t_drop_replies_helper(msg, NULL);
}

/* save the message lumps after t_newtran() but before t_relay() */
static int ki_t_save_lumps(sip_msg_t *msg)
{
	struct cell *t;

	if(is_route_type(REQUEST_ROUTE)) {
		t = get_t();
		if(!t || t == T_UNDEFINED) {
			LM_ERR("transaction has not been created yet\n");
			return -1;
		}

		if(save_msg_lumps(t->uas.request, msg)) {
			LM_ERR("failed to save the message lumps\n");
			return -1;
		}
	} /* else nothing to do, the lumps have already been saved */
	return 1;
}

static int w_t_save_lumps(struct sip_msg *msg, char *foo, char *bar)
{
	return ki_t_save_lumps(msg);
}


/** script function, check if a msg is assoc. to a transaction.
 * @return -1 (not), 1 (reply, e2e ack or cancel for an existing transaction),
 *          0 (request retransmission, ack to negative reply or ack to local
 *           transaction)
 * Note: the e2e ack matching works only for local e2e acks or for
 *       transactions with E2EACK* callbacks installed (but even in this
 *       case matching E2EACKs on proxied transaction is not entirely
 *       reliable: if the ACK  is delayed the proxied transaction might
 *       be already deleted when it reaches the proxy (wait_timeout))
 */
int t_check_trans(struct sip_msg *msg)
{
	struct cell *t;
	int branch;
	int ret;

	/* already processing a T */
	if(is_route_type(FAILURE_ROUTE) || is_route_type(BRANCH_ROUTE)
			|| is_route_type(BRANCH_FAILURE_ROUTE)
			|| is_route_type(TM_ONREPLY_ROUTE)) {
		return 1;
	}

	if(msg->first_line.type == SIP_REPLY) {
		branch = 0;
		ret = (t_check_msg(msg, &branch) == 1) ? 1 : -1;
		tm_ctx_set_branch_index(branch);
		return ret;
	} else if(msg->REQ_METHOD == METHOD_CANCEL) {
		return w_t_lookup_cancel(msg, 0, 0);
	} else {
		switch(t_check_msg(msg, 0)) {
			case -2: /* possible e2e ack */
				return 1;
			case 1: /* found */
				t = get_t();
				if(t == NULL || t == T_UNDEFINED) {
					LM_WARN("unexpected transaction value\n");
					return -1;
				}
				if(msg->REQ_METHOD == METHOD_ACK) {
					/* ack to neg. reply  or ack to local trans.
					 * => process it and end the script */
					/* - there's no way to distinguish here
					 * between acks to local trans. and neg. acks */
					if(unlikely(has_tran_tmcbs(t, TMCB_ACK_NEG_IN)))
						run_trans_callbacks(
								TMCB_ACK_NEG_IN, t, msg, 0, msg->REQ_METHOD);
					t_release_transaction(t);
				} else {
					/* is a retransmission */
					if(unlikely(has_tran_tmcbs(t, TMCB_REQ_RETR_IN)))
						run_trans_callbacks(
								TMCB_REQ_RETR_IN, t, msg, 0, msg->REQ_METHOD);
					t_retransmit_reply(t);
				}
				/* no need for UNREF(t); set_t(0) - the end-of-script
				 * t_unref callback will take care of them */
				return 0; /* exit from the script */
		}
		/* not found or error */
	}
	return -1;
}

static int w_t_check_trans(struct sip_msg *msg, char *foo, char *bar)
{
	return t_check_trans(msg);
}

static int hexatoi(str *s, unsigned int *result)
{
	int i, xv, fact;

	/* more than 32bit hexa? */
	if(s->len > 8)
		return -1;

	*result = 0;
	fact = 1;

	for(i = s->len - 1; i >= 0; i--) {
		xv = hex2int(s->s[i]);
		if(xv < 0)
			return -1;

		*result += (xv * fact);
		fact *= 16;
	}
	return 0;
}

static int fixup_t_relay_to(void **param, int param_no)
{

	int port;
	int proto;
	unsigned int flags;
	struct proxy_l *proxy;
	action_u_t *a;
	str s;
	str host;

	s.s = (char *)*param;
	s.len = strlen(s.s);
	LM_DBG("fixing (%s, %d)\n", s.s, param_no);
	if(param_no == 1) {
		a = fixup_get_param(param, param_no, 2);
		if(a == NULL) {
			LM_CRIT("server error for parameter <%s>\n", s.s);
			return E_UNSPEC;
		}
		if(a->u.string != NULL) {
			/* second parameter set, first should be proxy addr */
			if(parse_phostport(s.s, &host.s, &host.len, &port, &proto) != 0) {
				LM_CRIT("invalid proxy addr parameter <%s>\n", s.s);
				return E_UNSPEC;
			}

			proxy = mk_proxy(&host, port, proto);
			if(proxy == 0) {
				LM_ERR("failed to build proxy structure for <%.*s>\n", host.len,
						host.s);
				return E_UNSPEC;
			}
			*(param) = proxy;
			return 0;
		} else {
			/* no second parameter, then is proxy addr or flags */
			flags = 0;
			if(s.len > 2 && s.s[0] == '0' && s.s[1] == 'x') {
				s.s += 2;
				s.len -= 2;
				if(hexatoi(&s, &flags) < 0) {
					LM_CRIT("invalid hexa flags <%s>\n", s.s);
					return E_UNSPEC;
				}
				a->u.data = (void *)(unsigned long int)flags;
				*(param) = 0;
				return 0;
			} else {
				if(str2int(&s, &flags) == 0) {
					a->u.data = (void *)(unsigned long int)flags;
					*(param) = 0;
					return 0;
				} else {
					/* try proxy */
					if(parse_phostport(s.s, &host.s, &host.len, &port, &proto)
							!= 0) {
						LM_CRIT("invalid proxy addr parameter <%s>\n", s.s);
						return E_UNSPEC;
					}

					proxy = mk_proxy(&host, port, proto);
					if(proxy == 0) {
						LM_ERR("failed to build proxy structure for <%.*s>\n",
								host.len, host.s);
						return E_UNSPEC;
					}
					*(param) = proxy;
					return 0;
				}
			}
		}
	} else if(param_no == 2) {
		/* flags */
		flags = 0;
		if(s.len > 2 && s.s[0] == '0' && s.s[1] == 'x') {
			s.s += 2;
			s.len -= 2;
			if(hexatoi(&s, &flags) < 0) {
				LM_CRIT("invalid hexa flags <%s>\n", s.s);
				return E_UNSPEC;
			}
			*(param) = (void *)(unsigned long int)flags;
			return 0;
		} else {
			if(str2int(&s, &flags) == 0) {
				*(param) = (void *)(unsigned long int)flags;
				return 0;
			} else {
				LM_CRIT("invalid flags <%s>\n", s.s);
				return E_UNSPEC;
			}
		}
	} else {
		LM_ERR("invalid parameter number %d\n", param_no);
		return E_BUG;
	}
}


static int w_t_relay_to(struct sip_msg *msg, char *proxy, char *flags)
{
	unsigned int fl;
	struct proxy_l *px;
	fparam_t param;

	fl = (unsigned int)(long)(void *)flags;
	px = (struct proxy_l *)proxy;

	if(flags != 0) {
		memset(&param, 0, sizeof(fparam_t));
		param.type = FPARAM_INT;
		/* no auto 100 trying */
		if(fl & 1) {
			param.v.i = 0;
			w_t_set_auto_inv_100(msg, (char *)(&param), 0);
		}
		/* no auto negative reply */
		if(fl & 2) {
			param.v.i = 1;
			w_t_set_disable_internal_reply(msg, (char *)(&param), 0);
		}
		/* no dns failover */
		if(fl & 4) {
			param.v.i = 1;
			w_t_set_disable_failover(msg, (char *)(&param), 0);
		}
	}
	return _w_t_relay_to(msg, px, PROTO_NONE);
}


static int ki_t_use_uac_headers(sip_msg_t *msg)
{
	tm_cell_t *t;

	t = get_t();
	if(t != NULL && t != T_UNDEFINED) {
		t->uas.request->msg_flags |= FL_USE_UAC_FROM | FL_USE_UAC_TO;
	}
	msg->msg_flags |= FL_USE_UAC_FROM | FL_USE_UAC_TO;

	return 1;
}

static int w_t_use_uac_headers(sip_msg_t *msg, char *foo, char *bar)
{
	return ki_t_use_uac_headers(msg);
}

static int w_t_uac_send(sip_msg_t *msg, char *pmethod, char *pruri,
		char *pnexthop, char *psock, char *phdrs, char *pbody)
{
	str method = STR_NULL;
	str ruri = STR_NULL;
	str nexthop = STR_NULL;
	str send_socket = STR_NULL;
	str headers = STR_NULL;
	str body = STR_NULL;

	if(fixup_get_svalue(msg, (gparam_t *)pmethod, &method) != 0) {
		LM_ERR("invalid method parameter\n");
		return -1;
	}
	if(fixup_get_svalue(msg, (gparam_t *)pruri, &ruri) != 0) {
		LM_ERR("invalid ruri parameter\n");
		return -1;
	}
	if(fixup_get_svalue(msg, (gparam_t *)pnexthop, &nexthop) != 0) {
		LM_ERR("invalid nexthop parameter\n");
		return -1;
	}
	if(fixup_get_svalue(msg, (gparam_t *)psock, &send_socket) != 0) {
		LM_ERR("invalid send socket parameter\n");
		return -1;
	}
	if(fixup_get_svalue(msg, (gparam_t *)phdrs, &headers) != 0) {
		LM_ERR("invalid headers parameter\n");
		return -1;
	}
	if(fixup_get_svalue(msg, (gparam_t *)pbody, &body) != 0) {
		LM_ERR("invalid body parameter\n");
		return -1;
	}

	if(t_uac_send(&method, &ruri, &nexthop, &send_socket, &headers, &body)
			< 0) {
		return -1;
	}
	return 1;
}

static int ki_t_uac_send(sip_msg_t *msg, str *method, str *ruri, str *nexthop,
		str *ssock, str *hdrs, str *body)
{
	if(t_uac_send(method, ruri, nexthop, ssock, hdrs, body) < 0) {
		return -1;
	}
	return 1;
}

/* rpc docs */
/* clang-format off */
static const char *rpc_cancel_doc[2] = {
	"Cancel a pending transaction",
	0
};

static const char *rpc_reply_doc[2] = {
	"Reply transaction",
	0
};

static const char *rpc_reply_callid_doc[2] = {
	"Reply transaction by call-id",
	0
};

static const char *tm_rpc_stats_doc[2] = {
	"Print transaction statistics.",
	0
};

static const char *tm_rpc_hash_stats_doc[2] = {
	"Prints hash table statistics (can be used only if tm is compiled"
	" with -DTM_HASH_STATS).",
	0
};

static const char *rpc_t_uac_start_doc[2] = {
	"starts a tm uac using  a list of string parameters: method, ruri, "
	"dst_uri"
	", send_sock, headers (CRLF separated) and body (optional)",
	0
};

static const char *rpc_t_uac_wait_doc[2] = {
	"starts a tm uac and waits for the final reply, using a list of string "
	"parameters: method, ruri, dst_uri send_sock, headers (CRLF separated)"
	" and body (optional)",
	0
};

static const char *rpc_t_uac_wait_block_doc[2] = {
	"starts a tm uac and waits for the final reply in blocking mode, using a"
	" list of string parameters: method, ruri, dst_uri send_sock, headers"
	" (CRLF separated) and body (optional)",
	0
};

static const char *tm_rpc_list_doc[2] = {
	"List transactions.",
	0
};

static const char *tm_rpc_clean_doc[2] = {
	"Clean expired (lifetime exceeded) transactions.",
	0
};


/* rpc exports */
static rpc_export_t tm_rpc[] = {
	{"tm.cancel", rpc_cancel, rpc_cancel_doc, 0},
	{"tm.reply", rpc_reply, rpc_reply_doc, 0},
	{"tm.reply_callid", rpc_reply_callid, rpc_reply_callid_doc, 0},
	{"tm.stats", tm_rpc_stats, tm_rpc_stats_doc, 0},
	{"tm.hash_stats", tm_rpc_hash_stats, tm_rpc_hash_stats_doc, 0},
	{"tm.t_uac_start", rpc_t_uac_start, rpc_t_uac_start_doc, 0},
	{"tm.t_uac_wait", rpc_t_uac_wait, rpc_t_uac_wait_doc, RET_ARRAY},
	{"tm.t_uac_wait_block", rpc_t_uac_wait_block, rpc_t_uac_wait_block_doc, 0},
	{"tm.list", tm_rpc_list, tm_rpc_list_doc, RET_ARRAY},
	{"tm.clean", tm_rpc_clean, tm_rpc_clean_doc, 0},
	{0, 0, 0, 0}
};
/* clang-format on */

/**
 *
 */
static int ki_t_on_failure(sip_msg_t *msg, str *rname)
{
	int ridx;
	sr_kemi_eng_t *keng;

	if(rname == NULL || rname->s == NULL || rname->len <= 0
			|| rname->s[0] == '\0') {
		ridx = 0;
	} else {
		keng = sr_kemi_eng_get();
		if(keng == NULL) {
			ridx = route_get(&failure_rt, rname->s);
		} else {
			ridx = sr_kemi_cbname_lookup_name(rname);
		}
	}
	if(ridx < 0) {
		ridx = 0;
	}

	t_on_failure(ridx);
	return 1;
}

/**
 *
 */
static int ki_t_on_branch_failure(sip_msg_t *msg, str *rname)
{
	int ridx;
	sr_kemi_eng_t *keng;

	if(rname == NULL || rname->s == NULL || rname->len <= 0
			|| rname->s[0] == '\0') {
		ridx = 0;
	} else {
		keng = sr_kemi_eng_get();
		if(keng == NULL) {
			ridx = route_get(&event_rt, rname->s);
		} else {
			ridx = sr_kemi_cbname_lookup_name(rname);
		}
	}
	if(ridx < 0) {
		ridx = 0;
	}

	t_on_branch_failure(ridx);
	return 1;
}


/**
 *
 */
static int ki_t_on_branch(sip_msg_t *msg, str *rname)
{
	int ridx;
	sr_kemi_eng_t *keng;

	if(rname == NULL || rname->s == NULL || rname->len <= 0
			|| rname->s[0] == '\0') {
		ridx = 0;
	} else {
		keng = sr_kemi_eng_get();
		if(keng == NULL) {
			ridx = route_get(&branch_rt, rname->s);
		} else {
			ridx = sr_kemi_cbname_lookup_name(rname);
		}
	}
	if(ridx < 0) {
		ridx = 0;
	}

	t_on_branch(ridx);
	return 1;
}

/**
 *
 */
static int ki_t_on_reply(sip_msg_t *msg, str *rname)
{
	int ridx;
	sr_kemi_eng_t *keng;

	if(rname == NULL || rname->s == NULL || rname->len <= 0
			|| rname->s[0] == '\0') {
		ridx = 0;
	} else {
		keng = sr_kemi_eng_get();
		if(keng == NULL) {
			ridx = route_get(&onreply_rt, rname->s);
		} else {
			ridx = sr_kemi_cbname_lookup_name(rname);
		}
	}
	if(ridx < 0) {
		ridx = 0;
	}

	t_on_reply(ridx);
	return 1;
}

/**
 *
 */
static int ki_t_relay(sip_msg_t *msg)
{
	return _w_t_relay_to(msg, (struct proxy_l *)0, PROTO_NONE);
}

/**
 *
 */
static int ki_t_relay_to_proto(sip_msg_t *msg, str *sproto)
{

	int proto = PROTO_NONE;

	if(sproto != NULL && sproto->s != NULL && sproto->len == 3) {
		if(strncasecmp(sproto->s, "UDP", 3) == 0) {
			proto = PROTO_UDP;
		} else if(strncasecmp(sproto->s, "TCP", 3) == 0) {
			proto = PROTO_TCP;
		} else if(strncasecmp(sproto->s, "TLS", 3) == 0) {
			proto = PROTO_TLS;
		} else {
			LM_ERR("bad protocol specified <%s>\n", sproto->s);
			return E_UNSPEC;
		}
	}
	return _w_t_relay_to(msg, (struct proxy_l *)0, proto);
}

/**
 *
 */
static int ki_t_relay_to_proto_addr(
		sip_msg_t *msg, str *sproto, str *host, int port)
{

	int proto = PROTO_NONE;
	proxy_l_t *proxy = NULL;
	int ret = -1;

	if(sproto != NULL && sproto->s != NULL && sproto->len == 3) {
		if(strncasecmp(sproto->s, "UDP", 3) == 0) {
			proto = PROTO_UDP;
		} else if(strncasecmp(sproto->s, "TCP", 3) == 0) {
			proto = PROTO_TCP;
		} else if(strncasecmp(sproto->s, "TLS", 3) == 0) {
			proto = PROTO_TLS;
		} else {
			LM_ERR("bad protocol specified <%s>\n", sproto->s);
			return E_UNSPEC;
		}
	}
	proxy = mk_proxy(host, (unsigned short)port, 0);
	if(proxy == 0) {
		LM_ERR("bad host:port provided <%s:%d>\n", host->s, port);
		return E_BAD_ADDRESS;
	}
	ret = _w_t_relay_to(msg, proxy, proto);
	free_proxy(proxy);
	pkg_free(proxy);
	return ret;
}

/**
 *
 */
static int ki_t_relay_to_proxy_flags(sip_msg_t *msg, str *sproxy, int rflags)
{
	proxy_l_t *proxy = NULL;
	int ret = -1;
	int proto;
	str host;
	int port;

	if(sproxy != NULL && sproxy->s != NULL && sproxy->len > 0) {
		if(parse_phostport(sproxy->s, &host.s, &host.len, &port, &proto) != 0) {
			LM_CRIT("invalid proxy addr parameter <%s>\n", sproxy->s);
			return E_UNSPEC;
		}

		proxy = mk_proxy(&host, port, proto);
		if(proxy == 0) {
			LM_ERR("failed to build proxy structure for <%.*s>\n", sproxy->len,
					sproxy->s);
			return E_UNSPEC;
		}
	}
	if(rflags != 0) {
		/* no auto 100 trying */
		if(rflags & 1) {
			t_set_auto_inv_100(msg, 0);
		}
		/* no auto negative reply */
		if(rflags & 2) {
			t_set_disable_internal_reply(msg, 1);
		}
		/* no dns failover */
		if(rflags & 4) {
			t_set_disable_failover(msg, 1);
		}
	}

	ret = _w_t_relay_to(msg, proxy, PROTO_NONE);

	if(proxy != NULL) {
		free_proxy(proxy);
		pkg_free(proxy);
	}

	return ret;
}

/**
 *
 */
static int ki_t_relay_to_proxy(sip_msg_t *msg, str *sproxy)
{
	return ki_t_relay_to_proxy_flags(msg, sproxy, 0);
}

/**
 *
 */
static int ki_t_relay_to_flags(sip_msg_t *msg, int rflags)
{
	return ki_t_relay_to_proxy_flags(msg, NULL, rflags);
}

/* script function to clean active but very old transactions */
static int t_clean(struct sip_msg *msg, char *key, char *value)
{
	tm_clean_lifetime();
	return 1;
}

/* kemi function to clean active but very old transactions */
static int ki_t_clean(sip_msg_t *msg)
{
	tm_clean_lifetime();
	return 1;
}

static int ki_t_exists(sip_msg_t *msg)
{
	tm_cell_t *t = NULL;
	int br = -1;

	if(parse_headers(msg, HDR_EOH_F, 0) == -1) {
		LM_ERR("parsing error\n");
		return -1;
		;
	}
	if(parse_headers(msg, HDR_VIA1_F | HDR_CSEQ_F | HDR_CALLID_F | HDR_TO_F, 0)
			== -1) {
		LM_ERR("required headers cannot be parsed\n");
		return -1;
	}
	if(parse_from_header(msg) == -1) {
		LM_ERR("from header parsing failed\n");
		return -1;
	}

	if(msg->first_line.type == SIP_REQUEST) {
		t_request_search(msg, &t);
	} else {
		t_reply_search(msg, &t, &br);
	}

	if(!t) {
		return -1;
	}
	UNREF(t);

	return 1;
}

static int w_t_exists(struct sip_msg *msg, char *p1, char *p2)
{
	return ki_t_exists(msg);
}

#ifdef USE_DNS_FAILOVER
/* parse reply codes for failover given in module parameter */
static int t_failover_parse_reply_codes()
{
	param_t *params_list = NULL;
	param_t *pit = NULL;
	int list_size = 0;
	int i = 0;
	int pos = 0;
	int code = 0;
	str input = {0, 0};
	int *new_failover_reply_codes = NULL;
	int *old_failover_reply_codes = NULL;

	/* validate input string */
	if(failover_reply_codes_str.s == 0 || failover_reply_codes_str.len <= 0)
		return 0;

	/* parse_params() updates the string pointer of .s -- make a copy */
	input.s = failover_reply_codes_str.s;
	input.len = failover_reply_codes_str.len;

	if(parse_params(&input, CLASS_ANY, 0, &params_list) < 0)
		return -1;

	/* get the number of entries in the list */
	for(pit = params_list; pit; pit = pit->next) {
		if(pit->name.len == 4 && strncasecmp(pit->name.s, "code", 4) == 0) {
			str2sint(&pit->body, &code);
			if((code >= 300) && (code < 700))
				list_size += 1;
		} else if(pit->name.len == 5
				  && strncasecmp(pit->name.s, "class", 5) == 0) {
			str2sint(&pit->body, &code);
			if((code >= 3) && (code < 7))
				list_size += 1;
		}
	}
	LM_DBG("expecting %d reply codes and classes\n", list_size);

	if(list_size > 0) {
		/* Allocate Memory for the new list: */
		new_failover_reply_codes = (int *)shm_malloc(list_size * sizeof(int));
		if(new_failover_reply_codes == NULL) {
			free_params(params_list);
			LM_ERR("no more memory\n");
			return -1;
		}

		/* Now create the list of valid reply-codes: */
		for(pit = params_list; pit; pit = pit->next) {
			if(pit->name.len == 4 && strncasecmp(pit->name.s, "code", 4) == 0) {
				str2sint(&pit->body, &code);
				if((code >= 300) && (code < 700)) {
					new_failover_reply_codes[pos++] = code;
				}
			} else if(pit->name.len == 5
					  && strncasecmp(pit->name.s, "class", 5) == 0) {
				str2sint(&pit->body, &code);
				if((code >= 3) && (code < 7)) {
					new_failover_reply_codes[pos++] = code;
				}
			}
		}
	} else {
		new_failover_reply_codes = 0;
	}
	free_params(params_list);

	if(list_size > *failover_reply_codes_cnt) {
		/* if more reply-codes -- change pointer and then set number of codes */
		old_failover_reply_codes = *failover_reply_codes;
		*failover_reply_codes = new_failover_reply_codes;
		*failover_reply_codes_cnt = list_size;
		if(old_failover_reply_codes)
			shm_free(old_failover_reply_codes);
	} else {
		/* less or equal reply codea -- set the number of codes first */
		*failover_reply_codes_cnt = list_size;
		old_failover_reply_codes = *failover_reply_codes;
		*failover_reply_codes = new_failover_reply_codes;
		if(old_failover_reply_codes)
			shm_free(old_failover_reply_codes);
	}
	/* Print the list as INFO: */
	for(i = 0; i < *failover_reply_codes_cnt; i++) {
		LM_DBG("accepting reply %s %d (%d/%d) as valid\n",
				((*failover_reply_codes)[i] / 10) ? "code" : "class",
				(*failover_reply_codes)[i], (i + 1), *failover_reply_codes_cnt);
	}
	return 0;
}

int t_failover_check_reply_code(int code)
{
	int i;

	for(i = 0; i < *failover_reply_codes_cnt; i++) {
		if((*failover_reply_codes)[i] / 10) {
			/* reply code */
			if((*failover_reply_codes)[i] == code) {
				return 1;
			}
		} else {
			/* reply class */
			if((*failover_reply_codes)[i] == code / 100) {
				return 1;
			}
		}
	}

	return 0;
}
#endif

/**
 *
 */
/* clang-format off */
static sr_kemi_t tm_kemi_exports[] = {
	{ str_init("tm"), str_init("t_relay"),
		SR_KEMIP_INT, ki_t_relay,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_on_branch"),
		SR_KEMIP_INT, ki_t_on_branch,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_on_failure"),
		SR_KEMIP_INT, ki_t_on_failure,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_on_branch_failure"),
		SR_KEMIP_INT, ki_t_on_branch_failure,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_on_reply"),
		SR_KEMIP_INT, ki_t_on_reply,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_reply"),
		SR_KEMIP_INT, ki_t_reply,
		{ SR_KEMIP_INT, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_reply_error"),
		SR_KEMIP_INT, ki_t_reply_error,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_send_reply"),
		SR_KEMIP_INT, ki_t_send_reply,
		{ SR_KEMIP_INT, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_check_trans"),
		SR_KEMIP_INT, t_check_trans,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_is_canceled"),
		SR_KEMIP_INT, t_is_canceled,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_newtran"),
		SR_KEMIP_INT, t_newtran,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_release"),
		SR_KEMIP_INT, t_release,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_replicate"),
		SR_KEMIP_INT, t_replicate_uri,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_is_set"),
		SR_KEMIP_INT, t_is_set,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_lookup_request"),
		SR_KEMIP_INT, ki_t_lookup_request,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_lookup_cancel"),
		SR_KEMIP_INT, ki_t_lookup_cancel,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_lookup_cancel_flags"),
		SR_KEMIP_INT, ki_t_lookup_cancel_flags,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_retransmit_reply"),
		SR_KEMIP_INT, ki_t_retransmit_reply,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_fr_inv"),
		SR_KEMIP_INT, ki_t_set_fr_inv,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_fr"),
		SR_KEMIP_INT, ki_t_set_fr,
		{ SR_KEMIP_INT, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_reset_fr"),
		SR_KEMIP_INT, ki_t_reset_fr,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_max_lifetime"),
		SR_KEMIP_INT, ki_t_set_max_lifetime,
		{ SR_KEMIP_INT, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_reset_max_lifetime"),
		SR_KEMIP_INT, ki_t_reset_max_lifetime,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_retr"),
		SR_KEMIP_INT, ki_t_set_retr,
		{ SR_KEMIP_INT, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_reset_retr"),
		SR_KEMIP_INT, ki_t_reset_retr,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_uac_send"),
		SR_KEMIP_INT, ki_t_uac_send,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR }
	},
	{ str_init("tm"), str_init("t_load_contacts"),
		SR_KEMIP_INT, ki_t_load_contacts,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("ki_t_load_contacts_mode"),
		SR_KEMIP_INT, ki_t_load_contacts_mode,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_next_contacts"),
		SR_KEMIP_INT, ki_t_next_contacts,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_next_contact_flow"),
		SR_KEMIP_INT, ki_t_next_contact_flow,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_drop_replies_all"),
		SR_KEMIP_INT, ki_t_drop_replies_all,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_drop_replies"),
		SR_KEMIP_INT, ki_t_drop_replies,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_use_uac_headers"),
		SR_KEMIP_INT, ki_t_use_uac_headers,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_save_lumps"),
		SR_KEMIP_INT, ki_t_save_lumps,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_is_expired"),
		SR_KEMIP_INT, t_is_expired,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_check_status"),
		SR_KEMIP_INT, ki_t_check_status,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_grep_status"),
		SR_KEMIP_INT, t_grep_status,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_is_retr_async_reply"),
		SR_KEMIP_INT, t_is_retr_async_reply,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_any_replied"),
		SR_KEMIP_INT, t_any_replied,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_any_timeout"),
		SR_KEMIP_INT, t_any_timeout,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_branch_replied"),
		SR_KEMIP_INT, t_branch_replied,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_branch_timeout"),
		SR_KEMIP_INT, t_branch_timeout,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_auto_inv_100"),
		SR_KEMIP_INT, t_set_auto_inv_100,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_disable_6xx"),
		SR_KEMIP_INT, t_set_disable_6xx,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_disable_failover"),
		SR_KEMIP_INT, t_set_disable_failover,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_no_e2e_cancel_reason"),
		SR_KEMIP_INT, t_set_no_e2e_cancel_reason,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_set_disable_internal_reply"),
		SR_KEMIP_INT, t_set_disable_internal_reply,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_relay_to_proxy"),
		SR_KEMIP_INT, ki_t_relay_to_proxy,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_relay_to_flags"),
		SR_KEMIP_INT, ki_t_relay_to_flags,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_relay_to_proxy_flags"),
		SR_KEMIP_INT, ki_t_relay_to_proxy_flags,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_relay_to_proto"),
		SR_KEMIP_INT, ki_t_relay_to_proto,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_relay_to_proto_addr"),
		SR_KEMIP_INT, ki_t_relay_to_proto_addr,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_INT,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_get_status_code"),
		SR_KEMIP_INT, ki_t_get_status_code,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_get_branch_index"),
		SR_KEMIP_INT, ki_t_get_branch_index,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_clean"),
		SR_KEMIP_INT, ki_t_clean,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("tm"), str_init("t_exists"),
		SR_KEMIP_INT, ki_t_exists,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			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(tm_kemi_exports);
	return 0;
}
