Telephone system:Number lookup
This script uses mod_cidlookup combined with a custom php page. It tests the number against a mySQL database, several reversed number lookup websites, the national telecommunications authority database and a coarse array of areas of the world, which are called in order of grannularity. The script is called both on outgoing and incoming calls
We make use of the mod_cidlookup for ease of use, but not all features are functional in our implementation.
Every request is done via HTTP requests to a php script that will check if the number:
- is an extension (and returns the name for that)
- is stored in the local mySQL database, and return that
- can be found online by using reversed number lookup websites for landlines
- can be found online by using the national telecommunications authority database (opta.nl) for cell phones returning the associated cell provider
- can be categorized by a more coarse lookup, like continent, country, region, town or number block owner, stored in a local array (~500 entries)
The setting for mod_cidlookup is:
<param name="url" value="http://webserviceprovider/lookup.php?number=${caller_id_number}"/>
Contents
lookup.php
This script returns some information about the caller, preferrably the name. It uses a local MySQL database and fetches info from some sites.
<?php /* * Copyright (c) 2012, ACKspace foundation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those * of the authors and should not be interpreted as representing official policies, * either expressed or implied, of the FreeBSD Project. */ define( "COUNTRY", "31" ); define( "REGION", "45" ); define( "PLUS", "00" ); define( "MYSQL_DB", "freeswitch_cidlookup" ); // Number dial plan, ~500 entries, shortened for wiki $arrNumbers[1]['info'] = "(continent) Verenigde Staten"; $arrNumbers[1][4][4][1]['info'] = "Bermuda"; $arrNumbers[3]['info'] = "(continent) Europa"; $arrNumbers[3][1]['info'] = "(land) Nederland"; $arrNumbers[3][1][1][4]['info'] = "Testnetnummer van KPN Telecom"; $arrNumbers[3][1][4]['info'] = "(provincies) Oostelijk Noord-Brabant, Limburg"; $arrNumbers[3][1][4][5]['info'] = "(regio) Heerlen"; $arrNumbers[3][1][6]['info'] = "Mobiele nummers en Semafoondiensten"; $arrNumbers[3][1][6][1]['info'] = "Mobiele telefoon"; $arrNumbers[3][1][6][1][0]['info'] = "(GSM) KPN"; $arrNumbers[3][1][8][5]['info'] = "(type) Plaatsonafhankelijk/VoIP"; $arrNumbers[3][1][8][5][8][7]['info'] = "(VoIP) XS4ALL"; // Normalize the number $arrNumberInfo = normalizeNumber( getVar( "number", true ) ); // Extension? try and get from dialplan if ( $arrNumberInfo['type'] == "extension" ) { echo getExtension( $arrNumberInfo['local'] ); exit; } if ( !function_exists( "mysql_connect" )) { echo "ERROR"; exit; } // Fetch the number from the database if ( $strName = dbLookup( $arrNumberInfo )) { echo $strName; exit; } // Nothing in the database? // national number starting with 0[1-578]? // Fetch number from website (check last get timestamp to prevent DoS // put result in DB if ( preg_match( "/^0[1-578].*/", $arrNumberInfo['national'] ) && $strName = fetchWebsiteResult( $arrNumberInfo['national'] )) { echo $strName; // Add the name to the DB $arrNumberInfo['name'] = $strName; dbInsert( $arrNumberInfo ); exit; } else if ( preg_match( "/^0[6].*/", $arrNumberInfo['national'] ) && $strName = fetchOptaResult( $arrNumberInfo['national'] )) { // Number porting echo $strName; // Add the name to the DB, so we don't have to look it up anymore $arrNumberInfo['name'] = $strName; dbInsert( $arrNumberInfo ); exit; } // Nothing on the website? Try and find the number in the array if ( isset( $arrNumberInfo['international'] )) { $strName = _getInfo( str_split( $arrNumberInfo['international'] )); echo $strName; exit; } ///////////////////////////////////////////////////////////////////// function fetchWebsiteResult( $_strNumber ) { if ( $strName = fetch_delefoondetective_nl( $_strNumber )) return $strName; if ( $strName = fetch_gevonden_cc( $_strNumber )) return $strName; if ( $strName = fetch_zoekenbel_nl( $_strNumber )) return $strName; if ( $strName = fetch_nummerzoeker_com( $_strNumber )) return $strName; if ( $strName = fetch_nummerid_com( $_strNumber )) return $strName; if ( $strName = fetch_gebeld_nl( $_strNumber )) return $strName; return false; } function fetchOptaResult( $_strNumber ) { $strPage = file_get_contents( "http://www.opta.nl/nl/nummers/nummers-zoeken/resultaat/?query=".$_strNumber."&page=1&portering=1" ); if ( !preg_match( "/<strong>Huidige aanbieder<\/strong>.*?<p>(.*?)<\/p>/si", $strPage, $matches )) return false; return "(GSM) ".$matches[1]; } function fetch_delefoondetective_nl( $_strNumber ) { $strPage = file_get_contents( "http://www.telefoondetective.nl/telefoonnummer/".$_strNumber."/" ); if ( !preg_match( "/<div\sid=\"name\"><h\d>(.*?)<\/h\d><\/div>/i", $strPage, $matches )) return false; return $matches[1]; } function fetch_gevonden_cc( $_strNumber ) { return false; } function fetch_zoekenbel_nl( $_strNumber ) { return false; } function fetch_nummerzoeker_com( $_strNumber ) { return false; } function fetch_nummerid_com( $_strNumber ) { return false; } function fetch_gebeld_nl( $_strNumber ) { return false; } function dbLookup( $_arrNumberInfo ) { if (!$db = mysql_connect( NULL, "username", "password" )) return false; // TODO: close db if (!mysql_select_db( MYSQL_DB, $db )) return false; // Prevent SQL injection on variables $country = mysql_real_escape_string( $_arrNumberInfo['country'] ); $international = mysql_real_escape_string( $_arrNumberInfo['international'] ); // Full number partial listing (experimental) $query = "SELECT name FROM telephone_names WHERE country_code=".$country." AND INSTR( '".$international."', number ) = 1 ORDER BY LENGTH(number), sortorder LIMIT 1"; if (!$result = mysql_query( $query, $db )) return false; $row = mysql_fetch_row( $result ); mysql_close( $db ); return $row[0]; } function dbInsert( $_arrNumberInfo ) { if (!$db = mysql_connect( NULL, "username", "password" )) return false; // TODO: close db if (!mysql_select_db( "freeswitch_cidlookup", $db )) return false; // Prevent SQL injection on variables $country = mysql_real_escape_string( $_arrNumberInfo['country'] ); $international = mysql_real_escape_string( $_arrNumberInfo['international'] ); $name = mysql_real_escape_string( $_arrNumberInfo['name'] ); $query = "INSERT INTO telephone_names (country_code,number,name) VALUES (".$country.",'".$international."','".$name."')"; if (!$result = mysql_query( $query, $db )) { echo mysql_error( $db ); return false; } mysql_close( $db ); return true; } function getExtension( $_strExtension ) { return false; //return "Ext. ".$_strExtension; } function normalizeNumber( $_strNumber ) { $arrInfo = array(); if ( preg_match( "/^([19]\d+)/", $_strNumber, $matches )) { $arrInfo['local'] = $matches[1]; $arrInfo['type'] = 'extension'; return $arrInfo; } $_strNumber = preg_replace( "/^([2345678])/", COUNTRY.REGION.'$1', $_strNumber ); // Add country on national dials $_strNumber = preg_replace( "/^(0)([^0].*)/", COUNTRY.'$2', $_strNumber ); // Replace + and 00 international symbols before parsing (include space for URI conversion $_strNumber = preg_replace( "/^( |\+|00)/", '', $_strNumber ); $arrInfo['country'] = intval( substr( $_strNumber, 0, 2 )); $arrInfo['international'] = $_strNumber; $arrInfo['type'] = 'international'; // National number? if ( $arrInfo['country'] == intval( COUNTRY )) { // only works with countries of 2 digits; replaces it with a 0 $arrInfo['national'] = "0".substr( $_strNumber, 2 ); $arrInfo['type'] = 'national'; } return $arrInfo; }; function GetInfo( $_strNumber ) { global $arrNumbers; $_strNumber = preg_replace( "/^([2345678])/", COUNTRY.REGION.'$1', $_strNumber ); // Add country on national dials $_strNumber = preg_replace( "/^(0)([^0].*)/", COUNTRY.'$2', $_strNumber ); // Replace + and 00 international symbols before parsing $_strNumber = preg_replace( "/^(\+|00)/", '', $_strNumber ); return _getInfo( str_split( $_strNumber )); }; function _getInfo( $_arrNumber ) { global $arrNumbers; $arrInfo = array(); $arrNumberInfo = $arrNumbers; $nIndent = 0; $strDigits = ""; $strDetailedInfo = ""; foreach ( $_arrNumber as $digit ) { if ( isset( $arrNumberInfo[$digit] ) ) { $strDigits .= $digit; $arrNumberInfo = $arrNumberInfo[$digit]; if ( isset( $arrNumberInfo['info'] ) ) { $arrInfo[] = str_repeat( "-", $nIndent ) . $strDigits . " " . $arrNumberInfo['info']; $strDigits = ""; $strDetailedInfo = $arrNumberInfo['info']; } $nIndent++; } else { break; } } return $strDetailedInfo; } //////////////////////////////////////////////////////////////////////////////// // Helpers //////////////////////////////////////////////////////////////////////////////// function getVar( $_strVarName, $_bAllowGet = false ) { // If _POST var is set, return _POST var, // else, if _GET var is set and is allowed, return _GET var // else, requested var not found: return NULL if ( isset( $_POST[ $_strVarName ] ) ) return $_POST[ $_strVarName ]; else if ( isset( $_GET[ $_strVarName ] ) && ($_bAllowGet == true) ) return $_GET[ $_strVarName ]; else return NULL; } ?>
dialing out
This default dialplan snippet does a caller id lookup, and updates the callee name which will be visible on the local extension
<extension name="National_numbers"> <condition field="destination_number" expression="^0([1-578]\d{8})$"> <action application="set" data="effective_caller_id_number=${outbound_caller_id}"/> <action application="export" data="callee_id_name=${cidlookup(0031$1)}" /> <action application="bridge" data="sofia/gateway/myLandLineProvider/31$1"/> </condition> </extension>
incoming calls
This public dialplan snippet somewhat at the top sets the number (if any) first, checks if it has an international prefix, does a lookup for incoming calls and will set the name accordingly.
The second part will strip any leading + sign
<extension name="fix_cidnam" continue="true"> <!--make sure the module is loaded, or else loading it will kill our call!--> <!-- Simple case: name=number or name is empty --> <!-- and number is a 10digit (excluding optional leading 1), in nanpa nxx-nxx-xxxx form --> <!-- will skipurl lookup if not a 10digit # (don't lookup INTL), and instead just query the SQL --> <condition field="${module_exists(mod_cidlookup)}" expression="true"/> <condition field="caller_id_name" expression="^${caller_id_number}$|^$"/> <condition field="caller_id_number" expression="^(\+|00)(\d+)$"> <action application="cidlookup" data="00$2"/> <anti-action application="cidlookup" data="${caller_id_number}"/> </condition> </extension> <extension name="fix_cidnam_plus" continue="true"> <!-- if the name starts with + followed by digits, strip the + and then pass the number --> <condition field="caller_id_name" expression="^\+(1[2-9]\d\d[2-9]\d{6})$"> <action application="cidlookup" data="$1"/> </condition> </extension>
todo
items stored in the database will not be updated anymore. The only way to refresh the number's information is to remove the entry manually which will cause a new lookup the next time that number is requested.