#!/usr/bin/perl
#
# ESGF JAR Security Scanner
#
# Generates a JAR manifest from the installed Tomcat and SOLR and then
# searches CVEs for potential problems.
#
# On CentOS, this will require the following packages:
#   perl-Sort-Versions
#   perl-XML-Twig
#   perl-XML-XPath
#   perl-XML-XPathEngine
#
# You will also need to download CVRF files from:
#   https://cve.mitre.org/data/downloads/index.html
# and put them in /tmp (location can be changed in $cvrfdir below)
# Filename format will look like:
#   allitems-cvrf-year-XXXX.xml
# You will need to download a file for each year listed in @cvrf_years
#
# Usage:
#   jar_security_scan > scan_output.txt
#

use strict; use warnings;
use 5.010;

use Cwd 'abs_path';
use Data::Dumper;
use File::Basename;
use Sort::Versions;
use XML::Twig;

my $tomcatdir = '/usr/local/tomcat';
my $cvrfdir = '/tmp';
my $cvrfprefix = '/allitems-cvrf-year-';
my @cvrf_years = ( '2015','2016','2017' );
my %cvrf_twigs;

# %jar_searchregex is a hash of jar names to regular expressions that
# should be used to search the description fields of CVRF files
# instead of the simple jar name (which might be too short to be
# useful)
#
# Each regular expression will be used with /i (case-insensitive match)
#

my %jar_searchregex = (
    '52n-xml-gml-v321'          => qr/52n/i,
    '52n-xml-om-v20'            => qr/52n/i,
    'activation'		=> qr/activation.*java/i,
    'annotations'		=> qr/java.*annotations/i,
    'ant'			=> qr/apache ant/i,
    'antlr-runtime'             => qr/antlr/i,
    'asm'			=> qr/asm.*java/i,
    'bsh'                       => qr/beanshell/i,
    'commons-beanutils'		=> qr/(apache.*beanutils|commons-beanutils)/i,
    'commons-cli'		=> qr/(apache.*cli|commons-cli)/i,
    'commons-codec'		=> qr/(apache.*codec|commons-codec)/i,
    'commons-collections'       => qr/(apache.*collections|commons-collections)/i,
    'commons-dbcp'		=> qr/(apache.*dbcp|commons-dbcp)/i,
    'commons-dbutils'		=> qr/(apache.*dbutils|commons-dbutils)/i,
    'commons-digester'		=> qr/(apache.*digester|commons-digester)/i,
    'commons-discovery'		=> qr/(apache.*discovery|commons-discovery)/i,
    'commons-fileupload'        => qr/(apache.*fileupload|commons-fileupload)/i,
    'commons-httpclient'	=> qr/(apache.*httpclient|commons-httpclient)/i,
    'commons-lang'		=> qr/(apache.*lang|commons-lang)/i,
    'commons-logging'		=> qr/(apache.*log|commons-log)/i,
    'commons-pool'		=> qr/(apache.*pool|commons-pool)/i,
    'commons-validator'		=> qr/(apache.*validator|commons-validator)/i,
    'compiler'			=> qr/compiler.*java/i,
    'encoder'			=> qr/encoder.*java/i,
    'forms'			=> qr/forms.*java/i,
    'geronimo-servlet_3.0_spec' => qr/geronimo/i,
    'h2'                        => qr/h2 database/i,
    'je'			=> qr/berkeley.*db/i,
    'org.apache.aries.util'	=> qr/apache.*aries/i,
    'oro'			=> qr/jakarta.*oro/i,
    'jaxb-api'                  => qr/jaxb/i,
    'json'                      => qr/json.*java/i,
    'jstl'			=> qr/apache.*taglibs/i,
    'lang'              	=> qr/lang.*java/i,
    'lucene-core'               => qr/(apache.*lucene|lucene-core)/i,
    'mail'			=> qr/mail.*java/i,
    'org.apache.aries.blueprint.core' => qr/apache.*aries/i,
    'org.apache.aries.proxy.api' => qr/apache.*aries/i,
    'org.apache.aries.quiesce.api' => qr/apache.*aries/i,
    'org.apache.aries.util'     => qr/apache.*aries/i,
    'org.restlet.ext.servlet'   => qr/restlet/i,
    'protobuf-java'             => qr/(protocol buffers|protobuf)/i,
    'rome'              	=> qr/rome.*(java|rss)/i,
    'sac'                       => qr/sac .*css/i,
    'serializer'		=> qr/xalan/i,
    'slf4j-api'                 => qr/(simple logging facade|slf4j)/i,
    'solr-core'			=> qr/apache.*solr/i,
    'spring-beans'		=> qr/spring.*framework/i,
    'spring-core'		=> qr/spring.*framework/i,
    'spring-security-config'	=> qr/spring.*security/i,
    'spring-security-core'	=> qr/spring.*security/i,
    'spring-security-openid'	=> qr/spring.*security/i,
    'spring-security-taglibs'	=> qr/spring.*security/i,
    'spring-security-web'	=> qr/spring.*security/i,
    'standard'          	=> qr/standard.*java/i,
    'struts2-core'		=> qr/struts/i,
    'tds'			=> qr/thredds/i,
    'tiles-api'			=> qr/apache.*tiles/i,
    'tiles-compat'		=> qr/apache.*tiles/i,
    'tiles-servlet'		=> qr/apache.*tiles/i,
    'tiles-template'		=> qr/apache.*tiles/i,
    'velocity'			=> qr/velocity.*java/i,
    'xercesImpl'		=> qr/xerces/i,
    'xml-resolver'		=> qr/xml-resolver/i,
   );

# %falsepositives is a hash of vulnerability titles (generally CVE
# codes) mapped to a string starting with the name of the person
# declaring that the vulnerability is a false positive in this scan
# followed by a colon followed by a short explanation of why it's a
# false positive
#
# Keywords in use:
#   * unrelated: CVE is completely unrelated to the software being scanned
#   * oldversion: CVE applies only to versions older than any in use
#   * reject: CVE is flagged as rejected in the CVRFs
my %falsepositives = (
    'CVE-2011-2731' => 'zed: unrelated (VMWare SpringSource)',
    'CVE-2011-2732' => 'zed: unrelated (VMWare SpringSource)',
    'CVE-2011-2894' => 'zed: oldversion (Spring Framework <= 3.0.5)',
    'CVE-2011-4314' => 'zed: oldversion',
    'CVE-2012-0004' => 'zed: unrelated (MS DirectShow)',
    'CVE-2012-0391' => 'zed: oldversion (Struts < 2.2.3.1)',
    'CVE-2012-0392' => 'zed: oldversion (Struts < 2.3.1.1)',
    'CVE-2012-0393' => 'zed: oldversion (Struts < 2.3.1.1)',
    'CVE-2012-0394' => 'zed: oldversion (Struts < 2.3.1.1)',
    'CVE-2012-0657' => 'zed: unrelated (Apple Quartz Composer)',
    'CVE-2012-0838' => 'zed: oldversion (Struts < 2.2.3.1)',
    'CVE-2012-0936' => 'zed: unrelated (OpenNMS)',
    'CVE-2012-1006' => 'zed: oldversion (Struts <= 2.2.3)',
    'CVE-2012-1007' => 'zed: oldversion (struts 1.3.10)',
    'CVE-2012-1621' => 'zed: unrelated (Apache OFBiz)',
    'CVE-2012-2578' => 'zed: unrelated (SmarterMail)',
    'CVE-2012-2586' => 'zed: unrelated (Mailtraq)',
    'CVE-2012-4386' => 'zed: oldversion (Struts <= 2.3.4)',
    'CVE-2012-4387' => 'zed: oldversion (Struts <= 2.3.4)',
    'CVE-2012-5055' => 'zed: unrelated (VMWare SpringSource)',
    'CVE-2012-5616' => 'zed: unrelated (Apache CloudStack/Citrix CloudPlatform)',
    'CVE-2012-5783' => 'zed: oldversion (Apache HttpClient 3.x)',
    'CVE-2012-6127' => 'zed: reject',
    'CVE-2012-6153' => 'zed: oldversion (Apache HttpClient < 4.2.3)',
    'CVE-2012-6573' => 'zed: unrelated (Solr for Drupal)',
    'CVE-2012-6612' => 'zed: oldversion (Solr < 4.1)',
    'CVE-2013-0077' => 'zed: unrelated (MS DirectShow)',
    'CVE-2013-0753' => 'zed: unrelated (Firefox/Thunderbird/SeaMonkey)',
    'CVE-2013-1777' => 'zed: unrelated (full Geronimo, not just servlet)',
    'CVE-2013-1790' => 'zed: unrelated (Poppler)',
    'CVE-2013-1856' => 'zed: unrelated (Ruby on Rails)',
    'CVE-2013-1965' => 'zed: oldversion (Struts < 2.3.14.1)',
    'CVE-2013-1966' => 'zed: oldversion (Struts < 2.3.14.1)',
    'CVE-2013-2115' => 'zed: oldversion (Struts < 2.3.14.2)',
    'CVE-2013-2134' => 'zed: oldversion (Struts < 2.3.14.3)',
    'CVE-2013-2135' => 'zed: oldversion (Struts < 2.3.14.3)',
    'CVE-2013-2186' => 'zed: oldversion (Apache Commons FileUpload < 1.3.1)',
    'CVE-2013-2248' => 'zed: oldversion (Struts < 2.3.15)',
    'CVE-2013-2251' => 'zed: oldversion (Struts < 2.3.15)',
    'CVE-2013-3066' => 'zed: unrelated (Linksys EA6500)',
    'CVE-2013-3219' => 'zed: unrelated (bitcoind)',
    'CVE-2013-3220' => 'zed: unrelated (bitcoind)',
    'CVE-2013-4152' => 'zed: oldversion (Spring Framework < 4.0.0.M1)',
    'CVE-2013-4204' => 'zed: unrelated (Google Web Toolkit)',
    'CVE-2013-4212' => 'zed: unrelated (Apache Roller)',
    'CVE-2013-4310' => 'zed: oldversion (Struts < 2.3.15.1)',
    'CVE-2013-4316' => 'zed: oldversion (Struts < 2.3.15.1)',
    'CVE-2013-6288' => 'zed: unrelated (Apache Solr for TYPO3)',
    'CVE-2013-6289' => 'zed: unrelated (Apache Solr for TYPO3)',
    'CVE-2013-6348' => 'zed: oldversion (Struts < 2.3.15.3)',
    'CVE-2013-6397' => 'zed: oldversion (Solr < 4.6)',
    'CVE-2013-6407' => 'zed: oldversion (Solr < 4.1)',
    'CVE-2013-6408' => 'zed: oldversion (Solr < 4.3.1)',
    'CVE-2013-6429' => 'zed: oldversion (Spring Framework <= 4.0.0.RC1)',
    'CVE-2013-7295' => 'zed: unrelated (Tor)',
    'CVE-2013-7315' => 'zed: oldversion (Spring Framework <= 4.0.0.M3)',
    'CVE-2013-7398' => 'zed: unrelated (async-http-client < 1.9.0)',
    'CVE-2014-0085' => 'zed: unrelated (Actually a problem with Fuse Fabric, not Zookeeper)',
    'CVE-2014-0094' => 'zed: oldversion (Struts < 2.3.16.1)',
    'CVE-2014-0112' => 'zed: oldversion (Struts < 2.3.16.2)',
    'CVE-2014-0050' => 'zed: oldversion (Apache Commons FileUpload < 1.3.1)',
    'CVE-2014-0107' => 'zed: oldversion (Xalan < 2.7.2)',
    'CVE-2014-0111' => 'zed: unrelated (Apache Syncope)',
    'CVE-2014-0113' => 'zed: oldversion (Struts < 2.3.16.2)',
    'CVE-2014-0114' => 'zed: oldversion (Apache Commons BeanUtils < 1.9.2)',
    'CVE-2014-0116' => 'zed: oldversion (Struts < 2.3.16.3)',
    'CVE-2014-0722' => 'zed: unrelated (Cisco UCM)',
    'CVE-2014-0728' => 'zed: unrelated (Cisco UCM)',
    'CVE-2014-1512' => 'zed: unrelated (Firefox/Thunderbird/Seamonkey)',
    'CVE-2014-1904' => 'zed: oldversion (Spring Framework < 4.0.2)',
    'CVE-2014-1904' => 'zed: oldversion (Spring Framework < 4.0.2)',
    'CVE-2014-1972' => 'zed: unrelated (Apache Tapestry)',
    'CVE-2014-2483' => 'zed: oldversion (OpenJDK < 7u65, Oracle Java < 7u60/8u5)',
    'CVE-2014-3004' => 'zed: unrelated (Castor)',
    'CVE-2014-3577' => 'zed: oldversion (Apache HttpClient < 4.3.5)',
    'CVE-2014-3578' => 'zed: oldversion (Spring Framework < 4.0.5)',
    'CVE-2014-3625' => 'zed: oldversion (Spring Framework < 4.1.2)',
    'CVE-2014-3628' => 'zed: oldversion (SOLR < 4.10.3)',
    'CVE-2014-5185' => 'zed: unrelated (WordPress Quartz plugin)',
    'CVE-2014-5325' => 'zed: unrelated (Direct Web Remoting)',
    'CVE-2014-5886' => 'zed: unrelated (iVysilani)',
    'CVE-2014-6278' => 'zed: unrelated (GNU Bash)',
    'CVE-2014-7809' => 'zed: oldversion (Struts < 2.3.20)',
    'CVE-2014-8152' => 'zed: unrelated (Apache Santuario)',
    'CVE-2014-8244' => 'zed: unrelated (Linksys SMART WiFi)',
    'CVE-2015-0141' => 'zed: unrelated (IBM OpenPages)',
    'CVE-2015-0201' => 'zed: oldversion (Spring Framework < 4.1.5)',
    'CVE-2015-0231' => 'zed: unrelated (PHP)',
    'CVE-2015-0252' => 'zed: urelated (Xerces C, not Java)',
    'CVE-2015-0254' => 'zed: oldversion (Apache Standard Taglibs < 1.2.3)',
    'CVE-2015-0274' => 'zed: unrelated (XFS)',
    'CVE-2015-0279' => 'zed: unrelated (JBoss RichFaces)',
    'CVE-2015-0383' => 'zed: oldversion (Oracle Java <= 8u6)',
    'CVE-2015-0395' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0400' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0403' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0406' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0407' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0408' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0410' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0412' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0413' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0421' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0437' => 'zed: oldversion (Oracle Java <= 8u25)',
    'CVE-2015-0458' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0459' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0460' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0469' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0470' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0477' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0478' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0480' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0484' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0486' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0488' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0491' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0492' => 'zed: oldversion (Oracle Java <= 8u40)',
    'CVE-2015-0851' => 'zed: unrelated (OpenSAML-C, not Java)',
    'CVE-2015-0899' => 'zed: oldversion (Struts <= 1.3.10)',
    'CVE-2015-1058' => 'zed: unrelated (AdaptCMS)',
    'CVE-2015-1235' => 'zed: unrelated (Google Chrome)',
    'CVE-2015-1236' => 'zed: unrelated (Google Chrome)',
    'CVE-2015-1261' => 'zed: unrelated (Google Chrome)',
    'CVE-2015-1275' => 'zed: unrelated (Google Chrome)',
    'CVE-2015-1442' => 'zed: unrelated (ZeroCMS)',
    'CVE-2015-1643' => 'zed: unrelated (MS Server)',
    'CVE-2015-1762' => 'zed: unrelated (MSSQL)',
    'CVE-2015-1831' => 'zed: oldversion (Struts 2.3.20)',
    'CVE-2015-1836' => 'zed: unrelated (Apache HBase)',
    'CVE-2015-1875' => 'zed: unrelated (Elastix)',
    'CVE-2015-2242' => 'zed: unrelated (Webshop hun)',
    'CVE-2015-2243' => 'zed: unrelated (Webshop hun)',
    'CVE-2015-2244' => 'zed: unrelated (Webshop hun)',
    'CVE-2015-2327' => 'zed: unrelated (PCRE)',
    'CVE-2015-2328' => 'zed: unrelated (PCRE)',
    'CVE-2015-2335' => 'zed: unrelated (MyBB)',
    'CVE-2015-2583' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-2590' => 'zed: oldversion (Oracle Java <= 8u33)',
    'CVE-2015-2596' => 'zed: oldversion (Oracle Java <= 7u80)',
    'CVE-2015-2597' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2597' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2601' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2613' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2619' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2621' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2624' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-2625' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2626' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-2627' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2628' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2632' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2637' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2638' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2640' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-2654' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-2656' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-2659' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2664' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-2738' => 'zed: unrelated (Firefox)',
    'CVE-2015-2787' => 'zed: unrelated (PHP)',
    'CVE-2015-2838' => 'zed: unrelated (Citrix NetScaler)',
    'CVE-2015-2839' => 'zed: unrelated (Citrix NetScaler)',
    'CVE-2015-2858' => 'zed: unrelated (Datalex)',
    'CVE-2015-2912' => 'zed: unrelated (OrientDB Server)',
    'CVE-2015-2938' => 'zed: unrelated (MediaWiki)',
    'CVE-2015-3192' => 'zed: oldversion (Spring Framework < 4.1.7)',
    'CVE-2015-3227' => 'zed: unrelated (Ruby on Rails)',
    'CVE-2015-3397' => 'zed: unrelated (Yii Framework)',
    'CVE-2015-3834' => 'zed: unrelated (libstagefright)',
    'CVE-2015-4027' => 'zed: unrelated (Acunetix Web Vulnerability Scanner)',
    'CVE-2015-4478' => 'zed: unrelated (Mozilla Firefox)',
    'CVE-2015-4590' => 'zed: unrelated (Arduino)',
    'CVE-2015-4729' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-4731' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-4732' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-4733' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-4734' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4736' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-4748' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-4749' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-4754' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4760' => 'zed: oldversion (Oracle Java <= 8u45)',
    'CVE-2015-4764' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4774' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4775' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4776' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4777' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4778' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4779' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4780' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4781' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4782' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4783' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4784' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4785' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4786' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4787' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4788' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4789' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4790' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2015-4760' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4803' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4805' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4806' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4810' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4835' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4840' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4842' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4843' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4844' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4852' => 'zed: unrelated (WebLogic Server)',
    'CVE-2015-4860' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4868' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4871' => 'zed: oldversion (Oracle Java <= 7u85)',
    'CVE-2015-4872' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4881' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4882' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4883' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4893' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4901' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4902' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4903' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4906' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4908' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4911' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-4916' => 'zed: oldversion (Oracle Java <= 8u60)',
    'CVE-2015-5169' => 'zed: oldversion (Apache Struts < 2.3.20)',
    'CVE-2015-5209' => 'zed: oldversion (Apache Struts < 2.3.24.1)',
    'CVE-2015-5211' => 'zed: oldversion (Spring Framework <= 4.2.1)',
    'CVE-2015-5250' => 'zed: unrelated (OpenShift Origin)',
    'CVE-2015-5258' => 'zed: unrelated (springframework-social)',
    'CVE-2015-5262' => 'zed: oldversion (Apache HttpClient < 4.3.6)',
    'CVE-2015-5289' => 'zed: unrelated (PostgreSQL)',
    'CVE-2015-5344' => 'zed: unrelated (Apache Camel)',
    'CVE-2015-5506' => 'zed: unrelated (Drupal Solr)',
    'CVE-2015-5571' => 'zed: unrelated (Adobe Flash/Air)',
    'CVE-2015-5771' => 'zed: unrelated (Apple Quartz Composer)',
    'CVE-2015-5916' => 'zed: unrelated (Apple iOS)',
    'CVE-2015-6420' => 'zed: oldversion (Apache Commons Collections < 3.2.2)',
    'CVE-2015-6718' => 'zed: unrelated (Adobe Reader/Acrobat)',
    'CVE-2015-6719' => 'zed: unrelated (Adobe Reader/Acrobat)',
    'CVE-2015-6721' => 'zed: unrelated (Adobe Reader/Acrobat)',
    'CVE-2015-6722' => 'zed: unrelated (Adobe Reader/Acrobat)',
    'CVE-2015-6761' => 'zed: unrelated (Google Chrome)',
    'CVE-2015-6762' => 'zed: unrelated (Google Chrome)',
    'CVE-2015-6763' => 'zed: unrelated (Google Chrome)',
    'CVE-2015-6764' => 'zed: unrelated (Google V8)',
    'CVE-2015-6934' => 'zed: unrelated (VMWare-specific)',
    'CVE-2015-6968' => 'zed: unrelated (Serendipity)',
    'CVE-2015-7192' => 'zed: unrelated (Mozilla Firefox)',
    'CVE-2015-7392' => 'zed: unrelated (FreeSWITCH)',
    'CVE-2015-7450' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2015-7766' => 'zed: unrelated (ZOHO ManageEngine OpManager)',
    'CVE-2015-7834' => 'zed: unrelated (Google Chrome)',
    'CVE-2015-8103' => 'zed: unrelated (Jenkins)',
    'CVE-2015-8380' => 'zed: unrelated (PCRE)',
    'CVE-2015-8381' => 'zed: unrelated (PCRE)',
    'CVE-2015-8382' => 'zed: unrelated (PCRE)',
    'CVE-2015-8383' => 'zed: unrelated (PCRE)',
    'CVE-2015-8384' => 'zed: unrelated (PCRE)',
    'CVE-2015-8385' => 'zed: unrelated (PCRE)',
    'CVE-2015-8386' => 'zed: unrelated (PCRE)',
    'CVE-2015-8387' => 'zed: unrelated (PCRE)',
    'CVE-2015-8388' => 'zed: unrelated (PCRE)',
    'CVE-2015-8389' => 'zed: unrelated (PCRE)',
    'CVE-2015-8390' => 'zed: unrelated (PCRE)',
    'CVE-2015-8391' => 'zed: unrelated (PCRE)',
    'CVE-2015-8392' => 'zed: unrelated (PCRE)',
    'CVE-2015-8394' => 'zed: unrelated (PCRE)',
    'CVE-2015-8395' => 'zed: unrelated (PCRE)',
    'CVE-2015-8765' => 'zed: duplicate (Apache commons issues)',
    'CVE-2015-8795' => 'zed: oldversion (SOLR < 5.1)',
    'CVE-2015-8796' => 'zed: oldversion (SOLR < 5.3)',
    'CVE-2015-8797' => 'zed: oldversion (SOLR < 5.3.1)',
    'CVE-2016-0231' => 'zed: unrelated (IBM FTM)',
    'CVE-2016-0232' => 'zed: unrelated (IBM FTM)',
    'CVE-2016-0402' => 'zed: oldversion (Oracle Java <= 8u66)',
    'CVE-2016-0448' => 'zed: oldversion (Oracle Java <= 8u66)',
    'CVE-2016-0466' => 'zed: oldversion (Oracle Java <= 8u66)',
    'CVE-2016-0475' => 'zed: oldversion (Oracle Java <= 8u66)',
    'CVE-2016-0483' => 'zed: oldversion (Oracle Java <= 8u66)',
    'CVE-2016-0494' => 'zed: oldversion (Oracle Java <= 8u66)',
    'CVE-2016-0603' => 'zed: oldversion (Oracle Java <= 8u72)',
    'CVE-2016-0636' => 'zed: oldversion (Oracle Java <= 8u74)',
    'CVE-2016-0657' => 'zed: unrelated (MySQL)',
    'CVE-2016-0682' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2016-0686' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-0687' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-0689' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2016-0692' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2016-0694' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2016-0695' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-0785' => 'zed: oldversion (Apache Struts < 2.3.28)',
    'CVE-2016-0729' => 'zed: unrelated (Apache Xerces C)',
    'CVE-2016-0792' => 'zed: unrelated (Jenkins via XStream, CVE-2016-3674)',
    'CVE-2016-0811' => 'zed: unrelated (Android libmediaplayerservice)',
    'CVE-2016-0828' => 'zed: unrelated (Android mediaserver)',
    'CVE-2016-0829' => 'zed: unrelated (Android mediaserver)',
    'CVE-2016-10222' => 'zed: unrelated (WebKit)',
    'CVE-2016-1000031' => 'zed: invalid',
    'CVE-2016-1181' => 'zed: oldversion (Apache Struts <= 1.3.10)',
    'CVE-2016-1182' => 'zed: oldversion (Apache Struts <= 1.3.10)',
    'CVE-2016-1238' => 'zed: unrelated (Perl)',
    'CVE-2016-1283' => 'zed: unrelated (PCRE)',
    'CVE-2016-1406' => 'zed: unrelated (Cisco Prime Infrastructure)',
    'CVE-2016-1688' => 'zed: unrelated (Google V8)',
    'CVE-2016-1730' => 'zed: unrelated (WebSheet)',
    'CVE-2016-1928' => 'zed: unrelated (SAP HANA)',
    'CVE-2016-1985' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-1986' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-1997' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-1998' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-1999' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-2003' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-2045' => 'zed: unrelated (PHPMyAdmin)',
    'CVE-2016-2099' => 'zed: unrelated (Apache Xerces C++)',
    'CVE-2016-2170' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-2173' => 'zed: oldversion (Spring AMQP)',
    'CVE-2016-2162' => 'zed: oldversion (Apache Struts < 2.3.25)',
    'CVE-2016-2212' => 'zed: unrelated (Magento)',
    'CVE-2016-2425' => 'zed: unrelated (AOSP Mail for Android)',
    'CVE-2016-2458' => 'zed: unrelated (AOSP Mail for Android)',
    'CVE-2016-2510' => 'zed: unrelated (BeanShell/bsh)',
    'CVE-2016-2537' => 'zed: unrelated (node.js)',
    'CVE-2016-2560' => 'zed: unrelated (PHPMyAdmin)',
    'CVE-2016-3060' => 'zed: unrelated (IBM FTM)',
    'CVE-2016-3081' => 'zed: oldversion (Apache Struts < 2.3.28.1)',
    'CVE-2016-3082' => 'zed: oldversion (Apache Struts < 2.3.28.1)',
    'CVE-2016-3087' => 'zed: oldversion (Apache Struts < 2.3.28.1)',
    'CVE-2016-3093' => 'zed: oldversion (Apache Struts <= 2.3.24.1)',
    'CVE-2016-3168' => 'zed: unrelated (Drupal)',
    'CVE-2016-3191' => 'zed: unrelated (PCRE)',
    'CVE-2016-3341' => 'zed: unrelated (MS Windows Transaction Manager)',
    'CVE-2016-3418' => 'zed: unrelated (Berkeley DB non-JE)',
    'CVE-2016-3422' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-3425' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-3426' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-3427' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-3642' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-3443' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-3449' => 'zed: oldversion (Oracle Java <= 8u77)',
    'CVE-2016-3458' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3485' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3498' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3500' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3503' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3508' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3511' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3550' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3552' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3587' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3598' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3606' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3610' => 'zed: oldversion (Oracle Java <= 8u92)',
    'CVE-2016-3674' => 'zed: oldversion (XStream < 1.4.9)',
    'CVE-2016-3723' => 'zed: unrelated (Jenkins)',
    'CVE-2016-3918' => 'zed: unrelated (AOSP Mail for Android)',
    'CVE-2016-4003' => 'zed: unrelated (Apache Struts < 2.3.28)',
    'CVE-2016-4303' => 'zed: unrelated (cJSON)',
    'CVE-2016-4368' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-4369' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-4372' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-4385' => 'zed: duplicate (Apache Commons Collections, CVE-2015-6420)',
    'CVE-2016-4425' => 'zed: unrelated (Jansson)',
    'CVE-2016-4430' => 'zed: oldversion (Apache Struts < 2.3.28.1)',
    'CVE-2016-4431' => 'zed: oldversion (Apache Struts < 2.3.28.1)',
    'CVE-2016-4433' => 'zed: oldversion (Apache Struts < 2.3.28.1)',
    'CVE-2016-4436' => 'zed: oldversion (Apache Struts < 2.3.29)',
    'CVE-2016-4438' => 'zed: oldversion (Apache Struts < 2.3.28.1)',
    'CVE-2016-4462' => 'zed: unrelated (Apache OFBiz)',
    'CVE-2016-4463' => 'zed: unrelated (Apache Xerces C++)',
    'CVE-2016-4465' => 'zed: oldversion (Apache Struts < 2.3.28.1)',
    'CVE-2016-4814' => 'zed: unrelated (Geospatial Information Authority of Japan)',
    'CVE-2016-4849' => 'zed: unrelated (Geeklog IVYWE)',
    'CVE-2016-4875' => 'zed: unrelated (Geeklog IVYWE)',
    'CVE-2016-4974' => 'zed: unrelated (Apache Qpid)',
    'CVE-2016-4977' => 'zed: oldversion (Spring Security OAuth <= 2.0.9)',
    'CVE-2016-5160' => 'zed: unrelated (Google Chrome)',
    'CVE-2016-5162' => 'zed: unrelated (Google Chrome)',
    'CVE-2016-5229' => 'zed: duplicate (Atlassian Bamboo via XStream)',
    'CVE-2016-5412' => 'zed: unrelated (Linux Kernel on PowerPC)',
    'CVE-2016-5542' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5546' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5547' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5552' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5554' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5556' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5568' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5573' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5582' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5597' => 'zed: oldversion (Oracle Java <= 8u102)',
    'CVE-2016-5668' => 'zed: unrelated (Crestron Electronics)',
    'CVE-2016-5705' => 'zed: unrelated (phpMyAdmin)',
    'CVE-2016-5828' => 'zed: unrelated (Linux Kernel on PowerPC)',
    'CVE-2016-5981' => 'zed: unrelated (IBM FileNet)',
    'CVE-2016-6317' => 'zed: unrelated (Ruby on Rails)',
    'CVE-2016-6426' => 'zed: unrelated (Cisco Unified Intelligence Center)',
    'CVE-2016-6494' => 'zed: unrelated (MongoDB)',
    'CVE-2016-6629' => 'zed: unrelated (phpMyAdmin)',
    'CVE-2016-6795' => 'zed: oldversion (Apache Struts <= 2.3.30)',
    'CVE-2016-7040' => 'zed: unrelated (RedHat CloudForms)',
    'CVE-2016-7506' => 'zed: unrelated (Artifex)',
    'CVE-2016-7867' => 'zed: unrelated (Adobe Flash)',
    'CVE-2016-7868' => 'zed: unrelated (Adobe Flash)',
    'CVE-2016-7869' => 'zed: unrelated (Adobe Flash)',
    'CVE-2016-7870' => 'zed: unrelated (Adobe Flash)',
    'CVE-2016-7964' => 'zed: unrelated (DokuWiki)',
    'CVE-2016-8328' => 'zed: oldversion (Oracle Java <= 8u112)',
    'CVE-2016-8738' => 'zed: oldversion (Apache Struts <= 2.5.5)',
    'CVE-2016-8905' => 'zed: unrelated (dotCMS)',
    'CVE-2016-9108' => 'zed: unrelated (Artifex MuJS)',
    'CVE-2016-9318' => 'zed: unrelated (LibXML2)',
    'CVE-2016-10132' => 'zed: unrelated (Artifex MuJS)',
    'CVE-2016-10141' => 'zed: unrelated (Artifex MuJS)',
    'CVE-2017-7672' => 'zed: unrelated (Apache Struts < 2.5.12)',
    'CVE-2017-3503' => 'zed: unrelated (Oracle Primavera Products Suite)',
    'CVE-2017-3241' => 'zed: oldversion (Oracle Java <= 8u112)',
    'CVE-2017-3252' => 'zed: oldversion (Oracle Java <= 8u112)',
    'CVE-2017-3253' => 'zed: oldversion (Oracle Java <= 8u112)',
    'CVE-2017-3259' => 'zed: oldversion (Oracle Java <= 8u112)',
    'CVE-2017-3260' => 'zed: oldversion (Oracle Java <= 8u112)',
    'CVE-2017-3262' => 'zed: oldversion (Oracle Java <= 8u112)',
    'CVE-2017-3511' => 'zed: oldversion (Oracle Java <= 8u121)',
    'CVE-2017-3512' => 'zed: oldversion (Oracle Java <= 8u121)',
    'CVE-2017-3514' => 'zed: oldversion (Oracle Java <= 8u121)',
    'CVE-2017-3526' => 'zed: oldversion (Oracle Java <= 8u121)',
    'CVE-2017-3533' => 'zed: oldversion (Oracle Java <= 8u121)',
    'CVE-2017-3544' => 'zed: oldversion (Oracle Java <= 8u121)',
    'CVE-2017-3604' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3605' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3606' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3607' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3608' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3609' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3610' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3611' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3612' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3613' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3614' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3615' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3616' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-3617' => 'zed: unrelated (Berkeley DB)',
    'CVE-2017-5586' => 'zed: unrelated (OpenText Documentum D2)',
    'CVE-2017-5638' => 'zed: unrelated (Apache Struts < 2.5.10.1)',
    'CVE-2017-5656' => 'zed: unrelated (Apache CXF/STSClient)',
    'CVE-2017-6711' => 'zed: unrelated (Cisco Ultra Services Framework)',
    'CVE-2017-8444' => 'zed: unrelated (Elastic Cloud Enterprise)',
    'CVE-2017-9083' => 'zed: unrelated (Poppler)',
    'CVE-2017-9304' => 'zed: unrelated (YARA)',
    'CVE-2017-9438' => 'zed: unrelated (YARA)',
    'CVE-2017-9765' => 'zed: unrelated (Genivia gSOAP < 2.8.48)',
    'CVE-2017-9787' => 'zed: oldversion (Apache Struts < 2.5.12)',
    'CVE-2017-9791' => 'zed: oldversion (Apache Struts 2.3.x)',
    'CVE-2017-9793' => 'zed: oldversion (Apache Struts <= 2.5.12)',
    'CVE-2017-9804' => 'zed: oldversion (Apache Struts <= 2.5.12)',
    'CVE-2017-9805' => 'zed: oldversion (Apache Struts < 2.5.13)',
    'CVE-2017-10053' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10067' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10078' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10086' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10089' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10104' => 'zed: unrelated (Java Advanced Management Console 2.6)',
    'CVE-2017-10105' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10108' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10109' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10110' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10114' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10115' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10116' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10117' => 'zed: unrelated (Java Advanced Management Console 2.6)',
    'CVE-2017-10118' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10121' => 'zed: unrelated (Java Advanced Management Console 2.6)',
    'CVE-2017-10125' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10135' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10145' => 'zed: unrelated (Java Advanced Management Console 2.6)',
    'CVE-2017-10176' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10198' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10243' => 'zed: oldversion (Oracle Java <= 8u131)',
    'CVE-2017-10932' => 'zed: unrelated (ZTE Microwave NR8000)',
    'CVE-2017-11694' => 'zed: unrelated (MEDHOST Document Management System)',
    'CVE-2017-12611' => 'zed: oldversion (Apache Struts <= 2.5.10)',
    'CVE-2017-12621' => 'zed: unrelated (Apache Commons Jelly)',
    'CVE-2017-1000061' => 'zed: oldversion (xmlsec <= 1.2.23)',
   );


# %jars is a global hash of hashes modified directly by other subs
# Each key is the name of a jar, and the value is an anonymous hash of
# webapp => jarversion
my %jars;
my %lowversions;

# %found is a global hash keyed by CVE, where the value is an
# anonymous hash of the following structure:
#   jars => array of all potentially relevant jars
#   text => text of the CVE
#   urls => array of URLs referenced by the CVE
my %found;

find_all_jars(\%jars);
print_jars(%jars);

say "\n===========\n";

%lowversions = lowest_jar_versions(%jars);
foreach my $year (@cvrf_years) {
    say {*STDERR} "Parsing CVRF entries for $year... ";
    $cvrf_twigs{$year} = twig_cvrf($cvrfdir . $cvrfprefix . $year . '.xml');
}

foreach my $year (@cvrf_years) {
    say {*STDERR} "Searching $year CVRFs for potential issues...";
    foreach my $jar (sort keys %lowversions) {
        say {*STDERR} "- Checking $jar...";
        my @notes;
        if ($jar_searchregex{$jar}) {
            @notes = $cvrf_twigs{$year}->descendants("Note[string() =~ /$jar_searchregex{$jar}/]");
        }
        else {
            @notes = $cvrf_twigs{$year}->descendants("Note[string() =~ /$jar/i]");
        }
        foreach my $note (@notes) {
            next if ($note->parent->gi eq 'DocumentNotes');
            next if ($note->text =~ /\*\* REJECT \*\*/);
            my $vuln = $note->parent->parent;
            if(not $vuln) {
                die("Could not find grandparent of note!");
            }
            my $title_element = $vuln->first_child('Title');
            if(not $title_element) {
                die("Could not find title for note with text:\n",$note->text);
            }
            my $title = $title_element->text;
            next if $falsepositives{$title};

            # TODO: The title has so far always been identical to CVE.
            # Just always use this, since we absolutely need CVE to
            # generate the links?
            my $cve_element = $vuln->first_child('CVE');
            if(not $cve_element) {
                die("Could not find CVE for note with text:\n",$note->text);
            }
            my $cve = $cve_element->text;
            next if $falsepositives{$cve};


            # Attempt to isolate the last mentioned version string in
            # the description.
            #
            # This may be a bad idea, as it's not strictly guaranteed
            # to be the highest version with an exploit, so it should
            # be at most used informationally.  Sometimes the last
            # version mentioned is the last version including the
            # exploit, and sometimes the first version fixed.

            $note->text =~ /\s([0-9][0-9a-zA-Z;.-]+)(\s|,|\.)/;
            my $lastversionstring = $1 || '';
            my @urlelements = $vuln->descendants('URL');

            if(! $found{$cve}) {
                $found{$cve}->{'text'} = $note->text;
                foreach my $url (@urlelements) {
                    push (@{$found{$cve}->{'urls'}},$url->text);
                }
            }
            push (@{$found{$cve}->{'jars'}},$jar);
        }
    }
}

foreach my $cve (sort keys %found) {
    say "${cve} is possibly related to:";
    foreach my $jar (sort @{$found{$cve}->{'jars'}}) {
        foreach my $webapp (sort keys %{$jars{$jar}}) {
            say "  $jar\t$webapp\t$jars{$jar}{$webapp}";
        }
    }
    say "\n",$found{$cve}->{'text'};
    say "https://web.nvd.nist.gov/view/vuln/detail?vulnId=" . $cve;
    foreach my $url (@{$found{$cve}->{'urls'}}) {
        say $url;
    }
    say "\n----------\n";
}

##################
### PROCEDURES ###
##################

# sub find_all_jars
#
# Wrapper to find_webapp_jars that finds jars in every detected java module
#
# Arguments:
#   * $jars: global hashref of jar information (will be directly modified)
#
# Returns nothing
#
sub find_all_jars {
    my ($jars) = @_;
    my @webapps = list_webapps();

    find_oracle_java($jars);
    find_misc_jars($jars);
    foreach my $webapp (sort @webapps) {
        find_webapp_jars($webapp,$jars);
    }
    return;
}


# sub find_oracle_java
#
# Creates a synthetic hashref entry specifically for Oracle Java SE
# itself
#
# Arguments:
#   * hashref containing jar information that will be directly modified
# Returns: nothing
#
sub find_oracle_java {
    my ($jars) = @_;
    my $javadir='/usr/local/java';
    my $version;

    $javadir = basename(abs_path($javadir));
    $javadir =~ /^jdk1\.(\d)\.0_(\d+)/x;
    if (not $2) {
        say {*STDERR} "WARNING: Unable to find Oracle Java version";
        $version = 'unknown';
    }
    else {
        $version = "${1}u${2}";
    }
    $jars{'Oracle Java SE'}->{$javadir} = $version;
    return;
}


# sub find_misc_jars
#
# Updates the jars hashref to contain all informations on jars found
# in miscellaneous specified directories
#
# Arguments:
#   * hashref containing jar information that will be directly modified
# Returns: nothing
#
sub find_misc_jars {
    my ($jars) = @_;
    my $dirhandle;
    my @dirlist;
    my $appname;
    my $jarname;
    my $jarver;

    my %jardirs = (
        '/usr/local/las-esgf/8.6.4/las-esgf-v8.6.4/WebContent/WEB-INF/lib' => 'las-webcontent',
        '/usr/local/solr/server/lib/'                                      => 'solr-server',
        '/usr/local/solr/server/solr-webapp/webapp/WEB-INF/lib'            => 'solr-webapp',
       );

    foreach my $jardir (keys %jardirs) {
        $appname = $jardirs{$jardir};
        opendir($dirhandle,$jardir)
          or die ("Unable to read ${jardir} !");
        @dirlist = readdir $dirhandle;
        closedir($dirhandle);

        foreach my $entry (sort @dirlist) {
            next if ($entry eq '.');
            next if ($entry eq '..');
            if ( ($jarname,$jarver) = $entry =~ /([a-zA-Z0-9._-]+)-([0-9][a-zA-Z0-9.]+)\.jar/)
            {
                $jars{$jarname}->{$appname} = $jarver;
            }
        }
    }
    return;
}


# sub find_webapp_jars
#
# Updates the jars hashref to contain all information on jars found in
# a particular webapp directory
#
# Arguments:
#   * webapp to search
#   * hashref containing jar information that will be directly modified
# Returns: nothing

sub find_webapp_jars {
    my ($webapp,$jars) = @_;
    my $dirhandle;
    my @dirlist;
    my $jarname;
    my $jarver;

    opendir($dirhandle,"${tomcatdir}/webapps/${webapp}/WEB-INF/lib")
      or die ("Unable to read ${tomcatdir}/webapps/${webapp}/WEB-INF/lib !");
    @dirlist = readdir $dirhandle;
    closedir($dirhandle);

    foreach my $entry (sort @dirlist) {
        next if ($entry eq '.');
        next if ($entry eq '..');
        if ( ($jarname,$jarver) = $entry =~ /([a-zA-Z0-9._-]+)-([0-9][a-zA-Z0-9.]+)\.jar/)
        {
            $jars{$jarname}->{$webapp} = $jarver;
        }
    }
    return;
}

# sub lowest_jar_versions
#
# Arguments:
#   * %jars: hash containing jar information
#
# Returns a hash containing just the lowest version of each jar found
# This can also be used to generate a unique list of jars
#
sub lowest_jar_versions {
    my %jars = @_;
    my %lowversion;

    foreach my $jarname (keys %jars) {
        foreach my $webapp (keys %{$jars{$jarname}}) {
            if(not $lowversion{$jarname} or versioncmp($jars{$jarname}->{$webapp},$lowversion{$jarname}) <= 0) {
                $lowversion{$jarname} = $jars{$jarname}->{$webapp};
            }
        }
    }
    return %lowversion;
}

# sub print_jars
#
# Prints to the screen each jar name, the module it was found in, and
# what version was found in that module, in tab-separated format.
#
# Arguments:
#   * %jars: hash containing jar information
#
# Returns nothing
#
sub print_jars {
    my %jars = @_;

    say "Java JAR manifest";
    say "=================";
    foreach my $jar (sort keys %jars) {
        foreach my $webapp (sort keys %{$jars{$jar}}) {
            say "$jar\t$webapp\t$jars{$jar}{$webapp}";
        }
    }
    return;
}

# sub list_webapps
#
# Arguments: none
# Returns: array containing all webapps except ROOT
#
sub list_webapps {
    my @webapps;
    my $dirhandle;
    my @dirlist;

    opendir($dirhandle,"${tomcatdir}/webapps")
      or die ("Unable to read ${tomcatdir}/webapps directory!");
    @dirlist = readdir $dirhandle;
    closedir($dirhandle);

    foreach my $entry (sort @dirlist) {
        next if ($entry eq '.');
        next if ($entry eq '..');
        next if ($entry eq 'ROOT');

        if (-d "${tomcatdir}/webapps/${entry}") {
            push(@webapps,$entry);
        }
    }
    return @webapps;
}

# sub twig_cvrf
#
# Convert a CVRF file into a XML::Twig object for further usage
#
# Arguments:
#   $cvrf: full path to CVRF file
#
# Returns:
#   Scalar twig object
#
sub twig_cvrf {
    my ($cvrf) = @_;
    my $twig = XML::Twig->new(
        keep_atts_order => 1,
        output_encoding => 'utf-8',
        pretty_print => 'record'
       );
    $twig->parsefile($cvrf)
      or die ("Failed to parse CVRF file ${cvrf}!");
    return $twig;
}