View Javadoc
1   /*
2    * This file is part of dependency-check-cli.
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 ch.qos.logback.classic.LoggerContext;
21  import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
22  import java.io.File;
23  import java.io.FileNotFoundException;
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  import org.apache.commons.cli.ParseException;
30  import org.owasp.dependencycheck.data.nvdcve.CveDB;
31  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
32  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
33  import org.owasp.dependencycheck.dependency.Dependency;
34  import org.owasp.dependencycheck.org.apache.tools.ant.DirectoryScanner;
35  import org.owasp.dependencycheck.reporting.ReportGenerator;
36  import org.owasp.dependencycheck.utils.Settings;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  import ch.qos.logback.core.FileAppender;
40  import org.slf4j.impl.StaticLoggerBinder;
41  
42  /**
43   * The command line interface for the DependencyCheck application.
44   *
45   * @author Jeremy Long
46   */
47  public class App {
48  
49      /**
50       * The logger.
51       */
52      private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
53  
54      /**
55       * The main method for the application.
56       *
57       * @param args the command line arguments
58       */
59      public static void main(String[] args) {
60          try {
61              Settings.initialize();
62              final App app = new App();
63              app.run(args);
64          } finally {
65              Settings.cleanup(true);
66          }
67      }
68  
69      /**
70       * Main CLI entry-point into the application.
71       *
72       * @param args the command line arguments
73       */
74      public void run(String[] args) {
75          final CliParser cli = new CliParser();
76  
77          try {
78              cli.parse(args);
79          } catch (FileNotFoundException ex) {
80              System.err.println(ex.getMessage());
81              cli.printHelp();
82              return;
83          } catch (ParseException ex) {
84              System.err.println(ex.getMessage());
85              cli.printHelp();
86              return;
87          }
88  
89          if (cli.getVerboseLog() != null) {
90              prepareLogger(cli.getVerboseLog());
91          }
92  
93          if (cli.isGetVersion()) {
94              cli.printVersionInfo();
95          } else if (cli.isUpdateOnly()) {
96              populateSettings(cli);
97              runUpdateOnly();
98          } else if (cli.isRunScan()) {
99              populateSettings(cli);
100             try {
101                 runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getApplicationName(), cli.getScanFiles(),
102                         cli.getExcludeList(), cli.getSymLinkDepth());
103             } catch (InvalidScanPathException ex) {
104                 LOGGER.error("An invalid scan path was detected; unable to scan '//*' paths");
105             }
106         } else {
107             cli.printHelp();
108         }
109     }
110 
111     /**
112      * Scans the specified directories and writes the dependency reports to the reportDirectory.
113      *
114      * @param reportDirectory the path to the directory where the reports will be written
115      * @param outputFormat the output format of the report
116      * @param applicationName the application name for the report
117      * @param files the files/directories to scan
118      * @param excludes the patterns for files/directories to exclude
119      * @param symLinkDepth the depth that symbolic links will be followed
120      *
121      * @throws InvalidScanPathException thrown if the path to scan starts with "//"
122      */
123     private void runScan(String reportDirectory, String outputFormat, String applicationName, String[] files,
124             String[] excludes, int symLinkDepth) throws InvalidScanPathException {
125         Engine engine = null;
126         try {
127             engine = new Engine();
128             final List<String> antStylePaths = new ArrayList<String>();
129             for (String file : files) {
130                 final String antPath = ensureCanonicalPath(file);
131                 antStylePaths.add(antPath);
132             }
133 
134             final Set<File> paths = new HashSet<File>();
135             for (String file : antStylePaths) {
136                 LOGGER.debug("Scanning {}", file);
137                 final DirectoryScanner scanner = new DirectoryScanner();
138                 String include = file.replace('\\', '/');
139                 File baseDir;
140 
141                 if (include.startsWith("//")) {
142                     throw new InvalidScanPathException("Unable to scan paths specified by //");
143                 } else {
144                     final int pos = getLastFileSeparator(include);
145                     final String tmpBase = include.substring(0, pos);
146                     final String tmpInclude = include.substring(pos + 1);
147                     if (tmpInclude.indexOf('*') >= 0 || tmpInclude.indexOf('?') >= 0
148                             || (new File(include)).isFile()) {
149                         baseDir = new File(tmpBase);
150                         include = tmpInclude;
151                     } else {
152                         baseDir = new File(tmpBase, tmpInclude);
153                         include = "**/*";
154                     }
155                 }
156                 //LOGGER.debug("baseDir: {}", baseDir);
157                 //LOGGER.debug("include: {}", include);
158                 scanner.setBasedir(baseDir);
159                 scanner.setIncludes(include);
160                 scanner.setMaxLevelsOfSymlinks(symLinkDepth);
161                 if (symLinkDepth <= 0) {
162                     scanner.setFollowSymlinks(false);
163                 }
164                 if (excludes != null && excludes.length > 0) {
165                     scanner.addExcludes(excludes);
166                 }
167                 scanner.scan();
168                 if (scanner.getIncludedFilesCount() > 0) {
169                     for (String s : scanner.getIncludedFiles()) {
170                         final File f = new File(baseDir, s);
171                         LOGGER.debug("Found file {}", f.toString());
172                         paths.add(f);
173                     }
174                 }
175             }
176             engine.scan(paths);
177 
178             engine.analyzeDependencies();
179             final List<Dependency> dependencies = engine.getDependencies();
180             DatabaseProperties prop = null;
181             CveDB cve = null;
182             try {
183                 cve = new CveDB();
184                 cve.open();
185                 prop = cve.getDatabaseProperties();
186             } catch (DatabaseException ex) {
187                 LOGGER.debug("Unable to retrieve DB Properties", ex);
188             } finally {
189                 if (cve != null) {
190                     cve.close();
191                 }
192             }
193             final ReportGenerator report = new ReportGenerator(applicationName, dependencies, engine.getAnalyzers(), prop);
194             try {
195                 report.generateReports(reportDirectory, outputFormat);
196             } catch (IOException ex) {
197                 LOGGER.error("There was an IO error while attempting to generate the report.");
198                 LOGGER.debug("", ex);
199             } catch (Throwable ex) {
200                 LOGGER.error("There was an error while attempting to generate the report.");
201                 LOGGER.debug("", ex);
202             }
203         } catch (DatabaseException ex) {
204             LOGGER.error("Unable to connect to the dependency-check database; analysis has stopped");
205             LOGGER.debug("", ex);
206         } finally {
207             if (engine != null) {
208                 engine.cleanup();
209             }
210         }
211     }
212 
213     /**
214      * Only executes the update phase of dependency-check.
215      */
216     private void runUpdateOnly() {
217         Engine engine = null;
218         try {
219             engine = new Engine();
220             engine.doUpdates();
221         } catch (DatabaseException ex) {
222             LOGGER.error("Unable to connect to the dependency-check database; analysis has stopped");
223             LOGGER.debug("", ex);
224         } finally {
225             if (engine != null) {
226                 engine.cleanup();
227             }
228         }
229     }
230 
231     /**
232      * Updates the global Settings.
233      *
234      * @param cli a reference to the CLI Parser that contains the command line arguments used to set the corresponding settings in
235      * the core engine.
236      */
237     private void populateSettings(CliParser cli) {
238 
239         final boolean autoUpdate = cli.isAutoUpdate();
240         final String connectionTimeout = cli.getConnectionTimeout();
241         final String proxyServer = cli.getProxyServer();
242         final String proxyPort = cli.getProxyPort();
243         final String proxyUser = cli.getProxyUsername();
244         final String proxyPass = cli.getProxyPassword();
245         final String dataDirectory = cli.getDataDirectory();
246         final File propertiesFile = cli.getPropertiesFile();
247         final String suppressionFile = cli.getSuppressionFile();
248         final boolean jarDisabled = cli.isJarDisabled();
249         final boolean archiveDisabled = cli.isArchiveDisabled();
250         final boolean pyDistDisabled = cli.isPythonDistributionDisabled();
251         final boolean cMakeDisabled = cli.isCmakeDisabled();
252         final boolean pyPkgDisabled = cli.isPythonPackageDisabled();
253         final boolean autoconfDisabled = cli.isAutoconfDisabled();
254         final boolean assemblyDisabled = cli.isAssemblyDisabled();
255         final boolean nuspecDisabled = cli.isNuspecDisabled();
256         final boolean centralDisabled = cli.isCentralDisabled();
257         final boolean nexusDisabled = cli.isNexusDisabled();
258         final String nexusUrl = cli.getNexusUrl();
259         final String databaseDriverName = cli.getDatabaseDriverName();
260         final String databaseDriverPath = cli.getDatabaseDriverPath();
261         final String connectionString = cli.getConnectionString();
262         final String databaseUser = cli.getDatabaseUser();
263         final String databasePassword = cli.getDatabasePassword();
264         final String additionalZipExtensions = cli.getAdditionalZipExtensions();
265         final String pathToMono = cli.getPathToMono();
266         final String cveMod12 = cli.getModifiedCve12Url();
267         final String cveMod20 = cli.getModifiedCve20Url();
268         final String cveBase12 = cli.getBaseCve12Url();
269         final String cveBase20 = cli.getBaseCve20Url();
270 
271         if (propertiesFile != null) {
272             try {
273                 Settings.mergeProperties(propertiesFile);
274             } catch (FileNotFoundException ex) {
275                 LOGGER.error("Unable to load properties file '{}'", propertiesFile.getPath());
276                 LOGGER.debug("", ex);
277             } catch (IOException ex) {
278                 LOGGER.error("Unable to find properties file '{}'", propertiesFile.getPath());
279                 LOGGER.debug("", ex);
280             }
281         }
282         // We have to wait until we've merged the properties before attempting to set whether we use
283         // the proxy for Nexus since it could be disabled in the properties, but not explicitly stated
284         // on the command line
285         final boolean nexusUsesProxy = cli.isNexusUsesProxy();
286         if (dataDirectory != null) {
287             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
288         } else if (System.getProperty("basedir") != null) {
289             final File dataDir = new File(System.getProperty("basedir"), "data");
290             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
291         } else {
292             final File jarPath = new File(App.class.getProtectionDomain().getCodeSource().getLocation().getPath());
293             final File base = jarPath.getParentFile();
294             final String sub = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
295             final File dataDir = new File(base, sub);
296             Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
297         }
298         Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
299         if (proxyServer != null && !proxyServer.isEmpty()) {
300             Settings.setString(Settings.KEYS.PROXY_SERVER, proxyServer);
301         }
302         if (proxyPort != null && !proxyPort.isEmpty()) {
303             Settings.setString(Settings.KEYS.PROXY_PORT, proxyPort);
304         }
305         if (proxyUser != null && !proxyUser.isEmpty()) {
306             Settings.setString(Settings.KEYS.PROXY_USERNAME, proxyUser);
307         }
308         if (proxyPass != null && !proxyPass.isEmpty()) {
309             Settings.setString(Settings.KEYS.PROXY_PASSWORD, proxyPass);
310         }
311         if (connectionTimeout != null && !connectionTimeout.isEmpty()) {
312             Settings.setString(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
313         }
314         if (suppressionFile != null && !suppressionFile.isEmpty()) {
315             Settings.setString(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
316         }
317 
318         //File Type Analyzer Settings
319         Settings.setBoolean(Settings.KEYS.ANALYZER_JAR_ENABLED, !jarDisabled);
320         Settings.setBoolean(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, !archiveDisabled);
321         Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED, !pyDistDisabled);
322         Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED, !pyPkgDisabled);
323         Settings.setBoolean(Settings.KEYS.ANALYZER_AUTOCONF_ENABLED, !autoconfDisabled);
324         Settings.setBoolean(Settings.KEYS.ANALYZER_CMAKE_ENABLED, !cMakeDisabled);
325         Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, !nuspecDisabled);
326         Settings.setBoolean(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED, !assemblyDisabled);
327         Settings.setBoolean(Settings.KEYS.ANALYZER_OPENSSL_ENABLED, !cli.isOpenSSLDisabled());
328 
329         Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, !centralDisabled);
330         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, !nexusDisabled);
331         if (nexusUrl != null && !nexusUrl.isEmpty()) {
332             Settings.setString(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
333         }
334         Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_PROXY, nexusUsesProxy);
335         if (databaseDriverName != null && !databaseDriverName.isEmpty()) {
336             Settings.setString(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
337         }
338         if (databaseDriverPath != null && !databaseDriverPath.isEmpty()) {
339             Settings.setString(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
340         }
341         if (connectionString != null && !connectionString.isEmpty()) {
342             Settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
343         }
344         if (databaseUser != null && !databaseUser.isEmpty()) {
345             Settings.setString(Settings.KEYS.DB_USER, databaseUser);
346         }
347         if (databasePassword != null && !databasePassword.isEmpty()) {
348             Settings.setString(Settings.KEYS.DB_PASSWORD, databasePassword);
349         }
350         if (additionalZipExtensions != null && !additionalZipExtensions.isEmpty()) {
351             Settings.setString(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, additionalZipExtensions);
352         }
353         if (pathToMono != null && !pathToMono.isEmpty()) {
354             Settings.setString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH, pathToMono);
355         }
356         if (cveBase12 != null && !cveBase12.isEmpty()) {
357             Settings.setString(Settings.KEYS.CVE_SCHEMA_1_2, cveBase12);
358             Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, cveBase20);
359             Settings.setString(Settings.KEYS.CVE_MODIFIED_12_URL, cveMod12);
360             Settings.setString(Settings.KEYS.CVE_MODIFIED_20_URL, cveMod20);
361         }
362     }
363 
364     /**
365      * Creates a file appender and adds it to logback.
366      *
367      * @param verboseLog the path to the verbose log file
368      */
369     private void prepareLogger(String verboseLog) {
370         final StaticLoggerBinder loggerBinder = StaticLoggerBinder.getSingleton();
371         final LoggerContext context = (LoggerContext) loggerBinder.getLoggerFactory();
372 
373         final PatternLayoutEncoder encoder = new PatternLayoutEncoder();
374         encoder.setPattern("%d %C:%L%n%-5level - %msg%n");
375         encoder.setContext(context);
376         encoder.start();
377         final FileAppender fa = new FileAppender();
378         fa.setAppend(true);
379         fa.setEncoder(encoder);
380         fa.setContext(context);
381         fa.setFile(verboseLog);
382         final File f = new File(verboseLog);
383         String name = f.getName();
384         final int i = name.lastIndexOf('.');
385         if (i > 1) {
386             name = name.substring(0, i);
387         }
388         fa.setName(name);
389         fa.start();
390         final ch.qos.logback.classic.Logger rootLogger = context.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
391         rootLogger.addAppender(fa);
392     }
393 
394     /**
395      * Takes a path and resolves it to be a canonical & absolute path. The caveats are that this method will take an Ant style
396      * file selector path (../someDir/**\/*.jar) and convert it to an absolute/canonical path (at least to the left of the first *
397      * or ?).
398      *
399      * @param path the path to canonicalize
400      * @return the canonical path
401      */
402     protected String ensureCanonicalPath(String path) {
403         String basePath = null;
404         String wildCards = null;
405         final String file = path.replace('\\', '/');
406         if (file.contains("*") || file.contains("?")) {
407 
408             int pos = getLastFileSeparator(file);
409             if (pos < 0) {
410                 return file;
411             }
412             pos += 1;
413             basePath = file.substring(0, pos);
414             wildCards = file.substring(pos);
415         } else {
416             basePath = file;
417         }
418 
419         File f = new File(basePath);
420         try {
421             f = f.getCanonicalFile();
422             if (wildCards != null) {
423                 f = new File(f, wildCards);
424             }
425         } catch (IOException ex) {
426             LOGGER.warn("Invalid path '{}' was provided.", path);
427             LOGGER.debug("Invalid path provided", ex);
428         }
429         return f.getAbsolutePath().replace('\\', '/');
430     }
431 
432     /**
433      * Returns the position of the last file separator.
434      *
435      * @param file a file path
436      * @return the position of the last file separator
437      */
438     private int getLastFileSeparator(String file) {
439         if (file.contains("*") || file.contains("?")) {
440             int p1 = file.indexOf('*');
441             int p2 = file.indexOf('?');
442             p1 = p1 > 0 ? p1 : file.length();
443             p2 = p2 > 0 ? p2 : file.length();
444             int pos = p1 < p2 ? p1 : p2;
445             pos = file.lastIndexOf('/', pos);
446             return pos;
447         } else {
448             return file.lastIndexOf('/');
449         }
450     }
451 }