# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package Apache::TestConfig; #not TestConfigParse on purpose #dont really want/need a full-blown parser #but do want something somewhat generic use strict; use warnings FATAL => 'all'; use Apache::TestTrace; use File::Spec::Functions qw(rel2abs splitdir file_name_is_absolute); use File::Basename qw(dirname basename); sub strip_quotes { local $_ = shift || $_; s/^\"//; s/\"$//; $_; } my %wanted_config = ( TAKE1 => {map { $_, 1 } qw(ServerRoot ServerAdmin TypesConfig DocumentRoot)}, TAKE2 => {map { $_, 1 } qw(LoadModule LoadFile)}, ); my %spec_init = ( TAKE1 => sub { shift->{+shift} = "" }, TAKE2 => sub { shift->{+shift} = [] }, ); my %spec_apply = ( TypesConfig => \&inherit_server_file, ServerRoot => sub {}, #dont override $self->{vars}->{serverroot} DocumentRoot => \&inherit_directive_var, LoadModule => \&inherit_load_module, LoadFile => \&inherit_load_file, ); #where to add config, default is preamble my %spec_postamble = map { $_, 'postamble' } qw(TypesConfig); # need to enclose the following directives into .., since mod_foo might be unavailable my %ifmodule = ( TypesConfig => 'mod_mime.c', ); sub spec_add_config { my($self, $directive, $val) = @_; my $where = $spec_postamble{$directive} || 'preamble'; if (my $ifmodule = $ifmodule{TypesConfig}) { $self->postamble(< $directive $val EOI } else { $self->$where($directive => $val); } } # resolve relative files like Apache->server_root_relative # this function doesn't test whether the resolved file exists sub server_file_rel2abs { my($self, $file, $base) = @_; my ($serverroot, $result) = (); # order search sequence my @tries = ([ $base, 'user-supplied $base' ], [ $self->{inherit_config}->{ServerRoot}, 'httpd.conf inherited ServerRoot' ], [ $self->apxs('PREFIX', 1), 'apxs-derived ServerRoot' ]); # remove surrounding quotes if any # e.g. Include "/tmp/foo.html" $file =~ s/^\s*["']?//; $file =~ s/["']?\s*$//; if (file_name_is_absolute($file)) { debug "$file is already absolute"; $result = $file; } else { foreach my $try (@tries) { next unless defined $try->[0]; if (-d $try->[0]) { $serverroot = $try->[0]; debug "using $try->[1] to resolve $file"; last; } } if ($serverroot) { $result = rel2abs $file, $serverroot; } else { warning "unable to resolve $file - cannot find a suitable ServerRoot"; warning "please specify a ServerRoot in your httpd.conf or use apxs"; # return early, skipping file test below return $file; } } my $dir = dirname $result; # $file might not exist (e.g. if it's a glob pattern like # "conf/*.conf" but what we care about here is to check whether # the base dir was successfully resolved. we don't check whether # the file exists at all. it's the responsibility of the caller to # do this check if (defined $dir && -e $dir && -d _) { if (-e $result) { debug "$file successfully resolved to existing file $result"; } else { debug "base dir of '$file' successfully resolved to $dir"; } } else { $dir ||= ''; warning "dir '$dir' does not exist (while resolving '$file')"; # old behavior was to return the resolved but non-existent # file. preserve that behavior and return $result anyway. } return $result; } sub server_file { my $f = shift->server_file_rel2abs(@_); return qq("$f"); } sub inherit_directive_var { my($self, $c, $directive) = @_; $self->{vars}->{"inherit_\L$directive"} = $c->{$directive}; } sub inherit_server_file { my($self, $c, $directive) = @_; $self->spec_add_config($directive, $self->server_file($c->{$directive})); } #so we have the same names if these modules are linked static or shared my %modname_alias = ( 'mod_pop.c' => 'pop_core.c', 'mod_proxy_ajp.c' => 'proxy_ajp.c', 'mod_proxy_http.c' => 'proxy_http.c', 'mod_proxy_ftp.c' => 'proxy_ftp.c', 'mod_proxy_balancer.c' => 'proxy_balancer.c', 'mod_proxy_connect.c' => 'proxy_connect.c', 'mod_modperl.c' => 'mod_perl.c', ); # Block modules which inhibit testing: # - mod_jk requires JkWorkerFile or JkWorker to be configured # skip it for now, tomcat has its own test suite anyhow. # - mod_casp2 requires other settings in addition to LoadModule # - mod_bwshare and mod_evasive20 block fast requests that tests are doing # - mod_fcgid causes https://rt.cpan.org/Public/Bug/Display.html?id=54476 # - mod_modnss.c and mod_rev.c require further configuration my @autoconfig_skip_module = qw(mod_jk.c mod_casp2.c mod_bwshare.c mod_fcgid.c mod_evasive20.c mod_modnss.c mod_rev.c); # add modules to be not inherited from the existing config. # e.g. prevent from LoadModule perl_module to be included twice, when # mod_perl already configures LoadModule and it's certainly found in # the existing httpd.conf installed system-wide. sub autoconfig_skip_module_add { push @autoconfig_skip_module, @_; } sub should_skip_module { my($self, $name) = @_; for (@autoconfig_skip_module) { if (UNIVERSAL::isa($_, 'Regexp')) { return 1 if $name =~ /$_/; } else { return 1 if $name eq $_; } } return 0; } #inherit LoadModule sub inherit_load_module { my($self, $c, $directive) = @_; for my $args (@{ $c->{$directive} }) { my $modname = $args->[0]; my $file = $self->server_file_rel2abs($args->[1]); unless (-e $file) { debug "$file does not exist, skipping LoadModule"; next; } my $name = basename $args->[1]; $name =~ s/\.(s[ol]|dll)$/.c/; #mod_info.so => mod_info.c $name =~ s/^lib/mod_/; #libphp4.so => mod_php4.c $name = $modname_alias{$name} if $modname_alias{$name}; # remember all found modules $self->{modules}->{$name} = $file; debug "Found: $modname => $name"; if ($self->should_skip_module($name)) { debug "Skipping LoadModule of $name"; next; } debug "LoadModule $modname $name"; # sometimes people have broken system-wide httpd.conf files, # which include LoadModule of modules, which are built-in, but # won't be skipped above if they are found in the modules/ # directory. this usually happens when httpd is built once # with its modules built as shared objects and then again with # static ones: the old httpd.conf still has the LoadModule # directives, even though the modules are now built-in # so we try to workaround this problem using $self->preamble(IfModule => "!$name", qq{LoadModule $modname "$file"\n}); } } #inherit LoadFile sub inherit_load_file { my($self, $c, $directive) = @_; for my $args (@{ $c->{$directive} }) { my $file = $self->server_file_rel2abs($args->[0]); unless (-e $file) { debug "$file does not exist, skipping LoadFile"; next; } if ($self->should_skip_module($args->[0])) { debug "Skipping LoadFile of $args->[0]"; next; } # remember all found modules push @{$self->{load_file}}, $file; debug "LoadFile $file"; $self->preamble_first(qq{LoadFile "$file"\n}); } } sub parse_take1 { my($self, $c, $directive) = @_; $c->{$directive} = strip_quotes; } sub parse_take2 { my($self, $c, $directive) = @_; push @{ $c->{$directive} }, [map { strip_quotes } split]; } sub apply_take1 { my($self, $c, $directive) = @_; if (exists $self->{vars}->{lc $directive}) { #override replacement @Variables@ $self->{vars}->{lc $directive} = $c->{$directive}; } else { $self->spec_add_config($directive, qq("$c->{$directive}")); } } sub apply_take2 { my($self, $c, $directive) = @_; for my $args (@{ $c->{$directive} }) { $self->spec_add_config($directive => [map { qq("$_") } @$args]); } } sub inherit_config_file_or_directory { my ($self, $item) = @_; if (-d $item) { my $dir = $item; debug "descending config directory: $dir"; for my $entry (glob "$dir/*") { $self->inherit_config_file_or_directory($entry); } return; } my $file = $item; debug "inheriting config file: $file"; my $fh = Symbol::gensym(); open($fh, $file) or return; my $c = $self->{inherit_config}; while (<$fh>) { s/^\s*//; s/\s*$//; s/^\#.*//; next if /^$/; # support continuous config lines (which use \ to break the line) while (s/\\$//) { my $cont = <$fh>; $cont =~ s/^\s*//; $cont =~ s/\s*$//; $_ .= $cont; } (my $directive, $_) = split /\s+/, $_, 2; if ($directive eq "Include" or $directive eq "IncludeOptional") { foreach my $include (glob($self->server_file_rel2abs($_))) { $self->inherit_config_file_or_directory($include); } } #parse what we want while (my($spec, $wanted) = each %wanted_config) { next unless $wanted->{$directive}; my $method = "parse_\L$spec"; $self->$method($c, $directive); } } close $fh; } sub inherit_config { my $self = shift; $self->get_httpd_static_modules; $self->get_httpd_defines; #may change after parsing httpd.conf $self->{vars}->{inherit_documentroot} = catfile $self->{httpd_basedir}, 'htdocs'; my $file = $self->{vars}->{httpd_conf}; my $extra_file = $self->{vars}->{httpd_conf_extra}; unless ($file and -e $file) { if (my $base = $self->{httpd_basedir}) { my $default_conf = $self->{httpd_defines}->{SERVER_CONFIG_FILE}; $default_conf ||= catfile qw(conf httpd.conf); $file = catfile $base, $default_conf; # SERVER_CONFIG_FILE might be an absolute path unless (-e $file) { if (-e $default_conf) { $file = $default_conf; } else { # try a little harder if (my $root = $self->{httpd_defines}->{HTTPD_ROOT}) { debug "using HTTPD_ROOT to resolve $default_conf"; $file = catfile $root, $default_conf; } } } } } unless ($extra_file and -e $extra_file) { if ($extra_file and my $base = $self->{httpd_basedir}) { my $default_conf = catfile qw(conf $extra_file); $extra_file = catfile $base, $default_conf; # SERVER_CONFIG_FILE might be an absolute path $extra_file = $default_conf if !-e $extra_file and -e $default_conf; } } return unless $file or $extra_file; my $c = $self->{inherit_config}; #initialize array refs and such while (my($spec, $wanted) = each %wanted_config) { for my $directive (keys %$wanted) { $spec_init{$spec}->($c, $directive); } } $self->inherit_config_file_or_directory($file) if $file; $self->inherit_config_file_or_directory($extra_file) if $extra_file; #apply what we parsed while (my($spec, $wanted) = each %wanted_config) { for my $directive (keys %$wanted) { next unless $c->{$directive}; my $cv = $spec_apply{$directive} || $self->can("apply_\L$directive") || $self->can("apply_\L$spec"); $cv->($self, $c, $directive); } } } sub get_httpd_static_modules { my $self = shift; my $httpd = $self->{vars}->{httpd}; return unless $httpd; $httpd = shell_ready($httpd); my $cmd = "$httpd -l"; my $list = $self->open_cmd($cmd); while (<$list>) { s/\s+$//; next unless /\.c$/; chomp; s/^\s+//; $self->{modules}->{$_} = 1; } close $list; } sub get_httpd_defines { my $self = shift; my $httpd = $self->{vars}->{httpd}; return unless $httpd; $httpd = shell_ready($httpd); my $cmd = "$httpd -V"; my $httpdconf = $self->{vars}->{httpd_conf}; $cmd .= " -f $httpdconf" if $httpdconf; my $serverroot = $self->{vars}->{serverroot}; $cmd .= " -d $serverroot" if $serverroot; my $proc = $self->open_cmd($cmd); while (<$proc>) { chomp; if( s/^\s*-D\s*//) { s/\s+$//; my($key, $val) = split '=', $_, 2; $self->{httpd_defines}->{$key} = $val ? strip_quotes($val) : 1; debug "isolated httpd_defines $key = " . $self->{httpd_defines}->{$key}; } elsif (/(version|built|module magic number|server mpm):\s+(.*)/i) { my $val = $2; (my $key = uc $1) =~ s/\s/_/g; $self->{httpd_info}->{$key} = $val; debug "isolated httpd_info $key = " . $val; } } close $proc; if (my $mmn = $self->{httpd_info}->{MODULE_MAGIC_NUMBER}) { @{ $self->{httpd_info} } {qw(MODULE_MAGIC_NUMBER_MAJOR MODULE_MAGIC_NUMBER_MINOR)} = split ':', $mmn; } # get the mpm information where available # lowercase for consistency across the two extraction methods # XXX or maybe consider making have_apache_mpm() case-insensitive? if (my $mpm = $self->{httpd_info}->{SERVER_MPM}) { # 2.1 $self->{mpm} = lc $mpm; } elsif (my $mpm_dir = $self->{httpd_defines}->{APACHE_MPM_DIR}) { # 2.0 $self->{mpm} = lc basename $mpm_dir; } else { # Apache 1.3 - no mpm to speak of $self->{mpm} = ''; } my $version = $self->{httpd_info}->{VERSION} || ''; if ($version =~ qr,Apache/2,) { # PHP 4.x on httpd-2.x needs a special modname alias: $modname_alias{'mod_php4.c'} = 'sapi_apache2.c'; } unless ($version =~ qr,Apache/(2.0|1.3),) { # for 2.1 and later, mod_proxy_* are really called mod_proxy_* delete @modname_alias{grep {/^mod_proxy_/} keys %modname_alias}; } } sub httpd_version { my $self = shift; my $httpd = $self->{vars}->{httpd}; return unless $httpd; my $version; $httpd = shell_ready($httpd); my $cmd = "$httpd -v"; my $v = $self->open_cmd($cmd); local $_; while (<$v>) { next unless s/^Server\s+version:\s*//i; chomp; my @parts = split; foreach (@parts) { next unless /^Apache\//; $version = $_; last; } $version ||= $parts[0]; last; } close $v; return $version; } sub httpd_mpm { return shift->{mpm}; } 1;