private static LocalOutgoingServerSession attemptDialbackOverTLS(
     Connection connection, XMPPPacketReader reader, String domain, String hostname, String id) {
   final Logger log =
       LoggerFactory.getLogger(
           LocalOutgoingServerSession.class.getName() + "['" + hostname + "']");
   if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
     log.debug("Trying to connecting using dialback over TLS.");
     ServerDialback method = new ServerDialback(connection, domain);
     OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader);
     if (method.authenticateDomain(newSocketReader, domain, hostname, id)) {
       log.debug("Dialback over TLS was successful.");
       StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
       LocalOutgoingServerSession session =
           new LocalOutgoingServerSession(domain, connection, newSocketReader, streamID);
       connection.init(session);
       // Set the hostname as the address of the session
       session.setAddress(new JID(null, hostname, null));
       return session;
     } else {
       log.debug("Dialback over TLS failed");
       return null;
     }
   } else {
     log.debug("Skipping server dialback attempt as it has been disabled by local configuration.");
     return null;
   }
 }
 /**
  * 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;
 }
 @Override
 public boolean authenticateSubdomain(String domain, String hostname) {
   if (!usingServerDialback) {
     // Using SASL so just assume that the domain was validated
     // (note: this may not be correct)
     addAuthenticatedDomain(domain);
     addHostname(hostname);
     return true;
   }
   ServerDialback method = new ServerDialback(getConnection(), domain);
   if (method.authenticateDomain(socketReader, domain, hostname, getStreamID().getID())) {
     // Add the validated domain as an authenticated domain
     addAuthenticatedDomain(domain);
     addHostname(hostname);
     return true;
   }
   return false;
 }
  @Override
  public String getAvailableStreamFeatures() {
    StringBuilder sb = new StringBuilder();

    // Include Stream Compression Mechanism
    if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled
        && !conn.isCompressed()) {
      sb.append(
          "<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>");
    }

    // Offer server dialback if using self-signed certificates and no authentication has been done
    // yet
    boolean usingSelfSigned;
    final Certificate[] chain = conn.getLocalCertificates();
    if (chain == null || chain.length == 0) {
      usingSelfSigned = true;
    } else {
      try {
        usingSelfSigned =
            CertificateManager.isSelfSignedCertificate(
                SSLConfig.getKeyStore(), (X509Certificate) chain[0]);
      } catch (KeyStoreException ex) {
        Log.warn(
            "Exception occurred while trying to determine whether local certificate is self-signed. Proceeding as if it is.",
            ex);
        usingSelfSigned = true;
      } catch (IOException ex) {
        Log.warn(
            "Exception occurred while trying to determine whether local certificate is self-signed. Proceeding as if it is.",
            ex);
        usingSelfSigned = true;
      }
    }

    if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) {
      sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"/>");
    }

    return sb.toString();
  }
  /**
   * 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>
   *
   * @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();

    String version = xpp.getAttributeValue("", "version");
    int[] serverVersion = version != null ? decodeVersion(version) : new int[] {0, 0};

    try {
      // 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:db=\"jabber:server:dialback\"");
      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());

      if (serverVersion[0] >= 1) {
        // Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection
        // (and server dialback)

        // 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.getMessage(), 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();

      if (serverVersion[0] >= 1) {
        // Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection
        // (and server dialback)
        // Don't offer stream-features to pre-1.0 servers, as it confuses them. Sending features to
        // Openfire < 3.7.1 confuses it too - OF-443)
        sb.append("<stream:features>");

        if (JiveGlobals.getBooleanProperty("xmpp.server.tls.enabled", true)) {
          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));

        if (ServerDialback.isEnabled()) {
          // Also offer server dialback (when TLS is not required). Server dialback may be offered
          // after TLS has been negotiated and a self-signed certificate is being used
          sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"/>");
        }

        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;
    } catch (Exception e) {
      Log.error("Error establishing connection from remote server:" + connection, e);
      connection.close();
      return null;
    }
  }
 /**
  * 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());
 }
  private static LocalOutgoingServerSession secureAndAuthenticate(
      String hostname,
      SocketConnection connection,
      XMPPPacketReader reader,
      StringBuilder openingStream,
      String domain)
      throws Exception {
    final Logger log =
        LoggerFactory.getLogger(
            LocalOutgoingServerSession.class.getName() + "['" + hostname + "']");
    Element features;
    log.debug("Indicating we want TLS to " + hostname);
    connection.deliverRawText("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");

    MXParser xpp = reader.getXPPParser();
    // Wait for the <proceed> response
    Element proceed = reader.parseDocument().getRootElement();
    if (proceed != null && proceed.getName().equals("proceed")) {
      log.debug("Negotiating TLS...");
      try {
        //                boolean needed =
        // JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true) &&
        //
        // JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY,
        // true) &&
        //
        // !JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS,
        // false);
        connection.startTLS(true);
      } catch (Exception e) {
        log.debug("Got an exception whilst negotiating TLS: " + e.getMessage());
        throw e;
      }
      log.debug("TLS negotiation was successful.");
      if (!SASLAuthentication.verifyCertificates(
          connection.getPeerCertificates(), hostname, true)) {
        log.debug("X.509/PKIX failure on outbound session");
        if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
          log.debug("Will continue with dialback.");
        } else {
          log.warn("No TLS auth, but TLS auth required.");
          return null;
        }
      }

      // TLS negotiation was successful so initiate a new stream
      connection.deliverRawText(openingStream.toString());

      // Reset the parser to use the new secured reader
      xpp.setInput(
          new InputStreamReader(
              connection.getTLSStreamHandler().getInputStream(), StandardCharsets.UTF_8));
      // Skip new stream element
      for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG; ) {
        eventType = xpp.next();
      }
      // Get the stream ID
      String id = xpp.getAttributeValue("", "id");
      // Get new stream features
      features = reader.parseDocument().getRootElement();
      if (features != null) {
        // Check if we can use stream compression
        final Connection.CompressionPolicy compressionPolicy =
            connection.getConfiguration().getCompressionPolicy();
        if (Connection.CompressionPolicy.optional == compressionPolicy) {
          // Verify if the remote server supports stream compression
          Element compression = features.element("compression");
          if (compression != null) {
            boolean zlibSupported = false;
            Iterator it = compression.elementIterator("method");
            while (it.hasNext()) {
              Element method = (Element) it.next();
              if ("zlib".equals(method.getTextTrim())) {
                zlibSupported = true;
              }
            }
            if (zlibSupported) {
              log.debug("Suppressing request to perform compression; unsupported in this version.");
              zlibSupported = false;
            }
            if (zlibSupported) {
              log.debug("Requesting stream compression (zlib).");
              connection.deliverRawText(
                  "<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>");
              // Check if we are good to start compression
              Element answer = reader.parseDocument().getRootElement();
              if ("compressed".equals(answer.getName())) {
                // Server confirmed that we can use zlib compression
                connection.addCompression();
                connection.startCompression();
                log.debug("Stream compression was successful.");
                // Stream compression was successful so initiate a new stream
                connection.deliverRawText(openingStream.toString());
                // Reset the parser to use stream compression over TLS
                ZInputStream in =
                    new ZInputStream(connection.getTLSStreamHandler().getInputStream());
                in.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
                xpp.setInput(new InputStreamReader(in, StandardCharsets.UTF_8));
                // Skip the opening stream sent by the server
                for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG; ) {
                  eventType = xpp.next();
                }
                // Get new stream features
                features = reader.parseDocument().getRootElement();
                if (features == null) {
                  log.debug("Error, EXTERNAL SASL was not offered.");
                  return null;
                }
              } else {
                log.debug("Stream compression was rejected by " + hostname);
              }
            } else {
              log.debug("Stream compression found but zlib method is not supported by " + hostname);
            }
          } else {
            log.debug("Stream compression not supported by " + hostname);
          }
        }

        // Bookkeeping: determine what functionality the remote server offers.
        boolean saslEXTERNALoffered = false;
        if (features != null) {
          if (features.element("mechanisms") != null) {
            Iterator<Element> it = features.element("mechanisms").elementIterator();
            while (it.hasNext()) {
              Element mechanism = it.next();
              if ("EXTERNAL".equals(mechanism.getTextTrim())) {
                saslEXTERNALoffered = true;
                break;
              }
            }
          }
        }
        final boolean dialbackOffered = features.element("dialback") != null;

        log.debug("Offering dialback functionality: {}", dialbackOffered);
        log.debug("Offering EXTERNAL SASL: {}", saslEXTERNALoffered);

        LocalOutgoingServerSession result = null;
        // first, try SASL
        if (saslEXTERNALoffered) {
          result =
              attemptSASLexternal(connection, xpp, reader, domain, hostname, id, openingStream);
        }
        if (result == null) {
          // SASL unavailable or failed, try dialback.
          result = attemptDialbackOverTLS(connection, reader, domain, hostname, id);
        }

        return result;
      } else {
        log.debug(
            "Cannot create outgoing server session, as neither SASL mechanisms nor SERVER DIALBACK were offered by "
                + hostname);
        return null;
      }
    } else {
      log.debug("Error, <proceed> was not received!");
      return null;
    }
  }
  /**
   * Establishes a new outgoing session to a remote server. If the remote server supports TLS and
   * SASL then the new outgoing connection will be secured with TLS and authenticated using SASL.
   * However, if TLS or SASL is not supported by the remote server or if an error occured while
   * securing or authenticating the connection using SASL then server dialback method will be used.
   *
   * @param domain the local domain to authenticate with the remote server.
   * @param hostname the hostname of the remote server.
   * @param port default port to use to establish the connection.
   * @return new outgoing session to a remote server.
   */
  private static LocalOutgoingServerSession createOutgoingSession(
      String domain, String hostname, int port) {

    String localDomainName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
    boolean useTLS = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true);
    RemoteServerConfiguration configuration = RemoteServerManager.getConfiguration(hostname);
    if (configuration != null) {
      // TODO Use the specific TLS configuration for this remote server
      // useTLS = configuration.isTLSEnabled();
    }

    // Connect to remote server using XMPP 1.0 (TLS + SASL EXTERNAL or TLS + server dialback or
    // server dialback)
    String realHostname = null;
    int realPort = port;
    Socket socket = null;
    // Get a list of real hostnames to connect to using DNS lookup of the specified hostname
    List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, port);
    for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext(); ) {
      try {
        socket = new Socket();
        DNSUtil.HostAddress address = it.next();
        realHostname = address.getHost();
        realPort = address.getPort();
        Log.debug(
            "LocalOutgoingServerSession: OS - Trying to connect to "
                + hostname
                + ":"
                + port
                + "(DNS lookup: "
                + realHostname
                + ":"
                + realPort
                + ")");
        // Establish a TCP connection to the Receiving Server
        socket.connect(
            new InetSocketAddress(realHostname, realPort), RemoteServerManager.getSocketTimeout());
        Log.debug(
            "LocalOutgoingServerSession: OS - Plain connection to "
                + hostname
                + ":"
                + port
                + " successful");
        break;
      } catch (Exception e) {
        Log.warn(
            "Error trying to connect to remote server: "
                + hostname
                + "(DNS lookup: "
                + realHostname
                + ":"
                + realPort
                + "): "
                + e.toString());
        try {
          if (socket != null) {
            socket.close();
          }
        } catch (IOException ex) {
          Log.debug(
              "Additional exception while trying to close socket when connection to remote server failed: "
                  + ex.toString());
        }
      }
    }
    if (!socket.isConnected()) {
      return null;
    }

    SocketConnection connection = null;
    try {
      connection =
          new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, false);

      // Send the stream header
      StringBuilder openingStream = new StringBuilder();
      openingStream.append("<stream:stream");
      openingStream.append(" xmlns:db=\"jabber:server:dialback\"");
      openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
      openingStream.append(" xmlns=\"jabber:server\"");
      openingStream.append(" from=\"").append(localDomainName).append("\""); // OF-673
      openingStream.append(" to=\"").append(hostname).append("\"");
      openingStream.append(" version=\"1.0\">");
      connection.deliverRawText(openingStream.toString());

      // Set a read timeout (of 5 seconds) so we don't keep waiting forever
      int soTimeout = socket.getSoTimeout();
      socket.setSoTimeout(5000);

      XMPPPacketReader reader = new XMPPPacketReader();
      reader
          .getXPPParser()
          .setInput(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
      // Get the answer from the Receiving Server
      XmlPullParser xpp = reader.getXPPParser();
      for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG; ) {
        eventType = xpp.next();
      }

      String serverVersion = xpp.getAttributeValue("", "version");
      String id = xpp.getAttributeValue("", "id");

      // Check if the remote server is XMPP 1.0 compliant
      if (serverVersion != null && decodeVersion(serverVersion)[0] >= 1) {
        // Restore default timeout
        socket.setSoTimeout(soTimeout);
        // Get the stream features
        Element features = reader.parseDocument().getRootElement();
        if (features != null) {
          // Check if TLS is enabled
          if (useTLS && features.element("starttls") != null) {
            // Secure the connection with TLS and authenticate using SASL
            LocalOutgoingServerSession answer;
            answer = secureAndAuthenticate(hostname, connection, reader, openingStream, domain);
            if (answer != null) {
              // Everything went fine so return the secured and
              // authenticated connection
              return answer;
            }
          }
          // Check if we are going to try server dialback (XMPP 1.0)
          else if (ServerDialback.isEnabled() && features.element("dialback") != null) {
            Log.debug(
                "LocalOutgoingServerSession: OS - About to try connecting using server dialback XMPP 1.0 with: "
                    + hostname);
            ServerDialback method = new ServerDialback(connection, domain);
            OutgoingServerSocketReader newSocketReader = new OutgoingServerSocketReader(reader);
            if (method.authenticateDomain(newSocketReader, domain, hostname, id)) {
              Log.debug(
                  "LocalOutgoingServerSession: OS - SERVER DIALBACK XMPP 1.0 with "
                      + hostname
                      + " was successful");
              StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
              LocalOutgoingServerSession session =
                  new LocalOutgoingServerSession(domain, connection, newSocketReader, streamID);
              connection.init(session);
              // Set the hostname as the address of the session
              session.setAddress(new JID(null, hostname, null));
              return session;
            } else {
              Log.debug(
                  "LocalOutgoingServerSession: OS - Error, SERVER DIALBACK with "
                      + hostname
                      + " failed");
            }
          }
        } else {
          Log.debug("LocalOutgoingServerSession: OS - Error, <starttls> was not received");
        }
      }
      // Something went wrong so close the connection and try server dialback over
      // a plain connection
      if (connection != null) {
        connection.close();
      }
    } catch (SSLHandshakeException e) {
      Log.debug(
          "LocalOutgoingServerSession: Handshake error while creating secured outgoing session to remote "
              + "server: "
              + hostname
              + "(DNS lookup: "
              + realHostname
              + ":"
              + realPort
              + "):",
          e);
      // Close the connection
      if (connection != null) {
        connection.close();
      }
    } catch (Exception e) {
      Log.error(
          "Error creating secured outgoing session to remote server: "
              + hostname
              + "(DNS lookup: "
              + realHostname
              + ":"
              + realPort
              + ")",
          e);
      // Close the connection
      if (connection != null) {
        connection.close();
      }
    }

    if (ServerDialback.isEnabled()) {
      Log.debug(
          "LocalOutgoingServerSession: OS - Going to try connecting using server dialback with: "
              + hostname);
      // Use server dialback (pre XMPP 1.0) over a plain connection
      return new ServerDialback().createOutgoingSession(domain, hostname, port);
    }
    return null;
  }