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