1 /* 2 * Copyright (C) 2005-2008 Jive Software. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.jivesoftware.openfire.server; 18 19 import java.io.*; 20 import java.net.Socket; 21 import java.net.SocketAddress; 22 import java.nio.charset.StandardCharsets; 23 import java.util.Map; 24 import java.util.concurrent.TimeUnit; 25 import java.util.concurrent.locks.Lock; 26 27 import org.dom4j.Document; 28 import org.dom4j.DocumentException; 29 import org.dom4j.Element; 30 import org.dom4j.io.XMPPPacketReader; 31 import org.jivesoftware.openfire.*; 32 import org.jivesoftware.openfire.auth.AuthFactory; 33 import org.jivesoftware.openfire.net.*; 34 import org.jivesoftware.openfire.session.*; 35 import org.jivesoftware.openfire.spi.BasicStreamIDFactory; 36 import org.jivesoftware.openfire.spi.ConnectionManagerImpl; 37 import org.jivesoftware.openfire.spi.ConnectionType; 38 import org.jivesoftware.openfire.event.ServerSessionEventDispatcher; 39 import org.jivesoftware.util.JiveGlobals; 40 import org.jivesoftware.util.StringUtils; 41 import org.jivesoftware.util.cache.Cache; 42 import org.jivesoftware.util.cache.CacheFactory; 43 import org.slf4j.Logger; 44 import org.slf4j.LoggerFactory; 45 import org.xmlpull.v1.XmlPullParser; 46 import org.xmlpull.v1.XmlPullParserException; 47 import org.xmlpull.v1.XmlPullParserFactory; 48 import org.xmpp.packet.JID; 49 import org.xmpp.packet.PacketError; 50 import org.xmpp.packet.StreamError; 51 52 import javax.net.ssl.SSLException; 53 import javax.net.ssl.SSLHandshakeException; 54 55 /** 56 * Implementation of the Server Dialback method as defined by the RFC3920. 57 * 58 * The dialback method follows the following logic to validate the remote server: 59 * <ol> 60 * <li>The Originating Server establishes a connection to the Receiving Server.</li> 61 * <li>The Originating Server sends a 'key' value over the connection to the Receiving 62 * Server.</li> 63 * <li>The Receiving Server establishes a connection to the Authoritative Server.</li> 64 * <li>The Receiving Server sends the same 'key' value to the Authoritative Server.</li> 65 * <li>The Authoritative Server replies that key is valid or invalid.</li> 66 * <li>The Receiving Server informs the Originating Server whether it is authenticated or 67 * not.</li> 68 * </ol> 69 * 70 * By default a timeout of 20 seconds will be used for reading packets from remote servers. Use 71 * the property <b>xmpp.server.read.timeout</b> to change that value. The value should be in 72 * milliseconds. 73 * 74 * @author Gaston Dombiak 75 */ 76 public class ServerDialback { 77 private enum VerifyResult { 78 decline, // For some reason, we declined to do the verify. 79 error, // Remote error from the authoritative server. 80 valid, // Explicitly valid. 81 invalid // Invalid. 82 } 83 private static final Logger Log = LoggerFactory.getLogger(ServerDialback.class); 84 85 /** 86 * The utf-8 charset for decoding and encoding Jabber packet streams. 87 */ 88 protected static String CHARSET = "UTF-8"; 89 /** 90 * Cache (unlimited, never expire) that holds the secret key to be used for 91 * encoding and decoding keys used for authentication. 92 * Key: constant hard coded value, Value: random generated string 93 */ 94 private static Cache<String, String> secretKeyCache; 95 96 private static XmlPullParserFactory FACTORY = null; 97 98 static { 99 try { 100 FACTORY = XmlPullParserFactory.newInstance(MXParser.class.getName(), null); 101 } 102 catch (XmlPullParserException e) { 103 Log.error("Error creating a parser factory", e); 104 } 105 secretKeyCache = CacheFactory.createCache("Secret Keys Cache"); 106 } 107 108 private Connection connection; 109 private DomainPair domainPair; 110 private final SessionManager sessionManager = SessionManager.getInstance(); 111 private final RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable(); 112 113 /** 114 * Returns true if server dialback is enabled. When enabled remote servers may connect to this 115 * server using the server dialback method and this server may try the server dialback method 116 * to connect to remote servers.<p> 117 * 118 * When TLS is enabled between servers and server dialback method is enabled then TLS is going 119 * to be tried first, when connecting to a remote server, and if TLS fails then server dialback 120 * is going to be used as a last resort. If enabled and the remote server offered server-dialback 121 * after TLS and no SASL EXTERNAL then server dialback will be used. 122 * 123 * @return true if server dialback is enabled. 124 */ isEnabled()125 public static boolean isEnabled() { 126 return JiveGlobals.getBooleanProperty(ConnectionSettings.Server.DIALBACK_ENABLED, true); 127 } 128 129 /** 130 * Returns true if server dialback can be used when the remote server presented a self-signed 131 * certificate. During TLS the remote server can present a self-signed certificate, if this 132 * setting is enabled then the self-signed certificate will be accepted and if SASL EXTERNAL 133 * is not offered then server dialback will be used for verifying the remote server.<p> 134 * 135 * If self-signed certificates are accepted then server dialback over TLS is enabled. 136 * 137 * @return true if server dialback can be used when the remote server presented a self-signed 138 * certificate. 139 */ isEnabledForSelfSigned()140 public static boolean isEnabledForSelfSigned() { 141 return JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, false); 142 } 143 144 /** 145 * Sets if server dialback can be used when the remote server presented a self-signed 146 * certificate. During TLS the remote server can present a self-signed certificate, if this 147 * setting is enabled then the self-signed certificate will be accepted and if SASL EXTERNAL 148 * is not offered then server dialback will be used for verifying the remote server.<p> 149 * 150 * If self-signed certificates are accepted then server dialback over TLS is enabled. 151 * 152 * @param enabled if server dialback can be used when the remote server presented a self-signed 153 * certificate. 154 */ setEnabledForSelfSigned(boolean enabled)155 public static void setEnabledForSelfSigned(boolean enabled) { 156 JiveGlobals.setProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, Boolean.toString(enabled)); 157 } 158 159 /** 160 * Creates a new instance that will be used for creating {@link IncomingServerSession}, 161 * validating subsequent domains or authenticating new domains. Use 162 * {@link #createIncomingSession(org.dom4j.io.XMPPPacketReader)} for creating a new server 163 * session used for receiving packets from the remote server. Use 164 * {@link #validateRemoteDomain(org.dom4j.Element, org.jivesoftware.openfire.StreamID)} for 165 * validating subsequent domains and use 166 * {@link #authenticateDomain(OutgoingServerSocketReader, String)} for 167 * registering new domains that are allowed to send packets to the remote server.<p> 168 * 169 * For validating domains a new TCP connection will be established to the Authoritative Server. 170 * The Authoritative Server may be the same Originating Server or some other machine in the 171 * Originating Server's network. Once the remote domain gets validated the Originating Server 172 * will be allowed for sending packets to this server. However, this server will need to 173 * validate its domain/s with the Originating Server if this server needs to send packets to 174 * the Originating Server. Another TCP connection will be established for validation this 175 * server domain/s and for sending packets to the Originating Server. 176 * 177 * @param connection the connection created by the remote server. 178 * @param domainPair the local and remote domain for which authentication is to be established. 179 */ ServerDialback(Connection connection, DomainPair domainPair)180 public ServerDialback(Connection connection, DomainPair domainPair) { 181 this.connection = connection; 182 this.domainPair = domainPair; 183 } 184 ServerDialback(DomainPair domainPair)185 public ServerDialback(DomainPair domainPair) { 186 this.domainPair = domainPair; 187 } 188 189 /** 190 * Creates a new connection for the domain pair, where the local domain acts as the Originating Server and the 191 * remote domain as the Receiving Server. 192 * 193 * @param port port of the Receiving Server. 194 * @return an OutgoingServerSession if the domain was authenticated or {@code null} if none. 195 */ createOutgoingSession(int port)196 public LocalOutgoingServerSession createOutgoingSession(int port) { 197 final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Originating Server: Create Outgoing Session from: " + domainPair.getLocal() + " to a RS in the domain of: " + domainPair.getRemote() + " (port: " + port+ ")]" ); 198 199 log.debug( "Creating new outgoing session..." ); 200 201 String hostname = null; 202 int realPort = port; 203 try { 204 // Establish a TCP connection to the Receiving Server 205 final Map.Entry<Socket, Boolean> socketToXmppDomain = SocketUtil.createSocketToXmppDomain(domainPair.getRemote(), port ); 206 if ( socketToXmppDomain == null ) { 207 log.info( "Unable to create new outgoing session: Cannot create a plain socket connection with any applicable remote host." ); 208 return null; 209 } 210 211 final Socket socket = socketToXmppDomain.getKey(); 212 final boolean directTLS = socketToXmppDomain.getValue(); 213 connection = new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false); 214 if (directTLS) { 215 connection.startTLS( false, directTLS ); 216 } 217 218 log.debug( "Send the stream header and wait for response..." ); 219 StringBuilder stream = new StringBuilder(); 220 stream.append("<stream:stream"); 221 stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); 222 stream.append(" xmlns=\"jabber:server\""); 223 stream.append(" to=\"").append(domainPair.getRemote()).append("\""); 224 stream.append(" from=\"").append(domainPair.getLocal()).append("\""); 225 stream.append(" xmlns:db=\"jabber:server:dialback\""); 226 stream.append(">"); 227 connection.deliverRawText(stream.toString()); 228 229 // Set a read timeout (of 5 seconds) so we don't keep waiting forever 230 int soTimeout = socket.getSoTimeout(); 231 socket.setSoTimeout(RemoteServerManager.getSocketTimeout()); 232 233 XMPPPacketReader reader = new XMPPPacketReader(); 234 reader.setXPPFactory(FACTORY); 235 236 final InputStream input; 237 if (directTLS) { 238 input = ((SocketConnection)connection).getTLSStreamHandler().getInputStream(); 239 } else { 240 input = socket.getInputStream(); 241 } 242 reader.getXPPParser().setInput(new InputStreamReader( 243 ServerTrafficCounter.wrapInputStream(input), CHARSET)); 244 // Get the answer from the Receiving Server 245 XmlPullParser xpp = reader.getXPPParser(); 246 for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { 247 eventType = xpp.next(); 248 } 249 log.debug( "Got a response. Check if the remote server supports dialback..." ); 250 251 if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) { 252 log.debug( "Dialback seems to be supported by the remote server." ); 253 254 // Restore default timeout 255 socket.setSoTimeout(soTimeout); 256 String id = xpp.getAttributeValue("", "id"); 257 OutgoingServerSocketReader socketReader = new OutgoingServerSocketReader(reader); 258 if (authenticateDomain(socketReader, id)) { 259 log.debug( "Successfully authenticated the connection with dialback." ); 260 // Domain was validated so create a new OutgoingServerSession 261 StreamID streamID = BasicStreamIDFactory.createStreamID(id); 262 LocalOutgoingServerSession session = new LocalOutgoingServerSession(domainPair.getLocal(), connection, socketReader, streamID); 263 connection.init(session); 264 // Set the remote domain as the address of the session. 265 session.setAddress(new JID(null, domainPair.getRemote(), null)); 266 log.debug( "Successfully created new outgoing session!" ); 267 return session; 268 } 269 else { 270 log.debug( "Failed to authenticate the connection with dialback." ); 271 // Close the connection 272 connection.close(); 273 } 274 } 275 else { 276 log.debug("Error! Invalid namespace in packet: '{}'. Closing connection.", xpp.getText() ); 277 // Send an invalid-namespace stream error condition in the response 278 connection.deliverRawText( 279 new StreamError(StreamError.Condition.invalid_namespace).toXML()); 280 // Close the connection 281 connection.close(); 282 } 283 } 284 catch (Exception e) { 285 log.error( "An exception occurred while creating outgoing session to remote server: ", e ); 286 // Close the connection 287 if (connection != null) { 288 connection.close(); 289 } 290 } 291 log.warn( "Unable to create a new outgoing session" ); 292 return null; 293 } 294 295 /** 296 * Authenticates the Originating Server domain with the Receiving Server. Once the domain has 297 * been authenticated the Receiving Server will start accepting packets from the Originating 298 * Server.<p> 299 * 300 * The Receiving Server will connect to the Authoritative Server to verify the dialback key. 301 * Most probably the Originating Server machine will be the Authoritative Server too. 302 * 303 * @param socketReader the reader to use for reading the answer from the Receiving Server. 304 * @param id the stream id to be used for creating the dialback key. 305 * @return true if the Receiving Server authenticated the domain with the Authoritative Server. 306 */ authenticateDomain(OutgoingServerSocketReader socketReader, String id)307 public boolean authenticateDomain(OutgoingServerSocketReader socketReader, String id) { 308 309 final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Originating Server: Authenticate domain: " + domainPair.getLocal() + " with a RS in the domain of: " + domainPair.getRemote() + " (id: " + id + ")]" ); 310 311 log.debug( "Authenticating domain ..." ); 312 313 String key = AuthFactory.createDigest( id, getSecretkey() ); 314 315 synchronized (socketReader) { 316 log.debug( "Sending dialback key and wait for the validation response..." ); 317 StringBuilder sb = new StringBuilder(); 318 sb.append("<db:result"); 319 sb.append(" from=\"").append(domainPair.getLocal()).append("\""); 320 sb.append(" to=\"").append(domainPair.getRemote()).append("\">"); 321 sb.append(key); 322 sb.append("</db:result>"); 323 connection.deliverRawText(sb.toString()); 324 325 // Process the answer from the Receiving Server 326 try { 327 while (true) { 328 Element doc = socketReader.getElement(RemoteServerManager.getSocketTimeout(), TimeUnit.MILLISECONDS); 329 if (doc == null) { 330 log.debug( "Failed to authenticate domain: Time out waiting for validation response." ); 331 return false; 332 } 333 else if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) { 334 if ( "valid".equals(doc.attributeValue("type")) ) { 335 log.debug( "Authenticated succeeded!" ); 336 return true; 337 } else { 338 log.debug( "Failed to authenticate domain: the validation response was received, but did not grant authentication." ); 339 return false; 340 } 341 } 342 else { 343 log.warn( "Ignoring unexpected answer while waiting for dialback validation: " + doc.asXML() ); 344 } 345 } 346 } 347 catch (InterruptedException e) { 348 log.debug( "Failed to authenticate domain: An interrupt was received while waiting for validation response (is Openfire shutting down?)" ); 349 return false; 350 } 351 } 352 } 353 354 /** 355 * Returns a new {@link IncomingServerSession} with a domain validated by the Authoritative 356 * Server. New domains may be added to the returned IncomingServerSession after they have 357 * been validated. See 358 * {@link LocalIncomingServerSession#validateSubsequentDomain(org.dom4j.Element)}. The remote 359 * server will be able to send packets through this session whose domains were previously 360 * validated.<p> 361 * 362 * When acting as an Authoritative Server this method will verify the requested key 363 * and will return null since the underlying TCP connection will be closed after sending the 364 * response to the Receiving Server.<p> 365 * 366 * @param reader reader of DOM documents on the connection to the remote server. 367 * @return an IncomingServerSession that was previously validated against the remote server. 368 * @throws IOException if an I/O error occurs while communicating with the remote server. 369 * @throws XmlPullParserException if an error occurs while parsing XML packets. 370 */ createIncomingSession(XMPPPacketReader reader)371 public LocalIncomingServerSession createIncomingSession(XMPPPacketReader reader) throws IOException, 372 XmlPullParserException { 373 XmlPullParser xpp = reader.getXPPParser(); 374 StringBuilder sb; 375 if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) { 376 Log.debug("ServerDialback: Processing incoming session."); 377 378 StreamID streamID = sessionManager.nextStreamID(); 379 380 sb = new StringBuilder(); 381 sb.append("<stream:stream"); 382 sb.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); 383 sb.append(" xmlns=\"jabber:server\" xmlns:db=\"jabber:server:dialback\""); 384 sb.append(" id=\""); 385 sb.append(streamID.toString()); 386 sb.append("\">"); 387 connection.deliverRawText(sb.toString()); 388 389 try { 390 Element doc = reader.parseDocument().getRootElement(); 391 if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) { 392 String hostname = doc.attributeValue("from"); 393 String recipient = doc.attributeValue("to"); 394 Log.debug("ServerDialback: RS - Validating remote domain for incoming session from {} to {}", hostname, recipient); 395 if (validateRemoteDomain(doc, streamID)) { 396 Log.debug("ServerDialback: RS - Validation of remote domain for incoming session from {} to {} was successful.", hostname, recipient); 397 // Create a server Session for the remote server 398 LocalIncomingServerSession session = sessionManager. 399 createIncomingServerSession(connection, streamID, hostname); 400 // Add the validated domain as a valid domain 401 session.addValidatedDomain(hostname); 402 // Set the domain or subdomain of the local server used when 403 // validating the session 404 session.setLocalDomain(recipient); 405 // After the session has been created, inform all listeners as well. 406 ServerSessionEventDispatcher.dispatchEvent(session, ServerSessionEventDispatcher.EventType.session_created); 407 return session; 408 } else { 409 Log.debug("ServerDialback: RS - Validation of remote domain for incoming session from {} to {} was not successful.", hostname, recipient); 410 return null; 411 } 412 } 413 else if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) { 414 // When acting as an Authoritative Server the Receiving Server will send a 415 // db:verify packet for verifying a key that was previously sent by this 416 // server when acting as the Originating Server 417 verifyReceivedKey(doc, connection); 418 // Close the underlying connection 419 connection.close(); 420 String verifyFROM = doc.attributeValue("from"); 421 String id = doc.attributeValue("id"); 422 Log.debug("ServerDialback: AS - Connection closed for host: " + verifyFROM + " id: " + id); 423 return null; 424 } 425 else { 426 Log.debug("ServerDialback: Received an invalid/unknown packet while trying to process an incoming session: {}", doc.asXML()); 427 // The remote server sent an invalid/unknown packet 428 connection.deliverRawText( 429 new StreamError(StreamError.Condition.invalid_xml).toXML()); 430 // Close the underlying connection 431 connection.close(); 432 return null; 433 } 434 } 435 catch (Exception e) { 436 Log.error("An error occured while creating a server session", e); 437 // Close the underlying connection 438 connection.close(); 439 return null; 440 } 441 442 } 443 else { 444 Log.debug("ServerDialback: Received a stanza in an invalid namespace while trying to process an incoming session: {}", xpp.getNamespace("db")); 445 connection.deliverRawText( 446 new StreamError(StreamError.Condition.invalid_namespace).toXML()); 447 // Close the underlying connection 448 connection.close(); 449 return null; 450 } 451 } 452 453 /** 454 * Send a dialback error. 455 * 456 * @param from From 457 * @param to To 458 * @param err Error type. 459 */ dialbackError(String from, String to, PacketError err)460 protected void dialbackError(String from, String to, PacketError err) { 461 connection.deliverRawText("<db:result type=\"error\" from=\"" + from + "\" to=\"" + to + "\">" + err.toXML() + "</db:result>"); 462 } 463 464 /** 465 * Returns true if the domain requested by the remote server was validated by the Authoritative 466 * Server. To validate the domain a new TCP connection will be established to the 467 * Authoritative Server. The Authoritative Server may be the same Originating Server or 468 * some other machine in the Originating Server's network.<p> 469 * 470 * If the domain was not valid or some error occurred while validating the domain then the 471 * underlying TCP connection may be closed. 472 * 473 * @param doc the request for validating the new domain. 474 * @param streamID the stream id generated by this server for the Originating Server. 475 * @return true if the requested domain is valid. 476 */ validateRemoteDomain(Element doc, StreamID streamID)477 public boolean validateRemoteDomain(Element doc, StreamID streamID) { 478 StringBuilder sb; 479 String recipient = doc.attributeValue("to"); 480 String remoteDomain = doc.attributeValue("from"); 481 482 final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Receiving Server: Validate domain: " + recipient + "(id " + streamID + ") for OS: " + remoteDomain + "]" ); 483 484 log.debug( "Validating domain..."); 485 if (connection.getTlsPolicy() == Connection.TLSPolicy.required && 486 !connection.isSecure()) { 487 connection.deliverRawText(new StreamError(StreamError.Condition.policy_violation).toXML()); 488 // Close the underlying connection 489 connection.close(); 490 return false; 491 } 492 493 if (!RemoteServerManager.canAccess(remoteDomain)) { 494 connection.deliverRawText(new StreamError(StreamError.Condition.policy_violation).toXML()); 495 // Close the underlying connection 496 connection.close(); 497 log.debug( "Unable to validate domain: Remote domain is not allowed to establish a connection to this server." ); 498 return false; 499 } 500 else if (isHostUnknown(recipient)) { 501 dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.item_not_found, PacketError.Type.cancel, "Service not hosted here")); 502 log.debug( "Unable to validate domain: recipient not recognized as a local domain." ); 503 return false; 504 } 505 else { 506 log.debug( "Check if the remote domain already has a connection to the target domain/subdomain" ); 507 boolean alreadyExists = false; 508 for (IncomingServerSession session : sessionManager.getIncomingServerSessions(remoteDomain)) { 509 if (recipient.equals(session.getLocalDomain())) { 510 alreadyExists = true; 511 } 512 } 513 if (alreadyExists && !sessionManager.isMultipleServerConnectionsAllowed()) { 514 dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.resource_constraint, PacketError.Type.cancel, "Incoming session already exists")); 515 log.debug( "Unable to validate domain: An incoming connection already exists from this remote domain, and multiple connections are not allowed." ); 516 return false; 517 } 518 else { 519 log.debug( "Checking to see if the remote server provides stronger authentication based on SASL. If that's the case, dialback-based authentication can be skipped." ); 520 if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), remoteDomain, true)) { 521 log.debug( "Host authenticated based on SASL. Weaker dialback-based authentication is skipped." ); 522 sb = new StringBuilder(); 523 sb.append("<db:result"); 524 sb.append(" from=\"").append(recipient).append("\""); 525 sb.append(" to=\"").append(remoteDomain).append("\""); 526 sb.append(" type=\"valid\""); 527 sb.append("/>"); 528 connection.deliverRawText(sb.toString()); 529 530 log.debug( "Domain validated successfully!" ); 531 return true; 532 } 533 534 log.debug( "Unable to authenticate host based on stronger SASL. Proceeding with dialback..." ); 535 536 String key = doc.getTextTrim(); 537 538 final Map.Entry<Socket, Boolean> socketToXmppDomain = SocketUtil.createSocketToXmppDomain( remoteDomain, RemoteServerManager.getPortForServer(remoteDomain) ); 539 540 if ( socketToXmppDomain == null ) 541 { 542 log.debug( "Unable to validate domain: No server available for verifying key of remote server." ); 543 dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.remote_server_not_found, PacketError.Type.cancel, "Unable to connect to authoritative server")); 544 return false; 545 } 546 547 Socket socket = socketToXmppDomain.getKey(); 548 boolean directTLS = socketToXmppDomain.getValue(); 549 550 VerifyResult result; 551 try { 552 log.debug( "Verifying dialback key..." ); 553 final SocketAddress socketAddress = socket.getRemoteSocketAddress(); 554 log.debug( "Opening a new connection to {} {}.", socketAddress, directTLS ? "using directTLS" : "that is initially not encrypted" ); 555 try { 556 result = verifyKey( key, streamID, recipient, remoteDomain, socket, directTLS, directTLS ); 557 } 558 catch (SSLHandshakeException e) 559 { 560 result = VerifyResult.error; 561 log.debug( "Verification of dialback key failed due to TLS failure.", e ); 562 563 // The receiving entity is expected to close the socket *without* sending any more data (<failure/> nor </stream>). 564 // It is probably (see OF-794) best if we, as the initiating entity, therefor don't send any data either. 565 final SocketAddress oldAddress = socket.getRemoteSocketAddress(); 566 socket.close(); 567 568 if ( !directTLS ) 569 { 570 log.debug( "Retry without StartTLS... Re-opening socket (with the same remote peer)..." ); 571 572 // Retry, without TLS. 573 socket = new Socket(); 574 socket.connect( oldAddress, RemoteServerManager.getSocketTimeout() ); 575 log.debug( "Successfully re-opened socket! Try to validate dialback key again (without TLS this time)..." ); 576 577 result = verifyKey( key, streamID, recipient, remoteDomain, socket, true, directTLS ); 578 } 579 } catch ( SSLException ex ) { 580 if ( JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ON_PLAIN_DETECTION_ALLOW_NONDIRECTTLS_FALLBACK, true) && ex.getMessage().contains( "plaintext connection?" ) ) { 581 Log.warn( "Plaintext detected on a new connection that is was started in DirectTLS mode (socket address: {}). Attempting to restart the connection in non-DirectTLS mode.", socketAddress ); 582 try { 583 // Close old socket 584 socket.close(); 585 } catch ( Exception e ) { 586 Log.debug( "An exception occurred (and is ignored) while trying to close a socket that was already in an error state.", e ); 587 } 588 socket = new Socket(); 589 socket.connect( socketAddress, RemoteServerManager.getSocketTimeout() ); 590 result = verifyKey( key, streamID, recipient, remoteDomain, socket, false, false ); 591 directTLS = false; // No error this time? directTLS apparently is 'false'. Change it's value for future usage (if any) 592 Log.info( "Re-established connection to {}. Proceeding without directTLS.", socketAddress ); 593 } else { 594 // Do not retry as non-DirectTLS, rethrow the exception. 595 throw ex; 596 } 597 } 598 599 switch(result) { 600 case valid: 601 case invalid: 602 boolean valid = (result == VerifyResult.valid); 603 log.debug( "Dialback key is " + (valid? "valid":"invalid") + ". Sending verification result to remote domain." ); 604 sb = new StringBuilder(); 605 sb.append("<db:result"); 606 sb.append(" from=\"").append(recipient).append("\""); 607 sb.append(" to=\"").append(remoteDomain).append("\""); 608 sb.append(" type=\""); 609 sb.append(valid ? "valid" : "invalid"); 610 sb.append("\"/>"); 611 connection.deliverRawText(sb.toString()); 612 613 if (!valid) { 614 log.debug( "Close the underlying connection as key verification failed." ); 615 connection.close(); 616 log.debug( "Unable to validate domain: dialback key is invalid." ); 617 return false; 618 } else { 619 log.debug( "Successfully validated domain!" ); 620 return true; 621 } 622 default: 623 break; 624 } 625 log.debug( "Unable to validate domain: key verification did not complete (the AS likely returned an error or a time out occurred)." ); 626 dialbackError( recipient, remoteDomain, new PacketError( PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server returned error" ) ); 627 return false; 628 } 629 catch (Exception e) { 630 dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server failed")); 631 log.warn( "Unable to validate domain: An exception occurred while verifying the dialback key.", e ); 632 return false; 633 } 634 } 635 } 636 } 637 isHostUnknown(String recipient)638 private boolean isHostUnknown(String recipient) { 639 boolean host_unknown = !domainPair.getLocal().equals(recipient); 640 // If the recipient does not match the local domain then check if it matches a subdomain. This 641 // trick is useful when subdomains of this server are registered in the DNS so remote 642 // servers may establish connections directly to a subdomain of this server 643 if (host_unknown && recipient.contains(domainPair.getLocal())) { 644 host_unknown = !routingTable.hasComponentRoute(new JID(recipient)); 645 } 646 return host_unknown; 647 } 648 sendVerifyKey(String key, StreamID streamID, String recipient, String remoteDomain, Writer writer, XMPPPacketReader reader, Socket socket, boolean skipTLS, boolean directTLS)649 private VerifyResult sendVerifyKey(String key, StreamID streamID, String recipient, String remoteDomain, Writer writer, XMPPPacketReader reader, Socket socket, boolean skipTLS, boolean directTLS) throws IOException, XmlPullParserException, RemoteConnectionFailedException { 650 final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Receiving Server: Verify key with AS: " + remoteDomain + " for OS: " + recipient + " (id " + streamID + ")]" ); 651 652 VerifyResult result = VerifyResult.error; 653 654 final ConnectionManagerImpl connectionManager = ( (ConnectionManagerImpl) XMPPServer.getInstance().getConnectionManager() ); 655 final TLSStreamHandler tlsStreamHandler = new TLSStreamHandler( socket, connectionManager.getListener( ConnectionType.SOCKET_S2S, directTLS ).generateConnectionConfiguration(), true ); 656 657 if ( directTLS ) { 658 // Start handshake 659 log.debug( "Starting Direct TLS handshake." ); 660 tlsStreamHandler.start(); 661 662 // Use new wrapped writers 663 writer = new BufferedWriter( new OutputStreamWriter( tlsStreamHandler.getOutputStream(), StandardCharsets.UTF_8 ) ); 664 reader.getXPPParser().setInput( new InputStreamReader( tlsStreamHandler.getInputStream(), StandardCharsets.UTF_8 ) ); 665 } 666 667 log.debug( "Send the Authoritative Server a stream header and wait for answer." ); 668 StringBuilder stream = new StringBuilder(); 669 stream.append("<stream:stream"); 670 stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); 671 stream.append(" xmlns=\"jabber:server\""); 672 stream.append(" xmlns:db=\"jabber:server:dialback\""); 673 stream.append(" to=\""); 674 stream.append(remoteDomain); 675 stream.append("\""); 676 stream.append(" from=\""); 677 stream.append(recipient); 678 stream.append("\""); 679 stream.append(" version=\"1.0\">"); 680 writer.write(stream.toString()); 681 writer.flush(); 682 683 // Get the answer from the Authoritative Server 684 XmlPullParser xpp = reader.getXPPParser(); 685 for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { 686 eventType = xpp.next(); 687 } 688 689 log.debug( "Got a response." ); // TODO there's code duplication here with LocalOutgoingServerSession. 690 if ((xpp.getAttributeValue("", "version") != null) && (xpp.getAttributeValue("", "version").equals("1.0"))) { 691 log.debug( "The remote server is XMPP 1.0 compliant (or at least reports to be)."); 692 Document doc; 693 try { 694 doc = reader.parseDocument(); 695 } catch (DocumentException e) { 696 log.warn("Unable to verify key: XML Error!", e); 697 // Close the stream 698 writer.write("</stream:stream>"); 699 writer.flush(); 700 return VerifyResult.error; 701 } 702 Element features = doc.getRootElement(); 703 Element starttls = features.element("starttls"); 704 if (!directTLS && !skipTLS && starttls != null) { 705 writer.write("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); 706 writer.flush(); 707 try { 708 doc = reader.parseDocument(); 709 } catch (DocumentException e) { 710 log.warn("Unable to verify key: XML Error!", e); 711 // Close the stream 712 writer.write("</stream:stream>"); 713 writer.flush(); 714 return VerifyResult.error; 715 } 716 if (!doc.getRootElement().getName().equals("proceed")) { 717 log.warn("Unable to verify key: Got {} instead of proceed for starttls", doc.getRootElement().getName()); 718 log.debug("Like this: {}", doc.asXML()); 719 // Close the stream 720 writer.write("</stream:stream>"); 721 writer.flush(); 722 return VerifyResult.error; 723 } 724 725 log.debug( "Negotiating StartTLS with AS... " ); 726 // Start handshake 727 tlsStreamHandler.start(); 728 // Use new wrapped writers 729 writer = new BufferedWriter( new OutputStreamWriter( tlsStreamHandler.getOutputStream(), StandardCharsets.UTF_8 ) ); 730 reader.getXPPParser().setInput( new InputStreamReader( tlsStreamHandler.getInputStream(), StandardCharsets.UTF_8 ) ); 731 log.debug( "Successfully negotiated StartTLS with AS... " ); 732 /// Recurses! 733 return sendVerifyKey( key, streamID, recipient, remoteDomain, writer, reader, socket, skipTLS, directTLS ); 734 } 735 } 736 if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) { 737 log.debug("Request for verification of the key and wait for response"); 738 StringBuilder sb = new StringBuilder(); 739 sb.append("<db:verify"); 740 sb.append(" from=\"").append(recipient).append("\""); 741 sb.append(" to=\"").append(remoteDomain).append("\""); 742 sb.append(" id=\"").append(streamID.getID()).append("\">"); 743 sb.append(key); 744 sb.append("</db:verify>"); 745 writer.write(sb.toString()); 746 writer.flush(); 747 748 try { 749 Element doc = reader.parseDocument().getRootElement(); 750 if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) { 751 if (doc.attributeValue("id") == null || !streamID.equals(BasicStreamIDFactory.createStreamID( doc.attributeValue("id") ))) { 752 // Include the invalid-id stream error condition in the response 753 writer.write(new StreamError(StreamError.Condition.invalid_id).toXML()); 754 writer.write("</stream:stream>"); 755 writer.flush(); 756 // Thrown an error so <remote-connection-failed/> stream error 757 // condition is sent to the Originating Server 758 throw new RemoteConnectionFailedException("Invalid ID"); 759 } 760 else if (isHostUnknown( doc.attributeValue( "to" ) )) { 761 // Include the host-unknown stream error condition in the response 762 writer.write(new StreamError(StreamError.Condition.host_unknown).toXML()); 763 writer.write("</stream:stream>"); 764 writer.flush(); 765 // Thrown an error so <remote-connection-failed/> stream error 766 // condition is sent to the Originating Server 767 throw new RemoteConnectionFailedException("Host unknown"); 768 } 769 else if (!remoteDomain.equals(doc.attributeValue("from"))) { 770 // Include the invalid-from stream error condition in the response 771 writer.write(new StreamError(StreamError.Condition.invalid_from).toXML()); 772 writer.write("</stream:stream>"); 773 writer.flush(); 774 // Thrown an error so <remote-connection-failed/> stream error 775 // condition is sent to the Originating Server 776 throw new RemoteConnectionFailedException("Invalid From"); 777 } 778 else if ("valid".equals(doc.attributeValue("type"))){ 779 log.debug("Key was VERIFIED by the Authoritative Server."); 780 result = VerifyResult.valid; 781 } 782 else if ("invalid".equals(doc.attributeValue("type"))){ 783 log.debug("Key was NOT VERIFIED by the Authoritative Server."); 784 result = VerifyResult.invalid; 785 } 786 else { 787 log.debug("Key was ERRORED by the Authoritative Server."); 788 result = VerifyResult.error; 789 } 790 } 791 else { 792 log.debug("db:verify answer was: " + doc.asXML()); 793 } 794 } 795 catch (DocumentException | RuntimeException e) { 796 log.error("An error occurred while connecting to the Authoritative Server: ", e); 797 // Thrown an error so <remote-connection-failed/> stream error condition is 798 // sent to the Originating Server 799 writer.write("</stream:stream>"); 800 writer.flush(); 801 throw new RemoteConnectionFailedException("Error connecting to the Authoritative Server"); 802 } 803 804 } 805 else { 806 // Include the invalid-namespace stream error condition in the response 807 writer.write(new StreamError(StreamError.Condition.invalid_namespace).toXML()); 808 writer.write("</stream:stream>"); 809 writer.flush(); 810 // Thrown an error so <remote-connection-failed/> stream error condition is 811 // sent to the Originating Server 812 throw new RemoteConnectionFailedException("Invalid namespace"); 813 } 814 writer.write("</stream:stream>"); 815 writer.flush(); 816 return result; 817 } 818 819 /** 820 * Verifies the key with the Authoritative Server. 821 */ verifyKey(String key, StreamID streamID, String recipient, String remoteDomain, Socket socket, boolean skipTLS, boolean directTLS )822 private VerifyResult verifyKey(String key, StreamID streamID, String recipient, String remoteDomain, Socket socket, boolean skipTLS, boolean directTLS ) throws IOException, XmlPullParserException, RemoteConnectionFailedException { 823 824 final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Receiving Server: Verify key with AS: " + remoteDomain + " for OS: " + recipient + " (id " + streamID + ")]" ); 825 826 log.debug( "Verifying key ..." ); 827 XMPPPacketReader reader; 828 Writer writer = null; 829 // Set a read timeout 830 socket.setSoTimeout(RemoteServerManager.getSocketTimeout()); 831 VerifyResult result = VerifyResult.error; 832 try { 833 reader = new XMPPPacketReader(); 834 reader.setXPPFactory(FACTORY); 835 836 reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(), CHARSET)); 837 // Get a writer for sending the open stream tag 838 writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), CHARSET)); 839 result = sendVerifyKey(key, streamID, recipient, remoteDomain, writer, reader, socket, skipTLS, directTLS ); 840 } 841 finally { 842 try { 843 // Close the TCP connection 844 socket.close(); 845 } 846 catch (IOException ioe) { 847 // Do nothing 848 } 849 } 850 851 switch ( result ) { 852 case valid: 853 log.debug( "Successfully verified key!" ); 854 break; 855 856 case invalid: 857 log.debug( "Unable to verify key: AS reports that the key is invalid." ); 858 break; 859 860 default: 861 case decline: 862 case error: 863 log.debug( "Unable to verify key: An error occurred." ); 864 break; 865 } 866 return result; 867 } 868 869 /** 870 * Verifies the key sent by a Receiving Server. This server will be acting as the 871 * Authoritative Server when executing this method. The remote server may have established 872 * a new connection to the Authoritative Server (i.e. this server) for verifying the key 873 * or it may be reusing an existing incoming connection. 874 * 875 * @param doc the Element that contains the key to verify. 876 * @param connection the connection to use for sending the verification result 877 * @return true if the key was verified. 878 */ verifyReceivedKey(Element doc, Connection connection)879 public static boolean verifyReceivedKey(Element doc, Connection connection) { 880 String verifyFROM = doc.attributeValue("from"); 881 String verifyTO = doc.attributeValue("to"); 882 String key = doc.getTextTrim(); 883 StreamID streamID = BasicStreamIDFactory.createStreamID( doc.attributeValue("id") ); 884 885 final Logger log = LoggerFactory.getLogger( Log.getName() + "[Acting as Authoritative Server: Verify key sent by RS: " + verifyFROM + " (id " + streamID+ ")]" ); 886 887 log.debug( "Verifying key... "); 888 889 // TODO If the value of the 'to' address does not match a recognized hostname, 890 // then generate a <host-unknown/> stream error condition 891 // TODO If the value of the 'from' address does not match the hostname 892 // represented by the Receiving Server when opening the TCP connection, then 893 // generate an <invalid-from/> stream error condition 894 895 // Verify the received key 896 // Created the expected key based on the received ID value and the shared secret 897 String expectedKey = AuthFactory.createDigest(streamID.getID(), getSecretkey()); 898 boolean verified = expectedKey.equals(key); 899 900 // Send the result of the key verification 901 StringBuilder sb = new StringBuilder(); 902 sb.append("<db:verify"); 903 sb.append(" from=\"").append(verifyTO).append("\""); 904 sb.append(" to=\"").append(verifyFROM).append("\""); 905 sb.append(" type=\""); 906 sb.append(verified ? "valid" : "invalid"); 907 sb.append("\" id=\"").append(streamID.getID()).append("\"/>"); 908 connection.deliverRawText(sb.toString()); 909 log.debug("Verification successful! Key was: " + (verified ? "VALID" : "INVALID")); 910 return verified; 911 } 912 913 /** 914 * Returns the secret key that was randomly generated. When running inside of a cluster 915 * the key will be unique to all cluster nodes. 916 * 917 * @return the secret key that was randomly generated. 918 */ getSecretkey()919 private static String getSecretkey() { 920 String key = "secretKey"; 921 Lock lock = secretKeyCache.getLock(key); 922 lock.lock(); 923 try { 924 String secret = secretKeyCache.get(key); 925 if (secret == null) { 926 secret = StringUtils.randomString(10); 927 secretKeyCache.put(key, secret); 928 } 929 return secret; 930 } 931 finally { 932 lock.unlock(); 933 } 934 } 935 } 936 937