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