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