Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save klittlepage/7243505 to your computer and use it in GitHub Desktop.
Save klittlepage/7243505 to your computer and use it in GitHub Desktop.
A patch for ddclient 3.8.1 based off of http://blog.peter-r.co.uk/cloudflare-ddclient-patch.html to add support for Cloudflare. Note: this patch sets SSL_verify_mode => SSL_VERIFY_NONE() in the interest of getting things working on a freenas box short of tinkering with the distribution's certificate bundle (The Right Way). See line 132. A sample…
--- ddclient-3.8.1/ddclient 2011-07-11 15:04:21.000000000 -0600
+++ ddclient 2013-10-30 19:51:54.056629680 -0600
@@ -1,4 +1,3 @@
-#!/usr/bin/perl -w
#!/usr/local/bin/perl -w
######################################################################
# $Id: ddclient 130 2011-07-11 21:02:07Z wimpunk $
@@ -13,12 +12,18 @@
# Support for multiple IP numbers added by
# Astaro AG, Ingo Schwarze <ischwarze-OOs/4mkCeqbQT0dZR+AlfA@public.gmane.org> September 16, 2008
#
+# Modified to work with Cloudflare by Robert Ian Hawdon 2012-07-16: http://robertianhawdon.me.uk/
+#
+# Further modified to work with Cloudflare by Peter Roberts 2013-09-26: blog.peter-r.co.uk
+#
######################################################################
-require 5.004;
+require 5.014;
use strict;
use Getopt::Long;
use Sys::Hostname;
use IO::Socket;
+use POSIX 'setsid';
+use JSON::Any;
my ($VERSION) = q$Revision: 130 $ =~ /(\d+)/;
@@ -29,9 +34,9 @@
$program =~ s/d$//;
my $now = time;
my $hostname = hostname();
-my $etc = ($program =~ /test/i) ? './' : '/etc/ddclient/';
-my $cachedir = ($program =~ /test/i) ? './' : '/var/cache/ddclient/';
-my $savedir = ($program =~ /test/i) ? 'URL/' : '/tmp/';
+my $etc = ($program =~ /test/i) ? './' : '/usr/local/etc/';
+my $cachedir = ($program =~ /test/i) ? './' : '/var/tmp/';
+my $savedir = ($program =~ /test/i) ? 'URL/' : '/var/tmp/';
my $msgs = '';
my $last_msgs = '';
@@ -39,7 +44,7 @@
local $file = '';
local $lineno = '';
-$ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:";
+$ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/local/sbin:/bin:/usr/local/bin:";
sub T_ANY {'any'};
sub T_STRING {'string'};
@@ -429,6 +434,14 @@
'login' => setv(T_LOGIN, 0, 0, 0, 'unused', undef),
'client' => setv(T_STRING, 0, 1, 1, $program, undef),
},
+ 'cloudflare-common-defaults' => {
+ '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),
+ 'backupmx' => setv(T_BOOL, 0, 1, 1, 0, undef),
+ },
);
my %services = (
'dyndns1' => {
@@ -563,6 +576,17 @@
$variables{'service-common-defaults'},
),
},
+ 'cloudflare' => {
+ 'updateable' => undef,
+ 'update' => \&nic_cloudflare_update,
+ 'examples' => \&nic_cloudflare_examples,
+ 'variables' => merge(
+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'www.cloudflare.com', undef) },
+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
+ $variables{'cloudflare-common-defaults'},
+ $variables{'service-common-defaults'},
+ ),
+ },
);
$variables{'merged'} = merge($variables{'global-defaults'},
$variables{'service-common-defaults'},
@@ -668,6 +692,9 @@
;
} elsif (opt('daemon')) {
$SIG{'CHLD'} = 'IGNORE';
+ chdir '/';
+ open(STDIN, "</dev/null");
+ open(STDOUT, ">/dev/null");
my $pid = fork;
if ($pid < 0) {
print STDERR "${program}: can not fork ($!)\n";
@@ -675,10 +702,9 @@
} elsif ($pid) {
exit 0;
}
+ setsid;
$SIG{'CHLD'} = 'DEFAULT';
- open(STDOUT, ">/dev/null");
- open(STDERR, ">/dev/null");
- open(STDIN, "</dev/null");
+ open(STDERR, "&STDOUT");
}
# write out the pid file if we're daemon'ized
@@ -1463,17 +1489,17 @@
## execute the command.
local *FD;
if (! open(FD, $cmd)) {
- printf STDERR "$program: cannot execute command %s.\n", $cmd;
+ warning("$program: cannot execute command %s.\n", $cmd);
} elsif ($stdin && (! print FD "$stdin\n")) {
- printf STDERR "$program: failed writting to %s.\n", $cmd;
+ warning("$program: failed writing to %s.\n", $cmd);
close(FD);
} elsif (! close(FD)) {
- printf STDERR "$program: failed closing %s.($@)\n", $cmd;
+ warning("$program: failed closing %s.($@)\n", $cmd);
} elsif (opt('exec') && $?) {
- printf STDERR "$program: failed %s. ($@)\n", $cmd;
+ warning("$program: failed %s. ($@)\n", $cmd);
} else {
$ok = 1;
@@ -1861,6 +1887,7 @@
Proto => 'tcp',
MultiHomed => 1,
Timeout => opt('timeout'),
+ SSL_verify_mode => SSL_VERIFY_NONE(),
);
defined $sd or warning("cannot connect to $peer:$port socket: $@ " . IO::Socket::SSL::errstr());
} else {
@@ -3672,7 +3699,128 @@
}
######################################################################
-# vim: ai ts=4 sw=4 tw=78 :
+######################################################################
+## nic_cloudflare_examples
+##
+## written by Ian Pye
+##
+######################################################################
+sub nic_cloudflare_examples {
+ return <<EoEXAMPLE;
+o 'cloudflare'
+
+The 'cloudflare' protocol is used by DNS service offered by www.cloudflare.com.
+
+Configuration variables applicable to the 'cloudflare' protocol are:
+ protocol=cloudflare ##
+ server=fqdn.of.service ## defaults to www.cloudflare.com
+ zone=dns.zone ## your domain
+ login=service-login ## login name and password registered with the service
+ password=service-password ##
+ fully.qualified.host ## the host registered with the service.
+
+Example ${program}.conf file entries:
+ ## single host update
+ protocol=cloudflare,
+ zone=dns-zone, \\
+ login=my-cloudflare.com-login, \\
+ password=my-cloudflare.com-api-key \\
+ myhost.com
+
+ ## multiple host update to the custom DNS service
+ protocol=cloudflare,
+ zone=dns.zone \\
+ login=my-cloudflare.com-login, \\
+ password=my-cloudflare.com-api-key \\
+ my-toplevel-domain.com,my-other-domain.com
+EoEXAMPLE
+}
+######################################################################
+## nic_cloudflare_update
+######################################################################
+sub nic_cloudflare_update {
+ debug("\nnic_cloudflare_update -------------------");
+
+ ## group hosts with identical attributes together
+ my %groups = group_hosts_by([ @_ ], [ qw(ssh login password server wildcard mx backupmx) ]);
+
+ ## 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'};
+
+ # FQDNs
+ for my $domain (@hosts) {
+ my $hostname = $domain =~ s/\.$config{$key}{zone}$//r;
+ delete $config{$domain}{'wantip'};
+
+ info("setting IP address to %s for %s", $ip, $domain);
+ verbose("UPDATE:","updating %s", $domain);
+
+ # 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'};
+
+ 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);
+
+ # 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';
+ }
+ }
+}
__END__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment