mirror of
https://github.com/fusionpbx/fusionpbx.git
synced 2025-12-30 17:13:49 +00:00
Adding this fix enables both remote phone book and provisioning to work correctly on the newly released android based Yealink devices. It appears that during the provisioning process, the T5X series supply a colon formatted mac address in their user agent. However, when requesting a remote phone book, the presented user agent has no colons. This allows both formats to work as expected.
459 lines
17 KiB
PHP
459 lines
17 KiB
PHP
<?php
|
|
/*
|
|
FusionPBX
|
|
Version: MPL 1.1
|
|
|
|
The contents of this file are subject to the Mozilla Public License Version
|
|
1.1 (the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
for the specific language governing rights and limitations under the
|
|
License.
|
|
|
|
The Original Code is FusionPBX
|
|
|
|
The Initial Developer of the Original Code is
|
|
Mark J Crane <markjcrane@fusionpbx.com>
|
|
Copyright (C) 2008-2016 All Rights Reserved.
|
|
|
|
Contributor(s):
|
|
Mark J Crane <markjcrane@fusionpbx.com>
|
|
Luis Daniel Lucio Quiroz <dlucio@okay.com.mx>
|
|
*/
|
|
|
|
//includes
|
|
include "root.php";
|
|
require_once "resources/require.php";
|
|
require_once "resources/functions/device_by.php";
|
|
|
|
//logging
|
|
openlog("FusionPBX", LOG_PID | LOG_PERROR, LOG_LOCAL0);
|
|
|
|
//set default variables
|
|
$dir_count = 0;
|
|
$file_count = 0;
|
|
$row_count = 0;
|
|
$device_template = '';
|
|
|
|
//define PHP variables from the HTTP values
|
|
$mac = check_str($_REQUEST['mac']);
|
|
$file = check_str($_REQUEST['file']);
|
|
$ext = check_str($_REQUEST['ext']);
|
|
//if (strlen(check_str($_REQUEST['template'])) > 0) {
|
|
// $device_template = check_str($_REQUEST['template']);
|
|
//}
|
|
|
|
//get the mac address for Cisco 79xx in the URL as &name=SEP000000000000
|
|
if (empty($mac)){
|
|
$name = check_str($_REQUEST['name']);
|
|
if (substr($name, 0, 3) == "SEP") {
|
|
$mac = strtolower(substr($name, 3, 12));
|
|
unset($name);
|
|
}
|
|
}
|
|
|
|
// Escence make request based on UserID for Memory keys
|
|
// The file name is fixed to `Account1_Extern.xml`.
|
|
// (Account1 is the first account you register)
|
|
if(empty($mac) && !empty($ext)){
|
|
$domain_array = explode(":", $_SERVER["HTTP_HOST"]);
|
|
$domain_name = $domain_array[0];
|
|
$device = device_by_ext($db, $ext, $domain_name);
|
|
if(($device !== false)&&(($device['device_vendor']=='escene')||($device['device_vendor']=='grandstream'))){
|
|
$mac = $device['device_mac_address'];
|
|
}
|
|
}
|
|
|
|
//check alternate MAC source
|
|
if (empty($mac)){
|
|
//set the http user agent
|
|
//$_SERVER['HTTP_USER_AGENT'] = "Yealink SIP-T38G 38.70.0.125 00:15:65:00:00:00";
|
|
//$_SERVER['HTTP_USER_AGENT'] = "Yealink SIP-T56A 58.80.0.25 001565f429a4";
|
|
//Yealink: 17 digit mac appended to the user agent, so check for a space exactly 17 digits before the end.
|
|
if (strtolower(substr($_SERVER['HTTP_USER_AGENT'],0,7)) == "yealink" || strtolower(substr($_SERVER['HTTP_USER_AGENT'],0,5)) == "vp530") {
|
|
if (strstr(substr($_SERVER['HTTP_USER_AGENT'],-4), ':')) { //remove colons if they exist
|
|
$mac = substr($_SERVER['HTTP_USER_AGENT'],-17);
|
|
$mac = preg_replace("#[^a-fA-F0-9./]#", "", $mac);
|
|
} else { //take mac as is - fixes T5X series
|
|
$mac = substr($_SERVER['HTTP_USER_AGENT'],-12);
|
|
}
|
|
}
|
|
//Panasonic: $_SERVER['HTTP_USER_AGENT'] = "Panasonic_KX-UT670/01.022 (0080f000000)"
|
|
if (substr($_SERVER['HTTP_USER_AGENT'],0,9) == "Panasonic") {
|
|
$mac = substr($_SERVER['HTTP_USER_AGENT'],-14);
|
|
$mac = preg_replace("#[^a-fA-F0-9./]#", "", $mac);
|
|
}
|
|
//Grandstream: $_SERVER['HTTP_USER_AGENT'] = "Grandstream Model HW GXP2135 SW 1.0.7.97 DevId 000b828aa872"
|
|
if (substr($_SERVER['HTTP_USER_AGENT'],0,11) == "Grandstream") {
|
|
$mac = substr($_SERVER['HTTP_USER_AGENT'],-12);
|
|
$mac = preg_replace("#[^a-fA-F0-9./]#", "", $mac);
|
|
}
|
|
//Audiocodes: $_SERVER['HTTP_USER_AGENT'] = "AUDC-IPPhone/2.2.8.61 (440HDG-Rev0; 00908F602AAC)"
|
|
if (substr($_SERVER['HTTP_USER_AGENT'],0,12) == "AUDC-IPPhone") {
|
|
$mac = substr($_SERVER['HTTP_USER_AGENT'],-13);
|
|
$mac = preg_replace("#[^a-fA-F0-9./]#", "", $mac);
|
|
}
|
|
//Aastra: $_SERVER['HTTP_USER_AGENT'] = "Aastra6731i MAC:00-08-5D-29-4C-6B V:3.3.1.4365-SIP"
|
|
if (substr($_SERVER['HTTP_USER_AGENT'],0,6) == "Aastra") {
|
|
preg_match("/MAC:([A-F0-9-]{17})/", $_SERVER['HTTP_USER_AGENT'], $matches);
|
|
$mac = $matches[1];
|
|
$mac = preg_replace("#[^a-fA-F0-9./]#", "", $mac);
|
|
}
|
|
}
|
|
|
|
//prepare the mac address
|
|
if (isset($mac)) {
|
|
//normalize the mac address to lower case
|
|
$mac = strtolower($mac);
|
|
//replace all non hexadecimal values and validate the mac address
|
|
$mac = preg_replace("#[^a-fA-F0-9./]#", "", $mac);
|
|
if (strlen($mac) != 12) {
|
|
echo "invalid mac address";
|
|
exit;
|
|
}
|
|
}
|
|
|
|
//get the domain_name and domain_uuid
|
|
if ((!isset($_SESSION['provision']['http_domain_filter'])) or $_SESSION['provision']['http_domain_filter']['text'] == "false") {
|
|
//get the domain_uuid
|
|
$sql = "SELECT domain_uuid FROM v_devices ";
|
|
$sql .= "WHERE device_mac_address = '".$mac."' ";
|
|
$prep_statement = $db->prepare($sql);
|
|
$prep_statement->execute();
|
|
$result = $prep_statement->fetchAll(PDO::FETCH_NAMED);
|
|
foreach($result as $row) {
|
|
$domain_uuid = $row["domain_uuid"];
|
|
}
|
|
unset($result, $prep_statement);
|
|
$_SESSION['domain_uuid'] = $domain_uuid;
|
|
|
|
//get the domain name
|
|
$domain_name = $_SESSION['domains'][$domain_uuid]['domain_name'];
|
|
|
|
//set the PDO error mode
|
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
//get the default settings
|
|
$sql = "select * from v_default_settings ";
|
|
$sql .= "where default_setting_enabled = 'true' ";
|
|
try {
|
|
$prep_statement = $db->prepare($sql . " order by default_setting_order asc ");
|
|
$prep_statement->execute();
|
|
}
|
|
catch(PDOException $e) {
|
|
$prep_statement = $db->prepare($sql);
|
|
$prep_statement->execute();
|
|
}
|
|
$result = $prep_statement->fetchAll(PDO::FETCH_NAMED);
|
|
//unset the previous settings
|
|
foreach ($result as $row) {
|
|
unset($_SESSION[$row['default_setting_category']]);
|
|
}
|
|
//set the settings as a session
|
|
foreach ($result as $row) {
|
|
$name = $row['default_setting_name'];
|
|
$category = $row['default_setting_category'];
|
|
$subcategory = $row['default_setting_subcategory'];
|
|
if (strlen($subcategory) == 0) {
|
|
if ($name == "array") {
|
|
$_SESSION[$category][] = $row['default_setting_value'];
|
|
}
|
|
else {
|
|
$_SESSION[$category][$name] = $row['default_setting_value'];
|
|
}
|
|
} else {
|
|
if ($name == "array") {
|
|
$_SESSION[$category][$subcategory][] = $row['default_setting_value'];
|
|
}
|
|
else {
|
|
$_SESSION[$category][$subcategory]['uuid'] = $row['default_setting_uuid'];
|
|
$_SESSION[$category][$subcategory][$name] = $row['default_setting_value'];
|
|
}
|
|
}
|
|
}
|
|
|
|
//get the domains settings
|
|
if (strlen($_SESSION["domain_uuid"]) > 0) {
|
|
$sql = "select * from v_domain_settings ";
|
|
$sql .= "where domain_uuid = '" . $domain_uuid . "' ";
|
|
$sql .= "and domain_setting_enabled = 'true' ";
|
|
try {
|
|
$prep_statement = $db->prepare($sql . " order by domain_setting_order asc ");
|
|
$prep_statement->execute();
|
|
}
|
|
catch(PDOException $e) {
|
|
$prep_statement = $db->prepare($sql);
|
|
$prep_statement->execute();
|
|
}
|
|
$result = $prep_statement->fetchAll(PDO::FETCH_NAMED);
|
|
//unset the arrays that domains are overriding
|
|
foreach ($result as $row) {
|
|
$name = $row['domain_setting_name'];
|
|
$category = $row['domain_setting_category'];
|
|
$subcategory = $row['domain_setting_subcategory'];
|
|
if ($name == "array") {
|
|
unset($_SESSION[$category][$subcategory]);
|
|
}
|
|
}
|
|
//set the settings as a session
|
|
foreach ($result as $row) {
|
|
$name = $row['domain_setting_name'];
|
|
$category = $row['domain_setting_category'];
|
|
$subcategory = $row['domain_setting_subcategory'];
|
|
if (strlen($subcategory) == 0) {
|
|
//$$category[$name] = $row['domain_setting_value'];
|
|
if ($name == "array") {
|
|
$_SESSION[$category][] = $row['domain_setting_value'];
|
|
}
|
|
else {
|
|
$_SESSION[$category][$name] = $row['domain_setting_value'];
|
|
}
|
|
} else {
|
|
//$$category[$subcategory][$name] = $row['domain_setting_value'];
|
|
if ($name == "array") {
|
|
$_SESSION[$category][$subcategory][] = $row['domain_setting_value'];
|
|
}
|
|
else {
|
|
$_SESSION[$category][$subcategory][$name] = $row['domain_setting_value'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
//get the domain_name
|
|
$domain_array = explode(":", $_SERVER["HTTP_HOST"]);
|
|
$domain_name = $domain_array[0];
|
|
|
|
//get the domain_uuid
|
|
$sql = "SELECT * FROM v_domains ";
|
|
$sql .= "WHERE domain_name = '".$domain_name."' ";
|
|
$prep_statement = $db->prepare($sql);
|
|
$prep_statement->execute();
|
|
$result = $prep_statement->fetchAll(PDO::FETCH_NAMED);
|
|
foreach($result as $row) {
|
|
$domain_uuid = $row["domain_uuid"];
|
|
}
|
|
unset($result, $prep_statement);
|
|
}
|
|
|
|
//build the provision array
|
|
foreach($_SESSION['provision'] as $key=>$val) {
|
|
if (strlen($val['var']) > 0) { $value = $val['var']; }
|
|
if (strlen($val['text']) > 0) { $value = $val['text']; }
|
|
if (strlen($val['boolean']) > 0) { $value = $val['boolean']; }
|
|
if (strlen($val['numeric']) > 0) { $value = $val['numeric']; }
|
|
if (strlen($value) > 0) { $provision[$key] = $value; }
|
|
unset($value);
|
|
}
|
|
|
|
//check if provisioning has been enabled
|
|
if ($provision["enabled"] != "true") {
|
|
syslog(LOG_WARNING, '['.$_SERVER['REMOTE_ADDR']."] provision attempt but provisioning is not enabled for ".check_str($_REQUEST['mac']));
|
|
echo "access denied";
|
|
exit;
|
|
}
|
|
|
|
//send a request to a remote server to validate the MAC address and secret
|
|
if (strlen($_SERVER['auth_server']) > 0) {
|
|
$result = send_http_request($_SERVER['auth_server'], 'mac='.check_str($_REQUEST['mac']).'&secret='.check_str($_REQUEST['secret']));
|
|
if ($result == "false") {
|
|
syslog(LOG_WARNING, '['.$_SERVER['REMOTE_ADDR']."] provision attempt but the remote auth server said no for ".check_str($_REQUEST['mac']));
|
|
echo "access denied";
|
|
exit;
|
|
}
|
|
}
|
|
|
|
//use the mac address to get the vendor
|
|
$device_vendor = device::get_vendor($mac);
|
|
|
|
//keep backwards compatibility
|
|
if (strlen($_SESSION['provision']["cidr"]["text"]) > 0) {
|
|
$_SESSION['provision']["cidr"][] = $_SESSION['provision']["cidr"]["text"];
|
|
}
|
|
|
|
//check the cidr range
|
|
if (is_array($_SESSION['provision']["cidr"])) {
|
|
$found = false;
|
|
foreach($_SESSION['provision']["cidr"] as $cidr) {
|
|
if (check_cidr($cidr, $_SERVER['REMOTE_ADDR'])) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!$found) {
|
|
syslog(LOG_WARNING, '['.$_SERVER['REMOTE_ADDR']."] provision attempt but failed CIDR check for ".check_str($_REQUEST['mac']));
|
|
echo "access denied";
|
|
exit;
|
|
}
|
|
}
|
|
|
|
//http authentication - digest
|
|
if (strlen($provision["http_auth_username"]) > 0 && strlen($provision["http_auth_type"]) == 0) { $provision["http_auth_type"] = "digest"; }
|
|
if (strlen($provision["http_auth_username"]) > 0 && strlen($provision["http_auth_password"]) > 0 && $provision["http_auth_type"] === "digest" && $provision["http_auth_disable"] !== "true") {
|
|
//function to parse the http auth header
|
|
function http_digest_parse($txt) {
|
|
//protect against missing data
|
|
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
|
|
$data = array();
|
|
$keys = implode('|', array_keys($needed_parts));
|
|
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
|
|
foreach ($matches as $m) {
|
|
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
|
|
unset($needed_parts[$m[1]]);
|
|
}
|
|
return $needed_parts ? false : $data;
|
|
}
|
|
|
|
//function to request digest authentication
|
|
function http_digest_request($realm) {
|
|
header('HTTP/1.1 401 Authorization Required');
|
|
header('WWW-Authenticate: Digest realm="'.$realm.'", qop="auth", nonce="'.uniqid().'", opaque="'.md5($realm).'"');
|
|
header("Content-Type: text/html");
|
|
$content = 'Authorization Cancelled';
|
|
header("Content-Length: ".strval(strlen($content)));
|
|
echo $content;
|
|
die();
|
|
}
|
|
|
|
//set the realm
|
|
$realm = $_SESSION['domain_name'];
|
|
|
|
//request authentication
|
|
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
|
|
http_digest_request($realm);
|
|
}
|
|
|
|
//check for valid digest authentication details
|
|
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || ($data['username'] != $provision["http_auth_username"])) {
|
|
header('HTTP/1.1 401 Unauthorized');
|
|
header("Content-Type: text/html");
|
|
$content = 'Unauthorized '.$__line__;
|
|
header("Content-Length: ".strval(strlen($content)));
|
|
echo $content;
|
|
exit;
|
|
}
|
|
|
|
//generate the valid response
|
|
$A1 = md5($provision["http_auth_username"] . ':' . $realm . ':' . $provision["http_auth_password"]);
|
|
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
|
|
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
|
|
if ($data['response'] != $valid_response) {
|
|
header('HTTP/1.0 401 Unauthorized');
|
|
header("Content-Type: text/html");
|
|
$content = 'Unauthorized '.$__line__;
|
|
header("Content-Length: ".strval(strlen($content)));
|
|
echo $content;
|
|
exit;
|
|
}
|
|
}
|
|
|
|
//http authentication - basic
|
|
if (strlen($provision["http_auth_username"]) > 0 && strlen($provision["http_auth_password"]) > 0 && $provision["http_auth_type"] === "basic" && $provision["http_auth_disable"] !== "true") {
|
|
if (!isset($_SERVER['PHP_AUTH_USER'])) {
|
|
header('WWW-Authenticate: Basic realm="'.$_SESSION['domain_name'].'"');
|
|
header('HTTP/1.0 401 Authorization Required');
|
|
header("Content-Type: text/html");
|
|
$content = 'Authorization Required';
|
|
header("Content-Length: ".strval(strlen($content)));
|
|
echo $content;
|
|
exit;
|
|
} else {
|
|
if ($_SERVER['PHP_AUTH_USER'] == $provision["http_auth_username"] && $_SERVER['PHP_AUTH_PW'] == $provision["http_auth_password"]) {
|
|
//authorized
|
|
}
|
|
else {
|
|
//access denied
|
|
syslog(LOG_WARNING, '['.$_SERVER['REMOTE_ADDR']."] provision attempt but failed http basic authentication for ".check_str($_REQUEST['mac']));
|
|
header('HTTP/1.0 401 Unauthorized');
|
|
header('WWW-Authenticate: Basic realm="'.$_SESSION['domain_name'].'"');
|
|
unset($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']);
|
|
$content = 'Unauthorized';
|
|
header("Content-Length: ".strval(strlen($content)));
|
|
echo $content;
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
//if password was defined in the system -> variables page then require the password.
|
|
if (strlen($provision['password']) > 0) {
|
|
//deny access if the password doesn't match
|
|
if ($provision['password'] != check_str($_REQUEST['password'])) {
|
|
syslog(LOG_WARNING, '['.$_SERVER['REMOTE_ADDR']."] provision attempt bad password for ".check_str($_REQUEST['mac']));
|
|
//log the failed auth attempt to the system, to be available for fail2ban.
|
|
openlog('FusionPBX', LOG_NDELAY, LOG_AUTH);
|
|
syslog(LOG_WARNING, '['.$_SERVER['REMOTE_ADDR']."] provision attempt bad password for ".check_str($_REQUEST['mac']));
|
|
closelog();
|
|
echo "access denied";
|
|
return;
|
|
}
|
|
}
|
|
|
|
//register that we have seen the device
|
|
$sql = "UPDATE v_devices ";
|
|
$sql .= "SET device_provisioned_date=:date, device_provisioned_method=:method, device_provisioned_ip=:ip ";
|
|
$sql .= "WHERE domain_uuid=:domain_uuid AND device_mac_address=:mac ";
|
|
$prep_statement = $db->prepare(check_sql($sql));
|
|
if ($prep_statement) {
|
|
//use the prepared statement
|
|
$prep_statement->bindValue(':domain_uuid', $domain_uuid);
|
|
$prep_statement->bindValue(':mac', strtolower($mac));
|
|
$prep_statement->bindValue(':date', date("Y-m-d H:i:s"));
|
|
$prep_statement->bindValue(':method', (isset($_SERVER["HTTPS"]) ? 'https' : 'http'));
|
|
$prep_statement->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
|
|
$prep_statement->execute();
|
|
unset($prep_statement);
|
|
}
|
|
|
|
//output template to string for header processing
|
|
$prov = new provision;
|
|
$prov->domain_uuid = $domain_uuid;
|
|
$prov->mac = $mac;
|
|
$prov->file = $file;
|
|
$file_contents = $prov->render();
|
|
|
|
//deliver the customized config over HTTP/HTTPS
|
|
//need to make sure content-type is correct
|
|
if ($_REQUEST['content_type'] == 'application/octet-stream') {
|
|
//format the mac address and
|
|
$mac = $prov->format_mac($mac, $device_vendor);
|
|
|
|
//replace the variable name with the value
|
|
$file_name = str_replace("{\$mac}", $mac, $file);
|
|
|
|
//set the headers
|
|
header('Content-Description: File Transfer');
|
|
header('Content-Type: application/octet-stream');
|
|
header('Content-Disposition: attachment; filename="'.basename($file_name).'"');
|
|
header('Content-Transfer-Encoding: binary');
|
|
header('Expires: 0');
|
|
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
|
header('Pragma: public');
|
|
header('Content-Length: ' . strlen($file_contents));
|
|
}
|
|
else {
|
|
$cfg_ext = ".cfg";
|
|
if ($device_vendor === "aastra" && strrpos($file, $cfg_ext, 0) === strlen($file) - strlen($cfg_ext)) {
|
|
header("Content-Type: text/plain");
|
|
header("Content-Length: ".strlen($file_contents));
|
|
} else if ($device_vendor === "yealink") {
|
|
header("Content-Type: text/plain");
|
|
header("Content-Length: ".strval(strlen($file_contents)));
|
|
} else if ($device_vendor === "snom" && $device_template === "snom/m3") {
|
|
$file_contents = utf8_decode($file_contents);
|
|
header("Content-Type: text/plain; charset=iso-8859-1");
|
|
header("Content-Length: ".strlen($file_contents));
|
|
} else {
|
|
header("Content-Type: text/xml; charset=utf-8");
|
|
header("Content-Length: ".strlen($file_contents));
|
|
}
|
|
}
|
|
echo $file_contents;
|
|
closelog();
|
|
|
|
?>
|