diff --git a/kamailio/acl-role.cfg b/kamailio/acl-role.cfg new file mode 100644 index 0000000..a45365f --- /dev/null +++ b/kamailio/acl-role.cfg @@ -0,0 +1,238 @@ +######## DoS prevention module ######## +# Default "order" is "Deny,Allow"(DA). +# So if there is no data from DB request will be permitted by default. +# +loadmodule "ipops.so" +modparam("htable", "htable", "acl=>initval=-1;autoexpire=7200") + +#!trydef ACL_MESSAGE_DENY "Rejected by ACL" +#!trydef ACL_CODE_DENY "603" +#!trydef ACL_ORDER_ALLOW_DENY "AD" +#!trydef ACL_IP_ADDR_ANY "0.0.0.0/0" + +#!trydef IP_REGEX "[0-9]{1,3}\.[0-9]{1,3}.[0-9]{1,3}\.[0-9]{1,3}" + +## Route for ACL functionality +route[ACL_CHECK] { + + if (isflagset(FLAG_IS_REPLY)) { + $var(sip-packet) = $rs; + } else { + $var(sip-packet) = $rm; + } + + # FIXUP for BYE method with IPinstead of REALM in From, take REALM fron To header + if ($fd =~ IP_REGEX) { + xlog("L_WARNING","$ci |ACL-realm| Fixup for $var(sip-packet) with IP in from URI: use to-domain"); + $var(realm) = $td; + } else { + $var(realm) = $fd; + } + + $var(acl-realm-request) = "false"; + $var(acl-device-request) = "false"; + + $var(realm-decision) = $sht(acl=>$var(realm)/$si); + + if ($var(realm-decision) == -1) { # we do not have cached decision + $var(acl-realm-request) = "true"; + } else if ($var(realm-decision) == 1 ){ # We have cached decision, let's use it + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(realm)\n"); + return; + } else { + if (!isflagset(FLAG_IS_REPLY)) { + sl_send_reply(ACL_CODE_DENY, ACL_MESSAGE_DENY); + } + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is rejected by ACL for $var(realm)\n"); + exit; + } + + if (not_empty("$fU")) { + if ($fd =~ IP_REGEX) { + xlog("L_WARNING","$ci |ACL-device| Fixup for $var(sip-packet) with IP in from URI: use to-domain"); + $var(device) = $fU + "@" + $td; + } else { + $var(device) = $fU + "@" + $fd; + } + $var(device-decision) = $sht(acl=>$var(device)/$si); + + if ($var(device-decision) == -1) { # we do not have cached decision + $var(acl-device-request) = "true"; + } else if ($var(device-decision) == 1 ){ # We have cached decision, let's use it + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(device)\n"); + return; + } else { + if (!isflagset(FLAG_IS_REPLY)) { + sl_send_reply(ACL_CODE_DENY, ACL_MESSAGE_DENY); + } + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is rejected by ACL for $var(device)\n"); + exit; + } + } + + if ($var(acl-realm-request) == "true" || $var(acl-device-request)) { + if (not_empty("$fU")) + $var(query) = "{'Event-Category': 'acl', 'Event-Name': 'query', 'Entity': '" + $var(device) + "', 'With-Realm': " + $var(acl-realm-request) + "}"; + else + $var(query) = "{'Event-Category': 'acl', 'Event-Name': 'query', 'Entity': '" + $var(realm) + "'}"; + xlog("L_INFO", "$ci |ACL log| Query: $var(query)"); + if (kazoo_query("frontier", "sbc_config", $var(query), "$var(acl-response)")) { + xlog("L_INFO", "$ci |ACL log| Response: $var(acl-response)"); + + kazoo_json($var(acl-response), "Realm.Order", "$var(acl-realm-order)"); + kazoo_json($var(acl-response), "Realm.CIDR", "$var(acl-realm-cidr)"); + kazoo_json($var(acl-response), "Realm.CIDR.length", "$var(acl-realm-cidr-len)"); + kazoo_json($var(acl-response), "Realm.User-Agent", "$var(acl-realm-ua)"); + kazoo_json($var(acl-response), "Device.Order", "$var(acl-device-order)"); + kazoo_json($var(acl-response), "Device.CIDR", "$var(acl-device-cidr)"); + kazoo_json($var(acl-response), "Device.CIDR.length","$var(acl-device-cidr-len)"); + kazoo_json($var(acl-response), "Device.User-Agent", "$var(acl-device-ua)"); + + } else { + xlog("L_ERROR","$ci |ACL log| DB is unreachable"); + $sht(acl=>$var(device)/$si) = 1; + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(device)\n"); + return; + } + } + + route(ACL_CHECK_REALM); + if (not_empty("$fU")) { + route(ACL_CHECK_DEVICE); + } +} + +# Check ORDER setting for REALM +route[ACL_CHECK_REALM] { + if (not_empty("$var(acl-realm-order)")) { + if ($var(acl-realm-order) == ACL_ORDER_ALLOW_DENY) { + route(ACL_CHECK_REALM_ALLOW); + } else { + route(ACL_CHECK_REALM_DENY); + } + } else { + xlog("L_INFO","$ci |ACL-realm| undefined Order in response for $var(realm)"); + $sht(acl=>$var(realm)/$si) = 1; + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(realm)\n"); + } +} + +route[ACL_CHECK_REALM_ALLOW] { + if (not_empty("$var(acl-realm-cidr)")) { + $var(i) = 0; + xlog("L_INFO", "$ci |ACL-realm| checking $var(acl-realm-cidr-len) record(s)"); + while($var(i) < $var(acl-realm-cidr-len)) { + kazoo_json($var(acl-realm-cidr), "[$var(i)]", "$var(record)"); + xlog("L_INFO", "$ci |ACL-realm| checking if $si is in $var(record)"); + if (($var(record) == ACL_IP_ADDR_ANY) || is_in_subnet("$si", $var(record))) { + $sht(acl=>$var(realm)/$si) = 1; + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(realm)\n"); + return; + } + $var(i) = $var(i) + 1; + } + } else { + xlog("L_INFO", "$ci |ACL-realm| undefined CIDR in response for $var(realm)"); + } + # Remember in CACHE and DENY + $sht(acl=>$var(realm)/$si) = 0; + if (!isflagset(FLAG_IS_REPLY)) { + sl_send_reply(ACL_CODE_DENY, ACL_MESSAGE_DENY); + } + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is rejected by ACL for $var(realm)\n"); + exit; +} + +route[ACL_CHECK_REALM_DENY] { + $var(size) = $(kzR{kz.json,Realm.CIDR.length}); + if (not_empty("$var(acl-realm-cidr)")) { + $var(i) = 0; + xlog("L_INFO", "$ci |ACL-realm| checking $var(acl-realm-cidr-len) record(s)"); + while($var(i) < $var(acl-realm-cidr-len)) { + kazoo_json($var(acl-realm-cidr), "[$var(i)]", "$var(record)"); + xlog("L_INFO", "$ci |ACL-realm| checking if $si is in $var(record)"); + if (($var(record) == ACL_IP_ADDR_ANY) || is_in_subnet("$si", $var(record))) { + $sht(acl=>$var(realm)/$si) = 0; + if (!isflagset(FLAG_IS_REPLY)) { + sl_send_reply(ACL_CODE_DENY, ACL_MESSAGE_DENY); + } + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is rejected by ACL for $var(realm)\n"); + exit; + } + $var(i) = $var(i) + 1; + } + } else { + xlog("L_INFO", "$ci |ACL-realm| undefined CIDR in response for $var(realm)"); + } + # Remember in CACHE and ALLOW + $sht(acl=>$var(realm)/$si) = 1; + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(realm)\n"); + return; +} + +# Check ORDER setting for DEVICE +route[ACL_CHECK_DEVICE] { + if (not_empty("$var(acl-device-order)")) { + if ($var(acl-device-order) == ACL_ORDER_ALLOW_DENY) { + route(ACL_CHECK_DEVICE_ALLOW); + } else { + route(ACL_CHECK_DEVICE_DENY); + } + } else { + xlog("L_INFO","$ci |ACL-device| undefined Order in response for $var(device)"); + $sht(acl=>$var(device)/$si) = 1; + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(device)\n"); + } +} + +route[ACL_CHECK_DEVICE_ALLOW] { + if (not_empty("$var(acl-device-cidr)")) { + $var(i) = 0; + xlog("L_INFO", "$ci |ACL-realm| checking $var(acl-device-cidr-len) record(s)"); + while($var(i) < $var(acl-device-cidr-len)) { + kazoo_json($var(acl-device-cidr), "[$var(i)]", "$var(record)"); + xlog("L_INFO", "$ci |ACL-realm| checking if $si is in $var(record)"); + if (($var(record) == ACL_IP_ADDR_ANY) || is_in_subnet("$si", $var(record))) { + $sht(acl=>$var(device)/$si) = 1; + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(device)\n"); + return; + } + $var(i) = $var(i) + 1; + } + } else { + xlog("L_INFO", "$ci |ACL-realm| undefined CIDR in response for $var(device)"); + } + # Remember in CACHE and DENY + $sht(acl=>$var(device)/$si) = 0; + if (!isflagset(FLAG_IS_REPLY)) { + sl_send_reply(ACL_CODE_DENY, ACL_MESSAGE_DENY); + } + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is rejected by ACL for $var(device)\n"); + exit; +} + +route[ACL_CHECK_DEVICE_DENY] { + if (not_empty("$var(acl-device-cidr)")) { + $var(i) = 0; + xlog("L_INFO", "$ci |ACL-device| checking $var(acl-device-cidr-len) record(s)"); + while($var(i) < $var(acl-device-cidr-len)) { + kazoo_json($var(acl-device-cidr), "[$var(i)]", "$var(record)"); + xlog("L_INFO", "$ci |ACL-device| checking if $si is in $var(record)"); + if (($var(record) == ACL_IP_ADDR_ANY) || is_in_subnet("$si", $var(record))) { + $sht(acl=>$var(device)/$si) = 0; + if (!isflagset(FLAG_IS_REPLY)) { + sl_send_reply(ACL_CODE_DENY, ACL_MESSAGE_DENY); + } + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is rejected by ACL for $var(device)\n"); + exit; + } + $var(i) = $var(i) + 1; + } + } else { + xlog("L_INFO", "$ci |ACL-device| undefined CIDR in response for $var(device)"); + } + # Remember in CACHE and ALLOW + $sht(acl=>$var(device)/$si) = 1; + xlog("L_INFO", "$ci |ACL| $var(sip-packet) from $si is permitted by ACL for $var(device)\n"); + return; +} diff --git a/kamailio/default.cfg b/kamailio/default.cfg index e6c6837..e338c54 100644 --- a/kamailio/default.cfg +++ b/kamailio/default.cfg @@ -190,6 +190,9 @@ include_file "antiflood-role.cfg" #!ifdef TRAFFIC-FILTER-ROLE include_file "traffic-filter-role.cfg" #!endif +#!ifdef ACL-ROLE +include_file "acl-role.cfg" +#!endif #!ifdef RATE-LIMITER-ROLE include_file "rate-limiter-role.cfg" #!endif @@ -221,6 +224,10 @@ route route(TRAFFIC_FILTER); #!endif + #!ifdef ACL-ROLE + route(ACL_CHECK); + #!endif + #!ifdef RATE-LIMITER-ROLE route(DOS_PREVENTION); #!endif @@ -502,6 +509,11 @@ onreply_route[EXTERNAL_REPLY] route(NAT_TEST_AND_CORRECT); #!endif + #!ifdef ACL-ROLE + setflag(FLAG_IS_REPLY); + route(ACL_CHECK); + #!endif + #!ifdef RATE-LIMITER-ROLE setflag(FLAG_IS_REPLY); route(DOS_PREVENTION); @@ -518,6 +530,11 @@ onreply_route[INTERNAL_REPLY] route(NAT_WEBSOCKETS_CORRECT); #!endif + #!ifdef ACL-ROLE + setflag(FLAG_IS_REPLY); + route(ACL_CHECK); + #!endif + #!ifdef RATE-LIMITER-ROLE setflag(FLAG_IS_REPLY); route(DOS_PREVENTION); diff --git a/kamailio/local.cfg b/kamailio/local.cfg index 4d7357b..7e12270 100644 --- a/kamailio/local.cfg +++ b/kamailio/local.cfg @@ -18,6 +18,7 @@ debug = L_INFO # # #!trydef TLS-ROLE # # #!trydef ANTIFLOOD-ROLE # # #!trydef RATE-LIMITER-ROLE +# # #!trydef ACL-ROLE ################################################################################ ## SERVER INFORMATION