#!/usr/bin/perl -w

# ..::[ The Bitcoin Foundation ]::..
# Who: mod6 <modsix at gmail dot com>
# GPG Finger Print: 027A 8D7C 0FB8 A166 4372 0F40 7217 05A8 B71E ADAF
# What: Perl Script to Automate Bitcoin v0.5.3 Patching & Compilation
# When: 20150305
# Version: v0.0.9

# NOTE: If you don't have the dependencies already plugged in on
#       Debian 6 (squeeze), you'll want to run following commands:
#
# sudo apt-get update && sudo apt-get -y upgrade
# sudo apt-get install -y build-essential libssl-dev libdb4.8-dev \
# libdb4.8++-dev libboost-all-dev curl gnupg

my $who     = "mod6 0x721705A8B71EADAF";
my $when    = "20150305";
my $where   = "http://thebitcoin.foundation/";
my $version = "v0.0.9";

print "#####################################################################\n";
print "##                                                                 ##\n";
print "##   ..::[ The Bitcoin Foundation Patch & Compile Script ]::..     ##\n";
print "##                                                                 ##\n";
print "##          author: $who                        ##\n";
print "##            when: $when                                       ##\n";
print "##           where: $where                  ##\n";
print "##         version: $version                                         ##\n";
print "##                                                                 ##\n";
print "##         If you should encounter problems with this script,      ##\n";
print "##         please enquire with mod6 at irc.freenode.net channel    ##\n";
print "##         #bitcoin-assets or write to modsix at gmail dot com     ##\n";
print "##         Thank you!                                              ##\n";
print "##                                                                 ##\n";
print "#####################################################################\n";

###############################################################################
# Bitcoin Dev Home
# XXX Make sure that you set HOME to your build directory!
my $HOME = "CHANGEME";
# XXX Set this to TRUE to clean out all build components from build directory
my $FULL_CLEAN = "TRUE";
###############################################################################

my $os = "";
my $SHA256 = "/usr/bin/sha256sum"; #Default
my $v053_base = "bitcoin-bitcoin-a8def6b";
my $pruned    = "bitcoin";

my %artifacts = ( 
  v053_base => {
    name  => "v0.5.3-0-gd05c03a.tar.gz",
    url   => "http://thebitcoin.foundation/v0.5.3-0-gd05c03a.tar.gz",
    hash  => "aab1f8ea8c7f131ff69dfa3b9437ba35531018be760132dd6373f41a591f6382",
    order => "0"
  },
 
  chicken => {
    name  => "chicken.tar.gz",
    url   => "http://thebitcoin.foundation/chicken.tar.gz",
    hash  => "63164ea54f042226a6aa8768b98b0f323d66f08693c6fd271a850c00308517ad",
    patch => "bitcoin-asciilifeform.1.patch",
    sig   => "bitcoin-asciilifeform.1.patch.sig",
    order => "1" 
  },

  rm_rf_upnp => {
    name  => "rm_rf_upnp.tar.gz",
    url   => "http://thebitcoin.foundation/rm_rf_upnp.tar.gz",
    hash  => "f5f27b806b90b0ffd3d2d73e240eba02f13c5dec3d693b37e08686d04164861a",
    patch => "rm_rf_upnp.patch",
    sig   => "rm_rf_upnp.patch.sig",
    order => "2"
  },

  https => { 
    name  => "https-snipsnip.tar.gz",
    url   => "http://thebitcoin.foundation/https-snipsnip.tar.gz",
    hash  => "047844a601bc575d7660826b02f0d640f02ae84cd641f45552c825b72bc116f4",
    patch => "bitcoin-asciilifeform.2-https_snipsnip.patch",
    sig   => "bitcoin-asciilifeform.2-https_snipsnip.patch.sig",
    order => "3"
  },
 
  alert => {
    name  => "turdmeister-alert-snip.tar.gz",
    url   => "http://thebitcoin.foundation/turdmeister-alert-snip.tar.gz",
    hash  => "39b5aac2b6de016eda23a96fe6f9a565dbbe4f4aa46e8584c102ee71fa4fe7f4",
    patch => "bitcoin-asciilifeform.3-turdmeister-alert-snip.patch",
    sig   => "bitcoin-asciilifeform.3-turdmeister-alert-snip.patch.sig",
    order => "4"
  },

  win32 => { 
    name  => "goodbye-win32.tar.gz",
    url   => "http://thebitcoin.foundation/goodbye-win32.tar.gz",
    hash  => "5149a481629e0f10b659d38fee0736e0b6e631ab3bbeb6cde449aa61226cdbb3",
    patch => "bitcoin-asciilifeform.4-goodbye-win32.patch",
    sig   => "bitcoin-asciilifeform.4-goodbye-win32.patch.sig",
    order => "5"
  },

  db_config => {
    name  => "db_config.tar.gz",
    url   => "http://thebitcoin.foundation/db_config.tar.gz",
    hash  => "b315dc1494334d24606622b7bd3a2569b53e5ad970971ee666ae7adc28fc7d65",
    patch => "bitcoin-v0_5_3-db_config.6.patch",
    sig   => "bitcoin-v0_5_3-db_config.6.patch.sig",
    order => "6"
  },

  rev_bump => {
    name  => "rev_bump.tar.gz",
    url   => "http://thebitcoin.foundation/rev_bump.tar.gz",
    hash  => "5efe5de69b6fdb97ff445a1a37a5cc227dd77b59a8553b3ee235a378f352903a",
    patch => "bitcoin-v0_5_3_1-rev_bump.7.patch",
    sig   => "bitcoin-v0_5_3_1-rev_bump.7.patch.sig",
    order => "7"
  },

  'static-makefile' => {
    name  => "static-makefile.tar.gz",
    url   => "http://thebitcoin.foundation/static-makefile.tar.gz",
    hash  => "391b0a66f7f99e5ec17c52772c078a65045be64cda1dd184c6ec285f31425177",
    patch => "bitcoin-v0_5_3_1-static_makefile.8.patch",
    sig   => "bitcoin-v0_5_3_1-static_makefile.8.patch.sig",
    order => "8"
  }
);

# Check to make sure HOME is set to something other than CHANGEME
if($HOME =~ m/CHANGEME/) {
  print "Error! HOME variable not set!\n";
  print "Be sure to set HOME to your build directory.\n";
  exit 1;
}

chdir("$HOME") or die $!;

sub checkOS { 
  my @dmesg = `dmesg | grep -e ".*Linux.*"`;
  foreach(@dmesg) { 
    if($_ =~ m/Linux/i) { 
      print "OS Type Detected: Linux\n"; 
      $os = "LINUX";
      last;
    } 
  }
  
  if($os eq "") { 
    @dmesg = `dmesg | grep -e ".*FreeBSD.*"`;
    foreach(@dmesg) { 
      print "OS Type Detected: FreeBSD\n"; 
      $os = "FREEBSD";
      last;
    }
  }
  
  if($os eq "") { 
    @dmesg = `dmesg | grep -e ".*OpenBSD.*"`;
    foreach(@dmesg) { 
      print "OS Type Detected: OpenBSD\n"; 
      $os = "OPENBSD";
      last;
    }
  }
}

sub checkTools() { 
  @tools = ("curl", "gpg");
  foreach(@tools) { 
    my $r = `which $_`;
    if($r eq "") { 
      print "\`$_\` not found on local environment.\n";
      print "Install \`$_\` and try again.\n";
      exit 1;
    }
  }
}

sub pullArtifacts { 
  # Download Aritifacts
  foreach my $key (keys %artifacts) { 
    print "Downloading $artifacts{$key}{name}\n";
    `curl $artifacts{$key}{url} -s -o $artifacts{$key}{name}`;
  }
}

sub verifyArtifactHashes { 
  if($os eq "FREEBSD") { 
    $SHA256 = "/sbin/sha256";
  } elsif($os eq "OPENBSD") { 
    $SHA256 = "/bin/sha256";
  }

  # Verify Artifact Hashes
  foreach my $key (keys %artifacts) { 
    print "Checking $artifacts{$key}{name} hash...\n";
    my $hash = `$SHA256 $artifacts{$key}{name}`;
    if($os eq "FREEBSD" or $os eq "OPENBSD") {
      $hash =~ /.*= (.*)$/;
      $hash = $1;
    } else {
      $hash =~ /^(.*) .*$/;
      $hash = $1;
    }
    if($hash !~ m/$artifacts{$key}{hash}/) {
      print "Error! $artifacts{$key}{name} hash did not match!\n";
      print "found hash: |$hash|\n";
      print "  expected: |$artifacts{$key}{hash}|\n";
      exit 1;
    } else {
     print "  >> $artifacts{$key}{name} hash verified.\n";
    }
  }
}

sub unpackArchives { 
  # Unpack Artifact Archives
  foreach my $key (keys %artifacts) { 
    print "Unpacking $artifacts{$key}{name}\n";
    if($os ne "OPENBSD") { 
      if(  
        $key =~ m/chicken/ or 
        $key =~ m/https/ or 
        $key =~ m/alert/ or 
        $key =~ m/win32/) { 
        `mkdir -p $key && tar -xf $artifacts{$key}{name} -C $key`;
      } else { 
        `tar -xf $artifacts{$key}{name}`;
      } 
    } else {
      if(  
        $key =~ m/chicken/ or 
        $key =~ m/https/ or 
        $key =~ m/alert/ or 
        $key =~ m/win32/) { 
        $tar = substr($artifacts{$key}{name}, 0, -3);
        `mkdir -p $key `; 
        `gunzip $artifacts{$key}{name} && tar -xf $tar -C $key`;
      } else { 
        $tar = substr($artifacts{$key}{name}, 0, -3);
        `gunzip $artifacts{$key}{name} && tar -xf $tar`;
      } 
    } 
  }
}

sub getPublicKeys { 
  # Get PGP Public Keys
  my $begin_pgp_key = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
  my $end_pgp_key   = "-----END PGP PUBLIC KEY BLOCK-----";

  my %pubKeyURL = (
    ben_vulpes    => 
      "http://pgp.mit.edu/pks/lookup?op=get&search=0x2AFA1A9FD2D031DA",
    mod6          =>
      "http://pgp.mit.edu/pks/lookup?op=get&search=0x721705A8B71EADAF",
    asciilifeform =>
      "http://pgp.mit.edu/pks/lookup?op=get&search=0xB98228A001ABFFC7"
  );

  print "Downloading public keys...\n";
  `mkdir -p pubkeys`;

  foreach my $key (keys %pubKeyURL) { 
    my $part1 = "curl -s \"$pubKeyURL{$key}\" "; 
    my $part2 = "| sed -n \"/$begin_pgp_key/,/$end_pgp_key/p\" ";
    my $part3 = "> pubkeys/$key.pub.asc";
    my $fetch = $part1 . $part2 . $part3;
    `$fetch`;
  }
}

sub importPublicKeys { 
  # Verify GPG PubKey import
  print "Importing public keys to .gnupg keyring...\n";
  my $part1 = "gpg --homedir $HOME/.gnupg ";
  my $part2 = "--logger-fd 1 --import $HOME/pubkeys/*.asc ";
  my $part3 = "> $HOME/gpg_import.log";
  my $gpg_import = $part1 . $part2 . $part3;
  `$gpg_import`;
  my @import_log = `cat gpg_import.log`;
  foreach(@import_log) { 
    if($_ =~ /^.*imported: (\d+).*$/) {
      if($1 ne "3") { 
        print "Error! GnuPG did not import 3 keys!\n";
        exit 1;
      }
    }
  }
}

sub verifySignatures { 
  print "Verifing patch signatures..\n";
  
  my @sorted = sort { 
                 $artifacts{$a}{order} <=> $artifacts{$b}{order}
               } keys %artifacts;
  shift @sorted;  # Remove 'v053_base' from sorted list

  foreach my $key (@sorted) { 
    my $flag = "FALSE";
    my $part1 = "gpg --homedir $HOME/.gnupg --logger-fd 1 ";
    my $part2 = "--verify $key/$artifacts{$key}{sig} ";
    my $part3 = "> $key\_sig.log";
    my $verify = $part1 . $part2 . $part3;
    `$verify`;

    my @verify = `cat $key\_sig.log`;
    foreach(@verify) { 
      if($_ =~ /^.*Good signature.*$/) { 
        $flag = "TRUE";
        print "  >> Signature verified for $artifacts{$key}{sig}\n";
      } 
    } 

    if($flag =~ m/FALSE/) { 
      print "Error! Possible bad signature on:\n";
      print "$HOME/$key/$artifacts{$key}{sig}\n";
      exit 1;
    }
  }
}

sub pruneFiles { 
  # Prune files per Chicken Manifest from v0.5.3 base code.
  print "Pruning files from v0.5.3 codebase not contained in the manifest...\n";
  my $chickenManifest = "$HOME/chicken/bitcoin-0.5.3-no-crud.sha256.manifest";
  
  `mkdir -p $HOME/$pruned`;
 
  # Prune from the original v053_base directory
  chdir("$HOME/$v053_base") or die $!;
  
  # Extract filenames from manifest and copy them into new directory
  if($os eq "FREEBSD" or $os eq "OPENBSD") { 
    my $part1 = "/bin/sh -c \'cut -d \" \" -f 3- $chickenManifest ";
    my $part2 = "| cpio -pdm $HOME/$pruned\'";
    my $prune = $part1 . $part2;
    `$prune`;
  } else { 
    my $part1 = "for i in \`cut -d \' \' -f 3- $chickenManifest\`; ";
    my $part2 = "do cp --parents \$i $HOME/$pruned; done";
    my $prune = $part1 . $part2;
    `$prune`;
  } 
 
  # Change dir to HOME to check file count after pruning 
  chdir("$HOME") or die $!;

  my $count1 = 0;
  my $count2 = 0;
 
  if($os eq "FREEBSD" or $os eq "OPENBSD") { 
    $count1 = `find -X $pruned -type f -print0 | xargs -0 sha256 | wc -l`;
    $count2 = `find -X $pruned -type f -print0 | xargs -0 sha256 | wc -l`;
  } else {  
    $count1 = `find $pruned -xtype f -print0 | xargs -0 sha256sum | wc -l`;
    $count2 = `cat chicken/bitcoin-0.5.3-no-crud.sha256.manifest | wc -l`;
  }
  
  if($count1 ne $count2) { 
    print "Error! The resulting file count after pruning did not match\n";
    print "the file count from the manifest.\n";
    print "post-pruning: $count1\n";
    print "    manifest: $count2\n";
    exit 1;
  }
}

sub applyPatches { 
  # Apply Submitted v0.5.3 Patches
  print "Patching v0.5.3 source files...\n";
  my @sorted = sort { 
                 $artifacts{$a}{order} <=> $artifacts{$b}{order} 
               } keys %artifacts;
  shift @sorted;  # Remove 'v053_base' from sorted list
  foreach my $key (@sorted) { 
    if($os eq "FREEBSD" or $os eq "OPENBSD") { 
      `patch --posix -E -d $pruned/src < $key/$artifacts{$key}{patch}`;
    } else { 
      `patch -d $pruned/src < $key/$artifacts{$key}{patch}`;
    }
    print "  >> Patched: $key/$artifacts{$key}{patch}\n";
  }
}

sub cleanUp { 
  if($FULL_CLEAN =~ m/TRUE/i) {
    print "Performing FULL CLEAN...\n";
    `rm -rf chicken`;
    `rm -rf rm_rf_upnp`; 
    `rm -rf https`; 
    `rm -rf alert`; 
    `rm -rf win32`; 
    `rm -rf db_config`; 
    `rm -rf rev_bump`; 
    `rm -rf static-makefile`; 
    `rm -rf pubkeys`; 
    `rm -rf .gnupg`; 
    `rm -rf *.tar.gz`; 
    `rm -rf *.tar`; 
    `rm -rf *.log`;
    `rm -rf $v053_base`;
  }
}

sub main {
  # Execute subroutines
  checkOS();
  checkTools();
  pullArtifacts();
  verifyArtifactHashes();
  unpackArchives();
  getPublicKeys();
  importPublicKeys();
  verifySignatures();
  pruneFiles();
  applyPatches();
  cleanUp();
}

main();
