From d20cd0e5d52aea44c8ba74d709eb4984b708af9a Mon Sep 17 00:00:00 2001 From: Sebastian Damm Date: Tue, 15 May 2018 01:09:24 +0200 Subject: [PATCH] WIP: Implement request route as library functions * This takes the KEMI lua example file and moves its request route function to a library. Some functions are tested, most still need tests. --- .gitignore | 7 ++ Makefile | 18 +++ README.md | 19 +++ spec/actions_spec.lua | 9 ++ spec/core_spec.lua | 9 ++ spec/headers_spec.lua | 83 +++++++++++++ spec/init_spec.lua | 21 ++++ spec/message_spec.lua | 45 ++++++++ spec/nathandling_spec.lua | 81 +++++++++++++ spec/security_spec.lua | 9 ++ spec/traffic_spec.lua | 9 ++ src/kamailio-basic-kemi-lua.lua | 134 +++++++++++++++++++++ src/kamailio/actions.lua | 88 ++++++++++++++ src/kamailio/core.lua | 19 +++ src/kamailio/headers.lua | 39 +++++++ src/kamailio/init.lua | 199 ++++++++++++++++++++++++++++++++ src/kamailio/message.lua | 85 ++++++++++++++ src/kamailio/message_state.lua | 15 +++ src/kamailio/nathandling.lua | 37 ++++++ src/kamailio/reply.lua | 60 ++++++++++ src/kamailio/security.lua | 37 ++++++ src/kamailio/traffic.lua | 13 +++ 22 files changed, 1036 insertions(+) create mode 100644 Makefile create mode 100644 spec/actions_spec.lua create mode 100644 spec/core_spec.lua create mode 100644 spec/headers_spec.lua create mode 100644 spec/init_spec.lua create mode 100644 spec/message_spec.lua create mode 100644 spec/nathandling_spec.lua create mode 100644 spec/security_spec.lua create mode 100644 spec/traffic_spec.lua create mode 100644 src/kamailio-basic-kemi-lua.lua create mode 100644 src/kamailio/actions.lua create mode 100644 src/kamailio/core.lua create mode 100644 src/kamailio/headers.lua create mode 100644 src/kamailio/init.lua create mode 100644 src/kamailio/message.lua create mode 100644 src/kamailio/message_state.lua create mode 100644 src/kamailio/nathandling.lua create mode 100644 src/kamailio/reply.lua create mode 100644 src/kamailio/security.lua create mode 100644 src/kamailio/traffic.lua diff --git a/.gitignore b/.gitignore index 6fd0a37..d53269b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ # Compiled Lua sources luac.out +# Luacov files +luacov.stats.out +luacov.report.out.index +luacov.report.out + # luarocks build files *.src.rock *.zip @@ -39,3 +44,5 @@ luac.out *.x86_64 *.hex +# I still use Textmate, ignore its working files +._* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2cffac0 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +SHELL=/bin/bash +LUA_VERSION=5.1 +TARGET=/usr/local/share/lua/$(LUA_VERSION)/ +LIBRARY_NAME=kamailio + + +install: tests + @echo + @mkdir -p $(TARGET) || die "Can't create directory $(TARGET)" + @install -d $(TARGET)/$(LIBRARY_NAME) || die "Can't create directory $(TARGET)/$(LIBRARY_NAME)" + @install -m 644 src/kamailio/* $(TARGET)/$(LIBRARY_NAME) + @echo "Library successfully installed." + @echo "You have to install src/kamailio-basic-kemi-lua.lua manually to the desired directory" + @echo "and instruct your example Kamailio Kemi config file to use it." + +tests: + which busted || die "No busted installed. Please install busted so we can run unit tests!" + busted spec/[[:alpha:]]*_spec.lua \ No newline at end of file diff --git a/README.md b/README.md index ffe40f1..8c12710 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ # lua-kamailio A library to replace the monolithic Kemi example file, making it testable + +## State of the project + +This project is currently in an early alpha stage. It was created as a proof of concept for Kamailioworld 2018 showing how to develop Kamailio routing logic the test driven way. + +It aims to replace the example KEMI lua file. The original can be found here: [Kamailio KEMI example config](https://github.com/kamailio/kamailio/blob/master/misc/examples/kemi/kamailio-basic-kemi-lua.lua) + +Currently, only `ksr_request_route()` and all functions called by it have been replaced. The other functions are just a copy from the example file. + +## Installation + +To install the library, simply type: +```make install``` + +You may have to fix some permissions first. On Debian, this should work, if you are in group `staff`. + +After the installation of the library, please install the file `src/kamailio-basic-kemi-lua.lua` to the desired location. Then change the appropriate modparam for `app_lua` to point to the installed file. + +Now restart your Kamailio. \ No newline at end of file diff --git a/spec/actions_spec.lua b/spec/actions_spec.lua new file mode 100644 index 0000000..c17645b --- /dev/null +++ b/spec/actions_spec.lua @@ -0,0 +1,9 @@ +require 'busted.runner'() + +local actions = require "kamailio/actions" + +describe("Check Foo -> ", function() + it("Some Test", function() + pending("There are tests missing.") + end) +end) \ No newline at end of file diff --git a/spec/core_spec.lua b/spec/core_spec.lua new file mode 100644 index 0000000..ebb8abd --- /dev/null +++ b/spec/core_spec.lua @@ -0,0 +1,9 @@ +require 'busted.runner'() + +local core = require "kamailio/core" + +describe("Check Foo -> ", function() + it("Some Test", function() + pending("There are tests missing.") + end) +end) \ No newline at end of file diff --git a/spec/headers_spec.lua b/spec/headers_spec.lua new file mode 100644 index 0000000..5d1802f --- /dev/null +++ b/spec/headers_spec.lua @@ -0,0 +1,83 @@ +require 'busted.runner'() + +local function init_mock(options) + -- mock global variable 'KSR' + local ksr_mock = { + pv = { + get = function(key) + if key == "$rU" then + if options.rU ~= nil then return options.rU else return "01234567" end + end + end, + sets = function(k, v) end, + }, + hdr = { + append = function(header) end, + } + } + _G["KSR"] = mock(ksr_mock) + + local message_mock = { + is_invite = function() return options.is_invite end + } + _G["message"] = mock(message_mock) +end + +local headers = require "kamailio/headers" + +describe("Check and enable CLIR ->", function() + it("Caller dials *31021112345", function() + -- Initialize the mock + init_mock{ rU = "*31021112345", is_invite = true } + -- Call the function + headers.suppress_cid_if_needed() + -- Now check if everything is as expected + assert.spy(KSR.pv.sets).was.called_with("$rU", "021112345") + assert.spy(KSR.hdr.append).was.called_with("Privacy: id\r\n") + end) + it("Caller dials *31*021112345", function() + -- Initialize the mock + init_mock{ rU = "*31*021112345", is_invite = true } + -- Call the function + headers.suppress_cid_if_needed() + -- Now check if everything is as expected + assert.spy(KSR.pv.sets).was.called_with("$rU", "021112345") + assert.spy(KSR.hdr.append).was.called_with("Privacy: id\r\n") + end) + it("Caller dials *31#021112345", function() + -- Initialize the mock + init_mock{ rU = "*31#021112345", is_invite = true } + -- Call the function + headers.suppress_cid_if_needed() + -- Now check if everything is as expected + assert.spy(KSR.pv.sets).was.called_with("$rU", "021112345") + assert.spy(KSR.hdr.append).was.called_with("Privacy: id\r\n") + end) + it("Caller dials *31%23021112345", function() + -- Initialize the mock + init_mock{ rU = "*31%23021112345", is_invite = true } + -- Call the function + headers.suppress_cid_if_needed() + -- Now check if everything is as expected + assert.spy(KSR.pv.sets).was.called_with("$rU", "021112345") + assert.spy(KSR.hdr.append).was.called_with("Privacy: id\r\n") + end) + it("Caller dials +4921112345", function() + -- Initialize the mock + init_mock{ rU = "+4921112345", is_invite = true } + -- Call the function + headers.suppress_cid_if_needed() + -- Now check if everything is as expected + assert.spy(KSR.pv.sets).was.called(0) + assert.spy(KSR.hdr.append).was.called(0) + end) + it("Caller dials *31+4921112345, non-INVITE", function() + -- Initialize the mock + init_mock{ rU = "*31+4921112345" } + -- Call the function + headers.suppress_cid_if_needed() + -- Now check if everything is as expected + assert.spy(KSR.pv.sets).was.called_with("$rU", "+4921112345") + assert.spy(KSR.hdr.append).was.called(0) + end) +end) \ No newline at end of file diff --git a/spec/init_spec.lua b/spec/init_spec.lua new file mode 100644 index 0000000..6a2e2e7 --- /dev/null +++ b/spec/init_spec.lua @@ -0,0 +1,21 @@ +require 'busted.runner'() + +local kamailio = require "kamailio" + +describe("Check authorization -> ", function() + it("REGISTER is allowed by permissions", function() + pending("There are tests missing.") + end) + it("REGISTER is not authenticated", function() + pending("There are tests missing.") + end) + it("REGISTER is authenticated", function() + pending("We should check for remove_credentials, too.") + end) + it("INVITE is authenticated", function() + pending("There are tests missing.") + end) + it("Request is neither from local domain nor to local domain", function() + pending("There are tests missing.") + end) +end) \ No newline at end of file diff --git a/spec/message_spec.lua b/spec/message_spec.lua new file mode 100644 index 0000000..24063c3 --- /dev/null +++ b/spec/message_spec.lua @@ -0,0 +1,45 @@ +require 'busted.runner'() + +local function init_mock(options) + -- mock global variable 'KSR' + local ksr_mock = { + pv = { + get = function(key) + if key == "$rU" then + if options.rU ~= nil then return options.rU else return "01234567" end + elseif key == "$ua" then return options.ua end + end, + sets = function(k, v) end, + is_null = function(key) + if key == "$ua" then + if options.ua then return false else return true end + end + end, + }, + hdr = { + append = function(header) end, + } + } + _G["KSR"] = mock(ksr_mock) +end + + +local message = require "kamailio/message" + +describe("Check for bad user agent ->", function() + it("UA is friendly-scanner", function() + init_mock{ ua = "friendly-scanner" } + ua_check = message.is_from_bad_user_agent() + assert.are.equal(1, ua_check) + end) + it("UA is sipcli-1.1.8", function() + init_mock{ ua = "sipcli-1.1.8" } + ua_check = message.is_from_bad_user_agent() + assert.are.equal(1, ua_check) + end) + it("UA is Asterisk 15.1.1", function() + init_mock{ ua = "Asterisk 15.1.1" } + ua_check = message.is_from_bad_user_agent() + assert.are.equal(nil, ua_check) + end) +end) diff --git a/spec/nathandling_spec.lua b/spec/nathandling_spec.lua new file mode 100644 index 0000000..45cb206 --- /dev/null +++ b/spec/nathandling_spec.lua @@ -0,0 +1,81 @@ +require 'busted.runner'() + +local function init_mock(options) + -- mock global variable 'KSR' + local ksr_mock = { + pv = { + get = function(key) + if key == "$rU" then + if options.rU ~= nil then return options.rU else return "01234567" end + end + end, + sets = function(k, v) end, + }, + hdr = { + append = function(header) end, + }, + nathelper = { + nat_uac_test = function(flags) return options.is_nated end, + fix_nated_register = function() end, + set_contact_alias = function() end, + }, + setflag = function(flag) end, + } + _G["KSR"] = mock(ksr_mock) + + local message_mock = { + is_register = function() return options.is_register end, + is_first_hop = function() return options.is_first_hop end + } + _G["message"] = mock(message_mock) +end + +local nathandling = require "kamailio/nathandling" + +describe("Check for caller NAT -> ", function() + it("Detect NAT REGISTER", function() + init_mock{ is_register = true, is_nated = 1 } + nated = nathandling.detect_caller_nat() + assert.are.equal(1, nated) + assert.spy(KSR.nathelper.nat_uac_test).was.called_with(19) + assert.spy(KSR.nathelper.fix_nated_register).was.called() + assert.spy(KSR.nathelper.set_contact_alias).was.called(0) + assert.spy(KSR.setflag).was.called() + end) + it("Detect non-NAT REGISTER", function() + init_mock{ is_register = true, is_nated = 0 } + nated = nathandling.detect_caller_nat() + assert.are.equal(nil, nated) + assert.spy(KSR.nathelper.nat_uac_test).was.called_with(19) + assert.spy(KSR.nathelper.fix_nated_register).was.called(0) + assert.spy(KSR.nathelper.set_contact_alias).was.called(0) + assert.spy(KSR.setflag).was.called(0) + end) + it("Detect NAT INVITE, first hop", function() + init_mock{ is_register = false, is_nated = 1, is_first_hop = true } + nated = nathandling.detect_caller_nat() + assert.are.equal(1, nated) + assert.spy(KSR.nathelper.nat_uac_test).was.called_with(19) + assert.spy(KSR.nathelper.fix_nated_register).was.called(0) + assert.spy(KSR.nathelper.set_contact_alias).was.called() + assert.spy(KSR.setflag).was.called() + end) + it("Detect NAT INVITE, not first hop", function() + init_mock{ is_register = false, is_nated = 1, is_first_hop = false } + nated = nathandling.detect_caller_nat() + assert.are.equal(1, nated) + assert.spy(KSR.nathelper.nat_uac_test).was.called_with(19) + assert.spy(KSR.nathelper.fix_nated_register).was.called(0) + assert.spy(KSR.nathelper.set_contact_alias).was.called(0) + assert.spy(KSR.setflag).was.called() + end) + it("Detect non-NAT INVITE, first hop", function() + init_mock{ is_register = false, is_nated = 0, is_first_hop = true } + nated = nathandling.detect_caller_nat() + assert.are.equal(nil, nated) + assert.spy(KSR.nathelper.nat_uac_test).was.called_with(19) + assert.spy(KSR.nathelper.fix_nated_register).was.called(0) + assert.spy(KSR.nathelper.set_contact_alias).was.called(0) + assert.spy(KSR.setflag).was.called(0) + end) +end) \ No newline at end of file diff --git a/spec/security_spec.lua b/spec/security_spec.lua new file mode 100644 index 0000000..23f4bda --- /dev/null +++ b/spec/security_spec.lua @@ -0,0 +1,9 @@ +require 'busted.runner'() + +local security = require "kamailio/security" + +describe("Check Foo -> ", function() + it("Some Test", function() + pending("There are tests missing.") + end) +end) \ No newline at end of file diff --git a/spec/traffic_spec.lua b/spec/traffic_spec.lua new file mode 100644 index 0000000..41ed69c --- /dev/null +++ b/spec/traffic_spec.lua @@ -0,0 +1,9 @@ +require 'busted.runner'() + +local traffic = require "kamailio/traffic" + +describe("Check Foo -> ", function() + it("Some Test", function() + pending("There are tests missing.") + end) +end) \ No newline at end of file diff --git a/src/kamailio-basic-kemi-lua.lua b/src/kamailio-basic-kemi-lua.lua new file mode 100644 index 0000000..5a37ed8 --- /dev/null +++ b/src/kamailio-basic-kemi-lua.lua @@ -0,0 +1,134 @@ +-- Kamailio - equivalent of routing blocks in Lua +-- +-- KSR - the new dynamic object exporting Kamailio functions (kemi) +-- sr - the old static object exporting Kamailio functions +-- + +-- Relevant remarks: +-- * do not execute Lua 'exit' - that will kill Lua interpreter which is +-- embedded in Kamailio, resulting in killing Kamailio +-- * use KSR.x.exit() to trigger the stop of executing the script +-- * KSR.drop() is only marking the SIP message for drop, but doesn't stop +-- the execution of the script. Use KSR.x.exit() after it or KSR.x.drop() +-- + +kamailio = require "kamailio" + +-- global variables corresponding to defined values (e.g., flags) in kamailio.cfg +FLT_ACC=1 +FLT_ACCMISSED=2 +FLT_ACCFAILED=3 +FLT_NATS=5 + +FLB_NATB=6 +FLB_NATSIPPING=7 + +-- SIP request routing +-- equivalent of request_route{} +function ksr_request_route() + kamailio.process_request() + return 1 +end + +-- wrapper around tm relay function +function ksr_route_relay() + -- enable additional event routes for forwarded requests + -- - serial forking, RTP relaying handling, a.s.o. + if string.find("INVITE,BYE,SUBSCRIBE,UPDATE", KSR.pv.get("$rm")) then + if KSR.tm.t_is_set("branch_route")<0 then + KSR.tm.t_on_branch("ksr_branch_manage"); + end + end + if string.find("INVITE,SUBSCRIBE,UPDATE", KSR.pv.get("$rm")) then + if KSR.tm.t_is_set("onreply_route")<0 then + KSR.tm.t_on_reply("ksr_onreply_manage"); + end + end + + if KSR.pv.get("$rm")=="INVITE" then + if KSR.tm.t_is_set("failure_route")<0 then + KSR.tm.t_on_failure("ksr_failure_manage"); + end + end + + if KSR.tm.t_relay()<0 then + KSR.sl.sl_reply_error(); + end + KSR.x.exit(); +end + +-- RTPProxy control +function ksr_route_natmanage() + if KSR.siputils.is_request()>0 then + if KSR.siputils.has_totag()>0 then + if KSR.rr.check_route_param("nat=yes")>0 then + KSR.setbflag(FLB_NATB); + end + end + end + if (not (KSR.isflagset(FLT_NATS) or KSR.isbflagset(FLB_NATB))) then + return 1; + end + + KSR.rtpproxy.rtpproxy_manage("co"); + + if KSR.siputils.is_request()>0 then + if not KSR.siputils.has_totag() then + if KSR.tmx.t_is_branch_route()>0 then + KSR.rr.add_rr_param(";nat=yes"); + end + end + end + if KSR.siputils.is_reply()>0 then + if KSR.isbflagset(FLB_NATB) then + KSR.nathelper.set_contact_alias(); + end + end + return 1; +end + +-- URI update for dialog requests +function ksr_route_dlguri() + if not KSR.isdsturiset() then + KSR.nathelper.handle_ruri_alias(); + end + return 1; +end + +-- Manage outgoing branches +-- equivalent of branch_route[...]{} +function ksr_branch_manage() + KSR.dbg("new branch [".. KSR.pv.get("$T_branch_idx") + .. "] to ".. KSR.pv.get("$ru") .. "\n"); + ksr_route_natmanage(); + return 1; +end + +-- Manage incoming replies +-- equivalent of onreply_route[...]{} +function ksr_onreply_manage() + KSR.dbg("incoming reply\n"); + local scode = KSR.pv.get("$rs"); + if scode>100 and scode<299 then + ksr_route_natmanage(); + end + return 1; +end + +-- Manage failure routing cases +-- equivalent of failure_route[...]{} +function ksr_failure_manage() + ksr_route_natmanage(); + + if KSR.tm.t_is_canceled()>0 then + return 1; + end + return 1; +end + +-- SIP response handling +-- equivalent of reply_route{} +function ksr_reply_route() + KSR.info("===== response - from kamailio lua script\n"); + return 1; +end diff --git a/src/kamailio/actions.lua b/src/kamailio/actions.lua new file mode 100644 index 0000000..3b02174 --- /dev/null +++ b/src/kamailio/actions.lua @@ -0,0 +1,88 @@ +-- Define your actions here +message = require "kamailio.message" +core = require "kamailio.core" +reply = require "kamailio.reply" + +local actions = {} + +function actions.save_register() + if KSR.registrar.save("location", 0)<0 then + reply.with_stateless_error_and_exit() + end +end + +function actions.reject_looping_requests() + if KSR.maxfwd.process_maxfwd(10) < 0 then + reply.stateless(483, "Too Many Hops") + core.exit() + end +end + +function actions.handle_options() + if message.is_options() + and message.is_request_to_local_request_domain() + and message.has_empty_request_user() then + reply.stateless(200, "Keepalive") + core.exit() + end +end + +function actions.check_sanity() + if KSR.sanity.sanity_check(1511, 7)<0 then + KSR.err("Malformed SIP message from " + .. KSR.pv.get("$si") .. ":" .. KSR.pv.get("$sp") .."\n"); + core.exit() + end +end + +-- wrapper around tm relay function +function actions.relay_message() + -- enable additional event routes for forwarded requests + -- - serial forking, RTP relaying handling, a.s.o. + if message.is_invite() or message.is_bye() + or message.is_subscribe() or message.is_update() then + if KSR.tm.t_is_set("branch_route")<0 then + KSR.tm.t_on_branch("ksr_branch_manage"); + end + end + if message.is_invite() or message.is_subscribe() + or message.is_update() then + if KSR.tm.t_is_set("onreply_route")<0 then + KSR.tm.t_on_reply("ksr_onreply_manage"); + end + end + + if message.is_invite() then + if KSR.tm.t_is_set("failure_route")<0 then + KSR.tm.t_on_failure("ksr_failure_manage"); + end + end + + if KSR.tm.t_relay()<0 then + KSR.sl.sl_reply_error(); + end + KSR.x.exit(); +end + +function actions.route_to_location() + local rc = KSR.registrar.lookup("location") + if rc<0 then + message.create_transaction() + if rc==-1 or rc==-3 then + reply.with_stateful_404_and_exit() + elseif rc==-2 then + reply.with_stateless_405_and_exit() + end + end + + -- when routing via usrloc, log the missed calls also + if message.is_invite() then + core.set_flag(FLT_ACCMISSED) + end + + actions.relay_message() + core.exit() + +end + +return actions \ No newline at end of file diff --git a/src/kamailio/core.lua b/src/kamailio/core.lua new file mode 100644 index 0000000..32cdad1 --- /dev/null +++ b/src/kamailio/core.lua @@ -0,0 +1,19 @@ +local core = {} + +function core.exit() + KSR.x.exit() +end + +function core.set_flag(flag) + KSR.setflag(flag) +end + +function core.set_branch_flag(flag) + KSR.setbflag(flag) +end + +function core.is_flag_set(flag) + return KSR.isflagset(FLT_NATS) +end + +return core \ No newline at end of file diff --git a/src/kamailio/headers.lua b/src/kamailio/headers.lua new file mode 100644 index 0000000..235f92e --- /dev/null +++ b/src/kamailio/headers.lua @@ -0,0 +1,39 @@ +-- Functions working with headers +rex = require "rex_pcre" +message = require "kamailio.message" + +local headers = {} + +function headers.get_request_user() + return KSR.pv.get("$rU") +end + +function headers.set_request_user(value) + KSR.pv.sets("$rU", value) +end + +function headers.append_header(header, value) + if header == nil then return nil end + KSR.hdr.append(header..": "..value.."\r\n") +end + +function headers.remove_header(header) + if header == nil then + return false + end + KSR.hdr.remove(header) +end + +function headers.add_record_route() + KSR.rr.record_route() +end + +function headers.suppress_cid_if_needed() + request_user = headers.get_request_user() + if rex.find(request_user, "^\\*31([*#]|%23)?") then + headers.set_request_user(rex.gsub(request_user, "^\\*31([*#]|%23)?", "")) + if message.is_invite() then headers.append_header("Privacy", "id") end + end +end + +return headers \ No newline at end of file diff --git a/src/kamailio/init.lua b/src/kamailio/init.lua new file mode 100644 index 0000000..804f90e --- /dev/null +++ b/src/kamailio/init.lua @@ -0,0 +1,199 @@ +-- This is a sample Kamailio functions file +-- Use it at your own risk +actions = require "kamailio.actions" +core = require "kamailio.core" +headers = require "kamailio.headers" +message = require "kamailio.message" +message_state = require "kamailio.message_state" +nathandling = require "kamailio.nathandling" +security = require "kamailio.security" +traffic = require "kamailio.traffic" + + +local kamailio = {} + +function kamailio:process_request() + + -- per request initial checks + do_prechecks() + + detect_nat() + + -- CANCEL processing + if message.is_cancel() then + if message_state.has_transaction() then + actions.relay_message() + end + return 1; + end + + handle_in_dialog_requests() + + -- handle retransmissions + if message_state.is_handled_by_another_process() then + message_state.check_for_active_transaction() + return 1; + end + if message_state.check_for_active_transaction() == 0 then return 1 end + + authorize_request() + + -- record routing for dialog forming requests (in case they are routed) + -- - remove preloaded route headers + headers.remove_header("Route") + if message.is_invite() or message.is_subscribe() then + headers.add_record_route() + end + + -- account only INVITEs + if message.is_invite() then + core.set_flag(FLT_ACC) -- do accounting + end + + -- dispatch requests to foreign domains + route_to_foreign_domains() + + -- -- requests for my local domains + + -- handle registrations + handle_registers() + + --- TODO: Left off here + if message.has_empty_request_user() then + -- request with no Username in RURI + reply.with_stateless_484_and_exit() + end + + -- user location service + actions.route_to_location() + + return 1; + +end + +function handle_registers() + if not message.is_register() then return 1 end + if core.is_flagset(FLT_NATS) then + core.set_branch_flag(FLB_NATB) + -- do SIP NAT pinging + core.set_branch_flag(FLB_NATSIPPING) + end + actions.save_register() + core.exit() +end + +function route_to_foreign_domains() + if message.is_request_to_local_request_domain() then return 1 end + headers.append_header("P-Hint", "outbound") + actions.relay_message() + core.exit() +end + +-- IP authorization and user uthentication +function authorize_request() + + if message.is_register() then + if security.is_allowed_by_permissions() then + -- source IP allowed + return 1; + end + end + if message.is_register() or message.is_request_from_local_from_domain() then + -- authenticate requests + if security.is_not_authenticated() then + security.send_auth_chalenge() + core.exit() + end + -- user authenticated - remove auth header + if not (message.is_register() or message.is_publish()) then + security.remove_credentials() + end + end + + -- if caller is not local subscriber, then check if it calls + -- a local destination, otherwise deny, not an open relay here + if (not message.is_request_from_local_from_domain()) + and (not message.is_request_to_local_request_domain()) then + reply.with_stateless_403_and_exit() + end + + return 1; +end + + +-- Handle requests within SIP dialogs +function handle_in_dialog_requests() + if not message.has_to_tag() then return 1; end + + -- sequential request withing a dialog should + -- take the path determined by record-routing + -- TODO: Masquerade some more functions + if KSR.rr.loose_route()>0 then + ksr_route_dlguri(); + if message.is_bye() then + KSR.setflag(FLT_ACC); -- do accounting ... + KSR.setflag(FLT_ACCFAILED); -- ... even if the transaction fails + elseif message.is_ack() then + -- ACK is forwarded statelessly + ksr_route_natmanage(); + elseif message.is_notify() then + -- Add Record-Route for in-dialog NOTIFY as per RFC 6665. + headers.add_record_route() + end + actions.relay_message() + core.exit() + end + if message.is_ack() then + if message_state.has_transaction() then + -- no loose-route, but stateful ACK; + -- must be an ACK after a 487 + -- or e.g. 404 from upstream server + actions.relay_message() + core.exit() + else + -- ACK without matching transaction ... ignore and discard + core.exit() + end + end + reply.with_stateless_404_and_exit() +end + + +function do_prechecks() + if not traffic.is_request_from_local() then + if security.is_ip_banned() then + -- ip is already blocked + KSR.dbg("request from blocked IP - " .. KSR.pv.get("$rm") + .. " from " .. KSR.pv.get("$fu") .. " (IP:" + .. KSR.pv.get("$si") .. ":" .. KSR.pv.get("$sp") .. ")\n"); + core.exit() + end + if security.pike_above_limit() then + KSR.err("ALERT: pike blocking " .. KSR.pv.get("$rm") + .. " from " .. KSR.pv.get("$fu") .. " (IP:" + .. KSR.pv.get("$si") .. ":" .. KSR.pv.get("$sp") .. ")\n"); + security.ban_ip() + core.exit() + end + end + if message.is_from_bad_user_agent() then + reply.with_stateless_200_and_exit() + end + + actions.reject_looping_requests() + + actions.handle_options() + + actions.check_sanity() +end + +-- Caller NAT detection +function detect_nat() + nathandling.force_rport() + nathandling.detect_caller_nat() + return 1 +end + + + +return kamailio \ No newline at end of file diff --git a/src/kamailio/message.lua b/src/kamailio/message.lua new file mode 100644 index 0000000..3d134bb --- /dev/null +++ b/src/kamailio/message.lua @@ -0,0 +1,85 @@ +-- Message functions +local message = { +} + +-- Getter/Setter, don't test + +function message.get_useragent() + return KSR.pv.get("$ua") +end + +function message.get_type() + return KSR.pv.get("$rm") +end + +function message.has_empty_request_user() + return KSR.pv.is_null("$rU") +end + +function message.is_ack() + return message.get_type() == "ACK" +end + +function message.is_bye() + return message.get_type() == "BYE" +end + +function message.is_cancel() + return message.get_type() == "CANCEL" +end + +function message.is_invite() + return message.get_type() == "INVITE" +end + +function message.is_notify() + return message.get_type() == "NOTIFY" +end + +function message.is_options() + return message.get_type() == "OPTIONS" +end + +function message.is_register() + return message.get_type() == "REGISTER" +end + +function message.is_subscribe() + return message.get_type() == "BYE" +end + +function message.is_update() + return message.get_type() == "UPDATE" +end + +function message.is_request_to_local_request_domain() + return KSR.is_myself(KSR.pv.get("$ru")) +end + +function message.is_request_from_local_from_domain() + return KSR.is_myself(KSR.pv.get("$fu")) +end + +function message.is_first_hop() + return KSR.siputils.is_first_hop()>0 +end + +function message.has_to_tag() + return KSR.siputils.has_totag()>0 +end + +function message.create_transaction() + KSR.tm.t_newtran() +end + +-- Testworthy methods here + +-- Tested +function message.is_from_bad_user_agent() + ua = message.get_useragent() + return (ua ~= nil) + and (string.find(ua, "friendly%-scanner") + or string.find(ua, "sipcli")) +end + +return message \ No newline at end of file diff --git a/src/kamailio/message_state.lua b/src/kamailio/message_state.lua new file mode 100644 index 0000000..f29ef8b --- /dev/null +++ b/src/kamailio/message_state.lua @@ -0,0 +1,15 @@ +local message_state = {} + +function message_state.has_transaction() + return KSR.tm.t_check_trans() > 0 +end + +function message_state.check_for_active_transaction() + return KSR.tm.t_check_trans() +end + +function message_state.is_handled_by_another_process() + return KSR.tmx.t_precheck_trans() > 0 +end + +return message_state \ No newline at end of file diff --git a/src/kamailio/nathandling.lua b/src/kamailio/nathandling.lua new file mode 100644 index 0000000..bb1c0ad --- /dev/null +++ b/src/kamailio/nathandling.lua @@ -0,0 +1,37 @@ +-- Everything with NAT +message = require "kamailio.message" +core = require "kamailio.core" + +local nathandling = { +} + +-- Getter/Setter, don't test + +-- Local functions +local function set_nat_flag() + core.set_flag(FLT_NATS) +end + +-- Public functions + +function nathandling.force_rport() + KSR.force_rport() +end + +-- Testworthy methods here + +-- Tested +function nathandling.detect_caller_nat() + if KSR.nathelper.nat_uac_test(19)>0 then + if message.is_register() then + KSR.nathelper.fix_nated_register() + elseif message.is_first_hop() then + KSR.nathelper.set_contact_alias() + end + set_nat_flag() + return 1 + end +end + + +return nathandling \ No newline at end of file diff --git a/src/kamailio/reply.lua b/src/kamailio/reply.lua new file mode 100644 index 0000000..c1beaeb --- /dev/null +++ b/src/kamailio/reply.lua @@ -0,0 +1,60 @@ +core = require "kamailio.core" + +local reply = {} + +function reply.stateless(cause, reason) + if cause == nil then + KSR.err("Can't send stateless response w/o cause!") + return false + end + reason = reason or "Unknown" + KSR.sl.sl_send_reply(cause, reason) +end + +function reply.stateful(cause, reason) + if cause == nil then + KSR.err("Can't send stateless response w/o cause!") + return false + end + reason = reason or "Unknown" + KSR.tm.t_reply(cause, reason) +end + +function reply.with_stateless_error_and_exit() + KSR.sl.sl_reply_error() + core.exit() +end + +function reply.with_stateless_200_and_exit() + reply.stateless(200, "OK") + core.exit() +end + +function reply.with_stateless_403_and_exit() + reply.stateless(403, "Forbidden") + core.exit() +end + +function reply.with_stateless_404_and_exit() + reply.stateless(404, "Not Here") + core.exit() +end + +function reply.with_stateless_405_and_exit() + reply.stateless(405, "Method Not Allowed") + core.exit() +end + +function reply.with_stateless_484_and_exit() + reply.stateless(484, "Address Incomplete") + core.exit() +end + +function reply.with_stateful_404_and_exit() + reply.stateful(404, "Not Found") + core.exit() +end + + + +return reply \ No newline at end of file diff --git a/src/kamailio/security.lua b/src/kamailio/security.lua new file mode 100644 index 0000000..841c0f4 --- /dev/null +++ b/src/kamailio/security.lua @@ -0,0 +1,37 @@ +-- Security related functions + +local security = {} + +-- Getter/Setter, don't test + +function security.is_allowed_by_permissions() + return KSR.permissions.allow_source_address(1)>0 +end + +function security.is_ip_banned() + return not KSR.pv.is_null("$sht(ipban=>$si)") +end + +function security.pike_above_limit() + return KSR.pike.pike_check_req() < 0 +end + +function security.ban_ip() + KSR.pv.seti("$sht(ipban=>$si)", 1) +end + +function security.is_not_authenticated() + return KSR.auth_db.auth_check(KSR.pv.get("$fd"), "subscriber", 1) < 0 +end + +function security.send_auth_challenge() + KSR.auth.auth_challenge(KSR.pv.get("$fd"), 0) +end + +function security.remove_credentials() + KSR.auth.consume_credentials() +end + +-- Testworthy methods here + +return security \ No newline at end of file diff --git a/src/kamailio/traffic.lua b/src/kamailio/traffic.lua new file mode 100644 index 0000000..d821113 --- /dev/null +++ b/src/kamailio/traffic.lua @@ -0,0 +1,13 @@ +--- Traffic related functions +local traffic = { +} + +-- Getter/Setter, don't test + +function traffic.is_request_from_local() + return KSR.is_myself(KSR.pv.get("$si")) +end + +-- Testworthy methods here + +return traffic \ No newline at end of file