<commit_before>/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 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.messenger.server;

import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.XPPPacketReader;
import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.auth.AuthFactory;
import org.jivesoftware.messenger.net.DNSUtil;
import org.jivesoftware.messenger.net.SocketConnection;
import org.jivesoftware.messenger.spi.BasicStreamIDFactory;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.StreamError;

import javax.net.SocketFactory;
import java.io.*;
import java.net.Socket;

/**
 * Implementation of the Server Dialback method as defined by the RFC3920.
 *
 * The dialback method follows the following logic to validate the remote server:
 * <ol>
 *  <li>The Originating Server establishes a connection to the Receiving Server.</li>
 *  <li>The Originating Server sends a 'key' value over the connection to the Receiving
 *  Server.</li>
 *  <li>The Receiving Server establishes a connection to the Authoritative Server.</li>
 *  <li>The Receiving Server sends the same 'key' value to the Authoritative Server.</li>
 *  <li>The Authoritative Server replies that key is valid or invalid.</li>
 *  <li>The Receiving Server informs the Originating Server whether it is authenticated or
 *  not.</li>
 * </ol>
 *
 * @author <NAME>
 */
class ServerDialback {
    /**
     * The utf-8 charset for decoding and encoding Jabber packet streams.
     */
    protected static String CHARSET = "UTF-8";
    /**
     * Secret key to be used for encoding and decoding keys used for authentication.
     */
    private static final String secretKey = StringUtils.randomString(10);

    private static XmlPullParserFactory FACTORY = null;

    static {
        try {
            FACTORY = XmlPullParserFactory.newInstance();
        }
        catch (XmlPullParserException e) {
            Log.error("Error creating a parser factory", e);
        }
    }

    private Connection connection;
    private String serverName;
    private SessionManager sessionManager = SessionManager.getInstance();

    /**
     * Creates a new instance that will be used for creating {@link IncomingServerSession},
     * validating subsequent domains or authenticatig new domains. Use
     * {@link #createIncomingSession(org.dom4j.io.XPPPacketReader)} for creating a new server
     * session used for receiving packets from the remote server. Use
     * {@link #validateRemoteDomain(org.dom4j.Element, org.jivesoftware.messenger.StreamID)} for
     * validating subsequent domains and use
     * {@link #authenticateDomain(org.dom4j.io.XPPPacketReader, String, String, String)} for
     * registering new domains that are allowed to send packets to the remote server.<p>
     *
     * For validating domains a new TCP connection will be established to the Authoritative Server.
     * The Authoritative Server may be the same Originating Server or some other machine in the
     * Originating Server's network. Once the remote domain gets validated the Originating Server
     * will be allowed for sending packets to this server. However, this server will need to
     * validate its domain/s with the Originating Server if this server needs to send packets to
     * the Originating Server. Another TCP connection will be established for validation this
     * server domain/s and for sending packets to the Originating Server.
     *
     * @param connection the connection created by the remote server.
     * @param serverName the name of the local server.
     */
    ServerDialback(Connection connection, String serverName) {
        this.connection = connection;
        this.serverName = serverName;
    }

    ServerDialback() {
    }

    /**
     * Creates a new connection from the Originating Server to the Receiving Server for
     * authenticating the specified domain.
     *
     * @param domain domain of the Originating Server to authenticate with the Receiving Server.
     * @param hostname IP address or hostname of the Receiving Server.
     * @param port port of the Receiving Server.
     * @return an OutgoingServerSession if the domain was authenticated or <tt>null</tt> if none.
     */
    public OutgoingServerSession createOutgoingSession(String domain, String hostname, int port) {
        // TODO Check if the hostname is in the blacklist
        try {
            // Establish a TCP connection to the Receiving Server
            Log.debug("OS - Trying to connect to " + hostname + ":" + port);
            Socket socket = SocketFactory.getDefault().createSocket(hostname, port);
            // Set a read timeout of 60 seconds during the dialback operation. Then reset to 0.
            socket.setSoTimeout(60000);
            Log.debug("OS - Connection to " + hostname + ":" + port + " successfull");
            connection =
                    new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
                            false);
            // Get a writer for sending the open stream tag
            // Send to the Receiving Server a stream header
            StringBuilder stream = new StringBuilder();
            stream.append("<stream:stream");
            stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
            stream.append(" xmlns=\"jabber:server\"");
            stream.append(" xmlns:db=\"jabber:server:dialback\">");
            connection.deliverRawText(stream.toString());
            stream = null;

            XPPPacketReader reader = new XPPPacketReader();
            reader.setXPPFactory(XmlPullParserFactory.newInstance());
            reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(),
                    CHARSET));
            // Get the answer from the Receiving Server
            XmlPullParser xpp = reader.getXPPParser();
            for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
                eventType = xpp.next();
            }
            if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
                String id = xpp.getAttributeValue("", "id");
                if (authenticateDomain(reader, domain, hostname, id)) {
                    // Reset the read timeout to infinite.
                    socket.setSoTimeout(0);
                    // Domain was validated so create a new OutgoingServerSession
                    StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
                    OutgoingServerSession session = new OutgoingServerSession(domain, connection,
                            reader, streamID);
                    connection.init(session);
                    // Set the hostname as the address of the session
                    session.setAddress(new JID(null, hostname, null));
                    return session;
                }
                else {
                    // Close the connection
                    connection.close();
                }
            }
            else {
                Log.debug("OS - Invalid namespace in packet: " + xpp.getText());
                // Send an invalid-namespace stream error condition in the response
                StreamError error = new StreamError(StreamError.Condition.invalid_namespace);
                StringBuilder sb = new StringBuilder();
                sb.append(error.toXML());
                connection.deliverRawText(sb.toString());
                // Close the connection
                connection.close();
            }
        }
        catch (Exception e) {
            Log.error("Error connecting to the remote server: " + hostname, e);
            // Close the connection
            if (connection != null) {
                connection.close();
            }
        }
        return null;
    }

    /**<commit_msg>Use DNS lookup for establishing the outgoing connection.<commit_after>/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 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.messenger.server;

import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.XPPPacketReader;
import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.auth.AuthFactory;
import org.jivesoftware.messenger.net.DNSUtil;
import org.jivesoftware.messenger.net.SocketConnection;
import org.jivesoftware.messenger.spi.BasicStreamIDFactory;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.StreamError;

import javax.net.SocketFactory;
import java.io.*;
import java.net.Socket;

/**
 * Implementation of the Server Dialback method as defined by the RFC3920.
 *
 * The dialback method follows the following logic to validate the remote server:
 * <ol>
 *  <li>The Originating Server establishes a connection to the Receiving Server.</li>
 *  <li>The Originating Server sends a 'key' value over the connection to the Receiving
 *  Server.</li>
 *  <li>The Receiving Server establishes a connection to the Authoritative Server.</li>
 *  <li>The Receiving Server sends the same 'key' value to the Authoritative Server.</li>
 *  <li>The Authoritative Server replies that key is valid or invalid.</li>
 *  <li>The Receiving Server informs the Originating Server whether it is authenticated or
 *  not.</li>
 * </ol>
 *
 * @author <NAME>
 */
class ServerDialback {
    /**
     * The utf-8 charset for decoding and encoding Jabber packet streams.
     */
    protected static String CHARSET = "UTF-8";
    /**
     * Secret key to be used for encoding and decoding keys used for authentication.
     */
    private static final String secretKey = StringUtils.randomString(10);

    private static XmlPullParserFactory FACTORY = null;

    static {
        try {
            FACTORY = XmlPullParserFactory.newInstance();
        }
        catch (XmlPullParserException e) {
            Log.error("Error creating a parser factory", e);
        }
    }

    private Connection connection;
    private String serverName;
    private SessionManager sessionManager = SessionManager.getInstance();

    /**
     * Creates a new instance that will be used for creating {@link IncomingServerSession},
     * validating subsequent domains or authenticatig new domains. Use
     * {@link #createIncomingSession(org.dom4j.io.XPPPacketReader)} for creating a new server
     * session used for receiving packets from the remote server. Use
     * {@link #validateRemoteDomain(org.dom4j.Element, org.jivesoftware.messenger.StreamID)} for
     * validating subsequent domains and use
     * {@link #authenticateDomain(org.dom4j.io.XPPPacketReader, String, String, String)} for
     * registering new domains that are allowed to send packets to the remote server.<p>
     *
     * For validating domains a new TCP connection will be established to the Authoritative Server.
     * The Authoritative Server may be the same Originating Server or some other machine in the
     * Originating Server's network. Once the remote domain gets validated the Originating Server
     * will be allowed for sending packets to this server. However, this server will need to
     * validate its domain/s with the Originating Server if this server needs to send packets to
     * the Originating Server. Another TCP connection will be established for validation this
     * server domain/s and for sending packets to the Originating Server.
     *
     * @param connection the connection created by the remote server.
     * @param serverName the name of the local server.
     */
    ServerDialback(Connection connection, String serverName) {
        this.connection = connection;
        this.serverName = serverName;
    }

    ServerDialback() {
    }

    /**
     * Creates a new connection from the Originating Server to the Receiving Server for
     * authenticating the specified domain.
     *
     * @param domain domain of the Originating Server to authenticate with the Receiving Server.
     * @param hostname IP address or hostname of the Receiving Server.
     * @param port port of the Receiving Server.
     * @return an OutgoingServerSession if the domain was authenticated or <tt>null</tt> if none.
     */
    public OutgoingServerSession createOutgoingSession(String domain, String hostname, int port) {
        // TODO Check if the hostname is in the blacklist
        String realHostname = null;
        try {
            // Establish a TCP connection to the Receiving Server
            Log.debug("OS - Trying to connect to " + hostname + ":" + port);
            // Get the real hostname to connect to using DNS lookup of the specified hostname
            DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname);
            realHostname = address.getHost();
            Socket socket = SocketFactory.getDefault().createSocket(realHostname, port);
            // Set a read timeout of 60 seconds during the dialback operation. Then reset to 0.
            socket.setSoTimeout(60000);
            Log.debug("OS - Connection to " + hostname + ":" + port + " successfull");
            connection =
                    new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
                            false);
            // Get a writer for sending the open stream tag
            // Send to the Receiving Server a stream header
            StringBuilder stream = new StringBuilder();
            stream.append("<stream:stream");
            stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
            stream.append(" xmlns=\"jabber:server\"");
            stream.append(" xmlns:db=\"jabber:server:dialback\">");
            connection.deliverRawText(stream.toString());
            stream = null;

            XPPPacketReader reader = new XPPPacketReader();
            reader.setXPPFactory(XmlPullParserFactory.newInstance());
            reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(),
                    CHARSET));
            // Get the answer from the Receiving Server
            XmlPullParser xpp = reader.getXPPParser();
            for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
                eventType = xpp.next();
            }
            if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
                String id = xpp.getAttributeValue("", "id");
                if (authenticateDomain(reader, domain, hostname, id)) {
                    // Reset the read timeout to infinite.
                    socket.setSoTimeout(0);
                    // Domain was validated so create a new OutgoingServerSession
                    StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
                    OutgoingServerSession session = new OutgoingServerSession(domain, connection,
                            reader, streamID);
                    connection.init(session);
                    // Set the hostname as the address of the session
                    session.setAddress(new JID(null, hostname, null));
                    return session;
                }
                else {
                    // Close the connection
                    connection.close();
                }
            }
            else {
                Log.debug("OS - Invalid namespace in packet: " + xpp.getText());
                // Send an invalid-namespace stream error condition in the response
                StreamError error = new StreamError(StreamError.Condition.invalid_namespace);
                StringBuilder sb = new StringBuilder();
                sb.append(error.toXML());
                connection.deliverRawText(sb.toString());
                // Close the connection
                connection.close();
            }
        }
        catch (Exception e) {
            Log.error("Error connecting to the remote server: " + hostname + "(DNS lookup: " +
                    realHostname +
                    ")",
                    e);
            // Close the connection
            if (connection != null) {
                connection.close();
            }
        }
        return null;
    }

    /**
{'fraction_non_alphanumeric': 0.08425668068969604, 'fraction_numerical': 0.0035551342063162884, 'mean_word_length': 2.424918831168831, 'pattern_counts': {'":': 4, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 12}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2891190', 'n_tokens_mistral': 4375, 'n_tokens_neox': 4147, 'n_words': 1440}