diff options
Diffstat (limited to 'contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl')
-rwxr-xr-x | contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl | 249 |
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); +} |