use of org.jivesoftware.openfire.StreamID in project Openfire by igniterealtime.
the class HttpSessionManager method createSession.
private HttpSession createSession(long rid, InetAddress address, HttpConnection connection, Locale language) throws UnauthorizedException {
// Create a ClientSession for this user.
StreamID streamID = SessionManager.getInstance().nextStreamID();
// Send to the server that a new client session has been created
HttpSession session = sessionManager.createClientHttpSession(rid, address, streamID, connection, language);
// Register that the new session is associated with the specified stream ID
sessionMap.put(streamID.getID(), session);
session.addSessionCloseListener(sessionListener);
return session;
}
use of org.jivesoftware.openfire.StreamID in project Openfire by igniterealtime.
the class LocalOutgoingServerSession method attemptSASLexternal.
private static LocalOutgoingServerSession attemptSASLexternal(SocketConnection connection, MXParser xpp, XMPPPacketReader reader, String localDomain, String remoteDomain, String id, StringBuilder openingStream) throws DocumentException, IOException, XmlPullParserException {
final Logger log = LoggerFactory.getLogger(Log.getName() + "[EXTERNAL SASL for: " + localDomain + " to: " + remoteDomain + " (Stream ID: " + id + ")]");
log.debug("Starting EXTERNAL SASL.");
if (doExternalAuthentication(localDomain, connection, reader)) {
log.debug("EXTERNAL SASL was successful.");
// SASL was successful so initiate a new stream
connection.deliverRawText(openingStream.toString());
// Reset the parser
//xpp.resetInput();
// // Reset the parser to use the new secured reader
xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8));
// Skip the opening stream sent by the server
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG; ) {
eventType = xpp.next();
}
// SASL authentication was successful so create new OutgoingServerSession
id = xpp.getAttributeValue("", "id");
StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, new OutgoingServerSocketReader(reader), streamID);
connection.init(session);
// Set the hostname as the address of the session
session.setAddress(new JID(null, remoteDomain, null));
// Set that the session was created using TLS+SASL (no server dialback)
session.usingServerDialback = false;
return session;
} else {
log.debug("EXTERNAL SASL failed.");
return null;
}
}
use of org.jivesoftware.openfire.StreamID in project Openfire by igniterealtime.
the class LocalOutgoingServerSession method createOutgoingSession.
/**
* Establishes a new outgoing session to a remote domain. If the remote domain supports TLS and SASL then the new
* outgoing connection will be secured with TLS and authenticated using SASL. However, if TLS or SASL is not
* supported by the remote domain or if an error occurred while securing or authenticating the connection using SASL
* then server dialback will be used.
*
* @param localDomain the local domain to authenticate with the remote domain.
* @param remoteDomain the remote domain.
* @param port default port to use to establish the connection.
* @return new outgoing session to a remote domain, or null.
*/
private static LocalOutgoingServerSession createOutgoingSession(String localDomain, String remoteDomain, int port) {
final Logger log = LoggerFactory.getLogger(Log.getName() + "[Create outgoing session for: " + localDomain + " to " + remoteDomain + "]");
log.debug("Creating new session...");
// Connect to remote server using XMPP 1.0 (TLS + SASL EXTERNAL or TLS + server dialback or server dialback)
log.debug("Creating plain socket connection to a host that belongs to the remote XMPP domain.");
final Socket socket = SocketUtil.createSocketToXmppDomain(remoteDomain, port);
if (socket == null) {
log.info("Unable to create new session: Cannot create a plain socket connection with any applicable remote host.");
return null;
}
SocketConnection connection = null;
try {
connection = new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false);
log.debug("Send the stream header and wait for response...");
StringBuilder openingStream = new StringBuilder();
openingStream.append("<stream:stream");
openingStream.append(" xmlns:db=\"jabber:server:dialback\"");
openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
openingStream.append(" xmlns=\"jabber:server\"");
// OF-673
openingStream.append(" from=\"").append(localDomain).append("\"");
openingStream.append(" to=\"").append(remoteDomain).append("\"");
openingStream.append(" version=\"1.0\">");
connection.deliverRawText(openingStream.toString());
// Set a read timeout (of 5 seconds) so we don't keep waiting forever
int soTimeout = socket.getSoTimeout();
socket.setSoTimeout(5000);
XMPPPacketReader reader = new XMPPPacketReader();
reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
// Get the answer from the Receiving Server
XmlPullParser xpp = reader.getXPPParser();
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG; ) {
eventType = xpp.next();
}
String serverVersion = xpp.getAttributeValue("", "version");
String id = xpp.getAttributeValue("", "id");
log.debug("Got a response (stream ID: {}, version: {}). Check if the remote server is XMPP 1.0 compliant...", id, serverVersion);
if (serverVersion != null && decodeVersion(serverVersion)[0] >= 1) {
log.debug("The remote server is XMPP 1.0 compliant (or at least reports to be).");
// Restore default timeout
socket.setSoTimeout(soTimeout);
log.debug("Processing stream features of the remote domain...");
Element features = reader.parseDocument().getRootElement();
if (features != null) {
log.debug("Check if both us as well as the remote server have enabled STARTTLS and/or dialback ...");
final boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true);
if (useTLS && features.element("starttls") != null) {
log.debug("Both us and the remote server support the STARTTLS feature. Secure and authenticate the connection with TLS & SASL...");
LocalOutgoingServerSession answer = secureAndAuthenticate(remoteDomain, connection, reader, openingStream, localDomain);
if (answer != null) {
log.debug("Successfully secured/authenticated the connection with TLS/SASL)!");
// Everything went fine so return the secured and
// authenticated connection
log.debug("Successfully created new session!");
return answer;
}
log.debug("Unable to secure and authenticate the connection with TLS & SASL.");
} else if (connection.getTlsPolicy() == Connection.TLSPolicy.required) {
log.debug("I have no StartTLS yet I must TLS");
connection.close();
return null;
} else // Check if we are going to try server dialback (XMPP 1.0)
if (ServerDialback.isEnabled() && features.element("dialback") != null) {
log.debug("Both us and the remote server support the 'dialback' feature. Authenticate the connection with dialback...");
ServerDialback method = new ServerDialback(connection, localDomain);
OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader);
if (method.authenticateDomain(newSocketReader, localDomain, remoteDomain, id)) {
log.debug("Successfully authenticated the connection with dialback!");
StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, newSocketReader, streamID);
connection.init(session);
// Set the hostname as the address of the session
session.setAddress(new JID(null, remoteDomain, null));
log.debug("Successfully created new session!");
return session;
} else {
log.debug("Unable to authenticate the connection with dialback.");
}
}
} else {
log.debug("Error! No data from the remote server (expected a 'feature' element).");
}
} else {
log.debug("The remote server is not XMPP 1.0 compliant.");
}
log.debug("Something went wrong so close the connection and try server dialback over a plain connection");
if (connection.getTlsPolicy() == Connection.TLSPolicy.required) {
log.debug("I have no StartTLS yet I must TLS");
connection.close();
return null;
}
connection.close();
} catch (SSLHandshakeException e) {
// This is a failure as described in RFC3620, section 5.4.3.2 "STARTTLS Failure".
log.info("STARTTLS negotiation failed. Closing connection (without sending any data such as <failure/> or </stream>).", e);
// It is probably (see OF-794) best if we, as the initiating entity, therefor don't send any data either.
if (connection != null) {
connection.forceClose();
}
} catch (Exception e) {
// This might be RFC3620, section 5.4.2.2 "Failure Case" or even an unrelated problem. Handle 'normally'.
log.warn("An exception occurred while creating an encrypted session. Closing connection.", e);
if (connection != null) {
connection.close();
}
}
if (ServerDialback.isEnabled()) {
log.debug("Unable to create a new session. Going to try connecting using server dialback as a fallback.");
// Use server dialback (pre XMPP 1.0) over a plain connection
final LocalOutgoingServerSession outgoingSession = new ServerDialback().createOutgoingSession(localDomain, remoteDomain, port);
if (outgoingSession != null) {
// TODO this success handler behaves differently from a similar success handler above. Shouldn't those be the same?
log.debug("Successfully created new session (using dialback as a fallback)!");
return outgoingSession;
} else {
log.warn("Unable to create a new session: Dialback (as a fallback) failed.");
return null;
}
} else {
log.warn("Unable to create a new session: exhausted all options (not trying dialback as a fallback, as server dialback is disabled by configuration.");
return null;
}
}
use of org.jivesoftware.openfire.StreamID in project Openfire by igniterealtime.
the class HttpSessionManager method createSession.
private HttpSession createSession(HttpConnection connection, Locale language, Duration wait, int hold, boolean isSecure, Duration maxPollingInterval, int maxRequests, Duration maxPause, Duration defaultInactivityTimeout, int majorVersion, int minorVersion) throws UnauthorizedException, UnknownHostException {
// Create a ClientSession for this user.
StreamID streamID = SessionManager.getInstance().nextStreamID();
// Send to the server that a new client session has been created
HttpSession session = sessionManager.createClientHttpSession(streamID, connection, language, wait, hold, isSecure, maxPollingInterval, maxRequests, maxPause, defaultInactivityTimeout, majorVersion, minorVersion);
// Register that the new session is associated with the specified stream ID
sessionMap.put(streamID.getID(), session);
SessionEventDispatcher.addListener(sessionListener);
return session;
}
use of org.jivesoftware.openfire.StreamID in project Openfire by igniterealtime.
the class ConsistencyChecks method generateReportForSessionManagerIncomingServerSessions.
/**
* Verifies that #incomingServerSessionsCache, #localIncomingServerSessions and #incomingServerSessionsByClusterNode
* of {@link org.jivesoftware.openfire.SessionManager} are in a consistent state.
* <p>
* Note that this operation can be costly in terms of resource usage. Use with caution in large / busy systems.
* <p>
* The returned multi-map can contain up to four keys: info, fail, pass, data. All entry values are a human readable
* description of a checked characteristic. When the state is consistent, no 'fail' entries will be returned.
*
* @param incomingServerSessionsCache The cache that is used to share data across cluster nodes
* @param localIncomingServerSessions The data structure that keeps track of what data was added to the cache by the local cluster node.
* @param incomingServerSessionsByClusterNode The data structure that keeps track of what data was added to the cache by the remote cluster nodes.
* @return A consistency state report.
*/
public static Multimap<String, String> generateReportForSessionManagerIncomingServerSessions(@Nonnull final Cache<StreamID, IncomingServerSessionInfo> incomingServerSessionsCache, @Nonnull final Collection<LocalIncomingServerSession> localIncomingServerSessions, @Nonnull final Map<NodeID, Set<StreamID>> incomingServerSessionsByClusterNode) {
final Set<NodeID> clusterNodeIDs = ClusterManager.getNodesInfo().stream().map(ClusterNodeInfo::getNodeID).collect(Collectors.toSet());
// Take snapshots of all data structures at as much the same time as possible.
final ConcurrentMap<StreamID, IncomingServerSessionInfo> cache = new ConcurrentHashMap<>(incomingServerSessionsCache);
final List<StreamID> localIncomingServerSessionsStreamIDs = localIncomingServerSessions.stream().map(LocalIncomingServerSession::getStreamID).collect(Collectors.toList());
final List<StreamID> remoteIncomingServerSessions = incomingServerSessionsByClusterNode.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
final List<String> remoteIncomingServerSessionsWithNodeId = new ArrayList<>();
for (Map.Entry<NodeID, Set<StreamID>> entry : incomingServerSessionsByClusterNode.entrySet()) {
for (StreamID item : entry.getValue()) {
remoteIncomingServerSessionsWithNodeId.add(item + " (" + entry.getKey() + ")");
}
}
// Duplicates detection
final Set<StreamID> localIncomingServerSessionsDuplicates = CollectionUtils.findDuplicates(localIncomingServerSessionsStreamIDs);
final Set<StreamID> remoteIncomingServerSessionsDuplicates = CollectionUtils.findDuplicates(remoteIncomingServerSessions);
final Set<StreamID> incomingServerSessionsBothLocalAndRemote = CollectionUtils.findDuplicates(localIncomingServerSessionsStreamIDs, remoteIncomingServerSessions);
// Detection of other inconsistencies
final Set<StreamID> nonLocallyStoredCachedIncomingServerSessions = cache.keySet().stream().filter(v -> !localIncomingServerSessionsStreamIDs.contains(v)).filter(v -> !remoteIncomingServerSessions.contains(v)).collect(Collectors.toSet());
final Set<StreamID> nonCachedLocalIncomingServerSessions = localIncomingServerSessionsStreamIDs.stream().filter(v -> !cache.containsKey(v)).collect(Collectors.toSet());
final Set<StreamID> nonCachedRemoteIncomingServerSessions = remoteIncomingServerSessions.stream().filter(v -> !cache.containsKey(v)).collect(Collectors.toSet());
// Generate report
final Multimap<String, String> result = HashMultimap.create();
result.put("info", String.format("The cache named %s is used to share data in the cluster, which contains %d incoming server sessions.", incomingServerSessionsCache.getName(), cache.size()));
result.put("info", String.format("SessionManager's TODO response is used to track 'local' data to be restored after a cache switch-over. It tracks %d incoming server sessions.", localIncomingServerSessionsStreamIDs.size()));
result.put("info", String.format("The field incomingServerSessionsByClusterNode is used to track data in the cache from every other cluster node. It contains %d routes for %d cluster nodes.", incomingServerSessionsByClusterNode.values().stream().reduce(0, (subtotal, values) -> subtotal + values.size(), Integer::sum), incomingServerSessionsByClusterNode.size()));
result.put("data", String.format("%s contains these entries (these are shared in the cluster):\n%s", incomingServerSessionsCache.getName(), cache.keySet().stream().map(StreamID::getID).collect(Collectors.joining("\n"))));
result.put("data", String.format("SessionManager's localSessionManager contains these entries (these represent 'local' data):\n%s", localIncomingServerSessionsStreamIDs.stream().map(StreamID::getID).collect(Collectors.joining("\n"))));
result.put("data", String.format("incomingServerSessionsByClusterNode contains these entries (these represent 'remote' data):\n%s", String.join("\n", remoteIncomingServerSessionsWithNodeId)));
if (!incomingServerSessionsByClusterNode.containsKey(XMPPServer.getInstance().getNodeID())) {
result.put("pass", "incomingServerSessionsByClusterNode does not track data for the local cluster node.");
} else {
result.put("fail", "incomingServerSessionsByClusterNode tracks data for the local cluster node.");
}
if (clusterNodeIDs.containsAll(incomingServerSessionsByClusterNode.keySet())) {
result.put("pass", "incomingServerSessionsByClusterNode tracks data for cluster nodes that are recognized in the cluster.");
} else {
result.put("fail", String.format("incomingServerSessionsByClusterNode tracks data for cluster nodes that are not recognized. All cluster nodeIDs as recognized: %s All cluster nodeIDs for which data is tracked: %s.", clusterNodeIDs.stream().map(NodeID::toString).collect(Collectors.joining(", ")), incomingServerSessionsByClusterNode.keySet().stream().map(NodeID::toString).collect(Collectors.joining(", "))));
}
if (localIncomingServerSessionsDuplicates.isEmpty()) {
result.put("pass", "There is no overlap in local incoming server sessions (they are all unique values).");
} else {
result.put("fail", String.format("There is overlap in local incoming server sessions (they are not all unique values). These %d values are duplicated: %s", localIncomingServerSessionsDuplicates.size(), localIncomingServerSessionsDuplicates.stream().map(StreamID::getID).collect(Collectors.joining(", "))));
}
if (remoteIncomingServerSessionsDuplicates.isEmpty()) {
result.put("pass", "There is no overlap in incomingServerSessionsByClusterNode (they are all unique values).");
} else {
result.put("fail", String.format("There is overlap in incomingServerSessionsByClusterNode (they are not all unique values). These %d values are duplicated: %s", remoteIncomingServerSessionsDuplicates.size(), remoteIncomingServerSessionsDuplicates.stream().map(StreamID::getID).collect(Collectors.joining(", "))));
}
if (incomingServerSessionsBothLocalAndRemote.isEmpty()) {
result.put("pass", "There are no elements that are both 'remote' (in incomingServerSessionsByClusterNode) as well as 'local' (in SessionManager's localSessionManager).");
} else {
result.put("fail", String.format("There are %d elements that are both 'remote' (in incomingServerSessionsByClusterNode) as well as 'local' (in SessionManager's localSessionManager): %s", incomingServerSessionsBothLocalAndRemote.size(), incomingServerSessionsBothLocalAndRemote.stream().map(StreamID::getID).collect(Collectors.joining(", "))));
}
if (nonCachedLocalIncomingServerSessions.isEmpty()) {
result.put("pass", String.format("All elements in SessionManager's localSessionManager exist in %s.", incomingServerSessionsCache.getName()));
} else {
result.put("fail", String.format("Not all elements in SessionManager's localSessionManager exist in %s. These %d entries do not: %s", incomingServerSessionsCache.getName(), nonCachedLocalIncomingServerSessions.size(), nonCachedLocalIncomingServerSessions.stream().map(StreamID::getID).collect(Collectors.joining(", "))));
}
if (nonCachedRemoteIncomingServerSessions.isEmpty()) {
result.put("pass", String.format("All elements inincomingServerSessionsByClusterNode exist in %s.", incomingServerSessionsCache.getName()));
} else {
result.put("fail", String.format("Not all elements in incomingServerSessionsByClusterNode exist in %s. These %d entries do not: %s", incomingServerSessionsCache.getName(), nonCachedRemoteIncomingServerSessions.size(), nonCachedRemoteIncomingServerSessions.stream().map(StreamID::getID).collect(Collectors.joining(", "))));
}
if (nonLocallyStoredCachedIncomingServerSessions.isEmpty()) {
result.put("pass", String.format("All cache entries of %s exist in incomingServerSessionsByClusterNode and/or SessionManager's localSessionManager.", incomingServerSessionsCache.getName()));
} else {
result.put("fail", String.format("Not all cache entries of %s exist in incomingServerSessionsByClusterNode and/or SessionManager's localSessionManager. These %d entries do not: %s", incomingServerSessionsCache.getName(), nonLocallyStoredCachedIncomingServerSessions.size(), nonLocallyStoredCachedIncomingServerSessions.stream().map(StreamID::getID).collect(Collectors.joining(", "))));
}
return result;
}
Aggregations