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 final Integer cveValidForHours = cli.getCveValidForHours();
283
284 if (propertiesFile != null) {
285 try {
286 Settings.mergeProperties(propertiesFile);
287 } catch (FileNotFoundException ex) {
288 LOGGER.error("Unable to load properties file '{}'", propertiesFile.getPath());
289 LOGGER.debug("", ex);
290 } catch (IOException ex) {
291 LOGGER.error("Unable to find properties file '{}'", propertiesFile.getPath());
292 LOGGER.debug("", ex);
293 }
294 }
295
296
297
298 final boolean nexusUsesProxy = cli.isNexusUsesProxy();
299 if (dataDirectory != null) {
300 Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDirectory);
301 } else if (System.getProperty("basedir") != null) {
302 final File dataDir = new File(System.getProperty("basedir"), "data");
303 Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
304 } else {
305 final File jarPath = new File(App.class.getProtectionDomain().getCodeSource().getLocation().getPath());
306 final File base = jarPath.getParentFile();
307 final String sub = Settings.getString(Settings.KEYS.DATA_DIRECTORY);
308 final File dataDir = new File(base, sub);
309 Settings.setString(Settings.KEYS.DATA_DIRECTORY, dataDir.getAbsolutePath());
310 }
311 Settings.setBoolean(Settings.KEYS.AUTO_UPDATE, autoUpdate);
312 Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_SERVER, proxyServer);
313 Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PORT, proxyPort);
314 Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_USERNAME, proxyUser);
315 Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD, proxyPass);
316 Settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
317 Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
318 Settings.setIntIfNotNull(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, cveValidForHours);
319
320
321 Settings.setBoolean(Settings.KEYS.ANALYZER_JAR_ENABLED, !cli.isJarDisabled());
322 Settings.setBoolean(Settings.KEYS.ANALYZER_ARCHIVE_ENABLED, !cli.isArchiveDisabled());
323 Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED, !cli.isPythonDistributionDisabled());
324 Settings.setBoolean(Settings.KEYS.ANALYZER_PYTHON_PACKAGE_ENABLED, !cli.isPythonPackageDisabled());
325 Settings.setBoolean(Settings.KEYS.ANALYZER_AUTOCONF_ENABLED, !cli.isAutoconfDisabled());
326 Settings.setBoolean(Settings.KEYS.ANALYZER_CMAKE_ENABLED, !cli.isCmakeDisabled());
327 Settings.setBoolean(Settings.KEYS.ANALYZER_NUSPEC_ENABLED, !cli.isNuspecDisabled());
328 Settings.setBoolean(Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED, !cli.isAssemblyDisabled());
329 Settings.setBoolean(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_ENABLED, !cli.isBundleAuditDisabled());
330 Settings.setBoolean(Settings.KEYS.ANALYZER_OPENSSL_ENABLED, !cli.isOpenSSLDisabled());
331 Settings.setBoolean(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED, !cli.isComposerDisabled());
332 Settings.setBoolean(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, !cli.isNodeJsDisabled());
333 Settings.setBoolean(Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED, !cli.isRubyGemspecDisabled());
334 Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, !cli.isCentralDisabled());
335 Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, !cli.isNexusDisabled());
336
337 Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH, cli.getPathToBundleAudit());
338 Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_NEXUS_URL, nexusUrl);
339 Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY, nexusUsesProxy);
340 Settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_NAME, databaseDriverName);
341 Settings.setStringIfNotEmpty(Settings.KEYS.DB_DRIVER_PATH, databaseDriverPath);
342 Settings.setStringIfNotEmpty(Settings.KEYS.DB_CONNECTION_STRING, connectionString);
343 Settings.setStringIfNotEmpty(Settings.KEYS.DB_USER, databaseUser);
344 Settings.setStringIfNotEmpty(Settings.KEYS.DB_PASSWORD, databasePassword);
345 Settings.setStringIfNotEmpty(Settings.KEYS.ADDITIONAL_ZIP_EXTENSIONS, additionalZipExtensions);
346 Settings.setStringIfNotEmpty(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH, pathToMono);
347 if (cveBase12 != null && !cveBase12.isEmpty()) {
348 Settings.setString(Settings.KEYS.CVE_SCHEMA_1_2, cveBase12);
349 Settings.setString(Settings.KEYS.CVE_SCHEMA_2_0, cveBase20);
350 Settings.setString(Settings.KEYS.CVE_MODIFIED_12_URL, cveMod12);
351 Settings.setString(Settings.KEYS.CVE_MODIFIED_20_URL, cveMod20);
352 }
353 }
354
355
356
357
358
359
360 private void prepareLogger(String verboseLog) {
361 final StaticLoggerBinder loggerBinder = StaticLoggerBinder.getSingleton();
362 final LoggerContext context = (LoggerContext) loggerBinder.getLoggerFactory();
363
364 final PatternLayoutEncoder encoder = new PatternLayoutEncoder();
365 encoder.setPattern("%d %C:%L%n%-5level - %msg%n");
366 encoder.setContext(context);
367 encoder.start();
368 final FileAppender fa = new FileAppender();
369 fa.setAppend(true);
370 fa.setEncoder(encoder);
371 fa.setContext(context);
372 fa.setFile(verboseLog);
373 final File f = new File(verboseLog);
374 String name = f.getName();
375 final int i = name.lastIndexOf('.');
376 if (i > 1) {
377 name = name.substring(0, i);
378 }
379 fa.setName(name);
380 fa.start();
381 final ch.qos.logback.classic.Logger rootLogger = context.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
382 rootLogger.addAppender(fa);
383 }
384
385
386
387
388
389
390
391
392
393 protected String ensureCanonicalPath(String path) {
394 String basePath = null;
395 String wildCards = null;
396 final String file = path.replace('\\', '/');
397 if (file.contains("*") || file.contains("?")) {
398
399 int pos = getLastFileSeparator(file);
400 if (pos < 0) {
401 return file;
402 }
403 pos += 1;
404 basePath = file.substring(0, pos);
405 wildCards = file.substring(pos);
406 } else {
407 basePath = file;
408 }
409
410 File f = new File(basePath);
411 try {
412 f = f.getCanonicalFile();
413 if (wildCards != null) {
414 f = new File(f, wildCards);
415 }
416 } catch (IOException ex) {
417 LOGGER.warn("Invalid path '{}' was provided.", path);
418 LOGGER.debug("Invalid path provided", ex);
419 }
420 return f.getAbsolutePath().replace('\\', '/');
421 }
422
423
424
425
426
427
428
429 private int getLastFileSeparator(String file) {
430 if (file.contains("*") || file.contains("?")) {
431 int p1 = file.indexOf('*');
432 int p2 = file.indexOf('?');
433 p1 = p1 > 0 ? p1 : file.length();
434 p2 = p2 > 0 ? p2 : file.length();
435 int pos = p1 < p2 ? p1 : p2;
436 pos = file.lastIndexOf('/', pos);
437 return pos;
438 } else {
439 return file.lastIndexOf('/');
440 }
441 }
442 }