#!/usr/bin/perl -wT --

# vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab:

# Copyright (c) 2014 Jari Turkia (jatu@hqcodeshop.fi)
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

# Script inspired by http://seckev.blog.com/2011/12/14/how-to-update-exploits-from-packetstorm-website/
#
# Version history:
# 8 Feb 2014    0.1     Initial release


use LWP::UserAgent;
use Archive::Tar;
use File::Touch;
use File::Type;
use Getopt::Long;
use Data::Dumper;

use strict;
use utf8;

#
# Variables
#
my $upload_dir = "HOME/.msf4/updates";
my $modules_dir = "HOME/.msf4/modules";
my $exploitsSearchURL = "http://packetstormsecurity.com/search/?q=packet+storm+exploits";
my $exploitsDownloadURL = "http://dl.packetstormsecurity.net";

#
# Search for exploits in packetstormsecurity.com
# Installs those monthly packages that are not installed yet.
#
sub GetListOfAllExploits($$$\$\$\$)
{
    my ($year, $processed_dir, $module_dir, $cnt_total_ref, $cnt_new_ref, $cnt_installed_exploits_ref) = @_;

    $$cnt_total_ref = 0;
    $$cnt_new_ref = 0;
    $$cnt_installed_exploits_ref = 0;
# GET /search/?q=packet+storm+exploits HTTP/1.1
# User-Agent: Wget/1.14 (linux-gnu)
# Accept: */*
# Host: packetstormsecurity.com
# Connection: Keep-Alive

    my $ua = LWP::UserAgent->new();
    $ua->agent('Wget/1.14 (linux-gnu)');    # Needed to avoid HTTP/413 "Sorry, you look like part of a botnet. Please email 'staff' if this is incorrect."
    my $response = $ua->get($exploitsSearchURL);
    if (!$response->is_success) {
        # Debug:
        #print Dumper($response);
        die "Failed to load $exploitsSearchURL(HTTP/" . $response->code. ")";
    }

    my $files = $response->content;

    # Strip the page HTML from the beginning of the page to
    # the point where search result list begins.
    $files =~ s:^.+<input type="hidden" name="s" value="files" />\s+</form>\s+::s;

    # <dl id="F125023" class="file first">
    # <dt>
    # <a class="ico application-x-gzip" href="/files/125023/Packet-Storm-New-Exploits-For-January-2014.html" title="Size: 922.9 KB">Packet Storm New Exploits For January, 2014</a>
    # <a href="/files/download/125023/1401-exploits.tgz" title="Size: 922.9 KB" rel="nofollow">Download</a>
    # </dl>
    #
    # In reality the file is downloaded from
    # http://packetstormsecurity.com/files/download/125023/1401-exploits.tgz
    # vs.
    # http://dl.packetstormsecurity.net/1401-exploits/1401-exploits.tgz

    # Iterate the files search list
    while ($files =~ s:^(<dl.+?</dl>\s+)::s) {
        my $a_file = $1;
        die "Unknown HTML!" if ($a_file !~ m:<a [^>]*href="(/files/[^"]+)"[^>]*>([^<]*\S)\s*</a>.*<a href="(/files/download/[^"]+\.tgz)"[^>]*>Download:s);
        my $title = $2;
        my $download_url = $3;
        $title =~ s:[./]:_:;                # Title must not have a dot (.) or slash in it
        next if ($title !~ /,\s+(\d{4})$/);   # Skip the yearly packages, take only monthly ones
        my $package_year = $1;
        # XXX Debug
        #print "Got: $title ($download_url)\n";

        next if ($package_year ne $year);       # We'll be only processing for the year
        ++$$cnt_total_ref;
        next if (-e "$processed_dir/$title");   # Skip the ones we already have

        # Download a pacakage
        ++$$cnt_new_ref;

        die "Unknown file name $download_url!" if ($download_url !~ m:/(\d+-exploits).tgz$:);
        my $directory = $1;
        my $file_name = "$1.tgz";
        my $real_url = $exploitsDownloadURL . "/$directory/$file_name";

        my $cnt = GetAndInstallExploitPackage($module_dir, $real_url);
        $$cnt_installed_exploits_ref += $cnt;
        touch("$processed_dir/$title");     # Flag as processed
    }
}

#
# Download and install a single package
#
sub GetAndInstallExploitPackage($$)
{
	my ($module_dir, $real_url) = @_;

    my $ua = LWP::UserAgent->new('max_redirect' => 0);  # No redirects! It is a failure if one happens.
    $ua->agent('Wget/1.14 (linux-gnu)');    # Needed to avoid HTTP/413 "Sorry, you look like part of a botnet. Please email 'staff' if this is incorrect."
    my $package_tgz = $ua->get($real_url);
    if (!$package_tgz->is_success) {
        # Debug:
        #print Dumper($response);
        die "Failed to load package $real_url (HTTP/" . $package_tgz->code. ")";
    }
    # Confirm that we got a tar.gz (or .tgz)
    my $ft = File::Type->new();
    my $file_type = $ft->checktype_contents($package_tgz->content);
    die "Didn't get a proper .tar.gz file from download!" if ($file_type ne 'application/x-gzip');

    my $archive_filename = "/tmp/PacketStormExploits.$$.tar.gz";
    open (ARCHIVE, ">$archive_filename") or
        die "Cannot write to /tmp/-directory! Status: $!";
    print ARCHIVE $package_tgz->content;
    close (ARCHIVE);

	my $cnt = InstallExploitPackage($module_dir, $archive_filename);

    unlink($archive_filename);

	return $cnt;
}

#
# Install a single exploit package
#
sub InstallExploitPackage($$)
{
    my ($module_dir, $archive_filename) = @_;

    # Extract a package
    my $tar = Archive::Tar->new($archive_filename, 1);
    $tar->setcwd($module_dir);      # Performance boost!
    my %directories;
    foreach my $file ($tar->list_files()) {
        my @parts = split(/\//, $file, 2);
        next if ($directories{$parts[0]});
        $directories{$parts[0]} = 1;
    }

    # Extract
    die "Could not extract package!" if (!$tar->extract());

    # Post-process
    my $cnt_exploits = 0;
    foreach my $directory (keys(%directories)) {
        $cnt_exploits += FixExploitDiretory("$module_dir/$directory");
    }

	return $cnt_exploits
}

#
# Iterate an extracted explpoit-directory and fix Ruby-files
#
sub FixExploitDiretory($)
{
    my ($unpacked_dir) = @_;

    # Rename ruby-files
    my $cnt_exploits = 0;
    opendir(my $dh, $unpacked_dir) or
        die "Cannot read directory $unpacked_dir contents!";
    while (my $file = readdir($dh)) {
        next if ($file !~ m:^([^.][^/]+\.rb)\.txt$:);

        rename ("$unpacked_dir/$1.txt", "$unpacked_dir/$1") or
            die "Failed to rename ruby file $file in directory $unpacked_dir";

        ++$cnt_exploits;
    }
    closedir($dh);

    return $cnt_exploits;
}

#
# Begin script
#

# Un-taint
die "Weird home directory!" if ($ENV{HOME} !~ m:(^/.+):);
my $home_dir = $1;
$upload_dir =~ s/^HOME/$home_dir/;
$modules_dir =~ s/^HOME/$home_dir/;

# Make sure the directory exists
if (! -e $upload_dir) {
    mkdir($upload_dir) or
        die "Cannot create upload dir! Status: $!"
}

my $cnt_total;
my $cnt_new;
my $cnt_installed_exploits;

my $package_file = '';
my $package_URL = '';
my $package_year = '';
GetOptions ('package_file=s' => \$package_file,
            'package_url=s' => \$package_URL,
            'year=s', \$package_year);

if ($package_file ne "") {
    # Install from a given filename
    die "Cannot find file: $package_file\n" if (! -e $package_file);
    $cnt_installed_exploits = InstallExploitPackage($modules_dir, $package_file);
    print "Installed $cnt_installed_exploits new exploits.\n";
} elsif ($package_URL ne "") {
    # Install from a given URL
    $cnt_installed_exploits = GetAndInstallExploitPackage($modules_dir, $package_URL);
    print "Installed $cnt_installed_exploits new exploits.\n";
} elsif ($package_year ne "" && $package_year =~ /^\d{4}$/) {
    # Get list of updates for given year and update
    GetListOfAllExploits($package_year, $upload_dir, $modules_dir, $cnt_total, $cnt_new, $cnt_installed_exploits);
    print "Got $cnt_new updates out of total $cnt_total updates. Installed $cnt_installed_exploits new exploits.\n";
} else {
    # Default mode:
    # Help
    warn "Specify one of: --year, --package_file or --package_url\n";
    warn "Example: updateMetasploitFromPacketStormExploits.pl --year=2014\n";
    die;
}

