/** * yatejabber.h * Yet Another Jabber Stack * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2014 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * 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. */ #ifndef __YATEJABBER_H #define __YATEJABBER_H #include /** * Holds all Telephony Engine related classes. */ namespace TelEngine { class SASL; // SASL authentication mechanism class JBEvent; // A Jabber event class JBStream; // A Jabber stream class JBClientStream; // A client to server stream class JBServerStream; // A server to server stream class JBClusterStream; // A cluster stream class JBRemoteDomainDef; // Options and connect settings for a remote domain class JBConnect; // A socket connector class JBEngine; // A Jabber engine class JBServerEngine; // A Jabber server engine class JBClientEngine; // A Jabber client engine class JBStreamSet; // A set of streams to be processed in an uniform way class JBStreamSetProcessor; // Specialized stream processor class JBStreamSetReceive; // Specialized stream data receiver class JBStreamSetList; // A list of stream sets class JBEntityCaps; // Entity capability class JBEntityCapsList; // Entity capability list manager /** * Default port for client to server connections */ #define XMPP_C2S_PORT 5222 /** * Default port for server to server connections */ #define XMPP_S2S_PORT 5269 /** * Default value for maximum length of an incomplete xml allowed in a stream * parser's buffer */ #define XMPP_MAX_INCOMPLETEXML 8192 /** * This class handles PLAIN (rfc 4616) and DIGEST (rfc 2831) SASL authentication * @short SASL authentication mechanism */ class YJABBER_API SASL : public GenObject { YCLASS(SASL,GenObject) public: /** * Constructor * @param plain True to build a plain password auth object * @param realm Optional server realm */ SASL(bool plain, const char* realm = 0); /** * Destructor */ ~SASL() { TelEngine::destruct(m_params); } /** * Set auth params * @param user Optional username * @param pwd Optional password */ void setAuthParams(const char* user = 0, const char* pwd = 0); /** * Build a client initial auth or challenge response * @param buf Destination buffer. It will be filled with Base64 encoded result * @param digestUri Digest MD5 URI * @return True on success */ bool buildAuthRsp(String& buf, const char* digestUri = 0); /** * Build a server reply to challenge response * @param buf Destination buffer. It will be filled with Base64 encoded result * @param rsp The response */ inline void buildAuthRspReply(String& buf, const String& rsp) { if (m_plain) return; String tmp("rspauth=" + rsp); Base64 b((void*)tmp.c_str(),tmp.length(),false); b.encode(buf); b.clear(false); } /** * Check if a challenge response reply is valid * @param reply The reply to check * @return True if valid */ inline bool validAuthReply(const String& reply) { String tmp; if (m_params) buildMD5Digest(tmp,m_params->getValue("password"),false); return tmp == reply; } /** * Build an MD5 challenge from this object. * Generate a new nonce and increase nonce count * @param buf Destination buffer * @return True on success */ bool buildMD5Challenge(String& buf); /** * Build a Digest MD5 SASL (RFC 2831) to be sent with authentication responses * @param dest Destination string * @param password The password to use * @param challengeRsp True if building a Digest MD5 challenge response, false if * building a challenge response reply */ inline void buildMD5Digest(String& dest, const char* password, bool challengeRsp = true) { if (m_params) buildMD5Digest(dest,*m_params,password,challengeRsp); } /** * Parse plain password auth data * @param buf The buffer to parse * @return True if succesfully parsed */ bool parsePlain(const DataBlock& buf); /** * Parse and decode a buffer containing a SASL Digest MD5 challenge. * @param buf Already checked for valid UTF8 characters input string * @return True on success */ bool parseMD5Challenge(const String& buf); /** * Parse and decode a buffer containing a SASL Digest MD5 response. * Check realm, nonce and nonce count * @param buf Already checked for valid UTF8 characters input string * @return True on success */ bool parseMD5ChallengeRsp(const String& buf); /** * Parse and decode a buffer containing SASL plain authentication data * as defined in RFC 4616 * @param buf Input buffer * @param user Destination buffer for username part * @param pwd Destination buffer for password part * @param authzid Optional destination buffer for authorization identity part * @return True on success */ static bool parsePlain(const DataBlock& buf, String& user, String& pwd, String* authzid = 0); /** * Build a Digest MD5 SASL (RFC 2831) to be sent with authentication responses * @param dest Destination string * @param params List of parameters * @param password The password to use * @param challengeRsp True if building a Digest MD5 challenge response, false if * building a challenge response reply */ static void buildMD5Digest(String& dest, const NamedList& params, const char* password, bool challengeRsp = true); bool m_plain; NamedList* m_params; String m_realm; String m_nonce; String m_cnonce; unsigned int m_nonceCount; private: SASL() {} }; /** * This class holds a Jabber stream event. Stream events are raised by streams * and sent by the engine to the proper service * @short A Jabber stream event */ class YJABBER_API JBEvent : public RefObject { YCLASS(JBEvent,RefObject) friend class JBStream; friend class JBClientStream; friend class JBServerStream; public: /** * Event type enumeration */ enum Type { // Stream terminated. Try to connect or wait to be destroyed Terminated, // Stream is destroying Destroy, // Stream start was received: when processing this event, the upper // layer must call stream's start() method or terminate the stream 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 Bind, // Stream is running (can send/recv stanzas) Running, // The event's element is a 'message' Message, // The event's element is a 'presence' Presence, // The event's element is an 'iq' Iq, // The event's element is a 'db:result' one received by a server-to-server stream // containing the dialback key to verify // The event's text is filled with dialback key to verify DbResult, // The event's element is a 'db:verify' one received by a server-to-server stream DbVerify, // New user register or user password change succeeded RegisterOk, // New user register or user password change failed // The event's element is the response RegisterFailed, // Non stanza element received in Running state Unknown }; /** * Constructor. Constructs an event from a stream * @param type Type of this event * @param stream The stream that generated the event * @param element Element that generated the event * @param from Already parsed source JID * @param to Already parsed destination JID * @param child Optional type depending element's child */ inline JBEvent(Type type, JBStream* stream, XmlElement* element, const JabberID& from, const JabberID& to, XmlElement* child = 0) : m_type(type), m_stream(0), m_link(true), m_element(element), m_child(child) { init(stream,element,&from,&to); } /** * Constructor. Constructs an event from a stream * @param type Type of this event * @param stream The stream that generated the event * @param element Element that generated the event * @param child Optional type depending element's child */ inline JBEvent(Type type, JBStream* stream, XmlElement* element, XmlElement* child = 0) : m_type(type), m_stream(0), m_link(true), m_element(element), m_child(child) { init(stream,element); } /** * Destructor. Delete the XML element if valid */ virtual ~JBEvent(); /** * Get the event type * @return The type of this event as enumeration */ inline int type() const { return m_type; } /** * Get the event name * @return The name of this event */ inline const char* name() const { return lookup(type()); } /** * Get the element's 'type' attribute if any * @return The element's 'type' attribute */ inline const String& stanzaType() const { return m_stanzaType; } /** * Get the 'from' attribute of a received stanza * @return The 'from' attribute */ inline const JabberID& from() const { return m_from; } /** * Get the 'to' attribute of a received stanza * @return The 'to' attribute */ inline const JabberID& to() const { return m_to; } /** * Get the sender's id for Write... events or the 'id' attribute if the * event carries a received stanza * @return The event id */ inline const String& id() const { return m_id; } /** * The stanza's text or termination reason for Terminated/Destroy events * @return The event's text */ inline const String& text() const { return m_text; } /** * Get the stream that generated this event * @return The stream that generated this event */ inline JBStream* stream() const { return m_stream; } /** * Get a client-to-server stream from the event's stream * @return JBClientStream pointer or 0 */ JBClientStream* clientStream(); /** * Get a server-to-server stream from the event's stream * @return JBServerStream pointer or 0 */ JBServerStream* serverStream(); /** * Get a cluster stream from event's stream * @return JBClusterStream pointer or 0 */ JBClusterStream* clusterStream(); /** * Get the underlying XmlElement * @return XmlElement pointer or 0 */ inline XmlElement* element() const { return m_element; } /** * Get the first child of the underlying element if any * @return XmlElement pointer or 0 */ inline XmlElement* child() const { return m_child; } /** * Delete the underlying XmlElement(s). Release the ownership. * The caller will own the returned pointer * @param del True to delete all xml elements owned by this event * @return XmlElement pointer if not deleted or 0 */ XmlElement* releaseXml(bool del = false); /** * Build an 'iq' result stanza from event data * @param addTags True to add the 'from' and 'to' attributes * @param child Optional 'iq' child (will be consumed) * @return True on success */ XmlElement* buildIqResult(bool addTags, XmlElement* child = 0); /** * Build and send a stanza 'result' from enclosed 'iq' element * Release the element on success * @param child Optional 'iq' child (will be consumed) * @return True on success */ bool sendIqResult(XmlElement* child = 0); /** * Build an 'iq' error stanza from event data * The event's element will be released and added to the error one * if the id is empty * @param addTags True to add the 'from' and 'to' attributes * @param error Error to be returned to the event's XML sender * @param reason Optional text to be attached to the error * @param type Error type * @return True on success */ XmlElement* buildIqError(bool addTags, XMPPError::Type error, const char* reason = 0, XMPPError::ErrorType type = XMPPError::TypeModify); /** * Build and send a stanza error from enclosed element * Release the element on success * @param error Error to be returned to the event's XML sender * @param reason Optional text to be attached to the error * @param type Error type * @return True on success */ bool sendStanzaError(XMPPError::Type error, const char* reason = 0, XMPPError::ErrorType type = XMPPError::TypeModify); /** * Release the link with the stream to let the stream continue with events * @param release True to release the reference to the stream */ void releaseStream(bool release = false); /** * Get the name of an event type * @return The name an event type */ inline static const char* lookup(int type) { return TelEngine::lookup(type,s_type); } private: static const TokenDict s_type[]; // Event names JBEvent() {} // Don't use it! bool init(JBStream* stream, XmlElement* element, const JabberID* from = 0, const JabberID* to = 0); Type m_type; // Type of this event JBStream* m_stream; // The stream that generated this event bool m_link; // Stream link state XmlElement* m_element; // Received XML element, if any XmlElement* m_child; // The first child element for 'iq' elements String m_stanzaType; // Stanza's 'type' attribute JabberID m_from; // Stanza's 'from' attribute JabberID m_to; // Stanza's 'to' attribute String m_id; // 'id' attribute if the received element has one String m_text; // The stanza's text or termination reason for // Terminated/Destroy events }; /** * Base class for all Jabber streams. Basic stream data processing: send/receive * XML elements, keep stream state, generate events * @short A Jabber stream */ class YJABBER_API JBStream : public RefObject, public DebugEnabler, public Mutex { friend class JBEngine; friend class JBEvent; public: /** * Stream type enumeration */ enum Type { c2s = 0, // Client to server s2s, // Server to server comp, // External component cluster, // Cluster stream TypeCount // Unknown }; /** * Stream state enumeration */ enum State { Idle = 0, // Stream is waiting to be connected or destroyed Connecting, // Outgoing stream is waiting for the socket to connect WaitStart, // Waiting for remote's stream start // (outgoing: stream start already sent) Starting, // Incoming stream is processing a stream start element Features, // Outgoing: waiting for stream features // Incoming: stream features sent WaitTlsRsp, // 'starttls' sent: waiting for response Securing, // Stream is currently negotiating the TLS Auth, // Auth element (db:result for s2s streams) sent // Incoming comp: handshake received Challenge, // 'challenge' element sent/received Compressing, // Stream is negotiating compression // outgoing: compress element sent, wait for response // incoming: waiting for element to be sent Register, // A new user is currently registering // Keep Running state here: we expect all other states // (except for Destroy) to have lower values Running, // Established. Allow XML stanzas to pass over the stream Destroy, // Stream is destroying. No more traffic allowed }; /** * Stream behaviour options */ enum Flags { NoAutoRestart = 0x00000001,// Don't restart stream when down TlsRequired = 0x00000002,// TLS is mandatory on this stream AllowPlainAuth = 0x00000004,// Allow plain password authentication // If not allowed and this is the only method // offered by server the stream will be terminated DialbackOnly = 0x00000008,// Outgoing s2s dialback stream RegisterUser = 0x00000010,// Outgoing c2s register new user Compress = 0x00000020,// Offer/handle compression InError = 0x00000080,// The stream was terminated with error // Flags to be managed by the upper layer RosterRequested = 0x00000100,// c2s: the roster was already requested AvailableResource = 0x00000200,// c2s: available presence was sent/received PositivePriority = 0x00000400,// c2s: the resource advertised by the client has priority >= 0 // Internal flags (cleared when the stream is re-started) SetCompressed = 0x00010000,// Set StreamCompressed flag after succesfully sending // the current stream xml buffer StreamSecured = 0x00020000,// TLS stage was done (possible without using TLS) StreamTls = 0x00040000,// The stream is using TLS StreamAuthenticated = 0x00080000,// Stream already authenticated StreamRemoteVer1 = 0x00100000,// Remote party advertised RFC3920 version=1.0 StreamLocalVer1 = 0x00200000,// Advertise RFC3920 version=1.0 on incoming streams StreamWaitBindRsp = 0x01000000,// Outgoing c2s waiting for bind response StreamWaitSessRsp = 0x02000000,// Outgoing c2s waiting for session response StreamWaitChallenge = 0x04000000,// Outgoing waiting for auth challenge StreamWaitChgRsp = 0x08000000,// Outgoing waiting challenge response confirmation StreamRfc3920Chg = 0x10000000,// Outgoing sent empty response to challenge with rspauth (RFC3920) StreamCompressed = 0x20000000,// The stream is using compression StreamCanCompress = 0x40000000,// Incoming s2s may still be compressed // Flag masks StreamFlags = 0x000000ff, InternalFlags = 0xffff0000, }; /** * Destructor. * Gracefully close the stream and the socket */ virtual ~JBStream(); /** * Get the type of this stream. See the protocol enumeration of the engine * @return The type of this stream */ inline int type() const { return m_type; } /** * Retrieve this stream's default namespace * @return The stream default namespace */ inline int xmlns() const { return m_xmlns; } /** * Get the stream state * @return The stream state as enumeration. */ inline State state() const { return m_state; } /** * Get the stream direction * @return True if the stream is an incoming one */ inline bool incoming() const { return m_incoming; } /** * Get the stream direction * @return True if the stream is an outgoing one */ inline bool outgoing() const { return !m_incoming; } /** * Get the stream's owner * @return Pointer to the engine owning this stream */ inline JBEngine* engine() const { return m_engine; } /** * Get the stream's name * @return The stream's name */ inline const char* name() const { return m_name; } /** * Get the stream id * @return The stream id */ inline const String& id() const { return m_id; } /** * Check if the stream id equals a given string. * This method is thread safe * @param str The string to check * @return True if the given string equals this stream's id */ inline bool isId(const String& str) { Lock lock(this); return str == m_id; } /** * Get the JID of the local side of this stream * @return The JID of the local side of this stream */ inline const JabberID& local() const { return m_local; } /** * Get the JID of the local side of this stream. * This method is thread safe * @param jid The JID to be filled with the local side of this stream */ inline void local(JabberID& jid) { Lock lock(this); jid = m_local; } /** * Set the local party's JID * @param jid Local party's jid to set */ inline void setLocal(const char* jid) { m_local.set(jid); } /** * Get the JID of the remote side of this stream * @return The JID of the remote side of this stream */ inline const JabberID& remote() const { return m_remote; } /** * Get the JID of the remote side of this stream. * This method is thread safe * @param jid The JID to be filled with the remote side of this stream */ inline void remote(JabberID& jid) { Lock lock(this); jid = m_remote; } /** * Get the remote party's address * This method is thread safe * @param addr The socket address to be filled with remote party's address * @return True on success */ inline bool remoteAddr(SocketAddr& addr) { Lock lock(this); return m_socket && m_socket->getPeerName(addr); } /** * Get the local address * This method is thread safe * @param addr The socket address to be filled with local address * @return True on success */ inline bool localAddr(SocketAddr& addr) { Lock lock(this); return m_socket && m_socket->getSockName(addr); } /** * Get the stream flags * @return Stream flags */ inline int flags() const { return m_flags; } /** * Check if a given option (or option mask) is set * @param mask The flag(s) to check * @return True if set */ inline bool flag(int mask) const { return 0 != (m_flags & mask); } /** * Set or reset the TLS required flag * This method is not thread safe * @param set True to set, false to reset the flag */ inline void setTlsRequired(bool set) { Lock lock(this); if (set) setFlags(TlsRequired); else resetFlags(TlsRequired); } /** * Check if the stream has valid pending data (received xml elements in queue or * pending events or pending xml elements that can still be sent). * This method is thread safe * @return True if the stream have pending data, false otherwise */ bool haveData(); /** * Retrieve connection address(es), port and status * This method is not thread safe * @param addr The remote ip * @param port The remote port * @param localip Local ip to bind * @param stat Current connect status * @param isRedirect Optional pointer to be set to true if returned address is a redirect one * @param srvs List to copy stream SRV records */ void connectAddr(String& addr, int& port, String& localip, int& stat, ObjList& srvs, bool* isRedirect = 0) const; /** * Retrieve server host when connecting. * This method is not thread safe * @return Server host if set, remote jid's domain otherwise */ inline const String& serverHost() const { return m_serverHost ? m_serverHost : m_remote.domain(); } /** * Set/reset RosterRequested flag * This method is thread safe * @param ok True to set, false to reset it */ void setRosterRequested(bool ok); /** * Set/reset AvailableResource/PositivePriority flags * This method is thread safe * @param ok True to set, false to reset it * @param positive True if an available resource has positive priority * @return True if changed */ bool setAvailableResource(bool ok, bool positive = true); /** * Read data from socket. Send it to the parser. * Terminate the stream on socket or parser error * @param buf Destination buffer * @param len Buffer length (must be greater then 1) * @return True if data was received */ bool readSocket(char* buf, unsigned int len); /** * Get a client stream from this one * @return JBClientStream pointer or 0 */ virtual JBClientStream* clientStream() { return 0; } /** * Get a server stream from this one * @return JBServerStream pointer or 0 */ virtual JBServerStream* serverStream() { return 0; } /** * Get a cluster stream from this one * @return JBClusterStream pointer */ virtual JBClusterStream* clusterStream() { return 0; } /** * Stream state processor. * This method is thread safe * @param time Current time * @return JBEvent pointer or 0 */ JBEvent* getEvent(u_int64_t time = Time::msecNow()); /** * Send a stanza ('iq', 'message' or 'presence') or dialback elements in Running state. * This method is thread safe * @param xml Element to send (will be consumed and zeroed) * @return True on success */ bool sendStanza(XmlElement*& xml); /** * Send stream related XML when negotiating the stream or some other * stanza in non Running state * All elements will be consumed * This method is thread safe * @param newState The new stream state to set on success * @param first The first element to send * @param second Optional second element to send * @param third Optional third element to send * @return True on success */ bool sendStreamXml(State newState, XmlElement* first, XmlElement* second = 0, XmlElement* third = 0); /** * Start the stream. This method should be called by the upper layer * when processing an incoming stream Start event * This method is thread safe * @param features Optional features to advertise to the remote party of an * incoming stream. The caller is responsable of freeing it. * If processed, list's elements will be moved to stream's features list * @param caps Optional entity capabilities to be added to the stream features. * Ignored for outgoing streams * @param useVer1 Advertise RFC3920 version. Ignored for outgoing streams */ void start(XMPPFeatureList* features = 0, XmlElement* caps = 0, bool useVer1 = true); /** * Auth event result. This method should be called by the * upper layer when processing an Auth event * This method is thread safe * @param ok True if the remote party was authenticated, * 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, const char* username = 0, const char* id = 0, const char* resource = 0); /** * Terminate the stream. Send stream end tag or error. * Reset the stream. Deref stream if destroying. * This method is thread safe * @param location The terminate request location: * -1: upper layer, 0: internal, 1: remote * @param destroy True to destroy. False to terminate * @param xml Received XML element. The element will be consumed * @param error Termination reason. Set it to NoError to send stream end tag * @param reason Optional text to be added to the error stanza * @param final True if called from destructor * @param genEvent True to generate terminated event * @param content Optional sent error condition element text */ void terminate(int location, bool destroy, XmlElement* xml, int error = XMPPError::NoError, const char* reason = "", bool final = false, bool genEvent = true, const char* content = 0); /** * Outgoing stream connect terminated notification. * Send stream start if everithing is ok * @param sock The connected socket, will be consumed and zeroed */ virtual void connectTerminated(Socket*& sock); /** * Connecting notification. Start connect timer for synchronous connect * This method is thread safe * @param sync True if the connection is synchronous * @param stat Current status of the connect thread * @param srvs Current list of SRV records in the connect thread * @return True if accepted */ virtual bool connecting(bool sync, int stat, ObjList& srvs); /** * Get an object from this stream * @param name The name of the object to get */ virtual void* getObject(const String& name) const; /** * Get the name of a stream state * @return The name of the stream state */ inline const char* stateName() const { return lookup(state(),s_stateName); } /** * Get the name of a stream type * @return The name of the stream type */ inline const char* typeName() const { 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 */ virtual const String& toString() const; /** * Get the stream type associated with a given text * @param text Stream type text to find * @param defVal Value to return if not found * @return The stream type associated with a given text */ static inline Type lookupType(const char* text, Type defVal = TypeCount) { return (Type)lookup(text,s_typeName,defVal); } /** * SASL authentication data */ SASL* m_sasl; /** * Dictionary keeping the stream state names */ static const TokenDict s_stateName[]; /** * Dictionary keeping the flag names */ static const TokenDict s_flagName[]; /** * Dictionary keeping the stream type names */ static const TokenDict s_typeName[]; protected: /** * Constructor. Build an incoming stream from a socket * @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, bool ssl = false); /** * Constructor. Build an outgoing stream * @param engine Engine owning this stream * @param t Stream type as enumeration * @param local Local party jabber id * @param remote Remote party jabber id * @param name Optional stream name * @param params Optional stream parameters * @param serverHost Optional server host to use instead of jid domain */ JBStream(JBEngine* engine, Type t, const JabberID& local, const JabberID& remote, const char* name = 0, const NamedList* params = 0, const char* serverHost = 0); /** * Close the stream. Release memory */ virtual void destroyed(); /** * Check if stream state processor can continue. * This method is called from getEvent() with the stream locked * @param time Current time * @return True to indicate stream availability to process its state, * false to return the last event, if any */ virtual bool canProcess(u_int64_t time); /** * Process stream state. Get XML from parser's queue and process it * This method is called from getEvent() with the stream locked * @param time Current time */ virtual void process(u_int64_t time); /** * Process elements in Running state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processRunning(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Check stream timeouts. * This method is called from getEvent() with the stream locked, after * the process() method returned without setting the last event * @param time Current time */ virtual void checkTimeouts(u_int64_t time); /** * Reset the stream's connection. Build a new XML parser if the socket is valid * Release the old connection * @param sock The new socket */ virtual void resetConnection(Socket* sock = 0); /** * Build a ping iq stanza * @param stanzaId Stanza id * @return 0 */ virtual XmlElement* buildPing(const String& stanzaId); /** * Build a stream start XML element * @return XmlElement pointer */ virtual XmlElement* buildStreamStart(); /** * Process stream start elements while waiting for them * @param xml Received xml element * @param from The 'from' attribute * @param to The 'to' attribute * @return False if stream termination was initiated */ virtual bool processStart(const XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process elements in Auth state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processAuth(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process elements in Compressing state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processCompressing(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process elements in Register state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processRegister(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Check if a received stream start element is correct. * Check namespaces and set stream version * Check and set the id for outgoing streams * Generate an id for incoming streams * Terminate the stream if this conditions are met * @param xml Received xml element * @return False if stream termination was initiated */ bool processStreamStart(const XmlElement* xml); /** * Handle an already checked (tag and namespace) compress request * Respond to it. Change stream state on success * @param xml Received xml element (will be consumed) * @return False if stream termination was initiated */ bool handleCompressReq(XmlElement* xml); /** * Check if a received element is a stream error one * @param xml Received xml element * @return True if stream termination was initiated (the xml will be consumed) */ bool streamError(XmlElement* xml); /** * Retrieve and check the 'from' and 'to' JIDs from a receive element * @param xml Received xml element * @param from Jabber ID to set from the 'from' attribute * @param to Jabber ID to set from the 'to' attribute * @return False if stream termination was initiated (the xml will be consumed) */ bool getJids(XmlElement* xml, JabberID& from, JabberID& to); /** * Check if a received element is a presence, message or iq qualified by the stream * namespace and the stream is not authenticated. * Validate 'from' for c2s streams * Validate s2s 'to' domain and 'from' jid * Fix 'from' or 'to' is needed * @param xml Received xml element (will be consumed if false is returned) * @param from The sender of the stanza * @param to Stanza recipient * @return False if the element was consumed (error was sent or stream termination was initiated) */ bool checkStanzaRecv(XmlElement* xml, JabberID& from, JabberID& to); /** * Change stream state. Reset state depending data * @param newState The new stream state * @param time Current time */ void changeState(State newState, u_int64_t time = Time::msecNow()); /** * Check if the stream compress flag is set and compression was offered by remote party * @return Compress request XmlElement pointer or 0 */ XmlElement* checkCompress(); /** * Check for pending events. Set the last event */ void checkPendingEvent(); /** * Send pending stream XML or stanzas * Terminate the stream on error * @param streamOnly Try to send only existing stream related XML elements * @return True on success */ bool sendPending(bool streamOnly = false); /** * Write data to socket. Terminate the stream on socket error * @param data Buffer to sent * @param len The number of bytes to send. Filled with actually sent bytes on exit * @return True on success, false on failure */ bool writeSocket(const void* data, unsigned int& len); /** * Update stream flags and remote connection data from engine */ void updateFromRemoteDef(); /** * Retrieve the first required feature in the list * @return XMPPFeature pointer or 0 */ XMPPFeature* firstRequiredFeature(); /** * Drop (delete) received XML element * @param xml The element to delete * @param reason The reason * @return True */ bool dropXml(XmlElement*& xml, const char* reason); /** * Terminate (destroy) the stream. Drop (delete) received XML element * @param xml The element to delete * @param error Terminate error * @param reason Drop reason * @return False */ inline bool destroyDropXml(XmlElement*& xml, XMPPError::Type error, const char* reason) { dropXml(xml,reason); terminate(0,true,0,error); return false; } /** * Set stream flag mask * @param mask The bit mask to set */ void setFlags(int mask); /** * Reset stream flag mask * @param mask The bit mask to reset */ void resetFlags(int mask); /** * Set secured flag. Remove feature from list */ inline void setSecured() { setFlags(StreamSecured); m_features.remove(XMPPNamespace::Tls); } /** * Set the idle timer in Running state * @param msecNow Current time in milliseconds */ void setIdleTimer(u_int64_t msecNow = Time::msecNow()); /** * Reset ping data */ void resetPing(); /** * Set the time of the next ping if there is any timeout and we don't have a ping in progress. * Set the ping timeout if an element is returned * @param force True to set it even if already set * @return XmlElement containing the ping to send, 0 if no ping is going to be sent or 'force' is true */ XmlElement* setNextPing(bool force); /** * Generate a stanza index from stream id and current stanza index * Set the ping timeout if an element is returned * @param buf Destination string * @param extra Optional extra string */ inline void generateIdIndex(String& buf, const char* extra = 0) { buf = id() + extra + String(++m_stanzaIndex); } State m_state; // Stream state String m_id; // Stream id JabberID m_local; // Local peer's jid JabberID m_remote; // Remote peer's jid String m_serverHost; // Outgoing: optional server host (replaces remote domain when connecting) int m_flags; // Stream flags XMPPNamespace::Type m_xmlns; // Stream namespace XMPPFeatureList m_features; // Advertised features JBEvent* m_lastEvent; // Last event generated by this stream ObjList m_events; // Queued events ObjList m_pending; // Pending outgoing elements unsigned int m_stanzaIndex; // Index used to generate IDs for stanzas // Timers u_int64_t m_setupTimeout; // Overall stream setup timeout u_int64_t m_startTimeout; // Incoming: wait stream start period u_int64_t m_pingTimeout; // Sent ping timeout u_int64_t m_pingInterval; // Ping interval u_int64_t m_nextPing; // Next ping u_int64_t m_idleTimeout; // Stream idle timeout u_int64_t m_connectTimeout; // Stream connect timeout // unsigned int m_restart; // Remaining restarts u_int64_t m_timeToFillRestart; // The next time to increase the restart counter String m_pingId; private: // Forbidden default constructor inline JBStream() {} // Process incoming elements in Challenge state // The element will be consumed // Return false if stream termination was initiated bool processChallenge(XmlElement* xml, const JabberID& from, const JabberID& to); // Process incoming 'auth' elements qualified by SASL namespace // The element will be consumed // Return false if stream termination was initiated bool processSaslAuth(XmlElement* xml, const JabberID& from, const JabberID& to); // Process received elements in Features state (incoming stream) // The element will be consumed // Return false if stream termination was initiated bool processFeaturesIn(XmlElement* xml, const JabberID& from, const JabberID& to); // Process received elements in Features state (outgoing stream) // The element will be consumed // Return false if stream termination was initiated bool processFeaturesOut(XmlElement* xml, const JabberID& from, const JabberID& to); // Process received elements in WaitTlsRsp state (outgoing stream) // The element will be consumed // Return false if stream termination was initiated bool processWaitTlsRsp(XmlElement* xml, const JabberID& from, const JabberID& to); // Set stream namespace from type void setXmlns(); // Event termination notification // @param event The notifier. Ignored if it's not m_lastEvent void eventTerminated(const JBEvent* event); // Compress data to be sent (the pending stream xml buffer or pending stanza) // Return false on failure bool compress(XmlElementOut* xml = 0); // Reset connect status data void resetConnectStatus(); // Postpone stream terminate until all parsed elements are processed // Terminate now if allowed // This method is thread safe void postponeTerminate(int location, bool destroy, int error, const char* reason); // Handle postponed termination. Return true if found // This method is not thread safe bool postponedTerminate(); // Reset redirect data void setRedirect(const String& addr = String::empty(), int port = 0); // Reset postponed terminate data inline void resetPostponedTerminate() { m_ppTerminateTimeout = 0; TelEngine::destruct(m_ppTerminate); } enum { SocketCanRead = 0x01, SocketReading = 0x02, SocketCanWrite = 0x10, SocketWriting = 0x20, SocketWaitReset = 0x80, }; inline void socketSetCanRead(bool ok) { Lock lock(m_socketMutex); if (ok) m_socketFlags |= SocketCanRead; else m_socketFlags &= ~SocketCanRead; } inline void socketSetReading(bool ok) { if (ok) m_socketFlags |= SocketReading; else m_socketFlags &= ~SocketReading; } inline void socketSetCanWrite(bool ok) { Lock lock(m_socketMutex); if (ok) m_socketFlags |= SocketCanWrite; else m_socketFlags &= ~SocketCanWrite; } inline void socketSetWriting(bool ok) { if (ok) m_socketFlags |= SocketWriting; else m_socketFlags &= ~SocketWriting; } inline bool socketCanRead() const { return m_socket && (m_socketFlags & SocketCanRead) && !socketWaitReset(); } inline bool socketCanWrite() const { return m_socket && (m_socketFlags & SocketCanWrite) && !socketWaitReset(); } inline bool socketReading() const { return (m_socketFlags & SocketReading) != 0; } inline bool socketWriting() const { return (m_socketFlags & SocketWriting) != 0; } inline bool socketWaitReset() const { return 0 != (m_socketFlags & SocketWaitReset); } JBEngine* m_engine; // The owner of this stream int m_type; // Stream type bool m_incoming; // Stream direction String m_name; // Local (internal) name JBEvent* m_terminateEvent; // Pending terminate event NamedList* m_ppTerminate; // Postponed terminate parameters u_int64_t m_ppTerminateTimeout; // Postponed terminate timeout // Pending outgoing XML String m_outStreamXml; DataBlock m_outStreamXmlCompress; DataBlock m_outXmlCompress; // Connection related data XmlDomParser* m_xmlDom; Socket* m_socket; char m_socketFlags; // Socket flags: 0: unavailable Mutex m_socketMutex; // Protect the socket and parser String m_connectAddr; // Remote ip to connect to int m_connectPort; // Remote port to connect to String m_localIp; // Local ip to bind when connecting Compressor* m_compress; int m_connectStatus; // Current connect stream status ObjList m_connectSrvs; // Current connect stream SRV records unsigned int m_redirectMax; unsigned int m_redirectCount; String m_redirectAddr; int m_redirectPort; }; /** * This class holds a client to server stream * @short A client to server stream */ class YJABBER_API JBClientStream : public JBStream { YCLASS(JBClientStream,JBStream) friend class JBStream; 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, bool ssl = false); /** * Constructor. Build an outgoing stream * @param engine Engine owning this stream * @param jid User jid * @param account Account (stream) name * @param params Stream parameters * @param name Optional stream name * @param serverHost Optional server host to use instead of jid domain */ JBClientStream(JBEngine* engine, const JabberID& jid, const String& account, const NamedList& params, const char* name = 0, const char* serverHost = 0); /** * Retrieve stream's account * @return Stream account */ inline const String& account() const { return m_account; } /** * Retrieve stream's user data * @return GenObject pointer or 0 */ inline GenObject* userData() { return m_userData; } /** * Set stream's user data. Transfer data ownership to the stream * This method is thread safe * @param data Data to set */ inline void userData(GenObject* data) { Lock lock(this); TelEngine::destruct(m_userData); m_userData = data; } /** * Get a client stream from this one * @return JBClientStream pointer */ virtual JBClientStream* clientStream() { return this; } /** * Build a ping iq stanza * @param stanzaId Stanza id * @return Valid XmlElement pointer */ virtual XmlElement* buildPing(const String& stanzaId); /** * Bind a resource to an incoming stream. This method should be called * after processing a Bind event * This method is thread safe * @param resource Resource to bind. Empty on error * @param id Received bind request id * @param error Failure reason. Ignored on success */ void bind(const String& resource, const char* id, XMPPError::Type error = XMPPError::NoError); /** * 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 * @param newPass New password when requesting account setup on an already * authenticated stream * @return True on success */ bool requestRegister(bool data, bool set = true, const String& newPass = String::empty()); protected: /** * Process elements in Running state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processRunning(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process stream start elements while waiting for them * @param xml Received xml element * @param from The 'from' attribute * @param to The 'to' attribute * @return False if stream termination was initiated */ virtual bool processStart(const XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process elements in Auth state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processAuth(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process elements in Register state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processRegister(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Release memory */ virtual void destroyed(); /** * Start outgoing stream authentication * @return True on success */ bool startAuth(); /** * Start resource binding on outgoing stream * @return True on success */ bool bind(); private: inline bool isRegisterId(XmlElement& xml) { if (!m_registerReq) return false; String* id = xml.getAttribute("id"); return id && id->length() == 1 && (*id)[0] == m_registerReq; } String m_account; // Stream account GenObject* m_userData; // User (upper layer) data String m_password; // The password String m_newPassword; // New password char m_registerReq; // Register requested. 1(data) 2(register) 3(remove) }; /** * This class holds a server to server stream * @short A server to server stream */ class YJABBER_API JBServerStream : public JBStream { YCLASS(JBServerStream,JBStream) friend class JBStream; 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, bool component = false); /** * Constructor. Build an outgoing stream * @param engine Engine owning this stream * @param local Local party jabber id * @param remote Remote party jabber id * @param dbId Optional dialback id (stream id) * @param dbKey Optional dialback key to verify * @param dbOnly True if this is a dialback only stream * @param params Optional stream parameters */ JBServerStream(JBEngine* engine, const JabberID& local, const JabberID& remote, const char* dbId = 0, const char* dbKey = 0, bool dbOnly = false, const NamedList* params = 0); /** * Constructor. Build an outgoing component stream * @param engine Engine owning this stream * @param local Local party jabber id * @param remote Remote party jabber id * @param name Optional stream name * @param params Optional stream parameters */ JBServerStream(JBEngine* engine, const JabberID& local, const JabberID& remote, const String* name = 0, const NamedList* params = 0); /** * Check if this is an outgoing dialback stream * @return True if this stream is an outgoing dialback one */ inline bool dialback() const { return outgoing() && flag(DialbackOnly); } /** * Retrieve the list of remote domains. * This method is not thread safe * @return The list of remote domains */ inline const NamedList& remoteDomains() const { return m_remoteDomains; } /** * Check if this stream has an already authenticated remote domain. * This method is not thread safe * @param domain Domain to check * @param auth Check if the domain is authenticated * @return True if a domain was found */ inline bool hasRemoteDomain(const String& domain, bool auth = true) { NamedString* tmp = m_remoteDomains.getParam(domain); return tmp && (!auth || tmp->null()); } /** * Take the dialback key from this stream * @return NamedString pointer or 0 if there is no dialback key held by this stream */ inline NamedString* takeDb() { Lock lock(this); NamedString* tmp = m_dbKey; m_dbKey = 0; return tmp; } /** * Get a server stream from this one * @return JBServerStream pointer */ virtual JBServerStream* serverStream() { return this; } /** * Send a dialback verify response * @param from The 'from' attribute * @param to The 'to' attribute * @param id The 'id' attribute * @param rsp The response as enumeration: set it to NoError if valid, * NotAuthorized if invalid or any other error to send a db:verify error type * @return True on success */ bool sendDbVerify(const char* from, const char* to, const char* id, XMPPError::Type rsp = XMPPError::NoError); /** * Send a dialback key response. Update the remote domains list. * Terminate the stream if there are no more remote domains * @param from The 'from' attribute * @param to The 'to' attribute * @param rsp The response as enumeration: set it to NoError if valid, * NotAuthorized if invalid or any other error to send a db:result error type * @return True on success */ bool sendDbResult(const JabberID& from, const JabberID& to, XMPPError::Type rsp = XMPPError::NoError); /** * Send dialback data (key/verify) * @return False if stream termination was initiated */ bool sendDialback(); /** * Start a component stream (reply to received stream start). * Send handshake if outgoing * @param local Local domain. Ignored if outgoing * @param remote Remote domain. Ignored if outgoing * @return True on success */ bool startComp(const String& local = String::empty(), const String& remote = String::empty()); protected: /** * Release memory */ virtual void destroyed(); /** * Process elements in Running state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processRunning(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Build a stream start XML element * @return XmlElement pointer */ virtual XmlElement* buildStreamStart(); /** * Process stream start elements while waiting for them * @param xml Received xml element * @param from The 'from' attribute * @param to The 'to' attribute * @return False if stream termination was initiated */ virtual bool processStart(const XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process elements in Auth state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processAuth(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process dialback key (db:result) requests * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ bool processDbResult(XmlElement* xml, const JabberID& from, const JabberID& to); /** * Adjust a dialback response to avoid sending XEP 0220 'error' to a party * not advertising rfc3920 version=1 (might not support it) * @param rsp The response to adjust */ inline void adjustDbRsp(XMPPError::Type& rsp) { Lock lock(this); if (!flag(StreamRemoteVer1) && rsp != XMPPError::NoError) rsp = XMPPError::NotAuthorized; } /** * Incoming stream remote domains. * Each element's value will contain the dialback key if not authenticated */ NamedList m_remoteDomains; private: NamedString* m_dbKey; // Outgoing: initial dialback key to check String m_password; // Outgoing component: password }; /** * This class holds a cluster stream * @short A cluster stream */ class YJABBER_API JBClusterStream : public JBStream { YCLASS(JBClusterStream,JBStream) friend class JBStream; public: /** * Constructor. Build an incoming stream from a socket * @param engine Engine owning this stream * @param socket The socket */ JBClusterStream(JBEngine* engine, Socket* socket); /** * Constructor. Build an outgoing stream * @param engine Engine owning this stream * @param local Local party jabber id * @param remote Remote party jabber id * @param params Optional stream parameters */ JBClusterStream(JBEngine* engine, const JabberID& local, const JabberID& remote, const NamedList* params = 0); /** * Get a cluster stream from this one * @return JBClusterStream pointer */ virtual JBClusterStream* clusterStream() { return this; } protected: /** * Build a stream start XML element * @return XmlElement pointer */ virtual XmlElement* buildStreamStart(); /** * Process stream start elements while waiting for them * @param xml Received xml element * @param from The 'from' attribute * @param to The 'to' attribute * @return False if stream termination was initiated */ virtual bool processStart(const XmlElement* xml, const JabberID& from, const JabberID& to); /** * Process elements in Running state * @param xml Received element (will be consumed) * @param from Already parsed source JID * @param to Already parsed destination JID * @return False if stream termination was initiated */ virtual bool processRunning(XmlElement* xml, const JabberID& from, const JabberID& to); }; /** * This class holds data related to a remote domain. * The String holds the domain * @short Options and connect settings for a remote domain */ class YJABBER_API JBRemoteDomainDef : public String { YCLASS(JBRemoteDomainDef,String) public: /** * Constructor * @param domain Domain name */ inline JBRemoteDomainDef(const char* domain = 0) : String(domain), m_port(0), m_flags(0) {} /** * Remote address used to connect to */ String m_address; /** * Remote port used to connect to */ int m_port; /** * Domain flags */ int m_flags; }; /** * This class holds data used to connect an outgoing stream * A descendant class should implement the thread run method * @short A socket connector */ class YJABBER_API JBConnect : public GenObject { YCLASS(JBConnect,GenObject) public: enum Status { Start = 0, Address, // Use configured address Srv, // Use SRV records Domain // Use stream remote domain }; /** * Constructor. Add itself to the stream's engine * @param stream The stream to connect */ JBConnect(const JBStream& stream); /** * Destructor. Remove from engine if still there */ virtual ~JBConnect(); /** * Stop the thread. This method should be re-implemented */ virtual void stopConnect(); /** * Retrieve the stream name * @return Stream name */ virtual const String& toString() const; /** * Status name dictionary */ static const TokenDict s_statusName[]; protected: /** * Connect the socket. * Retrieve ip/port from engine ant use them if valid or try to use SRV records returned by * the given domain or use the domain's ip address and the default port given by the stream type. * Notify the stream on termination. * This method should be called from it's own thread */ void connect(); private: // No default constructor inline JBConnect() {} // Check if exiting. Release socket if exiting bool exiting(Socket*& sock); // Create and try to connect a socket. Return it on success // Set stop on fatal failure and return 0 Socket* connect(const char* addr, int port, bool& stop); // Notify termination, remove from engine void terminated(Socket* sock, bool final); // Notify connecting to the stream. Return false if stream vanished bool notifyConnecting(bool sync, bool useCurrentStat = false); // Delete a socket and zero the pointer void deleteSocket(Socket*& sock); // Advance connect status void advanceStatus(); int m_status; // Current status String m_domain; // Remote domain String m_address; // Remote ip address int m_port; // Port to connect to JBEngine* m_engine; // The engine owning this connector String m_stream; // Stream name JBStream::Type m_streamType; // Stream type String m_localIp; // Local ip to bind when connecting ObjList m_srvs; // SRV records list }; /** * This class holds a Jabber engine * @short A Jabber engine */ class YJABBER_API JBEngine : public DebugEnabler, public Mutex, public GenObject { YCLASS(JBEngine,GenObject) friend class JBStream; friend class JBConnect; friend class JBStreamSetProcessor; public: /** * Constructor * @param name Engine name */ JBEngine(const char* name = "jbengine"); /** * Destructor */ virtual ~JBEngine(); /** * Retrieve the stream read buffer length * @return Stream read buffer length */ inline unsigned int streamReadBuffer() const { return m_streamReadBuffer; } /** * Check if this engine is exiting * @return True if this engine is exiting */ inline bool exiting() const { return m_exiting; } /** * Set the exiting flag. Terminate all streams */ inline void setExiting() { if (m_exiting) return; m_exiting = true; dropAll(JBStream::TypeCount,JabberID::empty(),JabberID::empty(), XMPPError::Shutdown); } /** * Retrieve maximum redirect counter for outgoing streams * @return Maximum redirect counter for outgoing streams */ inline unsigned int redirectMax() const { return m_redirectMax; } /** * Check if TLS is available for outgoing streams * @return True if TLS is available for outgoing streams */ inline bool hasClientTls() const { return m_hasClientTls; } /** * Find a remote domain definition. Return the default settings if not found. * This method is not thread safe * @param domain The domain to find * @return Valid JBRemoteDomainDef pointer */ inline JBRemoteDomainDef* remoteDomainDef(const String& domain) { ObjList* o = m_remoteDomains.find(domain); return o ? static_cast(o->get()) : &m_remoteDomain; } /** * Cleanup streams. Stop all threads owned by this engine. Release memory */ virtual void destruct(); /** * Initialize the engine's parameters. Start private streams if requested * @param params Engine's parameters */ virtual void initialize(const NamedList& params); /** * Stop connect threads. Drop all streams. Stop all stream sets. Release memory if final * @param final True if called from destructor * @param waitTerminate True to wait for all streams to terminate */ virtual void cleanup(bool final = false, bool waitTerminate = true); /** * Accept an incoming stream connection. Build a stream. * Don't delete the socket if false is returned * @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 ssl = false); /** * Find a stream by its name. This method is thread safe * @param id The internal id of the stream to find * @param hint Optional stream type hint * @return Referenced JBStream pointer or 0 */ virtual JBStream* findStream(const String& id, JBStream::Type hint = JBStream::TypeCount); /** * Find all c2s streams whose local or remote bare jid matches a given one. * Ignore destroying streams. * This method is thread safe * @param in True for incoming, false for outgoing * @param jid JID to compare (the local one for outgoing, remote jid for incoming) * @param flags Optional stream flag to match * @return List of referenced JBClientStream pointers or 0 */ ObjList* findClientStreams(bool in, const JabberID& jid, int flags = 0xffffffff); /** * Find all c2s streams whose local or remote bare jid matches a given one and * their resource is found in the given list. * Ignore destroying streams. * This method is thread safe * @param in True for incoming, false for outgoing * @param jid JID to compare (the local one for outgoing, remote jid for incoming) * @param resources The list of resources to match * @param flags Optional stream flag to match * @return List of referenced JBClientStream pointers or 0 */ ObjList* findClientStreams(bool in, const JabberID& jid, const ObjList& resources, int flags = 0xffffffff); /** * Find a c2s stream by its local or remote jid. * This method is thread safe * @param in True for incoming, false for outgoing * @param jid JID to compare (the local one for outgoing, remote jid for incoming) * @return Referenced JBClientStream pointer or 0 */ JBClientStream* findClientStream(bool in, const JabberID& jid); /** * Terminate all streams matching type and/or local/remote jid * @param type Stream type. Match all stream types if unknown * @param local Optional local jid to match * @param remote Optional remote jid to match * @param error Optional error to be sent to the client * @param reason Optional error text to be sent to the client * @return The number of stream terminated */ virtual unsigned int dropAll(JBStream::Type type = JBStream::TypeCount, const JabberID& local = JabberID::empty(), const JabberID& remote = JabberID::empty(), XMPPError::Type error = XMPPError::NoError, const char* reason = 0); /** * Build an internal stream name * @param name Destination buffer * @param stream Stream requesting it */ virtual void buildStreamName(String& name, const JBStream* stream) {} /** * Check if a domain is serviced by this engine * @param domain Domain to check * @return True if the given domain is serviced by this engine */ virtual bool hasDomain(const String& domain) { return false; } /** * Process an event. The default implementation will return the event * to this engine * @param ev The event to process */ virtual void processEvent(JBEvent* ev); /** * Return an event to this engine. The default implementation will send an * error if apropriate and delete the event * @param ev The event to return * @param error Optional error to be returned to the event's XML sender * @param reason Optional text to be attached to the error */ virtual void returnEvent(JBEvent* ev, XMPPError::Type error = XMPPError::NoError, const char* reason = 0); /** * Start stream TLS * @param stream The stream to enchrypt */ virtual void encryptStream(JBStream* stream); /** * Connect an outgoing stream * @param stream The stream to connect */ virtual void connectStream(JBStream* stream); /** * Start stream compression * @param stream The stream to compress * @param formats Supported formats */ virtual void compressStream(JBStream* stream, const String& formats); /** * Build a dialback key * @param id The stream id * @param local Local domain * @param remote Remote domain * @param key The dialback key */ virtual void buildDialbackKey(const String& id, const String& local, const String& remote, String& key); /** * Check if an outgoing stream exists with the same id and remote peer * @param stream The calling stream * @return True if a duplicate is found */ bool checkDupId(JBStream* stream); /** * Print XML to output * @param stream Stream requesting the operation * @param send True if sending, false if receiving * @param xml XML to print */ virtual void printXml(const JBStream* stream, bool send, XmlChild& xml) const; /** * Print an XML fragment to output * @param stream Stream requesting the operation * @param send True if sending, false if receiving * @param frag XML fragment to print */ virtual void printXml(const JBStream* stream, bool send, XmlFragment& frag) const; protected: /** * Add a stream to one of the stream lists * @param stream The stream to add */ virtual void addStream(JBStream* stream); /** * Remove a stream * @param stream The stream to remove * @param delObj True to release the stream, false to remove it from list * without releasing it */ virtual void removeStream(JBStream* stream, bool delObj = true); /** * Stop all stream sets * @param waitTerminate True to wait for all streams to terminate */ virtual void stopStreamSets(bool waitTerminate = true) {} /** * Retrieve the list of streams of a given type. * Descendant must implement it * @param list The destination list to set * @param type Stream type */ virtual void getStreamList(RefPointer& list, int type) {} /** * Retrieve all streams * @param list The destination list to set. The first index will be filled with the * c2s streams list, the second index will be set to the s2s stream list * @param type Optional stream type */ inline void getStreamLists(RefPointer list[JBStream::TypeCount], int type = JBStream::TypeCount) { if (type == JBStream::c2s || type == JBStream::TypeCount) 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); if (type == JBStream::cluster || type == JBStream::TypeCount) getStreamList(list[JBStream::cluster],JBStream::cluster); } /** * Find a stream by its name in a given set list * @param id The name of the stream to find * @param list The list to search for a stream * @return Referenced JBStream pointer or 0 */ JBStream* findStream(const String& id, JBStreamSetList* list); bool m_exiting; // Engine exiting flag JBRemoteDomainDef m_remoteDomain; // Default remote domain definition ObjList m_remoteDomains; // Remote domain definitions unsigned char m_restartMax; // Maximum value for stream restart counter unsigned int m_restartUpdInterval; // Update interval for stream restart counter unsigned int m_setupTimeout; // Overall stream setup timeout unsigned int m_startTimeout; // Wait stream start period unsigned int m_connectTimeout; // Outgoing: socket connect timeout unsigned int m_srvTimeout; // SRV query timeout unsigned int m_pingInterval; // Stream idle interval (no data received) unsigned int m_pingTimeout; // Sent ping timeout unsigned int m_idleTimeout; // Stream idle timeout (nothing sent or received) unsigned int m_pptTimeoutC2s; // Client streams postpone termination intervals unsigned int m_pptTimeout; // Non client streams postpone stream termination intervals unsigned int m_streamReadBuffer; // Stream read buffer length unsigned int m_maxIncompleteXml; // Maximum length of an incomplete xml unsigned int m_redirectMax; // Max redirect counter for outgoing streams bool m_hasClientTls; // True if TLS is available for outgoing streams int m_printXml; // Print XML data to output bool m_initialized; // True if already initialized private: // Add/remove a connect stream thread when started/stopped void connectStatus(JBConnect* conn, bool started); // Stop a connect stream void stopConnect(const String& name); ObjList m_connect; // Connecting streams }; /** * This class implements a Jabber server engine * @short A Jabber server engine */ class YJABBER_API JBServerEngine : public JBEngine { YCLASS(JBServerEngine,JBEngine) public: /** * Constructor * @param name Engine name */ JBServerEngine(const char* name = "jbserverengine"); /** * Destructor */ ~JBServerEngine(); /** * Terminate all streams. Stop all sets processors. Release memory if final * @param final True if called from destructor * @param waitTerminate True to wait for all streams to terminate */ virtual void cleanup(bool final = false, bool waitTerminate = true); /** * Build an internal stream name * @param name Destination buffer * @param stream Stream requesting it */ virtual void buildStreamName(String& name, const JBStream* stream) { name << "stream/" << getStreamIndex(); } /** * 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. * Ignored for component streams * @param auth Check if the remote domain of an incoming s2s stream is authenticated * @return Referenced JBServerStream pointer or 0 */ JBServerStream* findServerStream(const String& local, const String& remote, bool out, bool auth = true); /** * Create an outgoing s2s stream. * @param local Local party domain * @param remote Remote party domain * @param dbId Optional dialback id (stream id) * @param dbKey Optional dialback key to verify * @param dbOnly True if this is a dialback only stream * @param params Optional stream parameters * @return Referenced JBServerStream pointer or 0 if a stream already exists */ JBServerStream* createServerStream(const String& local, const String& remote, const char* dbId = 0, const char* dbKey = 0, bool dbOnly = false, const NamedList* params = 0); /** * Create an outgoing comp stream. * @param name Stream name * @param local Local party domain * @param remote Remote party domain * @param params Optional stream parameters * @return Referenced JBServerStream pointer or 0 if a stream already exists */ JBServerStream* createCompStream(const String& name, const String& local, const String& remote, const NamedList* params = 0); /** * Find a cluster stream by remote domain. * This method is thread safe * @param remote Remote jid * @param skip Optional stream to skip * @return Referenced JBClusterStream pointer or 0 */ JBClusterStream* findClusterStream(const String& remote, JBClusterStream* skip = 0); /** * Create an outgoing cluster stream. * This method is thread safe * @param local Local party domain * @param remote Remote party domain * @param params Optional stream parameters * @return Referenced JBClusterStream pointer or 0 if a stream already exists */ virtual JBClusterStream* createClusterStream(const String& local, const String& remote, const NamedList* params = 0); /** * Terminate all incoming c2s streams matching a given JID * This method is thread safe * @param jid Client JID * @param error Optional error to be sent to the client * @param reason Optional error text to be sent to the client * @return The number of stream terminated */ unsigned int terminateClientStreams(const JabberID& jid, XMPPError::Type error = XMPPError::NoError, const char* reason = 0); protected: /** * Add a stream to one of the stream lists * @param stream The stream to add */ virtual void addStream(JBStream* stream); /** * Remove a stream * @param stream The stream to remove * @param delObj True to release the stream, false to remove it from list * without releasing it */ virtual void removeStream(JBStream* stream, bool delObj = true); /** * Stop all stream sets * @param waitTerminate True to wait for all streams to terminate */ virtual void stopStreamSets(bool waitTerminate = true); /** * Retrieve the list of streams of a given type * @param list The destination list to set * @param type Stream type */ virtual void getStreamList(RefPointer& list, int type); /** * Retrieve the stream lists of a given type * @param type Stream type * @param recv Receive stream list to set * @param process Process stream list to set */ virtual void getStreamListsType(int type, RefPointer& recv, RefPointer& process); /** * Increment and return the stream index counter * @return Current stream index */ inline unsigned int getStreamIndex() { Lock lock(this); return ++m_streamIndex; } unsigned int m_streamIndex; // Index used to build stream name JBStreamSetList* m_c2sReceive; // c2s streams receive list 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 JBStreamSetList* m_clusterReceive; // cluster streams receive list JBStreamSetList* m_clusterProcess; // cluster streams process list }; /** * This class implements a Jabber client engine * @short A Jabber client engine */ class YJABBER_API JBClientEngine : public JBEngine { YCLASS(JBClientEngine,JBEngine) public: /** * Constructor * @param name Engine name */ JBClientEngine(const char* name = "jbclientengine"); /** * Destructor */ ~JBClientEngine(); /** * Terminate all streams. Stop all sets processors. Release memory if final * @param final True if called from destructor * @param waitTerminate True to wait for all streams to terminate */ virtual void cleanup(bool final = false, bool waitTerminate = true); /** * Find a stream by account * @param account Account name * @return Referenced JBClientStream pointer or 0 */ JBClientStream* findAccount(const String& account); /** * Build an outgoing client stream * @param account Account name * @param params Stream parameters * @param name Optional stream name * @return Referenced JBClientStream pointer or 0 if a stream already exists */ JBClientStream* create(const String& account, const NamedList& params, const String& name = String::empty()); /** * Retrieve the list of streams of a given type * @param list The destination list to set * @param type Stream type */ virtual void getStreamList(RefPointer& list, int type); protected: /** * Add a stream to one of the stream lists * @param stream The stream to add */ virtual void addStream(JBStream* stream); /** * Remove a stream * @param stream The stream to remove * @param delObj True to release the stream, false to remove it from list * without releasing it */ virtual void removeStream(JBStream* stream, bool delObj = true); /** * Stop all stream sets * @param waitTerminate True to wait for all streams to terminate */ virtual void stopStreamSets(bool waitTerminate = true); JBStreamSetList* m_receive; // Streams receive list JBStreamSetList* m_process; // Streams process list }; /** * This class holds a set of streams to be processed in an uniform way. * This is a base class for specialized stream list processors. * Its process() method should be called in its own thread * @short A set of streams to be processed in an uniform way */ class YJABBER_API JBStreamSet : public GenObject, public Mutex { YCLASS(JBStreamSet,GenObject); friend class JBStreamSetList; public: /** * Destructor. Delete the owned streams. Remove from owner */ virtual ~JBStreamSet(); /** * Retrieve the list of clients. * Make sure the set is locked before calling this method * @return The list of clients */ inline ObjList& clients() { return m_clients; } /** * Add a stream to the set. The stream's reference counter will be increased. * This method doesn't check if the stream is already added * @param client The stream to append * @return True on success, false if there is no more room in this set */ virtual bool add(JBStream* client); /** * Remove a stream from set * @param client The stream to remove * @param delObj True to release the stream, false to remove it from list * without releasing it * @return True on success, false if not found */ virtual bool remove(JBStream* client, bool delObj = true); /** * Terminate all streams matching local/remote jid * @param local Optional local jid to match * @param remote Optional remote jid to match * @param error Optional error to be sent to the client * @param reason Optional error text to be sent to the client * @return The number of streams terminated */ unsigned int dropAll(const JabberID& local = JabberID::empty(), const JabberID& remote = JabberID::empty(), XMPPError::Type error = XMPPError::NoError, const char* reason = 0); /** * Process the list. * Returns as soon as there are no more streams in the list */ void run(); /** * Start running * @return True on success */ virtual bool start(); /** * Stop running */ virtual void stop(); protected: /** * Constructor * @param owner The list owning this set */ JBStreamSet(JBStreamSetList* owner); /** * This method is called from run() with the list unlocked and stream's * reference counter increased. * A specialized processor must implement this method * @param stream The stream to process * @return True if something was processed */ virtual bool process(JBStream& stream) = 0; bool m_changed; // List changed flag bool m_exiting; // The thread is exiting (don't accept clients) JBStreamSetList* m_owner; // The list owning this set ObjList m_clients; // The streams list private: JBStreamSet() {} // Private default constructor (forbidden) }; /** * This class holds a set specialized in stream processing * @short Specialized stream processor */ class YJABBER_API JBStreamSetProcessor : public JBStreamSet { YCLASS(JBStreamSetProcessor,JBStreamSet); protected: /** * Constructor * @param owner The list owning this set */ inline JBStreamSetProcessor(JBStreamSetList* owner) : JBStreamSet(owner) {} /** * This method is called from run() with the list unlocked and stream's * reference counter increased. * Calls stream's getEvent(). Pass a generated event to the engine * Remove the stream from its engine on destroy * @param stream The stream to process * @return True if an event was generated by the stream */ virtual bool process(JBStream& stream); }; /** * This class holds a set specialized in stream data receiver * @short Specialized stream data receiver */ class YJABBER_API JBStreamSetReceive : public JBStreamSet { YCLASS(JBStreamSetReceive,JBStreamSet); protected: /** * Constructor. Build the read buffer * @param owner The list owning this set */ JBStreamSetReceive(JBStreamSetList* owner); /** * This method is called from run() with the list unlocked and stream's * reference counter increased. * Calls stream's readSocket() * @param stream The stream to process * @return True if the stream received any data */ virtual bool process(JBStream& stream); protected: DataBlock m_buffer; // Read buffer }; /** * This class holds a list of stream sets. * The purpose is to create a list of threads * @short A list of stream sets */ class YJABBER_API JBStreamSetList : public RefObject, public Mutex { YCLASS(JBStreamSetList,RefObject); friend class JBStreamSet; public: /** * Constructor * @param engine Engine owning this list * @param max Maximum streams per set (0 for maximum possible) * @param sleepMs Time to sleep when idle * @param name List name (for debugging purposes) */ JBStreamSetList(JBEngine* engine, unsigned int max, unsigned int sleepMs, const char* name); /** * Retrieve the stream set list. * Make sure the list is locked before calling this method * @return The stream set list */ inline ObjList& sets() { return m_sets; } /** * Destructor */ virtual ~JBStreamSetList(); /** * Retrieve the maximum number of streams per set * @return The maximum number of streams per set */ inline unsigned int maxStreams() const { return m_max; } /** * Retrieve the number of streams in all sets * @return The number of streams in all sets */ inline unsigned int streamCount() const { return m_streamCount; } /** * Retrieve the engine owning this list * @return The engine owning this list */ inline JBEngine* engine() const { return m_engine; } /** * Add a stream to the list. Build a new set if there is no room in existing sets * @param client The stream to add * @return True on success */ bool add(JBStream* client); /** * Remove a stream from list * @param client The stream to remove * @param delObj True to release the stream, false to remove it from list * without releasing it */ void remove(JBStream* client, bool delObj = true); /** * Stop one set or all sets * @param set The set to stop, 0 to stop all * @param waitTerminate True to wait for all streams to terminate */ void stop(JBStreamSet* set = 0, bool waitTerminate = true); /** * Get the string representation of this list * @return The list name */ virtual const String& toString() const; protected: /** * Stop all sets. Release memory */ virtual void destroyed(); /** * Remove a set from list without deleting it * @param set The set to remove */ void remove(JBStreamSet* set); /** * Build a specialized stream set. Descendants must override this method * @return JBStreamSet pointer or 0 */ virtual JBStreamSet* build(); JBEngine* m_engine; // The engine owning this list String m_name; // List name unsigned int m_max; // The maximum number of streams per set unsigned int m_sleepMs; // Time to sleep if nothig processed ObjList m_sets; // The sets list private: JBStreamSetList() {} // Private default constructor (forbidden) unsigned int m_streamCount; // Current number of streams in this list }; /** * This class holds entity capability data * Implements XEP 0115 support * @short Entity capability */ class YJABBER_API JBEntityCaps : public String { YCLASS(JBEntityCaps,String); public: /** * Supported XEP 0115 versions */ enum { Ver1_3 = 1, // Version lower then 1.4 (m_data is the node version + advertised extensions) Ver1_4 = 2, // Version 1.4 or greater (m_data is the SHA-1 hash of features and identities) }; /** * Constructor * @param id Object id * @param version Entity caps version * @param node Entity node * @param data Entity data */ inline JBEntityCaps(const char* id, char version, const char* node, const char* data) : String(id), m_version(version), m_node(node), m_data(data) {} /** * Check if a given feature is found in the list * @param ns The feature to check * @return True if the feature was found in the list */ inline bool hasFeature(int ns) { return 0 != m_features.get(ns); } /** * Check if an audio capability is present * @return True if an audio capability is present */ inline bool hasAudio() { return hasFeature(XMPPNamespace::JingleAppsRtpAudio) || hasFeature(XMPPNamespace::JingleAudio) || hasFeature(XMPPNamespace::JingleVoiceV1); } /** * Build an entity caps id * @param buf Destination buffer * @param version Entity caps version * @param node Entity node * @param data Entity data * @param ext Optional entity extensions */ static inline void buildId(String& buf, char version, const char* node, const char* data, String* ext = 0) { buf << (int)version << node << data << (ext ? ext->c_str() : ""); } char m_version; String m_node; String m_data; XMPPFeatureList m_features; private: JBEntityCaps() {} }; /** * This class holds data and offer entity capability services. * Implements XEP 0115 support * @short Entity capability list manager */ class YJABBER_API JBEntityCapsList : public ObjList, public Mutex { YCLASS(JBEntityCapsList,ObjList); public: /** * Constructor */ inline JBEntityCapsList() : Mutex(true,"JBEntityCapsList"), m_enable(true), m_reqIndex(0) { m_reqPrefix << "xep0115" << (unsigned int)Time::msecNow() << "_"; } /** * Retrieve an entity caps object. This method is not thread safe * @param id The id to find * @return JBEntityCaps pointer or 0 */ inline JBEntityCaps* findCaps(const String& id) { for (ObjList* o = skipNull(); o; o = o->skipNext()) if (o->get()->toString() == id) return static_cast(o->get()); return 0; } /** * Expire pending requests. * This method is thread safe * @param msecNow Current time */ void expire(u_int64_t msecNow = Time::msecNow()); /** * Process a response. * This method is thread safe * @param rsp The element to process * @param id The element's id * @param ok True if the response is a result one, false if it's an error * @return True if the element was processed (handled) */ bool processRsp(XmlElement* rsp, const String& id, bool ok); /** * Request entity capabilities. * This method is thread safe * @param stream The stream to send the request * @param from The 'from' attribute * @param to The 'to' attribute * @param id Entity caps id * @param version Entity caps version * @param node Entity node * @param data Entity caps data */ void requestCaps(JBStream* stream, const char* from, const char* to, const String& id, char version, const char* node, const char* data); /** * Build an XML document from this list. * This method is thread safe * @param rootName Document root element name * @return XmlDocument pointer */ XmlDocument* toDocument(const char* rootName = "entitycaps"); /** * Build this list from an XML document. * This method is thread safe * @param doc Document to build from * @param rootName Document root element name (it will be checked if set) * @return XmlDocument pointer */ void fromDocument(XmlDocument& doc, const char* rootName = "entitycaps"); /** * Process an element containing an entity capabily child. * Request capabilities if not found in the list. * This method is thread safe * @param capsId String to be filled with entity caps object id * (empty if an entity caps child is not found in element ) * @param xml XML element to process * @param stream The stream used to request capabilities * @param from The 'from' attribute of the request stanza * @param to The 'to' attribute of the request stanza * @return True if processed (already found, added or request sent) */ virtual bool processCaps(String& capsId, XmlElement* xml, JBStream* stream, const char* from, const char* to); /** * Add capabilities to a list. * This method is thread safe * @param list Destination list * @param id Entity caps id */ inline void addCaps(NamedList& list, const String& id) { Lock lock(this); JBEntityCaps* caps = findCaps(id); if (caps) addCaps(list,*caps); } /** * Add capabilities to a list. * This method is not thread safe * @param list Destination list * @param caps Entity caps to add */ virtual void addCaps(NamedList& list, JBEntityCaps& caps); /** * Load (reset) this list from an XML document file. * This method is thread safe * @param file The file to load * @param enabler The debug enabler used to output messages * @return True on success */ bool loadXmlDoc(const char* file, DebugEnabler* enabler = 0); /** * Save this list to an XML document file. * This method is thread safe * @param file The file to save * @param enabler The debug enabler used to output messages * @return True on success */ bool saveXmlDoc(const char* file, DebugEnabler* enabler = 0); /** * Check if an XML element has a 'c' entity capability child and decode it * @param xml The element to process * @param version Entity caps version * @param node Entity node attribute * @param ver Entity ver attribute * @param ext Entity ext attribute if version is less the 1.4 * @return True if a child was succesfully decoded */ static bool decodeCaps(const XmlElement& xml, char& version, String*& node, String*& ver, String*& ext); /** * Enabled flag */ bool m_enable; protected: /** * Caps list item add notification for descendants. * This method is called when processing responses with the list locked * @param caps Changed caps object. 0 if none specified */ virtual void capsAdded(JBEntityCaps* caps) {} unsigned int m_reqIndex; // Disco info request index String m_reqPrefix; // Prefix for disco info stanza id ObjList m_requests; // List of sent disco info requests }; }; // namespace TelEngine #endif /* __YATEJABBER_H */ /* vi: set ts=8 sw=4 sts=4 noet: */