diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2017-09-13 15:13:34 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2017-09-13 15:23:44 +0200 |
commit | 6599db656e097d8eb22921d2c2ce3451c8147563 (patch) | |
tree | 2b04a44ba67b088e0c62ac364c75b72ef928b8a9 /lib | |
parent | 36f66056a4ddbba8f300ef2fd15955e292e2755f (diff) | |
download | acmeman-6599db656e097d8eb22921d2c2ce3451c8147563.tar.gz acmeman-6599db656e097d8eb22921d2c2ce3451c8147563.tar.bz2 |
Rewrite
This patch introduces acmeman configuration file, which can be used to
direct its action if a server other than Apache is used. It also can
be instructed to store certificate, certificate chain, and certificate
key in a single file, instead of three different ones. This can be used
with such servers as pound(8).
In the absense of a configuration file, the program operates as in
previous versions.
* MANIFEST: Update.
* Makefile.PL: Update.
* Changes: Update.
* acmeman: Use configuration file if present. Apache configuration
remains as a default source of TLS domains. Configuration file can
override or complement it.
* lib/App/Acmeman/Domain.pm: New file.
* lib/App/Acmeman/Config.pm: New file.
* lib/App/Acmeman/Source/Apache.pm: New file.
* lib/App/Acmeman/Apache/Layout.pm: New file.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/App/Acmeman/Apache/Layout.pm | 117 | ||||
-rw-r--r-- | lib/App/Acmeman/Config.pm | 359 | ||||
-rw-r--r-- | lib/App/Acmeman/Domain.pm | 138 | ||||
-rw-r--r-- | lib/App/Acmeman/Source/Apache.pm | 284 |
4 files changed, 898 insertions, 0 deletions
diff --git a/lib/App/Acmeman/Apache/Layout.pm b/lib/App/Acmeman/Apache/Layout.pm new file mode 100644 index 0000000..77e2f8f --- /dev/null +++ b/lib/App/Acmeman/Apache/Layout.pm @@ -0,0 +1,117 @@ +package App::Acmeman::Apache::Layout; + +use strict; +use warnings; +use Carp; +use File::Basename; + +require Exporter; +our @ISA = qw(Exporter); + +my %apache_layout_tab = ( + slackware => { + _config_file => '/etc/httpd/httpd.conf', + _incdir => '/etc/httpd/extra', + _restart => '/etc/rc.d/rc.httpd restart' + }, + debian => { + _config_file => '/etc/apache2/apache2.conf', + _incdir => sub { + for my $dir ('/etc/apache2/conf-available', + '/etc/apache2/conf.d') { + return $dir if -d $dir; + } + carp 'none of the expected configuration directories found; falling back to /etc/apache2'; + return '/etc/apache2'; + }, + _restart => 'service apache2 restart', + _post_setup => sub { + my ($filename) = @_; + my $dir = dirname($filename); + my $name = basename($filename); + if ($dir eq '/etc/apache2/conf-available') { + chdir('/etc/apache2/conf-enabled'); + symlink "../conf-available/$name", $name; + } + } + }, + rh => { + _config_file => '/etc/httpd/conf/httpd.conf', + _incdir => '/etc/httpd/conf.d', + _restart => 'service httpd restart' + }, + suse => { + _config_file => '/etc/apache2/httpd.conf', + _incdir => '/etc/apache2/conf.d', + _restart => 'service httpd restart' + # or systemctl restart apache2.service + } +); + +# new(NAME) +# new() +sub new { + my $class = shift; + my $self = bless { }, $class; + my $name; + + if (@_ == 0) { + # Autodetect + while (my ($n, $layout) = each %apache_layout_tab) { + if (-f $layout->{_config_file}) { + debug(2, "assuming Apache layout \"$n\""); + $name = $n; + last; + } + } + croak "unrecognized Apache layout" unless defined $name; + } elsif (@_ == 1) { + $name = shift; + } + + if (exists($apache_layout_tab{$name})) { + @{$self}{keys %{$apache_layout_tab{$name}}} = + values %{$apache_layout_tab{$name}}; + } else { + croak "undefined Apache layout $name"; + } + + $self->{_layout_name} = $name; + + return $self; +} + +sub debug { + if (defined(&::debug)) { + ::debug(@_); + } +} + +sub name { + my $self = shift; + return $self->{_layout_name}; +} + +sub config_file { + my $self = shift; + return $self->{_config_file}; +} + +sub restart_command { + my $self = shift; + return $self->{_restart}; +} + +sub incdir { + my $self = shift; + if (exists($self->{_incdir})) { + if (ref($self->{_incdir}) eq 'CODE') { + return &{$self->{_incdir}}; + } else { + return $self->{_incdir}; + } + } + return dirname($self->{_config_file}); +} + +1; diff --git a/lib/App/Acmeman/Config.pm b/lib/App/Acmeman/Config.pm new file mode 100644 index 0000000..bbe1f2b --- /dev/null +++ b/lib/App/Acmeman/Config.pm @@ -0,0 +1,359 @@ +package App::Acmeman::Config; + +use strict; +use warnings; +use Carp; + +require Exporter; +our @ISA = qw(Exporter); +our $VERSION = "1.00"; + +sub new { + my ($class, $filename, %args) = @_; + my $self = bless { _filename => $filename }, $class; + my $defaults; + my $v; + + if (defined($v = delete $args{syntax})) { + $self->{_syntax} = $v; + } + $defaults = delete $args{defaults}; + + carp "unrecognized parameters" if keys(%args); + $self->{_conf} = {}; + if (-e $filename) { + $self->_readconfig($filename, $self->{_conf}); + } elsif ($defaults) { + while (my ($k, $v) = each %$defaults) { + $self->set(split(/\./, $k), $v); + } + } else { + $self->error("configuration file \"$filename\" does not exist"); + } + + if ($self->success && exists($self->{_syntax})) { + $self->_fixup($self->{_syntax}, $self->{_conf}); + } + return $self; +} + +sub error { + my ($self, $msg) = @_; + push @{$self->{_errors}}, $msg; +} + +sub errors { + my $self = shift; + if (wantarray) { + return exists($self->{_errors}) ? (@{$self->{_errors}}) : (); + } elsif (!exists($self->{_errors})) { + return 0; + } else { + return 0 + @{$self->{_errors}}; + } +} + +sub clrerr { + my $self = shift; + delete $self->{_errors}; +} + +sub success { + my $self = shift; + return $self->errors == 0; +} + +sub _parse_section { + my ($self, $conf, $input) = @_; + my $quote; + my $kw = $self->{_syntax} if exists $self->{_syntax}; + 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 { + error("unparsable input $input"); + exit(2); + } + } + + if (defined($name)) { + $conf->{$name} = {} unless ref($conf->{$name}) eq 'HASH'; + $conf = $conf->{$name}; + if (defined($kw) and ref($kw) eq 'HASH') { + my $synt; + if (exists($kw->{$name})) { + $synt = $kw->{$name}; + } elsif (exists($kw->{'*'})) { + $synt = $kw->{'*'}; + if ($synt eq '*') { + $name = undef; + next; + } + } + if (defined($synt) + && ref($synt) eq 'HASH' + && exists($synt->{section})) { + $kw = $synt->{section}; + } else { + $kw = undef; + } + } else { + $kw = undef; + } + $name = undef; + } + } + return ($conf, $kw); +} + +sub _readconfig { + my ($self, $file, $conf) = @_; + +# debug(2, "reading $file"); + open(my $fd, "<", $file) + or do { + $self->error("can't open configuration file $file: $!"); + return; + }; + + my $line; + my $err; + my $section = $conf; + my $kw = $self->{_syntax}; + my $include = 0; + my $rootname; + + while (<$fd>) { + ++$line; + chomp; + if (/\\$/) { + chop; + $_ .= <$fd>; + redo; + } + + s/^\s+//; + s/\s+$//; + s/#.*//; + next if ($_ eq ""); + + if (/^\[(.+?)\]$/) { + $include = 0; + my $arg = $1; + $arg =~ s/^\s+//; + $arg =~ s/\s+$//; + if ($arg eq 'include') { + $include = 1; + } else { + ($section, $kw) = $self->_parse_section($conf, $1); + if (exists($self->{_syntax}) && !defined($kw)) { + $self->error("$file:$line: unknown section"); + } + } + } elsif (/([\w_-]+)\s*=\s*(.*)/) { + my ($k, $v) = ($1, $2); + + if ($include) { + if ($k eq 'path') { + $self->_readconfig($v, $conf); + } elsif ($k eq 'pathopt') { + $self->_readconfig($v, $conf) + if -f $v; + } elsif ($k eq 'glob') { + foreach my $file (bsd_glob($v, 0)) { + $self->_readconfig($file, $conf); + } + } else { + $self->error("$file:$line: unknown keyword"); + } + next; + } + + if (defined($kw)) { + my $x = $kw->{$k}; + if (!defined($x)) { + $self->error("$file:$line: unknown keyword $k"); + next; + } elsif (ref($x) eq 'HASH') { + if (exists($x->{re})) { + if ($v !~ /$x->{re}/) { + $self->error("$file:$line: invalid value for $k"); + next; + } + } + + if (exists($x->{check})) { + if (my $errstr = &{$x->{check}}($k, $v)) { + $self->error("$file:$line: $errstr"); + next; + } + } + + if (exists($x->{parser})) { + if (my $errstr = &{$x->{parser}}($k, \$v)) { + $self->error("$file:$line: $errstr"); + next; + } + } + + if ($x->{array}) { + if (exists($section->{$k})) { + $v = [ @{$section->{$k}}, $v ]; + } else { + $v = [ $v ] + } + } + } + } + + $section->{$k} = $v; + } else { + $self->error("$file:$line: malformed line"); + next; + } + } + close $fd; +} + +sub _is_section_ref { + my ($ref) = @_; + return ref($ref) eq 'HASH'; +} + +sub _fixup { + my ($self, $kw, $section, @path) = @_; + + while (my ($k, $d) = each %{$kw}) { + if (ref($d) eq 'HASH') { + if (exists($d->{default}) && !exists($section->{$k})) { + $section->{$k} = $d->{default}; + } + + if (exists($d->{section})) { + if ($k eq '*') { + while (my ($name, $vref) = each %{$section}) { + if (_is_section_ref($vref)) { + $self->_fixup($d->{section}, $vref, @_, $name); + } + } + } else { + my $temp; + unless (exists $section->{$k}) { + $section->{$k} = {} ; + $temp = 1; + } + $self->_fixup($d->{section}, $section->{$k}, @_, $k); + delete $section->{$k} + if $temp && keys(%{$section->{$k}}) == 0; + } + } + + if ($d->{mandatory} && !exists($section->{$k})) { + $self->error(exists($d->{section}) + ? "mandatory section [" + . join(' ', @_, $k) + . "] not present" + : "mandatory variable \"" + . join('.', @_, $k) + . "\" not set"); + } + } + } +} + +sub _getref { + my $self = shift; + + return undef unless exists $self->{_conf}; + my $ref = $self->{_conf}; + for (@_) { + carp "component undefined" unless defined $_; + return undef unless exists $ref->{$_}; + $ref = $ref->{$_}; + } + return $ref; +} + +sub get { + my $self = shift; + if (my $ref = $self->_getref(@_)) { + if (ref($ref) eq 'ARRAY') { + if (wantarray) { + return (@{$ref}); + } else { + return $ref->[0]; + } + } + return $ref; + } +} + +sub isset { + my $self = shift; + return defined $self->_getref(@_); +} + +sub set { + my $self = shift; + $self->{_conf} = {} unless exists $self->{_conf}; + my $ref = $self->{_conf}; + my $synt = $self->{_syntax}; + +# print "SET ".join('.',@_)."\n"; + while (my $arg = shift) { + if ($synt) { + if (exists($synt->{$arg})) { + $synt = $synt->{$arg}; + } elsif (exists($synt->{'*'})) { + $synt = $synt->{'*'}; + } else { + croak "no such component in syntax: $arg"; + } + } + + if (@_ == 1) { + my $v = shift; + if ($synt && ref($synt) eq 'HASH') { + if ($synt->{array}) { + if (exists($ref->{$arg})) { + $v = [ @{$ref->{$arg}}, $v ]; + } else { + $v = [ $v ]; + } + } + } + $ref->{$arg} = $v; + return; + } + + if ($synt) { + croak "component not a section: $arg" + unless $synt->{section}; + $synt = $synt->{section}; + } + $ref->{$arg} = {} unless exists $ref->{$arg}; + $ref = $ref->{$arg}; + } +} + +1; + + diff --git a/lib/App/Acmeman/Domain.pm b/lib/App/Acmeman/Domain.pm new file mode 100644 index 0000000..64d4275 --- /dev/null +++ b/lib/App/Acmeman/Domain.pm @@ -0,0 +1,138 @@ +package App::Acmeman::Domain; + +use strict; +use warnings; +use Carp; + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT_OK = qw(CERT_FILE KEY_FILE CA_FILE); +our %EXPORT_TAGS = ( files => [ qw(CERT_FILE KEY_FILE CA_FILE) ] ); +our $VERSION = '1.00'; + +use constant { + CERT_FILE => 0, + KEY_FILE => 1, + CA_FILE => 2 +}; + +sub uniq { + my %h; + map { + if (exists($h{$_})) { + () + } else { + $h{$_} = 1; + $_ + } + } @_; +} + +sub new { + my ($class, %args) = @_; + + my $self = bless { }, $class; + my $v; + + if ($v = delete $args{cn}) { + $self->{_cn} = lc $v; + } + if ($v = delete $args{alt}) { + if (ref($v) eq 'ARRAY') { + $self->{_alt} = [ uniq(map { lc } @$v) ]; + } else { + $self->{_alt} = [ lc $v ]; + } + } + if ($v = delete $args{type}) { + $self->{_cert_type} = $v; + } + if (($v = delete $args{certificate_file}) + || ($v = delete $args{'certificate-file'})) { + $self->{_file}[CERT_FILE] = $v; + } + if (($v = delete $args{ca_file}) + || ($v = delete $args{'ca-file'})) { + $self->{_file}[CA_FILE] = $v; + } + if (($v = delete $args{key_file}) + || ($v = delete $args{'key-file'})) { + $self->{_file}[KEY_FILE] = $v; + } + + $v = delete($args{argument}) || '$domain'; + $v =~ s{\$}{\\\$}; + $self->{_argument} = qr($v); + + croak "unrecognized arguments" if keys %args; + return $self; +} + +sub names { + my $self = shift; + if (wantarray) { + return ($self->cn, $self->alt); + } else { + return $self->alt() + 1; + } +} + +sub contains { + my $self = shift; + my $val = lc shift; + return grep { $_ eq $val } $self->names; +} + +sub _domain_cmp { + my ($a,$b) = @_; + + carp "righthand-side argument should be a App::Acmeman::Domain" + unless $b->isa('App::Acmeman::Domain'); + return $a->{_cn} cmp $b->{_cn}; +} + +sub _domain_plus { + my ($a, $b) = @_; + + carp "righthand-side argument should be a App::Acmeman::Domain" + unless $b->isa('App::Acmeman::Domain'); + + push @{$a->{_alt}}, $b->cn + unless $a->contains($b->cn); + @{$a->{_alt}} = uniq($a->alt, $b->alt); +} + +use overload + cmp => \&_domain_cmp, + '+' => \&domain_plus, + '""' => sub { $_[0]->cn }; + +sub cn { + my $self = shift; + return $self->{_cn}; +} + +sub alt { + my $self = shift; + if (wantarray) { + return exists($self->{_alt}) ? (@{$self->{_alt}}) : (); + } else { + return exists($self->{_alt}) ? (0+@{$self->{_alt}}) : 0; + } +} + +sub file { + my ($self, $type) = @_; + return undef unless exists($self->{_file}[$type]); + my $res = $self->{_file}[$type]; + $res =~ s{$self->{_argument}}{$self->cn}ge; + return $res; +} + +sub certificate_file { + my $self = shift; + return $self->file(CERT_FILE); +} + +1; + diff --git a/lib/App/Acmeman/Source/Apache.pm b/lib/App/Acmeman/Source/Apache.pm new file mode 100644 index 0000000..f86c02f --- /dev/null +++ b/lib/App/Acmeman/Source/Apache.pm @@ -0,0 +1,284 @@ +package App::Acmeman::Source::Apache; + +use strict; +use warnings; +use Carp; +use feature 'state'; +use File::Path qw(make_path); + +require App::Acmeman::Apache::Layout; +our @ISA = qw(App::Acmeman::Apache::Layout); + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + return $self; +} + +sub debug { + if (defined(&::debug)) { + ::debug(@_); + } +} + +sub configure { + my ($self, $config) = @_; + $config->set(qw(core restart), $self->restart_command); + $self->{_cfg} = $config; + return $self->examine_http_config($self->config_file); +} + +sub set { + my $self = shift; + croak "improper use of the set method" + unless exists $self->{_cfg}; + return $self->{_cfg}->set(@_); +} + +sub get { + my $self = shift; + croak "improper use of the get method" + unless exists $self->{_cfg}; + return $self->{_cfg}->get(@_); +} + +sub error { + my $self = shift; + if (exists($self->{_cfg})) { + $self->{_cfg}->error(@_); + } else { + carp @_; + } +} + +sub dequote { + my ($self, $arg) = @_; + $arg =~ s{^"(.*?)"$}{$1}; + 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->set('domain', $cn, 'files', 'apache'); + foreach my $name (@server_aliases) { + $self->set('domain', $cn, 'alt', $name); + } + debug(3, "$file:$line: will handle ". + join(',', $cn, @server_aliases)); + } elsif ($state == STATE_USE_REFERENCE) { + $self->set('domain', $reference, 'files', 'apache'); + foreach my $name (@server_aliases) { + $self->set('domain', $reference, + 'alt', $name); + } + } + } + $state = STATE_INITIAL; + } elsif (/^(?:(?i)Use)\s+LetsEncryptChallenge/) { + if ($state == STATE_VIRTUAL_HOST) { + $state = STATE_USE_CHALLENGE; + } elsif ($state == STATE_USE_CHALLENGE) { + $self->error("$file:$line: duplicate use of LetsEncryptChallenge"); + } else { + $self->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) { + $self->error("$file:$line: duplicate use of LetsEncryptReference"); + } else { + $self->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 %t = ( + '' => 'certificate-file', + key => 'key-file', + chain => 'ca-file' + ); + $self->set(qw(files apache), $t{lc($1||'')}, $2); + } + } + } + close $fd; + } else { + $self->error("can't open file \"$file\": $!"); + return 0; + } + return 1; +} + +sub http_include { + my ($self, $pattern, $optional) = @_; + $pattern = "$self->{_server_root}/$pattern" unless $pattern =~ m{^/}; + $pattern =~ s{/*$}{}; + $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) = @_; + my @created = make_path("$dir", { error => \my $err } ); + if (@$err) { + for my $diag (@$err) { + my ($file, $message) = %$diag; + if ($file eq '') { + $self->error($message); + } else { + $self->error("mkdir $file: $message"); + } + } + return 0; + } + return 1; +} + +sub setup { + my ($self, %args) = @_; + my $filename = $self->incdir() . "/httpd-letsencrypt.conf"; + if (-e $filename) { + if ($args{force}) { + ::error("the file \"$filename\" already exists", + prefix => 'warning'); + } else { + ::error("the file \"$filename\" already exists"); + ::error("use --force to continue"); + return 0; + } + } + my $www_root = $self->get(qw(core rootdir)); + debug(2, "writing $filename"); + unless ($args{dry_run}) { + unless ($self->mkpath($self->incdir())) { + return 0; + } + open(my $fd, '>', $filename) + or croak "can't open \"$filename\" for writing: $!"; + print $fd <<EOT; +<Macro LetsEncryptChallenge> + Alias /.well-known/acme-challenge $www_root/.well-known/acme-challenge + <Directory $www_root/.well-known/acme-challenge> + Options None + Require all granted + </Directory> + <IfModule mod_rewrite.c> + RewriteEngine On + RewriteRule /.well-known/acme-challenge - [L] + </IfModule> +</Macro> + +<Macro LetsEncryptReference \$domain> + Use LetsEncryptChallenge + Alias /.dummy/\$domain /dev/null +</Macro> + +<Macro LetsEncryptSSL \$domain> + SSLEngine on + SSLProtocol all -SSLv2 -SSLv3 + SSLHonorCipherOrder on + SSLCipherSuite EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:EECDH+RC4:RSA+RC4:!MD5 + SSLCertificateFile /etc/ssl/acme/\$domain/cert.pem + SSLCertificateKeyFile /etc/ssl/acme/\$domain/privkey.pem + SSLCACertificateFile /etc/ssl/acme/lets-encrypt-x3-cross-signed.pem +</Macro> + +<Macro LetsEncryptServer \$domain> + ServerName \$domain + Use LetsEncryptSSL \$domain +</Macro> + +EOT +; + close $fd; + + if (exists($self->{_post_setup})) { + &{$self->{_post_setup}}($filename); + } + } + + ::error("created file \"$filename\"", prefix => 'note'); + ::error("please, enable mod_macro and make sure your Apache configuration includes this file", prefix => 'note'); + + return 1; +} + +1; |