aboutsummaryrefslogtreecommitdiff
path: root/vhostcname
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2014-04-07 15:45:39 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2014-04-07 15:45:39 +0300
commitb12e3a907ec855affb3b91ae924e54887b90f3a8 (patch)
tree76f5dccf212b879fe86f5b08f046d3a0e6009808 /vhostcname
parentf65436abbfe64f380ee7af600bf569333fccb3bb (diff)
downloaddnstools-b12e3a907ec855affb3b91ae924e54887b90f3a8.tar.gz
dnstools-b12e3a907ec855affb3b91ae924e54887b90f3a8.tar.bz2
New program: vhostcname
* vhostcname/GNUmakefile: New file. * vhostcname/MANIFEST: New file. * vhostcname/Makefile.PL: New file. * vhostcname/rc.vhostnames: New file. * vhostcname/vhostcname: New file. * axfr2acl/MANIFEST: New file. * .gitignore: Add backup files.
Diffstat (limited to 'vhostcname')
-rw-r--r--vhostcname/GNUmakefile2
-rw-r--r--vhostcname/MANIFEST4
-rw-r--r--vhostcname/Makefile.PL15
-rwxr-xr-xvhostcname/rc.vhostnames136
-rwxr-xr-xvhostcname/vhostcname517
5 files changed, 674 insertions, 0 deletions
diff --git a/vhostcname/GNUmakefile b/vhostcname/GNUmakefile
new file mode 100644
index 0000000..e61b3c3
--- /dev/null
+++ b/vhostcname/GNUmakefile
@@ -0,0 +1,2 @@
+include ../Make.vars
+include ../Make.rules
diff --git a/vhostcname/MANIFEST b/vhostcname/MANIFEST
new file mode 100644
index 0000000..90f74bb
--- /dev/null
+++ b/vhostcname/MANIFEST
@@ -0,0 +1,4 @@
+MANIFEST
+Makefile.PL
+vhostcname
+
diff --git a/vhostcname/Makefile.PL b/vhostcname/Makefile.PL
new file mode 100644
index 0000000..4db4085
--- /dev/null
+++ b/vhostcname/Makefile.PL
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use ExtUtils::MakeMaker;
+
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'vhostcname',
+ 'FIRST_MAKEFILE' => 'Makefile',
+ 'VERSION' => '1.00',
+ 'EXE_FILES' => [ 'vhostcname' ],
+ 'PREREQ_PM' => { 'Getopt::Long' => 2.34,
+ 'Net::DNS' => 0.66,
+ 'Pod::Usage' => 1.51,
+ 'Pod::Man' => 2.25 }
+);
diff --git a/vhostcname/rc.vhostnames b/vhostcname/rc.vhostnames
new file mode 100755
index 0000000..03bcce1
--- /dev/null
+++ b/vhostcname/rc.vhostnames
@@ -0,0 +1,136 @@
+#! /bin/sh
+# /etc/rc.d/rc.vhostnames: Update DynDNS from apache configs
+# Copyright 2013 Sergey Poznyakoff
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+CNAMESLIST=/var/run/nssetup
+HOSTNAME=nix.nxc.od.ua
+ZONE=nix.nxc.od.ua
+NSKEY=/etc/bind/Kna.+157+43558.
+#TTL=
+
+get_cnames() {
+ awk -v pattern='.*\\.'"$ZONE" '
+ BEGIN {
+ npat = split(pattern,apat);
+ }
+ { sub(/^[ \t][ \t]*/,"") }
+ /#/ { sub(/#.*/, "") }
+ NF==0 { next }
+ /^ServerName/ {
+ for (j = 1; j <= npat; j++)
+ if (match($2, apat[j]))
+ print $2
+ }
+ /^ServerAlias/ {
+ for (i = 2; i <= NF; i++)
+ for (j = 1; j <= npat; j++)
+ if (match($i, apat[j]))
+ print $i
+ }' /etc/httpd/sites-enabled/* |
+ tr ' ' '\n' | sed '/^$/d' | sort | uniq
+}
+
+update_dns() {
+ if test ${dry_run:-no} = yes; then
+ echo "$0: ignoring DNS updates:"
+ cat
+ else
+ nsupdate -k $NSKEY 2>&1 | grep -v "update failed: NXRRSET"
+ fi
+}
+
+nscleanup() {
+ if test -s "$CNAMESLIST"; then
+ echo "$0: Removing DNS CNAME records"
+ while read cname
+ do
+ test -z "$cname" && continue
+ cat <<EOT
+prereq yxrrset $cname CNAME
+update delete $cname CNAME
+send
+EOT
+ done < $CNAMESLIST | update_dns
+ rm $CNAMESLIST
+ fi
+}
+
+nssetup() {
+ nscleanup
+ echo "$0: Updating DNS CNAME records"
+ echo "$*" | tr ' ' '\n' > $CNAMESLIST
+ for cname
+ do
+ test -z "$cname" && continue
+ cat <<EOT
+prereq yxrrset $cname CNAME
+update delete $cname CNAME
+send
+update add $cname ${TTL:-86400} CNAME $HOSTNAME
+send
+EOT
+ done < $CNAMESLIST | update_dns
+}
+
+update_cnames() {
+ cnames=$(get_cnames)
+ if test -r $CNAMESLIST; then
+ if test -n "$cnames" &&
+ echo "$cnames" | cmp - $CNAMESLIST >/dev/null; then
+ return
+ fi
+ else
+ if test -z "$cnames"; then
+ return
+ fi
+ fi
+ echo "$cnames" | diff - $CNAMESLIST
+ nssetup $cnames
+}
+
+if test -n "$DIRCOND_GENEV_NAME"; then
+ case $(pwd) in
+ /etc/httpd/sites-available)
+ for file in /etc/httpd/sites-enabled/*
+ do
+ if test -h $file; then
+ s=$(readlink -f $file)
+ if test "$s" = /etc/httpd/sites-available/$DIRCOND_FILE; then
+ update_cnames
+ break
+ fi
+ fi
+ done
+ ;;
+ /etc/httpd/sites-enabled)
+ update_cnames
+ esac
+else
+ case $1 in
+ start|restart|force-restart|reload)
+ cnames=$(get_cnames)
+ if test -n "$cnames"; then
+ nssetup $cnames
+ fi
+ ;;
+ stop)
+ nscleanup;;
+ status)
+ echo >&2 "$0: status ignored";;
+ *) echo >&2 "usage: $0 {start|restart|force-restart|reload|stop}"
+ echo >&2 " can be called as dircond(1) handler"
+ esac
+fi
diff --git a/vhostcname/vhostcname b/vhostcname/vhostcname
new file mode 100755
index 0000000..1d2562d
--- /dev/null
+++ b/vhostcname/vhostcname
@@ -0,0 +1,517 @@
+#! /usr/bin/perl
+# Copyright (C) 2014 Sergey Poznyakoff <gray@gnu.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use Getopt::Long qw(:config gnu_getopt no_ignore_case);
+use Pod::Usage;
+use Pod::Man;
+use Sys::Hostname;
+use Cwd qw(getcwd realpath);
+use Net::DNS;
+
+my $script; # This script name;
+
+my $config_file = "/etc/vhostcname.conf";
+
+my $cnamelist = "/var/run/vhostcname.cache";
+my $host; # This host name
+my @zone;
+my $nameserver;
+my $nskey;
+my $ttl = 3600; # Default TTL
+my $confdir; # Apache configuration directory
+my $confpat = "*"; # A globbing pattern for Apache configuration files
+my $dry_run; # Dry-run mode
+my $debug;
+
+my $help; # Display help summary
+my $man; # Ditto in manpage format
+
+sub err {
+ print STDERR "$script: ";
+ print STDERR $_ for (@_);
+ print STDERR "\n";
+}
+
+sub abend {
+ my $code = shift;
+ &err;
+ exit($code);
+}
+
+sub read_config_file($) {
+ my $file = shift;
+ unless (-f $file) {
+ print STDERR "$script: configuration file $file does not exist\n"
+ if ($debug);
+ return;
+ }
+ print STDERR "$script: reading $file\n" if ($debug);
+ open(my $fd, "<", $file) or abend(1, "cannot open $file: $!");
+ while (<$fd>) {
+ chomp;
+ s/^\s+//;
+ s/\s+$//;
+ s/\s+=\s+/=/;
+ s/#.*//;
+ next if ($_ eq "");
+ unshift(@ARGV, "--$_");
+ }
+ close($fd);
+ }
+
+sub get_cnames($) {
+ my $dir = shift;
+ my %ret;
+
+ foreach my $file (glob "$dir/$confpat") {
+ next unless (-f $file);
+ print STDERR "$script: reading cnames from $file\n" if ($debug > 3);
+
+ open(my $fd, "<", $file) or do {
+ err("can't open file $file: $!");
+ next;
+ };
+ while (<$fd>) {
+ s/#.*//;
+ s/^\s+//;
+ s/\s+$//;
+ next if (/^$/);
+ if (/^Server(Name|Alias)\s+(.*)/) {
+ foreach my $name (split /\s+/, $2) {
+ foreach my $z (@zone) {
+ $ret{$name} = $z if ($name =~ /.*\.$z/);
+ }
+ }
+ }
+ }
+ close($fd)
+ }
+
+ return %ret;
+}
+
+sub read_cname_list($) {
+ my $file = shift;
+ my %ret;
+
+ if (-f $file) {
+ open(my $fd, "<", $file) or abend(1, "cannot open $file: $!");
+ while (<$fd>) {
+ chomp;
+ s/^\s+//;
+ s/\s+$//;
+ s/#.*//;
+ next if ($_ eq "");
+ my @a = split / /;
+ $ret{$a[0]} = $a[1];
+ }
+ close($fd);
+ }
+ return %ret;
+}
+
+sub write_cname_list {
+ my ($file, %hash) = @_;
+
+ return if ($dry_run);
+
+ open(my $fd, ">", $file) or abend(1, "cannot open $file for writing: $!");
+ foreach my $h (sort keys %hash) {
+ print $fd "$h $hash{$h}\n";
+ }
+ close($fd);
+}
+
+sub ns_update {
+ my $resolver = shift;
+ my $name = shift;
+ my $domain = shift;
+ my %hash = @_;
+
+ print STDERR "$script: updating $name in $domain: ".
+ join(',', map { "$_ => $hash{$_}" } keys %hash) .
+ "\n" if ($debug > 1);
+ return 1 if ($dry_run);
+
+ my $update = new Net::DNS::Update($domain);
+
+ while (my ($k, $v) = each %hash) {
+ $update->push($k => $v);
+ }
+ $update->sign_tsig($nskey) if defined($nskey);
+ my $reply = $resolver->send($update);
+ if ($reply) {
+ if ($reply->header->rcode eq 'NOERROR') {
+ print STDERR "$script: update successful\n" if ($debug>3);
+ } else {
+ err("updating $name failed: ",
+ join(',', map { "$_ => $hash{$_}" } keys %hash),
+ ': ',
+ $reply->header->rcode);
+ return 0;
+ }
+ } else {
+ err("updating $name failed: ",
+ join(',', map { "$_ => $hash{$_}" } keys %hash),
+ ': ',
+ $resolver->errorstring);
+ return 0;
+ }
+ return 1;
+}
+
+sub update_cnames_from_hash {
+ my %hash = @_;
+
+ print STDERR "$script: " . keys(%hash) . " names to update\n"
+ if ($debug > 1);
+ my %oldhash = read_cname_list($cnamelist);
+ my @namelist = sort(keys(%hash));
+ if (join(",", @namelist) eq join(".", sort(keys(%oldhash)))) {
+ print STDERR "$script: nothing to update\n" if ($debug);
+ return;
+ }
+
+ my $resolver = new Net::DNS::Resolver;
+ $resolver->nameservers($nameserver) if defined($nameserver);
+
+ my $name;
+ foreach $name (@namelist) {
+ if ($oldhash{$name}) {
+ delete $oldhash{$name};
+ } else {
+ ns_update($resolver, $name, $hash{$name},
+ prereq => yxdomain($name),
+ update => rr_del($name));
+ print "$name $ttl CNAME $host\n";
+ delete $hash{$name}
+ unless ns_update($resolver, $name, $hash{$name},
+ update => rr_add("$name $ttl CNAME $host"));
+ }
+ }
+
+ foreach $name (keys %oldhash) {
+ ns_update($resolver, $name, $oldhash{$name},
+ prereq => yxrrset("$name CNAME"),
+ update => rr_del("$name CNAME"));
+ }
+
+ write_cname_list($cnamelist, %hash);
+}
+
+sub update_cnames_from_dir($) {
+ update_cnames_from_hash(get_cnames(shift));
+}
+
+sub nssetup {
+ if (-f $cnamelist) {
+ unlink($cnamelist) or abend("can't unlink $cnamelist: $!");
+ }
+ &update_cnames_from_hash;
+}
+
+sub nscleanup {
+ print STDERR "$script: Removing DNS CNAME records\n" if ($debug);
+
+ my $resolver = new Net::DNS::Resolver;
+ $resolver->nameservers($nameserver) if defined($nameserver);
+
+ my %hash = read_cname_list($cnamelist);
+ foreach my $name (keys %hash) {
+ delete $hash{$name}
+ if ns_update($resolver, $name, $hash{$name},
+ prereq => yxrrset("$name CNAME"),
+ update => rr_del("$name CNAME"));
+ }
+
+ write_cname_list($cnamelist, %hash);
+}
+
+
+###
+($script = $0) =~ s/.*\///;
+
+## Read configuration
+read_config_file($ENV{'VHOSTCNAME_CONF'} ?
+ $ENV{'VHOSTCNAME_CONF'} : $config_file);
+
+GetOptions("help" => \$man,
+ "h" => \$help,
+ "debug|d+" => \$debug,
+ "dry-run|n" => \$dry_run,
+ "hostname|H=s" => \$host,
+ "apache-config-pattern=s" => \$confpat,
+ "apache-config-directory=s" => \$confdir,
+ "ns-key=s" => \$nskey,
+ "cname-file=s" => \$cnamelist,
+ "zone|z=s@" => \@zone,
+ "ttl=i" => \$ttl,
+ "server=s" => \$nameserver,
+ ) or exit(1);
+
+pod2usage(-message => "$script: update DNS from Apache virtual host configuration",
+ -exitstatus => 0) if $help;
+pod2usage(-exitstatus => 0, -verbose => 2) if $man;
+
+unless (defined($confdir)) {
+ foreach my $dir ("/etc/apache2", "/etc/httpd") {
+ if (-e "$dir/sites-enabled" and -e "$dir/sites-available") {
+ $confdir = $dir;
+ last;
+ }
+ if (-e "$dir/vhosts.d") {
+ $confdir = "$dir/vhosts.d";
+ last;
+ }
+ }
+ abend(1, "don't know where virtual host configurations are located; use --apache-config-directory option")
+ unless defined($confdir);
+}
+
+$host = hostname() unless defined($host);
+push(@zone, $host) if ($#zone == -1);
+$debug++ if ($dry_run);
+
+if ($#ARGV == -1) {
+ abend(1, "command not given") unless ($ENV{'DIRCOND_FILE'});
+ print STDERR "$script: started as dircond handler for " .
+ "$ENV{'DIRCOND_GENEV_NAME'} on $ENV{'DIRCOND_FILE'}\n"
+ if ($debug);
+ my $cwd = getcwd;
+ my $update_dir;
+ if (-d "$confdir/sites-available" && -d "$confdir/sites-enabled") {
+ if ($cwd eq "$confdir/sites-available") {
+ foreach my $file (glob "$cwd/*") {
+ next unless (-l $file);
+ if (realpath(readlink($file)) eq
+ "$confdir/sites-enabled/$ENV{'DIRCOND_FILE'}") {
+ $update_dir = "$confdir/sites-enabled";
+ last;
+ }
+ }
+ } elsif ($cwd eq "$confdir/sites-enabled") {
+ $update_dir = $cwd;
+ }
+ } else {
+ $update_dir = $cwd;
+ }
+
+ update_cnames_from_dir($update_dir) if defined($update_dir);
+} elsif ($#ARGV != 0) {
+ abend(1, "too many arguments");
+} elsif ($ARGV[0] =~ /^start|restart|force-restart|reload$/) {
+ nscleanup unless ($ARGV[0] eq "start");
+ my %cnames = get_cnames(-d "$confdir/sites-enabled" ?
+ "$confdir/sites-enabled" : $confdir);
+ if (keys(%cnames) > 0) {
+ nssetup(%cnames);
+ } elsif ($debug) {
+ print STDERR "$script: no cnames defined\n";
+ }
+} elsif ($ARGV[0] eq "stop") {
+ nscleanup;
+} elsif ($ARGV[0] eq "status") {
+ err("status command ignored");
+} else {
+ abend(1, "invalid command, try $script --help for more info");
+}
+
+__END__
+=head1 NAME
+
+vhostcname - synchronize DNS with Apache virtual host configuration
+
+=head1 SYNOPSIS
+
+B<vhostcname> [OPTIONS] B<COMMAND>
+
+=head1 DESCRIPTION
+
+The program takes a list of DNS zones and scans Apache virtual host
+configuration files. For each hostname found in B<ServerName> and
+B<ServerAlias> statements, it checks whether this name ends in a
+zone from the list, and if so, attempts to register this hostname
+using the DNS dynamic updates mechanism (B<RFC 2136>).
+
+A reverse operation is also supported: deregister all host name
+registered on the previous run.
+
+The mode of operation is requested by the B<COMMAND> argument.
+The available B<COMMAND>s have been chosen so as to allow
+B<vhostcname> to be run as one of the machine's startup
+scripts. The exact ways to register it to be run on server startup
+and shutdown depend on the operating system and distribution in use.
+For example, on Debian-based GNU/Linux:
+
+ cd /etc/init.d
+ ln -sf /usr/bin/vhostcname /etc/init.d
+ update-rc.d vhostcname defaults
+
+The program can also be ised as a B<dircond>(1) handler. This use
+allows for immediate updates of the DNS records upon any modifications
+to the Apache configuration files. The following example shows the
+corresponding B<dircond.conf>(5) entry:
+
+ watcher {
+ path /etc/apache2/sites-available;
+ path /etc/apache2/sites-enabled;
+ event (create,delete,write);
+ timeout 10;
+ option (stderr,stdout);
+ command /usr/bin/vhostcname;
+ }
+
+=head1 COMMANDS
+
+=over 4
+
+=item B<start>
+
+Scan the apache configuration files and register all server names matching
+the supplied zones.
+
+=item B<stop>
+
+Deregister all hostnames registered previously.
+
+=item B<restart>, B<force-restart>, B<reload>
+
+Same as running B<vhostcname stop; vhostcname start>.
+
+=item B<status>
+
+Ignored
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--apache-config-directory=>I<DIR>
+
+Sets the Apache configuration directory. I<DIR> should be either a directory
+where virtual configuration file are located or a directory which hosts the
+B<sites-available> and B<sites-enabled> directories. In the latter case,
+B<vhostcname> will look for files matching B<apache-config-pattern> in
+I<DIR>B</sites-enabled>.
+
+If this option is not given, B<vhostcname> will try to deduce where the
+configuration files are located. It will issue a warning message and
+terminate if unable to do that.
+
+=item B<--apache-config-pattern=>I<GLOB>
+
+Shell globbing pattern for virtual host configuration files. By default,
+B<*> is used, meaning that B<vhostcname> will scan all files in the
+configuration directory.
+
+=item B<--cname-file=>I<NAME>
+
+Name of the file where B<vhostcname> will keep successfully registered
+host names. Default is B</var/run/vhostcname.cache>.
+
+=item B<-d>, B<--debug>
+
+Increases the debug level. Multiple B<-d> options are allowed.
+
+=item B<-n>, B<--dry-run>,
+
+Enables I<dry-run> mode: print what would have been done without actually
+doing it.
+
+=item B<--help>
+
+Displays B<vhostcname> man page.
+
+=item B<-h>
+
+Displays a short help summary and exits.
+
+=item B<-H>, B<--hostname>=I<NAME>
+
+Sets the hostname. Use this if B<vhostcname> is unable to correctly
+determine it.
+
+=item B<--ns-key=>I<KEYFILE>
+
+Name of the key file. The argument should be the name of a file
+generated by the B<dnssec-keygen> utility. Either B<.key> or B<.private>
+file can be used.
+
+=item B<--server=>I<NAME>
+
+Name of the DNS server to use. Normally B<vhostcname> determines what server
+to use based on the B<SOA> record of the zone to be updated, so this option
+is rarely needed.
+
+=item B<--ttl=>I<TIME>
+
+TTL value for new DNS records. Default is 3600.
+
+=item B<--zone=>I<NAME>
+
+Name of the zone which B<vhostcname> can update. Multiple B<--zone> options
+can be given.
+
+If no B<--zone> option is given, B<vhostcname> will take hostname as the
+name of the zone.
+
+=back
+
+=head1 CONFIGURATION FILE
+
+If the file B<etc/vhostcname.conf> exists, the program will read its
+configuration from it. A familiar UNIX configuration format is used.
+Empty lines and UNIX comments are ignored. Each non-empty line is either an
+option name, or option assignment, i.e. B<opt>=B<val>, with any amount of
+optional whitespace around the equals sign. Valid option names are
+the same as the long command line options, but without the leading B<-->.
+For example:
+
+ zone=vhost.example.com
+ ns-key=/etc/bind/Kvhost+157+43558.key
+ ttl=3600
+
+=head1 ENVIRONMENT
+
+=over 4
+
+=item VHOSTCNAME_CONF
+
+The name of the configuration file to use instead of the default
+F</etc/vhostcname.conf>.
+
+=back
+
+=head1 BUGS
+
+Only one key file can be given. This means that if you use multiple
+B<--zone> options, all zones must be configured to accept the same
+DNSSEC key. Ditto for the B<--server> option.
+
+=head1 SEE ALSO
+
+B<dircond>(1).
+
+=head1 AUTHOR
+
+Sergey Poznyakoff <gray@gnu.org>
+
+=cut
+
+

Return to:

Send suggestions and report system problems to the System administrator.