diff --git a/.gitignore b/.gitignore index 3fe47fa..e69a9a7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ patches release .svn .cvsignore +*~ diff --git a/README.md b/README.md index abd5a20..820222f 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Dynamic DNS services currently supported include: ChangeIP - See http://www.changeip.com/ for details dtdns - See http://www.dtdns.com/ for details nsupdate - See nsupdate(1) and ddns-confgen(8) for details + CloudFlare - See https://www.cloudflare.com/ for defails DDclient now supports many of cable/dsl broadband routers. @@ -37,8 +38,9 @@ REQUIREMENTS: - one or more accounts from one of the dynamic DNS services -- Perl 5.004 or later - (you need the IO::Socket::SSL perl library for ssl-support) +- Perl 5.014 or later + (you need the IO::Socket::SSL perl library for ssl-support + and JSON::Any perl library for JSON support) - Linux or probably any common Unix system diff --git a/ddclient b/ddclient index 194f2de..84884e0 100755 --- a/ddclient +++ b/ddclient @@ -13,14 +13,19 @@ # Support for multiple IP numbers added by # Astaro AG, Ingo Schwarze September 16, 2008 # -# Modified to work with Cloudflare by Robert Ian Hawdon 2012-07-16: http://robertianhawdon.me.uk/ +# Support for multiple domain support for Namecheap by Robert Ian Hawdon 2010-09-03: https://robertianhawdon.me.uk/ +# +# Initial Cloudflare support by Ian Pye, updated by Robert Ian Hawdon 2012-07-16 +# Further updates by Peter Roberts to support the new API 2013-09-26, 2014-06-22: http://blog.peter-r.co.uk/ +# # ###################################################################### -require 5.004; +require 5.014; use strict; use Getopt::Long; use Sys::Hostname; use IO::Socket; +use JSON::Any; # my ($VERSION) = q$Revision: 161 $ =~ /(\d+)/; @@ -436,7 +441,8 @@ my %variables = ( 'zone' => setv(T_STRING, 1, 1, 1, '', undef), }, 'cloudflare-common-defaults' => { - 'server' => setv(T_FQDNP, 1, 0, 1, 'www.cloudflare.com', undef), + 'server' => setv(T_FQDNP, 1, 0, 1, 'www.cloudflare.com', 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), 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef), @@ -618,7 +624,7 @@ my @opt = ( "usage: ${program} [options]", "options are:", [ "daemon", "=s", "-daemon delay : run as a daemon, specify delay as an interval." ], -+ [ "foreground", "!", "-foreground : do not fork" ], + [ "foreground", "!", "-foreground : do not fork" ], [ "proxy", "=s", "-proxy host : use 'host' as the HTTP proxy" ], [ "server", "=s", "-server host : update DNS information on 'host'" ], [ "protocol", "=s", "-protocol type : update protocol used" ], @@ -3907,8 +3913,6 @@ EoINSTR3 ## ## written by Ian Pye ## -## https://www.cloudflare.com/api.html?a=DIUP&u=myemail@mydomain.com&tkn=SecretPass&ip=192.168.10.4&hosts=example.com -## ###################################################################### sub nic_cloudflare_examples { return < [ $_ ] } @_; + ## update each set of hosts that had similar configurations + foreach my $sig (keys %groups) { + my @hosts = @{$groups{$sig}}; + my $hosts = join(',', @hosts); + my $key = $hosts[0]; + my $ip = $config{$key}{'wantip'}; - my %errors = ( - 'E_NOUPDATE' => 'No changes made to the hostname(s). Continual updates with no changes lead to blocked clients.', - 'E_NOHOST' => 'No valid FQDN (fully qualified domain name) was specified', - 'E_INVLDHST'=> 'An invalid hostname was specified. This may be due to the fact the hostname has not been created in the system. Creating new host names via clients is not supported.', - 'E_UNAUTH' => 'The username specified is not authorized to update this hostname and domain.', - 'E_INVLDIP' => 'The IP address given is not valid.', - 'E_DUPHST' => 'Duplicate values exist for a record. Only single values for records are supported currently.', - ); + # FQDNs + for my $domain (@hosts) { + my $hostname = $domain =~ s/\.$config{$key}{zone}$//r; + delete $config{$domain}{'wantip'}; - ## update each set of hosts that had similar configurations - foreach my $sig (keys %groups) { + info("setting IP address to %s for %s", $ip, $domain); + verbose("UPDATE:","updating %s", $domain); - my @hosts = @{$groups{$sig}}; - my $hosts = join(',', @hosts); - my $h = $hosts[0]; - my $ip = $config{$h}{'wantip'}; + # Get domain ID + my $url = "https://$config{$key}{'server'}/api_json.html?a=rec_load_all"; + $url .= "&z=".$config{$key}{'zone'}; + $url .= "&email=".$config{$key}{'login'}; + $url .= "&tkn=".$config{$key}{'password'}; - delete $config{$_}{'wantip'} foreach @hosts; - - info("setting IP address to %s for %s", $ip, $hosts); - verbose("UPDATE:","updating %s", $hosts); - - my $url; - $url = "https://$config{$h}{'server'}/api.html?a=DIUP"; - $url .= "&hosts=$hosts"; - $url .= "&u=".$config{$h}{'login'}; - $url .= "&tkn=".$config{$h}{'password'}; - $url .= "&ip="; - $url .= "$ip" if $ip; - - my $reply = geturl(opt('proxy'), $url); - if (!defined($reply) || !$reply) { - failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'}); - last; - } - last if !header_ok($hosts, $reply); - - my @reply = split /\n/, $reply; - my @body = (); - my $in_header = 1; - foreach my $line (@reply) { - if ($line eq "") { - $in_header = 0; - } elsif (!$in_header) { - push(@body, $line); - } - } + my $reply = geturl(opt('proxy'), $url); + unless ($reply) { + failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'}); + last; + } + last if !header_ok($domain, $reply); - if ($reply =~ /E_UNAUTH/) { - failed ("%s", $errors{"E_UNAUTH"}); - } elsif ($reply =~ /E_NOHOST/) { - failed ("%s", $errors{"E_NOHOST"}); - } elsif ($reply =~ /E_INVLDHST/) { - failed ("%s", $errors{"E_INVLDHST"}); - } elsif ($reply =~ /E_INVLDIP/) { - failed ("%s", $errors{"E_INVLDIP"}); - } elsif ($reply =~ /E_DUPHST/) { - failed ("%s", $errors{"E_DUPHST"}); - } else { - foreach my $line (@body) { - my @res = split / /, $line; - if ($res[1] eq "E_NOUPDATE") { - $config{$res[0]}{'ip'} = $ip; - $config{$res[0]}{'mtime'} = $now; - $config{$res[0]}{'status'} = 'good'; - warning ("%s -- %s", $res[0], $errors{"E_NOUPDATE"}); - } elsif ($res[1] eq "OK") { - $config{$res[0]}{'ip'} = $ip; - $config{$res[0]}{'mtime'} = $now; - $config{$res[0]}{'status'} = 'good'; - success ("%s -- Updated Successfully to %s", $res[0], $ip); - } - } - } - } + # Strip header + $reply =~ s/^.*?\n\n//s; + my $response = JSON::Any->jsonToObj($reply); + if ($response->{result} eq 'error') { + failed ("%s", $response->{msg}); + next; + } + + # Pull the ID out of the json, messy + my ($id) = map { $_->{name} eq $domain ? $_->{rec_id} : () } @{ $response->{response}->{recs}->{objs} }; + unless($id) { + failed("updating %s: No domain ID found.", $domain); + next; + } + + # Set domain + $url = "https://$config{$key}{'server'}/api_json.html?a=rec_edit&type=A&ttl=1"; + $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); + unless ($reply) { + failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'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}); + } else { + success ("%s -- Updated Successfully to %s", $domain, $ip); + + } + + # Cache + $config{$key}{'ip'} = $ip; + $config{$key}{'mtime'} = $now; + $config{$key}{'status'} = 'good'; + } + } } -###################################################################### -# vim: ai ts=4 sw=4 tw=78 : - __END__