#!KAMAILIO

# Sample demo config for Kamailio-CGRateS communication
# tested against Kamailio 5.1

####### Defined Values #########

#!define FLT_DIALOG 4
#!define FLT_NATS 5
#!define FLB_NATB 6
#!define FLB_NATSIPPING 7

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

debug=2
log_stderror=no

listen=udp:enp0s3:5060
listen=udp:127.0.0.1:5080
listen=udp:127.0.0.1:5060
listen=udp:enp0s3:5080

memdbg=5
memlog=5
log_facility=LOG_LOCAL0
fork=yes
children=4
tcp_connection_lifetime=3605
use_dns_cache=no
dns_try_ipv6=no
dns_retr_time=1
dns_retr_no=1
dns_servers_no=1
dns_use_search_list=no

####### Modules Section ########

mpath="/usr/lib/x86_64-linux-gnu/kamailio/modules/"

loadmodule "kex.so"
loadmodule "corex.so"
loadmodule "tm.so"
loadmodule "tmx.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "nathelper.so"
loadmodule "htable.so"
loadmodule "auth.so"
loadmodule "evapi.so"
loadmodule "json.so"
loadmodule "dialog.so"
loadmodule "jsonrpcs.so"



# ----------------- setting module-specific parameters ---------------

modparam("evapi", "bind_addr", "127.0.0.1:8448")

# ----- tm params -----
modparam("tm", "failure_reply_mode", 3)
modparam("tm", "fr_timer", 30000)
modparam("tm", "fr_inv_timer", 120000)

# ----- rr params -----
modparam("rr", "enable_full_lr", 0)
modparam("rr", "append_fromtag", 0)

# ----- registrar params -----
modparam("registrar", "method_filtering", 1)
modparam("registrar", "max_expires", 3600)

# ----- dialog params -----
modparam("dialog", "dlg_flag", FLT_DIALOG)
modparam("dialog", "send_bye", 1)
modparam("dialog", "timeout_noreset", 1)

# ----- nathelper params -----
modparam("nathelper", "natping_interval", 30)
modparam("nathelper", "ping_nated_only", 1)
modparam("nathelper", "sipping_bflag", FLB_NATSIPPING)
modparam("nathelper", "sipping_from", "sip:pinger@kamailio.org")

# params needed for NAT traversal in other modules
modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)")
modparam("usrloc", "nat_bflag", FLB_NATB)

# ----- htable params -----
modparam("htable", "htable", "cgrconn=>size=1;")

####### Routing Logic ########

include_file "kamailio-cgrates.cfg"


# Main SIP request routing logic
request_route {

	# per request initial checks
	route(REQINIT);

	# NAT detection
	route(NATDETECT);

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

	# handle requests within SIP dialogs
	route(WITHINDLG);

	### only initial requests (no To tag)

	# handle retransmissions
	if(t_precheck_trans()) {
		t_check_trans();
		exit;
	}
	t_check_trans();

	# record routing for dialog forming requests (in case they are routed)
	# - remove preloaded route headers
	remove_hf("Route");
	if (is_method("INVITE|SUBSCRIBE"))
		record_route();

	# Not handling requests towards external domains
	if uri != myself {
		sl_send_reply("604", "Only local destinations accepted");
		exit;
	}

	### requests for my local domains

	# SIMPLE AUTH methods
	if is_method("REGISTER|SUBSCRIBE|PUBLISH") {
		route(CGRATES_SIMPLEAUTH_REQUEST);
		exit;
	}

	if ($rU==$null) {
		# request with no Username in RURI
		sl_send_reply("484","Address Incomplete");
		exit;
	}

	if !is_method("INVITE") {
		sl_send_reply("503", "Unsupported method");
                exit;
        }

	route(CGRATES_SESSIONAUTH_REQUEST);
	exit;
}


# Here will land requests after processing them with CGRateS.
route[CGRATES_SIMPLEAUTH_REPLY] {
        if $var(cgrError) != "" {
                xlog("CGR_PROFILE_ERROR: $var(cgrError)");
                sl_send_reply("503","CGR_ERROR");
                exit;
        }

	# parse the CGRateS attributes
	route(PARSE_CGRATES_ATTRIBUTES);

        # password based auth
        route(AUTH);

        if is_method("REGISTER") {
                route(REGISTRAR);
                exit;
        }

        # other methods going over location
        route(LOCATION);
        route(RELAY);
}


# Here will land requests after processing them with CGRateS. Call RELAY or other routes following this route
route[CGRATES_SESSIONAUTH_REPLY] {

	if $var(cgrError) != "" {
		xlog("CGR_AUTH_ERROR: $var(cgrError)");
		sl_send_reply("503","CGR_ERROR");
		exit;
	}

	 # parse the CGRateS attributes
        route(PARSE_CGRATES_ATTRIBUTES);

        # password based auth
        route(AUTH);

	# track the dialog so we can set it's timeout and channel variables to store in CDRs
	# cannot initialize the dialog sooner due to AUTH
	dlg_manage();
	$dlg_var(SetupTime) = $TS;
        $dlg_var(cgrOriginID) = $dlg(callid) + ";" + $dlg(from_tag);
	$dlg_var(cgrTenant) = "cgrates.org";
	$dlg_var(cgrReqType) = $avp(RequestType);
        $dlg_var(cgrAccount) = $fU;
        $dlg_var(cgrDestination) = $rU;
	$dlg_var(paypalAccount) = $avp(PaypalAccount);

	if $var(cgrMaxUsage) != -1 {
		if $var(cgrMaxUsage) == 0 { // Not enough balance, do not allow the call to go through
			sl_send_reply("403","Insufficient credit");
			exit;
		} else if !dlg_set_timeout("$var(cgrMaxUsage)") {
			sl_send_reply("503","CGR_MAX_USAGE_ERROR");
			exit;
		}
	}

	if $var(cgrSuppliers) != "" { # Enforce the supplier variable to the first one received from CGRateS, here more for demo purposes
		$dlg_var(cgrSupplier) = $(var(cgrSuppliers){s.select,0,,});
	}

	# user location service
	route(LOCATION);

	# send out
	route(RELAY);

}


# Wrapper for relaying requests
route[RELAY] {
	# enable additional event routes for forwarded requests
	# - serial forking, RTP relaying handling, a.s.o.
	if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) {
		if(!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH");
	}
	if (is_method("INVITE|SUBSCRIBE|UPDATE")) {
		if(!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY");
	}
	if (is_method("INVITE")) {
		if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE");
	}

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

# Per SIP request initial checks
route[REQINIT] {
	if (!mf_process_maxfwd_header("10")) {
		sl_send_reply("483","Too Many Hops");
		exit;
	}

	if(is_method("OPTIONS") && uri==myself && $rU==$null) {
		sl_send_reply("200","Keepalive");
		exit;
	}

	if(!sanity_check("1511", "7")) {
		xlog("Malformed SIP message from $si:$sp\n");
		exit;
	}
}

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

	# sequential request withing a dialog should
	# take the path determined by record-routing
	if (loose_route()) {
		route(DLGURI);
		if ( is_method("ACK") ) {
			# ACK is forwarded statelessy
			route(NATMANAGE);
		}
		else if ( is_method("NOTIFY") ) {
			# Add Record-Route for in-dialog NOTIFY as per RFC 6665.
			record_route();
		}
		route(RELAY);
		exit;
	}
	if ( is_method("ACK") ) {
		if ( t_check_trans() ) {
			# no loose-route, but stateful ACK;
			# must be an ACK after a 487
			# or e.g. 404 from upstream server
			route(RELAY);
			exit;
		} else {
			# ACK without matching transaction ... ignore and discard
			exit;
		}
	}
	sl_send_reply("404","Not here");
	exit;
}

# Handle SIP registrations
route[REGISTRAR] {
	if(isflagset(FLT_NATS)) {
		setbflag(FLB_NATB);
	}
	if (!save("location"))
		sl_reply_error();
	exit;
}

# User location service
route[LOCATION] {
	$avp(oexten) = $rU;
	if (!lookup("location")) {
		$var(rc) = $rc;
		t_newtran();
		switch ($var(rc)) {
			case -1:
			case -3:
				send_reply("404", "Not Found");
				exit;
			case -2:
				send_reply("405", "Method Not Allowed");
				exit;
		}
	}
}


# user uthentication
route[AUTH] {
	if (is_method("REGISTER")) {
		if ( strempty($au) || !pv_www_authenticate("$td", "$avp(Password)", "0") ) {
			www_challenge("$td", "0");
			exit;
		}
	} else { # All other methods here
		if ( strempty($au) || !pv_proxy_authenticate("$td", "$avp(Password)", "0") ) {
			proxy_challenge("$td", "0");
			exit;
		}
	}
	consume_credentials();
	return;
}


# Caller NAT detection
route[NATDETECT] {
	force_rport();
	if (nat_uac_test("19")) {
		if (is_method("REGISTER")) {
			fix_nated_register();
		} else {
			if(is_first_hop())
				set_contact_alias();
		}
		setflag(FLT_NATS);
	}
	return;
}

# RTPProxy control and singaling updates for NAT traversal
route[NATMANAGE] {
	if (is_request()) {
		if(has_totag()) {
			if(check_route_param("nat=yes")) {
				setbflag(FLB_NATB);
			}
		}
	}
	if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB)))
		return;

	if (is_request()) {
		if (!has_totag()) {
			if(t_is_branch_route()) {
				add_rr_param(";nat=yes");
			}
		}
	}
	if (is_reply()) {
		if(isbflagset(FLB_NATB)) {
			if(is_first_hop())
				set_contact_alias();
		}
	}
	return;
}

# URI update for dialog requests
route[DLGURI] {
	if(!isdsturiset()) {
		handle_ruri_alias();
	}
	return;
}


# Manage outgoing branches
branch_route[MANAGE_BRANCH] {
	route(NATMANAGE);
}

# Manage incoming replies
onreply_route[MANAGE_REPLY] {
	if(status=~"[12][0-9][0-9]")
		route(NATMANAGE);
}

# Manage failure routing cases
failure_route[MANAGE_FAILURE] {
	route(NATMANAGE);

	if (t_is_canceled()) {
		exit;
	}

	if (t_check_status("3[0-9][0-9]")) {
		t_reply("404","Not found");
		exit;
	}
}

