You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
481 lines
12 KiB
481 lines
12 KiB
/*
|
|
* 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 <vector>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#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_ << "</stream:stream>";
|
|
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() == QN_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(QN_TYPE);
|
|
if (stanza->Name() == QN_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_ << "<stream:stream to=\"" << to << "\" version=\"1.0\" "
|
|
"xmlns:stream=\"http://etherx.jabber.org/streams\" "
|
|
"xmlns=\"jabber:client\">\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));
|
|
|
|
// TODO: consider caching the XmlPrinter
|
|
XmlPrinter::PrintXml(output_.get(), element,
|
|
XMPP_CLIENT_NAMESPACES, XMPP_CLIENT_NAMESPACES_LEN);
|
|
}
|
|
|
|
std::string
|
|
XmppEngineImpl::ChooseBestSaslMechanism(const std::vector<std::string> & 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.
|
|
}
|
|
}
|
|
|
|
}
|