All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.openfire.session.LocalClientSession Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.openfire.session;

import java.net.UnknownHostException;
import java.util.*;

import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.privacy.PrivacyList;
import org.jivesoftware.openfire.privacy.PrivacyListManager;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.spi.ConnectionConfiguration;
import org.jivesoftware.openfire.streammanagement.StreamManager;
import org.jivesoftware.openfire.user.PresenceEventDispatcher;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
import org.xmpp.packet.StreamError;

/**
 * Represents a session between the server and a client.
 *
 * @author Gaston Dombiak
 */
public class LocalClientSession extends LocalSession implements ClientSession {

    private static final Logger Log = LoggerFactory.getLogger(LocalClientSession.class);

    private static final String ETHERX_NAMESPACE = "http://etherx.jabber.org/streams";
    private static final String FLASH_NAMESPACE = "http://www.jabber.com/streams/flash";

    /**
     * Keep the list of IP address that are allowed to connect to the server.
     *
     * If the list is  empty then anyone is allowed to connect to the server, unless the IP is on the blacklist (which
     * always takes precedence over the whitelist).
     *
     * Note: the values in this list can be hostnames, IP addresses or IP ranges (with wildcards).
     */
    private static Set allowedIPs = new HashSet<>();
    private static Set allowedAnonymIPs = new HashSet<>();

    /**
     * Similar to {@link #allowedIPs}, but used for blacklisting rather than whitelisting.
     */
    private static Set blockedIPs = new HashSet<>();

    private boolean messageCarbonsEnabled;

    /**
     * The authentication token for this session.
     */
    protected AuthToken authToken;

    /**
     * Flag indicating if this session has been initialized yet (upon first available transition).
     */
    private boolean initialized;

    /**
     * Flag that indicates if the session was available ever.
     */
    private boolean wasAvailable = false;

    /**
     * Flag indicating if the user requested to not receive offline messages when sending
     * an available presence. The user may send a disco request with node
     * "http://jabber.org/protocol/offline" so that no offline messages are sent to the
     * user when he becomes online. If the user is connected from many resources then
     * if one of the sessions stopped the flooding then no session should flood the user.
     */
    private boolean offlineFloodStopped = false;

    private Presence presence = null;

    private int conflictCount = 0;

    /**
     * Privacy list that overrides the default privacy list. This list affects only this
     * session and only for the duration of the session.
     */
    private String activeList;
    /**
     * Default privacy list used for the session's user. This list is processed if there
     * is no active list set for the session.
     */
    private String defaultList;

    static {
        // Fill out the allowedIPs with the system property
        String allowed = JiveGlobals.getProperty(ConnectionSettings.Client.LOGIN_ALLOWED, "");
        StringTokenizer tokens = new StringTokenizer(allowed, ", ");
        while (tokens.hasMoreTokens()) {
            String address = tokens.nextToken().trim();
            allowedIPs.add( address );
        }
        String allowedAnonym = JiveGlobals.getProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED, "");
        tokens = new StringTokenizer(allowedAnonym, ", ");
        while (tokens.hasMoreTokens()) {
            String address = tokens.nextToken().trim();
            allowedAnonymIPs.add(address);
        }
        String blocked = JiveGlobals.getProperty(ConnectionSettings.Client.LOGIN_BLOCKED, "");
        tokens = new StringTokenizer(blocked, ", ");
        while (tokens.hasMoreTokens()) {
            String address = tokens.nextToken().trim();
            blockedIPs.add( address );
        }
    }

    /**
     * Returns the list of IP address that are allowed to connect to the server. If the list is
     * empty then anyone is allowed to connect to the server except for anonymous users that are
     * subject to {@link #getAllowedAnonymIPs()}. This list is used for both anonymous and
     * non-anonymous users.
     *
     * Note that the blacklist in {@link #getBlacklistedIPs()} should take precedence!
     *
     * @return the list of IP address that are allowed to connect to the server.
     * @deprecated Use #getWhitelistedIPs instead.
     */
    @Deprecated
    public static Map getAllowedIPs()
    {
        final Map result = new HashMap<>();
        for ( String item : allowedIPs )
        {
            result.put( item, null );
        }
        return result;
    }

    /**
     * Returns the list of IP address that are allowed to connect to the server. If the list is empty then anyone is
     * allowed to connect to the server except for anonymous users that are subject to
     * {@link #getWhitelistedAnonymousIPs()}. This list is used for both anonymous and non-anonymous users.
     *
     * Note that the blacklist in {@link #getBlacklistedIPs()} should take precedence!
     *
     * @return the collection of IP address that are allowed to connect to the server. Never null, possibly empty.
     */
    public static Set getWhitelistedIPs() { return allowedIPs; }

    /**
     * Returns the list of IP address that are allowed to connect to the server for anonymous
     * users. If the list is empty then anonymous will be only restricted by {@link #getAllowedIPs()}.
     *
     * Note that the blacklist in {@link #getBlacklistedIPs()} should take precedence!
     *
     * @return the list of IP address that are allowed to connect to the server.
     * @deprecated Use #getWhitelistedAnonymousIPs instead.
     */
    public static Map getAllowedAnonymIPs()
    {
        final Map result = new HashMap<>();
        for ( String item : allowedAnonymIPs )
        {
            result.put( item, null );
        }
        return result;
    }

    /**
     * Returns the list of IP address that are allowed to connect to the server for anonymous users. If the list is
     * empty then anonymous will be only restricted by {@link #getWhitelistedIPs()}.
     *
     * Note that the blacklist in {@link #getBlacklistedIPs()} should take precedence!
     *
     * @return the collection of IP address that are allowed to connect to the server. Never null, possibly empty.
     */
    public static Set getWhitelistedAnonymousIPs() {
        return allowedAnonymIPs;
    }

    /**
     * Returns the list of IP address that are disallowed to connect to the server. If the list is empty then anyone is
     * allowed to connect to the server, subject to whitelisting. This list is used for both anonymous and
     * non-anonymous users.
     *
     * @return the collection of IP address that are not allowed to connect to the server. Never null, possibly empty.
     */
    public static Set getBlacklistedIPs() { return blockedIPs; }

    /**
     * Returns a newly created session between the server and a client. The session will
     * be created and returned only if correct name/prefix (i.e. 'stream' or 'flash')
     * and namespace were provided by the client.
     *
     * @param serverName the name of the server where the session is connecting to.
     * @param xpp the parser that is reading the provided XML through the connection.
     * @param connection the connection with the client.
     * @return a newly created session between the server and a client.
     * @throws org.xmlpull.v1.XmlPullParserException if an error occurs while parsing incoming data.
     */
    public static LocalClientSession createSession(String serverName, XmlPullParser xpp, Connection connection)
            throws XmlPullParserException {
        boolean isFlashClient = xpp.getPrefix().equals("flash");
        connection.setFlashClient(isFlashClient);

        // Conduct error checking, the opening tag should be 'stream'
        // in the 'etherx' namespace
        if (!xpp.getName().equals("stream") && !isFlashClient) {
            throw new XmlPullParserException(
                    LocaleUtils.getLocalizedString("admin.error.bad-stream"));
        }

        if (!xpp.getNamespace(xpp.getPrefix()).equals(ETHERX_NAMESPACE) &&
                !(isFlashClient && xpp.getNamespace(xpp.getPrefix()).equals(FLASH_NAMESPACE)))
        {
            throw new XmlPullParserException(LocaleUtils.getLocalizedString(
                    "admin.error.bad-namespace"));
        }

        if (!isAllowed(connection))
        {
            // Client cannot connect from this IP address so end the stream and TCP connection.
            String hostAddress = "Unknown";
            try {
                hostAddress = connection.getHostAddress();
            } catch (UnknownHostException e) {
                // Do nothing
            }

            Log.debug("LocalClientSession: Closed connection to client attempting to connect from: " + hostAddress);
            // Include the not-authorized error in the response
            StreamError error = new StreamError(StreamError.Condition.not_authorized);
            connection.deliverRawText(error.toXML());
            // Close the underlying connection
            connection.close();
            return null;
        }

        // Default language is English ("en").
        Locale language = Locale.forLanguageTag("en");
        // Default to a version of "0.0". Clients written before the XMPP 1.0 spec may
        // not report a version in which case "0.0" should be assumed (per rfc3920
        // section 4.4.1).
        int majorVersion = 0;
        int minorVersion = 0;
        for (int i = 0; i < xpp.getAttributeCount(); i++) {
            if ("lang".equals(xpp.getAttributeName(i))) {
                language = Locale.forLanguageTag(xpp.getAttributeValue(i));
            }
            if ("version".equals(xpp.getAttributeName(i))) {
                try {
                    int[] version = decodeVersion(xpp.getAttributeValue(i));
                    majorVersion = version[0];
                    minorVersion = version[1];
                }
                catch (Exception e) {
                    Log.error(e.getMessage(), e);
                }
            }
        }

        // If the client supports a greater major version than the server,
        // set the version to the highest one the server supports.
        if (majorVersion > MAJOR_VERSION) {
            majorVersion = MAJOR_VERSION;
            minorVersion = MINOR_VERSION;
        }
        else if (majorVersion == MAJOR_VERSION) {
            // If the client supports a greater minor version than the
            // server, set the version to the highest one that the server
            // supports.
            if (minorVersion > MINOR_VERSION) {
                minorVersion = MINOR_VERSION;
            }
        }

        connection.setXMPPVersion(majorVersion, minorVersion);
        final ConnectionConfiguration connectionConfiguration = connection.getConfiguration();

        // Indicate the TLS policy to use for this connection
        if (!connection.isSecure()) {
            boolean hasCertificates = false;
            try {
                hasCertificates = connectionConfiguration.getIdentityStore().getAllCertificates().size() > 0;
            }
            catch (Exception e) {
                Log.error(e.getMessage(), e);
            }
            Connection.TLSPolicy tlsPolicy = connectionConfiguration.getTlsPolicy();
            if (Connection.TLSPolicy.required == tlsPolicy && !hasCertificates) {
                Log.error("Client session rejected. TLS is required but no certificates " +
                        "were created.");
                return null;
            }
            // Set default TLS policy
            connection.setTlsPolicy(hasCertificates ? tlsPolicy : Connection.TLSPolicy.disabled);
        } else {
            // Set default TLS policy
            connection.setTlsPolicy(Connection.TLSPolicy.disabled);
        }

        // Indicate the compression policy to use for this connection
        connection.setCompressionPolicy( connectionConfiguration.getCompressionPolicy() );

        // Create a ClientSession for this user.
        LocalClientSession session = SessionManager.getInstance().createClientSession(connection, language);

        // Build the start packet response
        StringBuilder sb = new StringBuilder(200);
        sb.append("");
        if (isFlashClient) {
            sb.append("");
        connection.deliverRawText(sb.toString());

        // If this is a "Jabber" connection, the session is now initialized and we can
        // return to allow normal packet parsing.
        if (majorVersion == 0) {
            return session;
        }
        // Otherwise, this is at least XMPP 1.0 so we need to announce stream features.

        sb = new StringBuilder(490);
        sb.append("");
        if (connection.getTlsPolicy() != Connection.TLSPolicy.disabled) {
            sb.append("");
            if (connection.getTlsPolicy() == Connection.TLSPolicy.required) {
                sb.append("");
            }
            sb.append("");
        }
        // Include available SASL Mechanisms
        sb.append(SASLAuthentication.getSASLMechanisms(session));
        // Include Stream features
        String specificFeatures = session.getAvailableStreamFeatures();
        if (specificFeatures != null) {
            sb.append(specificFeatures);
        }
        sb.append("");

        connection.deliverRawText(sb.toString());
        return session;
    }

    public static boolean isAllowed( Connection connection )
    {
        try
        {
            final String hostAddress = connection.getHostAddress();
            final byte[] address = connection.getAddress();

            // Blacklist takes precedence over whitelist.
            if ( blockedIPs.contains( hostAddress ) || isAddressInRange( address, blockedIPs ) ) {
                return false;
            }

            // When there's a whitelist (not empty), you must be on it to be allowed.
            return allowedIPs.isEmpty() || allowedIPs.contains( hostAddress ) || isAddressInRange( address, allowedIPs );
        }
        catch ( UnknownHostException e )
        {
            return false;
        }
    }

    public static boolean isAllowedAnonymous( Connection connection )
    {
        try
        {
            final String hostAddress = connection.getHostAddress();
            final byte[] address = connection.getAddress();

            // Blacklist takes precedence over whitelist.
            if ( blockedIPs.contains( hostAddress ) || isAddressInRange( address, blockedIPs ) ) {
                return false;
            }

            // When there's a whitelist (not empty), you must be on it to be allowed.
            return allowedAnonymIPs.isEmpty() || allowedAnonymIPs.contains( hostAddress ) || isAddressInRange( address, allowedAnonymIPs );
        }
        catch ( UnknownHostException e )
        {
            return false;
        }
    }

    // TODO Add IPv6 support
    public static boolean isAddressInRange( byte[] address, Set ranges ) {
        final String range0 = (address[0] & 0xff) + "." + (address[1] & 0xff) + "." + (address[2] & 0xff) + "." + (address[3] & 0xff);
        final String range1 = (address[0] & 0xff) + "." + (address[1] & 0xff) + "." + (address[2] & 0xff) + ".*";
        final String range2 = (address[0] & 0xff) + "." + (address[1] & 0xff) + ".*.*";
        final String range3 = (address[0] & 0xff) + ".*.*.*";
        return ranges.contains(range0) || ranges.contains(range1) || ranges.contains(range2) || ranges.contains(range3);
    }

    /**
     * Sets the list of IP address that are allowed to connect to the server. If the list is
     * empty then anyone is allowed to connect to the server except for anonymous users that are
     * subject to {@link #getAllowedAnonymIPs()}. This list is used for both anonymous and
     * non-anonymous users.
     *
     * @param allowed the list of IP address that are allowed to connect to the server.
     * @deprecated Use setWhitelistedIPs instead.
     */
    @Deprecated
    public static void setAllowedIPs(Map allowed) {
        setWhitelistedIPs( allowed.keySet() );
    }

    /**
     * Sets the list of IP address that are allowed to connect to the server. If the list is empty then anyone not on
     * {@link #getBlacklistedIPs()} is  allowed to connect to the server except for anonymous users that are subject to
     * {@link #getWhitelistedAnonymousIPs()}. This list is used for both anonymous and non-anonymous users.
     *
     * Note that blacklisting takes precedence over whitelisting: if an address is matched by both, access is denied.
     *
     * @param allowed the list of IP address that are allowed to connect to the server. Can be empty, but not null.
     */
    public static void setWhitelistedIPs(Set allowed) {
        if (allowed == null) {
            throw new NullPointerException();
        }
        allowedIPs = allowed;
        if (allowedIPs.isEmpty()) {
            JiveGlobals.deleteProperty(ConnectionSettings.Client.LOGIN_ALLOWED);
        }
        else {
            // Iterate through the elements in the map.
            StringBuilder buf = new StringBuilder();
            Iterator iter = allowedIPs.iterator();
            if (iter.hasNext()) {
                buf.append(iter.next());
            }
            while (iter.hasNext()) {
                buf.append(", ").append(iter.next());
            }
            JiveGlobals.setProperty(ConnectionSettings.Client.LOGIN_ALLOWED, buf.toString());
        }
    }

    /**
     * Sets the list of IP address that are allowed to connect to the server for anonymous
     * users. If the list is empty then anonymous will be only restricted by {@link #getAllowedIPs()}.
     *
     * @param allowed the list of IP address that are allowed to connect to the server.
     * @deprecated use #setWhitelistedAnonymousIPs instead.
     */
    @Deprecated
    public static void setAllowedAnonymIPs(Map allowed) {
        setWhitelistedAnonymousIPs( allowed.keySet() );
    }

    /**
     * Sets the list of IP address that are allowed to connect to the server for anonymous users. If the list is empty
     * then anonymous will be only restricted by {@link #getBlacklistedIPs()} and {@link #getWhitelistedIPs()}.
     *
     * @param allowed the list of IP address that are allowed to connect to the server. Can be empty, but not null.
     */
    public static void setWhitelistedAnonymousIPs(Set allowed) {
        if (allowed == null) {
            throw new NullPointerException();
        }
        allowedAnonymIPs = allowed;
        if (allowedAnonymIPs.isEmpty()) {
            JiveGlobals.deleteProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED);
        }
        else {
            // Iterate through the elements in the map.
            StringBuilder buf = new StringBuilder();
            Iterator iter = allowedAnonymIPs.iterator();
            if (iter.hasNext()) {
                buf.append(iter.next());
            }
            while (iter.hasNext()) {
                buf.append(", ").append(iter.next());
            }
            JiveGlobals.setProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED, buf.toString());
        }
    }

    /**
     * Sets the list of IP address that are not allowed to connect to the server. This list is used for both anonymous
     * and non-anonymous users, and always takes precedence over a whitelist.
     *
     * @param blocked the list of IP address that are not allowed to connect to the server. Can be empty, but not null.
     */
    public static void setBlacklistedIPs(Set blocked) {
        if (blocked == null) {
            throw new NullPointerException();
        }
        blockedIPs = blocked;
        if (blockedIPs.isEmpty()) {
            JiveGlobals.deleteProperty(ConnectionSettings.Client.LOGIN_BLOCKED);
        }
        else {
            // Iterate through the elements in the map.
            StringBuilder buf = new StringBuilder();
            Iterator iter = blocked.iterator();
            if (iter.hasNext()) {
                buf.append(iter.next());
            }
            while (iter.hasNext()) {
                buf.append(", ").append(iter.next());
            }
            JiveGlobals.setProperty(ConnectionSettings.Client.LOGIN_BLOCKED, buf.toString());
        }
    }

    /**
     * Returns the Privacy list that overrides the default privacy list. This list affects
     * only this session and only for the duration of the session.
     *
     * @return the Privacy list that overrides the default privacy list.
     */
    @Override
    public PrivacyList getActiveList() {
        if (activeList != null) {
            try {
                return PrivacyListManager.getInstance().getPrivacyList(getUsername(), activeList);
            } catch (UserNotFoundException e) {
                Log.error(e.getMessage(), e);
            }
        }
        return null;
    }

    /**
     * Sets the Privacy list that overrides the default privacy list. This list affects
     * only this session and only for the duration of the session.
     *
     * @param activeList the Privacy list that overrides the default privacy list.
     */
    @Override
    public void setActiveList(PrivacyList activeList) {
        this.activeList = activeList != null ? activeList.getName() : null;
        if (ClusterManager.isClusteringStarted()) {
            // Track information about the session and share it with other cluster nodes
            Cache cache = SessionManager.getInstance().getSessionInfoCache();
            cache.put(getAddress().toString(), new ClientSessionInfo(this));
        }
    }

    /**
     * Returns the default Privacy list used for the session's user. This list is
     * processed if there is no active list set for the session.
     *
     * @return the default Privacy list used for the session's user.
     */
    @Override
    public PrivacyList getDefaultList() {
        if (defaultList != null) {
            try {
                return PrivacyListManager.getInstance().getPrivacyList(getUsername(), defaultList);
            } catch (UserNotFoundException e) {
                Log.error(e.getMessage(), e);
            }
        }
        return null;
    }

    /**
     * Sets the default Privacy list used for the session's user. This list is
     * processed if there is no active list set for the session.
     *
     * @param defaultList the default Privacy list used for the session's user.
     */
    @Override
    public void setDefaultList(PrivacyList defaultList) {
        // Do nothing if nothing has changed
        if ((this.defaultList == null && defaultList == null) ||
                (defaultList != null && defaultList.getName().equals(this.defaultList))) {
            return;
        }
        this.defaultList = defaultList != null ? defaultList.getName() : null;
        if (ClusterManager.isClusteringStarted()) {
            // Track information about the session and share it with other cluster nodes
            Cache cache = SessionManager.getInstance().getSessionInfoCache();
            cache.put(getAddress().toString(), new ClientSessionInfo(this));
        }
    }

    /**
     * Creates a session with an underlying connection and permission protection.
     *
     * @param serverName name of the server.
     * @param connection The connection we are proxying.
     * @param streamID unique identifier of this session.
     */
    public LocalClientSession(String serverName, Connection connection, StreamID streamID, Locale language) {
        super(serverName, connection, streamID, language);
        // Set an unavailable initial presence
        presence = new Presence();
        presence.setType(Presence.Type.unavailable);
    }

    /**
     * Returns the username associated with this session. Use this information
     * with the user manager to obtain the user based on username.
     *
     * @return the username associated with this session
     * @throws org.jivesoftware.openfire.user.UserNotFoundException if a user is not associated with a session
     *      (the session has not authenticated yet)
     */
    @Override
    public String getUsername() throws UserNotFoundException {
        if (authToken == null) {
            throw new UserNotFoundException();
        }
        return getAddress().getNode();
    }

    /**
     * Sets the new Authorization Token for this session. The session is not yet considered fully
     * authenticated (i.e. active) since a resource has not been binded at this point. This
     * message will be sent after SASL authentication was successful but yet resource binding
     * is required.
     *
     * @param auth the authentication token obtained from SASL authentication.
     */
    public void setAuthToken(AuthToken auth) {
        authToken = auth;
    }

    /**
     * Initialize the session with a valid authentication token and
     * resource name. This automatically upgrades the session's
     * status to authenticated and enables many features that are not
     * available until authenticated (obtaining managers for example).
     *
     * @param auth the authentication token obtained from the AuthFactory.
     * @param resource the resource this session authenticated under.
     */
    public void setAuthToken(AuthToken auth, String resource) {
        setAddress(new JID(auth.getUsername(), getServerName(), resource));
        authToken = auth;
        setStatus(Session.STATUS_AUTHENTICATED);

        // Set default privacy list for this session
        setDefaultList(PrivacyListManager.getInstance().getDefaultPrivacyList(auth.getUsername()));
        // Add session to the session manager. The session will be added to the routing table as well
        sessionManager.addSession(this);
    }

    /**
     * Initialize the session as an anonymous login. This automatically upgrades the session's
     * status to authenticated and enables many features that are not available until
     * authenticated (obtaining managers for example).

*/ public void setAnonymousAuth() { // Anonymous users have a full JID. Use the random resource as the JID's node String resource = getAddress().getResource(); setAddress(new JID(resource, getServerName(), resource, true)); setStatus(Session.STATUS_AUTHENTICATED); if (authToken == null) { authToken = new AuthToken(resource, true); } // Add session to the session manager. The session will be added to the routing table as well sessionManager.addSession(this); } /** * Returns the authentication token associated with this session. * * @return the authentication token associated with this session (can be null). */ public AuthToken getAuthToken() { return authToken; } @Override public boolean isAnonymousUser() { return authToken == null || authToken.isAnonymous(); } /** * Flag indicating if this session has been initialized once coming * online. Session initialization occurs after the session receives * the first "available" presence update from the client. Initialization * actions include pushing offline messages, presence subscription requests, * and presence statuses to the client. Initialization occurs only once * following the first available presence transition. * * @return True if the session has already been initializsed */ @Override public boolean isInitialized() { return initialized; } /** * Sets the initialization state of the session. * * @param isInit True if the session has been initialized * @see #isInitialized */ @Override public void setInitialized(boolean isInit) { initialized = isInit; } /** * Returns true if the session was available ever. * * @return true if the session was available ever. */ public boolean wasAvailable() { return wasAvailable; } /** * Returns true if the offline messages of the user should be sent to the user when * the user becomes online. If the user sent a disco request with node * "http://jabber.org/protocol/offline" before the available presence then do not * flood the user with the offline messages. If the user is connected from many resources * then if one of the sessions stopped the flooding then no session should flood the user. * * @return true if the offline messages of the user should be sent to the user when the user * becomes online. * @see XEP-0160: Best Practices for Handling Offline Messages */ @Override public boolean canFloodOfflineMessages() { // XEP-0160: When the recipient next sends non-negative available presence to the server, the server delivers the message to the resource that has sent that presence. if(offlineFloodStopped || presence.getPriority() < 0) { return false; } String username = getAddress().getNode(); for (ClientSession session : sessionManager.getSessions(username)) { if (session.isOfflineFloodStopped()) { return false; } } return true; } /** * Returns true if the user requested to not receive offline messages when sending * an available presence. The user may send a disco request with node * "http://jabber.org/protocol/offline" so that no offline messages are sent to the * user when he becomes online. If the user is connected from many resources then * if one of the sessions stopped the flooding then no session should flood the user. * * @return true if the user requested to not receive offline messages when sending * an available presence. */ @Override public boolean isOfflineFloodStopped() { return offlineFloodStopped; } /** * Sets if the user requested to not receive offline messages when sending * an available presence. The user may send a disco request with node * "http://jabber.org/protocol/offline" so that no offline messages are sent to the * user when he becomes online. If the user is connected from many resources then * if one of the sessions stopped the flooding then no session should flood the user. * * @param offlineFloodStopped if the user requested to not receive offline messages when * sending an available presence. */ public void setOfflineFloodStopped(boolean offlineFloodStopped) { this.offlineFloodStopped = offlineFloodStopped; if (ClusterManager.isClusteringStarted()) { // Track information about the session and share it with other cluster nodes Cache cache = SessionManager.getInstance().getSessionInfoCache(); cache.put(getAddress().toString(), new ClientSessionInfo(this)); } } /** * Obtain the presence of this session. * * @return The presence of this session or null if not authenticated */ @Override public Presence getPresence() { return presence; } /** * Set the presence of this session * * @param presence The presence for the session */ @Override public void setPresence(Presence presence) { Presence oldPresence = this.presence; this.presence = presence; if (oldPresence.isAvailable() && !this.presence.isAvailable()) { // The client is no longer available sessionManager.sessionUnavailable(this); // Mark that the session is no longer initialized. This means that if the user sends // an available presence again the session will be initialized again thus receiving // offline messages and offline presence subscription requests setInitialized(false); // Notify listeners that the session is no longer available PresenceEventDispatcher.unavailableSession(this, presence); } else if (!oldPresence.isAvailable() && this.presence.isAvailable()) { // The client is available sessionManager.sessionAvailable(this, presence); wasAvailable = true; // Notify listeners that the session is now available PresenceEventDispatcher.availableSession(this, presence); } else if (this.presence.isAvailable() && oldPresence.getPriority() != this.presence.getPriority()) { // The client has changed the priority of his presence sessionManager.changePriority(this, oldPresence.getPriority()); // Notify listeners that the priority of the session/resource has changed PresenceEventDispatcher.presenceChanged(this, presence); } else if (this.presence.isAvailable()) { // Notify listeners that the show or status value of the presence has changed PresenceEventDispatcher.presenceChanged(this, presence); } if (ClusterManager.isClusteringStarted()) { // Track information about the session and share it with other cluster nodes Cache cache = SessionManager.getInstance().getSessionInfoCache(); cache.put(getAddress().toString(), new ClientSessionInfo(this)); } } @Override public String getAvailableStreamFeatures() { // Offer authenticate and registration only if TLS was not required or if required // then the connection is already secured if (conn.getTlsPolicy() == Connection.TLSPolicy.required && !conn.isSecure()) { return null; } StringBuilder sb = new StringBuilder(200); // Include Stream Compression Mechanism if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled && !conn.isCompressed()) { sb.append( "zlib"); } // If a server supports roster versioning, // then it MUST advertise the following stream feature during stream negotiation. if (RosterManager.isRosterVersioningEnabled()) { sb.append(""); } if (getAuthToken() == null) { // Advertise that the server supports Non-SASL Authentication if ( XMPPServer.getInstance().getIQRouter().supports( "jabber:iq:auth" ) ) { sb.append(""); } // Advertise that the server supports In-Band Registration if (XMPPServer.getInstance().getIQRegisterHandler().isInbandRegEnabled()) { sb.append(""); } } else { // If the session has been authenticated then offer resource binding, // and session establishment sb.append(""); sb.append(""); // Offer XEP-0198 stream management capabilities if enabled. if(JiveGlobals.getBooleanProperty(StreamManager.SM_ACTIVE, true)) { sb.append(String.format("", StreamManager.NAMESPACE_V2)); sb.append(String.format("", StreamManager.NAMESPACE_V3)); } } return sb.toString(); } /** * Increments the conflict by one. */ @Override public int incrementConflictCount() { conflictCount++; return conflictCount; } @Override public boolean isMessageCarbonsEnabled() { return messageCarbonsEnabled; } @Override public void setMessageCarbonsEnabled(boolean enabled) { messageCarbonsEnabled = enabled; } /** * Returns true if the specified packet must not be blocked based on the active or default * privacy list rules. The active list will be tried first. If none was found then the * default list is going to be used. If no default list was defined for this user then * allow the packet to flow. * * @param packet the packet to analyze if it must be blocked. * @return true if the specified packet must be blocked. */ @Override public boolean canProcess(Packet packet) { PrivacyList list = getActiveList(); if (list != null) { // If a privacy list is active then make sure that the packet is not blocked return !list.shouldBlockPacket(packet); } else { list = getDefaultList(); // There is no active list so check if there exists a default list and make // sure that the packet is not blocked return list == null || !list.shouldBlockPacket(packet); } } @Override public void deliver(Packet packet) throws UnauthorizedException { if (conn != null) { conn.deliver(packet); } streamManager.sentStanza(packet); } @Override public String toString() { return super.toString() + " presence: " + presence; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy