1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.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.owasp.dependencycheck.data.update.exception.UpdateException;
41 import org.owasp.dependencycheck.exception.ExceptionCollection;
42 import org.owasp.dependencycheck.exception.ReportException;
43 import org.owasp.dependencycheck.utils.InvalidSettingException;
44 import org.slf4j.impl.StaticLoggerBinder;
45
46
47
48
49
50
51 public class App {
52
53
54
55
56 private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
57
58
59
60
61
62
63 public static void main(String[] args) {
64 int exitCode = 0;
65 try {
66 Settings.initialize();
67 final App app = new App();
68 exitCode = app.run(args);
69 LOGGER.debug("Exit code: " + exitCode);
70 } finally {
71 Settings.cleanup(true);
72 }
73 System.exit(exitCode);
74 }
75
76
77
78
79
80
81
82 public int run(String[] args) {
83 int exitCode = 0;
84 final CliParser cli = new CliParser();
85
86 try {
87 cli.parse(args);
88 } catch (FileNotFoundException ex) {
89 System.err.println(ex.getMessage());
90 cli.printHelp();
91 return -1;
92 } catch (ParseException ex) {
93 System.err.println(ex.getMessage());
94 cli.printHelp();
95 return -2;
96 }
97
98 if (cli.getVerboseLog() != null) {
99 prepareLogger(cli.getVerboseLog());
100 }
101
102 if (cli.isPurge()) {
103 if (cli.getConnectionString() != null) {
104 LOGGER.error("Unable to purge the database when using a non-default connection string");
105 exitCode = -3;
106 } else {
107 try {
108 populateSettings(cli);
109 } catch (InvalidSettingException ex) {
110 LOGGER.error(ex.getMessage());
111 LOGGER.debug("Error loading properties file", ex);
112 exitCode = -4;
113 }
114 File db;
115 try {
116 db = new File(Settings.getDataDirectory(), "dc.h2.db");
117 if (db.exists()) {
118 if (db.delete()) {
119 LOGGER.info("Database file purged; local copy of the NVD has been removed");
120 } else {
121 LOGGER.error("Unable to delete '{}'; please delete the file manually", db.getAbsolutePath());
122 exitCode = -5;
123 }
124 } else {
125 LOGGER.error("Unable to purge database; the database file does not exists: {}", db.getAbsolutePath());
126 exitCode = -6;
127 }
128 } catch (IOException ex) {
129 LOGGER.error("Unable to delete the database");
130 exitCode = -7;
131 }
132 }
133 } else if (cli.isGetVersion()) {
134 cli.printVersionInfo();
135 } else if (cli.isUpdateOnly()) {
136 try {
137 populateSettings(cli);
138 } catch (InvalidSettingException ex) {
139 LOGGER.error(ex.getMessage());
140 LOGGER.debug("Error loading properties file", ex);
141 exitCode = -4;
142 }
143 try {
144 runUpdateOnly();
145 } catch (UpdateException ex) {
146 LOGGER.error(ex.getMessage());
147 exitCode = -8;
148 } catch (DatabaseException ex) {
149 LOGGER.error(ex.getMessage());
150 exitCode = -9;
151 }
152 } else if (cli.isRunScan()) {
153 try {
154 populateSettings(cli);
155 } catch (InvalidSettingException ex) {
156 LOGGER.error(ex.getMessage());
157 LOGGER.debug("Error loading properties file", ex);
158 exitCode = -4;
159 }
160 try {
161 final String[] scanFiles = cli.getScanFiles();
162 if (scanFiles != null) {
163 runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getProjectName(), scanFiles,
164 cli.getExcludeList(), cli.getSymLinkDepth());
165 } else {
166 LOGGER.error("No scan files configured");
167 }
168 } catch (InvalidScanPathException ex) {
169 LOGGER.error("An invalid scan path was detected; unable to scan '//*' paths");
170 exitCode = -10;
171 } catch (DatabaseException ex) {
172 LOGGER.error(ex.getMessage());
173 exitCode = -11;
174 } catch (ReportException ex) {
175 LOGGER.error(ex.getMessage());
176 exitCode = -12;
177 } catch (ExceptionCollection ex) {
178 if (ex.isFatal()) {
179 exitCode = -13;
180 LOGGER.error("One or more fatal errors occured");
181 } else {
182 exitCode = -14;
183 }
184 for (Throwable e : ex.getExceptions()) {
185 LOGGER.error(e.getMessage());
186 }
187 }
188 } else {
189 cli.printHelp();
190 }
191 return exitCode;
192 }
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215 private void runScan(String reportDirectory, String outputFormat, String applicationName, String[] files,
216 String[] excludes, int symLinkDepth) throws InvalidScanPathException, DatabaseException, ExceptionCollection, ReportException {
217 Engine engine = null;
218 try {
219 engine = new Engine();
220 final List<String> antStylePaths = new ArrayList<String>();
221 for (String file : files) {
222 final String antPath = ensureCanonicalPath(file);
223 antStylePaths.add(antPath);
224 }
225
226 final Set<File> paths = new HashSet<File>();
227 for (String file : antStylePaths) {
228 LOGGER.debug("Scanning {}", file);
229 final DirectoryScanner scanner = new DirectoryScanner();
230 String include = file.replace('\\', '/');
231 File baseDir;
232
233 if (include.startsWith("//")) {
234 throw new InvalidScanPathException("Unable to scan paths specified by //");
235 } else {
236 final int pos = getLastFileSeparator(include);
237 final String tmpBase = include.substring(0, pos);
238 final String tmpInclude = include.substring(pos + 1);
239 if (tmpInclude.indexOf('*') >= 0 || tmpInclude.indexOf('?') >= 0
240 || (new File(include)).isFile()) {
241 baseDir = new File(tmpBase);
242 include = tmpInclude;
243 } else {
244 baseDir = new File(tmpBase, tmpInclude);
245 include = "**/*";
246 }
247 }
248 scanner.setBasedir(baseDir);
249 final String[] includes = {include};
250 scanner.setIncludes(includes);
251 scanner.setMaxLevelsOfSymlinks(symLinkDepth);
252 if (symLinkDepth <= 0) {
253 scanner.setFollowSymlinks(false);
254 }
255 if (excludes != null && excludes.length > 0) {
256 scanner.addExcludes(excludes);
257 }
258 scanner.scan();
259 if (scanner.getIncludedFilesCount() > 0) {
260 for (String s : scanner.getIncludedFiles()) {
261 final File f = new File(baseDir, s);
262 LOGGER.debug("Found file {}", f.toString());
263 paths.add(f);
264 }
265 }
266 }
267 engine.scan(paths);
268
269 ExceptionCollection exCol = null;
270 try {
271 engine.analyzeDependencies();
272 } catch (ExceptionCollection ex) {
273 if (ex.isFatal()) {
274 throw ex;
275 }
276 exCol = ex;
277 }
278 final List<Dependency> dependencies = engine.getDependencies();
279 DatabaseProperties prop = null;
280 CveDB cve = null;
281 try {
282 cve = new CveDB();
283 cve.open();
284 prop = cve.getDatabaseProperties();
285 } finally {
286 if (cve != null) {
287 cve.close();
288 }
289 }
290 final ReportGenerator report = new ReportGenerator(applicationName, dependencies, engine.getAnalyzers(), prop);
291 try {
292 report.generateReports(reportDirectory, outputFormat);
293 } catch (ReportException ex) {
294 if (exCol != null) {
295 exCol.addException(ex);
296 throw exCol;
297 } else {
298 throw ex;
299 }
300 }
301 if (exCol != null && exCol.getExceptions().size() > 0) {
302 throw exCol;
303 }
304 } finally {
305 if (engine != null) {
306 engine.cleanup();
307 }
308 }
309
310 }
311
312
313
314
315
316
317
318
319 private void runUpdateOnly() throws UpdateException, DatabaseException {
320 Engine engine = null;
321 try {
322 engine = new Engine();
323 engine.doUpdates();
324 } finally {
325 if (engine != null) {
326 engine.cleanup();
327 }
328 }
329 }
330
331
332
333
334
335
336
337
338
339
340 private void populateSettings(CliParser cli) throws InvalidSettingException {
341 final boolean autoUpdate = cli.isAutoUpdate();
342 final String connectionTimeout = cli.getConnectionTimeout();
343 final String proxyServer = cli.getProxyServer();
344 final String proxyPort = cli.getProxyPort();
345 final String proxyUser = cli.getProxyUsername();
346 final String proxyPass = cli.getProxyPassword();
347 final String dataDirectory = cli.getDataDirectory();
348 final File propertiesFile = cli.getPropertiesFile();
349 final String suppressionFile = cli.getSuppressionFile();
350 final String nexusUrl = cli.getNexusUrl();
351 final String databaseDriverName = cli.getDatabaseDriverName();
352 final String databaseDriverPath = cli.getDatabaseDriverPath();
353 final String connectionString = cli.getConnectionString();
354 final String databaseUser = cli.getDatabaseUser();
355 final String databasePassword = cli.getDatabasePassword();
356 final String additionalZipExtensions = cli.getAdditionalZipExtensions();
357 final String pathToMono = cli.getPathToMono();
358 final String cveMod12 = cli.getModifiedCve12Url();
359 final String cveMod20 = cli.getModifiedCve20Url();
360 final String cveBase12 = cli.getBaseCve12Url();
361 final String cveBase20 = cli.getBaseCve20Url();
362 final Integer cveValidForHours = cli.getCveValidForHours();
363 final boolean experimentalEnabled = cli.isExperimentalEnabled();
364
365 if (propertiesFile != null) {
366 try {
367 Settings.mergeProperties(propertiesFile);
368 } catch (FileNotFoundException ex) {
369 throw new InvalidSettingException("Unable to find properties file '" + propertiesFile.getPath() + "'", ex);
370 } catch (IOException ex) {
371 throw new InvalidSettingException("Error reading properties file '" + propertiesFile.getPath() + "'", ex);
372 }
373 }
374
375
376
377 final boolean nexusUsesProxy = cli.isNexusUsesProxy();
378 if (dataDirectory != null) {
379 Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
380 } else if (System.getProperty("basedir") != null) {
381 final File dataDir = new File(System.getProperty("basedir"), "data");
382 Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
383 } else {
384 final File jarPath = new File(App.class.getProtectionDomain().getCodeSource().getLocation().getPath());
385 final File base = jarPath.getParentFile();
386 final String sub = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
387 final File dataDir = new File(base, sub);
388 Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
389 }
390 Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
391 Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_SERVER, proxyServer);
392 Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PORT, proxyPort);
393 Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_USERNAME, proxyUser);
394 Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD, proxyPass);
395 Settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
396 Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
397 Settings.setIntIfNotNull(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, cveValidForHours);
398
399
400 Settings.setBoolean(Settings.KEYS.ANALYZER_EXPERIMENTAL_ENABLED, experimentalEnabled);
401 Settings.setBoolean(Settings.KEYS.ANALYZER_JAR_ENABLED, !cli.isJarDisabled());
402 Settings.setBoolean(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, !cli.isArchiveDisabled());
403 Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED, !cli.isPythonDistributionDisabled());
404 Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED, !cli.isPythonPackageDisabled());
405 Settings.setBoolean(Settings.KEYS.ANALYZER_AUTOCONF_ENABLED, !cli.isAutoconfDisabled());
406 Settings.setBoolean(Settings.KEYS.ANALYZER_CMAKE_ENABLED, !cli.isCmakeDisabled());
407 Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, !cli.isNuspecDisabled());
408 Settings.setBoolean(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED, !cli.isAssemblyDisabled());
409 Settings.setBoolean(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_ENABLED, !cli.isBundleAuditDisabled());
410 Settings.setBoolean(Settings.KEYS.ANALYZER_OPENSSL_ENABLED, !cli.isOpenSSLDisabled());
411 Settings.setBoolean(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED, !cli.isComposerDisabled());
412 Settings.setBoolean(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, !cli.isNodeJsDisabled());
413 Settings.setBoolean(Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED, !cli.isRubyGemspecDisabled());
414 Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, !cli.isCentralDisabled());
415 Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, !cli.isNexusDisabled());
416
417 Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH, cli.getPathToBundleAudit());
418 Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
419 Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY, nexusUsesProxy);
420 Settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
421 Settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
422 Settings.setStringIfNotEmpty(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
423 Settings.setStringIfNotEmpty(Settings.KEYS.DB_USER, databaseUser);
424 Settings.setStringIfNotEmpty(Settings.KEYS.DB_PASSWORD, databasePassword);
425 Settings.setStringIfNotEmpty(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, additionalZipExtensions);
426 Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH, pathToMono);
427 if (cveBase12 != null && !cveBase12.isEmpty()) {
428 Settings.setString(Settings.KEYS.CVE_SCHEMA_1_2, cveBase12);
429 Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, cveBase20);
430 Settings.setString(Settings.KEYS.CVE_MODIFIED_12_URL, cveMod12);
431 Settings.setString(Settings.KEYS.CVE_MODIFIED_20_URL, cveMod20);
432 }
433 }
434
435
436
437
438
439
440 private void prepareLogger(String verboseLog) {
441 final StaticLoggerBinder loggerBinder = StaticLoggerBinder.getSingleton();
442 final LoggerContext context = (LoggerContext) loggerBinder.getLoggerFactory();
443
444 final PatternLayoutEncoder encoder = new PatternLayoutEncoder();
445 encoder.setPattern("%d %C:%L%n%-5level - %msg%n");
446 encoder.setContext(context);
447 encoder.start();
448 final FileAppender fa = new FileAppender();
449 fa.setAppend(true);
450 fa.setEncoder(encoder);
451 fa.setContext(context);
452 fa.setFile(verboseLog);
453 final File f = new File(verboseLog);
454 String name = f.getName();
455 final int i = name.lastIndexOf('.');
456 if (i > 1) {
457 name = name.substring(0, i);
458 }
459 fa.setName(name);
460 fa.start();
461 final ch.qos.logback.classic.Logger rootLogger = context.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
462 rootLogger.addAppender(fa);
463 }
464
465
466
467
468
469
470
471
472
473
474 protected String ensureCanonicalPath(String path) {
475 String basePath;
476 String wildCards = null;
477 final String file = path.replace('\\', '/');
478 if (file.contains("*") || file.contains("?")) {
479
480 int pos = getLastFileSeparator(file);
481 if (pos < 0) {
482 return file;
483 }
484 pos += 1;
485 basePath = file.substring(0, pos);
486 wildCards = file.substring(pos);
487 } else {
488 basePath = file;
489 }
490
491 File f = new File(basePath);
492 try {
493 f = f.getCanonicalFile();
494 if (wildCards != null) {
495 f = new File(f, wildCards);
496 }
497 } catch (IOException ex) {
498 LOGGER.warn("Invalid path '{}' was provided.", path);
499 LOGGER.debug("Invalid path provided", ex);
500 }
501 return f.getAbsolutePath().replace('\\', '/');
502 }
503
504
505
506
507
508
509
510 private int getLastFileSeparator(String file) {
511 if (file.contains("*") || file.contains("?")) {
512 int p1 = file.indexOf('*');
513 int p2 = file.indexOf('?');
514 p1 = p1 > 0 ? p1 : file.length();
515 p2 = p2 > 0 ? p2 : file.length();
516 int pos = p1 < p2 ? p1 : p2;
517 pos = file.lastIndexOf('/', pos);
518 return pos;
519 } else {
520 return file.lastIndexOf('/');
521 }
522 }
523 }