Преглед изворни кода

secfilter: new module for defining security filters

- the following features are available:
  * blacklist to filter user agents, IP addresses, countries, domains and users
  * whitelist to filter user agents, IP addresses, countries, domains and users
  * blacklist of destinations where calling is not allowed
  * SQL injection attacks prevention
Jose Luis Verdeguer пре 6 година
родитељ
комит
4e9558e1cb

+ 14 - 0
src/modules/secfilter/Makefile

@@ -0,0 +1,14 @@
+#
+# secfilter module makefile
+#
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=secfilter.so
+LIBS=
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb1/srdb1
+include ../../Makefile.modules

+ 612 - 0
src/modules/secfilter/README

@@ -0,0 +1,612 @@
+Secfilter Module
+
+Jose Luis Verdeguer
+
+   Copyright © 2018
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. db_url (string)
+              3.2. table_name (string)
+              3.3. action_col (string)
+              3.4. type_col (string)
+              3.5. data_col (string)
+              3.6. dst_exact_match (integer)
+
+        4. Functions
+
+              4.1. secf_check_ip ()
+              4.2. secf_check_ua ()
+              4.3. secf_check_country (string)
+              4.4. secf_check_from_hdr ()
+              4.5. secf_check_to_hdr ()
+              4.6. secf_check_contact_hdr ()
+              4.7. secf_check_dst (string)
+              4.8. secf_check_sqli_hdr (string)
+              4.9. secf_check_sqli_all ()
+
+        5. RPC commands
+
+              5.1. secfilter.reload
+              5.2. secfilter.print
+              5.3. secfilter.add_dst
+              5.4. secfilter.add_bl
+              5.5. secfilter.add_wl
+
+        6. Installation
+
+              6.1. Database setup
+
+   List of Examples
+
+   1.1. Set db_url parameter
+   1.2. Set table_name parameter
+   1.3. Set action_col parameter
+   1.4. Set type_col parameter
+   1.5. Set data_col parameter
+   1.6. Set dst_exact_match parameter
+   1.7. secf_check_ip usage
+   1.8. secf_check_ua usage
+   1.9. secf_check_country usage
+   1.10. secf_check_from_hdr usage
+   1.11. secf_check_to_hdr usage
+   1.12. secf_check_contact_hdr usage
+   1.13. secf_check_dst usage
+   1.14. secf_check_sqli_hdr usage
+   1.15. secf_check_sqli_all usage
+   1.16. secfilter.reload usage
+   1.17. secfilter.print usage
+   1.18. secfilter.add_dst usage
+   1.19. secfilter.add_bl usage
+   1.20. secfilter.add_wl usage
+   1.21. Example database content - secfilter table
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. db_url (string)
+        3.2. table_name (string)
+        3.3. action_col (string)
+        3.4. type_col (string)
+        3.5. data_col (string)
+        3.6. dst_exact_match (integer)
+
+   4. Functions
+
+        4.1. secf_check_ip ()
+        4.2. secf_check_ua ()
+        4.3. secf_check_country (string)
+        4.4. secf_check_from_hdr ()
+        4.5. secf_check_to_hdr ()
+        4.6. secf_check_contact_hdr ()
+        4.7. secf_check_dst (string)
+        4.8. secf_check_sqli_hdr (string)
+        4.9. secf_check_sqli_all ()
+
+   5. RPC commands
+
+        5.1. secfilter.reload
+        5.2. secfilter.print
+        5.3. secfilter.add_dst
+        5.4. secfilter.add_bl
+        5.5. secfilter.add_wl
+
+   6. Installation
+
+        6.1. Database setup
+
+1. Overview
+
+   This module has been designed to offer an additional layer of security
+   over our communications. To achieve this, the following features are
+   available:
+
+   - Blacklist to block user agents, IP addresses, countries, domains and
+   users.
+
+   - Whitelist to allow user agents, IP addresses, countries, domains and
+   users.
+
+   - Blacklist of destinations where the called number is not allowed.
+
+   - SQL injection attacks prevention.
+
+   When a function is called, it will be searched in the whitelist. If the
+   value is not found, then the blacklist will be searched.
+
+   All data will be loaded into memory when the module is started. There
+   is an RPC reload command to update the data in database and memory. It
+   is also possible to add new data to the blacklist or whitelist using
+   other RPC commands.
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * database -- Any db_* database module
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * none
+
+3. Parameters
+
+   3.1. db_url (string)
+   3.2. table_name (string)
+   3.3. action_col (string)
+   3.4. type_col (string)
+   3.5. data_col (string)
+   3.6. dst_exact_match (integer)
+
+3.1. db_url (string)
+
+   Database URL.
+
+   Default value is ""
+
+   Example 1.1. Set db_url parameter
+                ...
+                modparam("secfilter", "db_url", "mysql://user:pass@localhost/kam
+ailio")
+                ...
+
+3.2. table_name (string)
+
+   Name of the table used to store the blacklisted and whitelisted values.
+
+   Default value is secfilter
+
+   Example 1.2. Set table_name parameter
+                ...
+                modparam("secfilter", "table_name", "secfilter")
+                ...
+
+3.3. action_col (string)
+
+   Name of database column containing the type of list. The possible
+   values are:
+
+     * 0 = blacklisted data
+     * 1 = whitelisted data
+     * 2 = blacklisted destination number
+
+   Default value is action
+
+   Example 1.3. Set action_col parameter
+                ...
+                modparam("secfilter", "action_col", "action")
+                ...
+
+3.4. type_col (string)
+
+   Name of database column containing the type of values. The possible
+   values are:
+
+     * 0 = user-agent (if action=0 or action=1)
+     * 0 = destination number (if action=2)
+     * 1 = country
+     * 2 = domain
+     * 3 = IP address
+     * 4 = user
+
+   Default value is type
+
+   Example 1.4. Set type_col parameter
+                ...
+                modparam("secfilter", "type_col", "type")
+                ...
+
+3.5. data_col (string)
+
+   Name of database column containing blacklisted and whitelisted values.
+
+   Default value is data
+
+   Example 1.5. Set data_col parameter
+                ...
+                modparam("secfilter", "data_col", "data")
+                ...
+
+3.6. dst_exact_match (integer)
+
+   This value is used in the destinations blacklist and corresponds to the
+   numbers that we want to prevent calling. If the value is 1, the call
+   will appear as blacklisted if the destination is exactly the same. If
+   the value is 0, every destination whose number begins with a number
+   appearing on the destination blacklist will be rejected.
+
+   Default value is 1
+
+   Example 1.6. Set dst_exact_match parameter
+                ...
+                modparam("secfilter", "dst_exact_match", 1)
+                ...
+
+4. Functions
+
+   4.1. secf_check_ip ()
+   4.2. secf_check_ua ()
+   4.3. secf_check_country (string)
+   4.4. secf_check_from_hdr ()
+   4.5. secf_check_to_hdr ()
+   4.6. secf_check_contact_hdr ()
+   4.7. secf_check_dst (string)
+   4.8. secf_check_sqli_hdr (string)
+   4.9. secf_check_sqli_all ()
+
+4.1.  secf_check_ip ()
+
+   It checks if the source IP address is blacklisted. The search is
+   aproximate and data stored in the database will be compared as a
+   prefix. For example, if we have blacklisted IP address 192.168.1. all
+   messages from IPs like 192.168.1.% will be rejected.
+
+   Return values are:
+     * 2 = the value is whitelisted
+     * 1 = the value is not found
+     * -2 = the value is blacklisted
+
+   Example 1.7. secf_check_ip usage
+                ...
+        secf_check_ip();
+        if ($? == -2) {
+                xlog("L_ALERT", "$rm from $si blocked because IP address is blac
+klisted");
+                exit;
+        }
+                ...
+
+4.2.  secf_check_ua ()
+
+   It checks if the user-agent is blacklisted. The search is approximate
+   and the comparison will be made using the values of the database as a
+   prefix. If we add to the user-agent blacklist the word sipcli, every
+   message whose user-agent is named, for example, sipcli/1.6 or
+   sipcli/1.8 will be blocked. It is very useful to block different
+   versions of the same program.
+
+   Return values are:
+     * 2 = the value is whitelisted
+     * 1 = the value is not found
+     * -1 = error
+     * -2 = the value is blacklisted
+
+   Example 1.8. secf_check_ua usage
+                ...
+        secf_check_ua();
+        if ($? == -2) {
+                xlog("L_ALERT", "$rm from $si blocked because UserAgent '$ua' is
+ blacklisted");
+                exit;
+        }
+                ...
+
+4.3.  secf_check_country (string)
+
+   Similar to secf_check_ua. It checks if the country (IP address) is
+   blacklisted. Geoip module must be loaded to get the country code.
+
+   Return values are:
+     * 2 = the value is whitelisted
+     * 1 = the value is not found
+     * -1 = error
+     * -2 = the value is blacklisted
+
+   Example 1.9. secf_check_country usage
+                ...
+        if (geoip_match("$si", "src")) {
+                secf_check_country($gip(src=>cc));
+                if ($avp(secfilter) == -2) {
+                        xlog("L_ALERT", "$rm from $si blocked because Country '$
+gip(src=>cc)' is blacklisted");
+                        exit;
+                }
+        }
+                ...
+
+4.4.  secf_check_from_hdr ()
+
+   It checks if any value of from header is blacklisted. It checks if from
+   name or from user are in the users blacklist or whitelist. It also
+   checks if the from domain is in the domains blacklist or whitelist. The
+   blacklisted value will be used as a prefix and if we block, for
+   example, the user sipvicious, all users whose name starts with this
+   word will be considered as blacklisted.
+
+   Return values are:
+     * 4 = from name is whitelisted
+     * 3 = from domain is whitelisted
+     * 2 = from user is whitelisted
+     * 1 = from header not found
+     * -1 = error
+     * -2 = from user is blacklisted
+     * -3 = from domain is blacklisted
+     * -4 = from name is blacklisted
+
+   Example 1.10. secf_check_from_hdr usage
+                ...
+        secf_check_from_hdr();
+        switch ($?) {
+                case -2:
+                        xlog("L_ALERT", "$rm to $si blocked because From user '$
+fU' is blacklisted");
+                        exit;
+                case -3:
+                        xlog("L_ALERT", "$rm to $si blocked because From domain
+'$fd' is blacklisted");
+                        exit;
+                case -4:
+                        xlog("L_ALERT", "$rm to $si blocked because From name '$
+fn' is blacklisted");
+                        exit;
+        };
+                ...
+
+4.5.  secf_check_to_hdr ()
+
+   Do the same as secf_check_from_hdr function but with the to header.
+
+   Return values are:
+     * 4 = to name is whitelisted
+     * 3 = to domain is whitelisted
+     * 2 = to user is whitelisted
+     * 1 = to header not found
+     * -1 = error
+     * -2 = to user is blacklisted
+     * -3 = to domain is blacklisted
+     * -4 = to name is blacklisted
+
+   Example 1.11. secf_check_to_hdr usage
+                ...
+        secf_check_to_hdr();
+        switch ($?) {
+                case -2:
+                        xlog("L_ALERT", "$rm to $si blocked because To user '$tU
+' is blacklisted");
+                        exit;
+                case -3:
+                        xlog("L_ALERT", "$rm to $si blocked because To domain '$
+td' is blacklisted");
+                        exit;
+                case -4:
+                        xlog("L_ALERT", "$rm to $si blocked because To name '$tn
+' is blacklisted");
+                        exit;
+        };
+                ...
+
+4.6.  secf_check_contact_hdr ()
+
+   Do the same as secf_check_from_hdr function but with the contact
+   header.
+
+   Return values are:
+     * 3 = contact domain is whitelisted
+     * 2 = contact user is whitelisted
+     * 1 = contact header not found
+     * -1 = error
+     * -2 = contact user is blacklisted
+     * -3 = contact domain is blacklisted
+
+   Example 1.12. secf_check_contact_hdr usage
+                ...
+        secf_check_contact_hdr();
+        switch ($?) {
+                case -2:
+                        xlog("L_ALERT", "$rm to $si blocked because Contact user
+ '$ct' is blacklisted");
+                        exit;
+                case -3:
+                        xlog("L_ALERT", "$rm to $si blocked because Contact doma
+in '$ct' is blacklisted");
+                        exit;
+        };
+                ...
+
+4.7.  secf_check_dst (string)
+
+   It checks if the destination number is blacklisted. It must be user for
+   INVITE messages. If the value of dst_exact_match is 1, the call will
+   appear as blacklisted if the destination is exactly the same. If the
+   value is 0, every destination whose number begins with a number
+   appearing on the destination blacklist will be rejected.
+
+   Return values are:
+     * 2 (if the value is whitelisted)
+     * 1 (if the value not found)
+     * -2 (if the value is blacklisted)
+
+   Example 1.13. secf_check_dst usage
+                ...
+                if (is_method("INVITE")) {
+                        secf_check_dst($rU);
+                        if ($? == -2) {
+                                xlog("L_ALERT", "$rm from $si blocked because de
+stination $rU is blacklisted");
+                                send_reply("403", "Forbidden");
+                                exit;
+                        }
+                }
+                ...
+
+4.8.  secf_check_sqli_hdr (string)
+
+   Search for illegal characters in the given value.
+
+   Example 1.14. secf_check_sqli_hdr usage
+                ...
+        secf_check_sqli_hdr($ua);
+        if ($? == -1) {
+                xlog("L_ALERT", "$rm from $si blocked because possible SQLi foun
+d in the user-agent header ($ua)");
+                exit;
+        }
+
+                ...
+
+4.9.  secf_check_sqli_all ()
+
+   Search for illegal characters in several headers (user-agent, from, to
+   and contact). If illegal characters are found the message will be
+   dropped.
+
+   Example 1.15. secf_check_sqli_all usage
+                ...
+                secf_check_sqli_all();
+                ...
+
+5. RPC commands
+
+   5.1. secfilter.reload
+   5.2. secfilter.print
+   5.3. secfilter.add_dst
+   5.4. secfilter.add_bl
+   5.5. secfilter.add_wl
+
+5.1.  secfilter.reload
+
+   Reload all blacklisted and whitelisted values from database.
+
+   Example 1.16. secfilter.reload usage
+                ...
+                kamcmd secfilter.reload
+                ...
+
+5.2.  secfilter.print
+
+   Print blacklisted and whitelisted values. Without parameters it will
+   print all values. If you enter a type it will print this type values
+   only.
+
+   Possible values are:
+     * (none) (show all data)
+     * ua (show blacklisted and whitelisted user-agents)
+     * country (show blacklisted and whitelisted countries)
+     * domain (show blacklisted and whitelisted domains)
+     * user (show blacklisted and whitelisted users)
+     * ip (show blacklisted and whitelisted IP addresses)
+     * dst (show blacklisted destinations)
+
+   Example 1.17. secfilter.print usage
+                ...
+                kamcmd secfilter.print
+                kamcmd secfilter.print ua
+                kamcmd secfilter.print country
+                kamcmd secfilter.print dst
+                ...
+
+5.3.  secfilter.add_dst
+
+   Insert values into destination blacklist. These values will be checked
+   with the function secf_check_dst to verify if the destination number
+   can be called.
+
+   Parameters:
+     * number (number to add to the destination blacklist)
+
+   Example 1.18. secfilter.add_dst usage
+                ...
+                kamcmd secfilter.add_dst 555123123
+                ...
+
+5.4.  secfilter.add_bl
+
+   Insert values into blacklist.
+
+   Parameters:
+     * type (must be: ua, country, domain, user or ip)
+     * value (value to add to the blacklist)
+
+   Example 1.19. secfilter.add_bl usage
+                ...
+                kamcmd secfilter.add_bl ua friendly-scanner
+                kamcmd secfilter.add_bl user sipvicious
+                ...
+
+5.5.  secfilter.add_wl
+
+   Insert values into whitelist.
+
+   Parameters:
+     * type (must be: ua, country, domain, user or ip)
+     * value (value to add to the whitelist)
+
+   Example 1.20. secfilter.add_wl usage
+                ...
+                kamcmd secfilter.add_wl country es
+                kamcmd secfilter.add_wl user trusted_user
+                ...
+
+6. Installation
+
+   6.1. Database setup
+
+6.1. Database setup
+
+   Before running Kamailio with the secfilter module, it is necessary to
+   setup the database table where the module will read the blacklist data
+   from. In order to do that, if the table was not created by the
+   installation script or you choose to install everything by yourself you
+   can use the secfilter-create.sql SQL script in the database directories
+   in the kamailio/scripts folder as a template. Database and table name
+   can be set with module parameters so they can be changed, but the name
+   of the columns must match the ones in the SQL script. You can also find
+   the complete database documentation on the project webpage,
+   https://www.kamailio.org/docs/db-tables/kamailio-db-devel.html.
+
+   Example 1.21. Example database content - secfilter table
+                ...
+                +----+-----------+-----------+------------------+
+                | id | action    | type      | data             |
+                +----+-----------+-----------+------------------+
+                |  1 | 0         | 2         | 1.1.1.1          |
+                |  2 | 0         | 0         | friendly-scanner |
+                |  3 | 0         | 0         | pplsip           |
+                |  4 | 0         | 0         | sipcli           |
+                |  5 | 0         | 4         | sipvicious       |
+                |  6 | 0         | 1         | ps               |
+                |  7 | 0         | 3         | 5.56.57.58       |
+                |  8 | 1         | 0         | asterisk pbx     |
+                |  9 | 1         | 2         | sip.mydomain.com |
+                | 10 | 2         | 0         | 555123123        |
+                | 11 | 2         | 0         | 555998776        |
+                +----+-----------+-----------+------------------+
+                ...
+
+   Action values are:
+     * 0 (blacklist)
+     * 1 (whitelist)
+     * 2 (destination)
+
+   Type values are:
+     * 0 (user-agent)
+     * 1 (country)
+     * 2 (domain)
+     * 3 (IP address)
+     * 4 (user)

+ 3 - 0
src/modules/secfilter/doc/Makefile

@@ -0,0 +1,3 @@
+docs = secfilter.xml
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module

+ 33 - 0
src/modules/secfilter/doc/secfilter.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+	<bookinfo>
+	<title>Secfilter Module</title>
+	<productname class="trade">&kamailioname;</productname>
+	<authorgroup>
+		<author>
+		<firstname>Jose Luis</firstname>
+		<surname>Verdeguer</surname>
+		<affiliation><orgname></orgname></affiliation>
+		<address>
+			<email>[email protected]</email>
+		</address>
+		</author>
+	</authorgroup>
+	<copyright>
+		<year>2018</year>
+	</copyright>
+	</bookinfo>
+	<toc></toc>
+	
+	<xi:include href="secfilter_admin.xml"/>
+	
+</book>

+ 764 - 0
src/modules/secfilter/doc/secfilter_admin.xml

@@ -0,0 +1,764 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+	
+	<title>&adminguide;</title>
+	
+	<section>
+	<title>Overview</title>
+	<para>
+		This module has been designed to offer an additional layer of
+		security over our communications. To achieve this, the following
+		features are available:
+	</para>
+	<para>
+		- Blacklist to block user agents, IP addresses, countries, domains and users.
+	</para>
+	<para>
+		- Whitelist to allow user agents, IP addresses, countries, domains and users.
+	</para>
+	<para>
+		- Blacklist of destinations where the called number is not allowed.
+	</para>
+	<para>
+		- SQL injection attacks prevention.
+	</para>
+	<para>
+		When a function is called, it will be searched in the whitelist. If the
+		value is not found, then the blacklist will be searched.
+	</para>
+	<para>
+		All data will be loaded into memory when the module is started. There is an
+		RPC reload command to update the data in database and memory. It is also
+		possible to add new data to the blacklist or whitelist using other RPC commands.
+	</para>
+	</section>
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>database</emphasis>
+				-- Any db_* database module
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before 
+		running &kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>none</emphasis>
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+ 	<section>
+     <title>Parameters</title>
+ 
+	<section id="secfilter.p.db_url">
+       <title><varname>db_url</varname> (string)</title>
+ 
+ 		<para> 
+	 	Database URL.
+ 		</para> 
+       <para><emphasis> Default value is ""</emphasis></para>
+ 
+       <example>
+         <title>Set <varname>db_url</varname> parameter</title>
+ 
+         <programlisting format="linespecific">
+		...
+		modparam("secfilter", "db_url", "mysql://user:pass@localhost/kamailio")
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.p.table_name">
+       <title><varname>table_name</varname> (string)</title>
+ 
+ 		<para> 
+		Name of the table used to store the blacklisted and whitelisted values.
+ 		</para> 
+       <para><emphasis> Default value is secfilter</emphasis></para>
+ 
+       <example>
+         <title>Set <varname>table_name</varname> parameter</title>
+ 
+         <programlisting format="linespecific">
+		...
+		modparam("secfilter", "table_name", "secfilter")
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.p.action_col">
+       <title><varname>action_col</varname> (string)</title>
+ 
+ 		<para> 
+ 		Name of database column containing the type of list.
+ 		The possible values are:
+ 		</para>
+ 		<para>
+                <itemizedlist>
+                <listitem>0 = blacklisted data</listitem>
+                <listitem>1 = whitelisted data</listitem>
+                <listitem>2 = blacklisted destination number</listitem>
+                </itemizedlist>
+ 		</para> 
+       <para><emphasis> Default value is action</emphasis></para>
+ 
+       <example>
+         <title>Set <varname>action_col</varname> parameter</title>
+ 
+         <programlisting format="linespecific">
+		...
+		modparam("secfilter", "action_col", "action")
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.p.type_col">
+       <title><varname>type_col</varname> (string)</title>
+ 
+ 		<para> 
+ 		Name of database column containing the type of values.
+ 		The possible values are:
+ 		</para>
+		<para>
+                 <itemizedlist>
+                <listitem>0 = user-agent (if action=0 or action=1)</listitem>
+                <listitem>0 = destination number (if action=2)</listitem>
+                <listitem>1 = country</listitem>
+                <listitem>2 = domain</listitem>
+                <listitem>3 = IP address</listitem>
+                <listitem>4 = user</listitem>
+                </itemizedlist>
+ 		</para> 
+       <para><emphasis> Default value is type</emphasis></para>
+ 
+       <example>
+         <title>Set <varname>type_col</varname> parameter</title>
+ 
+         <programlisting format="linespecific">
+		...
+		modparam("secfilter", "type_col", "type")
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.p.data_col">
+       <title><varname>data_col</varname> (string)</title>
+ 
+ 		<para> 
+ 		Name of database column containing blacklisted and whitelisted values.
+ 		</para> 
+       <para><emphasis> Default value is data</emphasis></para>
+ 
+       <example>
+         <title>Set <varname>data_col</varname> parameter</title>
+ 
+         <programlisting format="linespecific">
+		...
+		modparam("secfilter", "data_col", "data")
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.p.dst_exact_match">
+       <title><varname>dst_exact_match</varname> (integer)</title>
+ 
+ 		<para> 
+ 		This value is used in the destinations blacklist and corresponds to the numbers
+ 		that we want to prevent calling. If the value is <emphasis>1</emphasis>, the call will appear as
+ 		blacklisted if the destination is exactly the same. If the value is <emphasis>0</emphasis>, every
+ 		destination whose number begins with a number appearing on the destination blacklist
+ 		will be rejected.
+    		</para> 
+       <para><emphasis> Default value is 1</emphasis></para>
+ 
+       <example>
+         <title>Set <varname>dst_exact_match</varname> parameter</title>
+ 
+         <programlisting format="linespecific">
+		...
+		modparam("secfilter", "dst_exact_match", 1)
+		...
+		</programlisting>
+       </example>
+     </section>
+	</section>
+
+ 	<section>
+     <title>Functions</title>
+ 
+	<section id="secfilter.fsecf_.check_ip">
+       <title>
+		<function moreinfo="none">secf_check_ip
+		()</function>
+	  </title>
+ 
+ 		<para> 
+		It checks if the source IP address is blacklisted. The search is aproximate and data stored
+		in the database will be compared as a prefix. For example, if we have blacklisted IP address
+		<emphasis>192.168.1.</emphasis> all messages from IPs like 192.168.1.% will be rejected.
+ 		</para> 
+ 		<para> 
+		Return values are:
+		<itemizedlist>
+		<listitem> 2 = the value is whitelisted</listitem>
+		<listitem> 1 = the value is not found</listitem>
+		<listitem>-2 = the value is blacklisted</listitem>
+		</itemizedlist>
+ 		</para> 
+ 
+       <example>
+         <title><function>secf_check_ip</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+        secf_check_ip();
+        if ($? == -2) {
+                xlog("L_ALERT", "$rm from $si blocked because IP address is blacklisted");
+                exit;
+        }
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.f.secf_check_ua">
+       <title>
+		<function moreinfo="none">secf_check_ua
+		()</function>
+	  </title>
+ 
+ 		<para> 
+		It checks if the user-agent is blacklisted. The search is approximate and the
+		comparison will be made using the values of the database as a prefix. If
+		we add to the user-agent blacklist the word <emphasis>sipcli</emphasis>,
+		every message whose user-agent is named, for example, <emphasis>sipcli/1.6</emphasis> or <emphasis>sipcli/1.8</emphasis> will
+		be blocked. It is very useful to block different versions of the same program.
+ 		</para> 
+ 		<para> 
+		Return values are:
+		<itemizedlist>
+		<listitem> 2 = the value is whitelisted</listitem>
+		<listitem> 1 = the value is not found</listitem>
+		<listitem>-1 = error</listitem>
+		<listitem>-2 = the value is blacklisted</listitem>
+		</itemizedlist>
+ 		</para> 
+ 
+       <example>
+         <title><function>secf_check_ua</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+        secf_check_ua();
+        if ($? == -2) {
+                xlog("L_ALERT", "$rm from $si blocked because UserAgent '$ua' is blacklisted");
+                exit;
+        }
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.f.secf_check_country">
+       <title>
+		<function moreinfo="none">secf_check_country
+		(string)</function>
+	  </title>
+ 
+ 		<para> 
+		Similar to secf_check_ua. It checks if the country (IP address) is blacklisted.
+		<emphasis>Geoip</emphasis> module must be loaded to get the country code.
+ 		</para> 
+ 		<para> 
+		Return values are:
+		<itemizedlist>
+		<listitem> 2 = the value is whitelisted</listitem>
+		<listitem> 1 = the value is not found</listitem>
+		<listitem>-1 = error</listitem>
+		<listitem>-2 = the value is blacklisted</listitem>
+		</itemizedlist>
+ 		</para> 
+ 
+       <example>
+         <title><function>secf_check_country</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+        if (geoip_match("$si", "src")) {
+                secf_check_country($gip(src=>cc));
+                if ($avp(secfilter) == -2) {
+                        xlog("L_ALERT", "$rm from $si blocked because Country '$gip(src=>cc)' is blacklisted");
+                        exit;
+                }
+        }
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.f.secf_check_from_hdr">
+       <title>
+		<function moreinfo="none">secf_check_from_hdr
+		()</function>
+	  </title>
+ 
+ 		<para> 
+		It checks if any value of <emphasis>from header</emphasis> is blacklisted. It checks if from name or from user
+		are in the users blacklist or whitelist. It also checks if the from domain is in the
+		domains blacklist or whitelist. The blacklisted value will be used as a prefix and if we block, for
+		example, the user <emphasis>sipvicious</emphasis>, all users whose name starts with this word will be
+		considered as blacklisted.
+ 		</para> 
+ 		<para> 
+		Return values are:
+		<itemizedlist>
+		<listitem> 4 = from name is whitelisted</listitem>
+		<listitem> 3 = from domain is whitelisted</listitem>
+		<listitem> 2 = from user is whitelisted</listitem>
+		<listitem> 1 = from header not found</listitem>
+		<listitem>-1 = error</listitem>
+		<listitem>-2 = from user is blacklisted</listitem>
+		<listitem>-3 = from domain is blacklisted</listitem>
+		<listitem>-4 = from name is blacklisted</listitem>
+		</itemizedlist>
+ 		</para> 
+ 
+       <example>
+         <title><function>secf_check_from_hdr</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+        secf_check_from_hdr();
+        switch ($?) {
+                case -2:
+                        xlog("L_ALERT", "$rm to $si blocked because From user '$fU' is blacklisted");
+                        exit;
+                case -3:
+                        xlog("L_ALERT", "$rm to $si blocked because From domain '$fd' is blacklisted");
+                        exit;
+                case -4:
+                        xlog("L_ALERT", "$rm to $si blocked because From name '$fn' is blacklisted");
+                        exit;
+        };
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.f.secf_check_to_hdr">
+       <title>
+		<function moreinfo="none">secf_check_to_hdr
+		()</function>
+	  </title>
+ 
+ 		<para> 
+		Do the same as <emphasis>secf_check_from_hdr</emphasis> function but with the <emphasis>to header</emphasis>.
+ 		</para> 
+ 		<para> 
+		Return values are:
+		<itemizedlist>
+		<listitem> 4 = to name is whitelisted</listitem>
+		<listitem> 3 = to domain is whitelisted</listitem>
+		<listitem> 2 = to user is whitelisted</listitem>
+		<listitem> 1 = to header not found</listitem>
+		<listitem>-1 = error</listitem>
+		<listitem>-2 = to user is blacklisted</listitem>
+		<listitem>-3 = to domain is blacklisted</listitem>
+		<listitem>-4 = to name is blacklisted</listitem>
+		</itemizedlist>
+ 		</para> 
+ 
+       <example>
+         <title><function>secf_check_to_hdr</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+        secf_check_to_hdr();
+        switch ($?) {
+                case -2:
+                        xlog("L_ALERT", "$rm to $si blocked because To user '$tU' is blacklisted");
+                        exit;
+                case -3:
+                        xlog("L_ALERT", "$rm to $si blocked because To domain '$td' is blacklisted");
+                        exit;
+                case -4:
+                        xlog("L_ALERT", "$rm to $si blocked because To name '$tn' is blacklisted");
+                        exit;
+        };
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.f.secf_check_contact_hdr">
+       <title>
+		<function moreinfo="none">secf_check_contact_hdr
+		()</function>
+	  </title>
+ 
+ 		<para> 
+		Do the same as <emphasis>secf_check_from_hdr</emphasis> function but with the <emphasis>contact header</emphasis>.
+ 		</para> 
+ 		<para> 
+		Return values are:
+		<itemizedlist>
+		<listitem> 3 = contact domain is whitelisted</listitem>
+		<listitem> 2 = contact user is whitelisted</listitem>
+		<listitem> 1 = contact header not found</listitem>
+		<listitem>-1 = error</listitem>
+		<listitem>-2 = contact user is blacklisted</listitem>
+		<listitem>-3 = contact domain is blacklisted</listitem>
+		</itemizedlist>
+ 		</para> 
+ 
+       <example>
+         <title><function>secf_check_contact_hdr</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+        secf_check_contact_hdr();
+        switch ($?) {
+                case -2:
+                        xlog("L_ALERT", "$rm to $si blocked because Contact user '$ct' is blacklisted");
+                        exit;
+                case -3:
+                        xlog("L_ALERT", "$rm to $si blocked because Contact domain '$ct' is blacklisted");
+                        exit;
+        };
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.f.secf_check_dst">
+       <title>
+		<function moreinfo="none">secf_check_dst
+		(string)</function>
+	  </title>
+ 
+ 		<para> 
+		It checks if the destination number is blacklisted. It must be user for INVITE
+		messages. If the value of <emphasis>dst_exact_match</emphasis> is <emphasis>1</emphasis>, the call will
+		appear as blacklisted if the destination is exactly the same. If the value is <emphasis>0</emphasis>,
+		every destination whose number begins with a number appearing on the destination blacklist will be rejected.
+ 		</para> 
+ 		<para> 
+		Return values are:
+		<itemizedlist>
+		<listitem> 2 (if the value is whitelisted)</listitem>
+		<listitem> 1 (if the value not found)</listitem>
+		<listitem>-2 (if the value is blacklisted)</listitem>
+		</itemizedlist>
+ 		</para> 
+ 
+       <example>
+         <title><function>secf_check_dst</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+		if (is_method("INVITE")) {
+			secf_check_dst($rU);
+			if ($? == -2) {
+				xlog("L_ALERT", "$rm from $si blocked because destination $rU is blacklisted");
+				send_reply("403", "Forbidden");
+				exit;
+			}
+		}
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.f.secf_check_sqli_hdr">
+       <title>
+		<function moreinfo="none">secf_check_sqli_hdr
+		(string)</function>
+	  </title>
+ 
+ 		<para> 
+		Search for illegal characters in the given value.
+		</para> 
+ 
+       <example>
+         <title><function>secf_check_sqli_hdr</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+        secf_check_sqli_hdr($ua);
+        if ($? == -1) {
+                xlog("L_ALERT", "$rm from $si blocked because possible SQLi found in the user-agent header ($ua)");
+                exit;
+        }
+
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.f.secf_check_sqli_all">
+       <title>
+		<function moreinfo="none">secf_check_sqli_all
+		()</function>
+	  </title>
+ 
+ 		<para> 
+		Search for illegal characters in several headers (user-agent, from, to and contact). If illegal
+		characters are found the message will be dropped.
+		</para> 
+ 
+       <example>
+         <title><function>secf_check_sqli_all</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+		secf_check_sqli_all();
+		...
+		</programlisting>
+       </example>
+     </section>
+	</section>
+
+ 	<section>
+     <title>RPC commands</title>
+ 
+	<section id="secfilter.r.reload">
+       <title>
+		<function moreinfo="none">secfilter.reload</function>
+	  </title>
+ 
+ 		<para> 
+		Reload all blacklisted and whitelisted values from database.
+ 		</para> 
+ 
+       <example>
+         <title><function>secfilter.reload</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+		&kamcmd; secfilter.reload
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.r.print">
+       <title>
+		<function moreinfo="none">secfilter.print</function>
+	  </title>
+ 
+ 		<para> 
+		Print blacklisted and whitelisted values. Without parameters it will print all
+		values. If you enter a type it will print this type values only.
+ 		</para> 
+ 
+ 		<para> 
+ 		Possible values are:
+		<itemizedlist>
+		<listitem>(none)  (show all data)</listitem>
+		<listitem>ua      (show blacklisted and whitelisted user-agents)</listitem>
+		<listitem>country (show blacklisted and whitelisted countries)</listitem>
+		<listitem>domain  (show blacklisted and whitelisted domains)</listitem>
+		<listitem>user    (show blacklisted and whitelisted users)</listitem>
+		<listitem>ip      (show blacklisted and whitelisted IP addresses)</listitem>
+		<listitem>dst     (show blacklisted destinations)</listitem>
+		</itemizedlist>
+ 		</para> 
+
+       <example>
+         <title><function>secfilter.print</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+		&kamcmd; secfilter.print
+		&kamcmd; secfilter.print ua
+		&kamcmd; secfilter.print country
+		&kamcmd; secfilter.print dst
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.r.add_dst">
+       <title>
+		<function moreinfo="none">secfilter.add_dst</function>
+	  </title>
+ 
+ 		<para> 
+		Insert values into destination blacklist. These values will be checked with the 
+		function <emphasis>secf_check_dst</emphasis> to verify if the destination number can be called. 
+ 		</para> 
+ 
+ 		<para> 
+ 		Parameters:
+		<itemizedlist>
+		<listitem>number (number to add to the destination blacklist)</listitem>
+		</itemizedlist>
+ 		</para> 
+
+       <example>
+         <title><function>secfilter.add_dst</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+		&kamcmd; secfilter.add_dst 555123123
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.r.add_bl">
+       <title>
+		<function moreinfo="none">secfilter.add_bl</function>
+	  </title>
+ 
+ 		<para> 
+		Insert values into blacklist.
+ 		</para> 
+ 
+ 		<para> 
+ 		Parameters:
+		<itemizedlist>
+		<listitem>type  (must be: ua, country, domain, user or ip)</listitem>
+		<listitem>value (value to add to the blacklist)</listitem>
+		</itemizedlist>
+ 		</para> 
+
+       <example>
+         <title><function>secfilter.add_bl</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+		&kamcmd; secfilter.add_bl ua friendly-scanner
+		&kamcmd; secfilter.add_bl user sipvicious
+		...
+		</programlisting>
+       </example>
+     </section>
+
+	<section id="secfilter.r.add_wl">
+       <title>
+		<function moreinfo="none">secfilter.add_wl</function>
+	  </title>
+ 
+ 		<para> 
+		Insert values into whitelist.
+ 		</para> 
+ 
+ 		<para> 
+ 		Parameters:
+		<itemizedlist>
+		<listitem>type  (must be: ua, country, domain, user or ip)</listitem>
+		<listitem>value (value to add to the whitelist)</listitem>
+		</itemizedlist>
+ 		</para> 
+
+       <example>
+         <title><function>secfilter.add_wl</function> usage</title>
+ 
+         <programlisting format="linespecific">
+		...
+		&kamcmd; secfilter.add_wl country es
+		&kamcmd; secfilter.add_wl user trusted_user
+		...
+		</programlisting>
+       </example>
+     </section>
+	</section>
+	
+ 	<section>
+     <title>Installation</title>
+ 
+	<section>
+       <title>Database setup</title>
+ 
+ 		<para> 
+		Before running &kamailio; with the secfilter module,
+		it is necessary to setup the database table where the module will
+		read the blacklist data from. In order to do that, if the table was 
+		not created by the installation script or you choose to install everything
+		by yourself you can use the <emphasis>secfilter-create.sql</emphasis>
+		<acronym>SQL</acronym> script in the database directories in the 
+		kamailio/scripts folder as a template. 
+		Database and table name can be set with module parameters so they 
+		can be changed, but the name of the columns must match the ones 
+		in the <acronym>SQL</acronym> script.
+		You can also find the complete database documentation on the
+		project webpage, &kamailiodbdocs;.
+ 		</para> 
+ 
+       <example>
+         <title>Example database content - secfilter table</title>
+ 
+         <programlisting format="linespecific">
+		...
+		+----+-----------+-----------+------------------+
+		| id | action    | type      | data             |
+		+----+-----------+-----------+------------------+
+		|  1 | 0         | 2         | 1.1.1.1          |
+		|  2 | 0         | 0         | friendly-scanner |
+		|  3 | 0         | 0         | pplsip           |
+		|  4 | 0         | 0         | sipcli           |
+		|  5 | 0         | 4         | sipvicious       |
+		|  6 | 0         | 1         | ps               |
+		|  7 | 0         | 3         | 5.56.57.58       |
+		|  8 | 1         | 0         | asterisk pbx     |
+		|  9 | 1         | 2         | sip.mydomain.com |
+		| 10 | 2         | 0         | 555123123        |
+		| 11 | 2         | 0         | 555998776        |
+		+----+-----------+-----------+------------------+
+		...
+		</programlisting>
+       </example>
+		<para>
+ 		Action values are:
+		<itemizedlist>
+		<listitem>0 (blacklist)</listitem>
+		<listitem>1 (whitelist)</listitem>
+		<listitem>2 (destination)</listitem>
+		</itemizedlist>
+		</para>
+		<para>
+ 		Type values are:
+		<itemizedlist>
+		<listitem>0 (user-agent)</listitem>
+		<listitem>1 (country)</listitem>
+		<listitem>2 (domain)</listitem>
+		<listitem>3 (IP address)</listitem>
+		<listitem>4 (user)</listitem>
+		</itemizedlist>
+		</para>
+     </section>
+	</section>
+</chapter>
+

+ 728 - 0
src/modules/secfilter/secfilter.c

@@ -0,0 +1,728 @@
+/**
+ * Copyright (C) 2018 Jose Luis Verdeguer
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../../core/sr_module.h"
+#include "../../core/mem/shm_mem.h"
+#include "../../core/rpc_lookup.h"
+#include "../../core/strutils.h"
+#include "../../lib/srdb1/db.h"
+#include "../../core/dprint.h"
+#include "../../core/locking.h"
+#include "secfilter.h"
+
+MODULE_VERSION
+
+secf_data_p secf_data;
+
+/* Static and shared functions */
+static int mod_init(void);
+int init_data(void);
+static int child_init(int rank);
+static int rpc_init(void);
+static void free_str_list(struct str_list *l);
+static void free_sec_info(secf_info_p info);
+void free_data(void);
+static void mod_destroy(void);
+static int w_check_sqli(str val);
+static int check_user(struct sip_msg *msg, int type);
+
+/* External functions */
+static int w_check_ua(struct sip_msg *msg);
+static int w_check_from_hdr(struct sip_msg *msg);
+static int w_check_to_hdr(struct sip_msg *msg);
+static int w_check_contact_hdr(struct sip_msg *msg);
+static int w_check_ip(struct sip_msg *msg);
+static int w_check_country(struct sip_msg *msg, char *val);
+static int w_check_dst(struct sip_msg *msg, char *val);
+static int w_check_sqli_all(struct sip_msg *msg);
+static int w_check_sqli_hdr(struct sip_msg *msg, char *val);
+
+/* Exported module parameters - default values */
+int secf_dst_exact_match = 1;
+str secf_db_url = {NULL, 0};
+str secf_table_name = str_init("secfilter");
+str secf_action_col = str_init("action");
+str secf_type_col = str_init("type");
+str secf_data_col = str_init("data");
+
+/* Exported commands */
+static cmd_export_t cmds[] = {
+		{"secf_check_ua", (cmd_function)w_check_ua, 0, 0, 0, ANY_ROUTE},
+		{"secf_check_from_hdr", (cmd_function)w_check_from_hdr, 0, 0, 0,
+				ANY_ROUTE},
+		{"secf_check_to_hdr", (cmd_function)w_check_to_hdr, 0, 0, 0, ANY_ROUTE},
+		{"secf_check_contact_hdr", (cmd_function)w_check_contact_hdr, 0, 0, 0,
+				ANY_ROUTE},
+		{"secf_check_ip", (cmd_function)w_check_ip, 0, 0, 0, ANY_ROUTE},
+		{"secf_check_country", (cmd_function)w_check_country, 1, 0, 0,
+				ANY_ROUTE},
+		{"secf_check_dst", (cmd_function)w_check_dst, 1, 0, 0, ANY_ROUTE},
+		{"secf_check_sqli_all", (cmd_function)w_check_sqli_all, 0, 0, 0,
+				ANY_ROUTE},
+		{"secf_check_sqli_hdr", (cmd_function)w_check_sqli_hdr, 1, 0, 0,
+				ANY_ROUTE},
+		{0, 0, 0, 0, 0, 0}};
+
+/* Exported module parameters */
+static param_export_t params[] = {{"db_url", PARAM_STRING, &secf_db_url},
+		{"table_name", PARAM_STR, &secf_table_name},
+		{"action_col", PARAM_STR, &secf_action_col},
+		{"type_col", PARAM_STR, &secf_type_col},
+		{"data_col", PARAM_STR, &secf_data_col},
+		{"dst_exact_match", PARAM_INT, &secf_dst_exact_match}, {0, 0, 0}};
+
+/* Module exports definition */
+struct module_exports exports = {
+		"secfilter",	 /* module name */
+		DEFAULT_DLFLAGS, /* dlopen flags */
+		cmds,		 /* exported functions */
+		params,		 /* exported parameters */
+		0,		 /* RPC method exports */
+		0,		 /* exported pseudo-variables */
+		0,		 /* response handling function */
+		mod_init,	 /* module initialization function */
+		child_init,	 /* per-child init function */
+		mod_destroy	 /* module destroy function */
+};
+
+/* RPC exported commands */
+static const char *rpc_reload_doc[2] = {"Reload values from database", NULL};
+static const char *rpc_print_doc[2] = {"Print values from database", NULL};
+static const char *rpc_add_dst_doc[2] = {
+		"Add new values to destination blacklist", NULL};
+static const char *rpc_add_bl_doc[2] = {"Add new values to blacklist", NULL};
+static const char *rpc_add_wl_doc[2] = {"Add new values to whitelist", NULL};
+
+rpc_export_t secfilter_rpc[] = {
+		{"secfilter.reload", rpc_reload, rpc_reload_doc, 0},
+		{"secfilter.print", rpc_print, rpc_print_doc, 0},
+		{"secfilter.add_dst", rpc_add_dst, rpc_add_dst_doc, 0},
+		{"secfilter.add_bl", rpc_add_bl, rpc_add_bl_doc, 0},
+		{"secfilter.add_wl", rpc_add_wl, rpc_add_wl_doc, 0}, {0, 0, 0, 0}};
+
+
+/***
+PREVENT SQL INJECTION
+***/
+
+/* External function to search for illegal characters in several headers */
+static int w_check_sqli_all(struct sip_msg *msg)
+{
+	str ua;
+	str name;
+	str user;
+	str domain;
+	int res;
+	int retval = 1;
+
+	/* Find SQLi in user-agent header */
+	res = secf_get_ua(msg, &ua);
+	if(res == 0) {
+		if(w_check_sqli(ua) != 1) {
+			LM_INFO("Possible SQL injection found in User-agent (%.*s)\n",
+					ua.len, ua.s);
+			retval = 0;
+			goto end_sqli;
+		}
+	}
+
+	/* Find SQLi in from header */
+	res = secf_get_from(msg, &name, &user, &domain);
+	if(res == 0) {
+		if(name.len > 0) {
+			if(w_check_sqli(name) != 1) {
+				LM_INFO("Possible SQL injection found in From name (%.*s)\n",
+						name.len, name.s);
+				retval = 0;
+				goto end_sqli;
+			}
+		}
+
+		if(user.len > 0) {
+			if(w_check_sqli(user) != 1) {
+				LM_INFO("Possible SQL injection found in From user (%.*s)\n",
+						user.len, user.s);
+				retval = 0;
+				goto end_sqli;
+			}
+		}
+
+		if(domain.len > 0) {
+			if(w_check_sqli(domain) != 1) {
+				LM_INFO("Possible SQL injection found in From domain (%.*s)\n",
+						domain.len, domain.s);
+				retval = 0;
+				goto end_sqli;
+			}
+		}
+	}
+
+	/* Find SQLi in to header */
+	res = secf_get_to(msg, &name, &user, &domain);
+	if(res == 0) {
+		if(name.len > 0) {
+			if(w_check_sqli(name) != 1) {
+				LM_INFO("Possible SQL injection found in To name (%.*s)\n",
+						name.len, name.s);
+				retval = 0;
+				goto end_sqli;
+			}
+		}
+
+		if(user.len > 0) {
+			if(w_check_sqli(user) != 1) {
+				LM_INFO("Possible SQL injection found in To user (%.*s)\n",
+						user.len, user.s);
+				retval = 0;
+				goto end_sqli;
+			}
+		}
+
+		if(domain.len > 0) {
+			if(w_check_sqli(domain) != 1) {
+				LM_INFO("Possible SQL injection found in To domain (%.*s)\n",
+						domain.len, domain.s);
+				retval = 0;
+				goto end_sqli;
+			}
+		}
+	}
+
+	/* Find SQLi in contact header */
+	res = secf_get_contact(msg, &user, &domain);
+	if(res == 0) {
+		if(user.len > 0) {
+			if(w_check_sqli(user) != 1) {
+				LM_INFO("Possible SQL injection found in Contact user (%.*s)\n",
+						user.len, user.s);
+				retval = 0;
+				goto end_sqli;
+			}
+		}
+
+		if(domain.len > 0) {
+			if(w_check_sqli(domain) != 1) {
+				LM_INFO("Possible SQL injection found in Contact domain "
+						"(%.*s)\n",
+						domain.len, domain.s);
+				retval = 0;
+				goto end_sqli;
+			}
+		}
+	}
+
+end_sqli:
+	return retval;
+}
+
+
+/* External function to search for illegal characters in some header */
+static int w_check_sqli_hdr(struct sip_msg *msg, char *cval)
+{
+	str val;
+	val.s = cval;
+	val.len = strlen(cval);
+
+	return w_check_sqli(val);
+}
+
+
+/* Search for illegal characters */
+static int w_check_sqli(str val)
+{
+	char *cval;
+	int res = 1;
+
+	cval = (char *)pkg_malloc(val.len + 1);
+	if(cval == NULL) {
+		LM_CRIT("Cannot allocate pkg memory\n");
+		return -2;
+	}
+	memset(cval, 0, val.len + 1);
+	memcpy(cval, val.s, val.len);
+
+	if(strstr(cval, "'") || strstr(cval, "\"") || strstr(cval, "--")
+			|| strstr(cval, "#") || strstr(cval, "%27") || strstr(cval, "%24")
+			|| strstr(cval, "%60")) {
+		/* Illegal characters found */
+		res = -1;
+		goto end;
+	}
+
+end:
+	if(cval)
+		pkg_free(cval);
+
+	return res;
+}
+
+
+/***
+BLACKLIST AND WHITELIST
+***/
+
+/* Check if the current destination is allowed */
+static int w_check_dst(struct sip_msg *msg, char *val)
+{
+	str dst;
+	struct str_list *list;
+
+	dst.s = val;
+	dst.len = strlen(val);
+
+	list = secf_data->bl.dst;
+	while(list) {
+		if(secf_dst_exact_match == 1) {
+			/* Exact match */
+			if(list->s.len == dst.len) {
+				if(cmpi_str(&list->s, &dst) == 0) {
+					return -2;
+				}
+			}
+		} else {
+			/* Any match */
+			if(dst.len > list->s.len)
+				dst.len = list->s.len;
+			if(cmpi_str(&list->s, &dst) == 0) {
+				return -2;
+			}
+		}
+		list = list->next;
+	}
+
+	return 1;
+}
+
+
+/* Check if the current user-agent is allowed
+Return codes:
+ 2 = user-agent whitelisted
+ 1 = not found
+-1 = error
+-2 = user-agent blacklisted
+*/
+static int w_check_ua(struct sip_msg *msg)
+{
+	int res, len;
+	str ua;
+	struct str_list *list;
+
+	res = secf_get_ua(msg, &ua);
+	if(res != 0)
+		return res;
+
+	len = ua.len;
+
+	/* User-agent whitelisted */
+	list = secf_data->wl.ua;
+	while(list) {
+		if(ua.len > list->s.len)
+			ua.len = list->s.len;
+		res = cmpi_str(&list->s, &ua);
+		if(res == 0) {
+			return 2;
+		}
+		list = list->next;
+		ua.len = len;
+	}
+
+	/* User-agent blacklisted */
+	list = secf_data->bl.ua;
+	while(list) {
+		if(ua.len > list->s.len)
+			ua.len = list->s.len;
+		res = cmpi_str(&list->s, &ua);
+		if(res == 0) {
+			return -2;
+		}
+		list = list->next;
+		ua.len = len;
+	}
+	return 1;
+}
+
+
+/* Check if the current from user is allowed */
+static int w_check_from_hdr(struct sip_msg *msg)
+{
+	return check_user(msg, 1);
+}
+
+
+/* Check if the current to user is allowed */
+static int w_check_to_hdr(struct sip_msg *msg)
+{
+	return check_user(msg, 2);
+}
+
+
+/* Check if the current contact user is allowed */
+static int w_check_contact_hdr(struct sip_msg *msg)
+{
+	return check_user(msg, 3);
+}
+
+
+/* 
+Check if the current user is allowed 
+
+Return codes:
+ 4 = name whitelisted
+ 3 = domain whitelisted
+ 2 = user whitelisted
+ 1 = not found
+-1 = error
+-2 = user blacklisted
+-3 = domain blacklisted
+-4 = name blacklisted
+*/
+static int check_user(struct sip_msg *msg, int type)
+{
+	str name;
+	str user;
+	str domain;
+	int res = 0;
+	int nlen, ulen, dlen;
+	struct str_list *list;
+
+	switch(type) {
+		case 1:
+			res = secf_get_from(msg, &name, &user, &domain);
+			break;
+		case 2:
+			res = secf_get_to(msg, &name, &user, &domain);
+			break;
+		case 3:
+			res = secf_get_contact(msg, &user, &domain);
+			break;
+		default:
+			return -1;
+	}
+	if(res != 0) {
+		return res;
+	}
+
+	nlen = name.len;
+	ulen = user.len;
+	dlen = domain.len;
+
+	/* User whitelisted */
+	list = secf_data->wl.user;
+	while(list) {
+		if(name.len > list->s.len)
+			name.len = list->s.len;
+		res = cmpi_str(&list->s, &name);
+		if(res == 0) {
+			return 4;
+		}
+		if(user.len > list->s.len)
+			user.len = list->s.len;
+		res = cmpi_str(&list->s, &user);
+		if(res == 0) {
+			return 2;
+		}
+		list = list->next;
+		name.len = nlen;
+		user.len = ulen;
+	}
+	/* User blacklisted */
+	list = secf_data->bl.user;
+	while(list) {
+		if(name.len > list->s.len)
+			name.len = list->s.len;
+		res = cmpi_str(&list->s, &name);
+		if(res == 0) {
+			return -4;
+		}
+		if(user.len > list->s.len)
+			user.len = list->s.len;
+		res = cmpi_str(&list->s, &user);
+		if(res == 0) {
+			return -2;
+		}
+		list = list->next;
+		name.len = nlen;
+		user.len = ulen;
+	}
+
+	/* Domain whitelisted */
+	list = secf_data->wl.domain;
+	while(list) {
+		if(domain.len > list->s.len)
+			domain.len = list->s.len;
+		res = cmpi_str(&list->s, &domain);
+		if(res == 0) {
+			return 3;
+		}
+		list = list->next;
+		domain.len = dlen;
+	}
+	/* Domain blacklisted */
+	list = secf_data->bl.domain;
+	while(list) {
+		if(domain.len > list->s.len)
+			domain.len = list->s.len;
+		res = cmpi_str(&list->s, &domain);
+		if(res == 0) {
+			return -3;
+		}
+		list = list->next;
+		domain.len = dlen;
+	}
+
+	return 1;
+}
+
+
+/* Check if the current IP is allowed
+
+Return codes:
+ 2 = IP address whitelisted
+ 1 = not found
+-1 = error
+-2 = IP address blacklisted
+*/
+static int w_check_ip(struct sip_msg *msg)
+{
+	int res, len;
+	str ip;
+	struct str_list *list;
+
+	if(msg == NULL)
+		return -1;
+	if(&msg->rcv.src_ip == NULL)
+		return -1;
+
+	ip.s = ip_addr2a(&msg->rcv.src_ip);
+	ip.len = strlen(ip.s);
+
+	len = ip.len;
+
+	/* IP address whitelisted */
+	list = secf_data->wl.ip;
+	while(list) {
+		if(ip.len > list->s.len)
+			ip.len = list->s.len;
+		res = cmpi_str(&list->s, &ip);
+		if(res == 0) {
+			return 2;
+		}
+		list = list->next;
+		ip.len = len;
+	}
+	/* IP address blacklisted */
+	list = secf_data->bl.ip;
+	while(list) {
+		if(ip.len > list->s.len)
+			ip.len = list->s.len;
+		res = cmpi_str(&list->s, &ip);
+		if(res == 0) {
+			return -2;
+		}
+		list = list->next;
+		ip.len = len;
+	}
+
+	return 1;
+}
+
+
+/* Check if the current country is allowed
+
+Return codes:
+ 2 = Country whitelisted
+ 1 = not found
+-2 = Country blacklisted
+*/
+static int w_check_country(struct sip_msg *msg, char *val)
+{
+	int res, len;
+	str country;
+	struct str_list *list;
+
+	country.s = val;
+	country.len = strlen(val);
+
+	len = country.len;
+
+	/* Country whitelisted */
+	list = secf_data->wl.country;
+	while(list) {
+		if(country.len > list->s.len)
+			country.len = list->s.len;
+		res = cmpi_str(&list->s, &country);
+		if(res == 0) {
+			return 2;
+		}
+		list = list->next;
+		country.len = len;
+	}
+	/* Country blacklisted */
+	list = secf_data->bl.country;
+	while(list) {
+		if(country.len > list->s.len)
+			country.len = list->s.len;
+		res = cmpi_str(&list->s, &country);
+		if(res == 0) {
+			return -2;
+		}
+		list = list->next;
+		country.len = len;
+	}
+
+	return 1;
+}
+
+
+/***
+INIT AND DESTROY FUNCTIONS
+***/
+
+/* Initialize data */
+int init_data(void)
+{
+	secf_data = (secf_data_p)shm_malloc(sizeof(secf_data_t));
+	if(!secf_data) {
+		SHM_MEM_ERROR;
+		return -1;
+	}
+	memset(secf_data, 0, sizeof(secf_data_t));
+
+	if(secf_dst_exact_match != 0)
+		secf_dst_exact_match = 1;
+
+	return 0;
+}
+
+
+/* Module init function */
+static int mod_init(void)
+{
+	LM_DBG("SECFILTER module init\n");
+	/* Init data to store database values */
+	if(init_data() == -1)
+		return -1;
+	/* Init RPC */
+	if(rpc_init() < 0)
+		return -1;
+	/* Init locks */
+	if(lock_init(&secf_data->lock) == 0) {
+		LM_CRIT("cannot initialize lock.\n");
+		return -1;
+	}
+	/* Init database connection and check version */
+	if(init_db() == -1)
+		return -1;
+	/* Load data from database */
+	if(load_db() == -1) {
+		LM_ERR("Error loading data from database\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/* Module child init function */
+static int child_init(int rank)
+{
+	if(rank == PROC_INIT || rank == PROC_MAIN || rank == PROC_TCP_MAIN)
+		return 0; /* do nothing for the main process */
+
+	return 0;
+}
+
+
+/* Module destroy function */
+static void mod_destroy(void)
+{
+	LM_DBG("SECFILTER module destroy\n");
+	/* Free shared data */
+	free_data();
+	/* Destroy lock */
+	lock_destroy(&secf_data->lock);
+}
+
+
+/* RPC init */
+static int rpc_init(void)
+{
+	/* Register RPC commands */
+	if(rpc_register_array(secfilter_rpc) != 0) {
+		LM_ERR("failed to register RPC commands\n");
+		return -1;
+	}
+	return 0;
+}
+
+/* Free shared data */
+static void free_str_list(struct str_list *l)
+{
+	struct str_list *i;
+	while(l) {
+		i = l->next;
+		LM_DBG("free '%.*s'[%p] next:'%p'\n", l->s.len, l->s.s, l, i);
+		shm_free(l->s.s);
+		shm_free(l);
+		l = i;
+	}
+}
+
+
+static void free_sec_info(secf_info_p info)
+{
+	LM_DBG("freeing ua[%p]\n", info->ua);
+	free_str_list(info->ua);
+	LM_DBG("freeing country[%p]\n", info->country);
+	free_str_list(info->country);
+	LM_DBG("freeing domain[%p]\n", info->domain);
+	free_str_list(info->domain);
+	LM_DBG("freeing user[%p]\n", info->user);
+	free_str_list(info->user);
+	LM_DBG("freeing ip[%p]\n", info->ip);
+	free_str_list(info->ip);
+	LM_DBG("freeing dst[%p]\n", info->dst);
+	free_str_list(info->dst);
+	LM_DBG("zeroed info[%p]\n", info);
+	memset(info, 0, sizeof(secf_info_t));
+}
+
+
+void free_data(void)
+{
+	lock_get(&secf_data->lock);
+
+	LM_DBG("freeing wl\n");
+	free_sec_info(&secf_data->wl);
+	memset(&secf_data->wl_last, 0, sizeof(secf_info_t));
+	LM_DBG("so, ua[%p] should be NULL\n", secf_data->wl.ua);
+
+	LM_DBG("freeing bl\n");
+	free_sec_info(&secf_data->bl);
+	memset(&secf_data->bl_last, 0, sizeof(secf_info_t));
+	LM_DBG("so, ua[%p] should be NULL\n", secf_data->bl.ua);
+
+	lock_release(&secf_data->lock);
+}

+ 58 - 0
src/modules/secfilter/secfilter.h

@@ -0,0 +1,58 @@
+#ifndef _SECFILTER_H
+#define _SECFILTER_H
+
+#include "../../core/str_list.h"
+#include "../../core/sr_module.h"
+
+
+typedef struct _secf_info
+{
+	struct str_list *ua;
+	struct str_list *country;
+	struct str_list *domain;
+	struct str_list *user;
+	struct str_list *ip;
+	struct str_list *dst;
+} secf_info_t, *secf_info_p;
+
+typedef struct _secf_data
+{
+	gen_lock_t lock;
+	secf_info_t wl; /* whitelist info */
+	secf_info_t wl_last;
+	secf_info_t bl; /* blacklist info */
+	secf_info_t bl_last;
+} secf_data_t, *secf_data_p;
+
+extern secf_data_p secf_data;
+
+int append_rule(int action, int type, str *value);
+
+/* Get header values from message */
+int secf_get_ua(struct sip_msg *msg, str *ua);
+int secf_get_from(struct sip_msg *msg, str *name, str *user, str *domain);
+int secf_get_to(struct sip_msg *msg, str *name, str *user, str *domain);
+int secf_get_contact(struct sip_msg *msg, str *user, str *domain);
+
+/* Database functions */
+int init_db(void);
+int init_data(void);
+void free_data(void);
+int load_db(void);
+
+/* Extern variables */
+extern str secf_db_url;
+extern str secf_table_name;
+extern str secf_action_col;
+extern str secf_type_col;
+extern str secf_data_col;
+extern int secf_dst_exact_match;
+
+/* RPC commands */
+void rpc_reload(rpc_t *rpc, void *ctx);
+void rpc_print(rpc_t *rpc, void *ctx);
+void rpc_add_dst(rpc_t *rpc, void *ctx);
+void rpc_add_bl(rpc_t *rpc, void *ctx);
+void rpc_add_wl(rpc_t *rpc, void *ctx);
+
+#endif

+ 322 - 0
src/modules/secfilter/secfilter_db.c

@@ -0,0 +1,322 @@
+/**
+ * Copyright (C) 2018 Jose Luis Verdeguer
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "../../core/mem/shm_mem.h"
+#include "../../lib/srdb1/db.h"
+#include "secfilter.h"
+
+
+int mod_version = 1;
+
+/* Database variables */
+static db_func_t db_funcs;		 /* Database API functions */
+static db1_con_t *db_handle = 0; /* Database connection handle */
+
+/* Version table */
+static str version_table_name = str_init("version");
+static str table_name_col = str_init("table_name");
+static str table_version_col = str_init("table_version");
+
+
+/* Check module version */
+int check_version(void)
+{
+	int version = 0;
+	db_key_t db_keys[1];
+	db_val_t db_vals[1];
+	db_key_t db_cols[1];
+	db1_res_t *db_res = NULL;
+
+	/* Connect to DB */
+	db_handle = db_funcs.init(&secf_db_url);
+	if(db_handle == NULL) {
+		LM_ERR("Invalid db handle\n");
+		return -1;
+	}
+
+	/* Prepare the data for the query */
+	db_cols[0] = &table_version_col;
+	db_keys[0] = &table_name_col;
+
+	db_vals[0].type = DB1_STRING;
+	db_vals[0].nul = 0;
+	db_vals[0].val.string_val = "secfilter";
+
+	/* Execute query */
+	if(db_funcs.use_table(db_handle, &version_table_name) < 0) {
+		LM_ERR("Unable to use table '%.*s'\n", version_table_name.len,
+				version_table_name.s);
+		return -1;
+	}
+	if(db_funcs.query(
+			   db_handle, db_keys, NULL, db_vals, db_cols, 1, 1, NULL, &db_res)
+			< 0) {
+		LM_ERR("Failed to query database\n");
+		db_funcs.close(db_handle);
+		return -1;
+	}
+
+	if(RES_ROW_N(db_res) == 0) {
+		LM_ERR("No version value found in database. It must be %d\n",
+				mod_version);
+		if(db_res != NULL && db_funcs.free_result(db_handle, db_res) < 0)
+			LM_DBG("Failed to free the result\n");
+		db_funcs.close(db_handle);
+		return -1;
+	}
+
+	/* Get the version value */
+	version = RES_ROWS(db_res)[0].values[0].val.int_val;
+
+	if(version != mod_version) {
+		LM_ERR("Wrong version value. Correct version is %d but found %d\n",
+				mod_version, version);
+		if(db_res != NULL && db_funcs.free_result(db_handle, db_res) < 0)
+			LM_DBG("Failed to free the result\n");
+		db_funcs.close(db_handle);
+		return -1;
+	}
+
+	if(db_res != NULL && db_funcs.free_result(db_handle, db_res) < 0)
+		LM_DBG("Failed to free the result\n");
+	db_funcs.close(db_handle);
+
+	return 0;
+}
+
+
+/**
+ * @brief Add a new allocated list element to an existing list
+ *
+ * Add a new allocated list element to an existing list, the allocation is done
+ * from the private memory pool
+ * @param s input character
+ * @param len length of input character
+ * @param last existing list
+ * @param total length of total characters in list
+ * @return extended list
+**/
+struct str_list *shm_append_str_list(
+		char *s, int len, struct str_list **last, int *total)
+{
+	struct str_list *new;
+
+	new = shm_malloc(sizeof(struct str_list));
+	if(!new) {
+		SHM_MEM_ERROR;
+		return 0;
+	}
+	new->s.s = s;
+	new->s.len = len;
+	new->next = 0;
+
+	if(*last) {
+		(*last)->next = new;
+		*last = new;
+	}
+	*total += len;
+
+	return new;
+}
+
+
+/**
+	Action => 0 = blacklist
+	          1 = whitelist
+	          2 = destination blacklist
+	Type (for action = 0 or 1) => 0 = user-agent
+				      1 = country
+				      2 = domain
+				      3 = IP address
+				      4 = user
+**/
+int append_rule(int action, int type, str *value)
+{
+	secf_info_p ini = NULL;
+	secf_info_p last = NULL;
+	struct str_list **ini_node = NULL;
+	struct str_list **last_node = NULL;
+	struct str_list *new = NULL;
+	int total = 0;
+	char *v = NULL;
+
+	if(action < 0 || action > 2) {
+		LM_ERR("Unkown action value %d", action);
+		return -1;
+	}
+
+	if(action == 1) {
+		ini = &secf_data->wl;
+		last = &secf_data->wl_last;
+	} else {
+		ini = &secf_data->bl;
+		last = &secf_data->bl_last;
+	}
+
+	switch(type) {
+		case 0:
+			if(action == 2) {
+				ini_node = &ini->dst;
+				last_node = &last->dst;
+			} else {
+				ini_node = &ini->ua;
+				last_node = &last->ua;
+			}
+			break;
+		case 1:
+			ini_node = &ini->country;
+			last_node = &last->country;
+			break;
+		case 2:
+			ini_node = &ini->domain;
+			last_node = &last->domain;
+			break;
+		case 3:
+			ini_node = &ini->ip;
+			last_node = &last->ip;
+			break;
+		case 4:
+			ini_node = &ini->user;
+			last_node = &last->user;
+			break;
+		default:
+			LM_ERR("Unkown type value %d", type);
+			return -1;
+	}
+
+	v = (char *)shm_malloc(sizeof(char) * value->len);
+	if(!v) {
+		SHM_MEM_ERROR;
+		return -1;
+	}
+	memcpy(v, value->s, value->len);
+	LM_DBG("ini_node:%p last_node:%p\n", *ini_node, *last_node);
+
+	new = shm_append_str_list(v, value->len, last_node, &total);
+	if(!new) {
+		LM_ERR("can't append new node\n");
+		return -1;
+	}
+	LM_DBG("new node[%p] str:'%.*s'[%d]\n", new, new->s.len, new->s.s, new->s.len);
+
+	*last_node = new;
+	if(!*ini_node) {
+		LM_DBG("ini_node[%p] was NULL, this is the first node\n", ini_node);
+		*ini_node = new;
+	}
+	LM_DBG("ini_node:%p last_node:%p\n", *ini_node, *last_node);
+
+	return 0;
+}
+
+
+/* Load data from database */
+int load_db(void)
+{
+	db_key_t db_cols[3];
+	db1_res_t *db_res = NULL;
+	str str_data = STR_NULL;
+	int i, action, type;
+	int rows = 0;
+	int res = 0;
+
+	/* Connect to DB */
+	db_handle = db_funcs.init(&secf_db_url);
+	if(db_handle == NULL) {
+		LM_ERR("Invalid db handle\n");
+		return -1;
+	}
+
+	/* Prepare the data for the query */
+	db_cols[0] = &secf_action_col;
+	db_cols[1] = &secf_type_col;
+	db_cols[2] = &secf_data_col;
+
+	/* Execute query */
+	if(db_funcs.use_table(db_handle, &secf_table_name) < 0) {
+		LM_ERR("Unable to use table '%.*s'\n", secf_table_name.len,
+				secf_table_name.s);
+		return -1;
+	}
+	if(db_funcs.query(db_handle, NULL, NULL, NULL, db_cols, 0, 3, NULL, &db_res)
+			< 0) {
+		LM_ERR("Failed to query database\n");
+		db_funcs.close(db_handle);
+		return -1;
+	}
+
+	rows = RES_ROW_N(db_res);
+	if(rows == 0) {
+		LM_DBG("No data found in database\n");
+		res = 0;
+		goto clean;
+	}
+
+	lock_get(&secf_data->lock);
+	for(i = 0; i < rows; i++) {
+		action = (int)RES_ROWS(db_res)[i].values[0].val.int_val;
+		type = (int)RES_ROWS(db_res)[i].values[1].val.int_val;
+		str_data.s = (char *)RES_ROWS(db_res)[i].values[2].val.string_val;
+		str_data.len = strlen(str_data.s);
+		LM_DBG("[%d] append_rule for action:%d type:%d data:%.*s\n",i, action, type, str_data.len, str_data.s);
+
+		if(append_rule(action, type, &str_data) < 0) {
+			LM_ERR("Can't append_rule with action:%d type:%d\n", action, type);
+			res = -1;
+			lock_release(&secf_data->lock);
+			goto clean;
+		}
+	}
+	lock_release(&secf_data->lock);
+
+clean:
+	if(db_res) {
+		if(db_funcs.free_result(db_handle, db_res) < 0) {
+			LM_DBG("Failed to free the result\n");
+		}
+	}
+	db_funcs.close(db_handle);
+	return res;
+}
+
+
+/* Init database connection */
+int init_db(void)
+{
+	if(secf_db_url.s == NULL) {
+		LM_ERR("Database not configured\n");
+		return -1;
+	}
+
+	secf_db_url.len = strlen(secf_db_url.s);
+
+	if(db_bind_mod(&secf_db_url, &db_funcs) < 0) {
+		LM_ERR("Unable to bind to db driver - %.*s\n", secf_db_url.len,
+				secf_db_url.s);
+		return -1;
+	}
+
+	if(check_version() == -1)
+		return -1;
+
+	return 0;
+}

+ 180 - 0
src/modules/secfilter/secfilter_hdr.c

@@ -0,0 +1,180 @@
+/**
+ * Copyright (C) 2018 Jose Luis Verdeguer
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <string.h>
+
+#include "../../core/dprint.h"
+#include "../../core/mem/mem.h"
+#include "../../core/parser/parse_uri.h"
+#include "../../core/parser/parse_param.h"
+#include "../../core/parser/parse_from.h"
+#include "../../core/parser/parse_to.h"
+#include "../../core/parser/contact/parse_contact.h"
+#include "../../core/sr_module.h"
+#include "secfilter.h"
+
+/* get 'user-agent' header */
+int secf_get_ua(struct sip_msg *msg, str *ua)
+{
+	ua->len = 0;
+
+	if(msg == NULL)
+		return -2;
+	if(parse_headers(msg, HDR_USERAGENT_F, 0) != 0)
+		return 1;
+	if(msg->user_agent == NULL || msg->user_agent->body.s == NULL)
+		return 1;
+
+	ua->s = msg->user_agent->body.s;
+	ua->len = msg->user_agent->body.len;
+
+	return 0;
+}
+
+
+/* get 'from' header */
+int secf_get_from(struct sip_msg *msg, str *name, str *user, str *domain)
+{
+	struct to_body *hdr;
+	struct sip_uri parsed_uri;
+
+	name->len = 0;
+	user->len = 0;
+	domain->len = 0;
+
+	if(msg == NULL)
+		return -1;
+	if(parse_from_header(msg) < 0)
+		return -1;
+	if(msg->from == NULL || msg->from->body.s == NULL)
+		return 1;
+
+	hdr = get_from(msg);
+	if(hdr->display.s != NULL) {
+		name->s = hdr->display.s;
+		name->len = hdr->display.len;
+
+		if(name->len > 1 && name->s[0] == '"'
+				&& name->s[name->len - 1] == '"') {
+			name->s = name->s + 1;
+			name->len = name->len - 2;
+		}
+	}
+
+	if(parse_uri(hdr->uri.s, hdr->uri.len, &parsed_uri) < 0)
+		return -1;
+
+	if(parsed_uri.user.s != NULL) {
+		user->s = parsed_uri.user.s;
+		user->len = parsed_uri.user.len;
+	}
+
+	if(parsed_uri.host.s != NULL) {
+		domain->s = parsed_uri.host.s;
+		domain->len = parsed_uri.host.len;
+	}
+
+	return 0;
+}
+
+
+/* get 'to' header */
+int secf_get_to(struct sip_msg *msg, str *name, str *user, str *domain)
+{
+	struct to_body *hdr;
+	struct sip_uri parsed_uri;
+
+	if(msg == NULL)
+		return -1;
+	if(parse_to_header(msg) < 0)
+		return -1;
+	if(msg->to == NULL || msg->to->body.s == NULL)
+		return 1;
+
+	hdr = get_to(msg);
+	if(hdr->display.s != NULL) {
+		name->s = hdr->display.s;
+		name->len = hdr->display.len;
+
+		if(name->len > 1 && name->s[0] == '"'
+				&& name->s[name->len - 1] == '"') {
+			name->s = name->s + 1;
+			name->len = name->len - 2;
+		}
+	}
+
+	if(parse_uri(hdr->uri.s, hdr->uri.len, &parsed_uri) < 0)
+		return -1;
+
+	if(parsed_uri.user.s != NULL) {
+		user->s = parsed_uri.user.s;
+		user->len = parsed_uri.user.len;
+	}
+
+	if(parsed_uri.host.s != NULL) {
+		domain->s = parsed_uri.host.s;
+		domain->len = parsed_uri.host.len;
+	}
+
+	return 0;
+}
+
+
+/* get 'contact' header */
+int secf_get_contact(struct sip_msg *msg, str *user, str *domain)
+{
+	str contact = {NULL, 0};
+	struct sip_uri parsed_uri;
+
+	if(msg == NULL)
+		return -1;
+	if(msg->contact == NULL)
+		return 1;
+	if(!msg->contact->parsed && (parse_contact(msg->contact) < 0)) {
+		LM_ERR("Error parsing contact header (%.*s)\n", msg->contact->body.len,
+				msg->contact->body.s);
+		return -1;
+	}
+	if(((contact_body_t *)msg->contact->parsed)->contacts
+			&& ((contact_body_t *)msg->contact->parsed)->contacts->uri.s != NULL
+			&& ((contact_body_t *)msg->contact->parsed)->contacts->uri.len
+					   > 0) {
+		contact.s = ((contact_body_t *)msg->contact->parsed)->contacts->uri.s;
+		contact.len =
+				((contact_body_t *)msg->contact->parsed)->contacts->uri.len;
+	}
+	if(contact.s == NULL)
+		return 1;
+
+	if(parse_uri(contact.s, contact.len, &parsed_uri) < 0) {
+		LM_ERR("Error parsing contact uri header (%.*s)\n", contact.len,
+				contact.s);
+		return -1;
+	}
+
+	user->s = parsed_uri.user.s;
+	user->len = parsed_uri.user.len;
+
+	domain->s = parsed_uri.host.s;
+	domain->len = parsed_uri.host.len;
+
+	return 0;
+}

+ 260 - 0
src/modules/secfilter/secfilter_rpc.c

@@ -0,0 +1,260 @@
+/**
+ * Copyright (C) 2018 Jose Luis Verdeguer
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include <string.h>
+
+#include "../../core/mem/mem.h"
+#include "../../core/ut.h"
+#include "secfilter.h"
+
+
+/* RPC commands */
+
+
+static int get_type(char *ctype)
+{
+	int type;
+
+	if(!strcmp(ctype, "ua")) {
+		type = 0;
+	} else if(!strcmp(ctype, "country")) {
+		type = 1;
+	} else if(!strcmp(ctype, "domain")) {
+		type = 2;
+	} else if(!strcmp(ctype, "ip")) {
+		type = 3;
+	} else if(!strcmp(ctype, "user")) {
+		type = 4;
+	} else {
+		LM_ERR("Invalid type\n");
+		return -1;
+	}
+
+	return type;
+}
+
+
+/* Add blacklist destination value */
+void rpc_add_dst(rpc_t *rpc, void *ctx)
+{
+	int number;
+	str data = STR_NULL;
+	char *text = NULL;
+
+	if(rpc->scan(ctx, "d", &number) < 1) {
+		rpc->fault(ctx, 0, "Invalid Parameters. Usage: secfilter.add_dst "
+						   "number\n     Example: secfilter.add_dst "
+						   "555123123");
+	} else {
+		text = int2str(number, &data.len);
+		data.s = pkg_malloc(data.len*sizeof(char));
+		if(!data.s){
+			PKG_MEM_ERROR;
+			rpc->rpl_printf(ctx,
+					"Error insert values in the blacklist");
+			return;
+		}
+		memcpy(data.s, text, data.len);
+		lock_get(&secf_data->lock);
+		if (append_rule(2, 0, &data) == 0) {
+			rpc->rpl_printf(ctx,
+					"Values (%s) inserted into blacklist destinations",
+					data);
+		} else {
+			rpc->rpl_printf(ctx,
+					"Error insert values in the blacklist");
+		}
+		lock_release(&secf_data->lock);
+		if(data.s)
+			pkg_free(data.s);
+	}
+}
+
+/* Add blacklist value */
+void rpc_add_bl(rpc_t *rpc, void *ctx)
+{
+	char *ctype;
+	str data = STR_NULL;
+	int type;
+
+	if(rpc->scan(ctx, "ss", (char *)(&ctype), (char *)(&data.s)) < 2) {
+		rpc->fault(ctx, 0, "Invalid Parameters. Usage: secfilter.add_bl type "
+						   "value\n     Example: secfilter.add_bl user "
+						   "sipvicious");
+	} else {
+		data.len = strlen(data.s);
+		type = get_type(ctype);
+		
+		lock_get(&secf_data->lock);
+		if (append_rule(0, type, &data) == 0) {
+			rpc->rpl_printf(ctx,
+					"Values (%s, %s) inserted into blacklist", ctype,
+					data);
+		} else {
+			rpc->rpl_printf(ctx, "Error insert values in the blacklist");
+		}
+		lock_release(&secf_data->lock);
+	}
+}
+
+
+/* Add whitelist value */
+void rpc_add_wl(rpc_t *rpc, void *ctx)
+{
+	char *ctype;
+	str data = STR_NULL;
+	int type;
+
+	if(rpc->scan(ctx, "ss", (char *)(&ctype), (char *)(&data.s)) < 2) {
+		rpc->fault(ctx, 0, "Invalid Parameters. Usage: secfilter.add_wl type "
+						   "value\n     Example: secfilter.add_wl user "
+						   "trusted_user");
+	} else {
+		data.len = strlen(data.s);
+		type = get_type(ctype);
+		
+		lock_get(&secf_data->lock);
+		if (append_rule(1, type, &data) == 0) {
+			rpc->rpl_printf(ctx,
+					"Values (%s, %s) inserted into whitelist", type,
+					data);
+		} else {
+			rpc->rpl_printf(ctx, "Error insert values in the whitelist");
+		}
+		lock_release(&secf_data->lock);
+	}
+}
+
+
+/* Reload arrays */
+void rpc_reload(rpc_t *rpc, void *ctx)
+{
+	free_data();
+
+	if(load_db() == -1) {
+		LM_ERR("Error loading data from database\n");
+		rpc->rpl_printf(ctx, "Error loading data from database");
+	}
+	else {
+		rpc->rpl_printf(ctx, "Data reloaded");
+	}
+}
+
+
+/* Print str_list data */
+static void rpc_print_data(rpc_t *rpc, void *ctx, struct str_list *list)
+{
+	int i = 1;
+
+	while(list) {
+		rpc->rpl_printf(ctx, "    %04d -> %.*s", i, list->s.len, list->s.s);
+		list = list->next;
+		i++;
+	}
+}
+
+
+/* Print values */
+void rpc_print(rpc_t *rpc, void *ctx)
+{
+	char *param = NULL;
+	int showall = 0;
+
+	if(rpc->scan(ctx, "s", (char *)(&param)) < 1)
+		showall = 1;
+
+	if(!strcmp(param, "dst")) {
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "Destinations");
+		rpc->rpl_printf(ctx, "============");
+		rpc->rpl_printf(ctx, "[+] Blacklisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->bl.dst);
+	}
+
+	if(showall == 1 || !strcmp(param, "ua")) {
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "User-agent");
+		rpc->rpl_printf(ctx, "==========");
+		rpc->rpl_printf(ctx, "[+] Blacklisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->bl.ua);
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "[+] Whitelisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->wl.ua);
+	}
+
+	if(showall == 1 || !strcmp(param, "country")) {
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "Country");
+		rpc->rpl_printf(ctx, "=======");
+		rpc->rpl_printf(ctx, "[+] Blacklisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->bl.country);
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "[+] Whitelisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->wl.country);
+	}
+
+	if(showall == 1 || !strcmp(param, "domain")) {
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "Domain");
+		rpc->rpl_printf(ctx, "======");
+		rpc->rpl_printf(ctx, "[+] Blacklisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->bl.domain);
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "[+] Whitelisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->wl.domain);
+	}
+
+	if(showall == 1 || !strcmp(param, "ip")) {
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "IP Address");
+		rpc->rpl_printf(ctx, "==========");
+		rpc->rpl_printf(ctx, "[+] Blacklisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->bl.ip);
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "[+] Whitelisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->wl.ip);
+	}
+
+	if(showall == 1 || !strcmp(param, "user")) {
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "User");
+		rpc->rpl_printf(ctx, "====");
+		rpc->rpl_printf(ctx, "[+] Blacklisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->bl.user);
+		rpc->rpl_printf(ctx, "");
+		rpc->rpl_printf(ctx, "[+] Whitelisted");
+		rpc->rpl_printf(ctx, "    -----------");
+		rpc_print_data(rpc, ctx, secf_data->wl.user);
+	}
+
+	rpc->rpl_printf(ctx, "");
+}