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 #define TRACK_ARRAY_ALLOC_PROBLEM
     29 
     30 #include <vector>
     31 #include <sstream>
     32 #include <algorithm>
     33 #include "talk/xmllite/xmlelement.h"
     34 #include "talk/base/common.h"
     35 #include "talk/xmpp/xmppengineimpl.h"
     36 #include "talk/xmpp/xmpplogintask.h"
     37 #include "talk/xmpp/constants.h"
     38 #include "talk/xmllite/xmlprinter.h"
     39 #include "talk/xmpp/saslhandler.h"
     40 
     41 namespace buzz {
     42 
     43 static const std::string XMPP_CLIENT_NAMESPACES[] = {
     44   "stream", "http://etherx.jabber.org/streams",
     45   "", "jabber:client",
     46 };
     47 
     48 static const size_t XMPP_CLIENT_NAMESPACES_LEN = 4;
     49 
     50 XmppEngine * XmppEngine::Create() {
     51   return new XmppEngineImpl();
     52 }
     53 
     54 
     55 XmppEngineImpl::XmppEngineImpl() :
     56     stanzaParseHandler_(this),
     57     stanzaParser_(&stanzaParseHandler_),
     58     engine_entered_(0),
     59     user_jid_(JID_EMPTY),
     60     password_(),
     61     requested_resource_(STR_EMPTY),
     62     tls_needed_(true),
     63     login_task_(new XmppLoginTask(this)),
     64     next_id_(0),
     65     bound_jid_(JID_EMPTY),
     66     state_(STATE_START),
     67     encrypted_(false),
     68     error_code_(ERROR_NONE),
     69     subcode_(0),
     70     stream_error_(NULL),
     71     raised_reset_(false),
     72     output_handler_(NULL),
     73     session_handler_(NULL),
     74     iq_entries_(new IqEntryVector()),
     75     sasl_handler_(NULL),
     76     output_(new std::stringstream()) {
     77   for (int i = 0; i < HL_COUNT; i+= 1) {
     78     stanza_handlers_[i].reset(new StanzaHandlerVector());
     79   }
     80 }
     81 
     82 XmppEngineImpl::~XmppEngineImpl() {
     83   DeleteIqCookies();
     84 }
     85 
     86 XmppReturnStatus
     87 XmppEngineImpl::SetOutputHandler(XmppOutputHandler* output_handler) {
     88   if (state_ != STATE_START)
     89     return XMPP_RETURN_BADSTATE;
     90 
     91   output_handler_ = output_handler;
     92 
     93   return XMPP_RETURN_OK;
     94 }
     95 
     96 XmppReturnStatus
     97 XmppEngineImpl::SetSessionHandler(XmppSessionHandler* session_handler) {
     98   if (state_ != STATE_START)
     99     return XMPP_RETURN_BADSTATE;
    100 
    101   session_handler_ = session_handler;
    102 
    103   return XMPP_RETURN_OK;
    104 }
    105 
    106 XmppReturnStatus
    107 XmppEngineImpl::HandleInput(const char * bytes, size_t len) {
    108   if (state_ < STATE_OPENING || state_ > STATE_OPEN)
    109     return XMPP_RETURN_BADSTATE;
    110 
    111   EnterExit ee(this);
    112 
    113   // TODO: The return value of the xml parser is not checked.
    114   stanzaParser_.Parse(bytes, len, false);
    115 
    116   return XMPP_RETURN_OK;
    117 }
    118 
    119 XmppReturnStatus
    120 XmppEngineImpl::ConnectionClosed(int subcode) {
    121   if (state_ != STATE_CLOSED) {
    122     EnterExit ee(this);
    123     // If told that connection closed and not already closed,
    124     // then connection was unpexectedly dropped.
    125     if (subcode) {
    126       SignalError(ERROR_SOCKET, subcode);
    127     } else {
    128       SignalError(ERROR_CONNECTION_CLOSED, 0);  // no subcode
    129     }
    130   }
    131   return XMPP_RETURN_OK;
    132 }
    133 
    134 XmppReturnStatus
    135 XmppEngineImpl::SetUseTls(bool useTls) {
    136   if (state_ != STATE_START)
    137     return XMPP_RETURN_BADSTATE;
    138 
    139   tls_needed_ = useTls;
    140 
    141   return XMPP_RETURN_OK;
    142 }
    143 
    144 XmppReturnStatus
    145 XmppEngineImpl::SetTlsServer(const std::string & tls_server_hostname,
    146                              const std::string & tls_server_domain) {
    147   if (state_ != STATE_START)
    148     return XMPP_RETURN_BADSTATE;
    149 
    150   tls_server_hostname_ = tls_server_hostname;
    151   tls_server_domain_= tls_server_domain;
    152 
    153   return XMPP_RETURN_OK;
    154 }
    155 
    156 bool
    157 XmppEngineImpl::GetUseTls() {
    158   return tls_needed_;
    159 }
    160 
    161 XmppReturnStatus
    162 XmppEngineImpl::SetUser(const Jid & jid) {
    163   if (state_ != STATE_START)
    164     return XMPP_RETURN_BADSTATE;
    165 
    166   user_jid_ = jid;
    167 
    168   return XMPP_RETURN_OK;
    169 }
    170 
    171 const Jid &
    172 XmppEngineImpl::GetUser() {
    173   return user_jid_;
    174 }
    175 
    176 XmppReturnStatus
    177 XmppEngineImpl::SetSaslHandler(SaslHandler * sasl_handler) {
    178   if (state_ != STATE_START)
    179     return XMPP_RETURN_BADSTATE;
    180 
    181   sasl_handler_.reset(sasl_handler);
    182   return XMPP_RETURN_OK;
    183 }
    184 
    185 XmppReturnStatus
    186 XmppEngineImpl::SetRequestedResource(const std::string & resource) {
    187   if (state_ != STATE_START)
    188     return XMPP_RETURN_BADSTATE;
    189 
    190   requested_resource_ = resource;
    191 
    192   return XMPP_RETURN_OK;
    193 }
    194 
    195 const std::string &
    196 XmppEngineImpl::GetRequestedResource() {
    197   return requested_resource_;
    198 }
    199 
    200 XmppReturnStatus
    201 XmppEngineImpl::AddStanzaHandler(XmppStanzaHandler * stanza_handler,
    202                                  XmppEngine::HandlerLevel level) {
    203   if (state_ == STATE_CLOSED)
    204     return XMPP_RETURN_BADSTATE;
    205 
    206   stanza_handlers_[level]->push_back(stanza_handler);
    207 
    208   return XMPP_RETURN_OK;
    209 }
    210 
    211 XmppReturnStatus
    212 XmppEngineImpl::RemoveStanzaHandler(XmppStanzaHandler * stanza_handler) {
    213 
    214   bool found = false;
    215 
    216   for (int level = 0; level < HL_COUNT; level += 1) {
    217     StanzaHandlerVector::iterator new_end =
    218       std::remove(stanza_handlers_[level]->begin(),
    219       stanza_handlers_[level]->end(),
    220       stanza_handler);
    221 
    222     if (new_end != stanza_handlers_[level]->end()) {
    223       stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end());
    224       found = true;
    225     }
    226   }
    227 
    228   if (!found) {
    229     return XMPP_RETURN_BADARGUMENT;
    230   }
    231 
    232   return XMPP_RETURN_OK;
    233 }
    234 
    235 XmppReturnStatus
    236 XmppEngineImpl::Connect() {
    237   if (state_ != STATE_START)
    238     return XMPP_RETURN_BADSTATE;
    239 
    240   EnterExit ee(this);
    241 
    242   // get the login task started
    243   state_ = STATE_OPENING;
    244   if (login_task_.get()) {
    245     login_task_->IncomingStanza(NULL, false);
    246     if (login_task_->IsDone())
    247       login_task_.reset();
    248   }
    249 
    250   return XMPP_RETURN_OK;
    251 }
    252 
    253 XmppReturnStatus
    254 XmppEngineImpl::SendStanza(const XmlElement * element) {
    255   if (state_ == STATE_CLOSED)
    256     return XMPP_RETURN_BADSTATE;
    257 
    258   EnterExit ee(this);
    259 
    260   if (login_task_.get()) {
    261     // still handshaking - then outbound stanzas are queued
    262     login_task_->OutgoingStanza(element);
    263   } else {
    264     // handshake done - send straight through
    265     InternalSendStanza(element);
    266   }
    267 
    268   return XMPP_RETURN_OK;
    269 }
    270 
    271 XmppReturnStatus
    272 XmppEngineImpl::SendRaw(const std::string & text) {
    273   if (state_ == STATE_CLOSED || login_task_.get())
    274     return XMPP_RETURN_BADSTATE;
    275 
    276   EnterExit ee(this);
    277 
    278   (*output_) << text;
    279 
    280   return XMPP_RETURN_OK;
    281 }
    282 
    283 std::string
    284 XmppEngineImpl::NextId() {
    285   std::stringstream ss;
    286   ss << next_id_++;
    287   return ss.str();
    288 }
    289 
    290 XmppReturnStatus
    291 XmppEngineImpl::Disconnect() {
    292 
    293   if (state_ != STATE_CLOSED) {
    294     EnterExit ee(this);
    295     if (state_ == STATE_OPEN)
    296       *output_ << "</stream:stream>";
    297     state_ = STATE_CLOSED;
    298   }
    299 
    300   return XMPP_RETURN_OK;
    301 }
    302 
    303 void
    304 XmppEngineImpl::IncomingStart(const XmlElement * pelStart) {
    305   if (HasError() || raised_reset_)
    306     return;
    307 
    308   if (login_task_.get()) {
    309     // start-stream should go to login task
    310     login_task_->IncomingStanza(pelStart, true);
    311     if (login_task_->IsDone())
    312       login_task_.reset();
    313   }
    314   else {
    315     // if not logging in, it's an error to see a start
    316     SignalError(ERROR_XML, 0);
    317   }
    318 }
    319 
    320 void
    321 XmppEngineImpl::IncomingStanza(const XmlElement * stanza) {
    322   if (HasError() || raised_reset_)
    323     return;
    324 
    325   if (stanza->Name() == QN_STREAM_ERROR) {
    326     // Explicit XMPP stream error
    327     SignalStreamError(stanza);
    328   } else if (login_task_.get()) {
    329     // Handle login handshake
    330     login_task_->IncomingStanza(stanza, false);
    331     if (login_task_->IsDone())
    332       login_task_.reset();
    333   } else if (HandleIqResponse(stanza)) {
    334     // iq is handled by above call
    335   } else {
    336     // give every "peek" handler a shot at all stanzas
    337     for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) {
    338       (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza);
    339     }
    340 
    341     // give other handlers a shot in precedence order, stopping after handled
    342     for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
    343       for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
    344         if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
    345           goto Handled;
    346       }
    347     }
    348 
    349     // If nobody wants to handle a stanza then send back an error.
    350     // Only do this for IQ stanzas as messages should probably just be dropped
    351     // and presence stanzas should certainly be dropped.
    352     std::string type = stanza->Attr(QN_TYPE);
    353     if (stanza->Name() == QN_IQ &&
    354         !(type == "error" || type == "result")) {
    355       SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
    356     }
    357   }
    358   Handled:
    359     ; // handled - we're done
    360 }
    361 
    362 void
    363 XmppEngineImpl::IncomingEnd(bool isError) {
    364   if (HasError() || raised_reset_)
    365     return;
    366 
    367   SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
    368 }
    369 
    370 void
    371 XmppEngineImpl::InternalSendStart(const std::string & to) {
    372   std::string hostname = tls_server_hostname_;
    373   if (hostname.empty()) {
    374     hostname = to;
    375   }
    376 
    377   // If not language is specified, the spec says use *
    378   std::string lang = lang_;
    379   if (lang.length() == 0)
    380     lang = "*";
    381 
    382   // send stream-beginning
    383   // note, we put a \r\n at tne end fo the first line to cause non-XMPP
    384   // line-oriented servers (e.g., Apache) to reveal themselves more quickly.
    385   *output_ << "<stream:stream to=\"" << hostname << "\" "
    386            << "xml:lang=\"" << lang << "\" "
    387            << "version=\"1.0\" "
    388            << "xmlns:stream=\"http://etherx.jabber.org/streams\" "
    389            << "xmlns=\"jabber:client\">\r\n";
    390 }
    391 
    392 void
    393 XmppEngineImpl::InternalSendStanza(const XmlElement * element) {
    394   // It should really never be necessary to set a FROM attribute on a stanza.
    395   // It is implied by the bind on the stream and if you get it wrong
    396   // (by flipping from/to on a message?) the server will close the stream.
    397   ASSERT(!element->HasAttr(QN_FROM));
    398 
    399   // TODO: consider caching the XmlPrinter
    400   XmlPrinter::PrintXml(output_.get(), element,
    401             XMPP_CLIENT_NAMESPACES, XMPP_CLIENT_NAMESPACES_LEN);
    402 }
    403 
    404 std::string
    405 XmppEngineImpl::ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
    406   return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
    407 }
    408 
    409 SaslMechanism *
    410 XmppEngineImpl::GetSaslMechanism(const std::string & name) {
    411   return sasl_handler_->CreateSaslMechanism(name);
    412 }
    413 
    414 void
    415 XmppEngineImpl::SignalBound(const Jid & fullJid) {
    416   if (state_ == STATE_OPENING) {
    417     bound_jid_ = fullJid;
    418     state_ = STATE_OPEN;
    419   }
    420 }
    421 
    422 void
    423 XmppEngineImpl::SignalStreamError(const XmlElement * pelStreamError) {
    424   if (state_ != STATE_CLOSED) {
    425     stream_error_.reset(new XmlElement(*pelStreamError));
    426     SignalError(ERROR_STREAM, 0);
    427   }
    428 }
    429 
    430 void
    431 XmppEngineImpl::SignalError(Error errorCode, int subCode) {
    432   if (state_ != STATE_CLOSED) {
    433     error_code_ = errorCode;
    434     subcode_ = subCode;
    435     state_ = STATE_CLOSED;
    436   }
    437 }
    438 
    439 bool
    440 XmppEngineImpl::HasError() {
    441   return error_code_ != ERROR_NONE;
    442 }
    443 
    444 void
    445 XmppEngineImpl::StartTls(const std::string & domain) {
    446   if (output_handler_) {
    447     output_handler_->StartTls(
    448       tls_server_domain_.empty() ? domain : tls_server_domain_);
    449     encrypted_ = true;
    450   }
    451 }
    452 
    453 XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
    454   : engine_(engine),
    455   state_(engine->state_),
    456   error_(engine->error_code_) {
    457   engine->engine_entered_ += 1;
    458 }
    459 
    460 XmppEngineImpl::EnterExit::~EnterExit()  {
    461  XmppEngineImpl* engine = engine_;
    462 
    463  engine->engine_entered_ -= 1;
    464 
    465  bool closing = (engine->state_ != state_ &&
    466        engine->state_ == STATE_CLOSED);
    467  bool flushing = closing || (engine->engine_entered_ == 0);
    468 
    469  if (engine->output_handler_ && flushing) {
    470    std::string output = engine->output_->str();
    471    if (output.length() > 0)
    472      engine->output_handler_->WriteOutput(output.c_str(), output.length());
    473    engine->output_->str("");
    474 
    475    if (closing) {
    476      engine->output_handler_->CloseConnection();
    477      engine->output_handler_ = 0;
    478    }
    479  }
    480 
    481  if (engine->engine_entered_)
    482    return;
    483 
    484  if (engine->raised_reset_) {
    485    engine->stanzaParser_.Reset();
    486    engine->raised_reset_ = false;
    487  }
    488 
    489  if (engine->session_handler_) {
    490    if (engine->state_ != state_)
    491      engine->session_handler_->OnStateChange(engine->state_);
    492      // Note: Handling of OnStateChange(CLOSED) should allow for the
    493      // deletion of the engine, so no members should be accessed
    494      // after this line.
    495  }
    496 }
    497 
    498 }
    499