
loadmodule "acc.so"
modparam("acc", "failed_transaction_flag", FLAG_TRUSTED_SOURCE)
modparam("acc", "early_media", 1)
modparam("acc", "report_cancels", 1)

loadmodule "mqueue.so"
modparam("mqueue", "mqueue", "name=acc_events;size=100000")
modparam("mqueue", "mqueue", "name=acc_failed_cdr_delivery;size=100000")

loadmodule "acc_json.so"
modparam("acc_json", "acc_output_mqueue", "acc_events")
modparam("acc_json", "acc_flag", FLAG_TRUSTED_SOURCE)
modparam("acc_json", "acc_extra", "source_ip=$si;ruri=$ru;from=$fu;to=$tu;hostname=$HN(f)")
modparam("acc_json", "acc_time_mode", 4)

loadmodule "rtimer.so"
modparam("rtimer", "timer", "name=accounting;interval=1;mode=1;")
modparam("rtimer", "exec", "timer=accounting;route=RUN_CDR_PUBLISH")
modparam("http_client", "keep_connections", 1)
modparam("http_client", "httpcon", "acc_server=>CDR_URL_SUBS")

route[RUN_CDR_PUBLISH] {
   $var(count) = 0;
   while (mq_fetch("acc_events")) {
      $var(q_size) = mq_size("acc_events");
      $var(count) = $var(count) + 1;
      xinfo("[RUN_ACC_PUBLISH][$var(q_size)][$var(count)][$mqk(acc_events)][$mqv(acc_events)]\n");
      $var(res) = http_connect_raw("acc_server", "", "application/json\r\nx-api-key: CDR_TOKEN_SUBS", $mqv(acc_events), "$var(acc_res)");
      if ($var(res) < 0) {
         xerr("[RUN_ACC_PUBLISH][$var(res)] http_connect_raw: timeout or error !\n");
         mq_add("acc_events", "acc_key", "$mqv(acc_events)");
      } else if ($var(res) < 200 || $var(res) > 299) {
         xerr("[RUN_ACC_PUBLISH][$var(res)] http unexpected response code !\n");
         mq_add("acc_failed_cdr_delivery", "acc_key", "$mqv(acc_events)");
         return;
      }
   }
   if ($var(count) > 0 ) {
      xinfo("[RUN_CDR_PUBLISH]done count[$var(count)]\n");
   }
}
loadmodule "carrierroute.so"
modparam("carrierroute", "config_source", "db")
modparam("carrierroute", "db_url", "KAMAILIO_DB_URL")

route[NEXT_988_SBC] {
	if (!cr_route("default", "route_988", "$fU", "$rU", "call_id")) {
		send_reply("403", "Not allowed");
		exit;
	}
}

route[NEXT_NGA_HOP] {
	if (isflagset(FLAG_OUTBOUND_CALL)){
		return;
	}

	if (is_method("REGISTER")) {
		return;
	}

	if (isflagset(FLAG_EMERGENCY_CALL) && is_present_hf("Route")) {
		$var(carrier_key) = $(route_uri{uri.domain});
	} else {
		$var(carrier_key) = $td;
	}

	if (isflagset(FLAG_EMERGENCY_CALL)
		&& sql_xquery("db", "select TRUE from carrier_name where carrier = '$var(carrier_key)'", "sql_res") == 1
		&& $xavp(sql_res) == $null) {
		route(LOST_REQUEST);
		$var(carrier_key) = $td;
	}

	if (isflagset(FLAG_TRUSTED_SOURCE)
		&& sql_xquery("db", "select TRUE from carrier_name where carrier = '$var(carrier_key)'", "sql_res") == 1
		&& $xavp(sql_res) == $null) {
		xlog("L_ERR", "No specific route for domain: $var(carrier_key)\n");
		return;
	}

	if (cr_route("$var(carrier_key)", "route_nga", "$tU", "$rU", "call_id")) {
		return;
	}

	if (isflagset(FLAG_EMERGENCY_CALL)) {
		send_reply("480", "All servers busy");
	} else {
		send_reply("403", "Not allowed");
	}
	exit;
}

route[OUTBOUND_FORMAT] {

	$var(tmp)=$ru;
	if (cr_route("$td", "outbound-format_from", "$fU", "$fU", "call_id")) {
		$fU=$rU;
		$ru=$var(tmp);
	}

	if (cr_route("$td", "outbound-format_to", "$tU", "$tU", "call_id")) {
		$tU=$rU;
		$ru=$var(tmp);
	}

	if(is_present_hf("P-Asserted-Identity")) && cr_route("$td", "outbound-format_pai", "$(ai{uri.user})", "$(ai{uri.user})", "call_id") {
		remove_hf("P-Asserted-Identity");
		append_hf("P-Asserted-Identity: <sip:$rU@$fd>\r\n");
		$ru=$var(tmp);
	}

	if (!has_totag()) {
		cr_route("$td", "outbound-format_ruri", "$rU", "$rU", "call_id");
		$var(tmp2)=$rU;
		$ru=$var(tmp);
		$rU=$var(tmp2);
	}
}

failure_route[RETRY_OTHER_HOST] {
	revert_uri();
	$var(urn_ori) = $ru;
	if (t_check_status("400|408|5[0-9][0-9]")) {
		route(NEXT_NGA_HOP);
		route(RELAY);
	}
}route[CLASSIFY_SOURCE]
{
	# this urn services defined by:
	# urn:service:sos - RFC5031
	# urn:service:test - RFC6881
	# urn:nena:service:sos - NENA-STA-010.2-2016 (urn:nena)
	# urn:nena:service:test
	# urn:nena:service:responder
	# urn:nena:service:responder.federal_police
	# urn:nena:service:agencyLocator
	if (is_method("INVITE|CANCEL") &&
	   ($rU == "911"
		|| $rU == "191"
		|| pcre_match ($rU, "^[0-9]{5}$$")
		|| pcre_match ($rU, "^911[0-9]{7}$$")
		|| $(ru{s.substr,0,15}) == "urn:service:sos"
		|| $(ru{s.substr,0,16}) == "urn:service:test"
		|| $(ru{s.substr,0,20}) == "urn:nena:service:sos"
		|| $(ru{s.substr,0,21}) == "urn:nena:service:test"
		|| $(ru{s.substr,0,26}) == "urn:nena:service:responder"
		|| $(ru{s.substr,0,30}) == "urn:nena:service:agencyLocator"
		|| $tU == "911"
		|| $tU == "191"
		|| pcre_match ($tU, "^[0-9]{5}$$")
		|| pcre_match ($tU, "^911[0-9]{7}$$")
		|| $(tu{s.substr,0,15}) == "urn:service:sos"
		|| $(tu{s.substr,0,16}) == "urn:service:test"
		|| $(tu{s.substr,0,20}) == "urn:nena:service:sos"
		|| $(tu{s.substr,0,21}) == "urn:nena:service:test"
		|| $(tu{s.substr,0,26}) == "urn:nena:service:responder"
		|| $(tu{s.substr,0,30}) == "urn:nena:service:agencyLocator"
		)) {
		setflag(FLAG_EMERGENCY_CALL);
		setflag(FLAG_TRUSTED_SOURCE);
	}

	if (is_present_hf("Route") && $(route_uri{param.in,ob}) == 1) {
		setflag(FLAG_OUTBOUND_CALL);
		return;
	}

	if ($rU == "988") {
		setflag(FLAG_988_CALL);
		setflag(FLAG_TRUSTED_SOURCE);
		return;
	}

	if (allow_source_address()) {
		setflag(FLAG_TRUSTED_SOURCE);
		return;
	}
}

#!trydef MYSQL_USER kamailio
#!trydef MYSQL_PASS change_me
#!trydef MYSQL_HOST 127.0.0.1
#!trydef MYSQL_PORT 3306

#!substdef "!KAMAILIO_DB_URL!mysql://$def(MYSQL_USER):$def(MYSQL_PASS)@$def(MYSQL_HOST):$def(MYSQL_PORT)/kamailio!g"

loadmodule "db_mysql.so"

loadmodule "sqlops.so"
modparam("sqlops","sqlcon","db=>KAMAILIO_DB_URL")
route[PROXY_MEDIA_HANDLE_ENCRYPTION] {
	if (is_request() && sdp_with_transport("UDP/TLS/RTP/SAVPF")) {
		$avp(caller_sdp_transport) = "UDP/TLS/RTP/SAVPF";
		$var(sdp_transport) = "RTP/AVPF";
	} else if (is_request() && sdp_with_transport("UDP/TLS/RTP/SAVP")) {
		$avp(caller_sdp_transport) = "UDP/TLS/RTP/SAVP";
		$var(sdp_transport) = "RTP/AVP";
	} else if (is_reply() && ($avp(caller_sdp_transport) != $null)) {
		$var(sdp_transport) = $avp(caller_sdp_transport);
	} else if($(ru{param.in,nga.ws}) == 1 && sdp_with_transport("RTP/AVP")) {
		$avp(caller_sdp_transport) = "RTP/AVP";
		$var(sdp_transport) = "UDP/TLS/RTP/SAVPF";
	} else {
		$var(sdp_transport) = "";
	}
}

route[UPDATE_CONTACT_WS] {
	if ($proto != "ws" && $proto != "wss") {
		return;
	}

	remove_hf("Contact");
	append_hf("Contact: $(ct{re.subst,/transport=wss?/transport=tcp;nga.ws/})\r\n");
}
# IPv4 listeners
listen=udp:IPV4_ADDR name "ipv4_udp"
listen=tcp:IPV4_ADDR name "ipv4_tcp"
listen=tcp:IPV4_ADDR:2855 name "ipv4_msrp"
#!ifdef TLS_ROLE
listen=tls:IPV4_ADDR name "ipv4_tls"
listen=tls:IPV4_ADDR:2856 name "ipv4_msrps"
#!endif

# IPv6 listeners
listen=udp:[IPV6_ADDR] name "ipv6_udp"
listen=tcp:[IPV6_ADDR] name "ipv6_tcp"
listen=tcp:[IPV6_ADDR]:2855 name "ipv6_msrp"
listen=tcp:[PROM_ADDR] name "prom_tcp"
#!ifdef TLS_ROLE
listen=tls:[IPV6_ADDR]:5061 name "ipv6_tls"
listen=tls:[IPV6_ADDR]:2856 name "ipv6_msrps"
listen=tls:[PROM_ADDR] name "prom_tls"
#!endif

######## Advanced logger module ########
loadmodule "xlog.so"
log_prefix="{$mt $hdr(CSeq) $ci} "

# set self identification strings
server_header="Server: nga911"
user_agent_header="User-Agent: nga911"

# We prefer IPv6 dns names rather IPv4
dns_cache_flags = 4
dns_try_naptr = yes

# use compact IPv6 addresses
ipv6_hex_style = "c"

# Enable PMTU discovery
pmtu_discovery = 0

# allow outbound connection from default port
tcp_reuse_port=yes

tcp_accept_haproxy=TCP_ACCEPT_HAPROXY

# Prevent error messages generated when HTTP health check copy send via siptrace
http_reply_parse=yes

# we try handle DoS attack
tcp_max_connections=2048

# set DSCP
tos=0xA0

######## debugger module ########
loadmodule "debugger.so"
#!ifdef WITH_DEBUG
modparam("debugger", "cfgtrace", 1)
modparam("debugger", "log_level_name", "exec")
debug=L_DBG
log_stderror=yes
#!else
debug=L_INFO
log_stderror=no
#!endif

#### tls module must be loaded first ##########
#!ifdef TLS_ROLE
include_file "tls-role.cfg"
#!endif

######## Kamailio control connector module ########
loadmodule "ctl.so"
modparam("ctl", "binrpc_buffer_size", 4096)
loadmodule "cfg_rpc.so"
loadmodule "jsonrpcs.so"

######## AVP's ########
loadmodule "avp.so"
loadmodule "avpops.so"

######## Transaction (stateful) module ########
loadmodule "tm.so"
loadmodule "tmx.so"
modparam("tm", "auto_inv_100", 0)
modparam("tm", "auto_inv_100_reason", "Attempting to connect your call")
modparam("tm", "cancel_b_method", 2)
modparam("tm", "fr_timer", 3200)

######## Stateless replier module ########
loadmodule "sl.so"

######## Max-Forward processor module ########
loadmodule "maxfwd.so"
modparam("maxfwd", "max_limit", 50)

######## SIP utilities [requires sl] ########
loadmodule "siputils.so"

######## Text operations module ########
loadmodule "textopsx.so"

######## sdp operations module ########
loadmodule "sdpops.so"

####### DATABASE module ##########
include_file "db_mysql.cfg"

loadmodule "permissions.so"
modparam("permissions", "db_url", "KAMAILIO_DB_URL")
modparam("permissions", "peer_tag_avp", "$avp(s:tag)")
modparam("permissions", "peer_tag_mode", 0)
modparam("permissions", "load_backends", 1)
modparam("permissions", "reload_delta", 1)

#### htable ####
loadmodule "htable.so"

######## regex module ########
loadmodule "regex.so"
modparam("regex", "pcre_extended", 1)

######## modules for outbound support ########
loadmodule "stun.so"
loadmodule "outbound.so"
modparam("outbound", "force_outbound_flag", FLAG_OUTBOUND_FORCE)

######## Kamailio path module ########
loadmodule "path.so"

######## Record-Route and Route module ########
loadmodule "rr.so"
modparam("rr", "enable_full_lr", RR_FULL_LR)
modparam("rr", "enable_double_rr", RR_DOUBLE_RR)
modparam("rr", "append_fromtag", 0)

## sanity ##
include_file "sanity.cfg"

## classiy source ##
include_file "classify-source.cfg"

## status functionality ##
include_file "status.cfg"

#!ifdef TRAFFIC_FILTER_ROLE
include_file "traffic-filter-role.cfg"
#!endif

#!ifdef SESSION_ID_ROLE
include_file "session_id.cfg"
#!endif

include_file "normalize_headers.cfg"

include_file "xhttp-role.cfg"

include_file "lost.cfg"

## accouting ##
include_file "accounting.cfg"

include_file "sip_trace_role.cfg"

#!ifdef CARRIERROUTE_ROLE
include_file "carrierroute.cfg"
#!endif

#!ifdef PROXY_MEDIA_ROLE
include_file "proxy-media.cfg"
#!endif

#!ifdef DECRYPT_SRTP_ROLE
include_file "decrypt_srtp_role.cfg"
#!endif

#!ifdef NAT_TRAVERSAL_ROLE
include_file "nat-traversal-role.cfg"
#!endif

#!ifdef TOPOH_ROLE
######## modules for topology hiding ########
loadmodule "topoh.so"
modparam("topoh", "mask_ip", "TOPOH_MASK_IP")
#!endif

## msrp proxy feature ##
#!ifdef MSRP_PROXY_ROLE
include_file "msrp-proxy.cfg"
#!endif

####### Routing Logic ########
request_route {
	route(SANITY_CHECK);

	#!ifdef MSRP_PROXY_ROLE
	route(MSRP_INJECT_PROXY);
	#!endif

	route(HANDLE_COMMA_HEADERS);

	route(CLASSIFY_SOURCE);

	route(STATUS_CHECK);

	#!ifdef TRAFFIC_FILTER_ROLE
	route(FILTER_REQUEST);
	#!endif

	route(LOG_REQUEST);

	#!ifdef SESSION_ID_ROLE
	route(MANAGE_SESSION_ID);
	#!endif

	# save RURI
	$var(urn_ori) = $ru;

	route(NORMALIZE_HEADERS);
	route(NORMALIZE_EMERGENCY_HEADERS);

	# CANCEL processing
	if (is_method("CANCEL")) {
		if (t_check_trans()) {
			route(RELAY);
		}
		exit;
	}

	# handle retransmissions
	if (!is_method("ACK")) {
		if(t_precheck_trans()) {
			t_check_trans();
			exit;
		}
		t_check_trans();
	}

	#!ifdef NAT_TRAVERSAL_ROLE
	route(NAT_MANAGE);
	#!endif

	# handle requests within SIP dialogs
	route(WITHINDLG);

	if (is_method("REGISTER" )) {
		if (!add_path()) {
			sl_send_reply("503", "Internal Path Error");
		}
		#!ifdef DECRYPT_SRTP_ROLE
		route(UPDATE_CONTACT_WS);
		#!endif

	}

	### only initial requests (no To tag)
	if (loose_route()) {
		if ($rc == 2) {
			xlog("L_DEB", "end|call routed using flow route\n");
		} else if ($rc == 1) {
			if (!isflagset(FLAG_TRUSTED_SOURCE)) {
				xlog("L_WARN", "end|dropping initial request with route-set\n");
				sl_send_reply("403", "No pre-loaded routes");
				exit();
			}
		}
	}

	if(is_method("REGISTER")) {
		sl_send_reply("100", "checking your credentialsl");
	} else {
		sl_send_reply("100", "Attempting to connect your call");
	}

	if (isflagset(FLAG_EMERGENCY_CALL)) {
		route(DEREFERENCE_GEOLOCATION);

		#!ifndef CARRIERROUTE_ROLE
		route(LOST_REQUEST);
		#!endif
	}

	#!ifdef CARRIERROUTE_ROLE
	if (isflagset(FLAG_988_CALL)) {
		route(NEXT_988_SBC);
	} else {
		route(NEXT_NGA_HOP);
	}
	#!endif

	#!ifdef ENCODE_HELD_ADR_URLS_ROLE
	if (isflagset(FLAG_EMERGENCY_CALL)) {
		route(ENCODE_HELD_ADR_URLS);
	}
	#!endif

	record_route();

	route(RELAY);
}

route[LOG_REQUEST]
{
	# log the basic info regarding this call
	xlog("L_INFO", "start|received $pr request $rm $ou\n");
	xlog("L_INFO", "log|source $si:$sp -> $RAi:$RAp\n");
	xlog("L_INFO", "log|from $fu\n");
	xlog("L_INFO", "log|to $tu\n");
}

route[RELAY] {
	route(OUTBOUND_FORMAT);
	if (!is_present_hf("X-AUTH-IP") || !is_present_hf("X-AUTH-PORT")) {
		remove_hf("X-AUTH-IP");
		remove_hf("X-AUTH-PORT");
		append_hf_value("X-AUTH-IP", "$si");
		append_hf_value("X-AUTH-PORT", "$sp");
	}

	if ($rz != "urn" && !rr_next_hop_route()) {
		$du = $ru;
		$ru = $var(urn_ori);
	}

	#!ifdef PROXY_MEDIA_ROLE
	route(PROXY_MEDIA);
	#!endif

	t_on_reply("ON_REPLY");
	t_on_failure("RETRY_OTHER_HOST");

	if (!t_relay()) {
		sl_reply_error();
	}
	exit;
}

onreply_route[ON_REPLY]
{
	# this route handles replies that are comming from our media server
	if ($rs < 300) {
		xlog("L_INFO", "log|internal reply $T_reply_code $T_reply_reason\n");
	}

	if (is_method("INVITE") && t_check_status("(180)|(183)|(200)")) {
		xlog("L_INFO", "log|call setup, now ignoring abnormal termination\n");
	}

	#!ifdef SESSION_ID_ROLE
	route(MANAGE_SESSION_ID);
	#!endif

	#!ifdef PROXY_MEDIA_ROLE
	#!ifdef MSRP_PROXY_ROLE
	route(MSRP_INJECT_PROXY);
	#!endif
	route(PROXY_MEDIA);
	#!endif
}

# Handle requests within SIP dialogs
route[WITHINDLG] {
	if (!has_totag()) {
		return;
	}

	if (is_method("INVITE|UPDATE|REFER")) {
		record_route();
	}

	# sequential request withing a dialog should
	# take the path determined by record-routing
	if (loose_route()) {
		route(RELAY);
	}

	if (is_method("SUBSCRIBE") && uri == myself) {
		# in-dialog subscribe requests
		route(PRESENCE);
	}

	if ( is_method("ACK") ) {
		if ( t_check_trans() ) {
			# non loose-route, but stateful ACK;
			# must be ACK after a 487 or e.g. 404 from upstream server
			t_relay();
			exit;
		} else {
			# ACK without matching transaction ... ignore and discard.
			exit;
		}
	}

	if (isflagset(FLAG_TRUSTED_SOURCE)) {
		sl_send_reply("404","Not here");
	} else {
		xlog("L_WARN", "end|ignoring message with to_tag and IP domain\n");
		drop;
	}
	exit;
}

# Presence server route
route[PRESENCE] {
	if(!is_method("PUBLISH|SUBSCRIBE"))
		return;

	sl_send_reply("404", "Not here");
	exit;
}

#!ifdef TOPOH_ROLE
event_route[topoh:msg-outgoing] {
	# Disable topology hidinge for all IP addresses except in "BADSBC_ADR_GROUP" ACL
	if (!allow_address("TOPOH_ACL_GROUP", "$sndto(ip)", "0")) {
		drop;
	}
}
#!endif

## permissions module access lists
#!define OPTIONS_503_NOT_SUPPORTED 12
#!define BADSBC_ADR_GROUP 13

# rr module defaults
#!trydef RR_FULL_LR 1
#!trydef RR_DOUBLE_RR 1

#### ATOS alarm levels #########
#!define ATOS_CRITICAL 0
#!define ATOS_MAJOR 1
#!define ATOS_MINOR 2
#!define ATOS_WARNING 3
#!define ATOS_NORMAL 4
#!define ATOS_CLEARED 5
#!define ATOS_INTERMEDIATE 6

#!ifdef TOPOH_ROLE
#!substdef "!TOPOH_MASK_IP!$def(IPV4_ADDR)!g"
#!substdef "!TOPOH_ACL_GROUP!$def(BADSBC_ADR_GROUP)!g"
#!endif

#### for accounting #########
#!substdef "!CDR_URL_SUBS!$def(CDR_URL)!g"
#!substdef "!CDR_TOKEN_SUBS!$def(CDR_TOKEN)!g"

#### anycast HTTP proxy address #########
#!trydef HTTP_PROXY 158.51.225.1

#!trydef DEFAULT_COUNTRY_CODE_CHARS US

#!ifdef HAPROXY_ROLE
#!trydef TCP_ACCEPT_HAPROXY yes
#!else
#!trydef TCP_ACCEPT_HAPROXY no
#!endif
## NOTE: DO NOT CHANGE THIS FILE, EDIT local.cfg ##

####### Flags #######
#!trydef FLAG_EMERGENCY_CALL 1
#!trydef FLAG_988_CALL 2
#!trydef FLAG_TRUSTED_SOURCE 3
#!trydef FLAG_OUTBOUND_CALL 4
#!trydef FLAG_OUTBOUND_FORCE 5


####### Global Parameters #########

#!/bin/bash

KAMAILIO_SHARE_DIR=${KAMAILIO_SHARE_DIR:-/usr/share/kamailio}
DB_ENGINE=${DB_ENGINE:-mysql}

set -x
set -o errexit -o nounset -o pipefail

init_sql=/tmp/init_sql.sql

template_drop_tables() {
cat <<EOF
drop table carrierroute;
drop table carrierfailureroute;
drop table carrier_name;
drop table domain_name;
drop table address;
drop table trusted;
drop table rtpengine;
EOF
}

template_init_carrier() {
cat <<EOF
INSERT INTO carrier_name (id, carrier) values (1,'default');
-- INSERT INTO carrier_name (id, carrier) values (2,'example.net');
INSERT INTO domain_name (id, domain) values (1,'route_988');
INSERT INTO domain_name (id, domain) values (2,'route_nga');
INSERT INTO domain_name (id, domain) values (3,'outbound-format_from');
INSERT INTO domain_name (id, domain) values (4,'outbound-format_to');
INSERT INTO domain_name (id, domain) values (5,'outbound-format_pai');
INSERT INTO domain_name (id, domain) values (6,'outbound-format_ruri');
-- INSERT INTO carrierroute (carrier,domain,scan_prefix,rewrite_host,prob) values (2,2,'','esrp.xx.nga911.com',1);
EOF
}

template_init_address() {
cat <<EOF
-- INSERT INTO address (ip_addr, mask, tag) values ("xx:xx:xx:xx::", 128, "carrier=example;realm=example.net;country=XX");
EOF
}

template_init_rtpengine() {
cat <<EOF
-- INSERT INTO rtpengine (url,disabled) values ("udp6:[::1]:2223",0);
EOF
}

sql_filelist() {
  echo `ls -A1 ${KAMAILIO_SHARE_DIR}/${DB_ENGINE}/*.sql | sed -e '/standard-create/d' -e '/ims_/d' | tr '\n' '\0' | xargs -0 -n 1 basename | sort`
}

sql_db_prepare() {
  cat ${KAMAILIO_SHARE_DIR}/${DB_ENGINE}/standard-create.sql
  for i in $(sql_filelist); do
    cat ${KAMAILIO_SHARE_DIR}/${DB_ENGINE}/$i
  done
}

maybe_sql_db_prepare() {
  mysqlshow > /dev/null
  rm -f ${init_sql}
  if ! mysqlshow kamailio version &> /dev/null; then
    sql_db_prepare >> ${init_sql}
    mysql kamailio < ${init_sql}
  fi
}

maybe_sql_db_prepare

template_drop_tables > ${init_sql}
cat /usr/share/kamailio/mysql/{carrierroute-create.sql,permissions-create.sql,rtpengine-create.sql} >> ${init_sql}
sed -i '/version/d' ${init_sql}

template_init_address >> ${init_sql}
template_init_carrier >> ${init_sql}
template_init_rtpengine >> ${init_sql}

mysql kamailio < ${init_sql}

#!KAMAILIO

## NOTE: DO NOT CHANGE THIS FILE, EDIT local.cfg ##

#### Preprocessor Directives #########
#!define L_ALERT -5
#!define L_BUG -4
#!define L_CRIT2 -3
#!define L_CRIT -2
#!define L_ERR -1
#!define L_WARN 0
#!define L_NOTICE 1
#!define L_INFO 2
#!define L_DBG 3
#!define L_DEBUG 4

################
# Kamailio modules to help substdef setup
# these need to go before local.cfg
# so they can be used
#
# ipops - ip , domain, hostname
# pv - $def(existing definition)
# textops - apply regexp
#
#
################
loadmodule "ipops.so"
loadmodule "pv.so"
loadmodule "textops.so"

####### Local Configuration ########
include_file "local.cfg"

####### defaults not configured in local ########
include_file "defs.cfg"

####### globals ########
include_file "globals.cfg"

####### default listeners ########
#!ifndef SKIP_DEFAULT_LISTENERS
include_file "default-listeners.cfg"
#!endif

####### Default Configuration ######
include_file "default.cfg"

################################################################################
## Config options
################################################################################
# # #!define WITH_DEBUG

#!define IPV4_ADDR 127.0.0.1
#!define IPV6_ADDR ::1
#!define PROM_ADDR ::1

#!define CDR_URL http://127.0.0.1:8080/cdr
#!define CDR_TOKEN xxxxxxxxxxxxxx

################################################################################
## ROLES
################################################################################
## Enabled Roles
#!define TRAFFIC_FILTER_ROLE
#!define SESSION_ID_ROLE
#!define CARRIERROUTE_ROLE
#!define PROXY_MEDIA_ROLE
#!define TLS_ROLE
# # #!define DECRYPT_SRTP_ROLE
#!define NAT_TRAVERSAL_ROLE
# # #!define ENCODE_HELD_ADR_URLS_ROLE
# # #!define TOPOH_ROLE
# # #!define MSRP_PROXY_ROLE
# # #!define HAPROXY_ROLE

################################################################################
## HELD and LOST settings
################################################################################
#!trydef NGA911_LOST_ENABLED 0
#!trydef NGA911_HELD_ENABLED 0

################################################################################
## SIPTRACE settings
################################################################################
#!trydef SIP_TRACE_URI "sip:sip-trace.nga911.com:9062;transport=tcp"
#!trydef HEP_CAPTURE_ID 1
#!trydef SIP_TRACE_ON 1

#!trydef HELD_REQUEST_TIMEOUT 4000
#!trydef PROXY_URL "http://http-proxy-6.nga911.com"
#!trydef PROXY_PORT 3128
#!trydef HTTP_CLIENT_TIMEOUT 4

#!trydef NGA_HELD_URL_PREFIX https://psap.agg.nga911.com/lis/

#!trydef NGA911_LOST_ENABLED 1
#!trydef NGA911_HELD_ENABLED 1

nga911.lost_enabled = NGA911_LOST_ENABLED descr "allow send LOST request"
nga911.held_enabled = NGA911_HELD_ENABLED descr "allow send HELD request"

####### HTTP client module  ########
loadmodule "http_client.so"
modparam("http_client", "query_result", 0)
modparam("http_client", "httpproxy", PROXY_URL)
modparam("http_client", "httpproxyport", PROXY_PORT)
modparam("http_client", "keep_connections", 1)
modparam("http_client", "connection_timeout", HTTP_CLIENT_TIMEOUT)
modparam("http_client", "useragent", "nga911 SIPAGG")
modparam("http_client", "httpcon", "lostsrv=>https://api.agg.nga911.com/api/v1/lost/find-service-by-location");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/default=>https://psap.agg.nga911.com/lis/default");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/atos=>https://psap.agg.nga911.com/lis/atos");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/bandwidth=>https://psap.agg.nga911.com/lis/bandwidth");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/frontier=>https://psap.agg.nga911.com/lis/frontier");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/intrado=>https://psap.agg.nga911.com/lis/intrado");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/motorola=>https://psap.agg.nga911.com/lis/motorola");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/nga911=>https://psap.agg.nga911.com/lis/nga911");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/onstar=>https://psap.agg.nga911.com/lis/onstar");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/t-mobile=>https://psap.agg.nga911.com/lis/t-mobile");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/telnyx=>https://psap.agg.nga911.com/lis/telnyx");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/truleap=>https://psap.agg.nga911.com/lis/truleap");
modparam("http_client", "httpcon", "https://psap.agg.nga911.com/lis/verizon=>https://psap.agg.nga911.com/lis/verizon");

####### Lost module  ########
loadmodule "lost.so"
modparam("lost", "response_time", 5000)
modparam("lost", "location_type", "locationURI geodetic")
modparam("lost", "exact_type", 1)
modparam("lost", "geoheader_type", 4)
modparam("lost", "geoheader_order", 1)

loadmodule "uuid.so"

route[NORMALIZE_EMERGENCY_HEADERS]
{
	if (!isflagset(FLAG_EMERGENCY_CALL)) {
		return;
	}

	# saving urn for LOST request
	if ($rz == "urn") {
		$var(urn) = "";
	} else {
		$var(urn) = "urn:" + $ru;
	}

	if ($rz != "urn") {
		$ru = "urn:service:sos";
	}

	if(strempty(@lost.nena.call_id)) {
		append_hf("Call-Info: <urn:nena:uid:callid:$(ci{s.md5}):$HN(f)>;purpose=nena-CallId\r\n");
	}

	if(strempty(@lost.nena.incident_id)) {
		append_hf("Call-Info: <urn:nena:uid:incidentid:$(ci{s.md5}):$HN(f)>;purpose=nena-IncidentId\r\n");
	}
}

route[DEREFERENCE_GEOLOCATION]
{
	$var(pidf) = "";
	$var(i) = $hdrc(Geolocation) - 1;
	while ($var(i) >= 0) {
		if ($(hdr(Geolocation)[$var(i)]) =~ "<cid:.*$") {
			xlog("L_DEBUG", "$ci|log|call already has Goelocation by_value, not required dereferencing\n");
			return;
		}
		$var(i) = $var(i) - 1;
	}

	if (is_present_hf("Geolocation")) {
		$var(geo_url) = @hf_value.geolocation[1].uri;
		xlog("L_INFO", "Trying HELD request to '$var(geo_url)'\n");
		$var(res) = lost_held_dereference("$var(geo_url)", "emergencyRouting", "geodetic civic", "$var(pidf)", "$var(err)");
		switch ($var(res)) {
			case 500:
				xlog("L_ERR", "Failed HELD dereferencing with code '$var(err)'\n");
				prom_counter_inc("external_requests", "1", "held", "failed");
				sl_send_reply("404", "Not found");
				exit;
			case 400:
				xlog("L_ERR", "Failed HELD dereferencing with code: result code $var(res), error message '$var(err)'\n");
				prom_counter_inc("external_requests", "1", "held", "failed");
				sl_send_reply("503", "Service Unavailable");
				exit;
			case 203:
			case 202:
				xlog("L_INFO", "Received HELD response'\n");
				prom_counter_inc("external_requests", "1", "held", "succesful");
				route(EMBED_GEOLOCATION);
				break;
			case 201:
				xlog("L_ERR", "Failed HELD dereferencing, responce do not containes Geolocation'\n");
				prom_counter_inc("external_requests", "1", "held", "failed");
				sl_send_reply("502", "Bad Gateway");
				exit;
			case 200:
				xlog("L_ERR", "Failed HELD dereferencing, responce do not containes LocatonURI and Geolocation'\n");
				prom_counter_inc("external_requests", "1", "held", "failed");
				sl_send_reply("404", "Not found");
				exit;
		}
	}

	if($sel(cfg_get.nga911.held_enabled) != 1) {
		return;
	}

	if (!allow_source_address() || !attr_exists("tag")) {
		xlog("L_INFO", "for '$si' address cannot be determined tag. Will be used default HELD URI\n");
		$avp(s:tag) = "carrier=default";
	}

	$var(lis_url) = $_s($def(NGA_HELD_URL_PREFIX)$(avp(tag){param.value,carrier}));

	# HELD request
	$var(res) = lost_held_query($var(lis_url), "$var(pidf)", "$var(geo_url)", "$var(err)");
	if ( $var(res) == 500) {
		xlog("L_ERR", "CSP rejected HELD request with error message '$var(err)'\n");
		sl_send_reply("404", "Not found");
		prom_counter_inc("external_requests", "1", "held", "failed");
		exit;
	} else if ( $var(res) != 200) {
		xlog("L_ERR", "HELD request failed: result code $var(res), error message '$var(err)'\n");
		sl_send_reply("503", "Service Unavailable");
		prom_counter_inc("external_requests", "1", "held", "failed");
		exit;
	}
	prom_counter_inc("external_requests", "1", "held", "succesful");
}

route[EMBED_GEOLOCATION] {
	if (!has_body("multipart/mixed")) {
		xlog("L_DEBUG", "Required add geolocation, converting body to multipart\n");
		set_body_multipart();
		msg_apply_changes();
	}
		$var(content_id) = $_s($(uuid(g){s.replace,-,})@$HN(f));
	append_hf_value("Geolocation", "<cid:$var(content_id)>");
	append_body_part("$var(pidf)", "application/pidf+xml\r\nContent-ID: <$var(content_id)>\r\nContent-Location: $var(geo_url)\r\nForwarded: by=$HN(f)");
	msg_apply_changes();
}

route[LOST_REQUEST] {
	if($sel(cfg_get.nga911.lost_enabled) != 1) {
		return;
	}

	$var(res) = lost_query("lostsrv", "$var(pidf)", "$var(urn)", "$var(uri)", "$var(name)", "$var(err)");
	if ( $var(res) == 500) {
		xlog("L_ERR", "CSP rejected LOST request with error message '$var(err)'\n");
		sl_send_reply("502", "Bad Gateway");
		prom_counter_inc("external_requests", "1", "lost", "failed");
		exit;
	} else if ( $var(res) != 200) {
		xlog("L_ERR", "LOST request failed: result code $var(res), error message '$var(err)'\n");
		sl_send_reply("503", "Service Unavailable");
		prom_counter_inc("external_requests", "1", "lost", "failed");
		exit;
	}
	prom_counter_inc("external_requests", "1", "lost", "succesful");
	# $tn = $var(name);
	$tu = $var(uri);

	xlog("L_WARN", "LOST request success: new uri '$var(uri)' with name '$var(name)'\n");
}

#!ifdef ENCODE_HELD_ADR_URLS_ROLE
route[ENCODE_HELD_ADR_URLS] {
	$var(hdrc) = $hdrc(Geolocation);
	$var(i) = 0;

	remove_hf("Geolocation");
	while($var(i) < $var(hdrc)) {
		if (pcre_match("$(hdr(Geolocation)[$var(i)])", "<https?://.*>")) {
			$var(url) = $(hdr(Geolocation)[$var(i)]{re.subst,|<(https?://.*)>.*|\1|});
			$var(tail) = $(hdr(Geolocation)[$var(i)]{re.subst,|.*>(.*)|\1|});
			$var(url) = "http://" + $def(HTTP_PROXY) + "/" + $(var(url){s.encode.base64});
			insert_hf("Geolocation: <$var(url)>$var(tail)\r\n", "Geolocation");
		} else {
			insert_hf("Geolocation: $(hdr(Geolocation)[$var(i)])\r\n", "Geolocation");
		}
		$var(i) = $var(i) + 1;
	}

	$var(hdrc) = $hdrc(Call-Info);
	$var(i) = 0;

	remove_hf("Call-Info");
	while($var(i) < $var(hdrc)) {
		if (pcre_match("$(hdr(Call-Info)[$var(i)])", "<https?://.*>")) {
			$var(url) = $(hdr(Call-Info)[$var(i)]{re.subst,|<(https?://.*)>.*|\1|});
			$var(tail) = $(hdr(Call-Info)[$var(i)]{re.subst,|.*>(.*)|\1|});
			$var(url) = "http://" + $def(HTTP_PROXY) + "/" + $(var(url){s.encode.base64});
			insert_hf("Call-Info: <$var(url)>$var(tail)\r\n", "Call-Info");
		} else {
			insert_hf("Call-Info: $(hdr(Call-Info)[$var(i)])\r\n", "Call-Info");
		}
		$var(i) = $var(i) + 1;
	}

	pv_unset("$var(hdrc)");
	pv_unset("$var(i)");
}
#!endif
tcp_connection_lifetime=1810

loadmodule "msrp.so"

# "msrpsesid" table we use forward messaes between sessions
modparam("htable", "htable", "msrpsesid=>size=8;autoexpire=3600;")
modparam("htable", "htable", "msrpconn=>size=8;autoexpire=3600;")

route[MSRP_INJECT_PROXY]
{
	if (!sdp_content()) {
		return;
	}

	if (!sdp_with_media("message")) {
		$var(sdp_for_rtpengine) = $sdp(body);
		return;
	}

	# m=message media description
	$var(message_media_description) = $(sdp(body){re.subst,/^.*m=message//s}{re.subst,/m=.*?//s}{re.subst,/(.*)/m=message\1/s});
	# MSRP transport TCP/TLS/MSRP or TCP/MSRP
	$var(message_media_transport) = $(var(message_media_description){re.subst,/.*(TCP\/TLS\/MSRP|TCP\/MSRP).*/\1/s});
	# MSRP attributes
	$var(message_media_attributes) = $(var(message_media_description){re.subst,/m=message.*//}{re.subst,/c=.*//}{re.subst,/a=path:msrp.*//}{s.trim});
	if ($var(message_media_attributes) != "") {
		$var(message_media_attributes) = $var(message_media_attributes) + "\r\n";
	}
	# MSRP port
	if ($var(message_media_transport) == "TCP/TLS/MSRP") {
		$var(message_media_port) = 2856;
		$var(message_uri_prefix) = "msrps";
	} else {
		$var(message_media_port) = 2855;
		$var(message_uri_prefix) = "msrp";
	}
	# MSRP "c=" line
	if (is_request()) {
		$var(message_c_line) = $_s(c=IN IP4 $def(IPV4_ADDR)) + "\r\n";
	} else {
		$var(message_c_line) = $_s(c=IN IP6 $def(IPV6_ADDR)) + "\r\n";
	}
	# MSRP session_id
	$var(message_session) = $(ci{s.md5});
	xlog("L_ERROR", "$ci|log|msrp_proxy SDP for RTPengine: $var(message_media_description)\n");

	# save request and reponse msrp uris, as key used prev and next session-id
	if (is_request()) {
		$avp(msrp_uris) = $(var(message_media_description){re.subst,/.*a=path://s}{re.subst,/^a=.*//g}{s.trim});
	} else {
		$var(msrp_session_id_caller) = $(avp(msrp_uris){re.subst,/ .*//}{msrpuri.session});
		$sht(msrpsesid=>$var(msrp_session_id_caller)) = $(var(message_media_description){re.subst,/.*a=path://s}{re.subst,/^a=.*//g}{s.trim});
		$var(msrp_session_id_callee) = $(sht(msrpsesid=>$var(msrp_session_id_caller)){re.subst,/ .*//}{msrpuri.session});
		$sht(msrpsesid=>$var(msrp_session_id_callee)) = $avp(msrp_uris);

		pv_unset("$var(msrp_session_id_caller)");
		pv_unset("$var(msrp_session_id_callee)");
	}

	if (is_request()) {
		$var(apath_ip) = $def(IPV4_ADDR);
	} else {
		$var(apath_ip) = $_s([$def(IPV6_ADDR)]);
	}
	$var(message_media_description_new) = $_s(m=message $var(message_media_port) $var(message_media_transport) *) + "\r\n"
		+ $var(message_c_line)
		+ $var(message_media_attributes)
		+ $_s(a=path:$var(message_uri_prefix)://$var(apath_ip):$var(message_media_port)/$var(message_session);tcp) + "\r\n";

	# Cleanup temporal vars
	pv_unset("$var(apath_ip)");
	pv_unset("$var(message_media_description)");
	pv_unset("$var(message_media_transport)");
	pv_unset("$var(message_media_attributes)");
	pv_unset("$var(message_media_port)");
	pv_unset("$var(message_uri_prefix)");
	pv_unset("$var(message_c_line)");
	pv_unset("$var(message_session)");

	# Strip message media
	$var(sdp_without_message_media_before) = $(sdp(body){re.subst,/m=message.*//s});
	$var(sdp_without_message_media_tmp) = $(sdp(body){re.subst,/.*m=message//s});
	if (!pcre_match("$var(sdp_without_message_media_tmp)", "(?m)m=")) {
		$var(sdp_without_message_media_after) = "";
	} else {
		$var(sdp_without_message_media_after) = $(var(sdp_without_message_media_tmp){re.subst,/.*?m=/m=/s});
	}

	$var(sdp_for_rtpengine) = $var(sdp_without_message_media_before)
		+ $var(sdp_without_message_media_after)
		+ $var(message_media_description_new);

	# Cleanup temporal vars
	pv_unset("$var(sdp_without_message_media_before)");
	pv_unset("$var(sdp_without_message_media_after)");
	pv_unset("$var(message_media_description_new)");

	xlog("L_ERROR", "$ci|log|msrp_proxy SDP for RTPengine: $var(sdp_for_rtpengine)\n");
}

route[MSRP_RELAY]
{
	# Updating current connection data for responses
	$sht(msrpconn=>$var(prevhop_session_id):srcaddr) = $msrp(srcaddr);
	$sht(msrpconn=>$var(prevhop_session_id):srcsock) = $msrp(srcsock);

	# Try get destination connection data
	$var(srcaddr) = $sht(msrpconn=>$var(nexthop_session_id):srcaddr);
	$var(srcsock) = $sht(msrpconn=>$var(nexthop_session_id):srcsock);

	if ($var(srcaddr) == 0 || $var(srcsock) == 0) {
		xlog("L_INFO", "$ci|log|no destination msrp connection data, try relay message for session_id: $msrp(sessid)\n");
		msrp_set_dst("$var(msrp_next_uri)", "");
		msrp_relay2($var(msrp_to_uris), $msrp(sessid));
		exit;
	}

	xlog("L_INFO", "$ci|log|relaying msrp message for session_id '$var(nexthop_session_id)' via socket $var(srcsock) to $var(srcaddr)\n");

	msrp_set_dst("$var(srcaddr)", "$var(srcsock)");
	msrp_relay_flags("1");
	msrp_relay2($var(msrp_to_uris), $msrp(sessid));

}

event_route[msrp:frame-in]
{
	$var(prevhop_session_id) = $(msrp(prevhop){msrpuri.session});
	$var(msrp_to_uris) = $sht(msrpsesid=>$var(prevhop_session_id));
	$var(nexthop_session_id) = $(var(msrp_to_uris){re.subst,/ .*//}{msrpuri.session});
	$var(msrp_from_uris) = $sht(msrpsesid=>$var(nexthop_session_id));

	$var(msrp_firsthop) = $(var(msrp_from_uris){re.subst,/.* //});;
	$var(msrp_next_uri) = $(var(msrp_to_uris){re.subst,/ .*//});

	msrp_reply_flags("1");

	if ($var(msrp_firsthop) == 0) {
		msrp_reply("481", "Session-does-not-exist");
	} else if (msrp_is_reply() && ($msrp(firsthop) == $var(msrp_firsthop))) {
		# Relay reply mesages from first hop
		route(MSRP_RELAY);
		exit;
	} else if (msrp_is_reply()) {
		# Relay ignore reply from intermediate hosts
		xlog("L_INFO", "ignored response from intermediate host\n");
		drop;
	}
	route(MSRP_RELAY);
}
####### NAT Traversal Logic ########
route[NAT_MANAGE]
{
	if (!is_first_hop()) {
		xlog("L_DBG", "skiping NAT correction for request not from UAC\n");
		return;
	}

	if ($proto == "ws" || $proto == "wss") {
		setflag(FLAG_OUTBOUND_FORCE);
		return;
	}

	if (is_method("REGISTER" )) {
		setflag(FLAG_OUTBOUND_FORCE);
		return;
	}

	setflag(FLAG_OUTBOUND_FORCE);
}
loadmodule "phonenum.so"

route[NORMALIZE_HEADERS] {
	if (!has_totag()) {
		route(NORMALIZE_RURI);
		route(NORMALIZE_FROM);
		route(NORMALIZE_TO);
		route(NORMALIZE_NUMBERS);
	} else if (is_method("REFER")) {
		route(NORMALIZE_REFER);
	}
}

route[NORMALIZE_RURI] {
	if (!is_ipv4($rd) && !is_ipv6_reference($rd)) {
		return;
	}

	if (isflagset(FLAG_OUTBOUND_CALL)){
		return;
	}

	if (attr_exists("tag")) {
		$rd = $(avp(tag){param.value,realm});
	}
}

route[NORMALIZE_FROM] {
	if (!is_ipv4($fd) && !is_ipv6_reference($fd)) {
		return;
	}

	if (attr_exists("tag")) {
		$fd = $(avp(tag){param.value,realm});
	}
}

route[NORMALIZE_TO] {
	if ($(tu{uri.scheme}) == "urn") {
		if (is_present_hf("Route")) {
			$tu = $_s(sip:911@$(route_uri{uri.domain}));
		} else {
			$tu = $_s(sip:911@$def(IPV4_ADDR));
		}
	}

	if (!is_ipv4($td) && !is_ipv6_reference($td)) {
		return;
	}

	if (attr_exists("tag")) {
		$td = $(avp(tag){param.value,realm});
	}
}

route[NORMALIZE_NUMBERS] {
	if (attr_exists("tag")) {
		$var(country_code_chars) = $(avp(tag){param.value,country});
	} else {
		$var(country_code_chars) = $def(DEFAULT_COUNTRY_CODE_CHARS);
	}

	if(!phonenum_match_cn("$fU", "$var(country_code_chars)", "phn_from")) {
		xlog("L_ERROR", "cannot call phonenum_match\n");
		return;
	}

	if($phn(phn_from=>valid)==1) {
		$fU = $phn(phn_from=>normalized);
		xlog("L_INFO", "From number normalized to: $phn(phn_from=>normalized)\n");
	}

	if(is_present_hf("P-Asserted-Identity") && !phonenum_match_cn("$(ai{uri.user})", "$var(country_code_chars)", "phn_pai")) {
		xlog("L_ERROR", "cannot call phonenum_match\n");
		return;
	}

	if($phn(phn_pai=>valid)==1) {
		remove_hf("P-Asserted-Identity");
		append_hf("P-Asserted-Identity: <sip:$phn(phn_pai=>normalized)@$fd>\r\n");
		xlog("L_INFO", "PAI number normalized to: $phn(phn_pai=>normalized)\n");
	}

	if (isflagset(FLAG_EMERGENCY_CALL) || isflagset(FLAG_988_CALL)) {
		msg_apply_changes();
		return;
	}

	if(!phonenum_match_cn("$tU", "$var(country_code_chars)", "phn_to")) {
		xlog("L_ERROR", "cannot call phonenum_match\n");
		return;
	}

	if($phn(phn_to=>valid)==1) {
		$tU = $phn(phn_to=>normalized);
		xlog("L_INFO", "To number normalized to: $phn(phn_to=>normalized)\n");
	}

	if(!phonenum_match_cn("$rU", "$var(country_code_chars)", "phn_ruri")) {
		xlog("L_ERROR", "cannot call phonenum_match\n");
		return;
	}

	if($phn(phn_ruri=>valid)==1) {
		$rU = $phn(phn_ruri=>normalized);
		xlog("L_INFO", "RURI number normalized to: $phn(phn_ruri=>normalized)\n");
	}

	msg_apply_changes();
}

route[NORMALIZE_REFER] {
	if (attr_exists("tag")) {
		$var(country_code_chars) = $(avp(tag){param.value,country});
	} else {
		$var(country_code_chars) = $def(DEFAULT_COUNTRY_CODE_CHARS);
	}

	if(!phonenum_match_cn("$(rt{uri.user})", "$var(country_code_chars)", "phn_rt")) {
		xlog("L_ERROR", "cannot call phonenum_match\n");
		return;
	}

	if($phn(phn_rt=>valid)==1) {
		append_hf("Refer-To: $(hdr(Refer-To){re.subst,/.*<?sip:.*@(.*)>?/<sip:$phn(phn_rt=>normalized)@\1>/})\r\n", "Refer-To");
		remove_hf("Refer-To");
		xlog("L_INFO", "Refer-To number normalized to: $phn(phn_rt=>normalized)\n");
	}
}
#!trydef SDP_ADDRESS_FAMILY IP4

####### RTP-proxy server managment module ##########
loadmodule "rtpengine.so"
modparam("rtpengine", "db_url", "KAMAILIO_DB_URL")
#!ifdef MSRP_PROXY_ROLE
modparam("rtpengine", "read_sdp_pv", "$var(sdp_for_rtpengine)")
#!endif

route[PROXY_MEDIA]
{
    if (!sdp_content()) {
        return;
    }

    #!ifdef DECRYPT_SRTP_ROLE
    route(PROXY_MEDIA_HANDLE_ENCRYPTION);
    #!else
    $var(sdp_transport) = "";
    #!endif

    rtpengine_manage("ICE=force address-family=$def(SDP_ADDRESS_FAMILY) SDES=off $var(sdp_transport)");
}

#!trydef SANITY_CHECK_USE_PORT 1
#!trydef SANITY_DROPS_REQUEST 1
#!trydef SANITY_DEFAULT_CHECK 17895
#!trydef SANITY_URI_CHECKS 7
#!trydef SANITY_TRACE_REQUEST 1

# # #!substdef "!SANITY_SUBST_CACHE_PERIOD!$def(SANITY_CACHE_PERIOD)!g"

######## SIP message formatting sanity checks [requires sl] ########
loadmodule "sanity.so"
modparam("sanity", "default_checks", SANITY_DEFAULT_CHECK)
modparam("sanity", "uri_checks", SANITY_URI_CHECKS)
modparam("sanity", "autodrop", 0)
modparam("sanity", "noreply", 1)

# # #!ifdef WITH_DEBUG
# # modparam("debugger", "mod_level", "sanity=-3")
# # #!endif

nga911.sanity_check_use_port = SANITY_CHECK_USE_PORT descr "should we keep track of ip and port for sanity failures"
nga911.sanity_drops_request = SANITY_DROPS_REQUEST descr "should we drop the request or send error on sanity failure"
nga911.sanity_trace_request = SANITY_TRACE_REQUEST descr "should we trace the request if sip trace role is enabled"

route[SANITY_CHECK]
{
	## CVE-2018-14767
	if($(hdr(To)[1]) != $null) {
		xlog("second To header not null - dropping message");
		drop;
	}

	$var(sanity_key) = "";
	if($sel(cfg_get.nga911.sanity_check_use_port) == 1) {
		$var(sanity_key) = $_s("$si::$sp");
	} else {
		$var(sanity_key) = $_s("$si");
	}

	if (!sanity_check()) {
		if($sel(cfg_get.nga911.sanity_drops_request) == 1) {
			xlog("L_WARN", "$ci|end|dropping insane message from $si:$sp\n");
			drop;
		} if (allow_source_address()) {
			sanity_reply();
			exit;
		} else {
			xlog("L_WARN", "$ci|end|insane message from $si:$sp\n");
			send_reply("400", "Bad Request");
			exit;
		}
	}

	if (!mf_process_maxfwd_header("10")) {
		xlog("L_WARN", "$ci|end|too much hops, not enough barley from $si:$sp\n");
		send_reply("483", "Too Many Hops");
		exit;
	}

	if ($ua == "friendly-scanner" ||
		$ua == "sundayddr" ||
		$ua == "pplsip" ||
		$ua =~ "NiceGuy" ||
		$ua =~ "PortSIP" ||
		$ua =~ "QNQ" ||
		$ua =~ "sipcli" ) {
		xlog("L_WARN", "$ci|end|dropping message with user-agent $ua from $si:$sp\n");
		drop;
	}

	if(sdp_content()) {
		if(sdp_get_line_startswith("$avp(sanity_sline)", "s=")) {
			if ($avp(sanity_sline) == "s=portsip.com") {
				xlog("L_WARN", "$ci|end|dropping message with '$avp(sanity_sline)' string in SDP\n");
				exit;
			}
		}
	}
}

route[HANDLE_COMMA_HEADERS] {
	remove_hf("Geolocation");
	$var(hdrc) = $hdrc(Geolocation);
	$var(i) = 0;
	while($var(i) < $var(hdrc)) {
		# Convert comma separated header to one value header
		insert_hf("Geolocation: $(hdr(Geolocation)[$var(i)]{re.subst,/,/\r\nGeolocation: /g})\r\n", "Geolocation");
		$var(i) = $var(i) + 1;
	}

	remove_hf("Call-Info");
	$var(hdrc) = $hdrc(Call-Info);
	$var(i) = 0;
	while($var(i) < $var(hdrc)) {
		# Convert comma separated header to one value header
		insert_hf("Call-Info: $(hdr(Call-Info)[$var(i)]{re.subst,/,/\r\nCall-Info: /g})\r\n", "Call-Info");
		$var(i) = $var(i) + 1;
	}

	msg_apply_changes();
	pv_unset("$var(hdrc)");
	pv_unset("$var(i)");
}
route[MANAGE_SESSION_ID] {
	if (is_request() && is_present_hf("Session-ID")) {
		$avp(local_session_id) = $(hdr(Session-ID){re.subst,/;remote=.*//});
		return;
	} else if (is_request()) {
		append_hf("Session-ID: $(ci{s.md5});remote=00000000000000000000000000000000\r\n");
		return;
	} else if (is_present_hf("Session-ID") && $avp(local_session_id) != $null) {
		return;
	} else if (!is_present_hf("Session-ID") && $avp(local_session_id) == $null) {
		return;
	} else if (is_present_hf("Session-ID") && $avp(local_session_id) == $null) {
		remove_hf("Session-ID");
		return;
	}

	# If response do not contains "Session-ID" header but request contain
	append_hf("Session-ID: $avp(local_session_id);remote=$(ci{s.md5})\r\n");
}
#################################
## SIP_TRACE_ALL_ROLE Defs

#!trydef SIP_TRACE_ON 0
#!trydef SIP_TRACE_URI "sip:127.0.0.1:9060"
#!trydef HEP_CAPTURE_ID 1

##############################################################
## Kamailio siptrace settings configuration examples at runtime
## kamcmd siptrace.status on

####### Siptrace module  ##########

loadmodule "siptrace.so"
modparam("siptrace", "duplicate_uri", SIP_TRACE_URI)
modparam("siptrace", "hep_mode_on", 1)
modparam("siptrace", "hep_version", 3)
modparam("siptrace", "hep_capture_id", HEP_CAPTURE_ID)
modparam("siptrace", "trace_to_database", 0)
modparam("siptrace", "trace_on", SIP_TRACE_ON)
modparam("siptrace", "trace_mode", 1)
modparam("siptrace", "trace_init_mode", 1)

# To enable OPTIONS relay to sip-trace
# kamcmd cfg.sets nga911 trace_ignored_methods NONE
# To enable sip-trace packet from/to specific network
# kamcmd cfg.sets nga911 trace_troubleshooting_net "2605:97c0:2051::/48"

#####!trydef NGA911_TRACE_IGNORED_METHODS "OPTIONS"
#!trydef NGA911_TRACE_IGNORED_METHODS "OPTIONS"
#!trydef NGA911_TRACE_TROUBLESHOOT_NET ""

nga911.trace_ignored_methods = NGA911_TRACE_IGNORED_METHODS descr "trace ignored methods"
nga911.trace_troubleshooting_net = NGA911_TRACE_TROUBLESHOOT_NET descr "trace all SIP messages from given network"

# kamcmd cfg.sets nga911 trace_ignored_methods NONE
# kamcmd cfg.sets nga911 trace_troubleshooting_net 2605:97c0:2050:1014:4::/64,2605:97c0:2050:1015:4::1/64

event_route[siptrace:msg]
{
	if (is_method("OPTIONS") && allow_address("1", "$siptrace(src_hostip)", "0")) {
		if (is_request()) {
			prom_counter_inc("options", "1", "$(avp(tag){param.value,carrier})", "from", "$siptrace(src_hostip)");
		} else if ($rs == 200) {
			prom_counter_inc("options", "1", "$(avp(tag){param.value,carrier})", "to", "$siptrace(src_hostip)");
		}
	}

	if ($sel(cfg_get.nga911.trace_troubleshooting_net) != "") {
		if (is_in_subnet($siptrace(src_hostip), "$sel(cfg_get.nga911.trace_troubleshooting_net)")) {
			return;
		}
		if (is_in_subnet($siptrace(dst_hostip), "$sel(cfg_get.nga911.trace_troubleshooting_net)")) {
			return;
		}
	}

	if($rm =~ $sel(cfg_get.nga911.trace_ignored_methods)) {
		xlog("L_DEBUG", "trace|dropping trace for method $rm\n");
		drop();
	}
}
#!trydef NGA911_STATUS_SIP_OPTIONS 1
#!trydef NGA911_STATUS_SIP_READY 1

##############################################################
## Kamailio status configuration examples at runtime
## kamcmd cfg.seti nga911 status_sip_options 0
## kamcmd cfg.seti nga911 status_sip_ready 0

nga911.status_sip_options = NGA911_STATUS_SIP_OPTIONS descr "allow options 200 OK response"
nga911.status_sip_ready = NGA911_STATUS_SIP_READY descr "allow SIP message processing"

route[STATUS_CHECK] {
    if ($sel(cfg_get.nga911.status_sip_ready) != 1 && is_method("INVITE|UPDATE|REGISTER") && ! t_check_trans()) {
        sl_send_reply("503", "Service Unavailable");
        exit;
    }

    if (is_method("OPTIONS")) {
        if ($sel(cfg_get.nga911.status_sip_options) == 1) {
            sl_send_reply("200", "OK");
        } else if (allow_source_address(OPTIONS_503_NOT_SUPPORTED)) {
            xlog("L_DEBUG", "$ci|log|request  from options address group. source:$si . ignoring OPTIONS request.\n");
        } else {
            sl_send_reply("503", "Service Unavailable");
        }
        exit;
    }
}

enable_tls=1

####### tls module ##########
loadmodule "tls.so"
modparam("tls", "config", "/etc/sip-aggregator/tls.cfg")
modparam("tls", "low_mem_threshold1", 0)
modparam("tls", "session_keylog_enable", 1)

[client:default]
method = TLSv1.2+
verify_certificate = yes
require_certificate = yes
#ca_list = /etc/kamailio/ca-bundle.crt

[server:default]
method = TLSv1.2+
verify_certificate = no
require_certificate = no
private_key = /etc/sip-aggregator/tls/privkey.pem
certificate = /etc/sip-aggregator/tls/fullchain.pem
#ca_list = /etc/kamailio/ca-bundle.crt

[server:any]
method = TLSv1.2+
verify_certificate = no
require_certificate = no
private_key = /etc/sip-aggregator/tls/privkey.pem
certificate = /etc/sip-aggregator/tls/fullchain.pem
#verify_depth = 3
#ca_list = /etc/kamailio/ca-bundle.crt
#crl = /etc/kamailio/tls/mysipserver_net_crl.pem
server_name = sbc-site0-s475dhspd-psap.ca.nga911.com
server_name_mode = 1

route[FILTER_REQUEST]
{
	# allow request from internal network or from whitelist
	if (isflagset(FLAG_TRUSTED_SOURCE)) {
		return;
	}

	#  drop requests with no To domain or IP To domain (friendly-scanner)
	if (is_method("REGISTER|SUBSCRIBE|OPTIONS")) {
		route(FILTER_TO_DOMAIN);
		route(FILTER_FROM_DOMAIN);
	}

	# drop Invite with IP auth realm
	if (is_method("INVITE|CANCEL|OPTIONS")) {
		route(FILTER_REQUEST_DOMAIN);
		route(FILTER_AUTHORIZATION_DOMAIN);
		route(FILTER_TO_DOMAIN);
		route(FILTER_FROM_DOMAIN);
	}
}

route[FILTER_REQUEST_DOMAIN]
{
	if (!is_ipv4($rd) && !is_ipv6_reference($rd)) {
		return;
	}

	if (isflagset(FLAG_OUTBOUND_CALL)){
		return;
	}

	if (!has_totag()) {
		xlog("L_WARN", "end|dropping $rm request with IP domain\n");
		drop();
		exit();
	}
	xlog("L_WARN", "end|not dropping $rm request with to_tag and IP domain\n");
}

route[FILTER_AUTHORIZATION_DOMAIN]
{
	if (is_present_hf("Proxy-Authorization") &&
	  (is_ipv4($ar) || is_ipv6_reference($ar))) {
		xlog("L_WARN", "$ci|end|dropping request with IP domain in Proxy-Authorization header\n");
		drop();
		exit;
	}
}

route[FILTER_FROM_DOMAIN]
{
	if (is_ipv4($fd) || is_ipv6_reference($fd)) {
		xlog("L_WARN", "$ci|end|dropping request with IP domain in From header\n");
		drop();
		exit;
	}
}

route[FILTER_TO_DOMAIN]
{
	if (is_ipv4($td) || is_ipv6_reference($td)) {
		xlog("L_WARN", "$ci|end|dropping request with IP domain in To header\n");
		drop();
		exit;
	}
}
#################################
## STATUS_CHECK_ROLE Defs

#!trydef NGA911_STATUS_HTTP 1

##############################################################
## Kamailio status configuration examples at runtime
## kamcmd cfg.seti nga911 status_http 0

nga911.status_http = NGA911_STATUS_HTTP descr "allow 200 responce via HTTP protocol"

# provide Promethes monitoring
loadmodule "xhttp_prom.so"
modparam("xhttp_prom", "xhttp_prom_timeout", 0)
modparam("xhttp_prom", "xhttp_prom_stats", "all")
modparam("xhttp_prom", "prom_gauge", "name=status;");
modparam("xhttp_prom", "prom_counter", "name=external_requests; label=type:response;");
modparam("xhttp_prom", "prom_counter", "name=options; label=partner:direction:ip;");

# provides sockets for WebRTC clients
loadmodule "websocket.so"
modparam("websocket", "keepalive_mechanism", 3)
modparam("websocket", "keepalive_timeout", 30)
modparam("websocket", "keepalive_processes", 3)
modparam("websocket", "keepalive_interval", 1)
modparam("websocket", "ping_application_data", "NGA911 encourages you to keep alive")
modparam("websocket", "sub_protocols", 1)
modparam("websocket", "cors_mode", 1)

# provides basic health check for AWS load-balancer
loadmodule "xhttp.so"

#### json rpc ####
loadmodule "jansson.so"

# allow HTTP request
tcp_accept_no_cl=yes

event_route[xhttp:request] {
	$var(status_code) = 404;
	$var(reason_phrase) = "Not found";
	if ($hu == "/up") {
		route(HTTP_STATUS_CHECK);
	} else if (prom_check_uri() && (allow_source_address() || src_ip == myself)) {
		route(PROM_DISPATCH);
	} else {
		route(WEBRTC_SOCKET_INIT);
	}

	jansson_set("integer", "status-code", $var(status_code), "$var(status_json_data)");
	jansson_set("string", "reason-phrase", $var(reason_phrase), "$var(status_json_data)");
	jansson_set("obj", "data", "$var(status_json_data)", "$var(status_json_response)");
	append_to_reply("Access-Control-Allow-Origin: *\n");
	xhttp_reply("$var(status_code)", "$var(reason_phrase)", "application/json", "$var(status_json_response)");
}

route[WEBRTC_SOCKET_INIT] {
	if (!($hdr(Upgrade)=~"websocket"
			&& $hdr(Connection)=~"Upgrade"
			&& $rm=~"GET")) {
		$var(status_code) = 400;
		$var(reason_phrase) = "Bad Request";
		return;
	}

	if (!ws_handle_handshake()) {
		$var(status_code) = 400;
		$var(reason_phrase) = "Bad Request";
		return;
	}

	exit;
}

route[HTTP_STATUS_CHECK] {
	if($sel(cfg_get.nga911.status_http) == 1) {
		$var(status_code) = 200;
		$var(reason_phrase) = "OK";
	} else {
		$var(status_code) = 503;
		$var(reason_phrase) = "Service Unavailable";
	}
}

route[PROM_DISPATCH] {
	if ($sel(cfg_get.nga911.status_sip_ready) != 1) {
		prom_gauge_set("status", "$def(ATOS_CRITICAL)");
	} else if ($sel(cfg_get.nga911.status_sip_options) != 1) {
		prom_gauge_set("status", "$def(ATOS_MINOR)");
	} else {
		prom_gauge_set("status", "$def(ATOS_NORMAL)");
	}

	prom_dispatch();
	exit;
}
