822 lines
27 KiB
C++
822 lines
27 KiB
C++
|
/**
|
||
|
* .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: */
|