All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.openfire.nio.NIOConnection Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.openfire.nio;

import static org.jivesoftware.openfire.spi.ConnectionManagerImpl.COMPRESSION_FILTER_NAME;
import static org.jivesoftware.openfire.spi.ConnectionManagerImpl.EXECUTOR_FILTER_NAME;
import static org.jivesoftware.openfire.spi.ConnectionManagerImpl.TLS_FILTER_NAME;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

import javax.net.ssl.*;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.compression.CompressionFilter;
import org.apache.mina.filter.ssl.SslFilter;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.ConnectionCloseListener;
import org.jivesoftware.openfire.PacketDeliverer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.net.StanzaHandler;
import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.openfire.spi.ConnectionConfiguration;
import org.jivesoftware.openfire.spi.EncryptionArtifactFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.Packet;

/**
 * Implementation of {@link Connection} interface specific for NIO connections when using the Apache MINA framework.
 *
 * @author Gaston Dombiak
 * @see Apache MINA
 */
public class NIOConnection implements Connection {

    private static final Logger Log = LoggerFactory.getLogger(NIOConnection.class);
    private ConnectionConfiguration configuration;

    /**
     * The utf-8 charset for decoding and encoding XMPP packet streams.
     */
    public static final String CHARSET = "UTF-8";

    private LocalSession session;
    private IoSession ioSession;

    private ConnectionCloseListener closeListener;

    /**
     * Deliverer to use when the connection is closed or was closed when delivering
     * a packet.
     */
    private PacketDeliverer backupDeliverer;
    private boolean flashClient = false;
    private int majorVersion = 1;
    private int minorVersion = 0;
    private String language = null;

    /**
     * TLS policy currently in use for this connection.
     */
    private TLSPolicy tlsPolicy = TLSPolicy.optional;
    private boolean usingSelfSignedCertificate;

    /**
     * Compression policy currently in use for this connection.
     */
    private CompressionPolicy compressionPolicy = CompressionPolicy.disabled;
    private static final ThreadLocal encoder = new ThreadLocalEncoder();

    /**
     * Flag that specifies if the connection should be considered closed. Closing a NIO connection
     * is an asynch operation so instead of waiting for the connection to be actually closed just
     * keep this flag to avoid using the connection between #close was used and the socket is actually
     * closed.
     */
    private AtomicReference state = new AtomicReference<>(State.OPEN);
    
    /**
     * Lock used to ensure the integrity of the underlying IoSession (refer to
     * https://issues.apache.org/jira/browse/DIRMINA-653 for details)
     * 

* This lock can be removed once Openfire guarantees a stable delivery * order, in which case {@link #deliver(Packet)} won't be called * concurrently any more, which made this lock necessary in the first place. *

*/ private final ReentrantLock ioSessionLock = new ReentrantLock(true); public NIOConnection( IoSession session, PacketDeliverer packetDeliverer, ConnectionConfiguration configuration ) { this.ioSession = session; this.backupDeliverer = packetDeliverer; this.configuration = configuration; } @Override public boolean validate() { if (isClosed()) { return false; } deliverRawText(" "); return !isClosed(); } @Override public void registerCloseListener(ConnectionCloseListener listener, Object ignore) { if (closeListener != null) { throw new IllegalStateException("Close listener already configured"); } if (isClosed()) { listener.onConnectionClose(session); } else { closeListener = listener; } } @Override public void removeCloseListener(ConnectionCloseListener listener) { if (closeListener == listener) { closeListener = null; } } @Override public byte[] getAddress() throws UnknownHostException { final SocketAddress remoteAddress = ioSession.getRemoteAddress(); if (remoteAddress == null) throw new UnknownHostException(); final InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress; final InetAddress address = socketAddress.getAddress(); return address.getAddress(); } @Override public String getHostAddress() throws UnknownHostException { final SocketAddress remoteAddress = ioSession.getRemoteAddress(); if (remoteAddress == null) throw new UnknownHostException(); final InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress; final InetAddress inetAddress = socketAddress.getAddress(); return inetAddress.getHostAddress(); } @Override public String getHostName() throws UnknownHostException { final SocketAddress remoteAddress = ioSession.getRemoteAddress(); if (remoteAddress == null) throw new UnknownHostException(); final InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress; final InetAddress inetAddress = socketAddress.getAddress(); return inetAddress.getHostName(); } @Override public Certificate[] getLocalCertificates() { SSLSession sslSession = (SSLSession) ioSession.getAttribute(SslFilter.SSL_SESSION); if (sslSession != null) { return sslSession.getLocalCertificates(); } return new Certificate[0]; } @Override public Certificate[] getPeerCertificates() { try { SSLSession sslSession = (SSLSession) ioSession.getAttribute(SslFilter.SSL_SESSION); if (sslSession != null) { return sslSession.getPeerCertificates(); } } catch (SSLPeerUnverifiedException e) { if (Log.isTraceEnabled()) { // This is perfectly acceptable when mutual authentication is not enforced by Openfire configuration. Log.trace( "Peer does not offer certificates in session: " + session, e); } } return new Certificate[0]; } @Override public void setUsingSelfSignedCertificate(boolean isSelfSigned) { this.usingSelfSignedCertificate = isSelfSigned; } @Override public boolean isUsingSelfSignedCertificate() { return usingSelfSignedCertificate; } @Override public PacketDeliverer getPacketDeliverer() { return backupDeliverer; } @Override public void close() { if (state.compareAndSet(State.OPEN, State.CLOSED)) { // Ensure that the state of this connection, its session and the MINA context are eventually closed. if (session != null) { session.setStatus(Session.STATUS_CLOSED); } try { deliverRawText0(flashClient ? "" : ""); } catch (Exception e) { Log.error("Failed to deliver stream close tag: " + e.getMessage()); } try { ioSession.close(true); } catch (Exception e) { Log.error("Exception while closing MINA session", e); } notifyCloseListeners(); // clean up session, etc. } } @Override public void systemShutdown() { deliverRawText(""); close(); } /** * Notifies all close listeners that the connection has been closed. * Used by subclasses to properly finish closing the connection. */ private void notifyCloseListeners() { if (closeListener != null) { try { closeListener.onConnectionClose(session); } catch (Exception e) { Log.error("Error notifying listener: " + closeListener, e); } } } @Override public void init(LocalSession owner) { session = owner; } @Override public void reinit(LocalSession owner) { session = owner; StanzaHandler stanzaHandler = getStanzaHandler(); stanzaHandler.setSession(owner); } protected StanzaHandler getStanzaHandler() { return (StanzaHandler)ioSession.getAttribute(ConnectionHandler.HANDLER); } @Override public boolean isClosed() { return state.get() == State.CLOSED; } @Override public boolean isSecure() { return ioSession.getFilterChain().contains(TLS_FILTER_NAME); } @Override public void deliver(Packet packet) throws UnauthorizedException { if (isClosed()) { backupDeliverer.deliver(packet); } else { boolean errorDelivering = false; IoBuffer buffer = IoBuffer.allocate(4096); buffer.setAutoExpand(true); try { buffer.putString(packet.getElement().asXML(), encoder.get()); if (flashClient) { buffer.put((byte) '\0'); } buffer.flip(); ioSessionLock.lock(); try { ioSession.write(buffer); } finally { ioSessionLock.unlock(); } } catch (Exception e) { Log.debug("Error delivering packet:\n" + packet, e); errorDelivering = true; } if (errorDelivering) { close(); // Retry sending the packet again. Most probably if the packet is a // Message it will be stored offline backupDeliverer.deliver(packet); } else { session.incrementServerPacketCount(); } } } @Override public void deliverRawText(String text) { if (!isClosed()) { deliverRawText0(text); } } private void deliverRawText0(String text){ boolean errorDelivering = false; IoBuffer buffer = IoBuffer.allocate(text.length()); buffer.setAutoExpand(true); try { //Charset charset = Charset.forName(CHARSET); //buffer.putString(text, charset.newEncoder()); buffer.put(text.getBytes(StandardCharsets.UTF_8)); if (flashClient) { buffer.put((byte) '\0'); } buffer.flip(); ioSessionLock.lock(); try { ioSession.write(buffer); } finally { ioSessionLock.unlock(); } } catch (Exception e) { Log.debug("Error delivering raw text:\n" + text, e); errorDelivering = true; } // Attempt to close the connection if delivering text fails. if (errorDelivering) { close(); } } @Deprecated @Override public void startTLS(boolean clientMode, String remoteServer, ClientAuth authentication) throws Exception { startTLS( clientMode ); } public void startTLS(boolean clientMode) throws Exception { final EncryptionArtifactFactory factory = new EncryptionArtifactFactory( configuration ); final SslFilter filter; if ( clientMode ) { filter = factory.createClientModeSslFilter(); } else { filter = factory.createServerModeSslFilter(); } ioSession.getFilterChain().addBefore(EXECUTOR_FILTER_NAME, TLS_FILTER_NAME, filter); ioSession.setAttribute(SslFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE); if ( !clientMode ) { // Indicate the client that the server is ready to negotiate TLS deliverRawText( "" ); } } @Override public void addCompression() { IoFilterChain chain = ioSession.getFilterChain(); String baseFilter = EXECUTOR_FILTER_NAME; if (chain.contains(TLS_FILTER_NAME)) { baseFilter = TLS_FILTER_NAME; } chain.addAfter(baseFilter, COMPRESSION_FILTER_NAME, new CompressionFilter(true, false, CompressionFilter.COMPRESSION_MAX)); } @Override public void startCompression() { CompressionFilter ioFilter = (CompressionFilter) ioSession.getFilterChain().get(COMPRESSION_FILTER_NAME); ioFilter.setCompressOutbound(true); } @Override public ConnectionConfiguration getConfiguration() { return configuration; } public boolean isFlashClient() { return flashClient; } @Override public void setFlashClient(boolean flashClient) { this.flashClient = flashClient; } @Override public int getMajorXMPPVersion() { return majorVersion; } @Override public int getMinorXMPPVersion() { return minorVersion; } @Override public void setXMPPVersion(int majorVersion, int minorVersion) { this.majorVersion = majorVersion; this.minorVersion = minorVersion; } @Override public boolean isCompressed() { return ioSession.getFilterChain().contains(COMPRESSION_FILTER_NAME); } @Override public CompressionPolicy getCompressionPolicy() { return compressionPolicy; } @Override public void setCompressionPolicy(CompressionPolicy compressionPolicy) { this.compressionPolicy = compressionPolicy; } @Override public TLSPolicy getTlsPolicy() { return tlsPolicy; } @Override public void setTlsPolicy(TLSPolicy tlsPolicy) { this.tlsPolicy = tlsPolicy; } @Override public String toString() { return super.toString() + " MINA Session: " + ioSession; } private static class ThreadLocalEncoder extends ThreadLocal { @Override protected CharsetEncoder initialValue() { return StandardCharsets.UTF_8.newEncoder() .onMalformedInput(CodingErrorAction.REPORT) .onUnmappableCharacter(CodingErrorAction.REPORT); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy