diff options
Diffstat (limited to 'misc/freeswitch/scripts/event')
-rw-r--r-- | misc/freeswitch/scripts/event/call_history_save.lua | 74 | ||||
-rw-r--r-- | misc/freeswitch/scripts/event/cdr_save.lua | 105 | ||||
-rw-r--r-- | misc/freeswitch/scripts/event/event.lua | 109 | ||||
-rw-r--r-- | misc/freeswitch/scripts/event/perimeter.lua | 106 | ||||
-rw-r--r-- | misc/freeswitch/scripts/event/presence_update.lua | 199 |
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 |