/* * 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. */ #include "talk/xmpp/xmppengineimpl.h" #include #include #include #include "talk/base/common.h" #include "talk/xmllite/xmlelement.h" #include "talk/xmllite/xmlprinter.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/saslhandler.h" #include "talk/xmpp/xmpplogintask.h" namespace buzz { XmppEngine* XmppEngine::Create() { return new XmppEngineImpl(); } XmppEngineImpl::XmppEngineImpl() : stanza_parse_handler_(this), stanza_parser_(&stanza_parse_handler_), engine_entered_(0), password_(), requested_resource_(STR_EMPTY), tls_option_(buzz::TLS_REQUIRED), login_task_(new XmppLoginTask(this)), next_id_(0), state_(STATE_START), encrypted_(false), error_code_(ERROR_NONE), subcode_(0), stream_error_(), raised_reset_(false), output_handler_(NULL), session_handler_(NULL), iq_entries_(new IqEntryVector()), sasl_handler_(), output_(new std::stringstream()) { for (int i = 0; i < HL_COUNT; i+= 1) { stanza_handlers_[i].reset(new StanzaHandlerVector()); } // Add XMPP namespaces to XML namespaces stack. xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams"); xmlns_stack_.AddXmlns("", "jabber:client"); } 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); // TODO: The return value of the xml parser is not checked. stanza_parser_.Parse(bytes, len, false); return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) { if (state_ != STATE_CLOSED) { EnterExit ee(this); // If told that connection closed and not already closed, // then connection was unpexectedly dropped. if (subcode) { SignalError(ERROR_SOCKET, subcode); } else { SignalError(ERROR_CONNECTION_CLOSED, 0); // no subcode } } return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; tls_option_ = use_tls; return XMPP_RETURN_OK; } XmppReturnStatus XmppEngineImpl::SetTlsServer( const std::string& tls_server_hostname, const std::string& tls_server_domain) { if (state_ != STATE_START) return XMPP_RETURN_BADSTATE; tls_server_hostname_ = tls_server_hostname; tls_server_domain_= tls_server_domain; return XMPP_RETURN_OK; } TlsOptions XmppEngineImpl::GetTls() { return tls_option_; } 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_) { 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_) { // 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_) 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* start) { if (HasError() || raised_reset_) return; if (login_task_) { // start-stream should go to login task login_task_->IncomingStanza(start, true); if (login_task_->IsDone()) login_task_.reset(); } else { // if not logging in, it's an error to see a start SignalError(ERROR_XML, 0); } } void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) { if (HasError() || raised_reset_) return; if (stanza->Name() == QN_STREAM_ERROR) { // Explicit XMPP stream error SignalStreamError(stanza); } else if (login_task_) { // 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)) return; } } // 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(QN_TYPE); if (stanza->Name() == QN_IQ && !(type == "error" || type == "result")) { SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY); } } } void XmppEngineImpl::IncomingEnd(bool isError) { if (HasError() || raised_reset_) return; SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0); } void XmppEngineImpl::InternalSendStart(const std::string& to) { std::string hostname = tls_server_hostname_; if (hostname.empty()) hostname = to; // If not language is specified, the spec says use * std::string lang = lang_; if (lang.length() == 0) lang = "*"; // 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(QN_FROM)); XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_); } 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* stream_error) { if (state_ != STATE_CLOSED) { stream_error_.reset(new XmlElement(*stream_error)); SignalError(ERROR_STREAM, 0); } } void XmppEngineImpl::SignalError(Error error_code, int sub_code) { if (state_ != STATE_CLOSED) { error_code_ = error_code; subcode_ = sub_code; state_ = STATE_CLOSED; } } bool XmppEngineImpl::HasError() { return error_code_ != ERROR_NONE; } void XmppEngineImpl::StartTls(const std::string& domain) { if (output_handler_) { // As substitute for the real (login jid's) domain, we permit // verifying a tls_server_domain_ instead, if one was passed. // This allows us to avoid running a proxy that needs to handle // valuable certificates. 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->stanza_parser_.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. } } } // namespace buzz