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