yate/libs/yjingle/jbengine.cpp

1966 lines
54 KiB
C++

/**
* jbengine.cpp
* Yet Another Jabber Component Protocol Stack
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatejabber.h>
using namespace TelEngine;
/**
* JBEngine
*/
static XMPPNamespace s_ns;
// Default values
#define JB_STREAM_RESTART_COUNT 2 // Stream restart counter default value
#define JB_STREAM_RESTART_COUNT_MIN 1
#define JB_STREAM_RESTART_COUNT_MAX 10
#define JB_STREAM_RESTART_UPDATE 15000 // Stream restart counter update interval
#define JB_STREAM_RESTART_UPDATE_MIN 5000
#define JB_STREAM_RESTART_UPDATE_MAX 300000
// Sleep time for threads
#define SLEEP_READSOCKET 2 // Read socket
#define SLEEP_PROCESSPRESENCE 2 // Process presence events
#define SLEEP_PROCESSTIMEOUT 10 // Process presence timeout
// Presence values
#define PRESENCE_PROBE_INTERVAL 1800000
#define PRESENCE_EXPIRE_INTERVAL 300000
#define JINGLE_VERSION "1.0" // Version capability
#define JINGLE_VOICE "voice-v1" // Voice capability for Google Talk
JBEngine::JBEngine()
: Mutex(true),
m_clientsMutex(true),
m_presence(0),
m_identity(0),
m_restartUpdateTime(0),
m_restartUpdateInterval(JB_STREAM_RESTART_UPDATE),
m_restartCount(JB_STREAM_RESTART_COUNT),
m_printXml(false),
m_streamID(0),
m_serverMutex(true)
{
debugName("jbengine");
m_identity = new JIDIdentity(JIDIdentity::Gateway,JIDIdentity::GatewayGeneric);
//m_features.add(XMPPNamespace::Command);
m_features.add(XMPPNamespace::Jingle);
m_features.add(XMPPNamespace::JingleAudio);
m_features.add(XMPPNamespace::Dtmf);
m_features.add(XMPPNamespace::DiscoInfo);
XDebug(this,DebugAll,"JBEngine. [%p]",this);
}
JBEngine::~JBEngine()
{
cleanup();
if (m_identity)
m_identity->deref();
XDebug(this,DebugAll,"~JBEngine. [%p]",this);
}
void JBEngine::initialize(const NamedList& params)
{
clearServerList();
m_printXml = params.getBoolValue("printxml",false);
// Alternate domain names
m_alternateDomain = params.getValue("extra_domain");
// Stream restart update interval
m_restartUpdateInterval =
params.getIntValue("stream_restartupdateinterval",JB_STREAM_RESTART_UPDATE);
if (m_restartUpdateInterval < JB_STREAM_RESTART_UPDATE_MIN)
m_restartUpdateInterval = JB_STREAM_RESTART_UPDATE_MIN;
else
if (m_restartUpdateInterval > JB_STREAM_RESTART_UPDATE_MAX)
m_restartUpdateInterval = JB_STREAM_RESTART_UPDATE_MAX;
// Stream restart count
m_restartCount =
params.getIntValue("stream_restartcount",JB_STREAM_RESTART_COUNT);
if (m_restartCount < JB_STREAM_RESTART_COUNT_MIN)
m_restartCount = JB_STREAM_RESTART_COUNT_MIN;
else
if (m_restartCount > JB_STREAM_RESTART_COUNT_MAX)
m_restartCount = JB_STREAM_RESTART_COUNT_MAX;
// XML parser max receive buffer
XMLParser::s_maxDataBuffer =
params.getIntValue("xmlparser_maxbuffer",XMLPARSER_MAXDATABUFFER);
// Default resource
m_defaultResource = params.getValue("default_resource","yate");
if (debugAt(DebugAll)) {
String s;
s << "\r\ndefault_resource=" << m_defaultResource;
s << "\r\nstream_restartupdateinterval=" << (unsigned int)m_restartUpdateInterval;
s << "\r\nstream_restartcount=" << (unsigned int)m_restartCount;
s << "\r\nxmlparser_maxbuffer=" << (unsigned int)XMLParser::s_maxDataBuffer;
s << "\r\nprintxml=" << String::boolText(m_printXml);
Debug(this,DebugAll,"Initialized:%s",s.c_str());
}
}
void JBEngine::cleanup()
{
Lock lock(this);
DDebug(this,DebugAll,"Cleanup.");
ObjList* obj = m_streams.skipNull();
for (; obj; obj = m_streams.skipNext()) {
JBComponentStream* s = static_cast<JBComponentStream*>(obj->get());
s->terminate(true,true,XMPPUtils::createStreamError(XMPPError::Shutdown),true);
}
}
void JBEngine::setComponentServer(const char* domain)
{
Lock lock(m_serverMutex);
JBServerInfo* p = getServer(domain,true);
// If doesn't exists try to get the first one from the list
if (!p) {
ObjList* obj = m_server.skipNull();
p = obj ? static_cast<JBServerInfo*>(obj->get()) : 0;
}
else
p->deref();
if (!p) {
Debug(this,DebugNote,"No default component server.");
return;
}
m_componentDomain = p->name();
m_componentAddr = p->address();
DDebug(this,DebugAll,"Default component server set to '%s' (%s).",
m_componentDomain.c_str(),m_componentAddr.c_str());
}
JBComponentStream* JBEngine::getStream(const char* domain, bool create)
{
Lock lock(this);
if (!domain)
domain = m_componentDomain;
JBComponentStream* stream = findStream(domain);
XDebug(this,DebugAll,
"getStream. Remote: '%s'. Stream exists: %s (%p). Create: %s.",
domain,stream?"YES":"NO",stream,create?"YES":"NO");
if (!stream && create) {
SocketAddr addr(PF_INET);
addr.host(domain);
addr.port(getPort(addr.host()));
stream = new JBComponentStream(this,domain,addr);
m_streams.append(stream);
}
return ((stream && stream->ref()) ? stream : 0);
}
bool JBEngine::receive()
{
bool ok = false;
lock();
ListIterator iter(m_streams);
for (;;) {
JBComponentStream* stream = static_cast<JBComponentStream*>(iter.get());
// End of iteration?
if (!stream)
break;
// Get a reference
RefPointer<JBComponentStream> sref = stream;
if (!sref)
continue;
// Read socket
unlock();
if (sref->receive())
ok = true;
lock();
}
unlock();
return ok;
}
void JBEngine::runReceive()
{
while (1) {
if (!receive())
Thread::msleep(SLEEP_READSOCKET,true);
}
}
JBEvent* JBEngine::getEvent(u_int64_t time)
{
lock();
ListIterator iter(m_streams);
for (;;) {
JBComponentStream* stream = static_cast<JBComponentStream*>(iter.get());
// End of iteration?
if (!stream)
break;
// Get a reference
RefPointer<JBComponentStream> sref = stream;
if (!sref)
continue;
// Get event
unlock();
JBEvent* event = sref->getEvent(time);
if (event)
// Check if the engine should process these events
switch (event->type()) {
case JBEvent::Presence:
if (!(m_presence && m_presence->receive(event)))
return event;
break;
case JBEvent::IqDiscoGet:
case JBEvent::IqDiscoSet:
case JBEvent::IqDiscoRes: {
JabberID jid(event->to());
if (jid.node()) {
if (!(m_presence && m_presence->receive(event)))
return event;
break;
}
if (!processDiscoInfo(event))
return event;
break;
}
case JBEvent::IqCommandGet:
case JBEvent::IqCommandSet:
case JBEvent::IqCommandRes: {
JabberID jid(event->to());
if (!processCommand(event))
return event;
break;
}
case JBEvent::Invalid:
event->deref();
break;
case JBEvent::Terminated:
// Restart stream
connect(event->stream());
event->deref();
break;
// Unhandled 'iq' element
case JBEvent::Iq:
DDebug(this,DebugInfo,
"getEvent. Event((%p): %u). Unhandled 'iq' element.",event,event->type());
stream->sendIqError(event->releaseXML(),XMPPError::TypeCancel,
XMPPError::SFeatureNotImpl);
event->deref();
break;
default:
return event;
}
lock();
}
unlock();
return 0;
}
bool JBEngine::remoteIdExists(const JBComponentStream* stream)
{
// IDs for incoming streams are generated by this engine
// Check the stream source and ID
if (!stream)
return false;
Lock lock(this);
ObjList* obj = m_streams.skipNull();
for (; obj; obj = obj->skipNext()) {
JBComponentStream* s = static_cast<JBComponentStream*>(obj->get());
if (s != stream &&
s->remoteName() == stream->remoteName() &&
s->id() == stream->id())
return true;
}
return false;
}
void JBEngine::createSHA1(String& sha, const String& id,
const String& password)
{
SHA1 sha1;
sha1 << id << password;
sha = sha1.hexDigest();
}
bool JBEngine::checkSHA1(const String& sha, const String& id,
const String& password)
{
String tmp;
createSHA1(tmp,id,password);
return tmp == sha;
}
void JBEngine::timerTick(u_int64_t time)
{
if (m_restartUpdateTime > time)
return;
// Update server streams counters
Lock lock(m_serverMutex);
ObjList* obj = m_server.skipNull();
for (; obj; obj = obj->skipNext()) {
JBServerInfo* server = static_cast<JBServerInfo*>(obj->get());
if (server->restartCount() < m_restartCount) {
server->incRestart();
DDebug(this,DebugAll,
"Increased stream restart counter (%u) for '%s'.",
server->restartCount(),server->name().c_str());
}
// Check if stream should be restarted (created)
if (server->autoRestart()) {
JBComponentStream* stream = getStream(server->name(),true);
if (stream)
stream->deref();
}
}
// Update next update time
m_restartUpdateTime = time + m_restartUpdateInterval;
}
bool JBEngine::connect(JBComponentStream* stream)
{
if (!stream)
return false;
stream->connect();
return true;
}
void JBEngine::returnEvent(JBEvent* event)
{
if (!event)
return;
if (event->type() == JBEvent::Message && processMessage(event))
return;
DDebug(this,DebugAll,
"returnEvent. Delete event((%p): %u).",event,event->type());
event->deref();
}
bool JBEngine::acceptOutgoing(const String& remoteAddr,
String& password)
{
bool b = getServerPassword(password,remoteAddr,false);
XDebug(this,DebugAll,
"acceptOutgoing. To: '%s'. %s.",remoteAddr.c_str(),
b ? "Accepted" : "Not accepted");
return b;
}
int JBEngine::getPort(const String& remoteAddr)
{
int port = 0;
getServerPort(port,remoteAddr,false);
XDebug(this,DebugAll,
"getPort. For: '%s'. Port: %d",remoteAddr.c_str(),port);
return port;
}
void JBEngine::appendServer(JBServerInfo* server, bool open)
{
if (!server)
return;
// Add if doesn't exists. Delete if already in the list
JBServerInfo* p = getServer(server->name(),true);
if (!p) {
m_serverMutex.lock();
m_server.append(server);
m_serverMutex.unlock();
}
else {
server->deref();
p->deref();
}
// Open stream
if (open) {
JBComponentStream* stream = getStream(server->name());
if (stream)
stream->deref();
}
}
bool JBEngine::getServerIdentity(String& destination,
const char* token, bool domain)
{
Lock lock(m_serverMutex);
JBServerInfo* server = getServer(token,domain);
if (!server)
return false;
destination = server->identity();
server->deref();
return true;
}
bool JBEngine::getFullServerIdentity(String& destination, const char* token,
bool domain)
{
Lock lock(m_serverMutex);
JBServerInfo* server = getServer(token,domain);
if (!server)
return false;
destination = server->fullIdentity();
server->deref();
return true;
}
bool JBEngine::getStreamRestart(const char* token, bool domain)
{
Lock lock(m_serverMutex);
JBServerInfo* server = getServer(token,domain);
if (!server)
return false;
bool restart = server->getRestart();
if (restart)
DDebug(this,DebugAll,"Decreased stream restart counter (%u) for '%s'.",
server->restartCount(),server->name().c_str());
server->deref();
return restart;
}
bool JBEngine::processDiscoInfo(JBEvent* event)
{
JBComponentStream* stream = event->stream();
// Check if we have a stream and this engine is the destination
if (!(stream && event->element()))
return false;
//TODO: Check if the engine is the destination.
// The destination might be a user
switch (event->type()) {
case JBEvent::IqDiscoGet:
{
DDebug(this,DebugAll,"processDiscoInfo. Get. From '%s' to '%s'.",
event->from().c_str(),event->to().c_str());
// Create response
XMLElement* query = XMPPUtils::createElement(XMLElement::Query,
XMPPNamespace::DiscoInfo);
// Set the identity & features
if (m_identity) {
*(String*)(m_identity) = stream->localName();
query->addChild(m_identity->toXML());
}
m_features.addTo(query);
XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqResult,event->to(),
event->from(),event->id());
iq->addChild(query);
// Send response
stream->sendStanza(iq);
}
break;
case JBEvent::IqDiscoRes:
DDebug(this,DebugAll,"processDiscoInfo. Result. From '%s' to '%s'.",
event->from().c_str(),event->to().c_str());
break;
case JBEvent::IqDiscoSet:
DDebug(this,DebugAll,"processDiscoInfo. Set. From '%s' to '%s'.",
event->from().c_str(),event->to().c_str());
break;
default:
Debug(this,DebugNote,"processDiscoInfo. From '%s' to '%s'. Unhandled.",
event->from().c_str(),event->to().c_str());
}
// Release event
event->deref();
return true;
}
bool JBEngine::processCommand(JBEvent* event)
{
JBComponentStream* stream = event->stream();
if (!(event && event->element() && event->child())) {
event->deref();
return true;
}
//TODO: Check if the engine is the destination.
// The destination might be a user
switch (event->type()) {
case JBEvent::IqCommandGet:
case JBEvent::IqCommandSet:
DDebug(this,DebugAll,"processCommand. From '%s' to '%s'.",
event->from().c_str(),event->to().c_str());
stream->sendIqError(event->releaseXML(),XMPPError::TypeCancel,
XMPPError::SFeatureNotImpl);
break;
case JBEvent::IqDiscoRes:
DDebug(this,DebugAll,"processCommand. Result. From '%s' to '%s'.",
event->from().c_str(),event->to().c_str());
break;
default:
Debug(this,DebugNote,"processCommand. From '%s' to '%s'. Unhandled.",
event->from().c_str(),event->to().c_str());
}
// Release event
event->deref();
return true;
}
JBComponentStream* JBEngine::findStream(const String& remoteName)
{
ObjList* obj = m_streams.skipNull();
for (; obj; obj = obj->skipNext()) {
JBComponentStream* stream = static_cast<JBComponentStream*>(obj->get());
if (stream->remoteName() == remoteName)
return stream;
}
return 0;
}
void JBEngine::removeStream(JBComponentStream* stream, bool del)
{
if (!m_streams.remove(stream,del))
return;
DDebug(this,DebugAll,
"removeStream (%p). Deleted: %s.",stream,del?"YES":"NO");
}
void JBEngine::addClient(JBClient* client)
{
if (!client)
return;
// Check existence
Lock lock(m_clientsMutex);
if (m_clients.find(client))
return;
m_clients.append(client);
}
void JBEngine::removeClient(JBClient* client)
{
if (!client)
return;
Lock lock(m_clientsMutex);
m_clients.remove(client,false);
}
JBServerInfo* JBEngine::getServer(const char* token, bool domain)
{
if (!token)
token = domain ? m_componentDomain : m_componentAddr;
if (!token)
return 0;
ObjList* obj = m_server.skipNull();
JBServerInfo* server = 0;
if (domain)
for (; obj; obj = obj->skipNext()) {
server = static_cast<JBServerInfo*>(obj->get());
if (server->name() == token)
break;
server = 0;
}
else
for (; obj; obj = obj->skipNext()) {
server = static_cast<JBServerInfo*>(obj->get());
if (server->address() == token)
break;
server = 0;
}
return (server && server->ref()) ? server : 0;
}
bool JBEngine::processMessage(JBEvent* event)
{
DDebug(this,DebugInfo,
"JBEngine::processMessage. From: '%s'. To: '%s'.",
event->from().c_str(),event->to().c_str());
return false;
}
bool JBEngine::getServerPassword(String& destination,
const char* token, bool domain)
{
Lock lock(m_serverMutex);
JBServerInfo* server = getServer(token,domain);
if (!server)
return false;
destination = server->password();
server->deref();
return true;
}
bool JBEngine::getServerPort(int& destination,
const char* token, bool domain)
{
Lock lock(m_serverMutex);
JBServerInfo* server = getServer(token,domain);
if (!server)
return false;
destination = server->port();
server->deref();
return true;
}
void JBEngine::setPresenceServer(JBPresence* presence)
{
if (m_presence)
return;
Lock lock(this);
if (presence)
m_presence = presence;
}
void JBEngine::unsetPresenceServer(JBPresence* presence)
{
Lock lock(this);
if (m_presence == presence)
m_presence = 0;
}
/**
* JBEvent
*/
JBEvent::JBEvent(Type type, JBComponentStream* stream)
: m_type(type),
m_stream(0),
m_link(true),
m_element(0),
m_child(0)
{
init(stream,0);
}
JBEvent::JBEvent(Type type, JBComponentStream* stream,
XMLElement* element, XMLElement* child)
: m_type(type),
m_stream(0),
m_link(true),
m_element(element),
m_child(child)
{
if (!init(stream,element))
m_type = Invalid;
}
JBEvent::JBEvent(Type type, JBComponentStream* stream,
XMLElement* element, const String& senderID)
: m_type(type),
m_stream(0),
m_link(true),
m_element(element),
m_child(0),
m_id(senderID)
{
if (!init(stream,element))
m_type = Invalid;
}
JBEvent::~JBEvent()
{
if (m_stream) {
releaseStream();
m_stream->deref();
}
if (m_element)
delete m_element;
XDebug(DebugAll,"JBEvent::~JBEvent. [%p]",this);
}
void JBEvent::releaseStream()
{
if (m_link && m_stream) {
m_stream->eventTerminated(this);
m_link = false;
}
}
bool JBEvent::init(JBComponentStream* stream, XMLElement* element)
{
bool bRet = true;
if (stream && stream->ref())
m_stream = stream;
else
bRet = false;
m_element = element;
XDebug(DebugAll,"JBEvent::JBEvent. Type: %u. Stream (%p). Element: (%p). [%p]",
m_type,m_stream,m_element,this);
return bRet;
}
/**
* JBClient
*/
JBClient::JBClient(JBEngine* engine)
: m_engine(0)
{
if (engine && engine->ref()) {
m_engine = engine;
m_engine->addClient(this);
}
}
JBClient::~JBClient()
{
if (m_engine) {
m_engine->removeClient(this);
m_engine->deref();
}
}
/**
* JIDResource
*/
TokenDict JIDResource::s_show[] = {
{"away", ShowAway},
{"chat", ShowChat},
{"dnd", ShowDND},
{"xa", ShowXA},
{0,0},
};
bool JIDResource::setPresence(bool value)
{
Presence p = value ? Available : Unavailable;
if (m_presence == p)
return false;
m_presence = p;
return true;
}
bool JIDResource::fromXML(XMLElement* element)
{
if (!(element && element->type() == XMLElement::Presence))
return false;
JBPresence::Presence p = JBPresence::presenceType(element->getAttribute("type"));
if (p != JBPresence::None && p != JBPresence::Unavailable)
return false;
// Show
XMLElement* tmp = element->findFirstChild("show");
m_show = tmp ? showType(tmp->getText()) : ShowNone;
// Status
tmp = element->findFirstChild("status");
m_status = tmp ? tmp->getText() : "";
// Capability
bool capsChanged = false;
tmp = element->findFirstChild("c");
if (tmp) {
NamedList caps("");
if (XMPPUtils::split(caps,tmp->getAttribute("ext"),' ',true)) {
// Check audio
bool tmp = (0 != caps.getParam(JINGLE_VOICE));
if (tmp != hasCap(CapAudio)) {
capsChanged = true;
if (tmp)
m_capability |= CapAudio;
else
m_capability &= ~CapAudio;
}
}
}
// Presence
bool presenceChanged = setPresence(p == JBPresence::None);
// Return
return capsChanged || presenceChanged;
}
void JIDResource::addTo(XMLElement* element)
{
if (!element)
return;
if (m_show != ShowNone)
element->addChild(new XMLElement("show",0,showText(m_show)));
element->addChild(new XMLElement("status",0,m_status));
// Add priority
XMLElement* priority = new XMLElement("priority",0,"25");
element->addChild(priority);
// Add capabilities
XMLElement* c = new XMLElement("c");
c->setAttribute("xmlns","http://jabber.org/protocol/caps");
c->setAttribute("node","http://www.google.com/xmpp/client/caps");
c->setAttribute("ver",JINGLE_VERSION);
if (hasCap(CapAudio))
c->setAttribute("ext",JINGLE_VOICE);
element->addChild(c);
}
/**
* JIDResourceList
*/
bool JIDResourceList::add(const String& name)
{
if (get(name))
return false;
m_resources.append(new JIDResource(name));
return true;
}
bool JIDResourceList::add(JIDResource* resource)
{
if (!resource)
return false;
if (get(resource->name())) {
resource->deref();
return false;
}
m_resources.append(resource);
return true;
}
JIDResource* JIDResourceList::get(const String& name)
{
ObjList* obj = m_resources.skipNull();
for (; obj; obj = obj->skipNext()) {
JIDResource* res = static_cast<JIDResource*>(obj->get());
if (res->name() == name)
return res;
}
return 0;
}
JIDResource* JIDResourceList::getAudio(bool availableOnly)
{
ObjList* obj = m_resources.skipNull();
for (; obj; obj = obj->skipNext()) {
JIDResource* res = static_cast<JIDResource*>(obj->get());
if (res->hasCap(JIDResource::CapAudio) &&
(!availableOnly || (availableOnly && res->available())))
return res;
}
return 0;
}
/**
* XMPPUser
*/
TokenDict XMPPUser::s_subscription[] = {
{"none", None},
{"to", To},
{"from", From},
{"both", Both},
{0,0},
};
XMPPUser::XMPPUser(XMPPUserRoster* local, const char* node, const char* domain,
Subscription sub, bool subTo, bool sendProbe)
: Mutex(true),
m_local(0),
m_jid(node,domain),
m_subscription(None),
m_nextProbe(0),
m_expire(0)
{
if (local && local->ref())
m_local = local;
if (!m_local)
Debug(m_local->engine(),DebugFail,
"XMPPUser. No local user for '%s'. [%p]",m_jid.c_str(),this);
else
DDebug(m_local->engine(),DebugNote,
"XMPPUser. Local: (%p): %s. Remote: %s. [%p]",
m_local,m_local->jid().c_str(),m_jid.c_str(),this);
m_local->addUser(this);
// Update subscription
switch (sub) {
case None:
break;
case Both:
updateSubscription(true,true,0);
updateSubscription(false,true,0);
break;
case From:
updateSubscription(true,true,0);
break;
case To:
updateSubscription(false,true,0);
break;
}
// Subscribe to remote user if not already subscribed and auto subscribe is true
if (subTo || (!subscribedTo() && (m_local->engine()->autoSubscribe() & To)))
sendSubscribe(JBPresence::Subscribe,0);
// Probe remote user
if (sendProbe)
probe(0);
}
XMPPUser::~XMPPUser()
{
DDebug(m_local->engine(),DebugNote, "~XMPPUser. Local: %s. Remote: %s. [%p]",
m_local->jid().c_str(),m_jid.c_str(),this);
// Remove all local resources: this will make us unavailable
clearLocalRes();
m_local->removeUser(this);
m_local->deref();
}
bool XMPPUser::addLocalRes(JIDResource* resource)
{
if (!resource)
return false;
Lock lock(this);
if (!m_localRes.add(resource))
return false;
DDebug(m_local->engine(),DebugAll,
"XMPPUser. Added local resource '%s'. Audio: %s. [%p]",
resource->name().c_str(),
resource->hasCap(JIDResource::CapAudio)?"YES":"NO",this);
resource->setPresence(true);
if (subscribedFrom())
sendPresence(resource,0,true);
return true;
}
void XMPPUser::removeLocalRes(JIDResource* resource)
{
if (!(resource && m_localRes.get(resource->name())))
return;
Lock lock(this);
resource->setPresence(false);
if (subscribedFrom())
sendPresence(resource,0);
m_localRes.remove(resource);
DDebug(m_local->engine(),DebugAll,
"XMPPUser. Removed local resource '%s'. [%p]",
resource->name().c_str(),this);
}
void XMPPUser::clearLocalRes()
{
Lock lock(this);
m_localRes.clear();
if (subscribedFrom())
sendUnavailable(0);
}
void XMPPUser::processError(JBEvent* event)
{
String code, type, error;
JBPresence::decodeError(event->element(),code,type,error);
DDebug(m_local->engine(),DebugAll,"XMPPUser. Error. '%s'. Code: '%s'. [%p]",
error.c_str(),code.c_str(),this);
}
void XMPPUser::processProbe(JBEvent* event, const String* resName)
{
updateTimeout(true);
XDebug(m_local->engine(),DebugAll,"XMPPUser. Process probe. [%p]",this);
if (resName)
notifyResource(false,*resName,event->stream(),true);
else
notifyResources(false,event->stream(),true);
}
bool XMPPUser::processPresence(JBEvent* event, bool available, const JabberID& from)
{
updateTimeout(true);
// No resource ?
Lock lock(&m_remoteRes);
if (from.resource().null()) {
// Available without resource ? No way !!!
if (available)
return true;
// Check if should notify
bool notify = true;
// User is unavailable for all resources
ListIterator iter(m_remoteRes.m_resources);
GenObject* obj;
for (; (obj = iter.get());) {
JIDResource* res = static_cast<JIDResource*>(obj);
if (res->setPresence(false)) {
DDebug(m_local->engine(),DebugNote,
"XMPPUser. Local user (%p). Resource (%s) changed state: %s. Audio: %s. [%p]",
m_local,res->name().c_str(),res->available()?"available":"unavailable",
res->hasCap(JIDResource::CapAudio)?"YES":"NO",this);
notify = false;
m_local->engine()->notifyPresence(this,res);
}
if (m_local->engine()->delUnavailable())
m_remoteRes.m_resources.remove(res,true);
}
if (notify)
m_local->engine()->notifyPresence(event,m_local->jid(),m_jid,false);
// No more resources ? Remove user
if (!m_remoteRes.getFirst() && m_local->engine()->delUnavailable())
return false;
// Notify local resources to remote user if not already done
if (subscribedFrom())
notifyResources(false,event->stream(),false);
return true;
}
// 'from' has a resource: check if we already have one
ObjList* obj = m_remoteRes.m_resources.skipNull();
JIDResource* res = 0;
for (; obj; obj = obj->skipNext()) {
res = static_cast<JIDResource*>(obj->get());
if (res->name() == from.resource())
break;
res = 0;
}
// Add a new resource if we don't have one
if (!res) {
res = new JIDResource(from.resource());
m_remoteRes.add(res);
DDebug(m_local->engine(),DebugNote,
"XMPPUser. Local user (%p). Added new remote resource (%s). [%p]",
m_local,res->name().c_str(),this);
}
// Changed: notify
if (res->fromXML(event->element())) {
DDebug(m_local->engine(),DebugNote,
"XMPPUser. Local user (%p). Resource (%s) changed state: %s. Audio: %s. [%p]",
m_local,res->name().c_str(),res->available()?"available":"unavailable",
res->hasCap(JIDResource::CapAudio)?"YES":"NO",this);
m_local->engine()->notifyPresence(this,res);
}
if (!available && m_local->engine()->delUnavailable()) {
m_remoteRes.m_resources.remove(res,true);
// No more resources ? Remove user
if (!m_remoteRes.getFirst())
return false;
}
// Notify local resources to remote user if not already done
if (subscribedFrom())
notifyResources(false,event->stream(),false);
return true;
}
void XMPPUser::processSubscribe(JBEvent* event, JBPresence::Presence type)
{
Lock lock(this);
switch (type) {
case JBPresence::Subscribe:
// Already subscribed to us: Confirm subscription
if (subscribedFrom()) {
sendSubscribe(JBPresence::Subscribed,event->stream());
return;
}
// Approve if auto subscribing
if ((m_local->engine()->autoSubscribe() & From))
sendSubscribe(JBPresence::Subscribed,event->stream());
DDebug(m_local->engine(),DebugAll,
"XMPPUser. processSubscribe. Subscribing. [%p]",this);
break;
case JBPresence::Subscribed:
// Already subscribed to remote user: do nothing
if (subscribedTo())
return;
updateSubscription(false,true,event->stream());
DDebug(m_local->engine(),DebugAll,
"XMPPUser. processSubscribe. Subscribed. [%p]",this);
break;
case JBPresence::Unsubscribe:
// Already unsubscribed from us: confirm it
if (!subscribedFrom()) {
sendSubscribe(JBPresence::Unsubscribed,event->stream());
return;
}
// Approve if auto subscribing
if ((m_local->engine()->autoSubscribe() & From))
sendSubscribe(JBPresence::Unsubscribed,event->stream());
DDebug(m_local->engine(),DebugAll,
"XMPPUser. processSubscribe. Unsubscribing. [%p]",this);
break;
case JBPresence::Unsubscribed:
// If not subscribed to remote user ignore the unsubscribed confirmation
if (!subscribedTo())
return;
updateSubscription(false,false,event->stream());
DDebug(m_local->engine(),DebugAll,
"XMPPUser. processSubscribe. Unsubscribed. [%p]",this);
break;
default:
return;
}
// Notify
m_local->engine()->notifySubscribe(this,type);
}
bool XMPPUser::probe(JBComponentStream* stream, u_int64_t time)
{
Lock lock(this);
updateTimeout(false,time);
XDebug(m_local->engine(),DebugAll,"XMPPUser. Sending '%s'. [%p]",
JBPresence::presenceText(JBPresence::Probe),this);
XMLElement* xml = JBPresence::createPresence(m_local->jid().bare(),m_jid.bare(),
JBPresence::Probe);
return m_local->engine()->sendStanza(xml,stream);
}
bool XMPPUser::sendSubscribe(JBPresence::Presence type, JBComponentStream* stream)
{
Lock lock(this);
bool from = false;
bool value = false;
switch (type) {
case JBPresence::Subscribed:
from = true;
value = true;
break;
case JBPresence::Unsubscribed:
from = true;
break;
case JBPresence::Subscribe:
case JBPresence::Unsubscribe:
break;
default:
return false;
}
XDebug(m_local->engine(),DebugAll,"XMPPUser. Sending '%s'. [%p]",
JBPresence::presenceText(type),this);
XMLElement* xml = JBPresence::createPresence(m_local->jid().bare(),m_jid.bare(),type);
bool result = m_local->engine()->sendStanza(xml,stream);
// Set subscribe data. Not for subscribe/unsubscribe
if (from && result)
updateSubscription(true,value,stream);
return result;
}
bool XMPPUser::timeout(u_int64_t time)
{
Lock lock(this);
if (!m_expire) {
if (m_nextProbe < time)
probe(0,time);
return false;
}
if (m_expire > time)
return false;
// Timeout. Clear resources & Notify
Debug(m_local->engine(),DebugNote,
"XMPPUser. Local user (%p). Timeout. [%p]",m_local,this);
m_remoteRes.clear();
m_local->engine()->notifyPresence(0,m_jid,m_local->jid(),false);
return true;
}
bool XMPPUser::sendPresence(JIDResource* resource, JBComponentStream* stream,
bool force)
{
Lock lock(this);
// Send presence for all resources
JabberID from(m_local->jid().node(),m_local->jid().domain());
if (!resource) {
ObjList* obj = m_localRes.m_resources.skipNull();
for (; obj; obj = obj->skipNext()) {
JIDResource* res = static_cast<JIDResource*>(obj->get());
from.resource(res->name());
XMLElement* xml = JBPresence::createPresence(from,m_jid.bare(),
res->available() ? JBPresence::None : JBPresence::Unavailable);
// Add capabilities
if (res->available())
res->addTo(xml);
m_local->engine()->sendStanza(xml,stream);
}
return true;
}
// Don't send if already done and not force
if (resource->presence() != JIDResource::Unknown && !force)
return false;
from.resource(resource->name());
XMLElement* xml = JBPresence::createPresence(from,m_jid.bare(),
resource->available() ? JBPresence::None : JBPresence::Unavailable);
// Add capabilities
if (resource->available())
resource->addTo(xml);
return m_local->engine()->sendStanza(xml,stream);
}
void XMPPUser::notifyResource(bool remote, const String& name,
JBComponentStream* stream, bool force)
{
if (remote) {
Lock lock(&m_remoteRes);
JIDResource* res = m_remoteRes.get(name);
if (res)
m_local->engine()->notifyPresence(this,res);
return;
}
Lock lock(&m_localRes);
JIDResource* res = m_localRes.get(name);
if (res)
sendPresence(res,stream,force);
}
void XMPPUser::notifyResources(bool remote, JBComponentStream* stream, bool force)
{
if (remote) {
Lock lock(&m_remoteRes);
ObjList* obj = m_remoteRes.m_resources.skipNull();
for (; obj; obj = obj->skipNext()) {
JIDResource* res = static_cast<JIDResource*>(obj->get());
m_local->engine()->notifyPresence(this,res);
}
return;
}
Lock lock(&m_localRes);
ObjList* obj = m_localRes.m_resources.skipNull();
for (; obj; obj = obj->skipNext()) {
JIDResource* res = static_cast<JIDResource*>(obj->get());
sendPresence(res,stream,force);
}
}
bool XMPPUser::sendUnavailable(JBComponentStream* stream)
{
XDebug(m_local->engine(),DebugAll,"XMPPUser. Sending 'unavailable'. [%p]",this);
XMLElement* xml = JBPresence::createPresence(m_local->jid().bare(),m_jid.bare(),
JBPresence::Unavailable);
return m_local->engine()->sendStanza(xml,stream);
}
void XMPPUser::updateSubscription(bool from, bool value, JBComponentStream* stream)
{
Lock lock(this);
// Don't update if nothing changed
if ((from && value == subscribedFrom()) ||
(!from && value == subscribedTo()))
return;
// Update
int s = (from ? From : To);
if (value)
m_subscription |= s;
else
m_subscription &= ~s;
DDebug(m_local->engine(),DebugNote,
"XMPPUser. Local user (%p). Subscription updated. State '%s'. [%p]",
m_local,subscribeText(m_subscription),this);
// Send presence if remote user is subscribed to us
if (from && subscribedFrom()) {
sendUnavailable(stream);
sendPresence(0,stream,true);
}
}
void XMPPUser::updateTimeout(bool from, u_int64_t time)
{
Lock lock(this);
m_nextProbe = time + m_local->engine()->probeInterval();
if (from)
m_expire = 0;
else
m_expire = time + m_local->engine()->expireInterval();
}
/**
* XMPPUserRoster
*/
XMPPUserRoster::XMPPUserRoster(JBPresence* engine, const char* node,
const char* domain)
: Mutex(true),
m_jid(node,domain),
m_engine(engine)
{
m_engine->addRoster(this);
DDebug(m_engine,DebugNote, "XMPPUserRoster. %s. [%p]",m_jid.c_str(),this);
}
XMPPUserRoster::~XMPPUserRoster()
{
m_engine->removeRoster(this);
lock();
m_remote.clear();
unlock();
DDebug(m_engine,DebugNote, "~XMPPUserRoster. %s. [%p]",m_jid.c_str(),this);
}
XMPPUser* XMPPUserRoster::getUser(const JabberID& jid, bool add, bool* added)
{
Lock lock(this);
ObjList* obj = m_remote.skipNull();
XMPPUser* u = 0;
for (; obj; obj = obj->skipNext()) {
u = static_cast<XMPPUser*>(obj->get());
if (jid.bare() == u->jid().bare())
break;
u = 0;
}
if (!u && !add)
return 0;
if (!u) {
u = new XMPPUser(this,jid.node(),jid.domain(),XMPPUser::From);
if (added)
*added = true;
}
return u->ref() ? u : 0;
}
bool XMPPUserRoster::removeUser(const JabberID& remote)
{
Lock lock(this);
ObjList* obj = m_remote.skipNull();
for (; obj; obj = obj->skipNext()) {
XMPPUser* u = static_cast<XMPPUser*>(obj->get());
if (remote.bare() == u->jid().bare()) {
m_remote.remove(u,true);
break;
}
}
return (0 != m_remote.skipNull());
}
bool XMPPUserRoster::timeout(u_int64_t time)
{
Lock lock(this);
ListIterator iter(m_remote);
GenObject* obj;
for (; (obj = iter.get());) {
XMPPUser* u = static_cast<XMPPUser*>(obj);
if (u->timeout(time))
m_remote.remove(u,true);
}
return (0 == m_remote.skipNull());
}
/**
* JBPresence
*/
TokenDict JBPresence::s_presence[] = {
{"error", Error},
{"probe", Probe},
{"subscribe", Subscribe},
{"subscribed", Subscribed},
{"unavailable", Unavailable},
{"unsubscribe", Unsubscribe},
{"unsubscribed", Unsubscribed},
{0,0}
};
JBPresence::JBPresence(JBEngine* engine, const NamedList& params)
: JBClient(engine),
Mutex(true),
m_autoSubscribe((int)XMPPUser::None),
m_delUnavailable(false),
m_addOnSubscribe(false),
m_addOnProbe(false),
m_addOnPresence(false),
m_autoProbe(true),
m_probeInterval(0),
m_expireInterval(0)
{
debugName("jbpresence");
XDebug(this,DebugAll,"JBPresence. [%p]",this);
if (m_engine)
m_engine->setPresenceServer(this);
initialize(params);
}
JBPresence::~JBPresence()
{
cleanup();
if (m_engine)
m_engine->unsetPresenceServer(this);
m_events.clear();
XDebug(this,DebugAll,"~JBPresence. [%p]",this);
}
void JBPresence::initialize(const NamedList& params)
{
m_autoSubscribe = XMPPUser::subscribeType(params.getValue("auto_subscribe"));
m_delUnavailable = params.getBoolValue("delete_unavailable",true);
m_autoProbe = params.getBoolValue("auto_probe",true);
if (m_engine) {
JBServerInfo* info = m_engine->getServer();
if (info) {
m_addOnSubscribe = m_addOnProbe = m_addOnPresence = info->roster();
// Automatically process (un)subscribe and probe requests if no roster
if (!info->roster()) {
m_autoProbe = true;
m_autoSubscribe = XMPPUser::From;
}
info->deref();
}
}
m_probeInterval = params.getIntValue("probe_interval",PRESENCE_PROBE_INTERVAL);
m_expireInterval = params.getIntValue("expire_interval",PRESENCE_EXPIRE_INTERVAL);
if (debugAt(DebugAll)) {
String s;
s << "\r\nauto_subscribe=" << XMPPUser::subscribeText(m_autoSubscribe);
s << "\r\ndelete_unavailable=" << String::boolText(m_delUnavailable);
s << "\r\nadd_onsubscribe=" << String::boolText(m_addOnSubscribe);
s << "\r\nadd_onprobe=" << String::boolText(m_addOnProbe);
s << "\r\nadd_onpresence=" << String::boolText(m_addOnPresence);
s << "\r\nauto_probe=" << String::boolText(m_autoProbe);
s << "\r\nprobe_interval=" << (unsigned int)m_probeInterval;
s << "\r\nexpire_interval=" << (unsigned int)m_expireInterval;
Debug(this,DebugAll,"Initialized:%s",s.c_str());
}
}
bool JBPresence::receive(JBEvent* event)
{
if (!event)
return false;
switch (event->type()) {
case JBEvent::Presence:
case JBEvent::IqDiscoGet:
case JBEvent::IqDiscoSet:
case JBEvent::IqDiscoRes:
break;
default:
return false;
}
XDebug(this,DebugAll,"Received event.");
Lock lock(this);
event->releaseStream();
m_events.append(event);
return true;
}
bool JBPresence::process()
{
Lock lock(this);
ObjList* obj = m_events.skipNull();
if (!obj)
return false;
JBEvent* event = static_cast<JBEvent*>(obj->get());
m_events.remove(event,false);
JabberID local(event->to());
JabberID remote(event->from());
switch (event->type()) {
case JBEvent::IqDiscoGet:
case JBEvent::IqDiscoSet:
case JBEvent::IqDiscoRes:
if (checkDestination(event,local))
processDisco(event,local,remote);
event->deref();
return true;
default: ;
}
XDebug(this,DebugAll,"Process presence: '%s'.",event->stanzaType().c_str());
Presence p = presenceType(event->stanzaType());
switch (p) {
case JBPresence::Error:
if (checkDestination(event,local))
processError(event,local,remote);
break;
case JBPresence::Probe:
if (checkDestination(event,local))
processProbe(event,local,remote);
break;
case JBPresence::Subscribe:
case JBPresence::Subscribed:
case JBPresence::Unsubscribe:
case JBPresence::Unsubscribed:
if (checkDestination(event,local))
processSubscribe(event,p,local,remote);
break;
case JBPresence::Unavailable:
// Check destination only if we have one. No destination: broadcast
if (local && !checkDestination(event,local))
break;
processUnavailable(event,local,remote);
break;
default:
// Check destination only if we have one. No destination: broadcast
if (local && !checkDestination(event,local))
break;
if (!event->element())
break;
// Simple presence shouldn't have a type
if (event->element()->getAttribute("type")) {
Debug(this,DebugNote,
"Received unexpected presence type '%s' from '%s' to '%s'.",
event->element()->getAttribute("type"),remote.c_str(),local.c_str());
sendError(XMPPError::SFeatureNotImpl,local,remote,
event->releaseXML(),event->stream());
break;
}
processPresence(event,local,remote);
}
event->deref();
return true;
}
void JBPresence::runProcess()
{
while(1) {
if (!process())
Thread::msleep(SLEEP_PROCESSPRESENCE,true);
}
}
void JBPresence::processDisco(JBEvent* event, const JabberID& local,
const JabberID& remote)
{
XDebug(this,DebugAll,
"processDisco. Event: ((%p): %u). Local: '%s'. Remote: '%s'.",
event,event->type(),local.c_str(),remote.c_str());
if (event->type() == JBEvent::IqDiscoRes || !event->stream())
return;
bool error = true;
XMPPNamespace::Type type;
// Check error and query type
while (true) {
if (!event->child())
break;
if (event->child()->hasAttribute("xmlns",s_ns[XMPPNamespace::DiscoInfo]))
type = XMPPNamespace::DiscoInfo;
else if (event->child()->hasAttribute("xmlns",s_ns[XMPPNamespace::DiscoItems]))
type = XMPPNamespace::DiscoItems;
else
break;
error = false;
break;
}
if (error) {
DDebug(this,DebugNote,"Received unacceptable 'disco' query");
sendError(XMPPError::SFeatureNotImpl,local,remote,event->releaseXML(),
event->stream(),&(event->id()));
return;
}
JabberID from(event->to());
if (from.resource().null() && m_engine)
from.resource(m_engine->defaultResource());
// Create response: add identity and features
XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqResult,from,event->from(),event->id());
XMLElement* query = XMPPUtils::createElement(XMLElement::Query,type);
JIDIdentity* identity = new JIDIdentity(JIDIdentity::Client,JIDIdentity::ComponentGeneric);
query->addChild(identity->toXML());
identity->deref();
JIDFeatureList fl;
fl.add(XMPPNamespace::CapVoiceV1);
fl.addTo(query);
iq->addChild(query);
sendStanza(iq,event->stream());
}
void JBPresence::processError(JBEvent* event, const JabberID& local,
const JabberID& remote)
{
XDebug(this,DebugAll,
"processError. Event: ((%p): %u). Local: '%s'. Remote: '%s'.",
event,event->type(),local.c_str(),remote.c_str());
XMPPUser* user = getRemoteUser(local,remote,false,0,false,0);
if (!user) {
DDebug(this,DebugAll,
"Received 'error' for non user. Local: '%s'. Remote: '%s'.",
local.c_str(),remote.c_str());
return;
}
user->processError(event);
user->deref();
}
void JBPresence::processProbe(JBEvent* event, const JabberID& local,
const JabberID& remote)
{
XDebug(this,DebugAll,
"processProbe. Event: ((%p): %u). Local: '%s'. Remote: '%s'.",
event,event->type(),local.c_str(),remote.c_str());
bool newUser = false;
XMPPUser* user = getRemoteUser(local,remote,m_addOnProbe,0,m_addOnProbe,&newUser);
if (!user) {
DDebug(this,DebugAll,
"Received 'probe' for non user. Local: '%s'. Remote: '%s'.",
local.c_str(),remote.c_str());
if (m_autoProbe) {
XMLElement* stanza = createPresence(local.bare(),remote);
JIDResource* resource = new JIDResource(m_engine->defaultResource(),JIDResource::Available,
JIDResource::CapAudio);
resource->addTo(stanza);
resource->deref();
if (event->stream())
event->stream()->sendStanza(stanza);
else
delete stanza;
}
else if (!notifyProbe(event,local,remote))
sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(),
event->stream());
return;
}
if (newUser)
notifyNewUser(user);
String resName = local.resource();
if (resName.null())
user->processProbe(event);
else
user->processProbe(event,&resName);
user->deref();
}
void JBPresence::processSubscribe(JBEvent* event, Presence presence,
const JabberID& local, const JabberID& remote)
{
XDebug(this,DebugAll,
"Process '%s'. Event: ((%p): %u). Local: '%s'. Remote: '%s'.",
presenceText(presence),event,event->type(),local.c_str(),remote.c_str());
bool addLocal = (presence == Subscribe) ? m_addOnSubscribe : false;
bool newUser = false;
XMPPUser* user = getRemoteUser(local,remote,addLocal,0,addLocal,&newUser);
if (!user) {
DDebug(this,DebugAll,
"Received '%s' for non user. Local: '%s'. Remote: '%s'.",
presenceText(presence),local.c_str(),remote.c_str());
if (!notifySubscribe(event,local,remote,presence) &&
(presence != Subscribed && presence != Unsubscribed))
sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(),
event->stream());
return;
}
if (newUser)
notifyNewUser(user);
user->processSubscribe(event,presence);
user->deref();
}
void JBPresence::processUnavailable(JBEvent* event, const JabberID& local,
const JabberID& remote)
{
XDebug(this,DebugAll,
"processUnavailable. Event: ((%p): %u). Local: '%s'. Remote: '%s'.",
event,event->type(),local.c_str(),remote.c_str());
// Don't add if delUnavailable is true
bool addLocal = m_addOnPresence && !m_delUnavailable;
// Check if broadcast
if (local.null()) {
Lock lock(this);
ObjList* obj = m_rosters.skipNull();
for (; obj; obj = obj->skipNext()) {
XMPPUserRoster* roster = static_cast<XMPPUserRoster*>(obj->get());
bool newUser = false;
XMPPUser* user = getRemoteUser(roster->jid(),remote,addLocal,0,
addLocal,&newUser);
if (!user)
continue;
if (newUser)
notifyNewUser(user);
if (!user->processPresence(event,false,remote))
removeRemoteUser(local,remote);
user->deref();
}
return;
}
// Not broadcast: find user
bool newUser = false;
XMPPUser* user = getRemoteUser(local,remote,addLocal,0,addLocal,&newUser);
if (!user) {
DDebug(this,DebugAll,
"Received 'unavailable' for non user. Local: '%s'. Remote: '%s'.",
local.c_str(),remote.c_str());
if (!notifyPresence(event,local,remote,false))
sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(),
event->stream());
return;
}
if (newUser)
notifyNewUser(user);
if (!user->processPresence(event,false,remote))
removeRemoteUser(local,remote);
user->deref();
}
void JBPresence::processPresence(JBEvent* event, const JabberID& local,
const JabberID& remote)
{
XDebug(this,DebugAll,
"processPresence. Event: ((%p): %u). Local: '%s'. Remote: '%s'.",
event,event->type(),local.c_str(),remote.c_str());
// Check if broadcast
if (local.null()) {
Lock lock(this);
ObjList* obj = m_rosters.skipNull();
for (; obj; obj = obj->skipNext()) {
XMPPUserRoster* roster = static_cast<XMPPUserRoster*>(obj->get());
bool newUser = false;
XMPPUser* user = getRemoteUser(roster->jid(),remote,
m_addOnPresence,0,m_addOnPresence,&newUser);
if (!user)
continue;
if (newUser)
notifyNewUser(user);
user->processPresence(event,true,remote);
user->deref();
}
return;
}
// Not broadcast: find user
bool newUser = false;
XMPPUser* user = getRemoteUser(local,remote,m_addOnPresence,0,m_addOnPresence,
&newUser);
if (!user) {
DDebug(this,DebugAll,
"Received presence for non user. Local: '%s'. Remote: '%s'.",
local.c_str(),remote.c_str());
if (!notifyPresence(event,local,remote,true))
sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(),
event->stream());
return;
}
if (newUser)
notifyNewUser(user);
user->processPresence(event,true,remote);
user->deref();
}
bool JBPresence::notifyProbe(JBEvent* event, const JabberID& local,
const JabberID& remote)
{
XDebug(this,DebugAll,
"JBPresence::notifyProbe. Local: '%s'. Remote: '%s'.",
local.c_str(),remote.c_str());
return false;
}
bool JBPresence::notifySubscribe(JBEvent* event, const JabberID& local,
const JabberID& remote, Presence presence)
{
XDebug(this,DebugAll,
"JBPresence::notifySubscribe(%s). Local: '%s'. Remote: '%s'.",
presenceText(presence),local.c_str(),remote.c_str());
return false;
}
void JBPresence::notifySubscribe(XMPPUser* user, Presence presence)
{
XDebug(this,DebugAll,
"JBPresence::notifySubscribe(%s). User: (%p).",
presenceText(presence),user);
}
bool JBPresence::notifyPresence(JBEvent* event, const JabberID& local,
const JabberID& remote, bool available)
{
XDebug(this,DebugAll,
"JBPresence::notifyPresence(%s). Local: '%s'. Remote: '%s'.",
available?"available":"unavailable",local.c_str(),remote.c_str());
return false;
}
void JBPresence::notifyPresence(XMPPUser* user, JIDResource* resource)
{
XDebug(this,DebugAll,
"JBPresence::notifyPresence. User: (%p). Resource: (%p).",
user,resource);
}
void JBPresence::notifyNewUser(XMPPUser* user)
{
XDebug(this,DebugAll,"JBPresence::notifyNewUser. User: (%p).",user);
}
XMPPUserRoster* JBPresence::getRoster(const JabberID& jid, bool add, bool* added)
{
if (jid.node().null() || jid.domain().null())
return 0;
Lock lock(this);
ObjList* obj = m_rosters.skipNull();
for (; obj; obj = obj->skipNext()) {
XMPPUserRoster* ur = static_cast<XMPPUserRoster*>(obj->get());
if (jid.bare() == ur->jid().bare())
return ur->ref() ? ur : 0;
}
if (!add)
return 0;
if (added)
*added = true;
XMPPUserRoster* ur = new XMPPUserRoster(this,jid.node(),jid.domain());
return ur->ref() ? ur : 0;
}
XMPPUser* JBPresence::getRemoteUser(const JabberID& local, const JabberID& remote,
bool addLocal, bool* addedLocal, bool addRemote, bool* addedRemote)
{
XMPPUserRoster* ur = getRoster(local,addLocal,addedLocal);
if (!ur)
return 0;
XMPPUser* user = ur->getUser(remote,addRemote,addedRemote);
ur->deref();
return user;
}
void JBPresence::removeRemoteUser(const JabberID& local, const JabberID& remote)
{
Lock lock(this);
ObjList* obj = m_rosters.skipNull();
XMPPUserRoster* ur = 0;
for (; obj; obj = obj->skipNext()) {
ur = static_cast<XMPPUserRoster*>(obj->get());
if (local.bare() == ur->jid().bare()) {
if (ur->removeUser(remote))
ur = 0;
break;
}
ur = 0;
}
if (ur)
m_rosters.remove(ur,true);
}
bool JBPresence::validDomain(const String& domain)
{
if (m_engine->getAlternateDomain() && (domain == m_engine->getAlternateDomain()))
return true;
JBServerInfo* server = m_engine->getServer();
if (!server)
return false;
bool ok = (domain == server->identity() || domain == server->fullIdentity());
server->deref();
return ok;
}
bool JBPresence::getStream(JBComponentStream*& stream, bool& release)
{
release = false;
if (stream)
return true;
stream = m_engine->getStream(0,!m_engine->exiting());
if (stream) {
release = true;
return true;
}
if (!m_engine->exiting())
DDebug(m_engine,DebugGoOn,"No stream to send data.");
return false;
}
bool JBPresence::sendStanza(XMLElement* element, JBComponentStream* stream)
{
if (!element)
return true;
bool release = false;
if (!getStream(stream,release)) {
delete element;
return false;
}
JBComponentStream::Error res = stream->sendStanza(element);
if (release)
stream->deref();
if (res == JBComponentStream::ErrorContext ||
res == JBComponentStream::ErrorNoSocket)
return false;
return true;
}
bool JBPresence::sendError(XMPPError::Type type,
const String& from, const String& to,
XMLElement* element, JBComponentStream* stream, const String* id)
{
XMLElement* xml = 0;
XMLElement::Type t = element ? element->type() : XMLElement::Invalid;
if (t == XMLElement::Iq)
xml = XMPPUtils::createIq(XMPPUtils::IqError,from,to,id ? id->c_str() : element->getAttribute("id"));
else
xml = createPresence(from,to,Error);
xml->addChild(element);
xml->addChild(XMPPUtils::createError(XMPPError::TypeModify,type));
return sendStanza(xml,stream);
}
void JBPresence::checkTimeout(u_int64_t time)
{
lock();
ListIterator iter(m_rosters);
for (;;) {
XMPPUserRoster* ur = static_cast<XMPPUserRoster*>(iter.get());
// End of iteration?
if (!ur)
break;
// Get a reference
RefPointer<XMPPUserRoster> sref = ur;
if (!sref)
continue;
// Check timeout
unlock();
if (sref->timeout(time)) {
lock();
m_rosters.remove(ur,true);
unlock();
}
lock();
}
unlock();
}
void JBPresence::runCheckTimeout()
{
while (1) {
checkTimeout(Time::msecNow());
Thread::msleep(SLEEP_PROCESSTIMEOUT,true);
}
}
XMLElement* JBPresence::createPresence(const char* from,
const char* to, Presence type)
{
XMLElement* presence = new XMLElement(XMLElement::Presence);
presence->setAttributeValid("type",presenceText(type));
presence->setAttribute("from",from);
presence->setAttribute("to",to);
return presence;
}
bool JBPresence::decodeError(const XMLElement* element,
String& code, String& type, String& error)
{
if (!(element && element->type() == XMLElement::Presence))
return false;
code = "";
type = "";
error = "";
XMLElement* child = ((XMLElement*)element)->findFirstChild("error");
if (!child)
return true;
child->getAttribute("code",code);
child->getAttribute("type",type);
child = child->findFirstChild();
if (child)
error = child->name();
return true;
}
bool JBPresence::checkDestination(JBEvent* event, const JabberID& jid)
{
if (!event || validDomain(jid.domain()))
return true;
bool respond = true;
switch (event->type()) {
case JBEvent::IqDiscoGet:
case JBEvent::IqDiscoSet:
break;
case JBEvent::IqDiscoRes:
respond = false;
break;
default:
// Never respond to responses: type error or result
if (event->stanzaType() == "error" || event->stanzaType() == "result")
respond = false;
}
Debug(this,DebugNote,"Received element with invalid domain '%s'. Send error: %s",
jid.domain().c_str(),String::boolText(respond));
if (respond) {
const String* id = event->id().null() ? 0 : &(event->id());
sendError(XMPPError::SNoRemote,event->to(),event->from(),
event->releaseXML(),event->stream(),id);
}
return false;
}
void JBPresence::cleanup()
{
Lock lock(this);
DDebug(this,DebugAll,"Cleanup.");
ListIterator iter(m_rosters);
GenObject* obj;
for (; (obj = iter.get());) {
XMPPUserRoster* ur = static_cast<XMPPUserRoster*>(obj);
ur->cleanup();
m_rosters.remove(ur,true);
}
}
void JBPresence::addRoster(XMPPUserRoster* ur)
{
Lock lock(this);
m_rosters.append(ur);
}
void JBPresence::removeRoster(XMPPUserRoster* ur)
{
Lock lock(this);
m_rosters.remove(ur,false);
}
/* vi: set ts=8 sw=4 sts=4 noet: */