summaryrefslogtreecommitdiffstats
path: root/debian/a2enmod
diff options
context:
space:
mode:
Diffstat (limited to 'debian/a2enmod')
-rwxr-xr-xdebian/a2enmod616
1 files changed, 616 insertions, 0 deletions
diff --git a/debian/a2enmod b/debian/a2enmod
new file mode 100755
index 0000000..da5fa26
--- /dev/null
+++ b/debian/a2enmod
@@ -0,0 +1,616 @@
+#!/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;
+ }
+
+ if ( $acton eq '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" );
+ }
+ }
+ elsif ( $acton eq '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/;
+}