Merge branch 'github'

This commit is contained in:
wimpunk 2016-01-26 19:46:24 +01:00
commit 747620cf66
3 changed files with 154 additions and 69 deletions

View file

@ -42,8 +42,9 @@ REQUIREMENTS:
- one or more accounts from one of the dynamic DNS services - one or more accounts from one of the dynamic DNS services
- Perl 5.014 or later - Perl 5.014 or later
(you need the IO::Socket::SSL perl library for ssl-support (you need the IO::Socket::SSL perl library for ssl-support,
and JSON::Any perl library for JSON support) JSON::Any perl library for JSON support and
IO::Socket:INET6 perl library for ipv6-support)
- Linux or probably any common Unix system - Linux or probably any common Unix system

206
ddclient
View file

@ -345,12 +345,12 @@ my %variables = (
'retry' => setv(T_BOOL, 0, 0, 0, 0, undef), 'retry' => setv(T_BOOL, 0, 0, 0, 0, undef),
'force' => 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), '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), 'syslog' => setv(T_BOOL, 0, 0, 1, 0, undef),
'facility' => setv(T_STRING,0, 0, 1, 'daemon', undef), 'facility' => setv(T_STRING,0, 0, 1, 'daemon', undef),
'priority' => setv(T_STRING,0, 0, 1, 'notice', undef), 'priority' => setv(T_STRING,0, 0, 1, 'notice', undef),
'mail' => setv(T_EMAIL, 0, 0, 1, '', undef), 'mail' => setv(T_EMAIL, 0, 0, 1, '', undef),
'mail-failure' => 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), 'exec' => setv(T_BOOL, 0, 0, 1, 1, undef),
'debug' => setv(T_BOOL, 0, 0, 1, 0, undef), 'debug' => setv(T_BOOL, 0, 0, 1, 0, undef),
@ -380,7 +380,7 @@ my %variables = (
'fw-password' => setv(T_PASSWD,0, 0, 1, '', undef), 'fw-password' => setv(T_PASSWD,0, 0, 1, '', undef),
'cmd' => setv(T_PROG, 0, 0, 1, '', undef), 'cmd' => setv(T_PROG, 0, 0, 1, '', undef),
'cmd-skip' => setv(T_STRING,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), 'ip' => setv(T_IP, 0, 1, 0, undef, undef),
'wtime' => setv(T_DELAY, 0, 1, 1, 0, interval('30s')), 'wtime' => setv(T_DELAY, 0, 1, 1, 0, interval('30s')),
'mtime' => setv(T_NUMBER, 0, 1, 0, 0, undef), 'mtime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
@ -437,6 +437,7 @@ my %variables = (
'nsupdate-common-defaults' => { 'nsupdate-common-defaults' => {
'ttl' => setv(T_NUMBER, 0, 1, 0, 600, undef), 'ttl' => setv(T_NUMBER, 0, 1, 0, 600, undef),
'zone' => setv(T_STRING, 1, 1, 1, '', undef), 'zone' => setv(T_STRING, 1, 1, 1, '', undef),
'tcp' => setv(T_BOOL, 0, 1, 1, 0, undef),
}, },
'cloudflare-common-defaults' => { '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),
@ -681,7 +682,7 @@ my @opt = (
"usage: ${program} [options]", "usage: ${program} [options]",
"options are:", "options are:",
[ "daemon", "=s", "-daemon delay : run as a daemon, specify delay as an interval." ], [ "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" ], [ "proxy", "=s", "-proxy host : use 'host' as the HTTP proxy" ],
[ "server", "=s", "-server host : update DNS information on 'host'" ], [ "server", "=s", "-server host : update DNS information on 'host'" ],
[ "protocol", "=s", "-protocol type : update protocol used" ], [ "protocol", "=s", "-protocol type : update protocol used" ],
@ -729,6 +730,7 @@ my @opt = (
[ "debug", "!", "-{no}debug : print {no} debugging information" ], [ "debug", "!", "-{no}debug : print {no} debugging information" ],
[ "verbose", "!", "-{no}verbose : print {no} verbose information" ], [ "verbose", "!", "-{no}verbose : print {no} verbose information" ],
[ "quiet", "!", "-{no}quiet : print {no} messages for unnecessary updates" ], [ "quiet", "!", "-{no}quiet : print {no} messages for unnecessary updates" ],
[ "ipv6", "!", "-{no}ipv6 : use ipv6" ],
[ "help", "", "-help : this message" ], [ "help", "", "-help : this message" ],
[ "postscript", "", "-postscript : script to run after updating ddclient, has new IP as param" ], [ "postscript", "", "-postscript : script to run after updating ddclient, has new IP as param" ],
@ -903,8 +905,10 @@ sub update_nics {
next; next;
} }
if ($ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) { if ($ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
warning("malformed IP address (%s)", $ip); if( !ipv6_match($ip) ) {
next; warning("malformed IP address (%s)", $ip);
next;
}
} }
$iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd} = $ip; $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd} = $ip;
} }
@ -1046,7 +1050,7 @@ sub parse_assignment {
my ($c, $name, $value); my ($c, $name, $value);
my ($escape, $quote) = (0, ''); 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, ''); ($name, $rest, $value) = ($1, $2, '');
while (length($c = substr($rest,0,1))) { while (length($c = substr($rest,0,1))) {
@ -1159,7 +1163,7 @@ sub _read_config {
## verify that keywords are valid...and check the value ## verify that keywords are valid...and check the value
foreach my $k (keys %locals) { 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}) { if (!exists $variables{'merged'}{$k}) {
warning("unrecognized keyword '%s' (ignored)", $k); warning("unrecognized keyword '%s' (ignored)", $k);
delete $locals{$k}; delete $locals{$k};
@ -1256,14 +1260,14 @@ sub init_config {
## and those in -options=... ## and those in -options=...
if (exists $options{'host'}) { if (exists $options{'host'}) {
foreach my $h (split_by_comma($options{'host'})) { foreach my $h (split_by_comma($options{'host'})) {
push @hosts, $h; push @hosts, $h;
} }
delete $options{'host'}; delete $options{'host'};
} }
## merge options into host definitions or globals ## merge options into host definitions or globals
if (@hosts) { if (@hosts) {
foreach my $h (@hosts) { foreach my $h (@hosts) {
$config{$h} = merge(\%options, $config{$h}); $config{$h} = merge(\%options, $config{$h});
} }
$opt{'host'} = join(',', @hosts); $opt{'host'} = join(',', @hosts);
} else { } else {
@ -1273,14 +1277,14 @@ sub init_config {
## override global options with those on the command-line. ## override global options with those on the command-line.
foreach my $o (keys %opt) { foreach my $o (keys %opt) {
if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) { if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) {
$globals{$o} = $opt{$o}; $globals{$o} = $opt{$o};
} }
} }
## sanity check ## sanity check
if (defined $opt{'host'} && defined $opt{'retry'}) { 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) ## determine hosts to update (those on the cmd-line, config-file, or failed cached)
@ -1310,14 +1314,14 @@ sub init_config {
## make sure config entries have all defaults and they meet minimums ## make sure config entries have all defaults and they meet minimums
## first the globals... ## first the globals...
foreach my $k (keys %globals) { foreach my $k (keys %globals) {
my $def = $variables{'merged'}{$k}; my $def = $variables{'merged'}{$k};
my $ovalue = define($globals{$k}, $def->{'default'}); my $ovalue = define($globals{$k}, $def->{'default'});
my $value = check_value($ovalue, $def); my $value = check_value($ovalue, $def);
if ($def->{'required'} && !defined $value) { if ($def->{'required'} && !defined $value) {
$value = default($k); $value = default($k);
warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value); warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value);
} }
$globals{$k} = $value; $globals{$k} = $value;
} }
## now the host definitions... ## now the host definitions...
@ -1850,7 +1854,9 @@ sub check_value {
# return undef if $value =~ /:/; # return undef if $value =~ /:/;
} elsif ($type eq T_IP) { } 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; return $value;
} }
@ -1889,6 +1895,24 @@ EOM
import IO::Socket::SSL; import IO::Socket::SSL;
{ no warnings; $IO::Socket::SSL::DEBUG = 0; } { 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 ## load_sha1_support
###################################################################### ######################################################################
@ -1984,8 +2008,8 @@ sub geturl {
# local $^W = 0; # local $^W = 0;
$0 = sprintf("%s - connecting to %s port %s", $program, $peer, $port); $0 = sprintf("%s - connecting to %s port %s", $program, $peer, $port);
if (! opt('exec')) { if (! opt('exec')) {
debug("skipped network connection"); debug("skipped network connection");
verbose("SENDING:", "%s", $request); verbose("SENDING:", "%s", $request);
} elsif ($use_ssl) { } elsif ($use_ssl) {
$sd = IO::Socket::SSL->new( $sd = IO::Socket::SSL->new(
PeerAddr => $peer, PeerAddr => $peer,
@ -1995,6 +2019,16 @@ sub geturl {
Timeout => opt('timeout'), Timeout => opt('timeout'),
); );
defined $sd or warning("cannot connect to $peer:$port socket: $@ " . IO::Socket::SSL::errstr()); 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 { } else {
$sd = IO::Socket::INET->new( $sd = IO::Socket::INET->new(
PeerAddr => $peer, PeerAddr => $peer,
@ -2137,64 +2171,69 @@ sub get_ip {
} }
} elsif (($use eq 'cisco')) { } elsif (($use eq 'cisco')) {
# Stuff added to support Cisco router ip http daemon # Stuff added to support Cisco router ip http daemon
# User fw-login should only have level 1 access to prevent # User fw-login should only have level 1 access to prevent
# password theft. This is pretty harmless. # password theft. This is pretty harmless.
my $queryif = opt('if', $h); my $queryif = opt('if', $h);
$skip = opt('fw-skip', $h) || ''; $skip = opt('fw-skip', $h) || '';
# Convert slashes to protected value "\/" # Convert slashes to protected value "\/"
$queryif =~ s%\/%\\\/%g; $queryif =~ s%\/%\\\/%g;
# Protect special HTML characters (like '?') # Protect special HTML characters (like '?')
$queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge; $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge;
$url = "http://".opt('fw', $h)."/level/1/exec/show/ip/interface/brief/${queryif}/CR"; $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)) || ''; $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || '';
$arg = $url; $arg = $url;
} elsif (($use eq 'cisco-asa')) { } elsif (($use eq 'cisco-asa')) {
# Stuff added to support Cisco ASA ip https daemon # Stuff added to support Cisco ASA ip https daemon
# User fw-login should only have level 1 access to prevent # User fw-login should only have level 1 access to prevent
# password theft. This is pretty harmless. # password theft. This is pretty harmless.
my $queryif = opt('if', $h); my $queryif = opt('if', $h);
$skip = opt('fw-skip', $h) || ''; $skip = opt('fw-skip', $h) || '';
# Convert slashes to protected value "\/" # Convert slashes to protected value "\/"
$queryif =~ s%\/%\\\/%g; $queryif =~ s%\/%\\\/%g;
# Protect special HTML characters (like '?') # Protect special HTML characters (like '?')
$queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge; $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge;
$url = "https://".opt('fw', $h)."/exec/show%20interface%20${queryif}"; $url = "https://".opt('fw', $h)."/exec/show%20interface%20${queryif}";
$reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || '';
$arg = $url; $arg = $url;
} else { } else {
$url = opt('fw', $h) || ''; $url = opt('fw', $h) || '';
$skip = opt('fw-skip', $h) || ''; $skip = opt('fw-skip', $h) || '';
if (exists $builtinfw{$use}) { if (exists $builtinfw{$use}) {
$skip = $builtinfw{$use}->{'skip'} unless $skip; $skip = $builtinfw{$use}->{'skip'} unless $skip;
$url = "http://${url}" . $builtinfw{$use}->{'url'} unless $url =~ /\//; $url = "http://${url}" . $builtinfw{$use}->{'url'} unless $url =~ /\//;
} }
$arg = $url; $arg = $url;
if ($url) { if ($url) {
$reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || ''; $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || '';
}
} }
} if (!defined $reply) {
if (!defined $reply) { $reply = '';
$reply = '';
} }
if ($skip) { if ($skip) {
$skip =~ s/ /\\s/is; $skip =~ s/ /\\s/is;
$reply =~ s/^.*?${skip}//is; $reply =~ s/^.*?${skip}//is;
} }
if ($reply =~ /^.*?\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b.*/is) { if ($reply =~ /^.*?\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b.*/is) {
$ip = $1; $ip = $1;
$ip = un_zero_pad($ip); $ip = un_zero_pad($ip);
$ip = filter_local($ip) if opt('fw-banlocal', $h); $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')) { if (($use ne 'ip') && (define($ip,'') eq '0.0.0.0')) {
$ip = undef; $ip = undef;
@ -2204,6 +2243,34 @@ sub get_ip {
return $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 ## group_hosts_by
###################################################################### ######################################################################
@ -4022,6 +4089,10 @@ Configuration variables applicable to the 'nsupdate' protocol are:
zone=dyn.example.com ## forward zone that is to be updated zone=dyn.example.com ## forward zone that is to be updated
ttl=600 ## time to live of the record; ttl=600 ## time to live of the record;
## defaults to 600 seconds ## 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; login=/usr/bin/nsupdate ## path and name of nsupdate binary;
## defaults to '/usr/bin/nsupdate' ## defaults to '/usr/bin/nsupdate'
<hostname> ## fully qualified hostname to update <hostname> ## fully qualified hostname to update
@ -4078,6 +4149,7 @@ EoINSTR2
send send
EoINSTR3 EoINSTR3
my $command = "$binary -k $keyfile"; my $command = "$binary -k $keyfile";
$command .= " -v" if ynu($config{$h}{'tcp'}, 1, 0, 0);
$command .= " -d" if (opt('debug')); $command .= " -d" if (opt('debug'));
verbose("UPDATE:", "nsupdate command is: %s", $command); verbose("UPDATE:", "nsupdate command is: %s", $command);
verbose("UPDATE:", "nsupdate instructions are:\n%s", $instructions); verbose("UPDATE:", "nsupdate instructions are:\n%s", $instructions);

View file

@ -221,3 +221,15 @@ ssl=yes # use ssl-support. Works with
# password=my-auto-generated-password # password=my-auto-generated-password
# protocol=duckdns hostwithoutduckdnsorg # protocol=duckdns hostwithoutduckdnsorg
##
## MyOnlinePortal (http://myonlineportal.net)
##
# protocol=dyndns2
# ssl=yes
# # ipv6=yes # optional
# use=web, web=myonlineportal.net/checkip
# # use=if, if=eth0 # alternative to use=web
# # if-skip=Scope:Link # alternative to use=web
# login=your-myonlineportal-username
# password=your-myonlineportal-password
# domain.myonlineportal.net