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