#!KAMAILIO
#
# Edge proxy configuration with SIP over WebSocket support
#

#!substdef "!DBURL!sqlite:///etc/kamailio/db.sqlite!g"
#!substdef "!MY_IP_ADDR!a.b.c.d!g"
#!substdef "!MY_DOMAIN!example.com!g"
#!substdef "!MY_WS_PORT!80!g"
#!substdef "!MY_WSS_PORT!443!g"
#!substdef "!MY_WS_ADDR!tcp:MY_IP_ADDR:MY_WS_PORT!g"
#!substdef "!MY_WSS_ADDR!tls:MY_IP_ADDR:MY_WSS_PORT!g"

#!substdef "!REGISTRAR_IP!e.f.g.h!g"
#!substdef "!REGISTRAR_PORT!5060!g"
#!substdef "!FLOW_TIMER!20!g"

#!define WITH_TLS
#!define WITH_WEBSOCKETS


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

debug=2
log_stderror=no
log_facility=LOG_LOCAL0
fork=yes
children=4
mpath="/usr/lib64/kamailio/modules/"
force_rport=yes

#!ifdef WITH_TLS
enable_tls=1
#!endif

listen=MY_IP_ADDR
#!ifdef WITH_WEBSOCKETS
listen=MY_WS_ADDR
#!ifdef WITH_TLS
listen=MY_WSS_ADDR
#!endif
#!endif

tcp_connection_lifetime=30 # FLOW_TIMER + 10
tcp_accept_no_cl=yes
tcp_rd_buf_size=16384


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

loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "outbound.so"
loadmodule "rr.so"
loadmodule "path.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "mi_rpc.so"
loadmodule "mi_fifo.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "stun.so"
loadmodule "kex.so"
loadmodule "corex.so"
#!ifdef WITH_TLS
loadmodule "tls.so"
#!endif
#!ifdef WITH_WEBSOCKETS
loadmodule "xhttp.so"
loadmodule "websocket.so"
#!endif

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

# ----- mi_fifo params -----
modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo")

# ----- tm params -----
modparam("tm", "failure_reply_mode", 3)

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

# ----- corex params -----
modparam("corex", "alias_subdomains", "MY_DOMAIN")

#!ifdef WITH_TLS
# ----- tls params -----
modparam("tls", "tls_method", "TLSv1.2+")
modparam("tls", "certificate", "/etc/pki/CA/ser1_cert.pem")
modparam("tls", "private_key", "/etc/pki/CA/privkey.pem")
modparam("tls", "ca_list", "/etc/pki/CA/calist.pem")
#!endif

#!ifdef WITH_WEBSOCKETS
# ----- websocket params -----
modparam("websocket", "keepalive_timeout", 25) # FLOW_TIMER + 5
#!endif


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

request_route {
	if (($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT)
		&& !(proto == WS || proto == WSS)) {
		xlog("L_WARN", "SIP request received on $Rp\n");
		sl_send_reply("403", "Forbidden");
		exit;
	}

	route(REQINIT);

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

	route(WITHINDLG);

	t_check_trans();

	if (is_method("REGISTER")) {
		remove_hf("Route");
		add_path();
		$du = "sip:REGISTRAR_IP:REGISTRAR_PORT";
	} else {
		if (is_method("INVITE|SUBSCRIBE"))
			record_route();

		if (@via[2] == "") {
			# From client so route to registrar...

			if ($rU == $null) {
				sl_send_reply("484", "Address Incomplete");
				exit;
			}
			remove_hf("Route");
			$du = "sip:REGISTRAR_IP:REGISTRAR_PORT";
		} else {
			# From registrar so route using "Route:" headers...

			if (!loose_route()) {
				switch($rc) {
				case -2:
					sl_send_reply("403", "Forbidden");
					exit;
				default:
					xlog("L_ERR", "in request_route\n");
					sl_reply_error();
					exit;
				}
			}

			t_on_failure("FAIL_OUTBOUND");
		}
	}

	route(RELAY);
}

route[RELAY] {
	if (!t_relay()) {
		sl_reply_error();
	}
	exit;
}

route[REQINIT] {
	if (!mf_process_maxfwd_header("10")) {
		sl_send_reply("483", "Too Many Hops");
		exit;
	}

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

route[WITHINDLG] {
	if (has_totag()) {
		if (!loose_route()) {
			switch($rc) {
			case -2:
				sl_send_reply("403", "Forbidden");
				exit;
			default:
				if (is_method("ACK")) {
					if ( t_check_trans() ) {
						route(RELAY);
						exit;
					} else {
						exit;
					}
				}
				sl_send_reply("404", "Not Found");
			}
		} else {
			if (is_method("NOTIFY")) {
				record_route();
			}
			route(RELAY);
		}
		exit;
	}
}

onreply_route {
	if (($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT)
		&& !(proto == WS || proto == WSS)) {
		xlog("L_WARN", "SIP response received on $Rp\n");
		drop;
	}

	if (!t_check_trans()) {
		drop;
	}

	if ($rm == "REGISTER" && $rs >= 200 && $rs <= 299) {
		remove_hf("Flow-Timer");
		if ($(hdr(Require)[*])=~"outbound")
			insert_hf("Flow-Timer: FLOW_TIMER\r\n", "Call-ID");
	}
}

failure_route[FAIL_OUTBOUND] {
	if (t_branch_timeout() || !t_branch_replied()) {
		send_reply("430", "Flow Failed");
	}
}

event_route[xhttp:request] {
	set_reply_close();
	set_reply_no_connect();

	if ($Rp != MY_WS_PORT
#!ifdef WITH_TLS
		&& $Rp != MY_WSS_PORT
#!endif
	) {
		xlog("L_WARN", "HTTP request received on $Rp\n");
		xhttp_reply("403", "Forbidden", "", "");
		exit;
	}

	xlog("L_DBG", "HTTP Request Received\n");

	if ($hdr(Upgrade)=~"websocket"
			&& $hdr(Connection)=~"Upgrade"
			&& $rm=~"GET") {

		# Validate Host - make sure the client is using the correct
		# alias for WebSockets
		if ($hdr(Host) == $null || !is_myself("sip:" + $hdr(Host))) {
			xlog("L_WARN", "Bad host $hdr(Host)\n");
			xhttp_reply("403", "Forbidden", "", "");
			exit;
		}

		# Optional... validate Origin - make sure the client is from an
		# authorised website.  For example,
		#
		# if ($hdr(Origin) != "http://communicator.MY_DOMAIN"
		#     && $hdr(Origin) != "https://communicator.MY_DOMAIN") {
		#	xlog("L_WARN", "Unauthorised client $hdr(Origin)\n");
		#	xhttp_reply("403", "Forbidden", "", "");
		#	exit;
                # }

		# Optional... perform HTTP authentication

		# ws_handle_handshake() exits (no further configuration file
		# processing of the request) when complete.
		if (ws_handle_handshake())
		{
			# Optional... cache some information about the
			# successful connection
			exit;
		}
	}

	xhttp_reply("404", "Not Found", "", "");
}

event_route[websocket:closed] {
	xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n");
}
