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