1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.analyzer;
19
20 import org.apache.commons.jcs3.access.exception.CacheException;
21 import org.owasp.dependencycheck.Engine;
22 import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
23 import org.owasp.dependencycheck.analyzer.exception.UnexpectedAnalysisException;
24 import org.owasp.dependencycheck.data.cache.DataCache;
25 import org.owasp.dependencycheck.data.cache.DataCacheFactory;
26 import org.owasp.dependencycheck.data.central.CentralSearch;
27 import org.owasp.dependencycheck.data.nexus.MavenArtifact;
28 import org.owasp.dependencycheck.dependency.Confidence;
29 import org.owasp.dependencycheck.dependency.Dependency;
30 import org.owasp.dependencycheck.dependency.Evidence;
31 import org.owasp.dependencycheck.dependency.EvidenceType;
32 import org.owasp.dependencycheck.exception.InitializationException;
33 import org.owasp.dependencycheck.utils.DownloadFailedException;
34 import org.owasp.dependencycheck.utils.Downloader;
35 import org.owasp.dependencycheck.utils.FileFilterBuilder;
36 import org.owasp.dependencycheck.utils.FileUtils;
37 import org.owasp.dependencycheck.utils.InvalidSettingException;
38 import org.owasp.dependencycheck.utils.ResourceNotFoundException;
39 import org.owasp.dependencycheck.utils.Settings;
40 import org.owasp.dependencycheck.utils.TooManyRequestsException;
41 import org.owasp.dependencycheck.xml.pom.Model;
42 import org.owasp.dependencycheck.xml.pom.PomUtils;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import javax.annotation.concurrent.ThreadSafe;
47 import java.io.File;
48 import java.io.FileFilter;
49 import java.io.FileNotFoundException;
50 import java.io.IOException;
51 import java.net.MalformedURLException;
52 import java.net.URL;
53 import java.text.MessageFormat;
54 import java.util.List;
55
56
57
58
59
60
61
62 @ThreadSafe
63 public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
64
65
66
67
68 private static final Logger LOGGER = LoggerFactory.getLogger(CentralAnalyzer.class);
69
70
71
72
73 private static final String ANALYZER_NAME = "Central Analyzer";
74
75
76
77
78 private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
79
80
81
82
83 private static final String SUPPORTED_EXTENSIONS = "jar";
84
85
86
87
88 private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(SUPPORTED_EXTENSIONS).build();
89
90
91
92
93 private static final int BASE_RETRY_WAIT = 1500;
94
95
96
97
98
99 private static int numberOfRetries = 7;
100
101
102
103
104 private CentralSearch searcher;
105
106
107
108 private DataCache<Model> cache;
109
110
111
112
113
114
115 @Override
116 public synchronized void initialize(Settings settings) {
117 super.initialize(settings);
118 setEnabled(checkEnabled());
119 numberOfRetries = getSettings().getInt(Settings.KEYS.ANALYZER_CENTRAL_RETRY_COUNT, numberOfRetries);
120 if (settings.getBoolean(Settings.KEYS.ANALYZER_CENTRAL_USE_CACHE, true)) {
121 try {
122 final DataCacheFactory factory = new DataCacheFactory(settings);
123 cache = factory.getPomCache();
124 } catch (CacheException ex) {
125 settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_USE_CACHE, false);
126 LOGGER.debug("Error creating cache, disabling caching", ex);
127 }
128 }
129 }
130
131
132
133
134
135
136
137 @Override
138 public boolean supportsParallelProcessing() {
139 return getSettings().getBoolean(Settings.KEYS.ANALYZER_CENTRAL_PARALLEL_ANALYSIS, true);
140 }
141
142
143
144
145
146
147
148 private boolean checkEnabled() {
149 try {
150 return getSettings().getBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED);
151 } catch (InvalidSettingException ise) {
152 LOGGER.warn("Invalid setting. Disabling the Central analyzer");
153 }
154 return false;
155 }
156
157
158
159
160
161
162
163 @Override
164 public void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
165 LOGGER.debug("Initializing Central analyzer");
166 LOGGER.debug("Central analyzer enabled: {}", isEnabled());
167 if (isEnabled()) {
168 try {
169 searcher = new CentralSearch(getSettings());
170 } catch (MalformedURLException ex) {
171 setEnabled(false);
172 throw new InitializationException("The configured URL to Maven Central is malformed", ex);
173 }
174 }
175 }
176
177
178
179
180
181
182 @Override
183 public String getName() {
184 return ANALYZER_NAME;
185 }
186
187
188
189
190
191
192
193 @Override
194 protected String getAnalyzerEnabledSettingKey() {
195 return Settings.KEYS.ANALYZER_CENTRAL_ENABLED;
196 }
197
198
199
200
201
202
203 @Override
204 public AnalysisPhase getAnalysisPhase() {
205 return ANALYSIS_PHASE;
206 }
207
208 @Override
209 protected FileFilter getFileFilter() {
210 return FILTER;
211 }
212
213
214
215
216
217
218
219
220 @Override
221 public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
222 for (Evidence e : dependency.getEvidence(EvidenceType.VENDOR)) {
223 if ("pom".equals(e.getSource())) {
224 return;
225 }
226 }
227 try {
228 final List<MavenArtifact> mas = fetchMavenArtifacts(dependency);
229 final Confidence confidence = mas.size() > 1 ? Confidence.HIGH : Confidence.HIGHEST;
230 for (MavenArtifact ma : mas) {
231 LOGGER.debug("Central analyzer found artifact ({}) for dependency ({})", ma, dependency.getFileName());
232 dependency.addAsEvidence("central", ma, confidence);
233
234 if (ma.getPomUrl() != null) {
235 File pomFile = null;
236 try {
237 final File baseDir = getSettings().getTempDirectory();
238 pomFile = File.createTempFile("pom", ".xml", baseDir);
239 if (!pomFile.delete()) {
240 LOGGER.warn("Unable to fetch pom.xml for {} from Central; "
241 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
242 LOGGER.debug("Unable to delete temp file");
243 }
244 final Downloader downloader = new Downloader(getSettings());
245 final int maxAttempts = this.getSettings().getInt(Settings.KEYS.ANALYZER_CENTRAL_RETRY_COUNT, 3);
246 int retryCount = 0;
247 long sleepingTimeBetweenRetriesInMillis = BASE_RETRY_WAIT;
248 boolean success = false;
249 Model model = null;
250 if (cache != null) {
251 model = cache.get(ma.getPomUrl());
252 }
253 if (model != null) {
254 success = true;
255 LOGGER.debug("Cache hit for {}", ma.getPomUrl());
256 } else {
257 LOGGER.debug("Downloading {}", ma.getPomUrl());
258 do {
259
260 try {
261 downloader.fetchFile(new URL(ma.getPomUrl()), pomFile);
262 success = true;
263 } catch (DownloadFailedException ex) {
264 try {
265 Thread.sleep(sleepingTimeBetweenRetriesInMillis);
266 } catch (InterruptedException ex1) {
267 Thread.currentThread().interrupt();
268 throw new UnexpectedAnalysisException(ex1);
269 }
270 sleepingTimeBetweenRetriesInMillis *= 2;
271 } catch (ResourceNotFoundException ex) {
272 LOGGER.debug("pom.xml does not exist in Central for {}", dependency.getFileName());
273 return;
274 }
275
276 } while (!success && retryCount++ < maxAttempts);
277 }
278 if (success) {
279 if (model == null) {
280 model = PomUtils.readPom(pomFile);
281 if (cache != null) {
282 cache.put(ma.getPomUrl(), model);
283 }
284 }
285 final boolean isMainPom = mas.size() == 1 || dependency.getActualFilePath().contains(ma.getVersion());
286 JarAnalyzer.setPomEvidence(dependency, model, null, isMainPom);
287 } else {
288 LOGGER.warn("Unable to download pom.xml for {} from Central; "
289 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
290 }
291
292 } catch (AnalysisException ex) {
293 LOGGER.warn(MessageFormat.format("Unable to analyze pom.xml for {0} from Central; "
294 + "this could result in undetected CPE/CVEs.", dependency.getFileName()), ex);
295
296 } finally {
297 if (pomFile != null && pomFile.exists() && !FileUtils.delete(pomFile)) {
298 LOGGER.debug("Failed to delete temporary pom file {}", pomFile);
299 pomFile.deleteOnExit();
300 }
301 }
302 }
303 }
304 } catch (TooManyRequestsException tre) {
305 this.setEnabled(false);
306 final String message = "Connections to Central search refused. Analysis failed.";
307 LOGGER.error(message, tre);
308 throw new AnalysisException(message, tre);
309 } catch (IllegalArgumentException iae) {
310 LOGGER.info("invalid sha1-hash on {}", dependency.getFileName());
311 } catch (FileNotFoundException fnfe) {
312 LOGGER.debug("Artifact not found in repository: '{}", dependency.getFileName());
313 } catch (IOException ioe) {
314 final String message = "Could not connect to Central search. Analysis failed.";
315 LOGGER.error(message, ioe);
316 throw new AnalysisException(message, ioe);
317 }
318 }
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334 protected List<MavenArtifact> fetchMavenArtifacts(Dependency dependency) throws IOException, TooManyRequestsException {
335 IOException lastException = null;
336 long sleepingTimeBetweenRetriesInMillis = BASE_RETRY_WAIT;
337 int triesLeft = numberOfRetries;
338 while (triesLeft-- > 0) {
339 try {
340 return searcher.searchSha1(dependency.getSha1sum());
341 } catch (FileNotFoundException fnfe) {
342
343 throw fnfe;
344 } catch (IOException ioe) {
345 LOGGER.debug("Could not connect to Central search (tries left: {}): {}",
346 triesLeft, ioe.getMessage());
347 lastException = ioe;
348
349 if (triesLeft > 0) {
350 try {
351 Thread.sleep(sleepingTimeBetweenRetriesInMillis);
352 } catch (InterruptedException e) {
353 Thread.currentThread().interrupt();
354 throw new UnexpectedAnalysisException(e);
355 }
356 sleepingTimeBetweenRetriesInMillis *= 2;
357 }
358 }
359 }
360
361 final String message = "Finally failed connecting to Central search."
362 + " Giving up after " + numberOfRetries + " tries.";
363 throw new IOException(message, lastException);
364 }
365
366
367
368
369
370
371 protected void setCentralSearch(CentralSearch searcher) {
372 this.searcher = searcher;
373 }
374 }