/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2014-2016 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.server.plugins; import static org.opends.server.util.CollectionUtils.*; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.io.ASN1; import org.forgerock.opendj.io.ASN1Reader; import org.forgerock.opendj.io.ASN1Writer; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.server.config.server.PluginCfg; import org.opends.server.api.plugin.DirectoryServerPlugin; import org.opends.server.api.plugin.PluginResult; import org.opends.server.api.plugin.PluginType; import org.opends.server.controls.ControlDecoder; import org.opends.server.types.Control; import org.opends.server.types.DirectoryException; import org.opends.server.types.OperationType; import org.opends.server.types.operation.*; /** * This class defines a very simple plugin that causes request processing to end * immediately and send a specific result code to the client. It will be * triggered by a control contained in the client request, and may be invoked * during either pre-parse or pre-operation processing. Short circuits can * also be registered for operations regardless of controls. */ public class ShortCircuitPlugin extends DirectoryServerPlugin { /** * The OID for the short circuit request control, which is used to flag * operations that should cause the operation processing to end immediately. */ public static final String OID_SHORT_CIRCUIT_REQUEST = "1.3.6.1.4.1.26027.1.999.3"; /** * The control used by this plugin. */ public static class ShortCircuitRequestControl extends Control { /** * ControlDecoder implementation to decode this control from a ByteString. */ private static final class Decoder implements ControlDecoder { /** {@inheritDoc} */ @Override public ShortCircuitRequestControl decode(boolean isCritical, ByteString value) throws DirectoryException { ASN1Reader reader = ASN1.getReader(value); try { reader.readStartSequence(); int resultCode = (int)reader.readInteger(); String section = reader.readOctetStringAsString(); reader.readEndSequence(); return new ShortCircuitRequestControl(isCritical, resultCode, section); } catch (Exception e) { // TODO: Need a better message throw new DirectoryException(ResultCode.PROTOCOL_ERROR, null, e); } } @Override public String getOID() { return OID_SHORT_CIRCUIT_REQUEST; } } /** * The Control Decoder that can be used to decode this control. */ public static final ControlDecoder DECODER = new Decoder(); private int resultCode; private String section; /** * Constructs a new control of this class. * * @param isCritical * Indicates whether support for this control should be considered * a critical part of the server processing. * @param resultCode * The result code to return to the client. * @param section * The section to use to determine when to short circuit. */ public ShortCircuitRequestControl(boolean isCritical, int resultCode, String section) { super(OID_SHORT_CIRCUIT_REQUEST, isCritical); this.resultCode = resultCode; this.section = section; } /** * Writes this control's value to an ASN.1 writer. The value (if any) * must be written as an ASN1OctetString. * * @param writer The ASN.1 writer to use. * @throws IOException If a problem occurs while writing to the stream. */ @Override protected void writeValue(ASN1Writer writer) throws IOException { writer.writeStartSequence(ASN1.UNIVERSAL_OCTET_STRING_TYPE); writer.writeStartSequence(); writer.writeInteger(resultCode); writer.writeOctetString(section); writer.writeEndSequence(); writer.writeEndSequence(); } /** * Retrieves the resultCode. * * @return The resultCode. */ public int getResultCode() { return resultCode; } /** * Retrieves the section. * * @return The section. */ public String getSection() { return section; } } /** * Creates a new instance of this Directory Server plugin. Every * plugin must implement a default constructor (it is the only one * that will be used to create plugins defined in the * configuration), and every plugin constructor must call * super() as its first element. */ public ShortCircuitPlugin() { super(); } /** {@inheritDoc} */ @Override public void initializePlugin(Set pluginTypes, PluginCfg configuration) throws ConfigException { // This plugin may only be used as a pre-parse or pre-operation plugin. for (PluginType t : pluginTypes) { switch (t) { case PRE_PARSE_ABANDON: case PRE_PARSE_ADD: case PRE_PARSE_BIND: case PRE_PARSE_COMPARE: case PRE_PARSE_DELETE: case PRE_PARSE_EXTENDED: case PRE_PARSE_MODIFY: case PRE_PARSE_MODIFY_DN: case PRE_PARSE_SEARCH: case PRE_PARSE_UNBIND: case PRE_OPERATION_ADD: case PRE_OPERATION_BIND: case PRE_OPERATION_COMPARE: case PRE_OPERATION_DELETE: case PRE_OPERATION_EXTENDED: case PRE_OPERATION_MODIFY: case PRE_OPERATION_MODIFY_DN: case PRE_OPERATION_SEARCH: // This is fine. break; default: throw new ConfigException(LocalizableMessage.raw("Invalid plugin type " + t + " for the short circuit plugin.")); } } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseAbandonOperation abandonOperation) { int resultCode = shortCircuitInternal(abandonOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseAddOperation addOperation) { int resultCode = shortCircuitInternal(addOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseBindOperation bindOperation) { int resultCode = shortCircuitInternal(bindOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseCompareOperation compareOperation) { int resultCode = shortCircuitInternal(compareOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseDeleteOperation deleteOperation) { int resultCode = shortCircuitInternal(deleteOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseExtendedOperation extendedOperation) { int resultCode = shortCircuitInternal(extendedOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseModifyOperation modifyOperation) { int resultCode = shortCircuitInternal(modifyOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseModifyDNOperation modifyDNOperation) { int resultCode = shortCircuitInternal(modifyDNOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseSearchOperation searchOperation) { int resultCode = shortCircuitInternal(searchOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreParse doPreParse(PreParseUnbindOperation unbindOperation) { int resultCode = shortCircuitInternal(unbindOperation, "PreParse"); if (resultCode >= 0) { return PluginResult.PreParse.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-parse")); } else { return PluginResult.PreParse.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationAddOperation addOperation) { int resultCode = shortCircuitInternal(addOperation, "PreOperation"); if (resultCode >= 0) { return PluginResult.PreOperation.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-operation")); } else { return PluginResult.PreOperation.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationBindOperation bindOperation) { int resultCode = shortCircuitInternal(bindOperation, "PreOperation"); if (resultCode >= 0) { return PluginResult.PreOperation.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-operation")); } else { return PluginResult.PreOperation.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationCompareOperation compareOperation) { int resultCode = shortCircuitInternal(compareOperation, "PreOperation"); if (resultCode >= 0) { return PluginResult.PreOperation.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-operation")); } else { return PluginResult.PreOperation.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationDeleteOperation deleteOperation) { int resultCode = shortCircuitInternal(deleteOperation, "PreOperation"); if (resultCode >= 0) { return PluginResult.PreOperation.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-operation")); } else { return PluginResult.PreOperation.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationExtendedOperation extendedOperation) { int resultCode = shortCircuitInternal(extendedOperation, "PreOperation"); if (resultCode >= 0) { return PluginResult.PreOperation.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-operation")); } else { return PluginResult.PreOperation.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationModifyOperation modifyOperation) { int resultCode = shortCircuitInternal(modifyOperation, "PreOperation"); if (resultCode >= 0) { return PluginResult.PreOperation.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-operation")); } else { return PluginResult.PreOperation.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationModifyDNOperation modifyDNOperation) { int resultCode = shortCircuitInternal(modifyDNOperation, "PreOperation"); if (resultCode >= 0) { return PluginResult.PreOperation.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-operation")); } else { return PluginResult.PreOperation.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationSearchOperation searchOperation) { int resultCode = shortCircuitInternal(searchOperation, "PreOperation"); if (resultCode >= 0) { return PluginResult.PreOperation.stopProcessing( ResultCode.valueOf(resultCode), LocalizableMessage.raw("Short-circuit in pre-operation")); } else { return PluginResult.PreOperation.continueOperationProcessing(); } } /** * Looks for a short-circuit request control in the operation and if one is * found with the correct section then generate the appropriate result. * * @param operation The operation to be processed. * @param section The section to match in the control value. * * @return The result code that should be immediately sent to the client, or * -1 if operation processing should continue as normal. */ private int shortCircuitInternal(PluginOperation operation, String section) { try { ShortCircuitRequestControl control = operation.getRequestControl(ShortCircuitRequestControl.DECODER); if (control != null && section.equalsIgnoreCase(control.getSection())) { return control.resultCode; } } catch (Exception e) { System.err.println("***** ERROR: Could not decode short circuit " + "control value: " + e); e.printStackTrace(); return -1; } // Check for registered short circuits. final String key = operation.getOperationType() + "/" + section.toLowerCase(); Integer resultCode = shortCircuits.get(key); if (resultCode != null) { final int reached = shortCircuitCounts.computeIfAbsent(key, k -> new AtomicInteger()).incrementAndGet(); final Integer maxTimes = shortCircuitLimits.get(key); if (maxTimes == null || reached <= maxTimes) { return resultCode; } // The short circuit was applied as many times as it was asked for: from now on the // operations are let through, which is how a transient failure is simulated. } // If we've gotten here, then we shouldn't short-circuit the operation // processing. return -1; } /** * Creates a short circuit request control with the specified result code and * section. * * @param resultCode The result code to return to the client. * @param section The section to use to determine when to short circuit. * * @return The appropriate short circuit request control. */ public static Control createShortCircuitControl(int resultCode, String section) { return new ShortCircuitRequestControl(false, resultCode, section); } /** * Retrieves a list containing a short circuit control with the specified * result code and section. * * @param resultCode The result code to return to the client. * @param section The section to use to determine when to short circuit. * * @return A list containing the appropriate short circuit request control. */ public static List createShortCircuitControlList(int resultCode, String section) { return newArrayList(createShortCircuitControl(resultCode, section)); } /** Registered short circuits for operations regardless of controls. */ private static Map shortCircuits = new ConcurrentHashMap<>(); /** How many times a registered short circuit was reached. */ private static final Map shortCircuitCounts = new ConcurrentHashMap<>(); /** How many times a registered short circuit must be applied, when it is limited. */ private static final Map shortCircuitLimits = new ConcurrentHashMap<>(); /** * Returns how many times the short circuit registered for the given operation type and * plugin point was reached. A short circuit registered for a limited number of times is * counted as reached by the operations it let through once that number was used up. * * @param operation The type of operation the short circuit applies to. * @param section The plugin point the short circuit applies to. * @return the number of operations which reached the short circuit */ public static int getShortCircuitCount(OperationType operation, String section) { final AtomicInteger count = shortCircuitCounts.get(operation + "/" + section.toLowerCase()); return count != null ? count.get() : 0; } /** * Register a short circuit for the given operation type and plugin point. * @param operation The type of operation the short circuit applies to. * @param section The plugin point the short circuit applies to. * @param resultCode The result code to be returned for the short circuit. */ public static void registerShortCircuit(OperationType operation, String section, int resultCode) { final String key = operation + "/" + section.toLowerCase(); // This registration applies to every operation, and it counts from zero: a limit or // a count left behind by a previous registration is not part of it. shortCircuitCounts.remove(key); shortCircuitLimits.remove(key); shortCircuits.put(key, resultCode); } /** * Register a short circuit which only applies to the given number of operations, the * ones which follow being let through: this is how a transient failure is simulated. * * @param operation The type of operation the short circuit applies to. * @param section The plugin point the short circuit applies to. * @param resultCode The result code to be returned for the short circuit. * @param maxTimes How many operations must be short circuited. */ public static void registerShortCircuit(OperationType operation, String section, int resultCode, int maxTimes) { final String key = operation + "/" + section.toLowerCase(); shortCircuitCounts.remove(key); shortCircuitLimits.put(key, maxTimes); shortCircuits.put(key, resultCode); } /** * Deregister a short circuit for the given operation type and plugin point. * @param operation The type of operation the short circuit applies to. * @param section The plugin point the short circuit applies to. */ public static void deregisterShortCircuit(OperationType operation, String section) { final String key = operation + "/" + section.toLowerCase(); shortCircuits.remove(key); shortCircuitLimits.remove(key); // The count belongs to the registration which is being removed: a test which counts // the operations it short circuits must not inherit the count of the previous one. shortCircuitCounts.remove(key); } }