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;
19  
20  import org.owasp.dependencycheck.analyzer.AnalysisPhase;
21  import org.owasp.dependencycheck.analyzer.Analyzer;
22  import org.owasp.dependencycheck.analyzer.AnalyzerService;
23  import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer;
24  import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
25  import org.owasp.dependencycheck.data.nvdcve.ConnectionFactory;
26  import org.owasp.dependencycheck.data.nvdcve.CveDB;
27  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
28  import org.owasp.dependencycheck.data.update.CachedWebDataSource;
29  import org.owasp.dependencycheck.data.update.UpdateService;
30  import org.owasp.dependencycheck.data.update.exception.UpdateException;
31  import org.owasp.dependencycheck.dependency.Dependency;
32  import org.owasp.dependencycheck.exception.NoDataException;
33  import org.owasp.dependencycheck.utils.InvalidSettingException;
34  import org.owasp.dependencycheck.utils.Settings;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import java.io.File;
39  import java.io.FileFilter;
40  import java.util.ArrayList;
41  import java.util.Collection;
42  import java.util.EnumMap;
43  import java.util.HashSet;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Set;
48  
49  /**
50   * Scans files, directories, etc. for Dependencies. Analyzers are loaded and used to process the files found by the scan, if a
51   * file is encountered and an Analyzer is associated with the file type then the file is turned into a dependency.
52   *
53   * @author Jeremy Long
54   */
55  public class Engine implements FileFilter {
56  
57      /**
58       * The list of dependencies.
59       */
60      private List<Dependency> dependencies = new ArrayList<Dependency>();
61      /**
62       * A Map of analyzers grouped by Analysis phase.
63       */
64      private Map<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<AnalysisPhase, List<Analyzer>>(AnalysisPhase.class);
65  
66      /**
67       * A Map of analyzers grouped by Analysis phase.
68       */
69      private Set<FileTypeAnalyzer> fileTypeAnalyzers = new HashSet<FileTypeAnalyzer>();
70  
71      /**
72       * The ClassLoader to use when dynamically loading Analyzer and Update services.
73       */
74      private ClassLoader serviceClassLoader = Thread.currentThread().getContextClassLoader();
75      /**
76       * The Logger for use throughout the class.
77       */
78      private static final Logger LOGGER = LoggerFactory.getLogger(Engine.class);
79  
80      /**
81       * Creates a new Engine.
82       *
83       * @throws DatabaseException thrown if there is an error connecting to the database
84       */
85      public Engine() throws DatabaseException {
86          initializeEngine();
87      }
88  
89      /**
90       * Creates a new Engine.
91       *
92       * @param serviceClassLoader a reference the class loader being used
93       * @throws DatabaseException thrown if there is an error connecting to the database
94       */
95      public Engine(ClassLoader serviceClassLoader) throws DatabaseException {
96          this.serviceClassLoader = serviceClassLoader;
97          initializeEngine();
98      }
99  
100     /**
101      * Creates a new Engine using the specified classloader to dynamically load Analyzer and Update services.
102      *
103      * @throws DatabaseException thrown if there is an error connecting to the database
104      */
105     protected final void initializeEngine() throws DatabaseException {
106         ConnectionFactory.initialize();
107         loadAnalyzers();
108     }
109 
110     /**
111      * Properly cleans up resources allocated during analysis.
112      */
113     public void cleanup() {
114         ConnectionFactory.cleanup();
115     }
116 
117     /**
118      * Loads the analyzers specified in the configuration file (or system properties).
119      */
120     private void loadAnalyzers() {
121         if (!analyzers.isEmpty()) {
122             return;
123         }
124         for (AnalysisPhase phase : AnalysisPhase.values()) {
125             analyzers.put(phase, new ArrayList<Analyzer>());
126         }
127 
128         final AnalyzerService service = new AnalyzerService(serviceClassLoader);
129         final Iterator<Analyzer> iterator = service.getAnalyzers();
130         while (iterator.hasNext()) {
131             final Analyzer a = iterator.next();
132             analyzers.get(a.getAnalysisPhase()).add(a);
133             if (a instanceof FileTypeAnalyzer) {
134                 this.fileTypeAnalyzers.add((FileTypeAnalyzer) a);
135             }
136         }
137     }
138 
139     /**
140      * Get the List of the analyzers for a specific phase of analysis.
141      *
142      * @param phase the phase to get the configured analyzers.
143      * @return the analyzers loaded
144      */
145     public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
146         return analyzers.get(phase);
147     }
148 
149     /**
150      * Get the dependencies identified.
151      *
152      * @return the dependencies identified
153      */
154     public List<Dependency> getDependencies() {
155         return dependencies;
156     }
157 
158     /**
159      * Sets the dependencies.
160      *
161      * @param dependencies the dependencies
162      */
163     public void setDependencies(List<Dependency> dependencies) {
164         this.dependencies = dependencies;
165     }
166 
167     /**
168      * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
169      * identified are added to the dependency collection.
170      *
171      * @param paths an array of paths to files or directories to be analyzed
172      * @return the list of dependencies scanned
173      * @since v0.3.2.5
174      */
175     public List<Dependency> scan(String[] paths) {
176         final List<Dependency> deps = new ArrayList<Dependency>();
177         for (String path : paths) {
178             final List<Dependency> d = scan(path);
179             if (d != null) {
180                 deps.addAll(d);
181             }
182         }
183         return deps;
184     }
185 
186     /**
187      * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified
188      * are added to the dependency collection.
189      *
190      * @param path the path to a file or directory to be analyzed
191      * @return the list of dependencies scanned
192      */
193     public List<Dependency> scan(String path) {
194         final File file = new File(path);
195         return scan(file);
196     }
197 
198     /**
199      * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
200      * identified are added to the dependency collection.
201      *
202      * @param files an array of paths to files or directories to be analyzed.
203      * @return the list of dependencies
204      * @since v0.3.2.5
205      */
206     public List<Dependency> scan(File[] files) {
207         final List<Dependency> deps = new ArrayList<Dependency>();
208         for (File file : files) {
209             final List<Dependency> d = scan(file);
210             if (d != null) {
211                 deps.addAll(d);
212             }
213         }
214         return deps;
215     }
216 
217     /**
218      * Scans a collection of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
219      * identified are added to the dependency collection.
220      *
221      * @param files a set of paths to files or directories to be analyzed
222      * @return the list of dependencies scanned
223      * @since v0.3.2.5
224      */
225     public List<Dependency> scan(Collection<File> files) {
226         final List<Dependency> deps = new ArrayList<Dependency>();
227         for (File file : files) {
228             final List<Dependency> d = scan(file);
229             if (d != null) {
230                 deps.addAll(d);
231             }
232         }
233         return deps;
234     }
235 
236     /**
237      * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified
238      * are added to the dependency collection.
239      *
240      * @param file the path to a file or directory to be analyzed
241      * @return the list of dependencies scanned
242      * @since v0.3.2.4
243      */
244     public List<Dependency> scan(File file) {
245         if (file.exists()) {
246             if (file.isDirectory()) {
247                 return scanDirectory(file);
248             } else {
249                 final Dependency d = scanFile(file);
250                 if (d != null) {
251                     final List<Dependency> deps = new ArrayList<Dependency>();
252                     deps.add(d);
253                     return deps;
254                 }
255             }
256         }
257         return null;
258     }
259 
260     /**
261      * Recursively scans files and directories. Any dependencies identified are added to the dependency collection.
262      *
263      * @param dir the directory to scan
264      * @return the list of Dependency objects scanned
265      */
266     protected List<Dependency> scanDirectory(File dir) {
267         final File[] files = dir.listFiles();
268         final List<Dependency> deps = new ArrayList<Dependency>();
269         if (files != null) {
270             for (File f : files) {
271                 if (f.isDirectory()) {
272                     final List<Dependency> d = scanDirectory(f);
273                     if (d != null) {
274                         deps.addAll(d);
275                     }
276                 } else {
277                     final Dependency d = scanFile(f);
278                     deps.add(d);
279                 }
280             }
281         }
282         return deps;
283     }
284 
285     /**
286      * Scans a specified file. If a dependency is identified it is added to the dependency collection.
287      *
288      * @param file The file to scan
289      * @return the scanned dependency
290      */
291     protected Dependency scanFile(File file) {
292         Dependency dependency = null;
293         if (file.isFile()) {
294             if (accept(file)) {
295                 dependency = new Dependency(file);
296                 dependencies.add(dependency);
297             }
298         } else {
299             LOGGER.debug("Path passed to scanFile(File) is not a file: {}. Skipping the file.", file);
300         }
301         return dependency;
302     }
303 
304     /**
305      * Runs the analyzers against all of the dependencies. Since the mutable dependencies list is exposed via
306      * {@link #getDependencies()}, this method iterates over a copy of the dependencies list. Thus, the potential for
307      * {@link java.util.ConcurrentModificationException}s is avoided, and analyzers may safely add or remove entries from the
308      * dependencies list.
309      */
310     public void analyzeDependencies() {
311         boolean autoUpdate = true;
312         try {
313             autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
314         } catch (InvalidSettingException ex) {
315             LOGGER.debug("Invalid setting for auto-update; using true.");
316         }
317         if (autoUpdate) {
318             doUpdates();
319         }
320 
321         //need to ensure that data exists
322         try {
323             ensureDataExists();
324         } catch (NoDataException ex) {
325             LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage());
326             LOGGER.debug("", ex);
327             return;
328         } catch (DatabaseException ex) {
329             LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage());
330             LOGGER.debug("", ex);
331             return;
332 
333         }
334 
335         LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------");
336         LOGGER.info("Analysis Starting");
337         final long analysisStart = System.currentTimeMillis();
338 
339         // analysis phases
340         for (AnalysisPhase phase : AnalysisPhase.values()) {
341             final List<Analyzer> analyzerList = analyzers.get(phase);
342 
343             for (Analyzer a : analyzerList) {
344                 a = initializeAnalyzer(a);
345 
346                 /* need to create a copy of the collection because some of the
347                  * analyzers may modify it. This prevents ConcurrentModificationExceptions.
348                  * This is okay for adds/deletes because it happens per analyzer.
349                  */
350                 LOGGER.debug("Begin Analyzer '{}'", a.getName());
351                 final Set<Dependency> dependencySet = new HashSet<Dependency>(dependencies);
352                 for (Dependency d : dependencySet) {
353                     boolean shouldAnalyze = true;
354                     if (a instanceof FileTypeAnalyzer) {
355                         final FileTypeAnalyzer fAnalyzer = (FileTypeAnalyzer) a;
356                         shouldAnalyze = fAnalyzer.accept(d.getActualFile());
357                     }
358                     if (shouldAnalyze) {
359                         LOGGER.debug("Begin Analysis of '{}'", d.getActualFilePath());
360                         try {
361                             a.analyze(d, this);
362                         } catch (AnalysisException ex) {
363                             LOGGER.warn("An error occurred while analyzing '{}'.", d.getActualFilePath());
364                             LOGGER.debug("", ex);
365                         } catch (Throwable ex) {
366                             //final AnalysisException ax = new AnalysisException(axMsg, ex);
367                             LOGGER.warn("An unexpected error occurred during analysis of '{}'", d.getActualFilePath());
368                             LOGGER.debug("", ex);
369                         }
370                     }
371                 }
372             }
373         }
374         for (AnalysisPhase phase : AnalysisPhase.values()) {
375             final List<Analyzer> analyzerList = analyzers.get(phase);
376 
377             for (Analyzer a : analyzerList) {
378                 closeAnalyzer(a);
379             }
380         }
381 
382         LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------");
383         LOGGER.info("Analysis Complete ({} ms)", System.currentTimeMillis() - analysisStart);
384     }
385 
386     /**
387      * Initializes the given analyzer.
388      *
389      * @param analyzer the analyzer to initialize
390      * @return the initialized analyzer
391      */
392     protected Analyzer initializeAnalyzer(Analyzer analyzer) {
393         try {
394             LOGGER.debug("Initializing {}", analyzer.getName());
395             analyzer.initialize();
396         } catch (Throwable ex) {
397             LOGGER.error("Exception occurred initializing {}.", analyzer.getName());
398             LOGGER.debug("", ex);
399             try {
400                 analyzer.close();
401             } catch (Throwable ex1) {
402                 LOGGER.trace("", ex1);
403             }
404         }
405         return analyzer;
406     }
407 
408     /**
409      * Closes the given analyzer.
410      *
411      * @param analyzer the analyzer to close
412      */
413     protected void closeAnalyzer(Analyzer analyzer) {
414         LOGGER.debug("Closing Analyzer '{}'", analyzer.getName());
415         try {
416             analyzer.close();
417         } catch (Throwable ex) {
418             LOGGER.trace("", ex);
419         }
420     }
421 
422     /**
423      * Cycles through the cached web data sources and calls update on all of them.
424      */
425     public void doUpdates() {
426         LOGGER.info("Checking for updates");
427         final long updateStart = System.currentTimeMillis();
428         final UpdateService service = new UpdateService(serviceClassLoader);
429         final Iterator<CachedWebDataSource> iterator = service.getDataSources();
430         while (iterator.hasNext()) {
431             final CachedWebDataSource source = iterator.next();
432             try {
433                 source.update();
434             } catch (UpdateException ex) {
435                 LOGGER.warn(
436                         "Unable to update Cached Web DataSource, using local data instead. Results may not include recent vulnerabilities.");
437                 LOGGER.debug("Unable to update details for {}", source.getClass().getName(), ex);
438             }
439         }
440         LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart);
441     }
442 
443     /**
444      * Returns a full list of all of the analyzers. This is useful for reporting which analyzers where used.
445      *
446      * @return a list of Analyzers
447      */
448     public List<Analyzer> getAnalyzers() {
449         final List<Analyzer> ret = new ArrayList<Analyzer>();
450         for (AnalysisPhase phase : AnalysisPhase.values()) {
451             final List<Analyzer> analyzerList = analyzers.get(phase);
452             ret.addAll(analyzerList);
453         }
454         return ret;
455     }
456 
457     /**
458      * Checks all analyzers to see if an extension is supported.
459      *
460      * @param file a file extension
461      * @return true or false depending on whether or not the file extension is supported
462      */
463     @Override
464     public boolean accept(File file) {
465         if (file == null) {
466             return false;
467         }
468         boolean scan = false;
469         for (FileTypeAnalyzer a : this.fileTypeAnalyzers) {
470             /* note, we can't break early on this loop as the analyzers need to know if
471              they have files to work on prior to initialization */
472             scan |= a.accept(file);
473         }
474         return scan;
475     }
476 
477     /**
478      * Returns the set of file type analyzers.
479      *
480      * @return the set of file type analyzers
481      */
482     public Set<FileTypeAnalyzer> getFileTypeAnalyzers() {
483         return this.fileTypeAnalyzers;
484     }
485 
486     /**
487      * Checks the CPE Index to ensure documents exists. If none exist a NoDataException is thrown.
488      *
489      * @throws NoDataException thrown if no data exists in the CPE Index
490      * @throws DatabaseException thrown if there is an exception opening the database
491      */
492     private void ensureDataExists() throws NoDataException, DatabaseException {
493         final CveDB cve = new CveDB();
494         try {
495             cve.open();
496             if (!cve.dataExists()) {
497                 throw new NoDataException("No documents exist");
498             }
499         } catch (DatabaseException ex) {
500             throw new NoDataException(ex.getMessage(), ex);
501         } finally {
502             cve.close();
503         }
504     }
505 }