#! /usr/bin/perl # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Copyright (C) 2016 - 2017 Raphaƫl Gertz # Best practice use strict; use warnings; # Fix use of acl use filetest qw(access); # Load dependancies use File::stat qw(stat); use File::Slurp qw(read_file); use JSON qw(decode_json); use Acme; # Load POSIX 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; # 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($configFilename)) && # Decode it ($config = decode_json($config)) && # Check hash validity defined($config->{certificates}) && # Check not empty scalar($config->{certificates}) && # Check hash validity defined($config->{thumbprint}) && # Check certificates array ! scalar map {unless(defined($_->{cert}) && defined($_->{key}) && defined($_->{mail}) && defined($_->{domain}) && defined($_->{domains})) {1;} else {();}} @{$config->{certificates}} } ) { print 'Config file '.$configFilename.' is not readable or invalid'."\n"; exit EXIT_FAILURE; } # 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; } } unless($found) { print 'Domain '.$domain.' not found in config file '.$configFilename."\n"; exit EXIT_FAILURE; } } # Without it } else { # Populate domains array with available ones foreach my $certificate (@{$config->{certificates}}) { push(@domains, $certificate); } } # Show usage if (scalar(@domains) < 1) { print "Usage: $0 [-(c|-config)[=/etc/acme/config]] [example.com] [...]\n"; exit EXIT_FAILURE; } # 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; } # Create new object my $acme = Acme->new($debug, $domain, {thumbprint => $config->{thumbprint}, pending => $config->{pending}, term => $config->{term}}); # Prepare environement $acme->prepare(); # Generate required keys $acme->genKeys(); # Generate csr $acme->genCsr(); # Directory $acme->directory(); # Register $acme->register(); # Authorize $acme->authorize(); # Issue $acme->issue(); } # Exit with success exit EXIT_SUCCESS;