org.jivesoftware.openfire.session.LocalConnectionMultiplexerSession Maven / Gradle / Ivy
/*
* Copyright (C) 2004-2009 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.util.Collection;
import java.util.Locale;
import org.dom4j.Element;
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.AuthFactory;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.multiplex.ConnectionMultiplexerManager;
import org.jivesoftware.openfire.multiplex.MultiplexerPacketDeliverer;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.spi.ConnectionConfiguration;
import org.jivesoftware.openfire.spi.ConnectionManagerImpl;
import org.jivesoftware.openfire.spi.ConnectionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import org.xmpp.packet.StreamError;
/**
* Represents a session between the server and a connection manager.
*
* Each Connection Manager has its own domain. Each connection from the same connection manager
* uses a different resource. Unlike any other session, connection manager sessions are not
* present in the routing table. This means that connection managers are not reachable entities.
* In other words, entities cannot send packets to connection managers but clients being hosted
* by them. The main reason behind this design decision is that connection managers are private
* components of the server so they can only be contacted by the server. Connection Manager
* sessions are present in {@link SessionManager} but not in {@link org.jivesoftware.openfire.RoutingTable}. Use
* {@link SessionManager#getConnectionMultiplexerSessions(String)} to get all sessions or
* {@link org.jivesoftware.openfire.multiplex.ConnectionMultiplexerManager#getMultiplexerSession(String)}
* to get a random session to a given connection manager.
*
* @author Gaston Dombiak
*/
public class LocalConnectionMultiplexerSession extends LocalSession implements ConnectionMultiplexerSession {
private static final Logger Log = LoggerFactory.getLogger(LocalConnectionMultiplexerSession.class);
public static LocalConnectionMultiplexerSession createSession(String serverName, XmlPullParser xpp, Connection connection)
throws XmlPullParserException {
String domain = xpp.getAttributeValue("", "to");
Log.debug("LocalConnectionMultiplexerSession: [ConMng] Starting registration of new connection manager for domain: " + domain);
// Default answer header in case of an error
StringBuilder sb = new StringBuilder();
sb.append("");
sb.append("");
// Check that a domain was provided in the stream header
if (domain == null) {
Log.debug("LocalConnectionMultiplexerSession: [ConMng] Domain not specified in stanza: " + xpp.getText());
// Include the bad-format in the response
StreamError error = new StreamError(StreamError.Condition.bad_format);
sb.append(error.toXML());
connection.deliverRawText(sb.toString());
// Close the underlying connection
connection.close();
return null;
}
// Get the requested domain
JID address = new JID(domain);
// Check that a secret key was configured in the server
String secretKey = ConnectionMultiplexerManager.getDefaultSecret();
if (secretKey == null) {
Log.debug("LocalConnectionMultiplexerSession: [ConMng] A shared secret for connection manager was not found.");
// Include the internal-server-error in the response
StreamError error = new StreamError(StreamError.Condition.internal_server_error);
sb.append(error.toXML());
connection.deliverRawText(sb.toString());
// Close the underlying connection
connection.close();
return null;
}
// Check that the requested subdomain is not already in use
if (SessionManager.getInstance().getConnectionMultiplexerSession(address) != null) {
Log.debug("LocalConnectionMultiplexerSession: [ConMng] Another connection manager is already using domain: " + domain);
// Domain already occupied so return a conflict error and close the connection
// Include the conflict error in the response
StreamError error = new StreamError(StreamError.Condition.conflict);
sb.append(error.toXML());
connection.deliverRawText(sb.toString());
// Close the underlying connection
connection.close();
return null;
}
// Indicate the TLS policy to use for this connection
connection.setTlsPolicy( connection.getConfiguration().getTlsPolicy() );
// Indicate the compression policy to use for this connection
connection.setCompressionPolicy( connection.getConfiguration().getCompressionPolicy() );
// Set the connection manager domain to use delivering a packet fails
((MultiplexerPacketDeliverer) connection.getPacketDeliverer())
.setConnectionManagerDomain(address.getDomain());
// Create a ConnectionMultiplexerSession for the new session originated
// from the connection manager
LocalConnectionMultiplexerSession session =
SessionManager.getInstance().createMultiplexerSession(connection, address);
// Set the address of the new session
session.setAddress(address);
connection.init(session);
try {
Log.debug("LocalConnectionMultiplexerSession: [ConMng] Send stream header with ID: " + session.getStreamID() +
" for connection manager with domain: " +
domain);
// Build the start packet response
sb = new StringBuilder();
sb.append("");
sb.append("");
connection.deliverRawText(sb.toString());
// 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 Stream features
String specificFeatures = session.getAvailableStreamFeatures();
if (specificFeatures != null) {
sb.append(specificFeatures);
}
sb.append(" ");
connection.deliverRawText(sb.toString());
return session;
}
catch (Exception e) {
Log.error("An error occured while creating a Connection Manager Session", e);
// Close the underlying connection
connection.close();
return null;
}
}
public LocalConnectionMultiplexerSession(String serverName, Connection connection, StreamID streamID) {
super(serverName, connection, streamID, Locale.getDefault());
}
@Override
public String getAvailableStreamFeatures() {
if (conn.getTlsPolicy() == Connection.TLSPolicy.required && !conn.isSecure()) {
return null;
}
// Include Stream Compression Mechanism
if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled &&
!conn.isCompressed()) {
return "zlib ";
}
return null;
}
/**
* Authenticates the connection manager. Shared secret is validated with the one provided
* by the connection manager. If everything went fine then the session will have a status
* of "authenticated" and the connection manager will receive the client configuration
* options.
*
* @param digest the digest provided by the connection manager with the handshake stanza.
* @return true if the connection manager was sucessfully authenticated.
*/
public boolean authenticate(String digest) {
// Perform authentication. Wait for the handshake (with the secret key)
String anticipatedDigest = AuthFactory.createDigest(getStreamID().getID(),
ConnectionMultiplexerManager.getDefaultSecret());
// Check that the provided handshake (secret key + sessionID) is correct
if (!anticipatedDigest.equalsIgnoreCase(digest)) {
Log.debug("LocalConnectionMultiplexerSession: [ConMng] Incorrect handshake for connection manager with domain: " +
getAddress().getDomain());
// The credentials supplied by the initiator are not valid (answer an error
// and close the connection)
conn.deliverRawText(new StreamError(StreamError.Condition.not_authorized).toXML());
// Close the underlying connection
conn.close();
return false;
}
else {
// Component has authenticated fine
setStatus(STATUS_AUTHENTICATED);
// Send empty handshake element to acknowledge success
conn.deliverRawText(" ");
Log.debug("LocalConnectionMultiplexerSession: [ConMng] Connection manager was AUTHENTICATED with domain: " + getAddress());
sendClientOptions();
return true;
}
}
/**
* Send to the Connection Manager the connection options available for clients. The info
* to send includes:
*
* - if TLS is available, optional or required
*
- SASL mechanisms available before TLS is negotiated
*
- if compression is available
*
- if Non-SASL authentication is available
*
- if In-Band Registration is available
*
mechanisms = SASLAuthentication.getSupportedMechanisms();
if (!mechanisms.isEmpty()) {
Element sasl = child.addElement("mechanisms", "urn:ietf:params:xml:ns:xmpp-sasl");
for (String mechanism : mechanisms) {
sasl.addElement("mechanism").setText(mechanism);
}
}
// Add info about Stream Compression
if (configuration.getCompressionPolicy() == Connection.CompressionPolicy.optional) {
Element comp = child.addElement("compression", "http://jabber.org/features/compress");
comp.addElement("method").setText("zlib");
}
// Add info about Non-SASL authentication
if (XMPPServer.getInstance().getIQRouter().supports("jabber:iq:auth")) {
child.addElement("auth", "http://jabber.org/features/iq-auth");
}
// Add info about In-Band Registration
if (XMPPServer.getInstance().getIQRegisterHandler().isInbandRegEnabled()) {
child.addElement("register", "http://jabber.org/features/iq-register");
}
// Send the options
process(options);
}
@Override
boolean canProcess(Packet packet) {
return true;
}
@Override
void deliver(Packet packet) throws UnauthorizedException {
if (!conn.isClosed()) {
conn.deliver(packet);
}
}
}