Add Dynamic DNS support for Linode #9268

(cherry picked from commit b923a8251c)
This commit is contained in:
Tom Embt 2019-01-15 16:33:20 -05:00 committed by jim-p
parent 2fb3b9bd39
commit e77f993f44
4 changed files with 136 additions and 8 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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()) {

View File

@ -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.', '<br />');
'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.', '<br />');
$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.', '<br />');
$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);