diff options
Diffstat (limited to 'chrome/browser/sync/engine/net')
12 files changed, 1943 insertions, 0 deletions
diff --git a/chrome/browser/sync/engine/net/gaia_authenticator.cc b/chrome/browser/sync/engine/net/gaia_authenticator.cc new file mode 100644 index 0000000..7276cec --- /dev/null +++ b/chrome/browser/sync/engine/net/gaia_authenticator.cc @@ -0,0 +1,483 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/engine/net/gaia_authenticator.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/port.h" +#include "base/string_util.h" +#include "chrome/browser/sync/engine/all_status.h" +#include "chrome/browser/sync/engine/net/http_return.h" +#include "chrome/browser/sync/engine/net/url_translator.h" +#include "chrome/browser/sync/util/event_sys-inl.h" +#include "googleurl/src/gurl.h" + +using std::pair; +using std::string; +using std::vector; + +// TODO(timsteele): Integrate the following two functions to string_util.h or +// somewhere that makes them unit-testable. +bool SplitStringIntoKeyValues(const string& line, + char key_value_delimiter, + string* key, vector<string>* values) { + key->clear(); + values->clear(); + + // find the key string + int end_key_pos = line.find_first_of(key_value_delimiter); + if (end_key_pos == string::npos) { + DLOG(INFO) << "cannot parse key from line: " << line; + return false; // no key + } + key->assign(line, 0, end_key_pos); + + // find the values string + string remains(line, end_key_pos, line.size() - end_key_pos); + int begin_values_pos = remains.find_first_not_of(key_value_delimiter); + if (begin_values_pos == string::npos) { + DLOG(INFO) << "cannot parse value from line: " << line; + return false; // no value + } + string values_string(remains, begin_values_pos, + remains.size() - begin_values_pos); + + // construct the values vector + values->push_back(values_string); + return true; +} + +bool SplitStringIntoKeyValuePairs(const string& line, + char key_value_delimiter, + char key_value_pair_delimiter, + vector<pair<string, string> >* kv_pairs) { + kv_pairs->clear(); + + vector<string> pairs; + SplitString(line, key_value_pair_delimiter, &pairs); + + bool success = true; + for (size_t i = 0; i < pairs.size(); ++i) { + string key; + vector<string> value; + if (!SplitStringIntoKeyValues(pairs[i], + key_value_delimiter, + &key, &value)) { + // Don't return here, to allow for keys without associated + // values; just record that our split failed. + success = false; + } + DCHECK_LE(value.size(), 1); + kv_pairs->push_back(make_pair(key, value.empty()? "" : value[0])); + } + return success; +} + +namespace browser_sync { + +static const char kGaiaV1IssueAuthTokenPath[] = "/accounts/IssueAuthToken"; + +static const char kGetUserInfoPath[] = "/accounts/GetUserInfo"; + +// Sole constructor with initializers for all fields. +GaiaAuthenticator::GaiaAuthenticator(const string& user_agent, + const string& service_id, + const string& gaia_url) + : user_agent_(user_agent), + service_id_(service_id), + gaia_url_(gaia_url), + request_count_(0), + early_auth_attempt_count_(0), + delay_(0), + next_allowed_auth_attempt_time_(0) { + GaiaAuthEvent done = { GaiaAuthEvent::GAIA_AUTHENTICATOR_DESTROYED, None, + this }; + channel_ = new Channel(done); +} + +GaiaAuthenticator::~GaiaAuthenticator() { + delete channel_; +} + +bool GaiaAuthenticator::LaunchAuthenticate(const AuthParams& params, + bool synchronous) { + if (synchronous) + return AuthenticateImpl(params); + AuthParams* copy = new AuthParams; + *copy = params; + pthread_t thread_id; + int result = pthread_create(&thread_id, 0, &GaiaAuthenticator::ThreadMain, + copy); + if (result) + return false; + return true; +} + + +void* GaiaAuthenticator::ThreadMain(void* arg) { + NameCurrentThreadForDebugging("SyncEngine_GaiaAuthenticatorThread"); + AuthParams* const params = reinterpret_cast<AuthParams*>(arg); + params->authenticator->AuthenticateImpl(*params); + delete params; + return 0; +} + +// mutex_ must be entered before calling this function. +GaiaAuthenticator::AuthParams GaiaAuthenticator::MakeParams( + const string& user_name, + const string& password, + SaveCredentials should_save_credentials, + const string& captcha_token, + const string& captcha_value, + SignIn try_first) { + AuthParams params; + params.request_id = ++request_count_; + params.email = user_name; + params.password = password; + params.should_save_credentials = should_save_credentials; + params.captcha_token = captcha_token; + params.captcha_value = captcha_value; + params.authenticator = this; + params.try_first = try_first; + return params; +} + +bool GaiaAuthenticator::Authenticate(const string& user_name, + const string& password, + SaveCredentials should_save_credentials, + bool synchronous, + const string& captcha_token, + const string& captcha_value, + SignIn try_first) { + mutex_.Lock(); + AuthParams const params = + MakeParams(user_name, password, should_save_credentials, captcha_token, + captcha_value, try_first); + mutex_.Unlock(); + return LaunchAuthenticate(params, synchronous); +} + +bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params) { + AuthResults results; + const bool succeeded = AuthenticateImpl(params, &results); + mutex_.Lock(); + if (params.request_id == request_count_) { + auth_results_ = results; + GaiaAuthEvent event = { succeeded ? GaiaAuthEvent::GAIA_AUTH_SUCCEEDED + : GaiaAuthEvent::GAIA_AUTH_FAILED, + results.auth_error, this }; + mutex_.Unlock(); + channel_->NotifyListeners(event); + } else { + mutex_.Unlock(); + } + return succeeded; +} + +// This method makes an HTTP request to the Gaia server, and calls other +// methods to help parse the response. If authentication succeeded, then +// Gaia-issued cookies are available in the respective variables; if +// authentication failed, then the exact error is available as an enum. If the +// client wishes to save the credentials, the last parameter must be true. +// If a subsequent request is made with fresh credentials, the saved credentials +// are wiped out; any subsequent request to the zero-parameter overload of this +// method preserves the saved credentials. +bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params, + AuthResults* results) { + results->credentials_saved = params.should_save_credentials; + results->auth_error = ConnectionUnavailable; + // Save credentials if so requested. + if (params.should_save_credentials != DONT_SAVE_CREDENTIALS) { + results->email = params.email.data(); + results->password = params.password; + } else { // Explicitly clear previously-saved credentials. + results->email = ""; + results->password = ""; + } + + // The aim of this code is to start failing requests if due to a logic error + // in the program we're hammering GAIA. + time_t now = time(0); + if (now > next_allowed_auth_attempt_time_) { + next_allowed_auth_attempt_time_ = now + 1; + // If we're more than 2 minutes past the allowed time we reset the early + // attempt count. + if (now - next_allowed_auth_attempt_time_ > 2 * 60) { + delay_ = 1; + early_auth_attempt_count_ = 0; + } + } else { + ++early_auth_attempt_count_; + // Allow 3 attempts, but then limit. + if (early_auth_attempt_count_ > 3) { + delay_ = AllStatus::GetRecommendedDelaySeconds(delay_); + next_allowed_auth_attempt_time_ = now + delay_; + return false; + } + } + + return PerformGaiaRequest(params, results); +} + +bool GaiaAuthenticator::PerformGaiaRequest(const AuthParams& params, + AuthResults* results) { + GURL gaia_auth_url(gaia_url_); + + string post_body; + post_body += "Email=" + CgiEscapeString(params.email); + post_body += "&Passwd=" + CgiEscapeString(params.password); + post_body += "&source=" + CgiEscapeString(user_agent_); + post_body += "&service=" + service_id_; + if (!params.captcha_token.empty() && !params.captcha_value.empty()) { + post_body += "&logintoken=" + CgiEscapeString(params.captcha_token); + post_body += "&logincaptcha=" + CgiEscapeString(params.captcha_value); + } + post_body += "&PersistentCookie=true"; + // We set it to GOOGLE (and not HOSTED or HOSTED_OR_GOOGLE) because we only + // allow consumer logins. + post_body += "&accountType=GOOGLE"; + + string message_text; + unsigned long server_response_code; + if (!Post(gaia_auth_url, post_body, &server_response_code, + &message_text)) { + results->auth_error = ConnectionUnavailable; + return false; + } + + // Parse reply in two different ways, depending on if request failed or + // succeeded. + if (RC_FORBIDDEN == server_response_code) { + ExtractAuthErrorFrom(message_text, results); + return false; + } else if (RC_REQUEST_OK == server_response_code) { + ExtractTokensFrom(message_text, results); + const bool old_gaia = + results->auth_token.empty() && !results->lsid.empty(); + const bool long_lived_token = + params.should_save_credentials == PERSIST_TO_DISK; + if ((old_gaia || long_lived_token) && + !IssueAuthToken(results, service_id_, long_lived_token)) + return false; + + return LookupEmail(results); + } else { + results->auth_error = Unknown; + return false; + } +} + +bool GaiaAuthenticator::LookupEmail(AuthResults* results) { + // Use the provided Gaia server, but change the path to what V1 expects. + GURL url(gaia_url_); // Gaia server + GURL::Replacements repl; + // Needs to stay in scope till GURL is out of scope + string path(kGetUserInfoPath); + repl.SetPathStr(path); + url = url.ReplaceComponents(repl); + + string post_body; + post_body += "LSID="; + post_body += CgiEscapeString(results->lsid); + + unsigned long server_response_code; + string message_text; + if (!Post(url, post_body, &server_response_code, &message_text)) { + return false; + } + + // Check if we received a valid AuthToken; if not, ignore it. + if (RC_FORBIDDEN == server_response_code) { + // Server says we're not authenticated. + ExtractAuthErrorFrom(message_text, results); + return false; + } else if (RC_REQUEST_OK == server_response_code) { + typedef vector<pair<string, string> > Tokens; + Tokens tokens; + SplitStringIntoKeyValuePairs(message_text, '=', '\n', &tokens); + for (Tokens::iterator i = tokens.begin(); i != tokens.end(); ++i) { + if ("accountType" == i->first) { + // We never authenticate an email as a hosted account. + DCHECK_EQ("GOOGLE", i->second); + results->signin = GMAIL_SIGNIN; + } else if ("email" == i->first) { + results->primary_email = i->second; + } + } + return true; + } + return false; +} + +// We need to call this explicitly when we need to obtain a long-lived session +// token. +bool GaiaAuthenticator::IssueAuthToken(AuthResults* results, + const string& service_id, + bool long_lived) { + // Use the provided Gaia server, but change the path to what V1 expects. + GURL url(gaia_url_); // Gaia server + GURL::Replacements repl; + // Needs to stay in scope till GURL is out of scope + string path(kGaiaV1IssueAuthTokenPath); + repl.SetPathStr(path); + url = url.ReplaceComponents(repl); + + string post_body; + post_body += "LSID="; + post_body += CgiEscapeString(results->lsid); + post_body += "&service=" + service_id; + if (long_lived) { + post_body += "&Session=true"; + } + + unsigned long server_response_code; + string message_text; + if (!Post(url, post_body, + &server_response_code, &message_text)) { + return false; + } + + // Check if we received a valid AuthToken; if not, ignore it. + if (RC_FORBIDDEN == server_response_code) { + // Server says we're not authenticated. + ExtractAuthErrorFrom(message_text, results); + return false; + } else if (RC_REQUEST_OK == server_response_code) { + // Note that the format of message_text is different from what is returned + // in the first request, or to the sole request that is made to Gaia V2. + // Specifically, the entire string is the AuthToken, and looks like: + // "<token>" rather than "AuthToken=<token>". Thus, we need not use + // ExtractTokensFrom(...), but simply assign the token. + int last_index = message_text.length() - 1; + if ('\n' == message_text[last_index]) + message_text.erase(last_index); + results->auth_token = message_text; + return true; + } + return false; +} + +// TOOD(sync): This passing around of AuthResults makes it really unclear who +// actually owns the authentication state and when it is valid, but this is +// endemic to this implementation. We should fix this. +bool GaiaAuthenticator::AuthenticateService(const string& service_id, + const string& sid, + const string& lsid, + string* other_service_cookie) { + // Copy the AuthResults structure and overload the auth_token field + // in the copy, local_results, to mean the auth_token for service_id. + AuthResults local_results; + local_results.sid = sid; + local_results.lsid = lsid; + + if (!IssueAuthToken(&local_results, service_id, true)) { + LOG(ERROR) << "[AUTH] Failed to obtain cookie for " << service_id; + return false; + } + + swap(*other_service_cookie, local_results.auth_token); + return true; +} + +// Helper method that extracts tokens from a successful reply, and saves them +// in the right fields. +void GaiaAuthenticator::ExtractTokensFrom(const string& response, + AuthResults* results) { + vector<pair<string, string> > tokens; + SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens); + for (vector<pair<string, string> >::iterator i = tokens.begin(); + i != tokens.end(); ++i) { + if (i->first == "SID") { + results->sid = i->second; + } else if (i->first == "LSID") { + results->lsid = i->second; + } else if (i->first == "Auth") { + results->auth_token = i->second; + } + } +} + +// Helper method that extracts tokens from a failure response, and saves them +// in the right fields. +void GaiaAuthenticator::ExtractAuthErrorFrom(const string& response, + AuthResults* results) { + vector<pair<string, string> > tokens; + SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens); + for (vector<pair<string, string> >::iterator i = tokens.begin(); + i != tokens.end(); ++i) { + if (i->first == "Error") { + results->error_msg = i->second; + } else if (i->first == "Url") { + results->auth_error_url = i->second; + } else if (i->first == "CaptchaToken") { + results->captcha_token = i->second; + } else if (i->first == "CaptchaUrl") { + results->captcha_url = i->second; + } + } + + // Convert string error messages to enum values. Each case has two different + // strings; the first one is the most current and the second one is + // deprecated, but available. + const string& error_msg = results->error_msg; + if (error_msg == "BadAuthentication" || error_msg == "badauth") { + results->auth_error = BadAuthentication; + } else if (error_msg == "NotVerified" || error_msg == "nv") { + results->auth_error = NotVerified; + } else if (error_msg == "TermsNotAgreed" || error_msg == "tna") { + results->auth_error = TermsNotAgreed; + } else if (error_msg == "Unknown" || error_msg == "unknown") { + results->auth_error = Unknown; + } else if (error_msg == "AccountDeleted" || error_msg == "adel") { + results->auth_error = AccountDeleted; + } else if (error_msg == "AccountDisabled" || error_msg == "adis") { + results->auth_error = AccountDisabled; + } else if (error_msg == "CaptchaRequired" || error_msg == "cr") { + results->auth_error = CaptchaRequired; + } else if (error_msg == "ServiceUnavailable" || error_msg == "ire") { + results->auth_error = ServiceUnavailable; + } +} + +// Reset all stored credentials, perhaps in preparation for letting a different +// user sign in. +void GaiaAuthenticator::ResetCredentials() { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + AuthResults blank; + auth_results_ = blank; +} + +void GaiaAuthenticator::SetUsernamePassword(const string& username, + const string& password) { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + auth_results_.password = password; + auth_results_.email = username; +} + +void GaiaAuthenticator::SetUsername(const string& username) { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + auth_results_.email = username; +} + +void GaiaAuthenticator::SetAuthToken(const string& auth_token, + SaveCredentials save) { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + auth_results_.auth_token = auth_token; + auth_results_.credentials_saved = save; +} + +bool GaiaAuthenticator::Authenticate(const string& user_name, + const string& password, + SaveCredentials should_save_credentials, + bool synchronous, SignIn try_first) { + const string empty; + return Authenticate(user_name, password, should_save_credentials, synchronous, + empty, empty, try_first); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/engine/net/gaia_authenticator.h b/chrome/browser/sync/engine/net/gaia_authenticator.h new file mode 100644 index 0000000..e18984c --- /dev/null +++ b/chrome/browser/sync/engine/net/gaia_authenticator.h @@ -0,0 +1,304 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Use this class to authenticate users with Gaia and access cookies sent +// by the Gaia servers. +// +// Sample usage: +// GaiaAuthenticator gaia_auth("User-Agent", SYNC_SERVICE_NAME, +// browser_sync::kExternalGaiaUrl); +// if (gaia_auth.Authenticate("email", "passwd", SAVE_IN_MEMORY_ONLY, +// true)) { // Synchronous +// // Do something with: gaia_auth.auth_token(), or gaia_auth.sid(), +// // or gaia_auth.lsid() +// } +// +// Sample asynchonous usage: +// GaiaAuthenticator gaia_auth("User-Agent", SYNC_SERVICE_NAME, +// browser_sync::kExternalGaiaUrl); +// EventListenerHookup* hookup = NewListenerHookup(gaia_auth.channel(), +// this, &OnAuthenticate); +// gaia_auth.Authenticate("email", "passwd", true, false); +// // OnAuthenticate() will get called with result; +// +// Credentials can also be preserved for subsequent requests, though these are +// saved in plain-text in memory, and not very secure on client systems. The +// email address associated with the Gaia account can be read; the password is +// write-only. + +#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_ +#define CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_ + +#include <string> + +#include "base/basictypes.h" +#include "chrome/browser/sync/engine/net/http_return.h" +#include "chrome/browser/sync/util/event_sys.h" +#include "chrome/browser/sync/util/pthread_helpers.h" +#include "chrome/browser/sync/util/signin.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST + +namespace browser_sync { + +static const char kGaiaUrl[] = + "https://www.google.com:443/accounts/ClientLogin"; + +// Use of the following enum is odd. GaiaAuthenticator only looks at +// and DONT_SAVE_CREDENTIALS and SAVE_IN_MEMORY_ONLY (PERSIST_TO_DISK is == to +// SAVE_IN_MEMORY_ONLY for GaiaAuthenticator). The sync engine never uses +// DONT_SAVE_CREDENTIALS. AuthWatcher does look in GaiaAuthenticator's results +// object to decide if it should save credentials to disk. This currently +// works so I'm leaving the odd dance alone. + +enum SaveCredentials { + DONT_SAVE_CREDENTIALS, + SAVE_IN_MEMORY_ONLY, + PERSIST_TO_DISK // Saved in both memory and disk +}; + +// Error codes from Gaia. These will be set correctly for both Gaia V1 +// (/ClientAuth) and V2 (/ClientLogin) +enum AuthenticationError { + None = 0, + BadAuthentication = 1, + NotVerified = 2, + TermsNotAgreed = 3, + Unknown = 4, + AccountDeleted = 5, + AccountDisabled = 6, + CaptchaRequired = 7, + ServiceUnavailable = 8, + // Errors generated by this class not Gaia. + CredentialsNotSet = 9, + ConnectionUnavailable = 10 +}; + +class GaiaAuthenticator; + +struct GaiaAuthEvent { + enum { + GAIA_AUTH_FAILED, + GAIA_AUTH_SUCCEEDED, + GAIA_AUTHENTICATOR_DESTROYED + } + what_happened; + AuthenticationError error; + const GaiaAuthenticator* authenticator; + + // Lets us use GaiaAuthEvent as its own traits type in hookups. + typedef GaiaAuthEvent EventType; + static inline bool IsChannelShutdownEvent(const GaiaAuthEvent& event) { + return event.what_happened == GAIA_AUTHENTICATOR_DESTROYED; + } +}; + +// GaiaAuthenticator can be used to pass user credentials to Gaia and obtain +// cookies set by the Gaia servers. +class GaiaAuthenticator { + FRIEND_TEST(GaiaAuthenticatorTest, TestNewlineAtEndOfAuthTokenRemoved); + public: + + // Since GaiaAuthenticator can be used for any service, or by any client, you + // must include a user-agent and a service-id when creating one. The + // user_agent is a short string used for simple log analysis. gaia_url is used + // to choose the server to authenticate with (e.g. + // http://www.google.com/accounts/ClientLogin). + GaiaAuthenticator(const std::string& user_agent, + const std::string& service_id, + const std::string& gaia_url); + + virtual ~GaiaAuthenticator(); + + // Pass credentials to authenticate with, or use saved credentials via an + // overload. If authentication succeeds, you can retrieve the authentication + // token via the respective accessors. Returns a boolean indicating whether + // authentication succeeded or not. + bool Authenticate(const std::string& user_name, const std::string& password, + SaveCredentials should_save_credentials, bool synchronous, + const std::string& captcha_token, + const std::string& captcha_value, + SignIn try_first); + + bool Authenticate(const std::string& user_name, const std::string& password, + SaveCredentials should_save_credentials, bool synchronous, + SignIn try_first); + + bool AuthenticateService(const std::string& service_id, + const std::string& sid, + const std::string& lsid, + std::string* other_service_cookie); + + // Resets all stored cookies to their default values. + void ResetCredentials(); + + void SetUsernamePassword(const std::string& username, + const std::string& password); + + void SetUsername(const std::string& username); + + void SetAuthToken(const std::string& auth_token, SaveCredentials); + + struct AuthResults { + SaveCredentials credentials_saved; + std::string email; + std::string password; + + // Fields that store various cookies. + std::string sid; + std::string lsid; + std::string auth_token; + + std::string primary_email; + + // Fields for items returned when authentication fails. + std::string error_msg; + enum AuthenticationError auth_error; + std::string auth_error_url; + std::string captcha_token; + std::string captcha_url; + SignIn signin; + + AuthResults () : credentials_saved(DONT_SAVE_CREDENTIALS), + auth_error(None) { } + }; + + protected: + + struct AuthParams { + GaiaAuthenticator* authenticator; + uint32 request_id; + SaveCredentials should_save_credentials; + std::string email; + std::string password; + std::string captcha_token; + std::string captcha_value; + SignIn try_first; + }; + + // mutex_ must be entered before calling this function. + AuthParams MakeParams(const std::string& user_name, + const std::string& password, + SaveCredentials should_save_credentials, + const std::string& captcha_token, + const std::string& captcha_value, + SignIn try_first); + + // The real Authenticate implementations. + bool AuthenticateImpl(const AuthParams& params); + bool AuthenticateImpl(const AuthParams& params, AuthResults* results); + bool PerformGaiaRequest(const AuthParams& params, AuthResults* results); + bool LaunchAuthenticate(const AuthParams& params, bool synchronous); + static void *ThreadMain(void *arg); + + // virtual for testing purposes + virtual bool Post(const GURL& url, const std::string& post_body, + unsigned long* response_code, std::string* response_body) { + return false; + } + + // Caller should fill in results->LSID before calling. Result in + // results->primary_email. + bool LookupEmail(AuthResults* results); + + public: + // Retrieve email + inline std::string email() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.email; + } + + // Retrieve password + inline std::string password() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.password; + } + + // Retrieve AuthToken, if previously authenticated; otherwise returns "". + inline std::string auth_token() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.auth_token; + } + + // Retrieve SID cookie. For details, see the Google Accounts documentation. + inline std::string sid() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.sid; + } + + // Retrieve LSID cookie. For details, see the Google Accounts documentation. + inline std::string lsid() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.lsid; + } + + // Get last authentication error. + inline enum AuthenticationError auth_error() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.auth_error; + } + + inline std::string auth_error_url() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.auth_error_url; + } + + inline std::string captcha_token() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.captcha_token; + } + + inline std::string captcha_url() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_.captcha_url; + } + + inline AuthResults results() const { + PThreadScopedLock<PThreadMutex> enter(&mutex_); + return auth_results_; + } + + typedef EventChannel<GaiaAuthEvent, PThreadMutex> Channel; + + inline Channel* channel() const { + return channel_; + } + + private: + bool IssueAuthToken(AuthResults* results, const std::string& service_id, + bool long_lived_token); + + // Helper method to parse response when authentication succeeds. + void ExtractTokensFrom(const std::string& response, AuthResults* results); + // Helper method to parse response when authentication fails. + void ExtractAuthErrorFrom(const std::string& response, AuthResults* results); + + // Fields for the obvious data items. + const std::string user_agent_; + const std::string service_id_; + const std::string gaia_url_; + + AuthResults auth_results_; + + // When multiple async requests are running, only the one that started most + // recently updates the values. + // + // Note that even though this code was written to handle multiple requests + // simultaneously, the sync code issues auth requests one at a time. + uint32 request_count_; + + Channel* channel_; + + // Used to compute backoff time for next allowed authentication. + int delay_; // In seconds. + time_t next_allowed_auth_attempt_time_; + int early_auth_attempt_count_; + + // Protects auth_results_, and request_count_. + mutable PThreadMutex mutex_; +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_ diff --git a/chrome/browser/sync/engine/net/gaia_authenticator_unittest.cc b/chrome/browser/sync/engine/net/gaia_authenticator_unittest.cc new file mode 100644 index 0000000..c7c6eb8 --- /dev/null +++ b/chrome/browser/sync/engine/net/gaia_authenticator_unittest.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/engine/net/gaia_authenticator.h" + +#include <string> + +#include "chrome/browser/sync/engine/net/http_return.h" +#include "chrome/browser/sync/util/sync_types.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace browser_sync { + +class GaiaAuthenticatorTest : public testing::Test { }; + +class GaiaAuthMock : public GaiaAuthenticator { + public: + GaiaAuthMock() : GaiaAuthenticator("useragent", + "serviceid", + "http://gaia_url") {} + ~GaiaAuthMock() {} + protected: + bool Post(const GURL& url, const string& post_body, + unsigned long* response_code, string* response_body) { + *response_code = RC_REQUEST_OK; + response_body->assign("body\n"); + return true; + } +}; + +TEST(GaiaAuthenticatorTest, TestNewlineAtEndOfAuthTokenRemoved) { + GaiaAuthMock mock_auth; + GaiaAuthenticator::AuthResults results; + EXPECT_TRUE(mock_auth.IssueAuthToken(&results, "sid", true)); + EXPECT_EQ(0, results.auth_token.compare("body")); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/engine/net/http_return.h b/chrome/browser/sync/engine/net/http_return.h new file mode 100644 index 0000000..fd5167b --- /dev/null +++ b/chrome/browser/sync/engine/net/http_return.h @@ -0,0 +1,16 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_HTTP_RETURN_H_ +#define CHROME_BROWSER_SYNC_ENGINE_NET_HTTP_RETURN_H_ + +namespace browser_sync { +enum HTTPReturnCode { + RC_REQUEST_OK = 200, + RC_UNAUTHORIZED = 401, + RC_FORBIDDEN = 403, +}; +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_ENGINE_NET_HTTP_RETURN_H_ diff --git a/chrome/browser/sync/engine/net/openssl_init.cc b/chrome/browser/sync/engine/net/openssl_init.cc new file mode 100644 index 0000000..afaf006 --- /dev/null +++ b/chrome/browser/sync/engine/net/openssl_init.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// OpenSSL multi-threading initialization + +#include "chrome/browser/sync/engine/net/openssl_init.h" + +#include <openssl/crypto.h> + +#include "base/logging.h" +#include "chrome/browser/sync/util/compat-pthread.h" +#include "chrome/browser/sync/util/pthread_helpers.h" + +// OpenSSL requires multithreading callbacks to be initialized prior to using +// the library so that it can manage thread locking as necessary. + +// Dynamic lock type +// +// This needs to be a struct and in global scope because OpenSSL relies on some +// macro magic. +struct CRYPTO_dynlock_value { + PThreadMutex mutex; + void Lock() { + mutex.Lock(); + } + void Unlock() { + mutex.Unlock(); + } +}; + +namespace { + +// This array stores all of the mutexes available to OpenSSL +PThreadMutex* mutex_buf = NULL; + +// OpenSSL mutex handling callback functions + +// OpenSSL Callback - Locks/unlocks the specified mutex held by OpenSSL. +void OpenSslMutexLockControl(int mode, int n, const char* file, int line) { + if (mode & CRYPTO_LOCK) { + mutex_buf[n].Lock(); + } else { + mutex_buf[n].Unlock(); + } +} + +// OpenSSL Callback - Returns the thread ID +unsigned long OpenSslGetThreadID(void) { + return GetCurrentThreadId(); +} + +// Dynamic locking functions + +// Allocate a new lock +struct CRYPTO_dynlock_value* dyn_create_function(const char* file, int line) { + return new CRYPTO_dynlock_value; +} + +void dyn_lock_function(int mode, struct CRYPTO_dynlock_value* lock, + const char* file, int line) { + if (mode & CRYPTO_LOCK) { + lock->Lock(); + } else { + lock->Unlock(); + } +} + +void dyn_destroy_function(struct CRYPTO_dynlock_value* lock, + const char* file, int line) { + delete lock; +} + +} // namespace + +// We want to log the version of the OpenSSL library being used, in particular +// for the case where it's dynamically linked. We want the version from the +// library, not from the header files. It seems the OpenSSL folks haven't +// bothered with an accessor for this, so we just pluck it out. +#ifdef OS_WINDOWS +// TODO(sync): Figure out how to get the SSL version string on Windows. +const char* SSL_version_str = "UNKNOWN"; +#else +extern const char* SSL_version_str; +#endif + +namespace browser_sync { + +// Initializes the OpenSSL multithreading callbacks. This isn't thread-safe, +// but it is called early enough that it doesn't matter. +void InitOpenSslMultithreading() { + LOG(INFO) << "Using OpenSSL headers version " << OPENSSL_VERSION_TEXT + << ", lib version " << SSL_version_str; + + if (mutex_buf) + return; + + mutex_buf = new PThreadMutex[CRYPTO_num_locks()]; + CHECK(NULL != mutex_buf); + + // OpenSSL has only one single global set of callbacks, so this + // initialization must be done only once, even though the OpenSSL lib may be + // used by multiple modules (jingle jabber connections and P2P tunnels). + CRYPTO_set_id_callback(OpenSslGetThreadID); + CRYPTO_set_locking_callback(OpenSslMutexLockControl); + + CRYPTO_set_dynlock_create_callback(dyn_create_function); + CRYPTO_set_dynlock_lock_callback(dyn_lock_function); + CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function); +} + +// Cleans up the OpenSSL multithreading callbacks. +void CleanupOpenSslMultithreading() { + if (!mutex_buf) { + return; + } + + CRYPTO_set_dynlock_create_callback(NULL); + CRYPTO_set_dynlock_lock_callback(NULL); + CRYPTO_set_dynlock_destroy_callback(NULL); + + CRYPTO_set_id_callback(NULL); + CRYPTO_set_locking_callback(NULL); + + delete [] mutex_buf; + mutex_buf = NULL; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/engine/net/openssl_init.h b/chrome/browser/sync/engine/net/openssl_init.h new file mode 100644 index 0000000..8cd4558 --- /dev/null +++ b/chrome/browser/sync/engine/net/openssl_init.h @@ -0,0 +1,20 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// OpenSSL multi-threading initialization + +#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_OPENSSL_INIT_H_ +#define CHROME_BROWSER_SYNC_ENGINE_NET_OPENSSL_INIT_H_ + +namespace browser_sync { + +// Initializes the OpenSSL multithreading callbacks. Returns false on failure. +void InitOpenSslMultithreading(); + +// Cleans up the OpenSSL multithreading callbacks. +void CleanupOpenSslMultithreading(); + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_ENGINE_NET_OPENSSL_INIT_H_ diff --git a/chrome/browser/sync/engine/net/server_connection_manager.cc b/chrome/browser/sync/engine/net/server_connection_manager.cc new file mode 100644 index 0000000..42b380b --- /dev/null +++ b/chrome/browser/sync/engine/net/server_connection_manager.cc @@ -0,0 +1,375 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/engine/net/server_connection_manager.h" + +#include <errno.h> + +#include <ostream> +#include <string> +#include <vector> + +#include "chrome/browser/sync/engine/net/http_return.h" +#include "chrome/browser/sync/engine/net/url_translator.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/engine/syncer.h" +#include "chrome/browser/sync/engine/syncproto.h" +#include "chrome/browser/sync/protocol/sync.pb.h" +#include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/browser/sync/util/character_set_converters.h" +#include "chrome/browser/sync/util/event_sys-inl.h" + +namespace browser_sync { + +using std::ostream; +using std::string; +using std::vector; + +static const char kSyncServerSyncPath[] = "/command/"; + +// At the /time/ path of the sync server, we expect to find a very simple +// time of day service that we can use to synchronize the local clock with +// server time. +static const char kSyncServerGetTimePath[] = "/time"; + +static const ServerConnectionEvent shutdown_event = + { ServerConnectionEvent::SHUTDOWN, HttpResponse::CONNECTION_UNAVAILABLE, + false }; + +typedef PThreadScopedLock<PThreadMutex> MutexLock; + +struct ServerConnectionManager::PlatformMembers { + explicit PlatformMembers(const string& user_agent) { } + void Kill() { } + void Reset() { } + void Reset(MutexLock*) { } +}; + +bool ServerConnectionManager::Post::ReadBufferResponse( + string* buffer_out, HttpResponse* response, bool require_response) { + if (RC_REQUEST_OK != response->response_code) { + response->server_status = HttpResponse::SYNC_SERVER_ERROR; + return false; + } + + if (require_response && (1 > response->content_length)) + return false; + + const int64 bytes_read = ReadResponse(buffer_out, response->content_length); + if (bytes_read != response->content_length) { + response->server_status = HttpResponse::IO_ERROR; + return false; + } + return true; +} + +bool ServerConnectionManager::Post::ReadDownloadResponse( + HttpResponse* response, string* buffer_out) { + const int64 bytes_read = ReadResponse(buffer_out, response->content_length); + + if (bytes_read != response->content_length) { + LOG(ERROR) << "Mismatched content lengths, server claimed " << + response->content_length << ", but sent " << bytes_read; + response->server_status = HttpResponse::IO_ERROR; + return false; + } + return true; +} + +namespace { + string StripTrailingSlash(const string& s) { + int stripped_end_pos = s.size(); + if (s.at(stripped_end_pos - 1) == '/') { + stripped_end_pos = stripped_end_pos - 1; + } + + return s.substr(0, stripped_end_pos); + } +} // namespace + +// TODO(chron): Use a GURL instead of string concatenation. + string ServerConnectionManager::Post::MakeConnectionURL( + const string& sync_server, const string& path, + bool use_ssl) const { + string connection_url = (use_ssl ? "https://" : "http://"); + connection_url += sync_server; + connection_url = StripTrailingSlash(connection_url); + connection_url += path; + + return connection_url; +} + +int ServerConnectionManager::Post::ReadResponse(string* out_buffer, + int length) { + int bytes_read = buffer_.length(); + CHECK(length <= bytes_read); + out_buffer->assign(buffer_); + return bytes_read; +} + +// A helper class that automatically notifies when the status changes: +struct WatchServerStatus { + WatchServerStatus(ServerConnectionManager* conn_mgr, HttpResponse* response) + : conn_mgr_(conn_mgr), response_(response), + reset_count_(conn_mgr->reset_count_), + server_reachable_(conn_mgr->server_reachable_) { + response->server_status = conn_mgr->server_status_; + } + ~WatchServerStatus() { + // Don't update the status of the connection if it has been reset. + // TODO(timsteele): Do we need this? Is this used by multiple threads? + if (reset_count_ != conn_mgr_->reset_count_) + return; + if (conn_mgr_->server_status_ != response_->server_status) { + conn_mgr_->server_status_ = response_->server_status; + conn_mgr_->NotifyStatusChanged(); + return; + } + // Notify if we've gone on or offline. + if (server_reachable_ != conn_mgr_->server_reachable_) + conn_mgr_->NotifyStatusChanged(); + } + ServerConnectionManager* const conn_mgr_; + HttpResponse* const response_; + // TODO(timsteele): Should this be Barrier:AtomicIncrement? + base::subtle::AtomicWord reset_count_; + bool server_reachable_; +}; + +ServerConnectionManager::ServerConnectionManager( + const string& server, int port, bool use_ssl, const string& user_agent, + const string& client_id) + : sync_server_(server), sync_server_port_(port), + channel_(new Channel(shutdown_event)), + server_status_(HttpResponse::NONE), server_reachable_(false), + client_id_(client_id), use_ssl_(use_ssl), + user_agent_(user_agent), + platform_(new PlatformMembers(user_agent)), + reset_count_(0), error_count_(0), + terminate_all_io_(false), + proto_sync_path_(kSyncServerSyncPath), + get_time_path_(kSyncServerGetTimePath) { +} + +ServerConnectionManager::~ServerConnectionManager() { + delete channel_; + delete platform_; + shutdown_event_mutex_.Lock(); + int result = pthread_cond_broadcast(&shutdown_event_condition_.condvar_); + shutdown_event_mutex_.Unlock(); + if (result) { + LOG(ERROR) << "Error signaling shutdown_event_condition_ last error = " + << result; + } +} + +void ServerConnectionManager::NotifyStatusChanged() { + ServerConnectionEvent event = { ServerConnectionEvent::STATUS_CHANGED, + server_status_, + server_reachable_ }; + channel_->NotifyListeners(event); +} + +// Uses currently set auth token. Set by AuthWatcher. +bool ServerConnectionManager::PostBufferWithCachedAuth( + const PostBufferParams* params) { + string path = + MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_)); + return PostBufferToPath(params, path, auth_token_); +} + +bool ServerConnectionManager::PostBufferWithAuth(const PostBufferParams* params, + const string& auth_token) { + string path = MakeSyncServerPath(proto_sync_path(), + MakeSyncQueryString(client_id_)); + + return PostBufferToPath(params, path, auth_token); +} + +bool ServerConnectionManager::PostBufferToPath(const PostBufferParams* params, + const string& path, + const string& auth_token) { + WatchServerStatus watcher(this, params->response); + scoped_ptr<Post> post(MakePost()); + post->set_timing_info(params->timing_info); + bool ok = post->Init(path.c_str(), auth_token, params->buffer_in, + params->response); + + if (!ok || RC_REQUEST_OK != params->response->response_code) { + IncrementErrorCount(); + return false; + } + + if (post->ReadBufferResponse(params->buffer_out, params->response, true)) { + params->response->server_status = HttpResponse::SERVER_CONNECTION_OK; + server_reachable_ = true; + return true; + } + return false; +} + +bool ServerConnectionManager::CheckTime(int32* out_time) { + // Verify that the server really is reachable by checking the time. We need + // to do this because of wifi interstitials that intercept messages from the + // client and return HTTP OK instead of a redirect. + HttpResponse response; + WatchServerStatus watcher(this, &response); + string post_body = "command=get_time"; + + // We only retry the CheckTime call if we were reset during the CheckTime + // attempt. We only try 3 times in case we're in a reset loop elsewhere. + base::subtle::AtomicWord start_reset_count = reset_count_ - 1; + for (int i = 0 ; i < 3 && start_reset_count != reset_count_ ; i++) { + start_reset_count = reset_count_; + scoped_ptr<Post> post(MakePost()); + + // Note that the server's get_time path doesn't require authentication. + string get_time_path = + MakeSyncServerPath(kSyncServerGetTimePath, post_body); + LOG(INFO) << "Requesting get_time from:" << get_time_path; + + string blank_post_body; + bool ok = post->Init(get_time_path.c_str(), blank_post_body, + blank_post_body, &response); + if (!ok) { + LOG(INFO) << "Unable to check the time"; + continue; + } + string time_response; + time_response.resize(response.content_length); + ok = post->ReadDownloadResponse(&response, &time_response); + if (!ok || string::npos != + time_response.find_first_not_of("0123456789")) { + LOG(ERROR) << "unable to read a non-numeric response from get_time:" + << time_response; + continue; + } + *out_time = atoi(time_response.c_str()); + LOG(INFO) << "Server was reachable."; + return true; + } + IncrementErrorCount(); + return false; +} + +bool ServerConnectionManager::IsServerReachable() { + int32 time; + return CheckTime(&time); +} + +bool ServerConnectionManager::IsUserAuthenticated() { + return IsGoodReplyFromServer(server_status_); +} + +bool ServerConnectionManager::CheckServerReachable() { + const bool server_is_reachable = IsServerReachable(); + if (server_reachable_ != server_is_reachable) { + server_reachable_ = server_is_reachable; + NotifyStatusChanged(); + } + return server_is_reachable; +} + +void ServerConnectionManager::kill() { + { + MutexLock lock(&terminate_all_io_mutex_); + terminate_all_io_ = true; + } + platform_->Kill(); + shutdown_event_mutex_.Lock(); + int result = pthread_cond_broadcast(&shutdown_event_condition_.condvar_); + shutdown_event_mutex_.Unlock(); + if (result) { + LOG(ERROR) << "Error signaling shutdown_event_condition_ last error = " + << result; + } +} + +void ServerConnectionManager::ResetAuthStatus() { + ResetConnection(); + server_status_ = HttpResponse::NONE; + NotifyStatusChanged(); +} + +void ServerConnectionManager::ResetConnection() { + base::subtle::NoBarrier_AtomicIncrement(&reset_count_, 1); + platform_->Reset(); +} + +bool ServerConnectionManager::IncrementErrorCount() { +#ifdef OS_WINDOWS + error_count_mutex_.Lock(); + error_count_++; + + if (error_count_ > kMaxConnectionErrorsBeforeReset) { + error_count_ = 0; + + // Be careful with this mutex because calling out to other methods can + // result in being called back. Unlock it here to prevent any potential + // double-acquisitions. + error_count_mutex_.Unlock(); + + if (!IsServerReachable()) { + LOG(WARNING) << "Too many connection failures, server is not reachable. " + << "Resetting connections."; + ResetConnection(); + } else { + LOG(WARNING) << "Multiple connection failures while server is reachable."; + } + return false; + } + + error_count_mutex_.Unlock(); + return true; +#endif + return true; +} + +void ServerConnectionManager::SetServerParameters(const string& server_url, + int port, bool use_ssl) { + { + ParametersLock lock(&server_parameters_mutex_); + sync_server_ = server_url; + sync_server_port_ = port; + use_ssl_ = use_ssl; + } + platform_->Reset(); +} + +// Returns the current server parameters in server_url and port. +void ServerConnectionManager::GetServerParameters(string* server_url, + int* port, bool* use_ssl) { + ParametersLock lock(&server_parameters_mutex_); + if (server_url != NULL) + *server_url = sync_server_; + if (port != NULL) + *port = sync_server_port_; + if (use_ssl != NULL) + *use_ssl = use_ssl_; +} + +bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm, + syncable::DirectoryManager* manager, + const PathString &share) { + syncable::ScopedDirLookup dir(manager, share); + if (!dir.good()) { + LOG(INFO) << "Dir lookup failed"; + return false; + } + string birthday = dir->store_birthday(); + if (!birthday.empty()) + csm->set_store_birthday(birthday); + csm->set_share(ToUTF8(share).get_string()); + return true; +} + +} // namespace browser_sync + +std::ostream& operator << (std::ostream& s, + const struct browser_sync::HttpResponse& hr) { + s << " Response Code (bogus on error): " << hr.response_code; + s << " Content-Length (bogus on error): " << hr.content_length; + s << " Server Status: " << hr.server_status; + return s; +} diff --git a/chrome/browser/sync/engine/net/server_connection_manager.h b/chrome/browser/sync/engine/net/server_connection_manager.h new file mode 100644 index 0000000..8093d45 --- /dev/null +++ b/chrome/browser/sync/engine/net/server_connection_manager.h @@ -0,0 +1,345 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_SERVER_CONNECTION_MANAGER_H_ +#define CHROME_BROWSER_SYNC_ENGINE_NET_SERVER_CONNECTION_MANAGER_H_ + +#include <iosfwd> +#include <string> +#include <vector> + +#include "base/atomicops.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "chrome/browser/sync/engine/net/http_return.h" +#include "chrome/browser/sync/syncable/syncable_id.h" +#include "chrome/browser/sync/util/event_sys.h" +#include "chrome/browser/sync/util/pthread_helpers.h" +#include "chrome/browser/sync/util/signin.h" +#include "chrome/browser/sync/util/sync_types.h" + +namespace syncable { +class WriteTransaction; +class DirectoryManager; +} + +namespace sync_pb { +class ClientToServerMessage; +}; + +struct RequestTimingInfo; + +namespace browser_sync { + +class ClientToServerMessage; + +// How many connection errors are accepted before network handles are closed +// and reopened. +static const int32 kMaxConnectionErrorsBeforeReset = 10; + +// HttpResponse gathers the relevant output properties of an HTTP request. +// Depending on the value of the server_status code, response_code, and +// content_length may not be valid. +struct HttpResponse { + enum ServerConnectionCode { + // For uninitialized state. + NONE, + + // CONNECTION_UNAVAILABLE is returned when InternetConnect() fails. + CONNECTION_UNAVAILABLE, + + // IO_ERROR is returned when reading/writing to a buffer has failed. + IO_ERROR, + + // SYNC_SERVER_ERROR is returned when the HTTP status code indicates that + // a non-auth error has occured. + SYNC_SERVER_ERROR, + + // SYNC_AUTH_ERROR is returned when the HTTP status code indicates that an + // auth error has occured (i.e. a 401) + SYNC_AUTH_ERROR, + + // All the following connection codes are valid responses from the server. + // Means the server is up. If you update this list, be sure to also update + // IsGoodReplyFromServer(). + + // SERVER_CONNECTION_OK is returned when request was handled correctly. + SERVER_CONNECTION_OK, + + // RETRY is returned when a Commit request fails with a RETRY response from + // the server. + // + // TODO(idana): the server no longer returns RETRY so we should remove this + // value. + RETRY, + }; + + // The HTTP Status code. + int64 response_code; + + // The value of the Content-length header. + int64 content_length; + + // The size of a download request's payload. + int64 payload_length; + + // Identifies the type of failure, if any. + ServerConnectionCode server_status; +}; + +inline bool IsGoodReplyFromServer(HttpResponse::ServerConnectionCode code) { + return code >= HttpResponse::SERVER_CONNECTION_OK; +} + +struct ServerConnectionEvent { + enum { SHUTDOWN, STATUS_CHANGED } what_happened; + HttpResponse::ServerConnectionCode connection_code; + bool server_reachable; + + // Traits. + typedef ServerConnectionEvent EventType; + static inline bool IsChannelShutdownEvent(const EventType& event) { + return SHUTDOWN == event.what_happened; + } +}; + +struct WatchServerStatus; + +// Use this class to interact with the sync server. +// The ServerConnectionManager currently supports POSTing protocol buffers. +// +// *** This class is thread safe. In fact, you should consider creating only +// one instance for every server that you need to talk to. +class ServerConnectionManager { + friend class Post; + friend struct WatchServerStatus; + public: + typedef EventChannel<ServerConnectionEvent, PThreadMutex> Channel; + + // The lifetime of the GaiaAuthenticator must be longer than the instance + // of the ServerConnectionManager that you're creating. + ServerConnectionManager(const std::string& server, int port, bool use_ssl, + const std::string& user_agent, + const std::string& client_id); + + virtual ~ServerConnectionManager(); + + // buffer_in - will be POSTed + // buffer_out - string will be overwritten with response + struct PostBufferParams { + const std::string& buffer_in; + std::string* buffer_out; + HttpResponse* response; + RequestTimingInfo* timing_info; + }; + + // Abstract class providing network-layer functionality to the + // ServerConnectionManager. Subclasses implement this using an HTTP stack of + // their choice. + class Post { + public: + explicit Post(ServerConnectionManager* scm) : scm_(scm), timing_info_(0) { + } + virtual ~Post() { } + + // Called to initialize and perform an HTTP POST. + virtual bool Init(const char* path, const std::string& auth_token, + const std::string& payload, + HttpResponse* response) = 0; + + bool ReadBufferResponse(std::string* buffer_out, HttpResponse* response, + bool require_response); + bool ReadDownloadResponse(HttpResponse* response, std::string* buffer_out); + + void set_timing_info(RequestTimingInfo* timing_info) { + timing_info_ = timing_info; + } + RequestTimingInfo* timing_info() { return timing_info_; } + + protected: + std::string MakeConnectionURL(const std::string& sync_server, + const std::string& path, bool use_ssl) const; + + void GetServerParams(std::string* server, int* server_port, + bool* use_ssl) { + ServerConnectionManager::ParametersLock lock( + &scm_->server_parameters_mutex_); + server->assign(scm_->sync_server_); + *server_port = scm_->sync_server_port_; + *use_ssl = scm_->use_ssl_; + } + + std::string buffer_; + ServerConnectionManager* scm_; + + private: + int ReadResponse(void* buffer, int length); + int ReadResponse(std::string* buffer, int length); + RequestTimingInfo* timing_info_; + }; + + // POSTS buffer_in and reads a response into buffer_out. Uses our currently + // set auth token in our headers. + // + // Returns true if executed successfully. + virtual bool PostBufferWithCachedAuth(const PostBufferParams* params); + + // POSTS buffer_in and reads a response into buffer_out. Add a specific auth + // token to http headers. + // + // Returns true if executed successfully. + virtual bool PostBufferWithAuth(const PostBufferParams* params, + const std::string& auth_token); + + // Checks the time on the server. Returns false if the request failed. |time| + // is an out parameter that stores the value returned from the server. + virtual bool CheckTime(int32* out_time); + + // Returns true if sync_server_ is reachable. This method verifies that the + // server is pingable and that traffic can be sent to and from it. + virtual bool IsServerReachable(); + + // Returns true if user has been successfully authenticated. + virtual bool IsUserAuthenticated(); + + // Updates status and broadcasts events on change. + bool CheckServerReachable(); + + // Signal the shutdown event to notify listeners. + virtual void kill(); + + inline Channel* channel() const { return channel_; } + + inline std::string user_agent() const { return user_agent_; } + + inline HttpResponse::ServerConnectionCode server_status() const { + return server_status_; + } + + inline bool server_reachable() const { return server_reachable_; } + + void ResetAuthStatus(); + + void ResetConnection(); + + void NotifyStatusChanged(); + + const std::string client_id() const { return client_id_; } + + void SetDomainFromSignIn(SignIn signin_type, const std::string& signin); + + // This changes the server info used by the connection manager. This allows + // a single client instance to talk to different backing servers. This is + // typically called during / after authentication so that the server url + // can be a function of the user's login id. A side effect of this call is + // that ResetConnection is called. + void SetServerParameters(const std::string& server_url, int port, + bool use_ssl); + + // Returns the current server parameters in server_url, port and use_ssl. + void GetServerParameters(std::string* server_url, int* port, bool* use_ssl); + + bool terminate_all_io() const { + PThreadScopedLock<PThreadMutex> lock(&terminate_all_io_mutex_); + return terminate_all_io_; + } + + // Factory method to create a Post object we can use for communication with + // the server. + virtual Post* MakePost() { + return NULL; // For testing. + }; + + void set_auth_token(const std::string& auth_token) { + auth_token_.assign(auth_token); + } + + protected: + + PThreadMutex shutdown_event_mutex_; + PThreadCondVar shutdown_event_condition_; + + // Protects access to sync_server_, sync_server_port_ and use_ssl_: + mutable PThreadMutex server_parameters_mutex_; + typedef PThreadScopedLock<PThreadMutex> ParametersLock; + + // The sync_server_ is the server that requests will be made to. + std::string sync_server_; + + // The sync_server_port_ is the port that HTTP requests will be made on. + int sync_server_port_; + + // The unique id of the user's client. + const std::string client_id_; + + // The user-agent string for HTTP. + std::string user_agent_; + + // Indicates whether or not requests should be made using HTTPS. + bool use_ssl_; + + // The paths we post to. + mutable PThreadMutex path_mutex_; + typedef PThreadScopedLock<PThreadMutex> ScopedPathLock; + + std::string proto_sync_path_; + std::string get_time_path_; + + // The auth token to use in authenticated requests. Set by the AuthWatcher. + std::string auth_token_; + + inline std::string proto_sync_path() const { + ScopedPathLock lock(&path_mutex_); + return proto_sync_path_; + } + std::string get_time_path() const { + ScopedPathLock lock(&path_mutex_); + return get_time_path_; + } + + // Called wherever a failure should be taken as an indication that we may + // be experiencing connection difficulties. + virtual bool IncrementErrorCount(); + mutable PThreadMutex error_count_mutex_; // Protects error_count_ + int error_count_; // Tracks the number of connection errors. + + protected: + Channel* const channel_; + // Volatile so various threads can call server_status() without + // synchronization. + volatile HttpResponse::ServerConnectionCode server_status_; + bool server_reachable_; + + struct PlatformMembers; // Contains platform specific member vars. + PlatformMembers* const platform_; + + // A counter that is incremented everytime ResetAuthStatus() is called. + volatile base::subtle::AtomicWord reset_count_; + + // NOTE: Tests rely on this protected function being virtual. + // + // Internal PostBuffer base function. + virtual bool PostBufferToPath(const PostBufferParams*, + const std::string& path, + const std::string& auth_token); + + private: + mutable PThreadMutex terminate_all_io_mutex_; + bool terminate_all_io_; // when set to true, terminate all connections asap + DISALLOW_COPY_AND_ASSIGN(ServerConnectionManager); +}; + +// Fills a ClientToServerMessage with the appropriate share and birthday +// settings. +bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm, + syncable::DirectoryManager* manager, + const PathString &share); + +} // namespace browser_sync + +std::ostream& operator<<(std::ostream& s, + const struct browser_sync::HttpResponse& hr); + +#endif // CHROME_BROWSER_SYNC_ENGINE_NET_SERVER_CONNECTION_MANAGER_H_ diff --git a/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc new file mode 100644 index 0000000..19981de --- /dev/null +++ b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h" + +#include "chrome/browser/sync/engine/net/http_return.h" +#include "chrome/browser/sync/engine/syncapi.h" + +using browser_sync::HttpResponse; +using std::string; + +namespace sync_api { + +bool SyncAPIBridgedPost::Init(const char* path, const string& auth_token, + const string& payload, HttpResponse* response) { + string sync_server; + int sync_server_port = 0; + bool use_ssl = false; + GetServerParams(&sync_server, &sync_server_port, &use_ssl); + std::string connection_url = MakeConnectionURL(sync_server, path, use_ssl); + + HttpPostProviderInterface* http = factory_->Create(); + http->SetUserAgent(scm_->user_agent().c_str()); + http->SetURL(connection_url.c_str(), sync_server_port); + + if (!auth_token.empty()) { + string headers = "Authorization: GoogleLogin auth=" + auth_token; + http->SetExtraRequestHeaders(headers.c_str()); + } + + // Must be octet-stream, or the payload may be parsed for a cookie. + http->SetPostPayload("application/octet-stream", payload.length(), + payload.data()); + + // Issue the POST, blocking until it finishes. + int os_error_code = 0; + int response_code = 0; + if (!http->MakeSynchronousPost(&os_error_code, &response_code)) { + LOG(INFO) << "Http POST failed, error returns: " << os_error_code; + response->server_status = HttpResponse::IO_ERROR; + return false; + } + + // We got a server response, copy over response codes and content. + response->response_code = response_code; + response->content_length = + static_cast<int64>(http->GetResponseContentLength()); + response->payload_length = + static_cast<int64>(http->GetResponseContentLength()); + if (response->response_code < 400) + response->server_status = HttpResponse::SERVER_CONNECTION_OK; + else if (response->response_code == browser_sync::RC_UNAUTHORIZED) + response->server_status = HttpResponse::SYNC_AUTH_ERROR; + else + response->server_status = HttpResponse::SYNC_SERVER_ERROR; + + // Write the content into our buffer. + buffer_.assign(http->GetResponseContent(), http->GetResponseContentLength()); + + // We're done with the HttpPostProvider. + factory_->Destroy(http); + return true; +} + +SyncAPIServerConnectionManager::~SyncAPIServerConnectionManager() { + delete post_provider_factory_; +} + +void SyncAPIServerConnectionManager::SetHttpPostProviderFactory( + HttpPostProviderFactory* factory) { + if (post_provider_factory_) + delete post_provider_factory_; + post_provider_factory_ = factory; +} + +} // namespace sync_api diff --git a/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h new file mode 100644 index 0000000..84a355e --- /dev/null +++ b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h @@ -0,0 +1,75 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_SYNCAPI_SERVER_CONNECTION_MANAGER_H_ +#define CHROME_BROWSER_SYNC_ENGINE_NET_SYNCAPI_SERVER_CONNECTION_MANAGER_H_ + +#include <string> + +#include "chrome/browser/sync/engine/net/server_connection_manager.h" + +namespace sync_api { + +class HttpPostProviderFactory; + +// This provides HTTP Post functionality through the interface provided +// to the sync API by the application hosting the syncer backend. +class SyncAPIBridgedPost : + public browser_sync::ServerConnectionManager::Post { + public: + SyncAPIBridgedPost(browser_sync::ServerConnectionManager* scm, + HttpPostProviderFactory* factory) + : Post(scm), factory_(factory) { + } + + virtual ~SyncAPIBridgedPost() { } + + virtual bool Init(const char* path, + const std::string& auth_token, + const std::string& payload, + browser_sync::HttpResponse* response); + + private: + // Pointer to the factory we use for creating HttpPostProviders. We do not + // own |factory_|. + HttpPostProviderFactory* factory_; + + DISALLOW_COPY_AND_ASSIGN(SyncAPIBridgedPost); +}; + +// A ServerConnectionManager subclass used by the syncapi layer. We use a +// subclass so that we can override MakePost() to generate a POST object using +// an instance of the HttpPostProviderFactory class. +class SyncAPIServerConnectionManager : + public browser_sync::ServerConnectionManager { + public: + SyncAPIServerConnectionManager(const std::string& server, + int port, + bool use_ssl, + const std::string& client_version, + const std::string& client_id) + : ServerConnectionManager(server, port, use_ssl, client_version, + client_id), + post_provider_factory_(NULL) { + } + + virtual ~SyncAPIServerConnectionManager(); + + // This method gives ownership of |factory| to |this|. + void SetHttpPostProviderFactory(HttpPostProviderFactory* factory); + protected: + virtual Post* MakePost() { + return new SyncAPIBridgedPost(this, post_provider_factory_); + } + private: + // A factory creating concrete HttpPostProviders for use whenever we need to + // issue a POST to sync servers. + HttpPostProviderFactory* post_provider_factory_; + + DISALLOW_COPY_AND_ASSIGN(SyncAPIServerConnectionManager); +}; + +} // namespace sync_api + +#endif // CHROME_BROWSER_SYNC_ENGINE_NET_SYNCAPI_SERVER_CONNECTION_MANAGER_H_ diff --git a/chrome/browser/sync/engine/net/url_translator.cc b/chrome/browser/sync/engine/net/url_translator.cc new file mode 100644 index 0000000..0931c36 --- /dev/null +++ b/chrome/browser/sync/engine/net/url_translator.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Contains the definition of a few helper functions used for generating sync +// URLs. + +#include "chrome/browser/sync/engine/net/url_translator.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/port.h" +#include "chrome/browser/sync/util/character_set_converters.h" +#include "net/base/escape.h" + +using std::string; + +namespace browser_sync { + +namespace { +// Parameters that the server understands. (here, a-Z) +const char kParameterAuthToken[] = "auth"; +const char kParameterClientID[] = "client_id"; +} + +// Convenience wrappers around CgiEscapePath(). +string CgiEscapeString(const char* src) { + return CgiEscapeString(string(src)); +} + +string CgiEscapeString(const string& src) { + return EscapePath(src); +} + +// This method appends the query string to the sync server path. +string MakeSyncServerPath(const string& path, const string& query_string) { + string result = path; + result.append("?"); + result.append(query_string); + return result; +} + +string MakeSyncQueryString(const string& client_id) { + string query; + query += kParameterClientID; + query += "=" + CgiEscapeString(client_id); + return query; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/engine/net/url_translator.h b/chrome/browser/sync/engine/net/url_translator.h new file mode 100644 index 0000000..717e15b --- /dev/null +++ b/chrome/browser/sync/engine/net/url_translator.h @@ -0,0 +1,27 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Contains the declaration of a few helper functions used for generating sync +// URLs. + +#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_URL_TRANSLATOR_H_ +#define CHROME_BROWSER_SYNC_ENGINE_NET_URL_TRANSLATOR_H_ + +#include <string> + +namespace browser_sync { + +// Convenience wrappers around CgiEscapePath(), used by gaia_auth. +std::string CgiEscapeString(const char* src); +std::string CgiEscapeString(const std::string& src); + +// This method appends the query string to the sync server path. +std::string MakeSyncServerPath(const std::string& path, + const std::string& query_string); + +std::string MakeSyncQueryString(const std::string& client_id); + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_ENGINE_NET_URL_TRANSLATOR_H_ |