From 0ef4d58e8558bef466276c53d98e98dac72f4e98 Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Fri, 15 Nov 2019 07:55:45 -0500 Subject: [PATCH] Add Linode Dynamic DNS support (#1579) This is mostly based on the existing Cloudflare logic since Linode's REST API is very similar. Signed-off-by: Andrew Gunnerson --- .../src/etc/inc/plugins.inc.d/dyndns.inc | 2 + .../inc/plugins.inc.d/dyndns/phpDynDNS.inc | 109 ++++++++++++++++++ dns/dyndns/src/www/services_dyndns_edit.php | 7 +- 3 files changed, 116 insertions(+), 2 deletions(-) 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 4349136e5..2130b0b9e 100644 --- a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc +++ b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns.inc @@ -119,6 +119,8 @@ function dyndns_list() 'he-net' => 'HE.net', 'he-net-tunnelbroker' => 'HE.net Tunnelbroker', 'he-net-v6' => 'HE.net (v6)', + 'linode' => 'Linode', + 'linode-v6' => 'Linode (v6)', 'loopia' => 'Loopia', 'namecheap' => 'Namecheap', 'noip' => 'No-IP', 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 0159bcaf6..1aa755c2f 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 @@ -39,6 +39,8 @@ * - regfish (regfish.de) * - regfish IPv6 (regfish.de) * - DigitalOcean (digitalocean.com) + * - Linode (linode.com) + * - Linode IPv6 (linode.com) * +----------------------------------------------------+ * Requirements: * - PHP version 4.0.2 or higher with the CURL Library and the PCRE Library @@ -90,6 +92,8 @@ * regfish v6 - Last Tested: 15 August 2017 * Amazon Route53 v6 - Last Tested: 19 November 2017 * DigitalOcean - Last Tested: 25 June 2019 + * Linode - Last Tested: 12 November 2019 + * Linode v6 - Last Tested: 12 November 2019 * +====================================================+ * * @author E.Kristensen @@ -192,6 +196,8 @@ class updatedns $this->_error(5); } break; + case 'linode': + case 'linode-v6': case 'namecheap': if (!$dnsPass) { $this->_error(4); @@ -242,6 +248,7 @@ class updatedns case 'cloudflare-v6': case 'custom-v6': case 'he-net-v6': + case 'linode-v6': case 'regfish-v6': case 'route53-v6': $this->_useIPv6 = true; @@ -309,6 +316,8 @@ class updatedns case 'he-net-tunnelbroker': case 'he-net-v6': case 'hn': + case 'linode': + case 'linode-v6': case 'loopia': case 'namecheap': case 'noip': @@ -859,6 +868,88 @@ class updatedns $server = "https://dyndns.regfish.de/?fqdn={$this->_dnsHost}&{$family}={$this->_dnsIP}&forcehost=1&token=" . urlencode($this->_dnsUser); curl_setopt($ch, CURLOPT_URL, $server); break; + case 'linode': + case 'linode-v6': + $baseUrl = "https://api.linode.com/v4"; + $fqdn = trim($this->_dnsHost); + $recordType = ($this->_useIPv6) ? 'AAAA' : 'A'; + + if ($this->_dnsWildcard == 'ON') { + $fqdn = "*.$fqdn"; + } + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Accept: application/json', + 'Authorization: Bearer ' . $this->_dnsPass, + 'Content-Type: application/json' + )); + + $domainsUrl = "$baseUrl/domains"; + curl_setopt($ch, CURLOPT_URL, $domainsUrl); + $output = json_decode(curl_exec($ch)); + $domainId = null; + + // Find matching domain and split the hostname part from it + foreach ($output->data as $key => $domainObj) { + if (preg_match("/^{$domainObj->domain}$|\.{$domainObj->domain}$/", $fqdn)) { + $domainId = $domainObj->id; + $hostName = preg_replace("/\.?{$domainObj->domain}$/", '', $fqdn); + $domainName = $domainObj->domain; + break; + } + } + + if ($domainId) { + if ($this->_dnsVerboseLog) { + log_error("Dynamic DNS ($fqdn): Found domain name: $domainName, ID: $domainId"); + } + + $dnsRecordsUrl = "$domainsUrl/$domainId/records"; + curl_setopt($ch, CURLOPT_URL, $dnsRecordsUrl); + $output = json_decode(curl_exec($ch)); + $recordId = null; + + // Find matching record + foreach ($output->data as $key => $recordObj) { + if ($recordObj->type == $recordType && $recordObj->name == $hostName) { + $recordId = $recordObj->id; + break; + } + } + + $hostData = array( + "target" => "{$this->_dnsIP}", + ); + + if ($recordId) { + // Update record + if ($this->_dnsVerboseLog) { + log_error("Dynamic DNS ($fqdn): Updating existing record ID: $recordId"); + } + + curl_setopt($ch, CURLOPT_URL, "$dnsRecordsUrl/$recordId"); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + } else { + // Create record + if ($this->_dnsVerboseLog) { + log_error("Dynamic DNS ($fqdn): Creating new record"); + } + + $hostData['type'] = $recordType; + $hostData['name'] = $hostName; + // Linode will round up to the nearest valid TTL + $hostData['ttl_sec'] = 0; + + curl_setopt($ch, CURLOPT_URL, $dnsRecordsUrl); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($hostData)); + } else { + log_error("Dynamic DNS($fqdn): No zone found for domain"); + } + break; default: break; } @@ -1254,6 +1345,24 @@ class updatedns log_error("Dynamic DNS: PAYLOAD: {$data}"); $this->_debug($data); } + case 'linode': + case 'linode-v6': + $fqdn = trim($this->_dnsHost); + if ($this->_dnsWildcard == 'ON') { + $fqdn = "*.$fqdn"; + } + + $output = json_decode($data); + if ($output->target === $this->_dnsIP) { + $status = "Dynamic DNS: (Success) $fqdn updated to {$this->_dnsIP}"; + $successful_update = true; + } elseif (!empty($output->errors)) { + $status = "Dynamic DNS ($fqdn): ERROR - Reason: {$output->errors[0]->reason}"; + } else { + $status = "Dynamic DNS ($fqdn): UNKNOWN ERROR"; + log_error("Dynamic DNS ($fqdn): PAYLOAD: {$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 6f0ce3c55..90e489e47 100644 --- a/dns/dyndns/src/www/services_dyndns_edit.php +++ b/dns/dyndns/src/www/services_dyndns_edit.php @@ -75,7 +75,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { } $input_errors = array(); $pconfig = $_POST; - if(($pconfig['type'] == "freedns" || $pconfig['type'] == "namecheap") && $pconfig['username'] == "") { + if(($pconfig['type'] == "freedns" || $pconfig['type'] == "linode" || $pconfig['type'] == "linode-v6" || $pconfig['type'] == "namecheap") && $pconfig['username'] == "") { $pconfig['username'] = "none"; } @@ -108,6 +108,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { case 'cloudflare-v6': case 'eurodns': case 'googledomains': + case 'linode': + case 'linode-v6': case 'namecheap': $host_to_check = preg_replace('/^[@*]\./', '', $host_to_check); break; @@ -329,7 +331,7 @@ include("head.inc");