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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
21 import org.apache.commons.jcs3.JCS;
22 import org.jetbrains.annotations.NotNull;
23 import org.jetbrains.annotations.Nullable;
24 import org.owasp.dependencycheck.analyzer.AnalysisPhase;
25 import org.owasp.dependencycheck.analyzer.Analyzer;
26 import org.owasp.dependencycheck.analyzer.AnalyzerService;
27 import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer;
28 import org.owasp.dependencycheck.data.nvdcve.DatabaseManager;
29 import org.owasp.dependencycheck.data.nvdcve.CveDB;
30 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
31 import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
32 import org.owasp.dependencycheck.data.update.CachedWebDataSource;
33 import org.owasp.dependencycheck.data.update.UpdateService;
34 import org.owasp.dependencycheck.data.update.exception.UpdateException;
35 import org.owasp.dependencycheck.dependency.Dependency;
36 import org.owasp.dependencycheck.exception.ExceptionCollection;
37 import org.owasp.dependencycheck.exception.InitializationException;
38 import org.owasp.dependencycheck.exception.NoDataException;
39 import org.owasp.dependencycheck.exception.ReportException;
40 import org.owasp.dependencycheck.exception.WriteLockException;
41 import org.owasp.dependencycheck.reporting.ReportGenerator;
42 import org.owasp.dependencycheck.utils.FileUtils;
43 import org.owasp.dependencycheck.utils.Settings;
44 import org.owasp.dependencycheck.utils.WriteLock;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import javax.annotation.concurrent.NotThreadSafe;
49 import java.io.File;
50 import java.io.FileFilter;
51 import java.io.IOException;
52 import java.nio.file.Files;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collection;
56 import java.util.Collections;
57 import java.util.EnumMap;
58 import java.util.HashMap;
59 import java.util.HashSet;
60 import java.util.Iterator;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Objects;
64 import java.util.Set;
65 import java.util.concurrent.CancellationException;
66 import java.util.concurrent.ExecutionException;
67 import java.util.concurrent.ExecutorService;
68 import java.util.concurrent.Executors;
69 import java.util.concurrent.Future;
70 import java.util.concurrent.TimeUnit;
71
72 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINAL;
73 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINDING_ANALYSIS;
74 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.FINDING_ANALYSIS_PHASE2;
75 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.IDENTIFIER_ANALYSIS;
76 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INFORMATION_COLLECTION;
77 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INFORMATION_COLLECTION2;
78 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.INITIAL;
79 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_FINDING_ANALYSIS;
80 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_IDENTIFIER_ANALYSIS;
81 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION1;
82 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION2;
83 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.POST_INFORMATION_COLLECTION3;
84 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_FINDING_ANALYSIS;
85 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_IDENTIFIER_ANALYSIS;
86 import static org.owasp.dependencycheck.analyzer.AnalysisPhase.PRE_INFORMATION_COLLECTION;
87 import org.owasp.dependencycheck.analyzer.DependencyBundlingAnalyzer;
88 import org.owasp.dependencycheck.dependency.naming.Identifier;
89 import org.owasp.dependencycheck.utils.Utils;
90
91
92
93
94
95
96
97
98
99 @NotThreadSafe
100 public class Engine implements FileFilter, AutoCloseable {
101
102
103
104
105 private static final Logger LOGGER = LoggerFactory.getLogger(Engine.class);
106
107
108
109 private final List<Dependency> dependencies = Collections.synchronizedList(new ArrayList<>());
110
111
112
113 private final Map<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<>(AnalysisPhase.class);
114
115
116
117 private final Set<FileTypeAnalyzer> fileTypeAnalyzers = new HashSet<>();
118
119
120
121
122 private final Mode mode;
123
124
125
126
127 private final ClassLoader serviceClassLoader;
128
129
130
131 private final Settings settings;
132
133
134
135 private final Map<String, Object> objects = new HashMap<>();
136
137
138
139 private Dependency[] dependenciesExternalView = null;
140
141
142
143 private CveDB database = null;
144
145
146
147
148
149
150 private final String accessExternalSchema;
151
152
153
154
155
156
157 public Engine(@NotNull final Settings settings) {
158 this(Mode.STANDALONE, settings);
159 }
160
161
162
163
164
165
166
167 public Engine(@NotNull final Mode mode, @NotNull final Settings settings) {
168 this(Thread.currentThread().getContextClassLoader(), mode, settings);
169 }
170
171
172
173
174
175
176
177 public Engine(@NotNull final ClassLoader serviceClassLoader, @NotNull final Settings settings) {
178 this(serviceClassLoader, Mode.STANDALONE, settings);
179 }
180
181
182
183
184
185
186
187
188 public Engine(@NotNull final ClassLoader serviceClassLoader, @NotNull final Mode mode, @NotNull final Settings settings) {
189 this.settings = settings;
190 this.serviceClassLoader = serviceClassLoader;
191 this.mode = mode;
192 this.accessExternalSchema = System.getProperty("javax.xml.accessExternalSchema");
193
194 checkRuntimeVersion();
195
196 initializeEngine();
197 }
198
199
200
201
202
203
204
205
206 protected final void initializeEngine() {
207 loadAnalyzers();
208 }
209
210
211
212
213 @Override
214 public void close() {
215 if (mode.isDatabaseRequired()) {
216 if (database != null) {
217 database.close();
218 database = null;
219 }
220 }
221 if (accessExternalSchema != null) {
222 System.setProperty("javax.xml.accessExternalSchema", accessExternalSchema);
223 } else {
224 System.clearProperty("javax.xml.accessExternalSchema");
225 }
226 JCS.shutdown();
227 }
228
229
230
231
232
233 private void loadAnalyzers() {
234 if (!analyzers.isEmpty()) {
235 return;
236 }
237 mode.getPhases().forEach((phase) -> analyzers.put(phase, new ArrayList<>()));
238 final AnalyzerService service = new AnalyzerService(serviceClassLoader, settings);
239 final List<Analyzer> iterator = service.getAnalyzers(mode.getPhases());
240 iterator.forEach((a) -> {
241 a.initialize(this.settings);
242 analyzers.get(a.getAnalysisPhase()).add(a);
243 if (a instanceof FileTypeAnalyzer) {
244 this.fileTypeAnalyzers.add((FileTypeAnalyzer) a);
245 }
246 });
247 }
248
249
250
251
252
253
254
255 public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
256 return analyzers.get(phase);
257 }
258
259
260
261
262
263
264
265
266 public synchronized void addDependency(Dependency dependency) {
267 if (dependency.isVirtual()) {
268 for (Dependency existing : dependencies) {
269 if (existing.isVirtual()
270 && existing.getSha256sum() != null
271 && existing.getSha256sum().equals(dependency.getSha256sum())
272 && existing.getDisplayFileName() != null
273 && existing.getDisplayFileName().equals(dependency.getDisplayFileName())
274 && identifiersMatch(existing.getSoftwareIdentifiers(), dependency.getSoftwareIdentifiers())) {
275 DependencyBundlingAnalyzer.mergeDependencies(existing, dependency, null);
276 return;
277 }
278 }
279 }
280 dependencies.add(dependency);
281 dependenciesExternalView = null;
282 }
283
284
285
286
287 public synchronized void sortDependencies() {
288
289
290
291 }
292
293
294
295
296
297
298 public synchronized void removeDependency(@NotNull final Dependency dependency) {
299 dependencies.remove(dependency);
300 dependenciesExternalView = null;
301 }
302
303
304
305
306
307
308 @SuppressFBWarnings(justification = "This is the intended external view of the dependencies", value = {"EI_EXPOSE_REP"})
309 public synchronized Dependency[] getDependencies() {
310 if (dependenciesExternalView == null) {
311 dependenciesExternalView = dependencies.toArray(new Dependency[0]);
312 }
313 return dependenciesExternalView;
314 }
315
316
317
318
319
320
321 public synchronized void setDependencies(@NotNull final List<Dependency> dependencies) {
322 this.dependencies.clear();
323 this.dependencies.addAll(dependencies);
324 dependenciesExternalView = null;
325 }
326
327
328
329
330
331
332
333
334
335
336 public List<Dependency> scan(@NotNull final String[] paths) {
337 return scan(paths, null);
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351 public List<Dependency> scan(@NotNull final String[] paths, @Nullable final String projectReference) {
352 final List<Dependency> deps = new ArrayList<>();
353 for (String path : paths) {
354 final List<Dependency> d = scan(path, projectReference);
355 if (d != null) {
356 deps.addAll(d);
357 }
358 }
359 return deps;
360 }
361
362
363
364
365
366
367
368
369
370 public List<Dependency> scan(@NotNull final String path) {
371 return scan(path, null);
372 }
373
374
375
376
377
378
379
380
381
382
383
384
385 public List<Dependency> scan(@NotNull final String path, String projectReference) {
386 final File file = new File(path);
387 return scan(file, projectReference);
388 }
389
390
391
392
393
394
395
396
397
398
399 public List<Dependency> scan(File[] files) {
400 return scan(files, null);
401 }
402
403
404
405
406
407
408
409
410
411
412
413
414 public List<Dependency> scan(File[] files, String projectReference) {
415 final List<Dependency> deps = new ArrayList<>();
416 for (File file : files) {
417 final List<Dependency> d = scan(file, projectReference);
418 if (d != null) {
419 deps.addAll(d);
420 }
421 }
422 return deps;
423 }
424
425
426
427
428
429
430
431
432
433
434 public List<Dependency> scan(Collection<File> files) {
435 return scan(files, null);
436 }
437
438
439
440
441
442
443
444
445
446
447
448
449 public List<Dependency> scan(Collection<File> files, String projectReference) {
450 final List<Dependency> deps = new ArrayList<>();
451 files.stream().map((file) -> scan(file, projectReference))
452 .filter(Objects::nonNull)
453 .forEach(deps::addAll);
454 return deps;
455 }
456
457
458
459
460
461
462
463
464
465
466 public List<Dependency> scan(File file) {
467 return scan(file, null);
468 }
469
470
471
472
473
474
475
476
477
478
479
480
481 @Nullable
482 public List<Dependency> scan(@NotNull final File file, String projectReference) {
483 if (file.exists()) {
484 if (file.isDirectory()) {
485 return scanDirectory(file, projectReference);
486 } else {
487 final Dependency d = scanFile(file, projectReference);
488 if (d != null) {
489 final List<Dependency> deps = new ArrayList<>();
490 deps.add(d);
491 return deps;
492 }
493 }
494 }
495 return null;
496 }
497
498
499
500
501
502
503
504
505 protected List<Dependency> scanDirectory(File dir) {
506 return scanDirectory(dir, null);
507 }
508
509
510
511
512
513
514
515
516
517
518
519 protected List<Dependency> scanDirectory(@NotNull final File dir, @Nullable final String projectReference) {
520 final File[] files = dir.listFiles();
521 final List<Dependency> deps = new ArrayList<>();
522 if (files != null) {
523 for (File f : files) {
524 if (f.isDirectory()) {
525 final List<Dependency> d = scanDirectory(f, projectReference);
526 if (d != null) {
527 deps.addAll(d);
528 }
529 } else {
530 final Dependency d = scanFile(f, projectReference);
531 if (d != null) {
532 deps.add(d);
533 }
534 }
535 }
536 }
537 return deps;
538 }
539
540
541
542
543
544
545
546
547 protected Dependency scanFile(@NotNull final File file) {
548 return scanFile(file, null);
549 }
550
551
552
553
554
555
556
557
558
559
560
561
562 protected synchronized Dependency scanFile(@NotNull final File file, @Nullable final String projectReference) {
563 Dependency dependency = null;
564 if (file.isFile()) {
565 if (accept(file)) {
566 dependency = new Dependency(file);
567 if (projectReference != null) {
568 dependency.addProjectReference(projectReference);
569 }
570 final String sha1 = dependency.getSha1sum();
571 boolean found = false;
572
573 if (sha1 != null) {
574 for (Dependency existing : dependencies) {
575 if (sha1.equals(existing.getSha1sum())) {
576 if (existing.getDisplayFileName().contains(": ")
577 || dependency.getDisplayFileName().contains(": ")
578 || dependency.getActualFilePath().contains("dctemp")) {
579 continue;
580 }
581 found = true;
582 if (projectReference != null) {
583 existing.addProjectReference(projectReference);
584 }
585 if (existing.getActualFilePath() != null && dependency.getActualFilePath() != null
586 && !existing.getActualFilePath().equals(dependency.getActualFilePath())) {
587
588 if (DependencyBundlingAnalyzer.firstPathIsShortest(existing.getFilePath(), dependency.getFilePath())) {
589 DependencyBundlingAnalyzer.mergeDependencies(existing, dependency, null);
590
591
592 return existing;
593 } else {
594
595
596 found = false;
597 }
598
599 } else {
600
601 return existing;
602 }
603 break;
604 }
605 }
606 }
607 if (!found) {
608 dependencies.add(dependency);
609 dependenciesExternalView = null;
610 }
611 }
612 } else {
613 LOGGER.debug("Path passed to scanFile(File) is not a file that can be scanned by dependency-check: {}. Skipping the file.", file);
614 }
615 return dependency;
616 }
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634 public void analyzeDependencies() throws ExceptionCollection {
635 final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>());
636
637 initializeAndUpdateDatabase(exceptions);
638
639
640 try {
641 ensureDataExists();
642 } catch (NoDataException ex) {
643 throwFatalExceptionCollection("Unable to continue dependency-check analysis.", ex, exceptions);
644 }
645 LOGGER.info("\n\nDependency-Check is an open source tool performing a best effort analysis of 3rd party dependencies; false positives and "
646 + "false negatives may exist in the analysis performed by the tool. Use of the tool and the reporting provided constitutes "
647 + "acceptance for use in an AS IS condition, and there are NO warranties, implied or otherwise, with regard to the analysis "
648 + "or its use. Any use of the tool and the reporting provided is at the user's risk. In no event shall the copyright holder "
649 + "or OWASP be held liable for any damages whatsoever arising out of or in connection with the use of this tool, the analysis "
650 + "performed, or the resulting report.\n\n\n"
651 + " About ODC: https://jeremylong.github.io/DependencyCheck/general/internals.html\n"
652 + " False Positives: https://jeremylong.github.io/DependencyCheck/general/suppression.html\n"
653 + "\n"
654 + "💖 Sponsor: https://github.com/sponsors/jeremylong\n\n");
655 LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------");
656 LOGGER.info("Analysis Started");
657 final long analysisStart = System.currentTimeMillis();
658
659
660 for (AnalysisPhase phase : mode.getPhases()) {
661 final List<Analyzer> analyzerList = analyzers.get(phase);
662
663 for (final Analyzer analyzer : analyzerList) {
664 final long analyzerStart = System.currentTimeMillis();
665 try {
666 initializeAnalyzer(analyzer);
667 } catch (InitializationException ex) {
668 exceptions.add(ex);
669 if (ex.isFatal()) {
670 continue;
671 }
672 }
673
674 if (analyzer.isEnabled()) {
675 executeAnalysisTasks(analyzer, exceptions);
676
677 final long analyzerDurationMillis = System.currentTimeMillis() - analyzerStart;
678 final long analyzerDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(analyzerDurationMillis);
679 LOGGER.info("Finished {} ({} seconds)", analyzer.getName(), analyzerDurationSeconds);
680 } else {
681 LOGGER.debug("Skipping {} (not enabled)", analyzer.getName());
682 }
683 }
684 }
685 mode.getPhases().stream()
686 .map(analyzers::get)
687 .forEach((analyzerList) -> analyzerList.forEach(this::closeAnalyzer));
688
689 LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------");
690 final long analysisDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - analysisStart);
691 LOGGER.info("Analysis Complete ({} seconds)", analysisDurationSeconds);
692 if (exceptions.size() > 0) {
693 throw new ExceptionCollection(exceptions);
694 }
695 }
696
697
698
699
700
701
702
703 private void initializeAndUpdateDatabase(@NotNull final List<Throwable> exceptions) throws ExceptionCollection {
704 if (!mode.isDatabaseRequired()) {
705 return;
706 }
707 final boolean autoUpdate;
708 autoUpdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
709 if (autoUpdate) {
710 try {
711 doUpdates(true);
712 } catch (UpdateException ex) {
713 exceptions.add(ex);
714 LOGGER.warn("Unable to update 1 or more Cached Web DataSource, using local "
715 + "data instead. Results may not include recent vulnerabilities.");
716 LOGGER.debug("Update Error", ex);
717 } catch (DatabaseException ex) {
718 throwFatalDatabaseException(ex, exceptions);
719 }
720 } else {
721 try {
722 if (DatabaseManager.isH2Connection(settings) && !DatabaseManager.h2DataFileExists(settings)) {
723 throw new ExceptionCollection(new NoDataException("Autoupdate is disabled and the database does not exist"), true);
724 } else {
725 openDatabase(true, true);
726 }
727 } catch (IOException ex) {
728 throw new ExceptionCollection(new DatabaseException("Autoupdate is disabled and unable to connect to the database"), true);
729 } catch (DatabaseException ex) {
730 throwFatalDatabaseException(ex, exceptions);
731 }
732 }
733 }
734
735
736
737
738
739
740
741
742
743 private void throwFatalDatabaseException(DatabaseException ex, final List<Throwable> exceptions) throws ExceptionCollection {
744 final String msg;
745 if (ex.getMessage().contains("Unable to connect") && DatabaseManager.isH2Connection(settings)) {
746 msg = "Unable to connect to the database - if this error persists it may be "
747 + "due to a corrupt database. Consider running `purge` to delete the existing database";
748 } else {
749 msg = "Unable to connect to the dependency-check database";
750 }
751 exceptions.add(new DatabaseException(msg, ex));
752 throw new ExceptionCollection(exceptions, true);
753 }
754
755
756
757
758
759
760
761
762
763 protected void executeAnalysisTasks(@NotNull final Analyzer analyzer, List<Throwable> exceptions) throws ExceptionCollection {
764 LOGGER.debug("Starting {}", analyzer.getName());
765 final List<AnalysisTask> analysisTasks = getAnalysisTasks(analyzer, exceptions);
766 final ExecutorService executorService = getExecutorService(analyzer);
767
768 try {
769 final int timeout = settings.getInt(Settings.KEYS.ANALYSIS_TIMEOUT, 180);
770 final List<Future<Void>> results = executorService.invokeAll(analysisTasks, timeout, TimeUnit.MINUTES);
771
772
773 for (Future<Void> result : results) {
774 try {
775 result.get();
776 } catch (ExecutionException e) {
777 throwFatalExceptionCollection("Analysis task failed with a fatal exception.", e, exceptions);
778 } catch (CancellationException e) {
779 throwFatalExceptionCollection("Analysis task was cancelled.", e, exceptions);
780 }
781 }
782 } catch (InterruptedException e) {
783 Thread.currentThread().interrupt();
784 throwFatalExceptionCollection("Analysis has been interrupted.", e, exceptions);
785 } finally {
786 executorService.shutdown();
787 }
788 }
789
790
791
792
793
794
795
796
797 protected synchronized List<AnalysisTask> getAnalysisTasks(Analyzer analyzer, List<Throwable> exceptions) {
798 final List<AnalysisTask> result = new ArrayList<>();
799 dependencies.stream().map((dependency) -> new AnalysisTask(analyzer, dependency, this, exceptions)).forEach(result::add);
800 return result;
801 }
802
803
804
805
806
807
808
809 protected ExecutorService getExecutorService(Analyzer analyzer) {
810 if (analyzer.supportsParallelProcessing()) {
811 final int maximumNumberOfThreads = Runtime.getRuntime().availableProcessors();
812 LOGGER.debug("Parallel processing with up to {} threads: {}.", maximumNumberOfThreads, analyzer.getName());
813 return Executors.newFixedThreadPool(maximumNumberOfThreads);
814 } else {
815 LOGGER.debug("Parallel processing is not supported: {}.", analyzer.getName());
816 return Executors.newSingleThreadExecutor();
817 }
818 }
819
820
821
822
823
824
825
826
827 protected void initializeAnalyzer(@NotNull final Analyzer analyzer) throws InitializationException {
828 try {
829 LOGGER.debug("Initializing {}", analyzer.getName());
830 analyzer.prepare(this);
831 } catch (InitializationException ex) {
832 LOGGER.error("Exception occurred initializing {}.", analyzer.getName());
833 LOGGER.debug("", ex);
834 if (ex.isFatal()) {
835 try {
836 analyzer.close();
837 } catch (Throwable ex1) {
838 LOGGER.trace("", ex1);
839 }
840 }
841 throw ex;
842 } catch (Throwable ex) {
843 LOGGER.error("Unexpected exception occurred initializing {}.", analyzer.getName());
844 LOGGER.debug("", ex);
845 try {
846 analyzer.close();
847 } catch (Throwable ex1) {
848 LOGGER.trace("", ex1);
849 }
850 throw new InitializationException("Unexpected Exception", ex);
851 }
852 }
853
854
855
856
857
858
859 protected void closeAnalyzer(@NotNull final Analyzer analyzer) {
860 LOGGER.debug("Closing Analyzer '{}'", analyzer.getName());
861 try {
862 analyzer.close();
863 } catch (Throwable ex) {
864 LOGGER.trace("", ex);
865 }
866 }
867
868
869
870
871
872
873
874
875
876
877 public boolean doUpdates() throws UpdateException, DatabaseException {
878 return doUpdates(false);
879 }
880
881
882
883
884
885
886
887
888
889
890
891
892 public boolean doUpdates(boolean remainOpen) throws UpdateException, DatabaseException {
893 if (mode.isDatabaseRequired()) {
894 try (WriteLock dblock = new WriteLock(getSettings(), DatabaseManager.isH2Connection(getSettings()))) {
895
896 openDatabase(false, false);
897 LOGGER.info("Checking for updates");
898 final long updateStart = System.currentTimeMillis();
899 final UpdateService service = new UpdateService(serviceClassLoader);
900 final Iterator<CachedWebDataSource> iterator = service.getDataSources();
901 boolean dbUpdatesMade = false;
902 UpdateException updateException = null;
903 while (iterator.hasNext()) {
904 try {
905 final CachedWebDataSource source = iterator.next();
906 dbUpdatesMade |= source.update(this);
907 } catch (UpdateException ex) {
908 updateException = ex;
909 LOGGER.error(ex.getMessage(), ex);
910 }
911 }
912 if (dbUpdatesMade) {
913 database.defrag();
914 }
915 database.close();
916 database = null;
917 if (updateException != null) {
918 throw updateException;
919 }
920 LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart);
921 if (remainOpen) {
922
923 openDatabase(true, false);
924 }
925
926 return dbUpdatesMade;
927 } catch (WriteLockException ex) {
928 throw new UpdateException("Unable to obtain an exclusive lock on the H2 database to perform updates", ex);
929 }
930 } else {
931 LOGGER.info("Skipping update check in evidence collection mode.");
932 return false;
933 }
934 }
935
936
937
938
939
940
941
942 public boolean purge() {
943 boolean result = true;
944 final UpdateService service = new UpdateService(serviceClassLoader);
945 final Iterator<CachedWebDataSource> iterator = service.getDataSources();
946 while (iterator.hasNext()) {
947 result &= iterator.next().purge(this);
948 }
949 try {
950 final File cache = new File(settings.getDataDirectory(), "cache");
951 if (cache.exists()) {
952 if (FileUtils.delete(cache)) {
953 LOGGER.info("Cache directory purged");
954 }
955 }
956 } catch (IOException ex) {
957 throw new RuntimeException(ex);
958 }
959 try {
960 final File cache = new File(settings.getDataDirectory(), "oss_cache");
961 if (cache.exists()) {
962 if (FileUtils.delete(cache)) {
963 LOGGER.info("OSS Cache directory purged");
964 }
965 }
966 } catch (IOException ex) {
967 throw new RuntimeException(ex);
968 }
969
970 return result;
971 }
972
973
974
975
976
977
978
979
980
981
982
983 public void openDatabase() throws DatabaseException {
984 openDatabase(false, true);
985 }
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001 @SuppressWarnings("try")
1002 public void openDatabase(boolean readOnly, boolean lockRequired) throws DatabaseException {
1003 if (mode.isDatabaseRequired() && database == null) {
1004 try (WriteLock dblock = new WriteLock(getSettings(), lockRequired && DatabaseManager.isH2Connection(settings))) {
1005 if (readOnly
1006 && DatabaseManager.isH2Connection(settings)
1007 && settings.getString(Settings.KEYS.DB_CONNECTION_STRING).contains("file:%s")) {
1008 final File db = DatabaseManager.getH2DataFile(settings);
1009 if (db.isFile()) {
1010 final File temp = settings.getTempDirectory();
1011 final File tempDB = new File(temp, db.getName());
1012 LOGGER.debug("copying database {} to {}", db.toPath(), temp.toPath());
1013 Files.copy(db.toPath(), tempDB.toPath());
1014 settings.setString(Settings.KEYS.H2_DATA_DIRECTORY, temp.getPath());
1015 final String connStr = settings.getString(Settings.KEYS.DB_CONNECTION_STRING);
1016 if (!connStr.contains("ACCESS_MODE_DATA")) {
1017 settings.setString(Settings.KEYS.DB_CONNECTION_STRING, connStr + "ACCESS_MODE_DATA=r");
1018 }
1019 settings.setBoolean(Settings.KEYS.AUTO_UPDATE, false);
1020 database = new CveDB(settings);
1021 } else {
1022 throw new DatabaseException("Unable to open database - configured database file does not exist: " + db);
1023 }
1024 } else {
1025 database = new CveDB(settings);
1026 }
1027 } catch (IOException ex) {
1028 throw new DatabaseException("Unable to open database in read only mode", ex);
1029 } catch (WriteLockException ex) {
1030 throw new DatabaseException("Failed to obtain lock - unable to open database", ex);
1031 }
1032 database.open();
1033 }
1034 }
1035
1036
1037
1038
1039
1040
1041 public CveDB getDatabase() {
1042 return this.database;
1043 }
1044
1045
1046
1047
1048
1049
1050
1051 @NotNull
1052 public List<Analyzer> getAnalyzers() {
1053 final List<Analyzer> analyzerList = new ArrayList<>();
1054
1055 mode.getPhases().stream()
1056 .map(analyzers::get)
1057 .forEachOrdered(analyzerList::addAll);
1058 return analyzerList;
1059 }
1060
1061
1062
1063
1064
1065
1066
1067
1068 @Override
1069 public boolean accept(@Nullable final File file) {
1070 if (file == null) {
1071 return false;
1072 }
1073
1074
1075 return this.fileTypeAnalyzers.stream().map((a) -> a.accept(file)).reduce(false, (accumulator, result) -> accumulator || result);
1076 }
1077
1078
1079
1080
1081
1082
1083 public Set<FileTypeAnalyzer> getFileTypeAnalyzers() {
1084 return this.fileTypeAnalyzers;
1085 }
1086
1087
1088
1089
1090
1091
1092 public Settings getSettings() {
1093 return settings;
1094 }
1095
1096
1097
1098
1099
1100
1101
1102 public Object getObject(String key) {
1103 return objects.get(key);
1104 }
1105
1106
1107
1108
1109
1110
1111
1112 public void putObject(String key, Object object) {
1113 objects.put(key, object);
1114 }
1115
1116
1117
1118
1119
1120
1121
1122
1123 public boolean hasObject(String key) {
1124 return objects.containsKey(key);
1125 }
1126
1127
1128
1129
1130
1131
1132 public void removeObject(String key) {
1133 objects.remove(key);
1134 }
1135
1136
1137
1138
1139
1140
1141 public Mode getMode() {
1142 return mode;
1143 }
1144
1145
1146
1147
1148
1149
1150
1151 protected void addFileTypeAnalyzer(@NotNull final FileTypeAnalyzer fta) {
1152 this.fileTypeAnalyzers.add(fta);
1153 }
1154
1155
1156
1157
1158
1159
1160
1161 private void ensureDataExists() throws NoDataException {
1162 if (mode.isDatabaseRequired() && (database == null || !database.dataExists())) {
1163 throw new NoDataException("No documents exist");
1164 }
1165 }
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176 private void throwFatalExceptionCollection(String message, @NotNull final Throwable throwable,
1177 @NotNull final List<Throwable> exceptions) throws ExceptionCollection {
1178 LOGGER.error(message);
1179 LOGGER.debug("", throwable);
1180 exceptions.add(throwable);
1181 throw new ExceptionCollection(exceptions, true);
1182 }
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195 @Deprecated
1196 public void writeReports(String applicationName, File outputDir, String format) throws ReportException {
1197 writeReports(applicationName, null, null, null, outputDir, format, null);
1198 }
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212 public void writeReports(String applicationName, File outputDir, String format, ExceptionCollection exceptions) throws ReportException {
1213 writeReports(applicationName, null, null, null, outputDir, format, exceptions);
1214 }
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231 @Deprecated
1232 public synchronized void writeReports(String applicationName, @Nullable final String groupId,
1233 @Nullable final String artifactId, @Nullable final String version,
1234 @NotNull final File outputDir, String format) throws ReportException {
1235 writeReports(applicationName, groupId, artifactId, version, outputDir, format, null);
1236 }
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253 public synchronized void writeReports(String applicationName, @Nullable final String groupId,
1254 @Nullable final String artifactId, @Nullable final String version,
1255 @NotNull final File outputDir, String format, ExceptionCollection exceptions) throws ReportException {
1256 if (mode == Mode.EVIDENCE_COLLECTION) {
1257 throw new UnsupportedOperationException("Cannot generate report in evidence collection mode.");
1258 }
1259 final DatabaseProperties prop = database.getDatabaseProperties();
1260
1261 final ReportGenerator r = new ReportGenerator(applicationName, groupId, artifactId, version,
1262 dependencies, getAnalyzers(), prop, settings, exceptions);
1263 try {
1264 r.write(outputDir.getAbsolutePath(), format);
1265 } catch (ReportException ex) {
1266 final String msg = String.format("Error generating the report for %s", applicationName);
1267 LOGGER.debug(msg, ex);
1268 throw new ReportException(msg, ex);
1269 }
1270 }
1271
1272
1273 private boolean identifiersMatch(Set<Identifier> left, Set<Identifier> right) {
1274 if (left != null && right != null && left.size() > 0 && left.size() == right.size()) {
1275 int count = 0;
1276 for (Identifier l : left) {
1277 for (Identifier r : right) {
1278 if (l.getValue().equals(r.getValue())) {
1279 count += 1;
1280 break;
1281 }
1282 }
1283 }
1284 return count == left.size();
1285 }
1286 return false;
1287 }
1288
1289
1290
1291
1292
1293
1294
1295 private void checkRuntimeVersion() {
1296 if (Utils.getJavaVersion() == 8 && Utils.getJavaUpdateVersion() < 251) {
1297 LOGGER.error("Non-supported Java Runtime: dependency-check requires at least Java 8 update 251 or higher.");
1298 throw new RuntimeException("dependency-check requires Java 8 update 251 or higher");
1299 }
1300 }
1301
1302
1303
1304
1305 public enum Mode {
1306
1307
1308
1309
1310 EVIDENCE_COLLECTION(
1311 false,
1312 INITIAL,
1313 PRE_INFORMATION_COLLECTION,
1314 INFORMATION_COLLECTION,
1315 INFORMATION_COLLECTION2,
1316 POST_INFORMATION_COLLECTION1,
1317 POST_INFORMATION_COLLECTION2,
1318 POST_INFORMATION_COLLECTION3
1319 ),
1320
1321
1322
1323
1324
1325
1326 EVIDENCE_PROCESSING(
1327 true,
1328 PRE_IDENTIFIER_ANALYSIS,
1329 IDENTIFIER_ANALYSIS,
1330 POST_IDENTIFIER_ANALYSIS,
1331 PRE_FINDING_ANALYSIS,
1332 FINDING_ANALYSIS,
1333 POST_FINDING_ANALYSIS,
1334 FINDING_ANALYSIS_PHASE2,
1335 FINAL
1336 ),
1337
1338
1339
1340
1341 STANDALONE(true, AnalysisPhase.values());
1342
1343
1344
1345
1346 private final boolean databaseRequired;
1347
1348
1349
1350 private final List<AnalysisPhase> phases;
1351
1352
1353
1354
1355
1356
1357
1358 Mode(boolean databaseRequired, AnalysisPhase... phases) {
1359 this.databaseRequired = databaseRequired;
1360 this.phases = Collections.unmodifiableList(Arrays.asList(phases));
1361 }
1362
1363
1364
1365
1366
1367
1368 private boolean isDatabaseRequired() {
1369 return databaseRequired;
1370 }
1371
1372
1373
1374
1375
1376
1377 public List<AnalysisPhase> getPhases() {
1378 return phases;
1379 }
1380 }
1381 }