summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org.ua>2016-02-02 10:54:52 (GMT)
committer Sergey Poznyakoff <gray@gnu.org.ua>2016-02-02 10:54:52 (GMT)
commitdb032057eb89e07b79eefb8617e326e2cd037300 (patch) (unidiff)
tree6b2c80bb44ef91afda06eca064347a6b42c3813a
parente3ea08d43ccf0a117b10e9d52c91612281a4193f (diff)
downloaddnstools-db032057eb89e07b79eefb8617e326e2cd037300.tar.gz
dnstools-db032057eb89e07b79eefb8617e326e2cd037300.tar.bz2
vhostcname: allow for different per-zone servers/keys/ttls
* vhostcname/vhostcname: Rewrite configuration handling. Store zone settings in individual hash cells. Redefine exit codes. Implement status command. Clean up the semantics of start/forced-restart vs. reload. Accept abbreviated command names.
Diffstat (more/less context) (ignore whitespace changes)
-rwxr-xr-xvhostcname/vhostcname774
1 files changed, 572 insertions, 202 deletions
diff --git a/vhostcname/vhostcname b/vhostcname/vhostcname
index 359101c..fc6dd2d 100755
--- a/vhostcname/vhostcname
+++ b/vhostcname/vhostcname
@@ -1,3 +1,3 @@
1#!/usr/bin/perl 1#!/usr/bin/perl
2# Copyright (C) 2014 Sergey Poznyakoff <gray@gnu.org> 2# Copyright (C) 2014-2016 Sergey Poznyakoff <gray@gnu.org>
3# 3#
@@ -24,26 +24,31 @@ use Net::DNS;
24 24
25my $script; # This script name; 25my $progname; # This script name;
26 26my $progdescr = "update DNS from Apache virtual host configuration";
27my $config_file = "/etc/vhostcname.conf"; 27my $config_file = "/etc/vhostcname.conf";
28my %config = (
29 core => {
30 'cache' => "/var/run/vhostcname.cache",
31# Default TTL.
32 'ttl' => 3600,
33# A globbing pattern for Apache configuration files.
34 'apache-config-pattern' => "*",
35 }
36);
37
38use constant EX_OK => 0;
39use constant EX_NOTUPDATED => 1;
40use constant EX_USAGE => 64;
41use constant EX_NOINPUT => 66;
42use constant EX_CANTCREAT => 73;
43use constant EX_CONFIG => 78;
28 44
29my $cnamelist = "/var/run/vhostcname.cache";
30my $host; # This host name. 45my $host; # This host name.
31my @zone; # List of acceptable DNS zones.
32my $nameserver; # Nameserver to use for updates. 46my $nameserver; # Nameserver to use for updates.
33my @tsig_args; # Arguments to sing_tsig (path to the DNSSEC key file, or
34 # the key name and hash.
35my $ttl = 3600; # Default TTL.
36my $confdir; # Apache configuration directory.
37my $confpat = "*"; # A globbing pattern for Apache configuration files.
38my $dry_run; # Dry-run mode. 47my $dry_run; # Dry-run mode.
39my $debug; # Debug level. 48my $debug; # Debug level.
40my $allow_wildcard_domains;
41 49
42my $help; # Display help summary. 50my $status = EX_OK;# Default exit status.
43my $man; # Ditto in manpage format.
44
45my $status = 0; # Default exit status.
46 51
47sub err { 52sub err {
48 print STDERR "$script: "; 53 print STDERR "$progname: ";
49 print STDERR $_ for (@_); 54 print STDERR $_ for (@_);
@@ -58,21 +63,189 @@ sub abend {
58 63
59sub read_config_file($) { 64sub parse_section {
60 my $file = shift; 65 my ($conf, $input) = @_;
61 unless (-f $file) { 66 my $ref = $conf;
62 print STDERR "$script: configuration file $file does not exist\n" 67 my $quote;
63 if ($debug); 68 my $rootname;
64 return; 69 while ($input ne '') {
70 my $name;
71 if (!defined($quote)) {
72 if ($input =~ /^"(.*)/) {
73 $quote = '';
74 $input = $1;
75 } elsif ($input =~ /^(.+?)(?:\s+|")(.*)/) {
76 $name = $1;
77 $input = $2;
78 } else {
79 $name = $input;
80 $input = '';
81 }
82 } else {
83 if ($input =~ /^([^\\"]*)\\(.)(.*)/) {
84 $quote .= $1 . $2;
85 $input = $3;
86 } elsif ($input =~ /^([^\\"]*)"\s*(.*)/) {
87 $name = $quote . $1;
88 $input = $2;
89 $quote = undef;
90 } else {
91 die "unparsable input $input";
92 }
93 }
94
95 if (defined($name)) {
96 $rootname = $name unless defined $rootname;
97 $ref->{$name} = {} unless ref($ref->{$name}) eq 'HASH';
98 $ref = $ref->{$name};
99 $name = undef;
100 }
65 } 101 }
66 print STDERR "$script: reading $file\n" if ($debug); 102 return ($ref, $rootname);
67 open(my $fd, "<", $file) or abend(1, "cannot open $file: $!"); 103}
104
105sub check_mandatory {
106 my ($section, $kw, $loc, $s) = @_;
107 my $err = 0;
108 while (my ($k, $d) = each %{$kw}) {
109 if (ref($d) eq 'HASH'
110 and $d->{mandatory}
111 and !exists($section->{$k})) {
112 if (exists($d->{section})) {
113 if ($s) {
114 err("$loc: mandatory section [$k] not present");
115 ++$err;
116 }
117 } else {
118 err("$loc: mandatory variable \"$k\" not set");
119 ++$err;
120 }
121 }
122 }
123 return $err;
124}
125
126sub readconfig {
127 my $file = shift;
128 my $conf = shift;
129 my %param = @_;
130
131# debug(2, "reading $file");
132 open(my $fd, "<", $file)
133 or do {
134 err("can't open configuration file $file: $!");
135 return 1 if $param{include};
136 exit(EX_NOINPUT);
137 };
138
139 my $line;
140 my $err;
141 my $section = $conf;
142 my $kw = $param{kw};
143 my $include = 0;
144 my $rootname;
145
68 while (<$fd>) { 146 while (<$fd>) {
69 chomp; 147 ++$line;
70 s/^\s+//; 148 chomp;
71 s/\s+$//; 149 if (/\\$/) {
72 s/\s+=\s+/=/; 150 chop;
73 s/#.*//; 151 $_ .= <$fd>;
74 next if ($_ eq ""); 152 redo;
75 unshift(@ARGV, "--$_"); 153 }
154
155 s/^\s+//;
156 s/\s+$//;
157 s/#.*//;
158 next if ($_ eq "");
159
160 if (/^\[(.+?)\]$/) {
161 $include = 0;
162 my $arg = $1;
163 $arg =~ s/^\s+//;
164 $arg =~ s/\s+$//;
165 if ($arg eq 'include') {
166 $include = 1;
167 } else {
168 ($section, $rootname) = parse_section($conf, $1);
169 if (ref($param{kw}) eq 'HASH') {
170 if (defined($rootname)) {
171 if (ref($param{kw}{$rootname}) eq 'HASH'
172 and exists($param{kw}{$rootname}{section})) {
173 $kw = $param{kw}{$rootname}{section};
174 } else {
175 err("$file:$line: unknown section");
176 $kw = undef;
177 }
178 } else {
179 $kw = $param{kw};
180 }
181 }
182 }
183 } elsif (/([\w_-]+)\s*=\s*(.*)/) {
184 my ($k, $v) = ($1, $2);
185 $k = lc($k) if $param{ci};
186
187 if ($include) {
188 if ($k eq 'path') {
189 $err += readconfig($v, $conf, include => 1, @_);
190 } elsif ($k eq 'pathopt') {
191 $err += readconfig($v, $conf, include => 1, @_)
192 if -f $v;
193 } elsif ($k eq 'glob') {
194 foreach my $file (bsd_glob($v, 0)) {
195 $err += readconfig($file, $conf, include => 1, @_);
196 }
197 } else {
198 err("$file:$line: unknown keyword");
199 ++$err;
200 }
201 next;
202 }
203
204 if (defined($kw)) {
205 my $x = $kw->{$k};
206 if (!defined($x)) {
207 err("$file:$line: unknown keyword $k");
208 ++$err;
209 next;
210 } elsif (ref($x) eq 'HASH') {
211 if (exists($x->{re})) {
212 if ($v !~ /$x->{re}/) {
213 err("$file:$line: invalid value for $k");
214 ++$err;
215 next;
216 }
217 if (exists($x->{check})
218 and !&{$x->{check}}($k, $v, "$file:$line")) {
219 ++$err;
220 next;
221 }
222 } elsif (exists($x->{check})) {
223 if (!&{$x->{check}}($k, $v, "$file:$line")) {
224 ++$err;
225 next;
226 }
227 } elsif (!exists($x->{var}) and
228 !exists($x->{parser}) and
229 !exists($x->{mandatory})) {
230 err("$file:$line: unknown keyword $k");
231 ++$err;
232 next;
233 }
234 if (exists($x->{parser})
235 and !&{$x->{parser}}($k, \$v, "$file:$line")) {
236 ++$err;
237 next;
238 }
239 }
240 }
241
242 $section->{$k} = $v;
243 } else {
244 err("$file:$line: malformed line");
245 ++$err;
246 next;
247 }
76 } 248 }
77 close($fd); 249 close $fd;
250 exit(EX_CONFIG) if $err;
78} 251}
@@ -85,3 +258,3 @@ sub valid_domain_name {
85 my $name = shift; 258 my $name = shift;
86 $name =~ s/^\*\.// if ($allow_wildcard_domains); 259 $name =~ s/^\*\.// if ($config{core}{'allow-wildcards'});
87 foreach my $label (split(/\./, $name)) { 260 foreach my $label (split(/\./, $name)) {
@@ -99,5 +272,5 @@ sub get_cnames($) {
99 272
100 foreach my $file (glob "$dir/$confpat") { 273 foreach my $file (glob "$dir/$config{core}{'apache-config-pattern'}") {
101 next unless (-f $file); 274 next unless (-f $file);
102 print STDERR "$script: reading cnames from $file\n" if ($debug > 2); 275 print STDERR "$progname: reading cnames from $file\n" if ($debug > 2);
103 276
@@ -116,9 +289,9 @@ sub get_cnames($) {
116 unless (valid_domain_name($name)) { 289 unless (valid_domain_name($name)) {
117 print STDERR "$script: $file:$line: $name: invalid domain name\n"; 290 print STDERR "$progname: $file:$line: $name: invalid domain name\n";
118 next; 291 next;
119 } 292 }
120 foreach my $z (@zone) { 293 foreach my $z (keys %{$config{zone}}) {
121 if ($name =~ /.*\.$z$/) { 294 if ($name =~ /.*\.$z$/) {
122 if ($name =~ /^\*\.(.+)/ and $1 eq $z) { 295 if ($name =~ /^\*\.(.+)/ and $1 eq $z) {
123 print STDERR "$script: $file:$line: $name: first-level wildcard\n"; 296 print STDERR "$progname: $file:$line: $name: first-level wildcard\n";
124 next; 297 next;
@@ -143,3 +316,3 @@ sub read_cname_list($) {
143 if (-f $file) { 316 if (-f $file) {
144 open(my $fd, "<", $file) or abend(1, "cannot open $file: $!"); 317 open(my $fd, "<", $file) or abend(EX_NOINPUT, "cannot open $file: $!");
145 while (<$fd>) { 318 while (<$fd>) {
@@ -163,3 +336,4 @@ sub write_cname_list {
163 336
164 open(my $fd, ">", $file) or abend(1, "cannot open $file for writing: $!"); 337 open(my $fd, ">", $file) or
338 abend(EX_CANTCREAT, "cannot open $file for writing: $!");
165 foreach my $h (sort keys %hash) { 339 foreach my $h (sort keys %hash) {
@@ -171,3 +345,2 @@ sub write_cname_list {
171sub ns_update { 345sub ns_update {
172 my $resolver = shift;
173 my $name = shift; 346 my $name = shift;
@@ -177,3 +350,5 @@ sub ns_update {
177 350
178 print STDERR "$script: updating $name in $domain: ". 351 my $resolver = get_zone_resolver($domain);
352
353 print STDERR "$progname: updating $name in $domain: ".
179 join(',', map { "$_ => $hash{$_}" } keys %hash) . 354 join(',', map { "$_ => $hash{$_}" } keys %hash) .
@@ -191,3 +366,3 @@ sub ns_update {
191 } 366 }
192 $update->sign_tsig(@tsig_args) if ($#tsig_args >= 0); 367 zone_sign_tsig($update, $domain);
193 my $reply = $resolver->send($update); 368 my $reply = $resolver->send($update);
@@ -195,5 +370,5 @@ sub ns_update {
195 if ($reply->header->rcode eq 'NOERROR') { 370 if ($reply->header->rcode eq 'NOERROR') {
196 print STDERR "$script: update successful\n" if ($debug>3); 371 print STDERR "$progname: update successful\n" if ($debug>3);
197 } elsif ($ignorerr{$reply->header->rcode}) { 372 } elsif ($ignorerr{$reply->header->rcode}) {
198 print STDERR "$script: ignoring " . $reply->header->rcode . ': ' . 373 print STDERR "$progname: ignoring " . $reply->header->rcode . ': ' .
199 join(',', map { "$_ => $hash{$_}" } keys %hash) . "\n" 374 join(',', map { "$_ => $hash{$_}" } keys %hash) . "\n"
@@ -205,3 +380,3 @@ sub ns_update {
205 $reply->header->rcode); 380 $reply->header->rcode);
206 $status = 2; 381 $status = EX_NOTUPDATED;
207 return 0; 382 return 0;
@@ -213,3 +388,3 @@ sub ns_update {
213 $resolver->errorstring); 388 $resolver->errorstring);
214 $status = 2; 389 $status = EX_NOTUPDATED;
215 return 0; 390 return 0;
@@ -219,2 +394,33 @@ sub ns_update {
219 394
395sub get_zone_resolver {
396 my $zone = shift;
397 unless (defined($config{zone}{$zone}{resolver})) {
398 my $resolver = new Net::DNS::Resolver;
399 $resolver->nameservers($config{zone}{$zone}{server})
400 if defined $config{zone}{$zone}{server};
401 $config{zone}{$zone}{resolver} = $resolver;
402 }
403 return $config{zone}{$zone}{resolver};
404}
405
406sub zone_sign_tsig {
407 my ($update, $zone) = @_;
408 my @tsig_args;
409
410 my $zcfg = $config{zone}{$zone};
411 if (exists($zcfg->{'ns-key-file'})) {
412 push @tsig_args, split(/\s+/, $zcfg->{'ns-key-file'});
413 } elsif (exists($zcfg->{'ns-key'})) {
414 push @tsig_args, @{$zcfg->{'ns-key'}};
415 }
416 if ($#tsig_args == -1) {
417 if (exists($config{core}{'ns-key-file'})) {
418 push @tsig_args, split(/\s+/, $config{core}{'ns-key-file'});
419 } elsif (exists($config{core}{'ns-key'})) {
420 push @tsig_args, @{$config{core}{'ns-key'}};
421 }
422 }
423 $update->sign_tsig(@tsig_args) if ($#tsig_args >= 0);
424}
425
220sub update_cnames_from_hash { 426sub update_cnames_from_hash {
@@ -222,13 +428,10 @@ sub update_cnames_from_hash {
222 428
223 print STDERR "$script: " . keys(%hash) . " names to update\n" 429 print STDERR "$progname: " . keys(%hash) . " names to update\n"
224 if ($debug > 2); 430 if ($debug > 2);
225 my %oldhash = read_cname_list($cnamelist); 431 my %oldhash = read_cname_list($config{core}{cache});
226 my @namelist = sort(keys(%hash)); 432 my @namelist = sort(keys(%hash));
227 if (join(",", @namelist) eq join(".", sort(keys(%oldhash)))) { 433 if (join(",", @namelist) eq join(",", sort(keys(%oldhash)))) {
228 print STDERR "$script: nothing to update\n" if ($debug); 434 print STDERR "$progname: nothing to update\n" if ($debug);
229 return; 435 return;
230 } 436 }
231
232 my $resolver = new Net::DNS::Resolver;
233 $resolver->nameservers($nameserver) if defined($nameserver);
234 437
@@ -239,3 +442,3 @@ sub update_cnames_from_hash {
239 } else { 442 } else {
240 ns_update($resolver, $name, $hash{$name}, 443 ns_update($name, $hash{$name},
241 prereq => yxdomain($name), 444 prereq => yxdomain($name),
@@ -243,6 +446,6 @@ sub update_cnames_from_hash {
243 ignore => 'NXDOMAIN'); 446 ignore => 'NXDOMAIN');
244 print STDERR "$script: $name $ttl CNAME $host\n" if ($debug); 447 print STDERR "$progname: $name $config{core}{ttl} CNAME $config{core}{hostname}\n" if ($debug);
245 delete $hash{$name} 448 delete $hash{$name}
246 unless ns_update($resolver, $name, $hash{$name}, 449 unless ns_update($name, $hash{$name},
247 update => rr_add("$name $ttl CNAME $host")); 450 update => rr_add("$name $config{core}{ttl} CNAME $config{core}{hostname}"));
248 } 451 }
@@ -251,3 +454,3 @@ sub update_cnames_from_hash {
251 foreach $name (keys %oldhash) { 454 foreach $name (keys %oldhash) {
252 ns_update($resolver, $name, $oldhash{$name}, 455 ns_update($name, $oldhash{$name},
253 prereq => yxrrset("$name CNAME"), 456 prereq => yxrrset("$name CNAME"),
@@ -257,3 +460,3 @@ sub update_cnames_from_hash {
257 460
258 write_cname_list($cnamelist, %hash); 461 write_cname_list($config{core}{cache}, %hash);
259} 462}
@@ -265,13 +468,10 @@ sub update_cnames_from_dir($) {
265sub nscleanup { 468sub nscleanup {
266 print STDERR "$script: Removing DNS CNAME records\n" if ($debug); 469 print STDERR "$progname: Removing DNS CNAME records\n" if ($debug);
267 470
268 my $resolver = new Net::DNS::Resolver; 471 my %hash = read_cname_list($config{core}{cache});
269 $resolver->nameservers($nameserver) if defined($nameserver);
270
271 my %hash = read_cname_list($cnamelist);
272 foreach my $name (keys %hash) { 472 foreach my $name (keys %hash) {
273 print STDERR "$script: removing $name from $hash{$name}\n" 473 print STDERR "$progname: removing $name from $hash{$name}\n"
274 if ($debug); 474 if ($debug);
275 delete $hash{$name} 475 delete $hash{$name}
276 if ns_update($resolver, $name, $hash{$name}, 476 if ns_update($name, $hash{$name},
277 prereq => yxrrset("$name CNAME"), 477 prereq => yxrrset("$name CNAME"),
@@ -281,48 +481,161 @@ sub nscleanup {
281 481
282 write_cname_list($cnamelist, %hash); 482 write_cname_list($config{core}{cache}, %hash);
483}
484
485###
486sub com_start {
487 abend(EX_USAGE, "too many arguments") unless $#_ == 0;
488 nscleanup();
489 com_reload(@_);
490}
491
492sub com_reload {
493 abend(EX_USAGE, "too many arguments") unless $#_ == 0;
494 my $confdir = -d "$config{core}{'apache-config-directory'}/sites-enabled"
495 ? "$config{core}{'apache-config-directory'}/sites-enabled"
496 : $config{core}{'apache-config-directory'};
497 my %cnames = get_cnames($confdir);
498 update_cnames_from_hash(%cnames);
499 print STDERR "$progname: no cnames defined\n" unless (keys(%cnames) > 0);
283} 500}
284 501
502sub com_stop {
503 abend(EX_USAGE, "too many arguments") unless $#_ == 0;
504 nscleanup;
505}
506
507sub com_status {
508 err("status command ignored");
509 my %stat;
510
511 my %hash = read_cname_list($config{core}{cache});
512 while (my ($name, $zone) = each %hash) {
513 #$name =~ s/.$zone$//;
514 push @{${stat}{$zone}}, $name;
515 }
516
517 foreach my $zone (sort(keys %stat)) {
518 print "Names in zone $zone:\n";
519 foreach my $name (sort(@{$stat{$zone}})) {
520 print " $name\n";
521 }
522 }
523}
285 524
286### 525###
287($script = $0) =~ s/.*\///; 526($progname = $0) =~ s/.*\///;
527
528my %comtab = (
529 start => \&com_start,
530 restart => \&com_reload,
531 'force-restart' => \&com_start,
532 reload => \&com_reload,
533 stop => \&com_stop,
534 status => \&com_status
535);
536
537sub getcom {
538 my $com = shift;
539
540 while (defined($comtab{$com}) and ref($comtab{$com}) ne 'CODE') {
541 $com = $comtab{$com};
542 }
543 die "internal error: unresolved command alias" unless defined $com;
544 return $comtab{$com} if defined $comtab{$com};
545
546 my @v = map { /^$com/ ? $_ : () } sort keys %comtab;
547 if ($#v == -1) {
548 abend(EX_USAGE, "unrecognized command");
549 } elsif ($#v > 0) {
550 abend(EX_USAGE, "ambiguous command: ".join(', ', @v));
551 }
552 return getcom($v[0]);
553}
554
288 555
289## Read configuration 556## Read configuration
290read_config_file($ENV{'VHOSTCNAME_CONF'} ? 557sub parse_ns_key {
291 $ENV{'VHOSTCNAME_CONF'} : $config_file); 558 my ($var, $ref, $loc) = @_;
559 my @result;
560 if ($$ref =~ /(.+?)=(.+)/) {
561 push @result, $1, $2;
562 $$ref = \@result;
563 } else {
564 err("$loc: $var argument must be must be NAME=KEY");
565 return 0;
566 }
567 return 1;
568}
292 569
293GetOptions("help" => \$man, 570sub parse_boolean {
294 "h" => \$help, 571 my ($var, $ref, $loc) = @_;
572 my %bool = ( yes => 1,
573 no => 0,
574 true => 1,
575 false => 0,
576 t => 1,
577 nil => 0,
578 f => 0,
579 on => 1,
580 off => 0,
581 1 => 1,
582 0 => 0);
583
584 my $s = $$ref;
585 $s =~ tr/A-Z/a-z/;
586 if (exists($bool{$s})) {
587 $$ref = $bool{$s};
588 } else {
589 err("$loc: argument must be boolean");
590 return 0;
591 }
592 return 1;
593}
594
595my %kw = (
596 core => {
597 section => {
598 'apache-config-directory' => 1,
599 'apache-config-pattern' => 1,
600 'cache' => 1,
601 'server' => 1,
602 'ttl' => 1,
603 'ns-key' => { parser => \&parse_ns_key },
604 'ns-key-file' => 1,
605 'hostname' => 1,
606 'allow-wildcards' => { parser => \&parse_boolean }
607 },
608 },
609 zone => {
610 section => {
611 'server' => 1,
612 'ttl' => 1,
613 'ns-key' => { parser => \&parse_ns_key },
614 'ns-key-file' => 1
615 },
616 }
617);
618
619GetOptions("help" => sub {
620 pod2usage(-exitstatus => EX_OK, -verbose => 2);
621 },
622 "h" => sub {
623 pod2usage(-message => "$progname: $progdescr",
624 -exitstatus => EX_OK);
625 },
626 "usage" => sub {
627 pod2usage(-exitstatus => EX_OK, -verbose => 0);
628 },
629
295 "debug|d+" => \$debug, 630 "debug|d+" => \$debug,
296 "dry-run|n" => \$dry_run, 631 "dry-run|n" => \$dry_run,
297 "hostname|H=s" => \$host, 632 "config|c=s" => \$config_file,
298 "apache-config-pattern=s" => \$confpat, 633 ) or exit(EX_USAGE);
299 "apache-config-directory=s" => \$confdir, 634
300 "ns-key-file=s" => sub { 635readconfig($config_file, \%config, kw => \%kw);
301 abend(3, "NS key already set") if ($#tsig_args >= 0); 636
302 push @tsig_args, $_[1]; 637unless (defined($config{core}{'apache-config-directory'})) {
303 },
304 "ns-key=s" => sub {
305 abend(3, "NS key already set") if ($#tsig_args >= 0);
306 if ($_[1] =~ /(.+?)=(.+)/) {
307 push @tsig_args, $1;
308 push @tsig_args, $2;
309 } else {
310 abend(3, "argument to --ns-key must be NAME=KEY");
311 }
312 },
313 "cname-file=s" => \$cnamelist,
314 "zone|z=s@" => \@zone,
315 "ttl=i" => \$ttl,
316 "server=s" => \$nameserver,
317 "allow-wildcard-domains" => \$allow_wildcard_domains
318 ) or exit(3);
319
320pod2usage(-message => "$script: update DNS from Apache virtual host configuration",
321 -exitstatus => 0) if $help;
322pod2usage(-exitstatus => 0, -verbose => 2) if $man;
323
324unless (defined($confdir)) {
325 foreach my $dir ("/etc/apache2", "/etc/httpd") { 638 foreach my $dir ("/etc/apache2", "/etc/httpd") {
326 if (-e "$dir/sites-enabled" and -e "$dir/sites-available") { 639 if (-e "$dir/sites-enabled" and -e "$dir/sites-available") {
327 $confdir = $dir; 640 $config{core}{'apache-config-directory'} = $dir;
328 last; 641 last;
@@ -330,3 +643,3 @@ unless (defined($confdir)) {
330 if (-e "$dir/vhosts.d") { 643 if (-e "$dir/vhosts.d") {
331 $confdir = "$dir/vhosts.d"; 644 $config{core}{'apache-config-directory'} = "$dir/vhosts.d";
332 last; 645 last;
@@ -334,9 +647,10 @@ unless (defined($confdir)) {
334 } 647 }
335 abend(3, 648 abend(EX_CONFIG,
336 "don't know where virtual host configurations are located; use --apache-config-directory option") 649 "don't know where virtual host configurations are located; define apache-config-directory")
337 unless defined($confdir); 650 unless defined($config{core}{'apache-config-directory'});
338} 651}
339 652
340$host = hostname() unless defined($host); 653$config{core}{hostname} = hostname() unless defined($config{core}{hostname});
341push(@zone, $host) if ($#zone == -1); 654$config{zone}{$host} = {} unless exists $config{zone};
655
342$debug++ if ($dry_run); 656$debug++ if ($dry_run);
@@ -344,4 +658,4 @@ $debug++ if ($dry_run);
344if ($#ARGV == -1) { 658if ($#ARGV == -1) {
345 abend(3, "command not given") unless ($ENV{'DIREVENT_FILE'}); 659 abend(EX_USAGE, "command not given") unless ($ENV{'DIREVENT_FILE'});
346 print STDERR "$script: started as direvent handler for " . 660 print STDERR "$progname: started as direvent handler for " .
347 "$ENV{'DIREVENT_GENEV_NAME'} on $ENV{'DIREVENT_FILE'}\n" 661 "$ENV{'DIREVENT_GENEV_NAME'} on $ENV{'DIREVENT_FILE'}\n"
@@ -350,5 +664,6 @@ if ($#ARGV == -1) {
350 my $update_dir; 664 my $update_dir;
665 my $confdir = $config{core}{'apache-config-directory'};
351 if (-d "$confdir/sites-available" && -d "$confdir/sites-enabled") { 666 if (-d "$confdir/sites-available" && -d "$confdir/sites-enabled") {
352 if ($cwd eq "$confdir/sites-available") { 667 if ($cwd eq "$confdir/sites-available") {
353 foreach my $file (glob "$confdir/sites-enabled/$confpat") { 668 foreach my $file (glob "$confdir/sites-enabled/$config{core}{'apache-config-pattern'}") {
354 next unless (-l $file); 669 next unless (-l $file);
@@ -368,18 +683,7 @@ if ($#ARGV == -1) {
368 update_cnames_from_dir($update_dir) if defined($update_dir); 683 update_cnames_from_dir($update_dir) if defined($update_dir);
369} elsif ($#ARGV != 0) {
370 abend(3, "too many arguments");
371} elsif ($ARGV[0] =~ /^start|restart|force-restart|reload$/) {
372 nscleanup if ($ARGV[0] =~ /start$/);
373 my %cnames = get_cnames(-d "$confdir/sites-enabled" ?
374 "$confdir/sites-enabled" : $confdir);
375 update_cnames_from_hash(%cnames);
376 print STDERR "$script: no cnames defined\n" unless (keys(%cnames) > 0);
377} elsif ($ARGV[0] eq "stop") {
378 nscleanup;
379} elsif ($ARGV[0] eq "status") {
380 err("status command ignored");
381} else {
382 abend(3, "invalid command, try $script --help for more info");
383} 684}
384 685
686my $command = getcom($ARGV[0]);
687&{$command}(@ARGV);
688
385exit($status); 689exit($status);
@@ -433,2 +737,8 @@ corresponding B<direvent.conf>(5) entry:
433 737
738Unless the program is started as a B<direvent>(8) handler, exactly one
739command must be given in the command line. A command may be supplied
740in full or abbreviated form. Any unambiguous abbreviation is allowed.
741
742Available commands are:
743
434=over 4 744=over 4
@@ -437,4 +747,4 @@ corresponding B<direvent.conf>(5) entry:
437 747
438Scan the apache configuration files and register all server names matching 748Scan the apache configuration files and register all server names that
439the supplied zones. 749match the configured zones.
440 750
@@ -444,5 +754,14 @@ Deregister all hostnames registered previously.
444 754
445=item B<restart>, B<force-restart>, B<reload> 755=item B<restart>, B<reload>
446 756
447Same as running B<vhostcname stop; vhostcname start>. 757Builds a list of names from the apache configuration (I<apache-list>) and
758compares them with the names registered at the previous run (I<cache>). If
759the two lists differ, the names present in I<apache-list>, but absent in
760I<cache> are registered. The names present in I<cache>, but lacking in
761I<apache-list> are deleted from the DNS.
762
763=item B<force-restart>
764
765Deregister all hostnames registered previously, rescan Apache files, and
766register all names that match the configured zones.
448 767
@@ -450,3 +769,3 @@ Same as running B<vhostcname stop; vhostcname start>.
450 769
451Ignored 770Displays registered host names.
452 771
@@ -458,9 +777,52 @@ Ignored
458 777
459=item B<--allow-wildcard-domains> 778=item B<-c>, B<--config=>I<FILE>
779
780Read configuration from I<FILE> instead of the default location
781(F</etc/vhostcname.conf>).
782
783=item B<-d>, B<--debug>
460 784
461Allow the use of wildcard (B<*>). When this option is in effect, a wildcard 785Increases the debug level. Multiple B<-d> options are allowed.
462will be allowed if it is the very first label in a domain name and it is
463separated from the base zone (see the B<--zone> option) by one or more labels.
464 786
465=item B<--apache-config-directory=>I<DIR> 787=item B<-n>, B<--dry-run>,
788
789Enables I<dry-run> mode: print what would have been done without actually
790doing it.
791
792=item B<--help>
793
794Displays B<vhostcname> man page.
795
796=item B<-h>
797
798Displays a short help summary and exits.
799
800=item B<--usage>
801
802Displays a short command line syntax reminder.
803
804=back
805
806=head1 CONFIGURATION FILE
807
808Configuration is read from F</etc/vhostcname.conf> or a file specified
809by the B<--config> (B<-c>) command line option. The file consists of
810a number of variable assignments (I<variable> B<=> I<value>), grouped into
811sections. Whitespace is ignored, except that it serves to separate input
812tokens. I<value> is read verbatim, including eventual whitespace characters
813that can appear within it.
814
815A section begins with the line containing its name within square brackets
816(e.g. B<[core]>). The name can be followed by one or more arguments, if
817the section semantics requires so (e.g. B<[zone example.com]>).
818
819The following sections are recognized:
820
821=over 4
822
823=item B<[core]>
824
825=over 8
826
827=item B<apache-config-directory => I<DIR>
466 828
@@ -476,3 +838,3 @@ terminate if unable to do that.
476 838
477=item B<--apache-config-pattern=>I<GLOB> 839=item B<apache-config-pattern => I<PATTERN>
478 840
@@ -480,27 +842,10 @@ Shell globbing pattern for virtual host configuration files. By default,
480B<*> is used, meaning that B<vhostcname> will scan all files in the 842B<*> is used, meaning that B<vhostcname> will scan all files in the
481configuration directory. 843configuration directory (note: that includes backup copies too!).
482 844
483=item B<--cname-file=>I<NAME> 845=item B<cache => I<FILE>
484 846
485Name of the file where B<vhostcname> will keep successfully registered 847Name of the cache file where B<vhostcname> keeps successfully registered
486host names. Default is B</var/run/vhostcname.cache>. 848host names. Default is B</var/run/vhostcname.cache>.
487
488=item B<-d>, B<--debug>
489
490Increases the debug level. Multiple B<-d> options are allowed.
491
492=item B<-n>, B<--dry-run>,
493
494Enables I<dry-run> mode: print what would have been done without actually
495doing it.
496
497=item B<--help>
498
499Displays B<vhostcname> man page.
500
501=item B<-h>
502 849
503Displays a short help summary and exits. 850=item B<hostname => I<HOSTNAME>
504
505=item B<-H>, B<--hostname>=I<NAME>
506 851
@@ -509,15 +854,21 @@ determine it.
509 854
510=item B<--ns-key=>I<NAME>=I<KEY> 855=item B<allow-wildcards => I<BOOL>
511 856
512Define the TSIG key. 857Allow the use of wildcard (B<*>) in host names. When this option is in
513 858effect, a wildcard will be allowed if it is the very first label in a domain
514=item B<--ns-key-file=>I<KEYFILE> 859name and it is separated from the base zone (see the B<zone> section) by one
860more labels.
515 861
516Name of the key file. The argument should be the name of a file 862I<BOOL> is one of B<yes>, B<true>, B<t>, B<on>, or B<1> to allow wildcards,
517generated by the B<dnssec-keygen> utility. Either B<.key> or B<.private> 863or one of B<no>, B<false>, B<f>, B<nil>, B<off>, B<0> to disallow them (the
518file can be used. 864default).
519 865
520This option cannot be used together with B<--ns-key>. 866=back
521 867
522=item B<--server=>I<NAME> 868The following variables provide defaults for zones that lack the
869corresponding settings:
870
871=over 8
872
873=item B<server => I<HOST>
523 874
@@ -525,41 +876,58 @@ Name of the DNS server to use. Normally B<vhostcname> determines what server
525to use based on the B<SOA> record of the zone to be updated, so this option 876to use based on the B<SOA> record of the zone to be updated, so this option
526is rarely needed. 877is rarely needed.
527 878
528=item B<--ttl=>I<TIME> 879=item B<ttl => I<SECONDS>
529 880
530TTL value for new DNS records. Default is 3600. 881TTL value for new DNS records. Default is 3600.
531 882
532=item B<--zone=>I<NAME> 883=item B<ns-key => I<NAME>=I<HASH>
533 884
534Name of the zone which B<vhostcname> can update. Multiple B<--zone> options 885Defines the TSIG key.
535can be given.
536 886
537If no B<--zone> option is given, B<vhostcname> will take hostname as the 887=item B<ns-key-file => I<FILE>
538name of the zone.
539 888
889Name of the key file. The argument should be the name of a file
890generated by the B<dnssec-keygen> utility. Either B<.key> or B<.private>
891file can be used.
892
893If both <ns-key> and B<ns-key-file> are used, the latter is given preference.
894
540=back 895=back
541 896
542=head1 CONFIGURATION FILE 897=item B<[zone I<NAME>]>
543 898
544If the file B<etc/vhostcname.conf> exists, the program will read its 899The B<zone> section informs B<vhostcname> that it should handle names
545configuration from it. A familiar UNIX configuration format is used. 900in zone I<NAME>. Any number of B<[zone]> sections can be defined. If
546Empty lines and UNIX comments are ignored. Each non-empty line is either an 901none is defined, B<vhostcname> will take hostname as the name of the zone
547option name, or option assignment, i.e. B<opt>=B<val>, with any amount of 902to update.
548optional whitespace around the equals sign. Valid option names are
549the same as the long command line options, but without the leading B<-->.
550For example:
551 903
552 zone = vhost.example.com 904The variables in a B<zone> section define parameters to be used for that
553 ns-key-file = /etc/bind/Kvhost+157+43558.key 905particular zone. As such, none of them is mandatory. If the zone I<NAME>
554 ttl = 3600 906uses default settings (or settings, defined in the B<[core]> section),
907the section can be empty.
908
909=over 8
910
911=item B<server => I<HOST>
555 912
556=head1 ENVIRONMENT 913Name of the DNS server to use when updating this zone.
914
915=item B<ttl => I<SECONDS>
557 916
558=over 4 917TTL for records in this zone.
918
919=item B<ns-key => I<NAME>=I<HASH>
559 920
560=item B<VHOSTCNAME_CONF> 921TSIG key.
922
923=item B<ns-key-file => I<FILE>
561 924
562The name of the configuration file to use instead of the default 925Name of the key file. The argument should be the name of a file
563F</etc/vhostcname.conf>. 926generated by the B<dnssec-keygen> utility. Either B<.key> or B<.private>
927file can be used.
564 928
929If both <ns-key> and B<ns-key-file> are used, the latter is given preference.
930
931=back
932
565=back 933=back
@@ -576,20 +944,22 @@ Success
576 944
577Operating system error (unable to open file, etc.) 945Some of the host names could not be updated.
946
947=item 64
578 948
579=item 2 949Command line usage error.
580 950
581Some of the host names could not be registered. 951=item 66
582
583=item 3
584 952
585Command line usage error 953Required input file cannot be opened.
586 954
587=back 955=item 73
588 956
589=head1 BUGS 957Required output file cannot be created or written.
958
959=item 78
590 960
591Only one key file can be given. This means that if you use multiple 961Configuration error.
592B<--zone> options, all zones must be configured to accept the same
593DNSSEC key. Ditto for the B<--server> option.
594 962
963=back
964
595=head1 SEE ALSO 965=head1 SEE ALSO

Return to:

Send suggestions and report system problems to the System administrator.