diff options
Diffstat (limited to 'lacme')
-rwxr-xr-x | lacme | 119 |
1 files changed, 68 insertions, 51 deletions
@@ -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; |