diff options
Diffstat (limited to 'lib/Lintian/Check/Apache2.pm')
-rw-r--r-- | lib/Lintian/Check/Apache2.pm | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/lib/Lintian/Check/Apache2.pm b/lib/Lintian/Check/Apache2.pm new file mode 100644 index 0000000..b8dde2d --- /dev/null +++ b/lib/Lintian/Check/Apache2.pm @@ -0,0 +1,337 @@ +# apache2 -- lintian check script -*- perl -*- +# +# Copyright (C) 2012 Arno Toell +# Copyright (C) 2017-2018 Chris Lamb <lamby@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, you can find it on the World Wide +# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free +# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, +# MA 02110-1301, USA. + +package Lintian::Check::Apache2; + +use v5.20; +use warnings; +use utf8; + +use File::Basename; +use Unicode::UTF8 qw(encode_utf8); + +use Lintian::Relation; + +use Moo; +use namespace::clean; + +with 'Lintian::Check'; + +# whether the package appears to be an Apache2 module/web application +has is_apache2_related => ( + is => 'rw', + lazy => 1, + default => sub { + my ($self) = @_; + + for my $item (@{$self->processable->installed->sorted_list}) { + + return 1 + if $item->name =~ m{^ usr/lib/apache2/modules/ }x + && $item->basename =~ m{ [.]so $}x; + + return 1 + if $item->name + =~ m{^ etc/apache2/ (?:conf|site) - (?:available|enabled) / }x; + + return 1 + if $item->name =~ m{^ etc/apache2/conf[.]d/}x; + } + + return 0; + } +); + +sub visit_installed_files { + my ($self, $item) = @_; + + # Do nothing if the package in question appears to be related to + # the web server itself + return + if $self->processable->name =~ m/^apache2(:?\.2)?(?:-\w+)?$/; + + # File is probably not relevant to us, ignore it + return + if $item->is_dir; + + return + if $item->name !~ m{^(?:usr/lib/apache2/modules/|etc/apache2/)}; + + # Package installs an unrecognized file - check this for all files + if ( $item->name !~ /\.conf$/ + && $item->name =~ m{^etc/apache2/(conf|site|mods)-available/(.*)$}){ + + my $temp_type = $1; + my $temp_file = $2; + + # ... except modules which are allowed to ship .load files + $self->pointed_hint('apache2-configuration-files-need-conf-suffix', + $item->pointer) + unless $temp_type eq 'mods' && $temp_file =~ /\.load$/; + } + + # Package appears to be a binary module + if ($item->name =~ m{^usr/lib/apache2/modules/(.*)\.so$}) { + + $self->check_module_package($item, $1); + } + + # Package appears to be a web application + elsif ($item->name =~ m{^etc/apache2/(conf|site)-available/(.*)$}) { + + $self->check_web_application_package($item, $1, $2); + } + + # Package appears to be a legacy web application + elsif ($item->name =~ m{^etc/apache2/conf\.d/(.*)$}) { + + $self->pointed_hint( + 'apache2-reverse-dependency-uses-obsolete-directory', + $item->pointer); + $self->check_web_application_package($item,'conf', $1); + } + + # Package does scary things + elsif ($item->name =~ m{^etc/apache2/(?:conf|sites|mods)-enabled/.*$}) { + + $self->pointed_hint( + 'apache2-reverse-dependency-ships-file-in-not-allowed-directory', + $item->pointer); + } + + return; +} + +sub installable { + my ($self) = @_; + + # Do nothing if the package in question appears to be related to + # the web server itself + return + if $self->processable->name =~ m/^apache2(:?\.2)?(?:-\w+)?$/; + + return; +} + +sub check_web_application_package { + my ($self, $item, $pkgtype, $webapp) = @_; + + my $pkg = $self->processable->name; + my $processable = $self->processable; + + $self->pointed_hint('non-standard-apache2-configuration-name', + $item->pointer, "$webapp != $pkg.conf") + if $webapp ne "$pkg.conf" + || $webapp =~ /^local-/; + + my $rel = $processable->relation('strong') + ->logical_and($processable->relation('Recommends')); + + # A web application must not depend on apache2-whatever + my $visit = sub { + if (m/^apache2(?:\.2)?-(?:common|data|bin)$/) { + $self->pointed_hint( + 'web-application-depends-on-apache2-data-package', + $item->pointer, $_, $webapp); + return 1; + } + return 0; + }; + $rel->visit($visit, Lintian::Relation::VISIT_STOP_FIRST_MATCH); + + # ... nor on apache2 only. Moreover, it should be in the form + # apache2 | httpd but don't worry about versions, virtual package + # don't support that + $self->pointed_hint('web-application-works-only-with-apache', + $item->pointer, $webapp) + if $rel->satisfies('apache2'); + + $self->inspect_conf_file($pkgtype, $item); + return; +} + +sub check_module_package { + my ($self, $item, $module) = @_; + + my $pkg = $self->processable->name; + my $processable = $self->processable; + + # We want packages to be follow our naming scheme. Modules should be named + # libapache2-mod-<foo> if it ships a mod_foo.so + # NB: Some modules have uppercase letters in them (e.g. Ruwsgi), but + # obviously the package should be in all lowercase. + my $expected_name = 'libapache2-' . lc($module); + + my $rel; + + $expected_name =~ tr/_/-/; + $self->pointed_hint('non-standard-apache2-module-package-name', + $item->pointer, "$pkg != $expected_name") + if $expected_name ne $pkg; + + $rel = $processable->relation('strong') + ->logical_and($processable->relation('Recommends')); + + $self->pointed_hint('apache2-module-does-not-depend-on-apache2-api', + $item->pointer) + if !$rel->matches(qr/^apache2-api-\d+$/); + + # The module is called mod_foo.so, thus the load file is expected to be + # named foo.load + my $load_file = $module; + my $conf_file = $module; + $load_file =~ s{^mod.(.*)$}{etc/apache2/mods-available/$1.load}; + $conf_file =~ s{^mod.(.*)$}{etc/apache2/mods-available/$1.conf}; + + if (my $f = $processable->installed->lookup($load_file)) { + $self->inspect_conf_file('mods', $f); + } else { + $self->pointed_hint('apache2-module-does-not-ship-load-file', + $item->pointer, $load_file); + } + + if (my $f = $processable->installed->lookup($conf_file)) { + $self->inspect_conf_file('mods', $f); + } + + return; +} + +sub visit_control_files { + my ($self, $item) = @_; + + return + unless $self->is_apache2_related; + + return + unless $item->is_maintainer_script; + + # skip anything but shell scripts + return + unless $item->is_shell_script; + + return + unless $item->is_open_ok; + + open(my $sfd, '<', $item->unpacked_path) + or die encode_utf8('Cannot open ' . $item->unpacked_path); + + my $position = 1; + while (my $line = <$sfd>) { + + # skip comments + next + if $line =~ /^ [#]/x; + + # Do not allow reverse dependencies to call "a2enmod" and friends + # directly + if ($line =~ m{ \b (a2(?:en|dis)(?:conf|site|mod)) \b }x) { + + my $command = $1; + + $self->pointed_hint( + 'apache2-reverse-dependency-calls-wrapper-script', + $item->pointer($position), $command); + } + + # Do not allow reverse dependencies to call "invoke-rc.d apache2 + $self->pointed_hint('apache2-reverse-dependency-calls-invoke-rc.d', + $item->pointer($position)) + if $line =~ /invoke-rc\.d\s+apache2/; + + # XXX: Check whether apache2-maintscript-helper is used + # unconditionally e.g. not protected by a [ -e ], [ -x ] or so. + # That's going to be complicated. Or not possible without grammar + # parser. + + } continue { + ++$position; + } + + return; +} + +sub inspect_conf_file { + my ($self, $conftype, $item) = @_; + + # Don't follow unsafe links + return + unless $item->is_open_ok; + + open(my $fd, '<', $item->unpacked_path) + or die encode_utf8('Cannot open ' . $item->unpacked_path); + + my $skip = 0; + + my $position = 1; + while (my $line = <$fd>) { + + ++$skip + if $line =~ m{<\s*IfModule.*!\s*mod_authz_core} + || $line =~ m{<\s*IfVersion\s+<\s*2\.3}; + + for my $directive ('Order', 'Satisfy', 'Allow', 'Deny', + qr{</?Limit.*?>}xsm, qr{</?LimitExcept.*?>}xsm) { + + if ($line =~ m{\A \s* ($directive) (?:\s+|\Z)}xsm && !$skip) { + + $self->pointed_hint('apache2-deprecated-auth-config', + $item->pointer($position), $1); + } + } + + if ($line =~ /^#\s*(Depends|Conflicts):\s+(.*?)\s*$/) { + my ($field, $value) = ($1, $2); + + $self->pointed_hint('apache2-unsupported-dependency', + $item->pointer($position), $field) + if $field eq 'Conflicts' && $conftype ne 'mods'; + + my @dependencies = split(/[\n\s]+/, $value); + for my $dep (@dependencies) { + + $self->pointed_hint('apache2-unparsable-dependency', + $item->pointer($position), $dep) + if $dep =~ /[^\w\.]/ + || $dep =~ /^mod\_/ + || $dep =~ /\.(?:conf|load)/; + } + } + + --$skip + if $line =~ m{<\s*/\s*If(Module|Version)}; + + } continue { + ++$position; + } + + close $fd; + + return; +} + +1; + +# Local Variables: +# indent-tabs-mode: nil +# cperl-indent-level: 4 +# End: +# vim: syntax=perl sw=4 sts=4 sr et |