/* * libjingle * Copyright 2004--2005, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define TRACK_ARRAY_ALLOC_PROBLEM #include #include #include #include "talk/xmllite/xmlelement.h" #include "talk/base/common.h" #include "talk/xmpp/xmppengineimpl.h" #include "talk/xmpp/xmpplogintask.h" #include "talk/xmpp/constants.h" #include "talk/xmllite/xmlprinter.h" #include "talk/xmpp/saslhandler.h" // #include "buzz/saslmechanism.h" #define new TRACK_NEW namespace buzz { static const std::string XMPP_CLIENT_NAMESPACES[] = { "stream", "http://etherx.jabber.org/streams", "", "jabber:client", }; static const size_t XMPP_CLIENT_NAMESPACES_LEN = 4; XmppEngine * XmppEngine::Create() { return new XmppEngineImpl(); } XmppEngineImpl::XmppEngineImpl() : stanzaParseHandler_(this), stanzaParser_(&stanzaParseHandler_), engine_entered_(0), user_jid_(JID_EMPTY), password_(), requested_resource_(STR_EMPTY), tls_needed_(true), login_task_(new XmppLoginTask(this)), next_id_(0), bound_jid_(JID_EMPTY), state_(STATE_START), encrypted_(false), error_code_(ERROR_NONE), stream_error_(NULL), raised_reset_(false), output_handler_(NULL), session_handler_(NULL), iq_entries_(new IqEntryVector()), output_(new std::stringstream()), sasl_handler_(NULL) { for (int i = 0; i < HL_COUNT; i+= 1) { stanza_handlers_[i].reset(new StanzaHandlerVector()); } } XmppEngineImpl::~XmppEngineImpl() { DeleteIqCookies(); } XmppReturnStatus XmppEngineImpl::SetOutputHandler(XmppOutputHandler* output_handler) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; output_handler_ = output_handler; return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::SetSessionHandler(XmppSessionHandler* session_handler) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; session_handler_ = session_handler; return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::HandleInput(const char * bytes, size_t len) { if (state_ < STATE_OPENING || state_ > STATE_OPEN) return XMPP_RETURN_BADSTATE; EnterExit ee(this); stanzaParser_.Parse(bytes, len, false); return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::ConnectionClosed() { if (state_ != STATE_CLOSED) { EnterExit ee(this); // If told that connection closed and not already closed, // then connection was unpexectedly dropped. SignalError(ERROR_CONNECTION_CLOSED); } return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::SetUseTls(bool useTls) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; tls_needed_ = useTls; return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::SetTlsServerDomain(const std::string & tls_server_domain) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; tls_server_domain_= tls_server_domain; return XMPP_RETURN_OK; } bool XmppEngineImpl::GetUseTls() { return tls_needed_; } XmppReturnStatus XmppEngineImpl::SetUser(const Jid & jid) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; user_jid_ = jid; return XMPP_RETURN_OK; } const Jid & XmppEngineImpl::GetUser() { return user_jid_; } XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler * sasl_handler) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; sasl_handler_.reset(sasl_handler); return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::SetRequestedResource(const std::string & resource) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; requested_resource_ = resource; return XMPP_RETURN_OK; } const std::string & XmppEngineImpl::GetRequestedResource() { return requested_resource_; } XmppReturnStatus XmppEngineImpl::AddStanzaHandler(XmppStanzaHandler * stanza_handler, XmppEngine::HandlerLevel level) { if (state_ == STATE_CLOSED) return XMPP_RETURN_BADSTATE; stanza_handlers_[level]->push_back(stanza_handler); return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler(XmppStanzaHandler * stanza_handler) { bool found = false; for (int level = 0; level < HL_COUNT; level += 1) { StanzaHandlerVector::iterator new_end = std::remove(stanza_handlers_[level]->begin(), stanza_handlers_[level]->end(), stanza_handler); if (new_end != stanza_handlers_[level]->end()) { stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end()); found = true; } } if (!found) { return XMPP_RETURN_BADARGUMENT; } return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::Connect() { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; EnterExit ee(this); // get the login task started state_ = STATE_OPENING; if (login_task_.get()) { login_task_->IncomingStanza(NULL, false); if (login_task_->IsDone()) login_task_.reset(); } return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement * element) { if (state_ == STATE_CLOSED) return XMPP_RETURN_BADSTATE; EnterExit ee(this); if (login_task_.get()) { // still handshaking - then outbound stanzas are queued login_task_->OutgoingStanza(element); } else { // handshake done - send straight through InternalSendStanza(element); } return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::SendRaw(const std::string & text) { if (state_ == STATE_CLOSED || login_task_.get()) return XMPP_RETURN_BADSTATE; EnterExit ee(this); (*output_) << text; return XMPP_RETURN_OK; } std::string XmppEngineImpl::NextId() { std::stringstream ss; ss << next_id_++; return ss.str(); } XmppReturnStatus XmppEngineImpl::Disconnect() { if (state_ != STATE_CLOSED) { EnterExit ee(this); if (state_ == STATE_OPEN) *output_ << ""; state_ = STATE_CLOSED; } return XMPP_RETURN_OK; } void XmppEngineImpl::IncomingStart(const XmlElement * pelStart) { if (HasError() || raised_reset_) return; if (login_task_.get()) { // start-stream should go to login task login_task_->IncomingStanza(pelStart, true); if (login_task_->IsDone()) login_task_.reset(); } else { // if not logging in, it's an error to see a start SignalError(ERROR_XML); } } void XmppEngineImpl::IncomingStanza(const XmlElement * stanza) { if (HasError() || raised_reset_) return; if (stanza->Name() == TQN_STREAM_ERROR) { // Explicit XMPP stream error SignalStreamError(stanza); } else if (login_task_.get()) { // Handle login handshake login_task_->IncomingStanza(stanza, false); if (login_task_->IsDone()) login_task_.reset(); } else if (HandleIqResponse(stanza)) { // iq is handled by above call } else { // give every "peek" handler a shot at all stanzas for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) { (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza); } // give other handlers a shot in precedence order, stopping after handled for (int level = HL_SINGLE; level <= HL_ALL; level += 1) { for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) { if ((*stanza_handlers_[level])[i]->HandleStanza(stanza)) goto Handled; } } // If nobody wants to handle a stanza then send back an error. // Only do this for IQ stanzas as messages should probably just be dropped // and presence stanzas should certainly be dropped. std::string type = stanza->Attr(TQN_TYPE); if (stanza->Name() == TQN_IQ && !(type == "error" || type == "result")) { SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY); } } Handled: ; // handled - we're done } void XmppEngineImpl::IncomingEnd(bool isError) { if (HasError() || raised_reset_) return; SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED); } void XmppEngineImpl::InternalSendStart(const std::string & to) { // send stream-beginning // note, we put a \r\n at tne end fo the first line to cause non-XMPP // line-oriented servers (e.g., Apache) to reveal themselves more quickly. *output_ << "\r\n"; } void XmppEngineImpl::InternalSendStanza(const XmlElement * element) { // It should really never be necessary to set a FROM attribute on a stanza. // It is implied by the bind on the stream and if you get it wrong // (by flipping from/to on a message?) the server will close the stream. ASSERT(!element->HasAttr(TQN_FROM)); // TODO: consider caching the XmlPrinter XmlPrinter::PrintXml(output_.get(), element, XMPP_CLIENT_NAMESPACES, XMPP_CLIENT_NAMESPACES_LEN); } std::string XmppEngineImpl::ChooseBestSaslMechanism(const std::vector & mechanisms, bool encrypted) { return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted); } SaslMechanism * XmppEngineImpl::GetSaslMechanism(const std::string & name) { return sasl_handler_->CreateSaslMechanism(name); } void XmppEngineImpl::SignalBound(const Jid & fullJid) { if (state_ == STATE_OPENING) { bound_jid_ = fullJid; state_ = STATE_OPEN; } } void XmppEngineImpl::SignalStreamError(const XmlElement * pelStreamError) { if (state_ != STATE_CLOSED) { stream_error_.reset(new XmlElement(*pelStreamError)); SignalError(ERROR_STREAM); } } void XmppEngineImpl::SignalError(Error errorCode) { if (state_ != STATE_CLOSED) { error_code_ = errorCode; state_ = STATE_CLOSED; } } bool XmppEngineImpl::HasError() { return error_code_ != ERROR_NONE; } void XmppEngineImpl::StartTls(const std::string & domain) { if (output_handler_) { output_handler_->StartTls( tls_server_domain_.empty() ? domain : tls_server_domain_); encrypted_ = true; } } XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine) : engine_(engine), state_(engine->state_), error_(engine->error_code_) { engine->engine_entered_ += 1; } XmppEngineImpl::EnterExit::~EnterExit() { XmppEngineImpl* engine = engine_; engine->engine_entered_ -= 1; bool closing = (engine->state_ != state_ && engine->state_ == STATE_CLOSED); bool flushing = closing || (engine->engine_entered_ == 0); if (engine->output_handler_ && flushing) { std::string output = engine->output_->str(); if (output.length() > 0) engine->output_handler_->WriteOutput(output.c_str(), output.length()); engine->output_->str(""); if (closing) { engine->output_handler_->CloseConnection(); engine->output_handler_ = 0; } } if (engine->engine_entered_) return; if (engine->raised_reset_) { engine->stanzaParser_.Reset(); engine->raised_reset_ = false; } if (engine->session_handler_) { if (engine->state_ != state_) engine->session_handler_->OnStateChange(engine->state_); // Note: Handling of OnStateChange(CLOSED) should allow for the // deletion of the engine, so no members should be accessed // after this line. } } }