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