1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "google_apis/gcm/engine/gservices_settings.h" 6 7 #include "base/bind.h" 8 #include "base/sha1.h" 9 #include "base/strings/string_number_conversions.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/stringprintf.h" 12 13 namespace { 14 // The expected time in seconds between periodic checkins. 15 const char kCheckinIntervalKey[] = "checkin_interval"; 16 // The override URL to the checkin server. 17 const char kCheckinURLKey[] = "checkin_url"; 18 // The MCS machine name to connect to. 19 const char kMCSHostnameKey[] = "gcm_hostname"; 20 // The MCS port to connect to. 21 const char kMCSSecurePortKey[] = "gcm_secure_port"; 22 // The URL to get MCS registration IDs. 23 const char kRegistrationURLKey[] = "gcm_registration_url"; 24 25 const int64 kDefaultCheckinInterval = 2 * 24 * 60 * 60; // seconds = 2 days. 26 const int64 kMinimumCheckinInterval = 12 * 60 * 60; // seconds = 12 hours. 27 const char kDefaultCheckinURL[] = "https://android.clients.google.com/checkin"; 28 const char kDefaultMCSHostname[] = "mtalk.google.com"; 29 const int kDefaultMCSMainSecurePort = 5228; 30 const int kDefaultMCSFallbackSecurePort = 443; 31 const char kDefaultRegistrationURL[] = 32 "https://android.clients.google.com/c2dm/register3"; 33 // Settings that are to be deleted are marked with this prefix in checkin 34 // response. 35 const char kDeleteSettingPrefix[] = "delete_"; 36 // Settings digest starts with verison number followed by '-'. 37 const char kDigestVersionPrefix[] = "1-"; 38 const char kMCSEnpointTemplate[] = "https://%s:%d"; 39 const int kMaxSecurePort = 65535; 40 41 std::string MakeMCSEndpoint(const std::string& mcs_hostname, int port) { 42 return base::StringPrintf(kMCSEnpointTemplate, mcs_hostname.c_str(), port); 43 } 44 45 // Default settings can be omitted, as GServicesSettings class provides 46 // reasonable defaults. 47 bool CanBeOmitted(const std::string& settings_name) { 48 return settings_name == kCheckinIntervalKey || 49 settings_name == kCheckinURLKey || 50 settings_name == kMCSHostnameKey || 51 settings_name == kMCSSecurePortKey || 52 settings_name == kRegistrationURLKey; 53 } 54 55 bool VerifyCheckinInterval( 56 const gcm::GServicesSettings::SettingsMap& settings) { 57 gcm::GServicesSettings::SettingsMap::const_iterator iter = 58 settings.find(kCheckinIntervalKey); 59 if (iter == settings.end()) 60 return CanBeOmitted(kCheckinIntervalKey); 61 62 int64 checkin_interval = kMinimumCheckinInterval; 63 if (!base::StringToInt64(iter->second, &checkin_interval)) { 64 DVLOG(1) << "Failed to parse checkin interval: " << iter->second; 65 return false; 66 } 67 if (checkin_interval == std::numeric_limits<int64>::max()) { 68 DVLOG(1) << "Checkin interval is too big: " << checkin_interval; 69 return false; 70 } 71 if (checkin_interval < kMinimumCheckinInterval) { 72 DVLOG(1) << "Checkin interval: " << checkin_interval 73 << " is less than allowed minimum: " << kMinimumCheckinInterval; 74 } 75 76 return true; 77 } 78 79 bool VerifyMCSEndpoint(const gcm::GServicesSettings::SettingsMap& settings) { 80 std::string mcs_hostname; 81 gcm::GServicesSettings::SettingsMap::const_iterator iter = 82 settings.find(kMCSHostnameKey); 83 if (iter == settings.end()) { 84 // Because endpoint has 2 parts (hostname and port) we are defaulting and 85 // moving on with verification. 86 if (CanBeOmitted(kMCSHostnameKey)) 87 mcs_hostname = kDefaultMCSHostname; 88 else 89 return false; 90 } else if (iter->second.empty()) { 91 DVLOG(1) << "Empty MCS hostname provided."; 92 return false; 93 } else { 94 mcs_hostname = iter->second; 95 } 96 97 int mcs_secure_port = 0; 98 iter = settings.find(kMCSSecurePortKey); 99 if (iter == settings.end()) { 100 // Simlarly we might have to default the port, when only hostname is 101 // provided. 102 if (CanBeOmitted(kMCSSecurePortKey)) 103 mcs_secure_port = kDefaultMCSMainSecurePort; 104 else 105 return false; 106 } else if (!base::StringToInt(iter->second, &mcs_secure_port)) { 107 DVLOG(1) << "Failed to parse MCS secure port: " << iter->second; 108 return false; 109 } 110 111 if (mcs_secure_port < 0 || mcs_secure_port > kMaxSecurePort) { 112 DVLOG(1) << "Incorrect port value: " << mcs_secure_port; 113 return false; 114 } 115 116 GURL mcs_main_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port)); 117 if (!mcs_main_endpoint.is_valid()) { 118 DVLOG(1) << "Invalid main MCS endpoint: " 119 << mcs_main_endpoint.possibly_invalid_spec(); 120 return false; 121 } 122 GURL mcs_fallback_endpoint( 123 MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort)); 124 if (!mcs_fallback_endpoint.is_valid()) { 125 DVLOG(1) << "Invalid fallback MCS endpoint: " 126 << mcs_fallback_endpoint.possibly_invalid_spec(); 127 return false; 128 } 129 130 return true; 131 } 132 133 bool VerifyCheckinURL(const gcm::GServicesSettings::SettingsMap& settings) { 134 gcm::GServicesSettings::SettingsMap::const_iterator iter = 135 settings.find(kCheckinURLKey); 136 if (iter == settings.end()) 137 return CanBeOmitted(kCheckinURLKey); 138 139 GURL checkin_url(iter->second); 140 if (!checkin_url.is_valid()) { 141 DVLOG(1) << "Invalid checkin URL provided: " << iter->second; 142 return false; 143 } 144 145 return true; 146 } 147 148 bool VerifyRegistrationURL( 149 const gcm::GServicesSettings::SettingsMap& settings) { 150 gcm::GServicesSettings::SettingsMap::const_iterator iter = 151 settings.find(kRegistrationURLKey); 152 if (iter == settings.end()) 153 return CanBeOmitted(kRegistrationURLKey); 154 155 GURL registration_url(iter->second); 156 if (!registration_url.is_valid()) { 157 DVLOG(1) << "Invalid registration URL provided: " << iter->second; 158 return false; 159 } 160 161 return true; 162 } 163 164 bool VerifySettings(const gcm::GServicesSettings::SettingsMap& settings) { 165 return VerifyCheckinInterval(settings) && VerifyMCSEndpoint(settings) && 166 VerifyCheckinURL(settings) && VerifyRegistrationURL(settings); 167 } 168 169 } // namespace 170 171 namespace gcm { 172 173 // static 174 const base::TimeDelta GServicesSettings::MinimumCheckinInterval() { 175 return base::TimeDelta::FromSeconds(kMinimumCheckinInterval); 176 } 177 178 // static 179 const GURL GServicesSettings::DefaultCheckinURL() { 180 return GURL(kDefaultCheckinURL); 181 } 182 183 // static 184 std::string GServicesSettings::CalculateDigest(const SettingsMap& settings) { 185 unsigned char hash[base::kSHA1Length]; 186 std::string data; 187 for (SettingsMap::const_iterator iter = settings.begin(); 188 iter != settings.end(); 189 ++iter) { 190 data += iter->first; 191 data += '\0'; 192 data += iter->second; 193 data += '\0'; 194 } 195 base::SHA1HashBytes( 196 reinterpret_cast<const unsigned char*>(&data[0]), data.size(), hash); 197 std::string digest = 198 kDigestVersionPrefix + base::HexEncode(hash, base::kSHA1Length); 199 digest = base::StringToLowerASCII(digest); 200 return digest; 201 } 202 203 GServicesSettings::GServicesSettings() : weak_ptr_factory_(this) { 204 digest_ = CalculateDigest(settings_); 205 } 206 207 GServicesSettings::~GServicesSettings() { 208 } 209 210 bool GServicesSettings::UpdateFromCheckinResponse( 211 const checkin_proto::AndroidCheckinResponse& checkin_response) { 212 if (!checkin_response.has_settings_diff()) { 213 DVLOG(1) << "Field settings_diff not set in response."; 214 return false; 215 } 216 217 bool settings_diff = checkin_response.settings_diff(); 218 SettingsMap new_settings; 219 // Only reuse the existing settings, if we are given a settings difference. 220 if (settings_diff) 221 new_settings = settings_map(); 222 223 for (int i = 0; i < checkin_response.setting_size(); ++i) { 224 std::string name = checkin_response.setting(i).name(); 225 if (name.empty()) { 226 DVLOG(1) << "Setting name is empty"; 227 return false; 228 } 229 230 if (settings_diff && name.find(kDeleteSettingPrefix) == 0) { 231 std::string setting_to_delete = 232 name.substr(arraysize(kDeleteSettingPrefix) - 1); 233 new_settings.erase(setting_to_delete); 234 DVLOG(1) << "Setting deleted: " << setting_to_delete; 235 } else { 236 std::string value = checkin_response.setting(i).value(); 237 new_settings[name] = value; 238 DVLOG(1) << "New setting: '" << name << "' : '" << value << "'"; 239 } 240 } 241 242 if (!VerifySettings(new_settings)) 243 return false; 244 245 settings_.swap(new_settings); 246 digest_ = CalculateDigest(settings_); 247 return true; 248 } 249 250 void GServicesSettings::UpdateFromLoadResult( 251 const GCMStore::LoadResult& load_result) { 252 // No need to try to update settings when load_result is empty. 253 if (load_result.gservices_settings.empty()) 254 return; 255 if (!VerifySettings(load_result.gservices_settings)) 256 return; 257 std::string digest = CalculateDigest(load_result.gservices_settings); 258 if (digest != load_result.gservices_digest) { 259 DVLOG(1) << "G-services settings digest mismatch. " 260 << "Expected digest: " << load_result.gservices_digest 261 << ". Calculated digest is: " << digest; 262 return; 263 } 264 265 settings_ = load_result.gservices_settings; 266 digest_ = load_result.gservices_digest; 267 } 268 269 base::TimeDelta GServicesSettings::GetCheckinInterval() const { 270 int64 checkin_interval = kMinimumCheckinInterval; 271 SettingsMap::const_iterator iter = settings_.find(kCheckinIntervalKey); 272 if (iter == settings_.end() || 273 !base::StringToInt64(iter->second, &checkin_interval)) { 274 checkin_interval = kDefaultCheckinInterval; 275 } 276 277 if (checkin_interval < kMinimumCheckinInterval) 278 checkin_interval = kMinimumCheckinInterval; 279 280 return base::TimeDelta::FromSeconds(checkin_interval); 281 } 282 283 GURL GServicesSettings::GetCheckinURL() const { 284 SettingsMap::const_iterator iter = settings_.find(kCheckinURLKey); 285 if (iter == settings_.end() || iter->second.empty()) 286 return GURL(kDefaultCheckinURL); 287 return GURL(iter->second); 288 } 289 290 GURL GServicesSettings::GetMCSMainEndpoint() const { 291 // Get alternative hostname or use default. 292 std::string mcs_hostname; 293 SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey); 294 if (iter != settings_.end() && !iter->second.empty()) 295 mcs_hostname = iter->second; 296 else 297 mcs_hostname = kDefaultMCSHostname; 298 299 // Get alternative secure port or use defualt. 300 int mcs_secure_port = 0; 301 iter = settings_.find(kMCSSecurePortKey); 302 if (iter == settings_.end() || iter->second.empty() || 303 !base::StringToInt(iter->second, &mcs_secure_port)) { 304 mcs_secure_port = kDefaultMCSMainSecurePort; 305 } 306 307 // If constructed address makes sense use it. 308 GURL mcs_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port)); 309 if (mcs_endpoint.is_valid()) 310 return mcs_endpoint; 311 312 // Otherwise use default settings. 313 return GURL(MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSMainSecurePort)); 314 } 315 316 GURL GServicesSettings::GetMCSFallbackEndpoint() const { 317 // Get alternative hostname or use default. 318 std::string mcs_hostname; 319 SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey); 320 if (iter != settings_.end() && !iter->second.empty()) 321 mcs_hostname = iter->second; 322 else 323 mcs_hostname = kDefaultMCSHostname; 324 325 // If constructed address makes sense use it. 326 GURL mcs_endpoint( 327 MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort)); 328 if (mcs_endpoint.is_valid()) 329 return mcs_endpoint; 330 331 return GURL( 332 MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSFallbackSecurePort)); 333 } 334 335 GURL GServicesSettings::GetRegistrationURL() const { 336 SettingsMap::const_iterator iter = settings_.find(kRegistrationURLKey); 337 if (iter == settings_.end() || iter->second.empty()) 338 return GURL(kDefaultRegistrationURL); 339 return GURL(iter->second); 340 } 341 342 } // namespace gcm 343