diff options
-rw-r--r-- | Makefile.PL | 4 | ||||
-rwxr-xr-x | acmeman | 2 | ||||
-rw-r--r-- | lib/App/Acmeman/Config.pm | 7 | ||||
-rw-r--r-- | lib/App/Acmeman/Source/Apache.pm | 232 |
4 files changed, 82 insertions, 163 deletions
diff --git a/Makefile.PL b/Makefile.PL index 8976da9..846d342 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -32,7 +32,9 @@ my %makefile_args = ( 'Text::ParseWords' => 3.27, 'Data::Dumper' => 0, 'Socket' => 0, - 'Sys::Hostname' => 1.16 + 'Sys::Hostname' => 1.16, + 'Apache::Defaults' => 1.02, + 'Apache::Config::Preproc' => 1.03 }, MIN_PERL_VERSION => 5.006, @@ -1,6 +1,6 @@ #!/bin/sh #! -*-perl-*- -eval 'exec perl -x -wS $0 ${1+"$@"}' +eval 'exec perl -x -S $0 ${1+"$@"}' if 0; use strict; diff --git a/lib/App/Acmeman/Config.pm b/lib/App/Acmeman/Config.pm index 25c940f..6badae3 100644 --- a/lib/App/Acmeman/Config.pm +++ b/lib/App/Acmeman/Config.pm @@ -95,8 +95,11 @@ sub mangle { ++$err; next; } - $obj->configure($self); - $self->set(qw(core source), $obj); + if ($obj->configure($self)) { + $self->set(qw(core source), $obj); + } else { + ++$err; + } } } diff --git a/lib/App/Acmeman/Source/Apache.pm b/lib/App/Acmeman/Source/Apache.pm index f35c267..042956a 100644 --- a/lib/App/Acmeman/Source/Apache.pm +++ b/lib/App/Acmeman/Source/Apache.pm @@ -6,11 +6,12 @@ use Carp; use feature 'state'; use File::Path qw(make_path); use File::Spec; -use IPC::Open3; use App::Acmeman::Apache::Layout; use App::Acmeman::Log qw(:all); use parent 'App::Acmeman::Source'; use Getopt::Long qw(GetOptionsFromArray :config gnu_getopt no_ignore_case); +use Apache::Defaults; +use Apache::Config::Preproc; sub new { my $class = shift; @@ -18,6 +19,9 @@ sub new { GetOptionsFromArray(\@_, 'server-root=s' => \$server_root); my $self = bless { _layout => new App::Acmeman::Apache::Layout(@_) }, $class; + unless ($server_root) { + $server_root = Apache::Defaults->new->server_root; + } $self->server_root($server_root) if $server_root; return $self; } @@ -33,126 +37,88 @@ sub scan { sub dequote { my ($self, $arg) = @_; - $arg =~ s{^"(.*?)"$}{$1}; + if (defined($arg) && $arg =~ s{^"(.*?)"$}{$1}) { + $arg =~ s{\\([\\"])}{$1}g; + } return $arg; } sub examine_http_config { my ($self, $file) = @_; - use constant { - STATE_INITIAL => 0, # Initial state - STATE_VIRTUAL_HOST => 1, # In VirtualHost block - STATE_USE_CHALLENGE => 2, # Ditto, but challenge macro was used - STATE_USE_REFERENCE => 3, # Ditto, but reference macro was used - STATE_MACRO_CHALLENGE => 4, # In LetsEncryptChallenge macro - STATE_MACRO_SSL => 5 - }; - - state $state = STATE_INITIAL; - - debug(3, "reading apache configuration file \"$file\""); - if (open(my $fd, '<', $file)) { - my $server_name; - my @server_aliases; - my $reference; - my $line; - - while (<$fd>) { - ++$line; - chomp; - s/^\s+//; - next if /^(#.*)?$/; - if (/^include(optional)?\s+(.+?)\s*$/i) { - #debug(3, "$file:$line: state $state: Include".($1||'')." $2"); - $self->http_include($self->dequote($2), defined($1)); - next; - } - - if ($state == STATE_INITIAL) { - if (/^<VirtualHost/i) { - $state = STATE_VIRTUAL_HOST; - $server_name = undef; - @server_aliases = (); - $reference = undef; - } elsif (/^ServerRoot\s+(.+)/i) { - $self->server_root($self->dequote($1)); - } elsif (/^<(?:(?i)Macro)\s+LetsEncryptChallenge/) { - $state = STATE_MACRO_CHALLENGE; - } elsif (/^<(?:(?i)Macro)\s+LetsEncryptSSL\s+(.+?)\s*>/) { - $state = STATE_MACRO_SSL; - $self->set(qw(files apache argument), $1); - } - } elsif ($state == STATE_VIRTUAL_HOST - || $state == STATE_USE_CHALLENGE - || $state == STATE_USE_REFERENCE) { - if (m{</VirtualHost}i) { - unshift @server_aliases, $server_name - if defined $server_name; - if (@server_aliases) { - if ($state == STATE_USE_CHALLENGE) { - my $cn = shift @server_aliases; - $self->define_domain($cn); - $self->define_alias($cn, @server_aliases); - debug(3, "$file:$line: will handle ". - join(',', $cn, @server_aliases)); - } elsif ($state == STATE_USE_REFERENCE) { - $self->set('domain', $reference, - 'files', 'apache'); - $self->define_alias($reference, @server_aliases); - } - } - $state = STATE_INITIAL; - } elsif (/^(?:(?i)Use)\s+LetsEncryptChallenge/) { - if ($state == STATE_VIRTUAL_HOST) { - $state = STATE_USE_CHALLENGE; - } elsif ($state == STATE_USE_CHALLENGE) { - error("$file:$line: duplicate use of LetsEncryptChallenge"); - } else { - error("$file:$line: LetsEncryptChallenge and LetsEncryptReference can't be used together"); - } - } elsif (/^(?:(?i)Use)\s+LetsEncryptReference\s+(.+?)\s*$/) { - if ($state == STATE_VIRTUAL_HOST) { - $state = STATE_USE_REFERENCE; - $reference = $1; - } elsif ($state == STATE_USE_REFERENCE) { - error("$file:$line: duplicate use of LetsEncryptReference"); - } else { - error("$file:$line: LetsEncryptChallenge and LetsEncryptReference can't be used together"); - } - } elsif (/^(?:(?i)ServerName)\s+(\S+)/) { - $server_name = $self->dequote($1); - } elsif (/^(?:(?i)ServerAlias)\s+(.+)\s*$/) { - push @server_aliases, - map { /^\*\./ ? () : $self->dequote($_) } - split /\s+/, $1; - } - } elsif ($state == STATE_MACRO_CHALLENGE) { - if (m{^</macro}i) { - $state = STATE_INITIAL; - } elsif (m{^(?:(?i)Alias)\s+/.well-known/acme-challenge\s+(.+)}) { - my $dir = $self->dequote($1); - $dir =~ s{/.well-known/acme-challenge$}{}; - $self->set(qw(core rootdir), $dir); - debug(3, "ACME challenge root dir: $dir"); - } - } elsif ($state == STATE_MACRO_SSL) { - if (m{^</macro}i) { - $state = STATE_INITIAL; - } elsif (/(?:(?i)SSLCertificate((?:Key)|(?:Chain))?File)\s+(.+)/) { + my $app = new Apache::Config::Preproc( + $file, + -expand => [ 'compact', + { 'include' => [ server_root => $self->server_root ] }, + { 'macro' => [ + 'keep' => [ qw(LetsEncryptChallenge + LetsEncryptReference + LetsEncryptSSL) + ] + ] + } + ]) + or do { + error($Apache::Admin::Config::ERROR); + return 0; + }; + + foreach my $sect ($app->section(-name => "macro")) { + if ($sect->value =~ m{^(?ix)letsencryptssl + \s+ + (.+)}) { + $self->set(qw(files apache argument), $1); + map { + if ($_->name =~ m{^(?ix) + SSLCertificate((?:Key)|(?:Chain))?File}) { my %t = ( '' => 'certificate-file', key => 'key-file', chain => 'ca-file' ); - $self->set(qw(files apache), $t{lc($1||'')}, $2); + $self->set(qw(files apache), $t{lc($1||'')}, + $self->dequote($_->value)); + } + } $sect->directive(); + } elsif ($sect->value =~ m{^(?ix)letsencryptchallenge$}) { + foreach my $alias ($sect->directive('alias')) { + if ($alias->value =~ m{^/.well-known/acme-challenge\s+(.+)}) { + my $dir = $self->dequote($1); + $dir =~ s{/.well-known/acme-challenge$}{}; + $self->set(qw(core rootdir), $dir); + debug(3, "ACME challenge root dir: $dir"); } } } - close $fd; - } else { - error("can't open file \"$file\": $!"); - return 0; + } + + foreach my $sect ($app->section(-name => "virtualhost")) { + my ($server_name) = (map { $self->dequote($_->value) } + $sect->directive('servername')); + my @server_aliases = map { $self->dequote($_->value) } + $sect->directive('serveralias'); + my @d = map { + if ($_->value =~ m{^(?ix) + (?:letsencrypt(challenge|ssl|reference)) + (?:\s+(.+))?}) { + [lc($1),$self->dequote($2)] + } else { + () + } + } $sect->directive('use'); + + if (grep { $_->[0] eq 'challenge' } @d) { + unless ($server_name) { + $server_name = shift @server_aliases; + } + $self->define_domain($server_name); + $self->define_alias($server_name, @server_aliases); + debug(3, "will handle ".join(',', $server_name, @server_aliases)); + } elsif (my ($ref) = map { $_->[1] } + grep { $_->[0] eq 'reference' } @d) { + $self->set('domain', $ref, 'files', 'apache'); + $self->define_alias($ref, @server_aliases); + } } return 1; } @@ -165,25 +131,6 @@ sub server_root { } return $self->{_server_root}; } - -sub http_include { - my ($self, $pattern, $optional) = @_; - - unless ($self->server_root) { - $self->probe; - } - - $pattern = File::Spec->catfile($self->{_server_root}, $pattern) - unless $pattern =~ m{^/}; - $pattern = File::Spec->catfile($pattern, '*') if -d $pattern; - foreach my $file (glob $pattern) { - if ($optional && ! -e $file) { - debug(1, "optional include file \"$file\" doesn't exist"); - next; - } - $self->examine_http_config($file); - } -} sub mkpath { my ($self, $dir) = @_; @@ -272,37 +219,4 @@ EOT return 1; } -sub probe { - my ($self, @servlist) = @_; - @servlist = qw(/usr/sbin/apachectl /usr/sbin/httpd /usr/sbin/apache2) - unless (@servlist); - open(my $nullout, '>', File::Spec->devnull); - open(my $nullin, '<', File::Spec->devnull); - foreach my $serv (@servlist) { - use Symbol 'gensym'; - my $fd = gensym; - eval { - if (my $pid = open3($nullin, $fd, $nullout, $serv, '-V')) { - while (<$fd>) { - chomp; - if (/^\s+-D\s+HTTPD_ROOT=(.+)\s*$/) { - $self->server_root($self->dequote($1)); - last; - } - } - } - }; - close $fd; - last unless ($@) - } - close $nullin; - close $nullout; - unless ($self->server_root) { - error("can't deduce server root directory"); - error("use `source = apache --server-root=DIR' in [core] section of /etc/acmeman.conf to declare it"); - exit(1); - } -} - - 1; |