1--
2--	FusionPBX
3--	Version: MPL 1.1
4--
5--	The contents of this file are subject to the Mozilla Public License Version
6--	1.1 (the "License"); you may not use this file except in compliance with
7--	the License. You may obtain a copy of the License at
8--	http://www.mozilla.org/MPL/
9--
10--	Software distributed under the License is distributed on an "AS IS" basis,
11--	WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12--	for the specific language governing rights and limitations under the
13--	License.
14--
15--	The Original Code is FusionPBX
16--
17--	The Initial Developer of the Original Code is
18--	Mark J Crane <markjcrane@fusionpbx.com>
19--	Copyright (C) 2010 - 2016
20--	the Initial Developer. All Rights Reserved.
21--
22--	Contributor(s):
23--	Mark J Crane <markjcrane@fusionpbx.com>
24--	Errol W Samuels <ewsamuels@gmail.com>
25
26--user defined variables
27	local extension = argv[1];
28	local direction = argv[2] or extension and 'inbound' or 'all';
29
30-- we can use any number because other box should check sip_h_X_*** headers first
31	local pickup_number = '*8' -- extension and '**' or '*8'
32
33--include config.lua
34	require "resources.functions.config";
35
36--add the function
37	require "resources.functions.explode";
38	require "resources.functions.trim";
39	require "resources.functions.channel_utils";
40
41--prepare the api object
42	api = freeswitch.API();
43
44--Get intercept logger
45	local log = require "resources.functions.log".intercept
46
47--include database class
48	local Database = require "resources.functions.database"
49
50--include json library
51	local json
52	if (debug["sql"]) then
53		json = require "resources.functions.lunajson"
54	end
55
56--get the hostname
57	local hostname = trim(api:execute("switchname", ""));
58
59-- redirect call to another box
60	local function make_proxy_call(destination, call_hostname)
61		destination = destination .. "@" .. domain_name
62		local profile, proxy = "internal", call_hostname;
63
64		local sip_auth_username = session:getVariable("sip_auth_username");
65		local sip_auth_password = api:execute("user_data", sip_auth_username .. "@" .. domain_name .." param password");
66		local auth = "sip_auth_username="..sip_auth_username..",sip_auth_password='"..sip_auth_password.."'"
67		dial_string = "{sip_invite_domain=" .. domain_name .. "," .. auth .. "}sofia/" .. profile .. "/" .. destination .. ";fs_path=sip:" .. proxy;
68		log.notice("Send call to other host....");
69		session:execute("bridge", dial_string);
70	end
71
72-- check pin number if defined
73	local function pin(pin_number)
74		if not pin_number then
75			return true
76		end
77
78		--sleep
79			session:sleep(500);
80		--get the user pin number
81			local min_digits = 2;
82			local max_digits = 20;
83			local max_tries = "3";
84			local digit_timeout = "5000";
85			local digits = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_pass:#", "", "\\d+");
86
87		--validate the user pin number
88			local pin_number_table = explode(",",pin_number);
89			for index,pin_number in pairs(pin_number_table) do
90				if (digits == pin_number) then
91					--set the authorized pin number that was used
92						session:setVariable("pin_number", pin_number);
93					--done
94						return true;
95				end
96			end
97
98		--if not authorized play a message and then hangup
99			session:streamFile("phrase:voicemail_fail_auth:#");
100			session:hangup("NORMAL_CLEARING");
101			return;
102	end
103
104-- do intercept if we get redirected request from another box
105	local function proxy_intercept()
106		-- Proceed calls from other boxes
107
108		-- Check if this call from other box with setted intercept_uuid
109			local intercept_uuid = session:getVariable("sip_h_X-intercept_uuid")
110
111			if intercept_uuid and #intercept_uuid > 0 then
112				log.notice("Get intercept_uuid from sip header. Do intercept....")
113				session:execute("intercept", intercept_uuid)
114				return true
115			end
116
117		-- Check if this call from other box and we need parent uuid for channel
118			local child_intercept_uuid = session:getVariable("sip_h_X-child_intercept_uuid")
119			if (not child_intercept_uuid) or (#child_intercept_uuid == 0) then
120				return
121			end
122
123		-- search parent uuid
124			log.notice("Get child_intercept_uuid from sip header.")
125			local parent_uuid =
126				channel_variable(child_intercept_uuid, 'ent_originate_aleg_uuid') or
127				channel_variable(child_intercept_uuid, 'cc_member_session_uuid') or
128				channel_variable(child_intercept_uuid, 'fifo_bridge_uuid') or
129				child_intercept_uuid
130
131			if parent_uuid == child_intercept_uuid then
132				log.notice("Can not found parent call. Try intercept child.")
133				session:execute("intercept", child_intercept_uuid)
134				return true
135			end
136
137		-- search parent hostname
138			call_hostname = hostname
139		--[[ parent and child have to be on same box so we do not search it
140			log.notice("Found parent channel try detect parent hostname")
141			local dbh = Database.new('switch')
142			local sql = "SELECT hostname FROM channels WHERE uuid='" .. parent_uuid .. "'"
143			local call_hostname = dbh:first_value(sql)
144			dbh:release()
145
146			if not call_hostname then
147				log.notice("Can not find host name. Channels is dead?")
148				return true
149			end
150		--]]
151
152			if hostname == call_hostname then
153				log.notice("Found parent call on local machine. Do intercept....")
154				session:execute("intercept", parent_uuid);
155				return true
156			end
157
158			log.noticef("Found parent call on remote machine `%s`.", call_hostname)
159			session:execute("export", "sip_h_X-intercept_uuid="..parent_uuid);
160			make_proxy_call(pickup_number, call_hostname)
161			return true
162	end
163
164-- return array of extensions for group
165	local function select_group_extensions()
166		-- connect to Fusion database
167			local dbh = Database.new('system');
168
169		--get the call groups the extension is a member of
170			local sql = "SELECT call_group FROM v_extensions ";
171			sql = sql .. "WHERE domain_uuid = :domain_uuid ";
172			sql = sql .. "AND (extension = :caller_id_number ";
173			sql = sql .. "OR  number_alias = :caller_id_number)";
174			sql = sql .. "limit 1";
175			local params = {domain_uuid = domain_uuid, caller_id_number = caller_id_number};
176			if (debug["sql"]) then
177				log.noticef("SQL: %s; params: %s", sql, json.encode(params));
178			end
179			local call_group = dbh:first_value(sql, params) or ''
180			log.noticef("call_group: `%s`", call_group);
181			call_groups = explode(",", call_group);
182
183			params = {domain_uuid = domain_uuid};
184
185		--get the extensions in the call groups
186			sql = "SELECT extension, number_alias FROM v_extensions ";
187			sql = sql .. "WHERE domain_uuid = :domain_uuid ";
188			sql = sql .. "AND (";
189			for key,call_group in ipairs(call_groups) do
190				if key > 1 then sql = sql .. " OR " end
191				if #call_group == 0 then
192					sql = sql .. "call_group = '' or call_group is NULL";
193				else
194					local param_name = "call_group_" .. tostring(key)
195					sql = sql .. "call_group like :" .. param_name;
196					params[param_name] = '%' .. call_group .. '%';
197				end
198			end
199			sql = sql .. ") ";
200			if (debug["sql"]) then
201				log.noticef("SQL: %s; params: %s", sql, json.encode(params));
202			end
203			local extensions = {}
204			dbh:query(sql, params, function(row)
205				local member = row.extension
206				if row.number_alias and #row.number_alias > 0 then
207					member = row.number_alias
208				end
209				extensions[#extensions+1] = member
210				log.noticef("member `%s`", member)
211			end);
212
213		-- release Fusion database
214			dbh:release()
215
216		-- return result
217			return extensions
218	end
219
220--check if the session is ready
221	if ( session:ready() ) then
222		--answer the session
223			session:answer();
224		--get session variables
225			domain_uuid = session:getVariable("domain_uuid");
226			domain_name = session:getVariable("domain_name");
227			pin_number = session:getVariable("pin_number");
228			context = session:getVariable("context");
229			caller_id_number = session:getVariable("caller_id_number");
230	end
231
232--check if the session is ready
233	if ( session:ready() ) then
234		if proxy_intercept() then
235			return
236		end
237	end
238
239--check if the session is ready
240	if ( session:ready() ) then
241		--if the pin number is provided then require it
242			if not pin(pin_number) then
243				return
244			end
245	end
246
247	if ( session:ready() ) then
248		-- select intercept mode
249			if not extension then
250				log.notice("GROUP INTERCEPT")
251				extensions = select_group_extensions()
252			else
253				log.noticef("INTERCEPT %s", extension)
254				extensions = {extension}
255			end
256
257		--connect to FS database
258			local dbh = Database.new('switch')
259
260		--check the database to get the uuid of a ringing call
261			call_hostname = "";
262			sql = "SELECT uuid, call_uuid, hostname FROM channels ";
263			sql = sql .. "WHERE callstate IN ('RINGING', 'EARLY') ";
264			-- next check should prevent pickup call from extension
265			-- e.g. if extension 100 dial some cell phone and some one else dial *8
266			-- he can pickup this call.
267			if not direction:find('all') then
268				sql = sql .. "AND (1 <> 1 "
269				-- calls from freeswitch to user
270					if direction:find('inbound') then
271						sql = sql .. "OR direction = 'outbound' ";
272					end
273
274				-- calls from user to freeswitch
275					if direction:find('outbound') then
276						sql = sql .. "OR direction = 'inbound' ";
277					end
278				sql = sql .. ") "
279			end
280
281			sql = sql .. "AND (1<>1 ";
282			local params = {};
283			for key,extension in pairs(extensions) do
284				local param_name = "presence_id_" .. tostring(key);
285				sql = sql .. "OR presence_id = :" .. param_name .. " ";
286				params[param_name] = extension.."@"..domain_name;
287			end
288			sql = sql .. ") ";
289			sql = sql .. "AND call_uuid IS NOT NULL ";
290			sql = sql .. "LIMIT 1 ";
291			if (debug["sql"]) then
292				log.noticef("SQL: %s; params: %s", sql, json.encode(params));
293			end
294			local is_child
295			dbh:query(sql, params, function(row)
296				--for key, val in pairs(row) do
297			 	--	log.notice("row "..key.." "..val);
298				--end
299				--log.notice("-----------------------");
300				is_child = (row.uuid == row.call_uuid)
301				uuid = row.call_uuid;
302				call_hostname = row.hostname;
303			end);
304			--log.notice("uuid: "..uuid);
305			--log.notice("call_hostname: "..call_hostname);
306			if is_child then
307				-- we need intercept `parent` call e.g. call in FIFO/CallCenter Queue
308				if (call_hostname == hostname) then
309					log.notice("Found child call on local machine. Try find parent channel.")
310					local parent_uuid =
311						channel_variable(uuid, 'ent_originate_aleg_uuid') or
312						channel_variable(uuid, 'cc_member_session_uuid') or
313						channel_variable(uuid, 'fifo_bridge_uuid') or
314						uuid
315
316					--[[ parent and child have to be on same box so we do not search it
317					if parent_uuid ~= uuid then
318						local sql = "SELECT hostname FROM channels WHERE uuid='" .. uuid .. "'"
319						call_hostname = dbh:first_value(sql)
320					end
321					--]]
322
323					if call_hostname then
324						uuid = parent_uuid
325						if call_hostname ~= hostname then
326							log.noticef("Found parent call on remote machine `%s`.", call_hostname)
327						else
328							log.notice("Found parent call on local machine.")
329						end
330					end
331
332				else
333					log.noticef("Found child call on remote machine `%s`.", call_hostname)
334					-- we can not find parent on this box because channel on other box so we have to
335					-- forward call to this box
336					session:execute("export", "sip_h_X-child_intercept_uuid="..uuid);
337					return make_proxy_call(pickup_number, call_hostname)
338				end
339			end
340
341		--release FS database
342			dbh:release()
343	end
344
345	log.noticef( "Hostname: %s Call Hostname: %s", hostname, call_hostname);
346
347--intercept a call that is ringing
348	if (uuid ~= nil) then
349		if (session:getVariable("billmsec") == nil) then
350			if (hostname == call_hostname) then
351				session:execute("intercept", uuid);
352			else
353				session:execute("export", "sip_h_X-intercept_uuid="..uuid);
354				make_proxy_call(pickup_number, call_hostname)
355			end
356		end
357	end
358
359--notes
360	--originate a call
361		--cmd = "originate user/1007@voip.example.com &intercept("..uuid..")";
362		--api = freeswitch.API();
363		--result = api:executeString(cmd);
364