15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <string>
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "jingle/notifier/communicator/single_login_attempt.h"
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/basictypes.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h"
11868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string_number_conversions.h"
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/strings/string_split.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "jingle/notifier/base/const_communicator.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "jingle/notifier/base/gaia_token_pre_xmpp_auth.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "jingle/notifier/listener/xml_element_util.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/base/host_port_pair.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "talk/xmllite/xmlelement.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "talk/xmpp/constants.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "talk/xmpp/xmppclientsettings.h"
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace notifier {
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SingleLoginAttempt::Delegate::~Delegate() {}
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SingleLoginAttempt::SingleLoginAttempt(const LoginSettings& login_settings,
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                       Delegate* delegate)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : login_settings_(login_settings),
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      delegate_(delegate),
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      settings_list_(
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          MakeConnectionSettingsList(login_settings_.GetServers(),
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     login_settings_.try_ssltcp_first())),
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      current_settings_(settings_list_.begin()) {
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (settings_list_.empty()) {
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    NOTREACHED();
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TryConnect(*current_settings_);
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SingleLoginAttempt::~SingleLoginAttempt() {}
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// In the code below, we assume that calling a delegate method may end
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// up in ourselves being deleted, so we always call it last.
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// TODO(akalin): Add unit tests to enforce the behavior above.
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void SingleLoginAttempt::OnConnect(
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    base::WeakPtr<buzz::XmppTaskParentInterface> base_task) {
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DVLOG(1) << "Connected to " << current_settings_->ToString();
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  delegate_->OnConnect(base_task);
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// This function is more permissive than
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// net::HostPortPair::FromString().  If the port is missing or
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// unparseable, it assumes the default XMPP port.  The hostname may be
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// empty.
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)net::HostPortPair ParseRedirectText(const std::string& redirect_text) {
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::vector<std::string> parts;
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::SplitString(redirect_text, ':', &parts);
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  net::HostPortPair redirect_server;
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  redirect_server.set_port(kDefaultXmppPort);
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (parts.empty()) {
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return redirect_server;
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  redirect_server.set_host(parts[0]);
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (parts.size() <= 1) {
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return redirect_server;
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Try to parse the port, falling back to kDefaultXmppPort.
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int port = kDefaultXmppPort;
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!base::StringToInt(parts[1], &port)) {
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    port = kDefaultXmppPort;
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (port <= 0 || port > kuint16max) {
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    port = kDefaultXmppPort;
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  redirect_server.set_port(port);
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return redirect_server;
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void SingleLoginAttempt::OnError(buzz::XmppEngine::Error error, int subcode,
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                 const buzz::XmlElement* stream_error) {
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DVLOG(1) << "Error: " << error << ", subcode: " << subcode
88c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)           << (stream_error
89c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                   ? (", stream error: " + XmlElementToString(*stream_error))
90c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                   : std::string());
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK_EQ(error == buzz::XmppEngine::ERROR_STREAM, stream_error != NULL);
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Check for redirection.  We expect something like:
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // <stream:error><see-other-host xmlns="urn:ietf:params:xml:ns:xmpp-streams"/><str:text xmlns:str="urn:ietf:params:xml:ns:xmpp-streams">talk.google.com</str:text></stream:error> [2]
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // There are some differences from the spec [1]:
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //   - we expect a separate text element with the redirection info
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //     (which is the format Google Talk's servers use), whereas the
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //     spec puts the redirection info directly in the see-other-host
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //     element;
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //   - we check for redirection only during login, whereas the
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //     server can send down a redirection at any time according to
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //     the spec. (TODO(akalin): Figure out whether we need to handle
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //     redirection at any other point.)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  //
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // [1]: http://xmpp.org/internet-drafts/draft-saintandre-rfc3920bis-08.html#streams-error-conditions-see-other-host
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // [2]: http://forums.miranda-im.org/showthread.php?24376-GoogleTalk-drops
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (stream_error) {
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const buzz::XmlElement* other =
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        stream_error->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST);
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (other) {
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      const buzz::XmlElement* text =
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          stream_error->FirstNamed(buzz::QN_XSTREAM_TEXT);
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (text) {
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // Yep, its a "stream:error" with "see-other-host" text,
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // let's parse out the server:port, and then reconnect
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // with that.
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        const net::HostPortPair& redirect_server =
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            ParseRedirectText(text->BodyText());
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // ParseRedirectText shouldn't return a zero port.
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        DCHECK_NE(redirect_server.port(), 0u);
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // If we don't have a host, ignore the redirection and treat
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        // it like a regular error.
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (!redirect_server.host().empty()) {
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          delegate_->OnRedirect(
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              ServerInformation(
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  redirect_server,
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  current_settings_->ssltcp_support));
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          // May be deleted at this point.
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          return;
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (error == buzz::XmppEngine::ERROR_UNAUTHORIZED) {
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    DVLOG(1) << "Credentials rejected";
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delegate_->OnCredentialsRejected();
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (current_settings_ == settings_list_.end()) {
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    NOTREACHED();
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ++current_settings_;
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (current_settings_ == settings_list_.end()) {
1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    DVLOG(1) << "Could not connect to any XMPP server";
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delegate_->OnSettingsExhausted();
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  TryConnect(*current_settings_);
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void SingleLoginAttempt::TryConnect(
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const ConnectionSettings& connection_settings) {
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DVLOG(1) << "Trying to connect to " << connection_settings.ToString();
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Copy the user settings and fill in the connection parameters from
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // |connection_settings|.
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  buzz::XmppClientSettings client_settings = login_settings_.user_settings();
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  connection_settings.FillXmppClientSettings(&client_settings);
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  buzz::Jid jid(client_settings.user(), client_settings.host(),
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                buzz::STR_EMPTY);
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  buzz::PreXmppAuth* pre_xmpp_auth =
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      new GaiaTokenPreXmppAuth(
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          jid.Str(), client_settings.auth_token(),
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          client_settings.token_service(),
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          login_settings_.auth_mechanism());
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  xmpp_connection_.reset(
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      new XmppConnection(client_settings,
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         login_settings_.request_context_getter(),
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         this,
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         pre_xmpp_auth));
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace notifier
183