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.core;
028    import org.opends.messages.MessageBuilder;
029    
030    
031    import java.util.ArrayList;
032    import java.util.Iterator;
033    import java.util.List;
034    
035    import org.opends.server.api.ClientConnection;
036    import org.opends.server.api.ExtendedOperationHandler;
037    import org.opends.server.api.plugin.PluginResult;
038    import org.opends.server.protocols.asn1.ASN1OctetString;
039    import org.opends.server.types.operation.PostOperationExtendedOperation;
040    import org.opends.server.types.operation.PostResponseExtendedOperation;
041    import org.opends.server.types.operation.PreOperationExtendedOperation;
042    import org.opends.server.types.operation.PreParseExtendedOperation;
043    
044    import static org.opends.server.core.CoreConstants.*;
045    import static org.opends.server.loggers.AccessLogger.*;
046    import static org.opends.server.loggers.debug.DebugLogger.*;
047    
048    import org.opends.server.loggers.debug.DebugLogger;
049    import org.opends.server.loggers.debug.DebugTracer;
050    import org.opends.server.types.*;
051    import static org.opends.messages.CoreMessages.*;
052    import static org.opends.server.util.ServerConstants.*;
053    
054    
055    
056    /**
057     * This class defines an extended operation, which can perform virtually any
058     * kind of task.
059     */
060    public class ExtendedOperationBasis
061           extends AbstractOperation
062           implements ExtendedOperation,
063                      PreParseExtendedOperation,
064                      PreOperationExtendedOperation,
065                      PostOperationExtendedOperation,
066                      PostResponseExtendedOperation
067    {
068      /**
069       * The tracer object for the debug logger.
070       */
071      private static final DebugTracer TRACER = DebugLogger.getTracer();
072    
073      // The value for the request associated with this extended operation.
074      private ASN1OctetString requestValue;
075    
076      // The value for the response associated with this extended operation.
077      private ASN1OctetString responseValue;
078    
079      // Indicates whether a response has yet been sent for this operation.
080      private boolean responseSent;
081    
082      // The set of response controls for this extended operation.
083      private List<Control> responseControls;
084    
085      // The OID for the request associated with this extended operation.
086      private String requestOID;
087    
088      // The OID for the response associated with this extended operation.
089      private String responseOID;
090    
091    
092    
093      /**
094       * Creates a new extended operation with the provided information.
095       *
096       * @param  clientConnection  The client connection with which this operation
097       *                           is associated.
098       * @param  operationID       The operation ID for this operation.
099       * @param  messageID         The message ID of the request with which this
100       *                           operation is associated.
101       * @param  requestControls   The set of controls included in the request.
102       * @param  requestOID        The OID for the request associated with this
103       *                           extended operation.
104       * @param  requestValue      The value for the request associated with this
105       *                           extended operation.
106       */
107      public ExtendedOperationBasis(ClientConnection clientConnection,
108                               long operationID,
109                               int messageID, List<Control> requestControls,
110                               String requestOID, ASN1OctetString requestValue)
111      {
112        super(clientConnection, operationID, messageID, requestControls);
113    
114    
115        this.requestOID   = requestOID;
116        this.requestValue = requestValue;
117    
118        responseOID      = null;
119        responseValue    = null;
120        responseControls = new ArrayList<Control>();
121        cancelRequest    = null;
122        responseSent     = false;
123    
124        if (requestOID.equals(OID_CANCEL_REQUEST))
125        {
126          cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL,
127              ERR_CANNOT_CANCEL_CANCEL.get());
128        }
129        if(requestOID.equals(OID_START_TLS_REQUEST))
130        {
131          cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL,
132              ERR_CANNOT_CANCEL_START_TLS.get());
133        }
134      }
135    
136    
137    
138      /**
139       * {@inheritDoc}
140       */
141      public final String getRequestOID()
142      {
143        return requestOID;
144      }
145    
146    
147    
148      /**
149       * Specifies the OID for the request associated with this extended operation.
150       * This should only be called by pre-parse plugins.
151       *
152       * @param  requestOID  The OID for the request associated with this extended
153       *                     operation.
154       */
155      public final void setRequestOID(String requestOID)
156      {
157        this.requestOID = requestOID;
158      }
159    
160    
161    
162      /**
163       * {@inheritDoc}
164       */
165      public final ASN1OctetString getRequestValue()
166      {
167        return requestValue;
168      }
169    
170    
171    
172      /**
173       * Specifies the value for the request associated with this extended
174       * operation.  This should only be called by pre-parse plugins.
175       *
176       * @param  requestValue  The value for the request associated with this
177       *                       extended operation.
178       */
179      public final void setRequestValue(ASN1OctetString requestValue)
180      {
181        this.requestValue = requestValue;
182      }
183    
184    
185    
186      /**
187       * {@inheritDoc}
188       */
189      public final String getResponseOID()
190      {
191        return responseOID;
192      }
193    
194    
195    
196      /**
197       * {@inheritDoc}
198       */
199      public final void setResponseOID(String responseOID)
200      {
201        this.responseOID = responseOID;
202      }
203    
204    
205    
206      /**
207       * {@inheritDoc}
208       */
209      public final ASN1OctetString getResponseValue()
210      {
211        return responseValue;
212      }
213    
214    
215    
216      /**
217       * {@inheritDoc}
218       */
219      public final void setResponseValue(ASN1OctetString responseValue)
220      {
221        this.responseValue = responseValue;
222      }
223    
224    
225      /**
226       * {@inheritDoc}
227       */
228      @Override()
229      public final OperationType getOperationType()
230      {
231        // Note that no debugging will be done in this method because it is a likely
232        // candidate for being called by the logging subsystem.
233    
234        return OperationType.EXTENDED;
235      }
236    
237    
238    
239      /**
240       * {@inheritDoc}
241       */
242      @Override()
243      public final String[][] getRequestLogElements()
244      {
245        // Note that no debugging will be done in this method because it is a likely
246        // candidate for being called by the logging subsystem.
247    
248        return new String[][]
249        {
250          new String[] { LOG_ELEMENT_EXTENDED_REQUEST_OID, requestOID }
251        };
252      }
253    
254    
255    
256      /**
257       * {@inheritDoc}
258       */
259      @Override()
260      public final String[][] getResponseLogElements()
261      {
262        // Note that no debugging will be done in this method because it is a likely
263        // candidate for being called by the logging subsystem.
264    
265        String resultCode = String.valueOf(getResultCode().getIntValue());
266    
267        String errorMessage;
268        MessageBuilder errorMessageBuffer = getErrorMessage();
269        if (errorMessageBuffer == null)
270        {
271          errorMessage = null;
272        }
273        else
274        {
275          errorMessage = errorMessageBuffer.toString();
276        }
277    
278        String matchedDNStr;
279        DN matchedDN = getMatchedDN();
280        if (matchedDN == null)
281        {
282          matchedDNStr = null;
283        }
284        else
285        {
286          matchedDNStr = matchedDN.toString();
287        }
288    
289        String referrals;
290        List<String> referralURLs = getReferralURLs();
291        if ((referralURLs == null) || referralURLs.isEmpty())
292        {
293          referrals = null;
294        }
295        else
296        {
297          StringBuilder buffer = new StringBuilder();
298          Iterator<String> iterator = referralURLs.iterator();
299          buffer.append(iterator.next());
300    
301          while (iterator.hasNext())
302          {
303            buffer.append(", ");
304            buffer.append(iterator.next());
305          }
306    
307          referrals = buffer.toString();
308        }
309    
310        String processingTime =
311             String.valueOf(getProcessingTime());
312    
313        return new String[][]
314        {
315          new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
316          new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
317          new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
318          new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
319          new String[] { LOG_ELEMENT_EXTENDED_RESPONSE_OID, responseOID },
320          new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
321        };
322      }
323    
324    
325    
326      /**
327       * {@inheritDoc}
328       */
329      @Override()
330      public final List<Control> getResponseControls()
331      {
332        return responseControls;
333      }
334    
335    
336    
337      /**
338       * {@inheritDoc}
339       */
340      @Override()
341      public final void addResponseControl(Control control)
342      {
343        responseControls.add(control);
344      }
345    
346    
347    
348      /**
349       * {@inheritDoc}
350       */
351      @Override()
352      public final void removeResponseControl(Control control)
353      {
354        responseControls.remove(control);
355      }
356    
357    
358    
359      /**
360       * Performs the work of actually processing this operation.  This
361       * should include all processing for the operation, including
362       * invoking plugins, logging messages, performing access control,
363       * managing synchronization, and any other work that might need to
364       * be done in the course of processing.
365       */
366      public final void run()
367      {
368        setResultCode(ResultCode.UNDEFINED);
369    
370        // Start the processing timer.
371        setProcessingStartTime();
372    
373        // Log the extended request message.
374        logExtendedRequest(this);
375    
376        // Get the plugin config manager that will be used for invoking plugins.
377        PluginConfigManager pluginConfigManager =
378             DirectoryServer.getPluginConfigManager();
379    
380        try
381        {
382          // Check for and handle a request to cancel this operation.
383          checkIfCanceled(false);
384    
385          // Invoke the pre-parse extended plugins.
386          PluginResult.PreParse preParseResult =
387               pluginConfigManager.invokePreParseExtendedPlugins(this);
388    
389          if(!preParseResult.continueProcessing())
390          {
391            setResultCode(preParseResult.getResultCode());
392            appendErrorMessage(preParseResult.getErrorMessage());
393            setMatchedDN(preParseResult.getMatchedDN());
394            setReferralURLs(preParseResult.getReferralURLs());
395            return;
396          }
397    
398          checkIfCanceled(false);
399    
400    
401          // Get the extended operation handler for the request OID.  If there is
402          // none, then fail.
403          ExtendedOperationHandler handler =
404               DirectoryServer.getExtendedOperationHandler(requestOID);
405          if (handler == null)
406          {
407            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
408            appendErrorMessage(ERR_EXTENDED_NO_HANDLER.get(
409                    String.valueOf(requestOID)));
410            return;
411          }
412    
413    
414          // Look at the controls included in the request and ensure that all
415          // critical controls are supported by the handler.
416          List<Control> requestControls = getRequestControls();
417          if ((requestControls != null) && (! requestControls.isEmpty()))
418          {
419            for (Control c : requestControls)
420            {
421              if (!AccessControlConfigManager.getInstance().
422                      getAccessControlHandler().
423                      isAllowed(this.getAuthorizationDN(), this, c)) {
424                setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
425    
426                appendErrorMessage(ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(
427                        c.getOID()));
428                return;
429              }
430              if (! c.isCritical())
431              {
432                // The control isn't critical, so we don't care if it's supported
433                // or not.
434              }
435              else if (! handler.supportsControl(c.getOID()))
436              {
437                setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
438    
439                appendErrorMessage(ERR_EXTENDED_UNSUPPORTED_CRITICAL_CONTROL.get(
440                        String.valueOf(requestOID),
441                        c.getOID()));
442    
443                return;
444              }
445            }
446          }
447    
448    
449          // Check to see if the client has permission to perform the
450          // extended operation.
451    
452          // FIXME: for now assume that this will check all permission
453          // pertinent to the operation. This includes proxy authorization
454          // and any other controls specified.
455          if (AccessControlConfigManager.getInstance()
456              .getAccessControlHandler().isAllowed(this) == false) {
457            setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
458    
459            appendErrorMessage(ERR_EXTENDED_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
460                    String.valueOf(requestOID)));
461    
462            return;
463          }
464    
465          try
466          {
467            // Invoke the pre-operation extended plugins.
468            PluginResult.PreOperation preOpResult =
469                pluginConfigManager.invokePreOperationExtendedPlugins(this);
470            if(!preOpResult.continueProcessing())
471            {
472              setResultCode(preParseResult.getResultCode());
473              appendErrorMessage(preParseResult.getErrorMessage());
474              setMatchedDN(preParseResult.getMatchedDN());
475              setReferralURLs(preParseResult.getReferralURLs());
476              return;
477            }
478    
479            checkIfCanceled(false);
480    
481            // Actually perform the processing for this operation.
482            handler.processExtendedOperation(this);
483    
484          }
485          finally
486          {
487            pluginConfigManager.invokePostOperationExtendedPlugins(this);
488          }
489    
490        }
491        catch(CanceledOperationException coe)
492        {
493          if (debugEnabled())
494          {
495            TRACER.debugCaught(DebugLogLevel.ERROR, coe);
496          }
497    
498          setResultCode(ResultCode.CANCELED);
499          cancelResult = new CancelResult(ResultCode.CANCELED, null);
500    
501          appendErrorMessage(coe.getCancelRequest().getCancelReason());
502        }
503        finally
504        {
505          // Stop the processing timer.
506          setProcessingStopTime();
507    
508          // Send the response to the client, if it has not already been sent.
509          if (! responseSent)
510          {
511            responseSent = true;
512            if(cancelRequest == null || cancelResult == null ||
513                cancelResult.getResultCode() != ResultCode.CANCELED ||
514                cancelRequest.notifyOriginalRequestor() ||
515                DirectoryServer.notifyAbandonedOperations())
516            {
517              clientConnection.sendResponse(this);
518            }
519          }
520    
521          // Log the extended response.
522          logExtendedResponse(this);
523    
524          // Invoke the post-response extended plugins.
525          pluginConfigManager.invokePostResponseExtendedPlugins(this);
526    
527          // If no cancel result, set it
528          if(cancelResult == null)
529          {
530            cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
531          }
532        }
533      }
534    
535    
536    
537      /**
538       * Sends an extended response to the client if none has already been sent.
539       * Note that extended operation handlers are strongly discouraged from using
540       * this method when it is not necessary because its use will prevent the
541       * response from being sent after post-operation plugin processing, which may
542       * impact the result that should be included.  Nevertheless, it may be needed
543       * in some special cases in which the response must be sent before the
544       * extended operation handler completes its processing (e.g., the StartTLS
545       * operation in which the response must be sent in the clear before actually
546       * enabling TLS protection).
547       */
548      public final void sendExtendedResponse()
549      {
550        if (! responseSent)
551        {
552          responseSent = true;
553          clientConnection.sendResponse(this);
554        }
555      }
556    
557    
558      /**
559       * {@inheritDoc}
560       */
561      public final void setResponseSent()
562      {
563        this.responseSent = true;
564      }
565    
566      /**
567       * {@inheritDoc}
568       */
569      @Override()
570      public final void toString(StringBuilder buffer)
571      {
572        buffer.append("ExtendedOperation(connID=");
573        buffer.append(clientConnection.getConnectionID());
574        buffer.append(", opID=");
575        buffer.append(operationID);
576        buffer.append(", oid=");
577        buffer.append(requestOID);
578        buffer.append(")");
579      }
580    
581    }
582