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