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