summaryrefslogtreecommitdiffabout
path: root/vhostcname/vhostcname
Side-by-side diff
Diffstat (limited to 'vhostcname/vhostcname') (more/less context) (show whitespace changes)
-rwxr-xr-xvhostcname/vhostcname744
1 files changed, 557 insertions, 187 deletions
diff --git a/vhostcname/vhostcname b/vhostcname/vhostcname
index 359101c..fc6dd2d 100755
--- a/vhostcname/vhostcname
+++ b/vhostcname/vhostcname
@@ -1,3 +1,3 @@
#!/usr/bin/perl
-# Copyright (C) 2014 Sergey Poznyakoff <gray@gnu.org>
+# Copyright (C) 2014-2016 Sergey Poznyakoff <gray@gnu.org>
#
@@ -24,26 +24,31 @@ use Net::DNS;
-my $script; # This script name;
-
+my $progname; # This script name;
+my $progdescr = "update DNS from Apache virtual host configuration";
my $config_file = "/etc/vhostcname.conf";
+my %config = (
+ core => {
+ 'cache' => "/var/run/vhostcname.cache",
+# Default TTL.
+ 'ttl' => 3600,
+# A globbing pattern for Apache configuration files.
+ 'apache-config-pattern' => "*",
+ }
+);
+
+use constant EX_OK => 0;
+use constant EX_NOTUPDATED => 1;
+use constant EX_USAGE => 64;
+use constant EX_NOINPUT => 66;
+use constant EX_CANTCREAT => 73;
+use constant EX_CONFIG => 78;
-my $cnamelist = "/var/run/vhostcname.cache";
my $host; # This host name.
-my @zone; # List of acceptable DNS zones.
my $nameserver; # Nameserver to use for updates.
-my @tsig_args; # Arguments to sing_tsig (path to the DNSSEC key file, or
- # the key name and hash.
-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; # Debug level.
-my $allow_wildcard_domains;
-
-my $help; # Display help summary.
-my $man; # Ditto in manpage format.
-my $status = 0; # Default exit status.
+my $status = EX_OK;# Default exit status.
sub err {
- print STDERR "$script: ";
+ print STDERR "$progname: ";
print STDERR $_ for (@_);
@@ -58,21 +63,189 @@ sub abend {
-sub read_config_file($) {
- my $file = shift;
- unless (-f $file) {
- print STDERR "$script: configuration file $file does not exist\n"
- if ($debug);
- return;
+sub parse_section {
+ my ($conf, $input) = @_;
+ my $ref = $conf;
+ my $quote;
+ my $rootname;
+ while ($input ne '') {
+ my $name;
+ if (!defined($quote)) {
+ if ($input =~ /^"(.*)/) {
+ $quote = '';
+ $input = $1;
+ } elsif ($input =~ /^(.+?)(?:\s+|")(.*)/) {
+ $name = $1;
+ $input = $2;
+ } else {
+ $name = $input;
+ $input = '';
+ }
+ } else {
+ if ($input =~ /^([^\\"]*)\\(.)(.*)/) {
+ $quote .= $1 . $2;
+ $input = $3;
+ } elsif ($input =~ /^([^\\"]*)"\s*(.*)/) {
+ $name = $quote . $1;
+ $input = $2;
+ $quote = undef;
+ } else {
+ die "unparsable input $input";
+ }
+ }
+
+ if (defined($name)) {
+ $rootname = $name unless defined $rootname;
+ $ref->{$name} = {} unless ref($ref->{$name}) eq 'HASH';
+ $ref = $ref->{$name};
+ $name = undef;
}
- print STDERR "$script: reading $file\n" if ($debug);
- open(my $fd, "<", $file) or abend(1, "cannot open $file: $!");
+ }
+ return ($ref, $rootname);
+}
+
+sub check_mandatory {
+ my ($section, $kw, $loc, $s) = @_;
+ my $err = 0;
+ while (my ($k, $d) = each %{$kw}) {
+ if (ref($d) eq 'HASH'
+ and $d->{mandatory}
+ and !exists($section->{$k})) {
+ if (exists($d->{section})) {
+ if ($s) {
+ err("$loc: mandatory section [$k] not present");
+ ++$err;
+ }
+ } else {
+ err("$loc: mandatory variable \"$k\" not set");
+ ++$err;
+ }
+ }
+ }
+ return $err;
+}
+
+sub readconfig {
+ my $file = shift;
+ my $conf = shift;
+ my %param = @_;
+
+# debug(2, "reading $file");
+ open(my $fd, "<", $file)
+ or do {
+ err("can't open configuration file $file: $!");
+ return 1 if $param{include};
+ exit(EX_NOINPUT);
+ };
+
+ my $line;
+ my $err;
+ my $section = $conf;
+ my $kw = $param{kw};
+ my $include = 0;
+ my $rootname;
+
while (<$fd>) {
+ ++$line;
chomp;
+ if (/\\$/) {
+ chop;
+ $_ .= <$fd>;
+ redo;
+ }
+
s/^\s+//;
s/\s+$//;
- s/\s+=\s+/=/;
s/#.*//;
next if ($_ eq "");
- unshift(@ARGV, "--$_");
+
+ if (/^\[(.+?)\]$/) {
+ $include = 0;
+ my $arg = $1;
+ $arg =~ s/^\s+//;
+ $arg =~ s/\s+$//;
+ if ($arg eq 'include') {
+ $include = 1;
+ } else {
+ ($section, $rootname) = parse_section($conf, $1);
+ if (ref($param{kw}) eq 'HASH') {
+ if (defined($rootname)) {
+ if (ref($param{kw}{$rootname}) eq 'HASH'
+ and exists($param{kw}{$rootname}{section})) {
+ $kw = $param{kw}{$rootname}{section};
+ } else {
+ err("$file:$line: unknown section");
+ $kw = undef;
+ }
+ } else {
+ $kw = $param{kw};
+ }
}
- close($fd);
+ }
+ } elsif (/([\w_-]+)\s*=\s*(.*)/) {
+ my ($k, $v) = ($1, $2);
+ $k = lc($k) if $param{ci};
+
+ if ($include) {
+ if ($k eq 'path') {
+ $err += readconfig($v, $conf, include => 1, @_);
+ } elsif ($k eq 'pathopt') {
+ $err += readconfig($v, $conf, include => 1, @_)
+ if -f $v;
+ } elsif ($k eq 'glob') {
+ foreach my $file (bsd_glob($v, 0)) {
+ $err += readconfig($file, $conf, include => 1, @_);
+ }
+ } else {
+ err("$file:$line: unknown keyword");
+ ++$err;
+ }
+ next;
+ }
+
+ if (defined($kw)) {
+ my $x = $kw->{$k};
+ if (!defined($x)) {
+ err("$file:$line: unknown keyword $k");
+ ++$err;
+ next;
+ } elsif (ref($x) eq 'HASH') {
+ if (exists($x->{re})) {
+ if ($v !~ /$x->{re}/) {
+ err("$file:$line: invalid value for $k");
+ ++$err;
+ next;
+ }
+ if (exists($x->{check})
+ and !&{$x->{check}}($k, $v, "$file:$line")) {
+ ++$err;
+ next;
+ }
+ } elsif (exists($x->{check})) {
+ if (!&{$x->{check}}($k, $v, "$file:$line")) {
+ ++$err;
+ next;
+ }
+ } elsif (!exists($x->{var}) and
+ !exists($x->{parser}) and
+ !exists($x->{mandatory})) {
+ err("$file:$line: unknown keyword $k");
+ ++$err;
+ next;
+ }
+ if (exists($x->{parser})
+ and !&{$x->{parser}}($k, \$v, "$file:$line")) {
+ ++$err;
+ next;
+ }
+ }
+ }
+
+ $section->{$k} = $v;
+ } else {
+ err("$file:$line: malformed line");
+ ++$err;
+ next;
+ }
+ }
+ close $fd;
+ exit(EX_CONFIG) if $err;
}
@@ -85,3 +258,3 @@ sub valid_domain_name {
my $name = shift;
- $name =~ s/^\*\.// if ($allow_wildcard_domains);
+ $name =~ s/^\*\.// if ($config{core}{'allow-wildcards'});
foreach my $label (split(/\./, $name)) {
@@ -99,5 +272,5 @@ sub get_cnames($) {
- foreach my $file (glob "$dir/$confpat") {
+ foreach my $file (glob "$dir/$config{core}{'apache-config-pattern'}") {
next unless (-f $file);
- print STDERR "$script: reading cnames from $file\n" if ($debug > 2);
+ print STDERR "$progname: reading cnames from $file\n" if ($debug > 2);
@@ -116,9 +289,9 @@ sub get_cnames($) {
unless (valid_domain_name($name)) {
- print STDERR "$script: $file:$line: $name: invalid domain name\n";
+ print STDERR "$progname: $file:$line: $name: invalid domain name\n";
next;
}
- foreach my $z (@zone) {
+ foreach my $z (keys %{$config{zone}}) {
if ($name =~ /.*\.$z$/) {
if ($name =~ /^\*\.(.+)/ and $1 eq $z) {
- print STDERR "$script: $file:$line: $name: first-level wildcard\n";
+ print STDERR "$progname: $file:$line: $name: first-level wildcard\n";
next;
@@ -143,3 +316,3 @@ sub read_cname_list($) {
if (-f $file) {
- open(my $fd, "<", $file) or abend(1, "cannot open $file: $!");
+ open(my $fd, "<", $file) or abend(EX_NOINPUT, "cannot open $file: $!");
while (<$fd>) {
@@ -163,3 +336,4 @@ sub write_cname_list {
- open(my $fd, ">", $file) or abend(1, "cannot open $file for writing: $!");
+ open(my $fd, ">", $file) or
+ abend(EX_CANTCREAT, "cannot open $file for writing: $!");
foreach my $h (sort keys %hash) {
@@ -171,3 +345,2 @@ sub write_cname_list {
sub ns_update {
- my $resolver = shift;
my $name = shift;
@@ -177,3 +350,5 @@ sub ns_update {
- print STDERR "$script: updating $name in $domain: ".
+ my $resolver = get_zone_resolver($domain);
+
+ print STDERR "$progname: updating $name in $domain: ".
join(',', map { "$_ => $hash{$_}" } keys %hash) .
@@ -191,3 +366,3 @@ sub ns_update {
}
- $update->sign_tsig(@tsig_args) if ($#tsig_args >= 0);
+ zone_sign_tsig($update, $domain);
my $reply = $resolver->send($update);
@@ -195,5 +370,5 @@ sub ns_update {
if ($reply->header->rcode eq 'NOERROR') {
- print STDERR "$script: update successful\n" if ($debug>3);
+ print STDERR "$progname: update successful\n" if ($debug>3);
} elsif ($ignorerr{$reply->header->rcode}) {
- print STDERR "$script: ignoring " . $reply->header->rcode . ': ' .
+ print STDERR "$progname: ignoring " . $reply->header->rcode . ': ' .
join(',', map { "$_ => $hash{$_}" } keys %hash) . "\n"
@@ -205,3 +380,3 @@ sub ns_update {
$reply->header->rcode);
- $status = 2;
+ $status = EX_NOTUPDATED;
return 0;
@@ -213,3 +388,3 @@ sub ns_update {
$resolver->errorstring);
- $status = 2;
+ $status = EX_NOTUPDATED;
return 0;
@@ -219,2 +394,33 @@ sub ns_update {
+sub get_zone_resolver {
+ my $zone = shift;
+ unless (defined($config{zone}{$zone}{resolver})) {
+ my $resolver = new Net::DNS::Resolver;
+ $resolver->nameservers($config{zone}{$zone}{server})
+ if defined $config{zone}{$zone}{server};
+ $config{zone}{$zone}{resolver} = $resolver;
+ }
+ return $config{zone}{$zone}{resolver};
+}
+
+sub zone_sign_tsig {
+ my ($update, $zone) = @_;
+ my @tsig_args;
+
+ my $zcfg = $config{zone}{$zone};
+ if (exists($zcfg->{'ns-key-file'})) {
+ push @tsig_args, split(/\s+/, $zcfg->{'ns-key-file'});
+ } elsif (exists($zcfg->{'ns-key'})) {
+ push @tsig_args, @{$zcfg->{'ns-key'}};
+ }
+ if ($#tsig_args == -1) {
+ if (exists($config{core}{'ns-key-file'})) {
+ push @tsig_args, split(/\s+/, $config{core}{'ns-key-file'});
+ } elsif (exists($config{core}{'ns-key'})) {
+ push @tsig_args, @{$config{core}{'ns-key'}};
+ }
+ }
+ $update->sign_tsig(@tsig_args) if ($#tsig_args >= 0);
+}
+
sub update_cnames_from_hash {
@@ -222,8 +428,8 @@ sub update_cnames_from_hash {
- print STDERR "$script: " . keys(%hash) . " names to update\n"
+ print STDERR "$progname: " . keys(%hash) . " names to update\n"
if ($debug > 2);
- my %oldhash = read_cname_list($cnamelist);
+ my %oldhash = read_cname_list($config{core}{cache});
my @namelist = sort(keys(%hash));
- if (join(",", @namelist) eq join(".", sort(keys(%oldhash)))) {
- print STDERR "$script: nothing to update\n" if ($debug);
+ if (join(",", @namelist) eq join(",", sort(keys(%oldhash)))) {
+ print STDERR "$progname: nothing to update\n" if ($debug);
return;
@@ -231,5 +437,2 @@ sub update_cnames_from_hash {
- my $resolver = new Net::DNS::Resolver;
- $resolver->nameservers($nameserver) if defined($nameserver);
-
my $name;
@@ -239,3 +442,3 @@ sub update_cnames_from_hash {
} else {
- ns_update($resolver, $name, $hash{$name},
+ ns_update($name, $hash{$name},
prereq => yxdomain($name),
@@ -243,6 +446,6 @@ sub update_cnames_from_hash {
ignore => 'NXDOMAIN');
- print STDERR "$script: $name $ttl CNAME $host\n" if ($debug);
+ print STDERR "$progname: $name $config{core}{ttl} CNAME $config{core}{hostname}\n" if ($debug);
delete $hash{$name}
- unless ns_update($resolver, $name, $hash{$name},
- update => rr_add("$name $ttl CNAME $host"));
+ unless ns_update($name, $hash{$name},
+ update => rr_add("$name $config{core}{ttl} CNAME $config{core}{hostname}"));
}
@@ -251,3 +454,3 @@ sub update_cnames_from_hash {
foreach $name (keys %oldhash) {
- ns_update($resolver, $name, $oldhash{$name},
+ ns_update($name, $oldhash{$name},
prereq => yxrrset("$name CNAME"),
@@ -257,3 +460,3 @@ sub update_cnames_from_hash {
- write_cname_list($cnamelist, %hash);
+ write_cname_list($config{core}{cache}, %hash);
}
@@ -265,13 +468,10 @@ sub update_cnames_from_dir($) {
sub nscleanup {
- print STDERR "$script: Removing DNS CNAME records\n" if ($debug);
-
- my $resolver = new Net::DNS::Resolver;
- $resolver->nameservers($nameserver) if defined($nameserver);
+ print STDERR "$progname: Removing DNS CNAME records\n" if ($debug);
- my %hash = read_cname_list($cnamelist);
+ my %hash = read_cname_list($config{core}{cache});
foreach my $name (keys %hash) {
- print STDERR "$script: removing $name from $hash{$name}\n"
+ print STDERR "$progname: removing $name from $hash{$name}\n"
if ($debug);
delete $hash{$name}
- if ns_update($resolver, $name, $hash{$name},
+ if ns_update($name, $hash{$name},
prereq => yxrrset("$name CNAME"),
@@ -281,48 +481,161 @@ sub nscleanup {
- write_cname_list($cnamelist, %hash);
+ write_cname_list($config{core}{cache}, %hash);
+}
+
+###
+sub com_start {
+ abend(EX_USAGE, "too many arguments") unless $#_ == 0;
+ nscleanup();
+ com_reload(@_);
+}
+
+sub com_reload {
+ abend(EX_USAGE, "too many arguments") unless $#_ == 0;
+ my $confdir = -d "$config{core}{'apache-config-directory'}/sites-enabled"
+ ? "$config{core}{'apache-config-directory'}/sites-enabled"
+ : $config{core}{'apache-config-directory'};
+ my %cnames = get_cnames($confdir);
+ update_cnames_from_hash(%cnames);
+ print STDERR "$progname: no cnames defined\n" unless (keys(%cnames) > 0);
}
+sub com_stop {
+ abend(EX_USAGE, "too many arguments") unless $#_ == 0;
+ nscleanup;
+}
+
+sub com_status {
+ err("status command ignored");
+ my %stat;
+
+ my %hash = read_cname_list($config{core}{cache});
+ while (my ($name, $zone) = each %hash) {
+# $name =~ s/.$zone$//;
+ push @{${stat}{$zone}}, $name;
+ }
+
+ foreach my $zone (sort(keys %stat)) {
+ print "Names in zone $zone:\n";
+ foreach my $name (sort(@{$stat{$zone}})) {
+ print " $name\n";
+ }
+ }
+}
###
-($script = $0) =~ s/.*\///;
+($progname = $0) =~ s/.*\///;
+
+my %comtab = (
+ start => \&com_start,
+ restart => \&com_reload,
+ 'force-restart' => \&com_start,
+ reload => \&com_reload,
+ stop => \&com_stop,
+ status => \&com_status
+);
+
+sub getcom {
+ my $com = shift;
+
+ while (defined($comtab{$com}) and ref($comtab{$com}) ne 'CODE') {
+ $com = $comtab{$com};
+ }
+ die "internal error: unresolved command alias" unless defined $com;
+ return $comtab{$com} if defined $comtab{$com};
+
+ my @v = map { /^$com/ ? $_ : () } sort keys %comtab;
+ if ($#v == -1) {
+ abend(EX_USAGE, "unrecognized command");
+ } elsif ($#v > 0) {
+ abend(EX_USAGE, "ambiguous command: ".join(', ', @v));
+ }
+ return getcom($v[0]);
+}
+
## Read configuration
-read_config_file($ENV{'VHOSTCNAME_CONF'} ?
- $ENV{'VHOSTCNAME_CONF'} : $config_file);
+sub parse_ns_key {
+ my ($var, $ref, $loc) = @_;
+ my @result;
+ if ($$ref =~ /(.+?)=(.+)/) {
+ push @result, $1, $2;
+ $$ref = \@result;
+ } else {
+ err("$loc: $var argument must be must be NAME=KEY");
+ return 0;
+ }
+ return 1;
+}
-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-file=s" => sub {
- abend(3, "NS key already set") if ($#tsig_args >= 0);
- push @tsig_args, $_[1];
- },
- "ns-key=s" => sub {
- abend(3, "NS key already set") if ($#tsig_args >= 0);
- if ($_[1] =~ /(.+?)=(.+)/) {
- push @tsig_args, $1;
- push @tsig_args, $2;
+sub parse_boolean {
+ my ($var, $ref, $loc) = @_;
+ my %bool = ( yes => 1,
+ no => 0,
+ true => 1,
+ false => 0,
+ t => 1,
+ nil => 0,
+ f => 0,
+ on => 1,
+ off => 0,
+ 1 => 1,
+ 0 => 0);
+
+ my $s = $$ref;
+ $s =~ tr/A-Z/a-z/;
+ if (exists($bool{$s})) {
+ $$ref = $bool{$s};
} else {
- abend(3, "argument to --ns-key must be NAME=KEY");
+ err("$loc: argument must be boolean");
+ return 0;
+ }
+ return 1;
}
+
+my %kw = (
+ core => {
+ section => {
+ 'apache-config-directory' => 1,
+ 'apache-config-pattern' => 1,
+ 'cache' => 1,
+ 'server' => 1,
+ 'ttl' => 1,
+ 'ns-key' => { parser => \&parse_ns_key },
+ 'ns-key-file' => 1,
+ 'hostname' => 1,
+ 'allow-wildcards' => { parser => \&parse_boolean }
+ },
},
- "cname-file=s" => \$cnamelist,
- "zone|z=s@" => \@zone,
- "ttl=i" => \$ttl,
- "server=s" => \$nameserver,
- "allow-wildcard-domains" => \$allow_wildcard_domains
- ) or exit(3);
-
-pod2usage(-message => "$script: update DNS from Apache virtual host configuration",
- -exitstatus => 0) if $help;
-pod2usage(-exitstatus => 0, -verbose => 2) if $man;
-
-unless (defined($confdir)) {
+ zone => {
+ section => {
+ 'server' => 1,
+ 'ttl' => 1,
+ 'ns-key' => { parser => \&parse_ns_key },
+ 'ns-key-file' => 1
+ },
+ }
+);
+
+GetOptions("help" => sub {
+ pod2usage(-exitstatus => EX_OK, -verbose => 2);
+ },
+ "h" => sub {
+ pod2usage(-message => "$progname: $progdescr",
+ -exitstatus => EX_OK);
+ },
+ "usage" => sub {
+ pod2usage(-exitstatus => EX_OK, -verbose => 0);
+ },
+
+ "debug|d+" => \$debug,
+ "dry-run|n" => \$dry_run,
+ "config|c=s" => \$config_file,
+ ) or exit(EX_USAGE);
+
+readconfig($config_file, \%config, kw => \%kw);
+
+unless (defined($config{core}{'apache-config-directory'})) {
foreach my $dir ("/etc/apache2", "/etc/httpd") {
if (-e "$dir/sites-enabled" and -e "$dir/sites-available") {
- $confdir = $dir;
+ $config{core}{'apache-config-directory'} = $dir;
last;
@@ -330,3 +643,3 @@ unless (defined($confdir)) {
if (-e "$dir/vhosts.d") {
- $confdir = "$dir/vhosts.d";
+ $config{core}{'apache-config-directory'} = "$dir/vhosts.d";
last;
@@ -334,9 +647,10 @@ unless (defined($confdir)) {
}
- abend(3,
- "don't know where virtual host configurations are located; use --apache-config-directory option")
- unless defined($confdir);
+ abend(EX_CONFIG,
+ "don't know where virtual host configurations are located; define apache-config-directory")
+ unless defined($config{core}{'apache-config-directory'});
}
-$host = hostname() unless defined($host);
-push(@zone, $host) if ($#zone == -1);
+$config{core}{hostname} = hostname() unless defined($config{core}{hostname});
+$config{zone}{$host} = {} unless exists $config{zone};
+
$debug++ if ($dry_run);
@@ -344,4 +658,4 @@ $debug++ if ($dry_run);
if ($#ARGV == -1) {
- abend(3, "command not given") unless ($ENV{'DIREVENT_FILE'});
- print STDERR "$script: started as direvent handler for " .
+ abend(EX_USAGE, "command not given") unless ($ENV{'DIREVENT_FILE'});
+ print STDERR "$progname: started as direvent handler for " .
"$ENV{'DIREVENT_GENEV_NAME'} on $ENV{'DIREVENT_FILE'}\n"
@@ -350,5 +664,6 @@ if ($#ARGV == -1) {
my $update_dir;
+ my $confdir = $config{core}{'apache-config-directory'};
if (-d "$confdir/sites-available" && -d "$confdir/sites-enabled") {
if ($cwd eq "$confdir/sites-available") {
- foreach my $file (glob "$confdir/sites-enabled/$confpat") {
+ foreach my $file (glob "$confdir/sites-enabled/$config{core}{'apache-config-pattern'}") {
next unless (-l $file);
@@ -368,18 +683,7 @@ if ($#ARGV == -1) {
update_cnames_from_dir($update_dir) if defined($update_dir);
-} elsif ($#ARGV != 0) {
- abend(3, "too many arguments");
-} elsif ($ARGV[0] =~ /^start|restart|force-restart|reload$/) {
- nscleanup if ($ARGV[0] =~ /start$/);
- my %cnames = get_cnames(-d "$confdir/sites-enabled" ?
- "$confdir/sites-enabled" : $confdir);
- update_cnames_from_hash(%cnames);
- print STDERR "$script: no cnames defined\n" unless (keys(%cnames) > 0);
-} elsif ($ARGV[0] eq "stop") {
- nscleanup;
-} elsif ($ARGV[0] eq "status") {
- err("status command ignored");
-} else {
- abend(3, "invalid command, try $script --help for more info");
}
+my $command = getcom($ARGV[0]);
+&{$command}(@ARGV);
+
exit($status);
@@ -433,2 +737,8 @@ corresponding B<direvent.conf>(5) entry:
+Unless the program is started as a B<direvent>(8) handler, exactly one
+command must be given in the command line. A command may be supplied
+in full or abbreviated form. Any unambiguous abbreviation is allowed.
+
+Available commands are:
+
=over 4
@@ -437,4 +747,4 @@ corresponding B<direvent.conf>(5) entry:
-Scan the apache configuration files and register all server names matching
-the supplied zones.
+Scan the apache configuration files and register all server names that
+match the configured zones.
@@ -444,5 +754,14 @@ Deregister all hostnames registered previously.
-=item B<restart>, B<force-restart>, B<reload>
+=item B<restart>, B<reload>
-Same as running B<vhostcname stop; vhostcname start>.
+Builds a list of names from the apache configuration (I<apache-list>) and
+compares them with the names registered at the previous run (I<cache>). If
+the two lists differ, the names present in I<apache-list>, but absent in
+I<cache> are registered. The names present in I<cache>, but lacking in
+I<apache-list> are deleted from the DNS.
+
+=item B<force-restart>
+
+Deregister all hostnames registered previously, rescan Apache files, and
+register all names that match the configured zones.
@@ -450,3 +769,3 @@ Same as running B<vhostcname stop; vhostcname start>.
-Ignored
+Displays registered host names.
@@ -458,9 +777,52 @@ Ignored
-=item B<--allow-wildcard-domains>
+=item B<-c>, B<--config=>I<FILE>
+
+Read configuration from I<FILE> instead of the default location
+(F</etc/vhostcname.conf>).
+
+=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<--usage>
+
+Displays a short command line syntax reminder.
+
+=back
+
+=head1 CONFIGURATION FILE
+
+Configuration is read from F</etc/vhostcname.conf> or a file specified
+by the B<--config> (B<-c>) command line option. The file consists of
+a number of variable assignments (I<variable> B<=> I<value>), grouped into
+sections. Whitespace is ignored, except that it serves to separate input
+tokens. I<value> is read verbatim, including eventual whitespace characters
+that can appear within it.
+
+A section begins with the line containing its name within square brackets
+(e.g. B<[core]>). The name can be followed by one or more arguments, if
+the section semantics requires so (e.g. B<[zone example.com]>).
+
+The following sections are recognized:
+
+=over 4
+
+=item B<[core]>
-Allow the use of wildcard (B<*>). When this option is in effect, a wildcard
-will be allowed if it is the very first label in a domain name and it is
-separated from the base zone (see the B<--zone> option) by one or more labels.
+=over 8
-=item B<--apache-config-directory=>I<DIR>
+=item B<apache-config-directory => I<DIR>
@@ -476,3 +838,3 @@ terminate if unable to do that.
-=item B<--apache-config-pattern=>I<GLOB>
+=item B<apache-config-pattern => I<PATTERN>
@@ -480,36 +842,47 @@ 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.
+configuration directory (note: that includes backup copies too!).
-=item B<--cname-file=>I<NAME>
+=item B<cache => I<FILE>
-Name of the file where B<vhostcname> will keep successfully registered
+Name of the cache file where B<vhostcname> keeps successfully registered
host names. Default is B</var/run/vhostcname.cache>.
-=item B<-d>, B<--debug>
+=item B<hostname => I<HOSTNAME>
-Increases the debug level. Multiple B<-d> options are allowed.
+Sets the hostname. Use this if B<vhostcname> is unable to correctly
+determine it.
-=item B<-n>, B<--dry-run>,
+=item B<allow-wildcards => I<BOOL>
-Enables I<dry-run> mode: print what would have been done without actually
-doing it.
+Allow the use of wildcard (B<*>) in host names. When this option is in
+effect, a wildcard will be allowed if it is the very first label in a domain
+name and it is separated from the base zone (see the B<zone> section) by one
+more labels.
-=item B<--help>
+I<BOOL> is one of B<yes>, B<true>, B<t>, B<on>, or B<1> to allow wildcards,
+or one of B<no>, B<false>, B<f>, B<nil>, B<off>, B<0> to disallow them (the
+default).
-Displays B<vhostcname> man page.
+=back
-=item B<-h>
+The following variables provide defaults for zones that lack the
+corresponding settings:
-Displays a short help summary and exits.
+=over 8
-=item B<-H>, B<--hostname>=I<NAME>
+=item B<server => I<HOST>
-Sets the hostname. Use this if B<vhostcname> is unable to correctly
-determine it.
+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<--ns-key=>I<NAME>=I<KEY>
+=item B<ttl => I<SECONDS>
-Define the TSIG key.
+TTL value for new DNS records. Default is 3600.
-=item B<--ns-key-file=>I<KEYFILE>
+=item B<ns-key => I<NAME>=I<HASH>
+
+Defines the TSIG key.
+
+=item B<ns-key-file => I<FILE>
@@ -519,46 +892,41 @@ file can be used.
-This option cannot be used together with B<--ns-key>.
+If both <ns-key> and B<ns-key-file> are used, the latter is given preference.
-=item B<--server=>I<NAME>
+=back
-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<[zone I<NAME>]>
-=item B<--ttl=>I<TIME>
+The B<zone> section informs B<vhostcname> that it should handle names
+in zone I<NAME>. Any number of B<[zone]> sections can be defined. If
+none is defined, B<vhostcname> will take hostname as the name of the zone
+to update.
-TTL value for new DNS records. Default is 3600.
+The variables in a B<zone> section define parameters to be used for that
+particular zone. As such, none of them is mandatory. If the zone I<NAME>
+uses default settings (or settings, defined in the B<[core]> section),
+the section can be empty.
-=item B<--zone=>I<NAME>
+=over 8
-Name of the zone which B<vhostcname> can update. Multiple B<--zone> options
-can be given.
+=item B<server => I<HOST>
-If no B<--zone> option is given, B<vhostcname> will take hostname as the
-name of the zone.
+Name of the DNS server to use when updating this zone.
-=back
+=item B<ttl => I<SECONDS>
-=head1 CONFIGURATION FILE
+TTL for records in this zone.
-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:
+=item B<ns-key => I<NAME>=I<HASH>
- zone = vhost.example.com
- ns-key-file = /etc/bind/Kvhost+157+43558.key
- ttl = 3600
+TSIG key.
-=head1 ENVIRONMENT
+=item B<ns-key-file => I<FILE>
-=over 4
+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<VHOSTCNAME_CONF>
+If both <ns-key> and B<ns-key-file> are used, the latter is given preference.
-The name of the configuration file to use instead of the default
-F</etc/vhostcname.conf>.
+=back
@@ -576,19 +944,21 @@ Success
-Operating system error (unable to open file, etc.)
+Some of the host names could not be updated.
-=item 2
+=item 64
-Some of the host names could not be registered.
+Command line usage error.
-=item 3
+=item 66
-Command line usage error
+Required input file cannot be opened.
-=back
+=item 73
-=head1 BUGS
+Required output file cannot be created or written.
-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.
+=item 78
+
+Configuration error.
+
+=back

Return to:

Send suggestions and report system problems to the System administrator.