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