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 2012-2016 ForgeRock AS.
016 */
017package org.opends.server.tools;
018
019import static com.forgerock.opendj.cli.ArgumentConstants.*;
020import static com.forgerock.opendj.cli.CliMessages.INFO_FILE_PLACEHOLDER;
021import static com.forgerock.opendj.cli.CliMessages.INFO_JMXPORT_PLACEHOLDER;
022import static com.forgerock.opendj.cli.CliMessages.INFO_PORT_PLACEHOLDER;
023import static com.forgerock.opendj.cli.CommonArguments.*;
024import static com.forgerock.opendj.cli.Utils.*;
025
026import static org.opends.messages.ConfigMessages.*;
027import static org.opends.messages.ToolMessages.*;
028import static org.opends.server.config.ConfigConstants.*;
029import static org.opends.server.util.ServerConstants.*;
030import static org.opends.server.util.StaticUtils.*;
031
032import java.io.File;
033import java.io.OutputStream;
034import java.io.PrintStream;
035import java.io.StringReader;
036import java.net.InetAddress;
037import java.security.GeneralSecurityException;
038import java.util.Collection;
039import java.util.HashSet;
040import java.util.LinkedList;
041import java.util.List;
042import java.util.Set;
043
044import javax.crypto.Cipher;
045
046import org.forgerock.i18n.LocalizableMessage;
047import org.forgerock.i18n.LocalizedIllegalArgumentException;
048import org.forgerock.opendj.adapter.server3x.Converters;
049import org.forgerock.opendj.config.DefaultBehaviorProvider;
050import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
051import org.forgerock.opendj.config.ManagedObjectDefinition;
052import org.forgerock.opendj.config.StringPropertyDefinition;
053import org.forgerock.opendj.config.server.ConfigException;
054import org.forgerock.opendj.ldap.AttributeDescription;
055import org.forgerock.opendj.ldap.DN;
056import org.forgerock.opendj.ldap.LinkedAttribute;
057import org.forgerock.opendj.ldap.LinkedHashMapEntry;
058import org.forgerock.opendj.ldap.schema.AttributeType;
059import org.forgerock.opendj.ldap.schema.CoreSchema;
060import org.forgerock.opendj.ldap.schema.Syntax;
061import org.forgerock.opendj.server.config.client.BackendCfgClient;
062import org.forgerock.opendj.server.config.meta.CryptoManagerCfgDefn;
063import org.forgerock.opendj.server.config.server.BackendCfg;
064import org.opends.quicksetup.installer.Installer;
065import org.opends.server.config.ConfigurationHandler;
066import org.opends.server.core.DirectoryServer;
067import org.opends.server.core.LockFileManager;
068import org.opends.server.extensions.SaltedSHA512PasswordStorageScheme;
069import org.opends.server.protocols.ldap.LDAPResultCode;
070import org.opends.server.types.DirectoryEnvironmentConfig;
071import org.opends.server.types.DirectoryException;
072import org.opends.server.types.Entry;
073import org.opends.server.types.InitializationException;
074import org.opends.server.types.LDIFImportConfig;
075import org.opends.server.types.NullOutputStream;
076import org.opends.server.util.LDIFReader;
077import org.opends.server.util.ServerConstants;
078
079import com.forgerock.opendj.cli.Argument;
080import com.forgerock.opendj.cli.ArgumentException;
081import com.forgerock.opendj.cli.ArgumentParser;
082import com.forgerock.opendj.cli.BooleanArgument;
083import com.forgerock.opendj.cli.CliConstants;
084import com.forgerock.opendj.cli.FileBasedArgument;
085import com.forgerock.opendj.cli.IntegerArgument;
086import com.forgerock.opendj.cli.StringArgument;
087
088/**
089 * This class provides a very basic tool that can be used to configure some of
090 * the most important settings in the Directory Server.  This configuration is
091 * performed by editing the server's configuration files and therefore the
092 * Directory Server must be offline.  This utility will be used during the
093 * Directory Server installation process.
094 * <BR><BR>
095 * The options that this tool can currently set include:
096 * <BR>
097 * <UL>
098 *   <LI>The port on which the server will listen for LDAP communication</LI>
099 *   <LI>The DN and password for the initial root user.
100 *   <LI>The set of base DNs for user data</LI>
101 * </UL>
102 */
103public class ConfigureDS
104{
105  private static final boolean WRONG_USAGE = true;
106
107  /** Private exception class to handle error message printing. */
108  @SuppressWarnings("serial")
109  private class ConfigureDSException extends Exception
110  {
111    private final int returnedErrorCode;
112    private final LocalizableMessage errorMessage;
113    private final boolean wrongUsage;
114
115    ConfigureDSException(final LocalizableMessage errorMessage)
116    {
117      this(new Exception("An error occured in ConfigureDS: " + errorMessage), errorMessage, false);
118    }
119
120    ConfigureDSException(final Exception parentException, final LocalizableMessage errorMessage)
121    {
122      this(parentException, errorMessage, false);
123    }
124
125    ConfigureDSException(final LocalizableMessage errorMessage, final boolean showUsage)
126    {
127      this(new Exception("An error occured in ConfigureDS: " + errorMessage), errorMessage, showUsage);
128    }
129
130    ConfigureDSException(final Exception parentException, final LocalizableMessage errorMessage,
131        final boolean showUsage)
132    {
133      this(parentException, errorMessage, showUsage, ERROR);
134    }
135
136    ConfigureDSException(final Exception parentException, final LocalizableMessage errorMessage,
137        final boolean wrongUsage, final int retCode)
138    {
139      super(parentException);
140      this.errorMessage = errorMessage;
141      this.wrongUsage = wrongUsage;
142      returnedErrorCode = retCode;
143    }
144
145    private LocalizableMessage getErrorMessage()
146    {
147      return errorMessage;
148    }
149
150    private boolean isWrongUsage()
151    {
152      return wrongUsage;
153    }
154
155    private int getErrorCode()
156    {
157      return returnedErrorCode;
158    }
159  }
160
161  private static final String NEW_LINE = System.getProperty("line.separator");
162
163  // FIXME: Find a better way to prevent hardcoded ldif entries.
164  private static final String JCKES_KEY_MANAGER_DN = "cn=JCEKS,cn=Key Manager Providers,cn=config";
165  private static final String JCKES_KEY_MANAGER_LDIF_ENTRY =
166        "dn: " + JCKES_KEY_MANAGER_DN + NEW_LINE
167      + "objectClass: top" + NEW_LINE
168      + "objectClass: ds-cfg-key-manager-provider" + NEW_LINE
169      + "objectClass: ds-cfg-file-based-key-manager-provider" + NEW_LINE
170      + "cn: JCEKS" + NEW_LINE
171      + "ds-cfg-java-class: org.opends.server.extensions.FileBasedKeyManagerProvider" + NEW_LINE
172      + "ds-cfg-enabled: true" + NEW_LINE
173      + "ds-cfg-key-store-type: JCEKS" + NEW_LINE
174      + "ds-cfg-key-store-file: config/keystore.jceks" + NEW_LINE
175      + "ds-cfg-key-store-pin-file: config/keystore.pin" + NEW_LINE;
176
177  private static final String JCKES_TRUST_MANAGER_DN = "cn=JCEKS,cn=Trust Manager Providers,cn=config";
178  private static final String JCKES_TRUST_MANAGER_LDIF_ENTRY =
179        "dn: " + JCKES_TRUST_MANAGER_DN + NEW_LINE
180      + "objectClass: top" + NEW_LINE
181      + "objectClass: ds-cfg-trust-manager-provider" + NEW_LINE
182      + "objectClass: ds-cfg-file-based-trust-manager-provider" + NEW_LINE
183      + "cn: JCEKS" + NEW_LINE
184      + "ds-cfg-java-class: org.opends.server.extensions.FileBasedTrustManagerProvider" + NEW_LINE
185      + "ds-cfg-enabled: false" + NEW_LINE
186      + "ds-cfg-trust-store-type: JCEKS" + NEW_LINE
187      + "ds-cfg-trust-store-file: config/truststore" + NEW_LINE;
188
189  /** The DN of the configuration entry defining the LDAP connection handler. */
190  private static final String DN_LDAP_CONNECTION_HANDLER = "cn=LDAP Connection Handler," + DN_CONNHANDLER_BASE;
191  /** The DN of the configuration entry defining the Administration connector. */
192  private static final String DN_ADMIN_CONNECTOR = "cn=Administration Connector," + DN_CONFIG_ROOT;
193  /** The DN of the configuration entry defining the LDAPS connection handler. */
194  private static final String DN_LDAPS_CONNECTION_HANDLER = "cn=LDAPS Connection Handler," + DN_CONNHANDLER_BASE;
195  /** The DN of the configuration entry defining the HTTP connection handler. */
196  private static final String DN_HTTP_CONNECTION_HANDLER =
197      "cn=HTTP Connection Handler,cn=Connection Handlers,cn=config";
198  /** The DN of the configuration entry defining the JMX connection handler. */
199  private static final String DN_JMX_CONNECTION_HANDLER = "cn=JMX Connection Handler," + DN_CONNHANDLER_BASE;
200  /** The DN of the configuration entry defining the initial root user. */
201  private static final String DN_ROOT_USER = "cn=Directory Manager," + DN_ROOT_DN_CONFIG_BASE;
202  /** The DN of the Crypto Manager. */
203  private static final String DN_CRYPTO_MANAGER = "cn=Crypto Manager,cn=config";
204  /** The DN of the DIGEST-MD5 SASL mechanism handler. */
205  private static final String DN_DIGEST_MD5_SASL_MECHANISM = "cn=DIGEST-MD5,cn=SASL Mechanisms,cn=config";
206
207  private static final int SUCCESS = 0;
208  private static final int ERROR = 1;
209
210  /**
211   * Provides the command-line arguments to the <CODE>configMain</CODE> method
212   * for processing.
213   *
214   * @param  args  The set of command-line arguments provided to this program.
215   */
216  public static void main(String[] args)
217  {
218    final int exitCode = configMain(args, System.out, System.err);
219    if (exitCode != SUCCESS)
220    {
221      System.exit(filterExitCode(exitCode));
222    }
223  }
224
225  /**
226   * Parses the provided command-line arguments and makes the appropriate
227   * changes to the Directory Server configuration.
228   *
229   * @param  args  The command-line arguments provided to this program.
230   *
231   * @param outStream Output stream.
232   * @param errStream Error stream.
233   * @return  The exit code from the configuration processing.  A nonzero value
234   *          indicates that there was some kind of problem during the
235   *          configuration processing.
236   */
237  public static int configMain(final String[] args, final OutputStream outStream, final OutputStream errStream)
238  {
239    final ConfigureDS tool = new ConfigureDS(args, outStream, errStream);
240    return tool.run();
241  }
242
243  private final String[] arguments;
244  private final PrintStream out;
245  private final PrintStream err;
246
247  private final ArgumentParser argParser;
248
249  private BooleanArgument showUsage;
250  private BooleanArgument enableStartTLS;
251  private FileBasedArgument rootPasswordFile;
252  private StringArgument hostName;
253  private IntegerArgument ldapPort;
254  private IntegerArgument adminConnectorPort;
255  private IntegerArgument ldapsPort;
256  private IntegerArgument jmxPort;
257  private StringArgument baseDNString;
258  private StringArgument configFile;
259  private StringArgument rootDNString;
260  private StringArgument rootPassword;
261  private StringArgument keyManagerProviderDN;
262  private StringArgument trustManagerProviderDN;
263  private StringArgument certNickNames;
264  private StringArgument keyManagerPath;
265  private StringArgument serverRoot;
266  private StringArgument backendType;
267
268  private final String serverLockFileName = LockFileManager.getServerLockFileName();
269  private final StringBuilder failureReason = new StringBuilder();
270  private ConfigurationHandler configHandler;
271
272  private ConfigureDS(final String[] args, final OutputStream outStream, final OutputStream errStream)
273  {
274    arguments = args;
275    out = NullOutputStream.wrapOrNullStream(outStream);
276    err = NullOutputStream.wrapOrNullStream(errStream);
277    argParser = new ArgumentParser(ConfigureDS.class.getName(), INFO_CONFIGDS_TOOL_DESCRIPTION.get(), false);
278  }
279
280  private int run()
281  {
282    try
283    {
284      initializeArguments();
285      parseArguments();
286      if (argParser.usageOrVersionDisplayed())
287      {
288        return SUCCESS;
289      }
290
291      checkArgumentsConsistency();
292      checkPortArguments();
293
294      tryAcquireExclusiveLocks();
295      updateBaseDNs(parseProvidedBaseDNs());
296
297      initializeDirectoryServer();
298
299      final DN rootDN = parseRootDN();
300      final String rootPW = parseRootDNPassword();
301
302      configHandler = DirectoryServer.getConfigurationHandler();
303
304      checkManagerProvider(keyManagerProviderDN, JCKES_KEY_MANAGER_DN, JCKES_KEY_MANAGER_LDIF_ENTRY, true);
305      checkManagerProvider(trustManagerProviderDN, JCKES_TRUST_MANAGER_DN, JCKES_TRUST_MANAGER_LDIF_ENTRY, false);
306      // Check that the keystore path values are valid.
307      if (keyManagerPath.isPresent() && !keyManagerProviderDN.isPresent())
308      {
309        final LocalizableMessage message = ERR_CONFIGDS_KEYMANAGER_PROVIDER_DN_REQUIRED.get(
310            keyManagerProviderDN.getLongIdentifier(), keyManagerPath.getLongIdentifier());
311        throw new ConfigureDSException(message);
312      }
313
314      updateLdapPort();
315      updateAdminConnectorPort();
316      updateLdapSecurePort();
317      updateJMXport();
318      updateStartTLS();
319      updateKeyManager();
320      updateTrustManager();
321      updateRootUser(rootDN, rootPW);
322      addFQDNDigestMD5();
323      updateCryptoCipher();
324      printWrappedText(out, INFO_CONFIGDS_WROTE_UPDATED_CONFIG.get());
325
326      return SUCCESS;
327    }
328    catch (final ConfigureDSException e)
329    {
330     if (e.isWrongUsage())
331     {
332       argParser.displayMessageAndUsageReference(err, e.getErrorMessage());
333     }
334     else
335     {
336       printWrappedText(err, e.getErrorMessage());
337     }
338     return e.getErrorCode();
339    }
340    finally
341    {
342      LockFileManager.releaseLock(serverLockFileName, failureReason);
343    }
344  }
345
346  private void initializeArguments() throws ConfigureDSException
347  {
348    try
349    {
350      configFile =
351              StringArgument.builder("configFile")
352                      .shortIdentifier('c')
353                      .description(INFO_DESCRIPTION_CONFIG_FILE.get())
354                      .hidden()
355                      .required()
356                      .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get())
357                      .buildAndAddToParser(argParser);
358      String defaultHostName;
359      try
360      {
361        defaultHostName = InetAddress.getLocalHost().getHostName();
362      }
363      catch (final Exception e)
364      {
365        // Not much we can do here.
366        defaultHostName = "localhost";
367      }
368
369      hostName =
370              StringArgument.builder(OPTION_LONG_HOST)
371                      .shortIdentifier(OPTION_SHORT_HOST)
372                      .description(INFO_INSTALLDS_DESCRIPTION_HOST_NAME.get())
373                      .defaultValue(defaultHostName)
374                      .valuePlaceholder(INFO_HOST_PLACEHOLDER.get())
375                      .buildAndAddToParser(argParser);
376      ldapPort =
377              IntegerArgument.builder("ldapPort")
378                      .shortIdentifier(OPTION_SHORT_PORT)
379                      .description(INFO_CONFIGDS_DESCRIPTION_LDAP_PORT.get())
380                      .range(1, 65535)
381                      .defaultValue(389)
382                      .valuePlaceholder(INFO_LDAPPORT_PLACEHOLDER.get())
383                      .buildAndAddToParser(argParser);
384      adminConnectorPort =
385              IntegerArgument.builder("adminConnectorPort")
386                      .description(INFO_INSTALLDS_DESCRIPTION_ADMINCONNECTORPORT.get())
387                      .range(1, 65535)
388                      .defaultValue(4444)
389                      .valuePlaceholder(INFO_PORT_PLACEHOLDER.get())
390                      .buildAndAddToParser(argParser);
391      ldapsPort =
392              IntegerArgument.builder("ldapsPort")
393                      .shortIdentifier('P')
394                      .description(INFO_CONFIGDS_DESCRIPTION_LDAPS_PORT.get())
395                      .range(1, 65535)
396                      .defaultValue(636)
397                      .valuePlaceholder(INFO_LDAPPORT_PLACEHOLDER.get())
398                      .buildAndAddToParser(argParser);
399      enableStartTLS =
400              BooleanArgument.builder("enableStartTLS")
401                      .shortIdentifier(OPTION_SHORT_START_TLS)
402                      .description(INFO_CONFIGDS_DESCRIPTION_ENABLE_START_TLS.get())
403                      .buildAndAddToParser(argParser);
404      jmxPort =
405              IntegerArgument.builder("jmxPort")
406                      .shortIdentifier('x')
407                      .description(INFO_CONFIGDS_DESCRIPTION_JMX_PORT.get())
408                      .range(1, 65535)
409                      .defaultValue(CliConstants.DEFAULT_JMX_PORT)
410                      .valuePlaceholder(INFO_JMXPORT_PLACEHOLDER.get())
411                      .buildAndAddToParser(argParser);
412      keyManagerProviderDN =
413              StringArgument.builder("keyManagerProviderDN")
414                      .shortIdentifier('k')
415                      .description(INFO_CONFIGDS_DESCRIPTION_KEYMANAGER_PROVIDER_DN.get())
416                      .valuePlaceholder(INFO_KEY_MANAGER_PROVIDER_DN_PLACEHOLDER.get())
417                      .buildAndAddToParser(argParser);
418      trustManagerProviderDN =
419              StringArgument.builder("trustManagerProviderDN")
420                      .shortIdentifier('t')
421                      .description(INFO_CONFIGDS_DESCRIPTION_TRUSTMANAGER_PROVIDER_DN.get())
422                      .valuePlaceholder(INFO_TRUST_MANAGER_PROVIDER_DN_PLACEHOLDER.get())
423                      .buildAndAddToParser(argParser);
424      keyManagerPath =
425              StringArgument.builder("keyManagerPath")
426                      .shortIdentifier('m')
427                      .description(INFO_CONFIGDS_DESCRIPTION_KEYMANAGER_PATH.get())
428                      .valuePlaceholder(INFO_KEY_MANAGER_PATH_PLACEHOLDER.get())
429                      .buildAndAddToParser(argParser);
430      certNickNames =
431              StringArgument.builder("certNickName")
432                      .shortIdentifier('a')
433                      .description(INFO_CONFIGDS_DESCRIPTION_CERTNICKNAME.get())
434                      .multiValued()
435                      .valuePlaceholder(INFO_NICKNAME_PLACEHOLDER.get())
436                      .buildAndAddToParser(argParser);
437      baseDNString =
438              StringArgument.builder(OPTION_LONG_BASEDN)
439                      .shortIdentifier(OPTION_SHORT_BASEDN)
440                      .description(INFO_CONFIGDS_DESCRIPTION_BASE_DN.get())
441                      .multiValued()
442                      .defaultValue("dc=example,dc=com")
443                      .valuePlaceholder(INFO_BASEDN_PLACEHOLDER.get())
444                      .buildAndAddToParser(argParser);
445      rootDNString =
446              StringArgument.builder(OPTION_LONG_ROOT_USER_DN)
447                      .shortIdentifier(OPTION_SHORT_ROOT_USER_DN)
448                      .description(INFO_CONFIGDS_DESCRIPTION_ROOT_DN.get())
449                      .defaultValue("cn=Directory Manager")
450                      .valuePlaceholder(INFO_ROOT_USER_DN_PLACEHOLDER.get())
451                      .buildAndAddToParser(argParser);
452      rootPassword =
453              StringArgument.builder("rootPassword")
454                      .shortIdentifier(OPTION_SHORT_BINDPWD)
455                      .description(INFO_CONFIGDS_DESCRIPTION_ROOT_PW.get())
456                      .valuePlaceholder(INFO_ROOT_USER_PWD_PLACEHOLDER.get())
457                      .buildAndAddToParser(argParser);
458      rootPasswordFile =
459              FileBasedArgument.builder("rootPasswordFile")
460                      .shortIdentifier(OPTION_SHORT_BINDPWD_FILE)
461                      .description(INFO_CONFIGDS_DESCRIPTION_ROOT_PW_FILE.get())
462                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
463                      .buildAndAddToParser(argParser);
464
465      showUsage = showUsageArgument();
466      argParser.addArgument(showUsage);
467      argParser.setUsageArgument(showUsage);
468
469      serverRoot =
470              StringArgument.builder(OPTION_LONG_SERVER_ROOT)
471                      .shortIdentifier(OPTION_SHORT_SERVER_ROOT)
472                      .hidden()
473                      .valuePlaceholder(INFO_SERVER_ROOT_DIR_PLACEHOLDER.get())
474                      .buildAndAddToParser(argParser);
475      backendType =
476              StringArgument.builder(OPTION_LONG_BACKEND_TYPE)
477                      .description(INFO_INSTALLDS_DESCRIPTION_BACKEND_TYPE.get())
478                      .valuePlaceholder(INFO_INSTALLDS_BACKEND_TYPE_PLACEHOLDER.get())
479                      .buildAndAddToParser(argParser);
480    }
481    catch (final ArgumentException ae)
482    {
483      throw new ConfigureDSException(ae, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
484    }
485  }
486
487  private int parseArguments() throws ConfigureDSException
488  {
489    try
490    {
491      argParser.parseArguments(arguments);
492      return SUCCESS;
493    }
494    catch (final ArgumentException ae)
495    {
496      throw new ConfigureDSException(ae, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()),
497          WRONG_USAGE, LDAPResultCode.CLIENT_SIDE_PARAM_ERROR);
498    }
499  }
500
501  /** Make sure that the user actually tried to configure something. */
502  private void checkArgumentsConsistency() throws ConfigureDSException
503  {
504    if (!baseDNString.isPresent()
505        && !ldapPort.isPresent()
506        && !jmxPort.isPresent()
507        && !rootDNString.isPresent())
508    {
509      throw new ConfigureDSException(ERR_CONFIGDS_NO_CONFIG_CHANGES.get(), WRONG_USAGE);
510    }
511  }
512
513  private void checkPortArguments() throws ConfigureDSException
514  {
515    try
516    {
517      final IntegerArgument[] portArgs = {ldapPort, adminConnectorPort, ldapsPort, jmxPort};
518      final Set<Integer> portsAdded = new HashSet<>();
519
520      for (final IntegerArgument portArg : portArgs)
521      {
522        if (portArg.isPresent())
523        {
524          final int portNumber = portArg.getIntValue();
525          if (portsAdded.contains(portNumber))
526          {
527            throw new ConfigureDSException(ERR_CONFIGDS_PORT_ALREADY_SPECIFIED.get(portArg.getIntValue()), WRONG_USAGE);
528          }
529          portsAdded.add(portNumber);
530        }
531      }
532    }
533    catch (final ArgumentException ae)
534    {
535      throw new ConfigureDSException(ae, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
536    }
537  }
538
539  private void initializeDirectoryServer() throws ConfigureDSException
540  {
541    if (serverRoot.isPresent()) {
542      final DirectoryEnvironmentConfig env = DirectoryServer.getEnvironmentConfig();
543      final String root = serverRoot.getValue();
544      try {
545        env.setServerRoot(new File(serverRoot.getValue()));
546      } catch (final InitializationException e) {
547        ERR_INITIALIZE_SERVER_ROOT.get(root, e.getMessageObject());
548      }
549    }
550
551    // Initialize the Directory Server configuration handler using the
552    // information that was provided.
553    final DirectoryServer directoryServer = DirectoryServer.getInstance();
554    DirectoryServer.bootstrapClient();
555
556    try
557    {
558      DirectoryServer.initializeJMX();
559    }
560    catch (final Exception e)
561    {
562      final LocalizableMessage msg = ERR_CONFIGDS_CANNOT_INITIALIZE_JMX.get(configFile.getValue(), e.getMessage());
563      throw new ConfigureDSException(e, msg);
564    }
565
566    try
567    {
568      directoryServer.initializeConfiguration(configFile.getValue());
569    }
570    catch (final Exception e)
571    {
572      final LocalizableMessage msg = ERR_CONFIGDS_CANNOT_INITIALIZE_CONFIG.get(configFile.getValue(), e.getMessage());
573      throw new ConfigureDSException(e, msg);
574    }
575
576    try
577    {
578      directoryServer.initializeSchema();
579    }
580    catch (final Exception e)
581    {
582      final LocalizableMessage msg = ERR_CONFIGDS_CANNOT_INITIALIZE_SCHEMA.get(configFile.getValue(), e.getMessage());
583      throw new ConfigureDSException(e, msg);
584    }
585  }
586
587  /**
588   * Make sure that we can get an exclusive lock for the Directory Server, so
589   * that no other operation will be allowed while this is in progress.
590   *
591   * @throws ConfigureDSException
592   */
593  private void tryAcquireExclusiveLocks() throws ConfigureDSException
594  {
595    if (! LockFileManager.acquireExclusiveLock(serverLockFileName, failureReason))
596    {
597      throw new ConfigureDSException(ERR_CONFIGDS_CANNOT_ACQUIRE_SERVER_LOCK.get(serverLockFileName, failureReason));
598    }
599  }
600
601  private LinkedList<DN> parseProvidedBaseDNs() throws ConfigureDSException
602  {
603    LinkedList<DN> baseDNs = new LinkedList<>();
604    if (baseDNString.isPresent())
605    {
606      for (final String dnString : baseDNString.getValues())
607      {
608        try
609        {
610          baseDNs.add(DN.valueOf(dnString));
611        }
612        catch (final Exception e)
613        {
614          throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_PARSE_BASE_DN.get(dnString, e.getMessage()));
615        }
616      }
617    }
618
619    return baseDNs;
620  }
621
622  private DN parseRootDN() throws ConfigureDSException
623  {
624    DN rootDN = null;
625    if (rootDNString.isPresent())
626    {
627      try
628      {
629        rootDN = DN.valueOf(rootDNString.getValue());
630      }
631      catch (final LocalizedIllegalArgumentException e)
632      {
633        final LocalizableMessage msg = ERR_CONFIGDS_CANNOT_PARSE_ROOT_DN.get(
634            rootDNString.getValue(), e.getMessageObject());
635        throw new ConfigureDSException(e, msg);
636      }
637    }
638    return rootDN;
639  }
640
641  private String parseRootDNPassword() throws ConfigureDSException
642  {
643    String rootPW = null;
644    if (rootDNString.isPresent())
645    {
646      if (rootPassword.isPresent())
647      {
648        rootPW = rootPassword.getValue();
649      }
650      else if (rootPasswordFile.isPresent())
651      {
652        rootPW = rootPasswordFile.getValue();
653      }
654      else
655      {
656        throw new ConfigureDSException(ERR_CONFIGDS_NO_ROOT_PW.get());
657      }
658    }
659    return rootPW;
660  }
661
662  private void checkManagerProvider(final Argument arg, final String jckesDN, final String ldifEntry,
663      final boolean isKeyManager) throws ConfigureDSException
664  {
665    if (arg.isPresent())
666    {
667      DN dn = null;
668      DN JCEKSManagerDN = null;
669      try
670      {
671        dn = DN.valueOf(trustManagerProviderDN.getValue());
672        JCEKSManagerDN = DN.valueOf(jckesDN);
673      }
674      catch (final LocalizedIllegalArgumentException e)
675      {
676        final String value = trustManagerProviderDN.getValue();
677        final LocalizableMessage errorMessage = e.getMessageObject();
678        final LocalizableMessage message =
679            isKeyManager ? ERR_CONFIGDS_CANNOT_PARSE_KEYMANAGER_PROVIDER_DN.get(value, errorMessage)
680                         : ERR_CONFIGDS_CANNOT_PARSE_TRUSTMANAGER_PROVIDER_DN.get(value, errorMessage);
681        throw new ConfigureDSException(e, message);
682      }
683
684      if (dn.equals(JCEKSManagerDN))
685      {
686        LDIFReader reader = null;
687        try
688        {
689
690          final String ldif = ldifEntry;
691          final LDIFImportConfig ldifImportConfig = new LDIFImportConfig(new StringReader(ldif));
692          reader = new LDIFReader(ldifImportConfig);
693          Entry mangerConfigEntry;
694          while ((mangerConfigEntry = reader.readEntry()) != null)
695          {
696            configHandler.addEntry(Converters.from(mangerConfigEntry));
697          }
698        }
699        catch (final Exception e)
700        {
701          final LocalizableMessage message = isKeyManager ? ERR_CONFIG_KEYMANAGER_CANNOT_CREATE_JCEKS_PROVIDER.get(e)
702                                                          : ERR_CONFIG_KEYMANAGER_CANNOT_GET_BASE.get(e);
703          throw new ConfigureDSException(e, message);
704        }
705        finally
706        {
707          close(reader);
708        }
709      }
710      else
711      {
712        try
713        {
714          configHandler.getEntry(dn);
715        }
716        catch (final Exception e)
717        {
718          final LocalizableMessage message = isKeyManager ? ERR_CONFIG_KEYMANAGER_CANNOT_GET_BASE.get(e)
719                                                          : ERR_CONFIG_TRUSTMANAGER_CANNOT_GET_BASE.get(e);
720          throw new ConfigureDSException(e, message);
721        }
722      }
723    }
724  }
725
726  @SuppressWarnings("unchecked")
727  private void updateBaseDNs(final List<DN> baseDNs) throws ConfigureDSException
728  {
729    if (!baseDNs.isEmpty())
730    {
731      final String backendTypeName = backendType.getValue();
732      final BackendTypeHelper backendTypeHelper = new BackendTypeHelper();
733      final ManagedObjectDefinition<?, ?> backend = backendTypeHelper.retrieveBackendTypeFromName(backendTypeName);
734      if (backend == null)
735      {
736        throw new ConfigureDSException(
737            ERR_CONFIGDS_BACKEND_TYPE_UNKNOWN.get(backendTypeName, backendTypeHelper.getPrintableBackendTypeNames()));
738      }
739
740      try
741      {
742        BackendCreationHelper.createBackendOffline(Installer.ROOT_BACKEND_NAME, baseDNs,
743            (ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg>) backend);
744      }
745      catch (Exception e)
746      {
747        throw new ConfigureDSException(ERR_CONFIGDS_SET_BACKEND_TYPE.get(backendTypeName, e.getMessage()));
748      }
749    }
750  }
751
752  private void updateLdapPort() throws ConfigureDSException
753  {
754    if (ldapPort.isPresent())
755    {
756      try
757      {
758        updateConfigEntryWithAttribute(
759            DN_LDAP_CONNECTION_HANDLER, ATTR_LISTEN_PORT,
760            CoreSchema.getIntegerSyntax(),
761            ldapPort.getIntValue());
762      }
763      catch (final Exception e)
764      {
765        throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_LDAP_PORT.get(e));
766      }
767    }
768  }
769
770  private void updateAdminConnectorPort() throws ConfigureDSException
771  {
772    if (adminConnectorPort.isPresent())
773    {
774      try
775      {
776        updateConfigEntryWithAttribute(
777            DN_ADMIN_CONNECTOR,
778            ATTR_LISTEN_PORT,
779            CoreSchema.getIntegerSyntax(),
780            adminConnectorPort.getIntValue());
781      }
782      catch (final Exception e)
783      {
784        throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_ADMIN_CONNECTOR_PORT.get(e));
785      }
786    }
787  }
788
789  private void updateLdapSecurePort() throws ConfigureDSException
790  {
791    if (ldapsPort.isPresent())
792    {
793      try
794      {
795        updateConfigEntryWithAttribute(
796            DN_LDAPS_CONNECTION_HANDLER,
797            ATTR_LISTEN_PORT,
798            CoreSchema.getIntegerSyntax(),
799            ldapsPort.getIntValue());
800
801        updateConfigEntryWithAttribute(
802            DN_LDAPS_CONNECTION_HANDLER,
803            ATTR_CONNECTION_HANDLER_ENABLED,
804            CoreSchema.getBooleanSyntax(),
805            ServerConstants.TRUE_VALUE);
806      }
807      catch (final Exception e)
808      {
809        throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_LDAPS_PORT.get(e));
810      }
811    }
812  }
813
814  private void updateJMXport() throws ConfigureDSException
815  {
816    if (jmxPort.isPresent())
817    {
818      try
819      {
820        updateConfigEntryWithAttribute(
821            DN_JMX_CONNECTION_HANDLER,
822            ATTR_LISTEN_PORT,
823            CoreSchema.getIntegerSyntax(),
824            jmxPort.getIntValue());
825
826        updateConfigEntryWithAttribute(
827            DN_JMX_CONNECTION_HANDLER,
828            ATTR_CONNECTION_HANDLER_ENABLED,
829            CoreSchema.getBooleanSyntax(),
830            ServerConstants.TRUE_VALUE);
831      }
832      catch (final Exception e)
833      {
834        throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_JMX_PORT.get(e));
835      }
836    }
837  }
838
839  private void updateStartTLS() throws ConfigureDSException
840  {
841    if (enableStartTLS.isPresent())
842    {
843      try
844      {
845        updateConfigEntryWithAttribute(
846            DN_LDAP_CONNECTION_HANDLER,
847            ATTR_ALLOW_STARTTLS,
848            CoreSchema.getBooleanSyntax(),
849            ServerConstants.TRUE_VALUE);
850      }
851      catch (final Exception e)
852      {
853        throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_ENABLE_STARTTLS.get(e));
854      }
855    }
856  }
857
858  private void updateKeyManager() throws ConfigureDSException
859  {
860    if (keyManagerProviderDN.isPresent())
861    {
862      if (enableStartTLS.isPresent() || ldapsPort.isPresent())
863      {
864        try
865        {
866          // Enable the key manager
867          updateConfigEntryWithAttribute(
868              keyManagerProviderDN.getValue(),
869              ATTR_KEYMANAGER_ENABLED,
870              CoreSchema.getBooleanSyntax(),
871              ServerConstants.TRUE_VALUE);
872        }
873        catch (final Exception e)
874        {
875          throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_ENABLE_KEYMANAGER.get(e));
876        }
877      }
878
879      putKeyManagerConfigAttribute(enableStartTLS, DN_LDAP_CONNECTION_HANDLER);
880      putKeyManagerConfigAttribute(ldapsPort, DN_LDAPS_CONNECTION_HANDLER);
881      putKeyManagerConfigAttribute(ldapsPort, DN_HTTP_CONNECTION_HANDLER);
882
883      if (keyManagerPath.isPresent())
884      {
885        try
886        {
887          updateConfigEntryWithAttribute(
888              keyManagerProviderDN.getValue(),
889              ATTR_KEYSTORE_FILE,
890              CoreSchema.getDirectoryStringSyntax(),
891              keyManagerPath.getValue());
892        }
893        catch (final Exception e)
894        {
895          throw new ConfigureDSException(e, LocalizableMessage.raw(e.toString()));
896        }
897      }
898    }
899  }
900
901  private void putKeyManagerConfigAttribute(final Argument arg, final String attributeDN)
902      throws ConfigureDSException
903  {
904    if (arg.isPresent())
905    {
906      try
907      {
908        updateConfigEntryWithAttribute(
909            attributeDN,
910            ATTR_KEYMANAGER_DN,
911            CoreSchema.getDirectoryStringSyntax(),
912            keyManagerProviderDN.getValue());
913      }
914      catch (final Exception e)
915      {
916        throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_KEYMANAGER_REFERENCE.get(e));
917      }
918    }
919  }
920
921  private void updateTrustManager() throws ConfigureDSException
922  {
923    if (trustManagerProviderDN.isPresent())
924    {
925      if (enableStartTLS.isPresent() || ldapsPort.isPresent())
926      {
927        try
928        {
929          updateConfigEntryWithAttribute(
930              trustManagerProviderDN.getValue(),
931              ATTR_TRUSTMANAGER_ENABLED,
932              CoreSchema.getBooleanSyntax(),
933              ServerConstants.TRUE_VALUE);
934        }
935        catch (final Exception e)
936        {
937          throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_ENABLE_TRUSTMANAGER.get(e));
938        }
939      }
940      putTrustManagerAttribute(enableStartTLS, DN_LDAP_CONNECTION_HANDLER);
941      putTrustManagerAttribute(ldapsPort, DN_LDAPS_CONNECTION_HANDLER);
942      putTrustManagerAttribute(ldapsPort, DN_HTTP_CONNECTION_HANDLER);
943    }
944
945    if (certNickNames.isPresent())
946    {
947      final List<String> attrValues = certNickNames.getValues();
948      updateCertNicknameEntry(ldapPort, DN_LDAP_CONNECTION_HANDLER, ATTR_SSL_CERT_NICKNAME, attrValues);
949      updateCertNicknameEntry(ldapsPort, DN_LDAPS_CONNECTION_HANDLER, ATTR_SSL_CERT_NICKNAME, attrValues);
950      updateCertNicknameEntry(certNickNames, DN_HTTP_CONNECTION_HANDLER, ATTR_SSL_CERT_NICKNAME, attrValues);
951      updateCertNicknameEntry(jmxPort, DN_JMX_CONNECTION_HANDLER, ATTR_SSL_CERT_NICKNAME, attrValues);
952    }
953    else
954    {
955      // Use the key manager specified for connection handlers
956      removeSSLCertNicknameAttribute(DN_LDAP_CONNECTION_HANDLER);
957      removeSSLCertNicknameAttribute(DN_LDAPS_CONNECTION_HANDLER);
958      removeSSLCertNicknameAttribute(DN_HTTP_CONNECTION_HANDLER);
959      removeSSLCertNicknameAttribute(DN_JMX_CONNECTION_HANDLER);
960    }
961  }
962
963  private void putTrustManagerAttribute(final Argument arg, final String attributeDN) throws ConfigureDSException
964  {
965    if (arg.isPresent())
966    {
967      try
968      {
969        updateConfigEntryWithAttribute(
970            attributeDN,
971            ATTR_TRUSTMANAGER_DN,
972            CoreSchema.getDirectoryStringSyntax(),
973            trustManagerProviderDN.getValue());
974      }
975      catch (final Exception e)
976      {
977        throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_TRUSTMANAGER_REFERENCE.get(e));
978      }
979    }
980  }
981
982  private void updateCertNicknameEntry(final Argument arg, final String attributeDN,
983      final String attrName, final List<String> attrValues) throws ConfigureDSException
984  {
985    try
986    {
987      if (arg.isPresent())
988      {
989        updateConfigEntryWithAttribute(
990            attributeDN,
991            attrName,
992            CoreSchema.getDirectoryStringSyntax(),
993            attrValues.toArray(new Object[attrValues.size()]));
994      }
995      else
996      {
997        updateConfigEntryByRemovingAttribute(attributeDN, ATTR_SSL_CERT_NICKNAME);
998      }
999    }
1000    catch (final Exception e)
1001    {
1002      throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_CERT_NICKNAME.get(e));
1003    }
1004  }
1005
1006  private void removeSSLCertNicknameAttribute(final String attributeDN) throws ConfigureDSException
1007  {
1008    try
1009    {
1010      updateConfigEntryByRemovingAttribute(attributeDN, ATTR_SSL_CERT_NICKNAME);
1011    }
1012    catch (final Exception e)
1013    {
1014      throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_CERT_NICKNAME.get(e));
1015    }
1016  }
1017
1018  private void updateRootUser(final DN rootDN, final String rootPW) throws ConfigureDSException
1019  {
1020    if (rootDN != null)
1021    {
1022      try
1023      {
1024        updateConfigEntryWithAttribute(
1025            DN_ROOT_USER,
1026            ATTR_ROOTDN_ALTERNATE_BIND_DN,
1027            CoreSchema.getDirectoryStringSyntax(),
1028            rootDN);
1029        final String encodedPassword = SaltedSHA512PasswordStorageScheme.encodeOffline(getBytes(rootPW));
1030        updateConfigEntryWithAttribute(
1031            DN_ROOT_USER,
1032            ATTR_USER_PASSWORD,
1033            CoreSchema.getDirectoryStringSyntax(),
1034            encodedPassword);
1035      }
1036      catch (final Exception e)
1037      {
1038        throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_ROOT_USER.get(e));
1039      }
1040    }
1041  }
1042
1043  /** Set the FQDN for the DIGEST-MD5 SASL mechanism. */
1044  private void addFQDNDigestMD5() throws ConfigureDSException
1045  {
1046    try
1047    {
1048      updateConfigEntryWithAttribute(
1049          DN_DIGEST_MD5_SASL_MECHANISM,
1050          "ds-cfg-server-fqdn",
1051          CoreSchema.getDirectoryStringSyntax(),
1052          hostName.getValue());
1053    }
1054    catch (final Exception e)
1055    {
1056      throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_DIGEST_MD5_FQDN.get(e));
1057    }
1058  }
1059
1060  /**
1061   * Check that the cipher specified is supported. This is intended to fix
1062   * issues with JVM that do not support the default cipher (see issue 3075 for
1063   * instance).
1064   *
1065   * @throws ConfigureDSException
1066   */
1067  private void updateCryptoCipher() throws ConfigureDSException
1068  {
1069    final CryptoManagerCfgDefn cryptoManager = CryptoManagerCfgDefn.getInstance();
1070    final StringPropertyDefinition prop = cryptoManager.getKeyWrappingTransformationPropertyDefinition();
1071    String defaultCipher = null;
1072
1073    final DefaultBehaviorProvider<?> p = prop.getDefaultBehaviorProvider();
1074    if (p instanceof DefinedDefaultBehaviorProvider)
1075    {
1076      final Collection<?> defaultValues = ((DefinedDefaultBehaviorProvider<?>) p).getDefaultValues();
1077      if (!defaultValues.isEmpty())
1078      {
1079        defaultCipher = defaultValues.iterator().next().toString();
1080      }
1081    }
1082
1083    if (defaultCipher != null)
1084    {
1085      // Check that the default cipher is supported by the JVM.
1086      try
1087      {
1088        Cipher.getInstance(defaultCipher);
1089      }
1090      catch (final GeneralSecurityException ex)
1091      {
1092        // The cipher is not supported: try to find an alternative one.
1093        final String alternativeCipher = getAlternativeCipher();
1094        if (alternativeCipher != null)
1095        {
1096          try
1097          {
1098            updateConfigEntryWithAttribute(
1099                DN_CRYPTO_MANAGER,
1100                ATTR_CRYPTO_CIPHER_KEY_WRAPPING_TRANSFORMATION,
1101                CoreSchema.getDirectoryStringSyntax(),
1102                alternativeCipher);
1103          }
1104          catch (final Exception e)
1105          {
1106            throw new ConfigureDSException(e, ERR_CONFIGDS_CANNOT_UPDATE_CRYPTO_MANAGER.get(e));
1107          }
1108        }
1109      }
1110    }
1111  }
1112
1113  /** Update a config entry with the provided attribute parameters. */
1114  private void updateConfigEntryWithAttribute(String entryDn, String attributeName, Syntax syntax, Object...values)
1115      throws DirectoryException, ConfigException
1116  {
1117    org.forgerock.opendj.ldap.Entry configEntry = configHandler.getEntry(DN.valueOf(entryDn));
1118    final org.forgerock.opendj.ldap.Entry newEntry = putAttribute(configEntry, attributeName, syntax, values);
1119    configHandler.replaceEntry(configEntry, newEntry);
1120  }
1121
1122  /** Update a config entry by removing the provided attribute. */
1123  private void updateConfigEntryByRemovingAttribute(String entryDn, String attributeName)
1124      throws DirectoryException, ConfigException
1125  {
1126    final org.forgerock.opendj.ldap.Entry configEntry = configHandler.getEntry(DN.valueOf(entryDn));
1127    final Entry newEntry = removeAttribute(Converters.to(configEntry), attributeName);
1128    configHandler.replaceEntry(configEntry, Converters.from(newEntry));
1129  }
1130
1131  /**
1132   * Duplicate the provided entry, and put an attribute to the duplicated entry.
1133   * <p>
1134   * Provided entry is not modified.
1135   *
1136   *  @return the duplicate entry, modified with the attribute
1137   */
1138  private org.forgerock.opendj.ldap.Entry putAttribute(
1139      org.forgerock.opendj.ldap.Entry configEntry, String attrName, Syntax syntax, Object...values)
1140  {
1141    org.forgerock.opendj.ldap.Entry newEntry = LinkedHashMapEntry.deepCopyOfEntry(configEntry);
1142    AttributeType attrType = DirectoryServer.getSchema().getAttributeType(attrName, syntax);
1143    newEntry.replaceAttribute(new LinkedAttribute(AttributeDescription.create(attrType), values));
1144    return newEntry;
1145  }
1146
1147  /**
1148   * Duplicate the provided entry, and remove an attribute to the duplicated entry.
1149   * <p>
1150   * Provided entry is not modified.
1151   *
1152   *  @return the duplicate entry, with removed attribute
1153   */
1154  private Entry removeAttribute(Entry entry, String attrName)
1155  {
1156    Entry duplicateEntry = entry.duplicate(false);
1157    for (AttributeType t : entry.getUserAttributes().keySet())
1158    {
1159      if (t.hasNameOrOID(attrName))
1160      {
1161        entry.getUserAttributes().remove(t);
1162        return duplicateEntry;
1163      }
1164    }
1165
1166    for (AttributeType t : entry.getOperationalAttributes().keySet())
1167    {
1168      if (t.hasNameOrOID(attrName))
1169      {
1170        entry.getOperationalAttributes().remove(t);
1171        return duplicateEntry;
1172      }
1173    }
1174    return duplicateEntry;
1175  }
1176
1177  /**
1178   * Returns a cipher that is supported by the JVM we are running at.
1179   * Returns <CODE>null</CODE> if no alternative cipher could be found.
1180   * @return a cipher that is supported by the JVM we are running at.
1181   */
1182  private static String getAlternativeCipher()
1183  {
1184    final String[] preferredAlternativeCiphers =
1185    {
1186        "RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING",
1187        "RSA/ECB/PKCS1Padding"
1188    };
1189    for (final String cipher : preferredAlternativeCiphers)
1190    {
1191      try
1192      {
1193        Cipher.getInstance(cipher);
1194        return cipher;
1195      }
1196      catch (final Throwable ignored)
1197      {
1198        // ignored
1199      }
1200    }
1201    return null;
1202  }
1203}