summaryrefslogtreecommitdiff
path: root/misc/freeswitch/scripts/common
diff options
context:
space:
mode:
Diffstat (limited to 'misc/freeswitch/scripts/common')
-rw-r--r--misc/freeswitch/scripts/common/database.lua40
-rw-r--r--misc/freeswitch/scripts/common/fapi.lua5
-rw-r--r--misc/freeswitch/scripts/common/intruder.lua51
-rw-r--r--misc/freeswitch/scripts/common/perimeter.lua241
-rw-r--r--misc/freeswitch/scripts/common/sip_account.lua8
-rw-r--r--misc/freeswitch/scripts/common/str.lua17
6 files changed, 359 insertions, 3 deletions
diff --git a/misc/freeswitch/scripts/common/database.lua b/misc/freeswitch/scripts/common/database.lua
index 1f39135..345f69d 100644
--- a/misc/freeswitch/scripts/common/database.lua
+++ b/misc/freeswitch/scripts/common/database.lua
@@ -16,6 +16,7 @@ function Database.new(self, arg)
self.class = 'database';
self.log = arg.log;
self.conn = nil;
+ self.ignore_on_update = arg.ignore_on_update or {};
return object;
end
@@ -71,6 +72,45 @@ function Database.last_insert_id(self)
end
+function Database.insert_or_update(self, db_table, record, use_on_update)
+ ignore_on_update = ignore_on_update or self.ignore_on_update;
+ local record_sql_create = {};
+ local record_sql_update = {};
+
+ for key, value in pairs(record) do
+ if ignore_on_update[key] ~= false then
+ table.insert(record_sql_update, self:key_value(key, value));
+ end
+ table.insert(record_sql_create, self:key_value(key, value));
+ end
+
+ local sql_query = 'INSERT INTO `' .. db_table .. '` SET ' .. table.concat(record_sql_create, ', ') .. ' ON DUPLICATE KEY UPDATE ' .. table.concat(record_sql_update, ', ');
+
+ return self:query(sql_query);
+end
+
+
+function Database.key_value(self, key, value)
+ return self:escape(key, '`') .. ' = ' .. self:escape(value, '"');
+end
+
+
+function Database.escape(self, value, str_quotes)
+ str_quotes = str_quotes or '';
+ if type(value) == 'boolean' then
+ return tostring(value):upper();
+ elseif type(value) == 'number' then
+ return tostring(value);
+ elseif type(value) == 'string' then
+ return str_quotes .. value:gsub('"', '\\"'):gsub("'", "\\'") .. str_quotes;
+ elseif type(value) == 'table' and value.raw then
+ return tostring(value[1]);
+ else
+ return 'NULL';
+ end
+end
+
+
function Database.release(self)
if self.conn then
self.conn:release();
diff --git a/misc/freeswitch/scripts/common/fapi.lua b/misc/freeswitch/scripts/common/fapi.lua
index 5b96633..b749a69 100644
--- a/misc/freeswitch/scripts/common/fapi.lua
+++ b/misc/freeswitch/scripts/common/fapi.lua
@@ -32,8 +32,10 @@ function FApi.return_result(self, result, positive, negative, unspecified)
return negative;
elseif result:match('^+OK') then
return positive;
- else
+ elseif type(unspecified) ~= 'nil' then
return unspecified;
+ else
+ return result;
end
end
@@ -75,6 +77,7 @@ function FApi.create_uuid(self, uuid)
end
function FApi.execute(self, function_name, function_parameters)
+ function_parameters = function_parameters or '';
local result = self.fs_api:execute(function_name, function_parameters);
return self:return_result(result, true);
end
diff --git a/misc/freeswitch/scripts/common/intruder.lua b/misc/freeswitch/scripts/common/intruder.lua
new file mode 100644
index 0000000..083ec37
--- /dev/null
+++ b/misc/freeswitch/scripts/common/intruder.lua
@@ -0,0 +1,51 @@
+-- Gemeinschaft 5 module: intruder class
+-- (c) AMOOMA GmbH 2013
+--
+
+module(...,package.seeall)
+
+
+Intruder = {}
+
+
+function Intruder.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self);
+ self.__index = self;
+ self.log = arg.log;
+ self.class = 'intruder'
+ self.database = arg.database;
+
+ return object;
+end
+
+
+function Intruder.update_blacklist(self, event)
+ local intruder_record = {
+ list_type = 'blacklist',
+ key = event.key,
+ points = event.points,
+ bans = event.record.banned,
+ contact_ip = event.received_ip,
+ contact_port = event.received_port,
+ contact_count = event.record.contact_count + 1,
+ contact_last = { 'FROM_UNIXTIME(' .. tostring(math.floor(event.timestamp/1000000)) .. ')', raw = true },
+ contacts_per_second = event.contacts_per_second,
+ contacts_per_second_max = event.contacts_per_second_max,
+ user_agent = event.user_agent,
+ to_user = event.to_user,
+ comment = 'Permimeter',
+ created_at = {'NOW()', raw = true },
+ updated_at = {'NOW()', raw = true },
+ };
+
+ if tonumber(event.ban_time) then
+ intruder_record.ban_last = { 'FROM_UNIXTIME(' .. event.ban_time .. ')', raw = true };
+ end
+ if tonumber(event.ban_end) then
+ intruder_record.ban_end = { 'FROM_UNIXTIME(' .. event.ban_end .. ')', raw = true };
+ end
+
+ self.database:insert_or_update('intruders', intruder_record, { created_at = false, comment = false });
+end
diff --git a/misc/freeswitch/scripts/common/perimeter.lua b/misc/freeswitch/scripts/common/perimeter.lua
new file mode 100644
index 0000000..0815d33
--- /dev/null
+++ b/misc/freeswitch/scripts/common/perimeter.lua
@@ -0,0 +1,241 @@
+-- Gemeinschaft 5 module: perimeter class
+-- (c) AMOOMA GmbH 2013
+--
+
+module(...,package.seeall)
+
+
+Perimeter = {}
+
+
+function Perimeter.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self);
+ self.__index = self;
+ self.log = arg.log;
+ self.class = 'perimeter'
+ self.database = arg.database;
+ self.domain = arg.domain;
+ self.sources = {};
+
+ self.checks_available = {
+ check_frequency = self.check_frequency,
+ check_username_scan = self.check_username_scan,
+ check_bad_headers = self.check_bad_headers,
+ };
+
+ return object;
+end
+
+
+function Perimeter.setup(self, event)
+ require 'common.configuration_table';
+ local config = common.configuration_table.get(self.database, 'perimeter');
+
+ self.contact_count_threshold = 10;
+ self.contact_span_threshold = 2;
+ self.name_changes_threshold = 2;
+ self.blacklist_file = '/var/opt/gemeinschaft/firewall/blacklist';
+ self.blacklist_file_comment = '# PERIMETER_BAN - points: {points}, generated: {date}';
+ self.blacklist_file_entry = '{received_ip} udp 5060';
+ self.ban_command = 'sudo /sbin/service shorewall refresh';
+ self.ban_threshold = 20;
+ self.ban_tries = 1;
+ self.checks = { register = {}, call = {} };
+ self.bad_headers = { register = {}, call = {} };
+
+ if config and config.general then
+ for key, value in pairs(config.general) do
+ self[key] = value;
+ end
+ end
+
+ self.checks.register = config.checks_register or {};
+ self.checks.call = config.checks_call or {};
+ self.bad_headers.register = config.bad_headers_register;
+ self.bad_headers.call = config.bad_headers_call;
+
+ self.log:info('[perimeter] PERIMETER - setup perimeter defense');
+end
+
+
+function Perimeter.record_load(self, event)
+ if not self.sources[event.key] then
+ self.sources[event.key] = {
+ contact_first = event.timestamp,
+ contact_last = event.timestamp,
+ contact_count = 0,
+ span_contact_count = 0,
+ span_start = event.timestamp,
+ points = 0,
+ banned = 0,
+ };
+ end
+
+ return self.sources[event.key];
+end
+
+
+function Perimeter.format_date(self, value)
+ local epoch = tonumber(tostring(value/1000000):match('^(%d-)%.'));
+ return os.date('%Y-%m-%d %X', tonumber(epoch)) .. '.' .. (value-(epoch*1000000));
+end
+
+
+function Perimeter.record_update(self, event)
+ event.record.contact_last = event.timestamp;
+ event.record.contact_count = event.record.contact_count + 1;
+ event.record.points = event.points or event.record.points;
+ event.record.span_start = event.span_start or event.record.span_start;
+ event.record.span_contact_count = (event.span_contact_count or event.record.span_contact_count) + 1;
+ event.record.users = event.users or event.record.users;
+end
+
+
+function Perimeter.check(self, event)
+ if not event or not event.key then
+ self.log:warning('[perimeter] PERIMETER_CHECK - no event/key');
+ return;
+ end
+
+ event.record = self:record_load(event);
+ if event.record.banned <= self.ban_tries then
+ for check_name, check_points in pairs(self.checks[event.action]) do
+ if self.checks_available[check_name] then
+ local result = self.checks_available[check_name](self, event);
+ if tonumber(result) then
+ event.points = (event.points or event.record.points) + result * check_points;
+ end
+ end
+ end
+ end
+
+ if tonumber(event.points) and event.points < 0 then
+ event.points = 0;
+ end
+
+ if event.points then
+ self.log:info('[', event.key, '/', event.sequence, '] PERIMETER suspicion rising - points: ', event.points,', ', event.action, '=', event.class, ', from: ', event.from_user, '@', event.from_host, ', to: ', event.to_user, '@', event.to_host, ', user_agent: ', event.user_agent);
+ end
+
+ if (event.points or event.record.points) > self.ban_threshold and event.record.banned <= self.ban_tries then
+ if event.record.banned > 0 and event.record.banned == self.ban_tries then
+ self.log:warning('[', event.key, '/', event.sequence, '] PERIMETER_BAN_FUTILE - points: ', event.points,', event: ', event.class, ', from: ', event.from_user, '@', event.from_host, ', to: ', event.to_user, '@', event.to_host);
+ else
+ self.log:notice('[', event.key, '/', event.sequence, '] PERIMETER_BAN - threshold reached: ', event.points,', event: ', event.class, ', from: ', event.from_user, '@', event.from_host, ', to: ', event.to_user, '@', event.to_host);
+ if event.record.banned == 0 then
+ self:append_blacklist_file(event);
+ end
+ self:execute_ban(event);
+ event.ban_time = os.time();
+ end
+
+ event.record.banned = event.record.banned + 1;
+ event.span_start = event.timestamp;
+ event.span_contact_count = 0;
+ event.points = 0;
+ end
+
+ if event.points then
+ self:update_intruder(event);
+ end
+
+ self:record_update(event);
+end
+
+
+function Perimeter.check_frequency(self, event)
+ if event.record.span_contact_count >= self.contact_count_threshold then
+ self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_FREQUENCY_CHECK - contacts: ', event.record.span_contact_count, ' in < ', (event.timestamp - event.record.span_start)/1000000, ' sec, threshold: ', self.contact_count_threshold, ' in ', self.contact_span_threshold, ' sec');
+ event.span_contact_count = 0;
+ event.span_start = event.timestamp;
+ event.contacts_per_second = event.record.span_contact_count / ((event.timestamp - event.record.span_start)/1000000)
+ return 1;
+ elseif (event.timestamp - event.record.span_start) > (self.contact_span_threshold * 1000000) then
+ event.span_contact_count = 0;
+ event.span_start = event.timestamp;
+ end
+end
+
+
+function Perimeter.check_username_scan(self, event)
+ if not event.to_user then
+ return;
+ end
+
+ if not event.record.users or tostring(event.auth_result) == 'SUCCESS' or tostring(event.auth_result) == 'RENEWED' then
+ event.users = { event.to_user };
+ return;
+ end
+
+ if #event.record.users >= self.name_changes_threshold then
+ self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_USER_SCAN - user names: ', #event.record.users, ', threshold: ', self.name_changes_threshold);
+ event.users = {};
+ return 1;
+ else
+ for index=1, #event.record.users do
+ if event.record.users[index] == tostring(event.to_user) then
+ return
+ end
+ end
+
+ if not event.users then
+ event.users = event.record.users or {};
+ end
+ table.insert(event.users, tostring(event.to_user));
+ end
+end
+
+
+function Perimeter.check_bad_headers(self, event)
+ local points = nil;
+ for name, pattern in pairs(self.bad_headers[event.action]) do
+ pattern = self:expand_variables(pattern, event);
+ local success, result = pcall(string.find, event[name], pattern);
+ if success and result then
+ self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_BAD_HEADERS - ', name, '=', event[name], ' ~= ', pattern);
+ points = (points or 0) + 1;
+ end
+ end
+
+ return points;
+end
+
+
+function Perimeter.append_blacklist_file(self, event)
+ local blacklist = io.open(self.blacklist_file, 'a');
+ if not blacklist then
+ self.log:error('[', event.key, '/', event.sequence, '] PERIMETER_APPEND_BLACKLIST - could not open file: ', self.blacklist_file);
+ return false;
+ end
+
+ event.date = self:format_date(event.timestamp);
+
+ if self.blacklist_file_comment then
+ blacklist:write(self:expand_variables(self.blacklist_file_comment, event), '\n');
+ end
+
+ self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_APPEND_BLACKLIST - file: ', self.blacklist_file);
+ blacklist:write(self:expand_variables(self.blacklist_file_entry, event), '\n');
+ blacklist:close();
+end
+
+
+function Perimeter.execute_ban(self, event)
+ local command = self:expand_variables(self.ban_command, event);
+ self.log:debug('[', event.key, '/', event.sequence, '] PERIMETER_EXECUTE_BAN - command: ', command);
+ local result = os.execute(command);
+end
+
+function Perimeter.update_intruder(self, event)
+ require 'common.intruder';
+ local result = common.intruder.Intruder:new{ log = self.log, database = self.database }:update_blacklist(event);
+end
+
+
+function Perimeter.expand_variables(self, line, variables)
+ return (line:gsub('{([%a%d%._]+)}', function(captured)
+ return variables[captured] or '';
+ end))
+end
diff --git a/misc/freeswitch/scripts/common/sip_account.lua b/misc/freeswitch/scripts/common/sip_account.lua
index 8dd432b..d023f20 100644
--- a/misc/freeswitch/scripts/common/sip_account.lua
+++ b/misc/freeswitch/scripts/common/sip_account.lua
@@ -38,8 +38,12 @@ function SipAccount.find_by_sql(self, where)
`a`.`sip_accountable_id`, \
`a`.`hotdeskable`, \
`a`.`gs_node_id`, \
- `b`.`host` \
- FROM `sip_accounts` `a` JOIN `sip_domains` `b` ON `a`.`sip_domain_id` = `b`.`id` \
+ `b`.`host`, \
+ `c`.`sip_host`, \
+ `c`.`profile_name` \
+ FROM `sip_accounts` `a` \
+ JOIN `sip_domains` `b` ON `a`.`sip_domain_id` = `b`.`id` \
+ LEFT JOIN `sip_registrations` `c` ON `a`.`auth_name` = `c`.`sip_user` \
WHERE ' .. where .. ' LIMIT 1';
local sip_account = nil;
diff --git a/misc/freeswitch/scripts/common/str.lua b/misc/freeswitch/scripts/common/str.lua
index 793c191..72ff388 100644
--- a/misc/freeswitch/scripts/common/str.lua
+++ b/misc/freeswitch/scripts/common/str.lua
@@ -18,6 +18,23 @@ function try(array, arguments)
return result;
end
+
+function set(array, arguments, value)
+ local nop, arguments_count = arguments:gsub('%.', '');
+ local structure = array;
+ arguments:gsub('([^%.]+)', function(entry)
+ if arguments_count <= 0 then
+ structure[entry] = value;
+ elseif type(structure[entry]) == 'table' then
+ structure = structure[entry];
+ else
+ structure[entry] = {};
+ structure = structure[entry];
+ end
+ arguments_count = arguments_count - 1;
+ end);
+end
+
-- to number
function to_n(value)
value = tostring(value):gsub('[^%d%.%+%-]', '');