Implemented jabber server. Changed jingle channel to support the new jabber library. Replaced tinyxml with yate own XML library. Added openssl support for server. Work in progress in jabber client support.

git-svn-id: http://voip.null.ro/svn/yate@2882 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
marian 2009-11-02 16:05:39 +00:00
parent 0c6cad75d7
commit 76c182e440
42 changed files with 27068 additions and 21087 deletions

View File

@ -22,7 +22,7 @@ YLIB := libyate.so.@PACKAGE_VERSION@
SLIBS:= $(YLIB) libyate.so \
libyatesig.so.@PACKAGE_VERSION@ libyatesig.so \
libyatemgcp.so.@PACKAGE_VERSION@ libyatemgcp.so \
libyatejingle.so.@PACKAGE_VERSION@ libyatejingle.so
libyatejabber.so.@PACKAGE_VERSION@ libyatejabber.so
INCS := yateclass.h yatemime.h yatengine.h yatephone.h yatecbase.h
GENS := yateversn.h
LIBS :=

View File

@ -0,0 +1,54 @@
[general]
; stream_readbuffer: integer: The length of the stream read buffer
; Defaults to 8192 if missing or invalid. Minimum allowed value is 1024
;stream_readbuffer=8192
; stream_parsermaxbuffer: integer: The maximum length of an incomplete xml allowed
; in a stream parser's buffer
; Defaults to 8192 if missing or invalid. Minimum allowed value is 1024
;stream_parsermaxbuffer=8192
; stream_restartcount: integer: The maximum value for stream restart counter
; Defaults to 2 if missing or invalid
; Minimum allowed value is 1, maximum allowed value is 10
;stream_restartcount=2
; stream_restartupdateinterval: integer: The interval, in milliseconds, to increase a
; stream's current restart counter (not exceeding the stream_restartcount value)
; Defaults to 15000 if missing or invalid
; Minimum allowed value is 5000, maximum allowed value is 300000
;stream_restartupdateinterval=15000
; stream_starttimeout: integer: The interval, in milliseconds, allowed for a remote
; party to send the stream start tag
; Defaults to 5000 if missing or invalid
; Minimum allowed value is 1000, maximum allowed value is 10000
;stream_starttimeout=5000
; stream_setuptimeout: integer: Overall stream setup interval in milliseconds. The timer
; will stop when the stream is authenticated
; Defaults to 60000 if missing or invalid
; Minimum allowed value is 5000, maximum allowed value is 120000
;stream_setuptimeout=60000
; stream_connecttimeout: integer: The interval, in milliseconds, allowed for an
; outgoing stream to make a TCP connection to a remote host, including SRV request
; and resolving domain(s)
; Defaults to 5000 if missing or invalid
; Minimum allowed value is 1000, maximum allowed value is 10000
;stream_connecttimeout=5000
; entitycaps: boolean: Enable entity capabilities cache.
; If enabled entity capabilities will be requested and cached each time a presence
; stanza is received
; Defaults to enable
;entitycaps=enable
; printxml: boolean/string: Print sent/received XML data to output if debug
; level is at least 9
; Allowed values are boolean values or 'verbose' string
; If verbose is specified, XML elements' children, attributes or text will be
; shown on separate lines
; Defaults to no
;printxml=no

View File

@ -0,0 +1,106 @@
[general]
; domains: string: Comma separated list of domains serviced by the server
; This parameter is required
;domains=
; dialback_secret: string: Dialback key to be used when authenticating with foreign domains
; A random one will be generated if missing
;dialback_secret=
; restricted_resources: string: Comma separated list of restricted resource names
; Users won't be allowed to use these resources or any other resource name starting
; with it
;restricted_resources=
; s2s_tlsrequired: boolean: Stream encryption is required on all server to server streams
; Defaults to no
;s2s_tlsrequired=
; c2s_tlsrequired: boolean: Stream encryption is required on all client to server streams
; Defaults to no
;c2s_tlsrequired=
; stream_readbuffer: integer: The length of the stream read buffer
; Defaults to 8192 if missing or invalid. Minimum allowed value is 1024
;stream_readbuffer=8192
; stream_parsermaxbuffer: integer: The maximum length of an incomplete xml allowed
; in a stream parser's buffer
; Defaults to 8192 if missing or invalid. Minimum allowed value is 1024
;stream_parsermaxbuffer=8192
; stream_restartcount: integer: The maximum value for stream restart counter
; Defaults to 2 if missing or invalid
; Minimum allowed value is 1, maximum allowed value is 10
;stream_restartcount=2
; stream_restartupdateinterval: integer: The interval, in milliseconds, to increase a
; stream's current restart counter (not exceeding the stream_restartcount value)
; Defaults to 15000 if missing or invalid
; Minimum allowed value is 5000, maximum allowed value is 300000
;stream_restartupdateinterval=15000
; stream_starttimeout: integer: The interval, in milliseconds, allowed for a remote
; party to send the stream start tag
; Defaults to 5000 if missing or invalid
; Minimum allowed value is 1000, maximum allowed value is 10000
;stream_starttimeout=5000
; stream_setuptimeout: integer: Overall stream setup interval in milliseconds. The timer
; will stop when the stream is authenticated
; Defaults to 60000 if missing or invalid
; Minimum allowed value is 5000, maximum allowed value is 120000
;stream_setuptimeout=60000
; stream_connecttimeout: integer: The interval, in milliseconds, allowed for an
; outgoing stream to make a TCP connection to a remote host, including SRV request
; and resolving domain(s)
; Defaults to 5000 if missing or invalid
; Minimum allowed value is 1000, maximum allowed value is 10000
;stream_connecttimeout=5000
; entitycaps: boolean: Enable entity capabilities cache.
; If enabled entity capabilities will be requested and cached each time a presence
; stanza is received
; Defaults to enable
;entitycaps=enable
; printxml: boolean/string: Print sent/received XML data to output if debug
; level is at least 9
; Allowed values are boolean values or 'verbose' string
; If verbose is specified, XML elements' children, attributes or text will be
; shown on separate lines
; Defaults to no
;printxml=no
;[listener name]
; This section configures a connection listener
; This section may repeat to configure more listeners
; enable: boolean: Enable or disable this listener
; Defaults to false if missing or invalid
;enable=
; type: string: The type of the expected incoming connection
; This parameter is required
; Allowed values:
; c2s Client to server connection
; s2s Server to server connection
;type=
; address: string: IP address to listen
; Listen on all available interfaces if empty or invalid
;address=
; port: integer: The port to listen
; These are the default values for some known types (only if this parameter is missing)
; c2s 5222
; s2s 5269
;port=
; backlog: integer: Maximum length of the queue of pending connections
; Set it to 0 for system maximum
; Defaults to 5 if missing or invalid
;backlog=5

View File

@ -0,0 +1,96 @@
[general]
; account: string: Default database account
; This parameter can be overridden in section using the database
;account=
[register]
; This section configures in stream user management (add, change, delete)
; allow_management: boolean: Enable user add/remove
; Defaults to yes
;allow_management=yes
; allow_change: boolean: Enable existing authenticated user to change its password
; Defaults to yes
;allow_change=yes
; allow_unsecure: boolean: Enable user management on unsecure connections
; Defaults to no
;allow_unsecure=no
; url: string: URL to be sent to the user when requesting registration data
; if in stream registration is not enabled
;url=
; intructions: string: Instructions to be sent with the URL
;intructions=
[vcard]
; User vcard management
; get: string: Query executed on user vcard get request
;get=SELECT vcard FROM users WHERE username='${username}'
; set: string: Query executed on user vcard set request
; The query must update the user's vcard
; Care must be taken if the user vcard is kept in the same table with the user: avoid
; inserting items when the user is not found
;set=UPDATE users SET vcard VALUES ('${vcard}') WHERE username='${username}'
; clear_user: string: Query used to delete an user's vcard
; This query must be set if an user's vcard is not stored in the users table
;clear_user=
[private_data]
; User private data management
; User private data chunk key is given by xml tag and namespace
; Data should be kept in a separate table with a one-to-many relationship from the users
; table
; get: string: Query executed on user private data get request
;get=SELECT xml FROM privatedata WHERE username='${username}' AND tag='${tag}' AND xmlns='${xmlns}'
; set: string: Query executed on user private data set request
; The query must update existing data or insert it if not found
;set=SELECT * FROM privatedata_set('${username}', '${tag}', '${xmlns}', '${xml}')
; clear_user: string: Query used to delete all private data belonging to an user
;clear_user=DELETE FROM privatedata WHERE username='${username}'
[offline_chat]
; Offline messages management
; maxcount: integer: Maximum number of chat messages saved for an user
; This parameter can be used to limit the number of chat messages stored on
; behalf of an user
; This parameter is applied on reload
; Defaults to 0 (no limit) if missing or invalid
;maxcount=
; expires: integer: The time interval (in minutes) a saved chat message
; will be kept in the database
; This parameter can be used to clear old messages
; This parameter is applied on reload
; Defaults to 0 (no limit) if missing or invalid
;expires=
; expire_query: string: Query used to expire offline messages
;expire_query=DELETE FROM offlinechat WHERE time<'${time}'
; get: string: Query used to retrieve an user's offline messages
;get=SELECT * FROM offlinechat WHERE username='${username}' ORDER BY time
; set: string: Query used to add an offline message addressed to an user
; The query should check if the user exists
; It should return a non 0 integer value indicating success or 0 on failure
; If 'maxcount' is greater then 0 the query should check for maximum
; allowed number of chat messages
;add=SELECT * FROM offlinechat_add('${username}', '${xml}', ${time}, ${maxcount})
; clear_user: string: Query used to delete all offline messages belonging to an user
;clear_user=DELETE FROM offlinechat WHERE username='${username}'

View File

@ -0,0 +1,24 @@
; This file keeps the configuration of the openssl module
; Each section, except for 'general' configures a server context
[general]
[server_context]
; This section configures a SSL server context
; enable: boolean: Enable or disable the context
; Defaults to yes
;enable=yes
; domains: string: Comma separated list of domains the context will be used for
; A subdomain wildcard can be specified for a given domain, e.g.
; *.null.ro will match any null.ro subdomains (including the 'null.ro' domain)
;domains=
; certificate: string: The name of the file containing the certificate for the context
; This parameter is required
;certificate=
; key: string: Optional certificate key file name
;key=

View File

@ -1104,7 +1104,7 @@ AC_CONFIG_FILES([packing/rpm/yate.spec
libs/ysdp/Makefile
libs/yiax/Makefile
libs/yxml/Makefile
libs/yjingle/Makefile
libs/yjabber/Makefile
libs/ymgcp/Makefile
libs/ysig/Makefile
libs/ypbx/Makefile

View File

@ -1,5 +1,5 @@
# Makefile
# This file holds the make rules for the libyatejingle
# This file holds the make rules for the libyatejabber
DEBUG :=
@ -9,12 +9,12 @@ DEFS :=
INCLUDES := -I@top_srcdir@ -I../.. -I@srcdir@/../yxml -I@srcdir@
CFLAGS := @CFLAGS@ @MODULE_CPPFLAGS@ @INLINE_FLAGS@
LDFLAGS:= @LDFLAGS@ -L../.. -lyate
INCFILES := @top_srcdir@/yateclass.h @srcdir@/../yxml/tinystr.h @srcdir@/../yxml/tinyxml.h @srcdir@/xmlparser.h @srcdir@/xmpputils.h @srcdir@/yatejabber.h @srcdir@/yatejingle.h
INCFILES := @top_srcdir@/yateclass.h @srcdir@/../yxml/yatexml.h @srcdir@/xmpputils.h @srcdir@/yatejabber.h @srcdir@/yatejingle.h
PROGS=
LIBS = libyatejingle.a
OBJS = xmlparser.o xmpputils.o jbstream.o jbengine.o session.o jgengine.o
LIBD_DEV:= libyatejingle.so
LIBS = libyatejabber.a
OBJS = xmpputils.o jbstream.o jbengine.o session.o jgengine.o
LIBD_DEV:= libyatejabber.so
LIBD_VER:= $(LIBD_DEV).@PACKAGE_VERSION@
LIBD:= ../../$(LIBD_VER) ../../$(LIBD_DEV)
YXML:= ../yxml/libyatexml.a

2271
libs/yjabber/jbengine.cpp Normal file

File diff suppressed because it is too large Load Diff

2509
libs/yjabber/jbstream.cpp Normal file

File diff suppressed because it is too large Load Diff

323
libs/yjabber/jgengine.cpp Normal file
View File

@ -0,0 +1,323 @@
/**
* jgengine.cpp
* Yet Another Jingle 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 <yatejingle.h>
#include <stdlib.h>
using namespace TelEngine;
const TokenDict JGEvent::s_typeName[] = {
{"Jingle", Jingle},
{"ResultOk", ResultOk},
{"ResultError", ResultError},
{"ResultTimeout", ResultTimeout},
{"Terminated", Terminated},
{"Destroy", Destroy},
{0,0}
};
/*
* JGEngine
*/
// Constructor
JGEngine::JGEngine(const char* name)
: Mutex(true,"JGEngine"),
m_sessionId(1), m_stanzaTimeout(20000), m_pingInterval(300000)
{
debugName(name);
}
JGEngine::~JGEngine()
{
}
// Create private stream(s) to get events from sessions
void JGEngine::initialize(const NamedList& params)
{
int lvl = params.getIntValue("debug_level",-1);
if (lvl != -1)
debugLevel(lvl);
int timeout = params.getIntValue("stanza_timeout",(int)m_stanzaTimeout);
m_stanzaTimeout = timeout > 10000 ? timeout : 10000;
int ping = params.getIntValue("ping_interval",(int)m_pingInterval);
if (ping == 0)
m_pingInterval = 0;
else if (ping < 60000)
m_pingInterval = 60000;
else
m_pingInterval = ping;
// Make sure we don't ping before a ping times out
if (m_pingInterval && m_stanzaTimeout && m_pingInterval <= m_stanzaTimeout)
m_pingInterval = m_stanzaTimeout + 100;
if (debugAt(DebugInfo)) {
String s;
s << " stanza_timeout=" << (unsigned int)m_stanzaTimeout;
s << " ping_interval=" << (unsigned int)m_pingInterval;
Debug(this,DebugInfo,"Jabber Jingle service initialized:%s [%p]",
s.c_str(),this);
}
}
// Make an outgoing call
JGSession* JGEngine::call(JGSession::Version ver, const JabberID& caller,
const JabberID& called, const ObjList& contents, XmlElement* extra,
const char* msg, const char* subject)
{
DDebug(this,DebugAll,"call() from '%s' to '%s'",caller.c_str(),called.c_str());
JGSession* session = 0;
switch (ver) {
case JGSession::Version1:
session = new JGSession1(this,caller,called);
break;
case JGSession::Version0:
session = new JGSession0(this,caller,called);
break;
case JGSession::VersionUnknown:
Debug(this,DebugNote,"Unhandled session version %d in call()",ver);
return 0;
}
if (session) {
if (!TelEngine::null(msg))
sendMessage(session,msg);
if (session->initiate(contents,extra,subject)) {
Lock lock(this);
m_sessions.append(session);
return (session && session->ref()) ? session : 0;
}
}
TelEngine::destruct(session);
Debug(this,DebugNote,"Outgoing call from '%s' to '%s' failed to initiate",
caller.c_str(),called.c_str());
return 0;
}
// Send a session's stanza.
bool JGEngine::sendStanza(JGSession* session, XmlElement*& stanza)
{
Debug(this,DebugStub,"JGEngine::sendStanza() not implemented!");
TelEngine::destruct(stanza);
return false;
}
// Send a chat message on behalf of a session
bool JGEngine::sendMessage(JGSession* session, const char* body)
{
XmlElement* x = XMPPUtils::createMessage(XMPPUtils::Chat,0,0,0,body);
return sendStanza(session,x);
}
// Get events from sessions
JGEvent* JGEngine::getEvent(u_int64_t time)
{
JGEvent* event = 0;
lock();
ListIterator iter(m_sessions);
for (;;) {
JGSession* session = static_cast<JGSession*>(iter.get());
// End of iteration?
if (!session)
break;
RefPointer<JGSession> s = session;
// Dead pointer?
if (!s)
continue;
unlock();
if (0 != (event = s->getEvent(time))) {
if (event->type() == JGEvent::Destroy) {
DDebug(this,DebugAll,"Deleting internal event (%p,Destroy)",event);
delete event;
}
else
return event;
}
lock();
}
unlock();
return 0;
}
// Ask this engine to accept an incoming xml 'iq' element
bool JGEngine::acceptIq(XMPPUtils::IqType type, const JabberID& from, const JabberID& to,
const String& id, XmlElement* xml, XMPPError::Type& error, String& text)
{
error = XMPPError::NoError;
if (!xml)
return false;
if (type == XMPPUtils::IqResult || type == XMPPUtils::IqError) {
for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) {
JGSession* session = static_cast<JGSession*>(o->get());
if (session->acceptIq(type,from,to,id,xml))
return true;
}
return false;
}
if (type != XMPPUtils::IqGet && type != XMPPUtils::IqSet)
return false;
// Handle set/get iq
XmlElement* child = xml->findFirstChild();
if (!child)
return false;
String sid;
JGSession::Version ver = JGSession::VersionUnknown;
int ns = XMPPUtils::xmlns(*child);
bool fileTransfer = false;
// Jingle or file transfer stanzas (jingle stanzas can only have type='set')
// Set version and session id
if (ns == XMPPNamespace::Jingle) {
if (type == XMPPUtils::IqSet) {
ver = JGSession::Version1;
sid = child->getAttribute("sid");
}
}
else if (ns == XMPPNamespace::JingleSession) {
if (type == XMPPUtils::IqSet) {
ver = JGSession::Version0;
sid = child->getAttribute("id");
}
}
else if (ns == XMPPNamespace::ByteStreams && XMPPUtils::isUnprefTag(*child,XmlTag::Query)) {
fileTransfer = true;
sid = child->getAttribute("sid");
}
else
return false;
if (!sid) {
if (!fileTransfer) {
error = XMPPError::BadRequest;
if (type == XMPPUtils::IqSet)
text = "Missing session id attribute";
}
return false;
}
Lock lock(this);
DDebug(this,DebugAll,"Accepting xml child=%s sid=%s version=%d filetransfer=%u",
child->tag(),sid.c_str(),ver,fileTransfer);
// Check for an existing session destination
for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) {
JGSession* session = static_cast<JGSession*>(o->get());
if (session->acceptIq(type,from,to,sid,xml))
return true;
}
// Check if this an incoming session request
JGSession* session = 0;
if (ver != JGSession::VersionUnknown) {
JGSession::Action action = JGSession::lookupAction(child->attribute("type"),ver);
if (action == JGSession::ActInitiate) {
switch (ver) {
case JGSession::Version1:
session = new JGSession1(this,to,from,xml,sid);
break;
case JGSession::Version0:
session = new JGSession0(this,to,from,xml,sid);
break;
default:
error = XMPPError::ServiceUnavailable;
Debug(this,DebugStub,"JGEngine::accept(): unhandled session version %d",ver);
}
}
else {
error = XMPPError::Request;
text = "Unknown session";
}
if (session)
m_sessions.append(session);
return error == XMPPError::NoError;
}
return false;
}
// Default event processor
void JGEngine::defProcessEvent(JGEvent* event)
{
if (!event)
return;
DDebug(this,DebugAll,"JGEngine::defprocessEvent. Deleting event (%p,%u)",
event,event->type());
delete event;
}
// Process generated events
void JGEngine::processEvent(JGEvent* event)
{
Debug(this,DebugStub,"JGEngine::processEvent. Calling default processor");
defProcessEvent(event);
}
// Create a local session id
void JGEngine::createSessionId(String& id)
{
Lock lock(this);
id = "JG";
id << (unsigned int)m_sessionId << "_" << (int)random();
m_sessionId++;
}
/**
* JGEvent
*/
JGEvent::~JGEvent()
{
if (m_session) {
if (!m_confirmed)
confirmElement(XMPPError::UndefinedCondition,"Unhandled");
m_session->eventTerminated(this);
TelEngine::destruct(m_session);
}
TelEngine::destruct(releaseXml());
XDebug(DebugAll,"JGEvent::~JGEvent [%p]",this);
}
void JGEvent::init(JGSession* session)
{
XDebug(DebugAll,"JGEvent::JGEvent [%p]",this);
if (session && session->ref())
m_session = session;
if (m_element) {
m_id = m_element->getAttribute("id");
if (m_session)
switch (m_session->version()) {
case JGSession::Version1:
m_jingle = XMPPUtils::findFirstChild(*m_element,XmlTag::Jingle);
break;
case JGSession::Version0:
m_jingle = XMPPUtils::findFirstChild(*m_element,XmlTag::Session);
break;
case JGSession::VersionUnknown:
;
}
}
}
// Set the jingle action as enumeration. Set confirmation flag if
// the element don't require it
void JGEvent::setAction(JGSession::Action act)
{
m_action = act;
m_confirmed = !(m_element && act != JGSession::ActCount);
}
/* vi: set ts=8 sw=4 sts=4 noet: */

File diff suppressed because it is too large Load Diff

1564
libs/yjabber/xmpputils.cpp Normal file

File diff suppressed because it is too large Load Diff

2129
libs/yjabber/xmpputils.h Normal file

File diff suppressed because it is too large Load Diff

2476
libs/yjabber/yatejabber.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
Makefile
YateLocal*
core*
*.o
*.a
*.orig
*~
.*.swp

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,377 +0,0 @@
/**
* jgengine.cpp
* Yet Another Jingle 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 <yatejingle.h>
using namespace TelEngine;
static XMPPError s_err;
TokenDict JGEvent::s_typeName[] = {
{"Jingle", Jingle},
{"ResultOk", ResultOk},
{"ResultError", ResultError},
{"ResultWriteFail", ResultWriteFail},
{"ResultTimeout", ResultTimeout},
{"Terminated", Terminated},
{"Destroy", Destroy},
{0,0}
};
/**
* JGEngine
*/
JGEngine::JGEngine(JBEngine* engine, const NamedList* params, int prio)
: JBService(engine,"jgengine",params,prio),
m_sessionIdMutex(true,"JGEngine::sessionId"),
m_sessionId(1), m_stanzaTimeout(20000), m_pingInterval(300000)
{
JBThreadList::setOwner(this);
}
JGEngine::~JGEngine()
{
cancelThreads();
}
// Create private stream(s) to get events from sessions
void JGEngine::initialize(const NamedList& params)
{
int lvl = params.getIntValue("debug_level",-1);
if (lvl != -1)
debugLevel(lvl);
int timeout = params.getIntValue("stanza_timeout",(int)m_stanzaTimeout);
m_stanzaTimeout = timeout > 10000 ? timeout : 10000;
int ping = params.getIntValue("ping_interval",(int)m_pingInterval);
if (ping == 0)
m_pingInterval = 0;
else if (ping < 60000)
m_pingInterval = 60000;
else
m_pingInterval = ping;
// Make sure we don't ping before a ping times out
if (m_pingInterval && m_stanzaTimeout && m_pingInterval <= m_stanzaTimeout)
m_pingInterval = m_stanzaTimeout + 100;
if (debugAt(DebugInfo)) {
String s;
s << " stanza_timeout=" << (unsigned int)m_stanzaTimeout;
s << " ping_interval=" << (unsigned int)m_pingInterval;
Debug(this,DebugInfo,"Jabber Jingle service initialized:%s [%p]",
s.c_str(),this);
}
if (!m_initialized) {
m_initialized = true;
int c = params.getIntValue("private_process_threads",1);
for (int i = 0; i < c; i++)
JBThread::start(JBThread::Jingle,this,this);
}
}
// Make an outgoing call
JGSession* JGEngine::call(JGSession::Version ver, const String& localJID, const String& remoteJID,
const ObjList& contents, XMLElement* extra, const char* message,
const char* subject)
{
DDebug(this,DebugAll,"New outgoing call from '%s' to '%s'",
localJID.c_str(),remoteJID.c_str());
// Get a stream from the engine
JBStream* stream = 0;
if (engine()->protocol() == JBEngine::Component)
stream = engine()->getStream();
else {
// Client: the stream must be already created
JabberID jid(localJID);
stream = engine()->getStream(&jid,false);
}
// Create outgoing session
bool hasStream = (0 != stream);
if (hasStream) {
JGSession* session = 0;
switch (ver) {
case JGSession::Version1:
session = new JGSession1(this,stream,localJID,remoteJID,message);
break;
case JGSession::Version0:
session = new JGSession0(this,stream,localJID,remoteJID,message);
break;
case JGSession::VersionUnknown:
;
}
TelEngine::destruct(stream);
if (session && session->initiate(contents,extra,subject)) {
Lock lock(this);
m_sessions.append(session);
return (session && session->ref()) ? session : 0;
}
TelEngine::destruct(session);
}
else
TelEngine::destruct(extra);
Debug(this,DebugNote,"Outgoing call from '%s' to '%s' failed: %s",
localJID.c_str(),remoteJID.c_str(),
!hasStream? "can't create stream" : "failed to send data");
return 0;
}
// Get events from sessions
JGEvent* JGEngine::getEvent(u_int64_t time)
{
JGEvent* event = 0;
lock();
ListIterator iter(m_sessions);
for (;;) {
JGSession* session = static_cast<JGSession*>(iter.get());
// End of iteration?
if (!session)
break;
RefPointer<JGSession> s = session;
// Dead pointer?
if (!s)
continue;
unlock();
if (0 != (event = s->getEvent(time))) {
if (event->type() == JGEvent::Destroy) {
DDebug(this,DebugAll,"Deleting internal event (%p,Destroy)",event);
delete event;
}
else
return event;
}
lock();
}
unlock();
return 0;
}
// Default event processor
void JGEngine::defProcessEvent(JGEvent* event)
{
if (!event)
return;
DDebug(this,DebugAll,"JGEngine::defprocessEvent. Deleting event (%p,%u)",
event,event->type());
delete event;
}
// Accept an event from the Jabber engine
bool JGEngine::accept(JBEvent* event, bool& processed, bool& insert)
{
if (!(event && event->stream()))
return false;
XMLElement* child = event->child();
XMPPError::Type error = XMPPError::NoError;
const char* errorText = 0;
bool respond = true;
JGSession::Version ver = JGSession::VersionUnknown;
String sid;
Lock lock(this);
switch (event->type()) {
case JBEvent::IqJingleGet:
// Jingle stanzas should never have type='get'
Debug(this,DebugNote,"Received iq jingle stanza with type='get'");
error = XMPPError::SServiceUnavailable;
break;
case JBEvent::IqJingleSet:
if (!(event->element() && child)) {
Debug(this,DebugNote,"Received jingle event %s with no element or child",event->name());
return false;
}
// Set version and session id
if (XMPPUtils::hasXmlns(*child,XMPPNamespace::Jingle)) {
ver = JGSession::Version1;
sid = child->getAttribute("sid");
}
else if (XMPPUtils::hasXmlns(*child,XMPPNamespace::JingleSession)) {
ver = JGSession::Version0;
sid = child->getAttribute("id");
}
DDebug(this,DebugAll,"Accepting event=%s child=%s sid=%s version=%d",
event->name(),child->name(),sid.c_str(),ver);
if (sid.null()) {
error = XMPPError::SBadRequest;
errorText = "Missing or empty session id";
break;
}
// Check for a destination
for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) {
JGSession* session = static_cast<JGSession*>(o->get());
if (session->acceptEvent(event,sid)) {
processed = true;
return true;
}
}
// Check if this an incoming session request
if (event->type() == JBEvent::IqJingleSet) {
JGSession::Action action = JGSession::lookupAction(child->getAttribute("type"),ver);
if (action == JGSession::ActInitiate) {
DDebug(this,DebugAll,"New incoming call from=%s to=%s sid=%s version=%d",
event->from().c_str(),event->to().c_str(),sid.c_str(),ver);
if (!event->ref()) {
error = XMPPError::SInternal;
break;
}
switch (ver) {
case JGSession::Version1:
m_sessions.append(new JGSession1(this,event,sid));
break;
case JGSession::Version0:
m_sessions.append(new JGSession0(this,event,sid));
break;
default:
Debug(this,DebugStub,
"JGEngine::accept(): unhandled session version %d",ver);
}
processed = true;
return true;
}
}
error = XMPPError::SRequest;
errorText = "Unknown session";
break;
case JBEvent::IqJingleRes:
case JBEvent::IqJingleErr:
case JBEvent::IqResult:
case JBEvent::IqError:
case JBEvent::WriteFail:
respond = false;
for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) {
JGSession* session = static_cast<JGSession*>(o->get());
if (session->acceptEvent(event)) {
processed = true;
return true;
}
}
break;
case JBEvent::Iq:
// Check file transfer
if (child && child->type() == XMLElement::Query &&
XMPPUtils::hasXmlns(*child,XMPPNamespace::ByteStreams)) {
sid = child->getAttribute("sid");
// Check for a destination
for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) {
JGSession* session = static_cast<JGSession*>(o->get());
if (session->acceptEvent(event,sid)) {
processed = true;
return true;
}
}
}
break;
case JBEvent::Terminated:
case JBEvent::Destroy:
for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) {
JGSession* session = static_cast<JGSession*>(o->get());
if (event->stream() == session->stream())
session->enqueue(new JBEvent((JBEvent::Type)event->type(),
event->stream(),0));
}
break;
default:
return false;
}
if (error == XMPPError::NoError)
return false;
Debug(this,DebugNote,"Accepted event=%s child=%s. Invalid: error=%s text=%s",
event->name(),child?child->name():"",s_err[error],errorText);
// Send error
if (respond) {
XMLElement* iq = XMPPUtils::createError(event->releaseXML(),XMPPError::TypeModify,
error,errorText);
event->stream()->sendStanza(iq);
}
processed = true;
return true;
}
// Process generated events
void JGEngine::processEvent(JGEvent* event)
{
Debug(this,DebugStub,"JGEngine::processEvent. Calling default processor");
defProcessEvent(event);
}
// Create a local session id
void JGEngine::createSessionId(String& id)
{
Lock lock(m_sessionIdMutex);
id = "JG";
id << (unsigned int)m_sessionId << "_" << (int)random();
m_sessionId++;
}
/**
* JGEvent
*/
JGEvent::~JGEvent()
{
if (m_session) {
if (!m_confirmed)
confirmElement(XMPPError::UndefinedCondition,"Unhandled");
m_session->eventTerminated(this);
TelEngine::destruct(m_session);
}
TelEngine::destruct(releaseXML());
XDebug(DebugAll,"JGEvent::~JGEvent [%p]",this);
}
void JGEvent::init(JGSession* session)
{
XDebug(DebugAll,"JGEvent::JGEvent [%p]",this);
if (session && session->ref())
m_session = session;
if (m_element) {
m_id = m_element->getAttribute("id");
if (m_session)
switch (m_session->version()) {
case JGSession::Version1:
m_jingle = m_element->findFirstChild(XMLElement::Jingle);
break;
case JGSession::Version0:
m_jingle = m_element->findFirstChild(XMLElement::Session);
break;
case JGSession::VersionUnknown:
;
}
}
}
// Set the jingle action as enumeration. Set confirmation flag if
// the element don't require it
void JGEvent::setAction(JGSession::Action act)
{
m_action = act;
m_confirmed = !(m_element && act != JGSession::ActCount);
}
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -1,487 +0,0 @@
/**
* xmlparser.cpp
* Yet Another XMPP 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 <xmlparser.h>
#include <string.h>
using namespace TelEngine;
/**
* XMLElement
*/
TokenDict XMLElement::s_names[] = {
{"stream:stream", StreamStart},
{"/stream:stream", StreamEnd},
{"stream:error", StreamError},
{"stream:features", StreamFeatures},
{"register", Register},
{"starttls", Starttls},
{"handshake", Handshake},
{"auth", Auth},
{"challenge", Challenge},
{"abort", Abort},
{"aborted", Aborted},
{"response", Response},
{"proceed", Proceed},
{"success", Success},
{"failure", Failure},
{"mechanisms", Mechanisms},
{"mechanism", Mechanism},
{"session", Session},
{"iq", Iq},
{"message", Message},
{"presence", Presence},
{"error", Error},
{"query", Query},
{"vCard", VCard},
{"jingle", Jingle},
{"description", Description},
{"payload-type", PayloadType},
{"transport", Transport},
{"candidate", Candidate},
{"body", Body},
{"subject", Subject},
{"feature", Feature},
{"bind", Bind},
{"resource", Resource},
{"transfer", Transfer},
{"hold", Hold},
{"active", Active},
{"ringing", Ringing},
{"mute", Mute},
{"registered", Registered},
{"remove", Remove},
{"jid", Jid},
{"username", Username},
{"password", Password},
{"digest", Digest},
{"required", Required},
{"dtmf", Dtmf},
{"dtmf-method", DtmfMethod},
{"command", Command},
{"text", Text},
{"item", Item},
{"group", Group},
{"reason", Reason},
{"content", Content},
{"parameter", Parameter},
{"crypto", Crypto},
{"crypto-required", CryptoRequired},
{"trying", Trying},
{"received", Received},
{"file", File},
{"offer", Offer},
{"request", Request},
{"streamhost", StreamHost},
{"streamhost-used", StreamHostUsed},
{0,0}
};
XMLElement::XMLElement()
: m_type(StreamEnd), m_owner(true), m_element(0)
{
m_element = new TiXmlElement(typeName(m_type));
// XDebug(DebugAll,"XMLElement::XMLElement(%s) [%p]",name(),this);
}
XMLElement::XMLElement(const XMLElement& src)
: GenObject(),
m_type(Invalid), m_owner(true), m_element(0)
{
TiXmlElement* e = src.get();
if (!e)
return;
m_element = new TiXmlElement(*e);
setType();
// XDebug(DebugAll,"XMLElement::XMLElement(%s) [%p]",name(),this);
}
// Partially build this element from another one.
// Copy name and 'to', 'from', 'type', 'id' attributes
XMLElement::XMLElement(const XMLElement& src, bool response, bool result)
: m_type(src.type()), m_owner(true), m_element(0)
{
m_element = new TiXmlElement(src.name());
if (response) {
setAttributeValid("from",src.getAttribute("to"));
setAttributeValid("to",src.getAttribute("from"));
setAttribute("type",result?"result":"error");
}
else {
setAttributeValid("from",src.getAttribute("from"));
setAttributeValid("to",src.getAttribute("to"));
setAttributeValid("type",src.getAttribute("type"));
}
setAttributeValid("id",src.getAttribute("id"));
// XDebug(DebugAll,"XMLElement::XMLElement(%s) [%p]",name(),this);
}
XMLElement::XMLElement(const char* name, NamedList* attributes,
const char* text)
: m_type(Unknown), m_owner(true), m_element(0)
{
m_element = new TiXmlElement(name);
// Set text
if (text)
m_element->LinkEndChild(new TiXmlText(text));
// Add attributes
if (attributes) {
u_int32_t attr_count = attributes->length();
for (u_int32_t i = 0; i < attr_count; i++) {
NamedString* ns = attributes->getParam(i);
if (!ns)
continue;
m_element->SetAttribute(ns->name().safe(),ns->safe());
}
}
setType();
// XDebug(DebugAll,"XMLElement::XMLElement(%s) [%p]",this->name(),this);
}
XMLElement::XMLElement(Type type, NamedList* attributes,
const char* text)
: m_type(type), m_owner(true), m_element(0)
{
m_element = new TiXmlElement(typeName(m_type));
// Set text
if (text)
m_element->LinkEndChild(new TiXmlText(text));
// Add attributes
if (attributes) {
u_int32_t attr_count = attributes->length();
for (u_int32_t i = 0; i < attr_count; i++) {
NamedString* ns = attributes->getParam(i);
if (!ns)
continue;
m_element->SetAttribute(ns->name().safe(),ns->safe());
}
}
// XDebug(DebugAll,"XMLElement::XMLElement(%s) [%p]",name(),this);
}
XMLElement::XMLElement(TiXmlElement* element, bool owner)
: m_type(Unknown), m_owner(owner), m_element(element)
{
setType();
// XDebug(DebugAll,"XMLElement::XMLElement(%s) owner=%u [%p]",
// name(),m_owner,this);
}
// Build this XML element from a list containing name, attributes and text.
XMLElement::XMLElement(NamedList& src, const char* prefix)
: m_type(Unknown), m_owner(true), m_element(0)
{
m_element = new TiXmlElement(src.getValue(prefix));
DDebug(DebugAll,"XMLElement(%s) src=%s prefix=%s [%p]",
m_name.c_str(),src.c_str(),prefix,this);
String pref(String(prefix) + ".");
// Set text
const char* text = src.getValue(pref);
if (text)
m_element->LinkEndChild(new TiXmlText(text));
// Add attributes
unsigned int n = src.count();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = src.getParam(i);
if (ns && ns->name().startsWith(pref))
setAttribute(ns->name().substr(pref.length()),*ns);
}
setType();
}
XMLElement::~XMLElement()
{
if (m_owner && m_element)
delete m_element;
// XDebug(DebugAll,"XMLElement::~XMLElement(%s) owner=%u [%p]",
// m_name.c_str(),m_owner,this);
}
void XMLElement::toString(String& dest, bool unclose) const
{
dest.clear();
if (valid()) {
TIXML_OSTREAM xmlStr;
m_element->StreamOut(&xmlStr,unclose);
dest.assign(xmlStr.c_str(),xmlStr.length());
}
}
// Put this element's name, text and attributes to a list of parameters
void XMLElement::toList(NamedList& dest, const char* prefix)
{
XDebug(DebugAll,"XMLElement(%s) to list=%s prefix=%s [%p]",
m_name.c_str(),dest.c_str(),prefix,this);
dest.addParam(prefix,name());
String pref(String(prefix) + ".");
const char* tmp = getText();
if (tmp)
dest.addParam(pref,tmp);
for (const TiXmlAttribute* a = firstAttribute(); a; a = a->Next())
dest.addParam(pref + a->Name(),a->Value());
}
void XMLElement::setAttribute(const char* name, const char* value)
{
if (!(valid() && name && value))
return;
m_element->SetAttribute(name,value);
}
const char* XMLElement::getAttribute(const char* name) const
{
if (valid() && name)
return m_element->Attribute(name);
return 0;
}
// Fill a list with element's attributes
void XMLElement::getAttributes(NamedList& dest) const
{
if (!valid())
return;
const TiXmlAttribute* attr = m_element->FirstAttribute();
for (; attr; attr = attr->Next())
dest.addParam(attr->Name(),attr->Value());
}
bool XMLElement::hasAttribute(const char* name, const char* value) const
{
String tmp;
if (getAttribute(name,tmp))
return tmp == value;
return false;
}
const char* XMLElement::getText() const
{
if (valid())
return m_element->GetText();
return 0;
}
void XMLElement::addChild(XMLElement* element)
{
if (valid() && element) {
TiXmlElement* tiElement = element->releaseOwnership();
if (tiElement)
m_element->LinkEndChild(tiElement);
}
TelEngine::destruct(element);
}
// Find the first child element of this one.
// Remove it from the children list.
// This element must own its TiXmlElement pointer.
XMLElement* XMLElement::removeChild(const char* name)
{
if (!valid() && m_owner)
return 0;
TiXmlElement* element;
if (name && *name)
element = ((TiXmlNode*)m_element)->FirstChildElement(name);
else
element = ((TiXmlNode*)m_element)->FirstChildElement();
if (!element)
return 0;
m_element->RemoveChild(element,false);
return new XMLElement(element,true);
}
XMLElement* XMLElement::findFirstChild(const char* name)
{
if (!valid())
return 0;
TiXmlElement* element;
if (name && *name)
element = ((TiXmlNode*)m_element)->FirstChildElement(name);
else
element = ((TiXmlNode*)m_element)->FirstChildElement();
if (element)
return new XMLElement(element,false);
return 0;
}
XMLElement* XMLElement::findNextChild(XMLElement* element, const char* name)
{
if (!valid()) {
TelEngine::destruct(element);
return 0;
}
TiXmlElement* tiElement = element ? element->get() : 0;
XMLElement* result = 0;
if (tiElement) {
if (name && *name)
tiElement = tiElement->NextSiblingElement(name);
else
tiElement = tiElement->NextSiblingElement();
if (tiElement)
result = new XMLElement(tiElement,false);
}
else
result = findFirstChild(name);
TelEngine::destruct(element);
return result;
}
// Get an xml element from a list's parameter
XMLElement* XMLElement::getXml(NamedList& list, bool stole,
const char* name, const char* value)
{
NamedString* ns = list.getParam(name);
if (!ns)
return 0;
NamedPointer* np = static_cast<NamedPointer*>(ns->getObject("NamedPointer"));
if (!(np && np->userObject("XMLElement")) || (value && *np != value))
return 0;
if (stole)
return static_cast<XMLElement*>(np->takeData());
return static_cast<XMLElement*>(np->userData());
}
TiXmlElement* XMLElement::releaseOwnership()
{
if (!(m_owner && m_element))
return 0;
TiXmlElement* tiElement = m_element;
m_element = 0;
m_owner = false;
return tiElement;
}
/**
* XMLParser
*/
const char* skipBlanks(const char* p)
{
for (; *p == ' ' || *p == '\r' || *p == '\n' || *p == '\t'; p++);
return p;
}
u_int32_t XMLParser::s_maxDataBuffer = XMLPARSER_MAXDATABUFFER;
TiXmlEncoding XMLParser::s_xmlEncoding = TIXML_ENCODING_UTF8;
bool XMLParser::consume(const char* data, u_int32_t len)
{
// * Input serialization is assumed to be done by the source
// * Lock is necessary only when modifying TiXMLDocument
// 1. Add data to buffer. Adjust stream start tag
// 2. Call TiXMLDocument::Parse()
// 3. Check result
// 1
String tmp(data,len);
m_buffer << tmp;
// XDebug(DebugAll,"XMLParser::consume. Buffer: '%s'.",m_buffer.c_str());
if (m_buffer.length() > s_maxDataBuffer) {
SetError(TIXML_ERROR_BUFFEROVERRUN,0,0,s_xmlEncoding);
return false;
}
// Check for start element.
// Adjust termination tag in order to pass correct data to the parser
if (m_findstart) {
int start = m_buffer.find("stream:stream");
// Don't process until found: 'stream:stream' ... '>'
if (start == -1)
return true;
int end = m_buffer.find(">",start);
if (end == -1)
return true;
// Check if we received an end stream
// Search for a '/' before 'stream:stream'
int i_end = m_buffer.find("/");
bool b_end = false;
if (i_end != -1 && i_end < start) {
const char* pEnd = m_buffer.c_str() + i_end + 1;
const char* pStart = m_buffer.c_str() + start;
if (pStart == skipBlanks(pEnd))
b_end = true;
}
if (!b_end) {
m_findstart = false;
if (i_end > start && i_end < end) {
// We found a '/' between 'stream:stream' and '>'
// Do nothing: The tag is already closed or the element
// is invalid and the parser will fail
}
// Found: insert '/'
String tmp = m_buffer.substr(end,m_buffer.length() - end);
m_buffer = m_buffer.substr(0,end) << " /" << tmp;
}
// If we received end stream tag before start stream tag
// The element will be parsed: the upper layer will deal with it
}
if (!m_buffer)
return true;
lock();
const char* src = m_buffer.c_str();
const char* ret = Parse(src,0,s_xmlEncoding);
unlock();
// Remove processed data from bufer
if (ret > src && ret <= src + m_buffer.length())
m_buffer = m_buffer.substr(ret - src);
// XDebug(DebugAll,
// "XMLParser::consume. Data parsed. Buffer: '%s'. Error: '%s'.",
// m_buffer.c_str(),ErrorDesc());
// 3
if (ErrorId() && ErrorId() != TIXML_ERROR_INCOMPLETE)
return false;
return true;
}
XMLElement* XMLParser::extract()
{
// Find the first TiXMLElement
// Check if we received stream end
// Remove any other object type
for (;;) {
TiXmlNode* node = FirstChild();
if (!node)
break;
// Check for XML elements
if (node->ToElement()) {
RemoveChild(node,false);
return new XMLElement(node->ToElement(),true);
}
// Check for end stream
// For Tiny XML '</...>' is an unknown element
if (node->ToUnknown() &&
XMLElement::isType(node->Value(),XMLElement::StreamEnd)) {
RemoveChild(node,true);
return new XMLElement();
}
// Remove non-element
RemoveChild(node,true);
}
return 0;
}
void XMLParser::reset()
{
Lock lock(this);
TiXmlDocument::Clear();
m_buffer.clear();
m_findstart = true;
}
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -1,704 +0,0 @@
/**
* xmlparser.h
* Yet Another XMPP 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.
*/
#ifndef __XMLPARSER_H
#define __XMLPARSER_H
#include <yateclass.h>
#include <tinyxml.h>
#ifdef _WINDOWS
#ifdef LIBYJINGLE_EXPORTS
#define YJINGLE_API __declspec(dllexport)
#else
#ifndef LIBYJINGLE_STATIC
#define YJINGLE_API __declspec(dllimport)
#endif
#endif
#endif /* _WINDOWS */
#ifndef YJINGLE_API
#define YJINGLE_API
#endif
/**
* Holds all Telephony Engine related classes.
*/
namespace TelEngine {
#define XMLPARSER_MAXDATABUFFER 8192 // Default max data buffer
class XMLElement;
class XMLParser;
class XMLElementOut;
/**
* This class holds an XML element
* @short An XML element
*/
class YJINGLE_API XMLElement : public GenObject
{
friend class XMLParser;
public:
/**
* Element type as enumeration
*/
enum Type {
// *** Stream related elements
StreamStart, // stream:stream
StreamEnd, // /stream:stream
StreamError, // stream::error
StreamFeatures, // stream::features
Register, // register
Starttls, // starttls
Handshake, // handshake
Auth, // auth
Challenge, // challenge
Abort, // abort
Aborted, // aborted
Response, // response
Proceed, // proceed
Success, // success
Failure, // failure
Mechanisms, // mechanisms
Mechanism, // mechanism
Session, // session
// *** Stanzas
Iq, // iq
Message, // message
Presence, // presence
// *** Stanza children
Error, // error
Query, // query
VCard, // vCard
Jingle, // session
// Description
Description, // description
PayloadType, // payload-type
// Transport
Transport, // transport
Candidate, // candidate
// Message
Body, // body
Subject, // subject
// Resources
Feature, // feature
Bind, // bind
Resource, // resource
// Jingle session info
Transfer, // transfer
Hold, // hold
Active, // active
Ringing, // ringing
Mute, // mute
// Miscellaneous
Registered, // registered
Remove, // remove
Jid, // jid
Username, // username
Password, // password
Digest, // digest
Required, // required
Dtmf, // dtmf
DtmfMethod, // dtmf-method
Command, // command
Text, // text
Item, // item
Group, // group
Reason, // reason
Content, // content
Parameter, // parameter
Crypto, // crypto
CryptoRequired, // crypto-required
Trying, // trying
Received, // received
File, // file
Offer, // offer
Request, // request
StreamHost, // streamhost
StreamHostUsed, // streamhost-used
Unknown, // Any text
Invalid, // m_element is 0
};
/**
* Constructor.
* Constructs a StreamEnd element
*/
XMLElement();
/**
* Copy constructor
* @param src Source element
*/
XMLElement(const XMLElement& src);
/**
* Constructor. Partially build this element from another one.
* Copy name and 'to', 'from', 'type', 'id' attributes
* @param src Source element
* @param response True to reverse 'to' and 'from' attributes
* @param result True to set type to "result", false to set it to "error".
* Ignored if response is false
*/
XMLElement(const XMLElement& src, bool response, bool result);
/**
* Constructor.
* Constructs an XML element with a TiXmlElement element with the given name.
* Used for outgoing elements
* @param name The element's name
* @param attributes Optional list of attributes
* @param text Optional text for the XML element
*/
XMLElement(const char* name, NamedList* attributes = 0, const char* text = 0);
/**
* Constructor.
* Constructs an XML element with a TiXmlElement element with the given type's name.
* Used for outgoing elements
* @param type The element's type
* @param attributes Optional list of attributes
* @param text Optional text for the XML element
*/
XMLElement(Type type, NamedList* attributes = 0, const char* text = 0);
/**
* Constructor.
* Build this XML element from a list containing name, attributes and text.
* Element's name must be a parameter whose name must be equal to prefix.
* Element's text must be a parameter whose name is prefix followed by a dot.
* The list of attributes will be built from parameters starting with prefix.attributename
* @param src The list containing data used to build this XML element
* @param prefix The prefix used to search the list of parameters
*/
XMLElement(NamedList& src, const char* prefix);
/**
* Destructor. Deletes the underlying TiXmlElement if owned
*/
virtual ~XMLElement();
/**
* Get the type of this object
* @return The type of this object as enumeration
*/
inline Type type() const
{ return m_type; }
/**
* Get the TiXmlElement's name
* @return The name of the TiXmlElement object or 0
*/
inline const char* name() const
{ return valid() ? m_element->Value() : 0; }
/**
* Check if the TiXmlElement's name is the given text
* @param text Text to compare with
* @return False if text is 0 or not equal to name
*/
inline bool nameIs(const char* text) const
{ return (text && name() && (0 == ::strcmp(name(),text))); }
/**
* Get the validity of this object
* @return True if m_element is non null
*/
inline bool valid() const
{ return m_element != 0; }
/**
* Change the type of this object
* @param t The new type of this object
*/
inline void changeType(Type t)
{ m_type = t; }
/**
* Put the element in a buffer
* @param dest Destination string
* @param unclose True to leave the tag unclosed
*/
void toString(String& dest, bool unclose = false) const;
/**
* Put this element's name, text and attributes to a list of parameters
* @param dest Destination list
* @param prefix Prefix to add to parameters
*/
void toList(NamedList& dest, const char* prefix);
/**
* Set the value of an existing attribute or adds a new one
* @param name Attribute's name
* @param value Attribute's value
*/
void setAttribute(const char* name, const char* value);
/**
* Set the value of an existing attribute or adds a new one if the value's length is not 0
* @param name Attribute's name
* @param value Attribute's value
*/
inline void setAttributeValid(const char* name, const String& value) {
if (value)
setAttribute(name,value);
}
/**
* Set the value of an existing attribute or adds a new one from an integer
* @param name Attribute's name
* @param value Attribute's value
*/
inline void setAttribute(const char* name, int value) {
String s(value);
setAttribute(name,s);
}
/**
* Get the value of an attribute
* @param name Attribute's name
* @return Attribute's value. May be 0 if doesn't exists or empty
*/
const char* getAttribute(const char* name) const;
/**
* Get the value of an attribute
* @param name Attribute's name
* @param value Destination string
* @return True if attribute with the given name exists and is not empty
*/
inline bool getAttribute(const char* name, String& value) const {
value = getAttribute(name);
return 0 != value.length();
}
/**
* Fill a list with element's attributes
* @param dest The destination list
*/
void getAttributes(NamedList& dest) const;
/**
* Check if an attribute with the given name and value exists
* @param name Attribute's name
* @param value Attribute's value
* @return True/False
*/
bool hasAttribute(const char* name, const char* value) const;
/**
* Get the text of this XML element
* @return Pointer to the text of this XML element or 0
*/
const char* getText() const;
/**
* Add a child to this object. Release the received element
* @param element XMLElement to add
*/
void addChild(XMLElement* element);
/**
* Find the first child element of this one.
* Remove it from the children list.
* If an element is returned, it owns the TiXmlElement pointer.
* This element must own its TiXmlElement pointer.
* @param name Optional name of the child
* @return Pointer to an XMLElement or 0 if not found
*/
XMLElement* removeChild(const char* name = 0);
/**
* Find the first child element of this one.
* Remove it from the children list.
* If an element is returned, it owns the TiXmlElement pointer.
* This element must own its TiXmlElement pointer.
* @param type Child's type to find
* @return Pointer to an XMLElement or 0 if not found
*/
inline XMLElement* removeChild(Type type)
{ return removeChild(typeName(type)); }
/**
* Find the first child element of this one.
* If an element is returned, it is a newly allocated one, not owning its TiXmlElement pointer
* @param name Optional name of the child
* @return Pointer to an XMLElement or 0 if not found
*/
XMLElement* findFirstChild(const char* name = 0);
/**
* Find the first child element of the given type.
* If an element is returned, it is a newly allocated one, not owning its TiXmlElement pointer
* @param type Child's type to find
* @return Pointer to an XMLElement or 0 if not found
*/
inline XMLElement* findFirstChild(Type type)
{ return findFirstChild(typeName(type)); }
/**
* Check if this element has a given child
* @param name Optional name of the child (check for the first one if 0)
* @return True if this element has the desired child
*/
inline bool hasChild(const char* name) {
XMLElement* tmp = findFirstChild(name);
bool ok = (0 != tmp);
TelEngine::destruct(tmp);
return ok;
}
/**
* Check if this element has a given child
* @param type Child's type to find
* @return True if this element has the desired child
*/
inline bool hasChild(Type type)
{ return hasChild(typeName(type)); }
/**
* Find the next child element. Delete the starting element if not 0.
* If an element is returned, it is a newly allocated one, not owning its TiXmlElement pointer
* @param element Starting XMLElement. O to find from the beginning
* @param name Optional name of the child
* @return Pointer to an XMLElement or 0 if not found
*/
XMLElement* findNextChild(XMLElement* element, const char* name = 0);
/**
* Find the next child element of the given type. Delete the starting element if not 0.
* If an element is returned, it is a newly allocated one, not owning its TiXmlElement pointer
* @param element Starting XMLElement. O to find from the beginning
* @param type Child's type to find
* @return Pointer to an XMLElement or 0 if not found
*/
inline XMLElement* findNextChild(XMLElement* element, Type type)
{ return findNextChild(element,typeName(type)); }
/**
* Find the first attribute
* @return Pointer to the first attribute or 0
*/
inline const TiXmlAttribute* firstAttribute() const
{ return valid() ? m_element->FirstAttribute() : 0; }
/**
* Get the name associated with the given type
* @param type Element type as enumeration
* @return Pointer to the name or 0
*/
static inline const char* typeName(Type type)
{ return lookup(type,s_names); }
/**
* Check if the given text is equal to the one associated with the given type
* @param txt Text to compare
* @param type Element type as enumeration
* @return True if txt equals the text associated with the given type
*/
static inline bool isType(const char* txt, Type type) {
const char* s = typeName(type);
return (txt && s && (0 == ::strcmp(txt,s)));
}
/**
* Get a pointer to this object
*/
virtual void* getObject(const String& name) const {
if (name == "XMLElement")
return (void*)this;
return GenObject::getObject(name);
}
/**
* Release memory
*/
virtual const String& toString() const
{ return m_name; }
/**
* Release memory
*/
virtual void destruct() {
if (m_owner && m_element)
delete m_element;
m_element = 0;
GenObject::destruct();
}
/**
* Get an xml element from a list's parameter
* @param list The list to be searched for the given parameter
* @param stole True to release parameter ownership (defaults to false)
* @param name Parameter name (defaults to 'xml')
* @param value Optional parameter value to check
* @return XMLElement pointer or 0. If a valid pointer is returned and
* stole is true the caller will own the pointer
*/
static XMLElement* getXml(NamedList& list, bool stole = false,
const char* name = "xml", const char* value = 0);
/**
* Associations between XML element name and type
*/
static TokenDict s_names[];
protected:
/**
* Constructor.
* Constructs an XML element from a TiXmlElement.
* Used to extract elements from parser and access the children.
* When extracting elements from parser the object will own the TiXmlElement.
* When accessing the children, the object will not own the TiXmlElement
* @param element Pointer to a valid TiXmlElement
* @param owner Owner flag
*/
XMLElement(TiXmlElement* element, bool owner);
/**
* Get the underlying TiXmlElement
* @return The underlying TiXmlElement object or 0
*/
inline TiXmlElement* get() const
{ return m_element; }
/**
* Release the ownership of the underlying TiXmlElement
* and returns it if the object owns it
* @return The underlying TiXmlElement object or 0 if not owned or 0
*/
TiXmlElement* releaseOwnership();
private:
// Set this object's type from m_element's name
inline void setType() {
m_name = name();
m_type = (Type)lookup(name(),s_names,Unknown);
}
Type m_type; // Element's type
bool m_owner; // Owner flag. If true, this object owns the XML element
String m_name; // The name of this element
TiXmlElement* m_element; // The underlying XML element
};
/**
* This class is responsable of parsing incoming data.
* Keeps the resulting XML elements and the input buffer
* @short An XML parser
*/
class YJINGLE_API XMLParser : public TiXmlDocument, public Mutex
{
public:
/**
* Constructor.
* Constructs an XML parser
*/
inline XMLParser()
: TiXmlDocument(), Mutex(true), m_findstart(true)
{}
/**
* Destructor
*/
virtual ~XMLParser()
{}
/**
* Add data to buffer. Parse the buffer.
* On success, the already parsed data is removed from buffer.
* This method is thread safe
* @param data Pointer to the data to consume
* @param len Data length
* @return True on successfully parsed
*/
bool consume(const char* data, u_int32_t len);
/**
* Extract the first XML element from document.
* Remove non-element children of the document (e.g. declaration).
* This method is thread safe
* @return Pointer to an XMLElement or 0 if the document is empty
*/
XMLElement* extract();
/**
* Get the buffer length (incomplete data)
* @return The number of bytes belonging to an incomplete XML element
*/
inline unsigned int bufLen() const
{ return m_buffer.length(); }
/**
* Get a copy of the parser's buffer
* @param dest Destination string
*/
inline void getBuffer(String& dest) const
{ dest = m_buffer; }
/**
* Clear the parser's input buffer and already parsed elements. Reset data
*/
void reset();
/**
* The maximum allowed buffer length
*/
static u_int32_t s_maxDataBuffer;
/**
* The XML encoding
*/
static TiXmlEncoding s_xmlEncoding;
private:
String m_buffer; // Input data buffer
bool m_findstart; // Search for stream start tag or not
};
/**
* This class holds an XML element to be sent through a stream
* @short An outgoing XML element
*/
class YJINGLE_API XMLElementOut : public RefObject
{
public:
/**
* Constructor
* @param element The XML element
* @param senderID Optional sender id
* @param unclose True to not close the tag when building the buffer
*/
inline XMLElementOut(XMLElement* element, const char* senderID = 0,
bool unclose = false)
: m_element(element), m_offset(0), m_id(senderID), m_unclose(unclose),
m_sent(false)
{}
/**
* Destructor
* Delete m_element if not 0
*/
virtual ~XMLElementOut()
{ TelEngine::destruct(m_element); }
/**
* Get the underlying element
* @return The underlying element
*/
inline XMLElement* element() const
{ return m_element; }
/**
* Check if this element was (partially) sent
* @return True if an attempt to send this element was already done
*/
inline bool sent() const
{ return m_sent; }
/**
* Get the data buffer
* @return The data buffer
*/
inline String& buffer()
{ return m_buffer; }
/**
* Get the id member
* @return The id member
*/
inline const String& id() const
{ return m_id; }
/**
* Get the remainig byte count to send
* @return The unsent number of bytes
*/
inline u_int32_t dataCount()
{ return m_buffer.length() - m_offset; }
/**
* Get the remainig data to send. Set the buffer if not already set
* @param nCount The number of unsent bytes
* @return Pointer to the remaining data or 0
*/
inline const char* getData(u_int32_t& nCount) {
if (!m_buffer)
prepareToSend();
nCount = dataCount();
return m_buffer.c_str() + m_offset;
}
/**
* Increase the offset with nCount bytes. Set the sent flag
* @param nCount The number of bytes sent
*/
inline void dataSent(u_int32_t nCount) {
m_sent = true;
m_offset += nCount;
if (m_offset > m_buffer.length())
m_offset = m_buffer.length();
}
/**
* Release the ownership of m_element
* The caller is responsable of returned pointer
* @return XMLElement pointer or 0
*/
inline XMLElement* release() {
XMLElement* e = m_element;
m_element = 0;
return e;
}
/**
* Fill a buffer with the XML element to send
* @param buffer The buffer to fill
*/
inline void toBuffer(String& buffer)
{ if (m_element) m_element->toString(buffer,m_unclose); }
/**
* Fill the buffer with the XML element to send
*/
inline void prepareToSend()
{ toBuffer(m_buffer); }
private:
XMLElement* m_element; // The XML element
String m_buffer; // Data to send
u_int32_t m_offset; // Offset to send
String m_id; // Sender's id
bool m_unclose; // Close or not the element's tag
bool m_sent; // Sent flag (true if an attempt to send was done)
};
};
#endif /* __XMLPARSER_H */
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -1,886 +0,0 @@
/**
* utils.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 <xmpputils.h>
#include <string.h>
using namespace TelEngine;
#include <time.h>
static XMPPNamespace s_ns;
static XMPPError s_err;
TokenDict XMPPServerInfo::s_flagName[] = {
{"noautorestart", NoAutoRestart},
{"keeproster", KeepRoster},
{"tlsrequired", TlsRequired},
{"oldstyleauth", OldStyleAuth},
{"allowplainauth", AllowPlainAuth},
{"allowunsafesetup", AllowUnsafeSetup},
{0,0}
};
TokenDict XMPPDirVal::s_names[] = {
{"none", None},
{"to", To},
{"from", From},
{"both", Both},
{0,0},
};
/**
* XMPPNamespace
*/
TokenDict XMPPNamespace::s_value[] = {
{"http://etherx.jabber.org/streams", Stream},
{"jabber:client", Client},
{"jabber:server", Server},
{"jabber:component:accept", ComponentAccept},
{"jabber:component:connect", ComponentConnect},
{"urn:ietf:params:xml:ns:xmpp-streams", StreamError},
{"urn:ietf:params:xml:ns:xmpp-stanzas", StanzaError},
{"http://jabber.org/features/iq-register", Register},
{"jabber:iq:register", IqRegister},
{"jabber:iq:private", IqPrivate},
{"jabber:iq:auth", IqAuth},
{"http://jabber.org/features/iq-auth", IqAuthFeature},
{"urn:ietf:params:xml:ns:xmpp-tls", Starttls},
{"urn:ietf:params:xml:ns:xmpp-sasl", Sasl},
{"urn:ietf:params:xml:ns:xmpp-session", Session},
{"urn:ietf:params:xml:ns:xmpp-bind", Bind},
{"jabber:iq:roster", Roster},
{"jabber:iq:roster-dynamic", DynamicRoster},
{"http://jabber.org/protocol/disco#info", DiscoInfo},
{"http://jabber.org/protocol/disco#items", DiscoItems},
{"vcard-temp", VCard},
{"http://jabber.org/protocol/si/profile/file-transfer",SIProfileFileTransfer},
{"http://jabber.org/protocol/bytestreams", ByteStreams},
{"urn:xmpp:jingle:0", Jingle},
{"urn:xmpp:jingle:errors:0", JingleError},
{"urn:xmpp:jingle:apps:rtp:0", JingleAppsRtp},
{"urn:xmpp:jingle:apps:rtp:info:0", JingleAppsRtpInfo},
{"urn:xmpp:jingle:apps:rtp:audio", JingleAppsRtpAudio},
{"urn:xmpp:jingle:apps:file-transfer:0", JingleAppsFileTransfer},
{"urn:xmpp:jingle:transports:ice-udp:0", JingleTransportIceUdp},
{"urn:xmpp:jingle:transports:raw-udp:0", JingleTransportRawUdp},
{"urn:xmpp:jingle:transports:raw-udp:info:0", JingleTransportRawUdpInfo},
{"urn:xmpp:jingle:transports:bytestreams:0", JingleTransportByteStreams},
{"urn:xmpp:jingle:transfer:0", JingleTransfer},
{"urn:xmpp:jingle:dtmf:0", Dtmf},
{"http://www.google.com/session", JingleSession},
{"http://www.google.com/session/phone", JingleAudio},
{"http://www.google.com/transport/p2p", JingleTransport},
{"urn:xmpp:jingle:apps:rtp:info", JingleRtpInfoOld},
{"http://jabber.org/protocol/jingle/info/dtmf", DtmfOld},
{"http://jabber.org/protocol/commands", Command},
{"http://www.google.com/xmpp/protocol/voice/v1", CapVoiceV1},
{0,0}
};
bool XMPPNamespace::isText(Type index, const char* txt)
{
const char* tmp = lookup(index,s_value,0);
if (!(txt && tmp))
return false;
return 0 == strcmp(tmp,txt);
}
/**
* XMPPError
*/
TokenDict XMPPError::s_value[] = {
{"cancel", TypeCancel},
{"continue", TypeContinue},
{"modify", TypeModify},
{"auth", TypeAuth},
{"wait", TypeWait},
{"bad-format", BadFormat},
{"bad-namespace-prefix", BadNamespace},
{"connection-timeout", ConnTimeout},
{"host-gone", HostGone},
{"host-unknown", HostUnknown},
{"improper-addressing", BadAddressing},
{"internal-server-error", Internal},
{"invalid-from", InvalidFrom},
{"invalid-id", InvalidId},
{"invalid-namespace", InvalidNamespace},
{"invalid-xml", InvalidXml},
{"not-authorized", NotAuth},
{"policy-violation", Policy},
{"remote-connection-failed", RemoteConn},
{"resource-constraint", ResConstraint},
{"restricted-xml", RestrictedXml},
{"see-other-host", SeeOther},
{"system-shutdown", Shutdown},
{"undefined-condition", UndefinedCondition},
{"unsupported-encoding", UnsupportedEnc},
{"unsupported-stanza-type", UnsupportedStanza},
{"unsupported-version", UnsupportedVersion},
{"xml-not-well-formed", Xml},
// Auth failures
{"aborted", Aborted},
{"incorrect-encoding", IncorrectEnc},
{"invalid-authzid", InvalidAuth},
{"invalid-mechanism", InvalidMechanism},
{"mechanism-too-weak", MechanismTooWeak},
{"not-authorized", NotAuthorized},
{"temporary-auth-failure", TempAuthFailure},
// Stanza errors
{"bad-request", SBadRequest},
{"conflict", SConflict},
{"feature-not-implemented", SFeatureNotImpl},
{"forbidden", SForbidden},
{"gone", SGone},
{"internal-server-error", SInternal},
{"item-not-found", SItemNotFound},
{"jid-malformed", SBadJid},
{"not-acceptable", SNotAcceptable},
{"not-allowed", SNotAllowed},
{"payment-required", SPayment},
{"recipient-unavailable", SUnavailable},
{"redirect", SRedirect},
{"registration-required", SReg},
{"remote-server-not-found", SNoRemote},
{"remote-server-timeout", SRemoteTimeout},
{"resource-constraint", SResource},
{"service-unavailable", SServiceUnavailable},
{"subscription-required", SSubscription},
{"undefined-condition", SUndefinedCondition},
{"unexpected-request", SRequest},
{"unsupported-dtmf-method", DtmfNoMethod},
{"item-not-found", ItemNotFound},
{0,0}
};
bool XMPPError::isText(int index, const char* txt)
{
const char* tmp = lookup(index,s_value,0);
if (!(txt && tmp))
return false;
return 0 == strcmp(tmp,txt);
}
/**
* JabberID
*/
void JabberID::set(const char* jid)
{
this->assign(jid);
parse();
}
void JabberID::set(const char* node, const char* domain, const char* resource)
{
if (node != m_node.c_str())
m_node = node;
if (domain != m_domain.c_str())
m_domain = domain;
if (resource != m_resource.c_str())
m_resource = resource;
clear();
if (m_node)
*this << m_node << "@";
*this << m_domain;
m_bare = *this;
if (m_node && m_resource)
*this << "/" << m_resource;
}
bool JabberID::valid(const String& value)
{
if (value.null())
return true;
return s_regExpValid.matches(value);
}
Regexp JabberID::s_regExpValid("^\\([[:alnum:]]*\\)");
#if 0
~`!#$%^*_-+=()[]{}|\;?.
#endif
void JabberID::parse()
{
String tmp = *this;
int i = tmp.find('@');
if (i == -1)
m_node = "";
else {
m_node = tmp.substr(0,i);
tmp = tmp.substr(i+1,tmp.length()-i-1);
}
i = tmp.find('/');
if (i == -1) {
m_domain = tmp;
m_resource = "";
}
else {
m_domain = tmp.substr(0,i);
m_resource = tmp.substr(i+1,tmp.length()-i-1);
}
// Set bare JID
m_bare = "";
if (m_node)
m_bare << m_node << "@";
m_bare << m_domain;
}
/**
* JIDIdentity
*/
TokenDict JIDIdentity::s_category[] = {
{"account", Account},
{"client", Client},
{"component", Component},
{"gateway", Gateway},
{0,0},
};
TokenDict JIDIdentity::s_type[] = {
{"registered", AccountRegistered},
{"phone", ClientPhone},
{"generic", ComponentGeneric},
{"presence", ComponentPresence},
{"generic", GatewayGeneric},
{0,0},
};
XMLElement* JIDIdentity::toXML()
{
return XMPPUtils::createIdentity(categoryText(m_category),typeText(m_type),m_name);
}
bool JIDIdentity::fromXML(const XMLElement* element)
{
if (!element)
return false;
XMLElement* id = ((XMLElement*)element)->findFirstChild("identity");
if (!id)
return false;
m_category = categoryValue(id->getAttribute("category"));
m_type = typeValue(id->getAttribute("type"));
id->getAttribute("name",m_name);
TelEngine::destruct(id);
return true;
}
/**
* JIDFeatureSasl
*/
TokenDict JIDFeatureSasl::s_authMech[] = {
{"DIGEST-MD5", MechMD5},
{"DIGEST-SHA1", MechSHA1},
{"PLAIN", MechPlain},
{0,0}
};
/**
* JIDFeatureList
*/
// Find a specific feature
JIDFeature* JIDFeatureList::get(XMPPNamespace::Type feature)
{
ObjList* obj = m_features.skipNull();
for (; obj; obj = obj->skipNext()) {
JIDFeature* f = static_cast<JIDFeature*>(obj->get());
if (*f == feature)
return f;
}
return 0;
}
// Build an XML element and add it to the destination
XMLElement* JIDFeatureList::addTo(XMLElement* element)
{
if (!element)
return 0;
ObjList* obj = m_features.skipNull();
for (; obj; obj = obj->skipNext()) {
JIDFeature* f = static_cast<JIDFeature*>(obj->get());
XMLElement* feature = new XMLElement(XMLElement::Feature);
feature->setAttribute("var",s_ns[*f]);
element->addChild(feature);
}
return element;
}
// Update the list from 'feature' children of the given element
void JIDFeatureList::fromXml(XMLElement* element, bool reset)
{
if (reset)
clear();
if (!element)
return;
XMLElement* x = 0;
while (0 != (x = element->findNextChild(x,XMLElement::Feature))) {
XMPPNamespace::Type t = XMPPNamespace::type(x->getAttribute("var"));
if (t != XMPPNamespace::Count && !get(t))
add(t);
}
}
/**
* XMPPUtils
*/
TokenDict XMPPUtils::s_iq[] = {
{"set", IqSet},
{"get", IqGet},
{"result", IqResult},
{"error", IqError},
{0,0}
};
TokenDict XMPPUtils::s_commandAction[] = {
{"execute", CommExecute},
{"cancel", CommCancel},
{"prev", CommPrev},
{"next", CommNext},
{"complete", CommComplete},
{0,0}
};
TokenDict XMPPUtils::s_commandStatus[] = {
{"executing", CommExecuting},
{"completed", CommCompleted},
{"cancelled", CommCancelled},
{0,0}
};
XMLElement* XMPPUtils::createElement(const char* name, XMPPNamespace::Type ns,
const char* text)
{
XMLElement* element = new XMLElement(name,0,text);
element->setAttribute("xmlns",s_ns[ns]);
return element;
}
XMLElement* XMPPUtils::createElement(XMLElement::Type type, XMPPNamespace::Type ns,
const char* text)
{
XMLElement* element = new XMLElement(type,0,text);
element->setAttribute("xmlns",s_ns[ns]);
return element;
}
XMLElement* XMPPUtils::createIq(IqType type, const char* from,
const char* to, const char* id)
{
XMLElement* iq = new XMLElement(XMLElement::Iq);
iq->setAttribute("type",lookup(type,s_iq,""));
iq->setAttributeValid("from",from);
iq->setAttributeValid("to",to);
iq->setAttributeValid("id",id);
return iq;
}
XMLElement* XMPPUtils::createIqBind( const char* from,
const char* to, const char* id, const ObjList& resources)
{
XMLElement* iq = createIq(IqSet,from,to,id);
XMLElement* bind = createElement(XMLElement::Bind,XMPPNamespace::Bind);
ObjList* obj = resources.skipNull();
for (; obj; obj = resources.skipNext()) {
String* s = static_cast<String*>(obj->get());
if (!(s && s->length()))
continue;
XMLElement* res = new XMLElement(XMLElement::Resource,0,*s);
bind->addChild(res);
}
iq->addChild(bind);
return iq;
}
// Create an 'iq' element of type 'get' with a 'vcard' child
XMLElement* XMPPUtils::createVCard(bool get, const char* from, const char* to, const char* id)
{
XMLElement* xml = createIq(get ? IqGet : IqSet,from,to,id);
xml->addChild(createElement(XMLElement::VCard,XMPPNamespace::VCard));
return xml;
}
XMLElement* XMPPUtils::createCommand(CommandAction action, const char* node,
const char* sessionId)
{
XMLElement* command = createElement(XMLElement::Command,XMPPNamespace::Command);
if (sessionId)
command->setAttribute("sessionid",sessionId);
command->setAttribute("node",node);
command->setAttribute("action",lookup(action,s_commandAction));
return command;
}
XMLElement* XMPPUtils::createIdentity(const char* category, const char* type,
const char* name)
{
XMLElement* id = new XMLElement("identity");
id->setAttribute("category",category);
id->setAttribute("type",type);
id->setAttribute("name",name);
return id;
}
XMLElement* XMPPUtils::createIqDisco(const char* from, const char* to,
const char* id, bool info)
{
XMLElement* xml = createIq(IqGet,from,to,id);
xml->addChild(createElement(XMLElement::Query,
info ? XMPPNamespace::DiscoInfo : XMPPNamespace::DiscoItems));
return xml;
}
XMLElement* XMPPUtils::createDiscoInfoRes(const char* from, const char* to,
const char* id, JIDFeatureList* features, JIDIdentity* identity)
{
XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqResult,from,to,id);
XMLElement* query = XMPPUtils::createElement(XMLElement::Query,XMPPNamespace::DiscoInfo);
if (identity)
query->addChild(identity->toXML());
if (features)
features->addTo(query);
iq->addChild(query);
return iq;
}
XMLElement* XMPPUtils::createError(XMPPError::ErrorType type,
XMPPError::Type condition, const char* text)
{
XMLElement* err = new XMLElement("error");
err->setAttribute("type",s_err[type]);
XMLElement* tmp = createElement(s_err[condition],XMPPNamespace::StanzaError);
err->addChild(tmp);
if (text) {
tmp = createElement(XMLElement::Text,XMPPNamespace::StanzaError,text);
err->addChild(tmp);
}
return err;
}
// Create an error from a received element. Consume the received element
XMLElement* XMPPUtils::createError(XMLElement* xml, XMPPError::ErrorType type,
XMPPError::Type error, const char* text)
{
if (!xml)
return 0;
XMLElement* err = new XMLElement(*xml,true,false);
// Copy children from xml to the error element
XMLElement* child = 0;
while (0 != (child = xml->removeChild()))
err->addChild(child);
TelEngine::destruct(xml);
// Create the error
err->addChild(createError(type,error,text));
return err;
}
XMLElement* XMPPUtils::createStreamError(XMPPError::Type error, const char* text)
{
XMLElement* element = new XMLElement(XMLElement::StreamError);
XMLElement* err = createElement(s_err[error],XMPPNamespace::StreamError);
element->addChild(err);
if (text) {
XMLElement* txt = createElement(XMLElement::Text,XMPPNamespace::StreamError,text);
element->addChild(txt);
}
return element;
}
// Build a register query element
XMLElement* XMPPUtils::createRegisterQuery(IqType type, const char* from,
const char* to, const char* id,
XMLElement* child1, XMLElement* child2, XMLElement* child3)
{
XMLElement* iq = createIq(type,from,to,id);
XMLElement* q = XMPPUtils::createElement(XMLElement::Query,XMPPNamespace::IqRegister);
if (child1)
q->addChild(child1);
if (child2)
q->addChild(child2);
if (child3)
q->addChild(child3);
iq->addChild(q);
return iq;
}
// Check if the given element has an attribute 'xmlns' equal to a given value
bool XMPPUtils::hasXmlns(XMLElement& element, XMPPNamespace::Type ns)
{
return element.hasAttribute("xmlns",s_ns[ns]);
}
// Decode an 'error' XML element
void XMPPUtils::decodeError(XMLElement* element, String& error, String& text)
{
if (!element)
return;
XMPPNamespace::Type nsErr;
error = "";
text = "";
XMLElement* child = 0;
switch (element->type()) {
case XMLElement::StreamError:
nsErr = XMPPNamespace::StreamError;
break;
case XMLElement::Error:
nsErr = XMPPNamespace::StanzaError;
break;
case XMLElement::Iq:
case XMLElement::Presence:
case XMLElement::Message:
child = element->findFirstChild(XMLElement::Error);
decodeError(child,error,text);
TelEngine::destruct(child);
default:
return;
}
while (0 != (child = element->findNextChild(child)))
if (hasXmlns(*child,nsErr)) {
error = child->name();
TelEngine::destruct(child);
break;
}
child = element->findFirstChild(XMLElement::Text);
if (child) {
text = child->getText();
TelEngine::destruct(child);
}
}
inline void addPaddedVal(String& buf, int val, const char* sep)
{
if (val < 10)
buf << "0";
buf << val << sep;
}
// Encode EPOCH time given in seconds to a date/time profile as defined in
// XEP-0082
void XMPPUtils::encodeDateTimeSec(String& buf, unsigned int timeSec,
unsigned int fractions)
{
int y;
unsigned int m,d,hh,mm,ss;
if (!Time::toDateTime(timeSec,y,m,d,hh,mm,ss))
return;
buf << y << "-";
addPaddedVal(buf,m,"-");
addPaddedVal(buf,d,"T");
addPaddedVal(buf,hh,":");
addPaddedVal(buf,mm,":");
addPaddedVal(buf,ss,"");
if (fractions)
buf << "." << fractions;
buf << "Z";
}
// Decode a date/time profile as defined in XEP-0082 and
// XML Schema Part 2: Datatypes Second Edition to EPOCH time
unsigned int XMPPUtils::decodeDateTimeSec(const String& time, unsigned int* fractions)
{
// XML Schema Part 2: Datatypes Second Edition
// (see http://www.w3.org/TR/xmlschema-2/#dateTime)
// Section 3.2.7: dateTime
// Format: [-]yyyy[y+]-mm-ddThh:mm:ss[.s+][Z|[+|-]hh:mm]
// NOTE: The document specify that yyyy may be negative and may have more then 4 digits:
// for now we only accept positive years greater then 1970
unsigned int ret = (unsigned int)-1;
unsigned int timeFractions = 0;
while (true) {
// Split date/time
int pos = time.find('T');
if (pos == -1)
return (unsigned int)-1;
// Decode date
if (time.at(0) == '-')
break;
int year = 0;
unsigned int month = 0;
unsigned int day = 0;
String date = time.substr(0,pos);
ObjList* list = date.split('-');
bool valid = (list->length() == 3 && list->count() == 3);
if (valid) {
year = (*list)[0]->toString().toInteger(-1,10);
month = (unsigned int)(*list)[1]->toString().toInteger(-1,10);
day = (unsigned int)(*list)[2]->toString().toInteger(-1,10);
valid = year >= 1970 && month && month <= 12 && day && day <= 31;
}
TelEngine::destruct(list);
if (valid)
DDebug(DebugAll,
"XMPPUtils::decodeDateTimeSec() decoded year=%d month=%u day=%u from '%s'",
year,month,day,time.c_str());
else {
DDebug(DebugNote,
"XMPPUtils::decodeDateTimeSec() incorrect date=%s in '%s'",
date.c_str(),time.c_str());
break;
}
// Decode Time
String t = time.substr(pos + 1,8);
if (t.length() != 8)
break;
unsigned int hh = 0;
unsigned int mm = 0;
unsigned int ss = 0;
int offsetSec = 0;
list = t.split(':');
valid = (list->length() == 3 && list->count() == 3);
if (valid) {
hh = (unsigned int)(*list)[0]->toString().toInteger(-1,10);
mm = (unsigned int)(*list)[1]->toString().toInteger(-1,10);
ss = (unsigned int)(*list)[2]->toString().toInteger(-1,10);
valid = (hh <= 23 && mm <= 59 && ss <= 59) ||
(hh == 24 && mm == 0 && ss == 0);
}
TelEngine::destruct(list);
if (!valid) {
DDebug(DebugNote,
"XMPPUtils::decodeDateTimeSec() incorrect time=%s in '%s'",
t.c_str(),time.c_str());
break;
}
#ifdef DEBUG
else
Debug(DebugAll,
"XMPPUtils::decodeDateTimeSec() decoded hour=%u minute=%u sec=%u from '%s'",
hh,mm,ss,time.c_str());
#endif
// Get the rest
unsigned int parsed = date.length() + t.length() + 1;
unsigned int len = time.length() - parsed;
const char* buf = time.c_str() + parsed;
if (len > 1) {
// Get time fractions
if (buf[0] == '.') {
unsigned int i = 1;
// FIXME: Trailing 0s are not allowed in fractions
for (; i < len && buf[i] >= '0' && buf[i] <= '9'; i++)
;
String fr(buf + 1,i - 1);
if (i > 2)
timeFractions = (unsigned int)fr.toInteger(-1);
else
timeFractions = (unsigned int)-1;
if (timeFractions != (unsigned int)-1)
DDebug(DebugAll,
"XMPPUtils::decodeDateTimeSec() decoded fractions=%u from '%s'",
timeFractions,time.c_str());
else {
DDebug(DebugNote,
"XMPPUtils::decodeDateTimeSec() incorrect fractions=%s in '%s'",
fr.c_str(),time.c_str());
break;
}
len -= i;
buf += i;
}
// Get offset
if (len > 1) {
int sign = 1;
if (*buf == '-' || *buf == '+') {
if (*buf == '-')
sign = -1;
buf++;
len--;
}
String offs(buf,5);
// We should have at least 5 bytes: hh:ss
if (len < 5 || buf[2] != ':') {
DDebug(DebugNote,
"XMPPUtils::decodeDateTimeSec() incorrect time offset=%s in '%s'",
offs.c_str(),time.c_str());
break;
}
unsigned int hhOffs = (unsigned int)offs.substr(0,2).toInteger(-1,10);
unsigned int mmOffs = (unsigned int)offs.substr(3,2).toInteger(-1,10);
// XML Schema Part 2 3.2.7.3: the hour may be 0..13. It can be 14 if minute is 0
if (mmOffs > 59 || (hhOffs > 13 && !mmOffs)) {
DDebug(DebugNote,
"XMPPUtils::decodeDateTimeSec() incorrect time offset values hour=%u minute=%u in '%s'",
hhOffs,mmOffs,time.c_str());
break;
}
DDebug(DebugAll,
"XMPPUtils::decodeDateTimeSec() decoded time offset '%c' hour=%u minute=%u from '%s'",
sign > 0 ? '+' : '-',hhOffs,mmOffs,time.c_str());
offsetSec = sign * (hhOffs * 3600 + mmOffs * 60);
buf += 5;
len -= 5;
}
}
// Check termination markup
if (len && (len != 1 || *buf != 'Z')) {
DDebug(DebugNote,
"XMPPUtils::decodeDateTimeSec() '%s' is incorrectly terminated '%s'",
time.c_str(),buf);
break;
}
ret = Time::toEpoch(year,month,day,hh,mm,ss,offsetSec);
#ifdef DEBUG
if (ret == (unsigned int)-1)
Debug(DebugNote,
"XMPPUtils::decodeDateTimeSec() failed to convert '%s'",
time.c_str());
#endif
break;
}
if (ret != (unsigned int)-1) {
if (fractions)
*fractions = timeFractions;
}
return ret;
}
// Check if an element or attribute name restricts value output
static const char* check(const String& name, const char* ok)
{
#define RESTRICT_LEN 2
static String restrict[RESTRICT_LEN] = {"auth", "password"};
static const char* pwd = "********";
for (unsigned int i = 0; i < RESTRICT_LEN; i++)
if (restrict[i] == name)
return pwd;
return ok;
#undef RESTRICT_LEN
}
void XMPPUtils::print(String& xmlStr, XMLElement& element, const char* indent)
{
#define STARTLINE(indent) "\r\n" << indent
const char* enclose = "-----";
bool hasAttr = (0 != element.firstAttribute());
bool hasChild = element.hasChild(0);
const char* txt = element.getText();
bool root = false;
if (!(indent && *indent)) {
indent = "";
root = true;
}
if (root)
xmlStr << STARTLINE(indent) << enclose;
// Name
if (!(hasAttr || hasChild || txt)) {
// indent<element.name()/>
xmlStr << STARTLINE(indent) << '<' << element.name();
if ((element.name())[0] != '/')
xmlStr << '/';
xmlStr << '>';
if (root)
xmlStr << STARTLINE(indent) << enclose;
return;
}
// <element.name()> or <element.name()
xmlStr << STARTLINE(indent) << '<' << element.name();
if (hasChild || txt)
xmlStr << '>';
String sindent = indent;
sindent << " ";
// Attributes
const TiXmlAttribute* attr = element.firstAttribute();
for (; attr; attr = attr->Next())
xmlStr << STARTLINE(sindent) << attr->Name() << "=\""
<< check(attr->Name(),attr->Value()) << '"';
// Text. Filter some known elements to avoid output of passwords
if (txt)
xmlStr << STARTLINE(sindent) << check(element.name(),txt);
// Children
XMLElement* child = element.findFirstChild();
String si = sindent;
for (; child; child = element.findNextChild(child))
print(xmlStr,*child,si);
// End tag
if (hasChild || txt)
xmlStr << STARTLINE(indent) << "</" << element.name() << '>';
else
xmlStr << STARTLINE(indent) << "/>";
if (root)
xmlStr << STARTLINE(indent) << enclose;
#undef STARTLINE
}
bool XMPPUtils::split(NamedList& dest, const char* src, const char sep,
bool nameFirst)
{
if (!src)
return false;
unsigned int index = 1;
String s = src;
ObjList* obj = s.split(sep,false);
for (ObjList* o = obj->skipNull(); o; o = o->skipNext(), index++) {
String* tmp = static_cast<String*>(o->get());
if (nameFirst)
dest.addParam(*tmp,String(index));
else
dest.addParam(String(index),*tmp);
}
TelEngine::destruct(obj);
return true;
}
// Decode a comma separated list of flags and put them into an integr mask
int XMPPUtils::decodeFlags(const String& src, const TokenDict* dict)
{
if (!dict)
return 0;
int mask = 0;
ObjList* obj = src.split(',',false);
for (ObjList* o = obj->skipNull(); o; o = o->skipNext())
mask |= lookup(static_cast<String*>(o->get())->c_str(),dict);
TelEngine::destruct(obj);
return mask;
}
// Encode a mask of flags to a comma separated list.
void XMPPUtils::buildFlags(String& dest, int src, const TokenDict* dict)
{
if (!dict)
return;
for (; dict->token; dict++)
if (0 != (src & dict->value))
dest.append(dict->token,",");
}
// Add child elements from a list to a destination element
bool XMPPUtils::addChidren(XMLElement* dest, ObjList& list)
{
if (!dest)
return false;
ObjList* o = list.skipNull();
bool added = (0 != o);
for (; o; o = o->skipNext()) {
XMLElement* xml = static_cast<XMLElement*>(o->get());
dest->addChild(new XMLElement(*xml));
}
return added;
}
/* vi: set ts=8 sw=4 sts=4 noet: */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,11 +9,11 @@ DEFS :=
INCLUDES := -I@top_srcdir@ -I../.. -I@srcdir@
CFLAGS := @CFLAGS@ @MODULE_CPPFLAGS@ @INLINE_FLAGS@
LDFLAGS:= @LDFLAGS@ -L../.. -lyate
INCFILES := @top_srcdir@/yateclass.h @srcdir@/tinystr.h @srcdir@/tinyxml.h
INCFILES := @top_srcdir@/yateclass.h @srcdir@/yatexml.h
PROGS=
LIBS = libyatexml.a
OBJS = tinystr.o tinyxml.o tinyxmlparser.o
OBJS = XML.o
LOCALFLAGS =
LOCALLIBS =

2018
libs/yxml/XML.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,117 +0,0 @@
/*
www.sourceforge.net/projects/tinyxml
Original file by Yves Berquin.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
/*
* THIS FILE WAS ALTERED BY Tyge Løvset, 7. April 2005.
*/
#ifndef TIXML_USE_STL
#include "tinystr.h"
using namespace TelEngine;
// Error value for find primitive
const TiXmlString::size_type TiXmlString::npos = static_cast< size_type >(-1);
// Null rep.
TiXmlString::Rep TiXmlString::nullrep_ = { 0, 0, '\0' };
void TiXmlString::reserve (size_type cap)
{
if (cap > capacity())
{
TiXmlString tmp;
tmp.init(length(), cap);
memcpy(tmp.start(), data(), length());
swap(tmp);
}
}
TiXmlString& TiXmlString::assign(const char* str, size_type len)
{
size_type cap = capacity();
if (len > cap || cap > 3*(len + 8))
{
TiXmlString tmp;
tmp.init(len);
memcpy(tmp.start(), str, len);
swap(tmp);
}
else
{
memmove(start(), str, len);
set_size(len);
}
return *this;
}
TiXmlString& TiXmlString::append(const char* str, size_type len)
{
size_type newsize = length() + len;
if (newsize > capacity())
{
reserve (newsize + capacity());
}
memmove(finish(), str, len);
set_size(newsize);
return *this;
}
TiXmlString operator + (const TiXmlString & a, const TiXmlString & b)
{
TiXmlString tmp;
tmp.reserve(a.length() + b.length());
tmp += a;
tmp += b;
return tmp;
}
TiXmlString operator + (const TiXmlString & a, const char* b)
{
TiXmlString tmp;
TiXmlString::size_type b_len = static_cast<TiXmlString::size_type>( strlen(b) );
tmp.reserve(a.length() + b_len);
tmp += a;
tmp.append(b, b_len);
return tmp;
}
TiXmlString operator + (const char* a, const TiXmlString & b)
{
TiXmlString tmp;
TiXmlString::size_type a_len = static_cast<TiXmlString::size_type>( strlen(a) );
tmp.reserve(a_len + b.length());
tmp.append(a, a_len);
tmp += b;
return tmp;
}
#endif // TIXML_USE_STL

View File

@ -1,342 +0,0 @@
/*
www.sourceforge.net/projects/tinyxml
Original file by Yves Berquin.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
/*
* THIS FILE WAS ALTERED BY Tyge Lovset, 7. April 2005.
*
* - completely rewritten. compact, clean, and fast implementation.
* - sizeof(TiXmlString) = pointer size (4 bytes on 32-bit systems)
* - fixed reserve() to work as per specification.
* - fixed buggy compares operator==(), operator<(), and operator>()
* - fixed operator+=() to take a const ref argument, following spec.
* - added "copy" constructor with length, and most compare operators.
* - added swap(), clear(), size(), capacity(), operator+().
*/
#ifdef _WINDOWS
#ifdef LIBYXML_EXPORTS
#define YXML_API __declspec(dllexport)
#else
#ifndef LIBYXML_STATIC
#define YXML_API __declspec(dllimport)
#endif
#endif
#endif /* _WINDOWS */
#ifndef YXML_API
#define YXML_API
#endif
/**
* Holds all Telephony Engine related classes.
*/
namespace TelEngine {
#ifndef TIXML_USE_STL
#ifndef TIXML_STRING_INCLUDED
#define TIXML_STRING_INCLUDED
#include <assert.h>
#include <string.h>
/* The support for explicit isn't that universal, and it isn't really
required - it is used to check that the TiXmlString class isn't incorrectly
used. Be nice to old compilers and macro it here:
*/
#if defined(_MSC_VER) && (_MSC_VER >= 1200 )
// Microsoft visual studio, version 6 and higher.
#define TIXML_EXPLICIT explicit
#elif defined(__GNUC__) && (__GNUC__ >= 3 )
// GCC version 3 and higher.s
#define TIXML_EXPLICIT explicit
#else
#define TIXML_EXPLICIT
#endif
/*
TiXmlString is an emulation of a subset of the std::string template.
Its purpose is to allow compiling TinyXML on compilers with no or poor STL support.
Only the member functions relevant to the TinyXML project have been implemented.
The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase
a string and there's no more room, we allocate a buffer twice as big as we need.
*/
class YXML_API TiXmlString
{
public :
// The size type used
typedef size_t size_type;
// Error value for find primitive
static const size_type npos; // = -1;
// TiXmlString empty constructor
TiXmlString () : rep_(&nullrep_)
{
}
// TiXmlString copy constructor
TiXmlString ( const TiXmlString & copy)
{
init(copy.length());
memcpy(start(), copy.data(), length());
}
// TiXmlString constructor, based on a string
TIXML_EXPLICIT TiXmlString ( const char * copy)
{
init( static_cast<size_type>( strlen(copy) ));
memcpy(start(), copy, length());
}
// TiXmlString constructor, based on a string
TIXML_EXPLICIT TiXmlString ( const char * str, size_type len)
{
init(len);
memcpy(start(), str, len);
}
// TiXmlString destructor
~TiXmlString ()
{
quit();
}
// = operator
TiXmlString& operator = (const char * copy)
{
return assign( copy, (size_type)strlen(copy));
}
// = operator
TiXmlString& operator = (const TiXmlString & copy)
{
return assign(copy.start(), copy.length());
}
// += operator. Maps to append
TiXmlString& operator += (const char * suffix)
{
return append(suffix, static_cast<size_type>( strlen(suffix) ));
}
// += operator. Maps to append
TiXmlString& operator += (char single)
{
return append(&single, 1);
}
// += operator. Maps to append
TiXmlString& operator += (const TiXmlString & suffix)
{
return append(suffix.data(), suffix.length());
}
// Convert a TiXmlString into a null-terminated char *
const char * c_str () const { return rep_->str; }
// Convert a TiXmlString into a char * (need not be null terminated).
const char * data () const { return rep_->str; }
// Return the length of a TiXmlString
size_type length () const { return rep_->size; }
// Alias for length()
size_type size () const { return rep_->size; }
// Checks if a TiXmlString is empty
bool empty () const { return rep_->size == 0; }
// Return capacity of string
size_type capacity () const { return rep_->capacity; }
// single char extraction
const char& at (size_type index) const
{
assert( index < length() );
return rep_->str[ index ];
}
// [] operator
char& operator [] (size_type index) const
{
assert( index < length() );
return rep_->str[ index ];
}
// find a char in a string. Return TiXmlString::npos if not found
size_type find (char lookup) const
{
return find(lookup, 0);
}
// find a char in a string from an offset. Return TiXmlString::npos if not found
size_type find (char tofind, size_type offset) const
{
if (offset >= length()) return npos;
for (const char* p = c_str() + offset; *p != '\0'; ++p)
{
if (*p == tofind) return static_cast< size_type >( p - c_str() );
}
return npos;
}
void clear ()
{
//Lee:
//The original was just too strange, though correct:
// TiXmlString().swap(*this);
//Instead use the quit & re-init:
quit();
init(0,0);
}
/* Function to reserve a big amount of data when we know we'll need it. Be aware that this
function DOES NOT clear the content of the TiXmlString if any exists.
*/
void reserve (size_type cap);
TiXmlString& assign (const char* str, size_type len);
TiXmlString& append (const char* str, size_type len);
void swap (TiXmlString& other)
{
Rep* r = rep_;
rep_ = other.rep_;
other.rep_ = r;
}
private:
void init(size_type sz) { init(sz, sz); }
void set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\0'; }
char* start() const { return rep_->str; }
char* finish() const { return rep_->str + rep_->size; }
struct Rep
{
size_type size, capacity;
char str[1];
};
void init(size_type sz, size_type cap)
{
if (cap)
{
// Lee: the original form:
// rep_ = static_cast<Rep*>(operator new(sizeof(Rep) + cap));
// doesn't work in some cases of new being overloaded. Switching
// to the normal allocation, although use an 'int' for systems
// that are overly picky about structure alignment.
const size_type bytesNeeded = sizeof(Rep) + cap;
const size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int );
rep_ = reinterpret_cast<Rep*>( new int[ intsNeeded ] );
rep_->str[ rep_->size = sz ] = '\0';
rep_->capacity = cap;
}
else
{
rep_ = &nullrep_;
}
}
void quit()
{
if (rep_ != &nullrep_)
{
// The rep_ is really an array of ints. (see the allocator, above).
// Cast it back before delete, so the compiler won't incorrectly call destructors.
delete [] ( reinterpret_cast<int*>( rep_ ) );
}
}
Rep * rep_;
static Rep nullrep_;
} ;
inline bool operator == (const TiXmlString & a, const TiXmlString & b)
{
return ( a.length() == b.length() ) // optimization on some platforms
&& ( strcmp(a.c_str(), b.c_str()) == 0 ); // actual compare
}
inline bool operator < (const TiXmlString & a, const TiXmlString & b)
{
return strcmp(a.c_str(), b.c_str()) < 0;
}
inline bool operator != (const TiXmlString & a, const TiXmlString & b) { return !(a == b); }
inline bool operator > (const TiXmlString & a, const TiXmlString & b) { return b < a; }
inline bool operator <= (const TiXmlString & a, const TiXmlString & b) { return !(b < a); }
inline bool operator >= (const TiXmlString & a, const TiXmlString & b) { return !(a < b); }
inline bool operator == (const TiXmlString & a, const char* b) { return strcmp(a.c_str(), b) == 0; }
inline bool operator == (const char* a, const TiXmlString & b) { return b == a; }
inline bool operator != (const TiXmlString & a, const char* b) { return !(a == b); }
inline bool operator != (const char* a, const TiXmlString & b) { return !(b == a); }
TiXmlString operator + (const TiXmlString & a, const TiXmlString & b);
TiXmlString operator + (const TiXmlString & a, const char* b);
TiXmlString operator + (const char* a, const TiXmlString & b);
/*
TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString.
Only the operators that we need for TinyXML have been developped.
*/
class YXML_API TiXmlOutStream : public TiXmlString
{
public :
// TiXmlOutStream << operator.
TiXmlOutStream & operator << (const TiXmlString & in)
{
*this += in;
return *this;
}
// TiXmlOutStream << operator.
TiXmlOutStream & operator << (const char * in)
{
*this += in;
return *this;
}
};
}; // TelEngine namespace
#endif // TIXML_STRING_INCLUDED
#endif // TIXML_USE_STL

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1609
libs/yxml/yatexml.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@ PROGS := cdrbuild.yate cdrfile.yate regexroute.yate \
yrtpchan.yate ystunchan.yate \
ysipchan.yate \
yiaxchan.yate \
yjinglechan.yate jingle/jinglefeatures.yate \
yjinglechan.yate jabber/jabberserver.yate jabber/jbfeatures.yate \
ysockschan.yate filetransfer.yate \
server/pbxassist.yate server/dbpbx.yate server/lateroute.yate \
server/park.yate server/queues.yate server/queuesnotify.yate \
@ -44,7 +44,10 @@ PROGS := cdrbuild.yate cdrfile.yate regexroute.yate \
server/mrcpspeech.yate \
server/ysigchan.yate \
server/ciscosm.yate \
server/presence.yate server/subscription.yate \
server/users.yate \
server/analog.yate server/analogdetect.yate \
client/jabberclient.yate \
callgen.yate analyzer.yate rmanager.yate msgsniff.yate
LIBS :=
DIRS := client server
@ -243,9 +246,9 @@ yiaxchan.yate: ../libs/yiax/libyateiax.a
yiaxchan.yate: LOCALFLAGS = -I@top_srcdir@/libs/yiax
yiaxchan.yate: LOCALLIBS = -L../libs/yiax -lyateiax
yjinglechan.yate jingle/jinglefeatures.yate: ../libyatejingle.so
yjinglechan.yate jingle/jinglefeatures.yate: LOCALFLAGS = -I@top_srcdir@/libs/yxml -I@top_srcdir@/libs/yjingle
yjinglechan.yate jingle/jinglefeatures.yate: LOCALLIBS = -lyatejingle
yjinglechan.yate jabber/jabberserver.yate jabber/jbfeatures.yate client/jabberclient.yate: ../libyatejabber.so
yjinglechan.yate jabber/jabberserver.yate jabber/jbfeatures.yate client/jabberclient.yate: LOCALFLAGS = -I@top_srcdir@/libs/yxml -I@top_srcdir@/libs/yjabber
yjinglechan.yate jabber/jabberserver.yate jabber/jbfeatures.yate client/jabberclient.yate: LOCALLIBS = -lyatejabber
server/dbpbx.yate server/pbxassist.yate: ../libs/ypbx/libyatepbx.a
server/dbpbx.yate server/pbxassist.yate: LOCALFLAGS = -I@top_srcdir@/libs/ypbx
@ -320,8 +323,8 @@ qt4/updater.yate: LOCALLIBS = @QT4_LIB_NET@
../libs/yxml/libyatexml.a:
$(MAKE) -C ../libs/yxml
../libyatejingle.so ../libs/yjingle/libyatejingle.a:
$(MAKE) -C ../libs/yjingle
../libyatejabber.so ../libs/yjabber/libyatejabber.a:
$(MAKE) -C ../libs/yjabber
../libs/ypbx/libyatepbx.a:
$(MAKE) -C ../libs/ypbx

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,821 @@
/**
* .cpp
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <yatephone.h>
#include <yatejabber.h>
// TODO:
// implement roster group and name max length when setting it from protocol
using namespace TelEngine;
namespace { // anonymous
class JBFeaturesModule; // The module
/*
* The module
*/
class JBFeaturesModule : public Module
{
public:
enum PrivateRelay {
JabberFeature = Private,
UserUpdate = Private << 1,
};
JBFeaturesModule();
virtual ~JBFeaturesModule();
virtual void initialize();
// Check if a message was sent by us
inline bool isModule(const Message& msg) const {
String* module = msg.getParam("module");
return module && *module == name();
}
// Handle 'jabber.feature' roster management
// RFC 3921
bool handleFeatureRoster(JabberID& from, Message& msg);
// Handle 'jabber.feature' private data get/set
// XEP-0049 Private XML storage
bool handleFeaturePrivateData(JabberID& from, Message& msg);
// Handle 'jabber.feature' vcard get/set
// XEP-0054 vcard-temp
bool handleFeatureVCard(JabberID& from, Message& msg);
// Handle 'jabber.feature' offline message get/add
bool handleFeatureMsgOffline(JabberID& from, Message& msg);
// Handle 'jabber.feature' in-band register get/set
// XEP-0077 In-Band Registration
bool handleFeatureRegister(JabberID& from, Message& msg);
protected:
virtual bool received(Message& msg, int id);
// Build and dispatch or enqueue a 'database' message
// Return 0 when the message is enqueued or dispatch failure
Message* queryDb(const NamedList& params, const String& account,
const String& query, bool sync = true);
private:
bool m_init; // Module already initialized
String m_defAccount; // Default database account
String m_vcardAccount; // Database vcard account
String m_vcardQueryGet; // vcard 'get' query
String m_vcardQuerySet; // vcard 'set' item query
String m_vcardQueryDel; // vcard 'delete' item query
String m_dataAccount; // Database private data account
String m_dataQueryGet; // Private data 'get' query
String m_dataQuerySet; // Private data 'set' query
String m_dataQueryDel; // Private data 'delete' query
// Offline messages
unsigned int m_maxChatCount; // Maximum number of chat messages to store
unsigned int m_nextCheck; // The next time (in seconds) to run the expire query
unsigned int m_expire; // Chat expiring interval (in seconds)
String m_chatAccount; // Database offline messages account
String m_chatQueryExpire; // Offline messages expire query
String m_chatQueryGet; // Offline messages 'get' query
String m_chatQueryAdd; // Offline messages 'add' query
String m_chatQueryDel; // Offline messages 'delete' query
// In-band user register (XEP-0077)
bool m_regEnable; // Enable user (un)register
bool m_regChange; // Enable user changes (such as password)
bool m_regAllowUnsecure; // Allow user registration support on unsecured streams
String m_regUrl; // URL to send to the user when creation is disabled
String m_regInfo; // Instructions to send along with the url
};
/*
* Local data
*/
INIT_PLUGIN(JBFeaturesModule); // The module
// Return a safe pointer to config section
static inline const NamedList* getSection(Configuration& cfg, const char* name)
{
NamedList* sect = cfg.getSection(name);
if (sect)
return sect;
return &NamedList::empty();
}
// Add a 'subscription' and, optionally, an 'ask' attribute to a roster item
static inline void addSubscription(XmlElement& dest, const String& sub)
{
XMPPDirVal d(sub);
if (d.test(XMPPDirVal::PendingOut))
dest.setAttribute("ask","subscribe");
String tmp;
d.toSubscription(tmp);
dest.setAttribute("subscription",tmp);
}
// Build a roster item XML element from message parameters
static XmlElement* buildRosterItem(NamedList& list, unsigned int index)
{
String prefix("contact.");
prefix << index;
const char* contact = list.getValue(prefix);
XDebug(&__plugin,DebugAll,"buildRosterItem(%s,%u) contact=%s",
list.c_str(),index,contact);
if (TelEngine::null(contact))
return 0;
XmlElement* item = new XmlElement("item");
item->setAttribute("jid",contact);
prefix << ".";
ObjList* groups = 0;
unsigned int n = list.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* param = list.getParam(i);
if (!(param && param->name().startsWith(prefix)))
continue;
String name = param->name();
name.startSkip(prefix,false);
if (name == "name")
item->setAttribute("name",*param);
else if (name == "subscription")
addSubscription(*item,*param);
else if (name == "groups") {
if (!groups)
groups = param->split(',',false);
}
else
item->addChild(XMPPUtils::createElement(name,*param));
}
if (!item->getAttribute("subscription"))
addSubscription(*item,String::empty());
for (ObjList* o = groups ? groups->skipNull() : 0; o; o = o->skipNext()) {
String* grp = static_cast<String*>(o->get());
item->addChild(XMPPUtils::createElement("group",*grp));
}
TelEngine::destruct(groups);
return item;
}
// Build a result and set it to a message parameter
// Release the given xml pointer and zero it
// Return true
static bool buildResult(Message& msg, XmlElement*& xml, XmlElement* child = 0)
{
const char* id = xml ? xml->attribute("id") : 0;
XmlElement* rsp = XMPPUtils::createIqResult(0,0,id);
TelEngine::destruct(xml);
if (child)
rsp->addChild(child);
msg.setParam(new NamedPointer("response",rsp));
return true;
}
// Build an error and set it to a message parameter
// Release the given xml pointer and zero it
// Return false
static bool buildError(Message& msg, XmlElement*& xml,
XMPPError::Type error = XMPPError::ServiceUnavailable,
XMPPError::ErrorType type = XMPPError::TypeModify)
{
const char* id = xml ? xml->attribute("id") : 0;
XmlElement* rsp = XMPPUtils::createIq(XMPPUtils::IqError,0,0,id);
if (TelEngine::null(id) && xml) {
rsp->addChild(xml);
xml = 0;
}
else
TelEngine::destruct(xml);
rsp->addChild(XMPPUtils::createError(type,error));
msg.setParam(new NamedPointer("response",rsp));
return false;
}
// Add xml data to a list
static inline void addXmlData(NamedList& list, XmlElement* xml, const char* param = "xml")
{
String buf;
if (xml)
xml->toString(buf);
list.addParam(param,buf);
}
/*
* JBFeaturesModule
*/
// Early load, late unload
JBFeaturesModule::JBFeaturesModule()
: Module("jbfeatures","misc",true),
m_init(false),
m_maxChatCount(0), m_nextCheck(0), m_expire(0),
m_regEnable(true), m_regChange(true), m_regAllowUnsecure(false)
{
Output("Loaded module Jabber Server Features");
}
JBFeaturesModule::~JBFeaturesModule()
{
Output("Unloading module Jabber Server Features");
}
void JBFeaturesModule::initialize()
{
Output("Initializing module Jabber Server Features");
Configuration cfg(Engine::configFile("jbfeatures"));
NamedList dummy("");
const NamedList* reg = getSection(cfg,"register");
m_regEnable = reg->getBoolValue("allow_management",true);
m_regChange = reg->getBoolValue("allow_change",true);
m_regAllowUnsecure = reg->getBoolValue("allow_unsecure",false);
m_regUrl = reg->getValue("url");
m_regInfo = reg->getValue("intructions");
// TODO: Notify feature XMPPNamespace::Register to the jabber server
const NamedList* offlinechat = getSection(cfg,"offline_chat");
int tmp = offlinechat->getIntValue("maxcount");
m_maxChatCount = tmp > 0 ? tmp : 0;
tmp = offlinechat->getIntValue("expires");
if (tmp < 0)
tmp = 0;
else if (tmp && tmp < 30)
tmp = 30;
m_expire = tmp * 60;
if (m_expire) {
if (!m_nextCheck)
m_nextCheck = Time::secNow();
}
else
m_nextCheck = 0;
if (m_init)
return;
m_init = true;
const NamedList* general = getSection(cfg,"general");
m_defAccount = general->getValue("account");
const NamedList* vcard = getSection(cfg,"vcard");
m_vcardAccount = vcard->getValue("account");
m_vcardQueryGet = vcard->getValue("get");
m_vcardQuerySet = vcard->getValue("set");
m_vcardQueryDel = vcard->getValue("clear_user");
const NamedList* pdata = getSection(cfg,"private_data");
m_dataAccount = pdata->getValue("account");
m_dataQueryGet = pdata->getValue("get");
m_dataQuerySet = pdata->getValue("set");
m_dataQueryDel = pdata->getValue("clear_user");
m_chatAccount = offlinechat->getValue("account");
m_chatQueryExpire = offlinechat->getValue("expire_query");
m_chatQueryGet = offlinechat->getValue("get");
m_chatQueryAdd = offlinechat->getValue("add");
m_chatQueryDel = offlinechat->getValue("clear_user");
setup();
installRelay(Halt);
installRelay(JabberFeature,"jabber.feature");
installRelay(UserUpdate,"user.update");
}
// Handle 'jabber.feature' roster management
// RFC 3921
bool JBFeaturesModule::handleFeatureRoster(JabberID& from, Message& msg)
{
XmlElement* xml = XMPPUtils::getXml(msg);
DDebug(this,DebugAll,"handleFeatureRoster() from=%s xml=%p",from.c_str(),xml);
if (!xml)
return false;
// Ignore responses
XMPPUtils::IqType t = XMPPUtils::iqType(xml->attribute("type"));
if (t != XMPPUtils::IqGet && t != XMPPUtils::IqSet) {
TelEngine::destruct(xml);
return false;
}
// The client must add it's resource in request
if (!from.resource())
return buildError(msg,xml);
// The request must be carried by a 'query' tag
XmlElement* child = xml->findFirstChild();
if (!(child && XMPPUtils::isUnprefTag(*child,XmlTag::Query)))
return buildError(msg,xml);
JabberID contact;
bool get = (t == XMPPUtils::IqGet);
bool set = !get;
if (!get) {
// Set/remove contact: check jid
// Don't allow user to operate on itself
XmlElement* item = XMPPUtils::findFirstChild(*child,XmlTag::Item,XMPPNamespace::Roster);
if (item) {
contact = item->getAttribute("jid");
String* sub = item->getAttribute("subscription");
set = !sub || *sub != "remove";
}
if (!contact)
return buildError(msg,xml,XMPPError::BadRequest);
else if (contact.bare() == from.bare())
return buildError(msg,xml,XMPPError::NotAllowed);
}
Message m("user.roster");
m.addParam("module",name());
m.addParam("operation",set ? "update" : (get ? "query": "delete"));
m.addParam("username",from.bare());
if (!get) {
m.addParam("contact",contact.bare());
if (set) {
// We already found the item
XmlElement* item = XMPPUtils::findFirstChild(*child,XmlTag::Item,XMPPNamespace::Roster);
NamedString* params = new NamedString("contact.parameters","name,groups");
m.addParam(params);
m.addParam("name",item->attribute("name"));
NamedString* groups = new NamedString("groups");
m.addParam(groups);
// Groups and other children
const String* ns = &XMPPUtils::s_ns[XMPPNamespace::Roster];
for (XmlElement* c = item->findFirstChild(0,ns); c; c = item->findNextChild(c,0,ns)) {
if (XMPPUtils::isUnprefTag(*c,XmlTag::Group))
groups->append(c->getText(),",");
else {
params->append(c->tag(),",");
m.addParam(c->tag(),c->getText());
}
}
}
}
if (Engine::dispatch(m)) {
DDebug(this,DebugAll,"Roster '%s' accepted user='%s'",
m.getValue("operation"),m.getValue("username"));
XmlElement* child = 0;
if (get) {
unsigned int n = m.getIntValue("contact.count");
child = XMPPUtils::createElement(XmlTag::Query,XMPPNamespace::Roster);
for (unsigned int i = 1; i <= n; i++)
child->addChild(buildRosterItem(m,i));
}
return buildResult(msg,xml,child);
}
if (m.getParam("error"))
return buildError(msg,xml,XMPPError::ItemNotFound);
return buildError(msg,xml);
}
// Handle 'jabber.feature' private data get/set
// XEP-0049 Private XML storage
bool JBFeaturesModule::handleFeaturePrivateData(JabberID& from, Message& msg)
{
XmlElement* xml = XMPPUtils::getXml(msg);
DDebug(this,DebugAll,"handleFeaturePrivateData() from=%s xml=%p",from.c_str(),xml);
if (!xml)
return false;
// Ignore responses
XMPPUtils::IqType t = XMPPUtils::iqType(xml->attribute("type"));
if (t != XMPPUtils::IqGet && t != XMPPUtils::IqSet) {
TelEngine::destruct(xml);
return false;
}
// The request must be carried by a 'query' tag
XmlElement* child = xml->findFirstChild();
if (!(child && XMPPUtils::isUnprefTag(*child,XmlTag::Query)))
return buildError(msg,xml);
// XEP-0049 2.3:
// At least one child with a valid namespace must exist
// Iq 'set' may contain more then 1 child qualified by the same namespace
XmlElement* ch = child->findFirstChild();
String* ns = ch ? ch->xmlns() : 0;
if (TelEngine::null(ns))
return buildError(msg,xml,XMPPError::BadFormat);
// TODO handle special jabber:iq:private requests:
// storage:imprefs (seen from Exodus)
// storage:bookmarks (XEP-0048 Bookmark storage)
// storage:metacontacts (seen from Gajim)
// storage:rosternotes (seen from Gajim)
// Handle 'get'
if (t == XMPPUtils::IqGet) {
String tag(ch->tag());
// We should have only 1 child
ch = child->findNextChild(ch);
if (ch)
return buildError(msg,xml,XMPPError::NotAcceptable);
NamedList p("");
p.addParam("username",from.bare());
p.addParam("tag",tag);
p.addParam("xmlns",*ns);
Message* m = queryDb(p,m_dataAccount,m_dataQueryGet);
XmlElement* query = XMPPUtils::createElement(XmlTag::Query,
XMPPNamespace::IqPrivate);
XmlElement* pdata = 0;
if (m) {
Array* a = static_cast<Array*>(m->userObject("Array"));
String* data = a ? YOBJECT(String,a->get(0,1)) : 0;
pdata = data ? XMPPUtils::getXml(*data) : 0;
if (pdata) {
// Avoid sending inconsistent tag or namespace
if (tag != pdata->toString() || *ns != pdata->xmlns()) {
Debug(this,DebugMild,
"User %s got invalid private data tag/ns='%s'/'%s' instead of '%s'/'%s'",
from.bare().c_str(),pdata->tag(),
TelEngine::c_safe(pdata->xmlns()),tag.c_str(),ns->c_str());
TelEngine::destruct(pdata);
}
}
else if (data)
Debug(this,DebugMild,"User %s got invalid xml private data",from.bare().c_str());
TelEngine::destruct(m);
}
if (!pdata)
pdata = XMPPUtils::createElement(tag,*ns);
query->addChild(pdata);
return buildResult(msg,xml,query);
}
// Handle 'set'
// All children must share the same namespace
for (; ch; ch = child->findNextChild(ch))
if (*ns != ch->xmlns())
return buildError(msg,xml,XMPPError::NotAcceptable);
// Update all data. Return error if at least one item fails or there is no data
for (ch = child->findFirstChild(); ch; ch = child->findNextChild(ch)) {
XDebug(this,DebugAll,"Setting private data for '%s' tag=%s xmlns=%s",
from.bare().c_str(),ch->tag(),ns->c_str());
NamedList p("");
p.addParam("username",from.bare());
p.addParam("tag",ch->tag());
p.addParam("xmlns",*ns);
addXmlData(p,ch);
Message* m = queryDb(p,m_dataAccount,m_dataQuerySet);
if (!m)
break;
TelEngine::destruct(m);
}
if (!ch)
return buildResult(msg,xml);
return buildError(msg,xml);
}
// Handle 'jabber.feature' vcard get/set
// XEP-0054 vcard-temp
bool JBFeaturesModule::handleFeatureVCard(JabberID& from, Message& msg)
{
JabberID to(msg.getValue("to"));
XmlElement* xml = XMPPUtils::getXml(msg);
DDebug(this,DebugAll,"handleFeatureVCard() from=%s to=%s xml=%p",
from.c_str(),to.c_str(),xml);
if (!xml)
return false;
// Ignore responses
XMPPUtils::IqType t = XMPPUtils::iqType(xml->attribute("type"));
if (t != XMPPUtils::IqGet && t != XMPPUtils::IqSet) {
TelEngine::destruct(xml);
return false;
}
NamedList p("");
bool otherUser = (to && to.bare() != from.bare());
if (otherUser) {
// Check auth
Message auth("resource.subscribe");
auth.addParam("operation","query");
auth.addParam("subscriber",from.bare());
auth.addParam("notifier",to.bare());
if (!Engine::dispatch(auth))
return buildError(msg,xml);
p.addParam("username",to.bare());
}
else
p.addParam("username",from.bare());
Message* m = 0;
if (t == XMPPUtils::IqGet)
m = queryDb(p,m_vcardAccount,m_vcardQueryGet);
else {
XmlElement* vcard = xml->findFirstChild();
addXmlData(p,vcard,"vcard");
m = queryDb(p,m_vcardAccount,m_vcardQuerySet);
}
// Don't return error on failure if the user requested its vcard
if (!m && (otherUser || t == XMPPUtils::IqSet))
return buildError(msg,xml);
XmlElement* vcard = 0;
if (t == XMPPUtils::IqGet && m) {
Array* a = static_cast<Array*>(m->userObject("Array"));
if (a) {
String* vc = YOBJECT(String,a->get(0,1));
XDebug(this,DebugInfo,"Got vcard for '%s': '%s'",p.getValue("username"),
TelEngine::c_safe(vc));
if (!TelEngine::null(vc)) {
vcard = XMPPUtils::getXml(*vc);
if (vcard) {
// Avoid sending inconsistent tag
if (!XMPPUtils::isTag(*vcard,XmlTag::VCard,XMPPNamespace::VCard)) {
Debug(this,DebugMild,"Wrong vcard tag='%s' or ns='%s' for '%s'",
vcard->tag(),TelEngine::c_safe(vcard->xmlns()),p.getValue("username"));
TelEngine::destruct(vcard);
}
}
else
Debug(this,DebugMild,"Failed to parse vcard for '%s'",p.getValue("username"));
}
}
}
if (!vcard && t == XMPPUtils::IqGet)
vcard = XMPPUtils::createElement(XmlTag::VCard,XMPPNamespace::VCard);
TelEngine::destruct(m);
return buildResult(msg,xml,vcard);
}
// Handle 'jabber.feature' offline message get/add
bool JBFeaturesModule::handleFeatureMsgOffline(JabberID& from, Message& msg)
{
String* oper = msg.getParam("operation");
DDebug(this,DebugAll,"handleFeatureMsgOffline() oper=%s",TelEngine::c_safe(oper));
if (!oper || *oper == "add") {
// Store offline message
JabberID user(msg.getValue("to"));
if (!(user && user.valid()))
return false;
XmlElement* xml = XMPPUtils::getXml(msg);
if (!xml)
return false;
XMPPUtils::MsgType t = XMPPUtils::msgType(xml->attribute("type"));
const String& body = XMPPUtils::body(*xml);
bool ok = body && (t == XMPPUtils::Normal || t == XMPPUtils::Chat);
if (ok) {
xml->removeAttribute("to");
NamedList p("");
p.addParam("username",user.bare());
addXmlData(p,xml);
const char* time = msg.getValue("time");
if (!TelEngine::null(time))
p.addParam("time",time);
else
p.addParam("time",String(msg.msgTime().sec()));
p.addParam("maxcount",String(m_maxChatCount));
Message* m = queryDb(p,m_chatAccount,m_chatQueryAdd);
if (m) {
Array* a = static_cast<Array*>(m->userObject("Array"));
String* res = a ? YOBJECT(String,a->get(0,1)) : 0;
if (res) {
DDebug(this,DebugAll,"Got result %s to add chat",res->c_str());
ok = (res->toInteger() != 0);
}
else
ok = false;
}
else
ok = false;
TelEngine::destruct(m);
}
if (ok)
TelEngine::destruct(xml);
else
msg.setParam(new NamedPointer("xml",xml));
return ok;
}
if (*oper == "query") {
// Retrieve offline messages
NamedList p("");
p.addParam("username",from.bare());
Message* m = queryDb(p,m_chatAccount,m_chatQueryGet);
if (!m)
return false;
Array* a = static_cast<Array*>(m->userObject("Array"));
int rows = a ? a->getRows() : 0;
int cols = a ? a->getColumns() : 0;
DDebug(this,DebugAll,"Got %d offline messages for user =%s",
rows ? rows - 1 : 0,from.bare().c_str());
for (int row = 1; row < rows; row++) {
String* data = 0;
String* time = 0;
for (int col = 0; col < cols; col++) {
String* s = YOBJECT(String,a->get(col,0));
if (!s)
continue;
if (*s == "xml")
data = YOBJECT(String,a->get(col,row));
else if (*s == "time")
time = YOBJECT(String,a->get(col,row));
}
if (TelEngine::null(data))
continue;
XmlElement* xml = XMPPUtils::getXml(*data);
if (xml) {
if (!TelEngine::null(time))
xml->addChild(XMPPUtils::createDelay(time->toInteger()));
msg.addParam(new NamedPointer("xml",xml));
continue;
}
Debug(this,DebugMild,"Invalid database offline chat xml for user=%s",
from.bare().c_str());
}
return true;
}
if (*oper == "delete") {
// Remove user's offline messages
NamedList p("");
p.addParam("username",from.bare());
queryDb(p,m_chatAccount,m_chatQueryDel,false);
return true;
}
return false;
}
// Handle 'jabber.feature' in-band register get/set
// XEP-0077 In-Band Registration
bool JBFeaturesModule::handleFeatureRegister(JabberID& from, Message& msg)
{
XmlElement* xml = XMPPUtils::getXml(msg);
DDebug(this,DebugAll,"handleFeatureRegister() from=%s xml=%p",from.c_str(),xml);
if (!xml)
return false;
// Ignore responses
XMPPUtils::IqType t = XMPPUtils::iqType(xml->attribute("type"));
if (t != XMPPUtils::IqGet && t != XMPPUtils::IqSet) {
TelEngine::destruct(xml);
return false;
}
// Handle 'query' elements only
XmlElement* child = xml->findFirstChild();
if (!(child && XMPPUtils::isUnprefTag(*child,XmlTag::Query)))
return buildError(msg,xml);
// Registration available only on secured streams
int flags = msg.getIntValue("stream_flags");
if (!(m_regAllowUnsecure || 0 != (flags & JBStream::StreamTls)))
return buildError(msg,xml,XMPPError::EncryptionRequired);
// Set auth or remove the user
if (t == XMPPUtils::IqSet) {
const char* oper = 0;
bool remove = XMPPUtils::remove(*child);
JabberID user;
if (0 == (flags & JBStream::StreamAuthenticated)) {
if (!m_regEnable || remove)
return buildError(msg,xml);
XmlElement* tmp = XMPPUtils::findFirstChild(*child,XmlTag::Username,
XMPPNamespace::IqRegister);
const String& username = tmp ? tmp->getText() : String::empty();
if (!username)
return buildError(msg,xml,XMPPError::BadRequest);
const char* domain = msg.getValue("stream_domain");
if (TelEngine::null(domain))
return buildError(msg,xml,XMPPError::BadRequest);
oper = "add";
user.set(username,domain);
}
else {
if (!remove) {
if (m_regChange)
oper = "update";
else
return buildError(msg,xml);
}
else if (m_regEnable)
oper = "delete";
else
return buildError(msg,xml);
user.set(from.node(),from.domain());
}
// Update the user
Message m("user.update");
m.addParam("module",name());
m.addParam("operation",oper);
m.addParam("user",user);
if (!remove) {
XmlElement* p = XMPPUtils::findFirstChild(*child,XmlTag::Password,
XMPPNamespace::IqRegister);
const String& pwd = p ? p->getText() : String::empty();
if (!pwd)
return buildError(msg,xml,XMPPError::BadRequest);
m.addParam("password",pwd);
}
if (Engine::dispatch(m))
return buildResult(msg,xml);
return buildError(msg,xml,XMPPError::NotAllowed);
}
// Get auth
XmlElement* query = 0;
if (m_regEnable) {
query = XMPPUtils::createElement(XmlTag::Query,
XMPPNamespace::IqRegister);
if (0 == (flags & JBStream::StreamAuthenticated)) {
query->addChild(XMPPUtils::createElement(XmlTag::Username));
query->addChild(XMPPUtils::createElement(XmlTag::Password));
}
else
query->addChild(XMPPUtils::createElement(XmlTag::Registered));
}
else if (m_regUrl) {
// XEP-0077 Section 5 Redirection
query = XMPPUtils::createElement(XmlTag::Query,XMPPNamespace::IqRegister);
if (m_regInfo)
query->addChild(XMPPUtils::createElement("instructions",m_regInfo));
query->addChild(XMPPUtils::createXOobUrl(m_regUrl));
}
if (query)
return buildResult(msg,xml,query);
return buildError(msg,xml);
}
// Message handler
bool JBFeaturesModule::received(Message& msg, int id)
{
if (id == JabberFeature) {
JabberID from(msg.getValue("from"));
switch (XMPPUtils::s_ns[msg.getValue("feature")]) {
case XMPPNamespace::VCard:
return from && handleFeatureVCard(from,msg);
case XMPPNamespace::Roster:
return from && handleFeatureRoster(from,msg);
case XMPPNamespace::IqPrivate:
return from && handleFeaturePrivateData(from,msg);
case XMPPNamespace::MsgOffline:
return from && handleFeatureMsgOffline(from,msg);
case XMPPNamespace::IqRegister:
return handleFeatureRegister(from,msg);
default: ;
}
return false;
}
if (id == UserUpdate) {
// Handle user deletion: remove vcard, private data, offline messages ...
String* notif = msg.getParam("notify");
if (TelEngine::null(notif) || *notif != "delete")
return false;
String* user = msg.getParam("user");
if (TelEngine::null(user))
return false;
DDebug(this,DebugAll,
"User '%s' deleted: removing vcard, private data, offline messages",
user->c_str());
NamedList p("");
p.addParam("username",*user);
queryDb(p,m_vcardAccount,m_vcardQueryDel,false);
queryDb(p,m_dataAccount,m_dataQueryDel,false);
queryDb(p,m_chatAccount,m_chatQueryDel,false);
return false;
}
if (id == Timer) {
unsigned int sec = msg.msgTime().sec();
if (m_nextCheck && m_nextCheck < sec) {
if (m_expire && m_chatQueryExpire) {
XDebug(this,DebugAll,"Running chat expire query");
NamedList p("");
p.addParam("time",String(sec));
queryDb(p,m_chatAccount,m_chatQueryExpire,false);
m_nextCheck = sec + m_expire / 2;
}
else
m_nextCheck = 0;
}
}
else if (id == Halt) {
uninstallRelays();
DDebug(this,DebugAll,"Halted");
}
return Module::received(msg,id);
}
// Build and dispatch or enqueue a 'database' message
Message* JBFeaturesModule::queryDb(const NamedList& params, const String& account,
const String& query, bool sync)
{
if (!((account || m_defAccount) && query))
return 0;
Message* m = new Message("database");
m->addParam("account",account ? account : m_defAccount);
String tmp = query;
params.replaceParams(tmp,true);
m->addParam("query",tmp);
if (sync) {
if (Engine::dispatch(m)) {
String* error = m->getParam("error");
if (error) {
DDebug(this,DebugNote,"'database' failed error='%s'",error->c_str());
TelEngine::destruct(m);
}
}
else
TelEngine::destruct(m);
}
else {
m->addParam("results",String::boolText(false));
Engine::enqueue(m);
m = 0;
}
return m;
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -1,833 +0,0 @@
/**
* jinglefeatures.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Additional XMPP features
*
* 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 <yatephone.h>
#include <yatejingle.h>
using namespace TelEngine;
namespace { // anonymous
/**
* NOTE:
* All responses sent to custom IQs set/get should carry the element
* with the custom application (namespace). This is nedded to match
* the application in the responses. Otherwise the module won't be
* able to detect responses to custom requests/notifications
*/
class CustomXmppIqMsg; // A custom message built from an xmpp.iq
class YJingleFeatures; // The module
// A custom message built from an xmpp.iq
// Send a response to received IQ after dispatched
class CustomXmppIqMsg : public Message
{
public:
// Params:
// iq The received IQ
// child The first child of the received IQ
CustomXmppIqMsg(Message& msg, XMLElement& iq, XMLElement& child);
protected:
virtual void dispatched(bool accepted);
private:
String m_app;
String m_oper;
};
// Features module
class YJingleFeatures : public Module
{
public:
enum PrivateRelay {
XmppIq = Private,
Custom = Private << 1,
UserInfo = Private << 2,
UserRoster = Private << 3,
};
YJingleFeatures();
virtual ~YJingleFeatures();
// Check if a message is sent by this module
inline bool isModule(Message& msg) {
NamedString* m = msg.getParam("module");
return m && *m == name();
}
// Build a message. Add the 'module' param
// Copy 'line' and/or 'account' parameters
inline Message* buildMsg(const char* msg, Message* line = 0) {
Message* m = new Message(msg);
m->addParam("module",name());
if (line)
m->copyParams(*line,"account,line");
return m;
}
// Inherited methods
virtual bool received(Message& msg, int id);
virtual void initialize();
// Message handlers
bool handleXmppIq(Message& msg);
bool handleCustom(Message& msg);
bool handleUserRoster(Message& msg);
bool handleUserInfo(Message& msg);
// Build and dispatch an "xmpp.generate" message. Copy error param on failure
// Consume the xml element
bool xmppGenerate(Message& recv, XMLElement* xml, bool rsp = false);
// Uninstall the relays
bool unload();
// Check if a custom application is handled by the module
inline bool isApplication(const String* app) {
if (!m_apps || null(app))
return false;
Lock lock(this);
return 0 != m_apps->find(*app);
}
// Build an XML element's children from a list
bool addChildren(NamedList& params, XMLElement& xml);
// Add an XML element's children to a list
// The element's name will be used as message-prefix
bool addChildren(XMLElement& xml, NamedList& params);
private:
// Check module and target parameters of a received message
// Optionally check the target parameter (if present, it must be one
// of the jingle module aliases)
bool acceptMsg(Message& msg, bool checkTarget);
// Handle dynamic roster data
bool handleXmppIqDynamicRoster(Message& msg, XMLElement& query, XMPPUtils::IqType t,
const JabberID& from, const JabberID& to, const String& id);
// Handle client private iq data
bool handleXmppIqPrivate(Message& msg, XMLElement& query, XMPPUtils::IqType t,
const JabberID& from, const JabberID& to, const String& id);
// Handle a valid vcard element
bool handleXmppIqVCard(Message& msg, XMLElement& vcard, XMPPUtils::IqType t,
const JabberID& from, const JabberID& to, const String& id);
ObjList* m_apps; // Custom applications
};
INIT_PLUGIN(YJingleFeatures);
static const String s_jingleAlias[] = {"jingle", "xmpp", "jabber", ""};
static bool s_handleVCard = true;
static bool s_handlePrivate = true;
static bool s_handleAddrbook = true;
// Strings used to compare or build other strings
static const String s_customPrefix = "custom.";
static const String s_group = "group";
static TokenDict s_customIqType[] = {
{"request", XMPPUtils::IqGet},
{"update", XMPPUtils::IqSet},
{"notify", XMPPUtils::IqResult},
{"error", XMPPUtils::IqError},
{0,0},
};
UNLOAD_PLUGIN(unloadNow)
{
if (unloadNow && !__plugin.unload())
return false;
return true;
}
// Check if a text is one of the jingle module's alias
static bool isJingleAlias(const String& name)
{
for (int i = 0; s_jingleAlias[i].length(); i++)
if (name == s_jingleAlias[i])
return true;
return false;
}
// Check if the message source is the jingle module
static inline bool isJingleMsg(Message& msg)
{
NamedString* module = msg.getParam("module");
return module && isJingleAlias(*module);
}
// Find an xml element's child text
static const char* getChildText(XMLElement& xml, const char* name,
XMLElement* start = 0)
{
XMLElement* child = xml.findNextChild(start,name);
const char* text = child ? child->getText() : 0;
TelEngine::destruct(child);
return text;
}
// Build an xml element error from error/reason parameters
static XMLElement* createXmlError(const Message& msg, const char* defaultText = 0)
{
XMPPError::ErrorType eType = XMPPError::TypeModify;
XMPPError::Type err = XMPPError::UndefinedCondition;
String* reason = msg.getParam("reason");
const char* error = msg.getValue("error",defaultText);
if (reason && *reason == "noauth") {
eType = XMPPError::TypeAuth;
err = XMPPError::NotAuthorized;
if (null(error))
error = "Not authorized";
}
return XMPPUtils::createError(eType,err,error);
}
/*
* CustomXmppIqMsg
*/
CustomXmppIqMsg::CustomXmppIqMsg(Message& msg, XMLElement& iq, XMLElement& child)
: Message("custom")
{
addParam("module",__plugin.name());
m_app = child.getAttribute("xmlns");
m_oper = child.name();
addParam("application",m_app);
addParam("operation",m_oper);
// Check for stanza failure (the stream failed to send a required element)
bool failure = msg.getBoolValue("failure");
if (failure) {
addParam("type","error");
addParam("reason","noconn");
addParam("error","Failed to send");
copyParams(msg,"account,line,username,id");
return;
}
// Process received element
const char* type = msg.getValue("type");
XMPPUtils::IqType iqType = XMPPUtils::iqType(type);
addParam("type",::lookup(iqType,s_customIqType,type));
copyParams(msg,"account,line,username,from,to,id");
bool needRsp = iqType == XMPPUtils::IqSet || iqType == XMPPUtils::IqGet;
addParam("need-response",String::boolText(needRsp));
// Build params if not error
if (iqType != XMPPUtils::IqError) {
unsigned int n = 1;
XMLElement* ch = child.findFirstChild();
for (; ch; ch = child.findNextChild(ch), n++)
ch->toList(*this,s_customPrefix + String(n));
}
else {
String err, errText;
XMPPUtils::decodeError(&iq,err,errText);
if (errText)
addParam("error",errText);
else if (err)
addParam("error",err);
}
}
void CustomXmppIqMsg::dispatched(bool accepted)
{
if (!getBoolValue("need-response"))
return;
XMPPUtils::IqType t = accepted ? XMPPUtils::IqResult : XMPPUtils::IqError;
XMLElement* iq = XMPPUtils::createIq(t,getValue("to"),getValue("from"),getValue("id"));
XMLElement* oper = new XMLElement(m_oper);
oper->setAttribute("xmlns",m_app);
// Check for result params
for (unsigned int n = 1; accepted; n++) {
String prefix;
prefix << "custom_out." << n;
if (TelEngine::null(getParam(prefix)))
break;
oper->addChild(new XMLElement(*this,prefix));
}
iq->addChild(oper);
if (!accepted)
iq->addChild(createXmlError(*this,"Unhandled message"));
__plugin.xmppGenerate(*this,iq,true);
}
/*
* YJingleFeatures
*/
YJingleFeatures::YJingleFeatures()
: Module("jinglefeatures","misc"),
m_apps(0)
{
Output("Loaded module Jingle Features");
m_apps = new ObjList;
}
YJingleFeatures::~YJingleFeatures()
{
Output("Unloading module Jingle Features");
TelEngine::destruct(m_apps);
}
bool YJingleFeatures::received(Message& msg, int id)
{
switch (id) {
case XmppIq:
return handleXmppIq(msg);
case Custom:
return handleCustom(msg);
case UserRoster:
return handleUserRoster(msg);
case UserInfo:
return handleUserInfo(msg);
case Halt:
unload();
break;
}
return Module::received(msg,id);
}
void YJingleFeatures::initialize()
{
Output("Initializing module Jingle Features");
Configuration cfg(Engine::configFile("jinglefeatures"));
cfg.load();
NamedList dummy("");
NamedList* general = cfg.getSection("general");
if (!general)
general = &dummy;
String apps = general->getValue("custom_applications");
TelEngine::destruct(m_apps);
m_apps = apps.split(',',false);
NamedList* iq = cfg.getSection("iq");
if (!iq)
iq = &dummy;
s_handleVCard = iq->getBoolValue("vcard",true);
s_handlePrivate = iq->getBoolValue("private",true);
s_handleAddrbook = iq->getBoolValue("addressbook",true);
if (debugAt(DebugAll)) {
String s;
s << "vcard=" << String::boolText(s_handleVCard);
s << " private=" << String::boolText(s_handlePrivate);
s << " addressbook=" << String::boolText(s_handleAddrbook);
s << " custom_applications=" << apps;
Debug(this,DebugAll,"Initialized %s",s.c_str());
}
lock();
static bool s_first = true;
if (s_first) {
s_first = false;
setup();
installRelay(XmppIq,"xmpp.iq",100);
installRelay(Custom,"custom",100);
}
if (s_handleVCard || s_handlePrivate)
installRelay(UserInfo,"user.info",100);
else
uninstallRelay(UserInfo);
if (s_handleAddrbook)
installRelay(UserRoster,"user.roster",100);
else
uninstallRelay(UserRoster);
unlock();
}
// xmpp.iq handler
bool YJingleFeatures::handleXmppIq(Message& msg)
{
if (!isJingleMsg(msg))
return false;
// No XML: nothing to be done
XMLElement* xml = XMLElement::getXml(msg,false);
if (!xml)
return false;
XMPPUtils::IqType t = XMPPUtils::iqType(msg.getValue("type"));
JabberID from = msg.getValue("from");
JabberID to = msg.getValue("to");
String id = msg.getValue("id");
Debug(this,DebugAll,"Processing '%s' from=%s to=%s id=%s",
msg.c_str(),from.c_str(),to.c_str(),id.c_str());
XMLElement* child = xml->findFirstChild();
bool ok = false;
// Use a while to break to the end
while (child) {
String xmlns = child->getAttribute("xmlns");
// Query
if (child->type() == XMLElement::Query) {
XMPPNamespace::Type ns = XMPPNamespace::type(xmlns);
switch (ns) {
case XMPPNamespace::DynamicRoster:
ok = handleXmppIqDynamicRoster(msg,*child,t,from,to,id);
break;
case XMPPNamespace::IqPrivate:
ok = handleXmppIqPrivate(msg,*child,t,from,to,id);
break;
default: ;
}
break;
}
// vCard
if (child->type() == XMLElement::VCard) {
if (XMPPUtils::hasXmlns(*child,XMPPNamespace::VCard))
ok = handleXmppIqVCard(msg,*child,t,from,to,id);
break;
}
// Custom
if (isApplication(&xmlns)) {
Engine::enqueue(new CustomXmppIqMsg(msg,*xml,*child));
ok = true;
break;
}
break;
}
TelEngine::destruct(child);
return ok;
}
// custom handler
bool YJingleFeatures::handleCustom(Message& msg)
{
// Don't handle jingle(features) messages or messages with
// other target
if (!acceptMsg(msg,true) || isJingleMsg(msg))
return false;
// Check parameters
const char* oper = msg.getValue("operation");
if (null(oper))
return false;
String* xmlns = msg.getParam("application");
if (!isApplication(xmlns))
return false;
const char* type = msg.getValue("type");
int iqType = ::lookup(type,s_customIqType,XMPPUtils::IqCount);
const char* error = 0;
if (iqType == XMPPUtils::IqError || iqType == XMPPUtils::IqCount) {
error = msg.getValue("error");
if (iqType == XMPPUtils::IqCount && null(error)) {
Debug(this,DebugMild,"Custom message app=%s oper=%s with invalid type=%s",
xmlns->c_str(),oper,type);
return false;
}
}
Debug(this,DebugAll,"Generating IQ from custom app=%s oper=%s type=%s",
xmlns->c_str(),oper,type);
XMLElement* iq = XMPPUtils::createIq((XMPPUtils::IqType)iqType,msg.getValue("from"),
msg.getValue("to"),0);
XMLElement* child = new XMLElement(oper);
child->setAttribute("xmlns",*xmlns);
// Build params
for (unsigned int n = 1; true; n++) {
String prefix;
prefix << s_customPrefix << n;
if (null(msg.getParam(prefix)))
break;
child->addChild(new XMLElement(msg,prefix));
}
iq->addChild(child);
if (iqType == XMPPUtils::IqError)
iq->addChild(createXmlError(msg));
bool rsp = iqType == XMPPUtils::IqResult || iqType == XMPPUtils::IqError;
return xmppGenerate(msg,iq,rsp);
}
// user.roster handler
bool YJingleFeatures::handleUserRoster(Message& msg)
{
// Don't handle jingle(features) messages
if (!acceptMsg(msg,true) || isJingleMsg(msg))
return false;
NamedString* oper = msg.getParam("operation");
if (!oper)
return false;
bool get = (*oper == "request");
if (!get) {
if (*oper == "update")
get = false;
else
return false;
}
bool dynamic = msg.getBoolValue("addressbook");
if (dynamic && !s_handleAddrbook)
return false;
Debug(this,DebugAll,"Processing '%s' operation=%s from=%s to=%s",
msg.c_str(),oper->c_str(),msg.getValue("from"),msg.getValue("to"));
XMLElement* xml = XMPPUtils::createIq((get ? XMPPUtils::IqGet : XMPPUtils::IqSet),
msg.getValue("from"),msg.getValue("to"),msg.getValue("id"));
XMPPNamespace::Type ns = XMPPNamespace::Roster;
if (dynamic)
ns = XMPPNamespace::DynamicRoster;
XMLElement* query = XMPPUtils::createElement(XMLElement::Query,ns);
// Fill items
unsigned int nParams = msg.length();
String prefix = "contact.";
int n = msg.getIntValue("contact.count");
for (int i = 1; i <= n; i++) {
String pref = prefix + String(i);
NamedString* jid = msg.getParam(pref);
if (!(jid && *jid))
continue;
XMLElement* item = new XMLElement(XMLElement::Item);
item->setAttributeValid("jid",*jid);
// Get data
if (get) {
query->addChild(item);
continue;
}
// Set item attributes/children
// Dynamic: all params are children
// Roster: all params except for 'group' are attributes
pref << ".";
for (unsigned int j = 0; j < nParams; j++) {
NamedString* ns = msg.getParam(j);
if (!(ns && ns->name().startsWith(pref)))
continue;
String tmp = ns->name().substr(pref.length());
if (!tmp)
continue;
if (dynamic || tmp == s_group)
item->addChild(new XMLElement(tmp,0,*ns));
else
item->setAttributeValid(tmp,*ns);
}
query->addChild(item);
}
xml->addChild(query);
return xmppGenerate(msg,xml);
}
// user.info handler
bool YJingleFeatures::handleUserInfo(Message& msg)
{
// Don't handle jingle(features) messages
if (!acceptMsg(msg,true) || isJingleMsg(msg))
return false;
NamedString* oper = msg.getParam("operation");
if (!oper)
return false;
bool get = (*oper == "request");
if (!get) {
if (*oper == "update")
get = false;
else
return false;
}
bool priv = msg.getBoolValue("private");
if (!s_handleVCard || (priv && !s_handlePrivate))
return false;
Debug(this,DebugAll,"Processing '%s' operation=%s from=%s to=%s",
msg.c_str(),oper->c_str(),msg.getValue("from"),msg.getValue("to"));
XMLElement* xml = 0;
if (!priv) {
xml = XMPPUtils::createVCard(get,msg.getValue("from"),
msg.getValue("to"),msg.getValue("id"));
XMLElement* vcard = !get ? xml->findFirstChild(XMLElement::VCard) : 0;
if (vcard) {
// Name
const char* first = msg.getValue("name.first");
const char* middle = msg.getValue("name.middle");
const char* last = msg.getValue("name.last");
String firstN, lastN;
// Try to build elements if missing
if (!(first || last || middle)) {
String* tmp = msg.getParam("name");
if (tmp) {
int pos = tmp->rfind(' ');
if (pos > 0) {
firstN = tmp->substr(0,pos);
lastN = tmp->substr(pos + 1);
}
else
lastN = *tmp;
}
first = firstN.c_str();
last = lastN.c_str();
}
XMLElement* n = new XMLElement("N");
n->addChild(new XMLElement("GIVEN",0,first));
n->addChild(new XMLElement("MIDDLE",0,middle));
n->addChild(new XMLElement("FAMILY",0,last));
vcard->addChild(n);
TelEngine::destruct(vcard);
}
}
else {
xml = XMPPUtils::createIq(get ? XMPPUtils::IqGet : XMPPUtils::IqSet,
msg.getValue("from"),msg.getValue("to"),msg.getValue("id"));
XMLElement* query = XMPPUtils::createElement(XMLElement::Query,XMPPNamespace::IqPrivate);
addChildren(msg,*query);
xml->addChild(query);
}
return xmppGenerate(msg,xml);
}
// Build and dispatch an "xmpp.generate" message. Copy error param on failure
// Consume the xml element
bool YJingleFeatures::xmppGenerate(Message& recv, XMLElement* xml, bool rsp)
{
// Make sure we have an 'id' for debug purposes
if (xml && !rsp) {
const char* id = xml->getAttribute("id");
if (!id)
xml->setAttribute("id",String((int)::random()));
}
Message* m = buildMsg("xmpp.generate",&recv);
m->addParam("protocol","xmpp");
m->addParam(new NamedPointer("xml",xml,""));
bool ok = Engine::dispatch(m);
if (!ok)
recv.copyParams(*m,"error");
TelEngine::destruct(m);
return ok;
}
// Unload the module: uninstall the relays
bool YJingleFeatures::unload()
{
DDebug(this,DebugAll,"Cleanup");
if (!lock(500000))
return false;
uninstallRelays();
unlock();
return true;
}
// Build an XML element's children from a list
bool YJingleFeatures::addChildren(NamedList& params, XMLElement& xml)
{
String prefix = params.getValue("message-prefix");
if (!prefix)
return false;
prefix << ".";
bool added = false;
for (unsigned int i = 1; i < 0xffffffff; i++) {
String childPrefix(prefix + String(i));
if (!params.getValue(childPrefix))
break;
xml.addChild(new XMLElement(params,childPrefix));
added = true;
}
return added;
}
// Add an XML element's children to a list
// The element's name will be used as message-prefix
bool YJingleFeatures::addChildren(XMLElement& xml, NamedList& params)
{
String pref = xml.name();
params.addParam("message-prefix",pref);
unsigned int n = 0;
pref << ".";
XMLElement* ch = xml.findFirstChild();
bool added = ch != 0;
for (; ch; ch = xml.findNextChild(ch)) {
String tmpPref = pref;
tmpPref << ++n;
params.addParam(tmpPref,ch->name());
tmpPref << ".";
const char* text = ch->getText();
if (!null(text))
params.addParam(tmpPref,text);
NamedList tmp("");
ch->getAttributes(tmp);
unsigned int count = tmp.length();
for (unsigned int i = 0; i < count; i++) {
NamedString* p = tmp.getParam(i);
if (p && p->name())
params.addParam(tmpPref + p->name(),*p);
}
}
return added;
}
// Check module and target parameters of a received message
bool YJingleFeatures::acceptMsg(Message& msg, bool checkTarget)
{
NamedString* module = msg.getParam("module");
if (module && *module == name())
return false;
if (checkTarget) {
NamedString* target = msg.getParam("target");
return !target || isJingleAlias(*target);
}
return true;
}
// Handle DynamicRoster received with xmpp.iq
bool YJingleFeatures::handleXmppIqDynamicRoster(Message& msg, XMLElement& query,
XMPPUtils::IqType t, const JabberID& from, const JabberID& to, const String& id)
{
if (!s_handleAddrbook || t != XMPPUtils::IqResult)
return false;
Debug(this,DebugAll,"Processing '%s' [DynamicRoster] from=%s to=%s id=%s",
msg.c_str(),from.c_str(),to.c_str(),id.c_str());
Message* m = buildMsg("user.roster",&msg);
m->addParam("operation","notify");
m->addParam("addressbook",String::boolText(true));
m->copyParams(msg,"from,to,id");
if (from.node())
m->addParam("username",from.node());
unsigned int n = 0;
String prefix = "contact.";
XMLElement* item = query.findFirstChild(XMLElement::Item);
for (; item; item = query.findNextChild(item,XMLElement::Item)) {
const char* jid = item->getAttribute("jid");
if (!(jid && *jid))
continue;
n++;
String pref = prefix + String(n);
m->addParam(pref,jid);
pref << ".";
for (XMLElement* x = item->findFirstChild(); x; x = item->findNextChild(x))
m->addParam(pref + x->name(),x->getText());
}
m->addParam("contact.count",String(n));
Engine::enqueue(m);
return true;
}
// Handle client private iq data responses
bool YJingleFeatures::handleXmppIqPrivate(Message& msg, XMLElement& query,
XMPPUtils::IqType t, const JabberID& from, const JabberID& to, const String& id)
{
if (!s_handlePrivate || t != XMPPUtils::IqResult)
return false;
Debug(this,DebugAll,"Processing '%s' [Private] from=%s to=%s id=%s",
msg.c_str(),from.c_str(),to.c_str(),id.c_str());
Message* m = buildMsg("user.info",&msg);
m->addParam("operation","notify");
m->addParam("private",String::boolText(true));
m->copyParams(msg,"from,to,id");
if (from.node())
m->addParam("username",from.node());
XMLElement* ch = query.findFirstChild();
if (ch) {
addChildren(*ch,*m);
TelEngine::destruct(ch);
}
Engine::enqueue(m);
return true;
}
// Handle a valid vcard received with xmpp.iq
bool YJingleFeatures::handleXmppIqVCard(Message& msg, XMLElement& vcard,
XMPPUtils::IqType t, const JabberID& from, const JabberID& to, const String& id)
{
if (!s_handleVCard || t != XMPPUtils::IqResult)
return false;
Debug(this,DebugAll,"Processing '%s' [VCard] from=%s to=%s id=%s",
msg.c_str(),from.c_str(),to.c_str(),id.c_str());
Message* m = buildMsg("user.info",&msg);
m->addParam("operation","notify");
m->addParam("vcard",String::boolText(true));
m->copyParams(msg,"from,to,id");
if (from.node())
m->addParam("username",from.node());
XMLElement* n = vcard.findFirstChild("N");
if (n) {
String name;
const char* given = getChildText(*n,"GIVEN");
if (given) {
m->addParam("name.first",given);
name << given;
}
const char* middle = getChildText(*n,"MIDDLE");
if (middle) {
m->addParam("name.middle",middle);
name.append(middle," ");
}
const char* family = getChildText(*n,"FAMILY");
if (family) {
m->addParam("name.last",family);
name.append(family," ");
}
if (name)
m->addParam("name",name);
TelEngine::destruct(n);
}
// EMAIL
XMLElement* email = vcard.findFirstChild("EMAIL");
if (email) {
const char* em = email->getText();
if (!null(em))
m->addParam("email",em);
TelEngine::destruct(email);
}
// Photo
XMLElement* photo = vcard.findFirstChild("PHOTO");
if (photo) {
XMLElement* type = photo->findFirstChild("TYPE");
XMLElement* binval = photo->findFirstChild("BINVAL");
if (type && binval) {
const char* t = type->getText();
const char* img = binval->getText();
if (!(null(t) || null(img))) {
m->addParam("photo_format",t);
m->addParam("photo",img);
}
}
TelEngine::destruct(type);
TelEngine::destruct(binval);
TelEngine::destruct(photo);
}
Engine::enqueue(m);
return true;
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -22,13 +22,14 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatengine.h>
#include <yatephone.h>
#include <string.h>
#include <openssl/opensslconf.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#ifndef OPENSSL_NO_AES
#include <openssl/aes.h>
@ -37,8 +38,6 @@
using namespace TelEngine;
namespace { // anonymous
#define MODNAME "openssl"
#define MAKE_ERR(x) { #x, X509_V_ERR_##x }
static TokenDict s_verifyCodes[] = {
MAKE_ERR(UNABLE_TO_GET_ISSUER_CERT),
@ -75,11 +74,31 @@ static TokenDict s_verifyMode[] = {
{ 0, 0 }
};
class SslContext : public String
{
public:
// Constructor. Build the context
SslContext(const char* name);
// Initialize certificate, key and domains. Check the key
// Return false on failure
bool init(const NamedList& params);
// Check if this context can be used for server sockets in a given domain
bool hasDomain(const String& domain);
// Add a comma separated list of domains to a buffer
void addDomains(String& buf);
// Release memory, free the context
virtual void destruct();
inline operator SSL_CTX*()
{ return m_context; }
protected:
SSL_CTX* m_context;
ObjList m_domains;
};
class SslSocket : public Socket, public Mutex
{
public:
SslSocket(SOCKET handle, bool server, int verify);
SslSocket(SOCKET handle, bool server, int verify, SslContext* context = 0);
virtual ~SslSocket();
virtual bool terminate();
virtual bool valid();
@ -132,20 +151,29 @@ public:
};
#endif
class OpenSSL : public Plugin
class OpenSSL : public Module
{
public:
OpenSSL();
~OpenSSL();
virtual void initialize();
// Find a context by name or domain
// This method is not thread safe. The caller must lock the plugin
// until the returned context is not used anymore
SslContext* findContext(const String& token, bool byDomain = false) const;
protected:
virtual void statusParams(String& str);
virtual void statusDetail(String& str);
SslHandler* m_handler;
ObjList m_contexts; // Server contexts list
String m_statusCmd; // Module status command
};
INIT_PLUGIN(OpenSSL);
static int s_index = -1;
static SSL_CTX* s_context = 0;
static OpenSSL __plugin;
// Attempt to add randomness from system time when called
@ -165,20 +193,142 @@ void infoCallback(const SSL* ssl, int where, int retVal)
if (sock->ssl() == ssl)
sock->onInfo(where,retVal);
else
Debug(MODNAME,DebugFail,"Mismatched session %p [%p]",ssl,sock);
Debug(&__plugin,DebugFail,"Mismatched session %p [%p]",ssl,sock);
}
}
// Callback function called from OpenSSL for protocol messages
void msgCallback(int write, int version, int content_type, const void* buf,
size_t len, SSL* ssl, void* arg)
{
Debug(&__plugin,DebugAll,
"%s SSL message: version=%d content_type=%d buf=%p len=%u ssl=%p",
write ? "Sent" : "Received",version,content_type,buf,(unsigned int)len,ssl);
}
SslContext::SslContext(const char* name)
: String(name),
m_context(0)
{
m_context = ::SSL_CTX_new(::SSLv23_server_method());
::SSL_CTX_set_info_callback(m_context,infoCallback);
#ifdef DEBUG
::SSL_CTX_set_msg_callback(m_context,msgCallback);
#endif
}
// Initialize certificate, key and domains. Check the key
// Return false on failure
bool SslContext::init(const NamedList& params)
{
String cert;
const char* c = params.getValue("certificate");
if (c) {
cert << Engine::configPath();
if (cert && !cert.endsWith(Engine::pathSeparator()))
cert << Engine::pathSeparator();
cert << c;
}
String key;
const char* k = params.getValue("key");
if (k) {
key << Engine::configPath();
if (key && !key.endsWith(Engine::pathSeparator()))
key << Engine::pathSeparator();
key << k;
}
else
key = cert;
// Load certificate and key. Check them
if (!::SSL_CTX_use_certificate_chain_file(m_context,cert)) {
unsigned long err = ::ERR_get_error();
Debug(&__plugin,DebugWarn,
"Context '%s' failed to load certificate from '%s' '%s'",
c_str(),c ? cert.c_str() : "",::ERR_error_string(err,0));
return false;
}
if (!::SSL_CTX_use_PrivateKey_file(m_context,key,SSL_FILETYPE_PEM)) {
unsigned long err = ::ERR_get_error();
Debug(&__plugin,DebugWarn,
"Context '%s' failed to load key from '%s' '%s'",
c_str(),k ? key.c_str() : "",::ERR_error_string(err,0));
return false;
}
if (!::SSL_CTX_check_private_key(m_context)) {
unsigned long err = ::ERR_get_error();
Debug(&__plugin,DebugWarn,
"Context '%s' certificate='%s' or key='%s' are invalid '%s'",
c_str(),cert.c_str(),key.c_str(),::ERR_error_string(err,0));
return false;
}
// Load domains
m_domains.clear();
String* d = params.getParam("domains");
if (d) {
ObjList* list = d->split(',',false);
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
s->trimBlanks();
if (s->null())
continue;
if (s->startsWith("*") && (s->length() < 3 || (*s)[1] != '.')) {
Debug(&__plugin,DebugNote,"Context '%s' ignoring invalid domain='%s'",
c_str(),s->c_str());
continue;
}
m_domains.append(new String(s->toLower()));
}
TelEngine::destruct(list);
}
DDebug(&__plugin,DebugAll,"Context '%s' loaded certificate='%s' key='%s' domains=%s",
c_str(),cert.c_str(),key.c_str(),TelEngine::c_safe(d));
return true;
}
// Check if this context can be used for server sockets in a given domain
bool SslContext::hasDomain(const String& domain)
{
for (ObjList* o = m_domains.skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
if (*s == domain ||
((*s)[0] == '*' && domain.endsWith(s->c_str() + 1)))
return true;
}
return false;
}
// Add a comma separated list of domains to a buffer
void SslContext::addDomains(String& buf)
{
bool notFirst = false;
for (ObjList* o = m_domains.skipNull(); o; o = o->skipNext()) {
if (notFirst)
buf << ",";
else
notFirst = true;
buf << (static_cast<String*>(o->get()))->c_str();
}
}
// Release memory, free the context
void SslContext::destruct()
{
::SSL_CTX_free(m_context);
String::destruct();
}
// Create a SSL socket from a regular socket handle
SslSocket::SslSocket(SOCKET handle, bool server, int verify)
SslSocket::SslSocket(SOCKET handle, bool server, int verify, SslContext* context)
: Socket(handle), Mutex(false,"SslSocket"),
m_ssl(0)
{
DDebug(DebugAll,"SslSocket::SslSocket(%d,%s,%s) [%p]",
handle,String::boolText(server),lookup(verify,s_verifyMode,"unknown"),this);
DDebug(&__plugin,DebugAll,"SslSocket::SslSocket(%d,%s,%s,%s) [%p]",
handle,String::boolText(server),lookup(verify,s_verifyMode,"unknown"),
context ? context->c_str() : "",this);
if (Socket::valid()) {
m_ssl = ::SSL_new(s_context);
m_ssl = ::SSL_new(context ? *context : s_context);
if (s_index >= 0)
::SSL_set_ex_data(m_ssl,s_index,this);
::SSL_set_verify(m_ssl,verify,0);
@ -193,7 +343,7 @@ SslSocket::SslSocket(SOCKET handle, bool server, int verify)
// Destructor, clean up early
SslSocket::~SslSocket()
{
DDebug(DebugAll,"SslSocket::~SslSocket() handle=%d [%p]",handle(),this);
DDebug(&__plugin,DebugAll,"SslSocket::~SslSocket() handle=%d [%p]",handle(),this);
clearFilters();
terminate();
}
@ -274,12 +424,12 @@ void SslSocket::onInfo(int where, int retVal)
{
#ifdef DEBUG
if (where & SSL_CB_LOOP)
Debug(MODNAME,DebugAll,"State %s [%p]",SSL_state_string_long(m_ssl),this);
Debug(&__plugin,DebugAll,"State %s [%p]",SSL_state_string_long(m_ssl),this);
#endif
if ((where & SSL_CB_EXIT) && (retVal == 0))
Debug(MODNAME,DebugMild,"Failed %s [%p]",SSL_state_string_long(m_ssl),this);
Debug(&__plugin,DebugMild,"Failed %s [%p]",SSL_state_string_long(m_ssl),this);
if (where & SSL_CB_ALERT)
Debug(MODNAME,DebugMild,"Alert %s: %s [%p]",
Debug(&__plugin,DebugMild,"Alert %s: %s [%p]",
SSL_alert_type_string_long(retVal),
SSL_alert_desc_string_long(retVal),this);
if (where & SSL_CB_HANDSHAKE_DONE) {
@ -287,7 +437,7 @@ void SslSocket::onInfo(int where, int retVal)
if (verify != X509_V_OK) {
// handshake succeeded but the certificate has problems
const char* error = lookup(verify,s_verifyCodes);
Debug(MODNAME,DebugWarn,"Certificate verify error %ld%s%s [%p]",
Debug(&__plugin,DebugWarn,"Certificate verify error %ld%s%s [%p]",
verify,error ? ": " : "",c_safe(error),this);
}
}
@ -300,23 +450,42 @@ bool SslHandler::received(Message& msg)
addRand(msg.msgTime());
Socket** ppSock = static_cast<Socket**>(msg.userObject("Socket*"));
if (!ppSock) {
Debug(MODNAME,DebugGoOn,"SslHandler: No pointer to Socket");
Debug(&__plugin,DebugGoOn,"SslHandler: No pointer to Socket");
return false;
}
Socket* pSock = *ppSock;
if (!pSock) {
Debug(MODNAME,DebugGoOn,"SslHandler: NULL Socket pointer");
Debug(&__plugin,DebugGoOn,"SslHandler: NULL Socket pointer");
return false;
}
if (!pSock->valid()) {
Debug(MODNAME,DebugWarn,"SslHandler: Invalid Socket");
Debug(&__plugin,DebugWarn,"SslHandler: Invalid Socket");
return false;
}
SslSocket* sSock = new SslSocket(pSock->handle(),
msg.getBoolValue("server",false),
msg.getIntValue("verify",s_verifyMode,SSL_VERIFY_NONE));
SslSocket* sSock = 0;
if (msg.getBoolValue("server",false)) {
Lock lock(&__plugin);
SslContext* c = 0;
String* token = msg.getParam("context");
if (!TelEngine::null(token))
c = __plugin.findContext(*token);
if (!c) {
token = msg.getParam("domain");
if (!TelEngine::null(token))
c = __plugin.findContext(String(*token).toLower(),true);
}
if (!c) {
Debug(&__plugin,DebugWarn,"SslHandler: Unable to find a server context");
return false;
}
sSock = new SslSocket(pSock->handle(),true,
msg.getIntValue("verify",s_verifyMode,SSL_VERIFY_NONE),c);
}
else
sSock = new SslSocket(pSock->handle(),false,
msg.getIntValue("verify",s_verifyMode,SSL_VERIFY_NONE));
if (!sSock->valid()) {
Debug(MODNAME,DebugWarn,"SslHandler: Invalid SSL Socket");
Debug(&__plugin,DebugWarn,"SslHandler: Invalid SSL Socket");
// detach and destroy new socket, preserve old one
sSock->detach();
delete sSock;
@ -335,12 +504,12 @@ AesCtrCipher::AesCtrCipher()
: m_key(0)
{
m_key = new AES_KEY;
DDebug(DebugAll,"AesCtrCipher::AesCtrCipher() key=%p [%p]",m_key,this);
DDebug(&__plugin,DebugAll,"AesCtrCipher::AesCtrCipher() key=%p [%p]",m_key,this);
}
AesCtrCipher::~AesCtrCipher()
{
DDebug(DebugAll,"AesCtrCipher::~AesCtrCipher() key=%p [%p]",m_key,this);
DDebug(&__plugin,DebugAll,"AesCtrCipher::~AesCtrCipher() key=%p [%p]",m_key,this);
delete m_key;
}
@ -410,10 +579,11 @@ bool CipherHandler::received(Message& msg)
OpenSSL::OpenSSL()
: Plugin("openssl",true),
: Module("openssl","misc",true),
m_handler(0)
{
Output("Loaded module OpenSSL - based on " OPENSSL_VERSION_TEXT);
m_statusCmd << "status " << name();
}
OpenSSL::~OpenSSL()
@ -424,20 +594,90 @@ OpenSSL::~OpenSSL()
void OpenSSL::initialize()
{
if (m_handler)
return;
Output("Initializing module OpenSSL");
::SSL_load_error_strings();
::SSL_library_init();
addRand(Time::now());
s_index = ::SSL_get_ex_new_index(0,const_cast<char*>("TelEngine::SslSocket"),0,0,0);
s_context = ::SSL_CTX_new(::SSLv23_method());
SSL_CTX_set_info_callback(s_context,infoCallback); // macro - no ::
m_handler = new SslHandler;
Engine::install(m_handler);
Configuration cfg(Engine::configFile("openssl"));
if (!m_handler) {
setup();
::SSL_load_error_strings();
::SSL_library_init();
addRand(Time::now());
s_index = ::SSL_get_ex_new_index(0,const_cast<char*>("TelEngine::SslSocket"),0,0,0);
s_context = ::SSL_CTX_new(::SSLv23_method());
SSL_CTX_set_info_callback(s_context,infoCallback); // macro - no ::
m_handler = new SslHandler;
Engine::install(m_handler);
#ifndef OPENSSL_NO_AES
Engine::install(new CipherHandler);
Engine::install(new CipherHandler);
#endif
}
lock();
// Load server contexts
unsigned int n = cfg.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* p = cfg.getSection(i);
if (!p || *p == "general" || !p->c_str())
continue;
SslContext* context = findContext(*p);
if (!p->getBoolValue("enable",true)) {
if (context) {
DDebug(this,DebugAll,"Removing disabled context '%s'",context->c_str());
m_contexts.remove(context);
}
continue;
}
if (!context)
context = new SslContext(*p);
if (context->init(*p)) {
if (!findContext(*p)) {
m_contexts.append(context);
DDebug(this,DebugAll,"Added context '%s'",context->c_str());
}
}
else {
if (findContext(*p)) {
DDebug(this,DebugAll,"Removing invalid context '%s'",context->c_str());
m_contexts.remove(context);
}
else {
DDebug(this,DebugAll,"Ignoring invalid context '%s'",context->c_str());
TelEngine::destruct(context);
}
}
}
unlock();
}
// Find a context by name or domain
SslContext* OpenSSL::findContext(const String& token, bool byDomain) const
{
if (!byDomain) {
ObjList* o = m_contexts.find(token);
return o ? static_cast<SslContext*>(o->get()) : 0;
}
for (ObjList* o = m_contexts.skipNull(); o; o = o->skipNext()) {
SslContext* c = static_cast<SslContext*>(o->get());
if (c->hasDomain(token))
return c;
}
return 0;
}
void OpenSSL::statusParams(String& str)
{
Lock lock(this);
str << "contexts=" << m_contexts.count();
}
void OpenSSL::statusDetail(String& str)
{
Lock lock(this);
for (ObjList* o = m_contexts.skipNull(); o; o = o->skipNext()) {
SslContext* c = static_cast<SslContext*>(o->get());
str.append(c->c_str(),";");
str << "=";
c->addDomains(str);
}
}
}; // anonymous namespace

910
modules/server/presence.cpp Normal file
View File

@ -0,0 +1,910 @@
/**
* presence.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Presence module
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2009 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 <yatecbase.h>
#include <yateclass.h>
using namespace TelEngine;
namespace {
#define MIN_COUNT 16 // number of lists for holding presences
#define EXPIRE_CHECK_MAX 10000 // interval in miliseconds for checking object for expiring
#define TIME_TO_KEEP 30000 // interval in miliseconds for keeping an object in memory
class PresenceList;
class Presence;
class ResNotifyHandler;
class EngineStartHandler;
class PresenceModule;
class ExpirePresence;
/*
* Class ResNotifyHandler
* Handles a resource.notify message
*/
class ResNotifyHandler : public MessageHandler
{
public:
inline ResNotifyHandler(unsigned int priority = 10)
: MessageHandler("resource.notify", priority)
{ }
virtual ~ResNotifyHandler()
{ }
virtual bool received(Message& msg);
};
/*
* Class EngineStartHandler
* Handles a engine.start message
*/
class EngineStartHandler : public MessageHandler
{
public:
inline EngineStartHandler()
: MessageHandler("engine.start",100)
{}
virtual bool received(Message& msg);
};
/*
* class Presence
* A presence object
*/
class Presence : public GenObject
{
public:
Presence(const String& id, bool online = true, const char* instance = 0,
const char* data = 0, unsigned int expiresMsecs = 0);
~Presence();
inline void update(const char* data, unsigned int expireMs) {
m_data = data;
updateExpireTime(expireMs);
}
inline const String& getInstance() const
{ return m_instance; }
inline const String& data() const
{ return m_data; }
virtual const String& toString() const
{ return m_id; }
inline bool hasExpired(u_int64_t time = Time::msecNow()) const
{ return m_expires && m_expires < time; }
inline bool isOnline() const
{ return m_online;}
inline void setOnline(bool online = true)
{ m_online = online; }
inline void updateExpireTime(unsigned int msecs)
{ m_expires = msecs ? Time::msecNow() + msecs : 0; }
inline bool isCaps(const String& capsid) const
{ return m_caps && *m_caps == capsid; }
inline void setCaps(const String& capsid, const NamedList& list) {
TelEngine::destruct(m_caps);
m_caps = new NamedList(capsid);
m_caps->copyParams(list,"caps",'.');
}
// Copy parameters to a list
void addCaps(NamedList& list, const String& prefix = String::empty());
private:
String m_id; // presence id
String m_instance; // presence instance, for jabber it represents the resource
String m_data; // presence data, format unknown
u_int64_t m_expires;// time at which this object will expire
bool m_online; // online/offline flag
NamedList* m_caps; // Capabilities
};
/*
* class PresenceList
* A list of presences
*/
class PresenceList : public ObjList, public Mutex
{
public:
// create a list
PresenceList();
~PresenceList();
// find all presences with given id, disregarding the instance
ObjList* findPresence(const String& id);
// find a presence with the given instance
inline Presence* findPresence(const String& contact, const String& instance) {
ObjList* o = find(contact,instance);
return o ? static_cast<Presence*>(o->get()) : 0;
}
// Remove an item from list. Optionally delete it
// Return the item if found and not deleted
inline Presence* removePresence(const String& contact, const String& instance,
bool delObj = true) {
ObjList* o = find(contact,instance);
return o ? static_cast<Presence*>(o->remove(delObj)) : 0;
}
// delete expired objects
void expire();
// Find an item by id and instance
ObjList* find(const String& contact, const String& instance);
};
/*
* Class PresenceModule
* Module for handling presences
*/
class PresenceModule : public Module
{
friend class ExpirePresence;
public:
PresenceModule();
virtual ~PresenceModule();
//inherited methods
virtual void initialize();
virtual bool received(Message& msg, int id);
// Get the list containing a given user
inline PresenceList* getList(const String& contact)
{ return m_list + (contact.hash() % m_listCount); }
// Update capabilities for all instances with the given caps id
void updateCaps(const String& capsid, Message& msg);
// append presence
void addPresence(Presence* pres, bool onlyLocal = false);
// remove presence
void removePresence(Presence* pres);
// remove presence by id
void removePresenceById(const String& id);
// find presence by id
ObjList* findPresenceById(const String& id);
// find presence with an instance
Presence* findPresence(const String& contact, const String& instance);
// update a presence
void updatePresence(Presence* pres, const char* data);
// uninstall relays and message handlers
bool unload();
// Build a 'database' message used to update presence
Message* buildUpdateDb(const Presence& pres, bool newPres);
// Build a 'database' message used to delete presence
Message* buildDeleteDb(const Presence& pres);
// database comunnication functions
bool insertDB(Presence* pres);
bool updateDB(Presence* pres);
bool removeDB(Presence* pres, bool allInstances = false, bool allPresences = false, String machine = "");
bool queryDB(String id, String instance);
bool getInfoDB(String id, String instance, NamedList* result);
// Build and dispatch a 'database' message. Replace query params
// Return a valid Message on success
Message* queryDb(const String& account, const String& query,
const NamedList& params);
// Dispatch a 'database' message. Return a valid Message pointer on success
// Consume the given pointer
Message* queryDb(Message* msg);
private:
// array of lists that hold presences
PresenceList* m_list;
// resource.notify handler
ResNotifyHandler* m_notifyHandler;
// engine.start handler
EngineStartHandler* m_engineStartHandler;
// number of lists in which presences will be held
unsigned int m_listCount;
// thread for removing expired objects
ExpirePresence* m_expireThread;
// Query strings
// SQL statement for inserting to database
String m_insertDB;
// SQL statement for updating information in the database
String m_updateDB;
// SQL statement for remove a presence with a resource
String m_removeResDB;
// SQL statement for removing all instances of a contact
String m_removePresDB;
// SQL statement for removing all presences of this node
String m_removeAllDB;
// SQL statement for interrogating the database about a contact with a resource
String m_selectResDB;
// SQL statement for interrogating the database about a contact without a resource
String m_selectPresDB;
// database connection
String m_accountDB;
};
/*
* class ExpirePresence
* A thread that deletes old presences
*/
class ExpirePresence : public Thread
{
public:
// create a thread whick will check for expired objects after the given "checkAfter" interval
ExpirePresence(unsigned int checkAfter = 5000);
~ExpirePresence();
void run();
static unsigned int s_expireTime;
private:
unsigned int m_checkMs;
};
static String s_msgPrefix = "presence";
static unsigned int s_presExpire; // Presence expire interval (relese memory only)
static unsigned int s_presExpireCheck; // Presence expire check interval
unsigned int ExpirePresence::s_expireTime = 0;
INIT_PLUGIN(PresenceModule);
UNLOAD_PLUGIN(unloadNow)
{
if (unloadNow && !__plugin.unload())
return false;
return true;
}
/*
* ResNotifyHandler
*/
bool ResNotifyHandler::received(Message& msg)
{
// TODO
// see message parameters and what to do with them
String* node = msg.getParam("nodename");
if (!TelEngine::null(node) && *node != Engine::nodeName())
__plugin.removeDB(0, true, true, *node);
String* operation = msg.getParam("operation");
if (TelEngine::null(operation))
return false;
if (*operation == "updatecaps") {
String* capsid = msg.getParam("caps.id");
if (TelEngine::null(capsid))
return false;
DDebug(&__plugin,DebugAll,"Processing %s oper=%s capsid=%s",
msg.c_str(),operation->c_str(),capsid->c_str());
__plugin.updateCaps(*capsid,msg);
return false;
}
String* contact = msg.getParam("contact");
if (TelEngine::null(contact))
return false;
DDebug(&__plugin,DebugAll,"Processing %s contact=%s oper=%s",
msg.c_str(),contact->c_str(),operation->c_str());
String* instance = msg.getParam("instance");
PresenceList* list = __plugin.getList(*contact);
if (*operation == "online" || *operation == "update") {
if (TelEngine::null(instance))
return false;
Lock lock(list);
Presence* pres = list->findPresence(*contact,*instance);
bool newPres = (pres == 0);
if (newPres) {
pres = new Presence(*contact,true,*instance);
list->append(pres);
}
pres->update(msg.getValue("data"),s_presExpire);
String* capsid = msg.getParam("caps.id");
if (!TelEngine::null(capsid))
pres->setCaps(*capsid,msg);
// Update database only if we expire the data from memory
if (s_presExpire) {
Message* m = __plugin.buildUpdateDb(*pres,newPres);
lock.drop();
TelEngine::destruct(__plugin.queryDb(m));
}
}
else if (*operation == "remove" || *operation == "offline") {
if (TelEngine::null(instance)) {
// TODO: all contact's instances are offline
return false;
}
list->lock();
Presence* pres = list->removePresence(*contact,*instance,false);
list->unlock();
// Remove from database only if we expire the data from memory
if (pres && s_presExpire)
TelEngine::destruct(__plugin.queryDb(__plugin.buildDeleteDb(*pres)));
TelEngine::destruct(pres);
}
else if (*operation == "query") {
Lock lock(list);
if (!TelEngine::null(instance)) {
Presence* pres = list->findPresence(*contact,*instance);
if (pres) {
msg.addParam("data",pres->data());
msg.addParam("nodename",Engine::nodeName());
pres->addCaps(msg);
return true;
}
}
else {
ObjList* l = list->findPresence(*contact);
if (!l)
return false;
msg.addParam("message-prefix",s_msgPrefix);
unsigned int n = 0;
String prefix = s_msgPrefix + ".";
for (ObjList* o = l->skipNull(); o; o = o->skipNext()) {
Presence* pres = static_cast<Presence*>(o->get());
String param;
param << prefix << ++n << ".";
msg.addParam(param + "instance",pres->getInstance());
msg.addParam(param + "data",pres->data());
msg.addParam(param + "nodename",Engine::nodeName());
pres->addCaps(msg,param);
}
msg.addParam(prefix + "count",String(n));
TelEngine::destruct(l);
return n != 0;
}
}
return false;
}
/*
* EngineStartHandler
*/
bool EngineStartHandler::received(Message& msg)
{
__plugin.removeDB(0,true,true);
return false;
}
/*
* Presence
*/
Presence::Presence(const String& id, bool online, const char* instance,
const char* data, unsigned int expireMs)
: m_id(id), m_instance(instance), m_data(data), m_online(online),
m_caps(0)
{
updateExpireTime(expireMs);
DDebug(&__plugin,DebugAll,"Presence contact='%s' instance='%s' online=%s [%p]",
id.c_str(),instance,String::boolText(m_online),this);
}
Presence::~Presence()
{
TelEngine::destruct(m_caps);
DDebug(&__plugin,DebugAll,"Presence contact='%s' instance='%s' destroyed [%p]",
m_id.c_str(),m_instance.c_str(),this);
}
// Copy parameters to a list
void Presence::addCaps(NamedList& list, const String& prefix)
{
if (!m_caps)
return;
if (!prefix) {
list.copyParams(*m_caps);
return;
}
unsigned int n = m_caps->count();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = m_caps->getParam(i);
if (ns)
list.addParam(prefix + ns->name(),*ns);
}
}
/*
* PresenceList
*/
PresenceList::PresenceList()
: Mutex("PresenceList")
{
XDebug(&__plugin,DebugAll,"PresenceList() [%p]",this);
}
PresenceList::~PresenceList()
{
XDebug(&__plugin,DebugAll, "PresenceList destroyed [%p]",this);
}
ObjList* PresenceList::findPresence(const String& id)
{
if (TelEngine::null(id))
return 0;
DDebug(&__plugin,DebugAll,"PresenceList::findPresence('%s') [%p]",id.c_str(),this);
ObjList* res = 0;
for (ObjList* o = skipNull(); o; o = o->skipNext()) {
if (id == o->get()->toString()) {
if (!res)
res = new ObjList();
res->append(o->get())->setDelete(false);
}
}
return res;
}
void PresenceList::expire()
{
u_int64_t time = Time::msecNow();
Lock lock(this);
for (ObjList* o = skipNull(); o; o = o->skipNext()) {
Presence* pres = static_cast<Presence*>(o->get());
if (pres->hasExpired(time)) {
Debug(&__plugin,DebugAll,"Presence (%p) contact=%s instance=%s expired",
pres,pres->toString().c_str(),pres->getInstance().c_str());
__plugin.removePresence(pres);
}
}
}
// Find an item by id and instance
ObjList* PresenceList::find(const String& contact, const String& instance)
{
if (contact.null() || instance.null())
return 0;
for (ObjList* o = skipNull(); o; o = o->skipNext()) {
Presence* pres = static_cast<Presence*>(o->get());
if (contact == pres->toString() && instance == pres->getInstance())
return o;
}
return 0;
}
/*
* ExpirePresence
*/
ExpirePresence::ExpirePresence(unsigned int checkAfter)
: Thread("ExpirePresence"), m_checkMs(checkAfter)
{
__plugin.lock();
__plugin.m_expireThread = this;
__plugin.unlock();
}
ExpirePresence::~ExpirePresence()
{
Debug(&__plugin,DebugAll,"ExpirePresence thread terminated [%p]",this);
__plugin.lock();
__plugin.m_expireThread = 0;
__plugin.unlock();
}
void ExpirePresence::run()
{
Debug(&__plugin,DebugAll,"%s started [%p]",currentName(),this);
while (true) {
if (Thread::check(false) || Engine::exiting())
break;
if (s_expireTime < m_checkMs)
Thread::idle();
else {
s_expireTime = 0;
for (unsigned int i = 0; i < __plugin.m_listCount; i++)
__plugin.m_list[i].expire();
}
}
}
/*
* PresenceModule
*/
PresenceModule::PresenceModule()
: Module("presence", "misc"),
m_list(0), m_notifyHandler(0), m_engineStartHandler(0),
m_listCount(0), m_expireThread(0)
{
Output("Loaded module Presence");
}
PresenceModule::~PresenceModule()
{
Output("Unloaded module Presence");
TelEngine::destruct(m_notifyHandler);
TelEngine::destruct(m_engineStartHandler);
if (m_list)
delete[] m_list;
}
void PresenceModule::initialize()
{
Output("Initializing module Presence");
Configuration cfg(Engine::configFile("presence"));
cfg.load();
if (!m_list) {
setup();
installRelay(Halt);
m_notifyHandler = new ResNotifyHandler();
Engine::install(m_notifyHandler);
m_engineStartHandler = new EngineStartHandler();
Engine::install(m_engineStartHandler);
m_listCount = cfg.getIntValue("general", "listcount", MIN_COUNT);
if (m_listCount < MIN_COUNT)
m_listCount = MIN_COUNT;
m_list = new PresenceList[m_listCount];
// expire thread?
// TODO: Make sure these values are correct
s_presExpireCheck = cfg.getIntValue("general", "expirecheck");
if (s_presExpireCheck < 0)
s_presExpireCheck = 0;
if (s_presExpireCheck) {
if (s_presExpireCheck > EXPIRE_CHECK_MAX)
s_presExpireCheck = EXPIRE_CHECK_MAX;
s_presExpire = cfg.getIntValue("general", "expiretime", TIME_TO_KEEP);
(new ExpirePresence(s_presExpireCheck))->startup();
}
// queries init
m_insertDB = cfg.getValue("database", "insert_presence", "");
m_updateDB = cfg.getValue("database", "update_presence", "");
m_removeResDB = cfg.getValue("database", "remove_instance", "");
m_removePresDB = cfg.getValue("database", "remove_presence", "");
m_removeAllDB = cfg.getValue("database", "remove_all", "");
m_selectResDB = cfg.getValue("database", "select_instance", "");
m_selectPresDB = cfg.getValue("database", "select_presence", "");
// database connection init
m_accountDB = cfg.getValue("database", "account");
}
}
bool PresenceModule::unload()
{
DDebug(this,DebugAll,"unload()");
if (!lock(500000))
return false;
uninstallRelays();
Engine::uninstall(m_notifyHandler);
Engine::uninstall(m_engineStartHandler);
if (m_expireThread)
m_expireThread->cancel();
unlock();
// Wait for expire thread termination
while (m_expireThread)
Thread::yield();
return true;
}
bool PresenceModule::received(Message& msg, int id)
{
if (id == Timer)
ExpirePresence::s_expireTime += 1000;
else if (id == Halt) {
unload();
DDebug(this,DebugAll,"Halted");
}
return Module::received(msg,id);
}
// Update capabilities for all instances with the given caps id
void PresenceModule::updateCaps(const String& capsid, Message& msg)
{
for (unsigned int i = 0; i < m_listCount; i++) {
Lock lock(m_list[i]);
for (ObjList* o = m_list[i].skipNull(); o; o = o->skipNext()) {
Presence* p = static_cast<Presence*>(o->get());
if (p->isCaps(capsid))
p->setCaps(capsid,msg);
}
}
}
void PresenceModule::addPresence(Presence* pres, bool onlyLocal)
{
if (!pres)
return;
unsigned int index = pres->toString().hash() % m_listCount;
DDebug(this,DebugAll,"Adding presence (%p) contact='%s' instance='%s'",
pres,pres->toString().c_str(),pres->getInstance().c_str());
Lock lock(m_list[index]);
m_list[index].append(pres);
pres->updateExpireTime(s_presExpire);
if (!onlyLocal)
insertDB(pres);
}
void PresenceModule::removePresence(Presence* pres)
{
if (!pres)
return;
unsigned int index = pres->toString().hash() % m_listCount;
DDebug(this,DebugAll,"Removing presence (%p) contact=%s instance=%s",
pres,pres->toString().c_str(),pres->getInstance().c_str());
Lock lock(m_list[index]);
if (!pres->isOnline())
removeDB(pres);
m_list[index].remove(pres);
}
void PresenceModule::removePresenceById(const String& id)
{
if (TelEngine::null(id))
return;
unsigned int index = id.hash() % m_listCount;
Lock lock(m_list[index]);
for (ObjList* o = m_list[index].skipNull(); o; o = o->skipNext()) {
Presence* pres = static_cast<Presence*>(o->get());
if (pres->toString() == id) {
m_list[index].remove(pres);
removeDB(pres, true);
}
}
// list->remove(id);
}
void PresenceModule::updatePresence(Presence* pres, const char* data)
{
if (!pres)
return;
DDebug(this,DebugAll,"updatePresence() contact='%s' instance='%s' data='%s'",
pres->toString().c_str(),pres->getInstance().c_str(),data);
pres->update(data,s_presExpire);
updateDB(pres);
}
ObjList* PresenceModule::findPresenceById(const String& id)
{
if (TelEngine::null(id))
return 0;
unsigned int index = id.hash() % m_listCount;
Lock lock(m_list[index]);
ObjList* ol = m_list[index].findPresence(id);
NamedList info("");
if (!getInfoDB(id, "", &info))
return ol;
int count = info.getIntValue("count");
for (int i = 1; i <= count; i++) {
String prefix = String(i) + ".";
String* instance = info.getParam(prefix + "instance");
const char* data = info.getValue(prefix + "data");
if (instance && !findPresence(id, *instance)) {
Presence* pres = new Presence(id, true, *instance, data);
ol->append(pres)->setDelete(false);
}
}
return ol;
}
Presence* PresenceModule::findPresence(const String& contact, const String& instance)
{
if (TelEngine::null(contact))
return 0;
DDebug(this,DebugAll,"findPresence('%s','%s')",contact.c_str(),instance.c_str());
unsigned int index = contact.hash() % m_listCount;
Presence* pres = m_list[index].findPresence(contact, instance);
if (pres)
return pres;
NamedList info("");
if (!getInfoDB(contact, instance, &info))
return 0;
int count = info.getIntValue("count");
for (int i = 1; i <= count; i++) {
String data = info.getValue("data");
Presence* pres = new Presence(contact, true, instance, data);
addPresence(pres, true);
return pres;
}
return 0;
}
// Build a 'database' message used to update presence
Message* PresenceModule::buildUpdateDb(const Presence& pres, bool newPres)
{
String& query = newPres ? m_insertDB : m_updateDB;
if (!(m_accountDB && query))
return 0;
Message* msg = new Message("database");
msg->addParam("account",m_accountDB);
NamedList p("");
p.addParam("contact",pres.toString());
p.addParam("instance",pres.getInstance());
p.addParam("nodename",Engine::nodeName());
p.addParam("data",pres.data());
String tmp = query;
p.replaceParams(tmp,true);
msg->addParam("query",tmp);
return msg;
}
// Build a 'database' message used to delete presence
Message* PresenceModule::buildDeleteDb(const Presence& pres)
{
if (!(m_accountDB && m_removeResDB))
return 0;
Message* msg = new Message("database");
msg->addParam("account",m_accountDB);
NamedList p("");
p.addParam("contact",pres.toString());
p.addParam("instance",pres.getInstance());
String tmp = m_removeResDB;
p.replaceParams(tmp,true);
msg->addParam("query",tmp);
return msg;
}
bool PresenceModule::insertDB(Presence* pres)
{
if (!pres || TelEngine::null(m_insertDB))
return false;
NamedList p("");
p.addParam("contact", pres->toString());
p.addParam("instance", pres->getInstance());
p.addParam("nodename", Engine::nodeName());
p.addParam("data", pres->data());
Message* msg = queryDb(m_accountDB, m_insertDB, p);
if (msg) {
TelEngine::destruct(msg);
return true;
}
return false;
}
bool PresenceModule::updateDB(Presence* pres)
{
if (!pres || TelEngine::null(m_updateDB))
return false;
NamedList p("");
p.addParam("contact", pres->toString());
p.addParam("instance", pres->getInstance());
p.addParam("nodename", Engine::nodeName());
p.addParam("data", pres->data());
Message* msg = queryDb(m_accountDB, m_updateDB, p);
if (msg) {
TelEngine::destruct(msg);
return true;
}
return false;
}
bool PresenceModule::removeDB(Presence* pres, bool allInstances, bool allPresences, String machine)
{
NamedList queryList("");
String query;
if (TelEngine::null(machine))
queryList.addParam("nodename", Engine::nodeName());
else
queryList.addParam("nodename", machine);
if (allPresences) {
query = m_removeAllDB;
}
else {
if (!pres)
return false;
if (!allInstances) {
query = m_removeResDB;
queryList.addParam("instance", pres->getInstance());
}
else
query = m_removePresDB;
queryList.addParam("contact", pres->toString());
}
Message* msg = queryDb(m_accountDB, query, queryList);
if (msg) {
int n = msg->getIntValue("affected");
if (n > 0)
Debug(this, DebugInfo, "Removed %d items from database", n);
TelEngine::destruct(msg);
return true;
}
return true;
}
// check only if a contact with/without instance is present, return true or false
bool PresenceModule::queryDB(String id, String instance)
{
if (TelEngine::null(id))
return false;
NamedList queryList("");
String query;
queryList.addParam("contact", id);
if (TelEngine::null(instance))
query = m_selectPresDB;
else {
queryList.addParam("instance", instance);
query = m_selectResDB;
}
Message* msg = queryDb(m_accountDB, query, queryList);
if (msg) {
bool ok = msg->getIntValue("rows") > 0;
TelEngine::destruct(msg);
return ok;
}
return false;
}
// Interrogate database about a contact and retrieve data about it
bool PresenceModule::getInfoDB(String id, String instance, NamedList* result)
{
if (TelEngine::null(id))
return false;
NamedList queryList("");
String query;
queryList.addParam("contact", id);
if (TelEngine::null(instance))
query = m_selectPresDB;
else {
queryList.addParam("instance", instance);
query = m_selectResDB;
}
Message* msg = queryDb(m_accountDB, query, queryList);
Array* res = msg ? static_cast<Array*>(msg->userObject("Array")) : 0;
int n = res ? msg->getIntValue("rows") : 0;
if (!msg || n < 1) {
TelEngine::destruct(msg);
return false;
}
result->setParam("count", String(res->getRows() - 1));
for (int i = 0; i < res->getColumns(); i++) {
String* colName = YOBJECT(String, res->get(i, 0));
if (!(colName && *colName))
continue;
for (int j = 1; j < res->getRows(); j++) {
String* val = YOBJECT(String, res->get(i, j));
String paramName = String(j);
paramName << "." << *colName;
if (!val)
continue;
result->setParam(paramName, *val);
}
}
TelEngine::destruct(msg);
return true;
}
// Build and dispatch a 'database' message. Replace query params
Message* PresenceModule::queryDb(const String& account, const String& query,
const NamedList& params)
{
Message* msg = new Message("database");
msg->addParam("account", account);
String tmp = query;
params.replaceParams(tmp,true);
msg->addParam("query", tmp);
msg->addParam("results", String::boolText(true));
if (!Engine::dispatch(msg) || msg->getParam("error")) {
DDebug(this,DebugNote,"Database query '%s' failed error='%s'",
tmp.c_str(),msg->getValue("error"));
TelEngine::destruct(msg);
}
return msg;
}
// Dispatch a 'database' message. Return a valid Message pointer on success
// Consume the given pointer
Message* PresenceModule::queryDb(Message* msg)
{
if (!msg)
return 0;
if (!Engine::dispatch(msg) || msg->getParam("error")) {
DDebug(this,DebugNote,"Database query '%s' failed error='%s'",
msg->getValue("query"),msg->getValue("error"));
TelEngine::destruct(msg);
}
return msg;
}
}
/* vi: set ts=8 sw=4 sts=4 noet: */

File diff suppressed because it is too large Load Diff

474
modules/server/users.cpp Normal file
View File

@ -0,0 +1,474 @@
/**
* users.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Users module
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2009 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 <yatephone.h>
using namespace TelEngine;
namespace {
class UsersModule;
class UserUpdateHandler;
/**
* Class UserUpdateHandler
* Handles a user.update message
*/
class UserUpdateHandler : public MessageHandler
{
public:
inline UserUpdateHandler(unsigned int priority = 100)
: MessageHandler("user.update", priority)
{ }
virtual ~UserUpdateHandler()
{ }
virtual bool received(Message& msg);
};
/**
* Class UsersModule
* Module for handling users operations
*/
class UsersModule : public Module
{
public:
enum Commands {
CmdUpdate = 1,
CmdAdd,
CmdDelete,
};
UsersModule();
virtual ~UsersModule();
// Check if a message was sent by us
inline bool isModule(const Message& msg) {
String* module = msg.getParam("module");
return module && *module == name();
}
// Build a message. Fill the module parameter
inline Message* message(const char* msg) {
Message* m = new Message(msg);
m->addParam("module",name());
return m;
}
//inherited methods
virtual void initialize();
// uninstall relays and message handlers
bool unload();
// User management
bool addUser(const NamedList& params, String* error = 0);
bool deleteUser(const NamedList& params, String* error = 0);
bool updateUser(const NamedList& params, String* error = 0);
bool searchUser(const NamedList& params);
// Notify user changes add/del/update
void notifyUser(const char* user, const char* notify);
// Build and dispatch a database message. Return true on success
bool queryDb(const String& account, const String& query,
const NamedList& params, String& error, bool search = false);
// parse the command line
bool parseParams(const String& line, NamedList& parsed, String& error);
// Module commands
static const TokenDict s_cmds[];
protected:
// inherited methods
virtual bool received(Message& msg, int id);
virtual bool commandExecute(String& retVal, const String& line);
virtual bool commandComplete(Message& msg, const String& partLine, const String& partWord);
private:
//flag for first time initialization
bool m_init;
// Query strings
// SQL statement for inserting to database
String m_insertDB;
// SQL statement for updating information in the database
String m_updateDB;
String m_removeDB;
// SQL statement for interrogating the database about a contact with a resource
String m_selectDB;
// database connection
String m_accountDB;
UserUpdateHandler* m_updateHandler;
};
INIT_PLUGIN(UsersModule);
const TokenDict UsersModule::s_cmds[] = {
{"update", CmdUpdate},
{"add", CmdAdd},
{"delete", CmdDelete},
{0,0}
};
static const char* s_cmdsLine = "users {add user [parameter=value...]|delete user|update user [parameter=value...]}";
UNLOAD_PLUGIN(unloadNow)
{
if (unloadNow && !__plugin.unload())
return false;
return true;
}
// Get a space separated word from a buffer. msgUnescape() it if requested
// Return false if empty
static bool getWord(String& buf, String& word, bool unescape = false)
{
XDebug(&__plugin,DebugAll,"getWord(%s)",buf.c_str());
int pos = buf.find(" ");
if (pos >= 0) {
word = buf.substr(0,pos);
buf = buf.substr(pos + 1);
}
else {
word = buf;
buf = "";
}
if (!word)
return false;
if (unescape)
word.msgUnescape();
return true;
}
/**
* UserUpdateHandler
*/
// could change
bool UserUpdateHandler::received(Message& msg)
{
if (__plugin.isModule(msg))
return false;
// TODO
// see message parameters and what to do with them
String* operation = msg.getParam("operation");
String* user = msg.getParam("user");
if (TelEngine::null(operation) || TelEngine::null(user)) {
msg.setParam("error","Mandatory parameters missing");
return false;
}
NamedList params("");
params.addParam("user",*user);
params.copyParams(msg,"password");
String msgPrefix = msg.getValue("message-prefix");
if (!msgPrefix.null()) {
msgPrefix << ".";
for (unsigned int i = 0; i < msg.length(); i++) {
NamedString* ns = msg.getParam(i);
if (ns && ns->name().startsWith(msgPrefix))
params.addParam(ns->name().substr(msgPrefix.length()),*ns);
}
}
bool ok = false;
if (*operation == "add")
ok = __plugin.addUser(params);
else if (*operation == "delete")
ok = __plugin.deleteUser(params);
else if (*operation == "update")
ok = __plugin.updateUser(params);
else
return false;
if (ok)
__plugin.notifyUser(*user,*operation);
else
msg.setParam("error","failure");
return ok;
}
/**
* UsersModule
*/
UsersModule:: UsersModule()
: Module("users","misc"),
m_updateHandler(0)
{
Output("Loaded module Users Management");
}
UsersModule::~UsersModule()
{
Output("Unloaded module Users Management");
TelEngine::destruct(m_updateHandler);
}
void UsersModule::initialize()
{
Output("Initializing module Users Management");
Configuration cfg(Engine::configFile("users"));
cfg.load();
if (!m_init) {
m_init = true;
setup();
installRelay(Halt);
installRelay(Help);
m_updateHandler = new UserUpdateHandler();
Engine::install(m_updateHandler);
// queries init
m_insertDB = cfg.getValue("database", "add_user");
m_updateDB = cfg.getValue("database", "update_user");
m_removeDB = cfg.getValue("database", "remove_user");
m_selectDB = cfg.getValue("database", "select_user");
// database connection init
m_accountDB = cfg.getValue("database", "account");
}
}
bool UsersModule::unload()
{
DDebug(this,DebugAll,"unload()");
if (!lock(500000))
return false;
uninstallRelays();
Engine::uninstall(m_updateHandler);
unlock();
return true;
}
bool UsersModule::addUser(const NamedList& params, String* error)
{
if (TelEngine::null(m_insertDB))
return false;
String tmp;
if (!error)
error = &tmp;
if (!searchUser(params)) {
if (queryDb(m_accountDB,m_insertDB,params,*error)) {
Debug(this,DebugAll,"Added user '%s'",params.getValue("user"));
return true;
}
if (error->null())
*error = "Failure";
}
else
*error = "Already exists";
Debug(this,DebugInfo,"Failed to add user '%s' error='%s'",
params.getValue("user"),error->c_str());
return false;
}
bool UsersModule::deleteUser(const NamedList& params, String* error)
{
if (TelEngine::null(m_removeDB))
return false;
String tmp;
if (!error)
error = &tmp;
if (queryDb(m_accountDB,m_removeDB,params,*error)) {
Debug(this,DebugAll,"Deleted user '%s'",params.getValue("user"));
return true;
}
if (error->null())
*error = "User not found";
Debug(this,DebugInfo,"Failed to delete user '%s' error='%s'",
params.getValue("user"),error->c_str());
return false;
}
bool UsersModule::updateUser(const NamedList& params, String* error)
{
if (TelEngine::null(m_updateDB))
return false;
String tmp;
if (!error)
error = &tmp;
if (queryDb(m_accountDB,m_updateDB,params,*error)) {
Debug(this,DebugAll,"Updated user '%s'",params.getValue("user"));
return true;
}
if (!error)
*error = "User not found";
Debug(this,DebugInfo,"Failed to update user '%s' error='%s'",
params.getValue("user"),error->c_str());
return false;
}
bool UsersModule::searchUser(const NamedList& params)
{
if (TelEngine::null(m_selectDB))
return false;
String error;
return queryDb(m_accountDB,m_selectDB,params,error,true);
}
// Notify user changes add/del/update
void UsersModule::notifyUser(const char* user, const char* notify)
{
Message* m = __plugin.message("user.update");
m->addParam("notify",notify);
m->addParam("user",user);
Engine::enqueue(m);
}
// Build and dispatch a database message. Return true on success
bool UsersModule::queryDb(const String& account, const String& query,
const NamedList& params, String& error, bool search)
{
Message msg("database");
msg.addParam("module",name());
msg.addParam("account", account);
String tmp = query;
params.replaceParams(tmp,true);
msg.addParam("query", tmp);
msg.addParam("results", String::boolText(true));
bool ok = Engine::dispatch(msg) && !msg.getParam("error");
if (ok) {
if (query != m_insertDB) {
if (search)
ok = msg.getIntValue("rows") > 1;
else
ok = msg.getIntValue("affected") >= 1;
}
else {
Array* a = static_cast<Array*>(msg.userObject("Array"));
String* res = a ? YOBJECT(String,a->get(0,1)) : 0;
ok = res && (res->toInteger() != 0);
}
}
if (!ok)
error = msg.getValue("error");
return ok;
}
bool UsersModule::parseParams(const String& line, NamedList& parsed, String& error)
{
Debug(this,DebugAll,"parseParams(%s)",line.c_str());
bool ok = true;
ObjList* list = line.split(' ',false);
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
int pos = s->find("=");
// Empty parameter name is not allowed
if (pos < 1) {
error << "Invalid parameter " << *s;
ok = false;
break;
}
String name = s->substr(0,pos);
String value = s->substr(pos + 1);
name.msgUnescape();
value.msgUnescape();
parsed.addParam(name,value);
DDebug(&__plugin,DebugAll,"parseParams() found '%s'='%s'",name.c_str(),value.c_str());
}
TelEngine::destruct(list);
return ok;
}
bool UsersModule::received(Message& msg, int id)
{
if (id == Halt)
unload();
else if (id == Help) {
String line = msg.getValue("line");
if (line.null()) {
msg.retValue() << " " << s_cmdsLine << "\r\n";
return false;
}
if (line != name())
return false;
msg.retValue() << "Commands used to control the Users Management module\r\n";
msg.retValue() << s_cmdsLine << "\r\n";
return true;
}
return Module::received(msg,id);
}
bool UsersModule::commandExecute(String& retVal, const String& line)
{
String tmp(line);
if (!tmp.startSkip(name(),false))
return false;
tmp.trimSpaces();
XDebug(this,DebugAll,"commandExecute(%s)",tmp.c_str());
// Retrieve the command
String cmdStr;
int cmd = 0;
if (getWord(tmp,cmdStr))
cmd = lookup(cmdStr,s_cmds);
if (!cmd) {
retVal << "Unknown command\r\n";
return true;
}
// Retrieve the user
String user;
if (!getWord(tmp,user,true)) {
retVal << "Empty username\r\n";
return true;
}
// Execute the command
bool ok = false;
String error;
if (cmd == CmdUpdate || cmd == CmdAdd || cmd == CmdDelete) {
NamedList p("");
p.addParam("user",user);
if (parseParams(tmp,p,error)) {
if (cmd == CmdUpdate)
ok = updateUser(p,&error);
else if (cmd == CmdAdd)
ok = addUser(p,&error);
else
ok = deleteUser(p,&error);
}
if (ok)
notifyUser(user,cmdStr);
}
else {
Debug(this,DebugStub,"Command '%s' not implemented",cmdStr.c_str());
error = "Unknown command";
}
retVal << name() << " " << cmdStr << (ok ? " succedded" : " failed");
if (!ok && error)
retVal << ". " << error;
retVal << "\r\n";
return true;
}
bool UsersModule::commandComplete(Message& msg, const String& partLine, const String& partWord)
{
if (partLine.null() && partWord.null())
return false;
XDebug(this,DebugAll,"commandComplete() partLine='%s' partWord=%s",
partLine.c_str(),partWord.c_str());
// No line or 'help': complete module name
if (partLine.null() || partLine == "help")
return Module::itemComplete(msg.retValue(),name(),partWord);
// Line is module name: complete module commands
if (partLine == name()) {
for (const TokenDict* list = s_cmds; list->token; list++)
Module::itemComplete(msg.retValue(),list->token,partWord);
return true;
}
return Module::commandComplete(msg,partLine,partWord);
}
}
/* vi: set ts=8 sw=4 sts=4 noet: */

File diff suppressed because it is too large Load Diff