Added external component support to jabber server.
git-svn-id: http://yate.null.ro/svn/yate/trunk@2893 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
parent
0ccfd52bc4
commit
96d283216d
|
@ -88,6 +88,7 @@
|
|||
; Allowed values:
|
||||
; c2s Client to server connection
|
||||
; s2s Server to server connection
|
||||
; comp External component connection
|
||||
;type=
|
||||
|
||||
; address: string: IP address to listen
|
||||
|
@ -98,6 +99,7 @@
|
|||
; These are the default values for some known types (only if this parameter is missing)
|
||||
; c2s 5222
|
||||
; s2s 5269
|
||||
; Tehre is no default value for external component listeners
|
||||
;port=
|
||||
|
||||
; backlog: integer: Maximum length of the queue of pending connections
|
||||
|
|
|
@ -819,7 +819,9 @@ bool JBEngine::acceptConn(Socket* sock, SocketAddr& remote, JBStream::Type t)
|
|||
if (t == JBStream::c2s)
|
||||
s = new JBClientStream(this,sock);
|
||||
else if (t == JBStream::s2s)
|
||||
s = new JBServerStream(this,sock);
|
||||
s = new JBServerStream(this,sock,false);
|
||||
else if (t == JBStream::comp)
|
||||
s = new JBServerStream(this,sock,true);
|
||||
if (s)
|
||||
addStream(s);
|
||||
else
|
||||
|
@ -1096,23 +1098,6 @@ void JBEngine::removeStream(JBStream* stream, bool delObj)
|
|||
unlock();
|
||||
}
|
||||
|
||||
// Find a stream by its name in a given set list
|
||||
JBStream* JBEngine::findStream(const String& id, JBStreamSetList* list)
|
||||
{
|
||||
if (!list)
|
||||
return 0;
|
||||
Lock lock(list);
|
||||
ObjList* found = 0;
|
||||
for (ObjList* o = list->sets().skipNull(); !found && o; o = o->skipNext()) {
|
||||
JBStreamSet* set = static_cast<JBStreamSet*>(o->get());
|
||||
found = set->clients().find(id);
|
||||
}
|
||||
JBStream* stream = found ? static_cast<JBStream*>(found->get()) : 0;
|
||||
if (stream && !stream->ref())
|
||||
stream = 0;
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Add/remove a connect stream thread when started/stopped
|
||||
void JBEngine::connectStatus(JBConnect* conn, bool started)
|
||||
{
|
||||
|
@ -1130,6 +1115,23 @@ void JBEngine::connectStatus(JBConnect* conn, bool started)
|
|||
}
|
||||
}
|
||||
|
||||
// Find a stream by its name in a given set list
|
||||
JBStream* JBEngine::findStream(const String& id, JBStreamSetList* list)
|
||||
{
|
||||
if (!list)
|
||||
return 0;
|
||||
Lock lock(list);
|
||||
ObjList* found = 0;
|
||||
for (ObjList* o = list->sets().skipNull(); !found && o; o = o->skipNext()) {
|
||||
JBStreamSet* set = static_cast<JBStreamSet*>(o->get());
|
||||
found = set->clients().find(id);
|
||||
}
|
||||
JBStream* stream = found ? static_cast<JBStream*>(found->get()) : 0;
|
||||
if (stream && !stream->ref())
|
||||
stream = 0;
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* JBServerEngine
|
||||
|
@ -1137,7 +1139,8 @@ void JBEngine::connectStatus(JBConnect* conn, bool started)
|
|||
JBServerEngine::JBServerEngine(const char* name)
|
||||
: JBEngine(name),
|
||||
m_streamIndex(0),
|
||||
m_c2sReceive(0), m_c2sProcess(0), m_s2sReceive(0), m_s2sProcess(0)
|
||||
m_c2sReceive(0), m_c2sProcess(0), m_s2sReceive(0), m_s2sProcess(0),
|
||||
m_compReceive(0), m_compProcess(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1158,6 +1161,8 @@ void JBServerEngine::cleanup(bool final, bool waitTerminate)
|
|||
TelEngine::destruct(m_c2sProcess);
|
||||
TelEngine::destruct(m_s2sReceive);
|
||||
TelEngine::destruct(m_s2sProcess);
|
||||
TelEngine::destruct(m_compReceive);
|
||||
TelEngine::destruct(m_compProcess);
|
||||
}
|
||||
|
||||
// Stop all stream sets
|
||||
|
@ -1166,23 +1171,14 @@ void JBServerEngine::stopStreamSets(bool waitTerminate)
|
|||
XDebug(this,DebugAll,"JBServerEngine::stopStreamSets() wait=%s",
|
||||
String::boolText(waitTerminate));
|
||||
lock();
|
||||
RefPointer<JBStreamSetList> c2sReceive = m_c2sReceive;
|
||||
RefPointer<JBStreamSetList> c2sProcess = m_c2sProcess;
|
||||
RefPointer<JBStreamSetList> s2sReceive = m_s2sReceive;
|
||||
RefPointer<JBStreamSetList> s2sProcess = m_s2sProcess;
|
||||
RefPointer<JBStreamSetList> sets[6] = {m_c2sReceive,m_c2sProcess,
|
||||
m_s2sReceive,m_s2sProcess,m_compReceive,m_compProcess};
|
||||
unlock();
|
||||
if (c2sReceive)
|
||||
c2sReceive->stop(0,waitTerminate);
|
||||
if (c2sProcess)
|
||||
c2sProcess->stop(0,waitTerminate);
|
||||
if (s2sReceive)
|
||||
s2sReceive->stop(0,waitTerminate);
|
||||
if (s2sProcess)
|
||||
s2sProcess->stop(0,waitTerminate);
|
||||
c2sReceive = 0;
|
||||
c2sProcess = 0;
|
||||
s2sReceive = 0;
|
||||
s2sProcess = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
if (sets[i])
|
||||
sets[i]->stop(0,waitTerminate);
|
||||
for (int j = 0; j < 6; j++)
|
||||
sets[j] = 0;
|
||||
}
|
||||
|
||||
// Retrieve the list of streams of a given type
|
||||
|
@ -1193,9 +1189,11 @@ void JBServerEngine::getStreamList(RefPointer<JBStreamSetList>& list, int type)
|
|||
list = m_c2sReceive;
|
||||
else if (type == JBStream::s2s)
|
||||
list = m_s2sReceive;
|
||||
else if (type == JBStream::comp)
|
||||
list = m_compReceive;
|
||||
}
|
||||
|
||||
// Find a server to server stream by local/remote domain.
|
||||
// Find a server to server or component stream by local/remote domain.
|
||||
// Skip over outgoing dialback streams
|
||||
JBServerStream* JBServerEngine::findServerStream(const String& local, const String& remote,
|
||||
bool out)
|
||||
|
@ -1203,29 +1201,34 @@ JBServerStream* JBServerEngine::findServerStream(const String& local, const Stri
|
|||
if (!(local && remote))
|
||||
return 0;
|
||||
lock();
|
||||
RefPointer<JBStreamSetList> list = m_s2sReceive;
|
||||
RefPointer<JBStreamSetList> list[2] = {m_s2sReceive,m_compReceive};
|
||||
unlock();
|
||||
if (!list)
|
||||
return 0;
|
||||
JBServerStream* stream = 0;
|
||||
list->lock();
|
||||
for (ObjList* o = list->sets().skipNull(); o; o = o->skipNext()) {
|
||||
JBStreamSet* set = static_cast<JBStreamSet*>(o->get());
|
||||
for (ObjList* s = set->clients().skipNull(); s; s = s->skipNext()) {
|
||||
stream = static_cast<JBServerStream*>(s->get());
|
||||
if (out == stream->outgoing() && !stream->dialback()) {
|
||||
// Lock the stream: remote jid might change
|
||||
Lock lock(stream);
|
||||
if (local == stream->local() && remote == stream->remote()) {
|
||||
stream->ref();
|
||||
break;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
list[i]->lock();
|
||||
for (ObjList* o = list[i]->sets().skipNull(); o; o = o->skipNext()) {
|
||||
JBStreamSet* set = static_cast<JBStreamSet*>(o->get());
|
||||
for (ObjList* s = set->clients().skipNull(); s; s = s->skipNext()) {
|
||||
stream = static_cast<JBServerStream*>(s->get());
|
||||
if (stream->type() == JBStream::comp ||
|
||||
(out == stream->outgoing() && !stream->dialback())) {
|
||||
// Lock the stream: remote jid might change
|
||||
Lock lock(stream);
|
||||
if (local == stream->local() && remote == stream->remote()) {
|
||||
stream->ref();
|
||||
break;
|
||||
}
|
||||
}
|
||||
stream = 0;
|
||||
}
|
||||
stream = 0;
|
||||
if (stream)
|
||||
break;
|
||||
}
|
||||
list[i]->unlock();
|
||||
if (stream)
|
||||
break;
|
||||
}
|
||||
list->unlock();
|
||||
list = 0;
|
||||
list[0] = list[1] = 0;
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
@ -1286,6 +1289,10 @@ void JBServerEngine::addStream(JBStream* stream)
|
|||
recv = m_s2sReceive;
|
||||
process = m_s2sProcess;
|
||||
}
|
||||
else if (stream->type() == JBStream::comp) {
|
||||
recv = m_compReceive;
|
||||
process = m_compProcess;
|
||||
}
|
||||
unlock();
|
||||
if (recv && process) {
|
||||
recv->add(stream);
|
||||
|
@ -1318,6 +1325,10 @@ void JBServerEngine::removeStream(JBStream* stream, bool delObj)
|
|||
recv = m_s2sReceive;
|
||||
process = m_s2sProcess;
|
||||
}
|
||||
else if (stream->type() == JBStream::comp) {
|
||||
recv = m_compReceive;
|
||||
process = m_compProcess;
|
||||
}
|
||||
unlock();
|
||||
if (recv)
|
||||
recv->remove(stream,delObj);
|
||||
|
|
|
@ -113,6 +113,7 @@ const TokenDict JBStream::s_flagName[] = {
|
|||
const TokenDict JBStream::s_typeName[] = {
|
||||
{"c2s", c2s},
|
||||
{"s2s", s2s},
|
||||
{"comp", comp},
|
||||
{0,0}
|
||||
};
|
||||
|
||||
|
@ -536,6 +537,10 @@ bool JBStream::authenticated(bool ok, const String& rsp, XMPPError::Type error)
|
|||
XmlElement* rsp = XMPPUtils::createDialbackResult(m_local,m_remote,true);
|
||||
ok = sendStreamXml(Running,rsp);
|
||||
}
|
||||
else if (m_type == comp) {
|
||||
XmlElement* rsp = XMPPUtils::createElement(XmlTag::Handshake);
|
||||
ok = sendStreamXml(Running,rsp);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (m_type == c2s) {
|
||||
|
@ -548,6 +553,8 @@ bool JBStream::authenticated(bool ok, const String& rsp, XMPPError::Type error)
|
|||
if (ok)
|
||||
terminate(0,true,0,XMPPError::NotAuthorized);
|
||||
}
|
||||
else if (m_type == comp)
|
||||
terminate(0,true,0,XMPPError::NotAuthorized);
|
||||
}
|
||||
TelEngine::destruct(m_sasl);
|
||||
return ok;
|
||||
|
@ -738,7 +745,7 @@ void JBStream::process(u_int64_t time)
|
|||
JabberID to;
|
||||
if (!getJids(root,from,to))
|
||||
break;
|
||||
XDebug(this,DebugAll,"Processing (%p,%s) in state %s [%p]",
|
||||
Debug(this,DebugAll,"Processing (%p,%s) in state %s [%p]",
|
||||
root,root->tag(),stateName(),this);
|
||||
processStart(root,from,to);
|
||||
break;
|
||||
|
@ -988,6 +995,7 @@ bool JBStream::processAuth(XmlElement* xml, const JabberID& from,
|
|||
// Check if a received start start element's namespaces are correct.
|
||||
bool JBStream::processStreamStart(const XmlElement* xml)
|
||||
{
|
||||
XDebug(this,DebugAll,"JBStream::processStreamStart() [%p]",this);
|
||||
if (m_state == Starting)
|
||||
return true;
|
||||
changeState(Starting);
|
||||
|
@ -1000,7 +1008,7 @@ bool JBStream::processStreamStart(const XmlElement* xml)
|
|||
XMPPError::Type error = XMPPError::NoError;
|
||||
const char* reason = 0;
|
||||
while (true) {
|
||||
if (m_type != c2s && m_type != s2s) {
|
||||
if (m_type != c2s && m_type != s2s && m_type != comp) {
|
||||
Debug(this,DebugStub,"processStreamStart() type %u not handled!",m_type);
|
||||
error = XMPPError::Internal;
|
||||
break;
|
||||
|
@ -1039,7 +1047,7 @@ bool JBStream::processStreamStart(const XmlElement* xml)
|
|||
else
|
||||
error = XMPPError::EncryptionRequired;
|
||||
}
|
||||
else
|
||||
else if (m_type != comp)
|
||||
error = XMPPError::Internal;
|
||||
}
|
||||
else if (remoteVersion > 1)
|
||||
|
@ -1162,14 +1170,16 @@ bool JBStream::checkStanzaRecv(XmlElement* xml, JabberID& from, JabberID& to)
|
|||
"Possible checkStanzaRecv() unhandled outgoing c2s stream [%p]",this);
|
||||
}
|
||||
break;
|
||||
case comp:
|
||||
case s2s:
|
||||
// RFC 3920bis 9.1.1.2 and 9.1.2.1:
|
||||
// Validate 'to' and 'from'
|
||||
// Accept anything for component streams
|
||||
if (!(to && from)) {
|
||||
terminate(0,m_incoming,xml,XMPPError::BadAddressing);
|
||||
return false;
|
||||
}
|
||||
if (!m_engine->hasDomain(to.domain())) {
|
||||
if (m_type == s2s && !m_engine->hasDomain(to.domain())) {
|
||||
terminate(0,m_incoming,xml,XMPPError::HostUnknown);
|
||||
return false;
|
||||
}
|
||||
|
@ -1582,6 +1592,20 @@ bool JBStream::processFeaturesIn(XmlElement* xml, const JabberID& from, const Ja
|
|||
if (!xml->getTag(t,nsName))
|
||||
return dropXml(xml,"invalid tag namespace prefix");
|
||||
int ns = nsName ? XMPPUtils::s_ns[*nsName] : XMPPNamespace::Count;
|
||||
|
||||
// Component: Waiting for handshake in the stream namespace
|
||||
if (type() == comp) {
|
||||
if (outgoing())
|
||||
return dropXml(xml,"invalid state for incoming stream");
|
||||
if (*t != XMPPUtils::s_tag[XmlTag::Handshake] || ns != m_xmlns)
|
||||
return dropXml(xml,"expecting handshake in stream's namespace");
|
||||
JBEvent* ev = new JBEvent(JBEvent::Auth,this,xml,from,to);
|
||||
ev->m_text = xml->getText();
|
||||
m_events.append(ev);
|
||||
changeState(Auth);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if received unexpected feature
|
||||
if (!m_features.get(ns)) {
|
||||
// Check for some features that can be negotiated via 'iq' elements
|
||||
|
@ -1812,6 +1836,9 @@ void JBStream::setXmlns()
|
|||
case s2s:
|
||||
m_xmlns = XMPPNamespace::Server;
|
||||
break;
|
||||
case comp:
|
||||
m_xmlns = XMPPNamespace::ComponentAccept;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2308,8 +2335,8 @@ bool JBClientStream::bind()
|
|||
* JBServerStream
|
||||
*/
|
||||
// Build an incoming stream from a socket
|
||||
JBServerStream::JBServerStream(JBEngine* engine, Socket* socket)
|
||||
: JBStream(engine,socket,s2s),
|
||||
JBServerStream::JBServerStream(JBEngine* engine, Socket* socket, bool component)
|
||||
: JBStream(engine,socket,component ? comp : s2s),
|
||||
m_dbKey(0)
|
||||
{
|
||||
}
|
||||
|
@ -2387,7 +2414,7 @@ bool JBServerStream::processRunning(XmlElement* xml, const JabberID& from,
|
|||
// Check the tags of known dialback elements:
|
||||
// there are servers who don't stamp them with the namespace
|
||||
// Let other elements stamped with dialback namespace go the upper layer
|
||||
if (isDbResult(*xml)) {
|
||||
if (type() != comp && isDbResult(*xml)) {
|
||||
if (outgoing())
|
||||
return dropXml(xml,"dialback result on outgoing stream");
|
||||
const char* key = xml->getText();
|
||||
|
@ -2422,15 +2449,21 @@ XmlElement* JBServerStream::buildStreamStart()
|
|||
start->setAttribute("id",m_id);
|
||||
XMPPUtils::setStreamXmlns(*start);
|
||||
start->setAttribute(XmlElement::s_ns,XMPPUtils::s_ns[m_xmlns]);
|
||||
start->setAttribute(XmlElement::s_nsPrefix + "db",XMPPUtils::s_ns[XMPPNamespace::Dialback]);
|
||||
if (!dialback()) {
|
||||
start->setAttributeValid("from",m_local.bare());
|
||||
start->setAttributeValid("to",m_remote.bare());
|
||||
if (!flag(StreamSecured) || flag(TlsRequired)) {
|
||||
start->setAttribute("version","1.0");
|
||||
start->setAttribute("xml:lang","en");
|
||||
if (type() == s2s) {
|
||||
start->setAttribute(XmlElement::s_nsPrefix + "db",XMPPUtils::s_ns[XMPPNamespace::Dialback]);
|
||||
if (!dialback()) {
|
||||
start->setAttributeValid("from",m_local.bare());
|
||||
start->setAttributeValid("to",m_remote.bare());
|
||||
if (!flag(StreamSecured) || flag(TlsRequired)) {
|
||||
start->setAttribute("version","1.0");
|
||||
start->setAttribute("xml:lang","en");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type() == comp) {
|
||||
if (incoming())
|
||||
start->setAttributeValid("from",m_remote.domain());
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
|
@ -2441,9 +2474,22 @@ XmlElement* JBServerStream::buildStreamStart()
|
|||
bool JBServerStream::processStart(const XmlElement* xml, const JabberID& from,
|
||||
const JabberID& to)
|
||||
{
|
||||
XDebug(this,DebugAll,"JBServerStream::processStart() [%p]",this);
|
||||
|
||||
if (!processStreamStart(xml))
|
||||
return true;
|
||||
|
||||
if (type() == comp) {
|
||||
if (incoming()) {
|
||||
changeState(Starting);
|
||||
m_events.append(new JBEvent(JBEvent::Start,this,0,to,JabberID::empty()));
|
||||
return true;
|
||||
}
|
||||
Debug(this,DebugStub,"JBComponentStream::processStart() not implemented for outgoing [%p]",this);
|
||||
terminate(0,true,0,XMPPError::NoError);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outgoing()) {
|
||||
// Wait features ?
|
||||
if (flag(StreamRemoteVer1)) {
|
||||
|
@ -2506,4 +2552,17 @@ bool JBServerStream::processAuth(XmlElement* xml, const JabberID& from,
|
|||
return dropXml(xml,"incomplete state process");
|
||||
}
|
||||
|
||||
// Start the stream (reply to received stream start)
|
||||
bool JBServerStream::startComp(const String& local, const String& remote)
|
||||
{
|
||||
if (state() != Starting || type() != comp)
|
||||
return false;
|
||||
Lock lock(this);
|
||||
m_local.set(local);
|
||||
m_remote.set(remote);
|
||||
setSecured();
|
||||
XmlElement* s = buildStreamStart();
|
||||
return sendStreamXml(Features,s);
|
||||
}
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
||||
|
|
|
@ -223,6 +223,7 @@ const String XMPPNamespace::s_array[Count] = {
|
|||
"jabber:x:oob", // XOob
|
||||
"http://jabber.org/protocol/command", // Command
|
||||
"msgoffline", // MsgOffline
|
||||
"jabber:component:accept", // ComponentAccept
|
||||
};
|
||||
|
||||
const String XMPPError::s_array[Count] = {
|
||||
|
@ -356,6 +357,7 @@ const String XmlTag::s_array[Count] = {
|
|||
"identity", // Identity
|
||||
"priority", // Priority
|
||||
"c", // EntityCapsTag
|
||||
"handshake", // Handshake
|
||||
};
|
||||
|
||||
XMPPNamespace XMPPUtils::s_ns;
|
||||
|
|
|
@ -214,7 +214,8 @@ public:
|
|||
XOob = 44, // jabber:x:oob
|
||||
Command= 45, // http://jabber.org/protocol/command
|
||||
MsgOffline= 46, // msgoffline
|
||||
Count = 47,
|
||||
ComponentAccept = 47, // jabber:component:accept
|
||||
Count = 48,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -399,7 +400,8 @@ public:
|
|||
Identity = 63, // identity
|
||||
Priority = 64, // priority
|
||||
EntityCapsTag = 65, // c
|
||||
Count = 66
|
||||
Handshake = 66, // handshake
|
||||
Count = 67
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
*/
|
||||
namespace TelEngine {
|
||||
|
||||
class SASL; // SASL authentication mechanism
|
||||
class JBEvent; // A Jabber event
|
||||
class JBStream; // A Jabber stream
|
||||
class JBClientStream; // A client to server stream
|
||||
|
@ -230,6 +231,7 @@ public:
|
|||
Start,
|
||||
// Incoming stream need auth: when processing this event, the upper
|
||||
// layer must call stream's authenticated() method
|
||||
// Component: the event's text contains the handshake data
|
||||
Auth,
|
||||
// The event's element is an 'iq' with a child qualified by bind namespace
|
||||
// This event is generated by an incoming client stream without a bound resource
|
||||
|
@ -470,7 +472,8 @@ public:
|
|||
enum Type {
|
||||
c2s = 0, // Client to server
|
||||
s2s = 1, // Server to server
|
||||
TypeCount = 2 // Unknown
|
||||
comp = 2, // External component
|
||||
TypeCount = 3 // Unknown
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -487,6 +490,7 @@ public:
|
|||
WaitTlsRsp = 5, // 'starttls' sent: waiting for response
|
||||
Securing = 10, // Stream is currently negotiating the TLS
|
||||
Auth = 11, // Auth element (db:result for s2s streams) sent
|
||||
// Incoming comp: handshake received
|
||||
Challenge = 12, // 'challenge' element sent/received
|
||||
Register = 20, // A new user is currently registering
|
||||
Running = 100, // Established. Allow XML stanzas to pass over the stream
|
||||
|
@ -1042,6 +1046,14 @@ protected:
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set secured flag. Remove feature from list
|
||||
*/
|
||||
inline void setSecured() {
|
||||
m_flags |= StreamSecured;
|
||||
m_features.remove(XMPPNamespace::Tls);
|
||||
}
|
||||
|
||||
State m_state; // Stream state
|
||||
String m_id; // Stream id
|
||||
JabberID m_local; // Local peer's jid
|
||||
|
@ -1093,11 +1105,6 @@ private:
|
|||
// Return false if stream termination was initiated
|
||||
bool processWaitTlsRsp(XmlElement* xml, const JabberID& from,
|
||||
const JabberID& to);
|
||||
// Set secured flag. Remove feature from list
|
||||
inline void setSecured() {
|
||||
m_flags |= StreamSecured;
|
||||
m_features.remove(XMPPNamespace::Tls);
|
||||
}
|
||||
// Set stream namespace from type
|
||||
void setXmlns();
|
||||
// Event termination notification
|
||||
|
@ -1309,8 +1316,9 @@ public:
|
|||
* Constructor. Build an incoming stream from a socket
|
||||
* @param engine Engine owning this stream
|
||||
* @param socket The socket
|
||||
* @param component True to build an external component stream
|
||||
*/
|
||||
JBServerStream(JBEngine* engine, Socket* socket);
|
||||
JBServerStream(JBEngine* engine, Socket* socket, bool component = false);
|
||||
|
||||
/**
|
||||
* Constructor. Build an outgoing stream
|
||||
|
@ -1380,6 +1388,25 @@ public:
|
|||
*/
|
||||
bool sendDialback();
|
||||
|
||||
/**
|
||||
* Build a component handshake from stream id and secret as defined in XEP 0114
|
||||
* @param buf Destination buffer
|
||||
* @param secret The secret
|
||||
*/
|
||||
inline void buildHandshake(String& buf, const String& secret) {
|
||||
SHA1 sha(id() + secret);
|
||||
buf = sha.hexDigest();
|
||||
buf.toLower();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a component stream (reply to received stream start)
|
||||
* @param local Local domain
|
||||
* @param remote Remote domain
|
||||
* @return True on success
|
||||
*/
|
||||
bool startComp(const String& local, const String& remote);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Release memory
|
||||
|
@ -1782,6 +1809,8 @@ protected:
|
|||
getStreamList(list[JBStream::c2s],JBStream::c2s);
|
||||
if (type == JBStream::s2s || type == JBStream::TypeCount)
|
||||
getStreamList(list[JBStream::s2s],JBStream::s2s);
|
||||
if (type == JBStream::comp || type == JBStream::TypeCount)
|
||||
getStreamList(list[JBStream::comp],JBStream::comp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1849,15 +1878,16 @@ public:
|
|||
{ name << "stream/" << getStreamIndex(); }
|
||||
|
||||
/**
|
||||
* Find a server to server stream by local/remote domain.
|
||||
* Find a server to server or component stream by local/remote domain.
|
||||
* Skip over outgoing dialback only streams
|
||||
* This method is thread safe
|
||||
* @param local Local domain
|
||||
* @param remote Remote domain
|
||||
* @param out True to find an outgoing stream, false to find an incoming one
|
||||
* @param out True to find an outgoing stream, false to find an incoming one.
|
||||
* Ignored for component streams
|
||||
* @return Referenced JBServerStream pointer or 0
|
||||
*/
|
||||
JBServerStream* findServerStream(const String& local, const String& remote, bool out);
|
||||
JBServerStream* findServerStream(const String& local, const String& remote, bool out);
|
||||
|
||||
/**
|
||||
* Create an outgoing s2s stream.
|
||||
|
@ -1924,6 +1954,8 @@ protected:
|
|||
JBStreamSetList* m_c2sProcess; // c2s streams process list
|
||||
JBStreamSetList* m_s2sReceive; // s2s streams receive list
|
||||
JBStreamSetList* m_s2sProcess; // s2s streams process list
|
||||
JBStreamSetList* m_compReceive; // comp streams receive list
|
||||
JBStreamSetList* m_compProcess; // comp streams process list
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -181,8 +181,19 @@ public:
|
|||
}
|
||||
// Replace the list of domains service by this engine
|
||||
void setDomains(const String& list);
|
||||
// Retrieve a subdomain of a serviced domain
|
||||
void getSubDomain(String& subdomain, const String& domain);
|
||||
// Add or remove a component to/from serviced domains and components list
|
||||
void setComponent(const String& domain, bool add);
|
||||
// Check if a component is serviced by this engine
|
||||
bool hasComponent(const String& domain);
|
||||
// Check if a resource name is retricted
|
||||
bool restrictedResource(const String& name);
|
||||
// Check if a domain is serviced by a server item
|
||||
bool isServerItemDomain(const String& domain);
|
||||
// Internally route c2s <--> comp stanzas
|
||||
// Return true if handled
|
||||
bool routeInternal(JBEvent* ev);
|
||||
// Process 'user.roster' notification messages
|
||||
void handleUserRoster(Message& msg);
|
||||
// Process 'user.update' messages
|
||||
|
@ -283,6 +294,7 @@ private:
|
|||
ObjList m_domains; // Domains serviced by this engine
|
||||
ObjList m_restrictedResources; // Resource names the users can't use
|
||||
ObjList m_items;
|
||||
ObjList m_components;
|
||||
XMPPFeatureList m_c2sFeatures; // Server features to advertise on c2s streams
|
||||
XMPPFeatureList m_features; // Server features to advertise on non c2s streams
|
||||
String m_dialbackSecret; // Server dialback secret used to build keys
|
||||
|
@ -651,7 +663,7 @@ static XmlElement* buildRosterItem(NamedList& list, unsigned int index)
|
|||
// Complete stream type
|
||||
static void completeStreamType(String& buf, const String& part, bool addAll = false)
|
||||
{
|
||||
static const String t[] = {"c2s","s2s",""};
|
||||
static const String t[] = {"c2s","s2s","comp", ""};
|
||||
static const String all[] = {"all","*",""};
|
||||
for (const String* d = t; !d->null(); d++)
|
||||
Module::itemComplete(buf,*d,part);
|
||||
|
@ -704,6 +716,8 @@ YJBEngine::YJBEngine()
|
|||
m_c2sProcess = new YStreamSetProcess(this,10,"c2s/process");
|
||||
m_s2sReceive = new YStreamSetReceive(this,0,"s2s/recv");
|
||||
m_s2sProcess = new YStreamSetProcess(this,0,"s2s/process");
|
||||
m_compReceive = new YStreamSetReceive(this,0,"comp/recv");
|
||||
m_compProcess = new YStreamSetProcess(this,0,"comp/process");
|
||||
// c2s features
|
||||
m_c2sFeatures.add(XMPPNamespace::DiscoInfo);
|
||||
m_c2sFeatures.add(XMPPNamespace::DiscoItems);
|
||||
|
@ -792,14 +806,22 @@ void YJBEngine::processEvent(JBEvent* ev)
|
|||
XDebug(this,DebugInfo,"Processing event (%p,%s)",ev,ev->name());
|
||||
switch (ev->type()) {
|
||||
case JBEvent::Message:
|
||||
JBPendingWorker::add(ev);
|
||||
if (!ev->element())
|
||||
break;
|
||||
if (!routeInternal(ev))
|
||||
JBPendingWorker::add(ev);
|
||||
break;
|
||||
case JBEvent::Presence:
|
||||
if (ev->element())
|
||||
if (!ev->element())
|
||||
break;
|
||||
if (!routeInternal(ev))
|
||||
processPresenceStanza(ev);
|
||||
break;
|
||||
case JBEvent::Iq:
|
||||
JBPendingWorker::add(ev);
|
||||
if (!ev->element())
|
||||
break;
|
||||
if (!routeInternal(ev))
|
||||
JBPendingWorker::add(ev);
|
||||
break;
|
||||
case JBEvent::Start:
|
||||
if (ev->stream()->incoming())
|
||||
|
@ -872,8 +894,15 @@ void YJBEngine::buildDialbackKey(const String& id, String& key)
|
|||
// Check if a domain is serviced by this engine
|
||||
bool YJBEngine::hasDomain(const String& domain)
|
||||
{
|
||||
if (!domain)
|
||||
return false;
|
||||
Lock lock(this);
|
||||
return domain && m_domains.find(domain);
|
||||
for (ObjList* o = m_domains.skipNull(); o; o = o->skipNext()) {
|
||||
String* tmp = static_cast<String*>(o->get());
|
||||
if (*tmp == domain)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace the list of domains service by this engine
|
||||
|
@ -902,6 +931,48 @@ void YJBEngine::setDomains(const String& list)
|
|||
Debug(this,DebugGoOn,"No domains configured");
|
||||
}
|
||||
|
||||
// Add or remove a component to/from serviced domains and components list
|
||||
void YJBEngine::setComponent(const String& domain, bool add)
|
||||
{
|
||||
Lock lock(this);
|
||||
ObjList* oc = m_components.skipNull();
|
||||
for (; oc; oc = oc->skipNext()) {
|
||||
String* tmp = static_cast<String*>(oc->get());
|
||||
if (*tmp == domain)
|
||||
break;
|
||||
}
|
||||
ObjList* od = m_domains.skipNull();
|
||||
for (; od; od = od->skipNext()) {
|
||||
String* tmp = static_cast<String*>(od->get());
|
||||
if (*tmp == domain)
|
||||
break;
|
||||
}
|
||||
if (add) {
|
||||
if (!oc)
|
||||
m_components.append(new String(domain));
|
||||
if (!od)
|
||||
m_domains.append(new String(domain));
|
||||
}
|
||||
else {
|
||||
if (oc)
|
||||
oc->remove();
|
||||
if (od)
|
||||
od->remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a component is serviced by this engine
|
||||
bool YJBEngine::hasComponent(const String& domain)
|
||||
{
|
||||
Lock lock(this);
|
||||
for (ObjList* o = m_components.skipNull(); o; o = o->skipNext()) {
|
||||
String* tmp = static_cast<String*>(o->get());
|
||||
if (*tmp == domain)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if a resource name is retricted
|
||||
bool YJBEngine::restrictedResource(const String& name)
|
||||
{
|
||||
|
@ -914,6 +985,60 @@ bool YJBEngine::restrictedResource(const String& name)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check if a domain is serviced by a server item
|
||||
bool YJBEngine::isServerItemDomain(const String& domain)
|
||||
{
|
||||
Lock lock(this);
|
||||
for (ObjList* o = m_items.skipNull(); o; o = o->skipNext()) {
|
||||
JabberID* jid = static_cast<JabberID*>(o->get());
|
||||
if (domain == jid->domain())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Internally route c2s <--> comp stanzas
|
||||
// Return true if handled
|
||||
bool YJBEngine::routeInternal(JBEvent* ev)
|
||||
{
|
||||
JBStream* s = 0;
|
||||
if (ev->stream()->type() == JBStream::s2s) {
|
||||
// Incoming on s2s: check if it should be routed to a component
|
||||
if (!hasComponent(ev->to().domain()))
|
||||
return false;
|
||||
String comp;
|
||||
getSubDomain(comp,ev->to().domain());
|
||||
if (comp) {
|
||||
String local = ev->to().domain().substr(comp.length() + 1);
|
||||
s = findServerStream(local,ev->to().domain(),true);
|
||||
}
|
||||
}
|
||||
else if (ev->stream()->type() == JBStream::comp) {
|
||||
// Incoming on comp: check if it should be routed to a remote domain
|
||||
if (hasDomain(ev->to().domain()))
|
||||
return false;
|
||||
s = findServerStream(ev->from().domain(),ev->to().domain(),true);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
DDebug(this,DebugAll,"routeInternal() src=%s from=%s to=%s stream=%p",
|
||||
ev->stream()->typeName(),ev->from().c_str(),ev->to().c_str(),s);
|
||||
if (s) {
|
||||
XmlElement* xml = ev->releaseXml();
|
||||
bool ok = false;
|
||||
if (xml) {
|
||||
xml->removeAttribute(XmlElement::s_ns);
|
||||
ok = s->sendStanza(xml);
|
||||
}
|
||||
if (!ok)
|
||||
ev->sendStanzaError(XMPPError::Internal);
|
||||
}
|
||||
else
|
||||
ev->sendStanzaError(XMPPError::NoRemote,0,XMPPError::TypeCancel);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process an 'user.roster' messages
|
||||
void YJBEngine::handleUserRoster(Message& msg)
|
||||
{
|
||||
|
@ -976,7 +1101,7 @@ bool YJBEngine::handleJabberIq(Message& msg)
|
|||
return false;
|
||||
DDebug(this,DebugAll,"YJBEngine::handleJabberIq() from=%s to=%s",from.c_str(),to.c_str());
|
||||
JBStream* stream = 0;
|
||||
if (hasDomain(to.domain())) {
|
||||
if (hasDomain(to.domain()) && !hasComponent(to.domain())) {
|
||||
stream = findClientStream(true,to);
|
||||
if (!(stream && stream->flag(JBStream::AvailableResource)))
|
||||
TelEngine::destruct(stream);
|
||||
|
@ -1014,7 +1139,7 @@ bool YJBEngine::handleResSubscribe(Message& msg)
|
|||
msg.c_str(),from.bare().c_str(),to.bare().c_str(),oper->c_str());
|
||||
XmlElement* xml = getPresenceXml(msg,from.bare(),presType);
|
||||
bool ok = false;
|
||||
if (hasDomain(to.domain())) {
|
||||
if (hasDomain(to.domain()) && !hasComponent(to.domain())) {
|
||||
xml->removeAttribute("to");
|
||||
// RFC 3921: (un)subscribe requests are sent only to available resources
|
||||
String* instance = msg.getParam("instance");
|
||||
|
@ -1054,7 +1179,7 @@ bool YJBEngine::handleResNotify(Message& msg)
|
|||
Debug(this,DebugAll,"Processing %s from=%s to=%s oper=%s",
|
||||
msg.c_str(),from.c_str(),to.c_str(),oper->c_str());
|
||||
XmlElement* xml = 0;
|
||||
bool c2s = hasDomain(to.domain());
|
||||
bool c2s = hasDomain(to.domain()) && !hasComponent(to.domain());
|
||||
bool online = (*oper == "online" || *oper == "update");
|
||||
if (online || *oper == "offline" || *oper == "delete") {
|
||||
if (!from.resource())
|
||||
|
@ -1135,7 +1260,7 @@ bool YJBEngine::handleMsgExecute(Message& msg)
|
|||
if (!(caller.resource()))
|
||||
return false;
|
||||
DDebug(this,DebugAll,"handleMsgExecute() caller=%s called=%s",caller.c_str(),called.c_str());
|
||||
if (hasDomain(called.domain())) {
|
||||
if (hasDomain(called.domain()) && !hasComponent(called.domain())) {
|
||||
// RFC 3921 11.1: Send chat only to clients with non-negative resource priority
|
||||
bool ok = false;
|
||||
unsigned int n = msg.getIntValue("instance.count");
|
||||
|
@ -1210,7 +1335,20 @@ bool YJBEngine::handleJabberItem(Message& msg)
|
|||
Debug(this,DebugAll,"Removed item '%s'",jid);
|
||||
}
|
||||
else if (!o) {
|
||||
m_items.append(new String(jid));
|
||||
JabberID* j = new JabberID(jid);
|
||||
String comp;
|
||||
getSubDomain(comp,j->domain());
|
||||
if (comp) {
|
||||
String local(j->domain().substr(comp.length() + 1));
|
||||
if (findServerStream(local,j->domain(),true)) {
|
||||
Debug(this,DebugMild,
|
||||
"Request to add server item '%s' while already having a component in the same domain",
|
||||
jid);
|
||||
TelEngine::destruct(j);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_items.append(j);
|
||||
Debug(this,DebugAll,"Added item '%s'",jid);
|
||||
}
|
||||
return false;
|
||||
|
@ -1324,6 +1462,19 @@ void YJBEngine::processPresenceStanza(JBEvent* ev)
|
|||
ev->sendStanzaError(XMPPError::ServiceUnavailable);
|
||||
}
|
||||
|
||||
// Retrieve a subdomain of a serviced domain
|
||||
void YJBEngine::getSubDomain(String& subdomain, const String& domain)
|
||||
{
|
||||
Lock lock(this);
|
||||
for (ObjList* o = m_domains.skipNull(); o; o = o->skipNext()) {
|
||||
String cmp("." + o->get()->toString());
|
||||
if (domain.endsWith(cmp) && domain.length() > cmp.length()) {
|
||||
subdomain = domain.substr(0,domain.length() - cmp.length());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process a stream start element received by an incoming stream
|
||||
// The given event is always valid and carry a valid stream
|
||||
// Set local domain and stream features to advertise to remote party
|
||||
|
@ -1331,6 +1482,30 @@ void YJBEngine::processStartIn(JBEvent* ev)
|
|||
{
|
||||
static const char* node = "http://yate.null.ro/yate/server/caps";
|
||||
|
||||
JBServerStream* comp = ev->serverStream();
|
||||
if (comp && comp->type() == JBStream::comp) {
|
||||
String sub;
|
||||
if (ev->from() && !ev->from().node() && !ev->from().resource())
|
||||
getSubDomain(sub,ev->from().domain());
|
||||
if (!sub) {
|
||||
comp->terminate(-1,true,0,XMPPError::HostUnknown);
|
||||
return;
|
||||
}
|
||||
String local(ev->from().substr(sub.length() + 1));
|
||||
bool isItem = isServerItemDomain(ev->from().domain());
|
||||
if (isItem || findServerStream(local,ev->from(),false)) {
|
||||
if (isItem)
|
||||
Debug(this,DebugMild,"Component request for server item domain '%s'",
|
||||
ev->from().domain().c_str());
|
||||
comp->terminate(-1,true,0,XMPPError::Conflict);
|
||||
return;
|
||||
}
|
||||
// Add component to serviced domain
|
||||
setComponent(ev->from(),true);
|
||||
comp->startComp(local,ev->from());
|
||||
return;
|
||||
}
|
||||
|
||||
// Set c2s stream TLS required flag
|
||||
if (ev->stream()->type() == JBStream::c2s)
|
||||
ev->stream()->setTlsRequired(m_c2sTlsRequired);
|
||||
|
@ -1485,6 +1660,9 @@ void YJBEngine::processStreamEvent(JBEvent* ev)
|
|||
// TODO: notify offline for all users in remote domain
|
||||
m = userRegister(*s,false);
|
||||
}
|
||||
// Remove component from serviced domain
|
||||
if (ev->stream()->type() == JBStream::comp)
|
||||
setComponent(ev->stream()->remote(),false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -1787,12 +1965,16 @@ bool YJBEngine::sendStanza(XmlElement*& xml, ObjList*& streams)
|
|||
// Build a new one if not found
|
||||
JBStream* YJBEngine::getServerStream(const JabberID& from, const JabberID& to)
|
||||
{
|
||||
// Avoid streams to internal components
|
||||
if (m_items.find(to.domain()) || !hasDomain(from.domain()))
|
||||
return 0;
|
||||
JBServerStream* s = findServerStream(from.domain(),to.domain(),true);
|
||||
if (s)
|
||||
return s;
|
||||
// Avoid streams to internal components or (sub)domains
|
||||
if (m_items.find(to.domain()) || !hasDomain(from.domain()))
|
||||
return 0;
|
||||
String comp;
|
||||
getSubDomain(comp,to.domain());
|
||||
if (comp)
|
||||
return 0;
|
||||
return createServerStream(from.domain(),to.domain());
|
||||
}
|
||||
|
||||
|
@ -1891,9 +2073,11 @@ void YJBEngine::statusParams(String& str)
|
|||
lock();
|
||||
unsigned int c2s = m_c2sReceive ? m_c2sReceive->streamCount() : 0;
|
||||
unsigned int s2s = m_s2sReceive ? m_s2sReceive->streamCount() : 0;
|
||||
unsigned int comp = m_compReceive ? m_compReceive->streamCount() : 0;
|
||||
unlock();
|
||||
str << lookup(JBStream::c2s,JBStream::s_typeName) << "=" << c2s;
|
||||
str << "," << lookup(JBStream::s2s,JBStream::s_typeName) << "=" << s2s;
|
||||
str << "," << lookup(JBStream::comp,JBStream::s_typeName) << "=" << comp;
|
||||
}
|
||||
|
||||
// Fill module status detail
|
||||
|
@ -2235,7 +2419,8 @@ void JBPendingWorker::processChat(JBPendingJob& job)
|
|||
return;
|
||||
}
|
||||
XMPPError::Type error = XMPPError::NoError;
|
||||
bool localTarget = s_jabber->hasDomain(ev->to().domain());
|
||||
bool localTarget = s_jabber->hasDomain(ev->to().domain()) &&
|
||||
!s_jabber->hasComponent(ev->to().domain());
|
||||
Message m("msg.route");
|
||||
while (true) {
|
||||
__plugin.complete(m);
|
||||
|
@ -2426,7 +2611,8 @@ void JBPendingWorker::processIq(JBPendingJob& job)
|
|||
bool respond = (t == XMPPUtils::IqGet || t == XMPPUtils::IqSet);
|
||||
// Destination at local domain: deny the request if the sender is not
|
||||
// the target's roster
|
||||
if (s_jabber->hasDomain(ev->to().domain())) {
|
||||
if (s_jabber->hasDomain(ev->to().domain()) &&
|
||||
!s_jabber->hasComponent(ev->to().domain())) {
|
||||
// Check auth
|
||||
Message auth("resource.subscribe");
|
||||
auth.addParam("module",__plugin.name());
|
||||
|
@ -2486,6 +2672,8 @@ UserAuthMessage::UserAuthMessage(JBEvent* ev)
|
|||
: Message("user.auth"),
|
||||
m_stream(ev->stream()->toString()), m_streamType((JBStream::Type)ev->stream()->type())
|
||||
{
|
||||
XDebug(&__plugin,DebugAll,"UserAuthMessage stream=%s type=%u",
|
||||
m_stream.c_str(),m_streamType);
|
||||
__plugin.complete(*this);
|
||||
addParam("streamtype",ev->stream()->typeName());
|
||||
ev->stream()->lock();
|
||||
|
@ -2498,6 +2686,10 @@ UserAuthMessage::UserAuthMessage(JBEvent* ev)
|
|||
user << ev->stream()->local().domain();
|
||||
setParam("username",user);
|
||||
}
|
||||
else if (m_streamType == JBStream::comp) {
|
||||
setParam("username",ev->stream()->remote());
|
||||
setParam("handshake",ev->text());
|
||||
}
|
||||
ev->stream()->unlock();
|
||||
SocketAddr addr;
|
||||
if (ev->stream()->remoteAddr(addr)) {
|
||||
|
@ -2510,13 +2702,13 @@ UserAuthMessage::UserAuthMessage(JBEvent* ev)
|
|||
void UserAuthMessage::dispatched(bool accepted)
|
||||
{
|
||||
JBStream* stream = s_jabber->findStream(m_stream,m_streamType);
|
||||
XDebug(&__plugin,DebugAll,"UserAuthMessage::dispatch(%u) stream=(%p,%s) type=%u",
|
||||
accepted,stream,m_stream.c_str(),m_streamType);
|
||||
bool ok = false;
|
||||
String rspValue;
|
||||
// Use a while() to break to the end
|
||||
while (stream) {
|
||||
Lock lock(stream);
|
||||
if (!stream->m_sasl)
|
||||
break;
|
||||
// Returned value '-' means deny
|
||||
if (accepted && retValue() == "-")
|
||||
break;
|
||||
|
@ -2527,16 +2719,28 @@ void UserAuthMessage::dispatched(bool accepted)
|
|||
if (!getValue("username"))
|
||||
break;
|
||||
// Check credentials
|
||||
String* rsp = getParam("response");
|
||||
if (rsp) {
|
||||
if (stream->m_sasl->m_plain)
|
||||
ok = (*rsp == retValue());
|
||||
else {
|
||||
if (m_streamType != JBStream::comp) {
|
||||
String* rsp = getParam("response");
|
||||
if (rsp) {
|
||||
if (!stream->m_sasl)
|
||||
break;
|
||||
if (stream->m_sasl->m_plain)
|
||||
ok = (*rsp == retValue());
|
||||
else {
|
||||
String digest;
|
||||
stream->m_sasl->buildMD5Digest(digest,retValue(),true);
|
||||
ok = (*rsp == digest);
|
||||
if (ok)
|
||||
stream->m_sasl->buildMD5Digest(rspValue,retValue(),false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
JBServerStream* comp = stream->serverStream();
|
||||
if (comp) {
|
||||
String digest;
|
||||
stream->m_sasl->buildMD5Digest(digest,retValue(),true);
|
||||
ok = (*rsp == digest);
|
||||
if (ok)
|
||||
stream->m_sasl->buildMD5Digest(rspValue,retValue(),false);
|
||||
comp->buildHandshake(digest,retValue());
|
||||
ok = (digest == getValue("handshake"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue