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 "xmppclient.h"
     29 #include "xmpptask.h"
     30 #include "talk/xmpp/constants.h"
     31 #include "talk/base/sigslot.h"
     32 #include "talk/xmpp/saslplainmechanism.h"
     33 #include "talk/xmpp/prexmppauth.h"
     34 #include "talk/base/scoped_ptr.h"
     35 #include "talk/xmpp/plainsaslhandler.h"
     36 
     37 namespace buzz {
     38 
     39 talk_base::TaskParent* XmppClient::GetParent(int code) {
     40   if (code == XMPP_CLIENT_TASK_CODE)
     41     return this;
     42   else
     43     return talk_base::Task::GetParent(code);
     44 }
     45 
     46 class XmppClient::Private :
     47     public sigslot::has_slots<>,
     48     public XmppSessionHandler,
     49     public XmppOutputHandler {
     50 public:
     51 
     52   Private(XmppClient * client) :
     53     client_(client),
     54     socket_(NULL),
     55     engine_(NULL),
     56     proxy_port_(0),
     57     pre_engine_error_(XmppEngine::ERROR_NONE),
     58     pre_engine_subcode_(0),
     59     signal_closed_(false),
     60     allow_plain_(false) {}
     61 
     62   // the owner
     63   XmppClient * const client_;
     64 
     65   // the two main objects
     66   talk_base::scoped_ptr<AsyncSocket> socket_;
     67   talk_base::scoped_ptr<XmppEngine> engine_;
     68   talk_base::scoped_ptr<PreXmppAuth> pre_auth_;
     69   talk_base::CryptString pass_;
     70   std::string auth_cookie_;
     71   talk_base::SocketAddress server_;
     72   std::string proxy_host_;
     73   int proxy_port_;
     74   XmppEngine::Error pre_engine_error_;
     75   int pre_engine_subcode_;
     76   CaptchaChallenge captcha_challenge_;
     77   bool signal_closed_;
     78   bool allow_plain_;
     79 
     80   // implementations of interfaces
     81   void OnStateChange(int state);
     82   void WriteOutput(const char * bytes, size_t len);
     83   void StartTls(const std::string & domainname);
     84   void CloseConnection();
     85 
     86   // slots for socket signals
     87   void OnSocketConnected();
     88   void OnSocketRead();
     89   void OnSocketClosed();
     90 };
     91 
     92 XmppReturnStatus
     93 XmppClient::Connect(const XmppClientSettings & settings, const std::string & lang, AsyncSocket * socket, PreXmppAuth * pre_auth) {
     94   if (socket == NULL)
     95     return XMPP_RETURN_BADARGUMENT;
     96   if (d_->socket_.get() != NULL)
     97     return XMPP_RETURN_BADSTATE;
     98 
     99   d_->socket_.reset(socket);
    100 
    101   d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected);
    102   d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead);
    103   d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed);
    104 
    105   d_->engine_.reset(XmppEngine::Create());
    106   d_->engine_->SetSessionHandler(d_.get());
    107   d_->engine_->SetOutputHandler(d_.get());
    108   if (!settings.resource().empty()) {
    109     d_->engine_->SetRequestedResource(settings.resource());
    110   }
    111   d_->engine_->SetUseTls(settings.use_tls());
    112 
    113   //
    114   // The talk.google.com server expects you to use "gmail.com" in the
    115   // stream, and expects the domain certificate to be "gmail.com" as well.
    116   // For all other servers, we leave the strings empty, which causes
    117   // the jid's domain to be used.  "foo (at) example.com" -> stream to="example.com"
    118   // tls certificate for "example.com"
    119   //
    120   // This is only true when using Gaia auth, so let's say if there's no preauth,
    121   // we should use the actual server name
    122   std::string server_name = settings.server().IPAsString();
    123   if ((server_name == buzz::STR_TALK_GOOGLE_COM ||
    124       server_name == buzz::STR_TALKX_L_GOOGLE_COM) &&
    125       pre_auth != NULL) {
    126     d_->engine_->SetTlsServer(buzz::STR_GMAIL_COM, buzz::STR_GMAIL_COM);
    127   }
    128 
    129   // Set language
    130   d_->engine_->SetLanguage(lang);
    131 
    132   d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY));
    133 
    134   d_->pass_ = settings.pass();
    135   d_->auth_cookie_ = settings.auth_cookie();
    136   d_->server_ = settings.server();
    137   d_->proxy_host_ = settings.proxy_host();
    138   d_->proxy_port_ = settings.proxy_port();
    139   d_->allow_plain_ = settings.allow_plain();
    140   d_->pre_auth_.reset(pre_auth);
    141 
    142   return XMPP_RETURN_OK;
    143 }
    144 
    145 XmppEngine::State
    146 XmppClient::GetState() {
    147   if (d_->engine_.get() == NULL)
    148     return XmppEngine::STATE_NONE;
    149   return d_->engine_->GetState();
    150 }
    151 
    152 XmppEngine::Error
    153 XmppClient::GetError(int *subcode) {
    154   if (subcode) {
    155     *subcode = 0;
    156   }
    157   if (d_->engine_.get() == NULL)
    158     return XmppEngine::ERROR_NONE;
    159   if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) {
    160     if (subcode) {
    161       *subcode = d_->pre_engine_subcode_;
    162     }
    163     return d_->pre_engine_error_;
    164   }
    165   return d_->engine_->GetError(subcode);
    166 }
    167 
    168 const XmlElement *
    169 XmppClient::GetStreamError() {
    170   if (d_->engine_.get() == NULL) {
    171     return NULL;
    172   }
    173   return d_->engine_->GetStreamError();
    174 }
    175 
    176 CaptchaChallenge XmppClient::GetCaptchaChallenge() {
    177   if (d_->engine_.get() == NULL)
    178     return CaptchaChallenge();
    179   return d_->captcha_challenge_;
    180 }
    181 
    182 std::string
    183 XmppClient::GetAuthCookie() {
    184   if (d_->engine_.get() == NULL)
    185     return "";
    186   return d_->auth_cookie_;
    187 }
    188 
    189 int
    190 XmppClient::ProcessStart() {
    191   if (d_->pre_auth_.get()) {
    192     d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone);
    193     d_->pre_auth_->StartPreXmppAuth(
    194         d_->engine_->GetUser(), d_->server_, d_->pass_, d_->auth_cookie_);
    195     d_->pass_.Clear(); // done with this;
    196     return STATE_PRE_XMPP_LOGIN;
    197   }
    198   else {
    199     d_->engine_->SetSaslHandler(new PlainSaslHandler(
    200               d_->engine_->GetUser(), d_->pass_, d_->allow_plain_));
    201     d_->pass_.Clear(); // done with this;
    202     return STATE_START_XMPP_LOGIN;
    203   }
    204 }
    205 
    206 void
    207 XmppClient::OnAuthDone() {
    208   Wake();
    209 }
    210 
    211 int
    212 XmppClient::ProcessCookieLogin() {
    213   // Don't know how this could happen, but crash reports show it as NULL
    214   if (!d_->pre_auth_.get()) {
    215     d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
    216     EnsureClosed();
    217     return STATE_ERROR;
    218   }
    219 
    220   // Wait until pre authentication is done is done
    221   if (!d_->pre_auth_->IsAuthDone())
    222     return STATE_BLOCKED;
    223 
    224   if (!d_->pre_auth_->IsAuthorized()) {
    225     // maybe split out a case when gaia is down?
    226     if (d_->pre_auth_->HadError()) {
    227       d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
    228       d_->pre_engine_subcode_ = d_->pre_auth_->GetError();
    229     }
    230     else {
    231       d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED;
    232       d_->pre_engine_subcode_ = 0;
    233       d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge();
    234     }
    235     d_->pre_auth_.reset(NULL); // done with this
    236     EnsureClosed();
    237     return STATE_ERROR;
    238   }
    239 
    240   // Save auth cookie as a result
    241   d_->auth_cookie_ = d_->pre_auth_->GetAuthCookie();
    242 
    243   // transfer ownership of pre_auth_ to engine
    244   d_->engine_->SetSaslHandler(d_->pre_auth_.release());
    245   return STATE_START_XMPP_LOGIN;
    246 }
    247 
    248 int
    249 XmppClient::ProcessStartXmppLogin() {
    250   // Done with pre-connect tasks - connect!
    251   if (!d_->socket_->Connect(d_->server_)) {
    252     EnsureClosed();
    253     return STATE_ERROR;
    254   }
    255 
    256   return STATE_RESPONSE;
    257 }
    258 
    259 int
    260 XmppClient::ProcessResponse() {
    261   // Hang around while we are connected.
    262   if (!delivering_signal_ && (d_->engine_.get() == NULL ||
    263     d_->engine_->GetState() == XmppEngine::STATE_CLOSED))
    264     return STATE_DONE;
    265   return STATE_BLOCKED;
    266 }
    267 
    268 XmppReturnStatus
    269 XmppClient::Disconnect() {
    270   if (d_->socket_.get() == NULL)
    271     return XMPP_RETURN_BADSTATE;
    272   d_->engine_->Disconnect();
    273   d_->socket_.reset(NULL);
    274   return XMPP_RETURN_OK;
    275 }
    276 
    277 XmppClient::XmppClient(TaskParent * parent)
    278     : Task(parent),
    279       delivering_signal_(false),
    280       valid_(false) {
    281   d_.reset(new Private(this));
    282   valid_ = true;
    283 }
    284 
    285 XmppClient::~XmppClient() {
    286   valid_ = false;
    287 }
    288 
    289 const Jid &
    290 XmppClient::jid() {
    291   return d_->engine_->FullJid();
    292 }
    293 
    294 
    295 std::string
    296 XmppClient::NextId() {
    297   return d_->engine_->NextId();
    298 }
    299 
    300 XmppReturnStatus
    301 XmppClient::SendStanza(const XmlElement * stanza) {
    302   return d_->engine_->SendStanza(stanza);
    303 }
    304 
    305 XmppReturnStatus
    306 XmppClient::SendStanzaError(const XmlElement * old_stanza, XmppStanzaError xse, const std::string & message) {
    307   return d_->engine_->SendStanzaError(old_stanza, xse, message);
    308 }
    309 
    310 XmppReturnStatus
    311 XmppClient::SendRaw(const std::string & text) {
    312   return d_->engine_->SendRaw(text);
    313 }
    314 
    315 XmppEngine*
    316 XmppClient::engine() {
    317   return d_->engine_.get();
    318 }
    319 
    320 void
    321 XmppClient::Private::OnSocketConnected() {
    322   engine_->Connect();
    323 }
    324 
    325 void
    326 XmppClient::Private::OnSocketRead() {
    327   char bytes[4096];
    328   size_t bytes_read;
    329   for (;;) {
    330     if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) {
    331       // TODO: deal with error information
    332       return;
    333     }
    334 
    335     if (bytes_read == 0)
    336       return;
    337 
    338 //#ifdef _DEBUG
    339     client_->SignalLogInput(bytes, bytes_read);
    340 //#endif
    341 
    342     engine_->HandleInput(bytes, bytes_read);
    343   }
    344 }
    345 
    346 void
    347 XmppClient::Private::OnSocketClosed() {
    348   int code = socket_->GetError();
    349   engine_->ConnectionClosed(code);
    350 }
    351 
    352 void
    353 XmppClient::Private::OnStateChange(int state) {
    354   if (state == XmppEngine::STATE_CLOSED) {
    355     client_->EnsureClosed();
    356   }
    357   else {
    358     client_->SignalStateChange((XmppEngine::State)state);
    359   }
    360   client_->Wake();
    361 }
    362 
    363 void
    364 XmppClient::Private::WriteOutput(const char * bytes, size_t len) {
    365 
    366 //#ifdef _DEBUG
    367   client_->SignalLogOutput(bytes, len);
    368 //#endif
    369 
    370   socket_->Write(bytes, len);
    371   // TODO: deal with error information
    372 }
    373 
    374 void
    375 XmppClient::Private::StartTls(const std::string & domain) {
    376 #if defined(FEATURE_ENABLE_SSL)
    377   socket_->StartTls(domain);
    378 #endif
    379 }
    380 
    381 void
    382 XmppClient::Private::CloseConnection() {
    383   socket_->Close();
    384 }
    385 
    386 void
    387 XmppClient::AddXmppTask(XmppTask * task, XmppEngine::HandlerLevel level) {
    388   d_->engine_->AddStanzaHandler(task, level);
    389 }
    390 
    391 void
    392 XmppClient::RemoveXmppTask(XmppTask * task) {
    393   d_->engine_->RemoveStanzaHandler(task);
    394 }
    395 
    396 void
    397 XmppClient::EnsureClosed() {
    398   if (!d_->signal_closed_) {
    399     d_->signal_closed_ = true;
    400     delivering_signal_ = true;
    401     SignalStateChange(XmppEngine::STATE_CLOSED);
    402     delivering_signal_ = false;
    403   }
    404 }
    405 
    406 
    407 }
    408