use warnings;
# Fix use of acl
-use filetest 'access';
+use filetest qw(access);
# Load dependancies
-use Carp qw(carp confess);
-use DateTime;
-use File::Path qw(make_path);
use File::stat qw(stat);
-use File::Spec;
-use File::Slurp qw(read_file write_file);
+use File::Slurp qw(read_file);
use JSON qw(decode_json);
-use IPC::System::Simple qw(capturex $EXITVAL);
-use Acme qw(CERT_DIR CONFIG DS KEY_DIR SERVER_CRT SERVER_KEY ACCOUNT_KEY);
+use Acme;
# Load POSIX
-use POSIX qw(strftime EXIT_SUCCESS EXIT_FAILURE);
+use POSIX qw(EXIT_SUCCESS EXIT_FAILURE);
# Init debug
my $debug = 0;
# Init config
my $config = undef;
+# Init config file name
+my $configFilename = '/etc/acme/config';
+
+# Init domains
+my @domains = ();
+
# Strip and enable debug
@ARGV = map { if ($_ eq '-d') { $debug = 1; (); } else { $_; } } @ARGV;
-# Check config file
-if (! -f CONFIG) {
- print 'Config file '.CONFIG.' do not exists'."\n";
- exit EXIT_FAILURE;
+# Strip and enable debug
+for (my $i = 0; $i <= $#ARGV; $i++) {
+ # Match redhat types
+ if ($ARGV[$i] =~ /^(?:(\-c|\-\-config)(?:=(.+))?)$/) {
+ if (defined($2) && -f $2) {
+ $configFilename = $2;
+ splice(@ARGV, $i, 1);
+ $i--;
+ # Extract next parameter
+ } elsif(defined($ARGV[$i+1]) && $ARGV[$i+1] =~ /^(.+)$/ && -f $1) {
+ $configFilename = $1;
+ splice(@ARGV, $i, 2);
+ $i--;
+ # Set default
+ } else {
+ print 'Config parameter without valid file name'."\n";
+ exit EXIT_FAILURE;
+ }
+ }
}
# Load config
unless (
#XXX: use eval to workaround a fatal in decode_json
eval {
+ # Check file
+ (-f $configFilename) &&
# Read it
- ($config = read_file(CONFIG)) &&
+ ($config = read_file($configFilename)) &&
# Decode it
($config = decode_json($config)) &&
# Check hash validity
! scalar map {unless(defined($_->{cert}) && defined($_->{key}) && defined($_->{mail}) && defined($_->{domain}) && defined($_->{domains})) {1;} else {();}} @{$config->{certificates}}
}
) {
- print 'Config file '.CONFIG.' is not readable or invalid'."\n";
+ print 'Config file '.$configFilename.' is not readable or invalid'."\n";
exit EXIT_FAILURE;
}
-# Deal with certificates
-foreach (@{$config->{certificates}}) {
- # Init variables
- my ($mtime, $dt, $suffix) = undef;
-
- # Skip certificate without 60 days
- if (-f $_->{cert} && ($mtime = stat($_->{cert})->mtime) >= (time() - 60*24*3600)) {
- next;
- }
-
- # Extract cert directory and filename
- my (undef, $certDir) = File::Spec->splitpath($_->{cert});
-
- # Check that certificate is writable
- unless (-w $certDir || -w $_->{cert}) {
- carp('directory '.$certDir.' or file '.$_->{cert}.' must be writable: '.$!);
- next;
- }
-
- # Check that key directory exists
- if (! -d KEY_DIR) {
- # Create all paths
- make_path(KEY_DIR, {error => \my $err});
- if (@$err) {
- map {
- my ($file, $msg) = %$_;
- carp ($file eq '' ? '' : $file.': ').$msg if ($debug);
- } @$err;
- confess 'make_path failed';
+# Deal with specified domains
+if (scalar(@ARGV) > 0) {
+ # Check that domains are present in config
+ foreach my $domain (@ARGV) {
+ my $found = undef;
+ foreach my $certificate (@{$config->{certificates}}) {
+ if ($certificate->{domain} eq $domain) {
+ push(@domains, $certificate);
+ $found = 1;
+ }
}
- }
-
- # Unlink if is a symlink
- if (-l KEY_DIR.DS.SERVER_KEY) {
- unless(unlink(KEY_DIR.DS.SERVER_KEY)) {
- carp('unlink '.KEY_DIR.DS.SERVER_KEY.' failed: '.$!);
- next;
+ unless($found) {
+ print 'Domain '.$domain.' not found in config file '.$configFilename."\n";
+ exit EXIT_FAILURE;
}
}
-
- # Symlink to key
- unless(symlink($_->{key}, KEY_DIR.DS.SERVER_KEY)) {
- carp('symlink '.$_->{key}.' to '.KEY_DIR.DS.SERVER_KEY.' failed: '.$!);
- next;
+# Without it
+} else {
+ # Populate domains array with available ones
+ foreach my $certificate (@{$config->{certificates}}) {
+ push(@domains, $certificate);
}
+}
- # Unlink if is a symlink
- if (-l KEY_DIR.DS.ACCOUNT_KEY) {
- unless(unlink(KEY_DIR.DS.ACCOUNT_KEY)) {
- carp('unlink '.KEY_DIR.DS.ACCOUNT_KEY.' failed: '.$!);
- next;
- }
- }
+# Show usage
+if (scalar(@domains) < 1) {
+ print "Usage: $0 [-(c|-config)[=/etc/acme/config]] [example.com] [...]\n";
+ exit EXIT_FAILURE;
+}
- # Symlink to key
- unless(symlink($_->{account}, KEY_DIR.DS.ACCOUNT_KEY)) {
- carp('symlink '.$_->{account}.' to '.KEY_DIR.DS.ACCOUNT_KEY.' failed: '.$!);
+# Deal with each domain
+foreach my $domain (@domains) {
+ # Skip certificate without 60 days
+ if (-f $domain->{cert} && stat($domain->{cert})->mtime >= (time() - 60*24*3600)) {
next;
}
- # Init args
- my @args = @{$_->{domains}};
-
- # Prepend mail and domain to other args
- unshift(@args, $_->{mail}, $_->{domain});
-
- # Preprend prod
- if (defined $_->{prod} && $_->{prod}) {
- unshift(@args, '-p');
- }
+ # Create new object
+ my $acme = Acme->new($debug, $domain, {thumbprint => $config->{thumbprint}, pending => $config->{pending}, term => $config->{term}});
- # Preprend debug
- if ($debug) {
- unshift(@args, '-d');
- }
+ # Prepare environement
+ $acme->prepare();
- # Run acmecert with args
- my @out = capturex([0..1], 'acmecert', @args);
+ # Generate required keys
+ $acme->genKeys();
- # Deal with error
- if ($EXITVAL != 0) {
- print join("\n", @out) if ($debug);
- carp('acmecert '.join(', ', @args).' failed: '.$!);
- next;
- }
+ # Generate csr
+ $acme->genCsr();
- # Read cert
- my $content;
- unless($content = read_file(CERT_DIR.DS.SERVER_CRT)) {
- carp('read_file '.CERT_DIR.DS.SERVER_CRT.' failed: '.$!);
- next;
- }
+ # Directory
+ $acme->directory();
- # Handle old certificate
- if (-w $certDir && -f $_->{cert}) {
- # Extract datetime suffix
- $suffix = ($dt = DateTime->from_epoch(epoch => $mtime))->ymd('').$dt->hms('');
+ # Register
+ $acme->register();
- # Rename old certificate
- unless(rename($_->{cert}, $_->{cert}.'.'.$suffix)) {
- carp('rename '.$_->{cert}.' to '.$_->{cert}.'.'.$suffix.' failed: '.$!);
- next;
- }
- }
+ # Authorize
+ $acme->authorize();
- # Save cert
- unless(write_file($_->{cert}, $content)) {
- carp('write_file '.$_->{cert}.' failed: '.$!);
- next;
- }
+ # Issue
+ $acme->issue();
}
# Exit with success