Annotation of embedaddon/dnsmasq/contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl, revision 1.1
1.1 ! misho 1: #!/usr/bin/perl
! 2: # dynamic-dnsmasq.pl - update dnsmasq's internal dns entries dynamically
! 3: # Copyright (C) 2004 Peter Willis
! 4: #
! 5: # This program is free software; you can redistribute it and/or modify
! 6: # it under the terms of the GNU General Public License as published by
! 7: # the Free Software Foundation; either version 2 of the License, or
! 8: # (at your option) any later version.
! 9: #
! 10: # This program is distributed in the hope that it will be useful,
! 11: # but WITHOUT ANY WARRANTY; without even the implied warranty of
! 12: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! 13: # GNU General Public License for more details.
! 14: #
! 15: # You should have received a copy of the GNU General Public License
! 16: # along with this program; if not, write to the Free Software
! 17: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
! 18: #
! 19: # the purpose of this script is to be able to update dnsmasq's dns
! 20: # records from a remote dynamic dns client.
! 21: #
! 22: # basic use of this script:
! 23: # dynamic-dnsmasq.pl add testaccount 1234 testaccount.mydomain.com
! 24: # dynamic-dnsmasq.pl listen &
! 25: #
! 26: # this script tries to emulate DynDNS.org's dynamic dns service, so
! 27: # technically you should be able to use any DynDNS.org client to
! 28: # update the records here. tested and confirmed to work with ddnsu
! 29: # 1.3.1. just point the client's host to the IP of this machine,
! 30: # port 9020, and include the hostname, user and pass, and it should
! 31: # work.
! 32: #
! 33: # make sure "addn-hosts=/etc/dyndns-hosts" is in your /etc/dnsmasq.conf
! 34: # file and "nopoll" is commented out.
! 35:
! 36: use strict;
! 37: use IO::Socket;
! 38: use MIME::Base64;
! 39: use DB_File;
! 40: use Fcntl;
! 41:
! 42: my $accountdb = "accounts.db";
! 43: my $recordfile = "/etc/dyndns-hosts";
! 44: my $dnsmasqpidfile = "/var/run/dnsmasq.pid"; # if this doesn't exist, will look for process in /proc
! 45: my $listenaddress = "0.0.0.0";
! 46: my $listenport = 9020;
! 47:
! 48: # no editing past this point should be necessary
! 49:
! 50: if ( @ARGV < 1 ) {
! 51: die "Usage: $0 ADD|DEL|LISTUSERS|WRITEHOSTSFILE|LISTEN\n";
! 52: } elsif ( lc $ARGV[0] eq "add" ) {
! 53: die "Usage: $0 ADD USER PASS HOSTNAME\n" unless @ARGV == 4;
! 54: add_acct($ARGV[1], $ARGV[2], $ARGV[3]);
! 55: } elsif ( lc $ARGV[0] eq "del" ) {
! 56: die "Usage: $0 DEL USER\n" unless @ARGV == 2;
! 57: print "Are you sure you want to delete user \"$ARGV[1]\"? [N/y] ";
! 58: my $resp = <STDIN>;
! 59: chomp $resp;
! 60: if ( lc substr($resp,0,1) eq "y" ) {
! 61: del_acct($ARGV[1]);
! 62: }
! 63: } elsif ( lc $ARGV[0] eq "listusers" or lc $ARGV[0] eq "writehostsfile" ) {
! 64: my $X = tie my %h, "DB_File", $accountdb, O_RDWR|O_CREAT, 0600, $DB_HASH;
! 65: my $fh;
! 66: if ( lc $ARGV[0] eq "writehostsfile" ) {
! 67: open($fh, ">$recordfile") || die "Couldn't open recordfile \"$recordfile\": $!\n";
! 68: flock($fh, 2);
! 69: seek($fh, 0, 0);
! 70: truncate($fh, 0);
! 71: }
! 72: while ( my ($key, $val) = each %h ) {
! 73: my ($pass, $domain, $ip) = split("\t",$val);
! 74: if ( lc $ARGV[0] eq "listusers" ) {
! 75: print "user $key, hostname $domain, ip $ip\n";
! 76: } else {
! 77: if ( defined $ip ) {
! 78: print $fh "$ip\t$domain\n";
! 79: }
! 80: }
! 81: }
! 82: if ( lc $ARGV[0] eq "writehostsfile" ) {
! 83: flock($fh, 8);
! 84: close($fh);
! 85: dnsmasq_rescan_configs();
! 86: }
! 87: undef $X;
! 88: untie %h;
! 89: } elsif ( lc $ARGV[0] eq "listen" ) {
! 90: listen_for_updates();
! 91: }
! 92:
! 93: sub listen_for_updates {
! 94: my $sock = IO::Socket::INET->new(Listen => 5,
! 95: LocalAddr => $listenaddress, LocalPort => $listenport,
! 96: Proto => 'tcp', ReuseAddr => 1,
! 97: MultiHomed => 1) || die "Could not open listening socket: $!\n";
! 98: $SIG{'CHLD'} = 'IGNORE';
! 99: while ( my $client = $sock->accept() ) {
! 100: my $p = fork();
! 101: if ( $p != 0 ) {
! 102: next;
! 103: }
! 104: $SIG{'CHLD'} = 'DEFAULT';
! 105: my @headers;
! 106: my %cgi;
! 107: while ( <$client> ) {
! 108: s/(\r|\n)//g;
! 109: last if $_ eq "";
! 110: push @headers, $_;
! 111: }
! 112: foreach my $header (@headers) {
! 113: if ( $header =~ /^GET \/nic\/update\?([^\s].+) HTTP\/1\.[01]$/ ) {
! 114: foreach my $element (split('&', $1)) {
! 115: $cgi{(split '=', $element)[0]} = (split '=', $element)[1];
! 116: }
! 117: } elsif ( $header =~ /^Authorization: basic (.+)$/ ) {
! 118: unless ( defined $cgi{'hostname'} ) {
! 119: print_http_response($client, undef, "badsys");
! 120: exit(1);
! 121: }
! 122: if ( !exists $cgi{'myip'} ) {
! 123: $cgi{'myip'} = $client->peerhost();
! 124: }
! 125: my ($user,$pass) = split ":", MIME::Base64::decode($1);
! 126: if ( authorize($user, $pass, $cgi{'hostname'}, $cgi{'myip'}) == 0 ) {
! 127: print_http_response($client, $cgi{'myip'}, "good");
! 128: update_dns(\%cgi);
! 129: } else {
! 130: print_http_response($client, undef, "badauth");
! 131: exit(1);
! 132: }
! 133: last;
! 134: }
! 135: }
! 136: exit(0);
! 137: }
! 138: return(0);
! 139: }
! 140:
! 141: sub add_acct {
! 142: my ($user, $pass, $hostname) = @_;
! 143: my $X = tie my %h, "DB_File", $accountdb, O_RDWR|O_CREAT, 0600, $DB_HASH;
! 144: $X->put($user, join("\t", ($pass, $hostname)));
! 145: undef $X;
! 146: untie %h;
! 147: }
! 148:
! 149: sub del_acct {
! 150: my ($user, $pass, $hostname) = @_;
! 151: my $X = tie my %h, "DB_File", $accountdb, O_RDWR|O_CREAT, 0600, $DB_HASH;
! 152: $X->del($user);
! 153: undef $X;
! 154: untie %h;
! 155: }
! 156:
! 157:
! 158: sub authorize {
! 159: my $user = shift;
! 160: my $pass = shift;
! 161: my $hostname = shift;
! 162: my $ip = shift;;
! 163: my $X = tie my %h, "DB_File", $accountdb, O_RDWR|O_CREAT, 0600, $DB_HASH;
! 164: my ($spass, $shost) = split("\t", $h{$user});
! 165: if ( defined $h{$user} and ($spass eq $pass) and ($shost eq $hostname) ) {
! 166: $X->put($user, join("\t", $spass, $shost, $ip));
! 167: undef $X;
! 168: untie %h;
! 169: return(0);
! 170: }
! 171: undef $X;
! 172: untie %h;
! 173: return(1);
! 174: }
! 175:
! 176: sub print_http_response {
! 177: my $sock = shift;
! 178: my $ip = shift;
! 179: my $response = shift;
! 180: print $sock "HTTP/1.0 200 OK\n";
! 181: my @tmp = split /\s+/, scalar gmtime();
! 182: print $sock "Date: $tmp[0], $tmp[2] $tmp[1] $tmp[4] $tmp[3] GMT\n";
! 183: print $sock "Server: Peter's Fake DynDNS.org Server/1.0\n";
! 184: print $sock "Content-Type: text/plain; charset=ISO-8859-1\n";
! 185: print $sock "Connection: close\n";
! 186: print $sock "Transfer-Encoding: chunked\n";
! 187: print $sock "\n";
! 188: #print $sock "12\n"; # this was part of the dyndns response but i'm not sure what it is
! 189: print $sock "$response", defined($ip)? " $ip" : "" . "\n";
! 190: }
! 191:
! 192: sub update_dns {
! 193: my $hashref = shift;
! 194: my @records;
! 195: my $found = 0;
! 196: # update the addn-hosts file
! 197: open(FILE, "+<$recordfile") || die "Couldn't open recordfile \"$recordfile\": $!\n";
! 198: flock(FILE, 2);
! 199: while ( <FILE> ) {
! 200: if ( /^(\d+\.\d+\.\d+\.\d+)\s+$$hashref{'hostname'}\n$/si ) {
! 201: if ( $1 ne $$hashref{'myip'} ) {
! 202: push @records, "$$hashref{'myip'}\t$$hashref{'hostname'}\n";
! 203: $found = 1;
! 204: }
! 205: } else {
! 206: push @records, $_;
! 207: }
! 208: }
! 209: unless ( $found ) {
! 210: push @records, "$$hashref{'myip'}\t$$hashref{'hostname'}\n";
! 211: }
! 212: sysseek(FILE, 0, 0);
! 213: truncate(FILE, 0);
! 214: syswrite(FILE, join("", @records));
! 215: flock(FILE, 8);
! 216: close(FILE);
! 217: dnsmasq_rescan_configs();
! 218: return(0);
! 219: }
! 220:
! 221: sub dnsmasq_rescan_configs {
! 222: # send the HUP signal to dnsmasq
! 223: if ( -r $dnsmasqpidfile ) {
! 224: open(PID,"<$dnsmasqpidfile") || die "Could not open PID file \"$dnsmasqpidfile\": $!\n";
! 225: my $pid = <PID>;
! 226: close(PID);
! 227: chomp $pid;
! 228: if ( kill(0, $pid) ) {
! 229: kill(1, $pid);
! 230: } else {
! 231: goto LOOKFORDNSMASQ;
! 232: }
! 233: } else {
! 234: LOOKFORDNSMASQ:
! 235: opendir(DIR,"/proc") || die "Couldn't opendir /proc: $!\n";
! 236: my @dirs = grep(/^\d+$/, readdir(DIR));
! 237: closedir(DIR);
! 238: foreach my $process (@dirs) {
! 239: if ( open(FILE,"</proc/$process/cmdline") ) {
! 240: my $cmdline = <FILE>;
! 241: close(FILE);
! 242: if ( (split(/\0/,$cmdline))[0] =~ /dnsmasq/ ) {
! 243: kill(1, $process);
! 244: }
! 245: }
! 246: }
! 247: }
! 248: return(0);
! 249: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>