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>