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 int maxAttempts = this.getSettings().getInt(Settings.KEYS.ANALYZER_CENTRAL_RETRY_COUNT, 3);
245 int retryCount = 0;
246 long sleepingTimeBetweenRetriesInMillis = BASE_RETRY_WAIT;
247 boolean success = false;
248 Model model = null;
249 if (cache != null) {
250 model = cache.get(ma.getPomUrl());
251 }
252 if (model != null) {
253 success = true;
254 LOGGER.debug("Cache hit for {}", ma.getPomUrl());
255 } else {
256 LOGGER.debug("Downloading {}", ma.getPomUrl());
257 do {
258
259 try {
260 Downloader.getInstance().fetchFile(new URL(ma.getPomUrl()), pomFile);
261 success = true;
262 } catch (DownloadFailedException ex) {
263 try {
264 Thread.sleep(sleepingTimeBetweenRetriesInMillis);
265 } catch (InterruptedException ex1) {
266 Thread.currentThread().interrupt();
267 throw new UnexpectedAnalysisException(ex1);
268 }
269 sleepingTimeBetweenRetriesInMillis *= 2;
270 } catch (ResourceNotFoundException ex) {
271 LOGGER.debug("pom.xml does not exist in Central for {}", dependency.getFileName());
272 return;
273 }
274
275 } while (!success && retryCount++ < maxAttempts);
276 }
277 if (success) {
278 if (model == null) {
279 model = PomUtils.readPom(pomFile);
280 if (cache != null) {
281 cache.put(ma.getPomUrl(), model);
282 }
283 }
284 final boolean isMainPom = mas.size() == 1 || dependency.getActualFilePath().contains(ma.getVersion());
285 JarAnalyzer.setPomEvidence(dependency, model, null, isMainPom);
286 } else {
287 LOGGER.warn("Unable to download pom.xml for {} from Central; "
288 + "this could result in undetected CPE/CVEs.", dependency.getFileName());
289 }
290
291 } catch (AnalysisException ex) {
292 LOGGER.warn(MessageFormat.format("Unable to analyze pom.xml for {0} from Central; "
293 + "this could result in undetected CPE/CVEs.", dependency.getFileName()), ex);
294
295 } finally {
296 if (pomFile != null && pomFile.exists() && !FileUtils.delete(pomFile)) {
297 LOGGER.debug("Failed to delete temporary pom file {}", pomFile);
298 pomFile.deleteOnExit();
299 }
300 }
301 }
302 }
303 } catch (TooManyRequestsException tre) {
304 this.setEnabled(false);
305 final String message = "Connections to Central search refused. Analysis failed.";
306 LOGGER.error(message, tre);
307 throw new AnalysisException(message, tre);
308 } catch (IllegalArgumentException iae) {
309 LOGGER.info("invalid sha1-hash on {}", dependency.getFileName());
310 } catch (FileNotFoundException fnfe) {
311 LOGGER.debug("Artifact not found in repository: '{}", dependency.getFileName());
312 } catch (IOException ioe) {
313 final String message = "Could not connect to Central search. Analysis failed.";
314 LOGGER.error(message, ioe);
315 throw new AnalysisException(message, ioe);
316 }
317 }
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333 protected List<MavenArtifact> fetchMavenArtifacts(Dependency dependency) throws IOException, TooManyRequestsException {
334 IOException lastException = null;
335 long sleepingTimeBetweenRetriesInMillis = BASE_RETRY_WAIT;
336 int triesLeft = numberOfRetries;
337 while (triesLeft-- > 0) {
338 try {
339 return searcher.searchSha1(dependency.getSha1sum());
340 } catch (FileNotFoundException fnfe) {
341
342 throw fnfe;
343 } catch (IOException ioe) {
344 LOGGER.debug("Could not connect to Central search (tries left: {}): {}",
345 triesLeft, ioe.getMessage());
346 lastException = ioe;
347
348 if (triesLeft > 0) {
349 try {
350 Thread.sleep(sleepingTimeBetweenRetriesInMillis);
351 } catch (InterruptedException e) {
352 Thread.currentThread().interrupt();
353 throw new UnexpectedAnalysisException(e);
354 }
355 sleepingTimeBetweenRetriesInMillis *= 2;
356 }
357 }
358 }
359
360 final String message = "Finally failed connecting to Central search."
361 + " Giving up after " + numberOfRetries + " tries.";
362 throw new IOException(message, lastException);
363 }
364
365
366
367
368
369
370 protected void setCentralSearch(CentralSearch searcher) {
371 this.searcher = searcher;
372 }
373 }