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.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      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      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          super();
77          statementBundle = ResourceBundle.getBundle("data/dbStatements");
78          try {
79              open();
80              databaseProperties = new DatabaseProperties(this);
81          } catch (DatabaseException ex) {
82              throw ex;
83          }
84      }
85  
86      /**
87       * Returns the database connection.
88       *
89       * @return the database connection
90       */
91      protected Connection getConnection() {
92          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         if (!isOpen()) {
102             conn = ConnectionFactory.getConnection();
103         }
104     }
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         if (conn != null) {
111             try {
112                 conn.close();
113             } catch (SQLException ex) {
114                 LOGGER.error("There was an error attempting to close the CveDB, see the log for more details.");
115                 LOGGER.debug("", ex);
116             } catch (Throwable ex) {
117                 LOGGER.error("There was an exception attempting to close the CveDB, see the log for more details.");
118                 LOGGER.debug("", ex);
119             }
120             conn = null;
121         }
122     }
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         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     }
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         LOGGER.debug("Entering finalize");
154         close();
155         super.finalize();
156     }
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         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         final Set<VulnerableSoftware> cpe = new HashSet<VulnerableSoftware>();
181         ResultSet rs = null;
182         PreparedStatement ps = null;
183         try {
184             ps = getConnection().prepareStatement(statementBundle.getString("SELECT_CPE_ENTRIES"));
185             ps.setString(1, vendor);
186             ps.setString(2, product);
187             rs = ps.executeQuery();
188 
189             while (rs.next()) {
190                 final VulnerableSoftware vs = new VulnerableSoftware();
191                 vs.setCpe(rs.getString(1));
192                 cpe.add(vs);
193             }
194         } catch (SQLException ex) {
195             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
196             LOGGER.debug("", ex);
197         } finally {
198             DBUtils.closeResultSet(rs);
199             DBUtils.closeStatement(ps);
200         }
201         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         final Set<Pair<String, String>> data = new HashSet<Pair<String, String>>();
212         ResultSet rs = null;
213         PreparedStatement ps = null;
214         try {
215             ps = getConnection().prepareStatement(statementBundle.getString("SELECT_VENDOR_PRODUCT_LIST"));
216             rs = ps.executeQuery();
217             while (rs.next()) {
218                 data.add(new Pair<String, String>(rs.getString(1), rs.getString(2)));
219             }
220         } catch (SQLException ex) {
221             final String msg = "An unexpected SQL Exception occurred; please see the verbose log for more details.";
222             throw new DatabaseException(msg, ex);
223         } finally {
224             DBUtils.closeResultSet(rs);
225             DBUtils.closeStatement(ps);
226         }
227         return data;
228     }
229 
230     /**
231      * Returns a set of properties.
232      *
233      * @return the properties from the database
234      */
235     Properties getProperties() {
236         final Properties prop = new Properties();
237         PreparedStatement ps = null;
238         ResultSet rs = null;
239         try {
240             ps = getConnection().prepareStatement(statementBundle.getString("SELECT_PROPERTIES"));
241             rs = ps.executeQuery();
242             while (rs.next()) {
243                 prop.setProperty(rs.getString(1), rs.getString(2));
244             }
245         } catch (SQLException ex) {
246             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
247             LOGGER.debug("", ex);
248         } finally {
249             DBUtils.closeStatement(ps);
250             DBUtils.closeResultSet(rs);
251         }
252         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         PreparedStatement updateProperty = null;
262         PreparedStatement insertProperty = null;
263         try {
264             try {
265                 updateProperty = getConnection().prepareStatement(statementBundle.getString("UPDATE_PROPERTY"));
266                 insertProperty = getConnection().prepareStatement(statementBundle.getString("INSERT_PROPERTY"));
267             } catch (SQLException ex) {
268                 LOGGER.warn("Unable to save properties to the database");
269                 LOGGER.debug("Unable to save properties to the database", ex);
270                 return;
271             }
272             for (Entry<Object, Object> entry : props.entrySet()) {
273                 final String key = entry.getKey().toString();
274                 final String value = entry.getValue().toString();
275                 try {
276                     updateProperty.setString(1, value);
277                     updateProperty.setString(2, key);
278                     if (updateProperty.executeUpdate() == 0) {
279                         insertProperty.setString(1, key);
280                         insertProperty.setString(2, value);
281                     }
282                 } catch (SQLException ex) {
283                     LOGGER.warn("Unable to save property '{}' with a value of '{}' to the database", key, value);
284                     LOGGER.debug("", ex);
285                 }
286             }
287         } finally {
288             DBUtils.closeStatement(updateProperty);
289             DBUtils.closeStatement(insertProperty);
290         }
291     }
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         PreparedStatement updateProperty = null;
301         PreparedStatement insertProperty = null;
302         try {
303             try {
304                 updateProperty = getConnection().prepareStatement(statementBundle.getString("UPDATE_PROPERTY"));
305             } catch (SQLException ex) {
306                 LOGGER.warn("Unable to save properties to the database");
307                 LOGGER.debug("Unable to save properties to the database", ex);
308                 return;
309             }
310             try {
311                 updateProperty.setString(1, value);
312                 updateProperty.setString(2, key);
313                 if (updateProperty.executeUpdate() == 0) {
314                     try {
315                         insertProperty = getConnection().prepareStatement(statementBundle.getString("INSERT_PROPERTY"));
316                     } catch (SQLException ex) {
317                         LOGGER.warn("Unable to save properties to the database");
318                         LOGGER.debug("Unable to save properties to the database", ex);
319                         return;
320                     }
321                     insertProperty.setString(1, key);
322                     insertProperty.setString(2, value);
323                     insertProperty.execute();
324                 }
325             } catch (SQLException ex) {
326                 LOGGER.warn("Unable to save property '{}' with a value of '{}' to the database", key, value);
327                 LOGGER.debug("", ex);
328             }
329         } finally {
330             DBUtils.closeStatement(updateProperty);
331             DBUtils.closeStatement(insertProperty);
332         }
333     }
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         final VulnerableSoftware cpe = new VulnerableSoftware();
344         try {
345             cpe.parseName(cpeStr);
346         } catch (UnsupportedEncodingException ex) {
347             LOGGER.trace("", ex);
348         }
349         final DependencyVersion detectedVersion = parseDependencyVersion(cpe);
350         final List<Vulnerability> vulnerabilities = new ArrayList<Vulnerability>();
351 
352         PreparedStatement ps = null;
353         ResultSet rs = null;
354         try {
355             ps = getConnection().prepareStatement(statementBundle.getString("SELECT_CVE_FROM_SOFTWARE"));
356             ps.setString(1, cpe.getVendor());
357             ps.setString(2, cpe.getProduct());
358             rs = ps.executeQuery();
359             String currentCVE = "";
360 
361             final Map<String, Boolean> vulnSoftware = new HashMap<String, Boolean>();
362             while (rs.next()) {
363                 final String cveId = rs.getString(1);
364                 if (!currentCVE.equals(cveId)) { //check for match and add
365                     final Entry<String, Boolean> matchedCPE = getMatchingSoftware(vulnSoftware, cpe.getVendor(), cpe.getProduct(), detectedVersion);
366                     if (matchedCPE != null) {
367                         final Vulnerability v = getVulnerability(currentCVE);
368                         v.setMatchedCPE(matchedCPE.getKey(), matchedCPE.getValue() ? "Y" : null);
369                         vulnerabilities.add(v);
370                     }
371                     vulnSoftware.clear();
372                     currentCVE = cveId;
373                 }
374 
375                 final String cpeId = rs.getString(2);
376                 final String previous = rs.getString(3);
377                 final Boolean p = previous != null && !previous.isEmpty();
378                 vulnSoftware.put(cpeId, p);
379             }
380             //remember to process the last set of CVE/CPE entries
381             final Entry<String, Boolean> matchedCPE = getMatchingSoftware(vulnSoftware, cpe.getVendor(), cpe.getProduct(), detectedVersion);
382             if (matchedCPE != null) {
383                 final Vulnerability v = getVulnerability(currentCVE);
384                 v.setMatchedCPE(matchedCPE.getKey(), matchedCPE.getValue() ? "Y" : null);
385                 vulnerabilities.add(v);
386             }
387         } catch (SQLException ex) {
388             throw new DatabaseException("Exception retrieving vulnerability for " + cpeStr, ex);
389         } finally {
390             DBUtils.closeResultSet(rs);
391             DBUtils.closeStatement(ps);
392         }
393         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         PreparedStatement psV = null;
405         PreparedStatement psR = null;
406         PreparedStatement psS = null;
407         ResultSet rsV = null;
408         ResultSet rsR = null;
409         ResultSet rsS = null;
410         Vulnerability vuln = null;
411         try {
412             psV = getConnection().prepareStatement(statementBundle.getString("SELECT_VULNERABILITY"));
413             psV.setString(1, cve);
414             rsV = psV.executeQuery();
415             if (rsV.next()) {
416                 vuln = new Vulnerability();
417                 vuln.setName(cve);
418                 vuln.setDescription(rsV.getString(2));
419                 String cwe = rsV.getString(3);
420                 if (cwe != null) {
421                     final String name = CweDB.getCweName(cwe);
422                     if (name != null) {
423                         cwe += " " + name;
424                     }
425                 }
426                 final int cveId = rsV.getInt(1);
427                 vuln.setCwe(cwe);
428                 vuln.setCvssScore(rsV.getFloat(4));
429                 vuln.setCvssAccessVector(rsV.getString(5));
430                 vuln.setCvssAccessComplexity(rsV.getString(6));
431                 vuln.setCvssAuthentication(rsV.getString(7));
432                 vuln.setCvssConfidentialityImpact(rsV.getString(8));
433                 vuln.setCvssIntegrityImpact(rsV.getString(9));
434                 vuln.setCvssAvailabilityImpact(rsV.getString(10));
435 
436                 psR = getConnection().prepareStatement(statementBundle.getString("SELECT_REFERENCES"));
437                 psR.setInt(1, cveId);
438                 rsR = psR.executeQuery();
439                 while (rsR.next()) {
440                     vuln.addReference(rsR.getString(1), rsR.getString(2), rsR.getString(3));
441                 }
442                 psS = getConnection().prepareStatement(statementBundle.getString("SELECT_SOFTWARE"));
443                 psS.setInt(1, cveId);
444                 rsS = psS.executeQuery();
445                 while (rsS.next()) {
446                     final String cpe = rsS.getString(1);
447                     final String prevVersion = rsS.getString(2);
448                     if (prevVersion == null) {
449                         vuln.addVulnerableSoftware(cpe);
450                     } else {
451                         vuln.addVulnerableSoftware(cpe, prevVersion);
452                     }
453                 }
454             }
455         } catch (SQLException ex) {
456             throw new DatabaseException("Error retrieving " + cve, ex);
457         } finally {
458             DBUtils.closeResultSet(rsV);
459             DBUtils.closeResultSet(rsR);
460             DBUtils.closeResultSet(rsS);
461             DBUtils.closeStatement(psV);
462             DBUtils.closeStatement(psR);
463             DBUtils.closeStatement(psS);
464         }
465         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         PreparedStatement selectVulnerabilityId = null;
476         PreparedStatement deleteVulnerability = null;
477         PreparedStatement deleteReferences = null;
478         PreparedStatement deleteSoftware = null;
479         PreparedStatement updateVulnerability = null;
480         PreparedStatement insertVulnerability = null;
481         PreparedStatement insertReference = null;
482         PreparedStatement selectCpeId = null;
483         PreparedStatement insertCpe = null;
484         PreparedStatement insertSoftware = null;
485 
486         try {
487             selectVulnerabilityId = getConnection().prepareStatement(statementBundle.getString("SELECT_VULNERABILITY_ID"));
488             deleteVulnerability = getConnection().prepareStatement(statementBundle.getString("DELETE_VULNERABILITY"));
489             deleteReferences = getConnection().prepareStatement(statementBundle.getString("DELETE_REFERENCE"));
490             deleteSoftware = getConnection().prepareStatement(statementBundle.getString("DELETE_SOFTWARE"));
491             updateVulnerability = getConnection().prepareStatement(statementBundle.getString("UPDATE_VULNERABILITY"));
492             final String[] ids = {"id"};
493             insertVulnerability = getConnection().prepareStatement(statementBundle.getString("INSERT_VULNERABILITY"),
494                     //Statement.RETURN_GENERATED_KEYS);
495                     ids);
496             insertReference = getConnection().prepareStatement(statementBundle.getString("INSERT_REFERENCE"));
497             selectCpeId = getConnection().prepareStatement(statementBundle.getString("SELECT_CPE_ID"));
498             insertCpe = getConnection().prepareStatement(statementBundle.getString("INSERT_CPE"),
499                     //Statement.RETURN_GENERATED_KEYS);
500                     ids);
501             insertSoftware = getConnection().prepareStatement(statementBundle.getString("INSERT_SOFTWARE"));
502             int vulnerabilityId = 0;
503             selectVulnerabilityId.setString(1, vuln.getName());
504             ResultSet rs = selectVulnerabilityId.executeQuery();
505             if (rs.next()) {
506                 vulnerabilityId = rs.getInt(1);
507                 // first delete any existing vulnerability info. We don't know what was updated. yes, slower but atm easier.
508                 deleteReferences.setInt(1, vulnerabilityId);
509                 deleteReferences.execute();
510                 deleteSoftware.setInt(1, vulnerabilityId);
511                 deleteSoftware.execute();
512             }
513             DBUtils.closeResultSet(rs);
514             rs = null;
515             if (vulnerabilityId != 0) {
516                 if (vuln.getDescription().contains("** REJECT **")) {
517                     deleteVulnerability.setInt(1, vulnerabilityId);
518                     deleteVulnerability.executeUpdate();
519                 } else {
520                     updateVulnerability.setString(1, vuln.getDescription());
521                     updateVulnerability.setString(2, vuln.getCwe());
522                     updateVulnerability.setFloat(3, vuln.getCvssScore());
523                     updateVulnerability.setString(4, vuln.getCvssAccessVector());
524                     updateVulnerability.setString(5, vuln.getCvssAccessComplexity());
525                     updateVulnerability.setString(6, vuln.getCvssAuthentication());
526                     updateVulnerability.setString(7, vuln.getCvssConfidentialityImpact());
527                     updateVulnerability.setString(8, vuln.getCvssIntegrityImpact());
528                     updateVulnerability.setString(9, vuln.getCvssAvailabilityImpact());
529                     updateVulnerability.setInt(10, vulnerabilityId);
530                     updateVulnerability.executeUpdate();
531                 }
532             } else {
533                 insertVulnerability.setString(1, vuln.getName());
534                 insertVulnerability.setString(2, vuln.getDescription());
535                 insertVulnerability.setString(3, vuln.getCwe());
536                 insertVulnerability.setFloat(4, vuln.getCvssScore());
537                 insertVulnerability.setString(5, vuln.getCvssAccessVector());
538                 insertVulnerability.setString(6, vuln.getCvssAccessComplexity());
539                 insertVulnerability.setString(7, vuln.getCvssAuthentication());
540                 insertVulnerability.setString(8, vuln.getCvssConfidentialityImpact());
541                 insertVulnerability.setString(9, vuln.getCvssIntegrityImpact());
542                 insertVulnerability.setString(10, vuln.getCvssAvailabilityImpact());
543                 insertVulnerability.execute();
544                 try {
545                     rs = insertVulnerability.getGeneratedKeys();
546                     rs.next();
547                     vulnerabilityId = rs.getInt(1);
548                 } catch (SQLException ex) {
549                     final String msg = String.format("Unable to retrieve id for new vulnerability for '%s'", vuln.getName());
550                     throw new DatabaseException(msg, ex);
551                 } finally {
552                     DBUtils.closeResultSet(rs);
553                     rs = null;
554                 }
555             }
556             insertReference.setInt(1, vulnerabilityId);
557             for (Reference r : vuln.getReferences()) {
558                 insertReference.setString(2, r.getName());
559                 insertReference.setString(3, r.getUrl());
560                 insertReference.setString(4, r.getSource());
561                 insertReference.execute();
562             }
563             for (VulnerableSoftware s : vuln.getVulnerableSoftware()) {
564                 int cpeProductId = 0;
565                 selectCpeId.setString(1, s.getName());
566                 try {
567                     rs = selectCpeId.executeQuery();
568                     if (rs.next()) {
569                         cpeProductId = rs.getInt(1);
570                     }
571                 } catch (SQLException ex) {
572                     throw new DatabaseException("Unable to get primary key for new cpe: " + s.getName(), ex);
573                 } finally {
574                     DBUtils.closeResultSet(rs);
575                     rs = null;
576                 }
577 
578                 if (cpeProductId == 0) {
579                     insertCpe.setString(1, s.getName());
580                     insertCpe.setString(2, s.getVendor());
581                     insertCpe.setString(3, s.getProduct());
582                     insertCpe.executeUpdate();
583                     cpeProductId = DBUtils.getGeneratedKey(insertCpe);
584                 }
585                 if (cpeProductId == 0) {
586                     throw new DatabaseException("Unable to retrieve cpeProductId - no data returned");
587                 }
588 
589                 insertSoftware.setInt(1, vulnerabilityId);
590                 insertSoftware.setInt(2, cpeProductId);
591                 if (s.getPreviousVersion() == null) {
592                     insertSoftware.setNull(3, java.sql.Types.VARCHAR);
593                 } else {
594                     insertSoftware.setString(3, s.getPreviousVersion());
595                 }
596                 insertSoftware.execute();
597             }
598 
599         } catch (SQLException ex) {
600             final String msg = String.format("Error updating '%s'", vuln.getName());
601             LOGGER.debug("", ex);
602             throw new DatabaseException(msg, ex);
603         } finally {
604             DBUtils.closeStatement(selectVulnerabilityId);
605             DBUtils.closeStatement(deleteReferences);
606             DBUtils.closeStatement(deleteSoftware);
607             DBUtils.closeStatement(updateVulnerability);
608             DBUtils.closeStatement(deleteVulnerability);
609             DBUtils.closeStatement(insertVulnerability);
610             DBUtils.closeStatement(insertReference);
611             DBUtils.closeStatement(selectCpeId);
612             DBUtils.closeStatement(insertCpe);
613             DBUtils.closeStatement(insertSoftware);
614         }
615     }
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         Statement cs = null;
624         ResultSet rs = null;
625         try {
626             cs = conn.createStatement();
627             rs = cs.executeQuery("SELECT COUNT(*) records FROM cpeEntry");
628             if (rs.next()) {
629                 if (rs.getInt(1) > 0) {
630                     return true;
631                 }
632             }
633         } catch (SQLException ex) {
634             String dd;
635             try {
636                 dd = Settings.getDataDirectory().getAbsolutePath();
637             } catch (IOException ex1) {
638                 dd = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
639             }
640             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             LOGGER.debug("", ex);
646         } finally {
647             DBUtils.closeResultSet(rs);
648             DBUtils.closeStatement(cs);
649         }
650         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         PreparedStatement ps = null;
659         try {
660             ps = getConnection().prepareStatement(statementBundle.getString("CLEANUP_ORPHANS"));
661             if (ps != null) {
662                 ps.executeUpdate();
663             }
664         } catch (SQLException ex) {
665             LOGGER.error("An unexpected SQL Exception occurred; please see the verbose log for more details.");
666             LOGGER.debug("", ex);
667         } finally {
668             DBUtils.closeStatement(ps);
669         }
670     }
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         final boolean isVersionTwoADifferentProduct = "apache".equals(vendor) && "struts".equals(product);
686 
687         final Set<String> majorVersionsAffectingAllPrevious = new HashSet<String>();
688         final boolean matchesAnyPrevious = identifiedVersion == null || "-".equals(identifiedVersion.toString());
689         String majorVersionMatch = null;
690         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
691             final DependencyVersion v = parseDependencyVersion(entry.getKey());
692             if (v == null || "-".equals(v.toString())) { //all versions
693                 return entry;
694             }
695             if (entry.getValue()) {
696                 if (matchesAnyPrevious) {
697                     return entry;
698                 }
699                 if (identifiedVersion != null && identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0))) {
700                     majorVersionMatch = v.getVersionParts().get(0);
701                 }
702                 majorVersionsAffectingAllPrevious.add(v.getVersionParts().get(0));
703             }
704         }
705         if (matchesAnyPrevious) {
706             return null;
707         }
708 
709         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         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
713             if (!entry.getValue()) {
714                 final DependencyVersion v = parseDependencyVersion(entry.getKey());
715                 //this can't dereference a null 'majorVersionMatch' as canSkipVersions accounts for this.
716                 if (canSkipVersions && !majorVersionMatch.equals(v.getVersionParts().get(0))) {
717                     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                 if (identifiedVersion.equals(v)) {
722                     return entry;
723                 }
724             }
725         }
726         for (Entry<String, Boolean> entry : vulnerableSoftware.entrySet()) {
727             if (entry.getValue()) {
728                 final DependencyVersion v = parseDependencyVersion(entry.getKey());
729                 //this can't dereference a null 'majorVersionMatch' as canSkipVersions accounts for this.
730                 if (canSkipVersions && !majorVersionMatch.equals(v.getVersionParts().get(0))) {
731                     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                 if (entry.getValue() && identifiedVersion.compareTo(v) <= 0) {
736                     if (!(isVersionTwoADifferentProduct && !identifiedVersion.getVersionParts().get(0).equals(v.getVersionParts().get(0)))) {
737                         return entry;
738                     }
739                 }
740             }
741         }
742         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         final VulnerableSoftware cpe = new VulnerableSoftware();
753         try {
754             cpe.parseName(cpeStr);
755         } catch (UnsupportedEncodingException ex) {
756             //never going to happen.
757             LOGGER.trace("", ex);
758         }
759         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         if (cpe.getVersion() != null && !cpe.getVersion().isEmpty()) {
771             final String versionText;
772             if (cpe.getUpdate() != null && !cpe.getUpdate().isEmpty()) {
773                 versionText = String.format("%s.%s", cpe.getVersion(), cpe.getUpdate());
774             } else {
775                 versionText = cpe.getVersion();
776             }
777             cpeVersion = DependencyVersionUtil.parseVersion(versionText);
778         } else {
779             cpeVersion = new DependencyVersion("-");
780         }
781         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         CallableStatement cs = null;
791         try {
792             cs = getConnection().prepareCall(statementBundle.getString("DELETE_UNUSED_DICT_CPE"));
793             cs.executeUpdate();
794         } catch (SQLException ex) {
795             LOGGER.error("Unable to delete CPE dictionary entries", ex);
796         } finally {
797             DBUtils.closeStatement(cs);
798         }
799     }
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         PreparedStatement ps = null;
812         try {
813             ps = getConnection().prepareCall(statementBundle.getString("ADD_DICT_CPE"));
814             ps.setString(1, cpe);
815             ps.setString(2, vendor);
816             ps.setString(3, product);
817             ps.executeUpdate();
818         } catch (SQLException ex) {
819             LOGGER.error("Unable to add CPE dictionary entry", ex);
820         } finally {
821             DBUtils.closeStatement(ps);
822         }
823     }
824 }