/** * 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; } }
/** * 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 (!allowedIPs.isEmpty()) { String hostAddress = "Unknown"; // The server is using a whitelist so check that the IP address of the client // is authorized to connect to the server try { hostAddress = connection.getHostAddress(); } catch (UnknownHostException e) { // Do nothing } if (!isAllowed(connection)) { // Client cannot connect from this IP address so end the stream and // TCP connection 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"). String language = "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 = 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; } } // Store language and version information in the connection. connection.setLanaguage(language); connection.setXMPPVersion(majorVersion, minorVersion); // Indicate the TLS policy to use for this connection if (!connection.isSecure()) { boolean hasCertificates = false; try { hasCertificates = SSLConfig.getKeyStore().size() > 0; } catch (Exception e) { Log.error(e.getMessage(), e); } Connection.TLSPolicy tlsPolicy = 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(getCompressionPolicy()); // Create a ClientSession for this user. LocalClientSession session = SessionManager.getInstance().createClientSession(connection); // Build the start packet response StringBuilder sb = new StringBuilder(200); sb.append("<?xml version='1.0' encoding='"); sb.append(CHARSET); sb.append("'?>"); if (isFlashClient) { sb.append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" "); } else { sb.append("<stream:stream "); } sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\" from=\""); sb.append(serverName); sb.append("\" id=\""); sb.append(session.getStreamID().toString()); sb.append("\" xml:lang=\""); sb.append(language); // Don't include version info if the version is 0.0. if (majorVersion != 0) { sb.append("\" version=\""); sb.append(majorVersion).append(".").append(minorVersion); } 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("<stream:features>"); if (connection.getTlsPolicy() != Connection.TLSPolicy.disabled) { sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">"); if (connection.getTlsPolicy() == Connection.TLSPolicy.required) { sb.append("<required/>"); } sb.append("</starttls>"); } // Include available SASL Mechanisms sb.append(SASLAuthentication.getSASLMechanisms(session)); // Include Stream features String specificFeatures = session.getAvailableStreamFeatures(); if (specificFeatures != null) { sb.append(specificFeatures); } sb.append("</stream:features>"); connection.deliverRawText(sb.toString()); return session; }
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; } }