LocalIncomingServerSession.java 15.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/**
 * $RCSfile: $
 * $Revision: $
 * $Date: $
 *
 * Copyright (C) 2007 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */
package org.jivesoftware.openfire.session;

import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.SocketConnection;
22
import org.jivesoftware.openfire.net.StreamCompressionManager;
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
import org.jivesoftware.openfire.server.ServerDialback;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.Packet;
import org.xmpp.packet.StreamError;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * Server-to-server communication is done using two TCP connections between the servers. One
 * connection is used for sending packets while the other connection is used for receiving packets.
 * The <tt>IncomingServerSession</tt> represents the connection to a remote server that will only
 * be used for receiving packets.<p>
 *
 * Currently only the Server Dialback method is being used for authenticating the remote server.
 * Once the remote server has been authenticated incoming packets will be processed by this server.
 * It is also possible for remote servers to authenticate more domains once the session has been
 * established. For optimization reasons the existing connection is used between the servers.
 * Therefore, the incoming server session holds the list of authenticated domains which are allowed
 * to send packets to this server.<p>
 *
 * Using the Server Dialback method it is possible that this server may also act as the
 * Authoritative Server. This implies that an incoming connection will be established with this
 * server for authenticating a domain. This incoming connection will only last for a brief moment
 * and after the domain has been authenticated the connection will be closed and no session will
 * exist.
 *
 * @author Gaston Dombiak
 */
public class LocalIncomingServerSession extends LocalSession implements IncomingServerSession {
    /**
     * List of domains, subdomains and virtual hostnames of the remote server that were
     * validated with this server. The remote server is allowed to send packets to this
     * server from any of the validated domains.
     */
    private Set<String> validatedDomains = new HashSet<String>();

    /**
     * Domains or subdomain of this server that was used by the remote server
     * when validating the new connection. This information is useful to prevent
     * many connections from the same remote server to the same local domain.
     */
    private String localDomain = null;

    /**
     * Creates a new session that will receive packets. The new session will be authenticated
     * before being returned. If the authentication process fails then the answer will be
     * <tt>null</tt>.<p>
     *
     * Currently the Server Dialback method is the only way to authenticate a remote server. Since
     * Server Dialback requires an Authoritative Server, it is possible for this server to receive
     * an incoming connection that will only exist until the requested domain has been validated.
     * In this case, this method will return <tt>null</tt> since the connection is closed after
     * the domain was validated. See
     * {@link org.jivesoftware.openfire.server.ServerDialback#createIncomingSession(org.dom4j.io.XMPPPacketReader)} for more
     * information.
     *
     * @param serverName hostname of this server.
     * @param reader reader on the new established connection with the remote server.
     * @param connection the new established connection with the remote server.
     * @return a new session that will receive packets or null if a problem occured while
     *         authenticating the remote server or when acting as the Authoritative Server during
     *         a Server Dialback authentication process.
     * @throws org.xmlpull.v1.XmlPullParserException if an error occurs while parsing the XML.
     * @throws java.io.IOException if an input/output error occurs while using the connection.
     */
    public static LocalIncomingServerSession createSession(String serverName, XMPPPacketReader reader,
            SocketConnection connection) throws XmlPullParserException, IOException {
        XmlPullParser xpp = reader.getXPPParser();
        if (xpp.getNamespace("db") != null) {
            // Server is trying to establish connection and authenticate using server dialback
            if (ServerDialback.isEnabled()) {
                ServerDialback method = new ServerDialback(connection, serverName);
                return method.createIncomingSession(reader);
            }
            Log.debug("Server dialback is disabled. Rejecting connection: " + connection);
        }
        String version = xpp.getAttributeValue("", "version");
        int[] serverVersion = version != null ? decodeVersion(version) : new int[] {0,0};
        if (serverVersion[0] >= 1) {
            // Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection
            if (JiveGlobals.getBooleanProperty("xmpp.server.tls.enabled", true)) {
                try {
                    return createIncomingSession(connection, serverName);
                }
                catch (Exception e) {
                    Log.error("Error establishing connection from remote server", e);
                }
            }
            else {
                connection.deliverRawText(
                        new StreamError(StreamError.Condition.invalid_namespace).toXML());
                Log.debug("Server TLS is disabled. Rejecting connection: " + connection);
            }
        }
        // Close the connection since remote server is not XMPP 1.0 compliant and is not
        // using server dialback to establish and authenticate the connection
        connection.close();
        return null;
    }

    /**
     * Returns a new incoming server session pending to be authenticated. The remote server
     * will be notified that TLS and SASL are available. The remote server will be able to
     * send packets to this server only after SASL authentication has been finished.
     *
     * @param connection the new established connection with the remote server.
     * @param serverName hostname of this server.
     * @return a new incoming server session pending to be authenticated.
     * @throws org.jivesoftware.openfire.auth.UnauthorizedException if this server is being shutdown.
     */
    private static LocalIncomingServerSession createIncomingSession(SocketConnection connection, String serverName)
            throws UnauthorizedException {
        // Get the stream ID for the new session
        StreamID streamID = SessionManager.getInstance().nextStreamID();
        // Create a server Session for the remote server
        LocalIncomingServerSession session =
                SessionManager.getInstance().createIncomingServerSession(connection, streamID);

        // Send the stream header
        StringBuilder openingStream = new StringBuilder();
        openingStream.append("<stream:stream");
        openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
        openingStream.append(" xmlns=\"jabber:server\"");
        openingStream.append(" from=\"").append(serverName).append("\"");
        openingStream.append(" id=\"").append(streamID).append("\"");
        openingStream.append(" version=\"1.0\">");
        connection.deliverRawText(openingStream.toString());

        // Indicate the TLS policy to use for this connection
        Connection.TLSPolicy tlsPolicy =
                ServerDialback.isEnabled() ? Connection.TLSPolicy.optional :
                        Connection.TLSPolicy.required;
        boolean hasCertificates = false;
        try {
            hasCertificates = SSLConfig.getKeyStore().size() > 0;
        }
        catch (Exception e) {
            Log.error(e);
        }
        if (Connection.TLSPolicy.required == tlsPolicy && !hasCertificates) {
            Log.error("Server session rejected. TLS is required but no certificates " +
                    "were created.");
            return null;
        }
        connection.setTlsPolicy(hasCertificates ? tlsPolicy : Connection.TLSPolicy.disabled);

        // Indicate the compression policy to use for this connection
        String policyName = JiveGlobals.getProperty("xmpp.server.compression.policy",
                Connection.CompressionPolicy.disabled.toString());
        Connection.CompressionPolicy compressionPolicy =
                Connection.CompressionPolicy.valueOf(policyName);
        connection.setCompressionPolicy(compressionPolicy);

        StringBuilder sb = new StringBuilder();
        sb.append("<stream:features>");
        sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
        if (!ServerDialback.isEnabled()) {
            // Server dialback is disabled so TLS is required
            sb.append("<required/>");
        }
        sb.append("</starttls>");
        // Include available SASL Mechanisms
        sb.append(SASLAuthentication.getSASLMechanisms(session));
        sb.append("</stream:features>");
        connection.deliverRawText(sb.toString());

        // Set the domain or subdomain of the local server targeted by the remote server
        session.setLocalDomain(serverName);
        return session;
    }

    public LocalIncomingServerSession(String serverName, Connection connection, StreamID streamID) {
        super(serverName, connection, streamID);
    }

    boolean canProcess(Packet packet) {
        return true;
    }


    void deliver(Packet packet) throws UnauthorizedException {
        // Do nothing
    }

    /**
     * Returns true if the request of a new domain was valid. Sessions may receive subsequent
     * domain validation request. If the validation of the new domain fails then the session and
     * the underlying TCP connection will be closed.<p>
     *
     * For optimization reasons, the same session may be servicing several domains of a
     * remote server.
     *
     * @param dbResult the DOM stanza requesting the domain validation.
     * @return true if the requested domain was valid.
     */
    public boolean validateSubsequentDomain(Element dbResult) {
        ServerDialback method = new ServerDialback(getConnection(), getServerName());
        if (method.validateRemoteDomain(dbResult, getStreamID())) {
            // Add the validated domain as a valid domain
            addValidatedDomain(dbResult.attributeValue("from"));
            return true;
        }
        return false;
    }

    /**
     * Returns true if the specified domain has been validated for this session. The remote
     * server should send a "db:result" packet for registering new subdomains or even
     * virtual hosts.<p>
     *
     * In the spirit of being flexible we allow remote servers to not register subdomains
     * and even so consider subdomains that include the server domain in their domain part
     * as valid domains.
     *
     * @param domain the domain to validate.
     * @return true if the specified domain has been validated for this session.
     */
    public boolean isValidDomain(String domain) {
        // Check if the specified domain is contained in any of the validated domains
        for (String validatedDomain : getValidatedDomains()) {
            if (domain.contains(validatedDomain)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns a collection with all the domains, subdomains and virtual hosts that where
     * validated. The remote server is allowed to send packets from any of these domains,
     * subdomains and virtual hosts.
     *
     * @return domains, subdomains and virtual hosts that where validated.
     */
    public Collection<String> getValidatedDomains() {
        return Collections.unmodifiableCollection(validatedDomains);
    }

    /**
     * Adds a new validated domain, subdomain or virtual host to the list of
     * validated domains for the remote server.
     *
     * @param domain the new validated domain, subdomain or virtual host to add.
     */
    public void addValidatedDomain(String domain) {
        if (validatedDomains.add(domain)) {
            // Register the new validated domain for this server session in SessionManager
            SessionManager.getInstance().registerIncomingServerSession(domain, this);
        }
    }

    /**
     * Removes the previously validated domain from the list of validated domains. The remote
     * server will no longer be able to send packets from the removed domain, subdomain or
     * virtual host.
     *
     * @param domain the domain, subdomain or virtual host to remove from the list of
     *        validated domains.
     */
    public void removeValidatedDomain(String domain) {
        validatedDomains.remove(domain);
        // Unregister the validated domain for this server session in SessionManager
        SessionManager.getInstance().unregisterIncomingServerSession(domain, this);
    }

    /**
     * Returns the domain or subdomain of the local server used by the remote server
     * when validating the session. This information is only used to prevent many
     * connections from the same remote server to the same domain or subdomain of
     * the local server.
     *
     * @return the domain or subdomain of the local server used by the remote server
     *         when validating the session.
     */
    public String getLocalDomain() {
        return localDomain;
    }

    /**
     * Sets the domain or subdomain of the local server used by the remote server when asking
     * to validate the session. This information is only used to prevent many connections from
     * the same remote server to the same domain or subdomain of the local server.
     *
     * @param domain the domain or subdomain of the local server used when validating the
     *        session.
     */
    public void setLocalDomain(String domain) {
        localDomain = domain;
    }

    /**
     * Verifies the received key sent by the remote server. This server is trying to generate
     * an outgoing connection to the remote server and the remote server is reusing an incoming
     * connection for validating the key.
     *
     * @param doc the received Element that contains the key to verify.
     */
    public void verifyReceivedKey(Element doc) {
        ServerDialback.verifyReceivedKey(doc, getConnection());
    }

    public String getAvailableStreamFeatures() {
        // Include Stream Compression Mechanism
        if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled &&
                !conn.isCompressed()) {
335 336 337 338
        	final Element compression = StreamCompressionManager.getStreamCompressionFeature();
        	if (compression != null) {
        		return compression.asXML();
        	}
339 340 341 342 343
        }
        // Nothing special to add
        return null;
    }
}