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://yate.null.ro/svn/yate/trunk@2882 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
parent
23507de97f
commit
d6966c6347
|
@ -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 :=
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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}'
|
|
@ -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=
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
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
File diff suppressed because it is too large
Load Diff
|
@ -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
|
@ -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: */
|
|
@ -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: */
|
|
@ -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: */
|
|
@ -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
|
@ -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 =
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
1596
libs/yxml/tinyxml.h
1596
libs/yxml/tinyxml.h
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
|
@ -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
|
@ -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: */
|
|
@ -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: */
|
|
@ -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),
|
||||
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,9 +594,10 @@ OpenSSL::~OpenSSL()
|
|||
|
||||
void OpenSSL::initialize()
|
||||
{
|
||||
if (m_handler)
|
||||
return;
|
||||
Output("Initializing module OpenSSL");
|
||||
Configuration cfg(Engine::configFile("openssl"));
|
||||
if (!m_handler) {
|
||||
setup();
|
||||
::SSL_load_error_strings();
|
||||
::SSL_library_init();
|
||||
addRand(Time::now());
|
||||
|
@ -438,6 +609,75 @@ void OpenSSL::initialize()
|
|||
#ifndef OPENSSL_NO_AES
|
||||
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
|
||||
|
|
|
@ -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
|
@ -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
Loading…
Reference in New Issue