diff options
Diffstat (limited to '')
-rwxr-xr-x | debian/a2enmod | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/debian/a2enmod b/debian/a2enmod new file mode 100755 index 0000000..9d2b091 --- /dev/null +++ b/debian/a2enmod @@ -0,0 +1,617 @@ +#!/usr/bin/perl -w +# +# a2enmod by Stefan Fritsch <sf@debian.org> +# Licensed under Apache License 2.0 +# +# The coding style is "perltidy -pbp" + +use strict; +use Cwd 'realpath'; +use File::Spec; +use File::Basename; +use File::Path; +use Getopt::Long; +use 5.014; +no if $] >= 5.017011, warnings => 'experimental::smartmatch'; + +my $quiet; +my $force; +my $maintmode; +my $purge; + +Getopt::Long::Configure('bundling'); +GetOptions( + 'quiet|q' => \$quiet, + 'force|f' => \$force, + 'maintmode|m' => \$maintmode, + 'purge|p' => \$purge +) or exit 2; + +my $basename = basename($0); +$basename =~ /^a2(en|dis)(mod|site|conf)((?:-.+)?)$/ + or die "$basename call name unknown\n"; +my $act = $1; +my $obj = $2; +my $dir_suffix = $3; + +my @essential_module_list = qw(alias auth_basic authn_file authz_host + authz_user autoindex deflate dir env filter logio mime negotiation + setenvif unixd version watchdog); +my $env_file = $ENV{APACHE_ENVVARS}; +if (! $env_file) { + if ($ENV{APACHE_CONFDIR}) { + $env_file = "$ENV{APACHE_CONFDIR}/envvars"; + } + else { + $env_file = "/etc/apache2$dir_suffix/envvars"; + } +} +$ENV{LANG} = 'C'; +read_env_file($env_file); + +$act .= 'able'; +my ( $name, $dir, $sffx, $reload ); +if ( $obj eq 'mod' ) { + $obj = 'module'; + $dir = 'mods'; + $sffx = '.load'; + $reload = 'restart'; +} +elsif ( $obj eq 'conf' ) { + $obj = 'conf'; + $dir = 'conf'; + $sffx = '.conf'; + $reload = 'reload'; +} +else { + $dir = 'sites'; + $sffx = '.conf'; + $reload = 'reload'; +} +$name = ucfirst($obj); + +my $confdir = $ENV{APACHE_CONFDIR} || "/etc/apache2$dir_suffix"; +my $availdir = $ENV{ uc("APACHE_${dir}_AVAILABLE") } + || "$confdir/$dir-available"; +my $enabldir = $ENV{ uc("APACHE_${dir}_ENABLED") } || "$confdir/$dir-enabled"; +my $statedir = $ENV{ uc("APACHE_STATE_DIRECTORY") } || "/var/lib/apache2"; + +$statedir .= "/$obj"; + +my $choicedir = $act eq 'enable' ? $availdir : $enabldir; +my $linkdir = File::Spec->abs2rel( $availdir, $enabldir ); + +my $request_reload = 0; +my $request_htcacheclean; +my $htc = "apache-htcacheclean$dir_suffix"; +my $htc_service = "apache-htcacheclean"; # Service name for systemd +my $apache_service = "apache2"; +if (defined($dir_suffix) and $dir_suffix ne '') { + # Uses '@instance.service' suffix instead of '-instance' suffix + my $service_suffix = '@' . substr($dir_suffix, 1) . '.service'; + $htc_service .= $service_suffix; + $apache_service .= $service_suffix; +} +my $rc = 0; + +if ( !scalar @ARGV ) { + my @choices = myglob('*'); + print "Your choices are: @choices\n"; + print "Which ${obj}(s) do you want to $act (wildcards ok)?\n"; + my $input = <>; + @ARGV = split /\s+/, $input; + +} + +my @objs; +foreach my $arg (@ARGV) { + $arg =~ s/${sffx}$//; + my @glob = myglob($arg); + if ( !@glob ) { + error("No $obj found matching $arg!\n"); + $rc = 1; + } + else { + push @objs, @glob; + } +} + +foreach my $acton (@objs) { + doit($acton) or $rc = 1; +} + +my $htcstart = ""; +my $apache_reload = ""; +my $cmd = ($act eq "enable") ? "start" : "stop"; +if (is_systemd()) { + $htcstart = " systemctl $cmd $htc_service\n"; + $apache_reload = " systemctl $reload $apache_service\n"; +} else { + $htcstart = " service $htc $cmd\n"; + $apache_reload = " service apache2$dir_suffix $reload\n"; +} +info( "To activate the new configuration, you need to run:\n" + . $apache_reload + . ($request_htcacheclean ? $htcstart : "") +) if $request_reload; + +exit($rc); + +############################################################################## + +sub myglob { + my $arg = shift; + + my @glob = map { + s{^$choicedir/}{}; + s{$sffx$}{}; + $_ + } glob("$choicedir/$arg$sffx"); + + return @glob; +} + +sub doit { + my $acton = shift; + + my ( $conftgt, $conflink ); + if ( $obj eq 'module' ) { + if ( $act eq 'enable' && $acton eq 'cgi' && threaded() ) { + print + "Your MPM seems to be threaded. Selecting cgid instead of cgi.\n"; + $acton = 'cgid'; + } + + $conftgt = "$availdir/$acton.conf"; + if ( -e $conftgt ) { + $conflink = "$enabldir/$acton.conf"; + } + } + + my $tgt = "$availdir/$acton$sffx"; + my $link = "$enabldir/$acton$sffx"; + + if ( !-e $tgt ) { + if ( -l $link && !-e $link ) { + if ( $act eq 'disable' ) { + info("removing dangling symlink $link\n"); + unlink($link); + + # force a .conf path. It may exist as dangling link, too + $conflink = "$enabldir/$acton.conf"; + + if ( -l $conflink && !-e $conflink ) { + info("removing dangling symlink $conflink\n"); + unlink($conflink); + } + + return 1; + } + else { + error("$link is a dangling symlink!\n"); + } + } + + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + # exit silently, we are purging anyway + return 1; + } + + error("$name $acton does not exist!\n"); + return 0; + } + + # handle module dependencies + if ( $obj eq 'module' ) { + if ( $act eq 'enable' ) { + my @depends = get_deps("$availdir/$acton.load"); + do_deps( $acton, @depends ) or return 0; + + my @conflicts = get_deps( "$availdir/$acton.load", "Conflicts" ); + check_conflicts( $acton, @conflicts ) or return 0; + } + else { + my @depending; + foreach my $d ( glob("$enabldir/*.load") ) { + my @deps = get_deps($d); + if ( is_in( $acton, @deps ) ) { + $d =~ m,/([^/]+).load$,; + push @depending, $1; + } + } + if ( scalar @depending ) { + if ($force) { + do_deps( $acton, @depending ) or return 0; + } + else { + error( + "The following modules depend on $acton ", + "and need to be disabled first: @depending\n" + ); + return 0; + } + } + } + } + elsif ( $act eq 'enable' ) { + my @depends = get_deps("$availdir/$acton$sffx"); + warn_deps( $acton, @depends ) or return 0; + } + + if ( $act eq 'enable' ) { + my $check = check_link( $tgt, $link ); + if ( $check eq 'ok' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'ok' ) { + info("$name $acton already enabled\n"); + return 1; + } + elsif ( $confcheck eq 'missing' ) { + print "Enabling config file $acton.conf.\n"; + add_link( $conftgt, $conflink ) or return 0; + } + else { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + else { + info("$name $acton already enabled\n"); + return 1; + } + } + elsif ( $check eq 'missing' ) { + if ($conflink) { + + # handle .conf file + my $confcheck = check_link( $conftgt, $conflink ); + if ( $confcheck eq 'missing' ) { + add_link( $conftgt, $conflink ) or return 0; + } + elsif ( $confcheck ne 'ok' ) { + error( + "Config file $acton.conf not properly enabled: $confcheck\n" + ); + return 0; + } + } + + print "Enabling $obj $acton.\n"; + special_module_handling($acton); + return add_link( $tgt, $link ) + && switch_marker( $obj, $act, $acton ); + } + else { + error("$name $acton not properly enabled: $check\n"); + return 0; + } + } + else { + if ( -e $link || -l $link ) { + special_module_handling($acton); + if ($obj eq 'module' && grep {$_ eq $acton} @essential_module_list) { + $force || essential_module_handling($acton); + } + remove_link($link); + if ( $conflink && -e $conflink ) { + remove_link($conflink); + } + switch_marker( $obj, $act, $acton ); + print "$name $acton disabled.\n"; + } + elsif ( $conflink && -e $conflink ) { + print "Disabling stale config file $acton.conf.\n"; + remove_link($conflink); + } + else { + info("$name $acton already disabled\n"); + if ( $purge ) { + switch_marker( $obj, $act, $acton ); + } + return 1; + } + } + + return 1; +} + +sub get_deps { + my $file = shift; + my $type = shift || "Depends"; + + my $fd; + if ( !open( $fd, '<', $file ) ) { + error("Can't open $file: $!"); + return; + } + my $line; + while ( defined( $line = <$fd> ) ) { + chomp $line; + if ( $line =~ /^# $type:\s+(.*?)\s*$/ ) { + my $deps = $1; + return split( /[\n\s]+/, $deps ); + } + + # only check until the first non-empty non-comment line + last if ( $line !~ /^\s*(?:#.*)?$/ ); + } + return; +} + +sub do_deps { + my $acton = shift; + foreach my $d (@_) { + info("Considering dependency $d for $acton:\n"); + if ( !doit($d) ) { + error("Could not $act dependency $d for $acton, aborting\n"); + return 0; + } + } + return 1; +} + +sub warn_deps { + my $acton = shift; + my $modsenabldir = $ENV{APACHE_MODS_ENABLED} || "$confdir/mods-enabled"; + foreach my $d (@_) { + info("Checking dependency $d for $acton:\n"); + if ( !-e "$modsenabldir/$d.load" ) { + warning( + "Module $d is not enabled, but $acton depends on it, aborting\n" + ); + return 0; + } + } + return 1; +} + +sub check_conflicts { + my $acton = shift; + my $haderror = 0; + foreach my $d (@_) { + info("Considering conflict $d for $acton:\n"); + + my $tgt = "$availdir/$d$sffx"; + my $link = "$enabldir/$d$sffx"; + + my $confcheck = check_link( $tgt, $link ); + if ( $confcheck eq 'ok' ) { + error( + "Module $d is enabled - cannot proceed due to conflicts. It needs to be disabled first!\n" + ); + + # Don't return immediately, there could be several conflicts + $haderror++; + } + } + + if ($haderror) { + return 0; + } + + return 1; +} + +sub add_link { + my ( $tgt, $link ) = @_; + + # create relative link + if ( !symlink( File::Spec->abs2rel( $tgt, dirname($link) ), $link ) ) { + die("Could not create $link: $!\n"); + } + $request_reload = 1; + return 1; +} + +sub check_link { + my ( $tgt, $link ) = @_; + + if ( !-e $link ) { + if ( -l $link ) { + + # points to nowhere + info("Removing dangling link $link"); + unlink($link) or die "Could not remove $link\n"; + } + return 'missing'; + } + + if ( -e $link && !-l $link ) { + return "$link is a real file, not touching it"; + } + if ( realpath($link) ne realpath($tgt) ) { + return "$link exists but does not point to $tgt, not touching it"; + } + return 'ok'; +} + +sub remove_link { + my ($link) = @_; + + if ( -l $link ) { + unlink($link) or die "Could not remove $link: $!\n"; + } + elsif ( -e $link ) { + error("$link is not a symbolic link, not deleting\n"); + return 0; + } + $request_reload = 1; + return 1; +} + +sub threaded { + my $result = ""; + $result = qx{/usr/sbin/apache2ctl -V | grep 'threaded'} + if -x '/usr/sbin/apache2ctl'; + if ( $? != 0 ) { + + # config doesn't work + if ( -e "$enabldir/mpm_prefork.load" ) + { + return 0; + } + elsif (-e "$enabldir/mpm_worker.load" + || -e "$enabldir/mpm_event.load" ) + { + return 1; + } + else { + error("Can't determine enabled MPM"); + + # do what user requested + return 0; + } + } + if ( $result =~ / no/ ) { + return 0; + } + elsif ( $result =~ / yes/ ) { + return 1; + } + else { + die("Can't parse output from apache2ctl -V:\n$result\n"); + } +} + +sub info { + print @_ if !$quiet; +} + +sub error { + print STDERR 'ERROR: ', @_; +} + +sub warning { + print STDERR 'WARNING: ', @_; +} + +sub is_in { + my $needle = shift; + foreach my $e (@_) { + return 1 if $needle eq $e; + } + return 0; +} + +sub read_env_file { + my $file = shift; + + -r $file or return; + my @lines = qx{env - sh -c '. $file && env'}; + if ($?) { + die "Could not read $file\n"; + } + + foreach my $l (@lines) { + chomp $l; + $l =~ /^(.*)?=(.*)$/ or die "Could not parse $file\n"; + $ENV{$1} = $2; + } +} + +sub switch_marker { + die('usage: switch_marker([module|site|conf], [enable|disable], $name)') + if @_ != 3; + my $which = shift; + my $what = shift; + my $name = shift; + + my $mode = "admin"; + $mode = "maint" if $maintmode; + + #print("switch_marker $which $what $name\n"); + # TODO: get rid of the magic string(s) + my $state_marker_dir = "$statedir/$what" . "d" . "_by_$mode"; + my $state_marker = "$state_marker_dir/$name"; + if ( !-d $state_marker_dir ) { + File::Path::mkpath("$state_marker_dir") + || error( + "Failed to create marker directory: '$state_marker_dir'\n"); + } + + # XXX: swap find with perl alternative + my @markers = qx{find "$statedir" -type f -a -name "$name"}; + chomp(@markers); + foreach (@markers) { + unless ( unlink $_ ) { + error("Failed to remove old marker '$_'!\n") && return 0; + } + } + unless ($purge) { + qx{touch "$state_marker"}; + if ( $? != 0 ) { + error("Failed to create marker '$state_marker'!\n") && return 0; + } + return 1; + } +} + +sub essential_module_handling { + my $module = shift; + + print "WARNING: The following essential module will be disabled.\n"; + print "This might result in unexpected behavior and should NOT be done\n"; + print "unless you know exactly what you are doing!\n $module\n\n"; + print "To continue type in the phrase 'Yes, do as I say!' or retry by passing '-f': "; + my $input = <STDIN>; + chomp($input); + if ($input ne 'Yes, do as I say!') { + print("Aborting\n"); + exit(1) + } +} + +sub special_module_handling { + my $acton = shift; + + if ($obj ne 'module') { + return; + } + + given ($acton) { + when ('ssl') { + if ( $act eq 'enable' ) { + info( "See /usr/share/doc/apache2/README.Debian.gz on " + . "how to configure SSL and create self-signed " + . "certificates.\n" + ); + } + } + when ('cache_disk') { + $request_htcacheclean = 1; + my $verb = "\u$act"; + my $command; + $verb =~ s/e$/ing/; + if (-d "/run/systemd" and -x "/bin/systemctl") { + info("$verb external service $htc_service\n"); + $command = "systemctl $act $htc_service"; + } else { + info("$verb external service $htc\n"); + $command = "update-rc.d $htc $act"; + } + my $res = system($command); + if ($res == 0) { + info("The service will be started on next reboot.\n") + if $act eq 'enable'; + } + else { + warning("'$command' failed\n"); + } + + } + } +} + +sub is_systemd +{ + my $init = readlink("/proc/1/exe") || ""; + return scalar $init =~ /systemd/; +} + +# vim: syntax=perl sw=4 sts=4 sr et |