File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / dnsmasq / contrib / dynamic-dnsmasq / dynamic-dnsmasq.pl
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Jul 29 19:37:40 2013 UTC (11 years, 1 month ago) by misho
Branches: elwix, dnsmasq, MAIN
CVS tags: v8_2p1, v2_84, v2_76p1, v2_71, v2_66p0, v2_66, HEAD
dnsmasq

    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>