use of org.jivesoftware.openfire.session.IncomingServerSession in project Openfire by igniterealtime.
the class ClusterListener method cleanupNode.
/**
* Executes close logic for each session hosted in the remote node that is
* no longer available. This logic is similar to the close listeners used by
* the {@link SessionManager}.<p>
*
* If the node that went down performed its own clean up logic then the other
* cluster nodes will have the correct state. That means that this method
* will not find any sessions to remove.<p>
*
* If this operation is too big and we are still in a cluster then we can
* distribute the work in the cluster to go faster.
*
* @param key the key that identifies the node that is no longer available.
*/
private void cleanupNode(NodeID key) {
// TODO Fork in another process and even ask other nodes to process work
RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable();
RemoteSessionLocator sessionLocator = XMPPServer.getInstance().getRemoteSessionLocator();
SessionManager manager = XMPPServer.getInstance().getSessionManager();
// TODO Consider removing each cached entry once processed instead of all at the end. Could be more error-prove.
Set<String> registeredUsers = lookupJIDList(key, C2SCache.getName());
if (!registeredUsers.isEmpty()) {
for (String fullJID : new ArrayList<String>(registeredUsers)) {
JID offlineJID = new JID(fullJID);
manager.removeSession(null, offlineJID, false, true);
}
}
Set<String> anonymousUsers = lookupJIDList(key, anonymousC2SCache.getName());
if (!anonymousUsers.isEmpty()) {
for (String fullJID : new ArrayList<String>(anonymousUsers)) {
JID offlineJID = new JID(fullJID);
manager.removeSession(null, offlineJID, true, true);
}
}
// Remove outgoing server sessions hosted in node that left the cluster
Set<String> remoteServers = lookupJIDList(key, S2SCache.getName());
if (!remoteServers.isEmpty()) {
for (String fullJID : new ArrayList<String>(remoteServers)) {
JID serverJID = new JID(fullJID);
routingTable.removeServerRoute(serverJID);
}
}
Set<String> components = lookupJIDList(key, componentsCache.getName());
if (!components.isEmpty()) {
for (String address : new ArrayList<String>(components)) {
Lock lock = CacheFactory.getLock(address, componentsCache);
try {
lock.lock();
Set<NodeID> nodes = (Set<NodeID>) componentsCache.get(address);
if (nodes != null) {
nodes.remove(key);
if (nodes.isEmpty()) {
componentsCache.remove(address);
} else {
componentsCache.put(address, nodes);
}
}
} finally {
lock.unlock();
}
}
}
Set<String> componentSessions = lookupJIDList(key, componentSessionsCache.getName());
if (!componentSessions.isEmpty()) {
for (String domain : new ArrayList<String>(componentSessions)) {
componentSessionsCache.remove(domain);
// Registered subdomains of external component will be removed
// by the clean up of the component cache
}
}
Set<String> multiplexers = lookupJIDList(key, multiplexerSessionsCache.getName());
if (!multiplexers.isEmpty()) {
for (String fullJID : new ArrayList<String>(multiplexers)) {
multiplexerSessionsCache.remove(fullJID);
// c2s connections connected to node that went down will be cleaned up
// by the c2s logic above. If the CM went down and the node is up then
// connections will be cleaned up as usual
}
}
Set<String> incomingSessions = lookupJIDList(key, incomingServerSessionsCache.getName());
if (!incomingSessions.isEmpty()) {
for (String streamIDValue : new ArrayList<>(incomingSessions)) {
StreamID streamID = BasicStreamIDFactory.createStreamID(streamIDValue);
IncomingServerSession session = sessionLocator.getIncomingServerSession(key.toByteArray(), streamID);
// Remove all the hostnames that were registered for this server session
for (String hostname : session.getValidatedDomains()) {
manager.unregisterIncomingServerSession(hostname, session);
}
}
}
nodeSessions.remove(key);
// TODO Make sure that routing table has no entry referring to node that is gone
}
use of org.jivesoftware.openfire.session.IncomingServerSession in project Openfire by igniterealtime.
the class ClusterListener method cleanupNode.
/**
* Executes close logic for each session hosted in the remote node that is
* no longer available. This logic is similar to the close listeners used by
* the {@link SessionManager}.<p>
*
* If the node that went down performed its own clean up logic then the other
* cluster nodes will have the correct state. That means that this method
* will not find any sessions to remove.<p>
*
* If this operation is too big and we are still in a cluster then we can
* distribute the work in the cluster to go faster.
*
* @param key the key that identifies the node that is no longer available.
*/
private void cleanupNode(NodeID key) {
// TODO Fork in another process and even ask other nodes to process work
RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable();
RemoteSessionLocator sessionLocator = XMPPServer.getInstance().getRemoteSessionLocator();
SessionManager manager = XMPPServer.getInstance().getSessionManager();
// TODO Consider removing each cached entry once processed instead of all at the end. Could be more error-prove.
Set<String> registeredUsers = lookupJIDList(key, C2SCache.getName());
if (!registeredUsers.isEmpty()) {
for (String fullJID : new ArrayList<String>(registeredUsers)) {
JID offlineJID = new JID(fullJID);
manager.removeSession(null, offlineJID, false, true);
}
}
Set<String> anonymousUsers = lookupJIDList(key, anonymousC2SCache.getName());
if (!anonymousUsers.isEmpty()) {
for (String fullJID : new ArrayList<String>(anonymousUsers)) {
JID offlineJID = new JID(fullJID);
manager.removeSession(null, offlineJID, true, true);
}
}
// Remove outgoing server sessions hosted in node that left the cluster
Set<String> remoteServers = lookupJIDList(key, S2SCache.getName());
if (!remoteServers.isEmpty()) {
for (String fullJID : new ArrayList<String>(remoteServers)) {
JID serverJID = new JID(fullJID);
routingTable.removeServerRoute(serverJID);
}
}
Set<String> components = lookupJIDList(key, componentsCache.getName());
if (!components.isEmpty()) {
for (String address : new ArrayList<String>(components)) {
Lock lock = CacheFactory.getLock(address, componentsCache);
try {
lock.lock();
Set<NodeID> nodes = (Set<NodeID>) componentsCache.get(address);
if (nodes != null) {
nodes.remove(key);
if (nodes.isEmpty()) {
componentsCache.remove(address);
} else {
componentsCache.put(address, nodes);
}
}
} finally {
lock.unlock();
}
}
}
Set<String> sessionInfo = lookupJIDList(key, sessionInfoCache.getName());
if (!sessionInfo.isEmpty()) {
for (String session : new ArrayList<String>(sessionInfo)) {
sessionInfoCache.remove(session);
// Registered sessions will be removed
// by the clean up of the session info cache
}
}
Set<String> componentSessions = lookupJIDList(key, componentSessionsCache.getName());
if (!componentSessions.isEmpty()) {
for (String domain : new ArrayList<String>(componentSessions)) {
componentSessionsCache.remove(domain);
// Registered subdomains of external component will be removed
// by the clean up of the component cache
}
}
Set<String> multiplexers = lookupJIDList(key, multiplexerSessionsCache.getName());
if (!multiplexers.isEmpty()) {
for (String fullJID : new ArrayList<String>(multiplexers)) {
multiplexerSessionsCache.remove(fullJID);
// c2s connections connected to node that went down will be cleaned up
// by the c2s logic above. If the CM went down and the node is up then
// connections will be cleaned up as usual
}
}
Set<String> incomingSessions = lookupJIDList(key, incomingServerSessionsCache.getName());
if (!incomingSessions.isEmpty()) {
for (String streamIDValue : new ArrayList<>(incomingSessions)) {
StreamID streamID = BasicStreamIDFactory.createStreamID(streamIDValue);
IncomingServerSession session = sessionLocator.getIncomingServerSession(key.toByteArray(), streamID);
// Remove all the hostnames that were registered for this server session
for (String hostname : session.getValidatedDomains()) {
manager.unregisterIncomingServerSession(hostname, session);
}
}
}
nodeSessions.remove(key);
// TODO Make sure that routing table has no entry referring to node that is gone
}
use of org.jivesoftware.openfire.session.IncomingServerSession in project Openfire by igniterealtime.
the class SASLAuthentication method handle.
/**
* Handles the SASL authentication packet. The entity may be sending an initial
* authentication request or a response to a challenge made by the server. The returned
* value indicates whether the authentication has finished either successfully or not or
* if the entity is expected to send a response to a challenge.
*
* @param session the session that is authenticating with the server.
* @param doc the stanza sent by the authenticating entity.
* @return value that indicates whether the authentication has finished either successfully
* or not or if the entity is expected to send a response to a challenge.
*/
public static Status handle(LocalSession session, Element doc) {
try {
if (!doc.getNamespaceURI().equals(SASL_NAMESPACE)) {
throw new IllegalStateException("Unexpected data received while negotiating SASL authentication. Name of the offending root element: " + doc.getName() + " Namespace: " + doc.getNamespaceURI());
}
switch(ElementType.valueOfCaseInsensitive(doc.getName())) {
case ABORT:
throw new SaslFailureException(Failure.ABORTED);
case AUTH:
if (doc.attributeValue("mechanism") == null) {
throw new SaslFailureException(Failure.INVALID_MECHANISM, "Peer did not specify a mechanism.");
}
final String mechanismName = doc.attributeValue("mechanism").toUpperCase();
// See if the mechanism is supported by configuration as well as by implementation.
if (!mechanisms.contains(mechanismName)) {
throw new SaslFailureException(Failure.INVALID_MECHANISM, "The configuration of Openfire does not contain or allow the mechanism.");
}
// OF-477: The SASL implementation requires the fully qualified host name (not the domain name!) of this server,
// yet, most of the XMPP implemenations of DIGEST-MD5 will actually use the domain name. To account for that,
// here, we'll use the host name, unless DIGEST-MD5 is being negotiated!
final XMPPServerInfo serverInfo = XMPPServer.getInstance().getServerInfo();
final String serverName = (mechanismName.equals("DIGEST-MD5") ? serverInfo.getXMPPDomain() : serverInfo.getHostname());
// Construct the configuration properties
final Map<String, Object> props = new HashMap<>();
props.put(LocalSession.class.getCanonicalName(), session);
props.put(Sasl.POLICY_NOANONYMOUS, Boolean.toString(!AnonymousSaslServer.ENABLED.getValue()));
props.put("com.sun.security.sasl.digest.realm", serverInfo.getXMPPDomain());
SaslServer saslServer = Sasl.createSaslServer(mechanismName, "xmpp", serverName, props, new XMPPCallbackHandler());
if (saslServer == null) {
throw new SaslFailureException(Failure.INVALID_MECHANISM, "There is no provider that can provide a SASL server for the desired mechanism and properties.");
}
session.setSessionData("SaslServer", saslServer);
if (mechanismName.equals("DIGEST-MD5")) {
// RFC2831 (DIGEST-MD5) says the client MAY provide data in the initial response. Java SASL does
// not (currently) support this and throws an exception. For XMPP, such data violates
// the RFC, so we just strip any initial token.
doc.setText("");
}
// intended fall-through
case RESPONSE:
saslServer = (SaslServer) session.getSessionData("SaslServer");
if (saslServer == null) {
// Client sends response without a preceding auth?
throw new IllegalStateException("A SaslServer instance was not initialized and/or stored on the session.");
}
// Decode any data that is provided in the client response.
final String encoded = doc.getTextTrim();
final byte[] decoded;
if (// java SaslServer cannot handle a null.
encoded == null || encoded.isEmpty() || encoded.equals("=")) {
decoded = new byte[0];
} else {
// TODO: We shouldn't depend on regex-based validation. Instead, use a proper decoder implementation and handle any exceptions that it throws.
if (!BASE64_ENCODED.matcher(encoded).matches()) {
throw new SaslFailureException(Failure.INCORRECT_ENCODING);
}
decoded = StringUtils.decodeBase64(encoded);
}
// Process client response.
// Either a challenge or success data.
final byte[] challenge = saslServer.evaluateResponse(decoded);
if (!saslServer.isComplete()) {
// Not complete: client is challenged for additional steps.
sendChallenge(session, challenge);
return Status.needResponse;
}
// Success!
if (session instanceof IncomingServerSession) {
// Flag that indicates if certificates of the remote server should be validated.
final boolean verify = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true);
if (verify) {
if (verifyCertificates(session.getConnection().getPeerCertificates(), saslServer.getAuthorizationID(), true)) {
((LocalIncomingServerSession) session).tlsAuth();
} else {
throw new SaslFailureException(Failure.NOT_AUTHORIZED, "Server-to-Server certificate verification failed.");
}
}
}
authenticationSuccessful(session, saslServer.getAuthorizationID(), challenge);
session.removeSessionData("SaslServer");
return Status.authenticated;
default:
throw new IllegalStateException("Unexpected data received while negotiating SASL authentication. Name of the offending root element: " + doc.getName() + " Namespace: " + doc.getNamespaceURI());
}
} catch (SaslException ex) {
Log.debug("SASL negotiation failed for session: {}", session, ex);
final Failure failure;
if (ex instanceof SaslFailureException && ((SaslFailureException) ex).getFailure() != null) {
failure = ((SaslFailureException) ex).getFailure();
} else {
failure = Failure.NOT_AUTHORIZED;
}
authenticationFailed(session, failure);
session.removeSessionData("SaslServer");
return Status.failed;
} catch (Exception ex) {
Log.warn("An unexpected exception occurred during SASL negotiation. Affected session: {}", session, ex);
authenticationFailed(session, Failure.NOT_AUTHORIZED);
session.removeSessionData("SaslServer");
return Status.failed;
}
}
use of org.jivesoftware.openfire.session.IncomingServerSession in project Openfire by igniterealtime.
the class SASLAuthentication method authenticationSuccessful.
private static void authenticationSuccessful(LocalSession session, String username, byte[] successData) {
if (username != null && LockOutManager.getInstance().isAccountDisabled(username)) {
// Interception! This person is locked out, fail instead!
LockOutManager.getInstance().recordFailedLogin(username);
authenticationFailed(session, Failure.ACCOUNT_DISABLED);
return;
}
sendElement(session, "success", successData);
// We only support SASL for c2s
if (session instanceof ClientSession) {
final AuthToken authToken;
if (username == null) {
// AuthzId is null, which indicates that authentication was anonymous.
authToken = AuthToken.generateAnonymousToken();
} else {
authToken = AuthToken.generateUserToken(username);
}
((LocalClientSession) session).setAuthToken(authToken);
} else if (session instanceof IncomingServerSession) {
String hostname = username;
// Add the validated domain as a valid domain. The remote server can
// now send packets from this address
((LocalIncomingServerSession) session).addValidatedDomain(hostname);
Log.info("Inbound Server {} authenticated (via TLS)", username);
}
}
use of org.jivesoftware.openfire.session.IncomingServerSession in project Openfire by igniterealtime.
the class SocketConnection method startTLS.
public void startTLS(boolean clientMode, boolean directTLS) throws IOException {
if (!secure) {
secure = true;
// Prepare for TLS
final ClientAuth clientAuth;
if (session instanceof IncomingServerSession) {
clientAuth = ClientAuth.needed;
} else {
clientAuth = ClientAuth.wanted;
}
tlsStreamHandler = new TLSStreamHandler(socket, getConfiguration(), clientMode);
if (!clientMode && !directTLS) {
// Indicate the client that the server is ready to negotiate TLS
deliverRawText("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
}
// Start handshake
tlsStreamHandler.start();
// Use new wrapped writers
writer = new BufferedWriter(new OutputStreamWriter(tlsStreamHandler.getOutputStream(), StandardCharsets.UTF_8));
xmlSerializer = new XMLSocketWriter(writer, this);
}
}
Aggregations