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 List<Analyzer> iterator = service.getAnalyzers();
130         for (Analyzer a : iterator) {
131             analyzers.get(a.getAnalysisPhase()).add(a);
132             if (a instanceof FileTypeAnalyzer) {
133                 this.fileTypeAnalyzers.add((FileTypeAnalyzer) a);
134             }
135         }
136     }
137 
138     /**
139      * Get the List of the analyzers for a specific phase of analysis.
140      *
141      * @param phase the phase to get the configured analyzers.
142      * @return the analyzers loaded
143      */
144     public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
145         return analyzers.get(phase);
146     }
147 
148     /**
149      * Get the dependencies identified.
150      *
151      * @return the dependencies identified
152      */
153     public List<Dependency> getDependencies() {
154         return dependencies;
155     }
156 
157     /**
158      * Sets the dependencies.
159      *
160      * @param dependencies the dependencies
161      */
162     public void setDependencies(List<Dependency> dependencies) {
163         this.dependencies = dependencies;
164     }
165 
166     /**
167      * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
168      * identified are added to the dependency collection.
169      *
170      * @param paths an array of paths to files or directories to be analyzed
171      * @return the list of dependencies scanned
172      * @since v0.3.2.5
173      */
174     public List<Dependency> scan(String[] paths) {
175         final List<Dependency> deps = new ArrayList<Dependency>();
176         for (String path : paths) {
177             final List<Dependency> d = scan(path);
178             if (d != null) {
179                 deps.addAll(d);
180             }
181         }
182         return deps;
183     }
184 
185     /**
186      * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified
187      * are added to the dependency collection.
188      *
189      * @param path the path to a file or directory to be analyzed
190      * @return the list of dependencies scanned
191      */
192     public List<Dependency> scan(String path) {
193         final File file = new File(path);
194         return scan(file);
195     }
196 
197     /**
198      * Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
199      * identified are added to the dependency collection.
200      *
201      * @param files an array of paths to files or directories to be analyzed.
202      * @return the list of dependencies
203      * @since v0.3.2.5
204      */
205     public List<Dependency> scan(File[] files) {
206         final List<Dependency> deps = new ArrayList<Dependency>();
207         for (File file : files) {
208             final List<Dependency> d = scan(file);
209             if (d != null) {
210                 deps.addAll(d);
211             }
212         }
213         return deps;
214     }
215 
216     /**
217      * Scans a collection of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
218      * identified are added to the dependency collection.
219      *
220      * @param files a set of paths to files or directories to be analyzed
221      * @return the list of dependencies scanned
222      * @since v0.3.2.5
223      */
224     public List<Dependency> scan(Collection<File> files) {
225         final List<Dependency> deps = new ArrayList<Dependency>();
226         for (File file : files) {
227             final List<Dependency> d = scan(file);
228             if (d != null) {
229                 deps.addAll(d);
230             }
231         }
232         return deps;
233     }
234 
235     /**
236      * Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified
237      * are added to the dependency collection.
238      *
239      * @param file the path to a file or directory to be analyzed
240      * @return the list of dependencies scanned
241      * @since v0.3.2.4
242      */
243     public List<Dependency> scan(File file) {
244         if (file.exists()) {
245             if (file.isDirectory()) {
246                 return scanDirectory(file);
247             } else {
248                 final Dependency d = scanFile(file);
249                 if (d != null) {
250                     final List<Dependency> deps = new ArrayList<Dependency>();
251                     deps.add(d);
252                     return deps;
253                 }
254             }
255         }
256         return null;
257     }
258 
259     /**
260      * Recursively scans files and directories. Any dependencies identified are added to the dependency collection.
261      *
262      * @param dir the directory to scan
263      * @return the list of Dependency objects scanned
264      */
265     protected List<Dependency> scanDirectory(File dir) {
266         final File[] files = dir.listFiles();
267         final List<Dependency> deps = new ArrayList<Dependency>();
268         if (files != null) {
269             for (File f : files) {
270                 if (f.isDirectory()) {
271                     final List<Dependency> d = scanDirectory(f);
272                     if (d != null) {
273                         deps.addAll(d);
274                     }
275                 } else {
276                     final Dependency d = scanFile(f);
277                     deps.add(d);
278                 }
279             }
280         }
281         return deps;
282     }
283 
284     /**
285      * Scans a specified file. If a dependency is identified it is added to the dependency collection.
286      *
287      * @param file The file to scan
288      * @return the scanned dependency
289      */
290     protected Dependency scanFile(File file) {
291         Dependency dependency = null;
292         if (file.isFile()) {
293             if (accept(file)) {
294                 dependency = new Dependency(file);
295                 dependencies.add(dependency);
296             }
297         } else {
298             LOGGER.debug("Path passed to scanFile(File) is not a file: {}. Skipping the file.", file);
299         }
300         return dependency;
301     }
302 
303     /**
304      * Runs the analyzers against all of the dependencies. Since the mutable dependencies list is exposed via
305      * {@link #getDependencies()}, this method iterates over a copy of the dependencies list. Thus, the potential for
306      * {@link java.util.ConcurrentModificationException}s is avoided, and analyzers may safely add or remove entries from the
307      * dependencies list.
308      */
309     public void analyzeDependencies() {
310         boolean autoUpdate = true;
311         try {
312             autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
313         } catch (InvalidSettingException ex) {
314             LOGGER.debug("Invalid setting for auto-update; using true.");
315         }
316         if (autoUpdate) {
317             doUpdates();
318         }
319 
320         //need to ensure that data exists
321         try {
322             ensureDataExists();
323         } catch (NoDataException ex) {
324             LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage());
325             LOGGER.debug("", ex);
326             return;
327         } catch (DatabaseException ex) {
328             LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage());
329             LOGGER.debug("", ex);
330             return;
331 
332         }
333 
334         LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------");
335         LOGGER.info("Analysis Starting");
336         final long analysisStart = System.currentTimeMillis();
337 
338         // analysis phases
339         for (AnalysisPhase phase : AnalysisPhase.values()) {
340             final List<Analyzer> analyzerList = analyzers.get(phase);
341 
342             for (Analyzer a : analyzerList) {
343                 a = initializeAnalyzer(a);
344 
345                 /* need to create a copy of the collection because some of the
346                  * analyzers may modify it. This prevents ConcurrentModificationExceptions.
347                  * This is okay for adds/deletes because it happens per analyzer.
348                  */
349                 LOGGER.debug("Begin Analyzer '{}'", a.getName());
350                 final Set<Dependency> dependencySet = new HashSet<Dependency>(dependencies);
351                 for (Dependency d : dependencySet) {
352                     boolean shouldAnalyze = true;
353                     if (a instanceof FileTypeAnalyzer) {
354                         final FileTypeAnalyzer fAnalyzer = (FileTypeAnalyzer) a;
355                         shouldAnalyze = fAnalyzer.accept(d.getActualFile());
356                     }
357                     if (shouldAnalyze) {
358                         LOGGER.debug("Begin Analysis of '{}'", d.getActualFilePath());
359                         try {
360                             a.analyze(d, this);
361                         } catch (AnalysisException ex) {
362                             LOGGER.warn("An error occurred while analyzing '{}'.", d.getActualFilePath());
363                             LOGGER.debug("", ex);
364                         } catch (Throwable ex) {
365                             //final AnalysisException ax = new AnalysisException(axMsg, ex);
366                             LOGGER.warn("An unexpected error occurred during analysis of '{}'", d.getActualFilePath());
367                             LOGGER.debug("", ex);
368                         }
369                     }
370                 }
371             }
372         }
373         for (AnalysisPhase phase : AnalysisPhase.values()) {
374             final List<Analyzer> analyzerList = analyzers.get(phase);
375 
376             for (Analyzer a : analyzerList) {
377                 closeAnalyzer(a);
378             }
379         }
380 
381         LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------");
382         LOGGER.info("Analysis Complete ({} ms)", System.currentTimeMillis() - analysisStart);
383     }
384 
385     /**
386      * Initializes the given analyzer.
387      *
388      * @param analyzer the analyzer to initialize
389      * @return the initialized analyzer
390      */
391     protected Analyzer initializeAnalyzer(Analyzer analyzer) {
392         try {
393             LOGGER.debug("Initializing {}", analyzer.getName());
394             analyzer.initialize();
395         } catch (Throwable ex) {
396             LOGGER.error("Exception occurred initializing {}.", analyzer.getName());
397             LOGGER.debug("", ex);
398             try {
399                 analyzer.close();
400             } catch (Throwable ex1) {
401                 LOGGER.trace("", ex1);
402             }
403         }
404         return analyzer;
405     }
406 
407     /**
408      * Closes the given analyzer.
409      *
410      * @param analyzer the analyzer to close
411      */
412     protected void closeAnalyzer(Analyzer analyzer) {
413         LOGGER.debug("Closing Analyzer '{}'", analyzer.getName());
414         try {
415             analyzer.close();
416         } catch (Throwable ex) {
417             LOGGER.trace("", ex);
418         }
419     }
420 
421     /**
422      * Cycles through the cached web data sources and calls update on all of them.
423      */
424     public void doUpdates() {
425         LOGGER.info("Checking for updates");
426         final long updateStart = System.currentTimeMillis();
427         final UpdateService service = new UpdateService(serviceClassLoader);
428         final Iterator<CachedWebDataSource> iterator = service.getDataSources();
429         while (iterator.hasNext()) {
430             final CachedWebDataSource source = iterator.next();
431             try {
432                 source.update();
433             } catch (UpdateException ex) {
434                 LOGGER.warn(
435                         "Unable to update Cached Web DataSource, using local data instead. Results may not include recent vulnerabilities.");
436                 LOGGER.debug("Unable to update details for {}", source.getClass().getName(), ex);
437             }
438         }
439         LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart);
440     }
441 
442     /**
443      * Returns a full list of all of the analyzers. This is useful for reporting which analyzers where used.
444      *
445      * @return a list of Analyzers
446      */
447     public List<Analyzer> getAnalyzers() {
448         final List<Analyzer> ret = new ArrayList<Analyzer>();
449         for (AnalysisPhase phase : AnalysisPhase.values()) {
450             final List<Analyzer> analyzerList = analyzers.get(phase);
451             ret.addAll(analyzerList);
452         }
453         return ret;
454     }
455 
456     /**
457      * Checks all analyzers to see if an extension is supported.
458      *
459      * @param file a file extension
460      * @return true or false depending on whether or not the file extension is supported
461      */
462     @Override
463     public boolean accept(File file) {
464         if (file == null) {
465             return false;
466         }
467         boolean scan = false;
468         for (FileTypeAnalyzer a : this.fileTypeAnalyzers) {
469             /* note, we can't break early on this loop as the analyzers need to know if
470              they have files to work on prior to initialization */
471             scan |= a.accept(file);
472         }
473         return scan;
474     }
475 
476     /**
477      * Returns the set of file type analyzers.
478      *
479      * @return the set of file type analyzers
480      */
481     public Set<FileTypeAnalyzer> getFileTypeAnalyzers() {
482         return this.fileTypeAnalyzers;
483     }
484 
485     /**
486      * Checks the CPE Index to ensure documents exists. If none exist a NoDataException is thrown.
487      *
488      * @throws NoDataException thrown if no data exists in the CPE Index
489      * @throws DatabaseException thrown if there is an exception opening the database
490      */
491     private void ensureDataExists() throws NoDataException, DatabaseException {
492         final CveDB cve = new CveDB();
493         try {
494             cve.open();
495             if (!cve.dataExists()) {
496                 throw new NoDataException("No documents exist");
497             }
498         } catch (DatabaseException ex) {
499             throw new NoDataException(ex.getMessage(), ex);
500         } finally {
501             cve.close();
502         }
503     }
504 }