Skip to content

Commit

Permalink
Add support to ionos.com
Browse files Browse the repository at this point in the history
  • Loading branch information
nelsonjr committed Sep 9, 2024
1 parent 1c0ba9a commit bcaf18a
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 0 deletions.
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master).
[#719](https://github.com/ddclient/ddclient/pull/719)
* `directnic`: Added support for updatng Directnic records.
[#726](https://github.com/ddclient/ddclient/pull/726)
* `ionos`: Added support for updating Ionos records.
[#743](https://github.com/ddclient/ddclient/pull/743)

### Bug fixes

Expand Down
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ handwritten_tests = \
t/protocol_directnic.pl \
t/protocol_dnsexit2.pl \
t/protocol_dyndns2.pl \
t/protocol_ionos.pl \
t/read_recap.pl \
t/skip.pl \
t/ssl-validate.pl \
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Dynamic DNS services currently supported include:
* [Gandi](https://gandi.net)
* [GoDaddy](https://www.godaddy.com)
* [Hurricane Electric](https://dns.he.net)
* [Ionos](https://ionos.com)
* [Infomaniak](https://faq.infomaniak.com/2376)
* [INWX](https://www.inwx.com/)
* [Loopia](https://www.loopia.se)
Expand Down
73 changes: 73 additions & 0 deletions ddclient.in
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,15 @@ our %protocols = (
'max-interval' => setv(T_DELAY, 0, 'inf', 0),
},
),
'ionos' => ddclient::Protocol->new(
'update' => \&nic_ionos_update,
'examples' => \&nic_ionos_examples,
'cfgvars' => {
%{$cfgvars{'protocol-common-defaults'}},
'server' => setv(T_FQDNP, 0, 'ipv4.api.hosting.ionos.com', undef),
'login' => undef,
},
),
);
$cfgvars{'merged'} = {
map({ %{$protocols{$_}{'cfgvars'}} } keys(%protocols)),
Expand Down Expand Up @@ -7641,6 +7650,70 @@ Example ${program}.conf file entries:
EoEXAMPLE
}

######################################################################
## nic_ionos_examples
######################################################################
sub nic_ionos_examples {
my $self = shift;
return <<EoEXAMPLE;
o 'ionos'
The 'ionos' protocol is used by DNS service offered by ionos.com.
Configuration variables applicable to the 'ionos' protocol are:
protocol=ionos ##
password=dyndns-update-key ## the key created to update the host below
fully.qualified.host ## the host registered with the service.
Example ${program}.conf file entries:
## single host update
protocol=ionos,
password=dyndns-update-key
myhost.com
Getting the DynDNS key (only available via Ionos API):
1. Create an API key
2. Enable DynDNS for your host using the /dns/v1/dyndns API
(documentation @ https://developer.hosting.ionos.es/docs/dns)
3. In the API response, get the key value. The key is a long string
from the "q=" parameter. The URL looks like this:
https://ipv4.api.hosting.ionos.com/dns/v1/dyndns?q=XXXXXXXXXXXXXXX
(in the example above the key is "XXXXXXXXXXXXXXX").
Note: Because the key is individual for each host, you cannot update
multiple hosts with the same key.
EoEXAMPLE
}

######################################################################
## nic_ionos_update
## response contains "code 200" on succesfull completion
######################################################################
sub nic_ionos_update {
my $self = shift;
## update each configured host
## should improve to update in one pass
for my $h (@_) {
local $_l = pushlogctx($h);
my $ip = delete $config{$h}{'wantip'};
info("setting IP address to $ip");
my $url = opt('server', $h) . "/dns/v1/dyndns?q=" . opt('password', $h);
my $reply = geturl(proxy => opt('proxy'), url => $url);
last if !header_ok($reply);

if ($reply =~ /200 OK/) {
$recap{$h}{'ip'} = $ip;
$recap{$h}{'mtime'} = $now;
$recap{$h}{'status'} = 'good';
success("IP address set to $ip");
} else {
$recap{$h}{'status'} = 'failed';
failed("server said: $reply");
}
}
}

# Execute main() if this file is run as a script or run via PAR (https://metacpan.org/pod/PAR),
# otherwise do nothing. This "modulino" pattern makes it possible to import this file as a module
# and test its functions directly; there's no need for test-only command-line arguments or stdout
Expand Down
69 changes: 69 additions & 0 deletions t/protocol_ionos.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use Test::More;
BEGIN { eval { require Test::Warnings; } or skip($@, 1); }
BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); }
BEGIN {
eval { require ddclient::t::HTTPD; 1; } or plan(skip_all => $@);
ddclient::t::HTTPD->import();
plan tests => 2;
}

{
package Logger;
use parent qw(-norequire ddclient::Logger);
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new(undef, $parent);
$self->{logs} = [];
return $self;
}
sub _log {
my ($self, $args) = @_;
push(@{$self->{logs}}, $args)
if ($args->{label} // '') =~ qr/^(?:WARNING|FATAL|SUCCESS|FAILED)$/;
return $self->SUPER::_log($args);
}
}

# Subtest for the protocol_ionos
subtest 'Testing protocol_ionos' => sub {
# Mock HTTP server
httpd()->run(sub {
my ($req) = @_;
diag('==============================================================================');
diag("Test server received request:\n" . $req->as_string());

return undef if $req->uri()->path() eq '/control';
return [400, $textplain, ['invalid method: ' . $req->method()]] if $req->method() ne 'GET';
return undef
});

local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
my $l = Logger->new($ddclient::_l);

local %ddclient::config = (
'host.my.example.com' => {
'protocol' => 'ionos',
'password' => 'mytestingpassword',
'server' => httpd()->endpoint(),
'wantip' => '1.2.3.4',
},
);

httpd()->reset([200, $textplain, []]);

{
local $ddclient::_l = $l;

ddclient::nic_ionos_update(undef, 'host.my.example.com');
}

my @requests = httpd()->reset();
is(scalar(@requests), 1, "Single update request");

my $req = shift(@requests);
is($req->uri()->path(), '/dns/v1/dyndns', "Correct request path");
is($req->uri()->query(), 'q=mytestingpassword', "Correct request query");
};

done_testing();

0 comments on commit bcaf18a

Please sign in to comment.