Annotation of embedaddon/dnsmasq/contrib/dynamic-dnsmasq/dynamic-dnsmasq.pl, revision 1.1.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>