aboutsummaryrefslogtreecommitdiffstats
path: root/lacme
diff options
context:
space:
mode:
Diffstat (limited to 'lacme')
-rwxr-xr-xlacme119
1 files changed, 68 insertions, 51 deletions
diff --git a/lacme b/lacme
index cf2f9eb..cb49818 100755
--- a/lacme
+++ b/lacme
@@ -60,7 +60,7 @@ sub usage(;$$) {
}
exit $rv;
}
-usage(1) unless GetOptions(\%OPTS, qw/config=s config-certs=s socket=s agreement-uri=s quiet|q debug help|h/);
+usage(1) unless GetOptions(\%OPTS, qw/config=s config-certs=s@ socket=s agreement-uri=s quiet|q debug help|h/);
usage(0) if $OPTS{help};
$COMMAND = shift(@ARGV) // usage(1, "Missing command");
@@ -68,15 +68,15 @@ $COMMAND = $COMMAND =~ /\A(new-reg|reg=\p{Print}*|new-cert|revoke-cert)\z/ ? $1
: usage(1, "Invalid command: $COMMAND"); # validate and untaint $COMMAND
@ARGV = map { /\A(\p{Print}*)\z/ ? $1 : die } @ARGV; # untaint @ARGV
+my $CONFFILENAME = $OPTS{config} // first { -f $_ }
+ ( "./$NAME.conf"
+ , ($ENV{XDG_CONFIG_HOME} // "$ENV{HOME}/.config")."/lacme/$NAME.conf"
+ , "/etc/lacme/$NAME.conf"
+ );
do {
- my $conffile = $OPTS{config} // first { -f $_ }
- ( "./$NAME.conf"
- , ($ENV{XDG_CONFIG_HOME} // "$ENV{HOME}/.config")."/lacme/$NAME.conf"
- , "/etc/lacme/$NAME.conf"
- );
- die "Error: Can't find configuration file\n" unless defined $conffile;
- print STDERR "Using configuration file: $conffile\n" if $OPTS{debug};
- open $CONFFILE, '<', $conffile or die "Can't open $conffile: $!\n";
+ die "Error: Can't find configuration file\n" unless defined $CONFFILENAME;
+ print STDERR "Using configuration file: $CONFFILENAME\n" if $OPTS{debug};
+ open $CONFFILE, '<', $CONFFILENAME or die "Can't open $CONFFILENAME: $!\n";
my $conf = do { local $/ = undef, <$CONFFILE> };
# don't close $CONFFILE so we can pass it to the client
@@ -395,38 +395,34 @@ sub spawn_webserver() {
# If $args->{in} is defined, the data is written to the client's STDIN.
# If $args->{out} is defined, its value is set to client's STDOUT data.
#
-my $ACCOUNTD = 0;
sub acme_client($@) {
my $args = shift;
my @args = @_;
- my $client;
+ my ($client, $cleanup);
my $conf = $CONFIG->{client};
if (defined (my $accountd = $CONFIG->{accountd})) {
- unless ($ACCOUNTD) {
- socketpair($client, my $s, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "socketpair: $!";
- my $pid = fork() // "fork: $!";
- unless ($pid) {
- drop_privileges($accountd->{user}, $accountd->{group}, '/');
- set_FD_CLOEXEC($s, 0);
- $client->close() or die "Can't close: $!";
- my @cmd = ($accountd->{command}, '--fdopen='.fileno($s));
- push @cmd, '--config='.$accountd->{config} if defined $accountd->{config};
- push @cmd, '--privkey='.$accountd->{privkey} if defined $accountd->{privkey};
- push @cmd, '--quiet' unless lc $accountd->{quiet} eq 'no';
- push @cmd, '--debug' if $OPTS{debug};
- exec { $cmd[0] } @cmd or die;
- }
- print STDERR "[$$] Forking lacme-accountd, child PID $pid\n" if $OPTS{debug};
- $ACCOUNTD = $pid;
- $s->close() or die "Can't close: $!";
- push @CLEANUP, sub() {
- print STDERR "[$$] Shutting down lacme-accountd\n" if $OPTS{debug};
- shutdown($client, SHUT_RDWR) or warn "shutdown: $!";
- kill 15 => $pid;
- waitpid $pid => 0;
- };
+ socketpair($client, my $s, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "socketpair: $!";
+ my $pid = fork() // "fork: $!";
+ unless ($pid) {
+ drop_privileges($accountd->{user}, $accountd->{group}, '/');
+ set_FD_CLOEXEC($s, 0);
+ $client->close() or die "Can't close: $!";
+ my @cmd = ($accountd->{command}, '--conn-fd='.fileno($s));
+ push @cmd, '--config='.$accountd->{config} if defined $accountd->{config};
+ push @cmd, '--privkey='.$accountd->{privkey} if defined $accountd->{privkey};
+ push @cmd, '--quiet' unless lc $accountd->{quiet} eq 'no';
+ push @cmd, '--debug' if $OPTS{debug};
+ exec { $cmd[0] } @cmd or die;
}
+ print STDERR "[$$] Forking lacme-accountd, child PID $pid\n" if $OPTS{debug};
+ $s->close() or die "Can't close: $!";
+ $cleanup = sub() {
+ print STDERR "[$$] Shutting down lacme-accountd\n" if $OPTS{debug};
+ shutdown($client, SHUT_RDWR) or warn "shutdown: $!";
+ $client->close() or warn "close: $!";
+ };
+ push @CLEANUP, $cleanup;
}
else {
my @stat;
@@ -455,12 +451,18 @@ sub acme_client($@) {
# child doesn't have access to the parent's memory
my @fileno = map { fileno($_) =~ /^(\d+)$/ ? $1 : die } ($CONFFILE, $client); # untaint fileno
set_FD_CLOEXEC($client, 1);
- spawn({%$args{qw/in out/}, child => sub() {
+ my $rv = spawn({%$args{qw/in out/}, child => sub() {
drop_privileges($conf->{user}, $conf->{group}, $args->{chdir} // '/');
set_FD_CLOEXEC($_, 0) foreach ($CONFFILE, $client);
seek($CONFFILE, SEEK_SET, 0) or die "Can't seek: $!";
$ENV{DEBUG} = $OPTS{debug};
}}, $conf->{command}, $COMMAND, @fileno, @args);
+
+ if (defined $cleanup) {
+ @CLEANUP = grep { $_ ne $cleanup } @CLEANUP;
+ $cleanup->();
+ }
+ return $rv;
}
sub spawn($@) {
@@ -557,24 +559,39 @@ if ($COMMAND eq 'new-reg' or $COMMAND =~ /^reg=/) {
# new-cert [SECTION ..]
#
elsif ($COMMAND eq 'new-cert') {
+ my $conffiles = defined $OPTS{'config-certs'} ? $OPTS{'config-certs'}
+ : defined $CONFIG->{_}->{'config-certs'} ? [ split(/\s+/, $CONFIG->{_}->{'config-certs'}) ]
+ : [ "$NAME-certs.conf", "$NAME-certs.conf.d/" ];
my $conf;
- do {
- my $conffile = $OPTS{'config-certs'} // $CONFIG->{_}->{'config-certs'} // first { -f $_ }
- ( "./$NAME-certs.conf"
- , ($ENV{XDG_CONFIG_HOME} // "$ENV{HOME}/.config")."/lacme/$NAME-certs.conf"
- , "/etc/lacme/$NAME-certs.conf"
- );
- die "Error: Can't find certificate configuration file\n" unless defined $conffile;
- my $h = Config::Tiny::->read($conffile) or die Config::Tiny::->errstr()."\n";
- my $defaults = delete $h->{_} // {};
- my @valid = qw/certificate certificate-chain certificate-key min-days CAfile
- hash keyUsage subject subjectAltName chown chmod notify/;
- foreach my $s (keys %$h) {
- $conf->{$s} = { map { $_ => delete $h->{$s}->{$_} } @valid };
- die "Unknown option(s) in [$s]: ".join(', ', keys %{$h->{$s}})."\n" if %{$h->{$s}};
- $conf->{$s}->{$_} //= $defaults->{$_} foreach keys %$defaults;
+ foreach my $conffile (@$conffiles) {
+ $conffile = ($CONFFILENAME =~ s#[^/]+\z##r).$conffile unless $conffile =~ /\A\//;
+ my @filenames;
+ unless ($conffile =~ s#/\z## or -d $conffile) {
+ @filenames = ($conffile);
+ } else {
+ opendir my $dh, $conffile or die "Can't opendir $conffile: $!\n";
+ while (readdir $dh) {
+ if (/\.conf\z/) {
+ push @filenames, "$conffile/$_";
+ } elsif ($_ ne '.' and $_ ne '..') {
+ warn "$conffile/$_ has unknown suffix, skipping\n";
+ }
+ }
+ closedir $dh;
}
- };
+ foreach my $filename (sort @filenames) {
+ print STDERR "Reading $filename\n" if $OPTS{debug};
+ my $h = Config::Tiny::->read($filename) or die Config::Tiny::->errstr()."\n";
+ my $defaults = delete $h->{_} // {};
+ my @valid = qw/certificate certificate-chain certificate-key min-days CAfile
+ hash keyUsage subject subjectAltName chown chmod notify/;
+ foreach my $s (keys %$h) {
+ $conf->{$s} = { map { $_ => delete $h->{$s}->{$_} } @valid };
+ die "Unknown option(s) in [$s]: ".join(', ', keys %{$h->{$s}})."\n" if %{$h->{$s}};
+ $conf->{$s}->{$_} //= $defaults->{$_} foreach keys %$defaults;
+ }
+ }
+ }
my $challenge_dir;
my $rv = 0;