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