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