diff --git a/src/etc/inc/dyndns.class b/src/etc/inc/dyndns.class index b55c26a8574..a76871aadbe 100644 --- a/src/etc/inc/dyndns.class +++ b/src/etc/inc/dyndns.class @@ -63,6 +63,7 @@ * - HE.net (dns.he.net) * - HE.net IPv6 (dns.he.net) * - HE.net Tunnelbroker IP update (ipv4.tunnelbroker.net) + * - Hetzner DNS API (dns.hetzner.com) * - HN (hn.org) -- incomplete checking! * - Hover (www.hover.com) * - Loopia (loopia.se) @@ -92,62 +93,64 @@ * - _detectChange() * - _debug() * +----------------------------------------------------+ - * All-Inkl - Last Tested: 12 November 2016 - * Amazon Route 53 - Last Tested: 04 February 2017 - * Azure DNS - Last Tested: 08 March 2018 - * City Network - Last Tested: 13 November 2013 - * Cloudflare - Last Tested: 05 September 2016 - * Cloudflare IPv6 - Last Tested: 17 July 2016 - * ClouDNS - Last Tested: 22 August 2017 - * deSEC - Last Tested: NEVER - * deSEC IPv6 - Last Tested: NEVER - * DHS - Last Tested: 12 July 2005 - * DigitalOcean - Not Yet Tested - * DNS Made Easy - Last Tested: 27 April 2015 - * DNS-O-Matic - Last Tested: 9 September 2010 - * DNSexit - Last Tested: 27 June 2022 - * DNSimple - Last Tested: 09 February 2015 - * DreamHost - Last Tested: 30 April 2017 - * DreamHost IPv6 - Not Yet Tested - * DuiaDNS - Last Tested: 25 November 2016 - * DuiaDNS IPv6 - Last Tested: 25 November 2016 - * DY.fi - Last Tested: 22 April 2021 - * DynDNS Custom - Last Tested: NEVER - * DynDNS Dynamic - Last Tested: 12 July 2005 - * DynDNS Static - Last Tested: NEVER - * Dyns - Last Tested: NEVER - * EasyDNS - Last Tested: 20 July 2008 - * Eurodns - Last Tested: 27 June 2013 - * FreeDNS - Last Tested: 01 May 2016 - * FreeDNS IPv6 - Last Tested: 01 May 2016 - * FreeDNS IPv6 v2 - Last Tested: 01 June 2020 - * FreeDNS v2 - Last Tested: 01 June 2020 - * Gandi LiveDNS - Not Yet Tested - * GleSYS - Last Tested: 3 February 2015 - * GoDaddy - Last Tested: 22 November 2017 - * GoDaddy IPv6 - Last Tested: 22 November 2017 - * Google Domains - Last Tested: 27 April 2015 - * GratisDNS - Last Tested: 15 August 2012 - * HE.net - Last Tested: 7 July 2013 - * HE.net IPv6 - Last Tested: 7 July 2013 - * HE.net Tunnel - Last Tested: 28 June 2011 - * HN.org - Last Tested: 12 July 2005 - * Hover - Last Tested: 15 February 2017 - * Loopia - Last Tested: 21 August 2019 - * Name.com - Last Tested: 5 Dec 2021 - * Name.com IPv6 - Last Tested: 5 Dec 2021 - * Namecheap - Last Tested: 31 August 2010 - * No-IP - Last Tested: 20 July 2008 - * ODS - Last Tested: 02 August 2005 - * OpenDNS - Last Tested: 4 August 2008 - * OVH DynHOST - Last Tested: NEVER - * Porkbun - Last Tested: 22 October 2024 - * SelfHost - Last Tested: 26 December 2011 - * SPDYN - Last Tested: 02 July 2016 - * SPDYN IPv6 - Last Tested: 02 July 2016 - * StaticCling - Last Tested: 27 April 2006 - * Strato - Last Tested: 29 May 2021 - * ZoneEdit - Last Tested: NEVER + * All-Inkl - Last Tested: 12 November 2016 + * Amazon Route 53 - Last Tested: 04 February 2017 + * Azure DNS - Last Tested: 08 March 2018 + * City Network - Last Tested: 13 November 2013 + * Cloudflare - Last Tested: 05 September 2016 + * Cloudflare IPv6 - Last Tested: 17 July 2016 + * ClouDNS - Last Tested: 22 August 2017 + * deSEC - Last Tested: NEVER + * deSEC IPv6 - Last Tested: NEVER + * DHS - Last Tested: 12 July 2005 + * DigitalOcean - Not Yet Tested + * DNS Made Easy - Last Tested: 27 April 2015 + * DNS-O-Matic - Last Tested: 9 September 2010 + * DNSexit - Last Tested: 27 June 2022 + * DNSimple - Last Tested: 09 February 2015 + * DreamHost - Last Tested: 30 April 2017 + * DreamHost IPv6 - Not Yet Tested + * DuiaDNS - Last Tested: 25 November 2016 + * DuiaDNS IPv6 - Last Tested: 25 November 2016 + * DY.fi - Last Tested: 22 April 2021 + * DynDNS Custom - Last Tested: NEVER + * DynDNS Dynamic - Last Tested: 12 July 2005 + * DynDNS Static - Last Tested: NEVER + * Dyns - Last Tested: NEVER + * EasyDNS - Last Tested: 20 July 2008 + * Eurodns - Last Tested: 27 June 2013 + * FreeDNS - Last Tested: 01 May 2016 + * FreeDNS IPv6 - Last Tested: 01 May 2016 + * FreeDNS IPv6 v2 - Last Tested: 01 June 2020 + * FreeDNS v2 - Last Tested: 01 June 2020 + * Gandi LiveDNS - Not Yet Tested + * GleSYS - Last Tested: 3 February 2015 + * GoDaddy - Last Tested: 22 November 2017 + * GoDaddy IPv6 - Last Tested: 22 November 2017 + * Google Domains - Last Tested: 27 April 2015 + * GratisDNS - Last Tested: 15 August 2012 + * HE.net - Last Tested: 7 July 2013 + * HE.net IPv6 - Last Tested: 7 July 2013 + * HE.net Tunnel - Last Tested: 28 June 2011 + * Hetzner DNS API - Last Tested: 02 June 2024 + * Hetzner DNS API v6 - Last Tested: 02 June 2024 + * HN.org - Last Tested: 12 July 2005 + * Hover - Last Tested: 15 February 2017 + * Loopia - Last Tested: 21 August 2019 + * Name.com - Last Tested: 5 Dec 2021 + * Name.com IPv6 - Last Tested: 5 Dec 2021 + * Namecheap - Last Tested: 31 August 2010 + * No-IP - Last Tested: 20 July 2008 + * ODS - Last Tested: 02 August 2005 + * OpenDNS - Last Tested: 4 August 2008 + * OVH DynHOST - Last Tested: NEVER + * Porkbun - Last Tested: 22 October 2024 + * SelfHost - Last Tested: 26 December 2011 + * SPDYN - Last Tested: 02 July 2016 + * SPDYN IPv6 - Last Tested: 02 July 2016 + * StaticCling - Last Tested: 27 April 2006 + * Strato - Last Tested: 29 May 2021 + * ZoneEdit - Last Tested: NEVER * +====================================================+ * * @author E.Kristensen @@ -272,6 +275,12 @@ if (!$dnsDomain) $this->_error(5); if (!$dnsTTL) $this->_error(9); break; + case 'hetzner': + case 'hetzner-v6': + if (!$dnsPass) $this->_error(4); + if (!$dnsHost) $this->_error(5); + if (!$dnsTTL) $this->_error(9); + break; case 'desec': case 'desec-v6': if (!$dnsPass) $this->_error(4); @@ -347,6 +356,7 @@ case 'gandi-livedns-v6': case 'godaddy-v6': case 'he-net-v6': + case 'hetzner-v6': case 'linode-v6': case 'mythicbeasts-v6': case 'name.com-v6': @@ -456,6 +466,8 @@ case 'he-net': case 'he-net-tunnelbroker': case 'he-net-v6': + case 'hetzner': + case 'hetzner-v6': case 'hn': case 'hover': case 'linode': @@ -579,6 +591,87 @@ curl_setopt($ch, CURLOPT_URL, $server); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); break; + case 'hetzner': + case 'hetzner-v6': + // API documentation: https://dns.hetzner.com/api-docs + $server = 'https://dns.hetzner.com/api/v1'; + $fqdn = str_replace(' ', '', $this->_dnsHost); + $recordType = $this->_useIPv6 ? "AAAA" : "A"; + $ttlData = intval($this->_dnsTTL) < 1 ? 120 : intval($this->_dnsTTL); + $hostData = [ + "value" => "{$this->_dnsIP}", + "type" => $recordType, + "name" => "", + "ttl" => $ttlData, + "zone_id" => "" + ]; + + $headerAuth = [ + "Auth-API-Token: {$this->_dnsPass}", + 'Content-Type: application/json' + ]; + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headerAuth); + + // Get all zone info + $zonesUrl = "{$server}/zones"; + curl_setopt($ch, CURLOPT_URL, $zonesUrl); + $output = json_decode(curl_exec($ch)); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $zoneId = null; // Set default value + + if (!empty($output->zones)) { + // Iterate zone objects, check if $fqdn is equal to or ends with zone name + foreach ($output->zones as $key => $zoneObj) { + if (preg_match("/^{$zoneObj->name}$|\.{$zoneObj->name}$/", $fqdn)) { + // Found matching zone + $zoneId = $zoneObj->id; + // Get $hostName from $fqdn, set $domainName + // These are only really used for log messages. + $hostName = preg_replace("/\.?{$zoneObj->name}$/", '', $fqdn); + $domainName = $zoneObj->name; + break; + } + } + } + + if ($zoneId) { // If zone ID was found get host ID + $dnsRecordsUrl = "{$server}/records?zone_id={$zoneId}"; + curl_setopt($ch, CURLOPT_URL, $dnsRecordsUrl); + $output = json_decode(curl_exec($ch)); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $recordId = null; // Set default value + + if (!empty($output->records)) { + // Iterate zone objects, check if $hostName exist of the same type + foreach ($output->records as $key => $recordObj) { + if (preg_match("/^{$recordObj->name}$/", $hostName)) { + if ($recordObj->type == $recordType) { + // Found matching host + $recordId = $recordObj->id; + break; + } + } + } + } + + if ($recordId) { // If record ID was found, update record + $setRecordUrl = "{$server}/records/{$recordId}"; + curl_setopt($ch, CURLOPT_URL, $setRecordUrl); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + } else { + $setRecordUrl = "{$server}/records"; + curl_setopt($ch, CURLOPT_URL, $setRecordUrl); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + } + + $hostData["zone_id"] = $zoneId; + $hostData["name"] = $hostName; + + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($hostData)); + } + break; // // Not yet ordered providers. // TODO: When editing a provider, move it above in a correct position. @@ -2572,6 +2665,22 @@ $this->_debug($data); } break; + case 'hetzner': + case 'hetzner-v6': + $output = json_decode($data); + if ($output->record->value === $this->_dnsIP) { + $status = "Dynamic DNS: (Success) {$this->_dnsHost} updated to {$this->_dnsIP}"; + $successful_update = true; + } elseif ($http_code == 401) { + $status = 'Dynamic DNS: (Error) Authentication failed, incorrect API Key.'; + } elseif ($http_code == 403) { + $status = 'Dynamic DNS: (Error) Access denied due to lack of permissions.'; + } else { + $status = 'Dynamic DNS: (Error) "Unknown Response"'; + log_error("Dynamic DNS ({$this->_dnsHost}): PAYLOAD: {$data}"); + $this->_debug("Unknown Response: " . $data); + } + break; case 'selfhost': if (preg_match('/notfqdn/i', $data)) { $status = $status_intro . $error_str . gettext("Not A FQDN!"); diff --git a/src/etc/inc/services.inc b/src/etc/inc/services.inc index 34ce45212a3..99e731534c7 100644 --- a/src/etc/inc/services.inc +++ b/src/etc/inc/services.inc @@ -27,8 +27,8 @@ require_once('services_dhcp.inc'); -define('DYNDNS_PROVIDER_VALUES', 'all-inkl azure azurev6 citynetwork cloudflare cloudflare-v6 cloudns custom custom-v6 desec desec-v6 digitalocean digitalocean-v6 dnsexit dnsimple dnsimple-v6 dnsmadeeasy dnsomatic domeneshop domeneshop-v6 dreamhost dreamhost-v6 duiadns duiadns-v6 dyfi dyndns dyndns-custom dyndns-static dyns dynv6 dynv6-v6 easydns easydns-v6 eurodns freedns freedns-v6 freedns2 freedns2-v6 glesys gandi-livedns gandi-livedns-v6 godaddy godaddy-v6 googledomains gratisdns he-net he-net-v6 he-net-tunnelbroker hover linode linode-v6 loopia mythicbeasts mythicbeasts-v6 name.com name.com-v6 namecheap nicru nicru-v6 noip noip-v6 noip-free noip-free-v6 onecom onecom-v6 ods opendns ovh-dynhost route53 route53-v6 selfhost spdyn spdyn-v6 strato yandex yandex-v6 zoneedit porkbun porkbun-v6'); -define('DYNDNS_PROVIDER_DESCRIPTIONS', 'All-Inkl.com,Azure DNS,Azure DNS (v6),City Network,Cloudflare,Cloudflare (v6),ClouDNS,Custom,Custom (v6),deSEC,deSEC (v6),DigitalOcean,DigitalOcean (v6),DNSexit,DNSimple,DNSimple (v6),DNS Made Easy,DNS-O-Matic,Domeneshop,Domeneshop (v6),DreamHost,Dreamhost (v6),DuiaDns.net,DuiaDns.net (v6),DY.fi,DynDNS (dynamic),DynDNS (custom),DynDNS (static),DyNS,Dynv6,Dynv6 (v6),easyDNS,easyDNS (v6),Euro Dns,freeDNS,freeDNS (v6),freeDNS API Version 2, freeDNS API Version 2 (v6),GleSYS,Gandi LiveDNS,Gandi LiveDNS (v6),GoDaddy,GoDaddy (v6),Google Domains,GratisDNS,HE.net,HE.net (v6),HE.net Tunnelbroker,Hover,Linode,Linode (v6),Loopia,Mythic Beasts,Mythic Beasts (v6),Name.com,Name.com (v6),Namecheap,NIC.RU,NIC.RU (v6),No-IP,No-IP (v6),No-IP (free),No-IP (free-v6),One.com,One.com (v6),ODS.org,OpenDNS,OVH DynHOST,Route 53,Route 53 (v6),SelfHost,SPDYN,SPDYN (v6),Strato,Yandex,Yandex (v6),ZoneEdit,Porkbun,Porkbun (v6)'); +define('DYNDNS_PROVIDER_VALUES', 'all-inkl azure azurev6 citynetwork cloudflare cloudflare-v6 cloudns custom custom-v6 desec desec-v6 digitalocean digitalocean-v6 dnsexit dnsimple dnsimple-v6 dnsmadeeasy dnsomatic domeneshop domeneshop-v6 dreamhost dreamhost-v6 duiadns duiadns-v6 dyfi dyndns dyndns-custom dyndns-static dyns dynv6 dynv6-v6 easydns easydns-v6 eurodns freedns freedns-v6 freedns2 freedns2-v6 glesys gandi-livedns gandi-livedns-v6 godaddy godaddy-v6 googledomains gratisdns he-net he-net-v6 he-net-tunnelbroker hetzner hetzner-v6 hover linode linode-v6 loopia mythicbeasts mythicbeasts-v6 name.com name.com-v6 namecheap nicru nicru-v6 noip noip-v6 noip-free noip-free-v6 onecom onecom-v6 ods opendns ovh-dynhost route53 route53-v6 selfhost spdyn spdyn-v6 strato yandex yandex-v6 zoneedit porkbun porkbun-v6'); +define('DYNDNS_PROVIDER_DESCRIPTIONS', 'All-Inkl.com,Azure DNS,Azure DNS (v6),City Network,Cloudflare,Cloudflare (v6),ClouDNS,Custom,Custom (v6),deSEC,deSEC (v6),DigitalOcean,DigitalOcean (v6),DNSexit,DNSimple,DNSimple (v6),DNS Made Easy,DNS-O-Matic,Domeneshop,Domeneshop (v6),DreamHost,Dreamhost (v6),DuiaDns.net,DuiaDns.net (v6),DY.fi,DynDNS (dynamic),DynDNS (custom),DynDNS (static),DyNS,Dynv6,Dynv6 (v6),easyDNS,easyDNS (v6),Euro Dns,freeDNS,freeDNS (v6),freeDNS API Version 2, freeDNS API Version 2 (v6),GleSYS,Gandi LiveDNS,Gandi LiveDNS (v6),GoDaddy,GoDaddy (v6),Google Domains,GratisDNS,HE.net,HE.net (v6),HE.net Tunnelbroker,Hetzner DNS Console,Hetzner DNS Console (v6),Hover,Linode,Linode (v6),Loopia,Mythic Beasts,Mythic Beasts (v6),Name.com,Name.com (v6),Namecheap,NIC.RU,NIC.RU (v6),No-IP,No-IP (v6),No-IP (free),No-IP (free-v6),One.com,One.com (v6),ODS.org,OpenDNS,OVH DynHOST,Route 53,Route 53 (v6),SelfHost,SPDYN,SPDYN (v6),Strato,Yandex,Yandex (v6),ZoneEdit,Porkbun,Porkbun (v6)'); /* implement ipv6 route advertising daemon */ function services_radvd_configure($blacklist = array()) { diff --git a/src/usr/local/www/services_dyndns_edit.php b/src/usr/local/www/services_dyndns_edit.php index ea1f1aec0a1..842669947c0 100644 --- a/src/usr/local/www/services_dyndns_edit.php +++ b/src/usr/local/www/services_dyndns_edit.php @@ -100,6 +100,8 @@ function is_dyndns_username($uname) { "godaddy" => array("apex" => true, "wildcard" => true, "username_none" => false), "godaddy-v6" => array("apex" => true, "wildcard" => true, "username_none" => false), "googledomains" => array("apex" => false, "wildcard" => true, "username_none" => false), + "hetzner" => array("apex" => false, "wildcard" => false, "username_none" => true), + "hetzner-v6" => array("apex" => false, "wildcard" => false, "username_none" => true), "linode" => array("apex" => false, "wildcard" => false, "username_none" => true), "linode-v6" => array("apex" => false, "wildcard" => false, "username_none" => true), "namecheap" => array("apex" => true, "wildcard" => true, "username_none" => true), @@ -148,6 +150,23 @@ function is_dyndns_username($uname) { if (((array_get_path($ddns_attr, "{$pconfig['type']}/apex") == true) && (($_POST['host'] == '@.') || ($_POST['host'] == '@'))) || ((array_get_path($ddns_attr, "{$pconfig['type']}/wildcard") == true) && (($_POST['host'] == '*.') || ($_POST['host'] == '*')))) { $host_to_check = $_POST['domainname']; + } elseif (($pconfig['type'] == "hetzner") || ($pconfig['type'] == "hetzner-v6")) { + /* Hetzner allows hostnames with '@' and '*', but not at the same time */ + $host_to_check = $_POST['host']; + + if (!strpos($host_to_check, '@') !== false && !strpos($host_to_check, '*') !== false) { + + if (strrpos($host_to_check, '@') !== false) { + $last_to_check = strrpos($host_to_check, '@'); + } else { + $last_to_check = strrpos($host_to_check, '*'); + } + + if ($last_to_check !== false) { + $host_to_check = substr_replace($host_to_check, '0', $last_to_check, 1); + } + unset($last_to_check); + } } else { switch ($pconfig['type']) { case 'azure': @@ -375,6 +394,7 @@ function build_if_list() { 'DNS Made Easy: Dynamic DNS ID (NOT hostname)%1$s' . 'GleSYS: Enter the record ID.%1$s' . 'he.net tunnelbroker: Enter the tunnel ID.%1$s' . + 'Hetzner DNS Console: Enter @ in the fully qualified domain name format, to indicate an empty field.%1$s' . 'Cloudflare, ClouDNS, DigitalOcean, GoDaddy, GratisDNS, Hover, Linode, Name.com, Namecheap, Porkbun: Enter the hostname and domain name separately. The domain name is the domain or subdomain zone being handled by the provider.', '
'); @@ -462,6 +482,7 @@ function build_if_list() { 'Gandi LiveDNS: Enter API token%1$s' . 'GleSYS: Enter the API key.%1$s' . 'GoDaddy: Enter the API secret.%1$s' . + 'Hetzner DNS Console: Enter the API token.%1$s' . 'Linode: Enter the Personal Access Token.%1$s' . 'Name.com: Enter the API token.%1$s' . 'Porkbun: Enter the API secret.%1$s' . @@ -637,6 +658,13 @@ function setVisible(service) { break; case "godaddy": case "godaddy-v6": + case "hetzner": + case "hetzner-v6": + hideInput('username', true); + hideInput('mx', true); + hideCheckbox('wildcard', true); + hideInput('ttl', false); + break; case "linode": case "linode-v6": case "name.com":