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 @Override
69 public void update() throws UpdateException {
70 try {
71 if (!Settings.getBoolean(Settings.KEYS.UPDATE_NVDCVE_ENABLED, true)) {
72 return;
73 }
74 } catch (InvalidSettingException ex) {
75 LOGGER.trace("inavlid setting UPDATE_NVDCVE_ENABLED", ex);
76 }
77
78 try {
79 openDataStores();
80 boolean autoUpdate = true;
81 try {
82 autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
83 } catch (InvalidSettingException ex) {
84 LOGGER.debug("Invalid setting for auto-update; using true.");
85 }
86 if (autoUpdate && checkUpdate()) {
87 final UpdateableNvdCve updateable = getUpdatesNeeded();
88 if (updateable.isUpdateNeeded()) {
89 performUpdate(updateable);
90 }
91 getProperties().save(DatabaseProperties.LAST_CHECKED, Long.toString(System.currentTimeMillis()));
92 }
93 } catch (MalformedURLException ex) {
94 throw new UpdateException("NVD CVE properties files contain an invalid URL, unable to update the data to use the most current data.", ex);
95 } catch (DownloadFailedException ex) {
96 LOGGER.warn(
97 "Unable to download the NVD CVE data; the results may not include the most recent CPE/CVEs from the NVD.");
98 if (Settings.getString(Settings.KEYS.PROXY_SERVER) == null) {
99 LOGGER.info(
100 "If you are behind a proxy you may need to configure dependency-check to use the proxy.");
101 }
102 throw new UpdateException("Unable to download the NVD CVE data.", ex);
103 } finally {
104 closeDataStores();
105 }
106 }
107
108
109
110
111
112
113
114
115
116
117
118
119 private boolean checkUpdate() throws UpdateException {
120 boolean proceed = true;
121
122 final int validForHours = Settings.getInt(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, 0);
123 if (dataExists() && 0 < validForHours) {
124
125 final long msValid = validForHours * 60L * 60L * 1000L;
126 final long lastChecked = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_CHECKED, "0"));
127 final long now = System.currentTimeMillis();
128 proceed = (now - lastChecked) > msValid;
129 if (!proceed) {
130 LOGGER.info("Skipping NVD check since last check was within {} hours.", validForHours);
131 LOGGER.debug("Last NVD was at {}, and now {} is within {} ms.",
132 lastChecked, now, msValid);
133 }
134 }
135 return proceed;
136 }
137
138
139
140
141
142
143 private boolean dataExists() {
144 CveDB cve = null;
145 try {
146 cve = new CveDB();
147 cve.open();
148 return cve.dataExists();
149 } catch (DatabaseException ex) {
150 return false;
151 } finally {
152 if (cve != null) {
153 cve.close();
154 }
155 }
156 }
157
158
159
160
161
162
163
164
165
166
167 private void performUpdate(UpdateableNvdCve updateable) throws UpdateException {
168 int maxUpdates = 0;
169 for (NvdCveInfo cve : updateable) {
170 if (cve.getNeedsUpdate()) {
171 maxUpdates += 1;
172 }
173 }
174 if (maxUpdates <= 0) {
175 return;
176 }
177 if (maxUpdates > 3) {
178 LOGGER.info("NVD CVE requires several updates; this could take a couple of minutes.");
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 }
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264 protected final UpdateableNvdCve getUpdatesNeeded() throws MalformedURLException, DownloadFailedException, UpdateException {
265 UpdateableNvdCve updates = null;
266 try {
267 updates = retrieveCurrentTimestampsFromWeb();
268 } catch (InvalidDataException ex) {
269 final String msg = "Unable to retrieve valid timestamp from nvd cve downloads page";
270 LOGGER.debug(msg, ex);
271 throw new DownloadFailedException(msg, ex);
272 } catch (InvalidSettingException ex) {
273 LOGGER.debug("Invalid setting found when retrieving timestamps", ex);
274 throw new DownloadFailedException("Invalid settings", ex);
275 }
276
277 if (updates == null) {
278 throw new DownloadFailedException("Unable to retrieve the timestamps of the currently published NVD CVE data");
279 }
280 if (!getProperties().isEmpty()) {
281 try {
282 final int startYear = Settings.getInt(Settings.KEYS.CVE_START_YEAR, 2002);
283 final int endYear = Calendar.getInstance().get(Calendar.YEAR);
284 boolean needsFullUpdate = false;
285 for (int y = startYear; y <= endYear; y++) {
286 final long val = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED_BASE + y, "0"));
287 if (val == 0) {
288 needsFullUpdate = true;
289 }
290 }
291
292 final long lastUpdated = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED, "0"));
293 final long now = System.currentTimeMillis();
294 final int days = Settings.getInt(Settings.KEYS.CVE_MODIFIED_VALID_FOR_DAYS, 7);
295 if (!needsFullUpdate && lastUpdated == updates.getTimeStamp(MODIFIED)) {
296 updates.clear();
297 } else if (!needsFullUpdate && DateUtil.withinDateRange(lastUpdated, now, days)) {
298 for (NvdCveInfo entry : updates) {
299 if (MODIFIED.equals(entry.getId())) {
300 entry.setNeedsUpdate(true);
301 } else {
302 entry.setNeedsUpdate(false);
303 }
304 }
305 } else {
306 for (NvdCveInfo entry : updates) {
307 if (MODIFIED.equals(entry.getId())) {
308 entry.setNeedsUpdate(true);
309 } else {
310 long currentTimestamp = 0;
311 try {
312 currentTimestamp = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED_BASE
313 + entry.getId(), "0"));
314 } catch (NumberFormatException ex) {
315 LOGGER.debug("Error parsing '{}' '{}' from nvdcve.lastupdated",
316 DatabaseProperties.LAST_UPDATED_BASE, entry.getId(), ex);
317 }
318 if (currentTimestamp == entry.getTimestamp()) {
319 entry.setNeedsUpdate(false);
320 }
321 }
322 }
323 }
324 } catch (NumberFormatException ex) {
325 LOGGER.warn("An invalid schema version or timestamp exists in the data.properties file.");
326 LOGGER.debug("", ex);
327 }
328 }
329 return updates;
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343
344 private UpdateableNvdCve retrieveCurrentTimestampsFromWeb()
345 throws MalformedURLException, DownloadFailedException, InvalidDataException, InvalidSettingException {
346
347 final UpdateableNvdCve updates = new UpdateableNvdCve();
348 updates.add(MODIFIED, Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL),
349 Settings.getString(Settings.KEYS.CVE_MODIFIED_12_URL),
350 false);
351
352 final int start = Settings.getInt(Settings.KEYS.CVE_START_YEAR);
353 final int end = Calendar.getInstance().get(Calendar.YEAR);
354 final String baseUrl20 = Settings.getString(Settings.KEYS.CVE_SCHEMA_2_0);
355 final String baseUrl12 = Settings.getString(Settings.KEYS.CVE_SCHEMA_1_2);
356 for (int i = start; i <= end; i++) {
357 updates.add(Integer.toString(i), String.format(baseUrl20, i),
358 String.format(baseUrl12, i),
359 true);
360 }
361 return updates;
362 }
363 }