private static LocalOutgoingServerSession attemptDialbackOverTLS( Connection connection, XMPPPacketReader reader, String domain, String hostname, String id) { final Logger log = LoggerFactory.getLogger( LocalOutgoingServerSession.class.getName() + "['" + hostname + "']"); if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) { log.debug("Trying to connecting using dialback over TLS."); ServerDialback method = new ServerDialback(connection, domain); OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader); if (method.authenticateDomain(newSocketReader, domain, hostname, id)) { log.debug("Dialback over TLS was successful."); StreamID streamID = new BasicStreamIDFactory().createStreamID(id); LocalOutgoingServerSession session = new LocalOutgoingServerSession(domain, connection, newSocketReader, streamID); connection.init(session); // Set the hostname as the address of the session session.setAddress(new JID(null, hostname, null)); return session; } else { log.debug("Dialback over TLS failed"); return null; } } else { log.debug("Skipping server dialback attempt as it has been disabled by local configuration."); return null; } }
/** * Returns true if the request of a new domain was valid. Sessions may receive subsequent domain * validation request. If the validation of the new domain fails then the session and the * underlying TCP connection will be closed. * * <p>For optimization reasons, the same session may be servicing several domains of a remote * server. * * @param dbResult the DOM stanza requesting the domain validation. * @return true if the requested domain was valid. */ public boolean validateSubsequentDomain(Element dbResult) { ServerDialback method = new ServerDialback(getConnection(), getServerName()); if (method.validateRemoteDomain(dbResult, getStreamID())) { // Add the validated domain as a valid domain addValidatedDomain(dbResult.attributeValue("from")); return true; } return false; }
@Override public boolean authenticateSubdomain(String domain, String hostname) { if (!usingServerDialback) { // Using SASL so just assume that the domain was validated // (note: this may not be correct) addAuthenticatedDomain(domain); addHostname(hostname); return true; } ServerDialback method = new ServerDialback(getConnection(), domain); if (method.authenticateDomain(socketReader, domain, hostname, getStreamID().getID())) { // Add the validated domain as an authenticated domain addAuthenticatedDomain(domain); addHostname(hostname); return true; } return false; }
@Override public String getAvailableStreamFeatures() { StringBuilder sb = new StringBuilder(); // Include Stream Compression Mechanism if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled && !conn.isCompressed()) { sb.append( "<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>"); } // Offer server dialback if using self-signed certificates and no authentication has been done // yet boolean usingSelfSigned; final Certificate[] chain = conn.getLocalCertificates(); if (chain == null || chain.length == 0) { usingSelfSigned = true; } else { try { usingSelfSigned = CertificateManager.isSelfSignedCertificate( SSLConfig.getKeyStore(), (X509Certificate) chain[0]); } catch (KeyStoreException ex) { Log.warn( "Exception occurred while trying to determine whether local certificate is self-signed. Proceeding as if it is.", ex); usingSelfSigned = true; } catch (IOException ex) { Log.warn( "Exception occurred while trying to determine whether local certificate is self-signed. Proceeding as if it is.", ex); usingSelfSigned = true; } } if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) { sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"/>"); } return sb.toString(); }
/** * Creates a new session that will receive packets. The new session will be authenticated before * being returned. If the authentication process fails then the answer will be <tt>null</tt>. * * <p> * * @param serverName hostname of this server. * @param reader reader on the new established connection with the remote server. * @param connection the new established connection with the remote server. * @return a new session that will receive packets or null if a problem occured while * authenticating the remote server or when acting as the Authoritative Server during a Server * Dialback authentication process. * @throws org.xmlpull.v1.XmlPullParserException if an error occurs while parsing the XML. * @throws java.io.IOException if an input/output error occurs while using the connection. */ public static LocalIncomingServerSession createSession( String serverName, XMPPPacketReader reader, SocketConnection connection) throws XmlPullParserException, IOException { XmlPullParser xpp = reader.getXPPParser(); String version = xpp.getAttributeValue("", "version"); int[] serverVersion = version != null ? decodeVersion(version) : new int[] {0, 0}; try { // Get the stream ID for the new session StreamID streamID = SessionManager.getInstance().nextStreamID(); // Create a server Session for the remote server LocalIncomingServerSession session = SessionManager.getInstance().createIncomingServerSession(connection, streamID); // Send the stream header 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\""); openingStream.append(" from=\"").append(serverName).append("\""); openingStream.append(" id=\"").append(streamID).append("\""); openingStream.append(" version=\"1.0\">"); connection.deliverRawText(openingStream.toString()); if (serverVersion[0] >= 1) { // Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection // (and server dialback) // Indicate the TLS policy to use for this connection Connection.TLSPolicy tlsPolicy = ServerDialback.isEnabled() ? Connection.TLSPolicy.optional : Connection.TLSPolicy.required; boolean hasCertificates = false; try { hasCertificates = SSLConfig.getKeyStore().size() > 0; } catch (Exception e) { Log.error(e.getMessage(), e); } if (Connection.TLSPolicy.required == tlsPolicy && !hasCertificates) { Log.error( "Server session rejected. TLS is required but no certificates " + "were created."); return null; } connection.setTlsPolicy(hasCertificates ? tlsPolicy : Connection.TLSPolicy.disabled); } // Indicate the compression policy to use for this connection String policyName = JiveGlobals.getProperty( "xmpp.server.compression.policy", Connection.CompressionPolicy.disabled.toString()); Connection.CompressionPolicy compressionPolicy = Connection.CompressionPolicy.valueOf(policyName); connection.setCompressionPolicy(compressionPolicy); StringBuilder sb = new StringBuilder(); if (serverVersion[0] >= 1) { // Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection // (and server dialback) // Don't offer stream-features to pre-1.0 servers, as it confuses them. Sending features to // Openfire < 3.7.1 confuses it too - OF-443) sb.append("<stream:features>"); if (JiveGlobals.getBooleanProperty("xmpp.server.tls.enabled", true)) { sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">"); if (!ServerDialback.isEnabled()) { // Server dialback is disabled so TLS is required sb.append("<required/>"); } sb.append("</starttls>"); } // Include available SASL Mechanisms sb.append(SASLAuthentication.getSASLMechanisms(session)); if (ServerDialback.isEnabled()) { // Also offer server dialback (when TLS is not required). Server dialback may be offered // after TLS has been negotiated and a self-signed certificate is being used sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"/>"); } sb.append("</stream:features>"); } connection.deliverRawText(sb.toString()); // Set the domain or subdomain of the local server targeted by the remote server session.setLocalDomain(serverName); return session; } catch (Exception e) { Log.error("Error establishing connection from remote server:" + connection, e); connection.close(); return null; } }
/** * Verifies the received key sent by the remote server. This server is trying to generate an * outgoing connection to the remote server and the remote server is reusing an incoming * connection for validating the key. * * @param doc the received Element that contains the key to verify. */ public void verifyReceivedKey(Element doc) { ServerDialback.verifyReceivedKey(doc, getConnection()); }
private static LocalOutgoingServerSession secureAndAuthenticate( String hostname, SocketConnection connection, XMPPPacketReader reader, StringBuilder openingStream, String domain) throws Exception { final Logger log = LoggerFactory.getLogger( LocalOutgoingServerSession.class.getName() + "['" + hostname + "']"); Element features; log.debug("Indicating we want TLS to " + hostname); connection.deliverRawText("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); MXParser xpp = reader.getXPPParser(); // Wait for the <proceed> response Element proceed = reader.parseDocument().getRootElement(); if (proceed != null && proceed.getName().equals("proceed")) { log.debug("Negotiating TLS..."); try { // boolean needed = // JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true) && // // JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY, // true) && // // !JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, // false); connection.startTLS(true); } catch (Exception e) { log.debug("Got an exception whilst negotiating TLS: " + e.getMessage()); throw e; } log.debug("TLS negotiation was successful."); if (!SASLAuthentication.verifyCertificates( connection.getPeerCertificates(), hostname, true)) { log.debug("X.509/PKIX failure on outbound session"); if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) { log.debug("Will continue with dialback."); } else { log.warn("No TLS auth, but TLS auth required."); return null; } } // TLS negotiation was successful so initiate a new stream connection.deliverRawText(openingStream.toString()); // Reset the parser to use the new secured reader xpp.setInput( new InputStreamReader( connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8)); // Skip new stream element for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG; ) { eventType = xpp.next(); } // Get the stream ID String id = xpp.getAttributeValue("", "id"); // Get new stream features features = reader.parseDocument().getRootElement(); if (features != null) { // Check if we can use stream compression final Connection.CompressionPolicy compressionPolicy = connection.getConfiguration().getCompressionPolicy(); if (Connection.CompressionPolicy.optional == compressionPolicy) { // Verify if the remote server supports stream compression Element compression = features.element("compression"); if (compression != null) { boolean zlibSupported = false; Iterator it = compression.elementIterator("method"); while (it.hasNext()) { Element method = (Element) it.next(); if ("zlib".equals(method.getTextTrim())) { zlibSupported = true; } } if (zlibSupported) { log.debug("Suppressing request to perform compression; unsupported in this version."); zlibSupported = false; } if (zlibSupported) { log.debug("Requesting stream compression (zlib)."); connection.deliverRawText( "<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>"); // Check if we are good to start compression Element answer = reader.parseDocument().getRootElement(); if ("compressed".equals(answer.getName())) { // Server confirmed that we can use zlib compression connection.addCompression(); connection.startCompression(); log.debug("Stream compression was successful."); // Stream compression was successful so initiate a new stream connection.deliverRawText(openingStream.toString()); // Reset the parser to use stream compression over TLS ZInputStream in = new ZInputStream(connection.getTLSStreamHandler().getInputStream()); in.setFlushMode(JZlib.Z_PARTIAL_FLUSH); xpp.setInput(new InputStreamReader(in, StandardCharsets.UTF_8)); // Skip the opening stream sent by the server for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG; ) { eventType = xpp.next(); } // Get new stream features features = reader.parseDocument().getRootElement(); if (features == null) { log.debug("Error, EXTERNAL SASL was not offered."); return null; } } else { log.debug("Stream compression was rejected by " + hostname); } } else { log.debug("Stream compression found but zlib method is not supported by " + hostname); } } else { log.debug("Stream compression not supported by " + hostname); } } // Bookkeeping: determine what functionality the remote server offers. boolean saslEXTERNALoffered = false; if (features != null) { if (features.element("mechanisms") != null) { Iterator<Element> it = features.element("mechanisms").elementIterator(); while (it.hasNext()) { Element mechanism = it.next(); if ("EXTERNAL".equals(mechanism.getTextTrim())) { saslEXTERNALoffered = true; break; } } } } final boolean dialbackOffered = features.element("dialback") != null; log.debug("Offering dialback functionality: {}", dialbackOffered); log.debug("Offering EXTERNAL SASL: {}", saslEXTERNALoffered); LocalOutgoingServerSession result = null; // first, try SASL if (saslEXTERNALoffered) { result = attemptSASLexternal(connection, xpp, reader, domain, hostname, id, openingStream); } if (result == null) { // SASL unavailable or failed, try dialback. result = attemptDialbackOverTLS(connection, reader, domain, hostname, id); } return result; } else { log.debug( "Cannot create outgoing server session, as neither SASL mechanisms nor SERVER DIALBACK were offered by " + hostname); return null; } } else { log.debug("Error, <proceed> was not received!"); return null; } }
/** * Establishes a new outgoing session to a remote server. If the remote server 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 server or if an error occured while * securing or authenticating the connection using SASL then server dialback method will be used. * * @param domain the local domain to authenticate with the remote server. * @param hostname the hostname of the remote server. * @param port default port to use to establish the connection. * @return new outgoing session to a remote server. */ private static LocalOutgoingServerSession createOutgoingSession( String domain, String hostname, int port) { String localDomainName = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true); RemoteServerConfiguration configuration = RemoteServerManager.getConfiguration(hostname); if (configuration != null) { // TODO Use the specific TLS configuration for this remote server // useTLS = configuration.isTLSEnabled(); } // Connect to remote server using XMPP 1.0 (TLS + SASL EXTERNAL or TLS + server dialback or // server dialback) String realHostname = null; int realPort = port; Socket socket = null; // Get a list of real hostnames to connect to using DNS lookup of the specified hostname List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, port); for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext(); ) { try { socket = new Socket(); DNSUtil.HostAddress address = it.next(); realHostname = address.getHost(); realPort = address.getPort(); Log.debug( "LocalOutgoingServerSession: OS - Trying to connect to " + hostname + ":" + port + "(DNS lookup: " + realHostname + ":" + realPort + ")"); // Establish a TCP connection to the Receiving Server socket.connect( new InetSocketAddress(realHostname, realPort), RemoteServerManager.getSocketTimeout()); Log.debug( "LocalOutgoingServerSession: OS - Plain connection to " + hostname + ":" + port + " successful"); break; } catch (Exception e) { Log.warn( "Error trying to connect to remote server: " + hostname + "(DNS lookup: " + realHostname + ":" + realPort + "): " + e.toString()); try { if (socket != null) { socket.close(); } } catch (IOException ex) { Log.debug( "Additional exception while trying to close socket when connection to remote server failed: " + ex.toString()); } } } if (!socket.isConnected()) { return null; } SocketConnection connection = null; try { connection = new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false); // Send the stream header 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\""); openingStream.append(" from=\"").append(localDomainName).append("\""); // OF-673 openingStream.append(" to=\"").append(hostname).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"); // Check if the remote server is XMPP 1.0 compliant if (serverVersion != null && decodeVersion(serverVersion)[0] >= 1) { // Restore default timeout socket.setSoTimeout(soTimeout); // Get the stream features Element features = reader.parseDocument().getRootElement(); if (features != null) { // Check if TLS is enabled if (useTLS && features.element("starttls") != null) { // Secure the connection with TLS and authenticate using SASL LocalOutgoingServerSession answer; answer = secureAndAuthenticate(hostname, connection, reader, openingStream, domain); if (answer != null) { // Everything went fine so return the secured and // authenticated connection return answer; } } // Check if we are going to try server dialback (XMPP 1.0) else if (ServerDialback.isEnabled() && features.element("dialback") != null) { Log.debug( "LocalOutgoingServerSession: OS - About to try connecting using server dialback XMPP 1.0 with: " + hostname); ServerDialback method = new ServerDialback(connection, domain); OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader); if (method.authenticateDomain(newSocketReader, domain, hostname, id)) { Log.debug( "LocalOutgoingServerSession: OS - SERVER DIALBACK XMPP 1.0 with " + hostname + " was successful"); StreamID streamID = new BasicStreamIDFactory().createStreamID(id); LocalOutgoingServerSession session = new LocalOutgoingServerSession(domain, connection, newSocketReader, streamID); connection.init(session); // Set the hostname as the address of the session session.setAddress(new JID(null, hostname, null)); return session; } else { Log.debug( "LocalOutgoingServerSession: OS - Error, SERVER DIALBACK with " + hostname + " failed"); } } } else { Log.debug("LocalOutgoingServerSession: OS - Error, <starttls> was not received"); } } // Something went wrong so close the connection and try server dialback over // a plain connection if (connection != null) { connection.close(); } } catch (SSLHandshakeException e) { Log.debug( "LocalOutgoingServerSession: Handshake error while creating secured outgoing session to remote " + "server: " + hostname + "(DNS lookup: " + realHostname + ":" + realPort + "):", e); // Close the connection if (connection != null) { connection.close(); } } catch (Exception e) { Log.error( "Error creating secured outgoing session to remote server: " + hostname + "(DNS lookup: " + realHostname + ":" + realPort + ")", e); // Close the connection if (connection != null) { connection.close(); } } if (ServerDialback.isEnabled()) { Log.debug( "LocalOutgoingServerSession: OS - Going to try connecting using server dialback with: " + hostname); // Use server dialback (pre XMPP 1.0) over a plain connection return new ServerDialback().createOutgoingSession(domain, hostname, port); } return null; }