Coverage Report - org.owasp.dependencycheck.data.nvdcve.CveDB
 
Classes in this File Line Coverage Branch Coverage Complexity
CveDB
47%
151/319
62%
59/94
5
 
 1  
 /*
 2  
  * This file is part of dependency-check-core.
 3  
  *
 4  
  * Dependency-check-core is free software: you can redistribute it and/or modify it
 5  
  * under the terms of the GNU General Public License as published by the Free
 6  
  * Software Foundation, either version 3 of the License, or (at your option) any
 7  
  * later version.
 8  
  *
 9  
  * Dependency-check-core is distributed in the hope that it will be useful, but
 10  
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  
  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 12  
  * details.
 13  
  *
 14  
  * You should have received a copy of the GNU General Public License along with
 15  
  * dependency-check-core. If not, see http://www.gnu.org/licenses/.
 16  
  *
 17  
  * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
 18  
  */
 19  
 package org.owasp.dependencycheck.data.nvdcve;
 20  
 
 21  
 import java.io.BufferedReader;
 22  
 import java.io.File;
 23  
 import java.io.IOException;
 24  
 import java.io.InputStream;
 25  
 import java.io.InputStreamReader;
 26  
 import java.io.UnsupportedEncodingException;
 27  
 import java.sql.Connection;
 28  
 import java.sql.DriverManager;
 29  
 import java.sql.PreparedStatement;
 30  
 import java.sql.ResultSet;
 31  
 import java.sql.SQLException;
 32  
 import java.sql.Statement;
 33  
 import java.util.ArrayList;
 34  
 import java.util.HashSet;
 35  
 import java.util.List;
 36  
 import java.util.Set;
 37  
 import java.util.logging.Level;
 38  
 import java.util.logging.Logger;
 39  
 import org.owasp.dependencycheck.data.cwe.CweDB;
 40  
 import org.owasp.dependencycheck.dependency.Reference;
 41  
 import org.owasp.dependencycheck.dependency.Vulnerability;
 42  
 import org.owasp.dependencycheck.dependency.VulnerableSoftware;
 43  
 import org.owasp.dependencycheck.utils.DependencyVersion;
 44  
 import org.owasp.dependencycheck.utils.DependencyVersionUtil;
 45  
 import org.owasp.dependencycheck.utils.Settings;
 46  
 
 47  
 /**
 48  
  * The database holding information about the NVD CVE data.
 49  
  *
 50  
  * @author Jeremy Long (jeremy.long@owasp.org)
 51  
  */
 52  19
 public class CveDB {
 53  
 
 54  
     /**
 55  
      * Resource location for SQL file used to create the database schema.
 56  
      */
 57  
     public static final String DB_STRUCTURE_RESOURCE = "data/initialize.sql";
 58  
     /**
 59  
      * The version of the current DB Schema.
 60  
      */
 61  
     public static final String DB_SCHEMA_VERSION = "2.6";
 62  
     /**
 63  
      * Database connection
 64  
      */
 65  
     private Connection conn;
 66  
     //<editor-fold defaultstate="collapsed" desc="Constants to create, maintain, and retrieve data from the CVE Database">
 67  
     /**
 68  
      * SQL Statement to delete references by vulnerability ID.
 69  
      */
 70  
     public static final String DELETE_REFERENCE = "DELETE FROM reference WHERE cveid = ?";
 71  
     /**
 72  
      * SQL Statement to delete software by vulnerability ID.
 73  
      */
 74  
     public static final String DELETE_SOFTWARE = "DELETE FROM software WHERE cveid = ?";
 75  
     /**
 76  
      * SQL Statement to delete a vulnerability by CVE.
 77  
      */
 78  
     public static final String DELETE_VULNERABILITY = "DELETE FROM vulnerability WHERE cve = ?";
 79  
     /**
 80  
      * SQL Statement to cleanup orphan entries. Yes, the db schema could be a
 81  
      * little tighter, but what we have works well to keep the data file size
 82  
      * down a bit.
 83  
      */
 84  
     public static final String CLEANUP_ORPHANS = "DELETE FROM CpeEntry WHERE id not in (SELECT CPEEntryId FROM Software); ";
 85  
     /**
 86  
      * SQL Statement to insert a new reference.
 87  
      */
 88  
     public static final String INSERT_REFERENCE = "INSERT INTO reference (cveid, name, url, source) VALUES (?, ?, ?, ?)";
 89  
     /**
 90  
      * SQL Statement to insert a new software.
 91  
      */
 92  
     public static final String INSERT_SOFTWARE = "INSERT INTO software (cveid, cpeEntryId, previousVersion) VALUES (?, ?, ?)";
 93  
     /**
 94  
      * SQL Statement to insert a new cpe.
 95  
      */
 96  
     public static final String INSERT_CPE = "INSERT INTO cpeEntry (cpe, vendor, product) VALUES (?, ?, ?)";
 97  
     /**
 98  
      * SQL Statement to get a CPEProductID.
 99  
      */
 100  
     public static final String SELECT_CPE_ID = "SELECT id FROM cpeEntry WHERE cpe = ?";
 101  
     /**
 102  
      * SQL Statement to insert a new vulnerability.
 103  
      */
 104  
     public static final String INSERT_VULNERABILITY = "INSERT INTO vulnerability (cve, description, cwe, cvssScore, cvssAccessVector, "
 105  
             + "cvssAccessComplexity, cvssAuthentication, cvssConfidentialityImpact, cvssIntegrityImpact, cvssAvailabilityImpact) "
 106  
             + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
 107  
     /**
 108  
      * SQL Statement to update a vulnerability.
 109  
      */
 110  
     public static final String UPDATE_VULNERABILITY = "UPDATE vulnerability SET description=?, cwe=?, cvssScore=?, cvssAccessVector=?, "
 111  
             + "cvssAccessComplexity=?, cvssAuthentication=?, cvssConfidentialityImpact=?, cvssIntegrityImpact=?, cvssAvailabilityImpact=? "
 112  
             + "WHERE id=?";
 113  
     /**
 114  
      * SQL Statement to find CVE entries based on CPE data.
 115  
      */
 116  
     public static final String SELECT_CVE_FROM_SOFTWARE = "SELECT cve, cpe, previousVersion "
 117  
             + "FROM software INNER JOIN vulnerability ON vulnerability.id = software.cveId "
 118  
             + "INNER JOIN cpeEntry ON cpeEntry.id = software.cpeEntryId "
 119  
             + "WHERE vendor = ? AND product = ?";
 120  
     //unfortunately, the version info is too complicated to do in a select. Need to filter this afterwards
 121  
     //        + " AND (version = '-' OR previousVersion IS NOT NULL OR version=?)";
 122  
     //
 123  
     /**
 124  
      * SQL Statement to find the CPE entry based on the vendor and product.
 125  
      */
 126  
     public static final String SELECT_CPE_ENTRIES = "SELECT cpe FROM cpeEntry WHERE vendor = ? AND product = ?";
 127  
     /**
 128  
      * SQL Statement to select references by CVEID.
 129  
      */
 130  
     public static final String SELECT_REFERENCE = "SELECT source, name, url FROM reference WHERE cveid = ?";
 131  
     /**
 132  
      * SQL Statement to select software by CVEID.
 133  
      */
 134  
     public static final String SELECT_SOFTWARE = "SELECT cpe, previousVersion "
 135  
             + "FROM software INNER JOIN cpeEntry ON software.cpeEntryId = cpeEntry.id WHERE cveid = ?";
 136  
 //    public static final String SELECT_SOFTWARE = "SELECT part, vendor, product, version, revision, previousVersion "
 137  
 //            + "FROM software INNER JOIN cpeProduct ON cpeProduct.id = software.cpeProductId LEFT JOIN cpeVersion ON "
 138  
 //            + "software.cpeVersionId = cpeVersion.id LEFT JOIN Version ON cpeVersion.versionId = version.id WHERE cveid = ?";
 139  
     /**
 140  
      * SQL Statement to select a vulnerability by CVEID.
 141  
      */
 142  
     public static final String SELECT_VULNERABILITY = "SELECT id, description, cwe, cvssScore, cvssAccessVector, cvssAccessComplexity, "
 143  
             + "cvssAuthentication, cvssConfidentialityImpact, cvssIntegrityImpact, cvssAvailabilityImpact FROM vulnerability WHERE cve = ?";
 144  
     /**
 145  
      * SQL Statement to select a vulnerability's primary key.
 146  
      */
 147  
     public static final String SELECT_VULNERABILITY_ID = "SELECT id FROM vulnerability WHERE cve = ?";
 148  
     //</editor-fold>
 149  
 
 150  
     /**
 151  
      * Opens the database connection. If the database does not exist, it will
 152  
      * create a new one.
 153  
      *
 154  
      * @throws IOException thrown if there is an IO Exception
 155  
      * @throws SQLException thrown if there is a SQL Exception
 156  
      * @throws DatabaseException thrown if there is an error initializing a new
 157  
      * database
 158  
      * @throws ClassNotFoundException thrown if the h2 database driver cannot be
 159  
      * loaded
 160  
      */
 161  
     @edu.umd.cs.findbugs.annotations.SuppressWarnings(
 162  
             value = "DMI_EMPTY_DB_PASSWORD",
 163  
             justification = "Yes, I know... Blank password.")
 164  
     public void open() throws IOException, SQLException, DatabaseException, ClassNotFoundException {
 165  19
         final String fileName = CveDB.getDataDirectory().getCanonicalPath();
 166  19
         final File f = new File(fileName, "cve." + DB_SCHEMA_VERSION);
 167  19
         final File check = new File(f.getAbsolutePath() + ".h2.db");
 168  19
         final boolean createTables = !check.exists();
 169  19
         final String connStr = String.format("jdbc:h2:file:%s;AUTO_SERVER=TRUE", f.getAbsolutePath());
 170  19
         Class.forName("org.h2.Driver");
 171  19
         conn = DriverManager.getConnection(connStr, "sa", "");
 172  19
         if (createTables) {
 173  0
             createTables();
 174  
         }
 175  19
     }
 176  
 
 177  
     /**
 178  
      * Commits all completed transactions.
 179  
      *
 180  
      * @throws SQLException thrown if a SQL Exception occurs
 181  
      */
 182  
     public void commit() throws SQLException {
 183  1
         if (conn != null) {
 184  1
             conn.commit();
 185  
         }
 186  1
     }
 187  
 
 188  
     /**
 189  
      * Cleans up the object and ensures that "close" has been called.
 190  
      *
 191  
      * @throws Throwable thrown if there is a problem
 192  
      */
 193  
     @Override
 194  
     protected void finalize() throws Throwable {
 195  12
         close();
 196  12
         super.finalize(); //not necessary if extending Object.
 197  12
     }
 198  
 
 199  
     /**
 200  
      * Closes the DB4O database. Close should be called on this object when it
 201  
      * is done being used.
 202  
      */
 203  
     public void close() {
 204  31
         if (conn != null) {
 205  
             try {
 206  19
                 conn.close();
 207  0
             } catch (SQLException ex) {
 208  0
                 final String msg = "There was an error attempting to close the CveDB, see the log for more details.";
 209  0
                 Logger.getLogger(CveDB.class.getName()).log(Level.SEVERE, msg, ex);
 210  0
                 Logger.getLogger(CveDB.class.getName()).log(Level.FINE, null, ex);
 211  19
             }
 212  19
             conn = null;
 213  
         }
 214  31
     }
 215  
 
 216  
     /**
 217  
      * Searches the CPE entries in the database and retrieves all entries for a
 218  
      * given vendor and product combination. The returned list will include all
 219  
      * versions of the product that are registered in the NVD CVE data.
 220  
      *
 221  
      * @param vendor the identified vendor name of the dependency being analyzed
 222  
      * @param product the identified name of the product of the dependency being
 223  
      * analyzed
 224  
      * @return a set of vulnerable software
 225  
      */
 226  
     public Set<VulnerableSoftware> getCPEs(String vendor, String product) {
 227  72
         final Set<VulnerableSoftware> cpe = new HashSet<VulnerableSoftware>();
 228  72
         ResultSet rs = null;
 229  72
         PreparedStatement ps = null;
 230  
         try {
 231  72
             ps = conn.prepareStatement(SELECT_CPE_ENTRIES);
 232  72
             ps.setString(1, vendor);
 233  72
             ps.setString(2, product);
 234  72
             rs = ps.executeQuery();
 235  
 
 236  4454
             while (rs.next()) {
 237  4382
                 final VulnerableSoftware vs = new VulnerableSoftware();
 238  4382
                 vs.setCpe(rs.getString(1));
 239  4382
                 cpe.add(vs);
 240  4382
             }
 241  0
         } catch (SQLException ex) {
 242  0
             Logger.getLogger(CveDB.class.getName()).log(Level.SEVERE, null, ex);
 243  
         } finally {
 244  72
             closeResultSet(rs);
 245  72
             closeStatement(ps);
 246  72
         }
 247  72
         return cpe;
 248  
     }
 249  
 
 250  
     /**
 251  
      * Retrieves the vulnerabilities associated with the specified CPE.
 252  
      *
 253  
      * @param cpeStr the CPE name
 254  
      * @return a list of Vulnerabilities
 255  
      * @throws DatabaseException thrown if there is an exception retrieving data
 256  
      */
 257  
     public List<Vulnerability> getVulnerabilities(String cpeStr) throws DatabaseException {
 258  12
         ResultSet rs = null;
 259  12
         final VulnerableSoftware cpe = new VulnerableSoftware();
 260  
         try {
 261  12
             cpe.parseName(cpeStr);
 262  0
         } catch (UnsupportedEncodingException ex) {
 263  0
             Logger.getLogger(CveDB.class.getName()).log(Level.FINEST, null, ex);
 264  12
         }
 265  12
         final DependencyVersion detectedVersion = parseDependencyVersion(cpe);
 266  12
         final List<Vulnerability> vulnerabilities = new ArrayList<Vulnerability>();
 267  
 
 268  
         PreparedStatement ps;
 269  12
         final HashSet<String> cveEntries = new HashSet<String>();
 270  
         try {
 271  12
             ps = conn.prepareStatement(SELECT_CVE_FROM_SOFTWARE);
 272  12
             ps.setString(1, cpe.getVendor());
 273  12
             ps.setString(2, cpe.getProduct());
 274  12
             rs = ps.executeQuery();
 275  4500
             while (rs.next()) {
 276  4488
                 final String cveId = rs.getString(1);
 277  4488
                 final String cpeId = rs.getString(2);
 278  4488
                 final String previous = rs.getString(3);
 279  4488
                 if (!cveEntries.contains(cveId) && isAffected(cpe.getVendor(), cpe.getProduct(), detectedVersion, cpeId, previous)) {
 280  59
                     cveEntries.add(cveId);
 281  
                 }
 282  4488
             }
 283  12
             closeResultSet(rs);
 284  12
             closeStatement(ps);
 285  12
             for (String cve : cveEntries) {
 286  59
                 final Vulnerability v = getVulnerability(cve);
 287  59
                 vulnerabilities.add(v);
 288  59
             }
 289  
 
 290  0
         } catch (SQLException ex) {
 291  0
             throw new DatabaseException("Exception retrieving vulnerability for " + cpeStr, ex);
 292  
         } finally {
 293  12
             closeResultSet(rs);
 294  12
         }
 295  12
         return vulnerabilities;
 296  
     }
 297  
 
 298  
     /**
 299  
      * Gets a vulnerability for the provided CVE.
 300  
      *
 301  
      * @param cve the CVE to lookup
 302  
      * @return a vulnerability object
 303  
      * @throws DatabaseException if an exception occurs
 304  
      */
 305  
     private Vulnerability getVulnerability(String cve) throws DatabaseException {
 306  59
         PreparedStatement psV = null;
 307  59
         PreparedStatement psR = null;
 308  59
         PreparedStatement psS = null;
 309  59
         ResultSet rsV = null;
 310  59
         ResultSet rsR = null;
 311  59
         ResultSet rsS = null;
 312  59
         Vulnerability vuln = null;
 313  
         try {
 314  59
             psV = conn.prepareStatement(SELECT_VULNERABILITY);
 315  59
             psV.setString(1, cve);
 316  59
             rsV = psV.executeQuery();
 317  59
             if (rsV.next()) {
 318  59
                 vuln = new Vulnerability();
 319  59
                 vuln.setName(cve);
 320  59
                 vuln.setDescription(rsV.getString(2));
 321  59
                 String cwe = rsV.getString(3);
 322  59
                 if (cwe != null) {
 323  48
                     final String name = CweDB.getCweName(cwe);
 324  48
                     if (name != null) {
 325  46
                         cwe += " " + name;
 326  
                     }
 327  
                 }
 328  59
                 final int cveId = rsV.getInt(1);
 329  59
                 vuln.setCwe(cwe);
 330  59
                 vuln.setCvssScore(rsV.getFloat(4));
 331  59
                 vuln.setCvssAccessVector(rsV.getString(5));
 332  59
                 vuln.setCvssAccessComplexity(rsV.getString(6));
 333  59
                 vuln.setCvssAuthentication(rsV.getString(7));
 334  59
                 vuln.setCvssConfidentialityImpact(rsV.getString(8));
 335  59
                 vuln.setCvssIntegrityImpact(rsV.getString(9));
 336  59
                 vuln.setCvssAvailabilityImpact(rsV.getString(10));
 337  
 
 338  59
                 psR = conn.prepareStatement(SELECT_REFERENCE);
 339  59
                 psR.setInt(1, cveId);
 340  59
                 rsR = psR.executeQuery();
 341  460
                 while (rsR.next()) {
 342  401
                     vuln.addReference(rsR.getString(1), rsR.getString(2), rsR.getString(3));
 343  
                 }
 344  59
                 psS = conn.prepareStatement(SELECT_SOFTWARE);
 345  59
                 psS.setInt(1, cveId);
 346  59
                 rsS = psS.executeQuery();
 347  2673
                 while (rsS.next()) {
 348  2614
                     final String cpe = rsS.getString(1);
 349  2614
                     final String prevVersion = rsS.getString(2);
 350  2614
                     if (prevVersion == null) {
 351  2572
                         vuln.addVulnerableSoftware(cpe);
 352  
                     } else {
 353  42
                         vuln.addVulnerableSoftware(cpe, prevVersion);
 354  
                     }
 355  2614
                 }
 356  
             }
 357  0
         } catch (SQLException ex) {
 358  0
             throw new DatabaseException("Error retrieving " + cve, ex);
 359  
         } finally {
 360  59
             closeResultSet(rsV);
 361  59
             closeResultSet(rsR);
 362  59
             closeResultSet(rsS);
 363  59
             closeStatement(psV);
 364  59
             closeStatement(psR);
 365  59
             closeStatement(psS);
 366  59
         }
 367  59
         return vuln;
 368  
     }
 369  
 
 370  
     /**
 371  
      * Updates the vulnerability within the database. If the vulnerability does
 372  
      * not exist it will be added.
 373  
      *
 374  
      * @param vuln the vulnerability to add to the database
 375  
      * @throws DatabaseException is thrown if the database
 376  
      */
 377  
     public void updateVulnerability(Vulnerability vuln) throws DatabaseException {
 378  0
         PreparedStatement selectVulnerabilityId = null;
 379  0
         PreparedStatement deleteReferences = null;
 380  0
         PreparedStatement deleteSoftware = null;
 381  0
         PreparedStatement updateVulnerability = null;
 382  0
         PreparedStatement insertVulnerability = null;
 383  0
         PreparedStatement insertReference = null;
 384  0
         PreparedStatement selectCpeId = null;
 385  0
         PreparedStatement insertCpe = null;
 386  0
         PreparedStatement insertSoftware = null;
 387  
 
 388  
         try {
 389  0
             selectVulnerabilityId = conn.prepareStatement(SELECT_VULNERABILITY_ID);
 390  0
             deleteReferences = conn.prepareStatement(DELETE_REFERENCE);
 391  0
             deleteSoftware = conn.prepareStatement(DELETE_SOFTWARE);
 392  0
             updateVulnerability = conn.prepareStatement(UPDATE_VULNERABILITY);
 393  0
             insertVulnerability = conn.prepareStatement(INSERT_VULNERABILITY, Statement.RETURN_GENERATED_KEYS);
 394  0
             insertReference = conn.prepareStatement(INSERT_REFERENCE);
 395  0
             selectCpeId = conn.prepareStatement(SELECT_CPE_ID);
 396  0
             insertCpe = conn.prepareStatement(INSERT_CPE, Statement.RETURN_GENERATED_KEYS);
 397  0
             insertSoftware = conn.prepareStatement(INSERT_SOFTWARE);
 398  0
             int vulnerabilityId = 0;
 399  0
             selectVulnerabilityId.setString(1, vuln.getName());
 400  0
             ResultSet rs = selectVulnerabilityId.executeQuery();
 401  0
             if (rs.next()) {
 402  0
                 vulnerabilityId = rs.getInt(1);
 403  
                 // first delete any existing vulnerability info. We don't know what was updated. yes, slower but atm easier.
 404  0
                 deleteReferences.setInt(1, vulnerabilityId);
 405  0
                 deleteReferences.execute();
 406  0
                 deleteSoftware.setInt(1, vulnerabilityId);
 407  0
                 deleteSoftware.execute();
 408  
             }
 409  0
             closeResultSet(rs);
 410  0
             rs = null;
 411  0
             if (vulnerabilityId != 0) {
 412  0
                 updateVulnerability.setString(1, vuln.getDescription());
 413  0
                 updateVulnerability.setString(2, vuln.getCwe());
 414  0
                 updateVulnerability.setFloat(3, vuln.getCvssScore());
 415  0
                 updateVulnerability.setString(4, vuln.getCvssAccessVector());
 416  0
                 updateVulnerability.setString(5, vuln.getCvssAccessComplexity());
 417  0
                 updateVulnerability.setString(6, vuln.getCvssAuthentication());
 418  0
                 updateVulnerability.setString(7, vuln.getCvssConfidentialityImpact());
 419  0
                 updateVulnerability.setString(8, vuln.getCvssIntegrityImpact());
 420  0
                 updateVulnerability.setString(9, vuln.getCvssAvailabilityImpact());
 421  0
                 updateVulnerability.setInt(10, vulnerabilityId);
 422  0
                 updateVulnerability.executeUpdate();
 423  
             } else {
 424  0
                 insertVulnerability.setString(1, vuln.getName());
 425  0
                 insertVulnerability.setString(2, vuln.getDescription());
 426  0
                 insertVulnerability.setString(3, vuln.getCwe());
 427  0
                 insertVulnerability.setFloat(4, vuln.getCvssScore());
 428  0
                 insertVulnerability.setString(5, vuln.getCvssAccessVector());
 429  0
                 insertVulnerability.setString(6, vuln.getCvssAccessComplexity());
 430  0
                 insertVulnerability.setString(7, vuln.getCvssAuthentication());
 431  0
                 insertVulnerability.setString(8, vuln.getCvssConfidentialityImpact());
 432  0
                 insertVulnerability.setString(9, vuln.getCvssIntegrityImpact());
 433  0
                 insertVulnerability.setString(10, vuln.getCvssAvailabilityImpact());
 434  0
                 insertVulnerability.execute();
 435  
                 try {
 436  0
                     rs = insertVulnerability.getGeneratedKeys();
 437  0
                     rs.next();
 438  0
                     vulnerabilityId = rs.getInt(1);
 439  0
                 } catch (SQLException ex) {
 440  0
                     final String msg = String.format("Unable to retrieve id for new vulnerability for '%s'", vuln.getName());
 441  0
                     throw new DatabaseException(msg, ex);
 442  
                 } finally {
 443  0
                     closeResultSet(rs);
 444  0
                     rs = null;
 445  0
                 }
 446  
             }
 447  0
             insertReference.setInt(1, vulnerabilityId);
 448  0
             for (Reference r : vuln.getReferences()) {
 449  0
                 insertReference.setString(2, r.getName());
 450  0
                 insertReference.setString(3, r.getUrl());
 451  0
                 insertReference.setString(4, r.getSource());
 452  0
                 insertReference.execute();
 453  
             }
 454  0
             for (VulnerableSoftware s : vuln.getVulnerableSoftware()) {
 455  0
                 int cpeProductId = 0;
 456  0
                 selectCpeId.setString(1, s.getName());
 457  
                 try {
 458  0
                     rs = selectCpeId.executeQuery();
 459  0
                     if (rs.next()) {
 460  0
                         cpeProductId = rs.getInt(1);
 461  
                     }
 462  0
                 } catch (SQLException ex) {
 463  0
                     throw new DatabaseException("Unable to get primary key for new cpe: " + s.getName(), ex);
 464  
                 } finally {
 465  0
                     closeResultSet(rs);
 466  0
                     rs = null;
 467  0
                 }
 468  
 
 469  0
                 if (cpeProductId == 0) {
 470  0
                     insertCpe.setString(1, s.getName());
 471  0
                     insertCpe.setString(2, s.getVendor());
 472  0
                     insertCpe.setString(3, s.getProduct());
 473  0
                     insertCpe.executeUpdate();
 474  0
                     cpeProductId = getGeneratedKey(insertCpe);
 475  
                 }
 476  0
                 if (cpeProductId == 0) {
 477  0
                     throw new DatabaseException("Unable to retrieve cpeProductId - no data returned");
 478  
                 }
 479  
 
 480  0
                 insertSoftware.setInt(1, vulnerabilityId);
 481  0
                 insertSoftware.setInt(2, cpeProductId);
 482  0
                 if (s.getPreviousVersion() == null) {
 483  0
                     insertSoftware.setNull(3, java.sql.Types.VARCHAR);
 484  
                 } else {
 485  0
                     insertSoftware.setString(3, s.getPreviousVersion());
 486  
                 }
 487  0
                 insertSoftware.execute();
 488  0
             }
 489  
 
 490  0
         } catch (SQLException ex) {
 491  0
             final String msg = String.format("Error updating '%s'", vuln.getName());
 492  0
             Logger.getLogger(CveDB.class.getName()).log(Level.FINE, null, ex);
 493  0
             throw new DatabaseException(msg, ex);
 494  
         } finally {
 495  0
             closeStatement(selectVulnerabilityId);
 496  0
             closeStatement(deleteReferences);
 497  0
             closeStatement(deleteSoftware);
 498  0
             closeStatement(updateVulnerability);
 499  0
             closeStatement(insertVulnerability);
 500  0
             closeStatement(insertReference);
 501  0
             closeStatement(selectCpeId);
 502  0
             closeStatement(insertCpe);
 503  0
             closeStatement(insertSoftware);
 504  0
         }
 505  0
     }
 506  
 
 507  
     /**
 508  
      * Retrieves the directory that the JAR file exists in so that we can ensure
 509  
      * we always use a common data directory.
 510  
      *
 511  
      * @return the data directory for this index.
 512  
      * @throws IOException is thrown if an IOException occurs of course...
 513  
      */
 514  
     public static File getDataDirectory() throws IOException {
 515  20
         final File path = Settings.getFile(Settings.KEYS.CVE_DATA_DIRECTORY);
 516  20
         if (!path.exists()) {
 517  1
             if (!path.mkdirs()) {
 518  0
                 throw new IOException("Unable to create NVD CVE Data directory");
 519  
             }
 520  
         }
 521  20
         return path;
 522  
     }
 523  
 
 524  
     /**
 525  
      * It is possible that orphaned rows may be generated during database
 526  
      * updates. This should be called after all updates have been completed to
 527  
      * ensure orphan entries are removed.
 528  
      */
 529  
     public void cleanupDatabase() {
 530  0
         PreparedStatement ps = null;
 531  
         try {
 532  0
             ps = conn.prepareStatement(CLEANUP_ORPHANS);
 533  0
             if (ps != null) {
 534  0
                 ps.executeUpdate();
 535  
             }
 536  0
         } catch (SQLException ex) {
 537  0
             Logger.getLogger(CveDB.class.getName()).log(Level.SEVERE, null, ex);
 538  
         } finally {
 539  0
             closeStatement(ps);
 540  0
         }
 541  0
     }
 542  
 
 543  
     /**
 544  
      * Creates the database structure (tables and indexes) to store the CVE data
 545  
      *
 546  
      * @throws SQLException thrown if there is a sql exception
 547  
      * @throws DatabaseException thrown if there is a database exception
 548  
      */
 549  
     protected void createTables() throws SQLException, DatabaseException {
 550  
         InputStream is;
 551  
         InputStreamReader reader;
 552  0
         BufferedReader in = null;
 553  
         try {
 554  0
             is = this.getClass().getClassLoader().getResourceAsStream(DB_STRUCTURE_RESOURCE);
 555  0
             reader = new InputStreamReader(is, "UTF-8");
 556  0
             in = new BufferedReader(reader);
 557  0
             final StringBuilder sb = new StringBuilder(2110);
 558  
             String tmp;
 559  0
             while ((tmp = in.readLine()) != null) {
 560  0
                 sb.append(tmp);
 561  
             }
 562  0
             Statement statement = null;
 563  
             try {
 564  0
                 statement = conn.createStatement();
 565  0
                 statement.execute(sb.toString());
 566  
             } finally {
 567  0
                 closeStatement(statement);
 568  0
             }
 569  0
         } catch (IOException ex) {
 570  0
             throw new DatabaseException("Unable to create database schema", ex);
 571  
         } finally {
 572  0
             if (in != null) {
 573  
                 try {
 574  0
                     in.close();
 575  0
                 } catch (IOException ex) {
 576  0
                     Logger.getLogger(CveDB.class
 577  
                             .getName()).log(Level.FINEST, null, ex);
 578  0
                 }
 579  
             }
 580  
         }
 581  0
     }
 582  
 
 583  
     /**
 584  
      * Closes the given statement object ignoring any exceptions that occur.
 585  
      *
 586  
      * @param statement a Statement object
 587  
      */
 588  
     private void closeStatement(Statement statement) {
 589  261
         if (statement != null) {
 590  
             try {
 591  261
                 statement.close();
 592  0
             } catch (SQLException ex) {
 593  0
                 Logger.getLogger(CveDB.class
 594  
                         .getName()).log(Level.FINEST, statement.toString(), ex);
 595  261
             }
 596  
         }
 597  261
     }
 598  
 
 599  
     /**
 600  
      * Closes the result set capturing and ignoring any SQLExceptions that
 601  
      * occur.
 602  
      *
 603  
      * @param rs a ResultSet to close
 604  
      */
 605  
     private void closeResultSet(ResultSet rs) {
 606  273
         if (rs != null) {
 607  
             try {
 608  273
                 rs.close();
 609  0
             } catch (SQLException ex) {
 610  0
                 Logger.getLogger(CveDB.class
 611  
                         .getName()).log(Level.FINEST, rs.toString(), ex);
 612  273
             }
 613  
         }
 614  273
     }
 615  
 
 616  
     /**
 617  
      * Returns the generated integer primary key for a newly inserted row.
 618  
      *
 619  
      * @param statement a prepared statement that just executed an insert
 620  
      * @return a primary key
 621  
      * @throws DatabaseException thrown if there is an exception obtaining the
 622  
      * key
 623  
      */
 624  
     private int getGeneratedKey(PreparedStatement statement) throws DatabaseException {
 625  0
         ResultSet rs = null;
 626  0
         int id = 0;
 627  
         try {
 628  0
             rs = statement.getGeneratedKeys();
 629  0
             rs.next();
 630  0
             id = rs.getInt(1);
 631  0
         } catch (SQLException ex) {
 632  0
             throw new DatabaseException("Unable to get primary key for inserted row");
 633  
         } finally {
 634  0
             closeResultSet(rs);
 635  0
         }
 636  0
         return id;
 637  
     }
 638  
 
 639  
     /**
 640  
      * Determines if the given identifiedVersion is affected by the given cpeId
 641  
      * and previous version flag. A non-null, non-empty string passed to the
 642  
      * previous version argument indicates that all previous versions are
 643  
      * affected.
 644  
      *
 645  
      * @param vendor the vendor of the dependency being analyzed
 646  
      * @param product the product name of the dependency being analyzed
 647  
      * @param identifiedVersion the identified version of the dependency being
 648  
      * analyzed
 649  
      * @param cpeId the cpe identifier of software that has a known
 650  
      * vulnerability
 651  
      * @param previous a flag indicating if previous versions of the product are
 652  
      * vulnerable
 653  
      * @return true if the identified version is affected, otherwise false
 654  
      */
 655  
     private boolean isAffected(String vendor, String product, DependencyVersion identifiedVersion, String cpeId, String previous) {
 656  3491
         boolean affected = false;
 657  3491
         final boolean isStruts = "apache".equals(vendor) && "struts".equals(product);
 658  3491
         final DependencyVersion v = parseDependencyVersion(cpeId);
 659  3491
         final boolean prevAffected = previous == null ? false : !previous.isEmpty();
 660  3491
         if (identifiedVersion == null || "-".equals(identifiedVersion.toString())) {
 661  96
             if (v == null || "-".equals(v.toString())) {
 662  0
                 affected = true;
 663  
             }
 664  3395
         } else if (identifiedVersion.equals(v) || (prevAffected && identifiedVersion.compareTo(v) < 0)) {
 665  81
             if (isStruts) { //struts 2 vulns don't affect struts 1
 666  70
                 if (identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0))) {
 667  48
                     affected = true;
 668  
                 }
 669  
             } else {
 670  11
                 affected = true;
 671  
             }
 672  
         }
 673  
         /*
 674  
          * TODO consider utilizing the matchThreeVersion method to get additional results. However, this
 675  
          *      might also introduce false positives.
 676  
          */
 677  3491
         return affected;
 678  
     }
 679  
 
 680  
     /**
 681  
      * Parses the version (including revision) from a CPE identifier. If no
 682  
      * version is identified then a '-' is returned.
 683  
      *
 684  
      * @param cpeStr a cpe identifier
 685  
      * @return a dependency version
 686  
      */
 687  
     private DependencyVersion parseDependencyVersion(String cpeStr) {
 688  3491
         final VulnerableSoftware cpe = new VulnerableSoftware();
 689  
         try {
 690  3491
             cpe.parseName(cpeStr);
 691  0
         } catch (UnsupportedEncodingException ex) {
 692  
             //never going to happen.
 693  0
             Logger.getLogger(CveDB.class.getName()).log(Level.FINEST, null, ex);
 694  3491
         }
 695  3491
         return parseDependencyVersion(cpe);
 696  
     }
 697  
 
 698  
     /**
 699  
      * Takes a CPE and parses out the version number. If no version is
 700  
      * identified then a '-' is returned.
 701  
      *
 702  
      * @param cpe a cpe object
 703  
      * @return a dependency version
 704  
      */
 705  
     private DependencyVersion parseDependencyVersion(VulnerableSoftware cpe) {
 706  
         DependencyVersion cpeVersion;
 707  3503
         if (cpe.getVersion() != null && cpe.getVersion().length() > 0) {
 708  
             String versionText;
 709  3499
             if (cpe.getRevision() != null && cpe.getRevision().length() > 0) {
 710  441
                 versionText = String.format("%s.%s", cpe.getVersion(), cpe.getRevision());
 711  
             } else {
 712  3058
                 versionText = cpe.getVersion();
 713  
             }
 714  3499
             cpeVersion = DependencyVersionUtil.parseVersion(versionText);
 715  3499
         } else {
 716  4
             cpeVersion = new DependencyVersion("-");
 717  
         }
 718  3503
         return cpeVersion;
 719  
     }
 720  
 }