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