From b64f9c7fe788a5670ce490d306445a3266985c15 Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Mon, 11 Jan 2016 16:01:05 +0900 Subject: [PATCH 1/5] Added MyDNS.JP Add support for MyDNS.JP http://www.mydns.jp/ --- README.md | 3 +- ddclient | 87 ++++++++++++++++++++++++++++++++++++++++ sample-etc_ddclient.conf | 8 ++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4560eeb..0cfb76a 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ Dynamic DNS services currently supported include: nsupdate - See nsupdate(1) and ddns-confgen(8) for details CloudFlare - See https://www.cloudflare.com/ for details Google - See http://www.google.com/domains for details - Duckdns - See https://duckdns.org/ for details + Duckdns - See https://duckdns.org/ for details woima.fi - See https://woima.fi/ for details + MyDNS.JP - See http://www.mydns.jp/ for details DDclient now supports many of cable/dsl broadband routers. diff --git a/ddclient b/ddclient index a2805e8..a2ab20b 100755 --- a/ddclient +++ b/ddclient @@ -477,6 +477,10 @@ my %variables = ( 'warned-min-interval' => setv(T_ANY, 0, 1, 0, 0, undef), 'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, 0, undef), }, + 'mydns-common-defaults' => { + 'server' => setv(T_FQDNP, 1, 0, 1, 'www.mydns.jp', undef), + 'max-interval' => setv(T_DELAY, 0, 0, 1, interval('6d'), 0), + }, ); my %services = ( 'dyndns1' => { @@ -670,6 +674,15 @@ my %services = ( $variables{'woima-service-common-defaults'}, ), }, + 'mydns' => { + 'updateable' => undef, + 'update' => \&nic_mydns_update, + 'examples' => \&nic_mydns_examples, + 'variables' => merge( + $variables{'mydns-common-defaults'}, + $variables{'service-common-defaults'}, + ), + }, ); $variables{'merged'} = merge($variables{'global-defaults'}, $variables{'service-common-defaults'}, @@ -4480,6 +4493,80 @@ sub nic_woima_update { } } +###################################################################### +## nic_mydns_examples +###################################################################### +sub nic_mydns_examples { + return </i; + $config{$h}{'status'} = 'failed'; + warning("SENT: %s", $url) unless opt('verbose'); + warning("REPLIED: %s", $1); + failed("updating %s: Invalid reply.", $h); + } + } +} ###################################################################### # vim: ai ts=4 sw=4 tw=78 : diff --git a/sample-etc_ddclient.conf b/sample-etc_ddclient.conf index 79a7130..64ebfe3 100644 --- a/sample-etc_ddclient.conf +++ b/sample-etc_ddclient.conf @@ -221,3 +221,11 @@ ssl=yes # use ssl-support. Works with # password=my-auto-generated-password # protocol=duckdns hostwithoutduckdnsorg +## +## MyDNS (www.mydns.jp) +## +# protocol=mydns +# server=www.mydns.jp +# login=my-mydns.jp-login +# password=my-mydns.jp-password +# your-host.mydns.jp From eeb0bb586d8cd162f9e8202da96b46bb42e23630 Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Mon, 11 Jan 2016 21:29:17 +0900 Subject: [PATCH 2/5] MyDNS.JP: support for HTTP-BASIC http://www.mydns.jp/?MENU=030 HTTP-BASIC(default): Let MyDNS.jp get the IP. HTTP-DIRECT(directip=yes): Use the IP specified by ddclient. --- ddclient | 54 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/ddclient b/ddclient index a2ab20b..f6fbba2 100755 --- a/ddclient +++ b/ddclient @@ -478,6 +478,7 @@ my %variables = ( 'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, 0, undef), }, 'mydns-common-defaults' => { + 'directip' => setv(T_BOOL, 0, 0, 1, 0, undef), 'server' => setv(T_FQDNP, 1, 0, 1, 'www.mydns.jp', undef), 'max-interval' => setv(T_DELAY, 0, 0, 1, interval('6d'), 0), }, @@ -678,7 +679,7 @@ my %services = ( 'updateable' => undef, 'update' => \&nic_mydns_update, 'examples' => \&nic_mydns_examples, - 'variables' => merge( + 'variables' => merge( $variables{'mydns-common-defaults'}, $variables{'service-common-defaults'}, ), @@ -4504,8 +4505,9 @@ The 'mydns' protocol is used by a free dynamic DNS service offered by www.mydns. Configuration variables applicable to the 'mydns' protocol are: protocol=mydns ## - server=fqdn.of.service ## defaults to www.mydns.jp - login=service-login ## login name and password registered with the service + directip=no|yes ## update the DNS record with a specified IP address. + server=fqdn.of.service ## defaults to www.mydns.jp. + login=service-login ## login name and password registered with the service. password=service-password ## fully.qualified.host ## the host registered with the service. @@ -4525,44 +4527,48 @@ sub nic_mydns_update { ## update each configured host foreach my $h (@_) { - my $ip = delete $config{$h}{'wantip'}; + my $ip = delete $config{$h}{'wantip'}; info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:","updating %s", $h); - # Set the URL that we're going to to update + ## update the DNS record my $url; - $url = "http://$config{$h}{'server'}/directip.html"; - $url .= "?MID="; - $url .= $config{$h}{'login'}; - $url .= "&PWD="; - $url .= $config{$h}{'password'}; - $url .= "&IPV4ADDR="; - $url .= $ip; + my $reply; + if ($config{$h}{'directip'}) { + $url = "http://$config{$h}{'server'}/directip.html"; + $url .= "?MID="; + $url .= $config{$h}{'login'}; + $url .= "&PWD="; + $url .= $config{$h}{'password'}; + $url .= "&IPV4ADDR="; + $url .= $ip; + $reply = geturl(opt('proxy'), $url); + } else { + $url = "http://$config{$h}{'server'}/login.html"; + $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'}); + } - # Try to get URL - my $reply = geturl(opt('proxy'), $url); - - # No response, declare as failed + ## no response, declare as failed if (!defined($reply) || !$reply) { failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'}); last; } last if !header_ok($h, $reply); - # Response found, check the reply - if ($reply =~ /login_status = 1/i) - { + ## response found, check the result + if ($reply =~ /login_status = 1/i) { $config{$h}{'ip'} = $ip; $config{$h}{'mtime'} = $now; $config{$h}{'status'} = 'good'; success("updating %s: good: IP address set to %s", $h, $ip); - } - else - { - $reply =~ /login_status.*\n.*\n(.+)\s*
/i; + } else { $config{$h}{'status'} = 'failed'; warning("SENT: %s", $url) unless opt('verbose'); - warning("REPLIED: %s", $1); + if ($config{$h}{'directip'}) { + if ($reply =~ /login_status.*\n.*\n(.+)\s*
/i) { + warning("REPLIED: %s", $1); + } + } failed("updating %s: Invalid reply.", $h); } } From b14f77e06807276a2c37e49c24de423be251ca20 Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Sun, 26 Feb 2017 17:32:44 +0900 Subject: [PATCH 3/5] Updated to the latest https://github.com/wimpunk/ddclient/commit/94dc35984f42f2b239ea2763fc58f63d30a07217 --- README.md | 6 +- README.ssl | 1 + ddclient | 462 +++++++++++++++++-------- sample-etc_ddclient.conf | 24 +- sample-etc_rc.d_init.d_ddclient.alpine | 2 +- sample-get-ip-from-fritzbox | 20 ++ 6 files changed, 375 insertions(+), 140 deletions(-) create mode 100644 sample-get-ip-from-fritzbox diff --git a/README.md b/README.md index 0cfb76a..ca8343f 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 MyDNS.JP - See http://www.mydns.jp/ for details @@ -43,8 +44,9 @@ REQUIREMENTS: - one or more accounts from one of the dynamic DNS services - Perl 5.014 or later - (you need the IO::Socket::SSL perl library for ssl-support - and JSON::Any perl library for JSON support) + (you need the IO::Socket::SSL perl library for ssl-support, + JSON::PP perl library for JSON support and + IO::Socket:INET6 perl library for ipv6-support) - Linux or probably any common Unix system 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 f6fbba2..f0f8754 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; @@ -345,12 +346,12 @@ my %variables = ( 'retry' => setv(T_BOOL, 0, 0, 0, 0, undef), 'force' => setv(T_BOOL, 0, 0, 0, 0, undef), 'ssl' => setv(T_BOOL, 0, 0, 0, 0, undef), - + 'ipv6' => setv(T_BOOL, 0, 0, 0, 0, undef), 'syslog' => setv(T_BOOL, 0, 0, 1, 0, undef), 'facility' => setv(T_STRING,0, 0, 1, 'daemon', undef), 'priority' => setv(T_STRING,0, 0, 1, 'notice', undef), - 'mail' => setv(T_EMAIL, 0, 0, 1, '', undef), - 'mail-failure' => setv(T_EMAIL, 0, 0, 1, '', undef), + 'mail' => setv(T_EMAIL, 0, 0, 1, '', undef), + 'mail-failure' => setv(T_EMAIL, 0, 0, 1, '', undef), 'exec' => setv(T_BOOL, 0, 0, 1, 1, undef), 'debug' => setv(T_BOOL, 0, 0, 1, 0, undef), @@ -380,7 +381,7 @@ my %variables = ( 'fw-password' => setv(T_PASSWD,0, 0, 1, '', undef), 'cmd' => setv(T_PROG, 0, 0, 1, '', undef), 'cmd-skip' => setv(T_STRING,0, 0, 1, '', undef), - + 'ipv6' => setv(T_BOOL, 0, 0, 0, 0, undef), 'ip' => setv(T_IP, 0, 1, 0, undef, undef), 'wtime' => setv(T_DELAY, 0, 1, 1, 0, interval('30s')), 'mtime' => setv(T_NUMBER, 0, 1, 0, 0, undef), @@ -437,9 +438,10 @@ my %variables = ( 'nsupdate-common-defaults' => { 'ttl' => setv(T_NUMBER, 0, 1, 0, 600, undef), 'zone' => setv(T_STRING, 1, 1, 1, '', undef), + '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), @@ -454,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), @@ -641,7 +647,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'}, @@ -666,6 +672,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, @@ -695,7 +710,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" ], @@ -743,6 +758,7 @@ my @opt = ( [ "debug", "!", "-{no}debug : print {no} debugging information" ], [ "verbose", "!", "-{no}verbose : print {no} verbose information" ], [ "quiet", "!", "-{no}quiet : print {no} messages for unnecessary updates" ], + [ "ipv6", "!", "-{no}ipv6 : use ipv6" ], [ "help", "", "-help : this message" ], [ "postscript", "", "-postscript : script to run after updating ddclient, has new IP as param" ], @@ -917,8 +933,10 @@ sub update_nics { next; } if ($ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) { - warning("malformed IP address (%s)", $ip); - next; + if( !ipv6_match($ip) ) { + warning("malformed IP address (%s)", $ip); + next; + } } $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd} = $ip; } @@ -1060,7 +1078,7 @@ sub parse_assignment { my ($c, $name, $value); my ($escape, $quote) = (0, ''); - if ($rest =~ /^\s*([a-z][a-z_-]*)=(.*)/i) { + if ($rest =~ /^\s*([a-z][0-9a-z_-]*)=(.*)/i) { ($name, $rest, $value) = ($1, $2, ''); while (length($c = substr($rest,0,1))) { @@ -1173,7 +1191,7 @@ sub _read_config { ## verify that keywords are valid...and check the value foreach my $k (keys %locals) { - $locals{$k} = $passwords{$k} if defined $passwords{$k}; + $locals{$k} = $passwords{$k} if defined $passwords{$k}; if (!exists $variables{'merged'}{$k}) { warning("unrecognized keyword '%s' (ignored)", $k); delete $locals{$k}; @@ -1270,14 +1288,14 @@ sub init_config { ## and those in -options=... if (exists $options{'host'}) { foreach my $h (split_by_comma($options{'host'})) { - push @hosts, $h; + push @hosts, $h; } delete $options{'host'}; } ## merge options into host definitions or globals if (@hosts) { foreach my $h (@hosts) { - $config{$h} = merge(\%options, $config{$h}); + $config{$h} = merge(\%options, $config{$h}); } $opt{'host'} = join(',', @hosts); } else { @@ -1287,14 +1305,14 @@ sub init_config { ## override global options with those on the command-line. foreach my $o (keys %opt) { - if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) { - $globals{$o} = $opt{$o}; - } + if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) { + $globals{$o} = $opt{$o}; + } } ## sanity check if (defined $opt{'host'} && defined $opt{'retry'}) { - usage("options -retry and -host (or -option host=..) are mutually exclusive"); + usage("options -retry and -host (or -option host=..) are mutually exclusive"); } ## determine hosts to update (those on the cmd-line, config-file, or failed cached) @@ -1324,14 +1342,14 @@ sub init_config { ## make sure config entries have all defaults and they meet minimums ## first the globals... foreach my $k (keys %globals) { - my $def = $variables{'merged'}{$k}; - my $ovalue = define($globals{$k}, $def->{'default'}); - my $value = check_value($ovalue, $def); - if ($def->{'required'} && !defined $value) { - $value = default($k); - warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value); - } - $globals{$k} = $value; + my $def = $variables{'merged'}{$k}; + my $ovalue = define($globals{$k}, $def->{'default'}); + my $value = check_value($ovalue, $def); + if ($def->{'required'} && !defined $value) { + $value = default($k); + warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value); + } + $globals{$k} = $value; } ## now the host definitions... @@ -1864,7 +1882,9 @@ sub check_value { # return undef if $value =~ /:/; } elsif ($type eq T_IP) { - return undef if $value !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; + if( !ipv6_match($value) ) { + return undef if $value !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; + } } return $value; } @@ -1903,6 +1923,24 @@ EOM import IO::Socket::SSL; { no warnings; $IO::Socket::SSL::DEBUG = 0; } } + +###################################################################### +## load_ipv6_support +###################################################################### +sub load_ipv6_support { + my $ipv6_loaded = eval {require IO::Socket::INET6}; + unless ($ipv6_loaded) { + fatal(<<"EOM"); +Error loading the Perl module IO::Socket::INET6 needed for ipv6 connect. +On Debian, the package libio-socket-inet6-perl must be installed. +On Red Hat, the package perl-IO-Socket-INET6 must be installed. +On Alpine, the package perl-io-socket-inet6 must be installed. +EOM + } + import IO::Socket::INET6; + { no warnings; $IO::Socket::INET6::DEBUG = 0; } +} + ###################################################################### ## load_sha1_support ###################################################################### @@ -1925,13 +1963,13 @@ EOM ## load_json_support ###################################################################### sub load_json_support { - my $json_loaded = eval {require JSON::Any}; + my $json_loaded = eval {require JSON::PP}; unless ($json_loaded) { fatal(<<"EOM"); -Error loading the Perl module JSON::Any needed for cloudflare update. +Error loading the Perl module JSON::PP needed for cloudflare update. EOM } - import JSON::Any; + import JSON::PP (qw/decode_json/); } ###################################################################### ## geturl @@ -1941,6 +1979,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); @@ -1981,7 +2022,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"; @@ -1990,7 +2031,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; @@ -1998,8 +2042,8 @@ sub geturl { # local $^W = 0; $0 = sprintf("%s - connecting to %s port %s", $program, $peer, $port); if (! opt('exec')) { - debug("skipped network connection"); - verbose("SENDING:", "%s", $request); + debug("skipped network connection"); + verbose("SENDING:", "%s", $request); } elsif ($use_ssl) { $sd = IO::Socket::SSL->new( PeerAddr => $peer, @@ -2009,6 +2053,16 @@ sub geturl { Timeout => opt('timeout'), ); defined $sd or warning("cannot connect to $peer:$port socket: $@ " . IO::Socket::SSL::errstr()); + } elsif ($globals{'ipv6'}) { + load_ipv6_support; + $sd = IO::Socket::INET6->new( + PeerAddr => $peer, + PeerPort => $port, + Proto => 'tcp', + MultiHomed => 1, + Timeout => opt('timeout'), + ); + defined $sd or warning("cannot connect to $peer:$port socket: $@"); } else { $sd = IO::Socket::INET->new( PeerAddr => $peer, @@ -2151,64 +2205,69 @@ sub get_ip { } } elsif (($use eq 'cisco')) { - # Stuff added to support Cisco router ip http daemon - # User fw-login should only have level 1 access to prevent - # password theft. This is pretty harmless. - my $queryif = opt('if', $h); - $skip = opt('fw-skip', $h) || ''; + # Stuff added to support Cisco router ip http daemon + # User fw-login should only have level 1 access to prevent + # password theft. This is pretty harmless. + my $queryif = opt('if', $h); + $skip = opt('fw-skip', $h) || ''; - # Convert slashes to protected value "\/" - $queryif =~ s%\/%\\\/%g; + # Convert slashes to protected value "\/" + $queryif =~ s%\/%\\\/%g; - # Protect special HTML characters (like '?') - $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge; + # Protect special HTML characters (like '?') + $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge; - $url = "http://".opt('fw', $h)."/level/1/exec/show/ip/interface/brief/${queryif}/CR"; - $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; - $arg = $url; + $url = "http://".opt('fw', $h)."/level/1/exec/show/ip/interface/brief/${queryif}/CR"; + $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; + $arg = $url; } elsif (($use eq 'cisco-asa')) { - # Stuff added to support Cisco ASA ip https daemon - # User fw-login should only have level 1 access to prevent - # password theft. This is pretty harmless. - my $queryif = opt('if', $h); - $skip = opt('fw-skip', $h) || ''; + # Stuff added to support Cisco ASA ip https daemon + # User fw-login should only have level 1 access to prevent + # password theft. This is pretty harmless. + my $queryif = opt('if', $h); + $skip = opt('fw-skip', $h) || ''; - # Convert slashes to protected value "\/" - $queryif =~ s%\/%\\\/%g; + # Convert slashes to protected value "\/" + $queryif =~ s%\/%\\\/%g; - # Protect special HTML characters (like '?') - $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge; + # Protect special HTML characters (like '?') + $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge; - $url = "https://".opt('fw', $h)."/exec/show%20interface%20${queryif}"; - $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; - $arg = $url; + $url = "https://".opt('fw', $h)."/exec/show%20interface%20${queryif}"; + $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; + $arg = $url; } else { - $url = opt('fw', $h) || ''; - $skip = opt('fw-skip', $h) || ''; + $url = opt('fw', $h) || ''; + $skip = opt('fw-skip', $h) || ''; - if (exists $builtinfw{$use}) { - $skip = $builtinfw{$use}->{'skip'} unless $skip; - $url = "http://${url}" . $builtinfw{$use}->{'url'} unless $url =~ /\//; - } - $arg = $url; + if (exists $builtinfw{$use}) { + $skip = $builtinfw{$use}->{'skip'} unless $skip; + $url = "http://${url}" . $builtinfw{$use}->{'url'} unless $url =~ /\//; + } + $arg = $url; - if ($url) { - $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; + if ($url) { + $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; + } } - } - if (!defined $reply) { - $reply = ''; + if (!defined $reply) { + $reply = ''; } if ($skip) { - $skip =~ s/ /\\s/is; - $reply =~ s/^.*?${skip}//is; + $skip =~ s/ /\\s/is; + $reply =~ s/^.*?${skip}//is; } if ($reply =~ /^.*?\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b.*/is) { - $ip = $1; - $ip = un_zero_pad($ip); - $ip = filter_local($ip) if opt('fw-banlocal', $h); + $ip = $1; + $ip = un_zero_pad($ip); + $ip = filter_local($ip) if opt('fw-banlocal', $h); + } elsif ( $ip = ipv6_match($reply) ) { + $ip = un_zero_pad($ip); + $ip = filter_local($ip) if opt('fw-banlocal', $h); + } else { + warning("found neither ipv4 nor ipv6 address"); } if (($use ne 'ip') && (define($ip,'') eq '0.0.0.0')) { $ip = undef; @@ -2218,6 +2277,34 @@ sub get_ip { return $ip; } +###################################################################### +## ipv6_match determine ipv6 address from given string and return them +###################################################################### +sub ipv6_match { + my $content = shift; + my $omits; + my $ip = ""; + my $linenumbers = 0; + + my @values = split('\n', $content); + foreach my $val (@values) { + next unless $val =~ /((:{0,2}[A-F0-9]{1,4}){0,7}:{1,2}[A-F0-9]{1,4})/ai; # invalid char + my $parsed = $1; + + # check for at least 7 colons + my $count_colon = () = $parsed =~ /:/g; + if ($count_colon != 7) { + # or one double colon + my $count_double_colon = () = $parsed =~ /::/g; + if ($count_double_colon != 1) { + next + } + } + return $parsed; + } + return; +} + ###################################################################### ## group_hosts_by ###################################################################### @@ -3718,7 +3805,10 @@ EoEXAMPLE ## ###################################################################### sub nic_freedns_update { + + debug("\nnic_freedns_update -------------------"); + ## First get the list of updatable hosts my $url; $url = "http://$config{$_[0]}{'server'}/api/?action=getdyndns&sha=".&sha1_hex("$config{$_[0]}{'login'}|$config{$_[0]}{'password'}"); @@ -3744,40 +3834,34 @@ sub nic_freedns_update { info("setting IP address to %s for %s", $ip, $h); verbose("UPDATE:","updating %s", $h); - if($ip eq $freedns_hosts{$h}->[1]) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; - $config{$h}{'status'} = 'good'; - success("update not necessary %s: good: IP address already set to %s", $h, $ip); - } else { - my $reply = geturl(opt('proxy'), $freedns_hosts{$h}->[2]); - if (!defined($reply) || !$reply) { - failed("updating %s: Could not connect to %s.", $h, $freedns_hosts{$h}->[2]); - last; - } - if(!header_ok($h, $reply)) { - $config{$h}{'status'} = 'failed'; - last; - } + if($ip eq $freedns_hosts{$h}->[1]) { + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; + $config{$h}{'status'} = 'good'; + success("update not necessary %s: good: IP address already set to %s", $h, $ip); + } else { + my $reply = geturl(opt('proxy'), $freedns_hosts{$h}->[2]); + if (!defined($reply) || !$reply) { + failed("updating %s: Could not connect to %s.", $h, $freedns_hosts{$h}->[2]); + last; + } + if(!header_ok($h, $reply)) { + $config{$h}{'status'} = 'failed'; + last; + } - if ($reply =~ /Updated.*$h.*to.*$ip/) { - $config{$h}{'ip'} = $ip; - $config{$h}{'mtime'} = $now; - $config{$h}{'status'} = 'good'; - success("updating %s: good: IP address set to %s", $h, $ip); - } elsif ($reply =~ /Address (\d+\.\d+\.\d+\.\d+) has not changed/) { - $ip = $1; - $config{$h}{'mtime'} = $now; - $config{$h}{'status'} = 'good'; - $config{$h}{'ip'} = $ip; - success("updating %s: good: IP address %s has not changed", $h, $ip); - } else { - $config{$h}{'status'} = 'failed'; - warning("SENT: %s", $freedns_hosts{$h}->[2]) unless opt('verbose'); - warning("REPLIED: %s", $reply); - failed("updating %s: Invalid reply.", $h); - } - } + if($reply =~ /Updated.*$h.*to.*$ip/) { + $config{$h}{'ip'} = $ip; + $config{$h}{'mtime'} = $now; + $config{$h}{'status'} = 'good'; + success("updating %s: good: IP address set to %s", $h, $ip); + } else { + $config{$h}{'status'} = 'failed'; + warning("SENT: %s", $freedns_hosts{$h}->[2]) unless opt('verbose'); + warning("REPLIED: %s", $reply); + failed("updating %s: Invalid reply.", $h); + } + } } } @@ -4039,6 +4123,10 @@ Configuration variables applicable to the 'nsupdate' protocol are: zone=dyn.example.com ## forward zone that is to be updated ttl=600 ## time to live of the record; ## defaults to 600 seconds + tcp=off|on ## nsupdate uses UDP by default, and switches to + ## TCP if the update is too large to fit in a + ## UDP datagram; this setting forces TCP; + ## defaults to off login=/usr/bin/nsupdate ## path and name of nsupdate binary; ## defaults to '/usr/bin/nsupdate' ## fully qualified hostname to update @@ -4075,6 +4163,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); @@ -4087,14 +4181,15 @@ zone $zone. EoINSTR1 foreach (@hosts) { $instructions .= <jsonToObj($reply); - if ($response->{result} eq 'error') { - failed ("%s", $response->{msg}); + my $response = eval {decode_json($reply)}; + if (!defined $response || !defined $response->{result}) { + failed ("invalid json or result."); 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); + 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 = eval {decode_json($reply)}; + if (!defined $response || !defined $response->{result}) { + failed ("invalid json or result."); + 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; @@ -4223,9 +4340,9 @@ sub nic_cloudflare_update { # Strip header $reply =~ s/^.*?\n\n//s; - $response = JSON::Any->jsonToObj($reply); - if ($response->{result} eq 'error') { - failed ("%s", $response->{msg}); + $response = eval {decode_json($reply)}; + if (!defined $response || !defined $response->{result}) { + failed ("invalid json or result."); } else { success ("%s -- Updated Successfully to %s", $domain, $ip); @@ -4319,6 +4436,80 @@ sub nic_duckdns_update { } } +###################################################################### +## nic_freemyip_examples +###################################################################### +sub nic_freemyip_examples { + return <' \ + http://169.254.1.1:49000/upnp/control/WANCommonIFC1 | \ + sed -n -e 's#^.*\(.*\).*$#\1#p' From 1352b8608a7e9adf43c71e7be8812d10b05d96ca Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Sun, 12 Mar 2017 14:13:19 +0900 Subject: [PATCH 4/5] Added support for ZXHN F660A --- ddclient | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/ddclient b/ddclient index f0f8754..560d1f1 100755 --- a/ddclient +++ b/ddclient @@ -296,6 +296,7 @@ my %ip_strategies = ( 'cmd' => ": obtain IP from the -cmd {external-command}", 'cisco' => ": obtain IP from Cisco FW at the -fw {address}", 'cisco-asa' => ": obtain IP from Cisco ASA at the -fw {address}", + 'f660a' => ": obtain IP from ZTE F660A at the -fw {address}", map { $_ => sprintf ": obtain IP from %s at the -fw {address}", $builtinfw{$_}->{'name'} } keys %builtinfw, ); sub ip_strategies_usage { @@ -2238,6 +2239,44 @@ sub get_ip { $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; $arg = $url; + } elsif ($use eq 'f660a') { + use WWW::Mechanize; + use Digest::MD5 qw(md5_hex); + + my $token; + my $random = '10000000'; + my $mech = WWW::Mechanize->new(autocheck => 1); + + $url = 'http://192.168.1.1'; + $skip = 'TextIPAddress0'; + $mech->get($url); + $reply = $mech->content(); + if ($reply =~ /(getObj\("Frm_Logintoken"\)\.value = ")(\d+)/) { + $token = $2; + } + + $mech->submit_form( + form_name => 'fLogin', + fields => { + Frm_Logintoken => $token, + UserRandomNum => $random, + Username => opt('fw-login', $h), + Password => md5_hex(opt('fw-password', $h) . $random) + } + ); + if (!$mech->success()) { + return; + } + + $mech->get($url . '/getpage.gch?pid=1002&nextpage=IPv46_status_wan_if_t.gch'); + $reply = $mech->content(); + + $mech->submit_form( + form_name => 'flogout', + fields => { + logout => '1' + } + ); } else { $url = opt('fw', $h) || ''; $skip = opt('fw-skip', $h) || ''; From 1a8d04548f3ea129bc1ce1df5e19609865ce9e4b Mon Sep 17 00:00:00 2001 From: epgdatacapbon Date: Tue, 27 Mar 2018 09:24:03 +0900 Subject: [PATCH 5/5] Supports the latest firmware of ZXHN F660A --- ddclient | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddclient b/ddclient index 560d1f1..ddea423 100755 --- a/ddclient +++ b/ddclient @@ -2248,7 +2248,7 @@ sub get_ip { my $mech = WWW::Mechanize->new(autocheck => 1); $url = 'http://192.168.1.1'; - $skip = 'TextIPAddress0'; + $skip = 'TestContent0'; $mech->get($url); $reply = $mech->content(); if ($reply =~ /(getObj\("Frm_Logintoken"\)\.value = ")(\d+)/) {