diff --git a/README.md b/README.md index 0da9459..7c58aa0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Dynamic DNS services currently supported include: CloudFlare - See https://www.cloudflare.com/ for details Google - See http://www.google.com/domains for details Duckdns - See https://duckdns.org/ for details + Freemyip - See https://freemyip.com for details woima.fi - See https://woima.fi/ for details DDclient now supports many of cable/dsl broadband routers. diff --git a/README.ssl b/README.ssl index 1d0e2f1..ed8d19b 100644 --- a/README.ssl +++ b/README.ssl @@ -8,3 +8,4 @@ On alpine, you need perl-io-socket-ssl to have IO::Socket::SSL ssl support is tested on folowing dynamic dns providers: - dyndns.com +- freemyip.com diff --git a/ddclient b/ddclient index 439a9cb..3ff2972 100755 --- a/ddclient +++ b/ddclient @@ -24,6 +24,7 @@ use strict; use Getopt::Long; use Sys::Hostname; use IO::Socket; +use Data::Validate::IP; my $version = "3.8.3"; my $programd = $0; @@ -440,7 +441,7 @@ my %variables = ( 'tcp' => setv(T_BOOL, 0, 1, 1, 0, undef), }, 'cloudflare-common-defaults' => { - 'server' => setv(T_FQDNP, 1, 0, 1, 'www.cloudflare.com', undef), + 'server' => setv(T_FQDNP, 1, 0, 1, 'api.cloudflare.com/client/v4', undef), 'zone' => setv(T_FQDN, 1, 0, 1, '', undef), 'static' => setv(T_BOOL, 0, 1, 1, 0, undef), 'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef), @@ -455,6 +456,10 @@ my %variables = ( 'server' => setv(T_FQDNP, 1, 0, 1, 'www.duckdns.org', undef), 'login' => setv(T_LOGIN, 0, 0, 0, 'unused', undef), }, + 'freemyip-common-defaults' => { + 'server' => setv(T_FQDNP, 1, 0, 1, 'freemyip.com', undef), + 'login' => setv(T_LOGIN, 0, 0, 0, 'unused', undef), + }, 'woima-common-defaults' => { 'static' => setv(T_BOOL, 0, 1, 1, 0, undef), 'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef), @@ -637,7 +642,7 @@ my %services = ( 'update' => \&nic_cloudflare_update, 'examples' => \&nic_cloudflare_examples, 'variables' => merge( - { 'server' => setv(T_FQDNP, 1, 0, 1, 'www.cloudflare.com', undef) }, + { 'server' => setv(T_FQDNP, 1, 0, 1, 'api.cloudflare.com/client/v4', undef) }, { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),}, $variables{'cloudflare-common-defaults'}, $variables{'service-common-defaults'}, @@ -662,6 +667,15 @@ my %services = ( $variables{'service-common-defaults'}, ), }, + 'freemyip' => { + 'updateable' => undef, + 'update' => \&nic_freemyip_update, + 'examples' => \&nic_freemyip_examples, + 'variables' => merge( + $variables{'freemyip-common-defaults'}, + $variables{'service-common-defaults'}, + ), + }, 'woima' => { 'updateable' => undef, 'update' => \&nic_woima_update, @@ -1951,6 +1965,9 @@ sub geturl { my $url = shift || ''; my $login = shift || ''; my $password = shift || ''; + my $headers = shift || ''; + my $method = shift || 'GET'; + my $data = shift || ''; my ($peer, $server, $port, $default_port, $use_ssl); my ($sd, $rq, $request, $reply); @@ -1991,7 +2008,7 @@ sub geturl { my $to = sprintf "%s%s", $server, $proxy ? " via proxy $peer:$port" : ""; verbose("CONNECT:", "%s", $to); - $request = "GET "; + $request = "$method "; $request .= "http://$server" if $proxy; $request .= "/$url HTTP/1.0\n"; $request .= "Host: $server\n"; @@ -2000,7 +2017,10 @@ sub geturl { $request .= "Authorization: Basic $auth\n" if $login || $password; $request .= "User-Agent: ${program}/${version}\n"; $request .= "Connection: close\n"; + $request .= "$headers\n"; + $request .= "Content-Length: ".length($data)."\n" if $data; $request .= "\n"; + $request .= $data; ## make sure newlines are for some pedantic proxy servers ($rq = $request) =~ s/\n/\r\n/g; @@ -4129,6 +4149,12 @@ sub nic_nsupdate_update { my $server = $config{$h}{'server'}; my $zone = $config{$h}{'zone'}; my $ip = $config{$h}{'wantip'}; + my $recordtype = ''; + if (is_ipv6($ip)) { + $recordtype = 'AAAA'; + } else { + $recordtype = 'A'; + } delete $config{$_}{'wantip'} foreach @hosts; info("setting IP address to %s for %s", $ip, $hosts); @@ -4141,8 +4167,8 @@ zone $zone. EoINSTR1 foreach (@hosts) { $instructions .= <{name} eq $domain ? $_->{rec_id} : () } @{ $response->{response}->{recs}->{objs} }; - unless($id) { - failed("updating %s: No domain ID found.", $domain); + my ($zone_id) = map { $_->{name} eq $config{$key}{'zone'} ? $_->{id} : () } @{ $response->{result} }; + unless($zone_id) { + failed("updating %s: No zone ID found.", $config{$key}{'zone'}); next; } + info("zone ID is $zone_id"); + + # Get DNS record ID + $url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records?"; + $url .= "type=A&name=$domain"; + + $reply = geturl(opt('proxy'), $url, undef, undef, $headers); + unless ($reply) { + failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'}); + last; + } + last if !header_ok($domain, $reply); + + # Strip header + $reply =~ s/^.*?\n\n//s; + $response = JSON::Any->jsonToObj($reply); + if ($response->{result} eq 'error') { + failed ("%s", $response->{msg}); + next; + } + + # Pull the ID out of the json, messy + my ($dns_rec_id) = map { $_->{name} eq $domain ? $_->{id} : () } @{ $response->{result} }; + unless($dns_rec_id) { + failed("updating %s: No DNS record ID found.", $domain); + next; + } + info("DNS record ID is $dns_rec_id"); # Set domain - $url = "https://$config{$key}{'server'}/api_json.html?a=rec_edit&type=A"; - $url .= "&ttl=".$config{$key}{'ttl'}; - $url .= "&name=$hostname"; - $url .= "&z=".$config{$key}{'zone'}; - $url .= "&id=".$id; - $url .= "&email=".$config{$key}{'login'}; - $url .= "&tkn=".$config{$key}{'password'}; - $url .= "&content="; - $url .= "$ip" if $ip; - - $reply = geturl(opt('proxy'), $url); + $url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records/$dns_rec_id"; + my $data = "{\"content\":\"$ip\"}"; + $reply = geturl(opt('proxy'), $url, undef, undef, $headers, "PATCH", $data); unless ($reply) { failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'}); last; @@ -4374,6 +4422,80 @@ sub nic_duckdns_update { } } +###################################################################### +## nic_freemyip_examples +###################################################################### +sub nic_freemyip_examples { + return <