rt 4.2.16
[freeside.git] / rt / lib / RT / Test / Apache.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
6 #                                          <sales@bestpractical.com>
7 #
8 # (Except where explicitly superseded by other copyright notices)
9 #
10 #
11 # LICENSE:
12 #
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17 #
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37 #
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 package RT::Test::Apache;
50 use strict;
51 use warnings;
52
53 my %MODULES = (
54     '2.2' => {
55         "mod_perl" => [qw(authz_host env alias perl)],
56         "fastcgi"  => [qw(authz_host env alias mime fastcgi)],
57     },
58     '2.4' => {
59         "mod_perl" => [qw(mpm_worker authz_core authn_core authz_host env alias perl)],
60         "fastcgi"  => [qw(mpm_worker authz_core authn_core authz_host env alias mime fastcgi)],
61     },
62 );
63
64 my $apache_module_prefix = $ENV{RT_TEST_APACHE_MODULES};
65 my $apxs =
66      $ENV{RT_TEST_APXS}
67   || RT::Test->find_executable('apxs')
68   || RT::Test->find_executable('apxs2');
69
70 if ($apxs and not $apache_module_prefix) {
71     $apache_module_prefix = `$apxs -q LIBEXECDIR`;
72     chomp $apache_module_prefix;
73 }
74
75 $apache_module_prefix ||= 'modules';
76
77 sub basic_auth {
78     my $self = shift;
79     my $passwd = File::Spec->rel2abs( File::Spec->catfile(
80         't', 'data', 'configs', 'passwords' ) );
81
82     return <<"EOT";
83     AuthType Basic
84     AuthName "restricted area"
85     AuthUserFile $passwd
86     Require user root
87 EOT
88 }
89
90 sub basic_auth_anon {
91     my $self = shift;
92
93     return <<"EOT";
94     AuthType Basic
95     AuthName "restricted area"
96     AuthBasicProvider anon
97
98     Anonymous *
99     Anonymous_NoUserID On
100     Anonymous_MustGiveEmail Off
101     Anonymous_VerifyEmail Off
102
103     Require valid-user
104 EOT
105 }
106
107 sub start_server {
108     my ($self, %config) = @_;
109     my %tmp = %{$config{tmp}};
110     my %info = $self->apache_server_info( %config );
111
112     RT::Test::diag(do {
113         open( my $fh, '<', $tmp{'config'}{'RT'} ) or die $!;
114         local $/;
115         <$fh>
116     });
117
118     my $tmpl = File::Spec->rel2abs( File::Spec->catfile(
119         't', 'data', 'configs',
120         'apache'. $info{'version'} .'+'. $config{variant} .'.conf'
121     ) );
122     my %opt = (
123         listen         => $config{port},
124         server_root    => $info{'HTTPD_ROOT'} || $ENV{'HTTPD_ROOT'}
125             || Test::More::BAIL_OUT("Couldn't figure out server root"),
126         document_root  => $RT::MasonComponentRoot,
127         tmp_dir        => "$tmp{'directory'}",
128         rt_bin_path    => $RT::BinPath,
129         rt_sbin_path   => $RT::SbinPath,
130         rt_site_config => $ENV{'RT_SITE_CONFIG'},
131         load_modules   => $info{load_modules},
132     );
133     if (not $config{basic_auth}) {
134         $opt{basic_auth} = "";
135     } elsif ($config{basic_auth} eq 'anon') {
136         $opt{basic_auth} = $self->basic_auth_anon;
137     } else {
138         $opt{basic_auth} = $self->basic_auth;
139     }
140     foreach (qw(log pid lock)) {
141         $opt{$_ .'_file'} = File::Spec->catfile(
142             "$tmp{'directory'}", "apache.$_"
143         );
144     }
145
146     $tmp{'config'}{'apache'} = File::Spec->catfile(
147         "$tmp{'directory'}", "apache.conf"
148     );
149     $self->process_in_file(
150         in      => $tmpl, 
151         out     => $tmp{'config'}{'apache'},
152         options => \%opt,
153     );
154
155     $self->fork_exec($info{'executable'}, '-f', $tmp{'config'}{'apache'});
156     my $pid = do {
157         my $tries = 15;
158         while ( !-s $opt{'pid_file'} ) {
159             $tries--;
160             last unless $tries;
161             sleep 1;
162         }
163         my $pid_fh;
164         unless (-e $opt{'pid_file'} and open($pid_fh, '<', $opt{'pid_file'})) {
165             Test::More::BAIL_OUT("Couldn't start apache server, no pid file (unknown error)")
166                   unless -e $opt{log_file};
167
168             open my $log, "<", $opt{log_file};
169             my $error = do {local $/; <$log>};
170             close $log;
171             $RT::Logger->error($error) if $error;
172             Test::More::BAIL_OUT("Couldn't start apache server!");
173         }
174
175         my $pid = <$pid_fh>;
176         chomp $pid;
177         $pid;
178     };
179
180     Test::More::ok($pid, "Started apache server #$pid");
181     return $pid;
182 }
183
184 sub apache_server_info {
185     my $self = shift;
186     my %res = @_;
187
188     my $bin = $res{'executable'} = $ENV{'RT_TEST_APACHE'}
189         || $self->find_apache_server
190         || Test::More::BAIL_OUT("Couldn't find apache server, use RT_TEST_APACHE");
191
192     Test::More::BAIL_OUT(
193         "Couldn't find apache modules directory (set APXS= or RT_TEST_APACHE_MODULES=)"
194     ) unless -d $apache_module_prefix;
195
196
197     RT::Test::diag("Using '$bin' apache executable for testing");
198
199     my $info = `$bin -v`;
200     ($res{'version'}) = ($info =~ m{Server\s+version:\s+Apache/(\d+\.\d+)\.});
201     Test::More::BAIL_OUT(
202         "Couldn't figure out version of the server"
203     ) unless $res{'version'};
204
205     $res{'modules'} = [
206         map {s/^\s+//; s/\s+$//; $_}
207         grep $_ !~ /Compiled in modules/i,
208         split /\r*\n/, `$bin -l`
209     ];
210
211     Test::More::BAIL_OUT(
212         "Unsupported apache version $res{version}"
213     ) unless exists $MODULES{$res{version}};
214
215     Test::More::BAIL_OUT(
216         "Unsupported apache variant $res{variant}"
217     ) unless exists $MODULES{$res{version}}{$res{variant}};
218
219     my @mlist = @{$MODULES{$res{version}}{$res{variant}}};
220     if ($res{basic_auth}) {
221         push @mlist, "auth_basic", "authz_user";
222         push @mlist, $res{basic_auth} eq 'anon' ? "authn_anon" : "authn_file";
223     }
224
225     $res{'load_modules'} = '';
226     foreach my $mod ( @mlist ) {
227         next if grep $_ =~ /^(mod_|)$mod\.c$/, @{ $res{'modules'} };
228
229         my $so_file = $apache_module_prefix."/mod_".$mod.".so";
230         Test::More::BAIL_OUT( "Couldn't load $mod module (expected in $so_file)" )
231               unless -f $so_file;
232         $res{'load_modules'} .=
233             "LoadModule ${mod}_module $so_file\n";
234     }
235
236     # Apache 2.4 wants to fully-parse a config file when running -V,
237     # because the MPM is no longer compiled-in.  Provide a trivial one.
238     require File::Temp;
239     my $tmp = File::Temp->new;
240     my ($mpm) = grep {/^mpm_/} @{$MODULES{$res{version}}{$res{variant}}};
241     print $tmp "LoadModule ${mpm}_module $apache_module_prefix/mod_${mpm}.so\n"
242         if $mpm;
243     print $tmp "ErrorLog /dev/null\n";
244     print $tmp "TransferLog /dev/null\n";
245     close $tmp;
246     $info = `$res{executable} -V -f $tmp`;
247     my %opts = ($info =~ m/^\s*-D\s+([A-Z_]+?)(?:="(.*)")$/mg);
248     %res = (%res, %opts);
249     return %res;
250 }
251
252 sub find_apache_server {
253     my $self = shift;
254     return $_ foreach grep defined,
255         map RT::Test->find_executable($_),
256         qw(httpd apache apache2 apache1);
257     return undef;
258 }
259
260 sub apache_mpm_type {
261     my $self = shift;
262     my $apache = $self->find_apache_server;
263     my $out = `$apache -l`;
264     if ( $out =~ /^\s*(worker|prefork|event|itk)\.c\s*$/m ) {
265         return $1;
266     }
267     return "worker";
268 }
269
270 sub fork_exec {
271     my $self = shift;
272
273     RT::Test::__disconnect_rt();
274     my $pid = fork;
275     unless ( defined $pid ) {
276         die "cannot fork: $!";
277     } elsif ( !$pid ) {
278         exec @_;
279         die "can't exec `". join(' ', @_) ."` program: $!";
280     } else {
281         RT::Test::__reconnect_rt();
282         return $pid;
283     }
284 }
285
286 sub process_in_file {
287     my $self = shift;
288     my %args = ( in => undef, options => undef, @_ );
289
290     my $text = RT::Test->file_content( $args{'in'} );
291     while ( my ($opt) = ($text =~ /\%\%(.+?)\%\%/) ) {
292         my $value = $args{'options'}{ lc $opt };
293         die "no value for $opt" unless defined $value;
294
295         $text =~ s/\%\%\Q$opt\E\%\%/$value/g;
296     }
297
298     my ($out_fh, $out_conf);
299     unless ( $args{'out'} ) {
300         ($out_fh, $out_conf) = tempfile();
301     } else {
302         $out_conf = $args{'out'};
303         open( $out_fh, '>', $out_conf )
304             or die "couldn't open '$out_conf': $!";
305     }
306     print $out_fh $text;
307     seek $out_fh, 0, 0;
308
309     return ($out_fh, $out_conf);
310 }
311
312 1;