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