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