001/*
002 * Copyright 2011-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2011-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.listener;
022
023
024
025import java.io.File;
026import java.io.OutputStream;
027import java.io.Serializable;
028import java.net.Socket;
029import java.util.ArrayList;
030import java.util.Iterator;
031import java.util.LinkedHashMap;
032import java.util.List;
033import java.util.logging.FileHandler;
034import java.util.logging.Level;
035import java.util.logging.StreamHandler;
036import javax.net.ssl.KeyManager;
037import javax.net.ssl.TrustManager;
038
039import com.unboundid.ldap.sdk.DN;
040import com.unboundid.ldap.sdk.LDAPException;
041import com.unboundid.ldap.sdk.ResultCode;
042import com.unboundid.ldap.sdk.Version;
043import com.unboundid.ldap.sdk.schema.Schema;
044import com.unboundid.util.CommandLineTool;
045import com.unboundid.util.Debug;
046import com.unboundid.util.MinimalLogFormatter;
047import com.unboundid.util.NotMutable;
048import com.unboundid.util.StaticUtils;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051import com.unboundid.util.args.ArgumentException;
052import com.unboundid.util.args.ArgumentParser;
053import com.unboundid.util.args.BooleanArgument;
054import com.unboundid.util.args.DNArgument;
055import com.unboundid.util.args.IntegerArgument;
056import com.unboundid.util.args.FileArgument;
057import com.unboundid.util.args.StringArgument;
058import com.unboundid.util.ssl.KeyStoreKeyManager;
059import com.unboundid.util.ssl.SSLUtil;
060import com.unboundid.util.ssl.TrustAllTrustManager;
061import com.unboundid.util.ssl.TrustStoreTrustManager;
062
063import static com.unboundid.ldap.listener.ListenerMessages.*;
064
065
066
067/**
068 * This class provides a command-line tool that can be used to run an instance
069 * of the in-memory directory server.  Instances of the server may also be
070 * created and controlled programmatically using the
071 * {@link InMemoryDirectoryServer} class.
072 * <BR><BR>
073 * The following command-line arguments may be used with this class:
074 * <UL>
075 *   <LI>"-b {baseDN}" or "--baseDN {baseDN}" -- specifies a base DN to use for
076 *       the server.  At least one base DN must be specified, and multiple
077 *       base DNs may be provided as separate arguments.</LI>
078 *   <LI>"-p {port}" or "--port {port}" -- specifies the port on which the
079 *       server should listen for client connections.  If this is not provided,
080 *       then a free port will be automatically chosen for use by the
081 *       server.</LI>
082 *   <LI>"-l {path}" or "--ldifFile {path}" -- specifies the path to an LDIF
083 *       file to use to initially populate the server.  If this is not provided,
084 *       then the server will initially be empty.  The LDIF file will not be
085 *       updated as operations are processed in the server.</LI>
086 *   <LI>"-D {bindDN}" or "--additionalBindDN {bindDN}" -- specifies an
087 *       additional DN that can be used to authenticate to the server, even if
088 *       there is no account for that user.  If this is provided, then the
089 *       --additionalBindPassword argument must also be given.</LI>
090 *   <LI>"-w {password}" or "--additionalBindPassword {password}" -- specifies
091 *       the password that should be used when attempting to bind as the user
092 *       specified with the "-additionalBindDN" argument.  If this is provided,
093 *       then the --additionalBindDN argument must also be given.</LI>
094 *   <LI>"-c {count}" or "--maxChangeLogEntries {count}" -- Indicates whether an
095 *       LDAP changelog should be enabled, and if so how many changelog records
096 *       should be maintained.  If this argument is not provided, or if it is
097 *       provided with a value of zero, then no changelog will be
098 *       maintained.</LI>
099 *   <LI>"-A" or "--accessLogToStandardOut" -- indicates that access log
100 *       information should be written to standard output.  This cannot be
101 *       provided in conjunction with the "--accessLogFile" argument.  If
102 *       that should be used as a server access log.  This cannot be provided in
103 *       neither argument is provided, then no access logging will be
104 *       performed</LI>
105 *   <LI>"-a {path}" or "--accessLogFile {path}" -- specifies the path to a file
106 *       that should be used as a server access log.  This cannot be provided in
107 *       conjunction with the "--accessLogToStandardOut" argument.  If neither
108 *       argument is provided, then no access logging will be performed</LI>
109 *   <LI>"--ldapDebugLogToStandardOut" -- Indicates that LDAP debug log
110 *       information should be written to standard output.  This cannot be
111 *       provided in conjunction with the "--ldapDebugLogFile" argument.  If
112 *       neither argument is provided, then no debug logging will be
113 *       performed.</LI>
114 *   <LI>"-d {path}" or "--ldapDebugLogFile {path}" -- specifies the path to a
115 *       file that should be used as a server LDAP debug log.  This cannot be
116 *       provided in conjunction with the "--ldapDebugLogToStandardOut"
117 *       argument.  If neither argument is provided, then no debug logging will
118 *       be performed.</LI>
119 *   <LI>"-s" or "--useDefaultSchema" -- Indicates that the server should use
120 *       the default standard schema provided as part of the LDAP SDK.  If
121 *       neither this argument nor the "--useSchemaFile" argument is provided,
122 *       then the server will not perform any schema validation.</LI>
123 *   <LI>"-S {path}" or "--useSchemaFile {path}" -- specifies the path to a file
124 *       or directory containing schema definitions to use for the server.  If
125 *       neither this argument nor the "--useDefaultSchema" argument is
126 *       provided, then the server will not perform any schema validation.  If
127 *       the specified path represents a file, then it must be an LDIF file
128 *       containing a valid LDAP subschema subentry.  If the path is a
129 *       directory, then its files will be processed in lexicographic order by
130 *       name.</LI>
131 *   <LI>"-I {attr}" or "--equalityIndex {attr}" -- specifies that an equality
132 *       index should be maintained for the specified attribute.  The equality
133 *       index may be used to speed up certain kinds of searches, although it
134 *       will cause the server to consume more memory.</LI>
135 *   <LI>"-Z" or "--useSSL" -- indicates that the server should encrypt all
136 *       communication using SSL.  If this is provided, then the
137 *       "--keyStorePath" and "--keyStorePassword" arguments must also be
138 *       provided, and the "--useStartTLS" argument must not be provided.</LI>
139 *   <LI>"-q" or "--useStartTLS" -- indicates that the server should support the
140 *       use of the StartTLS extended request.  If this is provided, then the
141 *       "--keyStorePath" and "--keyStorePassword" arguments must also be
142 *       provided, and the "--useSSL" argument must not be provided.</LI>
143 *   <LI>"-K {path}" or "--keyStorePath {path}" -- specifies the path to the JKS
144 *       key store file that should be used to obtain the server certificate to
145 *       use for SSL communication.  If this argument is provided, then the
146 *       "--keyStorePassword" argument must also be provided, along with exactly
147 *       one of the "--useSSL" or "--useStartTLS" arguments.</LI>
148 *   <LI>"-W {password}" or "--keyStorePassword {password}" -- specifies the
149 *       password that should be used to access the contents of the SSL key
150 *       store.  If this argument is provided, then the "--keyStorePath"
151 *       argument must also be provided, along with exactly one of the
152 *       "--useSSL" or "--useStartTLS" arguments.</LI>
153 *   <LI>"--keyStoreType {type}" -- specifies the type of keystore represented
154 *       by the file specified by the keystore path.  If this argument is
155 *       provided, then the "--keyStorePath" argument must also be provided,
156 *       along with exactly one of the "--useSSL" or "--useStartTLS" arguments.
157 *       If this argument is not provided, then a default key store type of
158 *       "JKS" will be assumed.</LI>
159 *   <LI>"-P {path}" or "--trustStorePath {path}" -- specifies the path to the
160 *       JKS trust store file that should be used to determine whether to trust
161 *       any SSL certificates that may be presented by the client.  If this
162 *       argument is provided, then exactly one of the "--useSSL" or
163 *       "--useStartTLS" arguments must also be provided.  If this argument is
164 *       not provided but SSL or StartTLS is to be used, then all client
165 *       certificates will be automatically trusted.</LI>
166 *   <LI>"-T {password}" or "--trustStorePassword {password}" -- specifies the
167 *       password that should be used to access the contents of the SSL trust
168 *       store.  If this argument is provided, then the "--trustStorePath"
169 *       argument must also be provided, along with exactly one of the
170 *       "--useSSL" or "--useStartTLS" arguments.  If an SSL trust store path
171 *       was provided without a trust store password, then the server will
172 *       attempt to use the trust store without a password.</LI>
173 *   <LI>"--trustStoreType {type}" -- specifies the type of trust store
174 *       represented by the file specified by the trust store path.  If this
175 *       argument is provided, then the "--trustStorePath" argument must also
176 *       be provided, along with exactly one of the "--useSSL" or
177 *       "--useStartTLS" arguments.  If this argument is not provided, then a
178 *       default trust store type of "JKS" will be assumed.</LI>
179 *   <LI>"--vendorName {name}" -- specifies the vendor name value to appear in
180 *       the server root DSE.</LI>
181 *   <LI>"--vendorVersion {version}" -- specifies the vendor version value to
182 *       appear in the server root DSE.</LI>
183 * </UL>
184 */
185@NotMutable()
186@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
187public final class InMemoryDirectoryServerTool
188       extends CommandLineTool
189       implements Serializable, LDAPListenerExceptionHandler
190{
191  /**
192   * The serial version UID for this serializable class.
193   */
194  private static final long serialVersionUID = 6484637038039050412L;
195
196
197
198  // The argument used to indicate that access log information should be written
199  // to standard output.
200  private BooleanArgument accessLogToStandardOutArgument;
201
202  // The argument used to prevent the in-memory server from starting.  This is
203  // only intended to be used for internal testing purposes.
204  private BooleanArgument dontStartArgument;
205
206  // The argument used to indicate that LDAP debug log information should be
207  // written to standard output.
208  private BooleanArgument ldapDebugLogToStandardOutArgument;
209
210  // The argument used to indicate that the default standard schema should be
211  // used.
212  private BooleanArgument useDefaultSchemaArgument;
213
214  // The argument used to indicate that the server should use SSL
215  private BooleanArgument useSSLArgument;
216
217  // The argument used to indicate that the server should support the StartTLS
218  // extended operation
219  private BooleanArgument useStartTLSArgument;
220
221  // The argument used to specify an additional bind DN to use for the server.
222  private DNArgument additionalBindDNArgument;
223
224  // The argument used to specify the base DNs to use for the server.
225  private DNArgument baseDNArgument;
226
227  // The argument used to specify the path to an access log file to which
228  // information should be written about operations processed by the server.
229  private FileArgument accessLogFileArgument;
230
231  // The argument used to specify the code log file to use, if any.
232  private FileArgument codeLogFile;
233
234  // The argument used to specify the path to the SSL key store file.
235  private FileArgument keyStorePathArgument;
236
237  // The argument used to specify the path to an LDAP debug log file to which
238  // information should be written about detailed LDAP communication performed
239  // by the server.
240  private FileArgument ldapDebugLogFileArgument;
241
242  // The argument used to specify the path to an LDIF file with data to use to
243  // initially populate the server.
244  private FileArgument ldifFileArgument;
245
246  // The argument used to specify the path to the SSL trust store file.
247  private FileArgument trustStorePathArgument;
248
249  // The argument used to specify the path to a directory containing schema
250  // definitions.
251  private FileArgument useSchemaFileArgument;
252
253  // The in-memory directory server instance that has been created by this tool.
254  private InMemoryDirectoryServer directoryServer;
255
256  // The argument used to specify the maximum number of changelog entries that
257  // the server should maintain.
258  private IntegerArgument maxChangeLogEntriesArgument;
259
260  // The argument used to specify the port on which the server should listen.
261  private IntegerArgument portArgument;
262
263  // The argument used to specify the password for the additional bind DN.
264  private StringArgument additionalBindPasswordArgument;
265
266  // The argument used to specify the attributes for which to maintain equality
267  // indexes.
268  private StringArgument equalityIndexArgument;
269
270  // The argument used to specify the password to use to access the contents of
271  // the SSL key store
272  private StringArgument keyStorePasswordArgument;
273
274  // The argument used to specify the key store type.
275  private StringArgument keyStoreTypeArgument;
276
277  // The argument used to specify the password to use to access the contents of
278  // the SSL trust store
279  private StringArgument trustStorePasswordArgument;
280
281  // The argument used to specify the trust store type.
282  private StringArgument trustStoreTypeArgument;
283
284  // The argument used to specify the server vendor name.
285  private StringArgument vendorNameArgument;
286
287  // The argument used to specify the server vendor version.
288  private StringArgument vendorVersionArgument;
289
290
291
292  /**
293   * Parse the provided command line arguments and uses them to start the
294   * directory server.
295   *
296   * @param  args  The command line arguments provided to this program.
297   */
298  public static void main(final String... args)
299  {
300    final ResultCode resultCode = main(args, System.out, System.err);
301    if (resultCode != ResultCode.SUCCESS)
302    {
303      System.exit(resultCode.intValue());
304    }
305  }
306
307
308
309  /**
310   * Parse the provided command line arguments and uses them to start the
311   * directory server.
312   *
313   * @param  outStream  The output stream to which standard out should be
314   *                    written.  It may be {@code null} if output should be
315   *                    suppressed.
316   * @param  errStream  The output stream to which standard error should be
317   *                    written.  It may be {@code null} if error messages
318   *                    should be suppressed.
319   * @param  args       The command line arguments provided to this program.
320   *
321   * @return  A result code indicating whether the processing was successful.
322   */
323  public static ResultCode main(final String[] args,
324                                final OutputStream outStream,
325                                final OutputStream errStream)
326  {
327    final InMemoryDirectoryServerTool tool =
328         new InMemoryDirectoryServerTool(outStream, errStream);
329    return tool.runTool(args);
330  }
331
332
333
334  /**
335   * Creates a new instance of this tool that use the provided output streams
336   * for standard output and standard error.
337   *
338   * @param  outStream  The output stream to use for standard output.  It may be
339   *                    {@code System.out} for the JVM's default standard output
340   *                    stream, {@code null} if no output should be generated,
341   *                    or a custom output stream if the output should be sent
342   *                    to an alternate location.
343   * @param  errStream  The output stream to use for standard error.  It may be
344   *                    {@code System.err} for the JVM's default standard error
345   *                    stream, {@code null} if no output should be generated,
346   *                    or a custom output stream if the output should be sent
347   *                    to an alternate location.
348   */
349  public InMemoryDirectoryServerTool(final OutputStream outStream,
350                                     final OutputStream errStream)
351  {
352    super(outStream, errStream);
353
354    directoryServer                   = null;
355    dontStartArgument                 = null;
356    useDefaultSchemaArgument          = null;
357    useSSLArgument                    = null;
358    useStartTLSArgument               = null;
359    additionalBindDNArgument          = null;
360    baseDNArgument                    = null;
361    accessLogToStandardOutArgument    = null;
362    accessLogFileArgument             = null;
363    keyStorePathArgument              = null;
364    ldapDebugLogToStandardOutArgument = null;
365    ldapDebugLogFileArgument          = null;
366    ldifFileArgument                  = null;
367    trustStorePathArgument            = null;
368    useSchemaFileArgument             = null;
369    maxChangeLogEntriesArgument       = null;
370    portArgument                      = null;
371    additionalBindPasswordArgument    = null;
372    equalityIndexArgument             = null;
373    keyStorePasswordArgument          = null;
374    keyStoreTypeArgument              = null;
375    trustStorePasswordArgument        = null;
376    trustStoreTypeArgument            = null;
377    vendorNameArgument                = null;
378    vendorVersionArgument             = null;
379  }
380
381
382
383  /**
384   * {@inheritDoc}
385   */
386  @Override()
387  public String getToolName()
388  {
389    return "in-memory-directory-server";
390  }
391
392
393
394  /**
395   * {@inheritDoc}
396   */
397  @Override()
398  public String getToolDescription()
399  {
400    return INFO_MEM_DS_TOOL_DESC.get(InMemoryDirectoryServer.class.getName());
401  }
402
403
404
405  /**
406   * Retrieves the version string for this tool.
407   *
408   * @return  The version string for this tool.
409   */
410  @Override()
411  public String getToolVersion()
412  {
413    return Version.NUMERIC_VERSION_STRING;
414  }
415
416
417
418  /**
419   * {@inheritDoc}
420   */
421  @Override()
422  public void addToolArguments(final ArgumentParser parser)
423         throws ArgumentException
424  {
425    portArgument = new IntegerArgument('p', "port", false, 1,
426         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PORT.get(),
427         INFO_MEM_DS_TOOL_ARG_DESC_PORT.get(), 0, 65_535);
428    portArgument.setArgumentGroupName(
429         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
430    parser.addArgument(portArgument);
431
432    useSSLArgument = new BooleanArgument('Z', "useSSL",
433         INFO_MEM_DS_TOOL_ARG_DESC_USE_SSL.get());
434    useSSLArgument.setArgumentGroupName(
435         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
436    useSSLArgument.addLongIdentifier("use-ssl", true);
437    parser.addArgument(useSSLArgument);
438
439    useStartTLSArgument = new BooleanArgument('q', "useStartTLS",
440         INFO_MEM_DS_TOOL_ARG_DESC_USE_START_TLS.get());
441    useStartTLSArgument.setArgumentGroupName(
442         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
443    useStartTLSArgument.addLongIdentifier("use-starttls", true);
444    useStartTLSArgument.addLongIdentifier("use-start-tls", true);
445    parser.addArgument(useStartTLSArgument);
446
447    keyStorePathArgument = new FileArgument('K', "keyStorePath", false, 1,
448         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
449         INFO_MEM_DS_TOOL_ARG_DESC_KEY_STORE_PATH.get(), true, true, true,
450         false);
451    keyStorePathArgument.setArgumentGroupName(
452         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
453    keyStorePathArgument.addLongIdentifier("key-store-path", true);
454    parser.addArgument(keyStorePathArgument);
455
456    keyStorePasswordArgument = new StringArgument('W', "keyStorePassword",
457         false, 1, INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PASSWORD.get(),
458         INFO_MEM_DS_TOOL_ARG_DESC_KEY_STORE_PW.get());
459    keyStorePasswordArgument.setSensitive(true);
460    keyStorePasswordArgument.setArgumentGroupName(
461         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
462    keyStorePasswordArgument.addLongIdentifier("keyStorePIN", true);
463    keyStorePasswordArgument.addLongIdentifier("key-store-password", true);
464    keyStorePasswordArgument.addLongIdentifier("key-store-pin", true);
465    parser.addArgument(keyStorePasswordArgument);
466
467    keyStoreTypeArgument = new StringArgument(null, "keyStoreType",
468         false, 1, "{type}", "The keystore type.", "JKS");
469    keyStoreTypeArgument.setArgumentGroupName(
470         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
471    keyStoreTypeArgument.addLongIdentifier("keyStoreFormat", true);
472    keyStoreTypeArgument.addLongIdentifier("key-store-type", true);
473    keyStoreTypeArgument.addLongIdentifier("key-store-format", true);
474    parser.addArgument(keyStoreTypeArgument);
475
476    trustStorePathArgument = new FileArgument('P', "trustStorePath", false, 1,
477         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
478         INFO_MEM_DS_TOOL_ARG_DESC_TRUST_STORE_PATH.get(), true, true, true,
479         false);
480    trustStorePathArgument.setArgumentGroupName(
481         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
482    trustStorePathArgument.addLongIdentifier("trust-store-path", true);
483    parser.addArgument(trustStorePathArgument);
484
485    trustStorePasswordArgument = new StringArgument('T', "trustStorePassword",
486         false, 1, INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PASSWORD.get(),
487         INFO_MEM_DS_TOOL_ARG_DESC_TRUST_STORE_PW.get());
488    trustStorePasswordArgument.setSensitive(true);
489    trustStorePasswordArgument.setArgumentGroupName(
490         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
491    trustStorePasswordArgument.addLongIdentifier("trustStorePIN", true);
492    trustStorePasswordArgument.addLongIdentifier("trust-store-password", true);
493    trustStorePasswordArgument.addLongIdentifier("trust-store-pin", true);
494    parser.addArgument(trustStorePasswordArgument);
495
496    trustStoreTypeArgument = new StringArgument(null, "trustStoreType",
497         false, 1, "{type}", "The trust store type.", "JKS");
498    trustStoreTypeArgument.setArgumentGroupName(
499         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
500    trustStoreTypeArgument.addLongIdentifier("trustStoreFormat", true);
501    trustStoreTypeArgument.addLongIdentifier("trust-store-type", true);
502    trustStoreTypeArgument.addLongIdentifier("trust-store-format", true);
503    parser.addArgument(trustStoreTypeArgument);
504
505    dontStartArgument = new BooleanArgument(null, "dontStart",
506         INFO_MEM_DS_TOOL_ARG_DESC_DONT_START.get());
507    dontStartArgument.setArgumentGroupName(
508         INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
509    dontStartArgument.setHidden(true);
510    dontStartArgument.addLongIdentifier("doNotStart", true);
511    dontStartArgument.addLongIdentifier("dont-start", true);
512    dontStartArgument.addLongIdentifier("do-not-start", true);
513    parser.addArgument(dontStartArgument);
514
515    baseDNArgument = new DNArgument('b', "baseDN", true, 0,
516         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_BASE_DN.get(),
517         INFO_MEM_DS_TOOL_ARG_DESC_BASE_DN.get());
518    baseDNArgument.setArgumentGroupName(INFO_MEM_DS_TOOL_GROUP_DATA.get());
519    baseDNArgument.addLongIdentifier("base-dn", true);
520    parser.addArgument(baseDNArgument);
521
522    ldifFileArgument = new FileArgument('l', "ldifFile", false, 1,
523         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
524         INFO_MEM_DS_TOOL_ARG_DESC_LDIF_FILE.get(), true, true, true, false);
525    ldifFileArgument.setArgumentGroupName(INFO_MEM_DS_TOOL_GROUP_DATA.get());
526    ldifFileArgument.addLongIdentifier("ldif-file", true);
527    parser.addArgument(ldifFileArgument);
528
529    additionalBindDNArgument = new DNArgument('D', "additionalBindDN", false, 1,
530         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_BIND_DN.get(),
531         INFO_MEM_DS_TOOL_ARG_DESC_ADDITIONAL_BIND_DN.get());
532    additionalBindDNArgument.setArgumentGroupName(
533         INFO_MEM_DS_TOOL_GROUP_DATA.get());
534    additionalBindDNArgument.addLongIdentifier("additional-bind-dn", true);
535    parser.addArgument(additionalBindDNArgument);
536
537    additionalBindPasswordArgument = new StringArgument('w',
538         "additionalBindPassword", false, 1,
539         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PASSWORD.get(),
540         INFO_MEM_DS_TOOL_ARG_DESC_ADDITIONAL_BIND_PW.get());
541    additionalBindPasswordArgument.setSensitive(true);
542    additionalBindPasswordArgument.setArgumentGroupName(
543         INFO_MEM_DS_TOOL_GROUP_DATA.get());
544    additionalBindPasswordArgument.addLongIdentifier(
545         "additional-bind-password", true);
546    parser.addArgument(additionalBindPasswordArgument);
547
548    useDefaultSchemaArgument = new BooleanArgument('s', "useDefaultSchema",
549         INFO_MEM_DS_TOOL_ARG_DESC_USE_DEFAULT_SCHEMA.get());
550    useDefaultSchemaArgument.setArgumentGroupName(
551         INFO_MEM_DS_TOOL_GROUP_DATA.get());
552    useDefaultSchemaArgument.addLongIdentifier("use-default-schema", true);
553    parser.addArgument(useDefaultSchemaArgument);
554
555    useSchemaFileArgument = new FileArgument('S', "useSchemaFile", false, 0,
556         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
557         INFO_MEM_DS_TOOL_ARG_DESC_USE_SCHEMA_FILE.get(), true, true, false,
558         false);
559    useSchemaFileArgument.setArgumentGroupName(
560         INFO_MEM_DS_TOOL_GROUP_DATA.get());
561    useSchemaFileArgument.addLongIdentifier("use-schema-file", true);
562    parser.addArgument(useSchemaFileArgument);
563
564    equalityIndexArgument = new StringArgument('I', "equalityIndex", false, 0,
565         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_ATTR.get(),
566         INFO_MEM_DS_TOOL_ARG_DESC_EQ_INDEX.get());
567    equalityIndexArgument.setArgumentGroupName(
568         INFO_MEM_DS_TOOL_GROUP_DATA.get());
569    equalityIndexArgument.addLongIdentifier("equality-index", true);
570    parser.addArgument(equalityIndexArgument);
571
572    maxChangeLogEntriesArgument = new IntegerArgument('c',
573         "maxChangeLogEntries", false, 1,
574         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_COUNT.get(),
575         INFO_MEM_DS_TOOL_ARG_DESC_MAX_CHANGELOG_ENTRIES.get(), 0,
576         Integer.MAX_VALUE, 0);
577    maxChangeLogEntriesArgument.setArgumentGroupName(
578         INFO_MEM_DS_TOOL_GROUP_DATA.get());
579    maxChangeLogEntriesArgument.addLongIdentifier("max-changelog-entries",
580         true);
581    maxChangeLogEntriesArgument.addLongIdentifier("max-change-log-entries",
582         true);
583    parser.addArgument(maxChangeLogEntriesArgument);
584
585    vendorNameArgument = new StringArgument(null, "vendorName", false, 1,
586         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_VALUE.get(),
587         INFO_MEM_DS_TOOL_ARG_DESC_VENDOR_NAME.get());
588    vendorNameArgument.setArgumentGroupName(INFO_MEM_DS_TOOL_GROUP_DATA.get());
589    vendorNameArgument.addLongIdentifier("vendor-name", true);
590    parser.addArgument(vendorNameArgument);
591
592    vendorVersionArgument = new StringArgument(null, "vendorVersion", false, 1,
593         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_VALUE.get(),
594         INFO_MEM_DS_TOOL_ARG_DESC_VENDOR_VERSION.get());
595    vendorVersionArgument.setArgumentGroupName(
596         INFO_MEM_DS_TOOL_GROUP_DATA.get());
597    vendorVersionArgument.addLongIdentifier("vendor-version", true);
598    parser.addArgument(vendorVersionArgument);
599
600    accessLogToStandardOutArgument = new BooleanArgument('A',
601         "accessLogToStandardOut",
602         INFO_MEM_DS_TOOL_ARG_DESC_ACCESS_LOG_TO_STDOUT.get());
603    accessLogToStandardOutArgument.setArgumentGroupName(
604         INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
605    accessLogToStandardOutArgument.addLongIdentifier(
606         "access-log-to-standard-out", true);
607    parser.addArgument(accessLogToStandardOutArgument);
608
609    accessLogFileArgument = new FileArgument('a', "accessLogFile", false, 1,
610         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
611         INFO_MEM_DS_TOOL_ARG_DESC_ACCESS_LOG_FILE.get(), false, true, true,
612         false);
613    accessLogFileArgument.setArgumentGroupName(
614         INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
615    accessLogFileArgument.addLongIdentifier("access-log-format", true);
616    parser.addArgument(accessLogFileArgument);
617
618    ldapDebugLogToStandardOutArgument = new BooleanArgument(null,
619         "ldapDebugLogToStandardOut",
620         INFO_MEM_DS_TOOL_ARG_DESC_LDAP_DEBUG_LOG_TO_STDOUT.get());
621    ldapDebugLogToStandardOutArgument.setArgumentGroupName(
622         INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
623    ldapDebugLogToStandardOutArgument.addLongIdentifier(
624         "ldap-debug-log-to-standard-out", true);
625    parser.addArgument(ldapDebugLogToStandardOutArgument);
626
627    ldapDebugLogFileArgument = new FileArgument('d', "ldapDebugLogFile", false,
628         1, INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
629         INFO_MEM_DS_TOOL_ARG_DESC_LDAP_DEBUG_LOG_FILE.get(), false, true, true,
630         false);
631    ldapDebugLogFileArgument.setArgumentGroupName(
632         INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
633    ldapDebugLogFileArgument.addLongIdentifier("ldap-debug-log-file", true);
634    parser.addArgument(ldapDebugLogFileArgument);
635
636    codeLogFile = new FileArgument('C', "codeLogFile", false, 1, "{path}",
637         INFO_MEM_DS_TOOL_ARG_DESC_CODE_LOG_FILE.get(), false, true, true,
638         false);
639    codeLogFile.setArgumentGroupName(INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
640    codeLogFile.addLongIdentifier("code-log-file", true);
641    parser.addArgument(codeLogFile);
642
643    parser.addExclusiveArgumentSet(useDefaultSchemaArgument,
644         useSchemaFileArgument);
645    parser.addExclusiveArgumentSet(useSSLArgument, useStartTLSArgument);
646
647    parser.addExclusiveArgumentSet(accessLogToStandardOutArgument,
648         accessLogFileArgument);
649    parser.addExclusiveArgumentSet(ldapDebugLogToStandardOutArgument,
650         ldapDebugLogFileArgument);
651
652    parser.addDependentArgumentSet(additionalBindDNArgument,
653         additionalBindPasswordArgument);
654    parser.addDependentArgumentSet(additionalBindPasswordArgument,
655         additionalBindDNArgument);
656
657    parser.addDependentArgumentSet(useSSLArgument, keyStorePathArgument);
658    parser.addDependentArgumentSet(useSSLArgument, keyStorePasswordArgument);
659    parser.addDependentArgumentSet(useStartTLSArgument, keyStorePathArgument);
660    parser.addDependentArgumentSet(useStartTLSArgument,
661         keyStorePasswordArgument);
662    parser.addDependentArgumentSet(keyStorePathArgument, useSSLArgument,
663         useStartTLSArgument);
664    parser.addDependentArgumentSet(keyStorePasswordArgument, useSSLArgument,
665         useStartTLSArgument);
666    parser.addDependentArgumentSet(trustStorePathArgument, useSSLArgument,
667         useStartTLSArgument);
668    parser.addDependentArgumentSet(trustStorePasswordArgument,
669         trustStorePathArgument);
670  }
671
672
673
674  /**
675   * {@inheritDoc}
676   */
677  @Override()
678  public boolean supportsInteractiveMode()
679  {
680    return true;
681  }
682
683
684
685  /**
686   * {@inheritDoc}
687   */
688  @Override()
689  public boolean defaultsToInteractiveMode()
690  {
691    return true;
692  }
693
694
695
696  /**
697   * Indicates whether this tool supports the use of a properties file for
698   * specifying default values for arguments that aren't specified on the
699   * command line.
700   *
701   * @return  {@code true} if this tool supports the use of a properties file
702   *          for specifying default values for arguments that aren't specified
703   *          on the command line, or {@code false} if not.
704   */
705  @Override()
706  public boolean supportsPropertiesFile()
707  {
708    return true;
709  }
710
711
712
713  /**
714   * {@inheritDoc}
715   */
716  @Override()
717  public ResultCode doToolProcessing()
718  {
719    // Create a base configuration.
720    final InMemoryDirectoryServerConfig serverConfig;
721    try
722    {
723      serverConfig = getConfig();
724    }
725    catch (final LDAPException le)
726    {
727      Debug.debugException(le);
728      err(ERR_MEM_DS_TOOL_ERROR_INITIALIZING_CONFIG.get(le.getMessage()));
729      return le.getResultCode();
730    }
731
732
733    // Create the server instance using the provided configuration, but don't
734    // start it yet.
735    try
736    {
737      directoryServer = new InMemoryDirectoryServer(serverConfig);
738    }
739    catch (final LDAPException le)
740    {
741      Debug.debugException(le);
742      err(ERR_MEM_DS_TOOL_ERROR_CREATING_SERVER_INSTANCE.get(le.getMessage()));
743      return le.getResultCode();
744    }
745
746
747    // If an LDIF file was provided, then use it to populate the server.
748    if (ldifFileArgument.isPresent())
749    {
750      final File ldifFile = ldifFileArgument.getValue();
751      try
752      {
753        final int numEntries = directoryServer.importFromLDIF(true,
754             ldifFile.getAbsolutePath());
755        out(INFO_MEM_DS_TOOL_ADDED_ENTRIES_FROM_LDIF.get(numEntries,
756             ldifFile.getAbsolutePath()));
757      }
758      catch (final LDAPException le)
759      {
760        Debug.debugException(le);
761        err(ERR_MEM_DS_TOOL_ERROR_POPULATING_SERVER_INSTANCE.get(
762             ldifFile.getAbsolutePath(), le.getMessage()));
763        return le.getResultCode();
764      }
765    }
766
767
768    // Start the server.
769    try
770    {
771      if (! dontStartArgument.isPresent())
772      {
773        directoryServer.startListening();
774        out(INFO_MEM_DS_TOOL_LISTENING.get(directoryServer.getListenPort()));
775      }
776    }
777    catch (final Exception e)
778    {
779      Debug.debugException(e);
780      err(ERR_MEM_DS_TOOL_ERROR_STARTING_SERVER.get(
781           StaticUtils.getExceptionMessage(e)));
782      return ResultCode.LOCAL_ERROR;
783    }
784
785    return ResultCode.SUCCESS;
786  }
787
788
789
790  /**
791   * Creates a server configuration based on information provided with
792   * command line arguments.
793   *
794   * @return  The configuration that was created.
795   *
796   * @throws  LDAPException  If a problem is encountered while creating the
797   *                         configuration.
798   */
799  private InMemoryDirectoryServerConfig getConfig()
800          throws LDAPException
801  {
802    final List<DN> dnList = baseDNArgument.getValues();
803    final DN[] baseDNs = new DN[dnList.size()];
804    dnList.toArray(baseDNs);
805
806    final InMemoryDirectoryServerConfig serverConfig =
807         new InMemoryDirectoryServerConfig(baseDNs);
808
809
810    // If a listen port was specified, then update the configuration to use it.
811    int listenPort = 0;
812    if (portArgument.isPresent())
813    {
814      listenPort = portArgument.getValue();
815    }
816
817
818    // If schema should be used, then get it.
819    if (useDefaultSchemaArgument.isPresent())
820    {
821      serverConfig.setSchema(Schema.getDefaultStandardSchema());
822    }
823    else if (useSchemaFileArgument.isPresent())
824    {
825      final ArrayList<File> schemaFiles = new ArrayList<>(10);
826      for (final File f : useSchemaFileArgument.getValues())
827      {
828        if (f.exists())
829        {
830          if (f.isFile())
831          {
832            schemaFiles.add(f);
833          }
834          else
835          {
836            for (final File subFile : f.listFiles())
837            {
838              if (subFile.isFile())
839              {
840                schemaFiles.add(subFile);
841              }
842            }
843          }
844        }
845        else
846        {
847          throw new LDAPException(ResultCode.PARAM_ERROR,
848               ERR_MEM_DS_TOOL_NO_SUCH_SCHEMA_FILE.get(f.getAbsolutePath()));
849        }
850      }
851
852      try
853      {
854        serverConfig.setSchema(Schema.getSchema(schemaFiles));
855      }
856      catch (final Exception e)
857      {
858        Debug.debugException(e);
859
860        final StringBuilder fileList = new StringBuilder();
861        final Iterator<File> fileIterator = schemaFiles.iterator();
862        while (fileIterator.hasNext())
863        {
864          fileList.append(fileIterator.next().getAbsolutePath());
865          if (fileIterator.hasNext())
866          {
867            fileList.append(", ");
868          }
869        }
870
871        throw new LDAPException(ResultCode.LOCAL_ERROR,
872             ERR_MEM_DS_TOOL_ERROR_READING_SCHEMA.get(
873                  fileList, StaticUtils.getExceptionMessage(e)),
874             e);
875      }
876    }
877    else
878    {
879      serverConfig.setSchema(null);
880    }
881
882
883    // If an additional bind DN and password are provided, then include them in
884    // the configuration.
885    if (additionalBindDNArgument.isPresent())
886    {
887      serverConfig.addAdditionalBindCredentials(
888           additionalBindDNArgument.getValue().toString(),
889           additionalBindPasswordArgument.getValue());
890    }
891
892
893    // If a maximum number of changelog entries was specified, then update the
894    // configuration with that.
895    if (maxChangeLogEntriesArgument.isPresent())
896    {
897      serverConfig.setMaxChangeLogEntries(
898           maxChangeLogEntriesArgument.getValue());
899    }
900
901
902    // If an access log file was specified, then create the appropriate log
903    // handler.
904    if (accessLogToStandardOutArgument.isPresent())
905    {
906      final StreamHandler handler = new StreamHandler(System.out,
907           new MinimalLogFormatter(null, false, false, true));
908      handler.setLevel(Level.INFO);
909      serverConfig.setAccessLogHandler(handler);
910    }
911    else if (accessLogFileArgument.isPresent())
912    {
913      final File logFile = accessLogFileArgument.getValue();
914      try
915      {
916        final FileHandler handler =
917             new FileHandler(logFile.getAbsolutePath(), true);
918        handler.setLevel(Level.INFO);
919        handler.setFormatter(new MinimalLogFormatter(null, false, false,
920             true));
921        serverConfig.setAccessLogHandler(handler);
922      }
923      catch (final Exception e)
924      {
925        Debug.debugException(e);
926        throw new LDAPException(ResultCode.LOCAL_ERROR,
927             ERR_MEM_DS_TOOL_ERROR_CREATING_LOG_HANDLER.get(
928                  logFile.getAbsolutePath(),
929                  StaticUtils.getExceptionMessage(e)),
930             e);
931      }
932    }
933
934
935    // If an LDAP debug log file was specified, then create the appropriate log
936    // handler.
937    if (ldapDebugLogToStandardOutArgument.isPresent())
938    {
939      final StreamHandler handler = new StreamHandler(System.out,
940           new MinimalLogFormatter(null, false, false, true));
941      handler.setLevel(Level.INFO);
942      serverConfig.setLDAPDebugLogHandler(handler);
943    }
944    else if (ldapDebugLogFileArgument.isPresent())
945    {
946      final File logFile = ldapDebugLogFileArgument.getValue();
947      try
948      {
949        final FileHandler handler =
950             new FileHandler(logFile.getAbsolutePath(), true);
951        handler.setLevel(Level.INFO);
952        handler.setFormatter(new MinimalLogFormatter(null, false, false,
953             true));
954        serverConfig.setLDAPDebugLogHandler(handler);
955      }
956      catch (final Exception e)
957      {
958        Debug.debugException(e);
959        throw new LDAPException(ResultCode.LOCAL_ERROR,
960             ERR_MEM_DS_TOOL_ERROR_CREATING_LOG_HANDLER.get(
961                  logFile.getAbsolutePath(),
962                  StaticUtils.getExceptionMessage(e)),
963             e);
964      }
965    }
966
967
968    // If a code log file was specified, then update the configuration
969    // accordingly.
970    if (codeLogFile.isPresent())
971    {
972      serverConfig.setCodeLogDetails(codeLogFile.getValue().getAbsolutePath(),
973           true);
974    }
975
976
977    // If SSL is to be used, then create the corresponding socket factories.
978    if (useSSLArgument.isPresent() || useStartTLSArgument.isPresent())
979    {
980      try
981      {
982        final KeyManager keyManager = new KeyStoreKeyManager(
983             keyStorePathArgument.getValue(),
984             keyStorePasswordArgument.getValue().toCharArray(),
985             keyStoreTypeArgument.getValue(), null);
986
987        final TrustManager trustManager;
988        if (trustStorePathArgument.isPresent())
989        {
990          final char[] password;
991          if (trustStorePasswordArgument.isPresent())
992          {
993            password = trustStorePasswordArgument.getValue().toCharArray();
994          }
995          else
996          {
997            password = null;
998          }
999
1000          trustManager = new TrustStoreTrustManager(
1001               trustStorePathArgument.getValue(), password,
1002               trustStoreTypeArgument.getValue(), true);
1003        }
1004        else
1005        {
1006          trustManager = new TrustAllTrustManager();
1007        }
1008
1009        final SSLUtil serverSSLUtil = new SSLUtil(keyManager, trustManager);
1010
1011        if (useSSLArgument.isPresent())
1012        {
1013          final SSLUtil clientSSLUtil = new SSLUtil(new TrustAllTrustManager());
1014          serverConfig.setListenerConfigs(
1015               InMemoryListenerConfig.createLDAPSConfig("LDAPS", null,
1016                    listenPort, serverSSLUtil.createSSLServerSocketFactory(),
1017                    clientSSLUtil.createSSLSocketFactory()));
1018        }
1019        else
1020        {
1021          serverConfig.setListenerConfigs(
1022               InMemoryListenerConfig.createLDAPConfig("LDAP+StartTLS", null,
1023                    listenPort, serverSSLUtil.createSSLSocketFactory()));
1024        }
1025      }
1026      catch (final Exception e)
1027      {
1028        Debug.debugException(e);
1029        throw new LDAPException(ResultCode.LOCAL_ERROR,
1030             ERR_MEM_DS_TOOL_ERROR_INITIALIZING_SSL.get(
1031                  StaticUtils.getExceptionMessage(e)),
1032             e);
1033      }
1034    }
1035    else
1036    {
1037      serverConfig.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig(
1038           "LDAP", listenPort));
1039    }
1040
1041
1042    // If vendor name and/or vendor version values were provided, then configure
1043    // them for use.
1044    if (vendorNameArgument.isPresent())
1045    {
1046      serverConfig.setVendorName(vendorNameArgument.getValue());
1047    }
1048
1049    if (vendorVersionArgument.isPresent())
1050    {
1051      serverConfig.setVendorVersion(vendorVersionArgument.getValue());
1052    }
1053
1054
1055    // If equality indexing is to be performed, then configure it.
1056    if (equalityIndexArgument.isPresent())
1057    {
1058      serverConfig.setEqualityIndexAttributes(
1059           equalityIndexArgument.getValues());
1060    }
1061
1062    return serverConfig;
1063  }
1064
1065
1066
1067  /**
1068   * {@inheritDoc}
1069   */
1070  @Override()
1071  public LinkedHashMap<String[],String> getExampleUsages()
1072  {
1073    final LinkedHashMap<String[],String> exampleUsages = new LinkedHashMap<>(2);
1074
1075    final String[] example1Args =
1076    {
1077      "--baseDN", "dc=example,dc=com"
1078    };
1079    exampleUsages.put(example1Args, INFO_MEM_DS_TOOL_EXAMPLE_1.get());
1080
1081    final String[] example2Args =
1082    {
1083      "--baseDN", "dc=example,dc=com",
1084      "--port", "1389",
1085      "--ldifFile", "test.ldif",
1086      "--accessLogFile", "access.log",
1087      "--useDefaultSchema"
1088    };
1089    exampleUsages.put(example2Args, INFO_MEM_DS_TOOL_EXAMPLE_2.get());
1090
1091    return exampleUsages;
1092  }
1093
1094
1095
1096  /**
1097   * Retrieves the in-memory directory server instance that has been created by
1098   * this tool.  It will only be valid after the {@link #doToolProcessing()}
1099   * method has been called.
1100   *
1101   * @return  The in-memory directory server instance that has been created by
1102   *          this tool, or {@code null} if the directory server instance has
1103   *          not been successfully created.
1104   */
1105  public InMemoryDirectoryServer getDirectoryServer()
1106  {
1107    return directoryServer;
1108  }
1109
1110
1111
1112  /**
1113   * {@inheritDoc}
1114   */
1115  @Override()
1116  public void connectionCreationFailure(final Socket socket,
1117                                        final Throwable cause)
1118  {
1119    err(ERR_MEM_DS_TOOL_ERROR_ACCEPTING_CONNECTION.get(
1120         StaticUtils.getExceptionMessage(cause)));
1121  }
1122
1123
1124
1125  /**
1126   * {@inheritDoc}
1127   */
1128  @Override()
1129  public void connectionTerminated(
1130                   final LDAPListenerClientConnection connection,
1131                   final LDAPException cause)
1132  {
1133    err(ERR_MEM_DS_TOOL_CONNECTION_TERMINATED_BY_EXCEPTION.get(
1134         StaticUtils.getExceptionMessage(cause)));
1135  }
1136}