aboutsummaryrefslogtreecommitdiffstats
path: root/lacme
diff options
context:
space:
mode:
Diffstat (limited to 'lacme')
-rwxr-xr-xlacme89
1 files changed, 64 insertions, 25 deletions
diff --git a/lacme b/lacme
index f6d2b7a..cf2f9eb 100755
--- a/lacme
+++ b/lacme
@@ -30,7 +30,7 @@ use File::Temp ();
use Getopt::Long qw/:config posix_default no_ignore_case gnu_getopt auto_version/;
use List::Util 'first';
use POSIX ();
-use Socket qw/PF_INET PF_INET6 PF_UNIX INADDR_ANY IN6ADDR_ANY
+use Socket qw/AF_UNIX PF_INET PF_INET6 PF_UNIX PF_UNSPEC INADDR_ANY IN6ADDR_ANY
SOCK_STREAM SOL_SOCKET SO_REUSEADDR SHUT_RDWR/;
use Config::Tiny ();
@@ -60,7 +60,7 @@ sub usage(;$$) {
}
exit $rv;
}
-usage(1) unless GetOptions(\%OPTS, qw/config=s config-certs=s socket=s agreement-uri=s 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");
@@ -82,6 +82,7 @@ do {
my $h = Config::Tiny::->read_string($conf) or die Config::Tiny::->errstr()."\n";
my $defaults = delete $h->{_} // {};
+ my $accountd = exists $h->{accountd} ? 1 : 0;
my %valid = (
client => {
socket => (defined $ENV{XDG_RUNTIME_DIR} ? "$ENV{XDG_RUNTIME_DIR}/S.lacme" : undef),
@@ -99,6 +100,14 @@ do {
command => '/usr/lib/lacme/webserver',
iptables => 'Yes'
+ },
+ accountd => {
+ user => '',
+ group => '',
+ command => '/usr/bin/lacme-accountd',
+ config => '/etc/lacme/lacme-accountd.conf',
+ privkey => undef,
+ quiet => 'Yes',
}
);
foreach my $s (keys %valid) {
@@ -110,6 +119,8 @@ do {
}
die "Invalid section(s): ".join(', ', keys %$h)."\n" if %$h;
$CONFIG->{_} = $defaults;
+ delete $CONFIG->{accountd} unless $accountd;
+ $OPTS{quiet} = 0 if $OPTS{debug};
};
# Regular expressions for domain validation
@@ -384,35 +395,66 @@ 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 @stat;
+ my $client;
my $conf = $CONFIG->{client};
- my $sockname = $OPTS{socket} // $conf->{socket} // die "Missing socket option\n";
- $sockname = $sockname =~ /\A(\p{Print}+)\z/ ? $1 : die "Invalid socket name\n"; # untaint $sockname
-
- # ensure we're the only user with write access to the parent dir
- my $dirname = $sockname =~ s/[^\/]+$//r;
- @stat = stat($dirname) or die "Can't stat $dirname: $!";
- die "Error: insecure permissions on $dirname\n" if ($stat[2] & 0022) != 0;
-
- # ensure we're the only user with read/write access to the socket
- @stat = stat($sockname) or die "Can't stat $sockname: $! (Is lacme-accountd running?)\n";
- die "Error: insecure permissions on $sockname\n" if ($stat[2] & 0066) != 0;
-
- # connect(2) to the socket
- socket(my $client, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
- my $sockaddr = Socket::sockaddr_un($sockname) // die "Invalid address $sockname\n";
- until (connect($client, $sockaddr)) {
- next if $! == EINTR; # try again if connect(2) was interrupted by a signal
- die "connect: $!";
+ 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;
+ };
+ }
+ }
+ else {
+ my @stat;
+ my $sockname = $OPTS{socket} // $conf->{socket} // die "Missing socket option\n";
+ $sockname = $sockname =~ /\A(\p{Print}+)\z/ ? $1 : die "Invalid socket name\n"; # untaint $sockname
+
+ # ensure we're the only user with write access to the parent dir
+ my $dirname = $sockname =~ s/[^\/]+$//r;
+ @stat = stat($dirname) or die "Can't stat $dirname: $!";
+ die "Error: insecure permissions on $dirname\n" if ($stat[2] & 0022) != 0;
+
+ # ensure we're the only user with read/write access to the socket
+ @stat = stat($sockname) or die "Can't stat $sockname: $! (Is lacme-accountd running?)\n";
+ die "Error: insecure permissions on $sockname\n" if ($stat[2] & 0066) != 0;
+
+ # connect(2) to the socket
+ socket($client, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ my $sockaddr = Socket::sockaddr_un($sockname) // die "Invalid address $sockname\n";
+ until (connect($client, $sockaddr)) {
+ next if $! == EINTR; # try again if connect(2) was interrupted by a signal
+ die "connect: $!";
+ }
}
# use execve(2) rather than a Perl pseudo-process to ensure that the
# 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() {
drop_privileges($conf->{user}, $conf->{group}, $args->{chdir} // '/');
set_FD_CLOEXEC($_, 0) foreach ($CONFFILE, $client);
@@ -513,9 +555,6 @@ if ($COMMAND eq 'new-reg' or $COMMAND =~ /^reg=/) {
#############################################################################
# new-cert [SECTION ..]
-# TODO: renewal without the account key, see
-# https://github.com/letsencrypt/acme-spec/pull/168
-# https://github.com/letsencrypt/acme-spec/issues/191
#
elsif ($COMMAND eq 'new-cert') {
my $conf;
@@ -558,7 +597,7 @@ elsif ($COMMAND eq 'new-cert') {
my $d = $conf->{'min-days'} // 10;
if ($d > 0 and $t - time > $d*86400) {
my $d = POSIX::strftime('%Y-%m-%d %H:%M:%S UTC', gmtime($t));
- print STDERR "[$s] Valid until $d, skipping\n";
+ print STDERR "[$s] Valid until $d, skipping\n" unless $OPTS{quiet};
next;
}
}