summaryrefslogtreecommitdiff
path: root/misc/freeswitch/scripts/event
diff options
context:
space:
mode:
Diffstat (limited to 'misc/freeswitch/scripts/event')
-rw-r--r--misc/freeswitch/scripts/event/call_history_save.lua74
-rw-r--r--misc/freeswitch/scripts/event/cdr_save.lua105
-rw-r--r--misc/freeswitch/scripts/event/event.lua109
-rw-r--r--misc/freeswitch/scripts/event/perimeter.lua106
-rw-r--r--misc/freeswitch/scripts/event/presence_update.lua199
5 files changed, 593 insertions, 0 deletions
diff --git a/misc/freeswitch/scripts/event/call_history_save.lua b/misc/freeswitch/scripts/event/call_history_save.lua
new file mode 100644
index 0000000..057ca16
--- /dev/null
+++ b/misc/freeswitch/scripts/event/call_history_save.lua
@@ -0,0 +1,74 @@
+-- Gemeinschaft 5 module: call_history event handler class
+-- (c) AMOOMA GmbH 2012
+--
+
+module(...,package.seeall)
+
+function handler_class()
+ return CallHistorySave
+end
+
+CallHistorySave = {}
+
+
+function CallHistorySave.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self);
+ self.__index = self;
+ self.log = arg.log;
+ self.class = 'callhistorysave'
+ self.database = arg.database;
+ self.domain = arg.domain;
+
+ return object;
+end
+
+
+function CallHistorySave.event_handlers(self)
+ return { CHANNEL_DESTROY = { [true] = self.channel_destroy } }
+end
+
+
+function CallHistorySave.channel_destroy(self, event)
+ local uuid = event:getHeader('Unique-ID');
+ local direction = event:getHeader('variable_direction');
+
+ require 'common.str'
+ local save_cdr = common.str.to_b(event:getHeader('variable_gs_save_cdr'));
+
+ if not save_cdr then
+ self.log:debug('[', uuid,'] CALL_HISTORY_SAVE - event: CHANNEL_DESTROY, direction: ', direction, ', save_cdr: ', save_cdr);
+ return false;
+ end
+
+ require 'common.call_history'
+ call_history_class = common.call_history.CallHistory:new{ log = self.log, database = self.database }
+
+ -- caller entry
+ local account_type = event:getHeader('variable_gs_account_type');
+ local account_id = common.str.to_i(event:getHeader('variable_gs_account_id'));
+
+ if account_type and account_id > 0 and common.str.to_b(event:getHeader('variable_gs_account_node_local')) then
+ call_history_class:insert_event(uuid, account_type, account_id, 'dialed', event);
+ else
+ self.log:info('[', uuid,'] CALL_HISTORY_SAVE - ignore caller entry - account: ', account_type, '=', account_id, ', local: ', event:getHeader('variable_gs_account_node_local'));
+ end
+
+ -- callee entry
+ local account_type = event:getHeader('variable_gs_destination_type');
+ local account_id = common.str.to_i(event:getHeader('variable_gs_destination_id'));
+
+ if account_type and account_id > 0
+ and common.str.to_b(event:getHeader('variable_gs_destination_node_local'))
+ and tostring(event:getHeader('variable_gs_call_service')) ~= 'pickup' then
+
+ if tostring(event:getHeader('variable_endpoint_disposition')) == 'ANSWER' then
+ call_history_class:insert_event(uuid, account_type, account_id, 'received', event);
+ else
+ call_history_class:insert_event(uuid, account_type, account_id, 'missed', event);
+ end
+ else
+ self.log:info('[', uuid,'] CALL_HISTORY_SAVE - ignore callee entry - account: ', account_type, '=', account_id, ', local: ', event:getHeader('variable_gs_destination_node_local'));
+ end
+end
diff --git a/misc/freeswitch/scripts/event/cdr_save.lua b/misc/freeswitch/scripts/event/cdr_save.lua
new file mode 100644
index 0000000..ed53aa3
--- /dev/null
+++ b/misc/freeswitch/scripts/event/cdr_save.lua
@@ -0,0 +1,105 @@
+-- Gemeinschaft 5 module: cdr event handler class
+-- (c) AMOOMA GmbH 2012
+--
+
+module(...,package.seeall)
+
+
+function handler_class()
+ return CdrSave
+end
+
+
+function camelize_type(account_type)
+ ACCOUNT_TYPES = {
+ sipaccount = 'SipAccount',
+ conference = 'Conference',
+ faxaccount = 'FaxAccount',
+ callthrough = 'Callthrough',
+ huntgroup = 'HuntGroup',
+ automaticcalldistributor = 'AutomaticCallDistributor',
+ }
+
+ return ACCOUNT_TYPES[account_type] or account_type;
+end
+
+
+CdrSave = {}
+
+
+function CdrSave.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self);
+ self.__index = self;
+ self.log = arg.log;
+ self.class = 'cdrsave'
+ self.database = arg.database;
+ self.domain = arg.domain;
+
+ return object;
+end
+
+
+function CdrSave.event_handlers(self)
+ return { CHANNEL_DESTROY = { [true] = self.channel_destroy } }
+end
+
+
+function CdrSave.channel_destroy(self, event)
+ local uuid = event:getHeader('Unique-ID');
+ local direction = event:getHeader('variable_direction');
+
+ require 'common.str'
+ local save_cdr = common.str.to_b(event:getHeader('variable_gs_save_cdr'));
+
+ if not save_cdr then
+ self.log:debug('[', uuid,'] CDR_SAVE - event: CHANNEL_DESTROY, direction: ', direction, ', save_cdr: ', save_cdr);
+ return false;
+ end
+
+ require 'common.str'
+ local cdr = {}
+
+ cdr.uuid = common.str.to_sql(uuid);
+ cdr.bleg_uuid = common.str.to_sql(event:getHeader('variable_bridge_uuid'));
+ cdr.dialed_number = common.str.to_sql(event:getHeader('Caller-Destination-Number'));
+ cdr.destination_number = common.str.to_sql(event:getHeader('variable_gs_destination_number'));
+ cdr.caller_id_number = common.str.to_sql(event:getHeader('variable_effective_caller_id_number'));
+ cdr.caller_id_name = common.str.to_sql(event:getHeader('variable_effective_caller_id_name'));
+ cdr.callee_id_number = common.str.to_sql(event:getHeader('variable_effective_callee_id_number'));
+ cdr.callee_id_name = common.str.to_sql(event:getHeader('variable_effective_callee_id_name'));
+ cdr.start_stamp = 'FROM_UNIXTIME(' .. math.floor(common.str.to_i(event:getHeader('Caller-Channel-Created-Time')) / 1000000) .. ')';
+ cdr.answer_stamp = 'FROM_UNIXTIME(' .. math.floor(common.str.to_i(event:getHeader('Caller-Channel-Answered-Time')) / 1000000) .. ')';
+ cdr.end_stamp = 'FROM_UNIXTIME(' .. math.floor(common.str.to_i(event:getHeader('Caller-Channel-Hangup-Time')) / 1000000) .. ')';
+ cdr.bridge_stamp = common.str.to_sql(event:getHeader('variable_bridge_stamp'));
+ cdr.duration = common.str.to_sql(event:getHeader('variable_duration'));
+ cdr.billsec = common.str.to_sql(event:getHeader('variable_billsec'));
+ cdr.hangup_cause = common.str.to_sql(event:getHeader('variable_hangup_cause'));
+ cdr.dialstatus = common.str.to_sql(event:getHeader('variable_DIALSTATUS'));
+ cdr.forwarding_number = common.str.to_sql(event:getHeader('variable_gs_forwarding_number'));
+ cdr.forwarding_service = common.str.to_sql(event:getHeader('variable_gs_forwarding_service'));
+ cdr.forwarding_account_id = common.str.to_sql(event:getHeader('variable_gs_auth_account_id'));
+ cdr.forwarding_account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_auth_account_type')));
+ cdr.account_id = common.str.to_sql(event:getHeader('variable_gs_account_id'));
+ cdr.account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_account_type')));
+ cdr.bleg_account_id = common.str.to_sql(event:getHeader('variable_gs_destination_id'));
+ cdr.bleg_account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_destination_type')));
+
+ local keys = {}
+ local values = {}
+
+ for key, value in pairs(cdr) do
+ table.insert(keys, key);
+ table.insert(values, value);
+ end
+
+ self.log:info('[', uuid,'] CDR_SAVE - account: ', cdr.account_type, '=', cdr.account_id,
+ ', caller: ', cdr.caller_id_number, ' ', cdr.caller_id_name,
+ ', callee: ', cdr.callee_id_number, ' ', cdr.callee_id_name,
+ ', cause: ', cdr.hangup_cause
+ );
+
+ local sql_query = 'INSERT INTO `cdrs` (`' .. table.concat(keys, "`, `") .. '`) VALUES (' .. table.concat(values, ", ") .. ')';
+ return self.database:query(sql_query);
+end
diff --git a/misc/freeswitch/scripts/event/event.lua b/misc/freeswitch/scripts/event/event.lua
new file mode 100644
index 0000000..8e67bc9
--- /dev/null
+++ b/misc/freeswitch/scripts/event/event.lua
@@ -0,0 +1,109 @@
+-- Gemeinschaft 5 module: event manager class
+-- (c) AMOOMA GmbH 2012
+--
+
+module(...,package.seeall)
+
+EventManager = {}
+
+-- create event manager object
+function EventManager.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self);
+ self.__index = self;
+ self.log = arg.log;
+ self.class = 'eventmanager'
+ self.database = arg.database;
+ self.domain = arg.domain;
+
+ return object;
+end
+
+
+function EventManager.register(self)
+ self.consumer = freeswitch.EventConsumer('all');
+ return (self.consumer ~= nil);
+end
+
+
+function EventManager.load_event_modules(self)
+ local CONFIG_FILE_NAME = '/opt/freeswitch/scripts/ini/events.ini';
+
+ require 'common.configuration_file'
+ self.config = common.configuration_file.get(CONFIG_FILE_NAME);
+
+ return self.config.modules;
+end
+
+
+function EventManager.load_event_handlers(self, event_modules)
+ event_handlers = {}
+
+ for index, event_module_name in ipairs(event_modules) do
+ event_module = require('event.' .. event_module_name);
+ if event_module then
+ self.log:info('[event] EVENT_MANAGER - loading handler module: ', event_module_name);
+ handler_class = event_module.handler_class();
+
+ if handler_class then
+ module_event_handlers = handler_class:new{ log = self.log, database = self.database, domain = self.domain }:event_handlers();
+ if module_event_handlers then
+ for event_name, event_subclasses in pairs(module_event_handlers) do
+ if not event_handlers[event_name] then
+ event_handlers[event_name] = {};
+ end
+
+ for event_subclass, module_event_handler in pairs(event_subclasses) do
+ if not event_handlers[event_name][event_subclass] then
+ event_handlers[event_name][event_subclass] = {};
+ end
+
+ table.insert(event_handlers[event_name][event_subclass], { class = handler_class, method = module_event_handler } );
+ self.log:info('[event] EVENT_MANAGER - module: ', event_module_name, ', handling events: ', event_name, ', subclass:', event_subclass);
+ end
+ end
+ end
+ end
+ end
+ end
+
+ return event_handlers;
+end
+
+
+function EventManager.run(self)
+
+ local event_modules = self:load_event_modules();
+ local event_handlers = self:load_event_handlers(event_modules);
+
+ if not event_handlers then
+ self.log:error('[event] EVENT_MANAGER - no handlers specified');
+ return nil;
+ end
+
+ if not self:register() then
+ return nil;
+ end
+
+ freeswitch.setGlobalVariable('gs_event_manager', 'true');
+ while freeswitch.getGlobalVariable('gs_event_manager') == 'true' do
+ local event = self.consumer:pop(1, 100);
+ if event then
+ local event_type = event:getType();
+ local event_subclass = event:getHeader('Event-Subclass');
+ if event_handlers[event_type] then
+ if event_handlers[event_type][event_subclass] and #event_handlers[event_type][event_subclass] > 0 then
+ for index, event_handler in ipairs(event_handlers[event_type][event_subclass]) do
+ event_handler.method(event_handler.class, event);
+ end
+ end
+ if event_handlers[event_type][true] and #event_handlers[event_type][true] > 0 then
+ for index, event_handler in ipairs(event_handlers[event_type][true]) do
+ event_handler.method(event_handler.class, event);
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/misc/freeswitch/scripts/event/perimeter.lua b/misc/freeswitch/scripts/event/perimeter.lua
new file mode 100644
index 0000000..3babba6
--- /dev/null
+++ b/misc/freeswitch/scripts/event/perimeter.lua
@@ -0,0 +1,106 @@
+-- Gemeinschaft 5 module: cdr event handler class
+-- (c) AMOOMA GmbH 2012
+--
+
+module(...,package.seeall)
+
+
+function handler_class()
+ return Perimeter
+end
+
+
+
+Perimeter = {}
+
+MALICIOUS_CONTACT_COUNT = 20;
+MALICIOUS_CONTACT_TIME_SPAN = 2;
+BAN_FUTILE = 2;
+
+function Perimeter.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self);
+ self.__index = self;
+ self.log = arg.log;
+ self.class = 'cdrsave'
+ self.database = arg.database;
+ self.domain = arg.domain;
+
+ self.ip_address_table = {}
+ self:init();
+
+ return object;
+end
+
+
+function Perimeter.event_handlers(self)
+ return { CUSTOM = { ['sofia::pre_register'] = self.sofia_pre_register } }
+end
+
+
+function Perimeter.init(self)
+ local config = common.configuration_file.get('/opt/freeswitch/scripts/ini/perimeter.ini');
+ if config and config.general then
+ self.malicious_contact_count = tonumber(config.general.malicious_contact_count) or MALICIOUS_CONTACT_COUNT;
+ self.malicious_contact_time_span = tonumber(config.general.malicious_contact_time_span) or MALICIOUS_CONTACT_TIME_SPAN;
+ self.ban_futile = tonumber(config.general.ban_futile) or BAN_FUTILE;
+ self.execute = config.general.execute;
+ end
+
+ self.log:info('[perimeter] PERIMETER - setup perimeter defense - config: ', self.malicious_contact_count, '/', self.malicious_contact_time_span, ', execute: ', self.execute);
+end
+
+
+function Perimeter.sofia_pre_register(self, event)
+ local ip_address = event:getHeader('network-ip');
+ self:check_ip(ip_address);
+end
+
+
+function Perimeter.check_ip(self, ip_address)
+ local event_time = os.time();
+
+ if not self.ip_address_table[ip_address] then
+ self.ip_address_table[ip_address] = { last_contact = event_time, contact_count = 0, start_stamp = event_time, banned = 0 }
+ end
+
+ local ip_record = self.ip_address_table[ip_address];
+ ip_record.last_contact = event_time;
+ ip_record.contact_count = ip_record.contact_count + 1;
+
+ if ip_record.contact_count > MALICIOUS_CONTACT_COUNT then
+ if (event_time - ip_record.start_stamp) <= MALICIOUS_CONTACT_TIME_SPAN then
+ self.log:warning('[', ip_address, '] PERIMETER - too many registration attempts');
+ ip_record.start_stamp = event_time;
+ ip_record.contact_count = 0;
+ if ip_record.banned < BAN_FUTILE then
+ ip_record.banned = ip_record.banned + 1;
+ self:ban_ip(ip_address);
+ else
+ self.log:error('[', ip_address, '] PERIMETER - ban futile');
+ end
+ end
+ end
+end
+
+
+function Perimeter.ban_ip(self, ip_address)
+ self.ip_address = ip_address;
+
+ if self.execute then
+ local command = self:expand_variables(self.execute);
+ self.log:debug('[', ip_address, '] PERIMETER - execute: ', command);
+ local result = os.execute(command);
+ if tostring(result) == '0' then
+ self.log:warning('[', ip_address, '] PERIMETER - IP banned');
+ end
+ end
+end
+
+
+function Perimeter.expand_variables(self, line)
+ return (line:gsub('{([%a%d_-]+)}', function(captured)
+ return self[captured];
+ end))
+end
diff --git a/misc/freeswitch/scripts/event/presence_update.lua b/misc/freeswitch/scripts/event/presence_update.lua
new file mode 100644
index 0000000..01ec17b
--- /dev/null
+++ b/misc/freeswitch/scripts/event/presence_update.lua
@@ -0,0 +1,199 @@
+
+module(...,package.seeall)
+
+function handler_class()
+ return PresenceUpdate
+end
+
+ACCOUNT_RECORD_TIMEOUT = 120;
+
+PresenceUpdate = {}
+
+function PresenceUpdate.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self);
+ self.__index = self;
+ self.log = arg.log;
+ self.class = 'presenceupdate'
+ self.database = arg.database;
+ self.domain = arg.domain;
+ self.presence_accounts = {}
+ self.account_record = {}
+
+ return object;
+end
+
+
+function PresenceUpdate.event_handlers(self)
+ return {
+ PRESENCE_PROBE = { [true] = self.presence_probe },
+ CUSTOM = { ['sofia::register'] = self.sofia_register, ['sofia::unregister'] = self.sofia_ungerister },
+ PRESENCE_IN = { [true] = self.presence_in },
+ }
+end
+
+
+function PresenceUpdate.presence_probe(self, event)
+ local DIALPLAN_FUNCTION_PATTERN = '^f[_%-].*';
+
+ require 'common.str'
+ local event_to = event:getHeader('to');
+ local event_from = event:getHeader('from');
+ local probe_type = event:getHeader('probe-type');
+ local account, domain = common.str.partition(event_from, '@');
+ local subscription, domain = common.str.partition(event_to, '@');
+
+ self.log:debug('[', account, '] PRESENCE_UPDATE - subscription: ', subscription,', type: ', probe_type);
+ if (not self.presence_accounts[account] or not self.presence_accounts[account][subscription]) and subscription:find(DIALPLAN_FUNCTION_PATTERN) then
+ if not self.presence_accounts[account] then
+ self.presence_accounts[account] = {};
+ end
+ if not self.presence_accounts[account][subscription] then
+ self.presence_accounts[account][subscription] = {};
+ end
+ self:update_function_presence(account, domain, subscription);
+ end
+end
+
+
+function PresenceUpdate.sofia_register(self, event)
+ local account = event:getHeader('from-user');
+ self.log:debug('[', account, '] PRESENCE_UPDATE - flushing account cache on register');
+ self.presence_accounts[account] = nil;
+end
+
+
+function PresenceUpdate.sofia_ungerister(self, event)
+ local account = event:getHeader('from-user');
+ self.log:debug('[', account, '] PRESENCE_UPDATE - flushing account cache on unregister');
+ self.presence_accounts[account] = nil;
+end
+
+
+function PresenceUpdate.presence_in(self, event)
+ if not event:getHeader('status') then
+ return
+ end
+
+ local account, domain = common.str.partition(event:getHeader('from'), '@');
+ local direction = tostring(event:getHeader('presence-call-direction'))
+ local state = event:getHeader('presence-call-info-state');
+ local uuid = event:getHeader('Unique-ID');
+ local caller_id = event:getHeader('Caller-Caller-ID-Number');
+
+ if direction == 'inbound' then
+ self.log:info('[', uuid,'] PRESENCE_INBOUND: account: ', account, ', state: ', state);
+ self:sip_account(true, account, domain, state, uuid);
+ elseif direction == 'outbound' then
+ self.log:info('[', uuid,'] PRESENCE_OUTBOUND: account: ', account, ', state: ', state, ', caller: ', caller_id);
+ self:sip_account(false, account, domain, state, uuid, caller_id);
+ end
+end
+
+
+function PresenceUpdate.update_function_presence(self, account, domain, subscription)
+ local parameters = common.str.to_a(subscription, '_%-');
+ local fid = parameters[2];
+ local function_parameter = parameters[3];
+
+ if not fid then
+ self.log:error('[', account, '] PRESENCE_UPDATE - no function specified');
+ return;
+ end
+
+ if fid == 'cftg' and tonumber(function_parameter) then
+ self:call_forwarding(account, domain, function_parameter);
+ elseif fid == 'hgmtg' then
+ self:hunt_group_membership(account, domain, function_parameter);
+ elseif fid == 'acdmtg' then
+ self:acd_membership(account, domain, function_parameter);
+ end
+
+end
+
+
+function PresenceUpdate.call_forwarding(self, account, domain, call_forwarding_id)
+ require 'common.call_forwarding'
+ local call_forwarding = common.call_forwarding.CallForwarding:new{ log=self.log, database=self.database, domain=domain }:find_by_id(call_forwarding_id);
+
+ require 'common.str'
+ if call_forwarding and common.str.to_b(call_forwarding.record.active) then
+ local destination_type = tostring(call_forwarding.record.call_forwardable_type):lower()
+
+ self.log:debug('[', account, '] PRESENCE_UPDATE - updating call forwarding presence - id: ', call_forwarding_id, ', destination: ', destination_type);
+
+ if destination_type == 'voicemail' then
+ call_forwarding:presence_set('early');
+ else
+ call_forwarding:presence_set('confirmed');
+ end
+ end
+end
+
+
+function PresenceUpdate.hunt_group_membership(self, account, domain, member_id)
+ local sql_query = 'SELECT `active` FROM `hunt_group_members` WHERE `active` IS TRUE AND `id`=' .. tonumber(member_id) .. ' LIMIT 1';
+ local status = self.database:query_return_value(sql_query);
+
+ if status then
+ self.log:debug('[', account, '] PRESENCE_UPDATE - updating hunt group membership presence - id: ', member_id);
+ require 'dialplan.presence'
+ local presence_class = dialplan.presence.Presence:new{
+ log = self.log,
+ database = self.database,
+ domain = domain,
+ accounts = {'f-hgmtg-' .. member_id},
+ uuid = 'hunt_group_member_' .. member_id
+ }:set('confirmed');
+ end
+end
+
+
+function PresenceUpdate.acd_membership(self, account, domain, member_id)
+ local sql_query = 'SELECT `status` FROM `acd_agents` WHERE `status` = "active" AND `id`=' .. tonumber(member_id) .. ' LIMIT 1';
+ local status = self.database:query_return_value(sql_query);
+
+ if status then
+ self.log:debug('[', account, '] PRESENCE_UPDATE - updating ACD membership presence - id: ', member_id);
+ require 'dialplan.presence'
+ local presence_class = dialplan.presence.Presence:new{
+ log = self.log,
+ database = self.database,
+ domain = domain,
+ accounts = {'f-acdmtg-' .. member_id},
+ uuid = 'acd_agent_' .. member_id
+ }:set(status);
+ end
+end
+
+
+function PresenceUpdate.sip_account(self, inbound, account, domain, status, uuid, caller_id)
+ local status_map = { progressing = 'early', alerting = 'confirmed', active = 'confirmed' }
+
+ if not self.account_record[account] or ((os.time() - self.account_record[account].created_at) > ACCOUNT_RECORD_TIMEOUT) then
+ self.log:debug('[', uuid,'] PRESENCE - retrieve account data - account: ', account);
+
+ require 'common.sip_account'
+ local sip_account = common.sip_account.SipAccount:new{ log = self.log, database = self.database }:find_by_auth_name(account);
+
+ if not sip_account then
+ return
+ end
+
+ require 'common.phone_number'
+ local phone_numbers = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database }:list_by_owner(sip_account.id, sip_account.class);
+
+ self.account_record[account] = { id = sip_account.id, class = sip_account.class, phone_numbers = phone_numbers, created_at = os.time() }
+ end
+
+ require 'dialplan.presence'
+ local result = dialplan.presence.Presence:new{
+ log = self.log,
+ database = self.database,
+ inbound = inbound,
+ domain = domain,
+ accounts = self.account_record[account].phone_numbers,
+ uuid = uuid
+ }:set(status_map[status] or 'terminated', caller_id);
+end