#!/usr/bin/env perl
#
# Clubber -- Less painful chroot environment building and verification. Please
# see README.markdown for documentation. Be sure you trust your binaries! This
# script won't protect you from ldd exploits.
#
# @author Tom Ryder <tom@sanctum.geek.nz>
# @copyright 2012 Sanctum
#
use strict;
use warnings;
use Cwd qw(abs_path);
use File::Basename;
use Getopt::Long;
#
# Bail if we're not root.
#
if ($< != 0 || $> != 0) {
error("You must have root permissions to use this script.");
}
#
# Check ldd is available.
#
chomp(my $ldd = `which ldd`);
if (!$ldd) {
error("Couldn't find ldd in your \$PATH.");
}
#
# Check md5sum is available.
#
chomp(my $md5sum = `which md5sum`);
if (!$md5sum) {
error("Couldn't find md5sum in your \$PATH.");
}
#
# Check options.
#
my ($chroot, $dry, $verify) = ("", 0, 0, 0);
my $config = GetOptions("chroot=s" => \$chroot,
"dry" => \$dry);
if ($chroot) {
$chroot = abs_path($chroot);
if (!-d $chroot) {
error("Nominated chroot %s doesn't seem to be a directory.", $chroot);
}
} elsif ($dry) {
error("Doesn't make sense to specify --dry without --chroot.");
}
#
# Check we were passed at least one parameter, otherwise print a helpful
# message.
#
if (!@ARGV) {
printf STDOUT "USAGE: ${0} [--chroot] [--verify] [--dry] binary1 binary2 ... \n";
exit 0;
}
#
# Check that all our parameters are real files, or point to one.
#
my $binaries = [];
foreach my $argument (@ARGV) {
$argument = abs_path($argument);
if (-f $argument) {
push @$binaries, $argument;
} else {
error("File %s doesn't seem to exist.", $argument);
}
}
#
# Run ldd on all the files and slurp all the absolute paths; put them into a
# hash to keep things unique.
#
my $libraries = {};
foreach my $binary (@$binaries) {
my $output = [qx/${ldd} ${binary}/];
foreach my $line (@$output) {
if ($line =~ m#(/\S*lib\S+)#) {
$libraries->{$1} = 1;
}
}
}
#
# If we have a chroot, we need to figure out what libraries require importing
# and which directories require creating.
#
if ($chroot) {
my ($directories, $imports) = ({}, {});
#
# First we'll recurse through the list of libraries and flag any
# directories that require creation. We won't complicate things by
# reproducing any symbolic link structures.
#
# If the directory does exist, we'll make sure the library is in place
# too, and if it is that its md5sum is the same as our root system
# library. If it doesn't exist or if it's different, we'll flag it for
# overwriting.
#
foreach my $library (keys(%$libraries)) {
my $directory = dirname($library);
if (!-d "${chroot}${directory}") {
$directories->{$directory} = 1;
}
if (-f "${chroot}{$library}") {
if (qx/${md5sum} ${library}/ ne qx/${md5sum} ${chroot}${library}/) {
$imports->{$library} = 1;
}
} else {
$imports->{$library} = 1;
}
}
#
# Check there's something for us to do.
#
if (keys %$directories || keys %$imports) {
#
# If we're just supposed to print what we do, all the better, do
# that and then quit.
#
if ($dry) {
if (keys %$directories) {
printf STDOUT "Create directories:\n";
foreach my $directory (keys(%$directories)) {
printf STDOUT " %s\n", "${chroot}${directory}";
}
}
if (keys %$imports) {
printf STDOUT "Copy libraries:\n";
foreach my $import (keys(%$imports)) {
printf STDOUT " %s -> %s\n", $import, "${chroot}${import}";
}
}
#
# Otherwise, we'd best get started.
#
} else {
foreach my $directory (keys(%$directories)) {
system("mkdir -pv ${chroot}${directory}");
}
foreach my $import (keys(%$imports)) {
system("cp -pv ${import} ${chroot}${import}");
}
}
#
# If there's nothing we need to do, say so.
#
} else {
printf STDOUT "Nothing to do.\n";
}
#
# If we don't have a chroot, we can just print the list of libraries, and we're
# done.
#
} else {
foreach my $library (keys(%$libraries)) {
printf "%s\n", $library;
}
}
#
# And one way or another, we're done.
#
exit 0;
#
# Print a usage message and exit with non-zero.
#
sub usage {
my $message = shift @_;
printf STDERR "USAGE: ${message}\n", @_;
exit 1;
}
#
# Print a usage message and exit with non-zero.
#
sub error {
my $message = shift @_;
printf STDERR "ERROR: ${message}\n", @_;
exit 1;
}