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