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