aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2019-08-26 10:36:31 +0300
committerSergey Poznyakoff <gray@gnu.org>2019-08-26 10:58:10 +0300
commit5d0c176c8f14151e54959bd898df69c82b5289c0 (patch)
tree67b916f75d8359892bab6a36efa8415e197ed9f3 /lib
parentf20aed261b6fcf97ab070dd743aba8ee6cd0e39c (diff)
downloadacmeman-5d0c176c8f14151e54959bd898df69c82b5289c0.tar.gz
acmeman-5d0c176c8f14151e54959bd898df69c82b5289c0.tar.bz2
Rewrite the Apache source support using Apache::Config::Preproc module
This enables acmeman to handle complex Apache configurations with lots of includes and eventual macros. * Makefile.PL: Require Apache::Defaults and Apache::Config::Preproc` * acmeman: Don't use the -w perl option. All acmeman sources use warnings, so they don't need it. Underlying modules, however, may emit warnings. In particular, Apache::Admin::Config is know to emit lots of warnings about undefined $_[0] being used in concatenation (in Apache/Admin/Config.pm:443). These are bening and completely out of my control. Eliminating the -w option suppresses them. * lib/App/Acmeman/Source/Apache.pm: Rewrite using Apache::Config::Preproc * lib/App/Acmeman/Config.pm (mangle): Raise error flag if the configure method fails.
Diffstat (limited to 'lib')
-rw-r--r--lib/App/Acmeman/Config.pm7
-rw-r--r--lib/App/Acmeman/Source/Apache.pm232
2 files changed, 78 insertions, 161 deletions
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;

Return to:

Send suggestions and report system problems to the System administrator.