Some device settings were being reset by Doctrine. Ensure that these settings are restored after Doctrine has finished. Clarify some settings Remove legacy code which could potentially reset settings
520 lines
22 KiB
PHP
520 lines
22 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) {
|
|
$enumFields = array();
|
|
$sccpTableDesc = $this->dbinterface->getSccpDeviceTableData("get_columns_${table}");
|
|
foreach ($sccpTableDesc as $key => $data) {
|
|
$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;
|
|
}
|
|
dbug($child->name);
|
|
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' => '');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dbug($this->sccpvalues['earlyrtp']);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
?>
|