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