root/modules/branches/2.6/sipstation/sipstation.utility.php

Revision 8417, 20.3 kB (checked in by p_lindheimer, 4 years ago)

new sipstation module to auto-configure and monitor FreePBX.com SIP trunks and service

  • Property svn:executable set to *
Line 
1 <?php
2 /* $Id:$ */
3
4 /* Copyright (c) 2009 Bandwidth.com
5    Licensed for use by active FreePBX.com SIP Trunking Customers (SIPSTATION(tm)). Not licensed to be modified or redistributed in any fashion.
6    No guarantees or warranties provided, use at your own risk. See license included with module for more details.
7 */
8
9 /* hard coded hash of Asterisk codec names compared to what XML official name may send down
10    will be filtered so only supported codecs remain, From Asterisk:
11
12        1 (1 <<  0)      (0x1)  audio       g723   (G.723.1)
13        2 (1 <<  1)      (0x2)  audio        gsm   (GSM)
14        4 (1 <<  2)      (0x4)  audio       ulaw   (G.711 u-law)
15        8 (1 <<  3)      (0x8)  audio       alaw   (G.711 A-law)
16       16 (1 <<  4)     (0x10)  audio   g726aal2   (G.726 AAL2)
17       32 (1 <<  5)     (0x20)  audio      adpcm   (ADPCM)
18       64 (1 <<  6)     (0x40)  audio       slin   (16 bit Signed Linear PCM)
19      128 (1 <<  7)     (0x80)  audio      lpc10   (LPC10)
20      256 (1 <<  8)    (0x100)  audio       g729   (G.729A)
21      512 (1 <<  9)    (0x200)  audio      speex   (SpeeX)
22     1024 (1 << 10)    (0x400)  audio       ilbc   (iLBC)
23     2048 (1 << 11)    (0x800)  audio       g726   (G.726 RFC3551)
24     4096 (1 << 12)   (0x1000)  audio       g722   (G722)
25 */
26 $failover = false;
27 $sipstation_xml_version = '1.0.0';
28 $ast_codec_hash = array(
29   'g723'     => 'G.723.1',
30   'gsm'      => 'GSM',
31   'ulaw'     => 'G.711.U',
32   'alaw'     => 'G.711.A',
33   'g722aal2' => 'G.726.AAl2',
34   'adpcm'    => 'ADPCM',
35   'slin'     => 'SLIN',
36   'lpc10'    => 'LPC10',
37   'g729'     => 'G.729.A',
38   'speex'    => 'SpeeX',
39   'ilbc'     => 'iLBC',
40   'g726'     => 'G.726',
41   'g722'     => 'G.722',
42 );
43
44 /* callback to filer out codecs not supported
45  */
46 function sipstation_codec_filter($codec) {
47   global $ast_codec_hash;
48
49   $codec_split = explode(':',$codec,2);
50   if (array_key_exists($codec_split[0],$ast_codec_hash)) {
51     return $codec_split[0];
52   } else {
53     return false;
54   }
55 }
56 function sipstation_supported_codecs($codec) {
57   global $codec_array;
58   return in_array($codec,$codec_array) ? $codec : false;
59 }
60
61 /* Returns a hash of settings from 'sip show peers'
62 */
63 function sipstation_get_peer_status($peer) {
64   global $astman;
65   $sip_peer['sipstation_status'] = 'ok';
66   $response = $astman->send_request('Command',array('Command'=>"sip show peer $peer"));
67   $buf = explode("\n",$response['data']);
68   foreach ($buf as $res) {
69     if (preg_match("/$peer\s*not\s+found\.{0,1}\s*$/",$res)) {
70       $sip_peer['sipstation_status'] = 'no_peer';
71     } elseif (preg_match("/^\s*(.*?)\s*:\s*(.*)$/",$res,$match)) {
72       $sip_peer[$match[1]] = $match[2];
73     }
74   }
75   return $sip_peer;
76 }
77
78 /* Returns a hash of settings from 'sip show settings'
79 */
80 function sipstation_get_sip_settings() {
81   global $astman;
82   $sip_peer['sipstation_status'] = 'ok';
83   $response = $astman->send_request('Command',array('Command'=>"sip show settings"));
84   $buf = explode("\n",$response['data']);
85   foreach ($buf as $res) {
86     if (preg_match("/$peer\s*not\s+found\.{0,1}\s*$/",$res)) {
87       $sip_peer['sipstation_status'] = 'no_peer';
88     } elseif (preg_match("/^\s*(.*?)\s*:\s*(.*)$/",$res,$match)) {
89       $sip_peer[$match[1]] = $match[2];
90     }
91   }
92   return $sip_peer;
93 }
94
95 /* Returns a filtered array of currently configured codecs, filtered
96    against the list of supported codecs
97 */
98 function sipstation_get_configured_codecs($peer, $peer_status=false) {
99   if (!is_array($peer_status) || empty($peer_status)) {
100     $peer_status = sipstation_get_peer_status($peer);
101   }
102   if ($peer_status['sipstation_status'] = 'ok') {
103     if (preg_match("/^\s*\((.*)\)\s*$/",$peer_status['Codec Order'],$match)) {
104       $codecs = explode(',',$match[1]);
105       return array_filter(array_map('sipstation_codec_filter',$codecs));
106     }
107   }
108 }
109
110 function sipstation_get_key() {
111   global $db;
112     $sql = "SELECT * FROM module_xml WHERE id = 'sipstation_key'";
113     $result = sql($sql,'getRow',DB_FETCHMODE_ASSOC);
114     if (!isset($result['data']) || trim($result['data']) == "") {
115     return false;
116   } else {
117     return $result['data'];
118   }
119 }
120
121 /* Check if there is a valid key
122  * Returns: nokey, valid, invalid, noserver (if server can't be contacted)
123  */
124 function sipstation_check_key() {
125     $sql = "SELECT * FROM `module_xml` WHERE `id` = 'sipstation_key'";
126     $result = sql($sql,'getRow',DB_FETCHMODE_ASSOC);
127
128     // if not set so this is a first time install
129     // get a new hash to account for first time install
130     //
131     if (!isset($result['data']) || trim($result['data']) == "") {
132     return 'nokey';
133   } else {
134     // TODO: should really encrypt/decrypt key
135     //
136     return sipstation_confirm_key($result['data']);
137   }
138 }
139
140 /* deleted saved configuration if confirmation determines it is stale
141  */
142 function sipstation_confirm_key($key) {
143   $xml_array = sipstation_get_config(trim($key));
144   if ($xml_array['status'] == 'success') {
145     switch ($xml_array['query_status']) {
146       case 'SUCCESS':
147         return 'valid';
148       case 'BADKEY':
149         sipstation_remove_key();
150       default:
151         return 'invalid';
152     }
153   } else {
154     return $xml_array['status'];
155   }
156 }
157
158 function sipstation_set_key($key) {
159   global $db;
160   $status = sipstation_confirm_key($key);
161   if ($status == 'valid') {
162     $data4sql = $db->escapeSimple($key);
163     sql("DELETE FROM `module_xml` WHERE `id` = 'sipstation_key'");
164     sql("INSERT INTO `module_xml` (`id`,`time`,`data`) VALUES ('sipstation_key',".time().",'".$data4sql."')");
165   }
166   return $status;
167 }
168
169 function sipstation_remove_key() {
170   sql("DELETE FROM `module_xml` WHERE `id` = 'sipstation_key'");
171   return $status;
172 }
173
174 /* save the retrieved configuration information into the db to be used to configure trunks and what not
175  */
176 function sipstation_save_config($xml) {
177   global $db;
178   $data4sql = $db->escapeSimple($xml);
179   sql("DELETE FROM `module_xml` WHERE `id` = 'sipstation_config'");
180   sql("INSERT INTO `module_xml` (`id`,`time`,`data`) VALUES ('sipstation_config',".time().",'".$data4sql."')");
181 }
182
183 function sipstation_del_saved_config() {
184   global $db;
185   sql("DELETE FROM `module_xml` WHERE `id` = 'sipstation_config'");
186 }
187
188 function sipstation_retrieve_saved_config($format='') {
189   global $db;
190     $sql = "SELECT `data` FROM `module_xml` WHERE `id` = 'sipstation_config'";
191     $xml_data = sql($sql, "getOne");
192   if ($format == 'xml') {
193     return $xml_data;
194   } else {
195     $parser = new xml2Array($xml_data);
196     return $parser;
197   }
198 }
199
200 function sipstation_get_settings($key, $online=true) {
201   global $amp_conf;
202
203   if ($online) {
204     /*
205     $fn = "https://store.freepbx.com/store/myaccount.xml?keycode=".urlencode($key);
206     if (!$amp_conf['MODULEADMINWGET']) {
207       $xml_data = @file_get_contents($fn);
208     }
209     if (empty($xml_data)) {
210       exec("wget -O - $fn 2> /dev/null", $data_arr, $retcode);
211       $xml_data = implode("\n",$data_arr);
212     }
213     */
214     $xml_data = sipstation_curl_xml($key);
215
216     sipstation_save_config($xml_data); // cache the latest
217         $parser = new xml2Array($xml_data);
218
219     return $parser;
220   } else {
221     return sipstation_retrieve_saved_config();
222   }
223 }
224
225 // TODO: handle timeouts, other issues.
226 // TODO: make more general for any post, make generic RESTFUL function
227 function sipstation_curl_xml($keycode) {
228
229   // TODO: TEST
230   /* would be better to serialize the array and send it
231   curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
232   $params = "keycode=".urlencode($keycode);
233   curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
234   */
235
236   $url = "https://store.freepbx.com/store/myaccount.xml";
237   //$params = "keycode=".urlencode($keycode);
238   $params = array("keycode" => urlencode($keycode));
239   $user_agent = "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)";
240
241   $ch = curl_init();
242   //curl_setopt ($ch, CURLOPT_POST, 1);
243   curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
244   curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
245   curl_setopt($ch, CURLOPT_URL, $url);
246   curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
247   curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
248   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return results to string
249   curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
250   curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookie.txt');    // imitate classic browser behavior
251   curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
252
253   curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
254   // TODO: deal with timeout!
255
256   $xml = curl_exec ($ch);
257
258   // TODO: curl info back from request, check return code
259   // freepbx_debug(curl_getinfo($ch));
260
261   // TODO: if ($xml === false) { handle it, retry, etc.
262   // TODO: or at least toss in some error xml response
263   curl_close ($ch);
264
265   return $xml;
266 }
267
268 //TODO: refactor with above to combine common parts
269
270 function sipstation_put_dids($dids) {
271   if (empty($dids)) {
272     return true;
273   }
274   $keycode = sipstation_get_key();
275   $xml = '
276 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
277 <xml type="sipstation/xml" version="1.0.0">
278 <xml_version>1.0.0</xml_version>';
279   $xml .= "
280 <keycode>$keycode</keycode>
281 <dids>";
282   foreach ($dids as $did => $failover) {
283     $xml .= "\n<did failover=\"$failover\">$did</did>";
284   }
285   $xml .= "
286 </dids>
287 </xml>
288 ";
289
290   // TODO: different URL for putting just dids
291   $url = "https://store.freepbx.com/store/myaccount.xml";
292   $user_agent = "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)";
293
294   $ch = curl_init();
295   curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
296   curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
297
298   curl_setopt($ch, CURLOPT_URL, $url);
299   curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
300   curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
301   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);            // print results to string and not STDOUT
302   curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
303   curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookie.txt');    // IMITATE CLASSIC BROWSER'S BEHAVIOUR
304   curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
305
306   curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
307   // TODO: deal with timeout!
308
309   $xml = curl_exec ($ch);
310
311   // curl info back from request
312   $return_status = curl_getinfo($ch);
313   // freepbx_debug($return_status);
314
315   // TODO: if ($xml === false) { handle it, retry, etc.
316   // TODO: or at least toss in some error xml response
317   curl_close ($ch);
318
319   return $xml;
320 }
321
322 function sipstation_get_or_create_trunks(&$json_array,&$globalvar1,&$trunknum1,&$globalvar2,&$trunknum2) {
323   // now check on trunk config
324   // fpbx-1-$sip_user / fpbx-2-$sip_user
325   // TODO: 2.6 has trunkname, provider options that should be used
326   //
327   global $ast_codec_hash;
328   $sip_user = $json_array['sip_username'];
329   $sip_pass = $json_array['sip_password'];
330   $default_did = $json_array['e911_address']['default_did'];
331   $need_reload = false;
332
333   $tlist       = core_trunks_list(true);
334   $tech        = 'sip';
335   $keepcid     = 'off';
336   $disabletrunk= 'off';
337
338     $peerdetails = "disallow=all\nallow=".implode('&',array_keys($ast_codec_hash))."\n";
339     $peer_array = array();
340   foreach ($json_array['asterisk_settings']['peer'] as $param) {
341     $peerdetails .= trim($param)."\n";
342     $parts = explode('=',$param,2);
343     $peer_array[$parts[0]] = $parts[1];
344   }
345
346     $peerdetails .= "username=$sip_user\nsecret=$sip_pass\nhost=";
347     $register    = "$sip_user:$sip_pass@";
348   $dialrules   = array();
349
350   for ($i=1;$i<3;$i++) {
351     $gidx = "gw$i";
352     $channelid   = "fpbx-$i-$sip_user";
353     $gw          = $json_array['gateways'][$gidx];
354     if (isset($tlist["SIP/$channelid"])) {
355       $globalvar = $tlist["SIP/$channelid"]['globalvar'];
356       $trunknum  = ltrim($globalvar,'OUT_');
357       // Now get some trunk status info
358       $trunk_status = sipstation_get_peer_status($channelid);
359       if ($trunk_status['sipstation_status'] == 'ok') {
360           $json_array['trunk_qualify'][$gidx] = $trunk_status['Status'];
361           $json_array['trunk_codecs'][$gidx] = implode(' | ',sipstation_get_configured_codecs($channelid,$trunk_status));
362       } else {
363         //TODO: probably nothing
364       }
365       $json_array['trunk_name'][$gidx] = core_trunks_getTrunkTrunkName($trunknum);
366     } else {
367           $trunknum = core_trunks_add($tech, $channelid, '', '', $default_did, $peerdetails.$gw, '', '', $register.$gw, $keepcid, '', $disabletrunk);
368       core_trunks_addDialRules($trunknum, $dialrules);
369       $globalvar = "OUT_".$trunknum;
370       $need_reload = true;
371         $json_array['created_trunks'][$gidx] = $channelid;
372       // TODO: 2.6 should be name
373       $json_array['trunk_name'][$gidx] = "SIP/$channelid";
374     }
375     // We need these next and need them past back up
376     $gv = "globalvar$i";
377     $tn = "trunknum$i";
378     $$gv = $globalvar;
379     $$tn = $trunknum;
380   }
381
382   $peer_array['username'] = $sip_user;
383   $peer_array['secret'] = $sip_pass;
384
385   $trunk_check = array($trunknum1, $trunknum2);
386   $cnt = 1;
387   foreach ($trunk_check as $tr) {
388       $json_array['trunk_id']["gw$cnt"] = $tr; // need to get this set for both anyhow
389     $gw = $json_array['gateways']["gw$cnt"];
390     $peer_array['host'] = $gw;
391     $peer_stuff = array();
392     $tr_reg = core_trunks_getTrunkRegister($tr);
393     foreach (explode("\n",core_trunks_getTrunkPeerDetails($tr)) as $elem) {
394       $temp = explode("=",$elem,2);
395       if ($temp[0] == 'allow') {
396         $peer_stuff[$temp[0]] = explode('&',$temp[1]);
397       } elseif ($temp[0] != '') {
398         $peer_stuff[$temp[0]] = $temp[1];
399       }
400     }
401     unset($peer_stuff['allow']);
402     unset($peer_stuff['disallow']);
403     if ($peer_array != $peer_stuff || $tr_reg != $register.$gw) {
404         $json_array['changed_trunks']["gw$cnt"] = $tr;
405     }
406     $cnt++;
407   }
408   return $need_reload;
409 }
410
411 /*
412 Current format of 'sip show registry' with various possible states
413 Host                            Username       Refresh State                Reg.Time                 
414 trunk1.freepbx.com:5060         b04c1dsr           585 Registered           Sat, 27 Jun 2009 00:33:47
415 trunk2.freepbx.com:5060         b04c1dsr           585 Registered           Sat, 27 Jun 2009 00:33:48
416 phonebooth.bandwidth.com:5060   9192221234         585 Timeout              Sat, 27 Jun 2009 00:33:47
417 67.131.62.22:5060               myusername         585 Auth.Sent.           Sat, 27 Jun 2009 00:33:47
418 */
419 function sipstation_get_registration_status($sip_user) {
420   global $astman;
421   $status_arr = array();
422   if (!isset($astman)) {
423     return $status_arr;
424   }
425   $response = $astman->send_request('Command',array('Command'=>"sip show registry"));
426   $buf = explode("\n",$response['data']);
427   $state_pos = false;
428   foreach ($buf as $line) {
429     if (trim($line) != '') {
430       if ($state_pos===false) {
431         // find the positions of the header columns so we can parse
432         if ($state_pos = strpos($line,"State")) {
433           $user_pos = strpos($line,"Username");
434           $reg_pos = strpos($line,"Reg.Time");
435           $host_pos = strpos($line,"Host");
436
437           // Asterisk 1.2 does not have Reg. Time
438           if ($reg_pos === false) {
439             $reg_pos = strlen($line);
440           }
441         }
442       } else {
443         // get the username and if ours, trunk (host) and State of reg
444         preg_match("/^([^\s]+)\s*/",substr($line,$user_pos),$matches);
445         if ($sip_user == $matches[1]) {
446           $trunk = trim(substr($line,$host_pos,($user_pos-$host_pos)));
447           $trunk = preg_match("/^([^\s:]+)[:]{0,1}[\d]{0,5}\s*/",$trunk,$matches) ?  $matches[1] : $trunk;;
448           $state = trim(substr($line,$state_pos,($reg_pos-$state_pos)));
449           $status_arr[$trunk] = $state;
450         }
451       }
452     }
453   }
454   return $status_arr;
455 }
456
457 function sipstation_get_config($account_key, $online=true, $filter_sections=array()) {
458   global $db;
459   global $ast_codec_hash;
460   global $codec_array;
461
462   if (!empty($account_key)) {
463     $json_array = array();
464     $xml_parser = sipstation_get_settings($account_key, $online);
465
466     if (!empty($xml_parser->data)) foreach ($xml_parser->data['xml'] as $key => $value) {
467       switch ($key) {
468         case 'xml_version':
469         case 'query_status':
470         case 'query_status_message':
471         case 'sip_username':
472         case 'sip_password':
473         case 'num_trunks':
474         case 'monthly_cost':
475         case 'cid_format':
476         case 'nat_troubleshooting':
477           if (!empty($filter_sections) && (!isset($filter_sections[$key]) || !$filter_sections[$key])) { continue; }
478           $json_array[$key] = trim("$value");
479         break;
480         case 'gateways':
481         case 'e911_address':
482         case 'registered_status':
483           if (!empty($filter_sections) && (!isset($filter_sections[$key]) || !$filter_sections[$key])) { continue; }
484           foreach ($value as $key2 => $value2) {
485             if (is_array($value2)) {
486               foreach ($value2 as $gw => $value3){
487                 $json_array[$key][$key2][$gw] = $value3 ? trim($value3) : '';
488               }
489             } else {
490               $json_array[$key][$key2] = $value2 ? trim($value2) : '';
491             }
492           }
493         break;
494         case 'dids':
495           if (!empty($filter_sections) && (!isset($filter_sections[$key]) || !$filter_sections[$key])) { continue; }
496           if (!empty($value['did']) && !is_array($value['did'])) {
497             $tmp = $value['did'];
498             unset($value['did']);
499             $value['did'][] = $tmp;
500             $single = true;
501           } else {
502             $single = false;
503           }
504
505           $idx = 0;
506           foreach ($value['did'] as $did) {
507
508             $path = $single ? "/xml/dids/did" : "/xml/dids/did/$idx";
509             $idx++;
510             $failover = $xml_parser->attributes[$path]['failover'];
511
512             $did = trim($did);
513             $exten = core_did_get($did);
514             if (empty($exten)) {
515               $json_array[$key][$did] = array('destination' => 'blank', 'desc' => _("Not Set"), 'description' => '', 'failover' => "$failover");
516             } else {
517               $dest_results = framework_identify_destinations($exten['destination']);
518               if (is_array($dest_results[$exten['destination']])) {
519                 /* This is really bad but the calls to core_users get are so heavy and core_users_list don't give details
520                   that we will do this for now and deal with it later.
521                 */
522                 $user_cid_hash = array();
523                 $sql = "SELECT `extension`, `outboundcid` FROM `users`";
524                     $user_cids = $db->getAll($sql,DB_FETCHMODE_ASSOC);
525                     if(DB::IsError($user_cids)) {
526                   freepbx_debug("Failed trying to get user cids");
527                   freepbx_debug($user_cids->getMessage());
528                   $user_cids = array();
529                 }
530                 foreach ($user_cids as $item) {
531                   $user_cid_hash[$item['extension']] = $item['outboundcid'];
532                 }
533                 foreach ($dest_results[$exten['destination']] as $mod => $info) {
534                   //$destination = (substr($exten['destination'],0,15) == 'from-did-direct' ? $exten['destination'] : 'assigned');
535                   $is_checked = 0;
536                   if (substr($exten['destination'],0,15) == 'from-did-direct') {
537                     $destination = $exten['destination'];
538                     $exten_arr = explode(',',$destination);
539                     if (isset($exten_arr[1]) && isset($user_cid_hash[$exten_arr[1]])) {
540                       $is_checked = preg_match('/^\s*[<]?('.$did.')[>]?\s*$|^\s*"[^"]*"\s*<('.$did.')>\s*$/',$user_cid_hash[$exten_arr[1]]);
541                     } else {
542                       $is_checked = 0;
543                     }
544                   } else {
545                     $destination = 'assigned';
546                   }
547                   $json_array[$key][$did] = array('destination' => $destination, 'desc' => $info['description'], 'description' => $exten['description'], 'outboundcid' => $is_checked, 'failover' => "$failover");
548                   break;
549                 }
550               } else {
551                 $json_array[$key][$did] = array('destination' => 'blank', 'desc' => _("Not Set"), 'description' => '', 'failover' => "$failover");
552               }
553             }
554           }
555         break;
556         case 'asterisk_settings':
557           if (!empty($filter_sections) && (!isset($filter_sections[$key]) || !$filter_sections[$key])) { continue; }
558           $json_array['asterisk_settings']['peer'] = $value['peer']['setting'];
559         break;
560       case 'codecs':
561           if (!empty($filter_sections) && (!isset($filter_sections[$key]) || !$filter_sections[$key])) { continue; }
562           $codec_array = $value['codec'];
563           /* filter the Asterisk codec hash to only those that we are told are supported
564           */
565           $ast_codec_hash = array_filter(array_map('sipstation_supported_codecs',$ast_codec_hash));
566         break;
567       default:
568       }
569       $json_array['status'] = 'success';
570     } else {
571       $json_array['status'] = 'noserver';
572     }
573   } else {
574     $json_array['status'] = 'nokey';
575   }
576 return $json_array;
577 }
578
Note: See TracBrowser for help on using the browser.