aboutsummaryrefslogblamecommitdiff
path: root/clubber
blob: 537a8dac4e8d77836dc7273a72b7b6bd38e9097e (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11



                                                                              

                                                                              




                                         


                                  

             



                                                                
                     
                
                   
               


                 




                                                     







                                               

                
                             















                                                                              
                                                                          






























                                                                            











                                                                               
                                              










                                                                                                         

















                                                                             



                                                               


                                           






























                                                                                          

                                         



                                                              











                                                                        
                                                                             



                                                      
                                                                   




                                                                      
                                                            




                                                                                  
                                                                        

                










                                                                                       
                                                               

                                                          
                                                        















                                                                               
                                                   
                                       
























                                               
 
#!/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
#

#
# Force me to write this properly.
#
use strict;
use warnings;

#
# Import required Perl libraries; these are all pretty standard.
#
use Cwd qw(abs_path);
use Digest::MD5;
use File::Basename;
use File::Find;
use Getopt::Long;

#
# Ignore stupid and useless messages from File::Find.
#
no warnings 'File::Find';

#
# Check ldd is available.
#
chomp(my $ldd = `which ldd`);
if (!$ldd) {
    error("Couldn't find ldd in your \$PATH.");
}

#
# Check options.
#
my ($chroot, $dry) = ("", 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] [--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;
        }
    }
}

#
# Include all libnss libraries available, because even static binaries depend
# on these for reading files like /etc/passwd. I leave importing those files to
# you because it's entirely possible you actually intend to have a different
# /etc/passwd or /etc/resolv.conf in your chroot environment. Good practice,
# even.
#
# If two of the libraries have the exact same filename, use the one with the
# shortest complete path.
#
my $nsslibs = {};
my $nssfind = sub {
    my $basename = $_;
    if ($File::Find::name =~ /libnss.+\.so/) {
        if (!exists $nsslibs->{$basename} or length($File::Find::name) < length($nsslibs->{$basename})) {
            $nsslibs->{$basename} = $File::Find::name;
        }
    }
};
find($nssfind, qw(/lib /usr/lib));
foreach my $nsslib (keys(%$nsslibs)) {
    $libraries->{$nsslibs->{$nsslib}} = 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 the directory doesn't exist, flag it for creation.
        #
        if (!-d "${chroot}${directory}") {
            $directories->{$directory} = 1;
        }

        #
        # If the library exists, we need to see if it's the same as our source
        # library.
        #
        if (-f "${chroot}${library}") {

            #
            # Get MD5 checksum of source library.
            #
            open(my $src, "<", $library)
                or error("Couldn't read file %s to checksum it.", $library);
            binmode($src);
            my $src_checksum = Digest::MD5->new->addfile($src)->hexdigest;
            close($src);

            #
            # Get MD5 checksum of library presently occupying the path to
            # which we intend to copy this library.
            #
            open(my $dst, "<", "${chroot}${library}")
                or error("Couldn't read file %s to checksum it.",  "${chroot}${library}");
            binmode($dst);
            my $dst_checksum = Digest::MD5->new->addfile($dst)->hexdigest;
            close($dst);

            #
            # Compare checksums; if they're different, we need to copy the
            # library in.
            #
            if ($src_checksum ne $dst_checksum) {
                $imports->{$library} = 1;
            }

        #
        # The library doesn't exist, so we need to copy it in.
        #
        } 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; and for that, we don't require root privileges.
        #
        if ($dry) {
            if (keys %$directories) {
                printf STDOUT "Create directories:\n";
                foreach my $directory (sort(keys(%$directories))) {
                    printf STDOUT "    %s\n", "${chroot}${directory}";
                }
            }
            if (keys %$imports) {
                printf STDOUT "Copy libraries:\n";
                foreach my $import (sort(keys(%$imports))) {
                    printf STDOUT "    %s -> %s\n", $import, "${chroot}${import}";
                }
            }

        #
        # Otherwise, we'd best get started, and we need root privileges.
        #
        } else {

            #
            # Bail if we're not root.
            #
            if ($< != 0 || $> != 0) {
                error("You must have root permissions to use the --chroot parameter.");
            }

            #
            # Create directories and import libraries.
            #
            foreach my $directory (sort(keys(%$directories))) {
                system("mkdir -pv ${chroot}${directory}");
            }
            foreach my $import (sort(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 (sort(keys(%$libraries))) {
        printf STDOUT "%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;
}