View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2014 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.data.update;
19  
20  import java.io.IOException;
21  import java.net.HttpURLConnection;
22  import java.net.MalformedURLException;
23  import java.net.URL;
24  import org.apache.commons.io.IOUtils;
25  import org.owasp.dependencycheck.data.nvdcve.CveDB;
26  import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
27  import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
28  import org.owasp.dependencycheck.data.update.exception.UpdateException;
29  import org.owasp.dependencycheck.utils.DateUtil;
30  import org.owasp.dependencycheck.utils.DependencyVersion;
31  import org.owasp.dependencycheck.utils.InvalidSettingException;
32  import org.owasp.dependencycheck.utils.Settings;
33  import org.owasp.dependencycheck.utils.URLConnectionFactory;
34  import org.owasp.dependencycheck.utils.URLConnectionFailureException;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * Checks the gh-pages dependency-check site to determine the current released
40   * version number. If the released version number is greater then the running
41   * version number a warning is printed recommending that an upgrade be
42   * performed.
43   *
44   * @author Jeremy Long
45   */
46  public class EngineVersionCheck implements CachedWebDataSource {
47  
48      /**
49       * Static logger.
50       */
51      private static final Logger LOGGER = LoggerFactory.getLogger(EngineVersionCheck.class);
52      /**
53       * The property key indicating when the last version check occurred.
54       */
55      public static final String ENGINE_VERSION_CHECKED_ON = "VersionCheckOn";
56      /**
57       * The property key indicating when the last version check occurred.
58       */
59      public static final String CURRENT_ENGINE_RELEASE = "CurrentEngineRelease";
60      /**
61       * Reference to the Cve Database.
62       */
63      private CveDB cveDB = null;
64  
65      /**
66       * The version retrieved from the database properties or web to check
67       * against.
68       */
69      private String updateToVersion;
70  
71      /**
72       * Getter for updateToVersion - only used for testing. Represents the
73       * version retrieved from the database.
74       *
75       * @return the version to test
76       */
77      protected String getUpdateToVersion() {
78          return updateToVersion;
79      }
80  
81      /**
82       * Setter for updateToVersion - only used for testing. Represents the
83       * version retrieved from the database.
84       *
85       * @param version the version to test
86       */
87      protected void setUpdateToVersion(String version) {
88          updateToVersion = version;
89      }
90  
91      /**
92       * Downloads the current released version number and compares it to the
93       * running engine's version number. If the released version number is newer
94       * a warning is printed recommending an upgrade.
95       *
96       * @throws UpdateException thrown if the local database properties could not
97       * be updated
98       */
99      @Override
100     public void update() throws UpdateException {
101         try {
102             final boolean autoupdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
103             final boolean enabled = Settings.getBoolean(Settings.KEYS.UPDATE_VERSION_CHECK_ENABLED, true);
104             final String original = Settings.getString(Settings.KEYS.CVE_ORIGINAL_MODIFIED_20_URL);
105             final String current = Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL);
106             /**
107              * Only update if auto-update is enabled, the engine check is
108              * enabled, and the NVD CVE URLs have not been modified (i.e. the
109              * user has not configured them to point to an internal source).
110              */
111             if (enabled && autoupdate && original != null && original.equals(current)) {
112                 openDatabase();
113                 LOGGER.debug("Begin Engine Version Check");
114                 final DatabaseProperties properties = cveDB.getDatabaseProperties();
115                 final long lastChecked = Long.parseLong(properties.getProperty(ENGINE_VERSION_CHECKED_ON, "0"));
116                 final long now = System.currentTimeMillis();
117                 updateToVersion = properties.getProperty(CURRENT_ENGINE_RELEASE, "");
118                 final String currentVersion = Settings.getString(Settings.KEYS.APPLICATION_VERSION, "0.0.0");
119                 LOGGER.debug("Last checked: {}", lastChecked);
120                 LOGGER.debug("Now: {}", now);
121                 LOGGER.debug("Current version: {}", currentVersion);
122                 final boolean updateNeeded = shouldUpdate(lastChecked, now, properties, currentVersion);
123                 if (updateNeeded) {
124                     LOGGER.warn("A new version of dependency-check is available. Consider updating to version {}.",
125                             updateToVersion);
126                 }
127             }
128         } catch (DatabaseException ex) {
129             LOGGER.debug("Database Exception opening databases to retrieve properties", ex);
130             throw new UpdateException("Error occurred updating database properties.");
131         } catch (InvalidSettingException ex) {
132             LOGGER.debug("Unable to determine if autoupdate is enabled", ex);
133         } finally {
134             closeDatabase();
135         }
136     }
137 
138     /**
139      * Determines if a new version of the dependency-check engine has been
140      * released.
141      *
142      * @param lastChecked the epoch time of the last version check
143      * @param now the current epoch time
144      * @param properties the database properties object
145      * @param currentVersion the current version of dependency-check
146      * @return <code>true</code> if a newer version of the database has been
147      * released; otherwise <code>false</code>
148      * @throws UpdateException thrown if there is an error connecting to the
149      * github documentation site or accessing the local database.
150      */
151     protected boolean shouldUpdate(final long lastChecked, final long now, final DatabaseProperties properties,
152             String currentVersion) throws UpdateException {
153         //check every 30 days if we know there is an update, otherwise check every 7 days
154         final int checkRange = 30;
155         if (!DateUtil.withinDateRange(lastChecked, now, checkRange)) {
156             LOGGER.debug("Checking web for new version.");
157             final String currentRelease = getCurrentReleaseVersion();
158             if (currentRelease != null) {
159                 final DependencyVersion v = new DependencyVersion(currentRelease);
160                 if (v.getVersionParts() != null && v.getVersionParts().size() >= 3) {
161                     updateToVersion = v.toString();
162                     if (!currentRelease.equals(updateToVersion)) {
163                         properties.save(CURRENT_ENGINE_RELEASE, updateToVersion);
164                     }
165                     properties.save(ENGINE_VERSION_CHECKED_ON, Long.toString(now));
166                 }
167             }
168             LOGGER.debug("Current Release: {}", updateToVersion);
169         }
170         if (updateToVersion == null) {
171             LOGGER.debug("Unable to obtain current release");
172             return false;
173         }
174         final DependencyVersion running = new DependencyVersion(currentVersion);
175         final DependencyVersion released = new DependencyVersion(updateToVersion);
176         if (running.compareTo(released) < 0) {
177             LOGGER.debug("Upgrade recommended");
178             return true;
179         }
180         LOGGER.debug("Upgrade not needed");
181         return false;
182     }
183 
184     /**
185      * Opens the CVE and CPE data stores.
186      *
187      * @throws DatabaseException thrown if a data store cannot be opened
188      */
189     protected final void openDatabase() throws DatabaseException {
190         if (cveDB != null) {
191             return;
192         }
193         cveDB = new CveDB();
194         cveDB.open();
195     }
196 
197     /**
198      * Closes the CVE and CPE data stores.
199      */
200     protected void closeDatabase() {
201         if (cveDB != null) {
202             try {
203                 cveDB.close();
204                 cveDB = null;
205             } catch (Throwable ignore) {
206                 LOGGER.trace("Error closing the cveDB", ignore);
207             }
208         }
209     }
210 
211     /**
212      * Retrieves the current released version number from the github
213      * documentation site.
214      *
215      * @return the current released version number
216      */
217     protected String getCurrentReleaseVersion() {
218         HttpURLConnection conn = null;
219         try {
220             final String str = Settings.getString(Settings.KEYS.ENGINE_VERSION_CHECK_URL, "http://jeremylong.github.io/DependencyCheck/current.txt");
221             final URL url = new URL(str);
222             conn = URLConnectionFactory.createHttpURLConnection(url);
223             conn.connect();
224             if (conn.getResponseCode() != 200) {
225                 return null;
226             }
227             final String releaseVersion = IOUtils.toString(conn.getInputStream(), "UTF-8");
228             if (releaseVersion != null) {
229                 return releaseVersion.trim();
230             }
231         } catch (MalformedURLException ex) {
232             LOGGER.debug("Unable to retrieve current release version of dependency-check - malformed url?");
233         } catch (URLConnectionFailureException ex) {
234             LOGGER.debug("Unable to retrieve current release version of dependency-check - connection failed");
235         } catch (IOException ex) {
236             LOGGER.debug("Unable to retrieve current release version of dependency-check - i/o exception");
237         } finally {
238             if (conn != null) {
239                 conn.disconnect();
240             }
241         }
242         return null;
243     }
244 }