From def6f87aac7e3e7415e45e9e3ea045450bfca2d7 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sat, 18 Jan 2020 15:08:04 +0200 Subject: Implement full Apache reconfiguration on Slackware This needs Apache::Config::Preproc f6db3dba or later --- lib/App/Acmeman.pm | 1 + lib/App/Acmeman/Apache/Layout/slackware.pm | 180 +++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) diff --git a/lib/App/Acmeman.pm b/lib/App/Acmeman.pm index dba6d3b..fafcb09 100644 --- a/lib/App/Acmeman.pm +++ b/lib/App/Acmeman.pm @@ -159,6 +159,7 @@ sub prep_dir { my $dir = dirname($name); if (! -d $dir) { debug(3, "creating directory $dir"); + return if $self->dry_run_option; my @created = make_path("$dir", { error => \my $err } ); if (@$err) { for my $diag (@$err) { diff --git a/lib/App/Acmeman/Apache/Layout/slackware.pm b/lib/App/Acmeman/Apache/Layout/slackware.pm index 89297fc..d09017a 100644 --- a/lib/App/Acmeman/Apache/Layout/slackware.pm +++ b/lib/App/Acmeman/Apache/Layout/slackware.pm @@ -3,6 +3,8 @@ use strict; use warnings; use Carp; use parent 'App::Acmeman::Apache::Layout'; +use Apache::Config::Preproc; +use App::Acmeman::Log qw(:all); our $PRIORITY = 10; @@ -19,6 +21,184 @@ sub new { } } +sub post_setup { + my ($self,$filename) = @_; + + my $master_config_file = $self->config_file; + my $app = new Apache::Config::Preproc( + $master_config_file, + '-no-comment-grouping', + -expand => [ 'locus', + { 'include' => [ server_root => $self->apache->server_root ] }, + { 'macro' => [ + 'keep' => [ qw(LetsEncryptChallenge + LetsEncryptReference + LetsEncryptSSL) + ] + ] + } + ]) + or do { + debug(1, "can't parse apache configuration: $Apache::Admin::Config::ERROR"); + return; + }; + + my $need_macro_module = !$self->apache_modules('macro'); + my $letsencrypt_macro_loaded = grep { + $_->value =~ /^letsencrypt(?:challenge|reference|ssl)/i + } $app->select('section', 'Macro'); + + debug(3, "Need macro module? ".($need_macro_module?'yes':'no')); + debug(3, "Need letsencrypt macro? ". + ($letsencrypt_macro_loaded==0 + ? 'yes' + : ($letsencrypt_macro_loaded==3 + ? 'no' : 'perhaps'))); + + my $last_loadmodule; + my $stop_search; + my $last_master_locus; + my $include_text = "Include $filename"; + + foreach my $node ($app->select) { + if (!$stop_search) { + if (($node->locus->filenames)[0] eq $master_config_file) { + $last_master_locus = $node->locus; + } + if ($node->type eq 'section') { + if ($node->name =~ /^virtualhost$/i) { + $stop_search = $last_master_locus; + last; + } + } + } + + if ($node->type eq 'comment') { + if ($node->value =~ m{^\s*loadmodule\s}) { + $last_loadmodule = $node->locus; + } + } elsif ($node->type eq 'directive') { + if ($node->name =~ /^loadmodule$/i) { + $last_loadmodule = $node->locus; + } + } + + if ($need_macro_module) { + if ($node->type eq 'comment') { + if ($node->value =~ m{^\s*(loadmodule\s+macro_module\s.*)}i) { + debug(3, "Will uncomment ".$node->locus. " ".$node->value); + $self->add_command($node->locus, \&_replace_directive_at, $1); + $last_loadmodule = $node->locus; + $need_macro_module = 0; + } + } + } + + if (!$letsencrypt_macro_loaded) { + if ($node->type eq 'directive' + && $node->name =~ /^include/i + && $node->value =~ m{^.+/httpd-vhosts.conf\s*$}) { + my $locus = $node->locus->has_file($master_config_file) + ? $node->locus : $last_master_locus; + debug(3, "Will insert \"$include_text\" before $locus"); + $self->add_command($locus, \&_insert_directive_before, + $include_text); + $letsencrypt_macro_loaded = 3; + } + } + } + + if ($need_macro_module && $last_loadmodule) { + debug(3, "Will insert a LoadModule directive after ".$last_loadmodule); + $self->add_command($last_loadmodule, + \&_insert_directive_after, + q{LoadModule macro_module lib64/httpd/modules/mod_macro.so}); + $need_macro_module = 0; + + $last_loadmodule->fixup_lines($app->filename => 1); + } + + if (!$letsencrypt_macro_loaded && !$need_macro_module) { + if ($stop_search) { + debug(3, "Will insert \"$include_text\" before $stop_search"); + $self->add_command($stop_search, \&_insert_directive_before, + $include_text); + } else { + debug(3, "Will insert \"$include_text\" after $last_loadmodule"); + $self->add_command($last_loadmodule, \&_insert_directive_after, + $include_text); + } + } + + $self->run_commands; +} + +sub add_command { + my ($self, $locus, $command, $text) = @_; + my $file = ($locus->filenames)[0]; + my $line = ($locus->filelines($file))[0]; + + push @{$self->{_edits}{$file}}, { line => $line, command => $command, text => $text }; +} + +sub run_commands { + my ($self) = @_; + foreach my $file (keys %{$self->{_edits}}) { + $self->run_commands_for_file($file); + } + $self->apache_modules(undef); +} + +sub run_commands_for_file { + my ($self, $file) = @_; + my @commands = sort { $a->{line} <=> $b->{line} } @{$self->{_edits}{$file}}; + + my $app = new Apache::Config::Preproc( + $file, + '-no-comment-grouping', + -expand => [ 'locus' ]) + or do { + error("can't parse apache configuration file $file: $Apache::Admin::Config::ERROR"); + return; + }; + + foreach my $node ($app->select) { + last if (!@commands); + my $line = ($node->locus->filelines(($node->locus->filenames)[0]))[0]; + if ($commands[0]->{line} == $line) { + my $cmd = shift @commands; + $self->${\ $cmd->{command} }($app, $node, $cmd->{text}); + } + } + + if (@commands) { + error((0+@commands) . " left unmatched in edit queue for file $file"); + error("$file left unchanged"); + } else { + # FIXME: backup/rollback + $app->save; + } +} + +sub _replace_directive_at { + my ($self, $app, $node, $text) = @_; + $self->_insert_directive_after($app, $node, $text); + $node->delete; +} + +sub _insert_directive_after { + my ($self, $app, $node, $text) = @_; + my ($name, $value) = split /\s+/, $text, 2; + $app->add('directive', $name, $value, -after => $node); +} + +sub _insert_directive_before { + my ($self, $app, $node, $text) = @_; + my ($name, $value) = split /\s+/, $text, 2; + $app->add('directive', $name, $value, -before => $node); +} + + 1; -- cgit v1.2.1