openpkg/lint-rpm.pl

Thu, 04 Oct 2012 20:30:05 +0200

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 04 Oct 2012 20:30:05 +0200
changeset 715
c10fb90893b9
permissions
-rw-r--r--

Correct out of date build configuration, porting to Solaris 11 network
link infrastructure and new libpcap logic. This additionally allows for
device drivers in subdirectories of /dev. Correct packaged nmap
personalities and signatures to work out of the box. Finally, hack
arpd logic to properly close sockets and quit on TERM by repeating
signaling in the run command script. Sadly, all this fails to correct
the run time behaviour of honeyd which fails to bind to the IP layer.

michael@428 1 ##
michael@428 2 ## lint-rpm.pl -- OpenPKG *.rpm File Checker
michael@428 3 ## Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/>
michael@428 4 ##
michael@428 5 ## This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
michael@428 6 ## All rights reserved. Licenses which grant limited permission to use,
michael@428 7 ## copy, modify and distribute this software are available from the
michael@428 8 ## OpenPKG GmbH.
michael@428 9 ##
michael@428 10 ## THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
michael@428 11 ## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
michael@428 12 ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
michael@428 13 ## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
michael@428 14 ## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
michael@428 15 ## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
michael@428 16 ## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
michael@428 17 ## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
michael@428 18 ## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
michael@428 19 ## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
michael@428 20 ## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
michael@428 21 ## SUCH DAMAGE.
michael@428 22 ##
michael@428 23
michael@428 24 # Perl run-time requirement
michael@428 25 require 5;
michael@428 26 BEGIN {
michael@428 27 eval "use Getopt::Long; use IO;";
michael@428 28 if ($@) {
michael@428 29 print STDERR
michael@428 30 "lint-rpm: ERROR: This command requires a full-size Perl installation!\n" .
michael@428 31 "lint-rpm: HINT: Install OpenPKG \"perl\" package to use this command.\n";
michael@428 32 exit(1);
michael@428 33 }
michael@428 34 }
michael@428 35
michael@428 36 # OpenPKG instance prefix
michael@428 37 my $my_prefix = $ENV{'OPENPKG_PREFIX'};
michael@428 38 delete $ENV{'OPENPKG_PREFIX'};
michael@428 39
michael@428 40 # program information
michael@428 41 my $progname = "lint-rpm";
michael@428 42 my $progvers = "1.0.0";
michael@428 43
michael@428 44 # parameters (defaults)
michael@428 45 my $version = 0;
michael@428 46 my $verbose = 0;
michael@428 47 my $help = 0;
michael@428 48 my $check = 'all';
michael@428 49 my $tmpdir = ($ENV{TMPDIR} || $ENV{TEMPDIR} || "/tmp") . "/$progname";
michael@428 50 my $rpm = "$my_prefix/bin/openpkg rpm";
michael@428 51 my $rpm2cpio = "$my_prefix/bin/openpkg rpm2cpio";
michael@428 52
michael@428 53 # exception handling support
michael@428 54 $SIG{__DIE__} = sub {
michael@428 55 my ($err) = @_;
michael@428 56 $err =~ s|\s+at\s+.*||s if (not $verbose);
michael@428 57 print STDERR "$progname:ERROR: $err ". ($! ? "($!)" : "") . "\n";
michael@428 58 exit(1);
michael@428 59 };
michael@428 60
michael@428 61 # command line parsing
michael@428 62 Getopt::Long::Configure("bundling");
michael@428 63 my $result = GetOptions(
michael@428 64 'V|version' => \$version,
michael@428 65 'v|verbose' => \$verbose,
michael@428 66 'h|help' => \$help,
michael@428 67 'c|check=s' => \$check,
michael@428 68 't|tmpdir=s' => \$tmpdir,
michael@428 69 'r|rpm=s' => \$rpm,
michael@428 70 ) || die "option parsing failed";
michael@428 71 if ($help) {
michael@428 72 print "Usage: $progname [options] [RPMFILE ...]\n" .
michael@428 73 "Available options:\n" .
michael@428 74 " -v,--verbose enable verbose run-time mode\n" .
michael@428 75 " -h,--help print out this usage page\n" .
michael@428 76 " -c,--check=CHECKS select checks to perform (default='all')\n" .
michael@428 77 " -r,--rpm=FILE filesystem path to RPM program\n" .
michael@428 78 " -t,--tmpdir=PATH filesystem path to temporary directory\n" .
michael@428 79 " -V,--version print program version\n";
michael@428 80 exit(0);
michael@428 81 }
michael@428 82 if ($version) {
michael@428 83 print "OpenPKG $progname $progvers\n";
michael@428 84 exit(0);
michael@428 85 }
michael@428 86
michael@428 87 # verbose message printing
michael@428 88 sub msg_verbose {
michael@428 89 my ($msg) = @_;
michael@428 90 print STDERR "$msg\n" if ($verbose);
michael@428 91 }
michael@428 92
michael@428 93 # warning message printing
michael@428 94 sub msg_warning {
michael@428 95 my ($msg) = @_;
michael@428 96 print STDERR "$progname:WARNING: $msg\n";
michael@428 97 }
michael@428 98
michael@428 99 # error message printing
michael@428 100 sub msg_error {
michael@428 101 my ($msg) = @_;
michael@428 102 print STDERR "$progname:ERROR: $msg\n";
michael@428 103 }
michael@428 104
michael@428 105 # determine check list
michael@428 106 my @check_list = (qw(
michael@428 107 layout
michael@428 108 attrib
michael@428 109 content
michael@428 110 ));
michael@428 111 my @checks = ();
michael@428 112 if ($check eq 'all') {
michael@428 113 @checks = @check_list;
michael@428 114 }
michael@428 115 else {
michael@428 116 foreach my $c (split(/,/, $check)) {
michael@428 117 if (not grep(/^$c$/, @check_list)) {
michael@428 118 die "invalid check \"$c\"";
michael@428 119 }
michael@428 120 push(@checks, $c);
michael@428 121 }
michael@428 122 }
michael@428 123
michael@428 124 # global return code
michael@428 125 $main::GRC = 0;
michael@428 126
michael@428 127 # environment preparation
michael@428 128 system("rm -rf $tmpdir");
michael@428 129 system("mkdir -p $tmpdir");
michael@428 130
michael@428 131 # iterate over all .rpm files
michael@428 132 foreach my $filename (@ARGV) {
michael@428 133 die "unable to open file \"$filename\" for reading"
michael@428 134 if (not -f $filename);
michael@428 135 my $info = &rpm_info($filename, \@checks);
michael@428 136 foreach my $check (@checks) {
michael@428 137 eval "\&check_$check(\$filename, \$info);";
michael@428 138 }
michael@428 139 &rpm_info_cleanup($filename, \@checks, $info);
michael@428 140 }
michael@428 141
michael@428 142 # environment cleanup
michael@428 143 system("rm -rf $tmpdir");
michael@428 144
michael@428 145 # die gracefully
michael@428 146 exit($main::GRC);
michael@428 147
michael@428 148 ## _________________________________________________________________
michael@428 149 ##
michael@428 150 ## COMMON SUBROUTINES
michael@428 151 ## _________________________________________________________________
michael@428 152 ##
michael@428 153
michael@428 154 sub lint_message {
michael@428 155 my ($type, $file, $msg) = @_;
michael@428 156 $file =~ s|^.+?/([^/]+)$|$1|s;
michael@428 157 printf(STDERR "%s:%s: %s: %s\n", $progname, $type, $file, $msg);
michael@428 158 }
michael@428 159
michael@428 160 sub lint_warning {
michael@428 161 my ($file, $msg) = @_;
michael@428 162 &lint_message("WARNING", $file, $msg);
michael@428 163 $main::GRC = 1 if ($main::GRC < 1);
michael@428 164 }
michael@428 165
michael@428 166 sub lint_error {
michael@428 167 my ($file, $msg) = @_;
michael@428 168 &lint_message("ERROR", $file, $msg);
michael@428 169 $main::GRC = 2 if ($main::GRC < 2);
michael@428 170 }
michael@428 171
michael@428 172 ## _________________________________________________________________
michael@428 173 ##
michael@428 174 ## RPM INFORMATION GATHERING
michael@428 175 ## _________________________________________________________________
michael@428 176 ##
michael@428 177
michael@428 178 sub rpm_info {
michael@428 179 my ($filename, $checks) = @_;
michael@428 180 my $info = {};
michael@428 181
michael@428 182 # query package name
michael@428 183 &msg_verbose("++ querying RPM package name");
michael@428 184 $info->{name} = `$rpm -qp --qf '%{NAME}' $filename`;
michael@428 185
michael@428 186 # query prefix
michael@428 187 &msg_verbose("++ querying RPM package installation prefix");
michael@428 188 $info->{prefix} = `$rpm -qp --qf '%{PREFIXES}' $filename`;
michael@428 189
michael@428 190 # query file listing
michael@428 191 &msg_verbose("++ querying RPM package file listing");
michael@428 192 my @list = `$rpm -qplv $filename`;
michael@428 193 my @config = `$rpm -qplc $filename`;
michael@428 194
michael@428 195 # process file listing
michael@428 196 $info->{ls} = {};
michael@428 197 foreach my $entry (@list) {
michael@428 198 if ($entry =~ m|^\(contains no files\)\s*$|s) {
michael@428 199 next;
michael@428 200 }
michael@428 201 elsif ($entry =~ m|^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+(.{12})\s+(.+)\s*$|s) {
michael@428 202 my ($perm, $links, $owner, $group, $size, $mtime, $path) = ($1, $2, $3, $4, $5, $6, $7);
michael@428 203 my $symlink = "";
michael@428 204 if ($path =~ m|^(\S+)\s+->\s+(\S+)$|) {
michael@428 205 ($path, $symlink) = ($1, $2);
michael@428 206 }
michael@428 207 $path =~ s|\s+$||s;
michael@428 208 my $config = 0;
michael@428 209 if (grep(m|^$path$|, @config)) {
michael@428 210 $config = 1;
michael@428 211 }
michael@428 212 $info->{ls}->{$path} = {
michael@428 213 'perm' => $perm,
michael@428 214 'links' => $links,
michael@428 215 'owner' => $owner,
michael@428 216 'group' => $group,
michael@428 217 'size' => $size,
michael@428 218 'time' => $mtime,
michael@428 219 'path' => $path,
michael@428 220 'symlink' => $symlink,
michael@428 221 'config' => $config,
michael@428 222 };
michael@428 223 }
michael@428 224 else {
michael@428 225 &lint_error($filename, "invalid file listing entry: \"$entry\"");
michael@428 226 }
michael@428 227 }
michael@428 228
michael@428 229 # unpacking files
michael@428 230 if (grep(/^content$/, @{$checks})) {
michael@428 231 &msg_verbose("++ unpacking RPM package files");
michael@428 232 $info->{root} = "$tmpdir/root";
michael@428 233 system("mkdir -p ".$info->{root});
michael@428 234 system("$rpm2cpio $filename | (cd ".$info->{root}." && cpio -idmu 2>/dev/null)");
michael@428 235 }
michael@428 236
michael@428 237 return $info;
michael@428 238 }
michael@428 239
michael@428 240 sub rpm_info_cleanup {
michael@428 241 my ($filename, $checks, $info) = @_;
michael@428 242
michael@428 243 if (grep(/^content$/, @{$checks})) {
michael@428 244 system("rm -rf ".$info->{root}) if ($info->{root} =~ m/^\/.+/ and -d $info->{root});
michael@428 245 }
michael@428 246 }
michael@428 247
michael@428 248 ## _________________________________________________________________
michael@428 249 ##
michael@428 250 ## CHECK "layout": file path layout
michael@428 251 ## _________________________________________________________________
michael@428 252 ##
michael@428 253
michael@428 254 sub check_layout {
michael@428 255 my ($rpm, $info) = @_;
michael@428 256
michael@428 257 # no need to check 'openpkg' package because it
michael@428 258 # has a hard-coded file list!
michael@428 259 return if ($rpm =~ m|^(.+?/)?openpkg-\d[^/]+$|);
michael@428 260
michael@428 261 # check prefix
michael@428 262 if ($info->{prefix} !~ m|^/.+$|) {
michael@428 263 &lint_error($rpm, "invalid installation prefix ".$info->{prefix}.
michael@428 264 " (expected to match \"^/.+\$\")");
michael@428 265 return;
michael@428 266 }
michael@428 267
michael@428 268 # check top-level path (all-in-one)
michael@428 269 my @topdirs = (qw(
michael@428 270 bin cgi etc include info lib libexec
michael@428 271 local man pub sbin share usr var
michael@428 272 ));
michael@428 273 my $topdirs = "{".join(",", @topdirs)."}";
michael@428 274 if (not keys(%{$info->{ls}})) {
michael@428 275 &lint_error($rpm, "invalid empty package (expected at least one file)");
michael@428 276 return;
michael@428 277 }
michael@428 278 foreach my $path (keys(%{$info->{ls}})) {
michael@428 279 my $ok = 0;
michael@428 280 foreach my $topdir (@topdirs) {
michael@428 281 my $prefix = quotemeta($info->{prefix} . "/" . $topdir);
michael@428 282 if ($path =~ m/^$prefix$/ && $rpm !~ m|^openpkg-\d+|) {
michael@428 283 &lint_error($rpm, "top-level directory \"$topdir\" provided" .
michael@428 284 " (expected none except for 'openpkg' package)");
michael@428 285 }
michael@428 286 if ($path =~ m/^$prefix/) {
michael@428 287 $ok = 1;
michael@428 288 last;
michael@428 289 }
michael@428 290 }
michael@428 291 if (not $ok) {
michael@428 292 &lint_error($rpm, "invalid top-level directory in path \"$path\"".
michael@428 293 " (expected one of $topdirs)");
michael@428 294 }
michael@428 295 }
michael@428 296
michael@428 297 # check for second-level path (all-in-one)
michael@428 298 my @topdirs_subdir_no = (qw(bin cgi info sbin));
michael@428 299 my @topdirs_subdir_yes = (qw(etc libexec share var));
michael@428 300 foreach my $path (keys(%{$info->{ls}})) {
michael@428 301 foreach my $topdir (@topdirs_subdir_yes) {
michael@428 302 my $prefix = quotemeta($info->{prefix} . "/" . $topdir);
michael@428 303 if ($path =~ m/^$prefix\/[^\/]+$/) {
michael@428 304 if ($info->{ls}->{$path}->{perm} !~ m|^d|) {
michael@428 305 &lint_error($rpm, "invalid positioned file \"$path\" under topdir \"$topdir\" (expected directory)");
michael@428 306 }
michael@428 307 }
michael@428 308 }
michael@428 309 foreach my $topdir (@topdirs_subdir_no) {
michael@428 310 my $prefix = quotemeta($info->{prefix} . "/" . $topdir);
michael@428 311 if ($path =~ m/^$prefix\/[^\/]+$/) {
michael@428 312 if ($info->{ls}->{$path}->{perm} =~ m|^d|) {
michael@428 313 &lint_error($rpm, "invalid positioned directory \"$path\" under topdir \"$topdir\" (expected file)");
michael@428 314 }
michael@428 315 }
michael@428 316 }
michael@428 317 }
michael@428 318
michael@428 319 # check "bin" and "sbin" directories
michael@428 320 foreach my $path (keys(%{$info->{ls}})) {
michael@428 321 foreach my $topdir (qw(bin sbin)) {
michael@428 322 my $prefix = quotemeta($info->{prefix} . "/" . $topdir);
michael@428 323 if ($path =~ m/^$prefix\/(.+)$/) {
michael@428 324 my $file = $1;
michael@428 325 if ($file =~ m|^[^/]+\.[^/.]+$|) {
michael@428 326 &lint_warning($rpm, "strange executable filename \"$path\" containing an extension (expected no extension)");
michael@428 327 }
michael@428 328 my $perm = $info->{ls}->{$path}->{'perm'};
michael@428 329 if ($perm =~ m|^-| && $perm !~ m|^-[-r][-w][sx][-r][-w][-sx][-r][-w][-tx]$|) {
michael@428 330 &lint_error($rpm, "non-executable file \"$path\" (with permissions \"$perm\" under topdir \"$topdir\" (expected to be executable)");
michael@428 331 }
michael@428 332 }
michael@428 333 }
michael@428 334 }
michael@428 335
michael@428 336 # check for symbolic link targets (outside absolute, dangling)
michael@428 337 foreach my $path (keys(%{$info->{ls}})) {
michael@428 338 my $symlink = $info->{ls}->{$path}->{'symlink'};
michael@428 339 if ($symlink ne '') {
michael@428 340 # check for outside absolute target
michael@428 341 my $prefix = quotemeta($info->{prefix});
michael@428 342 if ($symlink =~ m|^/.*| and $symlink !~ m|^$prefix|s) {
michael@428 343 &lint_warning($rpm, "symbolic link \"$path\" points to absolute path \"$symlink\" outside prefix \"$info->{prefix}\" (expected it to be under prefix only)");
michael@428 344 }
michael@428 345 # check for dangling target
michael@428 346 my $resolved = &resolve($info, $path);
michael@428 347 sub resolve {
michael@428 348 my ($info, $path) = @_;
michael@428 349 if (not defined($info->{ls}->{$path})) {
michael@428 350 return $path;
michael@428 351 }
michael@428 352 my $symlink = $info->{ls}->{$path}->{'symlink'};
michael@428 353 if ($symlink eq '') {
michael@428 354 return $path;
michael@428 355 }
michael@428 356 else {
michael@428 357 my $resolved;
michael@428 358 if ($symlink =~ m|^/|) {
michael@428 359 $resolved = $symlink;
michael@428 360 }
michael@428 361 else {
michael@428 362 $resolved = $path;
michael@428 363 $resolved =~ s|/[^/]+$||s;
michael@428 364 $resolved .= "/" . $symlink;
michael@428 365 $resolved =~ s|/{2,}|/|sg;
michael@428 366 $resolved =~ s|/\.(?=/)||sg;
michael@428 367 $resolved =~ s|/\.$||sg;
michael@428 368 1 while ($resolved =~ s|/[^/]+/\.\./|/|s);
michael@428 369 $resolved =~ s|/[^/]+/\.\.$||s;
michael@428 370 $resolved =~ s|(.)/$|$1|s;
michael@428 371 }
michael@428 372 return &resolve($info, $resolved);
michael@428 373 }
michael@428 374 }
michael@428 375 if (not defined($info->{ls}->{$resolved})) {
michael@428 376 &lint_error($rpm, "symbolic link \"$path\" points to not existing target path \"$resolved\" (expected existing target path)");
michael@428 377 }
michael@428 378 }
michael@428 379 }
michael@428 380 }
michael@428 381
michael@428 382 ## _________________________________________________________________
michael@428 383 ##
michael@428 384 ## CHECK "attrib": file attributes
michael@428 385 ## _________________________________________________________________
michael@428 386 ##
michael@428 387
michael@428 388 sub check_attrib {
michael@428 389 my ($rpm, $info) = @_;
michael@428 390
michael@428 391 # check for empty files
michael@428 392 foreach my $path (keys(%{$info->{ls}})) {
michael@428 393 if ( $info->{ls}->{$path}->{'size'} == 0
michael@428 394 and $info->{ls}->{$path}->{'perm'} !~ m|^d|
michael@428 395 and $path !~ m/^.*\/lib\/perl\/.+\.bs$/
michael@428 396 and $rpm !~ m/\bopenpkg-[0-9]+/) {
michael@428 397 &lint_warning($rpm, "empty file \"$path\" found (expected no empty files)");
michael@428 398 }
michael@428 399 }
michael@428 400
michael@428 401 # check for %config flagged files
michael@428 402 my $prefix = quotemeta($info->{'prefix'});
michael@428 403 my $name = quotemeta($info->{'name'});
michael@428 404 foreach my $path (keys(%{$info->{ls}})) {
michael@428 405 my $config = $info->{ls}->{$path}->{'config'};
michael@428 406 my $path = $info->{ls}->{$path}->{'path'};
michael@428 407 if ($rpm !~ m/\bopenpkg-[0-9]+/ and $config and $path !~ m/^$prefix\/etc\/($name\d*|fsl)\/.+/) {
michael@428 408 &lint_warning($rpm, "non-config file \"$path\" flagged as \%config (expected only \"$prefix/etc/$name/*\" files to be \%config files)");
michael@428 409 }
michael@428 410 if ($config and $path =~ m/^$prefix\/s?bin\/[^\/]+$/) {
michael@428 411 &lint_error($rpm, "executable file \"$path\" flagged as \%config");
michael@428 412 }
michael@428 413 if ($config and $path =~ m/^$prefix\/etc\/rc\.d\/rc\.$name$/) {
michael@428 414 &lint_error($rpm, "run-command file \"$path\" flagged as \%config");
michael@428 415 }
michael@428 416 }
michael@428 417
michael@428 418 # check for permissions
michael@428 419 foreach my $path (keys(%{$info->{ls}})) {
michael@428 420 my $perm = $info->{ls}->{$path}->{'perm'};
michael@428 421 if ($path =~ m/^$prefix\/(bin|sbin)\/[^\/]+$/) {
michael@428 422 if ($perm !~ m|^[^d]..[xs]..[xs]..[xt]$|) {
michael@428 423 &lint_warning($rpm, "executable file \"$path\" has permissions \"$perm\" only (expected it to be executable by everyone)");
michael@428 424 }
michael@428 425 }
michael@428 426 }
michael@428 427
michael@428 428 # TODO: user/group?
michael@428 429 }
michael@428 430
michael@428 431 ## _________________________________________________________________
michael@428 432 ##
michael@428 433 ## CHECK "content": file content
michael@428 434 ## _________________________________________________________________
michael@428 435 ##
michael@428 436
michael@428 437 sub check_content {
michael@428 438 my ($rpm, $info) = @_;
michael@428 439
michael@428 440 # TODO: stripped (file)
michael@428 441 # TODO: syslibs (ldd)
michael@428 442 # TODO: hard-coded paths pointing outside instance
michael@428 443 # TODO: sanity check for shebang-lines
michael@428 444 # TODO: contained temporary path
michael@428 445 # TODO: bad-files: .po
michael@428 446 }
michael@428 447

mercurial