Implemented c2s non sasl authentication. Added listener for direct c2s SSL connections. Restrict client resources with the same name while authenticating using non sasl.

git-svn-id: http://voip.null.ro/svn/yate@2943 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
marian 2009-11-13 11:36:05 +00:00
parent 5e64836656
commit aa0b6ec97d
7 changed files with 580 additions and 174 deletions

View File

@ -21,6 +21,15 @@
; Defaults to no
;c2s_tlsrequired=
; c2s_allowunsecureplainauth: boolean: Allow user plain password authentication on
; unsecured stream
; Defaults to no
;c2s_allowunsecureplainauth=
; c2s_oldstyleauth: boolean: Enable old style (XEP 0078) user authentication
; Defaults to yes
;c2s_oldstyleauth=
; 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
@ -115,10 +124,16 @@
; These are the default values for some known types (only if this parameter is missing)
; c2s 5222
; s2s 5269
; There is no default value for external component listeners
; There is no default value for external component listeners or c2s SSL listeners
;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
; sslcontext: string: The SSL context of a c2s listener using encryption
; The context will be used to encrypt the socket before starting a new stream
; This parameter is ignored if type is not c2s
;sslcontext=

View File

@ -805,7 +805,7 @@ void JBEngine::cleanup(bool final, bool waitTerminate)
}
// Accept an incoming stream connection. Build a stream
bool JBEngine::acceptConn(Socket* sock, SocketAddr& remote, JBStream::Type t)
bool JBEngine::acceptConn(Socket* sock, SocketAddr& remote, JBStream::Type t, bool ssl)
{
if (!sock)
return false;
@ -815,9 +815,13 @@ bool JBEngine::acceptConn(Socket* sock, SocketAddr& remote, JBStream::Type t)
remote.host().c_str(),remote.port(),lookup(t,JBStream::s_typeName));
return false;
}
if (ssl && t != JBStream::c2s) {
Debug(this,DebugStub,"SSL connection on non c2s stream");
return false;
}
JBStream* s = 0;
if (t == JBStream::c2s)
s = new JBClientStream(this,sock);
s = new JBClientStream(this,sock,ssl);
else if (t == JBStream::s2s)
s = new JBServerStream(this,sock,false);
else if (t == JBStream::comp)

View File

@ -129,7 +129,7 @@ static inline unsigned int timerMultiplier(JBStream* stream)
* JBStream
*/
// Incoming
JBStream::JBStream(JBEngine* engine, Socket* socket, Type t)
JBStream::JBStream(JBEngine* engine, Socket* socket, Type t, bool ssl)
: Mutex(true,"JBStream"),
m_sasl(0),
m_state(Idle), m_flags(0), m_xmlns(XMPPNamespace::Count), m_lastEvent(0),
@ -141,11 +141,13 @@ JBStream::JBStream(JBEngine* engine, Socket* socket, Type t)
m_incoming(true), m_terminateEvent(0),
m_xmlDom(0), m_socket(0), m_socketFlags(0), m_connectPort(0)
{
if (ssl)
m_flags |= (StreamSecured | StreamTls);
m_engine->buildStreamName(m_name);
debugName(m_name);
debugChain(m_engine);
Debug(this,DebugAll,"JBStream::JBStream(%p,%p,%s) incoming [%p]",
engine,socket,typeName(),this);
Debug(this,DebugAll,"JBStream::JBStream(%p,%p,%s,%s) incoming [%p]",
engine,socket,typeName(),String::boolText(ssl),this);
setXmlns();
// Don't restart incoming streams
m_flags |= NoAutoRestart;
@ -480,15 +482,21 @@ void JBStream::start(XMPPFeatureList* features, XmlElement* caps)
m_features.clear();
if (features)
m_features.add(*features);
// Set secured flag if we don't advertise TLS
if (!(flag(StreamSecured) || m_features.get(XMPPNamespace::Tls)))
if (flag(StreamRemoteVer1)) {
// Set secured flag if we don't advertise TLS
if (!(flag(StreamSecured) || m_features.get(XMPPNamespace::Tls)))
setSecured();
// Set authenticated flag if we don't advertise authentication mechanisms
if (flag(StreamSecured)) {
if (flag(StreamAuthenticated))
m_features.remove(XMPPNamespace::Sasl);
else if (!m_features.get(XMPPNamespace::Sasl))
m_flags |= JBStream::StreamAuthenticated;
}
}
else if (m_type == c2s) {
// c2s using non-sasl auth
setSecured();
// Set authenticated flag if we don't advertise authentication mechanisms
if (flag(StreamSecured)) {
if (flag(StreamAuthenticated))
m_features.remove(XMPPNamespace::Sasl);
else if (!m_features.get(XMPPNamespace::Sasl))
m_flags |= JBStream::StreamAuthenticated;
}
// Send start and features
XmlElement* s = buildStreamStart();
@ -510,7 +518,8 @@ void JBStream::start(XMPPFeatureList* features, XmlElement* caps)
}
// Authenticate an incoming stream
bool JBStream::authenticated(bool ok, const String& rsp, XMPPError::Type error)
bool JBStream::authenticated(bool ok, const String& rsp, XMPPError::Type error,
const char* username, const char* id, const char* resource)
{
Lock lock(this);
if (m_state != Auth || !incoming())
@ -519,20 +528,38 @@ bool JBStream::authenticated(bool ok, const String& rsp, XMPPError::Type error)
String::boolText(ok),rsp.safe(),XMPPUtils::s_error[error].c_str(),
m_local.c_str(),this);
if (ok) {
m_flags |= StreamAuthenticated;
if (m_type == c2s) {
// Set remote party node if not provided
if (m_type == c2s && m_sasl && m_sasl->m_params && !m_remote.node()) {
m_remote.set(m_sasl->m_params->getValue("username"),m_local.domain(),"");
Debug(this,DebugAll,"Remote party set to '%s' [%p]",m_remote.c_str(),this);
}
m_features.remove(XMPPNamespace::Sasl);
String text;
if (m_sasl)
if (m_sasl) {
// Set remote party node if provided
if (!TelEngine::null(username)) {
m_remote.set(username,m_local.domain(),"");
Debug(this,DebugAll,"Remote party set to '%s' [%p]",m_remote.c_str(),this);
}
String text;
m_sasl->buildAuthRspReply(text,rsp);
XmlElement* s = XMPPUtils::createElement(XmlTag::Success,
XMPPNamespace::Sasl,text);
ok = sendStreamXml(WaitStart,s);
XmlElement* s = XMPPUtils::createElement(XmlTag::Success,
XMPPNamespace::Sasl,text);
ok = sendStreamXml(WaitStart,s);
}
else if (m_features.get(XMPPNamespace::IqAuth)) {
// Set remote party if not provided
if (!TelEngine::null(username))
m_remote.set(username,m_local.domain(),resource);
else
m_remote.resource(resource);
if (m_remote.isFull()) {
Debug(this,DebugAll,"Remote party set to '%s' [%p]",m_remote.c_str(),this);
XmlElement* rsp = XMPPUtils::createIqResult(0,0,id,
XMPPUtils::createElement(XmlTag::Query,XMPPNamespace::IqAuth));
ok = sendStreamXml(Running,rsp);
if (!ok)
m_remote.set(m_local.domain());
}
else
terminate(0,true,0,XMPPError::Internal);
}
else
terminate(0,true,0,XMPPError::Internal);
}
else if (m_type == s2s) {
XmlElement* rsp = XMPPUtils::createDialbackResult(m_local,m_remote,true);
@ -542,11 +569,24 @@ bool JBStream::authenticated(bool ok, const String& rsp, XMPPError::Type error)
XmlElement* rsp = XMPPUtils::createElement(XmlTag::Handshake);
ok = sendStreamXml(Running,rsp);
}
if (ok) {
m_features.remove(XMPPNamespace::Sasl);
m_features.remove(XMPPNamespace::IqAuth);
m_flags |= StreamAuthenticated;
}
}
else {
if (m_type == c2s) {
XmlElement* failure = XMPPUtils::createFailure(XMPPNamespace::Sasl,error);
ok = sendStreamXml(Features,failure);
XmlElement* rsp = 0;
if (m_sasl)
rsp = XMPPUtils::createFailure(XMPPNamespace::Sasl,error);
else {
rsp = XMPPUtils::createIq(XMPPUtils::IqError,0,0,id);
if (TelEngine::null(id))
rsp->addChild(XMPPUtils::createElement(XmlTag::Query,XMPPNamespace::IqAuth));
rsp->addChild(XMPPUtils::createError(XMPPError::TypeAuth,error));
}
ok = sendStreamXml(Features,rsp);
}
else if (m_type == s2s) {
XmlElement* rsp = XMPPUtils::createDialbackResult(m_local,m_remote,false);
@ -919,7 +959,8 @@ void JBStream::checkTimeouts(u_int64_t time)
// Reset the stream's connection. Build a new XML parser if the socket is valid
void JBStream::resetConnection(Socket* sock)
{
XDebug(this,DebugAll,"JBStream::resetConnection(%p) [%p]",sock,this);
DDebug(this,DebugAll,"JBStream::resetConnection(%p) current=%p [%p]",
sock,m_socket,this);
// Release the old one
if (m_socket) {
// Wait for the socket to become available (not reading or writing)
@ -978,7 +1019,8 @@ XmlElement* JBStream::buildStreamStart()
start->setAttribute(XmlElement::s_ns,XMPPUtils::s_ns[m_xmlns]);
start->setAttributeValid("from",m_local.bare());
start->setAttributeValid("to",m_remote.bare());
start->setAttribute("version","1.0");
if (outgoing() || flag(StreamRemoteVer1))
start->setAttribute("version","1.0");
start->setAttribute("xml:lang","en");
return start;
}
@ -1051,9 +1093,9 @@ bool JBStream::processStreamStart(const XmlElement* xml)
m_flags |= StreamRemoteVer1;
else if (remoteVersion < 1) {
if (m_type == c2s)
error = XMPPError::UnsupportedVersion;
XDebug(this,DebugAll,"c2s stream start with version < 1 [%p]",this);
else if (m_type == s2s) {
// Accept invalid/unsupported version on if TLS is not required
// Accept invalid/unsupported version only if TLS is not required
if (!flag(TlsRequired)) {
// Check dialback
if (!xml->hasAttribute("xmlns:db",XMPPUtils::s_ns[XMPPNamespace::Dialback])) {
@ -1150,15 +1192,21 @@ bool JBStream::checkStanzaRecv(XmlElement* xml, JabberID& from, JabberID& to)
// RFC 3920bis 5.2: Accept stanzas only if the stream was authenticated
// Accept IQs in jabber:iq:register namespace
// Accept IQs in jabber:iq:auth namespace
// They might be received on a non authenticated stream)
if (!flag(StreamAuthenticated)) {
bool isIq = XMPPUtils::isTag(*xml,XmlTag::Iq,m_xmlns);
bool valid = isIq && XMPPUtils::findFirstChild(*xml,XmlTag::Count,
XMPPNamespace::IqRegister);
// Outgoing client stream: check register responses
if (!valid && outgoing()) {
JBClientStream* c2s = clientStream();
valid = c2s && c2s->isRegisterId(*xml);
JBClientStream* c2s = clientStream();
if (!valid && c2s) {
// Outgoing client stream: check register responses
// Incoming client stream: check auth stanzas
if (outgoing())
valid = c2s->isRegisterId(*xml);
else
valid = isIq && XMPPUtils::findFirstChild(*xml,XmlTag::Count,
XMPPNamespace::IqAuth);
}
if (!valid) {
terminate(0,false,xml,XMPPError::NotAuthorized,
@ -1640,18 +1688,26 @@ bool JBStream::processFeaturesIn(XmlElement* xml, const JabberID& from, const Ja
if (!m_features.get(ns)) {
// Check for some features that can be negotiated via 'iq' elements
if (m_type == c2s && *t == XMPPUtils::s_tag[XmlTag::Iq] && ns == m_xmlns) {
int chTag = XmlTag::Count;
int chNs = XMPPNamespace::Count;
XmlElement* child = xml->findFirstChild();
int chNs = child ? XMPPUtils::ns(*child) : XMPPNamespace::Count;
bool bindOk = chNs == XMPPNamespace::Bind && m_features.get(XMPPNamespace::Bind);
bool regOk = !bindOk && chNs == XMPPNamespace::IqRegister;
if (child)
XMPPUtils::getTag(*child,chTag,chNs);
// Bind
if (bindOk) {
if (chNs == XMPPNamespace::Bind && m_features.get(XMPPNamespace::Bind)) {
// We've sent bind feature
// Don't accept it if not authenticated and TLS/SASL must be negotiated
if (!flag(StreamAuthenticated)) {
XMPPFeature* tls = m_features.get(XMPPNamespace::Tls);
if (tls && tls->required()) {
XmlElement* e = XMPPUtils::createError(xml,XMPPError::TypeWait,
XMPPError::EncryptionRequired);
sendStreamXml(m_state,e);
return true;
}
XMPPFeature* sasl = m_features.get(XMPPNamespace::Sasl);
if ((tls && tls->required()) || (sasl && sasl->required())) {
XMPPFeature* iqAuth = m_features.get(XMPPNamespace::IqAuth);
if ((sasl && sasl->required()) || (iqAuth && iqAuth->required())) {
XmlElement* e = XMPPUtils::createError(xml,XMPPError::TypeAuth,
XMPPError::NotAllowed);
sendStreamXml(m_state,e);
@ -1662,14 +1718,55 @@ bool JBStream::processFeaturesIn(XmlElement* xml, const JabberID& from, const Ja
m_flags |= StreamSecured | StreamAuthenticated;
m_features.remove(XMPPNamespace::Tls);
m_features.remove(XMPPNamespace::Sasl);
m_features.remove(XMPPNamespace::IqAuth);
changeState(Running);
return processRunning(xml,from,to);
}
// Register
if (regOk) {
else if (chNs == XMPPNamespace::IqRegister) {
// Register
m_events.append(new JBEvent(JBEvent::Iq,this,xml,xml->findFirstChild()));
return true;
}
else if (chNs == XMPPNamespace::IqAuth) {
XMPPUtils::IqType type = XMPPUtils::iqType(xml->attribute("type"));
bool req = type == XMPPUtils::IqGet || type == XMPPUtils::IqSet;
// Stream non SASL auth
// Check if we support it
if (!m_features.get(XMPPNamespace::IqAuth)) {
if (req) {
XmlElement* e = XMPPUtils::createError(xml,XMPPError::TypeCancel,
XMPPError::NotAllowed);
return sendStreamXml(m_state,e);
}
return dropXml(xml,"unexpected jabber:iq:auth element");
}
if (flag(StreamRemoteVer1)) {
XMPPFeature* tls = m_features.get(XMPPNamespace::Tls);
if (tls && tls->required()) {
XmlElement* e = XMPPUtils::createError(xml,XMPPError::TypeWait,
XMPPError::EncryptionRequired);
sendStreamXml(m_state,e);
return true;
}
}
if (chTag != XmlTag::Query) {
if (req) {
XmlElement* e = XMPPUtils::createError(xml,XMPPError::TypeModify,
XMPPError::FeatureNotImpl);
sendStreamXml(m_state,e);
return true;
}
return dropXml(xml,"expecting iq auth with 'query' child");
}
// Send it to the uppper layer
if (type == XMPPUtils::IqSet) {
m_events.append(new JBEvent(JBEvent::Auth,this,xml,xml->findFirstChild()));
changeState(Auth);
}
else
m_events.append(new JBEvent(JBEvent::Iq,this,xml,xml->findFirstChild()));
return true;
}
}
// s2s waiting for dialback
if (m_type == s2s) {
@ -1736,7 +1833,7 @@ bool JBStream::processFeaturesIn(XmlElement* xml, const JabberID& from, const Ja
}
return true;
}
// Stream auth
// Stream SASL auth
if (ns == XMPPNamespace::Sasl) {
if (*t != XMPPUtils::s_tag[XmlTag::Auth])
return dropXml(xml,"expecting sasl 'auth' element");
@ -1885,8 +1982,8 @@ void JBStream::eventTerminated(const JBEvent* ev)
/*
* JBClientStream
*/
JBClientStream::JBClientStream(JBEngine* engine, Socket* socket)
: JBStream(engine,socket,c2s),
JBClientStream::JBClientStream(JBEngine* engine, Socket* socket, bool ssl)
: JBStream(engine,socket,c2s,ssl),
m_userData(0), m_registerReq(0)
{
}
@ -1926,7 +2023,7 @@ void JBClientStream::bind(const String& resource, const char* id, XMPPError::Typ
m_features.remove(XMPPNamespace::Bind);
}
// Request account setup (or info) an outgoing stream
// Request account setup (or info) on outgoing stream
bool JBClientStream::requestRegister(bool data, bool set, const String& newPass)
{
if (incoming())

View File

@ -964,6 +964,34 @@ XmlElement* XMPPUtils::createRegisterQuery(IqType type, const char* from,
return iq;
}
// Build a jabber:iq:auth 'iq' set element
XmlElement* XMPPUtils::createIqAuthSet(const char* id, const char* username,
const char* resource, const char* authStr, bool digest)
{
XmlElement* iq = createIq(IqSet,0,0,id);
XmlElement* q = createElement(XmlTag::Query,XMPPNamespace::IqAuth);
iq->addChild(q);
q->addChild(createElement(XmlTag::Username,username));
q->addChild(createElement(XmlTag::Resource,resource));
q->addChild(createElement(digest ? XmlTag::Digest : XmlTag::Password,authStr));
return iq;
}
// Build a jabber:iq:auth 'iq' offer in response to a 'get' request
XmlElement* XMPPUtils::createIqAuthOffer(const char* id, bool digest, bool plain)
{
XmlElement* iq = createIq(IqResult,0,0,id);
XmlElement* q = createElement(XmlTag::Query,XMPPNamespace::IqAuth);
iq->addChild(q);
q->addChild(createElement(XmlTag::Username));
q->addChild(createElement(XmlTag::Resource));
if (digest)
q->addChild(createElement(XmlTag::Digest));
if (plain)
q->addChild(createElement(XmlTag::Password));
return iq;
}
// Find an element's first child element in a given namespace
XmlElement* XMPPUtils::findFirstChild(const XmlElement& xml, int t, int ns)
{

View File

@ -1261,6 +1261,39 @@ public:
const char* to, const char* id,
XmlElement* child1 = 0, XmlElement* child2 = 0, XmlElement* child3 = 0);
/**
* Build a jabber:iq:auth 'iq' get element
* @param id Element 'id' attribute
* @return A valid XmlElement pointer
*/
static inline XmlElement* createIqAuthGet(const char* id) {
XmlElement* iq = createIq(IqGet,0,0,id);
iq->addChild(createElement(XmlTag::Query,XMPPNamespace::IqAuth));
return iq;
}
/**
* Build a jabber:iq:auth 'iq' set element
* @param id Element 'id' attribute
* @param username The username
* @param resource The resource
* @param authStr Authentication string
* @param digest True if authentication string is a digest, false if it's a plain password
* @return A valid XmlElement pointer
*/
static XmlElement* createIqAuthSet(const char* id, const char* username,
const char* resource, const char* authStr, bool digest);
/**
* Build a jabber:iq:auth 'iq' offer in response to a 'get' request
* @param id Element 'id' attribute
* @param digest Offer digest authentication
* @param plain Offer plain password authentication
* @return A valid XmlElement pointer
*/
static XmlElement* createIqAuthOffer(const char* id, bool digest = true,
bool plain = false);
/**
* Build an register query element used to create/set username/password
* @param from The 'from' attribute

View File

@ -761,10 +761,14 @@ public:
* false if authentication failed
* @param rsp Optional success response content. Ignored if not authenticated
* @param error Failure reason. Ignored if authenticated
* @param username Authenticated user
* @param id Non SASL auth response id
* @param resource Client resource to set when non SASL authentication is used
* @return False if stream state is incorrect
*/
bool authenticated(bool ok, const String& rsp = String::empty(),
XMPPError::Type error = XMPPError::NotAuthorized);
XMPPError::Type error = XMPPError::NotAuthorized,
const char* username = 0, const char* id = 0, const char* resource = 0);
/**
* Terminate the stream. Send stream end tag or error.
@ -809,6 +813,17 @@ public:
inline const char* typeName()
{ return lookup(type(),s_typeName); }
/**
* Build a SHA1 digest from stream id and secret
* @param buf Destination buffer
* @param secret The secret
*/
inline void buildSha1Digest(String& buf, const String& secret) {
SHA1 sha(id() + secret);
buf = sha.hexDigest();
buf.toLower();
}
/**
* Get the string representation of this stream
* @return Stream name
@ -850,8 +865,9 @@ protected:
* @param engine Engine owning this stream
* @param socket The socket
* @param t Stream type as enumeration
* @param ssl True if the socket is already using SSL/TLS
*/
JBStream(JBEngine* engine, Socket* socket, Type t);
JBStream(JBEngine* engine, Socket* socket, Type t, bool ssl = false);
/**
* Constructor. Build an outgoing stream
@ -1177,8 +1193,9 @@ public:
* Constructor. Build an incoming stream from a socket
* @param engine Engine owning this stream
* @param socket The socket
* @param ssl True if the socket is already using SSL/TLS
*/
JBClientStream(JBEngine* engine, Socket* socket);
JBClientStream(JBEngine* engine, Socket* socket, bool ssl = false);
/**
* Constructor. Build an outgoing stream
@ -1227,7 +1244,7 @@ public:
XMPPError::Type error = XMPPError::NoError);
/**
* Request account register or change an outgoing stream.
* Request account register or change on outgoing stream.
* This method is thread safe
* @param data True to request registration/change, false to request info
* @param set True to request new user registration, false to remove account from server
@ -1395,17 +1412,6 @@ 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
@ -1638,9 +1644,10 @@ public:
* @param sock Accepted socket
* @param remote Remote ip and port
* @param t Expected stream type
* @param ssl True if the socket is already using SSL/TLS
* @return True on success
*/
bool acceptConn(Socket* sock, SocketAddr& remote, JBStream::Type t);
bool acceptConn(Socket* sock, SocketAddr& remote, JBStream::Type t, bool ssl = false);
/**
* Find a stream by its name. This method is thread safe

View File

@ -245,6 +245,9 @@ public:
// The given event is always valid and carry a valid element
XmlElement* processIqRegister(JBEvent* ev, JBStream::Type sType, XMPPUtils::IqType t,
const String& domain, int flags);
// Process all incoming jabber:iq:auth stanzas
// The given event is always valid and carry a valid element
XmlElement* processIqAuth(JBEvent* ev, JBStream::Type sType, XMPPUtils::IqType t, int flags);
// Handle disco info requests addressed to the server
XmlElement* discoInfo(JBEvent* ev, JBStream::Type sType);
// Handle disco items requests addressed to the server
@ -280,6 +283,13 @@ public:
void completeStreamRemote(String& str, const String& partWord, JBStream::Type t);
// Complete stream name starting with partWord
void completeStreamName(String& str, const String& partWord);
// Remove a resource from binding resources list
inline void removeBindingResource(const JabberID& user) {
Lock lock(this);
ObjList* o = user ? findBindingRes(user) : 0;
if (o)
o->remove();
}
// Program name and version to be advertised on request
String m_progName;
String m_progVersion;
@ -295,8 +305,18 @@ private:
return o;
return 0;
}
// Add a resource to binding resources list. Make sure the resource is unique
// Return true on success
bool bindingResource(const JabberID& user);
inline ObjList* findBindingRes(const JabberID& user) {
for (ObjList* o = m_bindingResources.skipNull(); o; o = o->skipNext())
if (user == *static_cast<JabberID*>(o->get()))
return o;
return 0;
}
bool m_c2sTlsRequired; // TLS is required on c2s streams
bool m_allowUnsecurePlainAuth; // Allow user plain password auth on unsecured streams
ObjList m_domains; // Domains serviced by this engine
ObjList m_dynamicDomains; // Dynamic domains (components or items)
ObjList m_restrictedResources; // Resource names the users can't use
@ -305,6 +325,7 @@ private:
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
ObjList m_bindingResources; // List of resources in bind process
};
/*
@ -400,6 +421,8 @@ class UserAuthMessage : public Message
public:
// Fill the message with authentication data
UserAuthMessage(JBEvent* ev);
~UserAuthMessage();
JabberID m_bindingUser;
protected:
// Check accepted and returned value. Calls stream's authenticated() method
virtual void dispatched(bool accepted);
@ -450,6 +473,19 @@ public:
m_engine(engine), m_type(t), m_address(addr), m_port(port),
m_backlog(backlog)
{}
// Build a SSL/TLS c2s stream listener
// engine The engine to be notified when a connection request is received
// addr Address to bind
// port Port to bind
// backlog Maximum length of the queue of pending connections, 0 for system maximum
// prio Thread priority
inline TcpListener(const char* name, JBEngine* engine, const char* context,
const char* addr, int port, unsigned int backlog = 0,
Thread::Priority prio = Thread::Normal)
: Thread("TcpListener",prio), String(name),
m_engine(engine), m_type(JBStream::c2s), m_address(addr), m_port(port),
m_backlog(backlog), m_sslContext(context)
{}
// Remove from plugin
virtual ~TcpListener();
protected:
@ -465,6 +501,7 @@ private:
String m_address;
int m_port;
unsigned int m_backlog; // Pending connections queue length
String m_sslContext; // SSL/TLS context
};
/*
@ -680,6 +717,13 @@ static void completeStreamType(String& buf, const String& part, bool addAll = fa
Module::itemComplete(buf,*d,part);
}
// Retrieve an element's child text
static const String& getChildText(XmlElement& xml, int tag, int ns)
{
XmlElement* ch = XMPPUtils::findFirstChild(xml,tag,ns);
return ch ? ch->getText() : String::empty();
}
/*
* YJBEntityCapsList
@ -718,7 +762,8 @@ void YJBEntityCapsList::capsAdded(JBEntityCaps* caps)
* YJBEngine
*/
YJBEngine::YJBEngine()
: m_c2sTlsRequired(false)
: m_c2sTlsRequired(false),
m_allowUnsecurePlainAuth(false)
{
m_c2sReceive = new YStreamSetReceive(this,10,"c2s/recv");
m_c2sProcess = new YStreamSetProcess(this,10,"c2s/process");
@ -760,6 +805,8 @@ void YJBEngine::initialize(const NamedList* params, bool first)
if (!params)
params = &dummy;
m_allowUnsecurePlainAuth = params->getBoolValue("c2s_allowunsecureplainauth");
// Serviced domains
// Check if an existing domain is no longer accepted
// Terminate all streams having local party the deleted domain
@ -824,6 +871,11 @@ void YJBEngine::initialize(const NamedList* params, bool first)
else
m_remoteDomain.m_flags &= ~JBStream::TlsRequired;
// Allow old style client auth
m_c2sFeatures.remove(XMPPNamespace::IqAuth);
if (params->getBoolValue("c2s_oldstyleauth",true))
m_c2sFeatures.add(XmlTag::Auth,XMPPNamespace::IqAuth);
// Program name and version to be advertised on request
if (!m_progName) {
m_progName = "Yate";
@ -1543,14 +1595,33 @@ void YJBEngine::processStartIn(JBEvent* ev)
if (ev->stream()->type() == JBStream::c2s)
ev->stream()->setTlsRequired(m_c2sTlsRequired);
// Don't advertise any features if version is not 1
XMPPFeatureList features;
// Stream version is not 1
if (!ev->stream()->flag(JBStream::StreamRemoteVer1)) {
ev->stream()->start();
XMPPError::Type error = XMPPError::NoError;
if (ev->stream()->type() == JBStream::c2s) {
lock();
bool ok = m_c2sFeatures.get(XMPPNamespace::IqAuth);
unlock();
if (ok) {
if (ev->stream()->flag(JBStream::StreamTls) ||
!ev->stream()->flag(JBStream::TlsRequired))
features.add(XmlTag::Auth,XMPPNamespace::IqAuth,true);
else
error = XMPPError::EncryptionRequired;
}
else
error = XMPPError::UnsupportedVersion;
}
if (error == XMPPError::NoError)
ev->stream()->start(&features);
else
ev->stream()->terminate(-1,true,0,error);
return;
}
// Set stream features
XMPPFeatureList features;
// Add TLS if not secured
if (!ev->stream()->flag(JBStream::StreamSecured))
features.add(XmlTag::Starttls,XMPPNamespace::Tls,
@ -1566,20 +1637,13 @@ void YJBEngine::processStartIn(JBEvent* ev)
bool addCaps = false;
if (!(tls && tls->required())) {
addCaps = true;
// Stream secured
// Add SASL if stream is not authenticated
if (!ev->stream()->flag(JBStream::StreamAuthenticated)) {
int mech = 0;
switch (ev->stream()->type()) {
case JBStream::c2s:
mech = XMPPUtils::AuthMD5 | XMPPUtils::AuthPlain;
break;
case JBStream::s2s:
mech = XMPPUtils::AuthMD5 | XMPPUtils::AuthPlain;
break;
}
if (mech)
features.add(new XMPPFeatureSasl(mech,true));
// Add SASL auth if stream is not authenticated
if (!ev->stream()->flag(JBStream::StreamAuthenticated) &&
ev->stream()->type() == JBStream::c2s) {
int mech = XMPPUtils::AuthMD5;
if (ev->stream()->flag(JBStream::StreamTls) || m_allowUnsecurePlainAuth)
mech |= XMPPUtils::AuthPlain;
features.add(new XMPPFeatureSasl(mech,true));
}
// Add register if enabled
if (addReg)
@ -1607,18 +1671,90 @@ void YJBEngine::processStartIn(JBEvent* ev)
// The given event is always valid and carry a valid stream
void YJBEngine::processAuthIn(JBEvent* ev)
{
UserAuthMessage* m = new UserAuthMessage(ev);
XMPPError::Type error = XMPPError::NoError;
ev->stream()->lock();
bool plain = ev->stream()->m_sasl && ev->stream()->m_sasl->m_plain &&
!ev->stream()->flag(JBStream::StreamTls);
ev->stream()->unlock();
if (plain) {
// TODO: check if the remote party is allowed to use plain
// password auth on unsecured transport
ev->releaseStream();
ev->stream()->authenticated(false,String::empty(),XMPPError::EncryptionRequired);
return;
if (ev->stream()->type() == JBStream::c2s) {
bool allowPlain = ev->stream()->flag(JBStream::StreamTls) ||
m_allowUnsecurePlainAuth;
while (true) {
// Stream is using SASL auth
if (ev->stream()->m_sasl) {
XDebug(this,DebugAll,"processAuthIn(%s) c2s sasl",ev->stream()->name());
if (ev->stream()->m_sasl->m_plain && !allowPlain) {
error = XMPPError::EncryptionRequired;
break;
}
if (ev->stream()->m_sasl->m_params) {
m->copyParams(*(ev->stream()->m_sasl->m_params));
// Override username: set it to bare jid
String* user = ev->stream()->m_sasl->m_params->getParam("username");
if (!TelEngine::null(user))
m->setParam("username",*user + "@" + ev->stream()->local().domain());
}
break;
}
// Check non SASL request
XmlElement* q = ev->child();
if (q) {
int t,ns;
if (XMPPUtils::getTag(*q,t,ns)) {
if (t != XmlTag::Query || ns != XMPPNamespace::IqAuth) {
error = XMPPError::ServiceUnavailable;
break;
}
XDebug(this,DebugAll,"processAuthIn(%s) c2s non sasl",ev->stream()->name());
JabberID user(getChildText(*q,XmlTag::Username,XMPPNamespace::IqAuth),
ev->stream()->local().domain(),
getChildText(*q,XmlTag::Resource,XMPPNamespace::IqAuth));
if (!user.resource()) {
error = XMPPError::NotAcceptable;
break;
}
if (user.bare())
m->addParam("username",user.bare());
const String& pwd = getChildText(*q,XmlTag::Password,XMPPNamespace::IqAuth);
if (pwd) {
if (allowPlain)
m->addParam("password",pwd);
else {
error = XMPPError::EncryptionRequired;
break;
}
}
else {
const String& d = getChildText(*q,XmlTag::Digest,XMPPNamespace::IqAuth);
if (d)
m->addParam("digest",d);
}
// Make sure the resource is unique
if (!bindingResource(user)) {
error = XMPPError::Conflict;
break;
}
else
m->m_bindingUser = user;
m->addParam("instance",user.resource());
break;
}
}
error = XMPPError::Internal;
break;
}
}
else if (ev->stream()->type() == JBStream::comp) {
XDebug(this,DebugAll,"processAuthIn(%s) component handshake",ev->stream()->name());
m->setParam("username",ev->stream()->remote());
m->setParam("handshake",ev->text());
}
ev->stream()->unlock();
if (error == XMPPError::NoError)
Engine::enqueue(m);
else {
ev->releaseStream();
ev->stream()->authenticated(false,String::empty(),error,0,ev->id());
TelEngine::destruct(m);
}
Engine::enqueue(new UserAuthMessage(ev));
}
// Process Bind events
@ -1630,38 +1766,32 @@ void YJBEngine::processBind(JBEvent* ev)
ev->sendStanzaError(XMPPError::ServiceUnavailable);
return;
}
String resource;
XmlElement* res = XMPPUtils::findFirstChild(*ev->child(),XmlTag::Resource);
if (res) {
// Lock the engine to prevent other stream to bind the same resource
lock();
resource = res->getText();
if (resource) {
if (restrictedResource(resource))
resource.clear();
else {
Message* m = __plugin.message("resource.notify");
m->addParam("operation","query");
m->addParam("nodata",String::boolText(true));
m->addParam("contact",c2s->remote().bare());
m->addParam("instance",resource);
if (Engine::dispatch(*m))
resource.clear();
TelEngine::destruct(m);
}
c2s->lock();
JabberID jid(c2s->local());
c2s->unlock();
jid.resource(getChildText(*ev->child(),XmlTag::Resource,XMPPNamespace::Bind));
if (jid.resource() && !bindingResource(jid))
jid.resource("");
if (!jid.resource()) {
for (int i = 0; i < 3; i++) {
MD5 md5(c2s->id());
jid.resource(md5.hexDigest());
if (bindingResource(jid))
break;
jid.resource("");
}
unlock();
}
if (!resource) {
MD5 md5(c2s->id());
resource = md5.hexDigest();
bool ok = false;
if (jid.resource()) {
Message* m = userRegister(*c2s,true,jid.resource());
ok = Engine::dispatch(m);
TelEngine::destruct(m);
}
Message* m = userRegister(*c2s,true,resource);
if (Engine::dispatch(m))
c2s->bind(resource,ev->id());
if (ok)
c2s->bind(jid.resource(),ev->id());
else
ev->sendStanzaError(XMPPError::NotAuthorized);
TelEngine::destruct(m);
removeBindingResource(jid);
}
// Process stream Running, Destroy, Terminated events
@ -1904,6 +2034,29 @@ XmlElement* YJBEngine::processIqRegister(JBEvent* ev, JBStream::Type sType,
return rsp;
}
// Process all incoming jabber:iq:auth stanzas
// The given event is always valid and carry a valid element
XmlElement* YJBEngine::processIqAuth(JBEvent* ev, JBStream::Type sType, XMPPUtils::IqType t,
int flags)
{
if (sType != JBStream::c2s) {
Debug(this,DebugInfo,"processIqAuth(%p) type=%s on non-client stream",
ev,ev->stanzaType().c_str());
// Iq auth not allowed from other servers
if (t == XMPPUtils::IqGet || t == XMPPUtils::IqSet)
return ev->buildIqError(false,XMPPError::NotAllowed);
return 0;
}
DDebug(this,DebugAll,"processIqAuth(%p) type=%s",ev,ev->stanzaType().c_str());
// Ignore responses
if (t != XMPPUtils::IqGet && t != XMPPUtils::IqSet)
return 0;
if (t == XMPPUtils::IqGet)
return XMPPUtils::createIqAuthOffer(ev->id(),true,
m_allowUnsecurePlainAuth || (flags & JBStream::StreamTls));
return ev->buildIqError(false,XMPPError::ServiceUnavailable);
}
// Handle disco info requests addressed to the server
XmlElement* YJBEngine::discoInfo(JBEvent* ev, JBStream::Type sType)
{
@ -2243,6 +2396,26 @@ void YJBEngine::notifyDbVerifyResult(const JabberID& local, const JabberID& remo
TelEngine::destruct(notify);
}
// Add a resource to binding resources list. Make sure the resource is unique
// Return true on success
bool YJBEngine::bindingResource(const JabberID& user)
{
Lock lock(this);
if (!user.resource() || restrictedResource(user.resource()) ||
findBindingRes(user))
return false;
Message* m = __plugin.message("resource.notify");
m->addParam("operation","query");
m->addParam("nodata",String::boolText(true));
m->addParam("contact",user.bare());
m->addParam("instance",user.resource());
bool ok = !Engine::dispatch(*m);
TelEngine::destruct(m);
if (ok)
m_bindingResources.append(new JabberID(user));
return ok;
}
/*
* JBPendingJob
@ -2646,6 +2819,15 @@ void JBPendingWorker::processIq(JBPendingJob& job)
else
job.sendIqErrorStanza(XMPPError::ServiceUnavailable);
return;
// XEP-0078 Non SASL authentication
case XMPPNamespace::IqAuth:
if (job.m_serverTarget) {
rsp = s_jabber->processIqAuth(ev,job.m_streamType,t,job.m_flags);
job.sendStanza(rsp,false);
}
else
job.sendIqErrorStanza(XMPPError::ServiceUnavailable);
return;
default: ;
}
@ -2698,30 +2880,22 @@ 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);
XDebug(&__plugin,DebugAll,"UserAuthMessage stream=%s type=%u [%p]",
m_stream.c_str(),m_streamType,this);
__plugin.complete(*this);
addParam("streamtype",ev->stream()->typeName());
ev->stream()->lock();
if (ev->stream()->m_sasl && ev->stream()->m_sasl->m_params) {
copyParams(*(ev->stream()->m_sasl->m_params));
const char* username = ev->stream()->m_sasl->m_params->getValue("username");
String user;
if (username)
user << username << "@";
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)) {
addParam("ip_host",addr.host());
addParam("ip_port",String(addr.port()));
}
addParam("requestid",ev->id());
}
UserAuthMessage::~UserAuthMessage()
{
if (m_bindingUser)
s_jabber->removeBindingResource(m_bindingUser);
}
// Check accepted and returned value. Calls stream's authenticated() method
@ -2732,6 +2906,7 @@ void UserAuthMessage::dispatched(bool accepted)
accepted,stream,m_stream.c_str(),m_streamType);
bool ok = false;
String rspValue;
JabberID username = getValue("username");
// Use a while() to break to the end
while (stream) {
Lock lock(stream);
@ -2742,37 +2917,50 @@ void UserAuthMessage::dispatched(bool accepted)
if (!(accepted || retValue()))
break;
// Returned password works only with username
if (!getValue("username"))
if (!username)
break;
// Check credentials
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 {
if (m_streamType == JBStream::c2s) {
if (stream->m_sasl) {
XDebug(&__plugin,DebugAll,"UserAuthMessage checking c2s sasl [%p]",this);
String* rsp = getParam("response");
if (rsp) {
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 {
XDebug(&__plugin,DebugAll,"UserAuthMessage checking c2s non-sasl [%p]",this);
String* auth = getParam("digest");
if (auth) {
String digest;
stream->m_sasl->buildMD5Digest(digest,retValue(),true);
ok = (*rsp == digest);
if (ok)
stream->m_sasl->buildMD5Digest(rspValue,retValue(),false);
stream->buildSha1Digest(digest,retValue());
ok = (digest == *auth);
}
else {
auth = getParam("password");
ok = auth && (*auth == retValue());
}
}
}
else {
JBServerStream* comp = stream->serverStream();
if (comp) {
String digest;
comp->buildHandshake(digest,retValue());
ok = (digest == getValue("handshake"));
}
else if (stream->type() == JBStream::comp) {
XDebug(&__plugin,DebugAll,"UserAuthMessage checking component handshake [%p]",this);
String digest;
stream->buildSha1Digest(digest,retValue());
ok = (digest == getValue("handshake"));
}
break;
}
if (stream)
stream->authenticated(ok,rspValue);
stream->authenticated(ok,rspValue,XMPPError::NotAuthorized,username.node(),
getValue("requestid"),getValue("instance"));
TelEngine::destruct(stream);
}
@ -2825,12 +3013,31 @@ TcpListener::~TcpListener()
__plugin.listener(this,false);
}
// Objects added to socket.ssl message when incoming connection is using SSL
class RefSocket : public RefObject
{
public:
inline RefSocket(Socket** sock)
: m_socket(sock)
{}
virtual void* getObject(const String& name) const {
if (name == "Socket*")
return (void*)m_socket;
return RefObject::getObject(name);
}
Socket** m_socket;
private:
RefSocket() {}
};
// Bind and listen
void TcpListener::run()
{
__plugin.listener(this,true);
DDebug(&__plugin,DebugAll,"TcpListener(%s) '%s:%d' type='%s' start running [%p]",
c_str(),m_address.safe(),m_port,lookup(m_type,JBStream::s_typeName),this);
DDebug(&__plugin,DebugAll,
"TcpListener(%s) '%s:%d' type='%s' context=%s start running [%p]",
c_str(),m_address.safe(),m_port,lookup(m_type,JBStream::s_typeName),
m_sslContext.c_str(),this);
// Create the socket
if (!m_socket.create(PF_INET,SOCK_STREAM)) {
terminateSocket("failed to create socket");
@ -2853,6 +3060,7 @@ void TcpListener::run()
}
XDebug(&__plugin,DebugAll,"Listener(%s) '%s:%d' start listening [%p]",
c_str(),m_address.safe(),m_port,this);
bool plain = m_sslContext.null();
while (true) {
if (Thread::check(false))
break;
@ -2862,21 +3070,26 @@ void TcpListener::run()
if (sock) {
DDebug(&__plugin,DebugAll,"Listener(%s) '%s:%d' got conn from '%s:%d' [%p]",
c_str(),m_address.safe(),m_port,addr.host().c_str(),addr.port(),this);
processed = m_engine && m_engine->acceptConn(sock,addr,m_type);
if (plain)
processed = m_engine && m_engine->acceptConn(sock,addr,m_type);
else {
Message m("socket.ssl");
m.userData(new RefSocket(&sock));
m.addParam("server",String::boolText(true));
m.addParam("context",m_sslContext);
if (Engine::dispatch(m))
processed = m_engine && m_engine->acceptConn(sock,addr,m_type,true);
else {
Debug(&__plugin,DebugWarn,"Listener(%s) Failed to start SSL [%p]",
c_str(),this);
delete sock;
break;
}
}
if (!processed)
delete sock;
}
Thread::idle();
#if 0
if (processed) {
if (m_sleepMs)
Thread::msleep(m_sleepMs,false);
}
else if (m_sleepMsNone)
Thread::msleep(m_sleepMsNone,false);
else
Thread::yield(false);
#endif
}
terminateSocket();
DDebug(&__plugin,DebugAll,"Listener(%s) '%s:%d' terminated [%p]",c_str(),
@ -3296,21 +3509,30 @@ bool JBModule::buildListener(const String& name, NamedList& p)
name.c_str(),stype);
return false;
}
const char* context = 0;
String* sport = p.getParam("port");
int port = 0;
if (!TelEngine::null(sport))
port = sport->toInteger();
else if (t == JBStream::c2s)
port = XMPP_C2S_PORT;
else if (t == JBStream::s2s)
port = XMPP_S2S_PORT;
if (t == JBStream::c2s) {
context = p.getValue("sslcontext");
if (TelEngine::null(sport) && TelEngine::null(context))
port = XMPP_C2S_PORT;
}
if (!port) {
Debug(this,DebugWarn,"Can't build listener='%s' with invalid port='%s'",
name.c_str(),c_safe(sport));
return false;
}
TcpListener* l = new TcpListener(name,s_jabber,t,
p.getValue("address"),port,p.getIntValue("backlog",5));
const char* addr = p.getValue("address");
unsigned int backlog = p.getIntValue("backlog",5);
TcpListener* l = 0;
if (TelEngine::null(context))
l = new TcpListener(name,s_jabber,t,addr,port,backlog);
else
l = new TcpListener(name,s_jabber,context,addr,port,backlog);
if (l->startup())
return true;
Debug(this,DebugWarn,"Failed to start listener='%s' type='%s' addr='%s' port=%d",