Home | History | Annotate | Download | only in xmpp
      1 /*
      2  * libjingle
      3  * Copyright 2004--2005, Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include "talk/xmpp/xmppclient.h"
     29 
     30 #include "talk/xmpp/constants.h"
     31 #include "talk/xmpp/plainsaslhandler.h"
     32 #include "talk/xmpp/prexmppauth.h"
     33 #include "talk/xmpp/saslplainmechanism.h"
     34 #include "webrtc/base/logging.h"
     35 #include "webrtc/base/scoped_ptr.h"
     36 #include "webrtc/base/sigslot.h"
     37 #include "webrtc/base/stringutils.h"
     38 #include "xmpptask.h"
     39 
     40 namespace buzz {
     41 
     42 class XmppClient::Private :
     43     public sigslot::has_slots<>,
     44     public XmppSessionHandler,
     45     public XmppOutputHandler {
     46 public:
     47 
     48   explicit Private(XmppClient* client) :
     49     client_(client),
     50     socket_(),
     51     engine_(),
     52     proxy_port_(0),
     53     pre_engine_error_(XmppEngine::ERROR_NONE),
     54     pre_engine_subcode_(0),
     55     signal_closed_(false),
     56     allow_plain_(false) {}
     57 
     58   virtual ~Private() {
     59     // We need to disconnect from socket_ before engine_ is destructed (by
     60     // the auto-generated destructor code).
     61     ResetSocket();
     62   }
     63 
     64   // the owner
     65   XmppClient* const client_;
     66 
     67   // the two main objects
     68   rtc::scoped_ptr<AsyncSocket> socket_;
     69   rtc::scoped_ptr<XmppEngine> engine_;
     70   rtc::scoped_ptr<PreXmppAuth> pre_auth_;
     71   rtc::CryptString pass_;
     72   std::string auth_mechanism_;
     73   std::string auth_token_;
     74   rtc::SocketAddress server_;
     75   std::string proxy_host_;
     76   int proxy_port_;
     77   XmppEngine::Error pre_engine_error_;
     78   int pre_engine_subcode_;
     79   CaptchaChallenge captcha_challenge_;
     80   bool signal_closed_;
     81   bool allow_plain_;
     82 
     83   void ResetSocket() {
     84     if (socket_) {
     85       socket_->SignalConnected.disconnect(this);
     86       socket_->SignalRead.disconnect(this);
     87       socket_->SignalClosed.disconnect(this);
     88       socket_.reset(NULL);
     89     }
     90   }
     91 
     92   // implementations of interfaces
     93   void OnStateChange(int state);
     94   void WriteOutput(const char* bytes, size_t len);
     95   void StartTls(const std::string& domainname);
     96   void CloseConnection();
     97 
     98   // slots for socket signals
     99   void OnSocketConnected();
    100   void OnSocketRead();
    101   void OnSocketClosed();
    102 };
    103 
    104 bool IsTestServer(const std::string& server_name,
    105                   const std::string& test_server_domain) {
    106   return (!test_server_domain.empty() &&
    107           rtc::ends_with(server_name.c_str(),
    108                                test_server_domain.c_str()));
    109 }
    110 
    111 XmppReturnStatus XmppClient::Connect(
    112     const XmppClientSettings& settings,
    113     const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) {
    114   if (socket == NULL)
    115     return XMPP_RETURN_BADARGUMENT;
    116   if (d_->socket_)
    117     return XMPP_RETURN_BADSTATE;
    118 
    119   d_->socket_.reset(socket);
    120 
    121   d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected);
    122   d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead);
    123   d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed);
    124 
    125   d_->engine_.reset(XmppEngine::Create());
    126   d_->engine_->SetSessionHandler(d_.get());
    127   d_->engine_->SetOutputHandler(d_.get());
    128   if (!settings.resource().empty()) {
    129     d_->engine_->SetRequestedResource(settings.resource());
    130   }
    131   d_->engine_->SetTls(settings.use_tls());
    132 
    133   // The talk.google.com server returns a certificate with common-name:
    134   //   CN="gmail.com" for @gmail.com accounts,
    135   //   CN="googlemail.com" for @googlemail.com accounts,
    136   //   CN="talk.google.com" for other accounts (such as @example.com),
    137   // so we tweak the tls server setting for those other accounts to match the
    138   // returned certificate CN of "talk.google.com".
    139   // For other servers, we leave the strings empty, which causes the jid's
    140   // domain to be used.  We do the same for gmail.com and googlemail.com as the
    141   // returned CN matches the account domain in those cases.
    142   std::string server_name = settings.server().HostAsURIString();
    143   if (server_name == buzz::STR_TALK_GOOGLE_COM ||
    144       server_name == buzz::STR_TALKX_L_GOOGLE_COM ||
    145       server_name == buzz::STR_XMPP_GOOGLE_COM ||
    146       server_name == buzz::STR_XMPPX_L_GOOGLE_COM ||
    147       IsTestServer(server_name, settings.test_server_domain())) {
    148     if (settings.host() != STR_GMAIL_COM &&
    149         settings.host() != STR_GOOGLEMAIL_COM) {
    150       d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM);
    151     }
    152   }
    153 
    154   // Set language
    155   d_->engine_->SetLanguage(lang);
    156 
    157   d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY));
    158 
    159   d_->pass_ = settings.pass();
    160   d_->auth_mechanism_ = settings.auth_mechanism();
    161   d_->auth_token_ = settings.auth_token();
    162   d_->server_ = settings.server();
    163   d_->proxy_host_ = settings.proxy_host();
    164   d_->proxy_port_ = settings.proxy_port();
    165   d_->allow_plain_ = settings.allow_plain();
    166   d_->pre_auth_.reset(pre_auth);
    167 
    168   return XMPP_RETURN_OK;
    169 }
    170 
    171 XmppEngine::State XmppClient::GetState() const {
    172   if (!d_->engine_)
    173     return XmppEngine::STATE_NONE;
    174   return d_->engine_->GetState();
    175 }
    176 
    177 XmppEngine::Error XmppClient::GetError(int* subcode) {
    178   if (subcode) {
    179     *subcode = 0;
    180   }
    181   if (!d_->engine_)
    182     return XmppEngine::ERROR_NONE;
    183   if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) {
    184     if (subcode) {
    185       *subcode = d_->pre_engine_subcode_;
    186     }
    187     return d_->pre_engine_error_;
    188   }
    189   return d_->engine_->GetError(subcode);
    190 }
    191 
    192 const XmlElement* XmppClient::GetStreamError() {
    193   if (!d_->engine_) {
    194     return NULL;
    195   }
    196   return d_->engine_->GetStreamError();
    197 }
    198 
    199 CaptchaChallenge XmppClient::GetCaptchaChallenge() {
    200   if (!d_->engine_)
    201     return CaptchaChallenge();
    202   return d_->captcha_challenge_;
    203 }
    204 
    205 std::string XmppClient::GetAuthMechanism() {
    206   if (!d_->engine_)
    207     return "";
    208   return d_->auth_mechanism_;
    209 }
    210 
    211 std::string XmppClient::GetAuthToken() {
    212   if (!d_->engine_)
    213     return "";
    214   return d_->auth_token_;
    215 }
    216 
    217 int XmppClient::ProcessStart() {
    218   // Should not happen, but was observed in crash reports
    219   if (!d_->socket_) {
    220     LOG(LS_ERROR) << "socket_ already reset";
    221     return STATE_DONE;
    222   }
    223 
    224   if (d_->pre_auth_) {
    225     d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone);
    226     d_->pre_auth_->StartPreXmppAuth(
    227         d_->engine_->GetUser(), d_->server_, d_->pass_,
    228         d_->auth_mechanism_, d_->auth_token_);
    229     d_->pass_.Clear(); // done with this;
    230     return STATE_PRE_XMPP_LOGIN;
    231   }
    232   else {
    233     d_->engine_->SetSaslHandler(new PlainSaslHandler(
    234               d_->engine_->GetUser(), d_->pass_, d_->allow_plain_));
    235     d_->pass_.Clear(); // done with this;
    236     return STATE_START_XMPP_LOGIN;
    237   }
    238 }
    239 
    240 void XmppClient::OnAuthDone() {
    241   Wake();
    242 }
    243 
    244 int XmppClient::ProcessTokenLogin() {
    245   // Should not happen, but was observed in crash reports
    246   if (!d_->socket_) {
    247     LOG(LS_ERROR) << "socket_ already reset";
    248     return STATE_DONE;
    249   }
    250 
    251   // Don't know how this could happen, but crash reports show it as NULL
    252   if (!d_->pre_auth_) {
    253     d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
    254     EnsureClosed();
    255     return STATE_ERROR;
    256   }
    257 
    258   // Wait until pre authentication is done is done
    259   if (!d_->pre_auth_->IsAuthDone())
    260     return STATE_BLOCKED;
    261 
    262   if (!d_->pre_auth_->IsAuthorized()) {
    263     // maybe split out a case when gaia is down?
    264     if (d_->pre_auth_->HadError()) {
    265       d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
    266       d_->pre_engine_subcode_ = d_->pre_auth_->GetError();
    267     }
    268     else {
    269       d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED;
    270       d_->pre_engine_subcode_ = 0;
    271       d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge();
    272     }
    273     d_->pre_auth_.reset(NULL); // done with this
    274     EnsureClosed();
    275     return STATE_ERROR;
    276   }
    277 
    278   // Save auth token as a result
    279 
    280   d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism();
    281   d_->auth_token_ = d_->pre_auth_->GetAuthToken();
    282 
    283   // transfer ownership of pre_auth_ to engine
    284   d_->engine_->SetSaslHandler(d_->pre_auth_.release());
    285   return STATE_START_XMPP_LOGIN;
    286 }
    287 
    288 int XmppClient::ProcessStartXmppLogin() {
    289   // Should not happen, but was observed in crash reports
    290   if (!d_->socket_) {
    291     LOG(LS_ERROR) << "socket_ already reset";
    292     return STATE_DONE;
    293   }
    294 
    295   // Done with pre-connect tasks - connect!
    296   if (!d_->socket_->Connect(d_->server_)) {
    297     EnsureClosed();
    298     return STATE_ERROR;
    299   }
    300 
    301   return STATE_RESPONSE;
    302 }
    303 
    304 int XmppClient::ProcessResponse() {
    305   // Hang around while we are connected.
    306   if (!delivering_signal_ &&
    307       (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED))
    308     return STATE_DONE;
    309   return STATE_BLOCKED;
    310 }
    311 
    312 XmppReturnStatus XmppClient::Disconnect() {
    313   if (!d_->socket_)
    314     return XMPP_RETURN_BADSTATE;
    315   Abort();
    316   d_->engine_->Disconnect();
    317   d_->ResetSocket();
    318   return XMPP_RETURN_OK;
    319 }
    320 
    321 XmppClient::XmppClient(TaskParent* parent)
    322     : XmppTaskParentInterface(parent),
    323       delivering_signal_(false),
    324       valid_(false) {
    325   d_.reset(new Private(this));
    326   valid_ = true;
    327 }
    328 
    329 XmppClient::~XmppClient() {
    330   valid_ = false;
    331 }
    332 
    333 const Jid& XmppClient::jid() const {
    334   return d_->engine_->FullJid();
    335 }
    336 
    337 
    338 std::string XmppClient::NextId() {
    339   return d_->engine_->NextId();
    340 }
    341 
    342 XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) {
    343   return d_->engine_->SendStanza(stanza);
    344 }
    345 
    346 XmppReturnStatus XmppClient::SendStanzaError(
    347     const XmlElement* old_stanza, XmppStanzaError xse,
    348     const std::string& message) {
    349   return d_->engine_->SendStanzaError(old_stanza, xse, message);
    350 }
    351 
    352 XmppReturnStatus XmppClient::SendRaw(const std::string& text) {
    353   return d_->engine_->SendRaw(text);
    354 }
    355 
    356 XmppEngine* XmppClient::engine() {
    357   return d_->engine_.get();
    358 }
    359 
    360 void XmppClient::Private::OnSocketConnected() {
    361   engine_->Connect();
    362 }
    363 
    364 void XmppClient::Private::OnSocketRead() {
    365   char bytes[4096];
    366   size_t bytes_read;
    367   for (;;) {
    368     // Should not happen, but was observed in crash reports
    369     if (!socket_) {
    370       LOG(LS_ERROR) << "socket_ already reset";
    371       return;
    372     }
    373 
    374     if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) {
    375       // TODO: deal with error information
    376       return;
    377     }
    378 
    379     if (bytes_read == 0)
    380       return;
    381 
    382 //#ifdef _DEBUG
    383     client_->SignalLogInput(bytes, static_cast<int>(bytes_read));
    384 //#endif
    385 
    386     engine_->HandleInput(bytes, bytes_read);
    387   }
    388 }
    389 
    390 void XmppClient::Private::OnSocketClosed() {
    391   int code = socket_->GetError();
    392   engine_->ConnectionClosed(code);
    393 }
    394 
    395 void XmppClient::Private::OnStateChange(int state) {
    396   if (state == XmppEngine::STATE_CLOSED) {
    397     client_->EnsureClosed();
    398   }
    399   else {
    400     client_->SignalStateChange((XmppEngine::State)state);
    401   }
    402   client_->Wake();
    403 }
    404 
    405 void XmppClient::Private::WriteOutput(const char* bytes, size_t len) {
    406 //#ifdef _DEBUG
    407   client_->SignalLogOutput(bytes, static_cast<int>(len));
    408 //#endif
    409 
    410   socket_->Write(bytes, len);
    411   // TODO: deal with error information
    412 }
    413 
    414 void XmppClient::Private::StartTls(const std::string& domain) {
    415 #if defined(FEATURE_ENABLE_SSL)
    416   socket_->StartTls(domain);
    417 #endif
    418 }
    419 
    420 void XmppClient::Private::CloseConnection() {
    421   socket_->Close();
    422 }
    423 
    424 void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) {
    425   d_->engine_->AddStanzaHandler(task, level);
    426 }
    427 
    428 void XmppClient::RemoveXmppTask(XmppTask* task) {
    429   d_->engine_->RemoveStanzaHandler(task);
    430 }
    431 
    432 void XmppClient::EnsureClosed() {
    433   if (!d_->signal_closed_) {
    434     d_->signal_closed_ = true;
    435     delivering_signal_ = true;
    436     SignalStateChange(XmppEngine::STATE_CLOSED);
    437     delivering_signal_ = false;
    438   }
    439 }
    440 
    441 }  // namespace buzz
    442