X-Git-Url: https://the.earth.li/gitweb/?a=blobdiff_plain;f=autodns.pl;h=ddb237c06155656a9d7ad3314b0464f9f165ad37;hb=764aa0aa77e52fffb8d632760489b3b8cc61db4e;hp=afc6c5bb8d732488430b63fba646e4a985fc57ea;hpb=31b363d4355c1a7017d683c64dc271d76b4e14d4;p=autodns.git diff --git a/autodns.pl b/autodns.pl index afc6c5b..ddb237c 100755 --- a/autodns.pl +++ b/autodns.pl @@ -1,57 +1,41 @@ #!/usr/bin/perl -Tw -# autodns 0.0.6 -# Copyright 1999-2003 Project Purple. Written by Jonathan McDowell +# autodns 0.0.8 +# Copyright 1999-2005 Project Purple. Written by Jonathan McDowell # See ACKNOWLEDGEMENTS file for full details of contributors. # http://www.earth.li/projectpurple/progs/autodns.html # Released under the GPL. # -# $Id: autodns.pl,v 1.2 2003/06/04 17:27:00 noodles Exp $ +# $Id: autodns.pl,v 1.15 2005/06/15 10:26:25 noodles Exp $ # use strict; -use IPC::Open3; +use Date::Parse; use Fcntl qw(:flock); +use File::Temp qw(tempfile); +use IPC::Open3; +use MIME::Parser; $ENV{'PATH'}="/usr/local/bin:/usr/bin:/bin:/usr/sbin"; -my ($from, $subject, $gpguser, $gpggood, $usersfile, $lockfile, $priv); -my ($user, $server, $inprocess, $delcount, $addcount, $reload_command); -my ($domain, @MAIL, @GPGERROR, @COMMANDS, %zones); -my ($me, $ccreply, $conffile, $domainlistroot, @cfgfiles, $VERSION); +my ($from, $subject, $gpguser, $gpggood, $priv); +my ($user, $server, $inprocess, $delcount, $addcount); +my ($domain, @MAIL, @GPGERROR, @COMMANDS, %zones, $VERSION); -$VERSION="0.0.6"; +use vars qw($me $ccreply $conffile $domainlistroot @cfgfiles $usersfile + $lockfile $reload_command $expiry); + +$VERSION="0.0.8"; # -# Local configuration here (until it gets moved to a config file). -# -# These are sort of suitable for a Debian setup. +# Load our config # - -# Who I should reply as. -$me="autodns\@earth.li"; - -# Who replies should be CCed to. -$ccreply="noodles\@earth.li"; - -# Where to look for zones we're already hosting. -@cfgfiles=("/etc/bind/named.conf", - "/etc/bind/named.secondary.conf"); - -# The file we should add/delete domains from. -$conffile="/etc/bind/named.secondary.conf"; - -# The file that contains details of the authorized users. -$usersfile="/etc/bind/autodns.users"; - -# Base file name to for list of users domains. -$domainlistroot="/etc/bind/domains."; - -# The lockfile we use to ensure we have exclusive access to the -# $domainlistroot$user files and $conffile. -$lockfile="/etc/bind/autodns.lck"; - -# The command to reload the nameserver domains list. -$reload_command="sudo ndc reconfig 2>&1"; +my $file = '/etc/bind/autodns.conf'; +unless (my $ret = do $file) { + warn "Couldn't parse $file\n" if $@; + warn "Couldn't do $file\n" unless defined $ret; + warn "Couldn't run $file\n" unless $ret; + die "Problem reading config file!\n"; +} ### ### There should be no need to edit anything below (unless you're not @@ -64,13 +48,13 @@ $reload_command="sudo ndc reconfig 2>&1"; # # Call with the name of a config file to read: # -# &getzones("/etc/named.conf"); +# getzones("/etc/named.conf"); # -sub getzones { - my ($namedfile) = @_; +sub getzones($) { + my $namedfile = shift; open (NAMEDCONF, "< $namedfile") or - &fatalerror("Can't open $namedfile"); + fatalerror("Can't open $namedfile"); while () { if (/^\s*zone\s*"([^"]+)"/) { @@ -86,10 +70,11 @@ sub getzones { # # These are: a-z, 0-9, - or . # -sub valid_domain { +sub valid_domain($) { my $domain = shift; $domain = lc $domain; - if ($domain =~ /^(?:[a-z0-9-]+\.)+[a-z]{2,4}$/) { + + if ($domain =~ /^(?:[a-z0-9-]+\.)+[a-z]{2,6}$/) { return 1; } elsif ($domain =~ /^(?:[0-9\/-]+\.)+in-addr.arpa$/) { return 1; @@ -104,7 +89,7 @@ sub valid_domain { # # fatalerror("I'm melting!"); # -sub fatalerror { +sub fatalerror($) { my $message = shift; print REPLY $message; @@ -127,14 +112,14 @@ sub fatalerror { # # Priviledge level is not currently used. # -# ($user, $priv, $server) = &getuserinfo("5B430367"); +# ($user, $priv, $server) = getuserinfo("5B430367"); # -sub getuserinfo { +sub getuserinfo($) { my $gpguser = shift; my ($user, $priviledge, $server); open (CONFIGFILE, "< $usersfile") or - &fatalerror("Couldn't open user configuration file."); + fatalerror("Couldn't open user configuration file."); foreach () { if (/^([^#.]+):$gpguser:(\d+):(.+)$/) { @@ -147,13 +132,13 @@ sub getuserinfo { if ($user !~ /^.+$/) { close(CONFIGFILE); - &fatalerror("Error in user configuration file: Can't get username.\n"); + fatalerror("Error in user configuration file: Can't get username.\n"); } if ($server !~ /^(\d{1,3}\.){3}\d{1,3}$/) { $server =~ s/\d\.]//g; close(CONFIGFILE); - &fatalerror("Error in user configuration file: Invalid primary server IP address ($server)\n"); + fatalerror("Error in user configuration file: Invalid primary server IP address ($server)\n"); exit; } } @@ -161,7 +146,7 @@ sub getuserinfo { close(CONFIGFILE); if ($user =~ /^$/) { - &fatalerror("User not found.\n"); + fatalerror("User not found.\n"); } return ($user, $priviledge, $server); @@ -205,34 +190,90 @@ print REPLY <output_to_core(1); # No temporary files +my $entity = $parser->parse_data(\@MAIL); + +# +# Make sure locale is set to C so we get messages in English as we expect. # $ENV{'LC_ALL'}="C"; -open3(\*GPGIN, \*GPGOUT, \*GPGERR, "gpg --batch"); -# Feed it the mail. -print GPGIN @MAIL; -close GPGIN; +if ($entity->parts) { + # MIME + + my ($got_sig, $got_text) = (0, 0); + my ($sig_name,$sig_fh,$text_name,$text_fh); + ($sig_fh, $sig_name) = tempfile(); + ($text_fh, $text_name) = tempfile(); + + foreach my $subent ($entity->parts) { + if ($subent->effective_type eq "text/plain") { + @COMMANDS = split /\n/,$subent->bodyhandle->as_string; + + my $str = $subent->as_string; + $str =~ s/=\n$//; + $str =~ s/\n/\r\n/g; + print $text_fh $str; + close($text_fh); + $got_text++; + } elsif ($subent->effective_type eq + "application/pgp-signature") { + print $sig_fh $subent->as_string; + close($sig_fh); + $got_sig++; + } + } + + if ($got_sig && $got_text) { + my $pid = open3(\*GPGIN, \*GPGOUT, \*GPGERR, + "gpg --batch --verify ". + $sig_name." ".$text_name); + + close GPGIN; -# And grab what it has to say. -@GPGERROR=; -@COMMANDS=; -close GPGERR; -close GPGOUT; + @GPGERROR=; + my @GPGOUTPUT=; + close GPGERR; + close GPGOUT; + waitpid $pid, 0; + + unlink($text_name); + unlink($sig_name); + } +} else { + # Clear text. + + my $pid = open3(\*GPGIN, \*GPGOUT, \*GPGERR, "gpg --batch --verify"); + + # Feed it the mail. + print GPGIN $entity->bodyhandle->as_string; + close GPGIN; + + # And grab what it has to say. + @GPGERROR=; + @COMMANDS=; + close GPGERR; + close GPGOUT; + waitpid $pid, 0; +} # Check who it's from and if the signature was a good one. $gpggood=1; +my $sigtime = 0; foreach (@GPGERROR) { chomp; - if (/Signature made.* (.*)$/) { - $gpguser=$1; + if (/Signature made (.*) using.*ID (.*)$/) { + $sigtime = str2time($1); + $gpguser=$2; } elsif (/error/) { $gpggood = 0; print REPLY "Some errors ocurred\n"; @@ -261,8 +302,23 @@ if ($gpggood) { exit; } +# Check if the signature is outside our acceptable range. +if (!defined($sigtime)) { + print REPLY "Couldn't parse signature time.\n"; + close REPLY; + exit; +} elsif ($sigtime > (time + $expiry)) { + print REPLY "Signature too far into the future.\n"; + close REPLY; + exit; +} elsif ($sigtime < (time - $expiry)) { + print REPLY "Signature too far into the past.\n"; + close REPLY; + exit; +} + # Now let's check if we know this person. -($user, $priv, $server) = &getuserinfo($gpguser); +($user, $priv, $server) = getuserinfo($gpguser); if (! defined($user) || ! $user) { print REPLY "Unknown user.\n"; @@ -275,14 +331,21 @@ print REPLY "Got user '$user'\n"; # Right. We know this is a valid user. Get a lock to ensure we have exclusive # access to the configs from here on in. open (LOCKFILE,">$lockfile") || - &fatalerror("Couldn't open lock file\n"); -&fatalerror("Couldn't get lock\n") unless(flock(LOCKFILE,LOCK_EX)); + fatalerror("Couldn't open lock file\n"); +fatalerror("Couldn't get lock\n") unless(flock(LOCKFILE,LOCK_EX)); # Ok, now we should figure out what domains we already know about. foreach my $cfgfile (@cfgfiles) { getzones($cfgfile); } +# Force existance of the $domainlistroot$user file +if (! -e $domainlistroot.$user) { + open (DOMAINLIST, ">>$domainlistroot$user") or + fatalerror("Couldn't create domains file.\n"); + close DOMAINLIST; +} + foreach (@COMMANDS) { # Remove trailing CRs and leading/trailing whitespace chomp; @@ -316,6 +379,9 @@ foreach (@COMMANDS) { print REPLY "Adding domain $domain\n"; $zones{$domain}=1; + my $df = $domain; + $df =~ tr,/,:,; + open (DOMAINSFILE, ">>$conffile"); print DOMAINSFILE " ### Domain added for '$user' @@ -323,14 +389,14 @@ foreach (@COMMANDS) { zone \"$domain\" { type slave; masters { $server; }; - file \"secondary/$user/$domain\"; + file \"secondary/$user/$df\"; allow-transfer { none; }; allow-query { any; }; };\n"; close DOMAINSFILE; open (DOMAINLIST, ">>$domainlistroot$user") or - &fatalerror("Couldn't open file.\n"); + fatalerror("Couldn't open file.\n"); print DOMAINLIST "$domain\n"; close DOMAINLIST; $addcount++; @@ -350,7 +416,7 @@ zone \"$domain\" { my (@newcfg,$found); open (DOMAINLIST, "<$domainlistroot$user") or - &fatalerror("Couldn't open file $domainlistroot$user for reading: $!.\n"); + fatalerror("Couldn't open file $domainlistroot$user for reading: $!.\n"); my @cfg = ; close(DOMAINLIST); @newcfg = grep { ! /^$domain$/ } @cfg; @@ -361,14 +427,14 @@ zone \"$domain\" { } open (DOMAINLIST, ">$domainlistroot$user") or - &fatalerror("Couldn't open file $domainlistroot$user for writing: $!.\n"); + fatalerror("Couldn't open file $domainlistroot$user for writing: $!.\n"); print DOMAINLIST @newcfg; close DOMAINLIST; $found=0; @newcfg=(); open (DOMAINSFILE, "<$conffile") or - &fatalerror("Couldn't open file $conffile for reading: $!\n"); + fatalerror("Couldn't open file $conffile for reading: $!\n"); { local $/ = ''; # eat whole paragraphs while () { @@ -390,7 +456,7 @@ zone \"$domain\" { } open (DOMAINSFILE, ">$conffile") or - &fatalerror("Couldn't open $conffile for writing: $!\n"); + fatalerror("Couldn't open $conffile for writing: $!\n"); print DOMAINSFILE @newcfg; close DOMAINSFILE; $delcount++; @@ -411,6 +477,17 @@ zone \"$domain\" { } else { print REPLY "Couldn't open $domainlistroot$user: $!\n"; } + } elsif ($inprocess && /^MASTER\s(.*)$/) { + if (($priv & 1) != 1) { + print REPLY "You're not authorised to use the MASTER ", + "command.\n"; + } elsif ($1 =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) { + $server = $1; + print REPLY "Set master IP address to $1\n"; + } else { + print REPLY "$1 doesn't look like a valid IPv4 ", + "address to me.\n"; + } } elsif ($inprocess && /^HELP$/) { print REPLY "In order to use the service, you will need to send GPG signed\n"; print REPLY "messages.\n\n"; @@ -424,6 +501,10 @@ zone \"$domain\" { print REPLY "LIST - show all the zones currently held by you.\n"; print REPLY "ADD - adds the domain for processing.\n"; print REPLY "DEL - removes the domain if you own it.\n"; + if (($priv & 1) == 1) { + print REPLY "MASTER - set the nameserver". + " we should slave off for subsequent ADD commands.\n"; + } } elsif ($inprocess) { print REPLY "Unknown command!\n"; }