From b80bd744ad873f6fc43018bc4bfb90677de167bd Mon Sep 17 00:00:00 2001 From: Stefan Wintermeyer Date: Mon, 17 Dec 2012 12:01:45 +0100 Subject: Start of GS5. --- misc/freeswitch/scripts/dialplan/dialplan.lua | 996 ++++++++++++++++++++++++++ 1 file changed, 996 insertions(+) create mode 100644 misc/freeswitch/scripts/dialplan/dialplan.lua (limited to 'misc/freeswitch/scripts/dialplan/dialplan.lua') diff --git a/misc/freeswitch/scripts/dialplan/dialplan.lua b/misc/freeswitch/scripts/dialplan/dialplan.lua new file mode 100644 index 0000000..f4dca9e --- /dev/null +++ b/misc/freeswitch/scripts/dialplan/dialplan.lua @@ -0,0 +1,996 @@ +-- Gemeinschaft 5 module: dialplan class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +Dialplan = {} + +-- local constants +local CONFIG_FILE_NAME = '/opt/freeswitch/scripts/ini/dialplan.ini'; +local DIAL_TIMEOUT = 120; +local MAX_LOOPS = 20; +local DIALPLAN_FUNCTION_PATTERN = '^f[_%-].*'; +local CALL_FORWARDING_SERVICES = { + USER_BUSY = 'busy', + CALL_REJECTED = 'busy', + NO_ANSWER = 'noanswer', + USER_NOT_REGISTERED = 'offline', + HUNT_GROUP_EMPTY = 'offline', + ACD_NO_AGENTS = 'offline', + ACD_TIMEOUT = 'noanswer', +} + +-- create dialplan object +function Dialplan.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.log = arg.log; + self.database = arg.database; + self.caller = arg.caller; + + return object; +end + + +function Dialplan.domain_get(self, domain) + require 'common.str' + local global_domain = freeswitch.API():execute('global_getvar', 'domain'); + + if common.str.blank(global_domain) then + if common.str.blank(domain) then + require 'common.database' + local database = common.database.Database:new{ log = self.log }:connect(); + if not database:connected() then + self.log:error('[', uuid,'] DIALPLAN_DOMAIN - cannot connect to Gemeinschaft database'); + else + require 'configuration.sip' + local domains = configuration.sip.Sip:new{ log = self.log, database = database }:domains(); + if domains[1] then + domain = domains[1]['host']; + end + end + end + + if database then + database:release(); + end + + if not common.str.blank(domain) then + self.log:notice('DIALPLAN_DOMAIN - setting default domain: ', domain); + freeswitch.API():execute('global_setvar', 'domain=' .. tostring(domain)); + end + else + domain = global_domain; + end + + if common.str.blank(domain) then + self.log:error('DIALPLAN_DOMAIN - no domain found'); + end + + return domain; +end + + +function Dialplan.configuration_read(self, file_name) + require 'common.str' + require 'common.configuration_file' + + -- dialplan configuration + self.config = common.configuration_file.get(file_name or CONFIG_FILE_NAME); + self.node_id = common.str.to_i(self.config.parameters.node_id); + self.domain = self:domain_get(self.config.parameters.domain); + self.dial_timeout = tonumber(self.config.parameters.dial_timeout) or DIAL_TIMEOUT; + self.max_loops = tonumber(self.config.parameters.max_loops) or MAX_LOOPS; + self.user_image_url = common.str.to_s(self.config.parameters.user_image_url); + self.phone_book_entry_image_url = common.str.to_s(self.config.parameters.phone_book_entry_image_url); + self.phonebook_number_lookup = common.str.to_b(self.config.parameters.phonebook_number_lookup); + self.geo_number_lookup = common.str.to_b(self.config.parameters.geo_number_lookup); + self.default_language = self.config.parameters.default_language or 'en'; + self.send_ringing_to_gateways = common.str.to_b(self.config.parameters.send_ringing_to_gateways); + + if tonumber(self.config.parameters.default_ringtone) then + self.default_ringtone = 'http://amooma.de;info=Ringer' .. self.config.parameters.default_ringtone .. ';x-line-id=0'; + else + self.default_ringtone = 'http://amooma.de;info=Ringer1;x-line-id=0'; + end + + return (self.config ~= nil); +end + + +function Dialplan.hangup(self, code, phrase, cause) + if self.caller:ready() then + if tonumber(code) then + self.caller:respond(code, phrase or 'Thank you for flying Gemeinschaft5'); + end + self.caller:hangup(cause or 16); + else + self.log:info('HANGUP - caller sesson down - cause: ', self.caller.session:hangupCause()); + end +end + + +function Dialplan.check_auth(self) + local authenticated = false; + + require 'common.str' + if self.caller.from_node then + self.log:info('AUTH_FIRST_STAGE - node authenticated - node_id: ', self.caller.node_id); + authenticated = true; + elseif not common.str.blank(self.caller.auth_account_type) then + self.log:info('AUTH_FIRST_STAGE - sipaccount autheticated by name/password: ', self.caller.auth_account_type, '=', self.caller.account_id, '/', self.caller.account_uuid); + authenticated = true; + elseif self.caller.from_gateway then + self.log:info('AUTH_FIRST_STAGE - gateway autheticated by name/password: gateway=', self.caller.gateway_id, ', name: ', self.caller.gateway_name); + authenticated = true; + else + local gateways = common.configuration_file.get('/opt/freeswitch/scripts/ini/gateways.ini', false); + if not gateways then + return false; + end + for gateway, gateway_parameters in pairs(gateways) do + if common.str.to_s(gateway_parameters.proxy) == self.caller.sip_contact_host then + self.caller.gateway_name = gateway; + self.caller.from_gateway = true; + self.log:info('AUTH_FIRST_STAGE - gateway autheticated by ip: gateway=', self.caller.gateway_id, ', name: ', self.caller.gateway_name, ', ip: ', self.caller.sip_contact_host); + authenticated = true; + end + end + end + + return authenticated; +end + + +function Dialplan.check_auth_node(self) + require 'common.node' + local node = common.node.Node:new{ log = self.log, database = self.database }:find_by_address(self.caller.sip_contact_host); + + return (node ~= nil); +end + + +function Dialplan.check_auth_ip(self) + self.log:info('AUTH - node: ', self.caller.from_node, ', auth_account: ', self.caller.auth_account_type, ', gateway: ', self.caller.from_gateway); + require 'common.str' + if self.caller.from_node then + return true; + elseif not common.str.blank(self.caller.auth_account_type) then + return true; + elseif self.caller.from_gateway then + return true; + else + return nil; + end +end + + +function Dialplan.object_find(self, class, identifier, auth_name) + require 'common.str' + class = common.str.downcase(class); + + if class == 'user' then + require 'dialplan.user' + local user = nil; + if type(identifier) == 'number' then + user = dialplan.user.User:new{ log = self.log, database = self.database }:find_by_id(identifier); + else + user = dialplan.user.User:new{ log = self.log, database = self.database }:find_by_uuid(identifier); + end + + if user then + user.groups = user:list_groups(); + end + + return user; + elseif class == 'tenant' then + require 'dialplan.tenant' + local tenant = nil; + if type(identifier) == 'number' then + tenant = dialplan.tenant.Tenant:new{ log = self.log, database = self.database }:find_by_id(identifier); + else + tenant = dialplan.tenant.Tenant:new{ log = self.log, database = self.database }:find_by_uuid(identifier); + end + + return tenant; + elseif class == 'sipaccount' then + require 'common.sip_account' + local sip_account = nil; + if auth_name then + sip_account = common.sip_account.SipAccount:new{ log = self.log, database = self.database }:find_by_auth_name(auth_name, identifier); + elseif type(identifier) == 'number' then + sip_account = common.sip_account.SipAccount:new{ log = self.log, database = self.database }:find_by_id(identifier); + else + sip_account = common.sip_account.SipAccount:new{ log = self.log, database = self.database }:find_by_uuid(identifier); + end + if sip_account then + sip_account.owner = self:object_find(sip_account.record.sip_accountable_type, tonumber(sip_account.record.sip_accountable_id)); + end + return sip_account; + elseif class == 'huntgroup' then + require 'dialplan.hunt_group' + + local hunt_group = nil; + if type(identifier) == 'number' then + hunt_group = dialplan.hunt_group.HuntGroup:new{ log = self.log, database = self.database }:find_by_id(identifier); + else + hunt_group = dialplan.hunt_group.HuntGroup:new{ log = self.log, database = self.database }:find_by_uuid(identifier); + end + + if hunt_group then + hunt_group.owner = self:object_find('tenant', tonumber(hunt_group.record.tenant_id)); + end + + return hunt_group; + elseif class == 'automaticcalldistributor' then + require 'dialplan.acd' + + local acd = nil; + if type(identifier) == 'number' then + acd = dialplan.acd.AutomaticCallDistributor:new{ log = self.log, database = self.database, domain = self.domain }:find_by_id(identifier); + else + acd = dialplan.acd.AutomaticCallDistributor:new{ log = self.log, database = self.database, domain = self.domain }:find_by_uuid(identifier); + end + + if acd then + acd.owner = self:object_find(acd.record.automatic_call_distributorable_type, tonumber(acd.record.automatic_call_distributorable_id)); + end + + return acd; + elseif class == 'faxaccount' then + require 'dialplan.fax' + local fax_account = nil; + if type(identifier) == 'number' then + fax_account = dialplan.fax.Fax:new{ log = self.log, database = self.database }:find_by_id(identifier); + else + fax_account = dialplan.fax.Fax:new{ log = self.log, database = self.database }:find_by_uuid(identifier); + end + if fax_account then + fax_account.owner = self:object_find(fax_account.record.fax_accountable_type, tonumber(fax_account.record.fax_accountable_id)); + end + + return fax_account; + end +end + + +function Dialplan.retrieve_caller_data(self) + self.caller.caller_phone_numbers_hash = {} + + require 'common.str' + + local dialed_sip_user = self.caller:to_s('dialed_user'); + + -- TODO: Set auth_account on transfer initiated by calling party + if not common.str.blank(dialed_sip_user) then + self.caller.auth_account = self:object_find('sipaccount', self.caller:to_s('dialed_domain'), dialed_sip_user); + self.caller:set_auth_account(self.caller.auth_account); + elseif not common.str.blank(self.caller.auth_account_type) and not common.str.blank(self.caller.auth_account_uuid) then + self.caller.auth_account = self:object_find(self.caller.auth_account_type, self.caller.auth_account_uuid); + self.caller:set_auth_account(self.caller.auth_account); + end + + if self.caller.auth_account then + self.log:info('CALLER_DATA - auth account: ', self.caller.auth_account.class, '=', self.caller.auth_account.id, '/', self.caller.auth_account.uuid); + if self.caller.auth_account.owner then + self.log:info('CALLER_DATA - auth owner: ', self.caller.auth_account.owner.class, '=', self.caller.auth_account.owner.id, '/', self.caller.auth_account.owner.uuid); + else + self.log:error('CALLER_DATA - auth owner not found'); + end + else + self.log:info('CALLER_DATA - no data - unauthenticated call: ', self.caller.auth_account_type, '/', self.caller.auth_account_uuid); + end + + if not common.str.blank(self.caller.account_type) and not common.str.blank(self.caller.account_uuid) then + self.caller.account = self:object_find(self.caller.account_type, self.caller.account_uuid); + if self.caller.account then + require 'common.phone_number' + self.caller.caller_phone_numbers = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database }:list_by_owner(self.caller.account.id, self.caller.account.class); + for index, caller_number in ipairs(self.caller.caller_phone_numbers) do + self.caller.caller_phone_numbers_hash[caller_number] = true; + end + self.log:info('CALLER_DATA - caller account: ', self.caller.account.class, '=', self.caller.account.id, '/', self.caller.account.uuid, ', phone_numbers: ', #self.caller.caller_phone_numbers); + if self.caller.account.owner then + self.log:info('CALLER_DATA - caller owner: ', self.caller.account.owner.class, '=', self.caller.account.owner.id, '/', self.caller.account.owner.uuid); + else + self.log:error('CALLER_DATA - caller owner not found'); + end + + if not self.caller.clir then + self.caller:set_caller_id(self.caller.caller_phone_numbers[1], self.caller.account.record.caller_name or self.caller.account.record.name); + end + else + self.log:error('CALLER_DATA - caller account not found: ', self.caller.account_type, '/', self.caller.account_uuid); + end + end +end + + +function Dialplan.destination_new(self, arg) + require 'common.str' + + local destination = { + number = arg.number or '', + type = arg.type or 'unknown', + id = common.str.to_i(arg.id), + uuid = arg.uuid or '', + phone_number = arg.phone_number, + node_id = common.str.to_i(arg.node_id), + call_forwarding = {}, + data = arg.data, + } + + destination.type = common.str.downcase(destination.type); + + if not common.str.blank(destination.number) then + if destination.type == 'unknown' and destination.number:find(DIALPLAN_FUNCTION_PATTERN) then + destination.type = 'dialplanfunction'; + elseif destination.type == 'phonenumber' or destination.type == 'unknown' then + require 'common.phone_number' + destination.phone_number = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database }:find_by_number(destination.number); + + if destination.phone_number then + destination.type = common.str.downcase(destination.phone_number.record.phone_numberable_type); + destination.id = common.str.to_i(destination.phone_number.record.phone_numberable_id); + destination.uuid = common.str.to_s(destination.phone_number.record.phone_numberable_uuid); + destination.node_id = common.str.to_i(destination.phone_number.record.gs_node_id); + if self.caller then + destination.call_forwarding = destination.phone_number:call_forwarding(self.caller.caller_phone_numbers); + end + elseif destination.type == 'unknown' then + require 'common.sip_account' + destination.account = common.sip_account.SipAccount:new{ log = self.log, database = self.database }:find_by_auth_name(destination.number); + if destination.account then + destination.type = 'sipaccount'; + destination.id = common.str.to_i(destination.account.record.id); + destination.uuid = common.str.to_s(destination.account.record.uuid); + destination.node_id = common.str.to_i(destination.account.record.gs_node_id); + end + end + end + end + + if destination.node_id == 0 then + destination.node_id = self.node_id; + destination.node_local = true; + else + destination.node_local = (destination.node_id == self.node_id); + end + + self.log:info('DESTINATION_NEW - ', destination.type, '=', destination.id, '/', destination.uuid,'@', destination.node_id, ', number: ', destination.number); + + return destination; +end + + +function Dialplan.routes_get(self, destination) + require 'dialplan.route' + return dialplan.route.Route:new{ log = self.log, database = self.database, routing_table = self.routes }:outbound(self.caller, destination.number); +end + + +function Dialplan.set_caller_picture(self, entry_id, entry_type, image) + entry_type = entry_type:lower(); + if entry_type == 'user' then + require 'dialplan.user' + local user = dialplan.user.User:new{ log = self.log, database = self.database }:find_by_id(entry_id); + if user then + self.caller:set_variable('sip_h_Call-Info', '<' .. self.user_image_url .. '/' .. tonumber(entry_id) .. '/snom_caller_picture_' .. tostring(user.record.image) .. '>;purpose=icon'); + end + elseif entry_type == 'phonebookentry' and image then + self.caller:set_variable('sip_h_Call-Info', '<' .. self.phone_book_entry_image_url .. '/' .. tonumber(entry_id) .. '/snom_caller_picture_' .. tostring(image) .. '>;purpose=icon'); + end +end + + +function Dialplan.dial(self, destination) + require 'common.str' + destination.caller_id_number = destination.caller_id_number or self.caller.caller_phone_numbers[1]; + + if not self.caller.clir then + if destination.node_local and destination.type == 'sipaccount' then + local user_id = nil; + local tenant_id = nil; + + destination.account = self:object_find(destination.type, destination.id); + if destination.account then + if destination.account.class == 'sipaccount' then + destination.callee_id_name = destination.account.record.caller_name; + self.caller:set_callee_id(destination.number, destination.account.record.caller_name); + end + end + + if destination.account and destination.account.owner then + if destination.account.owner.class == 'user' then + user_id = destination.account.owner.id; + tenant_id = tonumber(destination.account.owner.record.current_tenant_id); + elseif destination.account.owner.class == 'tenant' then + tenant_id = destination.account.owner.id; + end + end + + if user_id or tenant_id then + require 'common.str' + local phone_book_entry = nil; + + if self.phonebook_number_lookup then + require 'dialplan.phone_book' + phone_book_entry = dialplan.phone_book.PhoneBook:new{ log = self.log, database = self.database }:find_entry_by_number_user_tenant(self.caller.caller_phone_numbers, user_id, tenant_id); + end + + if phone_book_entry then + self.log:info('PHONE_BOOK_ENTRY - phone_book=', phone_book_entry.phone_book_id, ' (', phone_book_entry.phone_book_name, '), caller_id_name: ', phone_book_entry.caller_id_name, ', ringtone: ', phone_book_entry.bellcore_id); + destination.caller_id_name = common.str.to_ascii(phone_book_entry.caller_id_name); + if tonumber(phone_book_entry.bellcore_id) then + self.log:debug('RINGTONE - phonebookentry, index: ', phone_book_entry.bellcore_id); + self.caller:export_variable('alert_info', 'http://amooma.de;info=Ringer' .. phone_book_entry.bellcore_id .. ';x-line-id=0'); + end + if phone_book_entry.image then + self:set_caller_picture(phone_book_entry.id, 'phonebookentry', phone_book_entry.image); + elseif self.caller.account and self.caller.account.owner then + self:set_caller_picture(self.caller.account.owner.id, self.caller.account.owner.class); + end + elseif self.caller.account and self.caller.account.owner then + self:set_caller_picture(self.caller.account.owner.id, self.caller.account.owner.class); + elseif self.geo_number_lookup then + require 'dialplan.geo_number' + local geo_number = dialplan.geo_number.GeoNumber:new{ log = self.log, database = self.database }:find(destination.caller_id_number); + if geo_number then + self.log:info('GEO_NUMBER - found: ', geo_number.name, ', ', geo_number.country); + if geo_number.name then + destination.caller_id_name = common.str.to_ascii(geo_number.name) .. ', ' .. common.str.to_ascii(geo_number.country); + else + destination.caller_id_name = common.str.to_ascii(geo_number.country); + end + end + end + end + end + self.caller:set_caller_id(destination.caller_id_number, destination.caller_id_name or self.caller.caller_id_name); + else + self.caller:set_caller_id('anonymous', 'Unknown'); + self.caller:set_privacy(true); + end + + local destinations = { destination }; + + if self.caller.forwarding_service == 'assistant' and self.caller.auth_account and self.caller.auth_account.class == 'sipaccount' then + self.caller.auth_account.type = self.caller.auth_account.class; + local forwarding_destination = self:destination_new( self.caller.auth_account ); + if forwarding_destination then + forwarding_destination.alert_info = 'http://amooma.com;info=Ringer0;x-line-id=0' + table.insert(destinations, forwarding_destination); + end + end + + if common.str.to_b(self.config.parameters.bypass_media) then + self.caller:set_variable('bypass_media', true); + end + + require 'dialplan.sip_call' + return dialplan.sip_call.SipCall:new{ log = self.log, database = self.database, caller = self.caller }:fork( + destinations, + { timeout = self.dial_timeout_active, + send_ringing = ( self.send_ringing_to_gateways and self.caller.from_gateway ), + bypass_media_network = self.config.parameters.bypass_media_network, + } + ); +end + + +function Dialplan.huntgroup(self, destination) + local hunt_group = self:object_find('huntgroup', tonumber(destination.id)); + + if not hunt_group then + self.log:error('DIALPLAN_HUNTGROUP - huntgroup not found'); + return { continue = true, code = 404, phrase = 'Huntgroup not found' } + end + + self.caller:set_callee_id(destination.number, hunt_group.record.name); + destination.caller_id_number = destination.caller_id_number or self.caller.caller_phone_numbers[1]; + + if not self.caller.clir then + self.caller:set_caller_id(destination.caller_id_number, tostring(hunt_group.record.name) .. ' '.. tostring(self.caller.caller_id_name)); + if self.caller.account and self.caller.account.owner then + self:set_caller_picture(self.caller.account.owner.id, self.caller.account.owner.class); + end + else + self.caller:set_caller_id('anonymous', tostring(hunt_group.record.name)); + self.caller:set_privacy(true); + end + + self.caller.auth_account = hunt_group; + self.caller:set_auth_account(self.caller.auth_account); + self.caller.forwarding_number = destination.number; + self.caller.forwarding_service = 'huntgroup'; + self.caller:set_variable('gs_forwarding_service', self.caller.forwarding_service); + self.caller:set_variable('gs_forwarding_number', self.caller.forwarding_number); + return hunt_group:run(self, self.caller, destination); +end + + +function Dialplan.acd(self, destination) + local acd = self:object_find('automaticcalldistributor', tonumber(destination.id)); + + if not acd then + self.log:error('DIALPLAN_ACD - acd not found'); + return { continue = true, code = 404, phrase = 'ACD not found' } + end + + self.caller:set_callee_id(destination.number, acd.record.name); + destination.caller_id_number = destination.caller_id_number or self.caller.caller_phone_numbers[1]; + + if not self.caller.clir then + self.caller:set_caller_id(destination.caller_id_number, tostring(acd.record.name) .. ' '.. tostring(self.caller.caller_id_name)); + if self.caller.account and self.caller.account.owner then + self:set_caller_picture(self.caller.account.owner.id, self.caller.account.owner.class); + end + else + self.caller:set_caller_id('anonymous', tostring(acd.record.name)); + self.caller:set_privacy(true); + end + + self.caller.auth_account = acd; + self.caller:set_auth_account(self.caller.auth_account); + self.caller.forwarding_number = destination.number; + self.caller.forwarding_service = 'automaticcalldistributor'; + self.caller:set_variable('gs_forwarding_service', self.caller.forwarding_service); + self.caller:set_variable('gs_forwarding_number', self.caller.forwarding_number); + + acd:caller_new(self.caller.uuid); + local result = acd:run(self, self.caller, destination); + acd:caller_delete(); + + return result; +end + + +function Dialplan.conference(self, destination) + -- call local conference + require 'common.conference' + conference = common.conference.Conference:new{ log = self.log, database = self.database }:find_by_id(destination.id); + + if not conference then + return { continue = false, code = 404, phrase = 'Conference not found' } + end + + local cause = conference:enter(self.caller, self.domain); + return { continue = false, cause = cause } +end + + +function Dialplan.faxaccount(self, destination) + require 'dialplan.fax' + local fax_account = dialplan.fax.Fax:new{ log = self.log, database = self.database }:find_by_id(destination.id); + + if not fax_account then + return { continue = false, code = 404, phrase = 'Fax not found' } + end + + self.log:info('FAX_RECEIVE start - fax_account=', fax_account.id, '/', fax_account.uuid, ', name: ', fax_account.record.name, ', station_id: ', fax_account.record.station_id); + + self.caller:set_caller_id(self.caller.caller_phone_number); + self.caller:set_callee_id(destination.number, fax_account.record.name); + + local fax_document = fax_account:receive(self.caller); + + if not fax_document then + self.log:error('FAX_RECEIVE - error receiving fax document - fax_account=', fax_account.id, '/', fax_account.uuid); + return { continue = false, code = 500, phrase = 'Error receiving fax' }; + end + + fax_document.caller_id_number = self.caller.caller_phone_number; + fax_document.caller_id_name = self.caller.caller_id_name; + fax_document.uuid = self.caller.uuid; + + self.log:info('FAX_RECEIVE end - success: ', fax_document.success, + ', remote: ', fax_document.remote_station_id, + ', pages: ', fax_document.transferred_pages, '/', fax_document.total_pages, + ', result: ', fax_document.result_code, ' ', fax_document.result_text); + + if fax_document.success then + self.log:notice('FAX_RECEIVE - saving fax document: ', fax_document.filename ); + if not fax_account:insert_document(fax_document) then + self.log:error('FAX_RECEIVE - error inserting fax document to database - fax_account=', fax_account.id, '/', fax_account.uuid, ', file: ', fax_document.filename); + end + end + + return { continue = false, code = 200, phrase = 'OK' } +end + + +function Dialplan.callthrough(self, destination) + -- Callthrough + require 'dialplan.callthrough' + callthrough = dialplan.callthrough.Callthrough:new{ log = self.log, database = self.database }:find_by_id(destination.id) + + if not callthrough then + self.log:error('CALLTHROUGH - no callthrough for destination number: ', destination.number); + return { continue = false, code = 404, phrase = 'Fax not found' } + end + self.log:info('CALLTHROUGH - number: ' .. destination.number .. ', name: ' .. callthrough.record.name); + + local authorization = callthrough:authenticate(self.caller); + + if not authorization then + self.log:notice('CALLTHROUGH - authentication failed'); + return { continue = false, code = 403, phrase = 'Authentication failed' } + end + + if type(authorization) == 'table' and tonumber(authorization.sip_account_id) and tonumber(authorization.sip_account_id) > 0 then + local auth_account = self:object_find('sipaccount', tonumber(authorization.sip_account_id)); + self.caller.forwarding_number = destination.number; + self.caller.forwarding_service = 'callthrough'; + self.caller:set_variable('gs_forwarding_service', self.caller.forwarding_service); + self.caller:set_variable('gs_forwarding_number', self.caller.forwarding_number); + + if auth_account then + self.caller.auth_account = auth_account; + self.caller:set_auth_account(self.caller.auth_account); + self.log:info('AUTH_ACCOUNT_UPDATE - account: ', self.caller.auth_account.class, '=', self.caller.auth_account.id, '/', self.caller.auth_account.uuid); + if self.caller.auth_account.owner then + self.log:info('AUTH_ACCOUNT_UPDATE - auth owner: ', self.caller.auth_account.owner.class, '=', self.caller.auth_account.owner.id, '/', self.caller.auth_account.owner.uuid); + else + self.log:error('AUTH_ACCOUNT_UPDATE - auth owner not found'); + end + self.log:info('CALLTHROUGH - use sip account: ', auth_account.id, ' (', auth_account.record.caller_name, ')'); + end + else + self.log:info('CALLTHROUGH - no sip account'); + end + + local destination_number = ''; + for i = 1, 3, 1 do + if destination_number ~= '' then + break; + end + destination_number = session:read(2, 16, "ivr/ivr-enter_destination_telephone_number.wav", 3000, "#"); + end + + if destination_number == '' then + self.log:debug("no callthrough destination - hangup call"); + return { continue = false, code = 404, phrase = 'No destination' } + end + + local route = dialplan.route.Route:new{ log = self.log, database = self.database, routing_table = self.routes }:prerouting(self.caller, destination_number); + if route and route.value then + destination_number = route.value; + end + + if not callthrough:whitelist(destination_number) then + self.log:debug('caller not authorized to call destination number: ' .. destination_number .. ' - hangup call'); + return { continue = false, code = 403, phrase = 'Unauthorized' } + end + + return { continue = true, code = 302, number = destination_number } +end + + +function Dialplan.voicemail(self, destination) + if not self.caller.auth_account or self.caller.auth_account.class ~= 'sipaccount' then + self.log:error('VOICEMAIL - incompatible destination'); + return { continue = false, code = 404, phrase = 'Mailbox not found' } + end + + require 'dialplan.voicemail' + local voicemail_account = dialplan.voicemail.Voicemail:new{ log = self.log, database = self.database }:find_by_sip_account_id(self.caller.auth_account.id); + + if not voicemail_account then + self.log:error('VOICEMAIL - no mailbox'); + return { continue = false, code = 404, phrase = 'Mailbox not found' } + end + + voicemail_account:leave(self.caller, self.caller.forwarding_number); + + if self.caller:to_s("voicemail_message_len") == '' then + self.log:info('VOICEMAIL - no message saved'); + end + + return { continue = false, code = 200 } +end + + +function Dialplan.dialplanfunction(self, destination) + require 'dialplan.functions' + return dialplan.functions.Functions:new{ log = self.log, database = self.database, domain = self.domain }:dialplan_function(self.caller, destination.number); +end + + +function Dialplan.switch(self, destination) + require 'common.str' + local result = nil; + self.dial_timeout_active = self.dial_timeout; + + if not destination.node_local then + return self:dial(destination); + end + + for service, call_forwarding in pairs(destination.call_forwarding) do + if self.caller.caller_phone_numbers_hash[call_forwarding.number] then + self.log:info('CALL_FORWARDING - caller number equals destination: ', call_forwarding.number,' - ignore service: ', service); + destination.call_forwarding[service] = nil; + end + end + + if destination.call_forwarding.noanswer then + self.dial_timeout_active = tonumber(destination.call_forwarding.noanswer.timeout) or self.dial_timeout; + end + + if destination.call_forwarding.always then + return { continue = true, call_forwarding = destination.call_forwarding.always } + elseif destination.call_forwarding.assistant then + if common.str.downcase(destination.call_forwarding.assistant.type) == 'huntgroup' then + require 'dialplan.hunt_group' + local hunt_group = dialplan.hunt_group.HuntGroup:new{ log = self.log, database = self.database }:find_by_id(destination.call_forwarding.assistant.id); + self.log:info('CALL_FORWARDING - huntgroup - auth_account: ', self.caller.auth_account_type, '=', self.caller.auth_account_uuid); + if hunt_group and (hunt_group:is_member_by_numbers(self.caller.caller_phone_numbers)) then + self.log:info('CALL_FORWARDING - caller is huntgroup member - ignore service: ', destination.call_forwarding.assistant.service); + else + return { continue = true, call_forwarding = destination.call_forwarding.assistant } + end + else + return { continue = true, call_forwarding = destination.call_forwarding.assistant } + end + end + + -- reset ringtone + self.caller:export_variable('alert_info', self.default_ringtone); + + if destination.phone_number then + local ringtone = destination.phone_number:ringtone(); + if ringtone and ringtone.bellcore_id then + self.log:debug('RINGTONE - ', ringtone.ringtoneable_type .. ', index: ' .. ringtone.bellcore_id); + self.caller:export_variable('alert_info', 'http://amooma.de;info=Ringer' .. tonumber(ringtone.bellcore_id) .. ';x-line-id=0'); + end + end + + if destination.type == 'sipaccount' then + result = self:dial(destination); + if CALL_FORWARDING_SERVICES[result.disposition] then + result.call_forwarding = destination.call_forwarding[CALL_FORWARDING_SERVICES[result.disposition]]; + if result.call_forwarding then + result.continue = true; + end + end + return result; + elseif destination.type == 'conference' then + return self:conference(destination); + elseif destination.type == 'faxaccount' then + return self:faxaccount(destination); + elseif destination.type == 'callthrough' then + return self:callthrough(destination); + elseif destination.type == 'huntgroup' then + result = self:huntgroup(destination); + if CALL_FORWARDING_SERVICES[result.disposition] then + result.call_forwarding = destination.call_forwarding[CALL_FORWARDING_SERVICES[result.disposition]]; + if result.call_forwarding then + result.continue = true; + end + end + return result; + elseif destination.type == 'automaticcalldistributor' then + result = self:acd(destination); + if CALL_FORWARDING_SERVICES[result.disposition] then + result.call_forwarding = destination.call_forwarding[CALL_FORWARDING_SERVICES[result.disposition]]; + if result.call_forwarding then + result.continue = true; + end + end + return result; + elseif destination.type == 'voicemail' then + return self:voicemail(destination); + elseif destination.type == 'dialplanfunction' then + return self:dialplanfunction(destination); + elseif not common.str.blank(destination.number) then + local result = { continue = false, code = 404, phrase = 'No route' } + local routes = self:routes_get(destination); + + if not routes or #routes == 0 then + self.log:notice('SWITCH - no route - number: ', destination.number); + return { continue = false, code = 404, phrase = 'No route' } + end + + destination.callee_id_number = destination.number; + destination.callee_id_name = nil; + + if self.phonebook_number_lookup then + require 'common.str' + local user_id = common.str.try(self.caller, 'account.owner.id'); + local tenant_id = common.str.try(self.caller, 'account.owner.record.current_tenant_id'); + + if user_id or tenant_id then + require 'dialplan.phone_book' + local phone_book_entry = dialplan.phone_book.PhoneBook:new{ log = self.log, database = self.database }:find_entry_by_number_user_tenant({ destination.number }, user_id, tenant_id); + if phone_book_entry then + self.log:info('PHONE_BOOK_ENTRY - phone_book=', phone_book_entry.phone_book_id, ' (', phone_book_entry.phone_book_name, '), callee_id_name: ', common.str.to_ascii(phone_book_entry.caller_id_name)); + destination.callee_id_name = common.str.to_ascii(phone_book_entry.caller_id_name); + end + end + end + + if self.geo_number_lookup and not destination.callee_id_name then + require 'dialplan.geo_number' + local geo_number = dialplan.geo_number.GeoNumber:new{ log = self.log, database = self.database }:find(destination.number); + if geo_number then + require 'common.str' + self.log:info('GEO_NUMBER - found: ', geo_number.name, ', ', geo_number.country); + if geo_number.name then + destination.callee_id_name = common.str.to_ascii(geo_number.name) .. ', ' .. common.str.to_ascii(geo_number.country); + else + destination.callee_id_name = common.str.to_ascii(geo_number.country); + end + end + end + + self.caller:set_callee_id(destination.callee_id_number, destination.callee_id_name); + + for index, route in ipairs(routes) do + if route.class == 'hangup' then + return { continue = false, code = route.endpoint, phrase = route.phrase, cause = route.value } + end + if route.class == 'forward' then + return { continue = true, call_forwarding = { number = route.value, service = 'route', type = 'phonenumber' }} + end + destination.gateway = route.endpoint; + destination.type = route.class; + destination.number = route.value; + destination.caller_id_number = route.caller_id_number; + destination.caller_id_name = route.caller_id_name; + result = self:dial(destination); + + if result.continue == false then + break; + end + + if common.str.to_b(self.routes.failover[tostring(result.code)]) == true then + self.log:info('SWITCH - failover - code: ', result.code); + elseif common.str.to_b(self.routes.failover[tostring(result.cause)]) == true then + self.log:info('SWITCH - failover - cause: ', result.cause); + else + self.log:info('SWITCH - no failover - cause: ', result.cause, ', code: ', result.code); + break; + end + end + + return result; + end + + self.log:error('SWITCH - destination not found - type: ', destination.type); + return { continue = true, code = 404, phrase = destination.type .. ' not found' } +end + + +function Dialplan.run(self, destination) + self.caller:set_variable('hangup_after_bridge', false); + self.caller:set_variable('ringback', self.config.parameters.ringback); + self.caller:set_variable('bridge_early_media', 'true'); + self.caller:set_variable('send_silence_when_idle', 0); + self.caller:set_variable('default_language', self.default_language); + self.caller:set_variable('gs_save_cdr', true); + self.caller:set_variable('gs_call_service', 'dial'); + self.caller.session:setAutoHangup(false); + + self.routes = common.configuration_file.get('/opt/freeswitch/scripts/ini/routes.ini'); + self.caller.domain_local = self.domain; + self:retrieve_caller_data(); + + if not destination or destination.type == 'unknown' then + require 'dialplan.route' + local route = nil; + + if self.caller.from_gateway then + local route_object = dialplan.route.Route:new{ log = self.log, database = self.database, routing_table = self.routes }; + route = route_object:inbound(self.caller, self.caller.destination_number); + local inbound_caller_id_number = route_object:inbound_cid_number(self.caller, self.caller.gateway_name, 'gateway'); + route_object.expandable.caller_id_number = inbound_caller_id_number; + local inbound_caller_id_name = route_object:inbound_cid_name(self.caller, self.caller.gateway_name, 'gateway'); + self.log:info('INBOUND_CALLER_ID_REWRITE - number: ', inbound_caller_id_number, ', name: ', inbound_caller_id_name); + self.caller.caller_id_number = inbound_caller_id_number or self.caller.caller_id_number; + self.caller.caller_id_name = inbound_caller_id_name or self.caller.caller_id_name; + self.caller.caller_phone_numbers[1] = self.caller.caller_id_number; + else + route = dialplan.route.Route:new{ log = self.log, database = self.database, routing_table = self.routes }:prerouting(self.caller, self.caller.destination_number); + end + + if route then + destination = self:destination_new{ number = route.value } + self.caller.destination_number = destination.number; + self.caller.destination = destination; + elseif not destination or destination.type == 'unknown' then + destination = self:destination_new{ number = self.caller.destination_number } + self.caller.destination = destination; + end + end + + self.log:info('DIALPLAN start - caller_id: ',self.caller.caller_id_number, ' "', self.caller.caller_id_name,'"', + ', number: ', destination.number); + + local result = { continue = false }; + local loop = self.caller.loop_count; + while self.caller:ready() and loop < self.max_loops do + loop = loop + 1; + self.caller.loop_count = loop; + + self.log:info('LOOP ', loop, + ' - destination: ', destination.type, '=', destination.id, '/', destination.uuid,'@', destination.node_id, + ', number: ', destination.number); + + self.caller:set_variable('gs_destination_type', destination.type); + self.caller:set_variable('gs_destination_id', destination.id); + self.caller:set_variable('gs_destination_uuid', destination.uuid); + self.caller:set_variable('gs_destination_number', destination.number); + self.caller:set_variable('gs_destination_node_local', destination.node_local); + + result = self:switch(destination); + result = result or { continue = false, code = 502, cause = 'DESTINATION_OUT_OF_ORDER', phrase = 'Destination out of order' } + + if result.call_service then + self.caller:set_variable('gs_call_service', result.call_service); + end + + if not result.continue then + break; + end + + if result.call_forwarding then + self.log:info('LOOP ', loop, ' CALL_FORWARDING - service: ', result.call_forwarding.service, + ', destination: ', result.call_forwarding.type, '=', result.call_forwarding.id, + ', number: ', result.call_forwarding.number); + + local auth_account = self:object_find(destination.type, destination.id); + self.caller.forwarding_number = destination.number; + self.caller.forwarding_service = result.call_forwarding.service; + self.caller:set_variable('gs_forwarding_service', self.caller.forwarding_service); + self.caller:set_variable('gs_forwarding_number', self.caller.forwarding_number); + + if auth_account then + self.caller.auth_account = auth_account; + self.caller:set_auth_account(self.caller.auth_account); + self.log:info('AUTH_ACCOUNT_UPDATE - account: ', self.caller.auth_account.class, '=', self.caller.auth_account.id, '/', self.caller.auth_account.uuid); + if self.caller.auth_account.owner then + self.log:info('AUTH_ACCOUNT_UPDATE - auth owner: ', self.caller.auth_account.owner.class, '=', self.caller.auth_account.owner.id, '/', self.caller.auth_account.owner.uuid); + else + self.log:error('AUTH_ACCOUNT_UPDATE - auth owner not found'); + end + end + + destination = self:destination_new(result.call_forwarding); + self.caller.destination = destination; + + if not result.no_cdr and auth_account then + require 'common.call_history' + common.call_history.CallHistory:new{ log = self.log, database = self.database }:insert_forwarded( + self.caller.uuid, + auth_account.class, + auth_account.id, + self.caller, + destination, + result + ); + end + end + + if result.number then + self.log:info('LOOP ', loop, ' NEW_DESTINATION_NUMBER - number: ', result.number ); + destination = self:destination_new{ number = result.number } + self.caller.destination = destination; + end + end + + if loop >= self.max_loops then + result = { continue = false, code = 483, cause = 'EXCHANGE_ROUTING_ERROR', phrase = 'Too many hops' } + end + + self.log:info('DIALPLAN end - caller_id: ',self.caller.caller_id_number, ' "', self.caller.caller_id_name,'"', + ', destination: ', destination.type, '=', destination.id, '/', destination.uuid,'@', destination.node_id, + ', number: ', destination.number, ', result: ', result.code, ' ', result.phrase); + + if self.caller:ready() then + self:hangup(result.code, result.phrase, result.cause); + end + + self.caller:set_variable('gs_save_cdr', not result.no_cdr); +end -- cgit v1.2.3