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