1 // Copyright (c) 2012 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 "jingle/notifier/communicator/single_login_attempt.h"
6 
7 #include <stdint.h>
8 
9 #include <limits>
10 #include <string>
11 
12 #include "base/logging.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "jingle/notifier/base/const_communicator.h"
16 #include "jingle/notifier/base/gaia_token_pre_xmpp_auth.h"
17 #include "jingle/notifier/listener/xml_element_util.h"
18 #include "net/base/host_port_pair.h"
19 #include "third_party/libjingle_xmpp/xmllite/xmlelement.h"
20 #include "third_party/libjingle_xmpp/xmpp/constants.h"
21 #include "third_party/libjingle_xmpp/xmpp/xmppclientsettings.h"
22 
23 namespace notifier {
24 
~Delegate()25 SingleLoginAttempt::Delegate::~Delegate() {}
26 
SingleLoginAttempt(const LoginSettings & login_settings,Delegate * delegate)27 SingleLoginAttempt::SingleLoginAttempt(const LoginSettings& login_settings,
28                                        Delegate* delegate)
29     : login_settings_(login_settings),
30       delegate_(delegate),
31       settings_list_(
32           MakeConnectionSettingsList(login_settings_.GetServers(),
33                                      login_settings_.try_ssltcp_first())),
34       current_settings_(settings_list_.begin()) {
35   if (settings_list_.empty()) {
36     NOTREACHED();
37     return;
38   }
39   TryConnect(*current_settings_);
40 }
41 
~SingleLoginAttempt()42 SingleLoginAttempt::~SingleLoginAttempt() {}
43 
44 // In the code below, we assume that calling a delegate method may end
45 // up in ourselves being deleted, so we always call it last.
46 //
47 // TODO(akalin): Add unit tests to enforce the behavior above.
48 
OnConnect(base::WeakPtr<jingle_xmpp::XmppTaskParentInterface> base_task)49 void SingleLoginAttempt::OnConnect(
50     base::WeakPtr<jingle_xmpp::XmppTaskParentInterface> base_task) {
51   DVLOG(1) << "Connected to " << current_settings_->ToString();
52   delegate_->OnConnect(base_task);
53 }
54 
55 namespace {
56 
57 // This function is more permissive than
58 // net::HostPortPair::FromString().  If the port is missing or
59 // unparseable, it assumes the default XMPP port.  The hostname may be
60 // empty.
ParseRedirectText(const std::string & redirect_text)61 net::HostPortPair ParseRedirectText(const std::string& redirect_text) {
62   std::vector<std::string> parts = base::SplitString(
63       redirect_text, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
64   net::HostPortPair redirect_server;
65   redirect_server.set_port(kDefaultXmppPort);
66   if (parts.empty()) {
67     return redirect_server;
68   }
69   redirect_server.set_host(parts[0]);
70   if (parts.size() <= 1) {
71     return redirect_server;
72   }
73   // Try to parse the port, falling back to kDefaultXmppPort.
74   int port = kDefaultXmppPort;
75   if (!base::StringToInt(parts[1], &port)) {
76     port = kDefaultXmppPort;
77   }
78   if (port <= 0 || port > std::numeric_limits<uint16_t>::max()) {
79     port = kDefaultXmppPort;
80   }
81   redirect_server.set_port(port);
82   return redirect_server;
83 }
84 
85 }  // namespace
86 
OnError(jingle_xmpp::XmppEngine::Error error,int subcode,const jingle_xmpp::XmlElement * stream_error)87 void SingleLoginAttempt::OnError(jingle_xmpp::XmppEngine::Error error, int subcode,
88                                  const jingle_xmpp::XmlElement* stream_error) {
89   DVLOG(1) << "Error: " << error << ", subcode: " << subcode
90            << (stream_error
91                    ? (", stream error: " + XmlElementToString(*stream_error))
92                    : std::string());
93 
94   DCHECK_EQ(error == jingle_xmpp::XmppEngine::ERROR_STREAM, stream_error != NULL);
95 
96   // Check for redirection.  We expect something like:
97   //
98   // <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]
99   //
100   // There are some differences from the spec [1]:
101   //
102   //   - we expect a separate text element with the redirection info
103   //     (which is the format Google Talk's servers use), whereas the
104   //     spec puts the redirection info directly in the see-other-host
105   //     element;
106   //   - we check for redirection only during login, whereas the
107   //     server can send down a redirection at any time according to
108   //     the spec. (TODO(akalin): Figure out whether we need to handle
109   //     redirection at any other point.)
110   //
111   // [1]: http://xmpp.org/internet-drafts/draft-saintandre-rfc3920bis-08.html#streams-error-conditions-see-other-host
112   // [2]: http://forums.miranda-im.org/showthread.php?24376-GoogleTalk-drops
113   if (stream_error) {
114     const jingle_xmpp::XmlElement* other =
115         stream_error->FirstNamed(jingle_xmpp::QN_XSTREAM_SEE_OTHER_HOST);
116     if (other) {
117       const jingle_xmpp::XmlElement* text =
118           stream_error->FirstNamed(jingle_xmpp::QN_XSTREAM_TEXT);
119       if (text) {
120         // Yep, its a "stream:error" with "see-other-host" text,
121         // let's parse out the server:port, and then reconnect
122         // with that.
123         const net::HostPortPair& redirect_server =
124             ParseRedirectText(text->BodyText());
125         // ParseRedirectText shouldn't return a zero port.
126         DCHECK_NE(redirect_server.port(), 0u);
127         // If we don't have a host, ignore the redirection and treat
128         // it like a regular error.
129         if (!redirect_server.host().empty()) {
130           delegate_->OnRedirect(
131               ServerInformation(
132                   redirect_server,
133                   current_settings_->ssltcp_support));
134           // May be deleted at this point.
135           return;
136         }
137       }
138     }
139   }
140 
141   if (error == jingle_xmpp::XmppEngine::ERROR_UNAUTHORIZED) {
142     DVLOG(1) << "Credentials rejected";
143     delegate_->OnCredentialsRejected();
144     return;
145   }
146 
147   if (current_settings_ == settings_list_.end()) {
148     NOTREACHED();
149     return;
150   }
151 
152   ++current_settings_;
153   if (current_settings_ == settings_list_.end()) {
154     DVLOG(1) << "Could not connect to any XMPP server";
155     delegate_->OnSettingsExhausted();
156     return;
157   }
158 
159   TryConnect(*current_settings_);
160 }
161 
TryConnect(const ConnectionSettings & connection_settings)162 void SingleLoginAttempt::TryConnect(
163     const ConnectionSettings& connection_settings) {
164   DVLOG(1) << "Trying to connect to " << connection_settings.ToString();
165   // Copy the user settings and fill in the connection parameters from
166   // |connection_settings|.
167   jingle_xmpp::XmppClientSettings client_settings = login_settings_.user_settings();
168   connection_settings.FillXmppClientSettings(&client_settings);
169 
170   jingle_xmpp::Jid jid(client_settings.user(), client_settings.host(),
171                 jingle_xmpp::STR_EMPTY);
172   jingle_xmpp::PreXmppAuth* pre_xmpp_auth =
173       new GaiaTokenPreXmppAuth(
174           jid.Str(), client_settings.auth_token(),
175           client_settings.token_service(),
176           login_settings_.auth_mechanism());
177   xmpp_connection_.reset(new XmppConnection(
178       client_settings, login_settings_.get_socket_factory_callback(), this,
179       pre_xmpp_auth, login_settings_.traffic_annotation()));
180 }
181 
182 }  // namespace notifier
183