Coverage Report - org.owasp.dependencycheck.data.nvdcve.CveDB
 
Classes in this File Line Coverage Branch Coverage Complexity
CveDB
45%
191/423
53%
70/132
5.714
 
 1  
 /*
 2  
  * This file is part of dependency-check-core.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  *
 16  
  * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
 17  
  */
 18  
 package org.owasp.dependencycheck.data.nvdcve;
 19  
 
 20  
 import java.io.IOException;
 21  
 import java.io.UnsupportedEncodingException;
 22  
 import java.sql.Connection;
 23  
 import java.sql.PreparedStatement;
 24  
 import java.sql.ResultSet;
 25  
 import java.sql.SQLException;
 26  
 import java.sql.Statement;
 27  
 import java.util.ArrayList;
 28  
 import java.util.HashMap;
 29  
 import java.util.HashSet;
 30  
 import java.util.List;
 31  
 import java.util.Map;
 32  
 import java.util.Map.Entry;
 33  
 import java.util.Properties;
 34  
 import java.util.Set;
 35  
 import java.util.logging.Level;
 36  
 import java.util.logging.Logger;
 37  
 import org.owasp.dependencycheck.data.cwe.CweDB;
 38  
 import org.owasp.dependencycheck.dependency.Reference;
 39  
 import org.owasp.dependencycheck.dependency.Vulnerability;
 40  
 import org.owasp.dependencycheck.dependency.VulnerableSoftware;
 41  
 import org.owasp.dependencycheck.utils.DBUtils;
 42  
 import org.owasp.dependencycheck.utils.DependencyVersion;
 43  
 import org.owasp.dependencycheck.utils.DependencyVersionUtil;
 44  
 import org.owasp.dependencycheck.utils.Pair;
 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  
 public class CveDB {
 53  
 
 54  
     /**
 55  
      * The logger.
 56  
      */
 57  1
     private static final Logger LOGGER = Logger.getLogger(CveDB.class.getName());
 58  
     /**
 59  
      * Database connection
 60  
      */
 61  
     private Connection conn;
 62  
 
 63  
     /**
 64  
      * Creates a new CveDB object and opens the database connection. Note, the connection must be closed by the caller by calling
 65  
      * the close method.
 66  
      *
 67  
      * @throws DatabaseException thrown if there is an exception opening the database.
 68  
      */
 69  
     public CveDB() throws DatabaseException {
 70  3
         super();
 71  
         try {
 72  3
             open();
 73  3
             databaseProperties = new DatabaseProperties(this);
 74  0
         } catch (DatabaseException ex) {
 75  0
             throw ex;
 76  3
         }
 77  3
     }
 78  
 
 79  
     /**
 80  
      * Returns the database connection.
 81  
      *
 82  
      * @return the database connection
 83  
      */
 84  
     protected Connection getConnection() {
 85  32
         return conn;
 86  
     }
 87  
 
 88  
     /**
 89  
      * Opens the database connection. If the database does not exist, it will create a new one.
 90  
      *
 91  
      * @throws DatabaseException thrown if there is an error opening the database connection
 92  
      */
 93  
     public final void open() throws DatabaseException {
 94  6
         if (!isOpen()) {
 95  3
             conn = ConnectionFactory.getConnection();
 96  
         }
 97  6
     }
 98  
 
 99  
     /**
 100  
      * Closes the DB4O database. Close should be called on this object when it is done being used.
 101  
      */
 102  
     public void close() {
 103  5
         if (conn != null) {
 104  
             try {
 105  3
                 conn.close();
 106  0
             } catch (SQLException ex) {
 107  0
                 final String msg = "There was an error attempting to close the CveDB, see the log for more details.";
 108  0
                 LOGGER.log(Level.SEVERE, msg);
 109  0
                 LOGGER.log(Level.FINE, null, ex);
 110  0
             } catch (Throwable ex) {
 111  0
                 final String msg = "There was an exception attempting to close the CveDB, see the log for more details.";
 112  0
                 LOGGER.log(Level.SEVERE, msg);
 113  0
                 LOGGER.log(Level.FINE, null, ex);
 114  3
             }
 115  3
             conn = null;
 116  
         }
 117  5
     }
 118  
 
 119  
     /**
 120  
      * Returns whether the database connection is open or closed.
 121  
      *
 122  
      * @return whether the database connection is open or closed
 123  
      */
 124  
     public boolean isOpen() {
 125  6
         return conn != null;
 126  
     }
 127  
 
 128  
     /**
 129  
      * Commits all completed transactions.
 130  
      *
 131  
      * @throws SQLException thrown if a SQL Exception occurs
 132  
      */
 133  
     public void commit() throws SQLException {
 134  
         //temporary remove this as autocommit is on.
 135  
         //if (conn != null) {
 136  
         //    conn.commit();
 137  
         //}
 138  0
     }
 139  
 
 140  
     /**
 141  
      * Cleans up the object and ensures that "close" has been called.
 142  
      *
 143  
      * @throws Throwable thrown if there is a problem
 144  
      */
 145  
     @Override
 146  
     @SuppressWarnings("FinalizeDeclaration")
 147  
     protected void finalize() throws Throwable {
 148  2
         LOGGER.log(Level.FINE, "Entering finalize");
 149  2
         close();
 150  2
         super.finalize();
 151  2
     }
 152  
     /**
 153  
      * Database properties object containing the 'properties' from the database table.
 154  
      */
 155  
     private DatabaseProperties databaseProperties;
 156  
 
 157  
     /**
 158  
      * Get the value of databaseProperties.
 159  
      *
 160  
      * @return the value of databaseProperties
 161  
      */
 162  
     public DatabaseProperties getDatabaseProperties() {
 163  0
         return databaseProperties;
 164  
     }
 165  
     //<editor-fold defaultstate="collapsed" desc="Constants to create, maintain, and retrieve data from the CVE Database">
 166  
     /**
 167  
      * SQL Statement to delete references by vulnerability ID.
 168  
      */
 169  
     private static final String DELETE_REFERENCE = "DELETE FROM reference WHERE cveid = ?";
 170  
     /**
 171  
      * SQL Statement to delete software by vulnerability ID.
 172  
      */
 173  
     private static final String DELETE_SOFTWARE = "DELETE FROM software WHERE cveid = ?";
 174  
     /**
 175  
      * SQL Statement to delete a vulnerability by CVE.
 176  
      */
 177  
     private static final String DELETE_VULNERABILITY = "DELETE FROM vulnerability WHERE id = ?";
 178  
     /**
 179  
      * SQL Statement to cleanup orphan entries. Yes, the db schema could be a little tighter, but what we have works well to keep
 180  
      * the data file size down a bit.
 181  
      */
 182  
     private static final String CLEANUP_ORPHANS = "DELETE FROM CpeEntry WHERE id not in (SELECT CPEEntryId FROM Software); ";
 183  
     /**
 184  
      * SQL Statement to insert a new reference.
 185  
      */
 186  
     private static final String INSERT_REFERENCE = "INSERT INTO reference (cveid, name, url, source) VALUES (?, ?, ?, ?)";
 187  
     /**
 188  
      * SQL Statement to insert a new software.
 189  
      */
 190  
     private static final String INSERT_SOFTWARE = "INSERT INTO software (cveid, cpeEntryId, previousVersion) VALUES (?, ?, ?)";
 191  
     /**
 192  
      * SQL Statement to insert a new cpe.
 193  
      */
 194  
     private static final String INSERT_CPE = "INSERT INTO cpeEntry (cpe, vendor, product) VALUES (?, ?, ?)";
 195  
     /**
 196  
      * SQL Statement to get a CPEProductID.
 197  
      */
 198  
     private static final String SELECT_CPE_ID = "SELECT id FROM cpeEntry WHERE cpe = ?";
 199  
     /**
 200  
      * SQL Statement to insert a new vulnerability.
 201  
      */
 202  
     private static final String INSERT_VULNERABILITY = "INSERT INTO vulnerability (cve, description, cwe, cvssScore, cvssAccessVector, "
 203  
             + "cvssAccessComplexity, cvssAuthentication, cvssConfidentialityImpact, cvssIntegrityImpact, cvssAvailabilityImpact) "
 204  
             + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
 205  
     /**
 206  
      * SQL Statement to update a vulnerability.
 207  
      */
 208  
     private static final String UPDATE_VULNERABILITY = "UPDATE vulnerability SET description=?, cwe=?, cvssScore=?, cvssAccessVector=?, "
 209  
             + "cvssAccessComplexity=?, cvssAuthentication=?, cvssConfidentialityImpact=?, cvssIntegrityImpact=?, cvssAvailabilityImpact=? "
 210  
             + "WHERE id=?";
 211  
     /**
 212  
      * SQL Statement to find CVE entries based on CPE data.
 213  
      */
 214  
     private static final String SELECT_CVE_FROM_SOFTWARE = "SELECT cve, cpe, previousVersion "
 215  
             + "FROM software INNER JOIN vulnerability ON vulnerability.id = software.cveId "
 216  
             + "INNER JOIN cpeEntry ON cpeEntry.id = software.cpeEntryId "
 217  
             + "WHERE vendor = ? AND product = ? "
 218  
             + "ORDER BY cve, cpe"; //, previousVersion
 219  
     //unfortunately, the version info is too complicated to do in a select. Need to filter this afterwards
 220  
     //        + " AND (version = '-' OR previousVersion IS NOT NULL OR version=?)";
 221  
     //
 222  
     /**
 223  
      * SQL Statement to find the CPE entry based on the vendor and product.
 224  
      */
 225  
     private static final String SELECT_CPE_ENTRIES = "SELECT cpe FROM cpeEntry WHERE vendor = ? AND product = ?";
 226  
     /**
 227  
      * SQL Statement to select references by CVEID.
 228  
      */
 229  
     private static final String SELECT_REFERENCE = "SELECT source, name, url FROM reference WHERE cveid = ?";
 230  
     /**
 231  
      * SQL Statement to select vendor and product for lucene index.
 232  
      */
 233  
     private static final String SELECT_VENDOR_PRODUCT_LIST = "SELECT vendor, product FROM cpeEntry GROUP BY vendor, product";
 234  
     /**
 235  
      * SQL Statement to select software by CVEID.
 236  
      */
 237  
     private static final String SELECT_SOFTWARE = "SELECT cpe, previousVersion "
 238  
             + "FROM software INNER JOIN cpeEntry ON software.cpeEntryId = cpeEntry.id WHERE cveid = ?";
 239  
 //    public static final String SELECT_SOFTWARE = "SELECT part, vendor, product, version, revision, previousVersion "
 240  
 //            + "FROM software INNER JOIN cpeProduct ON cpeProduct.id = software.cpeProductId LEFT JOIN cpeVersion ON "
 241  
 //            + "software.cpeVersionId = cpeVersion.id LEFT JOIN Version ON cpeVersion.versionId = version.id WHERE cveid = ?";
 242  
     /**
 243  
      * SQL Statement to select a vulnerability by CVEID.
 244  
      */
 245  
     private static final String SELECT_VULNERABILITY = "SELECT id, description, cwe, cvssScore, cvssAccessVector, cvssAccessComplexity, "
 246  
             + "cvssAuthentication, cvssConfidentialityImpact, cvssIntegrityImpact, cvssAvailabilityImpact FROM vulnerability WHERE cve = ?";
 247  
     /**
 248  
      * SQL Statement to select a vulnerability's primary key.
 249  
      */
 250  
     private static final String SELECT_VULNERABILITY_ID = "SELECT id FROM vulnerability WHERE cve = ?";
 251  
     /**
 252  
      * SQL Statement to retrieve the properties from the database.
 253  
      */
 254  
     private static final String SELECT_PROPERTIES = "SELECT id, value FROM properties";
 255  
     /**
 256  
      * SQL Statement to retrieve a property from the database.
 257  
      */
 258  
     @SuppressWarnings("unused")
 259  
     private static final String SELECT_PROPERTY = "SELECT id, value FROM properties WHERE id = ?";
 260  
     /**
 261  
      * SQL Statement to insert a new property.
 262  
      */
 263  
     private static final String INSERT_PROPERTY = "INSERT INTO properties (id, value) VALUES (?, ?)";
 264  
     /**
 265  
      * SQL Statement to update a property.
 266  
      */
 267  
     private static final String UPDATE_PROPERTY = "UPDATE properties SET value = ? WHERE id = ?";
 268  
     /**
 269  
      * SQL Statement to delete a property.
 270  
      */
 271  
     @SuppressWarnings("unused")
 272  
     private static final String DELETE_PROPERTY = "DELETE FROM properties WHERE id = ?";
 273  
 
 274  
     //</editor-fold>
 275  
     /**
 276  
      * Searches the CPE entries in the database and retrieves all entries for a given vendor and product combination. The returned
 277  
      * list will include all versions of the product that are registered in the NVD CVE data.
 278  
      *
 279  
      * @param vendor the identified vendor name of the dependency being analyzed
 280  
      * @param product the identified name of the product of the dependency being analyzed
 281  
      * @return a set of vulnerable software
 282  
      */
 283  
     public Set<VulnerableSoftware> getCPEs(String vendor, String product) {
 284  2
         final Set<VulnerableSoftware> cpe = new HashSet<VulnerableSoftware>();
 285  2
         ResultSet rs = null;
 286  2
         PreparedStatement ps = null;
 287  
         try {
 288  2
             ps = getConnection().prepareStatement(SELECT_CPE_ENTRIES);
 289  2
             ps.setString(1, vendor);
 290  2
             ps.setString(2, product);
 291  2
             rs = ps.executeQuery();
 292  
 
 293  80
             while (rs.next()) {
 294  78
                 final VulnerableSoftware vs = new VulnerableSoftware();
 295  78
                 vs.setCpe(rs.getString(1));
 296  78
                 cpe.add(vs);
 297  78
             }
 298  0
         } catch (SQLException ex) {
 299  0
             final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
 300  0
             LOGGER.log(Level.SEVERE, msg);
 301  0
             LOGGER.log(Level.FINE, null, ex);
 302  
         } finally {
 303  2
             DBUtils.closeResultSet(rs);
 304  2
             DBUtils.closeStatement(ps);
 305  2
         }
 306  2
         return cpe;
 307  
     }
 308  
 
 309  
     /**
 310  
      * Returns the entire list of vendor/product combinations.
 311  
      *
 312  
      * @return the entire list of vendor/product combinations
 313  
      * @throws DatabaseException thrown when there is an error retrieving the data from the DB
 314  
      */
 315  
     public Set<Pair<String, String>> getVendorProductList() throws DatabaseException {
 316  1
         final Set<Pair<String, String>> data = new HashSet<Pair<String, String>>();
 317  1
         ResultSet rs = null;
 318  1
         PreparedStatement ps = null;
 319  
         try {
 320  1
             ps = getConnection().prepareStatement(SELECT_VENDOR_PRODUCT_LIST);
 321  1
             rs = ps.executeQuery();
 322  24670
             while (rs.next()) {
 323  24669
                 data.add(new Pair<String, String>(rs.getString(1), rs.getString(2)));
 324  
             }
 325  0
         } catch (SQLException ex) {
 326  0
             final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
 327  0
             throw new DatabaseException(msg, ex);
 328  
         } finally {
 329  1
             DBUtils.closeResultSet(rs);
 330  1
             DBUtils.closeStatement(ps);
 331  1
         }
 332  1
         return data;
 333  
     }
 334  
 
 335  
     /**
 336  
      * Returns a set of properties.
 337  
      *
 338  
      * @return the properties from the database
 339  
      */
 340  
     Properties getProperties() {
 341  3
         final Properties prop = new Properties();
 342  3
         PreparedStatement ps = null;
 343  3
         ResultSet rs = null;
 344  
         try {
 345  3
             ps = getConnection().prepareStatement(SELECT_PROPERTIES);
 346  3
             rs = ps.executeQuery();
 347  60
             while (rs.next()) {
 348  57
                 prop.setProperty(rs.getString(1), rs.getString(2));
 349  
             }
 350  0
         } catch (SQLException ex) {
 351  0
             final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
 352  0
             LOGGER.log(Level.SEVERE, msg);
 353  0
             LOGGER.log(Level.FINE, null, ex);
 354  
         } finally {
 355  3
             DBUtils.closeStatement(ps);
 356  3
             DBUtils.closeResultSet(rs);
 357  3
         }
 358  3
         return prop;
 359  
     }
 360  
 
 361  
     /**
 362  
      * Saves a set of properties to the database.
 363  
      *
 364  
      * @param props a collection of properties
 365  
      */
 366  
     void saveProperties(Properties props) {
 367  0
         PreparedStatement updateProperty = null;
 368  0
         PreparedStatement insertProperty = null;
 369  
         try {
 370  
             try {
 371  0
                 updateProperty = getConnection().prepareStatement(UPDATE_PROPERTY);
 372  0
                 insertProperty = getConnection().prepareStatement(INSERT_PROPERTY);
 373  0
             } catch (SQLException ex) {
 374  0
                 LOGGER.log(Level.WARNING, "Unable to save properties to the database");
 375  0
                 LOGGER.log(Level.FINE, "Unable to save properties to the database", ex);
 376  
                 return;
 377  0
             }
 378  0
             for (Entry<Object, Object> entry : props.entrySet()) {
 379  0
                 final String key = entry.getKey().toString();
 380  0
                 final String value = entry.getValue().toString();
 381  
                 try {
 382  0
                     updateProperty.setString(1, value);
 383  0
                     updateProperty.setString(2, key);
 384  0
                     if (updateProperty.executeUpdate() == 0) {
 385  0
                         insertProperty.setString(1, key);
 386  0
                         insertProperty.setString(2, value);
 387  
                     }
 388  0
                 } catch (SQLException ex) {
 389  0
                     final String msg = String.format("Unable to save property '%s' with a value of '%s' to the database", key, value);
 390  0
                     LOGGER.log(Level.WARNING, msg);
 391  0
                     LOGGER.log(Level.FINE, null, ex);
 392  0
                 }
 393  0
             }
 394  
         } finally {
 395  0
             DBUtils.closeStatement(updateProperty);
 396  0
             DBUtils.closeStatement(insertProperty);
 397  0
         }
 398  0
     }
 399  
 
 400  
     /**
 401  
      * Saves a property to the database.
 402  
      *
 403  
      * @param key the property key
 404  
      * @param value the property value
 405  
      */
 406  
     void saveProperty(String key, String value) {
 407  0
         PreparedStatement updateProperty = null;
 408  0
         PreparedStatement insertProperty = null;
 409  
         try {
 410  
             try {
 411  0
                 updateProperty = getConnection().prepareStatement(UPDATE_PROPERTY);
 412  0
             } catch (SQLException ex) {
 413  0
                 LOGGER.log(Level.WARNING, "Unable to save properties to the database");
 414  0
                 LOGGER.log(Level.FINE, "Unable to save properties to the database", ex);
 415  
                 return;
 416  0
             }
 417  
             try {
 418  0
                 updateProperty.setString(1, value);
 419  0
                 updateProperty.setString(2, key);
 420  0
                 if (updateProperty.executeUpdate() == 0) {
 421  
                     try {
 422  0
                         insertProperty = getConnection().prepareStatement(INSERT_PROPERTY);
 423  0
                     } catch (SQLException ex) {
 424  0
                         LOGGER.log(Level.WARNING, "Unable to save properties to the database");
 425  0
                         LOGGER.log(Level.FINE, "Unable to save properties to the database", ex);
 426  
                         return;
 427  0
                     }
 428  0
                     insertProperty.setString(1, key);
 429  0
                     insertProperty.setString(2, value);
 430  0
                     insertProperty.execute();
 431  
                 }
 432  0
             } catch (SQLException ex) {
 433  0
                 final String msg = String.format("Unable to save property '%s' with a value of '%s' to the database", key, value);
 434  0
                 LOGGER.log(Level.WARNING, msg);
 435  0
                 LOGGER.log(Level.FINE, null, ex);
 436  0
             }
 437  
         } finally {
 438  0
             DBUtils.closeStatement(updateProperty);
 439  0
             DBUtils.closeStatement(insertProperty);
 440  0
         }
 441  0
     }
 442  
 
 443  
     /**
 444  
      * Retrieves the vulnerabilities associated with the specified CPE.
 445  
      *
 446  
      * @param cpeStr the CPE name
 447  
      * @return a list of Vulnerabilities
 448  
      * @throws DatabaseException thrown if there is an exception retrieving data
 449  
      */
 450  
     public List<Vulnerability> getVulnerabilities(String cpeStr) throws DatabaseException {
 451  2
         ResultSet rs = null;
 452  2
         final VulnerableSoftware cpe = new VulnerableSoftware();
 453  
         try {
 454  2
             cpe.parseName(cpeStr);
 455  0
         } catch (UnsupportedEncodingException ex) {
 456  0
             LOGGER.log(Level.FINEST, null, ex);
 457  2
         }
 458  2
         final DependencyVersion detectedVersion = parseDependencyVersion(cpe);
 459  2
         final List<Vulnerability> vulnerabilities = new ArrayList<Vulnerability>();
 460  
 
 461  
         PreparedStatement ps;
 462  
         try {
 463  2
             ps = getConnection().prepareStatement(SELECT_CVE_FROM_SOFTWARE);
 464  2
             ps.setString(1, cpe.getVendor());
 465  2
             ps.setString(2, cpe.getProduct());
 466  2
             rs = ps.executeQuery();
 467  2
             String currentCVE = "";
 468  
 
 469  2
             final Map<String, Boolean> vulnSoftware = new HashMap<String, Boolean>();
 470  225
             while (rs.next()) {
 471  223
                 final String cveId = rs.getString(1);
 472  223
                 if (!currentCVE.equals(cveId)) { //check for match and add
 473  8
                     final Entry<String, Boolean> matchedCPE = getMatchingSoftware(vulnSoftware, cpe.getVendor(), cpe.getProduct(), detectedVersion);
 474  8
                     if (matchedCPE != null) {
 475  6
                         final Vulnerability v = getVulnerability(currentCVE);
 476  6
                         v.setMatchedCPE(matchedCPE.getKey(), matchedCPE.getValue() ? "Y" : null);
 477  6
                         vulnerabilities.add(v);
 478  
                     }
 479  8
                     vulnSoftware.clear();
 480  8
                     currentCVE = cveId;
 481  
                 }
 482  
 
 483  223
                 final String cpeId = rs.getString(2);
 484  223
                 final String previous = rs.getString(3);
 485  223
                 final Boolean p = previous != null && !previous.isEmpty();
 486  223
                 vulnSoftware.put(cpeId, p);
 487  223
             }
 488  
             //remember to process the last set of CVE/CPE entries
 489  2
             final Entry<String, Boolean> matchedCPE = getMatchingSoftware(vulnSoftware, cpe.getVendor(), cpe.getProduct(), detectedVersion);
 490  2
             if (matchedCPE != null) {
 491  2
                 final Vulnerability v = getVulnerability(currentCVE);
 492  2
                 v.setMatchedCPE(matchedCPE.getKey(), matchedCPE.getValue() ? "Y" : null);
 493  2
                 vulnerabilities.add(v);
 494  
             }
 495  2
             DBUtils.closeResultSet(rs);
 496  2
             DBUtils.closeStatement(ps);
 497  0
         } catch (SQLException ex) {
 498  0
             throw new DatabaseException("Exception retrieving vulnerability for " + cpeStr, ex);
 499  
         } finally {
 500  2
             DBUtils.closeResultSet(rs);
 501  2
         }
 502  2
         return vulnerabilities;
 503  
     }
 504  
 
 505  
     /**
 506  
      * Gets a vulnerability for the provided CVE.
 507  
      *
 508  
      * @param cve the CVE to lookup
 509  
      * @return a vulnerability object
 510  
      * @throws DatabaseException if an exception occurs
 511  
      */
 512  
     private Vulnerability getVulnerability(String cve) throws DatabaseException {
 513  8
         PreparedStatement psV = null;
 514  8
         PreparedStatement psR = null;
 515  8
         PreparedStatement psS = null;
 516  8
         ResultSet rsV = null;
 517  8
         ResultSet rsR = null;
 518  8
         ResultSet rsS = null;
 519  8
         Vulnerability vuln = null;
 520  
         try {
 521  8
             psV = getConnection().prepareStatement(SELECT_VULNERABILITY);
 522  8
             psV.setString(1, cve);
 523  8
             rsV = psV.executeQuery();
 524  8
             if (rsV.next()) {
 525  8
                 vuln = new Vulnerability();
 526  8
                 vuln.setName(cve);
 527  8
                 vuln.setDescription(rsV.getString(2));
 528  8
                 String cwe = rsV.getString(3);
 529  8
                 if (cwe != null) {
 530  8
                     final String name = CweDB.getCweName(cwe);
 531  8
                     if (name != null) {
 532  7
                         cwe += " " + name;
 533  
                     }
 534  
                 }
 535  8
                 final int cveId = rsV.getInt(1);
 536  8
                 vuln.setCwe(cwe);
 537  8
                 vuln.setCvssScore(rsV.getFloat(4));
 538  8
                 vuln.setCvssAccessVector(rsV.getString(5));
 539  8
                 vuln.setCvssAccessComplexity(rsV.getString(6));
 540  8
                 vuln.setCvssAuthentication(rsV.getString(7));
 541  8
                 vuln.setCvssConfidentialityImpact(rsV.getString(8));
 542  8
                 vuln.setCvssIntegrityImpact(rsV.getString(9));
 543  8
                 vuln.setCvssAvailabilityImpact(rsV.getString(10));
 544  
 
 545  8
                 psR = getConnection().prepareStatement(SELECT_REFERENCE);
 546  8
                 psR.setInt(1, cveId);
 547  8
                 rsR = psR.executeQuery();
 548  74
                 while (rsR.next()) {
 549  66
                     vuln.addReference(rsR.getString(1), rsR.getString(2), rsR.getString(3));
 550  
                 }
 551  8
                 psS = getConnection().prepareStatement(SELECT_SOFTWARE);
 552  8
                 psS.setInt(1, cveId);
 553  8
                 rsS = psS.executeQuery();
 554  244
                 while (rsS.next()) {
 555  236
                     final String cpe = rsS.getString(1);
 556  236
                     final String prevVersion = rsS.getString(2);
 557  236
                     if (prevVersion == null) {
 558  228
                         vuln.addVulnerableSoftware(cpe);
 559  
                     } else {
 560  8
                         vuln.addVulnerableSoftware(cpe, prevVersion);
 561  
                     }
 562  236
                 }
 563  
             }
 564  0
         } catch (SQLException ex) {
 565  0
             throw new DatabaseException("Error retrieving " + cve, ex);
 566  
         } finally {
 567  8
             DBUtils.closeResultSet(rsV);
 568  8
             DBUtils.closeResultSet(rsR);
 569  8
             DBUtils.closeResultSet(rsS);
 570  8
             DBUtils.closeStatement(psV);
 571  8
             DBUtils.closeStatement(psR);
 572  8
             DBUtils.closeStatement(psS);
 573  8
         }
 574  8
         return vuln;
 575  
     }
 576  
 
 577  
     /**
 578  
      * Updates the vulnerability within the database. If the vulnerability does not exist it will be added.
 579  
      *
 580  
      * @param vuln the vulnerability to add to the database
 581  
      * @throws DatabaseException is thrown if the database
 582  
      */
 583  
     public void updateVulnerability(Vulnerability vuln) throws DatabaseException {
 584  0
         PreparedStatement selectVulnerabilityId = null;
 585  0
         PreparedStatement deleteVulnerability = null;
 586  0
         PreparedStatement deleteReferences = null;
 587  0
         PreparedStatement deleteSoftware = null;
 588  0
         PreparedStatement updateVulnerability = null;
 589  0
         PreparedStatement insertVulnerability = null;
 590  0
         PreparedStatement insertReference = null;
 591  0
         PreparedStatement selectCpeId = null;
 592  0
         PreparedStatement insertCpe = null;
 593  0
         PreparedStatement insertSoftware = null;
 594  
 
 595  
         try {
 596  0
             selectVulnerabilityId = getConnection().prepareStatement(SELECT_VULNERABILITY_ID);
 597  0
             deleteVulnerability = getConnection().prepareStatement(DELETE_VULNERABILITY);
 598  0
             deleteReferences = getConnection().prepareStatement(DELETE_REFERENCE);
 599  0
             deleteSoftware = getConnection().prepareStatement(DELETE_SOFTWARE);
 600  0
             updateVulnerability = getConnection().prepareStatement(UPDATE_VULNERABILITY);
 601  0
             insertVulnerability = getConnection().prepareStatement(INSERT_VULNERABILITY, Statement.RETURN_GENERATED_KEYS);
 602  0
             insertReference = getConnection().prepareStatement(INSERT_REFERENCE);
 603  0
             selectCpeId = getConnection().prepareStatement(SELECT_CPE_ID);
 604  0
             insertCpe = getConnection().prepareStatement(INSERT_CPE, Statement.RETURN_GENERATED_KEYS);
 605  0
             insertSoftware = getConnection().prepareStatement(INSERT_SOFTWARE);
 606  0
             int vulnerabilityId = 0;
 607  0
             selectVulnerabilityId.setString(1, vuln.getName());
 608  0
             ResultSet rs = selectVulnerabilityId.executeQuery();
 609  0
             if (rs.next()) {
 610  0
                 vulnerabilityId = rs.getInt(1);
 611  
                 // first delete any existing vulnerability info. We don't know what was updated. yes, slower but atm easier.
 612  0
                 deleteReferences.setInt(1, vulnerabilityId);
 613  0
                 deleteReferences.execute();
 614  0
                 deleteSoftware.setInt(1, vulnerabilityId);
 615  0
                 deleteSoftware.execute();
 616  
             }
 617  0
             DBUtils.closeResultSet(rs);
 618  0
             rs = null;
 619  0
             if (vulnerabilityId != 0) {
 620  0
                 if (vuln.getDescription().contains("** REJECT **")) {
 621  0
                     deleteVulnerability.setInt(1, vulnerabilityId);
 622  0
                     deleteVulnerability.executeUpdate();
 623  
                 } else {
 624  0
                     updateVulnerability.setString(1, vuln.getDescription());
 625  0
                     updateVulnerability.setString(2, vuln.getCwe());
 626  0
                     updateVulnerability.setFloat(3, vuln.getCvssScore());
 627  0
                     updateVulnerability.setString(4, vuln.getCvssAccessVector());
 628  0
                     updateVulnerability.setString(5, vuln.getCvssAccessComplexity());
 629  0
                     updateVulnerability.setString(6, vuln.getCvssAuthentication());
 630  0
                     updateVulnerability.setString(7, vuln.getCvssConfidentialityImpact());
 631  0
                     updateVulnerability.setString(8, vuln.getCvssIntegrityImpact());
 632  0
                     updateVulnerability.setString(9, vuln.getCvssAvailabilityImpact());
 633  0
                     updateVulnerability.setInt(10, vulnerabilityId);
 634  0
                     updateVulnerability.executeUpdate();
 635  
                 }
 636  
             } else {
 637  0
                 insertVulnerability.setString(1, vuln.getName());
 638  0
                 insertVulnerability.setString(2, vuln.getDescription());
 639  0
                 insertVulnerability.setString(3, vuln.getCwe());
 640  0
                 insertVulnerability.setFloat(4, vuln.getCvssScore());
 641  0
                 insertVulnerability.setString(5, vuln.getCvssAccessVector());
 642  0
                 insertVulnerability.setString(6, vuln.getCvssAccessComplexity());
 643  0
                 insertVulnerability.setString(7, vuln.getCvssAuthentication());
 644  0
                 insertVulnerability.setString(8, vuln.getCvssConfidentialityImpact());
 645  0
                 insertVulnerability.setString(9, vuln.getCvssIntegrityImpact());
 646  0
                 insertVulnerability.setString(10, vuln.getCvssAvailabilityImpact());
 647  0
                 insertVulnerability.execute();
 648  
                 try {
 649  0
                     rs = insertVulnerability.getGeneratedKeys();
 650  0
                     rs.next();
 651  0
                     vulnerabilityId = rs.getInt(1);
 652  0
                 } catch (SQLException ex) {
 653  0
                     final String msg = String.format("Unable to retrieve id for new vulnerability for '%s'", vuln.getName());
 654  0
                     throw new DatabaseException(msg, ex);
 655  
                 } finally {
 656  0
                     DBUtils.closeResultSet(rs);
 657  0
                     rs = null;
 658  0
                 }
 659  
             }
 660  0
             insertReference.setInt(1, vulnerabilityId);
 661  0
             for (Reference r : vuln.getReferences()) {
 662  0
                 insertReference.setString(2, r.getName());
 663  0
                 insertReference.setString(3, r.getUrl());
 664  0
                 insertReference.setString(4, r.getSource());
 665  0
                 insertReference.execute();
 666  0
             }
 667  0
             for (VulnerableSoftware s : vuln.getVulnerableSoftware()) {
 668  0
                 int cpeProductId = 0;
 669  0
                 selectCpeId.setString(1, s.getName());
 670  
                 try {
 671  0
                     rs = selectCpeId.executeQuery();
 672  0
                     if (rs.next()) {
 673  0
                         cpeProductId = rs.getInt(1);
 674  
                     }
 675  0
                 } catch (SQLException ex) {
 676  0
                     throw new DatabaseException("Unable to get primary key for new cpe: " + s.getName(), ex);
 677  
                 } finally {
 678  0
                     DBUtils.closeResultSet(rs);
 679  0
                     rs = null;
 680  0
                 }
 681  
 
 682  0
                 if (cpeProductId == 0) {
 683  0
                     insertCpe.setString(1, s.getName());
 684  0
                     insertCpe.setString(2, s.getVendor());
 685  0
                     insertCpe.setString(3, s.getProduct());
 686  0
                     insertCpe.executeUpdate();
 687  0
                     cpeProductId = DBUtils.getGeneratedKey(insertCpe);
 688  
                 }
 689  0
                 if (cpeProductId == 0) {
 690  0
                     throw new DatabaseException("Unable to retrieve cpeProductId - no data returned");
 691  
                 }
 692  
 
 693  0
                 insertSoftware.setInt(1, vulnerabilityId);
 694  0
                 insertSoftware.setInt(2, cpeProductId);
 695  0
                 if (s.getPreviousVersion() == null) {
 696  0
                     insertSoftware.setNull(3, java.sql.Types.VARCHAR);
 697  
                 } else {
 698  0
                     insertSoftware.setString(3, s.getPreviousVersion());
 699  
                 }
 700  0
                 insertSoftware.execute();
 701  0
             }
 702  
 
 703  0
         } catch (SQLException ex) {
 704  0
             final String msg = String.format("Error updating '%s'", vuln.getName());
 705  0
             LOGGER.log(Level.FINE, null, ex);
 706  0
             throw new DatabaseException(msg, ex);
 707  
         } finally {
 708  0
             DBUtils.closeStatement(selectVulnerabilityId);
 709  0
             DBUtils.closeStatement(deleteReferences);
 710  0
             DBUtils.closeStatement(deleteSoftware);
 711  0
             DBUtils.closeStatement(updateVulnerability);
 712  0
             DBUtils.closeStatement(deleteVulnerability);
 713  0
             DBUtils.closeStatement(insertVulnerability);
 714  0
             DBUtils.closeStatement(insertReference);
 715  0
             DBUtils.closeStatement(selectCpeId);
 716  0
             DBUtils.closeStatement(insertCpe);
 717  0
             DBUtils.closeStatement(insertSoftware);
 718  0
         }
 719  0
     }
 720  
 
 721  
     /**
 722  
      * Checks to see if data exists so that analysis can be performed.
 723  
      *
 724  
      * @return <code>true</code> if data exists; otherwise <code>false</code>
 725  
      */
 726  
     public boolean dataExists() {
 727  1
         Statement cs = null;
 728  1
         ResultSet rs = null;
 729  
         try {
 730  1
             cs = conn.createStatement();
 731  1
             rs = cs.executeQuery("SELECT COUNT(*) records FROM cpeEntry");
 732  1
             if (rs.next()) {
 733  1
                 if (rs.getInt(1) > 0) {
 734  1
                     return true;
 735  
                 }
 736  
             }
 737  0
         } catch (SQLException ex) {
 738  
             String dd;
 739  
             try {
 740  0
                 dd = Settings.getDataDirectory().getAbsolutePath();
 741  0
             } catch (IOException ex1) {
 742  0
                 dd = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
 743  0
             }
 744  0
             final String msg = String.format("Unable to access the local database.%n%nEnsure that '%s' is a writable directory. "
 745  
                     + "If the problem persist try deleting the files in '%s' and running %s again. If the problem continues, please "
 746  
                     + "create a log file (see documentation at http://jeremylong.github.io/DependencyCheck/) and open a ticket at "
 747  
                     + "https://github.com/jeremylong/DependencyCheck/issues and include the log file.%n%n",
 748  
                     dd, dd, Settings.getString(Settings.KEYS.APPLICATION_VAME));
 749  0
             LOGGER.log(Level.SEVERE, msg);
 750  0
             LOGGER.log(Level.FINE, "", ex);
 751  
         } finally {
 752  1
             DBUtils.closeResultSet(rs);
 753  1
             DBUtils.closeStatement(cs);
 754  0
         }
 755  0
         return false;
 756  
     }
 757  
 
 758  
     /**
 759  
      * It is possible that orphaned rows may be generated during database updates. This should be called after all updates have
 760  
      * been completed to ensure orphan entries are removed.
 761  
      */
 762  
     public void cleanupDatabase() {
 763  0
         PreparedStatement ps = null;
 764  
         try {
 765  0
             ps = getConnection().prepareStatement(CLEANUP_ORPHANS);
 766  0
             if (ps != null) {
 767  0
                 ps.executeUpdate();
 768  
             }
 769  0
         } catch (SQLException ex) {
 770  0
             final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
 771  0
             LOGGER.log(Level.SEVERE, msg);
 772  0
             LOGGER.log(Level.FINE, null, ex);
 773  
         } finally {
 774  0
             DBUtils.closeStatement(ps);
 775  0
         }
 776  0
     }
 777  
 
 778  
     /**
 779  
      * Determines if the given identifiedVersion is affected by the given cpeId and previous version flag. A non-null, non-empty
 780  
      * string passed to the previous version argument indicates that all previous versions are affected.
 781  
      *
 782  
      * @param vendor the vendor of the dependency being analyzed
 783  
      * @param product the product name of the dependency being analyzed
 784  
      * @param vulnerableSoftware a map of the vulnerable software with a boolean indicating if all previous versions are affected
 785  
      * @param identifiedVersion the identified version of the dependency being analyzed
 786  
      * @return true if the identified version is affected, otherwise false
 787  
      */
 788  
     Entry<String, Boolean> getMatchingSoftware(Map<String, Boolean> vulnerableSoftware, String vendor, String product,
 789  
             DependencyVersion identifiedVersion) {
 790  
 
 791  10
         final boolean isVersionTwoADifferentProduct = "apache".equals(vendor) && "struts".equals(product);
 792  
 
 793  10
         final Set<String> majorVersionsAffectingAllPrevious = new HashSet<String>();
 794  10
         final boolean matchesAnyPrevious = identifiedVersion == null || "-".equals(identifiedVersion.toString());
 795  10
         String majorVersionMatch = null;
 796  10
         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
 797  223
             final DependencyVersion v = parseDependencyVersion(entry.getKey());
 798  223
             if (v == null || "-".equals(v.toString())) { //all versions
 799  0
                 return entry;
 800  
             }
 801  223
             if (entry.getValue()) {
 802  8
                 if (matchesAnyPrevious) {
 803  0
                     return entry;
 804  
                 }
 805  8
                 if (identifiedVersion != null && identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0))) {
 806  6
                     majorVersionMatch = v.getVersionParts().get(0);
 807  
                 }
 808  8
                 majorVersionsAffectingAllPrevious.add(v.getVersionParts().get(0));
 809  
             }
 810  223
         }
 811  10
         if (matchesAnyPrevious) {
 812  0
             return null;
 813  
         }
 814  
 
 815  10
         final boolean canSkipVersions = majorVersionMatch != null && majorVersionsAffectingAllPrevious.size() > 1;
 816  
         //yes, we are iterating over this twice. The first time we are skipping versions those that affect all versions
 817  
         //then later we process those that affect all versions. This could be done with sorting...
 818  10
         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
 819  176
             if (!entry.getValue()) {
 820  169
                 final DependencyVersion v = parseDependencyVersion(entry.getKey());
 821  
                 //this can't dereference a null 'majorVersionMatch' as canSkipVersions accounts for this.
 822  169
                 if (canSkipVersions && !majorVersionMatch.equals(v.getVersionParts().get(0))) {
 823  10
                     continue;
 824  
                 }
 825  
                 //this can't dereference a null 'identifiedVersion' because if it was null we would have exited
 826  
                 //in the above loop or just after loop (if matchesAnyPrevious return null).
 827  159
                 if (identifiedVersion.equals(v)) {
 828  8
                     return entry;
 829  
                 }
 830  
             }
 831  158
         }
 832  2
         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
 833  0
             if (entry.getValue()) {
 834  0
                 final DependencyVersion v = parseDependencyVersion(entry.getKey());
 835  
                 //this can't dereference a null 'majorVersionMatch' as canSkipVersions accounts for this.
 836  0
                 if (canSkipVersions && !majorVersionMatch.equals(v.getVersionParts().get(0))) {
 837  0
                     continue;
 838  
                 }
 839  
                 //this can't dereference a null 'identifiedVersion' because if it was null we would have exited
 840  
                 //in the above loop or just after loop (if matchesAnyPrevious return null).
 841  0
                 if (entry.getValue() && identifiedVersion.compareTo(v) <= 0) {
 842  0
                     if (!(isVersionTwoADifferentProduct && !identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0)))) {
 843  0
                         return entry;
 844  
                     }
 845  
                 }
 846  
             }
 847  0
         }
 848  2
         return null;
 849  
     }
 850  
 
 851  
     /**
 852  
      * Parses the version (including revision) from a CPE identifier. If no version is identified then a '-' is returned.
 853  
      *
 854  
      * @param cpeStr a cpe identifier
 855  
      * @return a dependency version
 856  
      */
 857  
     private DependencyVersion parseDependencyVersion(String cpeStr) {
 858  392
         final VulnerableSoftware cpe = new VulnerableSoftware();
 859  
         try {
 860  392
             cpe.parseName(cpeStr);
 861  0
         } catch (UnsupportedEncodingException ex) {
 862  
             //never going to happen.
 863  0
             LOGGER.log(Level.FINEST, null, ex);
 864  392
         }
 865  392
         return parseDependencyVersion(cpe);
 866  
     }
 867  
 
 868  
     /**
 869  
      * Takes a CPE and parses out the version number. If no version is identified then a '-' is returned.
 870  
      *
 871  
      * @param cpe a cpe object
 872  
      * @return a dependency version
 873  
      */
 874  
     private DependencyVersion parseDependencyVersion(VulnerableSoftware cpe) {
 875  
         DependencyVersion cpeVersion;
 876  394
         if (cpe.getVersion() != null && !cpe.getVersion().isEmpty()) {
 877  
             String versionText;
 878  394
             if (cpe.getRevision() != null && !cpe.getRevision().isEmpty()) {
 879  130
                 versionText = String.format("%s.%s", cpe.getVersion(), cpe.getRevision());
 880  
             } else {
 881  264
                 versionText = cpe.getVersion();
 882  
             }
 883  394
             cpeVersion = DependencyVersionUtil.parseVersion(versionText);
 884  394
         } else {
 885  0
             cpeVersion = new DependencyVersion("-");
 886  
         }
 887  394
         return cpeVersion;
 888  
     }
 889  
 }