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.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
45
46
47
48 public class App {
49
50
51
52
53 private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
54
55
56
57
58
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
72
73
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
135
136
137
138
139
140
141
142
143
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
179
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
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
256
257
258
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
297
298
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
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
358
359
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
388
389
390
391
392
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
426
427
428
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 }