diff --git a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc index 706754707..02847b288 100644 --- a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc +++ b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc @@ -97,6 +97,8 @@ function dyndns_list() return array( '3322' => '3322', + 'azure' => 'Azure DNS', + 'azurev6' => 'Azure DNS (v6)', 'citynetwork' => 'City Network', 'cloudflare' => 'CloudFlare', 'cloudflare-v6' => 'CloudFlare (v6)', @@ -167,6 +169,7 @@ function dyndns_configure_client($conf) $dnsUpdateURL = "{$conf['updateurl']}", $forceUpdate = $conf['force'], $dnsZoneID = $conf['zoneid'], + $dnsResourceID = $conf['resourceid'], $dnsTTL = $conf['ttl'], $dnsResultMatch = "{$conf['resultmatch']}", $dnsRequestIf = "{$conf['requestif']}", diff --git a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/phpDynDNS.inc b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/phpDynDNS.inc index 21baac6d0..bce46851b 100644 --- a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/phpDynDNS.inc +++ b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/phpDynDNS.inc @@ -40,6 +40,7 @@ * - regfish IPv6 (regfish.de) * - dynv6 IPv6 (dynv6.com) * - DigitalOcean (digitalocean.com) + * - Azure DNS (azure.microsoft.com) * - Linode (linode.com) * - Linode IPv6 (linode.com) * +----------------------------------------------------+ @@ -95,6 +96,7 @@ * dynv6 - Last Tested: 25 June 2019 * dynv6 v6 - Last Tested: 25 June 2019 * DigitalOcean - Last Tested: 25 June 2019 + * Azure DNS - Last Tested: 16 October 2019 * Linode - Last Tested: 12 November 2019 * Linode v6 - Last Tested: 12 November 2019 * +====================================================+ @@ -127,6 +129,7 @@ class updatedns var $_dnsPort; var $_dnsUpdateURL; var $_dnsZoneID; + var $_dnsResourceID; var $_dnsTTL; var $status; var $_debugID; @@ -165,6 +168,7 @@ class updatedns $dnsUpdateURL = '', $forceUpdate = false, $dnsZoneID = '', + $dnsResourceID = '', $dnsTTL = '', $dnsResultMatch = '', $dnsRequestIf = '', @@ -204,8 +208,7 @@ class updatedns case 'namecheap': if (!$dnsPass) { $this->_error(4); - } - if (!$dnsHost) { + } elseif (!$dnsHost) { $this->_error(5); } break; @@ -213,8 +216,7 @@ class updatedns case 'route53-v6': if (!$dnsZoneID) { $this->_error(8); - } - if (!$dnsTTL) { + } elseif (!$dnsTTL) { $this->_error(9); } break; @@ -231,25 +233,37 @@ class updatedns case 'regfish-v6': if (!$dnsUser) { $this->_error(3); - } - if (!$dnsHost) { + } elseif (!$dnsHost) { $this->_error(5); } break; + case 'azure': + case 'azurev6': + if (!$dnsUser) { + $this->_error(3); + } elseif (!$dnsPass) { + $this->_error(4); + } elseif (!$dnsHost) { + $this->_error(5); + } elseif (!$dnsResourceID) { + $this->_error(8); + } elseif (!$dnsTTL) { + $this->_error(9); + } + break; default: if (!$dnsUser) { $this->_error(3); - } - if (!$dnsPass) { + } elseif (!$dnsPass) { $this->_error(4); - } - if (!$dnsHost) { + } elseif (!$dnsHost) { $this->_error(5); } break; } switch ($dnsService) { + case 'azurev6': case 'cloudflare-v6': case 'custom-v6': case 'dynv6-v6': @@ -271,6 +285,7 @@ class updatedns $this->_dnsWildcard = $dnsWildcard; $this->_dnsMX = $dnsMX; $this->_dnsZoneID = $dnsZoneID; + $this->_dnsResourceID = $dnsResourceID; $this->_dnsTTL = $dnsTTL; $this->_if = dyndns_failover_interface($dnsIf, $this->_useIPv6 ? 'inet6' : 'all'); $this->_checkIP(); @@ -299,6 +314,8 @@ class updatedns } else { switch ($this->_dnsService) { case '3322': + case 'azure': + case 'azurev6': case 'citynetwork': case 'cloudflare': case 'cloudflare-v6': @@ -973,6 +990,73 @@ class updatedns } else { log_error("Dynamic DNS($fqdn): No zone found for domain"); } + case 'azurev6': + case 'azure': + $hostname = "{$this->_dnsHost}"; + $resourceid = trim($this->_dnsResourceID); + $app_id = $this->_dnsUser; + $client_secret = $this->_dnsPass; + $newip = $this->_dnsIP; + $newttl = $this->_dnsTTL; + // ensure resourceid starts with / and has no trailing / + $resourceid = '/' . trim($resourceid, '/'); + // extract subscription id from resource id + preg_match('/\\/subscriptions\\/(?[^\\/]*)/', $resourceid, $result); + $subscriptionid = isset($result['sid']) ? $result['sid'] : ''; + if (isset($result['sid'])) { + $subscriptionid = $result['sid']; + } else { + log_error("Azure subscription id not found in resource id ({$resourceid})"); + return false; + } + // find tenant id from subscription id + curl_setopt($ch, CURLOPT_URL, "https://management.azure.com/subscriptions/" . $subscriptionid . "?api-version=2016-09-01"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_NOBODY, 1); + $output = curl_exec($ch); + $pattern = '/Bearer authorization_uri="https:\\/\\/login.windows.net\\/(?[^"]*)/i'; + preg_match($pattern, $output, $result); + if (isset($result['tid'])) { + $tenantid = $result['tid']; + } else { + log_error("Tenant ID not found"); + return false; + } + // get an bearer token + curl_setopt($ch, CURLOPT_URL, "https://login.microsoftonline.com/" . $tenantid . "/oauth2/token"); + curl_setopt($ch, CURLOPT_POST, 1); + $body = "resource=" . urlencode("https://management.core.windows.net/") . "&grant_type=client_credentials&client_id=" . $app_id . "&client_secret=" . urlencode($client_secret); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $server_output = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + preg_match("/\"access_token\":\"(?[^\"]*)\"/", $server_output, $result); + if (isset($result['tok'])) { + $bearertoken = $result['tok']; + } else { + log_error("no valid bearer token"); + return false; + } + // Update the DNS record + if ($this->_useIPv6) { + $url = "https://management.azure.com" . $resourceid . "/AAAA/" . $hostname . "?api-version=2017-09-01"; + $body = '{"properties":{"TTL":"' . $newttl . '", "AAAARecords":[{"ipv6Address":"' . $newip . '"}]}}'; + } else { + $url = "https://management.azure.com" . $resourceid . "/A/" . $hostname . "?api-version=2017-09-01"; + $body = '{"properties":{"TTL":"' . $newttl . '", "ARecords":[{"ipv4Address":"' . $newip . '"}]}}'; + } + $request_headers = array(); + $request_headers[] = 'Accept: application/json'; + $request_headers[] = 'Authorization: Bearer ' . $bearertoken; + $request_headers[] = 'Content-Type: application/json'; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent); + curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); break; default: break; @@ -1402,6 +1486,23 @@ class updatedns log_error("Dynamic DNS ($fqdn): PAYLOAD: {$data}"); } break; + case 'azure': + case 'azurev6': + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($http_code == 401) { + $status = $status_intro . '(Error) User Authorization Failed'; + } else if ($http_code == 201) { + $status = $status_intro . '(Success) IP Address Changed Successfully!'; + $successful_update = true; + } else if ($http_code == 200) { + $status = $status_intro . '(Success) IP Address Changed Successfully!'; + $successful_update = true; + } else { + $status = $status_intro . '(Error)" Unknown Response"'; + log_error("Dynamic DNS: HTTP Status: {$http_code} PAYLOAD: {$data}"); + $this->_debug($data); + } + break; default: break; } diff --git a/dns/dyndns/src/www/services_dyndns_edit.php b/dns/dyndns/src/www/services_dyndns_edit.php index 3d2d2e6ae..df29a8eae 100644 --- a/dns/dyndns/src/www/services_dyndns_edit.php +++ b/dns/dyndns/src/www/services_dyndns_edit.php @@ -50,7 +50,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { if (isset($_GET['id']) && !empty($a_dyndns[$_GET['id']])) { $id = $_GET['id']; } - $config_copy_fieldnames = array('username', 'password', 'host', 'mx', 'type', 'zoneid', 'ttl', 'updateurl', + $config_copy_fieldnames = array('username', 'password', 'host', 'mx', 'type', 'zoneid','resourceid', 'ttl', 'updateurl', 'resultmatch', 'requestif', 'descr', 'interface'); foreach ($config_copy_fieldnames as $fieldname) { if (isset($id) && isset($a_dyndns[$id][$fieldname])) { @@ -84,7 +84,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $reqdfieldsn = array(); $reqdfields = array('type'); $reqdfieldsn = array(gettext('Service type')); - if ($pconfig['type'] != 'custom' && $pconfig['type'] != 'custom-v6') { + + if (in_array($pconfig['type'], array('azure', 'azurev6'))) { + $reqdfields[] = 'password'; + $reqdfieldsn[] = gettext('Password'); + $reqdfields[] = 'resourceid'; + $reqdfieldsn[] = gettext('Resource Id'); + $reqdfields[] = 'ttl'; + $reqdfieldsn[] = gettext('TTL'); + } elseif ($pconfig['type'] != 'custom' && $pconfig['type'] != 'custom-v6') { $reqdfields[] = 'host'; $reqdfieldsn[] = gettext('Hostname'); $reqdfields[] = 'username'; @@ -144,6 +152,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $dyndns['enable'] = !empty($pconfig['enable']); $dyndns['interface'] = $pconfig['interface']; $dyndns['zoneid'] = $pconfig['zoneid']; + $dyndns['resourceid'] = $pconfig['resourceid']; $dyndns['ttl'] = $pconfig['ttl']; $dyndns['updateurl'] = $pconfig['updateurl']; // Trim hard-to-type but sometimes returned characters @@ -199,6 +208,10 @@ include("head.inc"); case "route53-v6": $(".type_route53").show(); break; + case "azure": + case "azurev6": + $(".type_azure").show(); + break; default: $(".type_default").show(); break; @@ -335,6 +348,7 @@ include("head.inc");


+

@@ -348,6 +362,7 @@ include("head.inc");


+

@@ -361,6 +376,15 @@ include("head.inc"); + + + + + + + @@ -389,7 +413,7 @@ include("head.inc"); - + @@ -408,12 +432,10 @@ include("head.inc");   - + - + @@ -433,4 +455,6 @@ include("head.inc"); - +