diff --git a/src/etc/inc/dyndns.class b/src/etc/inc/dyndns.class
index f2515b4dbb..f461999c23 100644
--- a/src/etc/inc/dyndns.class
+++ b/src/etc/inc/dyndns.class
@@ -245,6 +245,8 @@
if (!$dnsTTL) $this->_error(9);
break;
case 'digitalocean':
+ case 'linode':
+ case 'linode-v6':
if (!$dnsPass) $this->_error(4);
if (!$dnsHost) $this->_error(5);
if (!$dnsDomain) $this->_error(5);
@@ -279,6 +281,7 @@
case 'dreamhost-v6':
case 'godaddy-v6':
case 'azurev6':
+ case 'linode-v6':
$this->_useIPv6 = true;
break;
default:
@@ -371,6 +374,8 @@
case 'godaddy-v6':
case 'azure':
case 'azurev6':
+ case 'linode':
+ case 'linode-v6':
$this->_update();
if ($this->_dnsDummyUpdateDone == true) {
// If a dummy update was needed, then sleep a while and do the update again to put the proper address back.
@@ -1117,7 +1122,92 @@
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
break;
+ case 'linode':
+ case 'linode-v6':
+ $linode_api = "https://api.linode.com/v4";
+ $record_type = $this->_useIPv6 ? "AAAA" : "A";
+ $record_name = $this->_dnsHost == "@" ? "" : $this->_dnsHost;
+ $err_prefix = gettext("Dynamic DNS") . " {$this->_dnsService} ({$this->_FQDN}): (" . gettext("Error") . ")";
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array(
+ 'Accept: application/json',
+ 'Content-Type: application/json',
+ 'Authorization: Bearer ' . $this->_dnsPass
+ ));
+ // get domain id
+ curl_setopt($ch, CURLOPT_URL, "$linode_api/domains");
+ $domains_output = curl_exec($ch);
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ if ( $http_code == 401 && preg_match('/invalid oauth token/i', $domains_output) ) {
+ log_error("$err_prefix " . gettext("querying domains") . ": " . gettext("User Authentication Failed"));
+ return false;
+ } else if ( $http_code == 401 ) {
+ log_error("$err_prefix " . gettext("querying domains") . ": " . gettext("User Authorization Failed"));
+ return false;
+ } else if ( $http_code != 200 ) {
+ log_error("$err_prefix " . gettext("querying domains") . ": " .
+ ( isset($domains_result["errors"][0]["reason"]) ? $domains_result["errors"][0]["reason"] : "HTTP $http_code" ) );
+ return false;
+ }
+
+ $domains_result = json_decode($domains_output, TRUE);
+ foreach($domains_result["data"] as $domains_entry) {
+ if ($domains_entry["domain"] == $this->_dnsDomain) {
+ $domain_id = $domains_entry["id"];
+ }
+ }
+ if ( ! $domain_id ) {
+ log_error("$err_prefix " . gettext("no domain ID for domain") . ": '{$this->_dnsDomain}'");
+ return false;
+ }
+ if ($this->_dnsVerboseLog) {
+ log_error("_update(): " . sprintf(gettext("found domain id: %s"), $domain_id));
+ }
+
+ // get existing record if present
+ curl_setopt($ch, CURLOPT_URL, "$linode_api/domains/$domain_id/records");
+ $records_output = curl_exec($ch);
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ if ( $http_code != 200 )
+ {
+ log_error("$err_prefix " . gettext("querying domains") . ": " .
+ ( isset($domains_result["errors"][0]["reason"]) ? $domains_result["errors"][0]["reason"] : "HTTP $http_code" ) );
+ return false;
+ }
+
+ $records_result = json_decode($records_output, TRUE);
+ foreach($records_result["data"] as $records_entry) {
+ if ( $records_entry["type"] == $record_type && $records_entry["name"] == $record_name ) {
+ // not adding support for pagination at this time, hope you have < 100 records!
+ $record = $records_entry;
+ }
+ }
+ if ($this->_dnsVerboseLog) {
+ log_error("_update(): " . ( $record ? sprintf(gettext("found existing record id: %s"), $record["id"]) : gettext("no matching record found") ));
+ }
+
+ if (is_array($record)) {
+ // update existing record
+ $record["target"] = $this->_dnsIP;
+ $record["ttl_sec"] = (int) $this->_dnsTTL; // linode may round this up, 0 = zone default
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($record));
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
+ curl_setopt($ch, CURLOPT_URL, "$linode_api/domains/$domain_id/records/" . $record["id"]);
+ } else {
+ // create a new record
+ $record = array(
+ "type" => $record_type,
+ "name" => $record_name,
+ "target" => $this->_dnsIP,
+ "ttl_sec" => (int) $this->_dnsTTL, // linode may round this up, 0 = zone default
+ );
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($record));
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
+ curl_setopt($ch, CURLOPT_URL, "$linode_api/domains/$domain_id/records");
+ }
+
+ break;
default:
break;
}
@@ -2103,6 +2193,24 @@
$this->_debug($data);
}
break;
+ case 'linode':
+ case 'linode-v6':
+ $status_intro = gettext("Dynamic DNS") . " {$this->_dnsService} ({$this->_FQDN}): ";
+ $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $result = json_decode($data,true);
+ if ($this->_dnsVerboseLog) {
+ log_error(sprintf(gettext('_checkStatus() results: %1$s'), $data));
+ }
+ if ($http_code == 200 && isset($result["id"]) && ! isset($result["errors"])) {
+ $status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
+ $successful_update = true;
+ } else if ( $http_code == 401 && preg_match('/not authorized to use/i', $data) ) {
+ $status = $status_intro . $error_str . gettext("User Authorization Failed");
+ } else {
+ $status = $status_intro . $error_str .
+ ( isset($domains_result["errors"][0]["reason"]) ? $domains_result["errors"][0]["reason"] : "HTTP $http_code" );
+ }
+ break;
default:
break;
}
diff --git a/src/etc/inc/globals.inc b/src/etc/inc/globals.inc
index 39017f591c..f4e4b6f5d7 100644
--- a/src/etc/inc/globals.inc
+++ b/src/etc/inc/globals.inc
@@ -209,7 +209,7 @@ $factory_default_checkipservice = array(
"descr" => 'Default Check IP Service'
);
-$dyndns_split_domain_types = array("namecheap", "cloudflare", "cloudflare-v6", "gratisdns", "cloudns", "godaddy", "godaddy-v6");
+$dyndns_split_domain_types = array("namecheap", "cloudflare", "cloudflare-v6", "gratisdns", "cloudns", "godaddy", "godaddy-v6", "linode", "linode-v6");
/* pf tokens from FreeBSD source sbin/pfctl/parse.y (plus our custom entries at the end)*/
global $pf_reserved_keywords;
diff --git a/src/etc/inc/services.inc b/src/etc/inc/services.inc
index 894a19e73f..f70a32f146 100644
--- a/src/etc/inc/services.inc
+++ b/src/etc/inc/services.inc
@@ -24,8 +24,8 @@
*/
-define('DYNDNS_PROVIDER_VALUES', 'all-inkl azure azurev6 citynetwork cloudflare cloudflare-v6 cloudns custom custom-v6 digitalocean dnsexit dnsimple dnsmadeeasy dnsomatic dreamhost dreamhost-v6 duiadns duiadns-v6 dyndns dyndns-custom dyndns-static dyns easydns eurodns freedns freedns-v6 glesys godaddy godaddy-v6 googledomains gratisdns he-net he-net-v6 he-net-tunnelbroker hover loopia namecheap noip noip-free ods opendns ovh-dynhost route53 route53-v6 selfhost spdyn spdyn-v6 zoneedit');
-define('DYNDNS_PROVIDER_DESCRIPTIONS', 'All-Inkl.com,Azure DNS,Azure DNS (v6),City Network,Cloudflare,Cloudflare (v6),ClouDNS,Custom,Custom (v6),DigitalOcean,DNSexit,DNSimple,DNS Made Easy,DNS-O-Matic,DreamHost,Dreamhost (v6),DuiaDns.net,DuiaDns.net (v6),DynDNS (dynamic),DynDNS (custom),DynDNS (static),DyNS,easyDNS,Euro Dns,freeDNS,freeDNS (v6),GleSYS,GoDaddy,GoDaddy (v6),Google Domains,GratisDNS,HE.net,HE.net (v6),HE.net Tunnelbroker,Hover,Loopia,Namecheap,No-IP,No-IP (free),ODS.org,OpenDNS,OVH DynHOST,Route 53,Route 53 (v6),SelfHost,SPDYN,SPDYN (v6),ZoneEdit');
+define('DYNDNS_PROVIDER_VALUES', 'all-inkl azure azurev6 citynetwork cloudflare cloudflare-v6 cloudns custom custom-v6 digitalocean dnsexit dnsimple dnsmadeeasy dnsomatic dreamhost dreamhost-v6 duiadns duiadns-v6 dyndns dyndns-custom dyndns-static dyns easydns eurodns freedns freedns-v6 glesys godaddy godaddy-v6 googledomains gratisdns he-net he-net-v6 he-net-tunnelbroker hover linode linode-v6 loopia namecheap noip noip-free ods opendns ovh-dynhost route53 route53-v6 selfhost spdyn spdyn-v6 zoneedit');
+define('DYNDNS_PROVIDER_DESCRIPTIONS', 'All-Inkl.com,Azure DNS,Azure DNS (v6),City Network,Cloudflare,Cloudflare (v6),ClouDNS,Custom,Custom (v6),DigitalOcean,DNSexit,DNSimple,DNS Made Easy,DNS-O-Matic,DreamHost,Dreamhost (v6),DuiaDns.net,DuiaDns.net (v6),DynDNS (dynamic),DynDNS (custom),DynDNS (static),DyNS,easyDNS,Euro Dns,freeDNS,freeDNS (v6),GleSYS,GoDaddy,GoDaddy (v6),Google Domains,GratisDNS,HE.net,HE.net (v6),HE.net Tunnelbroker,Hover,Linode,Linode (v6),Loopia,Namecheap,No-IP,No-IP (free),ODS.org,OpenDNS,OVH DynHOST,Route 53,Route 53 (v6),SelfHost,SPDYN,SPDYN (v6),ZoneEdit');
/* 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 7e0a47f380..eb5eaa972c 100644
--- a/src/usr/local/www/services_dyndns_edit.php
+++ b/src/usr/local/www/services_dyndns_edit.php
@@ -73,7 +73,7 @@ if ($_POST['save'] || $_POST['force']) {
unset($input_errors);
$pconfig = $_POST;
- if (($pconfig['type'] == "freedns" || $pconfig['type'] == "freedns-v6" || $pconfig['type'] == "namecheap" || $pconfig['type'] == "digitalocean") && $_POST['username'] == "") {
+ if (($pconfig['type'] == "freedns" || $pconfig['type'] == "freedns-v6" || $pconfig['type'] == "namecheap" || $pconfig['type'] == "digitalocean" || $pconfig['type'] == "linode" || $pconfig['type'] == "linode-v6") && $_POST['username'] == "") {
$_POST['username'] = "none";
}
@@ -106,6 +106,7 @@ if ($_POST['save'] || $_POST['force']) {
}
if (isset($_POST['host']) && in_array("host", $reqdfields)) {
+ $allow_wildcard = false;
/* Namecheap can have a @. and *. in hostname */
if ($pconfig['type'] == "namecheap" && ($_POST['host'] == '*.' || $_POST['host'] == '*' || $_POST['host'] == '@.' || $_POST['host'] == '@')) {
$host_to_check = $_POST['domainname'];
@@ -115,6 +116,9 @@ if ($_POST['save'] || $_POST['force']) {
$host_to_check = $_POST['domainname'];
} elseif (($pconfig['type'] == "digitalocean") && ($_POST['host'] == '@.' || $_POST['host'] == '@')) {
$host_to_check = $_POST['domainname'];
+ } elseif (($pconfig['type'] == "linode") || ($pconfig['type'] == "linode-v6")) {
+ $host_to_check = $_POST['host'] == '@' ? $_POST['domainname'] : ( $_POST['host'] . '.' . $_POST['domainname'] );
+ $allow_wildcard = true;
} else {
$host_to_check = $_POST['host'];
@@ -130,7 +134,7 @@ if ($_POST['save'] || $_POST['force']) {
}
if ($pconfig['type'] != "custom" && $pconfig['type'] != "custom-v6") {
- if (!is_domain($host_to_check)) {
+ if (!is_domain($host_to_check, $allow_wildcard)) {
$input_errors[] = gettext("The hostname contains invalid characters.");
}
}
@@ -301,8 +305,8 @@ $group->setHelp('Enter the complete fully qualified domain name. Example: myhost
'he.net tunnelbroker: Enter the tunnel ID.%1$s' .
'GleSYS: Enter the record ID.%1$s' .
'DNSimple: Enter only the domain name.%1$s' .
- 'Namecheap, Cloudflare, GratisDNS, Hover, ClouDNS, GoDaddy: Enter the hostname and the domain separately, with the domain being the domain or subdomain zone being handled by the provider.%1$s' .
- 'Cloudflare and DigitalOcean: Enter @ as the hostname to indicate an empty field.', '
');
+ 'Namecheap, Cloudflare, GratisDNS, Hover, ClouDNS, GoDaddy, Linode: Enter the hostname and the domain separately, with the domain being the domain or subdomain zone being handled by the provider.%1$s' .
+ 'Cloudflare, DigitalOcean, Linode: Enter @ as the hostname to indicate an empty field.', '
');
$section->add($group);
@@ -356,7 +360,7 @@ $section->addInput(new Form_Input(
'Username',
'text',
$pconfig['username']
-))->setHelp('Username is required for all types except Namecheap, FreeDNS , FreeDNS-v6, DigitalOcean and Custom Entries.%1$s' .
+))->setHelp('Username is required for all types except Namecheap, FreeDNS, FreeDNS-v6, DigitalOcean, Linode and Custom Entries.%1$s' .
'Azure: Enter your Azure AD application ID%1$s' .
'DNS Made Easy: Dynamic DNS ID%1$s' .
'DNSimple: User account ID (In the URL after the \'/a/\')%1$s' .
@@ -381,6 +385,7 @@ $section->addPassword(new Form_Input(
'Dreamhost: Enter the API Key.%1$s' .
'GoDaddy: Enter the API secret.%1$s' .
'DNSimple: Enter the API token.%1$s' .
+ 'Linode: Enter the Personal Access Token.%1$s' .
'Cloudflare: Enter the Global API Key.', '
');
$section->addInput(new Form_Input(
@@ -589,6 +594,21 @@ events.push(function() {
hideInput('zoneid', false);
hideInput('ttl', false);
break;
+ case "linode-v6":
+ case "linode":
+ hideGroupInput('domainname', false);
+ hideInput('resultmatch', true);
+ hideInput('updateurl', true);
+ hideInput('requestif', true);
+ hideCheckbox('curl_ipresolve_v4', true);
+ hideCheckbox('curl_ssl_verifypeer', true);
+ hideInput('host', false);
+ hideInput('mx', true);
+ hideCheckbox('wildcard', true);
+ hideCheckbox('proxied', true);
+ hideInput('zoneid', true);
+ hideInput('ttl', false);
+ break;
default:
hideGroupInput('domainname', true);
hideInput('resultmatch', true);