examples/websocket.cfg
48ba7477
 #!KAMAILIO
dc7fa93f
 #
 # Simple/sample kamailio.cfg for running a proxy/registrar with TLS and
 # WebSockets support.
 
eff18c9c
 #!substdef "!DBURL!sqlite:///etc/kamailio/db.sqlite!g"
324e8f36
 #!substdef "!MY_IP_ADDR!a.b.c.d!g"
84563257
 #!substdef "!MY_DOMAIN!example.com!g"
dc7fa93f
 #!substdef "!MY_WS_PORT!80!g"
 #!substdef "!MY_WSS_PORT!443!g"
132509a7
 #!substdef "!MY_MSRP_PORT!9000!g"
dc7fa93f
 #!substdef "!MY_WS_ADDR!tcp:MY_IP_ADDR:MY_WS_PORT!g"
 #!substdef "!MY_WSS_ADDR!tls:MY_IP_ADDR:MY_WSS_PORT!g"
132509a7
 #!substdef "!MY_MSRP_ADDR!tls:MY_IP_ADDR:MY_MSRP_PORT!g"
cb74d38f
 #!substdef "!MSRP_MIN_EXPIRES!1800!g"
 #!substdef "!MSRP_MAX_EXPIRES!3600!g"
dc7fa93f
 
324e8f36
 ##!define LOCAL_TEST_RUN
dc7fa93f
 #!define WITH_TLS
 #!define WITH_WEBSOCKETS
132509a7
 #!define WITH_MSRP
48ba7477
 
 
 ####### Global Parameters #########
 
 fork=yes
 children=4
 
dc7fa93f
 #!ifdef WITH_TLS
 enable_tls=1
 #!endif
ad7ea60f
 
dc7fa93f
 listen=MY_IP_ADDR
 #!ifdef WITH_WEBSOCKETS
 listen=MY_WS_ADDR
 #!ifdef WITH_TLS
 listen=MY_WSS_ADDR
 #!endif
 #!endif
132509a7
 #!ifdef WITH_MSRP
 listen=MY_MSRP_ADDR
 #!endif
48ba7477
 
 tcp_connection_lifetime=3604
 tcp_accept_no_cl=yes
dc7fa93f
 tcp_rd_buf_size=16384
ad7ea60f
 
dc7fa93f
 #!ifdef LOCAL_TEST_RUN
 debug=2
a0c85d11
 mpath="modules"
dc7fa93f
 #!else
 debug=0
a0c85d11
 mpath="/usr/lib64/kamailio/modules/"
dc7fa93f
 #!endif
48ba7477
 
 loadmodule "db_sqlite.so"
 loadmodule "tm.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 "ctl.so"
 loadmodule "auth.so"
 loadmodule "auth_db.so"
 loadmodule "kex.so"
48e7ee70
 loadmodule "mi_rpc.so"
84563257
 loadmodule "corex.so"
dc7fa93f
 #!ifdef WITH_TLS
ad7ea60f
 loadmodule "tls.so"
dc7fa93f
 #!endif
132509a7
 #!ifdef WITH_MSRP
 loadmodule "msrp.so"
 loadmodule "htable.so"
 loadmodule "cfgutils.so"
 #!endif
dc7fa93f
 #!ifdef WITH_WEBSOCKETS
 loadmodule "xhttp.so"
 loadmodule "websocket.so"
 loadmodule "nathelper.so"
 #!endif
48ba7477
 
 # ----------------- setting module-specific parameters ---------------
 
 # ----- tm params -----
 # auto-discard branches from previous serial forking leg
 modparam("tm", "failure_reply_mode", 3)
 # default retransmission timeout: 30sec
 modparam("tm", "fr_timer", 30000)
 # default invite retransmission timeout after 1xx: 120sec
 modparam("tm", "fr_inv_timer", 120000)
 
 # ----- rr params -----
 # add value to ;lr param to cope with most of the UAs
 modparam("rr", "enable_full_lr", 1)
 # do not append from tag to the RR (no need for this script)
 modparam("rr", "append_fromtag", 0)
 
 # ----- registrar params -----
 modparam("registrar", "method_filtering", 1)
 modparam("registrar", "max_expires", 3600)
 modparam("registrar", "gruu_enabled", 0)
 
 # ----- usrloc params -----
dc7fa93f
 modparam("usrloc", "db_url", "DBURL")
48ba7477
 modparam("usrloc", "db_mode", 0)
 
cb74d38f
 # ----- auth params -----
 modparam("auth", "nonce_count", 1)
 modparam("auth", "qop", "auth")
 
48ba7477
 # ----- auth_db params -----
dc7fa93f
 modparam("auth_db", "db_url", "DBURL")
48ba7477
 modparam("auth_db", "calculate_ha1", yes)
 modparam("auth_db", "password_column", "password")
5a8b8da4
 modparam("auth_db", "load_credentials", "id")
48ba7477
 
84563257
 # ----- corex params -----
 modparam("corex", "alias_subdomains", "MY_DOMAIN")
 
dc7fa93f
 #!ifdef WITH_TLS
ad7ea60f
 # ----- tls params -----
 modparam("tls", "tls_method", "SSLv23")
324e8f36
 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")
dc7fa93f
 #!endif
48ba7477
 
dc7fa93f
 #!ifdef WITH_WEBSOCKETS
 # ----- nathelper params -----
 modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)")
 # Note: leaving NAT pings turned off here as nathelper is _only_ being used for
 #       WebSocket connections.  NAT pings are not needed as WebSockets have
 #       their own keep-alives.
 #!endif
 
132509a7
 #!ifdef WITH_MSRP
 # ----- htable params -----
cb74d38f
 modparam("htable", "htable", "msrp=>size=8;autoexpire=MSRP_MAX_EXPIRES;")
132509a7
 #!endif
 
dc7fa93f
 
 ####### Routing Logic ########
48ba7477
 
 # Main SIP request routing logic
 # - processing of any incoming SIP request starts with this route
 # - note: this is the same as route { ... }
 request_route {
5a8b8da4
 	if ((($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT)
 		&& !(proto == WS || proto == WSS)) || $Rp == MY_MSRP_PORT) {
 		xlog("L_WARN", "SIP request received on $Rp\n");
 		sl_send_reply("403", "Forbidden");
 		exit;
 	}
48ba7477
 
 	# per request initial checks
 	route(REQINIT);
 
dc7fa93f
 #!ifdef WITH_WEBSOCKETS
 	if (nat_uac_test(64)) {
 		# Do NAT traversal stuff for requests from a WebSocket
 		# connection - even if it is not behind a NAT!
 		# This won't be needed in the future if Kamailio and the
c3958092
 		# WebSocket client support Outbound and Path.
dc7fa93f
 		force_rport();
5a8b8da4
 		if (is_method("REGISTER")) {
dc7fa93f
 			fix_nated_register();
5a8b8da4
 		} else {
dc7fa93f
 			if (!add_contact_alias()) {
 				xlog("L_ERR", "Error aliasing contact <$ct>\n");
 				sl_send_reply("400", "Bad Request");
 				exit;
 			}
 		}
 	}
 #!endif
 
48ba7477
 	# handle requests within SIP dialogs
 	route(WITHINDLG);
 
 	### only initial requests (no To tag)
 
 	# CANCEL processing
dc7fa93f
 	if (is_method("CANCEL")) {
5a8b8da4
 		if (t_check_trans()) {
48ba7477
 			t_relay();
5a8b8da4
 		}
48ba7477
 		exit;
 	}
 
 	t_check_trans();
 
 	# authentication
 	route(AUTH);
 
 	# record routing for dialog forming requests (in case they are routed)
 	# - remove preloaded route headers
 	remove_hf("Route");
5a8b8da4
 	if (is_method("INVITE")) {
48ba7477
 		record_route();
5a8b8da4
 	}
48ba7477
 
 	# handle registrations
 	route(REGISTRAR);
 
dc7fa93f
 	if ($rU==$null) {
48ba7477
 		# request with no Username in RURI
5a8b8da4
 		sl_send_reply("484", "Address Incomplete");
48ba7477
 		exit;
 	}
 
 	# user location service
 	route(LOCATION);
 
 	route(RELAY);
 }
 
 route[RELAY] {
 	if (!t_relay()) {
 		sl_reply_error();
 	}
 	exit;
 }
 
 # Per SIP request initial checks
 route[REQINIT] {
 	if (!mf_process_maxfwd_header("10")) {
132509a7
 		sl_send_reply("483", "Too Many Hops");
48ba7477
 		exit;
 	}
 
84563257
 	if (!sanity_check("1511", "7")) {
48ba7477
 		xlog("Malformed SIP message from $si:$sp\n");
 		exit;
 	}
84563257
 
 	if (uri == myself && is_method("OPTIONS") && !(uri=~"sip:.*[@]+.*")) {
 		options_reply();
 		exit;
 	}
48ba7477
 }
 
 # Handle requests within SIP dialogs
 route[WITHINDLG] {
 	if (has_totag()) {
 		# sequential request withing a dialog should
 		# take the path determined by record-routing
 		if (loose_route()) {
dc7fa93f
 #!ifdef WITH_WEBSOCKETS
 			if ($du == "") {
 				if (!handle_ruri_alias()) {
 					xlog("L_ERR", "Bad alias <$ru>\n");
 					sl_send_reply("400", "Bad Request");
 					exit;
 				}
 			}
 #!endif
48ba7477
 			route(RELAY);
 		} else {
 			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
 					t_relay();
 					exit;
 				} else {
 					# ACK without matching transaction...
 					# ignore and discard
 					exit;
 				}
 			}
5a8b8da4
 			sl_send_reply("404", "Not Found");
48ba7477
 		}
 		exit;
 	}
 }
 
 # Handle SIP registrations
 route[REGISTRAR] {
dc7fa93f
 	if (is_method("REGISTER")) {
5a8b8da4
 		if (!save("location")) {
48ba7477
 			sl_reply_error();
5a8b8da4
 		}
48ba7477
 		exit;
 	}
 }
 
 # USER location service
 route[LOCATION] {
5a8b8da4
 	if (!is_subscriber("$ru", "subscriber", "1")) {
 		t_newtran();
 		send_reply("404", "Not Found");
 		exit;
 	}
 
48ba7477
 	if (!lookup("location")) {
 		$var(rc) = $rc;
 		t_newtran();
 		switch ($var(rc)) {
5a8b8da4
 		case -1:
 			send_reply("480", "Temporarily Unavailable");
 			exit;
 		case -2:
 			send_reply("405", "Method Not Allowed");
 			exit;
 		case -3:
 			send_reply("500", "Server Internal Error");
 			exit;
48ba7477
 		}
 	}
 }
 
 # Authentication route
 route[AUTH] {
dc7fa93f
 	if (is_method("REGISTER") || from_uri==myself) {
48ba7477
 		# authenticate requests
 		if (!auth_check("$fd", "subscriber", "1")) {
 			auth_challenge("$fd", "0");
 			exit;
 		}
 		# user authenticated - remove auth header
5a8b8da4
 		if(!is_method("REGISTER")) {
48ba7477
 			consume_credentials();
5a8b8da4
 		}
48ba7477
 	}
 	# if caller is not local subscriber, then check if it calls
 	# a local destination, otherwise deny, not an open relay here
dc7fa93f
 	if (from_uri!=myself && uri!=myself) {
5a8b8da4
 		sl_send_reply("403", "Forbidden");
48ba7477
 		exit;
 	}
 }
 
dc7fa93f
 #!ifdef WITH_WEBSOCKETS
6af91d1f
 onreply_route {
5a8b8da4
 	if ((($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT)
 		&& !(proto == WS || proto == WSS)) || $Rp == MY_MSRP_PORT) {
 		xlog("L_WARN", "SIP response received on $Rp\n");
 		drop;
 	}
 
eff18c9c
 	if (nat_uac_test(64)) {
c3958092
 		# Do NAT traversal stuff for replies to a WebSocket connection
 		# - even if it is not behind a NAT!
 		# This won't be needed in the future if Kamailio and the
 		# WebSocket client support Outbound and Path.
eff18c9c
 		add_contact_alias();
 	}
dc7fa93f
 }
 
48ba7477
 event_route[xhttp:request] {
5456e4e9
 	set_reply_close();
 	set_reply_no_connect();
 	
84563257
 	if ($Rp != MY_WS_PORT
 #!ifdef WITH_TLS
 	    && $Rp != MY_WSS_PORT
 #!endif
 	) {
48ba7477
 		xlog("L_WARN", "HTTP request received on $Rp\n");
 		xhttp_reply("403", "Forbidden", "", "");
 		exit;
 	}
 
64406b20
 	xlog("L_DBG", "HTTP Request Received\n");
ad7ea60f
 
48ba7477
 	if ($hdr(Upgrade)=~"websocket"
 			&& $hdr(Connection)=~"Upgrade"
 			&& $rm=~"GET") {
 
84563257
 		# Validate Host - make sure the client is using the correct
 		# alias for WebSockets
 		if ($hdr(Host) == $null || !is_myself("sip:" + $hdr(Host))) {
48ba7477
 			xlog("L_WARN", "Bad host $hdr(Host)\n");
 			xhttp_reply("403", "Forbidden", "", "");
 			exit;
 		}
 
84563257
 		# 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;
 		# }
 
48ba7477
 		# Optional... perform HTTP authentication
 
5456e4e9
 		# ws_handle_handshake() exits (no further configuration file
 		# processing of the request) when complete.
6f928a54
 		if (ws_handle_handshake())
 		{
a24ce948
 			# Optional... cache some information about the
6f928a54
 			# successful connection
 			exit;
 		}
48ba7477
 	}
 
84563257
 	xhttp_reply("404", "Not Found", "", "");
48ba7477
 }
6f928a54
 
 event_route[websocket:closed] {
 	xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n");
 }
dc7fa93f
 #!endif
132509a7
 
 #!ifdef WITH_MSRP
 event_route[msrp:frame-in] {
 	msrp_reply_flags("1");
 
5a8b8da4
 	if ((($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT)
 		&& !(proto == WS || proto == WSS)) && $Rp != MY_MSRP_PORT) {
 		xlog("L_WARN", "MSRP request received on $Rp\n");
 		msrp_reply("403", "Action-not-allowed");
 		exit;
132509a7
 	}
5a8b8da4
 
 	if (msrp_is_reply()) {
 		msrp_relay();
 	} else if($msrp(method)=="AUTH") {
 		if($msrp(nexthops)>0) {
132509a7
 			msrp_relay();
 			exit;
 		}
cb74d38f
 
5a8b8da4
 		if (!www_authenticate("MY_DOMAIN", "subscriber",
 					"$msrp(method)")) {
 			if (auth_get_www_authenticate("MY_DOMAIN", "1",
 							"$var(wauth)")) {
c062817d
 				msrp_reply("401", "Unauthorized",
5a8b8da4
 							"$var(wauth)");
132509a7
 			} else {
 				msrp_reply("500", "Server Error");
 			}
 			exit;
 		}
cb74d38f
 
 		if ($hdr(Expires) != $null) {
 			$var(expires) = (int) $hdr(Expires);
 			if ($var(expires) < MSRP_MIN_EXPIRES) {
 				msrp_reply("423", "Interval Out-of-Bounds",
 					"Min-Expires: MSRP_MIN_EXPIRES\r\n");
 				exit;
 			} else if ($var(expires) > MSRP_MAX_EXPIRES) {
 				msrp_reply("423", "Interval Out-of-Bounds",
 					"Max-Expires: MSRP_MAX_EXPIRES\r\n");
5a8b8da4
 				exit;
cb74d38f
 			}
5a8b8da4
 		} else {
cb74d38f
 			$var(expires) = MSRP_MAX_EXPIRES;
5a8b8da4
 		}
cb74d38f
 
132509a7
 		$var(cnt) = $var(cnt) + 1;
 		pv_printf("$var(sessid)", "s.$(pp).$(var(cnt)).$(RANDOM)");
 		$sht(msrp=>$var(sessid)::srcaddr) = $msrp(srcaddr);
 		$sht(msrp=>$var(sessid)::srcsock) = $msrp(srcsock);
5a8b8da4
 		$shtex(msrp=>$var(sessid)) = $var(expires) + 5;
132509a7
 		# - Use-Path: the MSRP address for server + session id
70f5cefa
 		$var(hdrs) = "Use-Path: msrps://MY_IP_ADDR:MY_MSRP_PORT/"
 					+ $var(sessid) + ";tcp\r\n"
 					+ "Expires: " + $var(expires) + "\r\n";
5a8b8da4
 		msrp_reply("200", "OK", "$var(hdrs)");
 	} else if ($msrp(method)=="SEND" || $msrp(method)=="REPORT") {
 		if ($msrp(nexthops)>1) {
 			if ($msrp(method)!="REPORT") {
c062817d
 				msrp_reply("200", "OK");
 			}
132509a7
 			msrp_relay();
 			exit;
 		}
 		$var(sessid) = $msrp(sessid);
5a8b8da4
 		if ($sht(msrp=>$var(sessid)::srcaddr) == $null) {
132509a7
 			# one more hop, but we don't have address in htable
c062817d
 			msrp_reply("481", "Session-does-not-exist");
132509a7
 			exit;
70f5cefa
 		} else if ($msrp(method)!="REPORT") {
5a8b8da4
 			msrp_reply("200", "OK");
c062817d
 		}
132509a7
 		msrp_relay_flags("1");
 		msrp_set_dst("$sht(msrp=>$var(sessid)::srcaddr)",
 				"$sht(msrp=>$var(sessid)::srcsock)");
 		msrp_relay();
5a8b8da4
 	} else {
c062817d
 		msrp_reply("501", "Request-method-not-understood");
 	}
132509a7
 }
 #!endif