use Tie::IxHash;
use POSIX qw(EXIT_FAILURE);
-# Debug
-use Data::Dumper;
-
# Documentation links
#XXX: see https://letsencrypt.github.io/acme-spec/ (probably based on https://ietf-wg-acme.github.io/acme/)
#XXX: see jwk rfc http://www.rfc-editor.org/rfc/rfc7517.txt
ACME_TERMS => 'https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf',
# Version
- VERSION => 'v0.2'
+ VERSION => 'v0.3'
};
# User agent object
our $ua;
-# Debug
-our $_debug = 0;
-
# Strerr backup
our $_stderr;
# Constructor
sub new {
# Extract params
- my ($class, $mail, @domains) = @_;
+ my ($class, $mail, $debug, $prod, @domains) = @_;
# Create self hash
my $self = {};
# Link self to package
bless($self, $class);
+ # Save debug
+ $self->{debug} = $debug;
+
+ # Save prod
+ $self->{prod} = $prod;
+
# Add extra check to mail validity
#XXX: mxcheck fail if there is only a A record on the domain
my $ev = Email::Valid->new(-fqdn => 1, -tldcheck => 1, -mxcheck => 1);
# Show error if check fail
if (! defined $ev->address($mail)) {
- map { carp 'failed check: '.$_ if ($_debug) } $ev->details();
+ map { carp 'failed check: '.$_ if ($self->{debug}) } $ev->details();
confess 'Email::Valid->address failed';
}
# Prepare environement
sub prepare {
- my ($self, $prod) = @_;
+ my ($self) = @_;
# Create all paths
- make_path(CERT_DIR, KEY_DIR, PENDING_DIR.'/'.$self->{mail}.'.'.($prod ? 'prod' : 'staging'), {error => \my $err});
+ make_path(CERT_DIR, KEY_DIR, PENDING_DIR.'/'.$self->{mail}.'.'.($self->{prod} ? 'prod' : 'staging'), {error => \my $err});
if (@$err) {
map {
my ($file, $msg) = %$_;
- carp ($file eq '' ? '' : $file.': ').$msg if ($_debug);
+ carp ($file eq '' ? '' : $file.': ').$msg if ($self->{debug});
} @$err;
confess 'make_path failed';
}
# Directory call
sub directory {
- my ($self, $prod) = @_;
+ my ($self) = @_;
# Set time
my $time = time;
# Set directory
- my $dir = $prod ? ACME_PROD_DIR : ACME_DIR;
+ my $dir = $self->{prod} ? ACME_PROD_DIR : ACME_DIR;
# Create a request
my $req = HTTP::Request->new(GET => $dir.'?'.$time);
# Check if we get dns answer
unless(my $rep = $res->search($domain, 'TXT')) {
- carp 'TXT record search for '.$domain.' failed' if ($_debug);
+ carp 'TXT record search for '.$domain.' failed' if ($self->{debug});
return;
} else {
unless (scalar map { $_->type eq 'TXT' && $_->txtdata =~ /^$signature$/ ? 1 : (); } $rep->answer) {
- carp 'TXT record recursive search for '.$domain.' failed' if ($_debug);
+ carp 'TXT record recursive search for '.$domain.' failed' if ($self->{debug});
return;
}
}
# Handle error
unless ($res->is_success) {
- carp 'GET http://'.$domain.'/.well-known/acme-challenge/'.$token.' failed: '.$res->status_line if ($_debug);
+ carp 'GET http://'.$domain.'/.well-known/acme-challenge/'.$token.' failed: '.$res->status_line if ($self->{debug});
return;
}
# Handle invalid content
unless($res->content =~ /^$token.$self->{account}{thumbprint}\s*$/) {
- carp 'GET http://'.$domain.'/.well-known/acme-challenge/'.$token.' content match failed: /^'.$token.'.'.$self->{account}{thumbprint}.'\s*$/ !~ '.$res->content if ($_debug);
+ carp 'GET http://'.$domain.'/.well-known/acme-challenge/'.$token.' content match failed: /^'.$token.'.'.$self->{account}{thumbprint}.'\s*$/ !~ '.$res->content if ($self->{debug});
return;
}
# Authorize domains
sub authorize {
- my ($self, $prod) = @_;
+ my ($self) = @_;
# Create challenges hash
%{$self->{challenges}} = ();
my $content = undef;
# Init file
- my $file = PENDING_DIR.'/'.$self->{mail}.'.'.($prod ? 'prod' : 'staging').'/'.$_;
+ my $file = PENDING_DIR.'/'.$self->{mail}.'.'.($self->{prod} ? 'prod' : 'staging').'/'.$_;
# Load auth request content or post a new one
#TODO: add more check on cache file ???
# Handle error
unless ($res->is_success) {
- carp 'GET '.$self->{challenges}{$_}{http_challenge}.' failed: '.$res->status_line if ($_debug);
+ carp 'GET '.$self->{challenges}{$_}{http_challenge}.' failed: '.$res->status_line if ($self->{debug});
}
# Extract content
#} map { $self->{challenges}{$_}{status} eq 'valid' ? $_ : () } keys %{$self->{challenges}};
# Stop here as a domain of csr list failed authorization
- if ($_debug) {
+ if ($self->{debug}) {
confess 'Fix the challenges for domains: '.join(', ', map { ! defined $self->{challenges}{$_}{status} or $self->{challenges}{$_}{status} ne 'valid' ? $_ : (); } keys %{$self->{challenges}});
} else {
exit EXIT_FAILURE;
# Handle error
unless ($res->is_success) {
- print Dumper($res);
confess 'POST '.$self->{'new-cert'}.' failed: '.$res->status_line;
}
# Handle error
unless ($res->is_success) {
- carp 'GET '.ACME_CERT.' failed: '.$res->status_line if ($_debug);
+ carp 'GET '.ACME_CERT.' failed: '.$res->status_line if ($self->{debug});
}
# Append content
close($fh) or die $!;
# Print success
- carp 'Success, pem certificate in '.CERT_DIR.DS.SERVER_CRT if ($_debug);
+ carp 'Success, pem certificate in '.CERT_DIR.DS.SERVER_CRT if ($self->{debug});
}
1;