aboutsummaryrefslogtreecommitdiffstats
path: root/lacme
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2021-02-14 17:01:17 +0100
committerGuilhem Moulin <guilhem@fripost.org>2021-02-14 20:04:40 +0100
commita903ea92dd736c560d21fe45063d4914765fa173 (patch)
treeca4f7e352cc01d1f05b63df20e4eb142f98e28ca /lacme
parent9dfb2cde7baf686113e49266c28940c8a564c1ca (diff)
challenge-directory now needs to be set to an *existing* directory.
Since lacme(8) spawns a builtin webserver by default the change doesn't affect default configurations. See https://bugs.debian.org/970800 for the rationale.
Diffstat (limited to 'lacme')
-rwxr-xr-xlacme82
1 files changed, 46 insertions, 36 deletions
diff --git a/lacme b/lacme
index d7ae8ce..7ad7aa8 100755
--- a/lacme
+++ b/lacme
@@ -26,9 +26,8 @@ our $VERSION = '0.3';
my $NAME = 'lacme';
use Errno 'EINTR';
-use Fcntl qw/F_GETFD F_SETFD FD_CLOEXEC SEEK_SET/;
+use Fcntl qw/F_GETFD F_SETFD FD_CLOEXEC O_CREAT O_EXCL O_WRONLY SEEK_SET/;
use File::Temp ();
-use File::Path 'remove_tree';
use Getopt::Long qw/:config posix_default no_ignore_case gnu_getopt auto_version/;
use List::Util 'first';
use POSIX ();
@@ -105,7 +104,6 @@ do {
webserver => {
listen => '@@runstatedir@@/lacme-www.socket',
'challenge-directory' => undef,
- 'hard-copy-challenge-directory' => 'No',
user => '@@lacme_www_user@@',
group => '@@lacme_www_group@@',
command => '@@libexecdir@@/lacme/webserver',
@@ -258,15 +256,6 @@ sub set_FD_CLOEXEC($$) {
# The temporary challenge directory is returned.
#
sub spawn_webserver() {
- # create a temporary directory; give write access to the ACME client
- # and read access to the webserver
- my $tmpdir = File::Temp::->newdir(CLEANUP => 1, TMPDIR => 1) // die;
- chmod 0755, $tmpdir or die "Can't chmod: $!";
- if ((my $username = $CONFIG->{client}->{user}) ne '') {
- my $uid = getpwnam($username) // die "Can't getpwnam($username): $!";
- chown($uid, -1, $tmpdir) or die "Can't chown: $!";
- }
-
my $conf = $CONFIG->{webserver};
# parse and pack addresses to listen to
@@ -286,35 +275,56 @@ sub spawn_webserver() {
push @sockaddr, $sockaddr;
}
- # symlink the 'challenge-directory' configuration option to the
- # temporary challenge directory (so an existing httpd can directly
- # serve ACME challenge reponses).
+ # Use existing HTTPd to serve challenge files using 'challenge-directory'
+ # as document root
if (defined (my $dir = $conf->{'challenge-directory'})) {
print STDERR "[$$] Using existing webserver on $dir\n" if $OPTS{debug};
- if (lc ($conf->{'hard-copy-challenge-directory'} // 'No') eq 'yes') {
- mkdir $dir or die "Can't create directory $dir: $!";
- $tmpdir = $dir;
- push @CLEANUP, sub() {
- my $error = undef;
- remove_tree($dir, { safe => 1, error => \$error });
- if ($error && @$error) {
- foreach my $e (@$error) {
- my ($file, $message) = %$e;
- my $msghead = $file?"Error removing $file in":"Error while removing";
- warn "$msghead challenge dir $dir: $message\n";
- }
+ # lacme(8) doesn't have the list of challenge files to delete on
+ # cleanup -- instead, we unlink all files and fails at
+ # initialization stage when the challenge directory is not empty
+
+ opendir my $dh, $dir or die "opendir($dir): $!\n";
+ while (readdir $dh) {
+ die "Error: Refusing to use non-empty challenge directory $dir\n"
+ unless $_ eq '.' or $_ eq '..';
+ }
+ closedir $dh or die "close: $!";
+ undef $dh;
+
+ # use a "lock file" (NFS-friendly) to avoid concurrent usages
+ my $lockfile = ".$NAME.lock";
+ sysopen(my $fh, "$dir/$lockfile", O_CREAT|O_EXCL|O_WRONLY, 0600)
+ or die "Can't create lockfile in challenge directory: $!";
+ print $fh $$, "\n";
+ close $fh or die "close: $!";
+ undef $fh;
+
+ push @CLEANUP, sub() {
+ if (opendir(my $dh, $dir)) {
+ my @files = grep { $_ ne '.' and $_ ne '..' and $_ ne $lockfile } readdir $dh;
+ closedir $dh or warn "close: $!";
+ push @files, $lockfile; # unlink $lockfile last
+ foreach (@files) {
+ die unless /\A(.+)\z/; # untaint
+ unlink "$dir/$1" or warn "unlink($dir/$1): $!";
}
+ } else {
+ warn "opendir($dir): $!\n";
}
- } else {
- symlink $tmpdir, $dir or die "Can't symlink $dir -> $tmpdir: $!";
- push @CLEANUP, sub() {
- print STDERR "Unlinking $dir\n" if $OPTS{debug};
- unlink $dir or warn "Warning: Can't unlink $dir: $!";
- }
- }
+ };
+ return $dir; # ignore 'listen' and 'iptables'
}
- elsif (!@sockaddr) {
- die "'challenge-directory' option of section [webserver] is required when 'listen' is empty\n";
+
+ die "'challenge-directory' option is required in section [webserver] when 'listen' is empty\n"
+ unless @sockaddr;
+
+ # create a temporary directory; give write access to the ACME client
+ # and read access to the webserver
+ my $tmpdir = File::Temp::->newdir(CLEANUP => 1, TMPDIR => 1) // die;
+ chmod 0755, $tmpdir or die "Can't chmod: $!";
+ if ((my $username = $CONFIG->{client}->{user}) ne '') {
+ my $uid = getpwnam($username) // die "Can't getpwnam($username): $!";
+ chown($uid, -1, $tmpdir) or die "Can't chown: $!";
}
# create socket(s) and spawn webserver(s)