org.jivesoftware.openfire.session.LocalClientSession Maven / Gradle / Ivy
/*
* 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;
}
}