/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.extensions; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import org.opends.server.api.ClientConnection; import org.opends.server.api.ConnectionSecurityProvider; import org.opends.server.config.ConfigEntry; import org.opends.server.config.ConfigException; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.DirectoryException; import org.opends.server.types.DisconnectReason; import org.opends.server.types.InitializationException; import org.opends.server.types.DebugLogLevel; import static org.opends.messages.ExtensionMessages.*; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.util.StaticUtils.*; /** * This class provides an implementation of a connection security provider that * does not actually provide any security for the communication process. Any * data read or written will be assumed to be clear text. */ public class NullConnectionSecurityProvider extends ConnectionSecurityProvider { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * The buffer size in bytes that will be used for data on this connection. */ private static final int BUFFER_SIZE = 4096; // The buffer that will be used when reading clear-text data. private ByteBuffer clearBuffer; // The client connection with which this security provider is associated. private ClientConnection clientConnection; // The socket channel that may be used to communicate with the client. private SocketChannel socketChannel; /** * Creates a new instance of this connection security provider. Note that * no initialization should be done here, since it should all be done in the * initializeConnectionSecurityProvider method. Also note that * this instance should only be used to create new instances that are * associated with specific client connections. This instance itself should * not be used to attempt secure communication with the client. */ public NullConnectionSecurityProvider() { super(); } /** * Creates a new instance of this connection security provider that will be * associated with the provided client connection. * * @param clientConnection The client connection with which this connection * security provider should be associated. * @param socketChannel The socket channel that may be used to * communicate with the client. */ protected NullConnectionSecurityProvider(ClientConnection clientConnection, SocketChannel socketChannel) { super(); this.clientConnection = clientConnection; this.socketChannel = socketChannel; clearBuffer = ByteBuffer.allocate(BUFFER_SIZE); } /** * {@inheritDoc} */ @Override() public void initializeConnectionSecurityProvider(ConfigEntry configEntry) throws ConfigException, InitializationException { clearBuffer = null; clientConnection = null; socketChannel = null; } /** * {@inheritDoc} */ @Override() public void finalizeConnectionSecurityProvider() { // No implementation is required. } /** * {@inheritDoc} */ @Override() public String getSecurityMechanismName() { return "NULL"; } /** * {@inheritDoc} */ @Override() public boolean isSecure() { // This is not a secure provider. return false; } /** * {@inheritDoc} */ @Override() public ConnectionSecurityProvider newInstance(ClientConnection clientConnection, SocketChannel socketChannel) throws DirectoryException { return new NullConnectionSecurityProvider(clientConnection, socketChannel); } /** * {@inheritDoc} */ @Override() public void disconnect(boolean connectionValid) { // No implementation is required. } /** * {@inheritDoc} */ @Override() public int getClearBufferSize() { return BUFFER_SIZE; } /** * {@inheritDoc} */ @Override() public int getEncodedBufferSize() { return BUFFER_SIZE; } /** * {@inheritDoc} */ @Override() public boolean readData() { clearBuffer.clear(); while (true) { try { int bytesRead = socketChannel.read(clearBuffer); clearBuffer.flip(); if (bytesRead < 0) { // The connection has been closed by the client. Disconnect and // return. clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null); return false; } else if (bytesRead == 0) { // We have read all the data that there is to read right now (or there // wasn't any in the first place). Just return and wait for future // notification. return true; } else { // We have read data from the client. Since there is no actual // security on this connection, then just deal with it as-is. if (! clientConnection.processDataRead(clearBuffer)) { return false; } } } catch (IOException ioe) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, ioe); } // An error occurred while trying to read data from the client. // Disconnect and return. clientConnection.disconnect(DisconnectReason.IO_ERROR, false, null); return false; } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } // An unexpected error occurred. Disconnect and return. clientConnection.disconnect(DisconnectReason.SERVER_ERROR, true, ERR_NULL_SECURITY_PROVIDER_READ_ERROR.get( getExceptionMessage(e))); return false; } } } /** * {@inheritDoc} */ @Override() public boolean writeData(ByteBuffer clearData) { int position = clearData.position(); int limit = clearData.limit(); try { while (clearData.hasRemaining()) { int bytesWritten = socketChannel.write(clearData); if (bytesWritten < 0) { // The client connection has been closed. Disconnect and return. clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null); return false; } else if (bytesWritten == 0) { // This can happen if the server can't send data to the client (e.g., // because the client is blocked or there is a network problem. In // that case, then use a selector to perform the write, timing out and // terminating the client connection if necessary. return writeWithTimeout(clientConnection, socketChannel, clearData); } } return true; } catch (IOException ioe) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, ioe); } // An error occurred while trying to write data to the client. Disconnect // and return. clientConnection.disconnect(DisconnectReason.IO_ERROR, false, null); return false; } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } // An unexpected error occurred. Disconnect and return. clientConnection.disconnect(DisconnectReason.SERVER_ERROR, true, ERR_NULL_SECURITY_PROVIDER_WRITE_ERROR.get( getExceptionMessage(e))); return false; } finally { clearData.position(position); clearData.limit(limit); } } /** * Writes the contents of the provided buffer to the client, terminating the * connection if the write is unsuccessful for too long (e.g., if the client * is unresponsive or there is a network problem). If possible, it will * attempt to use the selector returned by the * {@code ClientConnection.getWriteSelector} method, but it is capable of * working even if that method returns {@code null}. *

* Note that this method has been written in a generic manner so that other * connection security providers can use it to send data to the client, * provided that the given buffer contains the appropriate pre-encoded * information. *

* Also note that the original position and limit values will not be * preserved, so if that is important to the caller, then it should record * them before calling this method and restore them after it returns. * * @param clientConnection The client connection to which the data is to be * written. * @param socketChannel The socket channel over which to write the data. * @param buffer The data to be written to the client. * * @return true if all the data in the provided buffer was * written to the client and the connection may remain established, * or false if a problem occurred and the client * connection is no longer valid. Note that if this method does * return false, then it must have already disconnected * the client. * * @throws IOException If a problem occurs while attempting to write data * to the client. The caller will be responsible for * catching this and terminating the client connection. */ public static boolean writeWithTimeout(ClientConnection clientConnection, SocketChannel socketChannel, ByteBuffer buffer) throws IOException { long startTime = System.currentTimeMillis(); long waitTime = clientConnection.getMaxBlockedWriteTimeLimit(); if (waitTime <= 0) { // We won't support an infinite time limit, so fall back to using // five minutes, which is a very long timeout given that we're // blocking a worker thread. waitTime = 300000L; } long stopTime = startTime + waitTime; Selector selector = clientConnection.getWriteSelector(); if (selector == null) { // The client connection does not provide a selector, so we'll fall back // to a more inefficient way that will work without a selector. while (buffer.hasRemaining() && (System.currentTimeMillis() < stopTime)) { if (socketChannel.write(buffer) < 0) { // The client connection has been closed. Disconnect and return. clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null); return false; } } if (buffer.hasRemaining()) { // If we've gotten here, then the write timed out. Terminate the client // connection. clientConnection.disconnect(DisconnectReason.IO_TIMEOUT, false, null); return false; } return true; } // Register with the selector for handling write operations. SelectionKey key = socketChannel.register(selector, SelectionKey.OP_WRITE); try { selector.select(waitTime); while (buffer.hasRemaining()) { long currentTime = System.currentTimeMillis(); if (currentTime >= stopTime) { // We've been blocked for too long. Terminate the client connection. clientConnection.disconnect(DisconnectReason.IO_TIMEOUT, false, null); return false; } else { waitTime = stopTime - currentTime; } Iterator iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey k = iterator.next(); if (k.isWritable()) { int bytesWritten = socketChannel.write(buffer); if (bytesWritten < 0) { // The client connection has been closed. Disconnect and return. clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null); return false; } iterator.remove(); } } if (buffer.hasRemaining()) { selector.select(waitTime); } } return true; } finally { if (key.isValid()) { key.cancel(); selector.selectNow(); } } } }