Skip to content

Commit

Permalink
feat(trunks): allow to enable/disable TOPOS for each trunk (#341)
Browse files Browse the repository at this point in the history
* feat(trunks): add switch to enable/disable TOPOS

- Added a switch in the trunks interface to enable or disable TOPOS.
- When the "disable TOPOS" switch is activated, an "topos=0" header is sent in INVITE requests to instruct the nethvoice-proxy Kamailio to disable the TOPOS module for that specific invite.
- TOPOS configuration for trunks is now stored in the NethCTI FreePBX module's configuration key-value store.
- Modified the trunk interface and dialplan using dialplan hooks to accommodate the new feature.
- Automatically set the `disable_topos` value for trunks created via the wizard.
- Remove the `disable_topos` value when a trunk is deleted via the wizard.
- Added a new MySQL table to store default `disable_topos` values.
- If no default `disable_topos` value is specified, no header will be sent and Kamailio will use TOPOS nethesis/ns8-nethvoice-proxy#51
- Add switch to control the sending of encrypted media on trunks.
- Migrate existing trunks to avoid attempting encrypted media with providers where media encryption is disabled.
- Removed legacy SQL tables that were used for database initialization in previous versions.
- Now all database tables are initialized in mariadb container and altered in freepbx/initdb.d

NethServer/dev#7183
  • Loading branch information
Stell0 authored Jan 8, 2025
1 parent 449c761 commit 9c493b8
Show file tree
Hide file tree
Showing 17 changed files with 380 additions and 1,861 deletions.
100 changes: 100 additions & 0 deletions freepbx/initdb.d/migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,103 @@
$stmt = $db->prepare($sql);
$stmt->execute(['127.0.0.1:'.$_ENV['NETHVOICE_MARIADB_PORT']]);

# Create pjsip trunks custom flags table if not exist
# create NethCTI3 configuration table if not exist
$sql = "CREATE TABLE IF NOT EXISTS `kvstore_FreePBX_modules_Nethcti3` (
`key` char(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`val` varchar(4096) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`type` char(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`id` char(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
UNIQUE KEY `uniqueindex` (`key`(190),`id`(190)),
KEY `keyindex` (`key`(190)),
KEY `idindex` (`id`(190))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$stmt = $db->prepare($sql);
$stmt->execute();
# check if table exists
$sql = "SELECT * FROM information_schema.tables WHERE TABLE_SCHEMA = 'asterisk' AND TABLE_NAME = 'pjsip_trunks_custom_flags'";
$stmt = $db->prepare($sql);
$stmt->execute();
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
if (count($res) == 0) {
// Create table and add default values
$db->query("CREATE TABLE `rest_pjsip_trunks_custom_flags` (
`provider_id` bigint(20) NOT NULL,
`keyword` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`value` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`provider_id`,`keyword`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" );
$db->query("INSERT INTO `rest_pjsip_trunks_custom_flags` (`provider_id`, `keyword`, `value`) VALUES
(1,'disable_topos_header',0),
(2,'disable_topos_header',0),
(3,'disable_topos_header',0),
(4,'disable_topos_header',0),
(5,'disable_topos_header',0),
(6,'disable_topos_header',0),
(7,'disable_topos_header',0),
(8,'disable_topos_header',0),
(9,'disable_topos_header',0),
(10,'disable_topos_header',0),
(11,'disable_topos_header',0),
(12,'disable_topos_header',0),
(13,'disable_topos_header',0),
(14,'disable_topos_header',0),
(15,'disable_topos_header',0),
(16,'disable_topos_header',0),
(17,'disable_topos_header',0),
(18,'disable_topos_header',0),
(19,'disable_topos_header',0),
(20,'disable_topos_header',0),
(21,'disable_topos_header',0),
(22,'disable_topos_header',0),
(23,'disable_topos_header',0),
(24,'disable_topos_header',0),
(25,'disable_topos_header',0),
(1,'disable_srtp_header',1),
(2,'disable_srtp_header',1),
(3,'disable_srtp_header',0),
(4,'disable_srtp_header',1),
(5,'disable_srtp_header',1),
(6,'disable_srtp_header',1),
(7,'disable_srtp_header',1),
(8,'disable_srtp_header',1),
(9,'disable_srtp_header',1),
(10,'disable_srtp_header',0),
(11,'disable_srtp_header',1),
(12,'disable_srtp_header',1),
(13,'disable_srtp_header',1),
(14,'disable_srtp_header',1),
(15,'disable_srtp_header',1),
(16,'disable_srtp_header',1),
(17,'disable_srtp_header',1),
(18,'disable_srtp_header',1),
(19,'disable_srtp_header',1),
(20,'disable_srtp_header',1),
(21,'disable_srtp_header',1),
(22,'disable_srtp_header',1),
(23,'disable_srtp_header',1),
(24,'disable_srtp_header',1);
");
}
// Add disable_srtp_header configuration for existing trunks that doesn't have media encription enabled and proxy configured
$sql = "SELECT DISTINCT id
FROM pjsip
WHERE id IN (
SELECT id
FROM pjsip
WHERE keyword = 'media_encryption' AND data = 'no'
)
AND id IN (
SELECT id
FROM pjsip
WHERE keyword = 'outbound_proxy' AND data IS NOT NULL AND data != ''
)";
$stmt = $db->prepare($sql);
$stmt->execute();
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$trunk_ids = array_column($res, 'id');
foreach ($trunk_ids as $trunk_id) {
$sql = "INSERT IGNORE INTO `kvstore_FreePBX_modules_Nethcti3` (`key`, `value`,`id`) VALUES ('disable_srtp_header`,'1',?)";
$stmt = $db->prepare($sql);
$stmt->execute([$trunk_id]);
}
145 changes: 142 additions & 3 deletions freepbx/var/www/html/freepbx/admin/modules/nethcti3/Nethcti3.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

namespace FreePBX\modules;

class Nethcti3 implements \BMO
class Nethcti3 extends \FreePBX_Helpers implements \BMO
{
public function __construct($freepbx = null) {
if ($freepbx == null)
Expand All @@ -39,8 +39,6 @@ public function backup() {
}
public function restore($backup) {
}
public function doConfigPageInit($page) {
}

/*Write a CTI configuration file in JSON format*/
public function writeCTIConfigurationFile($filename, $obj) {
Expand Down Expand Up @@ -243,4 +241,145 @@ public function genConfig() {
}
return $out;
}

// Add custom headers for trunks to trunks module
public static function myGuiHooks() {
return array("core", "INTERCEPT" => array("modules/core/page.trunks.php"));
}

public function doGuiHook($filename, &$output){}

public function doGuiIntercept($filename, &$output) {
# Show the custom field in the trunks module
if ($filename == "modules/core/page.trunks.php" && $_REQUEST['display'] == "trunks" && strtolower($_REQUEST['tech']) == "pjsip") {
$trunkid = str_replace("OUT_", "", $_REQUEST['extdisplay']);
$disable_topos_header = $this->getConfig('disable_topos_header', $trunkid);
$disable_srtp_header = $this->getConfig('disable_srtp_header', $trunkid);
$topos_section = '
<!--DISABLE TOPOS-->
<div class="element-container">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="form-group">
<div class="col-md-3">
<label class="control-label" for="disable_topos_header">'._("Disable TOPOS proxy header").'</label>
<i class="fa fa-question-circle fpbx-help-icon" data-for="disable_topos_header"></i>
</div>
<div class="col-md-9 radioset">
<input type="radio" name="disable_topos_header" id="disable_topos_headeryes" value="yes" '.($disable_topos_header == 1?"CHECKED":"").'>
<label for="disable_topos_headeryes">'. _("Yes") .'</label>
<input type="radio" name="disable_topos_header" id="disable_topos_headerno" value="no" '.($disable_topos_header == 0 || empty($disable_topos_header) ? "CHECKED" : "").'>
<label for="disable_topos_headerno">'._("No").'</label>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<span id="disable_topos_header-help" class="help-block fpbx-help-block">'. _("If yes, send topos=0 header to nethvoice-proxy to disable TOPOS for this trunk").'</span>
</div>
</div>
</div>
<!--END DISABLE TOPOS-->';
$disable_srtp_header_section = '
<!--DISABLE SRTP-->
<div class="element-container">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="form-group">
<div class="col-md-3">
<label class="control-label" for="disable_srtp_header">'._("Disable SRTP proxy header").'</label>
<i class="fa fa-question-circle fpbx-help-icon" data-for="disable_srtp_header"></i>
</div>
<div class="col-md-9 radioset">
<input type="radio" name="disable_srtp_header" id="disable_srtp_headeryes" value="yes" '.($disable_srtp_header == 1?"CHECKED":"").'>
<label for="disable_srtp_headeryes">'. _("Yes") .'</label>
<input type="radio" name="disable_srtp_header" id="disable_srtp_headerno" value="no" '.($disable_srtp_header == 0 || empty($disable_srtp_header) ? "CHECKED" : "").'>
<label for="disable_srtp_headerno">'._("No").'</label>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<span id="disable_srtp_header-help" class="help-block fpbx-help-block">'. _("If yes, send isTrunk=1 header to nethvoice-proxy to disable SRTP for this trunk").'</span>
</div>
</div>
</div>
<!--END DISABLE SRTP-->';
$output = str_replace('<!--END OUTBOUND PROXY-->','<!--END OUTBOUND PROXY-->'.$topos_section.$disable_srtp_header_section,$output);
}
}

public static function myConfigPageInits() {
return array("extensions", "recallonbusy", "trunks");

}

public function doConfigPageInit($display) {
global $astman;
if ($display == "extensions" && !empty($_REQUEST['recallonbusy'])) {
// Save Recall On Busy option for the extension
$astman->database_put("ROBconfig",$_REQUEST['extdisplay'],$_REQUEST['recallonbusy']);
} elseif ($display == "recallonbusy") {
if (!empty($_REQUEST['default'])) {
$this->setConfig('default',$_REQUEST['default']);
}
if (!empty($_REQUEST['digit'])) {
$this->setConfig('digit',$_REQUEST['digit']);
}
needreload();
} elseif ($display == "trunks") {
global $db;
if ($_REQUEST['action'] == "edittrunk" && !empty($_REQUEST['extdisplay'])) {
if (!empty($_REQUEST['disable_topos_header'])) {
// save topos configuratino for the trunk on trunk edit
$disable_topos_header = $_REQUEST['disable_topos_header'] == "yes" ? 1 : 0;
$trunkid = str_replace("OUT_", "", $_REQUEST['extdisplay']);
$this->setConfig('disable_topos_header', $disable_topos_header, $trunkid);
}
if (!empty($_REQUEST['disable_srtp_header'])) {
// save srtp configuration for the trunk on trunk edit
$disable_srtp_header = $_REQUEST['disable_srtp_header'] == "yes" ? 1 : 0;
$trunkid = str_replace("OUT_", "", $_REQUEST['extdisplay']);
$this->setConfig('disable_srtp_header', $disable_srtp_header, $trunkid);
}
} elseif ($_REQUEST['action'] == "addtrunk") {
// Get the future trunk id
$sql = 'SELECT trunkid FROM trunks';
$sth = $db->prepare($sql);
$sth->execute();
$trunkid = 1;
while ($res = $sth->fetchColumn()) {
if ($res > $trunkid) {
break;
}
$trunkid++;
}
if ($res == $trunkid) {
$trunkid++;
}
if (!empty($_REQUEST['disable_topos_header'])){
// save topos configuration for the trunk on trunk add
$disable_topos_header = $_REQUEST['disable_topos_header'] == "yes" ? 1 : 0;
$this->setConfig('disable_topos_header', $disable_topos_header, $trunkid);
}
if (!empty($_REQUEST['disable_srtp_header'])){
// save srtp configuration for the trunk on trunk add
$disable_srtp_header = $_REQUEST['disable_srtp_header'] == "yes" ? 1 : 0;
$this->setConfig('disable_srtp_header', $disable_srtp_header, $trunkid);
}
} elseif ($_REQUEST['action'] == "deltrunk") {
$trunkid = str_replace("OUT_", "", $_REQUEST['extdisplay']);
// delete topos configuration for the trunk
$this->delConfig('disable_topos_header', $trunkid);
// delete srtp configuration for the trunk
$this->delConfig('disable_srtp_header', $trunkid);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function nethcti3_get_config($engine) {
if (isset($core_conf) && (method_exists($core_conf, 'addSipNotify'))) {
$core_conf->addSipNotify('generic-reload', array('Event' => 'check-sync\;reboot=false', 'Content-Length' => '0'));
}
/*Add contexts for gateway trunks identity*/
/*Add contexts for gateway trunks identity*/
$context = 'from-pstn-identity';
$ext->add($context, '_X!', '', new ext_noop('P-Preferred-Identity ${CUT(CUT(PJSIP_HEADER(read,P-Preferred-Identity),@,1),:,2)}'));
$ext->add($context, '_X!', '', new ext_noop('P-Asserted-Identity ${CUT(CUT(PJSIP_HEADER(read,P-Asserted-Identity),@,1),:,2)}'));
Expand All @@ -113,8 +113,8 @@ function nethcti3_get_config_late($engine) {
global $db;
switch($engine) {
case "asterisk":
/* Change CF for CTI voicemail status */
$ext->replace('macro-dial-one', 'cf', '2', new ext_execif('$["${DB(AMPUSER/${DB_RESULT}/cidnum)}" == "" && "${DB_RESULT:0:2}" != "vm"]', 'Set','__REALCALLERIDNUM=${DEXTEN}'));
/* Change CF for CTI voicemail status */
$ext->replace('macro-dial-one', 'cf', '2', new ext_execif('$["${DB(AMPUSER/${DB_RESULT}/cidnum)}" == "" && "${DB_RESULT:0:2}" != "vm"]', 'Set','__REALCALLERIDNUM=${DEXTEN}'));

/* Use main extension on login/logout/pause*/
if (!empty(\FreePBX::Queues()->listQueues())) {
Expand All @@ -138,31 +138,33 @@ function nethcti3_get_config_late($engine) {
$ext->splice('macro-dial-one','s','dial', new ext_execif('$["${DB(AMPUSER/${ARG3}/cidname)}" != "" && "${DB(AMPUSER/${CALLERID(num)}/cidname)}" = "" && "${ATTENDEDTRANSFER}" != "" && "${DB(AMPUSER/${FROMEXTEN}/cidname)}" != ""]', 'Set', 'CALLERID(num)=${DB(AMPUSER/${FROMEXTEN}/cidnum)}'),'',-1);
$ext->splice('macro-dial-one','s','dial', new ext_execif('$["${DB(AMPUSER/${CALLERID(num)}/cidname)}" != "" && "${ATTENDEDTRANSFER}" != ""]', 'Set', 'CALLERID(name)=${DB(AMPUSER/${CALLERID(num)}/cidname)}'),'',-1);
}
/*Add isTrunk = 1 header to VoIP trunks that doesn't require SRTP encryption*/
// Get all voip providers ip that doesn't need media encryption
$sql = "SELECT t1.data
FROM rest_pjsip_trunks_defaults AS t1
JOIN rest_pjsip_providers AS t2 ON t1.provider_id = t2.id
JOIN rest_pjsip_trunks_defaults AS t3 ON t2.id = t3.provider_id
WHERE t3.keyword = 'media_encryption' AND t3.data = 'no'
AND t1.keyword = 'sip_server'";
$stmt = $db->prepare($sql);
$stmt->execute();
$voip_providers = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Get all trunks
$nethcti3 = \FreePBX::Nethcti3();
$trunks = FreePBX::Core()->listTrunks();
$voip_trunk_if = [];
foreach ($trunks as $trunk) {
$details = FreePBX::Core()->getTrunkDetails($trunk['trunkid']);
if (in_array($details['sip_server'], $voip_providers)) {
// Trunk needs needs media encryption disabled, set isTrunk header to 1
try {
$ext->splice('macro-dialout-trunk', 's', 'gocall', new ext_gosubif('$["${DIAL_TRUNK}" = "' . $trunk['trunkid'] . '"]', 'func-set-sipheader,s,1', false, 'isTrunk,1'));
} Catch(Exception $e) {
error_log('error adding isTrunk header setter to dialplan');
try {
/*Add isTrunk = 1 header to VoIP trunks that doesn't require SRTP encryption*/
$disable_srtp_header = $nethcti3->getConfig('disable_srtp_header', $trunk['trunkid']);
if ($disable_srtp_header==1) {
$ext->splice('macro-dialout-trunk', 's', 'gocall', new ext_gosubif('$["${DIAL_TRUNK}" = "' . $trunk['trunkid'] . '"]', 'func-set-sipheader,s,1', false, 'isTrunk,1'),'',6);
$add_unset_istrunk = true;
}
/*Add topos=0 header to voip trunks with disabled TOPOS for compatibility*/
$disable_topos_header = $nethcti3->getConfig('disable_topos_header', $trunk['trunkid']);
if ($disable_topos_header==1) {
$ext->splice('macro-dialout-trunk', 's', 'gocall', new ext_gosubif('$["${DIAL_TRUNK}" = "' . $trunk['trunkid'] . '"]', 'func-set-sipheader,s,1', false, 'topos,0'),'',6);
$add_unset_topos = true;
}
} catch (Exception $e) {
error_log('Error adding additional headers to trunk: '.$e->getMessage());
}
}
/* unset topos and isTrunk headers for calls to local extensions */
if ($add_unset_istrunk) {
$ext->splice('macro-dial-one', 's', 'setexttocall', new ext_gosub(1,'s','func-set-sipheader', 'isTrunk,unset'), '', 1);
}
if ($add_unset_topos) {
$ext->splice('macro-dial-one', 's', 'setexttocall', new ext_gosub(1,'s','func-set-sipheader', 'topos,unset'), '', 1);
}
/* Add inboundlookup agi for each inbound routes*/
$dids = FreePBX::Core()->getAllDIDs();
if (!empty($dids)) {
Expand Down Expand Up @@ -458,10 +460,10 @@ function nethcti3_get_config_late($engine) {
// Generate nethvoice report based on NethCTI configuration
nethvoice_report_config();

// Convert /etc/asterisk symlinks to file copied
if (file_exists('/var/lib/asterisk/bin/symlink2copies.sh')) {
system("/var/lib/asterisk/bin/symlink2copies.sh");
}
// Convert /etc/asterisk symlinks to file copied
if (file_exists('/var/lib/asterisk/bin/symlink2copies.sh')) {
system("/var/lib/asterisk/bin/symlink2copies.sh");
}

//Reload CTI
system("/var/www/html/freepbx/rest/lib/ctiReloadHelper.sh > /dev/null 2>&1 &");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<rawname>nethcti3</rawname>
<repo>unsupported</repo>
<name>NethCTI 3 Configuration Module</name>
<version>1.4.31</version>
<version>1.5.0</version>
<category>Applications</category>
<Publisher>Nethesis</Publisher>
<info></info>
Expand Down
Loading

0 comments on commit 9c493b8

Please sign in to comment.