001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.extensions;
028    
029    
030    
031    import java.io.IOException;
032    import java.nio.ByteBuffer;
033    import java.nio.channels.SelectionKey;
034    import java.nio.channels.Selector;
035    import java.nio.channels.SocketChannel;
036    import java.util.Iterator;
037    
038    import org.opends.server.api.ClientConnection;
039    import org.opends.server.api.ConnectionSecurityProvider;
040    import org.opends.server.config.ConfigEntry;
041    import org.opends.server.config.ConfigException;
042    import org.opends.server.loggers.debug.DebugTracer;
043    import org.opends.server.types.DirectoryException;
044    import org.opends.server.types.DisconnectReason;
045    import org.opends.server.types.InitializationException;
046    import org.opends.server.types.DebugLogLevel;
047    
048    import static org.opends.messages.ExtensionMessages.*;
049    import static org.opends.server.loggers.debug.DebugLogger.*;
050    import static org.opends.server.util.StaticUtils.*;
051    
052    
053    
054    /**
055     * This class provides an implementation of a connection security provider that
056     * does not actually provide any security for the communication process.  Any
057     * data read or written will be assumed to be clear text.
058     */
059    public class NullConnectionSecurityProvider
060           extends ConnectionSecurityProvider
061    {
062      /**
063       * The tracer object for the debug logger.
064       */
065      private static final DebugTracer TRACER = getTracer();
066    
067    
068    
069      /**
070       * The buffer size in bytes that will be used for data on this connection.
071       */
072      private static final int BUFFER_SIZE = 4096;
073    
074    
075    
076      // The buffer that will be used when reading clear-text data.
077      private ByteBuffer clearBuffer;
078    
079      // The client connection with which this security provider is associated.
080      private ClientConnection clientConnection;
081    
082      // The socket channel that may be used to communicate with the client.
083      private SocketChannel socketChannel;
084    
085    
086    
087      /**
088       * Creates a new instance of this connection security provider.  Note that
089       * no initialization should be done here, since it should all be done in the
090       * <CODE>initializeConnectionSecurityProvider</CODE> method.  Also note that
091       * this instance should only be used to create new instances that are
092       * associated with specific client connections.  This instance itself should
093       * not be used to attempt secure communication with the client.
094       */
095      public NullConnectionSecurityProvider()
096      {
097        super();
098      }
099    
100    
101    
102      /**
103       * Creates a new instance of this connection security provider that will be
104       * associated with the provided client connection.
105       *
106       * @param  clientConnection  The client connection with which this connection
107       *                           security provider should be associated.
108       * @param  socketChannel     The socket channel that may be used to
109       *                           communicate with the client.
110       */
111      protected NullConnectionSecurityProvider(ClientConnection clientConnection,
112                                               SocketChannel socketChannel)
113      {
114        super();
115    
116    
117        this.clientConnection = clientConnection;
118        this.socketChannel    = socketChannel;
119    
120        clearBuffer = ByteBuffer.allocate(BUFFER_SIZE);
121      }
122    
123    
124    
125      /**
126       * {@inheritDoc}
127       */
128      @Override()
129      public void initializeConnectionSecurityProvider(ConfigEntry configEntry)
130             throws ConfigException, InitializationException
131      {
132        clearBuffer      = null;
133        clientConnection = null;
134        socketChannel    = null;
135      }
136    
137    
138    
139      /**
140       * {@inheritDoc}
141       */
142      @Override()
143      public void finalizeConnectionSecurityProvider()
144      {
145        // No implementation is required.
146      }
147    
148    
149    
150      /**
151       * {@inheritDoc}
152       */
153      @Override()
154      public String getSecurityMechanismName()
155      {
156        return "NULL";
157      }
158    
159    
160    
161      /**
162       * {@inheritDoc}
163       */
164      @Override()
165      public boolean isSecure()
166      {
167        // This is not a secure provider.
168        return false;
169      }
170    
171    
172    
173      /**
174       * {@inheritDoc}
175       */
176      @Override()
177      public ConnectionSecurityProvider newInstance(ClientConnection
178                                                          clientConnection,
179                                                    SocketChannel socketChannel)
180             throws DirectoryException
181      {
182        return new NullConnectionSecurityProvider(clientConnection,
183                                                  socketChannel);
184      }
185    
186    
187    
188      /**
189       * {@inheritDoc}
190       */
191      @Override()
192      public void disconnect(boolean connectionValid)
193      {
194        // No implementation is required.
195      }
196    
197    
198    
199      /**
200       * {@inheritDoc}
201       */
202      @Override()
203      public int getClearBufferSize()
204      {
205        return BUFFER_SIZE;
206      }
207    
208    
209    
210      /**
211       * {@inheritDoc}
212       */
213      @Override()
214      public int getEncodedBufferSize()
215      {
216        return BUFFER_SIZE;
217      }
218    
219    
220    
221      /**
222       * {@inheritDoc}
223       */
224      @Override()
225      public boolean readData()
226      {
227        clearBuffer.clear();
228        while (true)
229        {
230          try
231          {
232            int bytesRead = socketChannel.read(clearBuffer);
233            clearBuffer.flip();
234    
235            if (bytesRead < 0)
236            {
237              // The connection has been closed by the client.  Disconnect and
238              // return.
239              clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT, false,
240                                          null);
241              return false;
242            }
243            else if (bytesRead == 0)
244            {
245              // We have read all the data that there is to read right now (or there
246              // wasn't any in the first place).  Just return and wait for future
247              // notification.
248              return true;
249            }
250            else
251            {
252              // We have read data from the client.  Since there is no actual
253              // security on this connection, then just deal with it as-is.
254              if (! clientConnection.processDataRead(clearBuffer))
255              {
256                return false;
257              }
258            }
259          }
260          catch (IOException ioe)
261          {
262            if (debugEnabled())
263            {
264              TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
265            }
266    
267            // An error occurred while trying to read data from the client.
268            // Disconnect and return.
269            clientConnection.disconnect(DisconnectReason.IO_ERROR, false, null);
270            return false;
271          }
272          catch (Exception e)
273          {
274            if (debugEnabled())
275            {
276              TRACER.debugCaught(DebugLogLevel.ERROR, e);
277            }
278    
279            // An unexpected error occurred.  Disconnect and return.
280            clientConnection.disconnect(DisconnectReason.SERVER_ERROR, true,
281                                        ERR_NULL_SECURITY_PROVIDER_READ_ERROR.get(
282                                          getExceptionMessage(e)));
283            return false;
284          }
285        }
286      }
287    
288    
289    
290      /**
291       * {@inheritDoc}
292       */
293      @Override()
294      public boolean writeData(ByteBuffer clearData)
295      {
296        int position = clearData.position();
297        int limit    = clearData.limit();
298    
299        try
300        {
301          while (clearData.hasRemaining())
302          {
303            int bytesWritten = socketChannel.write(clearData);
304            if (bytesWritten < 0)
305            {
306              // The client connection has been closed.  Disconnect and return.
307              clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT, false,
308                                          null);
309              return false;
310            }
311            else if (bytesWritten == 0)
312            {
313              // This can happen if the server can't send data to the client (e.g.,
314              // because the client is blocked or there is a network problem.  In
315              // that case, then use a selector to perform the write, timing out and
316              // terminating the client connection if necessary.
317              return writeWithTimeout(clientConnection, socketChannel, clearData);
318            }
319          }
320    
321          return true;
322        }
323        catch (IOException ioe)
324        {
325          if (debugEnabled())
326          {
327            TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
328          }
329    
330          // An error occurred while trying to write data to the client.  Disconnect
331          // and return.
332          clientConnection.disconnect(DisconnectReason.IO_ERROR, false, null);
333          return false;
334        }
335        catch (Exception e)
336        {
337          if (debugEnabled())
338          {
339            TRACER.debugCaught(DebugLogLevel.ERROR, e);
340          }
341    
342          // An unexpected error occurred.  Disconnect and return.
343          clientConnection.disconnect(DisconnectReason.SERVER_ERROR, true,
344                                      ERR_NULL_SECURITY_PROVIDER_WRITE_ERROR.get(
345                                      getExceptionMessage(e)));
346          return false;
347        }
348        finally
349        {
350          clearData.position(position);
351          clearData.limit(limit);
352        }
353      }
354    
355    
356    
357      /**
358       * Writes the contents of the provided buffer to the client, terminating the
359       * connection if the write is unsuccessful for too long (e.g., if the client
360       * is unresponsive or there is a network problem).  If possible, it will
361       * attempt to use the selector returned by the
362       * {@code ClientConnection.getWriteSelector} method, but it is capable of
363       * working even if that method returns {@code null}.
364       * <BR><BR>
365       * Note that this method has been written in a generic manner so that other
366       * connection security providers can use it to send data to the client,
367       * provided that the given buffer contains the appropriate pre-encoded
368       * information.
369       * <BR><BR>
370       * Also note that the original position and limit values will not be
371       * preserved, so if that is important to the caller, then it should record
372       * them before calling this method and restore them after it returns.
373       *
374       * @param  clientConnection  The client connection to which the data is to be
375       *                           written.
376       * @param  socketChannel     The socket channel over which to write the data.
377       * @param  buffer            The data to be written to the client.
378       *
379       * @return  <CODE>true</CODE> if all the data in the provided buffer was
380       *          written to the client and the connection may remain established,
381       *          or <CODE>false</CODE> if a problem occurred and the client
382       *          connection is no longer valid.  Note that if this method does
383       *          return <CODE>false</CODE>, then it must have already disconnected
384       *          the client.
385       *
386       * @throws  IOException  If a problem occurs while attempting to write data
387       *                       to the client.  The caller will be responsible for
388       *                       catching this and terminating the client connection.
389       */
390      public static boolean writeWithTimeout(ClientConnection clientConnection,
391                                             SocketChannel socketChannel,
392                                             ByteBuffer buffer)
393             throws IOException
394      {
395        long startTime = System.currentTimeMillis();
396        long waitTime  = clientConnection.getMaxBlockedWriteTimeLimit();
397        if (waitTime <= 0)
398        {
399          // We won't support an infinite time limit, so fall back to using
400          // five minutes, which is a very long timeout given that we're
401          // blocking a worker thread.
402          waitTime = 300000L;
403        }
404    
405        long stopTime = startTime + waitTime;
406    
407    
408        Selector selector = clientConnection.getWriteSelector();
409        if (selector == null)
410        {
411          // The client connection does not provide a selector, so we'll fall back
412          // to a more inefficient way that will work without a selector.
413          while (buffer.hasRemaining() && (System.currentTimeMillis() < stopTime))
414          {
415            if (socketChannel.write(buffer) < 0)
416            {
417              // The client connection has been closed.  Disconnect and return.
418              clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT, false,
419                                          null);
420              return false;
421            }
422          }
423    
424          if (buffer.hasRemaining())
425          {
426            // If we've gotten here, then the write timed out.  Terminate the client
427            // connection.
428            clientConnection.disconnect(DisconnectReason.IO_TIMEOUT, false, null);
429            return false;
430          }
431    
432          return true;
433        }
434    
435    
436        // Register with the selector for handling write operations.
437        SelectionKey key = socketChannel.register(selector, SelectionKey.OP_WRITE);
438    
439        try
440        {
441          selector.select(waitTime);
442          while (buffer.hasRemaining())
443          {
444            long currentTime = System.currentTimeMillis();
445            if (currentTime >= stopTime)
446            {
447              // We've been blocked for too long.  Terminate the client connection.
448              clientConnection.disconnect(DisconnectReason.IO_TIMEOUT, false, null);
449              return false;
450            }
451            else
452            {
453              waitTime = stopTime - currentTime;
454            }
455    
456            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
457            while (iterator.hasNext())
458            {
459              SelectionKey k = iterator.next();
460              if (k.isWritable())
461              {
462                int bytesWritten = socketChannel.write(buffer);
463                if (bytesWritten < 0)
464                {
465                  // The client connection has been closed.  Disconnect and return.
466                  clientConnection.disconnect(DisconnectReason.CLIENT_DISCONNECT,
467                                              false, null);
468                  return false;
469                }
470    
471                iterator.remove();
472              }
473            }
474    
475            if (buffer.hasRemaining())
476            {
477              selector.select(waitTime);
478            }
479          }
480    
481          return true;
482        }
483        finally
484        {
485          if (key.isValid())
486          {
487            key.cancel();
488            selector.selectNow();
489          }
490        }
491      }
492    }
493