diff --git a/db/schema/fraud_detection.xml b/db/schema/fraud_detection.xml
new file mode 100644
index 0000000000..f8e6a5df7f
--- /dev/null
+++ b/db/schema/fraud_detection.xml
@@ -0,0 +1,141 @@
+
+
+%entities;
+
+]>
+
+
+ fraud_detection
+ 1
+ &MYSQL_TABLE_TYPE;
+
+ This table is used by the Fraud Detection module to store
+ information about fraud-profiles.
+ More information can be found at: &OPENSIPS_MOD_DOC;fraud_detection.html.
+
+
+
+
+ ruleid
+ unsigned int
+ &table_id_len;
+
+
+
+ int,auto
+ Rule unique ID
+
+
+
+
+ profileid
+ unsigned int
+ The ID of the profile the current rule is part of
+
+
+
+
+ prefix
+ string
+ 64
+ Numerical prefix to match this rule
+
+
+
+ start_hour
+ string
+ 5
+ Start of the interval in which the rule should be matched.
+
+
+
+
+ end_hour
+ string
+ 5
+ End of the interval in which the rule should be matched.
+
+
+
+
+ daysoftheweek
+ string
+ 64
+ List/interval of days in which the rule is available.
+
+
+
+
+ cpm_warning
+ unsigned int
+ 5
+ Warning threshold for calls per minute.
+
+
+
+ cpm_critical
+ unsigned int
+ 5
+ Crtical threshold for calls per minute.
+
+
+
+ call_duration_warning
+ unsigned int
+ 5
+ Warning threshold for calls per minute.
+
+
+
+ call_duration_critical
+ unsigned int
+ 5
+ Crtical threshold for call duration.
+
+
+
+ total_calls_warning
+ unsigned int
+ 5
+ Warning threshold for total calls.
+
+
+
+ total_calls_critical
+ unsigned int
+ 5
+ Crtical threshold for total calls.
+
+
+
+ concurrent_calls_warning
+ unsigned int
+ 5
+ Warning threshold for concurrent calls.
+
+
+
+ concurrent_calls_critical
+ unsigned int
+ 5
+ Crtical threshold for concurrent calls.
+
+
+
+ sequential_calls_warning
+ unsigned int
+ 5
+ Warning threshold for sequential calls.
+
+
+
+ sequential_calls_critical
+ unsigned int
+ 5
+ Crtical threshold for sequential calls.
+
+
+
diff --git a/db/schema/opensips-fraud_detection.xml b/db/schema/opensips-fraud_detection.xml
new file mode 100644
index 0000000000..a8abef30b7
--- /dev/null
+++ b/db/schema/opensips-fraud_detection.xml
@@ -0,0 +1,13 @@
+
+
+%entities;
+
+]>
+
+
+ Fraud Detection
+
+
diff --git a/modules/fraud_detection/Makefile b/modules/fraud_detection/Makefile
new file mode 100644
index 0000000000..8f482eceea
--- /dev/null
+++ b/modules/fraud_detection/Makefile
@@ -0,0 +1,11 @@
+# New module name
+#
+#-
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=fraud_detection.so
+LIBS=
+
+include ../../Makefile.modules
diff --git a/modules/fraud_detection/README b/modules/fraud_detection/README
new file mode 100644
index 0000000000..e542849ec9
--- /dev/null
+++ b/modules/fraud_detection/README
@@ -0,0 +1,448 @@
+Fraud Detection Module
+
+Andrei Daniel Datcu
+
+
+
+ Copyright © 2014 OpenSIPs Foundation
+ Revision History
+ Revision $Revision: 1 $ $Date$
+ __________________________________________________________
+
+ Table of Contents
+
+ 1. Admin Guide
+
+ 1.1. Overview
+
+ 1.1.1. Monitorized Stats
+ 1.1.2. Fraud rules
+
+ 1.2. Dependencies
+
+ 1.2.1. OpenSIPS modules
+ 1.2.2. External libraries or applications
+
+ 1.3. Exported Parameters
+
+ 1.3.1. db_url (string)
+ 1.3.2. table_name (string)
+ 1.3.3. rid_col (string)
+ 1.3.4. pid_col (string)
+ 1.3.5. prefix_col (string)
+ 1.3.6. start_h (string)
+ 1.3.7. end_h (string)
+ 1.3.8. days_col (string)
+ 1.3.9. cpm_thresh_warn_col (string)
+ 1.3.10. cpm_thresh_crit_col (string)
+ 1.3.11. calldur_thresh_warn_col (string)
+ 1.3.12. calldur_thresh_crit_col (string)
+ 1.3.13. totalc_thresh_warn_col (string)
+ 1.3.14. totalc_thresh_crit_col (string)
+ 1.3.15. concalls_thresh_warn_col (string)
+ 1.3.16. concalls_thresh_crit_col (string)
+ 1.3.17. seqcalls_thresh_warn_col (string)
+ 1.3.18. seqcalls_thresh_crit_col (string)
+
+ 1.4. Exported Functions
+
+ 1.4.1. check_fraud(user, number, profile_id)
+
+ 1.5. Exported MI Functions
+
+ 1.5.1. show_fraud_stats
+ 1.5.2. fraud_reload
+
+ 1.6. Exported Events
+
+ 1.6.1. E_FRD_WARNING
+ 1.6.2. E_FRD_CRITICAL
+
+ List of Examples
+
+ 1.1. Set the “db_url” parameter
+ 1.2. Set the “table_name” parameter
+ 1.3. Set “rid_col” parameter
+ 1.4. Set “pid_col” parameter
+ 1.5. Set “prefix_col” parameter
+ 1.6. Set “start_h” parameter
+ 1.7. Set “end_h” parameter
+ 1.8. Set “days_col” parameter
+ 1.9. Set “cpm_thresh_warn_col” parameter
+ 1.10. Set “cpm_thresh_crit_col” parameter
+ 1.11. Set “calldur_thresh_warn_col” parameter
+ 1.12. Set “calldur_thresh_crit_col” parameter
+ 1.13. Set “totalc_thresh_warn_col” parameter
+ 1.14. Set “totalc_thresh_crit_col” parameter
+ 1.15. Set “concalls_thresh_warn_col” parameter
+ 1.16. Set “concalls_thresh_crit_col” parameter
+ 1.17. Set “seqcalls_thresh_warn_col” parameter
+ 1.18. Set “seqcalls_thresh_crit_col” parameter
+
+Chapter 1. Admin Guide
+
+1.1. Overview
+
+ This module provides a way to prevent some basic fraud attacks.
+ Alerts are provided through return codes and events.
+
+1.1.1. Monitorized Stats
+
+ Basically, this module watches the following parameters:
+ * Total calls
+ * Calls per minute
+ * Concurrent calls
+ * Number of sequential calls
+ * Call duration
+
+ Each of the above parameters is monitored for every user and
+ every called prefix separately. The stats are altered whenever
+ the check_fraud function is called. The function assumes a new
+ call is made, and checks the called number against all the
+ rules from the supplied profile. The rule's prefix is
+ considered to be the called prefix which along with the
+ provided user will be used to monitor values for the 5
+ parameters.
+
+1.1.2. Fraud rules
+
+ A rule is a set of two thresholds (warning and critical
+ thresholds) for each of the five parameters (as described
+ above) and is only available for a specified prefix. Further
+ more, a rule will only match between the indicated hours in the
+ indicated days of the week (similarly to a dr rule). A fraud
+ profile is simply a group of fraud rules and is used to only to
+ limit the list of rules to match when calling the check_fraud
+ function.
+
+1.2. Dependencies
+
+1.2.1. OpenSIPS modules
+
+ The following modules must be loaded before this module:
+ * drouting
+ * dialog
+
+1.2.2. External libraries or applications
+
+ The following libraries or applications must be installed
+ before running OpenSIPS with this module:
+ * none.
+
+1.3. Exported Parameters
+
+1.3.1. db_url (string)
+
+ Database where to load the rules from.
+
+ Default value is “NULL”. At least one db_url should be defined
+ for the fraud_detection module to work.
+
+ Example 1.1. Set the “db_url” parameter
+...
+modparam("fraud_detection", "db_url", "mysql://user:passwb@localhost/dat
+abase")
+...
+
+1.3.2. table_name (string)
+
+ If you want to load the rules from the database you must set
+ this parameter as the database name.
+
+ The default value is “fraud_detection”.
+
+ Example 1.2. Set the “table_name” parameter
+...
+modparam("fraud_detection", "table_name", "my_fraud")
+...
+
+1.3.3. rid_col (string)
+
+ The column's name in the database storing the fraud rule's id.
+
+ Default value is “ruleid”.
+
+ Example 1.3. Set “rid_col” parameter
+...
+modparam("dispatcher", "rid_col", "theruleid"")
+...
+
+1.3.4. pid_col (string)
+
+ The column's name in the database storing the fraud profile's
+ id.
+
+ Please keep in mind that a profile is merely a set of rules.
+
+ Default value is “profileid”.
+
+ Example 1.4. Set “pid_col” parameter
+...
+modparam("dispatcher", "pid_col", "profile"")
+...
+
+1.3.5. prefix_col (string)
+
+ The column's name in the database storing the prefix for which
+ the fraud rule will match.
+
+ Default value is “prefix”.
+
+ Example 1.5. Set “prefix_col” parameter
+...
+modparam("dispatcher", "prefix_col", "myprefix")
+...
+
+1.3.6. start_h (string)
+
+ The column's name in the database storing the the start time of
+ the interval in which the rule will match.
+
+ The time needs to be specified as string using the format:
+ “HH:MM”
+
+ Default value is “start_hour”.
+
+ Example 1.6. Set “start_h” parameter
+...
+modparam("dispatcher", "start_h", "the_start_time")
+...
+
+1.3.7. end_h (string)
+
+ The column's name in the database storing the the end time of
+ the interval in which the rule will match.
+
+ The time needs to be specified as string using the format:
+ “HH:MM”
+
+ Default value is “end_hour”.
+
+ Example 1.7. Set “end_h” parameter
+...
+modparam("dispatcher", "end_h", "the_end_time")
+...
+
+1.3.8. days_col (string)
+
+ The column's name in the database storing the week days in
+ which the fraud rule's interval is available.
+
+ The daysoftheweek needs to be specified as a string containing
+ a list of days or intervals. Each day must be specified using
+ the first three letters of its name. A valid string would be:
+ "Fri-Mon, Wed, Thu"
+
+ Default value is “daysoftheweek”.
+
+ Example 1.8. Set “days_col” parameter
+...
+modparam("dispatcher", "days_col", "days")
+...
+
+1.3.9. cpm_thresh_warn_col (string)
+
+ The column's name in the database storing the warning threshold
+ value for calls per minute.
+
+ Default value is “cpm_warning”.
+
+ Example 1.9. Set “cpm_thresh_warn_col” parameter
+...
+modparam("dispatcher", "cpm_thresh_warn_col", "cpm_warn_thresh")
+...
+
+1.3.10. cpm_thresh_crit_col (string)
+
+ The column's name in the database storing the critical
+ threshold value for calls per minute.
+
+ Default value is “cpm_critical”.
+
+ Example 1.10. Set “cpm_thresh_crit_col” parameter
+...
+modparam("dispatcher", "cpm_thresh_crit_col", "cpm_crit_thresh")
+...
+
+1.3.11. calldur_thresh_warn_col (string)
+
+ The column's name in the database storing the warning threshold
+ value for call duration.
+
+ Default value is “call_duration_warning”.
+
+ Example 1.11. Set “calldur_thresh_warn_col” parameter
+...
+modparam("dispatcher", "calldur_thresh_warn_col", "calldur_warn_thresh")
+...
+
+1.3.12. calldur_thresh_crit_col (string)
+
+ The column's name in the database storing the critical
+ threshold value for call duration.
+
+ Default value is “call_duration_critical”.
+
+ Example 1.12. Set “calldur_thresh_crit_col” parameter
+...
+modparam("dispatcher", "calldur_thresh_crit_col", "calldur_crit_thresh")
+...
+
+1.3.13. totalc_thresh_warn_col (string)
+
+ The column's name in the database storing the warning threshold
+ value for the number of total calls.
+
+ Default value is “total_calls_warning”.
+
+ Example 1.13. Set “totalc_thresh_warn_col” parameter
+...
+modparam("dispatcher", "totalc_thresh_warn_col", "totalc_warn_thresh")
+...
+
+1.3.14. totalc_thresh_crit_col (string)
+
+ The column's name in the database storing the critical
+ threshold value for the number of total calls.
+
+ Default value is “total_calls_critical”.
+
+ Example 1.14. Set “totalc_thresh_crit_col” parameter
+...
+modparam("dispatcher", "totalc_thresh_crit_col", "totalc_crit_thresh")
+...
+
+1.3.15. concalls_thresh_warn_col (string)
+
+ The column's name in the database storing the warning threshold
+ value for the number of concurrent calls.
+
+ Default value is “concurrent_calls_warning”.
+
+ Example 1.15. Set “concalls_thresh_warn_col” parameter
+...
+modparam("dispatcher", "concalls_thresh_warn_col", "concalls_warn_thresh
+")
+...
+
+1.3.16. concalls_thresh_crit_col (string)
+
+ The column's name in the database storing the critical
+ threshold value for the number of concurrent calls.
+
+ Default value is “concurrent_calls_critical”.
+
+ Example 1.16. Set “concalls_thresh_crit_col” parameter
+...
+modparam("dispatcher", "concalls_thresh_crit_col", "concalls_crit_thresh
+")
+...
+
+1.3.17. seqcalls_thresh_warn_col (string)
+
+ The column's name in the database storing the warning threshold
+ value for the number of sequential calls.
+
+ Default value is “sequential_calls_warning”.
+
+ Example 1.17. Set “seqcalls_thresh_warn_col” parameter
+...
+modparam("dispatcher", "seqcalls_thresh_warn_col", "seqcalls_warn_thresh
+")
+...
+
+1.3.18. seqcalls_thresh_crit_col (string)
+
+ The column's name in the database storing the critical
+ threshold value for the number of sequential calls.
+
+ Default value is “sequential_calls_critical”.
+
+ Example 1.18. Set “seqcalls_thresh_crit_col” parameter
+...
+modparam("dispatcher", "seqcalls_thresh_crit_col", "seqcalls_crit_thresh
+")
+...
+
+1.4. Exported Functions
+
+1.4.1. check_fraud(user, number, profile_id)
+
+ This method should be called each time a given user calls a
+ given number. It will try to match a fraud rule within de given
+ fraud profile and update the stats (see above). Furthermore,
+ the stats will be checked against the rule's thresholds. If any
+ of the stats is above it's threhsold value the appropriate
+ event will also be raised (see further details below).
+
+ Meaning of the parameters is as follows:
+ * user - the user who is making the call. Please keep in mind
+ that the user doesn't have to be registered. This string is
+ only used do keep different stats for different registered
+ users.
+ * number - the number the user is calling to.
+ * profile_id - the fraud profile id (i.e. the subset of fraud
+ rules) in which to try and find a matching fraud rule.
+
+ The meaning of the return code is as follows:
+ * 2 - no matching fraud rule was found
+ * 1 - a matching rule was found, but there is no parameter
+ above the rule's threshlod, i.e - everything is ok
+ * -1 - there is a parameter above the warning threhsold
+ value. Check the raised event for more info
+ * -2 - there is a parameter above the critical threhsold
+ value. Check the raised event for more info
+ * -3 - something went wrong (internal mechanism failed)
+
+ This function can be used from REQUEST_ROUTE and ONREPLY_ROUTE.
+
+1.5. Exported MI Functions
+
+1.5.1. show_fraud_stats
+
+ Show the current stats of a given user for a given prefix.
+
+ Name: show_fraud_stats
+
+ Parameters:
+ * user
+ * number
+ * prefix
+
+1.5.2. fraud_reload
+
+ Reload the all the fraud rules.
+
+ Name: fraud_reload
+
+ Parameters: none
+
+1.6. Exported Events
+
+1.6.1. E_FRD_WARNING
+
+ This event is raised whenever one of the 5 monitored parameters
+ is above the warning threhsold value
+
+ Parameters:
+ * param - the name of the parameter.
+ * value - the current value of the parameter.
+ * threshold - the warning threshold value.
+ * user - the user who initiated the call.
+ * called_number - the number that was called.
+ * rule_id - the id of the fraud rule that matched when the
+ call was initiated
+
+1.6.2. E_FRD_CRITICAL
+
+ This event is raised whenever one of the 5 monitored parameters
+ is above the warning threhsold value
+
+ Parameters:
+ * param - the name of the parameter.
+ * value - the current value of the parameter.
+ * threshold - the warning threshold value.
+ * user - the user who initiated the call.
+ * called_number - the number that was called.
+ * rule_id - the id of the fraud rule that matched when the
+ call was initiated
diff --git a/modules/fraud_detection/doc/fraud_detection.xml b/modules/fraud_detection/doc/fraud_detection.xml
new file mode 100644
index 0000000000..16d6ecb03f
--- /dev/null
+++ b/modules/fraud_detection/doc/fraud_detection.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+%docentities;
+
+]>
+
+
+
+ Fraud Detection Module
+ &osipsname;
+
+
+ Andrei Daniel
+ Datcu
+ datcuandrei@gmail.com
+
+
+
+ 2014
+ OpenSIPs Foundation
+
+
+
+ $Revision: 1 $
+ $Date$
+
+
+
+
+
+ &admin;
+
diff --git a/modules/fraud_detection/doc/fraud_detection_admin.xml b/modules/fraud_detection/doc/fraud_detection_admin.xml
new file mode 100644
index 0000000000..f1167e629b
--- /dev/null
+++ b/modules/fraud_detection/doc/fraud_detection_admin.xml
@@ -0,0 +1,688 @@
+
+
+
+
+ &adminguide;
+
+
+ Overview
+
+ This module provides a way to prevent some basic fraud attacks.
+ Alerts are provided through return codes and events.
+
+
+ Monitorized Stats
+
+ Basically, this module watches the following parameters:
+
+
+
+ Total calls
+
+
+
+
+ Calls per minute
+
+
+
+
+ Concurrent calls
+
+
+
+
+ Number of sequential calls
+
+
+
+
+ Call duration
+
+
+
+
+
+ Each of the above parameters is monitored for every user and
+ every called prefix separately. The stats are altered whenever
+ the check_fraud function is called. The
+ function assumes a new call is made, and checks the called
+ number against all the rules from the supplied profile. The
+ rule's prefix is considered to be the called prefix which along with
+ the provided user will be used to monitor values for the 5
+ parameters.
+
+
+
+
+ Fraud rules
+
+ A rule is a set of two thresholds (warning and critical thresholds) for each of the
+ five parameters (as described above) and is only available for a specified prefix.
+ Further more, a rule will only match between the indicated hours in the indicated days
+ of the week (similarly to a dr rule). A fraud profile is simply a group of fraud rules
+ and is used to only to limit the list of rules to match when calling the check_fraud
+ function.
+
+
+
+
+ Dependencies
+
+ &osips; modules
+
+ The following modules must be loaded before this module:
+
+
+
+ drouting
+
+
+
+
+ dialog
+
+
+
+
+
+
+ External libraries or applications
+
+ The following libraries or applications must be installed before
+ running &osips; with this module:
+
+
+
+ none.
+
+
+
+
+
+
+
+
+ Exported Parameters
+
+ db_url (string)
+
+ Database where to load the rules from.
+
+
+
+ Default value is NULL
. At least one db_url should
+ be defined for the fraud_detection module to work.
+
+
+
+ Set the db_url
parameter
+
+...
+modparam("fraud_detection", "db_url", "mysql://user:passwb@localhost/database")
+...
+
+
+
+
+
+ table_name (string)
+
+ If you want to load the rules from the database you must set
+ this parameter as the database name.
+
+
+
+ The default value is fraud_detection
.
+
+
+
+ Set the table_name
parameter
+
+...
+modparam("fraud_detection", "table_name", "my_fraud")
+...
+
+
+
+
+
+ rid_col (string)
+
+ The column's name in the database storing the
+ fraud rule's id.
+
+
+
+ Default value is ruleid
.
+
+
+
+ Set rid_col
parameter
+
+...
+modparam("dispatcher", "rid_col", "theruleid"")
+...
+
+
+
+
+
+ pid_col (string)
+
+ The column's name in the database storing the
+ fraud profile's id.
+
+
+ Please keep in mind that a profile is merely
+ a set of rules.
+
+
+
+ Default value is profileid
.
+
+
+
+ Set pid_col
parameter
+
+...
+modparam("dispatcher", "pid_col", "profile"")
+...
+
+
+
+
+
+ prefix_col (string)
+
+ The column's name in the database storing the
+ prefix for which the fraud rule will match.
+
+
+
+ Default value is prefix
.
+
+
+
+ Set prefix_col
parameter
+
+...
+modparam("dispatcher", "prefix_col", "myprefix")
+...
+
+
+
+
+
+ start_h (string)
+
+ The column's name in the database storing the
+ the start time of the interval in which the
+ rule will match.
+
+
+ The time needs to be specified as string using
+ the format: HH:MM
+
+
+
+ Default value is start_hour
.
+
+
+
+ Set start_h
parameter
+
+...
+modparam("dispatcher", "start_h", "the_start_time")
+...
+
+
+
+
+
+ end_h (string)
+
+ The column's name in the database storing the
+ the end time of the interval in which the
+ rule will match.
+
+
+ The time needs to be specified as string using
+ the format: HH:MM
+
+
+
+ Default value is end_hour
.
+
+
+
+ Set end_h
parameter
+
+...
+modparam("dispatcher", "end_h", "the_end_time")
+...
+
+
+
+
+
+ days_col (string)
+
+ The column's name in the database storing the
+ week days in which the fraud rule's interval
+ is available.
+
+
+ The daysoftheweek needs to be specified as a
+ string containing a list of days or intervals.
+ Each day must be specified using the first
+ three letters of its name. A valid string
+ would be: "Fri-Mon, Wed, Thu"
+
+
+
+ Default value is daysoftheweek
.
+
+
+
+ Set days_col
parameter
+
+...
+modparam("dispatcher", "days_col", "days")
+...
+
+
+
+
+
+ cpm_thresh_warn_col (string)
+
+ The column's name in the database storing the
+ warning threshold value for calls per minute.
+
+
+
+ Default value is cpm_warning
.
+
+
+
+ Set cpm_thresh_warn_col
parameter
+
+...
+modparam("dispatcher", "cpm_thresh_warn_col", "cpm_warn_thresh")
+...
+
+
+
+
+
+ cpm_thresh_crit_col (string)
+
+ The column's name in the database storing the
+ critical threshold value for calls per minute.
+
+
+
+ Default value is cpm_critical
.
+
+
+
+ Set cpm_thresh_crit_col
parameter
+
+...
+modparam("dispatcher", "cpm_thresh_crit_col", "cpm_crit_thresh")
+...
+
+
+
+
+
+ calldur_thresh_warn_col (string)
+
+ The column's name in the database storing the
+ warning threshold value for call duration.
+
+
+
+ Default value is call_duration_warning
.
+
+
+
+ Set calldur_thresh_warn_col
parameter
+
+...
+modparam("dispatcher", "calldur_thresh_warn_col", "calldur_warn_thresh")
+...
+
+
+
+
+
+ calldur_thresh_crit_col (string)
+
+ The column's name in the database storing the
+ critical threshold value for call duration.
+
+
+
+ Default value is call_duration_critical
.
+
+
+
+ Set calldur_thresh_crit_col
parameter
+
+...
+modparam("dispatcher", "calldur_thresh_crit_col", "calldur_crit_thresh")
+...
+
+
+
+
+
+ totalc_thresh_warn_col (string)
+
+ The column's name in the database storing the
+ warning threshold value for the number of total calls.
+
+
+
+ Default value is total_calls_warning
.
+
+
+
+ Set totalc_thresh_warn_col
parameter
+
+...
+modparam("dispatcher", "totalc_thresh_warn_col", "totalc_warn_thresh")
+...
+
+
+
+
+
+ totalc_thresh_crit_col (string)
+
+ The column's name in the database storing the
+ critical threshold value for the number of total calls.
+
+
+
+ Default value is total_calls_critical
.
+
+
+
+ Set totalc_thresh_crit_col
parameter
+
+...
+modparam("dispatcher", "totalc_thresh_crit_col", "totalc_crit_thresh")
+...
+
+
+
+
+
+ concalls_thresh_warn_col (string)
+
+ The column's name in the database storing the
+ warning threshold value for the number of
+ concurrent calls.
+
+
+
+ Default value is concurrent_calls_warning
.
+
+
+
+ Set concalls_thresh_warn_col
parameter
+
+...
+modparam("dispatcher", "concalls_thresh_warn_col", "concalls_warn_thresh")
+...
+
+
+
+
+
+ concalls_thresh_crit_col (string)
+
+ The column's name in the database storing the
+ critical threshold value for the number of
+ concurrent calls.
+
+
+
+ Default value is concurrent_calls_critical
.
+
+
+
+ Set concalls_thresh_crit_col
parameter
+
+...
+modparam("dispatcher", "concalls_thresh_crit_col", "concalls_crit_thresh")
+...
+
+
+
+
+
+ seqcalls_thresh_warn_col (string)
+
+ The column's name in the database storing the
+ warning threshold value for the number of
+ sequential calls.
+
+
+
+ Default value is sequential_calls_warning
.
+
+
+
+ Set seqcalls_thresh_warn_col
parameter
+
+...
+modparam("dispatcher", "seqcalls_thresh_warn_col", "seqcalls_warn_thresh")
+...
+
+
+
+
+
+ seqcalls_thresh_crit_col (string)
+
+ The column's name in the database storing the
+ critical threshold value for the number of
+ sequential calls.
+
+
+
+ Default value is sequential_calls_critical
.
+
+
+
+ Set seqcalls_thresh_crit_col
parameter
+
+...
+modparam("dispatcher", "seqcalls_thresh_crit_col", "seqcalls_crit_thresh")
+...
+
+
+
+
+
+
+
+ Exported Functions
+
+
+ check_fraud(user, number, profile_id)
+
+
+ This method should be called each time a given user
+ calls a given number. It will try to match a fraud rule
+ within de given fraud profile and update the stats (see above). Furthermore,
+ the stats will be checked against the rule's thresholds. If any of the stats
+ is above it's threhsold value the appropriate event will also be raised
+ (see further details below).
+
+ Meaning of the parameters is as follows:
+
+
+
+ user - the user who is making the call. Please keep in mind that
+ the user doesn't have to be registered. This string is only used do keep different stats
+ for different registered users.
+
+
+
+
+ number - the number the user is calling to.
+
+
+
+
+ profile_id - the fraud profile id (i.e. the subset of fraud
+ rules) in which to try and find a matching fraud rule.
+
+
+
+
+ The meaning of the return code is as follows:
+
+
+
+
+ 2 - no matching fraud rule was found
+
+
+
+
+ 1 - a matching rule was found, but there is no
+ parameter above the rule's threshlod, i.e - everything is ok
+
+
+
+
+ -1 - there is a parameter above the warning threhsold value.
+ Check the raised event for more info
+
+
+
+
+ -2 - there is a parameter above the critical threhsold value.
+ Check the raised event for more info
+
+
+
+
+ -3 - something went wrong (internal mechanism failed)
+
+
+
+
+ This function can be used from REQUEST_ROUTE and ONREPLY_ROUTE.
+
+
+
+
+
+ Exported MI Functions
+
+
+ show_fraud_stats
+
+
+ Show the current stats of a given user for a given prefix.
+
+
+ Name: show_fraud_stats
+
+ Parameters:
+
+ user
+
+ number
+
+ prefix
+
+
+
+
+ fraud_reload
+
+
+ Reload the all the fraud rules.
+
+
+ Name: fraud_reload
+
+ Parameters: none
+
+
+
+
+
+ Exported Events
+
+
+ E_FRD_WARNING
+
+
+ This event is raised whenever one of the 5 monitored parameters
+ is above the warning threhsold value
+ Parameters:
+
+
+ param - the name of the parameter.
+
+
+ value - the current value of the parameter.
+
+
+ threshold - the warning threshold value.
+
+
+ user - the user who initiated the call.
+
+
+ called_number - the number that was called.
+
+
+ rule_id - the id of the fraud rule that matched
+ when the call was initiated
+
+
+
+
+
+ E_FRD_CRITICAL
+
+
+ This event is raised whenever one of the 5 monitored parameters
+ is above the warning threhsold value
+ Parameters:
+
+
+ param - the name of the parameter.
+
+
+ value - the current value of the parameter.
+
+
+ threshold - the warning threshold value.
+
+
+ user - the user who initiated the call.
+
+
+ called_number - the number that was called.
+
+
+ rule_id - the id of the fraud rule that matched
+ when the call was initiated
+
+
+
+
+
+
+
diff --git a/modules/fraud_detection/fraud_detection.c b/modules/fraud_detection/fraud_detection.c
new file mode 100644
index 0000000000..ef986bcebe
--- /dev/null
+++ b/modules/fraud_detection/fraud_detection.c
@@ -0,0 +1,522 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#include "../../ut.h"
+#include "../../db/db.h"
+#include "../../time_rec.h"
+#include "../../mod_fix.h"
+#include "../drouting/dr_api.h"
+#include "../dialog/dlg_load.h"
+
+#include "frd_stats.h"
+#include "frd_load.h"
+#include "frd_events.h"
+
+extern str db_url;
+extern str table_name;
+
+extern str rid_col;
+extern str pid_col;
+extern str prefix_col;
+extern str start_h_col;
+extern str end_h_col;
+extern str days_col;
+extern str cpm_thresh_warn_col;
+extern str cpm_thresh_crit_col;
+extern str calldur_thresh_warn_col;
+extern str calldur_thresh_crit_col;
+extern str totalc_thresh_warn_col;
+extern str totalc_thresh_crit_col;
+extern str concalls_thresh_warn_col;
+extern str concalls_thresh_crit_col;
+extern str seqcalls_thresh_warn_col;
+extern str seqcalls_thresh_crit_col;
+
+#define DEF_PARAM_STR_NAME(pname, strname)\
+ static str pname ## _name = str_init(strname)
+
+DEF_PARAM_STR_NAME(cpm, "calls per minute");
+DEF_PARAM_STR_NAME(total_calls, "total calls");
+DEF_PARAM_STR_NAME(concurrent_calls, "concurrent calls");
+DEF_PARAM_STR_NAME(seq_calls, "sequential calls");
+#undef DEF_PARAM_STR_NAME
+
+
+dr_head_p *dr_head;
+struct dr_binds drb;
+rw_lock_t *frd_data_lock;
+gen_lock_t *frd_seq_calls_lock;
+
+struct dlg_binds dlgb;
+
+static int mod_init(void);
+static int child_init(int);
+static void destroy(void);
+
+static int check_fraud(struct sip_msg *msg, char *user, char *number, char *pid);
+static int fixup_check_fraud(void **param, int param_no);
+static struct mi_root* mi_show_stats(struct mi_root *cmd_tree, void *param);
+static struct mi_root* mi_reload(struct mi_root *cmd_tree, void *param);
+
+static cmd_export_t cmds[]={
+ {"check_fraud", (cmd_function)check_fraud, 3, fixup_check_fraud, 0,
+ REQUEST_ROUTE | ONREPLY_ROUTE},
+ {0,0,0,0,0,0}
+};
+
+static param_export_t params[]={
+ {"db_url", STR_PARAM, &db_url.s},
+ {"table_name", STR_PARAM, &table_name.s},
+ {"rid_col", STR_PARAM, &rid_col.s},
+ {"pid_col", STR_PARAM, &pid_col.s},
+ {"prefix_col", STR_PARAM, &prefix_col.s},
+ {"start_h_col", STR_PARAM, &start_h_col.s},
+ {"end_h_col", STR_PARAM, &end_h_col.s},
+ {"days_col", STR_PARAM, &days_col.s},
+ {"cpm_thresh_warn_col", STR_PARAM, &cpm_thresh_warn_col.s},
+ {"cpm_thresh_crit_col", STR_PARAM, &cpm_thresh_crit_col.s},
+ {"calldur_thresh_warn_col", STR_PARAM, &calldur_thresh_warn_col.s},
+ {"calldur_thresh_crit_col", STR_PARAM, &calldur_thresh_crit_col.s},
+ {"totalc_thresh_warn_col", STR_PARAM, &totalc_thresh_warn_col.s},
+ {"totalc_thresh_crit_col", STR_PARAM, &totalc_thresh_crit_col.s},
+ {"concalls_thresh_warn_col", STR_PARAM, &concalls_thresh_warn_col.s},
+ {"concalls_thresh_crit_col", STR_PARAM, &concalls_thresh_crit_col.s},
+ {"seqcalls_thresh_warn_col", STR_PARAM, &seqcalls_thresh_warn_col.s},
+ {"seqcalls_thresh_crit_col", STR_PARAM, &seqcalls_thresh_crit_col.s},
+ {0,0,0}
+};
+
+static mi_export_t mi_cmds[] = {
+ //{ "get_maps","return all mappings",mi_get_maps,MI_NO_INPUT_FLAG,0,0},
+ {"show_fraud_stats", "print current stats for a particular user",
+ mi_show_stats, 0, 0, 0},
+ {"fraud_reload", "reload fraud profiles from db", mi_reload, 0, 0, 0},
+ {0,0,0,0,0,0}
+};
+
+static dep_export_t deps = {
+ {
+ {MOD_TYPE_SQLDB, NULL, DEP_ABORT},
+ {MOD_TYPE_DEFAULT, "drouting", DEP_ABORT},
+ {MOD_TYPE_DEFAULT, "dialog", DEP_ABORT},
+ {MOD_TYPE_NULL, NULL, 0},
+ },
+ {
+ {NULL, NULL},
+ },
+};
+
+/** module exports */
+struct module_exports exports= {
+ "fraud_detection", /* module name */
+ MOD_TYPE_DEFAULT,
+ MODULE_VERSION,
+ DEFAULT_DLFLAGS, /* dlopen flags */
+ &deps,
+ cmds, /* exported functions */
+ params, /* exported parameters */
+ 0, /* exported statistics */
+ mi_cmds, /* exported MI functions */
+ 0, /* exported pseudo-variables */
+ 0, /* extra processes */
+ mod_init, /* module initialization function */
+ (response_function) 0, /* response handling function */
+ (destroy_function)destroy, /* destroy function */
+ child_init /* per-child init function */
+};
+
+
+static void set_lengths(void)
+{
+ db_url.len = strlen(db_url.s);
+ table_name.len = strlen(table_name.s);
+ rid_col.len = strlen(rid_col.s);
+ pid_col.len = strlen(pid_col.s);
+ prefix_col.len = strlen(prefix_col.s);
+ start_h_col.len = strlen(start_h_col.s);
+ end_h_col.len = strlen(end_h_col.s);
+ days_col.len = strlen(days_col.s);
+ cpm_thresh_warn_col.len = strlen(cpm_thresh_warn_col.s);
+ cpm_thresh_crit_col.len = strlen(cpm_thresh_crit_col.s);
+ calldur_thresh_warn_col.len = strlen(calldur_thresh_warn_col.s);
+ calldur_thresh_crit_col.len = strlen(calldur_thresh_crit_col.s);
+ totalc_thresh_warn_col.len = strlen(totalc_thresh_warn_col.s);
+ totalc_thresh_crit_col.len = strlen(totalc_thresh_crit_col.s);
+ concalls_thresh_warn_col.len = strlen(concalls_thresh_warn_col.s);
+ concalls_thresh_crit_col.len = strlen(concalls_thresh_crit_col.s);
+ seqcalls_thresh_warn_col.len = strlen(seqcalls_thresh_warn_col.s);
+ seqcalls_thresh_crit_col.len = strlen(seqcalls_thresh_crit_col.s);
+}
+
+static int mod_init(void)
+{
+ LM_INFO("Initializing module\n");
+
+ if ((frd_data_lock = lock_init_rw()) == NULL) {
+ LM_CRIT("failed to init reader/writer lock\n");
+ return -1;
+ }
+
+ if ((frd_seq_calls_lock = lock_alloc()) == NULL) {
+ LM_ERR("cannot alloc seq_calls lock\n");
+ return -1;
+ }
+ if (lock_init(frd_seq_calls_lock) == NULL) {
+ LM_ERR ("cannot init seq_calls lock\n");
+ return -1;
+ }
+
+ if (load_dlg_api(&dlgb) != 0) {
+ LM_ERR("failed to load dialog binds\n");
+ return -1;
+ }
+
+ if (frd_event_init() != 0) {
+ LM_ERR("cannot register events\n");
+ return -1;
+ }
+
+ if (load_dr_api(&drb) != 0) {
+ LM_ERR("cannot load dr_api\n");
+ return -1;
+ }
+
+ dr_head = shm_malloc(sizeof(dr_head_p));
+ if (dr_head == NULL) {
+ LM_ERR("no more shm memory\n");
+ return -1;
+ }
+
+ set_lengths();
+ if (init_stats_table() != 0)
+ return -1;
+
+ /* Check if table version is ok */
+ frd_init_db();
+ frd_disconnect_db();
+
+ return 0;
+}
+
+static int child_init(int rank)
+{
+ if (rank == 1) {
+
+ if (frd_connect_db() != 0 || frd_reload_data() != 0) {
+ LM_ERR ("cannot load data from db\n");
+ return -1;
+ }
+ frd_disconnect_db();
+ }
+ return 0;
+}
+
+/*
+ * destroy function
+ */
+static void destroy(void)
+{
+ free_stats_table();
+ frd_destroy_data();
+}
+
+static int fixup_check_fraud(void **param, int param_no)
+{
+ switch (param_no) {
+
+ case 1:
+ case 2:
+ return fixup_spve(param);
+
+ case 3:
+ return fixup_igp(param);
+
+ default:
+ LM_CRIT ("Too many parameters for check_fraud\n");
+ return -1;
+ }
+}
+
+static int check_fraud(struct sip_msg *msg, char *_user, char *_number, char *_pid)
+{
+
+ static const int rc_error = -3, rc_critical_thr = -2, rc_warning_thr = -1,
+ rc_ok_thr = 1, rc_no_rule = 2;
+ str user, number;
+ unsigned int pid;
+
+ static str last_called_prefix;
+ extern unsigned int frd_data_rev;
+
+ if (dr_head == NULL) {
+ /* No data, probably still loading */
+ LM_ERR("no data\n");
+ return rc_error;
+ }
+
+ /* Get the actual params */
+
+ if (fixup_get_svalue(msg, (gparam_p) _user, &user) != 0) {
+ LM_ERR("Cannot get user value\n");
+ return rc_error;
+ }
+ if (fixup_get_svalue(msg, (gparam_p) _number, &number) != 0) {
+ LM_ERR("Cannot get number value\n");
+ return rc_error;
+ }
+ if (fixup_get_ivalue(msg, (gparam_p)_pid, (int*)&pid) != 0) {
+ LM_ERR("Cannot get the profile-id value\n");
+ return rc_error;
+ }
+
+ /* Find a rule */
+
+ unsigned int matched_len;
+ lock_start_read(frd_data_lock);
+ rt_info_t *rule = drb.match_number(*dr_head, pid, &number, &matched_len);
+
+ if (rule == NULL) {
+ /* No match */
+ LM_DBG("No rule matched for number=<%.*s>, pid=<%d>\n",
+ number.len, number.s, pid);
+
+ lock_stop_read(frd_data_lock);
+ return rc_no_rule;
+ }
+
+ /* We matched a rule */
+ str prefix = number;
+ prefix.len = matched_len;
+ str shm_user;
+ frd_stats_entry_t *se = get_stats(user, prefix, &shm_user);
+
+ /* Check if we need to reset the stats */
+
+ struct tm now, then;
+ time_t nowt = time(NULL);
+
+ /* We lock all the stats values */
+ lock_get(&se->lock);
+ if (gmtime_r(&se->stats.last_matched_time, &then) == NULL
+ || gmtime_r(&nowt, &now) == NULL) {
+ LM_ERR ("Cannot use gmtime function. Will exit\n");
+ return rc_ok_thr;
+ }
+
+ if (se->stats.last_matched_time == 0 || se->stats.last_matched_rule != rule->id
+ || then.tm_yday != now.tm_yday || then.tm_year != now.tm_year) {
+ se->stats.cpm = 0;
+ se->stats.total_calls = 0;
+ se->stats.concurrent_calls = 0;
+ }
+
+ /* Update the stats */
+
+ lock_get(frd_seq_calls_lock);
+ if (last_called_prefix.len == matched_len &&
+ memcmp(last_called_prefix.s, number.s, matched_len) == 0) {
+
+ /* We have called the same number last time */
+ ++se->stats.seq_calls;
+ }
+ else {
+ last_called_prefix.s = shm_realloc(last_called_prefix.s,
+ matched_len * sizeof(char));
+ last_called_prefix.len = matched_len;
+ se->stats.seq_calls = 1;
+ }
+ lock_release(frd_seq_calls_lock);
+
+ se->stats.last_matched_rule = rule->id;
+ ++se->stats.total_calls;
+
+ /* Calls per FRD_SECS_PER_WINDOW */
+ if (nowt - se->stats.last_matched_time >= FRD_SECS_PER_WINDOW) {
+ se->stats.cpm = 0;
+ memset(se->stats.calls_window, 0,
+ sizeof(unsigned short) * FRD_SECS_PER_WINDOW);
+ se->stats.calls_window[nowt % FRD_SECS_PER_WINDOW] = 1;
+ }
+ else {
+ unsigned int i = nowt % FRD_SECS_PER_WINDOW;
+ unsigned int j = (se->stats.last_matched_time + 1) % FRD_SECS_PER_WINDOW;
+ for (;i != j; i = (i - 1 + FRD_SECS_PER_WINDOW) % FRD_SECS_PER_WINDOW) {
+ se->stats.cpm -= se->stats.calls_window[i];
+ se->stats.calls_window[i] = 0;
+ }
+ }
+
+ ++se->stats.cpm;
+ ++se->stats.concurrent_calls;
+ se->stats.last_matched_time = nowt;
+
+ /* Check the thresholds */
+
+ int rc = rc_no_rule;
+
+ frd_thresholds_t *thr = (frd_thresholds_t*)rule->attrs.s;
+
+#define CHECK_AND_RAISE(pname, type) \
+ (se->stats.pname >= thr->pname ## _thr.type) { \
+ raise_ ## type ## _event(&pname ## _name, &se->stats.pname,\
+ &thr->pname ## _thr.type, &user, &number, &rule->id);\
+ rc = rc_ ## type ## _thr;\
+ }
+
+ if CHECK_AND_RAISE(cpm, critical)
+ else if CHECK_AND_RAISE(total_calls, critical)
+ else if CHECK_AND_RAISE(concurrent_calls, critical)
+ else if CHECK_AND_RAISE(seq_calls, critical)
+ else if CHECK_AND_RAISE(cpm, warning)
+ else if CHECK_AND_RAISE(total_calls, warning)
+ else if CHECK_AND_RAISE(concurrent_calls, warning)
+ else if CHECK_AND_RAISE(seq_calls, warning);
+
+#undef CHECK_AND_RAISE
+
+ lock_release(&se->lock);
+
+ /* Set dialog callback to check call duration */
+ struct dlg_cell *dlgc = dlgb.get_dlg();
+ if (dlgc == NULL) {
+ if (dlgb.create_dlg(msg, 0) < 0)
+ LM_ERR ("cannot create new_dlg\n");
+ else if ( (dlgc = dlgb.get_dlg()) == NULL)
+ LM_ERR("cannot get the new dlg\n");
+ }
+
+ if (dlgc) {
+ frd_dlg_param *param = shm_malloc(sizeof(frd_dlg_param));
+ if (param == NULL)
+ LM_ERR("no more shm memory");
+ else if (shm_str_dup(¶m->number, &number) == 0){
+ param->stats = se;
+ param->thr = thr;
+ param->user = shm_user;
+ param->ruleid = rule->id;
+ param->data_rev = frd_data_rev;
+
+ /* Register the dlg_terminate cb */
+ if (shm_str_dup(¶m->number, &number) != 0)
+ shm_free(param);
+ else if (dlgb.register_dlgcb(dlgc, DLGCB_TERMINATED,
+ dialog_terminate_CB, param, NULL) != 0) {
+ LM_ERR("cannot register dialog callback\n");
+ shm_free(param->number.s);
+ shm_free(param);
+ }
+ }
+ else {
+ shm_free(param);
+ }
+ }
+
+ lock_stop_read(frd_data_lock);
+
+ return rc;
+}
+
+static struct mi_root* mi_show_stats(struct mi_root *cmd_tree, void *param)
+{
+ /* User, number, pid */
+
+ struct mi_node *node = cmd_tree->node.kids;
+ str user, prefix;
+ unsigned int pid;
+
+ if (node == NULL)
+ return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
+
+ user = node->value;
+ node = node->next;
+
+ if (node == NULL)
+ return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
+
+ prefix = node->value;
+ node = node->next;
+
+ if (node == NULL)
+ return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
+
+ if (str2int(&node->value, &pid) != 0) {
+ LM_WARN("Wrong value for profile id. Token <%.*s>\n", node->value.len,
+ node->value.s);
+ return init_mi_tree(400, MI_BAD_PARM_S, MI_BAD_PARM_LEN);
+ }
+
+ if (!stats_exist(user, prefix)) {
+ LM_WARN("There is no data for user<%.*s> and prefix=<%.*s>\n",
+ user.len, user.s, prefix.len, prefix.s);
+ return init_mi_tree(400, MI_BAD_PARM_S, MI_BAD_PARM_LEN);
+ }
+
+
+ struct mi_root* rpl_tree = init_mi_tree(200, MI_OK_S, MI_OK_LEN);
+ if (rpl_tree == NULL)
+ return 0;
+ rpl_tree->node.flags |= MI_IS_ARRAY;
+
+ frd_stats_entry_t *se = get_stats(user, prefix, NULL);
+ lock_get(&se->lock);
+
+#define ADD_STAT_CHILD(pname, pval) do {\
+ int val_len;\
+ char *cval = int2str(pval, &val_len);\
+ if (add_mi_node_child(&rpl_tree->node, MI_DUP_VALUE,\
+ pname ## _name.s, pname ## _name.len, cval, val_len) == 0)\
+ goto add_error;\
+} while (0)
+
+ ADD_STAT_CHILD(cpm, se->stats.cpm);
+ ADD_STAT_CHILD(total_calls, se->stats.total_calls);
+ ADD_STAT_CHILD(concurrent_calls, se->stats.concurrent_calls);
+ ADD_STAT_CHILD(seq_calls, se->stats.seq_calls);
+
+#undef ADD_STAT_CHILD
+
+ lock_release(&se->lock);
+ return rpl_tree;
+
+add_error:
+ lock_release(&se->lock);
+ LM_ERR("failed to add node\n");
+ free_mi_tree(rpl_tree);
+ return 0;
+}
+
+static struct mi_root* mi_reload(struct mi_root *cmd_tree, void *param)
+{
+ if (frd_connect_db() != 0 || frd_reload_data() != 0) {
+ LM_ERR ("cannot load data from db\n");
+ return init_mi_tree(500, MI_INTERNAL_ERR_S, MI_INTERNAL_ERR_LEN);
+ }
+ else {
+ frd_disconnect_db();
+ return init_mi_tree(200, MI_OK_S, MI_OK_LEN);
+ }
+}
diff --git a/modules/fraud_detection/fraud_detection.h b/modules/fraud_detection/fraud_detection.h
new file mode 100644
index 0000000000..f309258841
--- /dev/null
+++ b/modules/fraud_detection/fraud_detection.h
@@ -0,0 +1,13 @@
+#ifndef new_mod_h
+#define new_mod_h
+
+#define MAPPING_DELIM '='
+
+struct ua_mapping {
+ str value;
+ str translated;
+ struct ua_mapping *next;
+};
+
+#endif
+
diff --git a/modules/fraud_detection/frd_events.c b/modules/fraud_detection/frd_events.c
new file mode 100644
index 0000000000..51ac81c702
--- /dev/null
+++ b/modules/fraud_detection/frd_events.c
@@ -0,0 +1,175 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#include "../../evi/evi_params.h"
+#include "../../evi/evi_modules.h"
+
+#include "../dialog/dlg_load.h"
+#include "frd_events.h"
+#include "../../mem/shm_mem.h"
+
+
+/* Events name and ids */
+
+static event_id_t ei_warn_id = EVI_ERROR;
+static event_id_t ei_crit_id = EVI_ERROR;
+
+static str ei_warn_name = str_init("E_FRD_WARNING");
+static str ei_crit_name = str_init("E_FRD_CRITICAL");
+static evi_params_p event_params;
+
+/* Events' parameters name and pointers*/
+
+static str ei_param_name = str_init("param");
+static str ei_val_name = str_init("value");
+static str ei_thr_name = str_init("threshold");
+static str ei_user_name = str_init("user");
+static str ei_number_name = str_init("called_number");
+static str ei_ruleid_name = str_init("rule_id");
+
+static evi_param_p param_p, val_p, thr_p, user_p, number_p, ruleid_p;
+
+
+/*
+ * Function to init the warning and critical events
+*/
+
+int frd_event_init(void)
+{
+ /* First publish the events */
+ ei_warn_id = evi_publish_event(ei_warn_name);
+ if (ei_warn_id == EVI_ERROR) {
+ LM_ERR("cannot register warning event\n");
+ return -1;
+ }
+ ei_crit_id = evi_publish_event(ei_crit_name);
+ if (ei_crit_id == EVI_ERROR) {
+ LM_ERR("cannot register critical event\n");
+ return -1;
+ }
+
+ event_params = pkg_malloc(sizeof(evi_params_t));
+ if (event_params == NULL)
+ return -1;
+ memset(event_params, 0, sizeof(evi_params_t));
+
+#define CREATE_PARAM(pname) \
+ pname ## _p = evi_param_create(event_params, &ei_ ## pname ## _name);\
+ if (! pname ## _p) \
+ goto create_param_err
+
+ CREATE_PARAM(param);
+ CREATE_PARAM(val);
+ CREATE_PARAM(thr);
+ CREATE_PARAM(user);
+ CREATE_PARAM(number);
+ CREATE_PARAM(ruleid);
+#undef CREATE_PARAM
+
+ return 0;
+
+create_param_err:
+ LM_ERR("cannot create event parameter");
+ return -1;
+}
+
+void frd_event_destroy(void)
+{
+ evi_free_params(event_params);
+}
+
+/*
+ * Function to be called internally for raising an event
+*/
+static void raise_event(event_id_t e,
+ str *param, unsigned int *val, unsigned int *thr, str *user,
+ str *number, unsigned int *ruleid)
+{
+#define SET_PARAM(pname, ptype) \
+ if (evi_param_set_ ##ptype (pname ## _p, pname) < 0) { \
+ LM_ERR("cannot set " # pname "parameter\n"); \
+ return; \
+ }
+
+ SET_PARAM(param, str);
+ SET_PARAM(val, int);
+ SET_PARAM(thr, int);
+ SET_PARAM(user, str);
+ SET_PARAM(number, str);
+ SET_PARAM(ruleid, int);
+#undef SET_PARAM
+
+ if (evi_raise_event(e, event_params) < 0)
+ LM_ERR("cannot raise event\n");
+}
+
+void raise_warning_event(str *param, unsigned int *val, unsigned int *thr,
+ str *user, str *number, unsigned int *ruleid)
+{
+ raise_event(ei_warn_id, param, val, thr, user, number, ruleid);
+}
+
+void raise_critical_event(str *param, unsigned int *val, unsigned int *thr,
+ str *user, str *number, unsigned int *ruleid)
+{
+ raise_event(ei_crit_id, param, val, thr, user, number, ruleid);
+}
+
+
+/*
+ * Callback called whenever a dialog is ended.
+ * Check the duration against the thresholds (sent through the params)
+ * and raise appropriate event
+*/
+
+ void dialog_terminate_CB(struct dlg_cell *dlgc, int type,
+ struct dlg_cb_params *params)
+{
+ static str call_dur_name = str_init ("call_duration");
+ frd_dlg_param *frdparam = (frd_dlg_param*) *(params->param);
+ extern unsigned int frd_data_rev;
+
+ if (type == DLGCB_TERMINATED && frd_data_rev == frdparam->data_rev) {
+ unsigned int duration = time(NULL) - dlgc->start_ts;
+ if ( duration >= frdparam->thr->call_duration_thr.critical)
+ raise_critical_event(&call_dur_name, &duration,
+ &frdparam->thr->call_duration_thr.critical,
+ &frdparam->user, &frdparam->number, &frdparam->ruleid);
+
+ else if ( duration >= frdparam->thr->call_duration_thr.warning)
+ raise_warning_event(&call_dur_name, &duration,
+ &frdparam->thr->call_duration_thr.warning,
+ &frdparam->user, &frdparam->number, &frdparam->ruleid);
+ }
+
+ lock_get(&frdparam->stats->lock);
+ --frdparam->stats->stats.concurrent_calls;
+ lock_release(&frdparam->stats->lock);
+
+ shm_free(frdparam->number.s);
+ shm_free(frdparam);
+}
diff --git a/modules/fraud_detection/frd_events.h b/modules/fraud_detection/frd_events.h
new file mode 100644
index 0000000000..64abe9f40f
--- /dev/null
+++ b/modules/fraud_detection/frd_events.h
@@ -0,0 +1,56 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#ifndef __FRD_EVENTS_H__
+#define __FRD_EVENTS_H__
+
+#include "frd_stats.h"
+
+int frd_event_init(void);
+void frd_event_destroy(void);
+void raise_warning_event(str *param, unsigned int *val, unsigned int *thr,
+ str *user, str *number, unsigned int *ruleid);
+void raise_critical_event(str *param, unsigned int *val, unsigned int *thr,
+ str *user, str *number, unsigned int *ruleid);
+
+
+/* Dialog callback */
+
+typedef struct {
+ frd_stats_entry_t *stats;
+ frd_thresholds_t *thr;
+ str user;
+ str number;
+ unsigned int ruleid;
+ unsigned int data_rev;
+} frd_dlg_param;
+
+ void dialog_terminate_CB(struct dlg_cell *dlgc, int type,
+ struct dlg_cb_params *params);
+
+
+#endif
diff --git a/modules/fraud_detection/frd_hashmap.c b/modules/fraud_detection/frd_hashmap.c
new file mode 100644
index 0000000000..3c4e8ac2f4
--- /dev/null
+++ b/modules/fraud_detection/frd_hashmap.c
@@ -0,0 +1,87 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#include "frd_hashmap.h"
+
+#include "../../hash_func.h"
+#include "../../str.h"
+#include "../../locking.h"
+
+int init_hash_map(hash_map_t *hm)
+{
+ hm->buckets = shm_malloc(hm->size * sizeof(hash_bucket_t));
+ if (hm->buckets == NULL) {
+ LM_ERR("No more shm memory\n");
+ return -1;
+ }
+
+ unsigned int i;
+
+ for (i = 0; i < hm->size; ++i) {
+ hm->buckets[i].items = map_create(AVLMAP_SHARED);
+ hm->buckets[i].lock = lock_init_rw();
+ if (hm->buckets[i].lock == NULL) {
+ LM_ERR("cannot init lock\n");
+ shm_free(hm->buckets);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void** get_item (hash_map_t *hm, str key)
+{
+ unsigned int hash = core_hash(&key, NULL, hm->size);
+
+ lock_start_read(hm->buckets[hash].lock);
+ void **find_res = map_find(hm->buckets[hash].items, key);
+ lock_stop_read(hm->buckets[hash].lock);
+ if (find_res) {
+ return find_res;
+ }
+ else {
+ lock_start_write(hm->buckets[hash].lock);
+ find_res = map_get(hm->buckets[hash].items, key);
+ lock_stop_write(hm->buckets[hash].lock);
+ return find_res;
+ }
+}
+
+void free_hash_map(hash_map_t* hm, void (*value_destroy_func)(void *))
+{
+ unsigned int i;
+ for (i = 0; i < hm->size; ++i) {
+ lock_start_write(hm->buckets[i].lock);
+ map_destroy(hm->buckets[i].items, value_destroy_func);
+ lock_stop_write(hm->buckets[i].lock);
+ lock_destroy(hm->buckets[i].lock);
+ }
+ shm_free(hm->buckets);
+}
+
+
diff --git a/modules/fraud_detection/frd_hashmap.h b/modules/fraud_detection/frd_hashmap.h
new file mode 100644
index 0000000000..7f6cdda839
--- /dev/null
+++ b/modules/fraud_detection/frd_hashmap.h
@@ -0,0 +1,50 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#ifndef __FRD_HASHMAP_H__
+#define __FRD_HASHMAP_H__
+
+#include "../../map.h"
+#include "../../rw_locking.h"
+
+typedef struct {
+ map_t items;
+ rw_lock_t *lock;
+} hash_bucket_t;
+
+typedef struct {
+ hash_bucket_t *buckets;
+ size_t size;
+} hash_map_t;
+
+
+
+int init_hash_map(hash_map_t* hm);
+void** get_item (hash_map_t *hm, str key);
+void free_hash_map(hash_map_t* hm, void (*value_destroy_func)(void *));
+
+#endif
diff --git a/modules/fraud_detection/frd_load.c b/modules/fraud_detection/frd_load.c
new file mode 100644
index 0000000000..15baef2ac4
--- /dev/null
+++ b/modules/fraud_detection/frd_load.c
@@ -0,0 +1,531 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#include "../../ut.h"
+#include "../../db/db.h"
+#include "../drouting/dr_api.h"
+#include "../../time_rec.h"
+
+#include "frd_stats.h"
+
+#define FRD_TABLE_VERSION 1
+#define FRD_TIME_SEP ':'
+
+#define FRD_RID_COL "ruleid"
+#define FRD_PID_COL "profileid"
+#define FRD_PREFIX_COL "prefix"
+#define FRD_START_H_COL "start_hour"
+#define FRD_END_H_COL "end_hour"
+#define FRD_DAYS_COL "daysoftheweek"
+#define FRD_CPM_THRESH_WARN_COL "cpm_warning"
+#define FRD_CPM_THRESH_CRIT_COL "cpm_critical"
+#define FRD_CALLDUR_THRESH_WARN_COL "call_duration_warning"
+#define FRD_CALLDUR_THRESH_CRIT_COL "call_duration_critical"
+#define FRD_TOTALC_THRESH_WARN_COL "total_calls_warning"
+#define FRD_TOTALC_THRESH_CRIT_COL "total_calls_critical"
+#define FRD_CONCALLS_THRESH_WARN_COL "concurrent_calls_warning"
+#define FRD_CONCALLS_THRESH_CRIT_COL "concurrent_calls_critical"
+#define FRD_SEQCALLS_THRESH_WARN_COL "sequential_calls_warning"
+#define FRD_SEQCALLS_THRESH_CRIT_COL "sequential_calls_critical"
+
+
+str db_url;
+str table_name = str_init("fraud_detection");
+
+str rid_col = str_init(FRD_RID_COL);
+str pid_col = str_init(FRD_PID_COL);
+str prefix_col = str_init(FRD_PREFIX_COL);
+str start_h_col = str_init(FRD_START_H_COL);
+str end_h_col = str_init(FRD_END_H_COL);
+str days_col = str_init(FRD_DAYS_COL);
+str cpm_thresh_warn_col = str_init(FRD_CPM_THRESH_WARN_COL);
+str cpm_thresh_crit_col = str_init(FRD_CPM_THRESH_CRIT_COL);
+str calldur_thresh_warn_col = str_init(FRD_CALLDUR_THRESH_WARN_COL);
+str calldur_thresh_crit_col = str_init(FRD_CALLDUR_THRESH_CRIT_COL);
+str totalc_thresh_warn_col = str_init(FRD_TOTALC_THRESH_WARN_COL);
+str totalc_thresh_crit_col = str_init(FRD_TOTALC_THRESH_CRIT_COL);
+str concalls_thresh_warn_col = str_init(FRD_CONCALLS_THRESH_WARN_COL);
+str concalls_thresh_crit_col = str_init(FRD_CONCALLS_THRESH_CRIT_COL);
+str seqcalls_thresh_warn_col = str_init(FRD_SEQCALLS_THRESH_WARN_COL);
+str seqcalls_thresh_crit_col = str_init(FRD_SEQCALLS_THRESH_CRIT_COL);
+
+
+unsigned int frd_data_rev;
+
+static db_con_t *db_handle;
+static db_func_t dbf;
+
+extern dr_head_p *dr_head;
+extern struct dr_binds drb;
+extern rw_lock_t *frd_data_lock;
+
+/* List of data kept in dr's attr and freed here - pkg */
+
+typedef struct _free_list_t{
+ tmrec_p trec;
+ frd_thresholds_t *thr;
+ unsigned int n;
+ struct _free_list_t *next;
+} free_list_t;
+
+static free_list_t *free_list;
+
+
+/*
+ * Function that parse time string like %H:%M to a tm struct
+ * tm struct must be allocated and initialized
+*/
+
+static int strtime(const str *time, int *ihrs, int *imin)
+{
+ char *colon = q_memchr(time->s, FRD_TIME_SEP, time->len);
+ if (colon == NULL)
+ goto parse_error;
+
+ str hrs = {time->s, colon - time->s};
+ str min = {colon + 1, time->len - hrs.len - 1};
+ if (hrs.len == 0 || min.len == 0)
+ goto parse_error;
+
+ unsigned int uhrs, umin;
+ if (str2int(&hrs, &uhrs) || str2int(&min, &umin))
+ goto parse_error;
+
+ if (uhrs > 23 || umin >= 60)
+ goto parse_error;
+
+ *imin = umin;
+ *ihrs = uhrs;
+
+ return 0;
+parse_error:
+ LM_ERR("cannot parse time-value <%.*s>", time->len, time->s);
+ return -1;
+}
+
+static int strcmp_case_insensitive(char *s1, char *s2, int len)
+{
+ int i;
+ for (i = 0; i < len; ++i)
+ if (tolower(s1[i]) != tolower(s2[i]))
+ return -1;
+
+ return 0;
+}
+
+static int parse_week_days(const str *week_days, unsigned short *day_set)
+{
+ static const str str_days[] = {
+ str_init("sun"), str_init("mon"), str_init("tue"), str_init("wed"),
+ str_init("thu"), str_init("fri"), str_init("sat")
+ };
+
+ static const char interval_delim = '-';
+ static const char list_delim = ',';
+
+ if (week_days->len == 0)
+ return 0;
+
+ char *p = week_days->s, *np, *dash;
+ int rem_len = week_days->len, token_len, i, j, n = 0;
+ str t1, t2;
+
+ do {
+ np = q_memchr(p, list_delim, rem_len);
+ token_len = np ? np - p : rem_len;
+ rem_len -= token_len + 1;
+
+ if (token_len < 3)
+ goto parse_error;
+
+ /* Now we see if it is an interval */
+ dash = q_memchr(p, interval_delim, token_len);
+
+ if (dash){
+ /* It is an interval */
+ t1.s = p;
+ t1.len = dash - p;
+ trim_spaces_lr(t1);
+
+ t2.s = dash + 1;
+ t2.len = token_len - t1.len - 1;
+ trim_spaces_lr(t2);
+
+ if (t1.len != 3 || t2.len != 3)
+ goto parse_error;
+
+ for (i = 0; i < 7; ++i)
+ if (strcmp_case_insensitive(str_days[i].s, t1.s, 3) == 0)
+ break;
+ if (i == 7)
+ goto parse_error;
+
+ for (j = 0; j < 7; ++j)
+ if (strcmp_case_insensitive(str_days[j].s, t2.s, 3) == 0)
+ break;
+ if (j == 7)
+ goto parse_error;
+
+ /* We increase the size of the days set */
+ n += (j - i + 7) % 7 + 1;
+
+ for (; i <= j; i = (i + 1) % 7)
+ *day_set |= 1 << i;
+ }
+ else {
+ /* Just one value */
+ t1.s = p;
+ t1.len = token_len;
+ trim_spaces_lr(t1);
+
+ if (t1.len != 3)
+ goto parse_error;
+
+ for (i = 0; i < 7; ++i)
+ if (strcmp_case_insensitive(str_days[i].s, t1.s, 3) == 0)
+ break;
+ if (i == 7)
+ goto parse_error;
+
+ *day_set |= 1 << i;
+ ++n;
+ }
+
+ p = np + 1;
+ } while (rem_len > 0);
+
+ return n;
+
+parse_error:
+ LM_ERR("Cannot parse week day list <%.*s>", week_days->len, week_days->s);
+ return -1;
+}
+
+static int create_time_rec(const str *time_start, const str *time_end,
+ const str *week_days, tmrec_p trec)
+{
+ int end_h, end_m;
+
+ memset(trec, 0, sizeof(tmrec_t));
+
+ if (strtime(time_start, &trec->ts.tm_hour, &trec->ts.tm_min) != 0
+ || strtime(time_end, &end_h, &end_m) != 0)
+ return -1;
+
+ trec->duration = (end_h * 3600 + end_m * 60) -
+ (trec->ts.tm_hour * 3600 + trec->ts.tm_min * 60);
+ trec->ts.tm_isdst = -1 /*daylight*/;
+ trec->dtstart = trec->duration;
+ trec->freq = FREQ_DAILY;
+
+ unsigned short day_set = 0;
+ int n = parse_week_days(week_days, &day_set);
+
+ if (n == -1)
+ return -1;
+
+ if (n) {
+ //TODO - byday custom init - no req needed
+ trec->byday = tr_byxxx_new(SHM_ALLOC);
+ if (trec->byday == NULL)
+ return -1;
+
+ if (tr_byxxx_init(trec->byday, n) < 0) {
+ tr_byxxx_free(trec->byday);
+ return -1;
+ }
+
+ short i, j = 0;
+
+ for (i = 0; i < 7; ++i)
+ if (day_set & 1 << i)
+ trec->byday->xxx[j++] = i;
+ }
+
+ return 0;
+}
+
+static int frd_load_data(dr_head_p drp, free_list_t **fl)
+{
+ static const size_t col_count = 16;
+ db_res_t *res = NULL;
+ unsigned int no_rows = 0, row_count, i;
+ db_row_t *rows;
+ db_val_t *values;
+
+ db_key_t query_cols[] = {
+ &rid_col, &pid_col, &prefix_col, &start_h_col, &end_h_col, &days_col,
+ &cpm_thresh_warn_col, &cpm_thresh_crit_col, &calldur_thresh_warn_col,
+ &calldur_thresh_crit_col, &totalc_thresh_warn_col, &totalc_thresh_crit_col,
+ &concalls_thresh_warn_col, &concalls_thresh_crit_col, &seqcalls_thresh_warn_col,
+ &seqcalls_thresh_crit_col
+ };
+
+ if (db_handle == NULL) {
+ LM_ERR("Invalid db handler\n");
+ return -1;
+ }
+
+ if (dbf.use_table(db_handle, &table_name) != 0) {
+ LM_ERR("Cannot use table\n");
+ return -1;
+ }
+
+ if (DB_CAPABILITY(dbf, DB_CAP_FETCH)) {
+ if (dbf.query(db_handle, 0, 0, 0, query_cols, 0, col_count, 0, 0) != 0) {
+ LM_ERR("Error while querying db\n");
+ goto error;
+ }
+ /* estimate rows */
+ no_rows = estimate_available_rows(4 + 64 + 5 + 5 + 64 + 5 * 2 * 4, col_count);
+
+ if (no_rows == 0)
+ no_rows = 10;
+
+ if (dbf.fetch_result(db_handle, &res, no_rows) != 0) {
+ LM_ERR("Error while fetching rows\n");
+ goto error;
+ }
+ } else {
+ /* No fetching capability */
+ if (dbf.query(db_handle, 0, 0, 0, query_cols, 0, col_count, 0, &res) != 0) {
+ LM_ERR("Error while querying db\n");
+ goto error;
+ }
+ }
+
+ /* Process the actual data */
+
+ unsigned int rid, pid, j;
+ str prefix, start_time, end_time, days;
+ free_list_t *fl_it = NULL;
+ *fl = NULL;
+
+ do {
+ row_count = RES_ROW_N(res);
+ rows = RES_ROWS(res);
+ fl_it = pkg_malloc(sizeof(free_list_t));
+ if (fl_it == NULL) {
+ LM_ERR ("no more pkg memory");
+ dbf.free_result(db_handle, res);
+ return -1;
+ }
+ fl_it ->next = *fl;
+ *fl = fl_it;
+ fl_it->trec = shm_malloc(sizeof(tmrec_t) * row_count);
+ if (fl_it->trec == NULL)
+ goto no_more_shm;
+ fl_it->thr = shm_malloc(sizeof(frd_thresholds_t) * row_count);
+ if (fl_it->thr == NULL)
+ goto no_more_shm;
+ fl_it->n = row_count;
+
+ for (i = 0; i < row_count; ++i) {
+ values = ROW_VALUES(rows + i);
+ fl_it->trec[i].byday = NULL;
+
+ /* rule id */
+ if (VAL_NULL(values)) {
+ LM_ERR("rule id cannot be NULL - skipping rule\n");
+ continue;
+ }
+ rid = VAL_INT(values);
+
+ /* profile id */
+ if (VAL_NULL(values + 1)) {
+ LM_ERR("profile id cannot be NULL - skipping rule\n");
+ continue;
+ }
+ pid = VAL_INT(values + 1);
+
+ get_str_from_dbval(prefix_col.s, values + 2, 1, 1, prefix, null_val);
+ get_str_from_dbval(start_h_col.s, values + 3, 1, 1, start_time, null_val);
+ get_str_from_dbval(end_h_col.s, values + 4, 1, 1, end_time, null_val);
+ get_str_from_dbval(days_col.s, values + 5, 1, 1, days, null_val);
+
+ if (create_time_rec(&start_time, &end_time, &days, fl_it->trec + i) != 0)
+ goto null_val;
+
+ /* Now load the thresholds */
+ for (j = 0; j < 2 * 5; ++j) {
+ if (VAL_NULL(values + 6 + j))
+ goto null_val;
+ memcpy((char*)fl_it->thr + i * sizeof(frd_thresholds_t) +
+ j * sizeof(unsigned int), &VAL_INT(values + 6 + j),
+ sizeof(unsigned int));
+ }
+
+ /* Rule OK, time to put it in DR */
+ if (drb.add_rule(drp, rid, &prefix, pid, 0, fl_it->trec + i,
+ (void*)(&fl_it->thr[i])) != 0) {
+
+ LM_ERR("Cannot add rule in dr <%u>. Skipping...\n", rid);
+ }
+
+ null_val:
+ continue;
+ }
+
+ if (DB_CAPABILITY(dbf, DB_CAP_FETCH)) {
+ /* any more rows to fetch ? */
+ if(dbf.fetch_result(db_handle, &res, no_rows)<0) {
+ LM_ERR("error while fetching rows\n");
+ goto error;
+ }
+ /* success in fetching more rows - continue the loop */
+ } else
+ break;
+
+ } while (RES_ROW_N(res) > 0);
+
+ dbf.free_result(db_handle, res);
+ return 0;
+
+no_more_shm:
+ LM_ERR ("no more shm memory\n");
+ dbf.free_result(db_handle, res);
+
+error:
+ return -1;
+}
+
+/* This function assumes no one is using the dr_head anymore */
+static void frd_destroy_data_unsafe(dr_head_p dr_head, free_list_t *fl)
+{
+ if (dr_head == NULL && fl == NULL)
+ return;
+
+ drb.free_head(dr_head);
+ free_list_t *it = fl, *aux;
+ int i;
+
+ while (it) {
+ for (i = 0; i < it->n; ++i)
+ if (it->trec[i].byday)
+ tr_byxxx_free(it->trec[i].byday);
+ shm_free(it->trec);
+ shm_free(it->thr);
+ aux = it;
+ it = it->next;
+ pkg_free(aux);
+ }
+}
+
+/* Function to be called in mod_destroy
+ * Still unsafe!!!
+*/
+
+void frd_destroy_data(void)
+{
+ frd_destroy_data_unsafe(*dr_head, free_list);
+}
+
+int frd_reload_data(void)
+{
+ dr_head_p new_head, old_head;
+
+ if ((new_head = drb.create_head()) == NULL) {
+ LM_ERR ("cannot create dr_head\n");
+ return -1;
+ }
+
+ free_list_t *new_list = NULL, *old_list;
+
+ if (frd_load_data(new_head, &new_list) != 0) {
+ LM_ERR("cannot load fraud data\n");
+ return -1;
+ }
+
+ old_head = *dr_head;
+ old_list = free_list;
+ ++frd_data_rev;
+ lock_start_write(frd_data_lock);
+ *dr_head = new_head;
+ free_list = new_list;
+ lock_stop_write(frd_data_lock);
+ frd_destroy_data_unsafe(old_head, old_list);
+ return 0;
+}
+
+int frd_connect_db(void)
+{
+ if (db_url.s == NULL || db_url.len == 0) {
+ LM_ERR("invalid db_url\n");
+ return -1;
+ }
+
+ if (db_handle != NULL) {
+ LM_CRIT("[BUG] connection already open\n");
+ return -1;
+ }
+
+ if ((db_handle = dbf.init(&db_url)) == 0) {
+ LM_ERR("unable to connect to the database\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+void frd_disconnect_db(void)
+{
+ if (db_handle) {
+ dbf.close(db_handle);
+ db_handle = NULL;
+ }
+}
+
+
+int frd_init_db(void)
+{
+ int table_version;
+
+ if (table_name.s == NULL || table_name.len == 0) {
+ LM_ERR("invalid table name\n");
+ return -1;
+ }
+
+ if (db_bind_mod(&db_url, &dbf) != 0) {
+ LM_ERR("unable to bind to a database driver\n");
+ return -1;
+ }
+
+ if(frd_connect_db() != 0)
+ return -1;
+
+ table_version = db_table_version(&dbf, db_handle, &table_name);
+ if (table_version < 0) {
+ LM_ERR("failed to query table version\n");
+ return -1;
+ } else if (table_version != FRD_TABLE_VERSION) {
+ LM_ERR("invalid table version (found %d , required %d)\n",
+ table_version, FRD_TABLE_VERSION );
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/modules/fraud_detection/frd_load.h b/modules/fraud_detection/frd_load.h
new file mode 100644
index 0000000000..44cc9974f6
--- /dev/null
+++ b/modules/fraud_detection/frd_load.h
@@ -0,0 +1,38 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#ifndef __FRD_LOAD_H__
+#define __FRD_LOAD_H__
+
+int frd_init_db(void);
+int frd_connect_db(void);
+void frd_disconnect_db(void);
+
+int frd_reload_data(void);
+void frd_destroy_data(void);
+
+#endif
diff --git a/modules/fraud_detection/frd_stats.c b/modules/fraud_detection/frd_stats.c
new file mode 100644
index 0000000000..6943f10063
--- /dev/null
+++ b/modules/fraud_detection/frd_stats.c
@@ -0,0 +1,150 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#include
+#include "frd_stats.h"
+#include "frd_hashmap.h"
+#include "../../ut.h"
+
+
+/* Struct used for the first level of the hashmap
+ * the user is kept in shm for two reasons :
+ * a) to keep using core's map
+ * b) to pass it for the dialog_end callback
+*/
+
+typedef struct {
+
+ hash_map_t numbers_hm;
+ str user;
+} frd_users_map_item_t;
+
+static hash_map_t stats_table;
+
+/*
+ * Function to init the stats hash table
+*/
+
+int init_stats_table(void)
+{
+ stats_table.size = FRD_USER_HASH_SIZE;
+ return init_hash_map(&stats_table);
+}
+
+
+frd_stats_entry_t* get_stats(str user, str prefix, str *shm_user)
+{
+ /* First go one level below using the user key */
+ frd_users_map_item_t **hm =
+ (frd_users_map_item_t **)get_item(&stats_table, user);
+
+ if (*hm == NULL) {
+ /* First time the user is seen, we must create a hashmap */
+ *hm = shm_malloc(sizeof(frd_users_map_item_t));
+ if (*hm == NULL) {
+ LM_ERR("no more shm memory\n");
+ return NULL;
+ }
+
+ (*hm)->numbers_hm.size = FRD_PREFIX_HASH_SIZE;
+ if (init_hash_map(&(*hm)->numbers_hm) != 0) {
+ LM_ERR("cannot init hashmap\n");
+ shm_free(*hm);
+ return NULL;
+ }
+
+ if (shm_str_dup(&(*hm)->user, &user) != 0) {
+ shm_free(*hm);
+ return NULL;
+ }
+ }
+
+ if (shm_user)
+ *shm_user = (*hm)->user;
+
+ frd_stats_entry_t **stats_entry =
+ (frd_stats_entry_t**)get_item(&(*hm)->numbers_hm, prefix);
+ if (*stats_entry == NULL) {
+ /* First time the prefix is seen for this user */
+ *stats_entry = shm_malloc(sizeof(frd_stats_entry_t));
+ if (*stats_entry == NULL) {
+ LM_ERR("no more shm memory\n");
+ return NULL;
+ }
+
+ /* Now init the auxiliary info for a stats structure */
+ if (!lock_init(&(*stats_entry)->lock)) {
+ LM_ERR ("cannot init lock\n");
+ shm_free(*stats_entry);
+ return NULL;
+ }
+ memset(&((*stats_entry)->stats), 0, sizeof(frd_stats_t));
+ }
+
+ return *stats_entry;
+}
+
+
+int stats_exist(str user, str prefix)
+{
+ /* First go one level below using the user key */
+ frd_users_map_item_t **hm =
+ (frd_users_map_item_t **)get_item(&stats_table, user);
+
+ if (*hm == NULL)
+ return 0;
+
+ frd_stats_entry_t **stats_entry =
+ (frd_stats_entry_t**)get_item(&(*hm)->numbers_hm, prefix);
+ if (*stats_entry == NULL)
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Functions for freeing the stats hash table
+*/
+
+static void destroy_stats_entry(void *e)
+{
+ lock_destroy( &((frd_stats_entry_t*)e)->lock );
+ shm_free(e);
+}
+
+static void destroy_users(void *u)
+{
+ frd_users_map_item_t *hm = (frd_users_map_item_t*)u;
+ free_hash_map(&hm->numbers_hm, destroy_stats_entry);
+ shm_free(hm->user.s);
+ shm_free(u);
+}
+
+void free_stats_table(void)
+{
+ free_hash_map(&stats_table, destroy_users);
+}
diff --git a/modules/fraud_detection/frd_stats.h b/modules/fraud_detection/frd_stats.h
new file mode 100644
index 0000000000..cfa7f573a6
--- /dev/null
+++ b/modules/fraud_detection/frd_stats.h
@@ -0,0 +1,71 @@
+/**
+ *
+ * Fraud Detection Module
+ *
+ * Copyright (C) 2014 OpenSIPS Foundation
+ *
+ * This file is part of opensips, a free SIP server.
+ *
+ * opensips 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
+ *
+ * opensips 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History
+ * -------
+ * 2014-09-26 initial version (Andrei Datcu)
+*/
+
+#ifndef __FRD_STATS_H__
+#define __FRD_STATS_H__
+
+#include "../../str.h"
+#include "../../locking.h"
+#include "../../rw_locking.h"
+
+#define FRD_USER_HASH_SIZE 1000
+#define FRD_PREFIX_HASH_SIZE 10
+#define FRD_SECS_PER_WINDOW 60
+
+typedef struct {
+ unsigned int cpm;
+ unsigned int total_calls;
+ unsigned int concurrent_calls;
+ unsigned int seq_calls;
+
+ unsigned int last_matched_rule;
+ time_t last_matched_time;
+ unsigned short calls_window[FRD_SECS_PER_WINDOW];
+} frd_stats_t;
+
+typedef struct _frd_hash_item {
+ gen_lock_t lock;
+ frd_stats_t stats;
+} frd_stats_entry_t;
+
+int init_stats_table(void);
+frd_stats_entry_t* get_stats(str user, str prefix, str *shm_user);
+int stats_exist(str user, str prefix);
+void free_stats_table(void);
+
+
+typedef struct {
+ unsigned int warning;
+ unsigned int critical;
+} frd_threshold_t;
+
+typedef struct {
+ frd_threshold_t cpm_thr, call_duration_thr, total_calls_thr,
+ concurrent_calls_thr, seq_calls_thr;
+} frd_thresholds_t;
+
+#endif
diff --git a/scripts/db_berkeley/opensips/fraud_detection b/scripts/db_berkeley/opensips/fraud_detection
new file mode 100644
index 0000000000..0a1c190160
--- /dev/null
+++ b/scripts/db_berkeley/opensips/fraud_detection
@@ -0,0 +1,12 @@
+METADATA_COLUMNS
+ruleid(int) profileid(int) prefix(str) start_hour(str) end_hour(str) daysoftheweek(str) cpm_warning(int) cpm_critical(int) call_duration_warning(int) call_duration_critical(int) total_calls_warning(int) total_calls_critical(int) concurrent_calls_warning(int) concurrent_calls_critical(int) sequential_calls_warning(int) sequential_calls_critical(int)
+METADATA_KEY
+0
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL
+fraud_detection|
+fraud_detection|1
diff --git a/scripts/dbtext/opensips/fraud_detection b/scripts/dbtext/opensips/fraud_detection
new file mode 100644
index 0000000000..7aacc07939
--- /dev/null
+++ b/scripts/dbtext/opensips/fraud_detection
@@ -0,0 +1,2 @@
+ruleid(int,auto) profileid(int) prefix(string) start_hour(string) end_hour(string) daysoftheweek(string) cpm_warning(int) cpm_critical(int) call_duration_warning(int) call_duration_critical(int) total_calls_warning(int) total_calls_critical(int) concurrent_calls_warning(int) concurrent_calls_critical(int) sequential_calls_warning(int) sequential_calls_critical(int)
+fraud_detection:1
diff --git a/scripts/mysql/fraud_detection-create.sql b/scripts/mysql/fraud_detection-create.sql
new file mode 100644
index 0000000000..8c981c9ab4
--- /dev/null
+++ b/scripts/mysql/fraud_detection-create.sql
@@ -0,0 +1,20 @@
+INSERT INTO version (table_name, table_version) values ('fraud_detection','1');
+CREATE TABLE fraud_detection (
+ ruleid INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ profileid INT UNSIGNED NOT NULL,
+ prefix CHAR(64) NOT NULL,
+ start_hour CHAR(5) NOT NULL,
+ end_hour CHAR(5) NOT NULL,
+ daysoftheweek CHAR(64) NOT NULL,
+ cpm_warning INT(5) UNSIGNED NOT NULL,
+ cpm_critical INT(5) UNSIGNED NOT NULL,
+ call_duration_warning INT(5) UNSIGNED NOT NULL,
+ call_duration_critical INT(5) UNSIGNED NOT NULL,
+ total_calls_warning INT(5) UNSIGNED NOT NULL,
+ total_calls_critical INT(5) UNSIGNED NOT NULL,
+ concurrent_calls_warning INT(5) UNSIGNED NOT NULL,
+ concurrent_calls_critical INT(5) UNSIGNED NOT NULL,
+ sequential_calls_warning INT(5) UNSIGNED NOT NULL,
+ sequential_calls_critical INT(5) UNSIGNED NOT NULL
+) ENGINE=MyISAM;
+
diff --git a/scripts/oracle/fraud_detection-create.sql b/scripts/oracle/fraud_detection-create.sql
new file mode 100644
index 0000000000..37f01a357d
--- /dev/null
+++ b/scripts/oracle/fraud_detection-create.sql
@@ -0,0 +1,28 @@
+INSERT INTO version (table_name, table_version) values ('fraud_detection','1');
+CREATE TABLE fraud_detection (
+ ruleid NUMBER(10) PRIMARY KEY,
+ profileid NUMBER(10),
+ prefix VARCHAR2(64),
+ start_hour VARCHAR2(5),
+ end_hour VARCHAR2(5),
+ daysoftheweek VARCHAR2(64),
+ cpm_warning NUMBER(10),
+ cpm_critical NUMBER(10),
+ call_duration_warning NUMBER(10),
+ call_duration_critical NUMBER(10),
+ total_calls_warning NUMBER(10),
+ total_calls_critical NUMBER(10),
+ concurrent_calls_warning NUMBER(10),
+ concurrent_calls_critical NUMBER(10),
+ sequential_calls_warning NUMBER(10),
+ sequential_calls_critical NUMBER(10)
+);
+
+CREATE OR REPLACE TRIGGER fraud_detection_tr
+before insert on fraud_detection FOR EACH ROW
+BEGIN
+ auto_id(:NEW.id);
+END fraud_detection_tr;
+/
+BEGIN map2users('fraud_detection'); END;
+/
diff --git a/scripts/pi_http/fraud_detection-mod b/scripts/pi_http/fraud_detection-mod
new file mode 100644
index 0000000000..99b1199ef0
--- /dev/null
+++ b/scripts/pi_http/fraud_detection-mod
@@ -0,0 +1,77 @@
+
+ fraud_detection
+ show
+ fraud_detection
+ DB_QUERY
+
+ ruleidupdate
+ profileid
+ prefix
+ start_hour
+ end_hour
+ daysoftheweek
+ cpm_warning
+ cpm_critical
+ call_duration_warning
+ call_duration_critical
+ total_calls_warning
+ total_calls_critical
+ concurrent_calls_warning
+ concurrent_calls_critical
+ sequential_calls_warning
+ sequential_calls_critical
+
+
+ add
+ fraud_detection
+ DB_INSERT
+
+ profileid
+ prefix
+ start_hour
+ end_hour
+ daysoftheweek
+ cpm_warning
+ cpm_critical
+ call_duration_warning
+ call_duration_critical
+ total_calls_warning
+ total_calls_critical
+ concurrent_calls_warning
+ concurrent_calls_critical
+ sequential_calls_warning
+ sequential_calls_critical
+
+
+ update
+ fraud_detection
+ DB_UPDATE
+
+ ruleid=
+
+
+ profileid
+ prefix
+ start_hour
+ end_hour
+ daysoftheweek
+ cpm_warning
+ cpm_critical
+ call_duration_warning
+ call_duration_critical
+ total_calls_warning
+ total_calls_critical
+ concurrent_calls_warning
+ concurrent_calls_critical
+ sequential_calls_warning
+ sequential_calls_critical
+
+
+ delete
+ fraud_detection
+ DB_DELETE
+
+ ruleid=
+
+
+
diff --git a/scripts/pi_http/fraud_detection-table b/scripts/pi_http/fraud_detection-table
new file mode 100644
index 0000000000..55edc4d1fa
--- /dev/null
+++ b/scripts/pi_http/fraud_detection-table
@@ -0,0 +1,21 @@
+
+
+ fraud_detection
+ mysql
+ ruleidDB_INT
+ profileidDB_INT
+ prefixDB_STR
+ start_hourDB_STR
+ end_hourDB_STR
+ daysoftheweekDB_STR
+ cpm_warningDB_INT
+ cpm_criticalDB_INT
+ call_duration_warningDB_INT
+ call_duration_criticalDB_INT
+ total_calls_warningDB_INT
+ total_calls_criticalDB_INT
+ concurrent_calls_warningDB_INT
+ concurrent_calls_criticalDB_INT
+ sequential_calls_warningDB_INT
+ sequential_calls_criticalDB_INT
+
diff --git a/scripts/postgres/fraud_detection-create.sql b/scripts/postgres/fraud_detection-create.sql
new file mode 100644
index 0000000000..80695237bb
--- /dev/null
+++ b/scripts/postgres/fraud_detection-create.sql
@@ -0,0 +1,21 @@
+INSERT INTO version (table_name, table_version) values ('fraud_detection','1');
+CREATE TABLE fraud_detection (
+ ruleid SERIAL PRIMARY KEY NOT NULL,
+ profileid INTEGER NOT NULL,
+ prefix VARCHAR(64) NOT NULL,
+ start_hour VARCHAR(5) NOT NULL,
+ end_hour VARCHAR(5) NOT NULL,
+ daysoftheweek VARCHAR(64) NOT NULL,
+ cpm_warning INTEGER NOT NULL,
+ cpm_critical INTEGER NOT NULL,
+ call_duration_warning INTEGER NOT NULL,
+ call_duration_critical INTEGER NOT NULL,
+ total_calls_warning INTEGER NOT NULL,
+ total_calls_critical INTEGER NOT NULL,
+ concurrent_calls_warning INTEGER NOT NULL,
+ concurrent_calls_critical INTEGER NOT NULL,
+ sequential_calls_warning INTEGER NOT NULL,
+ sequential_calls_critical INTEGER NOT NULL
+);
+
+ALTER SEQUENCE fraud_detection_ruleid_seq MAXVALUE 2147483647 CYCLE;