001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.protocols.jmx; 018 019import java.net.InetAddress; 020import java.util.Collection; 021import java.util.LinkedList; 022import java.util.concurrent.atomic.AtomicInteger; 023import java.util.concurrent.atomic.AtomicLong; 024 025import javax.management.Notification; 026import javax.management.NotificationListener; 027import javax.management.remote.JMXConnectionNotification; 028 029import org.forgerock.i18n.LocalizableMessage; 030import org.forgerock.i18n.LocalizableMessageBuilder; 031import org.forgerock.i18n.slf4j.LocalizedLogger; 032import org.forgerock.opendj.ldap.DN; 033import org.forgerock.opendj.ldap.ResultCode; 034import org.opends.server.api.ClientConnection; 035import org.opends.server.api.ConnectionHandler; 036import org.opends.server.core.*; 037import org.opends.server.protocols.internal.InternalSearchOperation; 038import org.opends.server.protocols.internal.SearchRequest; 039import org.opends.server.types.*; 040 041import static org.opends.messages.ProtocolMessages.*; 042 043/** 044 * This class defines the set of methods and structures that must be implemented 045 * by a Directory Server client connection. 046 */ 047public class JmxClientConnection 048 extends ClientConnection implements NotificationListener 049{ 050 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 051 052 /** The message ID counter to use for jmx connections. */ 053 private final AtomicInteger nextMessageID; 054 /** The operation ID counter to use for operations on this connection. */ 055 private final AtomicLong nextOperationID; 056 /** The empty operation list for this connection. */ 057 private final LinkedList<Operation> operationList; 058 /** The connection ID for this client connection. */ 059 private final long connectionID; 060 /** The JMX connection ID for this client connection. */ 061 protected String jmxConnectionID; 062 /** The reference to the connection handler that accepted this connection. */ 063 private final JmxConnectionHandler jmxConnectionHandler; 064 /** Indicate that the disconnect process is started. */ 065 private boolean disconnectStarted; 066 067 /** 068 * Creates a new Jmx client connection that will be authenticated as 069 * as the specified user. 070 * 071 * @param jmxConnectionHandler 072 * The connection handler on which we should be registered 073 * @param authInfo 074 * the User authentication info 075 */ 076 public JmxClientConnection(JmxConnectionHandler jmxConnectionHandler, 077 AuthenticationInfo authInfo) 078 { 079 super(); 080 081 nextMessageID = new AtomicInteger(1); 082 nextOperationID = new AtomicLong(0); 083 084 this.jmxConnectionHandler = jmxConnectionHandler; 085 jmxConnectionHandler.registerClientConnection(this); 086 087 setAuthenticationInfo(authInfo); 088 089 connectionID = DirectoryServer.newConnectionAccepted(this); 090 if (connectionID < 0) 091 { 092 disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true, 093 ERR_CONNHANDLER_REJECTED_BY_SERVER.get()); 094 } 095 operationList = new LinkedList<>(); 096 097 // Register the Jmx Notification listener (this) 098 jmxConnectionHandler.getRMIConnector().jmxRmiConnectorNoClientCertificate 099 .addNotificationListener(this, null, null); 100 } 101 102 /** {@inheritDoc} */ 103 @Override 104 public void handleNotification(Notification notif, Object handback) 105 { 106 // We don't have the expected notification 107 if ( ! (notif instanceof JMXConnectionNotification)) 108 { 109 return ; 110 } 111 JMXConnectionNotification jcn = (JMXConnectionNotification) notif; 112 113 // The only handled notifications are CLOSED and FAILED 114 if (!JMXConnectionNotification.CLOSED.equals(jcn.getType()) 115 && !JMXConnectionNotification.FAILED.equals(jcn.getType())) 116 { 117 return; 118 } 119 120 // Check if the closed connection corresponds to the current connection 121 if (!jcn.getConnectionId().equals(jmxConnectionID)) 122 { 123 return; 124 } 125 126 // Ok, we can perform the unbind: call finalize 127 disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null); 128 } 129 130 131 /** 132 * Retrieves the operation ID that should be used for the next Jmx 133 * operation. 134 * 135 * @return The operation ID that should be used for the next Jmx 136 * operation. 137 */ 138 public long nextOperationID() 139 { 140 long opID = nextOperationID.getAndIncrement(); 141 if (opID < 0) 142 { 143 synchronized (nextOperationID) 144 { 145 if (nextOperationID.get() < 0) 146 { 147 nextOperationID.set(1); 148 return 0; 149 } 150 else 151 { 152 return nextOperationID.getAndIncrement(); 153 } 154 } 155 } 156 157 return opID; 158 } 159 160 161 162 /** 163 * Retrieves the message ID that should be used for the next Jmx 164 * operation. 165 * 166 * @return The message ID that should be used for the next Jmx 167 * operation. 168 */ 169 public int nextMessageID() 170 { 171 int msgID = nextMessageID.getAndIncrement(); 172 if (msgID < 0) 173 { 174 synchronized (nextMessageID) 175 { 176 if (nextMessageID.get() < 0) 177 { 178 nextMessageID.set(2); 179 return 1; 180 } 181 else 182 { 183 return nextMessageID.getAndIncrement(); 184 } 185 } 186 } 187 188 return msgID; 189 } 190 191 192 193 /** 194 * Retrieves the unique identifier that has been assigned to this connection. 195 * 196 * @return The unique identifier that has been assigned to this connection. 197 */ 198 @Override 199 public long getConnectionID() 200 { 201 return connectionID; 202 } 203 204 /** 205 * Retrieves the connection handler that accepted this client connection. 206 * 207 * @return The connection handler that accepted this client connection. 208 */ 209 @Override 210 public ConnectionHandler<?> getConnectionHandler() 211 { 212 return jmxConnectionHandler; 213 } 214 215 /** 216 * Retrieves the protocol that the client is using to communicate with the 217 * Directory Server. 218 * 219 * @return The protocol that the client is using to communicate with the 220 * Directory Server. 221 */ 222 @Override 223 public String getProtocol() 224 { 225 return "jmx"; 226 } 227 228 229 230 /** 231 * Retrieves a string representation of the address of the client. 232 * 233 * @return A string representation of the address of the client. 234 */ 235 @Override 236 public String getClientAddress() 237 { 238 return "jmx"; 239 } 240 241 242 243 /** 244 * Retrieves the port number for this connection on the client system. 245 * 246 * @return The port number for this connection on the client system. 247 */ 248 @Override 249 public int getClientPort() 250 { 251 return -1; 252 } 253 254 255 256 /** 257 * Retrieves a string representation of the address on the server to which the 258 * client connected. 259 * 260 * @return A string representation of the address on the server to which the 261 * client connected. 262 */ 263 @Override 264 public String getServerAddress() 265 { 266 return "jmx"; 267 } 268 269 270 271 /** 272 * Retrieves the port number for this connection on the server 273 * system if available. 274 * 275 * @return The port number for this connection on the server system 276 * or -1 if there is no server port associated with this 277 * connection (e.g. internal client). 278 */ 279 @Override 280 public int getServerPort() 281 { 282 return -1; 283 } 284 285 286 287 /** 288 * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the remote 289 * client system. 290 * 291 * @return The <CODE>java.net.InetAddress</CODE> associated with the remote 292 * client system. It may be <CODE>null</CODE> if the client is not 293 * connected over an IP-based connection. 294 */ 295 @Override 296 public InetAddress getRemoteAddress() 297 { 298 return null; 299 } 300 301 302 303 /** 304 * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory Server 305 * system to which the client has established the connection. 306 * 307 * @return The <CODE>java.net.InetAddress</CODE> for the Directory Server 308 * system to which the client has established the connection. It may 309 * be <CODE>null</CODE> if the client is not connected over an 310 * IP-based connection. 311 */ 312 @Override 313 public InetAddress getLocalAddress() 314 { 315 return null; 316 } 317 318 /** {@inheritDoc} */ 319 @Override 320 public boolean isConnectionValid() 321 { 322 return !disconnectStarted; 323 } 324 325 /** 326 * Indicates whether this client connection is currently using a secure 327 * mechanism to communicate with the server. Note that this may change over 328 * time based on operations performed by the client or server (e.g., it may go 329 * from <CODE>false</CODE> to <CODE>true</CODE> if the client uses the 330 * StartTLS extended operation). 331 * 332 * @return <CODE>true</CODE> if the client connection is currently using a 333 * secure mechanism to communicate with the server, or 334 * <CODE>false</CODE> if not. 335 */ 336 @Override 337 public boolean isSecure() 338 { 339 return false; 340 } 341 342 343 /** 344 * Retrieves the human-readable name of the security mechanism that is used to 345 * protect communication with this client. 346 * 347 * @return The human-readable name of the security mechanism that is used to 348 * protect communication with this client, or <CODE>null</CODE> if no 349 * security is in place. 350 */ 351 public String getSecurityMechanism() 352 { 353 return "NULL"; 354 } 355 356 357 358 /** 359 * Sends a response to the client based on the information in the provided 360 * operation. 361 * 362 * @param operation The operation for which to send the response. 363 */ 364 @Override 365 public void sendResponse(Operation operation) 366 { 367 // There will not be any response sent by this method, since there is not an 368 // actual connection. 369 } 370 371 372 /** 373 * Processes an Jmx search operation with the provided information. 374 * 375 * @param request The search request. 376 * @return A reference to the internal search operation that was processed 377 * and contains information about the result of the processing as 378 * well as lists of the matching entries and search references. 379 */ 380 public InternalSearchOperation processSearch(SearchRequest request) 381 { 382 InternalSearchOperation searchOperation = 383 new InternalSearchOperation(this, nextOperationID(), nextMessageID(), request); 384 385 if (! hasPrivilege(Privilege.JMX_READ, null)) 386 { 387 LocalizableMessage message = ERR_JMX_SEARCH_INSUFFICIENT_PRIVILEGES.get(); 388 searchOperation.setErrorMessage(new LocalizableMessageBuilder(message)); 389 searchOperation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS) ; 390 } 391 else 392 { 393 searchOperation.run(); 394 } 395 return searchOperation; 396 } 397 398 /** 399 * Sends the provided search result entry to the client. 400 * 401 * @param searchOperation The search operation with which the entry is 402 * associated. 403 * @param searchEntry The search result entry to be sent to the client. 404 * 405 * @throws DirectoryException If a problem occurs while attempting to send 406 * the entry to the client and the search should 407 * be terminated. 408 */ 409 @Override 410 public void sendSearchEntry(SearchOperation searchOperation, 411 SearchResultEntry searchEntry) 412 throws DirectoryException 413 { 414 ((InternalSearchOperation) searchOperation).addSearchEntry(searchEntry); 415 } 416 417 418 419 /** 420 * Sends the provided search result reference to the client. 421 * 422 * @param searchOperation The search operation with which the reference is 423 * associated. 424 * @param searchReference The search result reference to be sent to the 425 * client. 426 * 427 * @return <CODE>true</CODE> if the client is able to accept referrals, or 428 * <CODE>false</CODE> if the client cannot handle referrals and no 429 * more attempts should be made to send them for the associated 430 * search operation. 431 * 432 * @throws DirectoryException If a problem occurs while attempting to send 433 * the reference to the client and the search 434 * should be terminated. 435 */ 436 @Override 437 public boolean sendSearchReference(SearchOperation searchOperation, 438 SearchResultReference searchReference) 439 throws DirectoryException 440 { 441 ((InternalSearchOperation) 442 searchOperation).addSearchReference(searchReference); 443 return true; 444 } 445 446 447 448 449 /** 450 * Sends the provided intermediate response message to the client. 451 * 452 * @param intermediateResponse The intermediate response message to be sent. 453 * 454 * @return <CODE>true</CODE> if processing on the associated operation should 455 * continue, or <CODE>false</CODE> if not. 456 */ 457 @Override 458 protected boolean sendIntermediateResponseMessage( 459 IntermediateResponse intermediateResponse) 460 { 461 // FIXME -- Do we need to support Jmx intermediate responses? If so, 462 // then implement this. 463 return false; 464 } 465 466 467 468 469 /** 470 * Closes the connection to the client, optionally sending it a message 471 * indicating the reason for the closure. Note that the ability to send a 472 * notice of disconnection may not be available for all protocols or under all 473 * circumstances. 474 * 475 * @param disconnectReason The disconnect reason that provides the generic 476 * cause for the disconnect. 477 * @param sendNotification Indicates whether to try to provide notification 478 * to the client that the connection will be closed. 479 * @param message The message to send to the client. It may be 480 * <CODE>null</CODE> if no notification is to be 481 * sent. 482 */ 483 @Override 484 public void disconnect(DisconnectReason disconnectReason, 485 boolean sendNotification, 486 LocalizableMessage message) 487 { 488 // we are already performing a disconnect 489 if (disconnectStarted) 490 { 491 return; 492 } 493 disconnectStarted = true ; 494 jmxConnectionHandler.unregisterClientConnection(this); 495 DirectoryServer.connectionClosed(this); 496 finalizeConnectionInternal(); 497 498 // unbind the underlying connection 499 try 500 { 501 UnbindOperationBasis unbindOp = new UnbindOperationBasis( 502 this, nextOperationID(), nextMessageID(), null); 503 unbindOp.run(); 504 } 505 catch (Exception e) 506 { 507 // TODO print a message ? 508 logger.traceException(e); 509 } 510 511 // Call postDisconnectPlugins 512 try 513 { 514 PluginConfigManager pluginManager = 515 DirectoryServer.getPluginConfigManager(); 516 pluginManager.invokePostDisconnectPlugins(this, disconnectReason, 517 message); 518 } 519 catch (Exception e) 520 { 521 logger.traceException(e); 522 } 523 } 524 525 526 527 /** 528 * Retrieves the set of operations in progress for this client connection. 529 * This list must not be altered by any caller. 530 * 531 * @return The set of operations in progress for this client connection. 532 */ 533 @Override 534 public Collection<Operation> getOperationsInProgress() 535 { 536 return operationList; 537 } 538 539 540 541 /** 542 * Retrieves the operation in progress with the specified message ID. 543 * 544 * @param messageID The message ID of the operation to retrieve. 545 * 546 * @return The operation in progress with the specified message ID, or 547 * <CODE>null</CODE> if no such operation could be found. 548 */ 549 @Override 550 public Operation getOperationInProgress(int messageID) 551 { 552 // Jmx operations will not be tracked. 553 return null; 554 } 555 556 557 558 /** 559 * Removes the provided operation from the set of operations in progress for 560 * this client connection. Note that this does not make any attempt to 561 * cancel any processing that may already be in progress for the operation. 562 * 563 * @param messageID The message ID of the operation to remove from the set 564 * of operations in progress. 565 * 566 * @return <CODE>true</CODE> if the operation was found and removed from the 567 * set of operations in progress, or <CODE>false</CODE> if not. 568 */ 569 @Override 570 public boolean removeOperationInProgress(int messageID) 571 { 572 // No implementation is required, since Jmx operations will not be 573 // tracked. 574 return false; 575 } 576 577 578 579 /** 580 * Attempts to cancel the specified operation. 581 * 582 * @param messageID The message ID of the operation to cancel. 583 * @param cancelRequest An object providing additional information about how 584 * the cancel should be processed. 585 * 586 * @return A cancel result that either indicates that the cancel was 587 * successful or provides a reason that it was not. 588 */ 589 @Override 590 public CancelResult cancelOperation(int messageID, 591 CancelRequest cancelRequest) 592 { 593 // Jmx operations cannot be cancelled. 594 // TODO: i18n 595 return new CancelResult(ResultCode.CANNOT_CANCEL, 596 LocalizableMessage.raw("Jmx operations cannot be cancelled")); 597 } 598 599 600 601 /** 602 * Attempts to cancel all operations in progress on this connection. 603 * 604 * @param cancelRequest An object providing additional information about how 605 * the cancel should be processed. 606 */ 607 @Override 608 public void cancelAllOperations(CancelRequest cancelRequest) 609 { 610 // No implementation is required since Jmx operations cannot be 611 // cancelled. 612 } 613 614 615 616 /** 617 * Attempts to cancel all operations in progress on this connection except the 618 * operation with the specified message ID. 619 * 620 * @param cancelRequest An object providing additional information about how 621 * the cancel should be processed. 622 * @param messageID The message ID of the operation that should not be 623 * canceled. 624 */ 625 @Override 626 public void cancelAllOperationsExcept(CancelRequest cancelRequest, 627 int messageID) 628 { 629 // No implementation is required since Jmx operations cannot be 630 // cancelled. 631 } 632 633 /** {@inheritDoc} */ 634 @Override 635 public String getMonitorSummary() 636 { 637 StringBuilder buffer = new StringBuilder(); 638 buffer.append("connID=\""); 639 buffer.append(connectionID); 640 buffer.append("\" connectTime=\""); 641 buffer.append(getConnectTimeString()); 642 buffer.append("\" jmxConnID=\""); 643 buffer.append(jmxConnectionID); 644 buffer.append("\" authDN=\""); 645 646 DN authDN = getAuthenticationInfo().getAuthenticationDN(); 647 if (authDN != null) 648 { 649 buffer.append(authDN); 650 } 651 buffer.append("\""); 652 653 return buffer.toString(); 654 } 655 656 657 658 /** 659 * Appends a string representation of this client connection to the provided 660 * buffer. 661 * 662 * @param buffer The buffer to which the information should be appended. 663 */ 664 @Override 665 public void toString(StringBuilder buffer) 666 { 667 buffer.append("JmxClientConnection(connID="); 668 buffer.append(connectionID); 669 buffer.append(", authDN=\""); 670 buffer.append(getAuthenticationInfo().getAuthenticationDN()); 671 buffer.append("\")"); 672 } 673 674 /** 675 * Called by the Gc when the object is garbage collected 676 * Release the cursor in case the iterator was badly used and releaseCursor 677 * was never called. 678 */ 679 @Override 680 protected void finalize() 681 { 682 disconnect(DisconnectReason.OTHER, false, null); 683 } 684 685 /** 686 * To be implemented. 687 * 688 * @return number of operations performed on this connection 689 */ 690 @Override 691 public long getNumberOfOperations() { 692 // JMX connections will not be limited. 693 return 0; 694 } 695 696 /** {@inheritDoc} */ 697 @Override 698 public int getSSF() { 699 return 0; 700 } 701} 702