Advertisement
illusionpt

ServerDialback.java Fix

Aug 7th, 2013
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 40.94 KB | None | 0 0
  1. /**
  2.  * $RCSfile: ServerDialback.java,v $
  3.  * $Revision: 3188 $
  4.  * $Date: 2005-12-12 00:28:19 -0300 (Mon, 12 Dec 2005) $
  5.  *
  6.  * Copyright (C) 2005-2008 Jive Software. All rights reserved.
  7.  *
  8.  * Licensed under the Apache License, Version 2.0 (the "License");
  9.  * you may not use this file except in compliance with the License.
  10.  * You may obtain a copy of the License at
  11.  *
  12.  *     http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing, software
  15.  * distributed under the License is distributed on an "AS IS" BASIS,
  16.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.  * See the License for the specific language governing permissions and
  18.  * limitations under the License.
  19.  */
  20.  
  21. package org.jivesoftware.openfire.server;
  22.  
  23. import java.io.BufferedWriter;
  24. import java.io.IOException;
  25. import java.io.InputStreamReader;
  26. import java.io.OutputStreamWriter;
  27. import java.io.Writer;
  28. import java.net.InetSocketAddress;
  29. import java.net.Socket;
  30. import java.util.Iterator;
  31. import java.util.List;
  32. import java.util.concurrent.TimeUnit;
  33. import java.util.concurrent.locks.Lock;
  34.  
  35. import org.dom4j.DocumentException;
  36. import org.dom4j.Element;
  37. import org.dom4j.io.XMPPPacketReader;
  38. import org.jivesoftware.openfire.Connection;
  39. import org.jivesoftware.openfire.RemoteConnectionFailedException;
  40. import org.jivesoftware.openfire.RoutingTable;
  41. import org.jivesoftware.openfire.SessionManager;
  42. import org.jivesoftware.openfire.StreamID;
  43. import org.jivesoftware.openfire.XMPPServer;
  44. import org.jivesoftware.openfire.auth.AuthFactory;
  45. import org.jivesoftware.openfire.net.DNSUtil;
  46. import org.jivesoftware.openfire.net.MXParser;
  47. import org.jivesoftware.openfire.net.ServerTrafficCounter;
  48. import org.jivesoftware.openfire.net.SocketConnection;
  49. import org.jivesoftware.openfire.session.IncomingServerSession;
  50. import org.jivesoftware.openfire.session.LocalIncomingServerSession;
  51. import org.jivesoftware.openfire.session.LocalOutgoingServerSession;
  52. import org.jivesoftware.openfire.spi.BasicStreamIDFactory;
  53. import org.jivesoftware.util.JiveGlobals;
  54. import org.jivesoftware.util.StringUtils;
  55. import org.jivesoftware.util.cache.Cache;
  56. import org.jivesoftware.util.cache.CacheFactory;
  57. import org.slf4j.Logger;
  58. import org.slf4j.LoggerFactory;
  59. import org.xmlpull.v1.XmlPullParser;
  60. import org.xmlpull.v1.XmlPullParserException;
  61. import org.xmlpull.v1.XmlPullParserFactory;
  62. import org.xmpp.packet.JID;
  63. import org.xmpp.packet.StreamError;
  64.  
  65. /**
  66.  * Implementation of the Server Dialback method as defined by the RFC3920.
  67.  *
  68.  * The dialback method follows the following logic to validate the remote server:
  69.  * <ol>
  70.  *  <li>The Originating Server establishes a connection to the Receiving Server.</li>
  71.  *  <li>The Originating Server sends a 'key' value over the connection to the Receiving
  72.  *  Server.</li>
  73.  *  <li>The Receiving Server establishes a connection to the Authoritative Server.</li>
  74.  *  <li>The Receiving Server sends the same 'key' value to the Authoritative Server.</li>
  75.  *  <li>The Authoritative Server replies that key is valid or invalid.</li>
  76.  *  <li>The Receiving Server informs the Originating Server whether it is authenticated or
  77.  *  not.</li>
  78.  * </ol>
  79.  *
  80.  * By default a timeout of 20 seconds will be used for reading packets from remote servers. Use
  81.  * the property <b>xmpp.server.read.timeout</b> to change that value. The value should be in
  82.  * milliseconds.
  83.  *
  84.  * @author Gaston Dombiak
  85.  */
  86. public class ServerDialback {
  87.    
  88.     private static final Logger Log = LoggerFactory.getLogger(ServerDialback.class);
  89.  
  90.     /**
  91.      * The utf-8 charset for decoding and encoding Jabber packet streams.
  92.      */
  93.     protected static String CHARSET = "UTF-8";
  94.     /**
  95.      * Cache (unlimited, never expire) that holds the secret key to be used for
  96.      * encoding and decoding keys used for authentication.
  97.      * Key: constant hard coded value, Value: random generated string
  98.      */
  99.     private static Cache<String, String> secretKeyCache;
  100.  
  101.     private static XmlPullParserFactory FACTORY = null;
  102.  
  103.     static {
  104.         try {
  105.             FACTORY = XmlPullParserFactory.newInstance(MXParser.class.getName(), null);
  106.         }
  107.         catch (XmlPullParserException e) {
  108.             Log.error("Error creating a parser factory", e);
  109.         }
  110.         secretKeyCache = CacheFactory.createCache("Secret Keys Cache");
  111.     }
  112.  
  113.     private Connection connection;
  114.     private String serverName;
  115.     private SessionManager sessionManager = SessionManager.getInstance();
  116.     private RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable();
  117.  
  118.     /**
  119.      * Returns true if server dialback is enabled. When enabled remote servers may connect to this
  120.      * server using the server dialback method and this server may try the server dialback method
  121.      * to connect to remote servers.<p>
  122.      *
  123.      * When TLS is enabled between servers and server dialback method is enabled then TLS is going
  124.      * to be tried first, when connecting to a remote server, and if TLS fails then server dialback
  125.      * is going to be used as a last resort. If enabled and the remote server offered server-dialback
  126.      * after TLS and no SASL EXTERNAL then server dialback will be used.
  127.      *
  128.      * @return true if server dialback is enabled.
  129.      */
  130.     public static boolean isEnabled() {
  131.         return JiveGlobals.getBooleanProperty("xmpp.server.dialback.enabled", true);
  132.     }
  133.  
  134.     /**
  135.      * Returns true if server dialback can be used when the remote server presented a self-signed
  136.      * certificate. During TLS the remote server can present a self-signed certificate, if this
  137.      * setting is enabled then the self-signed certificate will be accepted and if SASL EXTERNAL
  138.      * is not offered then server dialback will be used for verifying the remote server.<p>
  139.      *
  140.      * If self-signed certificates are accepted then server dialback over TLS is enabled.
  141.      *
  142.      * @return true if server dialback can be used when the remote server presented a self-signed
  143.      * certificate.
  144.      */
  145.     public static boolean isEnabledForSelfSigned() {
  146.         return JiveGlobals.getBooleanProperty("xmpp.server.certificate.accept-selfsigned", false);
  147.     }
  148.  
  149.     /**
  150.      * Sets if server dialback can be used when the remote server presented a self-signed
  151.      * certificate. During TLS the remote server can present a self-signed certificate, if this
  152.      * setting is enabled then the self-signed certificate will be accepted and if SASL EXTERNAL
  153.      * is not offered then server dialback will be used for verifying the remote server.<p>
  154.      *
  155.      * If self-signed certificates are accepted then server dialback over TLS is enabled.
  156.      *
  157.      * @param enabled if server dialback can be used when the remote server presented a self-signed
  158.      * certificate.
  159.      */
  160.     public static void setEnabledForSelfSigned(boolean enabled) {
  161.         JiveGlobals.setProperty("xmpp.server.certificate.accept-selfsigned", Boolean.toString(enabled));
  162.     }
  163.  
  164.     /**
  165.      * Creates a new instance that will be used for creating {@link IncomingServerSession},
  166.      * validating subsequent domains or authenticatig new domains. Use
  167.      * {@link #createIncomingSession(org.dom4j.io.XMPPPacketReader)} for creating a new server
  168.      * session used for receiving packets from the remote server. Use
  169.      * {@link #validateRemoteDomain(org.dom4j.Element, org.jivesoftware.openfire.StreamID)} for
  170.      * validating subsequent domains and use
  171.      * {@link #authenticateDomain(OutgoingServerSocketReader, String, String, String)} for
  172.      * registering new domains that are allowed to send packets to the remote server.<p>
  173.      *
  174.      * For validating domains a new TCP connection will be established to the Authoritative Server.
  175.      * The Authoritative Server may be the same Originating Server or some other machine in the
  176.      * Originating Server's network. Once the remote domain gets validated the Originating Server
  177.      * will be allowed for sending packets to this server. However, this server will need to
  178.      * validate its domain/s with the Originating Server if this server needs to send packets to
  179.      * the Originating Server. Another TCP connection will be established for validation this
  180.      * server domain/s and for sending packets to the Originating Server.
  181.      *
  182.      * @param connection the connection created by the remote server.
  183.      * @param serverName the name of the local server.
  184.      */
  185.     public ServerDialback(Connection connection, String serverName) {
  186.         this.connection = connection;
  187.         this.serverName = serverName;
  188.     }
  189.  
  190.     public ServerDialback() {
  191.     }
  192.  
  193.     /**
  194.      * Creates a new connection from the Originating Server to the Receiving Server for
  195.      * authenticating the specified domain.
  196.      *
  197.      * @param localDomain domain of the Originating Server to authenticate with the Receiving Server.
  198.      * @param remoteDomain IP address or hostname of the Receiving Server.
  199.      * @param port port of the Receiving Server.
  200.      * @return an OutgoingServerSession if the domain was authenticated or <tt>null</tt> if none.
  201.      */
  202.     public LocalOutgoingServerSession createOutgoingSession(String localDomain, String remoteDomain, int port) {
  203.         String hostname = null;
  204.         int realPort = port;
  205.         try {
  206.             // Establish a TCP connection to the Receiving Server
  207.             Socket socket = new Socket();
  208.             // Get a list of real hostnames to connect to using DNS lookup of the specified hostname
  209.             List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(remoteDomain, port);
  210.             for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) {
  211.                 try {
  212.                     DNSUtil.HostAddress address = it.next();
  213.                     hostname = address.getHost();
  214.                     realPort = address.getPort();
  215.                     Log.debug("ServerDialback: OS - Trying to connect to " + remoteDomain + ":" + port +
  216.                             "(DNS lookup: " + hostname + ":" + realPort + ")");
  217.                     // Establish a TCP connection to the Receiving Server
  218.                     socket.connect(new InetSocketAddress(hostname, realPort),
  219.                             RemoteServerManager.getSocketTimeout());
  220.                     Log.debug("ServerDialback: OS - Connection to " + remoteDomain + ":" + port + " successful");
  221.                     break;
  222.                 }
  223.                 catch (Exception e) {
  224.                     Log.warn("Error trying to connect to remote server: " + remoteDomain +
  225.                             "(DNS lookup: " + hostname + ":" + realPort + ")", e);
  226.                 }
  227.             }
  228.             connection =
  229.                     new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
  230.                             false);
  231.             // Get a writer for sending the open stream tag
  232.             // Send to the Receiving Server a stream header
  233.             StringBuilder stream = new StringBuilder();
  234.             stream.append("<stream:stream");
  235.             stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
  236.             stream.append(" xmlns=\"jabber:server\"");
  237.             stream.append(" to=\"").append(remoteDomain).append("\"");
  238.             stream.append(" from=\"").append(localDomain).append("\"");
  239.             stream.append(" xmlns:db=\"jabber:server:dialback\"");
  240.             stream.append(" version=\"1.0\">");
  241.             connection.deliverRawText(stream.toString());
  242.  
  243.             // Set a read timeout (of 5 seconds) so we don't keep waiting forever
  244.             int soTimeout = socket.getSoTimeout();
  245.             socket.setSoTimeout(RemoteServerManager.getSocketTimeout());
  246.  
  247.             XMPPPacketReader reader = new XMPPPacketReader();
  248.             reader.setXPPFactory(FACTORY);
  249.             reader.getXPPParser().setInput(new InputStreamReader(
  250.                     ServerTrafficCounter.wrapInputStream(socket.getInputStream()), CHARSET));
  251.             // Get the answer from the Receiving Server
  252.             XmlPullParser xpp = reader.getXPPParser();
  253.             for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
  254.                 eventType = xpp.next();
  255.             }
  256.             if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
  257.                 // Restore default timeout
  258.                 socket.setSoTimeout(soTimeout);
  259.                 String id = xpp.getAttributeValue("", "id");
  260.                 OutgoingServerSocketReader socketReader = new OutgoingServerSocketReader(reader);
  261.                 if (authenticateDomain(socketReader, localDomain, remoteDomain, id)) {
  262.                     // Domain was validated so create a new OutgoingServerSession
  263.                     StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
  264.                     LocalOutgoingServerSession session = new LocalOutgoingServerSession(localDomain, connection, socketReader, streamID);
  265.                     connection.init(session);
  266.                     // Set the hostname as the address of the session
  267.                     session.setAddress(new JID(null, remoteDomain, null));
  268.                     return session;
  269.                 }
  270.                 else {
  271.                     // Close the connection
  272.                     connection.close();
  273.                 }
  274.             }
  275.             else {
  276.                 Log.debug("ServerDialback: OS - Invalid namespace in packet: " + 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 (IOException e) {
  285.             Log.debug("ServerDialback: Error connecting to the remote server: " + remoteDomain + "(DNS lookup: " +
  286.                     hostname + ":" + realPort + ")", e);
  287.             // Close the connection
  288.             if (connection != null) {
  289.                 connection.close();
  290.             }
  291.         }
  292.         catch (Exception e) {
  293.             Log.error("Error creating outgoing session to remote server: " + remoteDomain +
  294.                     "(DNS lookup: " +
  295.                     hostname +
  296.                     ")",
  297.                     e);
  298.             // Close the connection
  299.             if (connection != null) {
  300.                 connection.close();
  301.             }
  302.         }
  303.         return null;
  304.     }
  305.  
  306.     /**
  307.      * Authenticates the Originating Server domain with the Receiving Server. Once the domain has
  308.      * been authenticated the Receiving Server will start accepting packets from the Originating
  309.      * Server.<p>
  310.      *
  311.      * The Receiving Server will connect to the Authoritative Server to verify the dialback key.
  312.      * Most probably the Originating Server machine will be the Authoritative Server too.
  313.      *
  314.      * @param socketReader the reader to use for reading the answer from the Receiving Server.
  315.      * @param domain the domain to authenticate.
  316.      * @param hostname the hostname of the remote server (i.e. Receiving Server).
  317.      * @param id the stream id to be used for creating the dialback key.
  318.      * @return true if the Receiving Server authenticated the domain with the Authoritative Server.
  319.      */
  320.     public boolean authenticateDomain(OutgoingServerSocketReader socketReader, String domain,
  321.             String hostname, String id) {
  322.         String key = AuthFactory.createDigest(id, getSecretkey());
  323.         Log.debug("ServerDialback: OS - Sent dialback key to host: " + hostname + " id: " + id + " from domain: " +
  324.                 domain);
  325.  
  326.         synchronized (socketReader) {
  327.             // Send a dialback key to the Receiving Server
  328.             StringBuilder sb = new StringBuilder();
  329.             sb.append("<db:result");
  330.             sb.append(" from=\"").append(domain).append("\"");
  331.             sb.append(" to=\"").append(hostname).append("\">");
  332.             sb.append(key);
  333.             sb.append("</db:result>");
  334.             connection.deliverRawText(sb.toString());
  335.  
  336.             // Process the answer from the Receiving Server
  337.             try {
  338.                 while (true) {
  339.                     Element doc = socketReader.getElement(RemoteServerManager.getSocketTimeout(),
  340.                             TimeUnit.MILLISECONDS);
  341.                     if (doc == null) {
  342.                         Log.debug("ServerDialback: OS - Time out waiting for answer in validation from: " + hostname +
  343.                                 " id: " +
  344.                                 id +
  345.                                 " for domain: " +
  346.                                 domain);
  347.                         return false;
  348.                     }
  349.                     else if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) {
  350.                         boolean success = "valid".equals(doc.attributeValue("type"));
  351.                         Log.debug("ServerDialback: OS - Validation " + (success ? "GRANTED" : "FAILED") + " from: " +
  352.                                 hostname +
  353.                                 " id: " +
  354.                                 id +
  355.                                 " for domain: " +
  356.                                 domain);
  357.                         return success;
  358.                     }
  359.                     else {
  360.                         Log.warn("ServerDialback: OS - Ignoring unexpected answer in validation from: " + hostname + " id: " +
  361.                                 id +
  362.                                 " for domain: " +
  363.                                 domain +
  364.                                 " answer:" +
  365.                                 doc.asXML());
  366.                     }
  367.                 }
  368.             }
  369.             catch (InterruptedException e) {
  370.                 Log.debug("ServerDialback: OS - Validation FAILED from: " + hostname +
  371.                         " id: " +
  372.                         id +
  373.                         " for domain: " +
  374.                         domain, e);
  375.                 return false;
  376.             }
  377.         }
  378.     }
  379.  
  380.     /**
  381.      * Returns a new {@link IncomingServerSession} with a domain validated by the Authoritative
  382.      * Server. New domains may be added to the returned IncomingServerSession after they have
  383.      * been validated. See
  384.      * {@link LocalIncomingServerSession#validateSubsequentDomain(org.dom4j.Element)}. The remote
  385.      * server will be able to send packets through this session whose domains were previously
  386.      * validated.<p>
  387.      *
  388.      * When acting as an Authoritative Server this method will verify the requested key
  389.      * and will return null since the underlying TCP connection will be closed after sending the
  390.      * response to the Receiving Server.<p>
  391.      *
  392.      * @param reader reader of DOM documents on the connection to the remote server.
  393.      * @return an IncomingServerSession that was previously validated against the remote server.
  394.      * @throws IOException if an I/O error occurs while communicating with the remote server.
  395.      * @throws XmlPullParserException if an error occurs while parsing XML packets.
  396.      */
  397.     public LocalIncomingServerSession createIncomingSession(XMPPPacketReader reader) throws IOException,
  398.             XmlPullParserException {
  399.         XmlPullParser xpp = reader.getXPPParser();
  400.         StringBuilder sb;
  401.         if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
  402.             Log.debug("ServerDialback: Processing incoming session.");
  403.  
  404.             StreamID streamID = sessionManager.nextStreamID();
  405.  
  406.             sb = new StringBuilder();
  407.             sb.append("<stream:stream");
  408.             sb.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
  409.             sb.append(" xmlns=\"jabber:server\" xmlns:db=\"jabber:server:dialback\"");
  410.             sb.append(" id=\"");
  411.             sb.append(streamID.toString());
  412.             sb.append("\">");
  413.             connection.deliverRawText(sb.toString());
  414.  
  415.             try {
  416.                 Element doc = reader.parseDocument().getRootElement();
  417.                 if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) {
  418.                     String hostname = doc.attributeValue("from");
  419.                     String recipient = doc.attributeValue("to");
  420.                     Log.debug("ServerDialback: RS - Validating remote domain for incoming session from {} to {}", hostname, recipient);
  421.                     if (validateRemoteDomain(doc, streamID)) {
  422.                         Log.debug("ServerDialback: RS - Validation of remote domain for incoming session from {} to {} was successful.", hostname, recipient);
  423.                         // Create a server Session for the remote server
  424.                         LocalIncomingServerSession session = sessionManager.
  425.                                 createIncomingServerSession(connection, streamID);
  426.                         // Add the validated domain as a valid domain
  427.                         session.addValidatedDomain(hostname);
  428.                         // Set the domain or subdomain of the local server used when
  429.                         // validating the session
  430.                         session.setLocalDomain(recipient);
  431.                         return session;
  432.                     } else {
  433.                         Log.debug("ServerDialback: RS - Validation of remote domain for incoming session from {} to {} was not successful.", hostname, recipient);
  434.                         return null;
  435.                     }
  436.                 }
  437.                 else if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) {
  438.                     // When acting as an Authoritative Server the Receiving Server will send a
  439.                     // db:verify packet for verifying a key that was previously sent by this
  440.                     // server when acting as the Originating Server
  441.                     verifyReceivedKey(doc, connection);
  442.                     // Close the underlying connection
  443.                     connection.close();
  444.                     String verifyFROM = doc.attributeValue("from");
  445.                     String id = doc.attributeValue("id");
  446.                     Log.debug("ServerDialback: AS - Connection closed for host: " + verifyFROM + " id: " + id);
  447.                     return null;
  448.                 }
  449.                 else {
  450.                     Log.debug("ServerDialback: Received an invalid/unknown packet while trying to process an incoming session: {}", doc.asXML());
  451.                     // The remote server sent an invalid/unknown packet
  452.                     connection.deliverRawText(
  453.                             new StreamError(StreamError.Condition.invalid_xml).toXML());
  454.                     // Close the underlying connection
  455.                     connection.close();
  456.                     return null;
  457.                 }
  458.             }
  459.             catch (Exception e) {
  460.                 Log.error("An error occured while creating a server session", e);
  461.                 // Close the underlying connection
  462.                 connection.close();
  463.                 return null;
  464.             }
  465.  
  466.         }
  467.         else {
  468.             Log.debug("ServerDialback: Received a stanza in an invalid namespace while trying to process an incoming session: {}", xpp.getNamespace("db"));
  469.             connection.deliverRawText(
  470.                     new StreamError(StreamError.Condition.invalid_namespace).toXML());
  471.             // Close the underlying connection
  472.             connection.close();
  473.             return null;
  474.         }
  475.     }
  476.  
  477.     /**
  478.      * Returns true if the domain requested by the remote server was validated by the Authoritative
  479.      * Server. To validate the domain a new TCP connection will be established to the
  480.      * Authoritative Server. The Authoritative Server may be the same Originating Server or
  481.      * some other machine in the Originating Server's network.<p>
  482.      *
  483.      * If the domain was not valid or some error occurred while validating the domain then the
  484.      * underlying TCP connection will be closed.
  485.      *
  486.      * @param doc the request for validating the new domain.
  487.      * @param streamID the stream id generated by this server for the Originating Server.
  488.      * @return true if the requested domain is valid.
  489.      */
  490.     public boolean validateRemoteDomain(Element doc, StreamID streamID) {
  491.         StringBuilder sb;
  492.         String recipient = doc.attributeValue("to");
  493.         String hostname = doc.attributeValue("from");
  494.         Log.debug("ServerDialback: RS - Received dialback key from host: " + hostname + " to: " + recipient);
  495.         if (!RemoteServerManager.canAccess(hostname)) {
  496.             // Remote server is not allowed to establish a connection to this server
  497.             connection.deliverRawText(new StreamError(StreamError.Condition.host_unknown).toXML());
  498.             // Close the underlying connection
  499.             connection.close();
  500.             Log.debug("ServerDialback: RS - Error, hostname is not allowed to establish a connection to " +
  501.                     "this server: " +
  502.                     recipient);
  503.             return false;
  504.         }
  505.         else if (isHostUnknown(recipient)) {
  506.             // address does not match a recognized hostname
  507.             connection.deliverRawText(new StreamError(StreamError.Condition.host_unknown).toXML());
  508.             // Close the underlying connection
  509.             connection.close();
  510.             Log.debug("ServerDialback: RS - Error, hostname not recognized: " + recipient);
  511.             return false;
  512.         }
  513.         else {
  514.             // Check if the remote server already has a connection to the target domain/subdomain
  515.             boolean alreadyExists = false;
  516.             for (IncomingServerSession session : sessionManager.getIncomingServerSessions(hostname)) {
  517.                 if (recipient.equals(session.getLocalDomain())) {
  518.                     alreadyExists = true;
  519.                 }
  520.             }
  521.             if (alreadyExists && !sessionManager.isMultipleServerConnectionsAllowed()) {
  522.                 // Remote server already has a IncomingServerSession created
  523.                 connection.deliverRawText(
  524.                         new StreamError(StreamError.Condition.not_authorized).toXML());
  525.                 // Close the underlying connection
  526.                 connection.close();
  527.                 Log.debug("ServerDialback: RS - Error, incoming connection already exists from: " + hostname);
  528.                 return false;
  529.             }
  530.             else {
  531.                 String key = doc.getTextTrim();
  532.  
  533.                 // Get a list of real hostnames and try to connect using DNS lookup of the specified domain
  534.                 List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname,
  535.                         RemoteServerManager.getPortForServer(hostname));
  536.                 Socket socket = new Socket();
  537.                 String realHostname = null;
  538.                 int realPort;
  539.                 for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) {
  540.                     try {
  541.                         DNSUtil.HostAddress address = it.next();
  542.                         realHostname = address.getHost();
  543.                         realPort = address.getPort();
  544.                         Log.debug("ServerDialback: RS - Trying to connect to Authoritative Server: " + hostname +
  545.                                 "(DNS lookup: " + realHostname + ":" + realPort + ")");
  546.                         // Establish a TCP connection to the Receiving Server
  547.                         socket.connect(new InetSocketAddress(realHostname, realPort),
  548.                                 RemoteServerManager.getSocketTimeout());
  549.                         Log.debug("ServerDialback: RS - Connection to AS: " + hostname + " successful");
  550.                         break;
  551.                     }
  552.                     catch (Exception e) {
  553.                         Log.warn("Error trying to connect to remote server: " + hostname +
  554.                                 "(DNS lookup: " + realHostname + ")", e);
  555.                     }
  556.                 }
  557.                 if (!socket.isConnected()) {
  558.                     Log.warn("No server available for verifying key of remote server: " + hostname);
  559.                     return false;
  560.                 }
  561.  
  562.                 try {
  563.                     Log.debug("verifyKey before");
  564.                     boolean valid = verifyKey(key, streamID.toString(), recipient, hostname, socket);
  565.                     //boolean valid = true;
  566.                    
  567.                     Log.debug("ServerDialback: RS - Sending key verification result to OS: " + hostname);
  568.                     Log.debug("ServerDialback: RS - Valid: " + valid);
  569.                     sb = new StringBuilder();
  570.                     sb.append("<db:result");
  571.                     sb.append(" from=\"").append(recipient).append("\"");
  572.                     sb.append(" to=\"").append(hostname).append("\"");
  573.                     sb.append(" type=\"");
  574.                     sb.append(valid ? "valid" : "invalid");
  575.                     sb.append("\"/>");
  576.                     Log.debug("ServerDialback: RS - sb: " + sb.toString());
  577.                     connection.deliverRawText(sb.toString());
  578.  
  579.                     if (!valid) {
  580.                         // Close the underlying connection
  581.                         connection.close();
  582.                     }
  583.                     return valid;
  584.                 }
  585.                 catch (Exception e) {
  586.                     Log.warn("Error verifying key of remote server: " + hostname, e);
  587.                     // Send a <remote-connection-failed/> stream error condition
  588.                     // and terminate both the XML stream and the underlying
  589.                     // TCP connection
  590.                     connection.deliverRawText(new StreamError(
  591.                             StreamError.Condition.remote_connection_failed).toXML());
  592.                     // Close the underlying connection
  593.                     connection.close();
  594.                     return false;
  595.                 }
  596.             }
  597.         }
  598.     }
  599.  
  600.     private boolean isHostUnknown(String recipient) {
  601.         boolean host_unknown = !serverName.equals(recipient);
  602.         // If the recipient does not match the serverName then check if it matches a subdomain. This
  603.         // trick is useful when subdomains of this server are registered in the DNS so remote
  604.         // servers may establish connections directly to a subdomain of this server
  605.         if (host_unknown && recipient.contains(serverName)) {
  606.             host_unknown = !routingTable.hasComponentRoute(new JID(recipient));
  607.         }
  608.         return host_unknown;
  609.     }
  610.  
  611.     /**
  612.      * Verifies the key with the Authoritative Server.
  613.      */
  614.     private boolean verifyKey(String key, String streamID, String recipient, String hostname,
  615.             Socket socket) throws IOException, XmlPullParserException,
  616.             RemoteConnectionFailedException {
  617.        
  618.         Log.debug("ServerDialback: RS - (verifyKey) key: " + key);
  619.         Log.debug("ServerDialback: RS - (verifyKey) streamID: " + streamID);
  620.         Log.debug("ServerDialback: RS - (verifyKey) recipient: " + recipient);
  621.         Log.debug("ServerDialback: RS - (verifyKey) hostname: " + hostname);
  622.         Log.debug("ServerDialback: RS - (verifyKey) socket: " + socket.toString());
  623.        
  624.         XMPPPacketReader reader;
  625.         Writer writer = null;
  626.         // Set a read timeout
  627.         socket.setSoTimeout(RemoteServerManager.getSocketTimeout());
  628.         try {
  629.             reader = new XMPPPacketReader();
  630.             reader.setXPPFactory(FACTORY);
  631.  
  632.             reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(),
  633.                     CHARSET));
  634.             // Get a writer for sending the open stream tag
  635.             writer =
  636.                     new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),
  637.                             CHARSET));
  638.             // Send the Authoritative Server a stream header
  639.             StringBuilder stream = new StringBuilder();
  640.             stream.append("<stream:stream");
  641.             stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
  642.             stream.append(" xmlns=\"jabber:server\"");
  643.             stream.append(" xmlns:db=\"jabber:server:dialback\">");
  644.             writer.write(stream.toString());
  645.             writer.flush();
  646.             Log.debug("ServerDialback: RS - (verifyKey) sending: " + stream.toString());
  647.  
  648.             // Get the answer from the Authoritative Server
  649.             XmlPullParser xpp = reader.getXPPParser();
  650.             for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
  651.                 eventType = xpp.next();
  652.             }
  653.            
  654.             if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
  655.                 Log.debug("ServerDialback: RS - (verifyKey) Asking AS to verify dialback key for id" + streamID);
  656.                 // Request for verification of the key
  657.                 StringBuilder sb = new StringBuilder();
  658.                 sb.append("<db:verify");
  659.                 sb.append(" from=\"").append(recipient).append("\"");
  660.                 sb.append(" to=\"").append(hostname).append("\"");
  661.                 sb.append(" id=\"").append(streamID).append("\">");
  662.                 sb.append(key);
  663.                 sb.append("</db:verify>");
  664.                 writer.write(sb.toString());
  665.                 writer.flush();
  666.                
  667.                 Log.debug("ServerDialback: RS - (verifyKey) sending: " + sb.toString());
  668.                
  669.                 try
  670.                 {
  671.                     Element doc = reader.parseDocument().getRootElement();
  672.                     Log.debug("ServerDialback: RS - (verifyKey) doc: " + doc.asXML());
  673.                    
  674.                     if(!"db".equals(doc.getNamespacePrefix()) && !"verify".equals(doc.getName()))
  675.                     {
  676.                         doc = reader.parseDocument().getRootElement();
  677.                         Log.debug("ServerDialback: RS - (verifyKey) doc: " + doc.asXML());
  678.                     }
  679.                    
  680.                     if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) {
  681.                    
  682.                         Log.debug("ServerDialback: RS - (verifyKey) - db verify");
  683.                         if (!streamID.equals(doc.attributeValue("id"))) {
  684.                             Log.debug("ServerDialback: RS - (verifyKey) - no id in db");
  685.                             // Include the invalid-id stream error condition in the response
  686.                             writer.write(new StreamError(StreamError.Condition.invalid_id).toXML());
  687.                             writer.flush();
  688.                             // Thrown an error so <remote-connection-failed/> stream error
  689.                             // condition is sent to the Originating Server
  690.                             throw new RemoteConnectionFailedException("Invalid ID");
  691.                         }
  692.                         else if (isHostUnknown(doc.attributeValue("to"))) {
  693.                             Log.debug("ServerDialback: RS - (verifyKey) - host unknow");
  694.                             // Include the host-unknown stream error condition in the response
  695.                             writer.write(
  696.                                     new StreamError(StreamError.Condition.host_unknown).toXML());
  697.                             writer.flush();
  698.                             // Thrown an error so <remote-connection-failed/> stream error
  699.                             // condition is sent to the Originating Server
  700.                             throw new RemoteConnectionFailedException("Host unknown");
  701.                         }
  702.                         else if (!hostname.equals(doc.attributeValue("from"))) {
  703.                             Log.debug("ServerDialback: RS - (verifyKey) - invalid from");
  704.                             // Include the invalid-from stream error condition in the response
  705.                             writer.write(
  706.                                     new StreamError(StreamError.Condition.invalid_from).toXML());
  707.                             writer.flush();
  708.                             // Thrown an error so <remote-connection-failed/> stream error
  709.                             // condition is sent to the Originating Server
  710.                             throw new RemoteConnectionFailedException("Invalid From");
  711.                         }
  712.                         else {
  713.                             boolean valid = "valid".equals(doc.attributeValue("type"));
  714.                             Log.debug("ServerDialback: RS - (verifyKey) - Key was " + (valid ? "" : "NOT ") +
  715.                                     "VERIFIED by the Authoritative Server for: " + hostname);
  716.                             return valid;
  717.                         }
  718.                     }
  719.                     else {
  720.                         Log.debug("ServerDialback: RS - (verifyKey) - db:verify answer was: " + doc.asXML());
  721.                     }
  722.                 }
  723.                 catch (DocumentException e) {
  724.                     Log.error("An error occured connecting to the Authoritative Server", e);
  725.                     // Thrown an error so <remote-connection-failed/> stream error condition is
  726.                     // sent to the Originating Server
  727.                     throw new RemoteConnectionFailedException("Error connecting to the Authoritative Server");
  728.                 }
  729.  
  730.             }
  731.             else {
  732.                 // Include the invalid-namespace stream error condition in the response
  733.                 writer.write(new StreamError(StreamError.Condition.invalid_namespace).toXML());
  734.                 writer.flush();
  735.                 // Thrown an error so <remote-connection-failed/> stream error condition is
  736.                 // sent to the Originating Server
  737.                 throw new RemoteConnectionFailedException("Invalid namespace");
  738.             }
  739.         }
  740.         finally {
  741.             try {
  742.                 Log.debug("ServerDialback: RS - Closing connection to Authoritative Server: " + hostname);
  743.                 // Close the stream
  744.                 StringBuilder sb = new StringBuilder();
  745.                 sb.append("</stream:stream>");
  746.                 writer.write(sb.toString());
  747.                 writer.flush();
  748.                 // Close the TCP connection
  749.                 socket.close();
  750.             }
  751.             catch (IOException ioe) {
  752.                 // Do nothing
  753.             }
  754.         }
  755.         return false;
  756.     }
  757.  
  758.     /**
  759.      * Verifies the key sent by a Receiving Server. This server will be acting as the
  760.      * Authoritative Server when executing this method. The remote server may have established
  761.      * a new connection to the Authoritative Server (i.e. this server) for verifying the key
  762.      * or it may be reusing an existing incoming connection.
  763.      *
  764.      * @param doc the Element that contains the key to verify.
  765.      * @param connection the connection to use for sending the verification result
  766.      * @return true if the key was verified.
  767.      */
  768.     public static boolean verifyReceivedKey(Element doc, Connection connection) {
  769.         String verifyFROM = doc.attributeValue("from");
  770.         String verifyTO = doc.attributeValue("to");
  771.         String key = doc.getTextTrim();
  772.         String id = doc.attributeValue("id");
  773.         Log.debug("ServerDialback: AS - Verifying key for host: " + verifyFROM + " id: " + id);
  774.  
  775.         // TODO If the value of the 'to' address does not match a recognized hostname,
  776.         // then generate a <host-unknown/> stream error condition
  777.         // TODO If the value of the 'from' address does not match the hostname
  778.         // represented by the Receiving Server when opening the TCP connection, then
  779.         // generate an <invalid-from/> stream error condition
  780.  
  781.         // Verify the received key
  782.         // Created the expected key based on the received ID value and the shared secret
  783.         String expectedKey = AuthFactory.createDigest(id, getSecretkey());
  784.         Log.debug("ExpectedKey: " + expectedKey);
  785.         Log.debug("key:         " + key);
  786.        
  787.         boolean verified = expectedKey.equals(key);
  788.         Log.debug("verified: " + verified);
  789.  
  790.         // Send the result of the key verification
  791.         StringBuilder sb = new StringBuilder();
  792.         sb.append("<db:verify");
  793.         sb.append(" from=\"").append(verifyTO).append("\"");
  794.         sb.append(" to=\"").append(verifyFROM).append("\"");
  795.         sb.append(" type=\"");
  796.         sb.append(verified ? "valid" : "invalid");
  797.         sb.append("\" id=\"").append(id).append("\"/>");
  798.         connection.deliverRawText(sb.toString());
  799.         Log.debug("ServerDialback: AS - Key was: " + (verified ? "VALID" : "INVALID") + " for host: " +
  800.                 verifyFROM +
  801.                 " id: " +
  802.                 id);
  803.         return verified;
  804.     }
  805.  
  806.     /**
  807.      * Returns the secret key that was randomly generated. When running inside of a cluster
  808.      * the key will be unique to all cluster nodes.
  809.      *
  810.      * @return the secret key that was randomly generated.
  811.      */
  812.     private static String getSecretkey() {
  813.         String key = "secretKey";
  814.         Lock lock = CacheFactory.getLock(key, secretKeyCache);
  815.         try {
  816.             lock.lock();
  817.             String secret = secretKeyCache.get(key);
  818.             if (secret == null) {
  819.                 secret = StringUtils.randomString(10);
  820.                 secretKeyCache.put(key, secret);
  821.             }
  822.             return secret;
  823.         }
  824.         finally {
  825.             lock.unlock();
  826.         }
  827.     }
  828. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement