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-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.core;
018
019import static org.opends.messages.ConfigMessages.*;
020import static org.opends.server.util.StaticUtils.*;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.concurrent.ConcurrentHashMap;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.i18n.slf4j.LocalizedLogger;
028import org.forgerock.opendj.config.server.ConfigException;
029import org.forgerock.opendj.ldap.ResultCode;
030import org.forgerock.util.Utils;
031import org.forgerock.opendj.config.ClassPropertyDefinition;
032import org.forgerock.opendj.config.server.ConfigurationAddListener;
033import org.forgerock.opendj.config.server.ConfigurationChangeListener;
034import org.forgerock.opendj.config.server.ConfigurationDeleteListener;
035import org.forgerock.opendj.server.config.meta.SASLMechanismHandlerCfgDefn;
036import org.forgerock.opendj.server.config.server.RootCfg;
037import org.forgerock.opendj.server.config.server.SASLMechanismHandlerCfg;
038import org.opends.server.api.SASLMechanismHandler;
039import org.forgerock.opendj.config.server.ConfigChangeResult;
040import org.forgerock.opendj.ldap.DN;
041import org.opends.server.types.InitializationException;
042
043/**
044 * This class defines a utility that will be used to manage the set of SASL
045 * mechanism handlers defined in the Directory Server.  It will initialize the
046 * handlers when the server starts, and then will manage any additions,
047 * removals, or modifications to any SASL mechanism handlers while the server is
048 * running.
049 */
050public class SASLConfigManager implements
051    ConfigurationChangeListener<SASLMechanismHandlerCfg>,
052    ConfigurationAddListener<SASLMechanismHandlerCfg>,
053    ConfigurationDeleteListener<SASLMechanismHandlerCfg>
054{
055  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
056
057  /** A mapping between the DNs of the config entries and the associated SASL mechanism handlers. */
058  private final ConcurrentHashMap<DN, SASLMechanismHandler> handlers;
059
060  private final ServerContext serverContext;
061
062  /**
063   * Creates a new instance of this SASL mechanism handler config manager.
064   *
065   * @param serverContext
066   *            The server context.
067   */
068  public SASLConfigManager(ServerContext serverContext)
069  {
070    this.serverContext = serverContext;
071    handlers = new ConcurrentHashMap<>();
072  }
073
074  /**
075   * Initializes all SASL mechanism handlers currently defined in the Directory
076   * Server configuration.  This should only be called at Directory Server
077   * startup.
078   *
079   * @throws  ConfigException  If a configuration problem causes the SASL
080   *                           mechanism handler initialization process to fail.
081   *
082   * @throws  InitializationException  If a problem occurs while initializing
083   *                                   the SASL mechanism handlers that is not
084   *                                   related to the server configuration.
085   */
086  public void initializeSASLMechanismHandlers()
087         throws ConfigException, InitializationException
088  {
089    RootCfg rootConfiguration = serverContext.getRootConfig();
090    rootConfiguration.addSASLMechanismHandlerAddListener(this);
091    rootConfiguration.addSASLMechanismHandlerDeleteListener(this);
092
093    //Initialize the existing SASL mechanism handlers.
094    for (String handlerName : rootConfiguration.listSASLMechanismHandlers())
095    {
096      SASLMechanismHandlerCfg handlerConfiguration =
097           rootConfiguration.getSASLMechanismHandler(handlerName);
098      handlerConfiguration.addChangeListener(this);
099
100      if (handlerConfiguration.isEnabled())
101      {
102        String className = handlerConfiguration.getJavaClass();
103        try
104        {
105          SASLMechanismHandler handler = loadHandler(className,
106                                                     handlerConfiguration,
107                                                     true);
108          handlers.put(handlerConfiguration.dn(), handler);
109        }
110        catch (InitializationException ie)
111        {
112          logger.error(ie.getMessageObject());
113          continue;
114        }
115      }
116    }
117  }
118
119  @Override
120  public boolean isConfigurationAddAcceptable(
121                      SASLMechanismHandlerCfg configuration,
122                      List<LocalizableMessage> unacceptableReasons)
123  {
124    if (configuration.isEnabled())
125    {
126      // Get the name of the class and make sure we can instantiate it as a SASL
127      // mechanism handler.
128      String className = configuration.getJavaClass();
129      try
130      {
131        loadHandler(className, configuration, false);
132      }
133      catch (InitializationException ie)
134      {
135        unacceptableReasons.add(ie.getMessageObject());
136        return false;
137      }
138    }
139
140    // If we've gotten here, then it's fine.
141    return true;
142  }
143
144  @Override
145  public ConfigChangeResult applyConfigurationAdd(
146              SASLMechanismHandlerCfg configuration)
147  {
148    final ConfigChangeResult ccr = new ConfigChangeResult();
149
150    configuration.addChangeListener(this);
151
152    if (! configuration.isEnabled())
153    {
154      return ccr;
155    }
156
157    SASLMechanismHandler handler = null;
158
159    // Get the name of the class and make sure we can instantiate it as a SASL
160    // mechanism handler.
161    String className = configuration.getJavaClass();
162    try
163    {
164      handler = loadHandler(className, configuration, true);
165    }
166    catch (InitializationException ie)
167    {
168      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
169      ccr.addMessage(ie.getMessageObject());
170    }
171
172    if (ccr.getResultCode() == ResultCode.SUCCESS)
173    {
174      handlers.put(configuration.dn(), handler);
175    }
176
177    return ccr;
178  }
179
180  @Override
181  public boolean isConfigurationDeleteAcceptable(
182                      SASLMechanismHandlerCfg configuration,
183                      List<LocalizableMessage> unacceptableReasons)
184  {
185    // FIXME -- We should try to perform some check to determine whether the
186    // SASL mechanism handler is in use.
187    return true;
188  }
189
190  @Override
191  public ConfigChangeResult applyConfigurationDelete(
192              SASLMechanismHandlerCfg configuration)
193  {
194    final ConfigChangeResult ccr = new ConfigChangeResult();
195
196    SASLMechanismHandler handler = handlers.remove(configuration.dn());
197    if (handler != null)
198    {
199      handler.finalizeSASLMechanismHandler();
200    }
201
202    return ccr;
203  }
204
205  @Override
206  public boolean isConfigurationChangeAcceptable(
207                      SASLMechanismHandlerCfg configuration,
208                      List<LocalizableMessage> unacceptableReasons)
209  {
210    if (configuration.isEnabled())
211    {
212      // Get the name of the class and make sure we can instantiate it as a SASL
213      // mechanism handler.
214      String className = configuration.getJavaClass();
215      try
216      {
217        loadHandler(className, configuration, false);
218      }
219      catch (InitializationException ie)
220      {
221        unacceptableReasons.add(ie.getMessageObject());
222        return false;
223      }
224    }
225
226    // If we've gotten here, then it's fine.
227    return true;
228  }
229
230  @Override
231  public ConfigChangeResult applyConfigurationChange(
232              SASLMechanismHandlerCfg configuration)
233  {
234    final ConfigChangeResult ccr = new ConfigChangeResult();
235
236    // Get the existing handler if it's already enabled.
237    SASLMechanismHandler existingHandler = handlers.get(configuration.dn());
238
239    // If the new configuration has the handler disabled, then disable it if it
240    // is enabled, or do nothing if it's already disabled.
241    if (! configuration.isEnabled())
242    {
243      if (existingHandler != null)
244      {
245        SASLMechanismHandler handler = handlers.remove(configuration.dn());
246        if (handler != null)
247        {
248          handler.finalizeSASLMechanismHandler();
249        }
250      }
251
252      return ccr;
253    }
254
255    // Get the class for the SASL handler.  If the handler is already enabled,
256    // then we shouldn't do anything with it although if the class has changed
257    // then we'll at least need to indicate that administrative action is
258    // required.  If the handler is disabled, then instantiate the class and
259    // initialize and register it as a SASL mechanism handler.
260    String className = configuration.getJavaClass();
261    if (existingHandler != null)
262    {
263      if (! className.equals(existingHandler.getClass().getName()))
264      {
265        ccr.setAdminActionRequired(true);
266      }
267
268      return ccr;
269    }
270
271    SASLMechanismHandler handler = null;
272    try
273    {
274      handler = loadHandler(className, configuration, true);
275    }
276    catch (InitializationException ie)
277    {
278      ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode());
279      ccr.addMessage(ie.getMessageObject());
280    }
281
282    if (ccr.getResultCode() == ResultCode.SUCCESS)
283    {
284      handlers.put(configuration.dn(), handler);
285    }
286
287    return ccr;
288  }
289
290  /**
291   * Loads the specified class, instantiates it as a SASL mechanism hanlder, and
292   * optionally initializes that instance.
293   *
294   * @param  className      The fully-qualified name of the SASL mechanism
295   *                        handler class to load, instantiate, and initialize.
296   * @param  configuration  The configuration to use to initialize the handler.
297   *                        It must not be {@code null}.
298   * @param  initialize     Indicates whether the SASL mechanism handler
299   *                        instance should be initialized.
300   *
301   * @return  The possibly initialized SASL mechanism handler.
302   *
303   * @throws  InitializationException  If a problem occurred while attempting to
304   *                                   initialize the SASL mechanism handler.
305   */
306  private SASLMechanismHandler loadHandler(String className,
307                                           SASLMechanismHandlerCfg
308                                                configuration,
309                                           boolean initialize)
310          throws InitializationException
311  {
312    try
313    {
314      SASLMechanismHandlerCfgDefn definition =
315           SASLMechanismHandlerCfgDefn.getInstance();
316      ClassPropertyDefinition propertyDefinition =
317           definition.getJavaClassPropertyDefinition();
318      Class<? extends SASLMechanismHandler> handlerClass =
319           propertyDefinition.loadClass(className, SASLMechanismHandler.class);
320      SASLMechanismHandler handler = handlerClass.newInstance();
321
322      if (initialize)
323      {
324        handler.initializeSASLMechanismHandler(configuration);
325      }
326      else
327      {
328        List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
329        if (!handler.isConfigurationAcceptable(configuration, unacceptableReasons))
330        {
331          String reasons = Utils.joinAsString(".  ", unacceptableReasons);
332          throw new InitializationException(
333              ERR_CONFIG_SASL_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), reasons));
334        }
335      }
336
337      return handler;
338    }
339    catch (Exception e)
340    {
341      LocalizableMessage message = ERR_CONFIG_SASL_INITIALIZATION_FAILED.
342          get(className, configuration.dn(), stackTraceToSingleLineString(e));
343      throw new InitializationException(message, e);
344    }
345  }
346}