524 lines
23 KiB
PHP
524 lines
23 KiB
PHP
<?php
|
|
|
|
namespace FreePBX\modules\Sccp_manager\sccpManTraits;
|
|
|
|
trait helperfunctions {
|
|
private function convertCsvToArray($stringToConvert = "") {
|
|
// Take a csv string form of net/mask or ip/port and convert to an array
|
|
// sub arrays are separated by ";"
|
|
$outputArr = array();
|
|
if (empty($stringToConvert)) {
|
|
return $outputArr;
|
|
}
|
|
foreach (explode(";", $stringToConvert) as $value) {
|
|
//internal is always the first setting if present
|
|
if ($value == 'internal') {
|
|
$outputArr[] = array('internal' => 'on');
|
|
continue;
|
|
}
|
|
// Now handle rest of value types
|
|
$subArr = explode("/", $value);
|
|
if (count($subArr) === 2) {
|
|
// Have net/mask
|
|
$outputArr[] = array('net' => $subArr[0], 'mask' => $subArr[1]);
|
|
} else {
|
|
// have ip:port
|
|
$subArr = explode(":", $value);
|
|
$outputArr[] = array('ip' => $subArr[0], 'port' => $subArr[1]);
|
|
}
|
|
}
|
|
return $outputArr;
|
|
}
|
|
|
|
private function convertArrayToCsv(array $arrayToConvert) {
|
|
// About to save to db so need to convert to string
|
|
// Take an array form of net mask or ip port and convert to a csv
|
|
// sub arrays are separated by ";"
|
|
if (empty($arrayToConvert)) {
|
|
return '';
|
|
}
|
|
$output = array();
|
|
// Internal is always element 0, nets and ips start at element 1.
|
|
if ((isset($arrayToConvert[1]['net'])) || (isset($arrayToConvert[0]['internal']))) {
|
|
// Have net masks
|
|
foreach ($arrayToConvert as $netValue) {
|
|
if (isset($netValue['internal'])) {
|
|
$output[] = 'internal';
|
|
continue;
|
|
}
|
|
if (empty($netValue['net'])) {
|
|
// If network not set, user error, has added empty row so delete
|
|
continue;
|
|
}
|
|
// If the mask has not been set, set to this subnet
|
|
$netValue['mask'] = (empty($netValue['mask'])) ? "255.255.255.0" : $netValue['mask'];
|
|
$output[] = implode('/', $netValue);
|
|
}
|
|
} else {
|
|
// Have ip addresses
|
|
foreach ($arrayToConvert as $ipArr) {
|
|
if (isset($ipArr['internal'])) {
|
|
// should not be set for an ip address
|
|
continue;
|
|
}
|
|
if (empty($ipArr['ip'])) {
|
|
// If ip not set, user error, has added empty row so delete
|
|
continue;
|
|
}
|
|
$ipArr['port'] = (empty($ipArr['port'])) ? "2000" : $ipArr['port'];
|
|
$output[] = implode(':', $ipArr);
|
|
}
|
|
}
|
|
return implode(';', $output);
|
|
}
|
|
|
|
private function getIpInformation($type = '') {
|
|
$interfaces = array();
|
|
switch ($type) {
|
|
case 'ip4':
|
|
exec("/sbin/ip -4 -o addr", $result, $ret);
|
|
break;
|
|
case 'ip6':
|
|
exec("/sbin/ip -6 -o addr", $result, $ret);
|
|
break;
|
|
|
|
default:
|
|
exec("/sbin/ip -o addr", $result, $ret);
|
|
break;
|
|
}
|
|
foreach ($result as $line) {
|
|
$vals = preg_split("/\s+/", $line);
|
|
if ($vals[3] == "mtu") {
|
|
continue;
|
|
}
|
|
if ($vals[2] != "inet" && $vals[2] != "inet6") {
|
|
continue;
|
|
}
|
|
if (preg_match("/(.+?)(?:@.+)?:$/", $vals[1], $res)) {
|
|
continue;
|
|
}
|
|
$ret = preg_match("/(\d*+.\d*+.\d*+.\d*+)[\/(\d*+)]*/", $vals[3], $ip);
|
|
|
|
$interfaces[$vals[1] . ':' . $vals[2]] = array('name' => $vals[1], 'type' => $vals[2], 'ip' => ((empty($ip[1]) ? '' : $ip[1])));
|
|
}
|
|
return $interfaces;
|
|
}
|
|
|
|
private function before($thing, $inthat) {
|
|
return substr($inthat, 0, strpos($inthat, $thing));
|
|
}
|
|
|
|
private function array_key_exists_recursive($key, $arr) {
|
|
if (array_key_exists($key, $arr)) {
|
|
return true;
|
|
}
|
|
foreach ($arr as $currentKey => $value) {
|
|
if (is_array($value)) {
|
|
return $this->array_key_exists_recursive($key, $value);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function strpos_array($haystack, $needles) {
|
|
if (is_array($needles)) {
|
|
foreach ($needles as $str) {
|
|
if (is_array($str)) {
|
|
$pos = $this->strpos_array($haystack, $str);
|
|
} else {
|
|
$pos = strpos($haystack, $str);
|
|
}
|
|
if ($pos !== FALSE) {
|
|
return $pos;
|
|
}
|
|
}
|
|
} else {
|
|
return strpos($haystack, $needles);
|
|
}
|
|
return FALSE;
|
|
}
|
|
private function getTableDefaults($table, $trim_underscore = true) {
|
|
$def_val = array();
|
|
// TODO: This is ugly and overkill - needs to be cleaned up in dbinterface
|
|
if ($table == 'sccpsettings') {
|
|
// sccpsettings has a different structure and already have values in $sccpvalues
|
|
return $this->sccpvalues;
|
|
}
|
|
$sccpTableDesc = $this->dbinterface->getSccpDeviceTableData("get_columns_{$table}");
|
|
foreach ($sccpTableDesc as $key => $data) {
|
|
// function has 2 roles: return actual table keys (trim_underscore = false)
|
|
// return sanitised keys to add defaults (trim_underscore = true)
|
|
if ($trim_underscore) {
|
|
// Remove any leading (or trailing but should be none) underscore
|
|
// These are only used to hide fields from chan-sccp for compatibility
|
|
$key = trim($key,'_');
|
|
}
|
|
$def_val[$key] = array("keyword" => $key, "data" => $data['Default'], "seq" => "99");
|
|
}
|
|
return $def_val;
|
|
}
|
|
|
|
private function getTableEnums($table, $trim_underscore = true) {
|
|
$enumFields = array();
|
|
$sccpTableDesc = $this->dbinterface->getSccpDeviceTableData("get_columns_{$table}");
|
|
foreach ($sccpTableDesc as $key => $data) {
|
|
// function has 2 roles: return actual table keys (trim_underscore = false)
|
|
// return sanitised keys to add defaults (trim_underscore = true)
|
|
if ($trim_underscore) {
|
|
// Remove any leading (or trailing but should be none) underscore
|
|
// These are only used to hide fields from chan-sccp for compatibility
|
|
$key = trim($key,'_');
|
|
}
|
|
$typeArray = explode('(', $data['Type']);
|
|
if ($typeArray[0] == 'enum') {
|
|
$enumOptions = explode(',', trim($typeArray[1],')'));
|
|
$enumFields[$key] = $enumOptions;
|
|
}
|
|
}
|
|
return $enumFields;
|
|
}
|
|
|
|
private function findAllFiles($searchDir, $file_mask = array(), $mode = 'full') {
|
|
$result = array();
|
|
if (!is_dir($searchDir)) {
|
|
return $result;
|
|
}
|
|
foreach (array_diff(scandir($searchDir),array('.', '..')) as $value) {
|
|
if (is_file("$searchDir/$value")) {
|
|
$extFound = '';
|
|
$foundFile = true;
|
|
if (!empty($file_mask)) {
|
|
$foundFile = false;
|
|
foreach ($file_mask as $k) {
|
|
if (strpos($value, $k) !== false) {
|
|
$foundFile = true;
|
|
$extFound = $k;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ($foundFile) {
|
|
switch ($mode) {
|
|
case 'fileonly':
|
|
$result[] = $value;
|
|
break;
|
|
case 'fileBaseName':
|
|
$result[] = basename("/$value", $extFound);
|
|
break;
|
|
case 'dirFileBaseName':
|
|
$result[] = $searchDir . "/" . basename("/$value", $extFound);
|
|
break;
|
|
default:
|
|
$result[] = "$searchDir/$value";
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
// Now iterate over sub directories
|
|
$sub_find = $this->findAllFiles("$searchDir/$value", $file_mask, $mode);
|
|
if (!empty($sub_find)) {
|
|
foreach ($sub_find as $sub_value) {
|
|
$result[] = $sub_value;
|
|
}
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
function is_assoc($array) {
|
|
foreach (array_keys($array) as $k => $v) {
|
|
if ($k !== $v)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function tftpReadTestFile($remoteFileName, $host = "127.0.0.1")
|
|
{
|
|
// https://datatracker.ietf.org/doc/html/rfc1350
|
|
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
|
|
// Set timeout so that do not hang if no data received.
|
|
socket_set_option($socket,SOL_SOCKET, SO_RCVTIMEO, array('sec'=>1, 'usec'=>0));
|
|
|
|
if ($socket) {
|
|
$port = 69; // Initial TFTP port. Changed in received packet.
|
|
|
|
// create the RRQ request packet
|
|
$packet = chr(0) . chr(1) . $remoteFileName . chr(0) . 'netascii' . chr(0);
|
|
// UDP is connectionless, so we just send it.
|
|
socket_sendto($socket, $packet, strlen($packet), MSG_EOR, $host, $port);
|
|
|
|
$buffer = null;
|
|
$port = "";
|
|
$ret = "";
|
|
// MSG_WAITALL is blocking but socket has timeout set to 1 sec.
|
|
$numbytes = socket_recvfrom($socket, $buffer, 84, MSG_WAITALL, $host, $port);
|
|
|
|
if ($numbytes < 2) {
|
|
// socket has timed out before data received.
|
|
return false;
|
|
}
|
|
// unpack the returned buffer and discard the first two bytes.
|
|
$pkt = unpack("nopcode/nblockno/a*data", $buffer);
|
|
|
|
// send ack and close socket.
|
|
$packet = chr(4) . chr($pkt["blockno"]);
|
|
socket_sendto($socket, $packet, strlen($packet), MSG_EOR, $host, $port);
|
|
|
|
socket_close($socket);
|
|
|
|
if ($pkt["opcode"] == 3 && $numbytes) {
|
|
return $pkt["data"];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function initialiseConfInit(){
|
|
$read_config = \FreePBX::LoadConfig()->getConfig('sccp.conf');
|
|
$sccp_conf_init['general'] = $read_config['general'];
|
|
foreach ($read_config as $key => $value) {
|
|
if (isset($read_config[$key]['type'])) { // copy soft key
|
|
if ($read_config[$key]['type'] == 'softkeyset') {
|
|
$sccp_conf_init[$key] = $read_config[$key];
|
|
}
|
|
}
|
|
}
|
|
return $sccp_conf_init;
|
|
}
|
|
|
|
|
|
public function checkTftpMapping(){
|
|
exec('in.tftpd -V', $tftpInfo);
|
|
$info['TFTP Server'] = array('Version' => 'Not Found', 'about' => 'Mapping not available');
|
|
|
|
if (isset($tftpInfo[0])) {
|
|
$tftpInfo = explode(',',$tftpInfo[0]);
|
|
$info['TFTP Server'] = array('Version' => $tftpInfo[0], 'about' => 'Mapping not available');
|
|
$tftpInfo[1] = trim($tftpInfo[1]);
|
|
$this->sccpvalues['tftp_rewrite']['data'] = 'off';
|
|
if ($tftpInfo[1] == 'with remap') {
|
|
$info['TFTP Server'] = array('Version' => $tftpInfo[0], 'about' => $tftpInfo[1]);
|
|
|
|
$remoteFileName = ".sccp_manager_remap_probe_sentinel_temp".mt_rand(0, 9999999).".tlzz";
|
|
$remoteFileContent = "# This is a test file created by Sccp_Manager. It can be deleted without impact";
|
|
$testFtpDir = "{$this->sccpvalues['tftp_path']['data']}/settings";
|
|
|
|
// write a sentinel to a tftp subdirectory to see if mapping is working
|
|
|
|
if (is_dir($testFtpDir) && is_writable($testFtpDir)) {
|
|
$tempFile = "${testFtpDir}/{$remoteFileName}";
|
|
file_put_contents($tempFile, $remoteFileContent);
|
|
// try to pull the written file through tftp.
|
|
// this way we can determine if mapping is active and using sccp_manager maps
|
|
if ($remoteFileContent == $this->tftpReadTestFile($remoteFileName)) {
|
|
//found the file and contents are correct
|
|
$this->sccpvalues['tftp_rewrite']['data'] = 'pro';
|
|
} else {
|
|
// Did not find sentinel so mapping not available
|
|
$this->sccpvalues['tftp_rewrite']['data'] = 'off';
|
|
}
|
|
unlink($tempFile);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// helper function to save xml with proper indentation
|
|
public function saveXml($xml, $filename) {
|
|
$dom = new \DOMDocument("1.0");
|
|
$dom->preserveWhiteSpace = false;
|
|
$dom->formatOutput = true;
|
|
$dom->loadXML($xml->asXML());
|
|
$dom->save($filename);
|
|
}
|
|
|
|
public function getFileListFromProvisioner(string $tftpRootPath) {
|
|
|
|
$provisionerUrl = "https://github.com/dkgroot/provision_sccp/raw/master/";
|
|
// Get master tftpboot directory structure
|
|
try {
|
|
file_put_contents("{$tftpRootPath}/masterFilesStructure.xml",file_get_contents("{$provisionerUrl}tools/tftpbootFiles.xml"));
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function getChanSccpSettings() {
|
|
// This is a utility function for debug only, and is not used by core code
|
|
foreach (array('general','line', 'device') as $section) {
|
|
$sysConfig = $this->aminterface->getSCCPConfigMetaData($section);
|
|
dbug($sysConfig);
|
|
}
|
|
unset($sysConfig);
|
|
}
|
|
|
|
public function createDefaultSccpConfig(array $sccpvalues, string $asteriskPath, $conf_init) {
|
|
global $cnf_wr;
|
|
// Make sccp.conf data
|
|
// [general] section
|
|
// TODO: Need to review sccpsettings seq numbering, as will speed this up, and remove the need for $permittedSettings.
|
|
$cnf_wr = \FreePBX::WriteConfig();
|
|
//clear old general settings, and initiate with allow/disallow and permit/deny keys in correct order
|
|
$conf_init['general'] = array();
|
|
$conf_init['general']['disallow'] = 'all';
|
|
$conf_init['general']['allow'] = '';
|
|
$conf_init['general']['deny'] = '0.0.0.0/0.0.0.0';
|
|
$conf_init['general']['permit'] = '0.0.0.0/0.0.0.0';
|
|
// permitted chan-sccp settings array
|
|
$permittedSettings = array(
|
|
'debug', 'servername', 'keepalive', 'context', 'dateformat', 'bindaddr', 'port', 'secbindaddr', 'secport', 'disallow', 'allow', 'deny', 'permit',
|
|
'localnet', 'externip', 'externrefresh', 'firstdigittimeout', 'digittimeout', 'digittimeoutchar', 'recorddigittimeoutchar', 'simulate_enbloc',
|
|
'ringtype', 'autoanswer_ring_time', 'autoanswer_tone', 'remotehangup_tone', 'transfer', 'transfer_tone', 'transfer_on_hangup', 'dnd_tone',
|
|
'callwaiting_tone', 'callwaiting_interval', 'musicclass', 'language', 'callevents', 'accountcode', 'sccp_tos', 'sccp_cos', 'audio_tos',
|
|
'audio_cos', 'video_tos', 'video_cos', 'echocancel', 'silencesuppression', 'earlyrtp', 'dndFeature', 'private', 'mwilamp', 'mwioncall',
|
|
'cfwdall', 'cfwdbusy', 'cfwdnoanswer', 'cfwdnoanswer_timeout', 'nat', 'directrtp', 'allowoverlap', 'pickup_modeanswer',
|
|
'callhistory_answered_elsewhere', 'amaflags', 'callanswerorder', 'devicetable', 'linetable', 'meetmeopts', 'jbenable', 'jbforce',
|
|
'jblog', 'jbmaxsize', 'jbresyncthreshold', 'jbimpl', 'hotline_enabled', 'hotline_extension', 'hotline_context', 'hotline_label', 'fallback',
|
|
'backoff_time', 'server_priority');
|
|
|
|
foreach ($sccpvalues as $key => $value) {
|
|
if (!in_array($key, $permittedSettings, true)) {
|
|
continue;
|
|
}
|
|
if ($value['seq'] == 0) {
|
|
switch ($key) {
|
|
case "allow":
|
|
case "disallow":
|
|
case "deny":
|
|
case "localnet":
|
|
case "permit":
|
|
$conf_init['general'][$key] = explode(';', $value['data']);
|
|
break;
|
|
case "devlang":
|
|
/*
|
|
$lang_data = $this->extconfigs->getExtConfig('sccp_lang', $value['data']);
|
|
if (!empty($lang_data)) {
|
|
// TODO: will always get here, but lang_data['codepage'] will be empty as not a valid key
|
|
$this->sccp_conf_init['general']['phonecodepage'] = $lang_data['codepage'];
|
|
}
|
|
break;
|
|
*/
|
|
case "netlang": // Remove Key
|
|
case "tftp_path":
|
|
case "sccp_compatible": // This is equal to SccpDBmodel
|
|
break;
|
|
default:
|
|
if (!empty($value['data'])) {
|
|
$conf_init['general'][$key] = $value['data'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// ----- It is a very bad idea to add an external configuration file "sccp_custom.conf" !!!!
|
|
// This will complicate solving problems caused by unexpected solutions from users.
|
|
//
|
|
if (file_exists($asteriskPath . "/sccp_custom.conf")) {
|
|
$conf_init['HEADER'] = array(
|
|
"; ;",
|
|
"; It is a very bad idea to add an external configuration file !!!! ;",
|
|
"; This will complicate solving problems caused by unexpected solutions ;",
|
|
"; from users. ;",
|
|
";--------------------------------------------------------------------------------;",
|
|
"#include sccp_custom.conf"
|
|
);
|
|
}
|
|
$cnf_wr->WriteConfig('sccp.conf', $conf_init);
|
|
}
|
|
|
|
public function initVarfromXml() {
|
|
if ((array) $this->xml_data) {
|
|
foreach ($this->xml_data->xpath('//page_group') as $item) {
|
|
foreach ($item->children() as $child) {
|
|
$seq = 0;
|
|
if (!empty($child['seq'])) {
|
|
$seq = (string) $child['seq'];
|
|
}
|
|
if ($seq < 99) {
|
|
if ($child['type'] == 'IE') {
|
|
foreach ($child->xpath('input') as $value) {
|
|
$tp = 0;
|
|
if (empty($value->value)) {
|
|
$datav = (string) $value->default;
|
|
} else {
|
|
$datav = (string) $value->value;
|
|
}
|
|
if (strtolower($value->type) == 'number') {
|
|
$tp = 1;
|
|
}
|
|
if (empty($this->sccpvalues[(string) $value->name])) {
|
|
$this->sccpvalues[(string) $value->name] = array('keyword' => (string) $value->name, 'data' => $datav, 'type' => $tp, 'seq' => $seq, 'systemdefault' => '');
|
|
}
|
|
}
|
|
}
|
|
if ($child['type'] == 'IS' || $child['type'] == 'IED') {
|
|
if (empty($child->value)) {
|
|
$datav = (string) $child->default;
|
|
} else {
|
|
$datav = (string) $child->value;
|
|
}
|
|
if (empty($this->sccpvalues[(string) $child->name])) {
|
|
$this->sccpvalues[(string) $child->name] = array('keyword' => (string) $child->name, 'data' => $datav, 'type' => '2', 'seq' => $seq, 'systemdefault' => '');
|
|
}
|
|
}
|
|
if (in_array($child['type'], array('SLD', 'SLS', 'SLT', 'SLNA', 'SLDA', 'SL', 'SLM', 'SLZ', 'SLTZN', 'SLA'))) {
|
|
if (empty($child->value)) {
|
|
$datav = (string) $child->default;
|
|
} else {
|
|
$datav = (string) $child->value;
|
|
}
|
|
if (empty($this->sccpvalues[(string) $child->name])) {
|
|
$this->sccpvalues[(string) $child->name] = array('keyword' => (string) $child->name, 'data' => $datav, 'type' => '2', 'seq' => $seq, 'systemdefault' => '');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getSipConfig() {
|
|
// Only called from sccp_manager class when saving SIP device
|
|
$result = array();
|
|
|
|
$tmp_binds = \FreePBX::Sipsettings()->getBinds();
|
|
$if_list = $this->getIpInformation('ip4');
|
|
if (!is_array($tmp_binds)) {
|
|
// FreePBX has no sip bindings.
|
|
die_freepbx(_("SIP server configuration error ! No SIP protocols enabled"));
|
|
}
|
|
foreach ($tmp_binds as $fpbx_protocol => $fpbx_bind) {
|
|
foreach ($fpbx_bind as $protocol_ip => $protocol_port_arr) {
|
|
if (empty($protocol_port_arr)) {
|
|
continue;
|
|
}
|
|
if (($protocol_ip == '0.0.0.0') || ($protocol_ip == '[::]')) {
|
|
foreach ($if_list as $if_type => $if_data) {
|
|
if ($if_data['ip'] == "127.0.0.1") {
|
|
continue;
|
|
}
|
|
if (empty($result[$fpbx_protocol][$if_data['ip']])) {
|
|
$result[$fpbx_protocol][$if_data['ip']]= $protocol_port_arr;
|
|
} else {
|
|
$result[$fpbx_protocol][$if_data['ip']]= array_merge($result[$fpbx_protocol][$if_data['ip']],$protocol_port_arr);
|
|
}
|
|
$result[$fpbx_protocol][$if_data['ip']]['ip']=$if_data['ip'];
|
|
}
|
|
} else {
|
|
$result[$fpbx_protocol][$protocol_ip]=$protocol_port_arr;
|
|
$result[$fpbx_protocol][$protocol_ip]['ip']=$protocol_ip;
|
|
}
|
|
}
|
|
}
|
|
if (empty($result)) {
|
|
die_freepbx(_("SIP server configuration error ! No SIP protocols enabled"));
|
|
}
|
|
return $result;
|
|
}
|
|
}
|
|
?>
|