summaryrefslogtreecommitdiffstats
path: root/contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl')
-rwxr-xr-xcontrib/dynamic-dnsmasq/dynamic-dnsmasq.pl249
1 files changed, 249 insertions, 0 deletions
diff --git a/contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl b/contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl
new file mode 100755
index 0000000..3c4a1f1
--- /dev/null
+++ b/contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl
@@ -0,0 +1,249 @@
+#!/usr/bin/perl
+# dynamic-dnsmasq.pl - update dnsmasq's internal dns entries dynamically
+# Copyright (C) 2004 Peter Willis
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# the purpose of this script is to be able to update dnsmasq's dns
+# records from a remote dynamic dns client.
+#
+# basic use of this script:
+# dynamic-dnsmasq.pl add testaccount 1234 testaccount.mydomain.com
+# dynamic-dnsmasq.pl listen &
+#
+# this script tries to emulate DynDNS.org's dynamic dns service, so
+# technically you should be able to use any DynDNS.org client to
+# update the records here. tested and confirmed to work with ddnsu
+# 1.3.1. just point the client's host to the IP of this machine,
+# port 9020, and include the hostname, user and pass, and it should
+# work.
+#
+# make sure "addn-hosts=/etc/dyndns-hosts" is in your /etc/dnsmasq.conf
+# file and "nopoll" is commented out.
+
+use strict;
+use IO::Socket;
+use MIME::Base64;
+use DB_File;
+use Fcntl;
+
+my $accountdb = "accounts.db";
+my $recordfile = "/etc/dyndns-hosts";
+my $dnsmasqpidfile = "/var/run/dnsmasq.pid"; # if this doesn't exist, will look for process in /proc
+my $listenaddress = "0.0.0.0";
+my $listenport = 9020;
+
+# no editing past this point should be necessary
+
+if ( @ARGV < 1 ) {
+ die "Usage: $0 ADD|DEL|LISTUSERS|WRITEHOSTSFILE|LISTEN\n";
+} elsif ( lc $ARGV[0] eq "add" ) {
+ die "Usage: $0 ADD USER PASS HOSTNAME\n" unless @ARGV == 4;
+ add_acct($ARGV[1], $ARGV[2], $ARGV[3]);
+} elsif ( lc $ARGV[0] eq "del" ) {
+ die "Usage: $0 DEL USER\n" unless @ARGV == 2;
+ print "Are you sure you want to delete user \"$ARGV[1]\"? [N/y] ";
+ my $resp = <STDIN>;
+ chomp $resp;
+ if ( lc substr($resp,0,1) eq "y" ) {
+ del_acct($ARGV[1]);
+ }
+} elsif ( lc $ARGV[0] eq "listusers" or lc $ARGV[0] eq "writehostsfile" ) {
+ my $X = tie my %h, "DB_File", $accountdb, O_RDWR|O_CREAT, 0600, $DB_HASH;
+ my $fh;
+ if ( lc $ARGV[0] eq "writehostsfile" ) {
+ open($fh, ">$recordfile") || die "Couldn't open recordfile \"$recordfile\": $!\n";
+ flock($fh, 2);
+ seek($fh, 0, 0);
+ truncate($fh, 0);
+ }
+ while ( my ($key, $val) = each %h ) {
+ my ($pass, $domain, $ip) = split("\t",$val);
+ if ( lc $ARGV[0] eq "listusers" ) {
+ print "user $key, hostname $domain, ip $ip\n";
+ } else {
+ if ( defined $ip ) {
+ print $fh "$ip\t$domain\n";
+ }
+ }
+ }
+ if ( lc $ARGV[0] eq "writehostsfile" ) {
+ flock($fh, 8);
+ close($fh);
+ dnsmasq_rescan_configs();
+ }
+ undef $X;
+ untie %h;
+} elsif ( lc $ARGV[0] eq "listen" ) {
+ listen_for_updates();
+}
+
+sub listen_for_updates {
+ my $sock = IO::Socket::INET->new(Listen => 5,
+ LocalAddr => $listenaddress, LocalPort => $listenport,
+ Proto => 'tcp', ReuseAddr => 1,
+ MultiHomed => 1) || die "Could not open listening socket: $!\n";
+ $SIG{'CHLD'} = 'IGNORE';
+ while ( my $client = $sock->accept() ) {
+ my $p = fork();
+ if ( $p != 0 ) {
+ next;
+ }
+ $SIG{'CHLD'} = 'DEFAULT';
+ my @headers;
+ my %cgi;
+ while ( <$client> ) {
+ s/(\r|\n)//g;
+ last if $_ eq "";
+ push @headers, $_;
+ }
+ foreach my $header (@headers) {
+ if ( $header =~ /^GET \/nic\/update\?([^\s].+) HTTP\/1\.[01]$/ ) {
+ foreach my $element (split('&', $1)) {
+ $cgi{(split '=', $element)[0]} = (split '=', $element)[1];
+ }
+ } elsif ( $header =~ /^Authorization: basic (.+)$/ ) {
+ unless ( defined $cgi{'hostname'} ) {
+ print_http_response($client, undef, "badsys");
+ exit(1);
+ }
+ if ( !exists $cgi{'myip'} ) {
+ $cgi{'myip'} = $client->peerhost();
+ }
+ my ($user,$pass) = split ":", MIME::Base64::decode($1);
+ if ( authorize($user, $pass, $cgi{'hostname'}, $cgi{'myip'}) == 0 ) {
+ print_http_response($client, $cgi{'myip'}, "good");
+ update_dns(\%cgi);
+ } else {
+ print_http_response($client, undef, "badauth");
+ exit(1);
+ }
+ last;
+ }
+ }
+ exit(0);
+ }
+ return(0);
+}
+
+sub add_acct {
+ my ($user, $pass, $hostname) = @_;
+ my $X = tie my %h, "DB_File", $accountdb, O_RDWR|O_CREAT, 0600, $DB_HASH;
+ $X->put($user, join("\t", ($pass, $hostname)));
+ undef $X;
+ untie %h;
+}
+
+sub del_acct {
+ my ($user, $pass, $hostname) = @_;
+ my $X = tie my %h, "DB_File", $accountdb, O_RDWR|O_CREAT, 0600, $DB_HASH;
+ $X->del($user);
+ undef $X;
+ untie %h;
+}
+
+
+sub authorize {
+ my $user = shift;
+ my $pass = shift;
+ my $hostname = shift;
+ my $ip = shift;;
+ my $X = tie my %h, "DB_File", $accountdb, O_RDWR|O_CREAT, 0600, $DB_HASH;
+ my ($spass, $shost) = split("\t", $h{$user});
+ if ( defined $h{$user} and ($spass eq $pass) and ($shost eq $hostname) ) {
+ $X->put($user, join("\t", $spass, $shost, $ip));
+ undef $X;
+ untie %h;
+ return(0);
+ }
+ undef $X;
+ untie %h;
+ return(1);
+}
+
+sub print_http_response {
+ my $sock = shift;
+ my $ip = shift;
+ my $response = shift;
+ print $sock "HTTP/1.0 200 OK\n";
+ my @tmp = split /\s+/, scalar gmtime();
+ print $sock "Date: $tmp[0], $tmp[2] $tmp[1] $tmp[4] $tmp[3] GMT\n";
+ print $sock "Server: Peter's Fake DynDNS.org Server/1.0\n";
+ print $sock "Content-Type: text/plain; charset=ISO-8859-1\n";
+ print $sock "Connection: close\n";
+ print $sock "Transfer-Encoding: chunked\n";
+ print $sock "\n";
+ #print $sock "12\n"; # this was part of the dyndns response but i'm not sure what it is
+ print $sock "$response", defined($ip)? " $ip" : "" . "\n";
+}
+
+sub update_dns {
+ my $hashref = shift;
+ my @records;
+ my $found = 0;
+ # update the addn-hosts file
+ open(FILE, "+<$recordfile") || die "Couldn't open recordfile \"$recordfile\": $!\n";
+ flock(FILE, 2);
+ while ( <FILE> ) {
+ if ( /^(\d+\.\d+\.\d+\.\d+)\s+$$hashref{'hostname'}\n$/si ) {
+ if ( $1 ne $$hashref{'myip'} ) {
+ push @records, "$$hashref{'myip'}\t$$hashref{'hostname'}\n";
+ $found = 1;
+ }
+ } else {
+ push @records, $_;
+ }
+ }
+ unless ( $found ) {
+ push @records, "$$hashref{'myip'}\t$$hashref{'hostname'}\n";
+ }
+ sysseek(FILE, 0, 0);
+ truncate(FILE, 0);
+ syswrite(FILE, join("", @records));
+ flock(FILE, 8);
+ close(FILE);
+ dnsmasq_rescan_configs();
+ return(0);
+}
+
+sub dnsmasq_rescan_configs {
+ # send the HUP signal to dnsmasq
+ if ( -r $dnsmasqpidfile ) {
+ open(PID,"<$dnsmasqpidfile") || die "Could not open PID file \"$dnsmasqpidfile\": $!\n";
+ my $pid = <PID>;
+ close(PID);
+ chomp $pid;
+ if ( kill(0, $pid) ) {
+ kill(1, $pid);
+ } else {
+ goto LOOKFORDNSMASQ;
+ }
+ } else {
+ LOOKFORDNSMASQ:
+ opendir(DIR,"/proc") || die "Couldn't opendir /proc: $!\n";
+ my @dirs = grep(/^\d+$/, readdir(DIR));
+ closedir(DIR);
+ foreach my $process (@dirs) {
+ if ( open(FILE,"</proc/$process/cmdline") ) {
+ my $cmdline = <FILE>;
+ close(FILE);
+ if ( (split(/\0/,$cmdline))[0] =~ /dnsmasq/ ) {
+ kill(1, $process);
+ }
+ }
+ }
+ }
+ return(0);
+}