1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.data.update;
19
20 import java.net.MalformedURLException;
21 import java.util.Calendar;
22 import java.util.HashSet;
23 import java.util.Set;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.ExecutorService;
26 import java.util.concurrent.Executors;
27 import java.util.concurrent.Future;
28 import org.owasp.dependencycheck.data.nvdcve.CveDB;
29 import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
30 import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
31 import static org.owasp.dependencycheck.data.nvdcve.DatabaseProperties.MODIFIED;
32 import org.owasp.dependencycheck.data.update.exception.InvalidDataException;
33 import org.owasp.dependencycheck.data.update.exception.UpdateException;
34 import org.owasp.dependencycheck.data.update.nvd.DownloadTask;
35 import org.owasp.dependencycheck.data.update.nvd.NvdCveInfo;
36 import org.owasp.dependencycheck.data.update.nvd.ProcessTask;
37 import org.owasp.dependencycheck.data.update.nvd.UpdateableNvdCve;
38 import org.owasp.dependencycheck.utils.DateUtil;
39 import org.owasp.dependencycheck.utils.DownloadFailedException;
40 import org.owasp.dependencycheck.utils.InvalidSettingException;
41 import org.owasp.dependencycheck.utils.Settings;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46
47
48
49
50 public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
51
52
53
54
55 private static final Logger LOGGER = LoggerFactory.getLogger(NvdCveUpdater.class);
56
57
58
59 public static final int MAX_THREAD_POOL_SIZE = Settings.getInt(Settings.KEYS.MAX_DOWNLOAD_THREAD_POOL_SIZE, 3);
60
61
62
63
64
65
66
67
68
69 @Override
70 public void update() throws UpdateException {
71 try {
72 openDataStores();
73 boolean autoUpdate = true;
74 try {
75 autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
76 } catch (InvalidSettingException ex) {
77 LOGGER.debug("Invalid setting for auto-update; using true.");
78 }
79 if (autoUpdate && checkUpdate()) {
80 final UpdateableNvdCve updateable = getUpdatesNeeded();
81 getProperties().save(DatabaseProperties.LAST_CHECKED, Long.toString(System.currentTimeMillis()));
82 if (updateable.isUpdateNeeded()) {
83 performUpdate(updateable);
84 }
85 }
86 } catch (MalformedURLException ex) {
87 LOGGER.warn(
88 "NVD CVE properties files contain an invalid URL, unable to update the data to use the most current data.");
89 LOGGER.debug("", ex);
90 } catch (DownloadFailedException ex) {
91 LOGGER.warn(
92 "Unable to download the NVD CVE data; the results may not include the most recent CPE/CVEs from the NVD.");
93 if (Settings.getString(Settings.KEYS.PROXY_SERVER) == null) {
94 LOGGER.info(
95 "If you are behind a proxy you may need to configure dependency-check to use the proxy.");
96 }
97 LOGGER.debug("", ex);
98 } finally {
99 closeDataStores();
100 }
101 }
102
103
104
105
106
107
108
109
110
111
112
113
114 private boolean checkUpdate() throws UpdateException {
115 boolean proceed = true;
116
117 final int validForHours = Settings.getInt(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, 0);
118 if (dataExists() && 0 < validForHours) {
119
120 final long msValid = validForHours * 60L * 60L * 1000L;
121 final long lastChecked = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_CHECKED, "0"));
122 final long now = System.currentTimeMillis();
123 proceed = (now - lastChecked) > msValid;
124 if (!proceed) {
125 LOGGER.info("Skipping NVD check since last check was within {} hours.", validForHours);
126 LOGGER.debug("Last NVD was at {}, and now {} is within {} ms.",
127 lastChecked, now, msValid);
128 }
129 }
130 return proceed;
131 }
132
133
134
135
136
137
138 private boolean dataExists() {
139 CveDB cve = null;
140 try {
141 cve = new CveDB();
142 cve.open();
143 return cve.dataExists();
144 } catch (DatabaseException ex) {
145 return false;
146 } finally {
147 if (cve != null) {
148 cve.close();
149 }
150 }
151 }
152
153
154
155
156
157
158
159
160
161
162 public void performUpdate(UpdateableNvdCve updateable) throws UpdateException {
163 int maxUpdates = 0;
164 try {
165 for (NvdCveInfo cve : updateable) {
166 if (cve.getNeedsUpdate()) {
167 maxUpdates += 1;
168 }
169 }
170 if (maxUpdates <= 0) {
171 return;
172 }
173 if (maxUpdates > 3) {
174 LOGGER.info(
175 "NVD CVE requires several updates; this could take a couple of minutes.");
176 }
177 if (maxUpdates > 0) {
178 openDataStores();
179 }
180
181 final int poolSize = (MAX_THREAD_POOL_SIZE < maxUpdates) ? MAX_THREAD_POOL_SIZE : maxUpdates;
182
183 final ExecutorService downloadExecutors = Executors.newFixedThreadPool(poolSize);
184 final ExecutorService processExecutor = Executors.newSingleThreadExecutor();
185 final Set<Future<Future<ProcessTask>>> downloadFutures = new HashSet<Future<Future<ProcessTask>>>(maxUpdates);
186 for (NvdCveInfo cve : updateable) {
187 if (cve.getNeedsUpdate()) {
188 final DownloadTask call = new DownloadTask(cve, processExecutor, getCveDB(), Settings.getInstance());
189 downloadFutures.add(downloadExecutors.submit(call));
190 }
191 }
192 downloadExecutors.shutdown();
193
194
195 final Set<Future<ProcessTask>> processFutures = new HashSet<Future<ProcessTask>>(maxUpdates);
196 for (Future<Future<ProcessTask>> future : downloadFutures) {
197 Future<ProcessTask> task = null;
198 try {
199 task = future.get();
200 } catch (InterruptedException ex) {
201 downloadExecutors.shutdownNow();
202 processExecutor.shutdownNow();
203
204 LOGGER.debug("Thread was interrupted during download", ex);
205 throw new UpdateException("The download was interrupted", ex);
206 } catch (ExecutionException ex) {
207 downloadExecutors.shutdownNow();
208 processExecutor.shutdownNow();
209
210 LOGGER.debug("Thread was interrupted during download execution", ex);
211 throw new UpdateException("The execution of the download was interrupted", ex);
212 }
213 if (task == null) {
214 downloadExecutors.shutdownNow();
215 processExecutor.shutdownNow();
216 LOGGER.debug("Thread was interrupted during download");
217 throw new UpdateException("The download was interrupted; unable to complete the update");
218 } else {
219 processFutures.add(task);
220 }
221 }
222
223 for (Future<ProcessTask> future : processFutures) {
224 try {
225 final ProcessTask task = future.get();
226 if (task.getException() != null) {
227 throw task.getException();
228 }
229 } catch (InterruptedException ex) {
230 processExecutor.shutdownNow();
231 LOGGER.debug("Thread was interrupted during processing", ex);
232 throw new UpdateException(ex);
233 } catch (ExecutionException ex) {
234 processExecutor.shutdownNow();
235 LOGGER.debug("Execution Exception during process", ex);
236 throw new UpdateException(ex);
237 } finally {
238 processExecutor.shutdown();
239 }
240 }
241
242 if (maxUpdates >= 1) {
243 getProperties().save(updateable.get(MODIFIED));
244 LOGGER.info("Begin database maintenance.");
245 getCveDB().cleanupDatabase();
246 LOGGER.info("End database maintenance.");
247 }
248 } finally {
249 closeDataStores();
250 }
251 }
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 protected final UpdateableNvdCve getUpdatesNeeded() throws MalformedURLException, DownloadFailedException, UpdateException {
268 UpdateableNvdCve updates = null;
269 try {
270 updates = retrieveCurrentTimestampsFromWeb();
271 } catch (InvalidDataException ex) {
272 final String msg = "Unable to retrieve valid timestamp from nvd cve downloads page";
273 LOGGER.debug(msg, ex);
274 throw new DownloadFailedException(msg, ex);
275 } catch (InvalidSettingException ex) {
276 LOGGER.debug("Invalid setting found when retrieving timestamps", ex);
277 throw new DownloadFailedException("Invalid settings", ex);
278 }
279
280 if (updates == null) {
281 throw new DownloadFailedException("Unable to retrieve the timestamps of the currently published NVD CVE data");
282 }
283 if (!getProperties().isEmpty()) {
284 try {
285 final long lastUpdated = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED, "0"));
286 final long now = System.currentTimeMillis();
287 final int days = Settings.getInt(Settings.KEYS.CVE_MODIFIED_VALID_FOR_DAYS, 7);
288 if (lastUpdated == updates.getTimeStamp(MODIFIED)) {
289 updates.clear();
290 } else if (DateUtil.withinDateRange(lastUpdated, now, days)) {
291 for (NvdCveInfo entry : updates) {
292 if (MODIFIED.equals(entry.getId())) {
293 entry.setNeedsUpdate(true);
294 } else {
295 entry.setNeedsUpdate(false);
296 }
297 }
298 } else {
299 for (NvdCveInfo entry : updates) {
300 if (MODIFIED.equals(entry.getId())) {
301 entry.setNeedsUpdate(true);
302 } else {
303 long currentTimestamp = 0;
304 try {
305 currentTimestamp = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED_BASE
306 + entry.getId(), "0"));
307 } catch (NumberFormatException ex) {
308 LOGGER.debug("Error parsing '{}' '{}' from nvdcve.lastupdated",
309 DatabaseProperties.LAST_UPDATED_BASE, entry.getId(), ex);
310 }
311 if (currentTimestamp == entry.getTimestamp()) {
312 entry.setNeedsUpdate(false);
313 }
314 }
315 }
316 }
317 } catch (NumberFormatException ex) {
318 LOGGER.warn("An invalid schema version or timestamp exists in the data.properties file.");
319 LOGGER.debug("", ex);
320 }
321 }
322 return updates;
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336
337 private UpdateableNvdCve retrieveCurrentTimestampsFromWeb()
338 throws MalformedURLException, DownloadFailedException, InvalidDataException, InvalidSettingException {
339
340 final UpdateableNvdCve updates = new UpdateableNvdCve();
341 updates.add(MODIFIED, Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL),
342 Settings.getString(Settings.KEYS.CVE_MODIFIED_12_URL),
343 false);
344
345 final int start = Settings.getInt(Settings.KEYS.CVE_START_YEAR);
346 final int end = Calendar.getInstance().get(Calendar.YEAR);
347 final String baseUrl20 = Settings.getString(Settings.KEYS.CVE_SCHEMA_2_0);
348 final String baseUrl12 = Settings.getString(Settings.KEYS.CVE_SCHEMA_1_2);
349 for (int i = start; i <= end; i++) {
350 updates.add(Integer.toString(i), String.format(baseUrl20, i),
351 String.format(baseUrl12, i),
352 true);
353 }
354 return updates;
355 }
356 }