001/*
002 * Copyright 2011-2015 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2011-2015 UnboundID Corp.
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 path to the SSL key store file.
232  private FileArgument keyStorePathArgument;
233
234  // The argument used to specify the path to an LDAP debug log file to which
235  // information should be written about detailed LDAP communication performed
236  // by the server.
237  private FileArgument ldapDebugLogFileArgument;
238
239  // The argument used to specify the path to an LDIF file with data to use to
240  // initially populate the server.
241  private FileArgument ldifFileArgument;
242
243  // The argument used to specify the path to the SSL trust store file.
244  private FileArgument trustStorePathArgument;
245
246  // The argument used to specify the path to a directory containing schema
247  // definitions.
248  private FileArgument useSchemaFileArgument;
249
250  // The in-memory directory server instance that has been created by this tool.
251  private InMemoryDirectoryServer directoryServer;
252
253  // The argument used to specify the maximum number of changelog entries that
254  // the server should maintain.
255  private IntegerArgument maxChangeLogEntriesArgument;
256
257  // The argument used to specify the port on which the server should listen.
258  private IntegerArgument portArgument;
259
260  // The argument used to specify the password for the additional bind DN.
261  private StringArgument additionalBindPasswordArgument;
262
263  // The argument used to specify the attributes for which to maintain equality
264  // indexes.
265  private StringArgument equalityIndexArgument;
266
267  // The argument used to specify the password to use to access the contents of
268  // the SSL key store
269  private StringArgument keyStorePasswordArgument;
270
271  // The argument used to specify the key store type.
272  private StringArgument keyStoreTypeArgument;
273
274  // The argument used to specify the password to use to access the contents of
275  // the SSL trust store
276  private StringArgument trustStorePasswordArgument;
277
278  // The argument used to specify the trust store type.
279  private StringArgument trustStoreTypeArgument;
280
281  // The argument used to specify the server vendor name.
282  private StringArgument vendorNameArgument;
283
284  // The argument used to specify the server vendor veresion.
285  private StringArgument vendorVersionArgument;
286
287
288
289  /**
290   * Parse the provided command line arguments and uses them to start the
291   * directory server.
292   *
293   * @param  args  The command line arguments provided to this program.
294   */
295  public static void main(final String... args)
296  {
297    final ResultCode resultCode = main(args, System.out, System.err);
298    if (resultCode != ResultCode.SUCCESS)
299    {
300      System.exit(resultCode.intValue());
301    }
302  }
303
304
305
306  /**
307   * Parse the provided command line arguments and uses them to start the
308   * directory server.
309   *
310   * @param  outStream  The output stream to which standard out should be
311   *                    written.  It may be {@code null} if output should be
312   *                    suppressed.
313   * @param  errStream  The output stream to which standard error should be
314   *                    written.  It may be {@code null} if error messages
315   *                    should be suppressed.
316   * @param  args       The command line arguments provided to this program.
317   *
318   * @return  A result code indicating whether the processing was successful.
319   */
320  public static ResultCode main(final String[] args,
321                                final OutputStream outStream,
322                                final OutputStream errStream)
323  {
324    final InMemoryDirectoryServerTool tool =
325         new InMemoryDirectoryServerTool(outStream, errStream);
326    return tool.runTool(args);
327  }
328
329
330
331  /**
332   * Creates a new instance of this tool that use the provided output streams
333   * for standard output and standard error.
334   *
335   * @param  outStream  The output stream to use for standard output.  It may be
336   *                    {@code System.out} for the JVM's default standard output
337   *                    stream, {@code null} if no output should be generated,
338   *                    or a custom output stream if the output should be sent
339   *                    to an alternate location.
340   * @param  errStream  The output stream to use for standard error.  It may be
341   *                    {@code System.err} for the JVM's default standard error
342   *                    stream, {@code null} if no output should be generated,
343   *                    or a custom output stream if the output should be sent
344   *                    to an alternate location.
345   */
346  public InMemoryDirectoryServerTool(final OutputStream outStream,
347                                     final OutputStream errStream)
348  {
349    super(outStream, errStream);
350
351    directoryServer                   = null;
352    dontStartArgument                 = null;
353    useDefaultSchemaArgument          = null;
354    useSSLArgument                    = null;
355    useStartTLSArgument               = null;
356    additionalBindDNArgument          = null;
357    baseDNArgument                    = null;
358    accessLogToStandardOutArgument    = null;
359    accessLogFileArgument             = null;
360    keyStorePathArgument              = null;
361    ldapDebugLogToStandardOutArgument = null;
362    ldapDebugLogFileArgument          = null;
363    ldifFileArgument                  = null;
364    trustStorePathArgument            = null;
365    useSchemaFileArgument             = null;
366    maxChangeLogEntriesArgument       = null;
367    portArgument                      = null;
368    additionalBindPasswordArgument    = null;
369    equalityIndexArgument             = null;
370    keyStorePasswordArgument          = null;
371    keyStoreTypeArgument              = null;
372    trustStorePasswordArgument        = null;
373    trustStoreTypeArgument            = null;
374    vendorNameArgument                = null;
375    vendorVersionArgument             = null;
376  }
377
378
379
380  /**
381   * {@inheritDoc}
382   */
383  @Override()
384  public String getToolName()
385  {
386    return "in-memory-directory-server";
387  }
388
389
390
391  /**
392   * {@inheritDoc}
393   */
394  @Override()
395  public String getToolDescription()
396  {
397    return INFO_MEM_DS_TOOL_DESC.get(InMemoryDirectoryServer.class.getName());
398  }
399
400
401
402  /**
403   * Retrieves the version string for this tool.
404   *
405   * @return  The version string for this tool.
406   */
407  @Override()
408  public String getToolVersion()
409  {
410    return Version.NUMERIC_VERSION_STRING;
411  }
412
413
414
415  /**
416   * {@inheritDoc}
417   */
418  @Override()
419  public void addToolArguments(final ArgumentParser parser)
420         throws ArgumentException
421  {
422    baseDNArgument = new DNArgument('b', "baseDN", true, 0,
423         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_BASE_DN.get(),
424         INFO_MEM_DS_TOOL_ARG_DESC_BASE_DN.get());
425    parser.addArgument(baseDNArgument);
426
427    portArgument = new IntegerArgument('p', "port", false, 1,
428         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PORT.get(),
429         INFO_MEM_DS_TOOL_ARG_DESC_PORT.get(), 0, 65535);
430    parser.addArgument(portArgument);
431
432    ldifFileArgument = new FileArgument('l', "ldifFile", false, 1,
433         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
434         INFO_MEM_DS_TOOL_ARG_DESC_LDIF_FILE.get(), true, true, true, false);
435    parser.addArgument(ldifFileArgument);
436
437    additionalBindDNArgument = new DNArgument('D', "additionalBindDN", false, 1,
438         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_BIND_DN.get(),
439         INFO_MEM_DS_TOOL_ARG_DESC_ADDITIONAL_BIND_DN.get());
440    parser.addArgument(additionalBindDNArgument);
441
442    additionalBindPasswordArgument = new StringArgument('w',
443         "additionalBindPassword", false, 1,
444         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PASSWORD.get(),
445         INFO_MEM_DS_TOOL_ARG_DESC_ADDITIONAL_BIND_PW.get());
446    parser.addArgument(additionalBindPasswordArgument);
447
448    maxChangeLogEntriesArgument = new IntegerArgument('c',
449         "maxChangeLogEntries", false, 1,
450         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_COUNT.get(),
451         INFO_MEM_DS_TOOL_ARG_DESC_MAX_CHANGELOG_ENTRIES.get(), 0,
452         Integer.MAX_VALUE, 0);
453    parser.addArgument(maxChangeLogEntriesArgument);
454
455    accessLogToStandardOutArgument = new BooleanArgument('A',
456         "accessLogToStandardOut",
457         INFO_MEM_DS_TOOL_ARG_DESC_ACCESS_LOG_TO_STDOUT.get());
458    parser.addArgument(accessLogToStandardOutArgument);
459
460    accessLogFileArgument = new FileArgument('a', "accessLogFile", false, 1,
461         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
462         INFO_MEM_DS_TOOL_ARG_DESC_ACCESS_LOG_FILE.get(), false, true, true,
463         false);
464    parser.addArgument(accessLogFileArgument);
465
466    ldapDebugLogToStandardOutArgument = new BooleanArgument(null,
467         "ldapDebugLogToStandardOut",
468         INFO_MEM_DS_TOOL_ARG_DESC_LDAP_DEBUG_LOG_TO_STDOUT.get());
469    parser.addArgument(ldapDebugLogToStandardOutArgument);
470
471    ldapDebugLogFileArgument = new FileArgument('d', "ldapDebugLogFile", false,
472         1, INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
473         INFO_MEM_DS_TOOL_ARG_DESC_LDAP_DEBUG_LOG_FILE.get(), false, true, true,
474         false);
475    parser.addArgument(ldapDebugLogFileArgument);
476
477    useDefaultSchemaArgument = new BooleanArgument('s', "useDefaultSchema",
478         INFO_MEM_DS_TOOL_ARG_DESC_USE_DEFAULT_SCHEMA.get());
479    parser.addArgument(useDefaultSchemaArgument);
480
481    useSchemaFileArgument = new FileArgument('S', "useSchemaFile", false, 0,
482         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
483         INFO_MEM_DS_TOOL_ARG_DESC_USE_SCHEMA_FILE.get(), true, true, false,
484         false);
485    parser.addArgument(useSchemaFileArgument);
486
487    equalityIndexArgument = new StringArgument('I', "equalityIndex", false, 0,
488         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_ATTR.get(),
489         INFO_MEM_DS_TOOL_ARG_DESC_EQ_INDEX.get());
490    parser.addArgument(equalityIndexArgument);
491
492    useSSLArgument = new BooleanArgument('Z', "useSSL",
493         INFO_MEM_DS_TOOL_ARG_DESC_USE_SSL.get());
494    parser.addArgument(useSSLArgument);
495
496    useStartTLSArgument = new BooleanArgument('q', "useStartTLS",
497         INFO_MEM_DS_TOOL_ARG_DESC_USE_START_TLS.get());
498    parser.addArgument(useStartTLSArgument);
499
500    keyStorePathArgument = new FileArgument('K', "keyStorePath", false, 1,
501         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
502         INFO_MEM_DS_TOOL_ARG_DESC_KEY_STORE_PATH.get(), true, true, true,
503         false);
504    parser.addArgument(keyStorePathArgument);
505
506    keyStorePasswordArgument = new StringArgument('W', "keyStorePassword",
507         false, 1, INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PASSWORD.get(),
508         INFO_MEM_DS_TOOL_ARG_DESC_KEY_STORE_PW.get());
509    parser.addArgument(keyStorePasswordArgument);
510
511    keyStoreTypeArgument = new StringArgument(null, "keyStoreType",
512         false, 1, "{type}", "The keystore type.", "JKS");
513    parser.addArgument(keyStoreTypeArgument);
514
515    trustStorePathArgument = new FileArgument('P', "trustStorePath", false, 1,
516         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
517         INFO_MEM_DS_TOOL_ARG_DESC_TRUST_STORE_PATH.get(), true, true, true,
518         false);
519    parser.addArgument(trustStorePathArgument);
520
521    trustStorePasswordArgument = new StringArgument('T', "trustStorePassword",
522         false, 1, INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PASSWORD.get(),
523         INFO_MEM_DS_TOOL_ARG_DESC_TRUST_STORE_PW.get());
524    parser.addArgument(trustStorePasswordArgument);
525
526    trustStoreTypeArgument = new StringArgument(null, "trustStoreType",
527         false, 1, "{type}", "The trust store type.", "JKS");
528    parser.addArgument(trustStoreTypeArgument);
529
530    vendorNameArgument = new StringArgument(null, "vendorName", false, 1,
531         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_VALUE.get(),
532         INFO_MEM_DS_TOOL_ARG_DESC_VENDOR_NAME.get());
533    parser.addArgument(vendorNameArgument);
534
535    vendorVersionArgument = new StringArgument(null, "vendorVersion", false, 1,
536         INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_VALUE.get(),
537         INFO_MEM_DS_TOOL_ARG_DESC_VENDOR_VERSION.get());
538    parser.addArgument(vendorVersionArgument);
539
540    dontStartArgument = new BooleanArgument(null, "dontStart",
541         INFO_MEM_DS_TOOL_ARG_DESC_DONT_START.get());
542    dontStartArgument.setHidden(true);
543    parser.addArgument(dontStartArgument);
544
545    parser.addExclusiveArgumentSet(useDefaultSchemaArgument,
546         useSchemaFileArgument);
547    parser.addExclusiveArgumentSet(useSSLArgument, useStartTLSArgument);
548
549    parser.addExclusiveArgumentSet(accessLogToStandardOutArgument,
550         accessLogFileArgument);
551    parser.addExclusiveArgumentSet(ldapDebugLogToStandardOutArgument,
552         ldapDebugLogFileArgument);
553
554    parser.addDependentArgumentSet(additionalBindDNArgument,
555         additionalBindPasswordArgument);
556    parser.addDependentArgumentSet(additionalBindPasswordArgument,
557         additionalBindDNArgument);
558
559    parser.addDependentArgumentSet(useSSLArgument, keyStorePathArgument);
560    parser.addDependentArgumentSet(useSSLArgument, keyStorePasswordArgument);
561    parser.addDependentArgumentSet(useStartTLSArgument, keyStorePathArgument);
562    parser.addDependentArgumentSet(useStartTLSArgument,
563         keyStorePasswordArgument);
564    parser.addDependentArgumentSet(keyStorePathArgument, useSSLArgument,
565         useStartTLSArgument);
566    parser.addDependentArgumentSet(keyStorePasswordArgument, useSSLArgument,
567         useStartTLSArgument);
568    parser.addDependentArgumentSet(trustStorePathArgument, useSSLArgument,
569         useStartTLSArgument);
570    parser.addDependentArgumentSet(trustStorePasswordArgument,
571         trustStorePathArgument);
572  }
573
574
575
576  /**
577   * {@inheritDoc}
578   */
579  @Override()
580  public ResultCode doToolProcessing()
581  {
582    // Create a base configuration.
583    final InMemoryDirectoryServerConfig serverConfig;
584    try
585    {
586      serverConfig = getConfig();
587    }
588    catch (final LDAPException le)
589    {
590      Debug.debugException(le);
591      err(ERR_MEM_DS_TOOL_ERROR_INITIALIZING_CONFIG.get(le.getMessage()));
592      return le.getResultCode();
593    }
594
595
596    // Create the server instance using the provided configuration, but don't
597    // start it yet.
598    try
599    {
600      directoryServer = new InMemoryDirectoryServer(serverConfig);
601    }
602    catch (final LDAPException le)
603    {
604      Debug.debugException(le);
605      err(ERR_MEM_DS_TOOL_ERROR_CREATING_SERVER_INSTANCE.get(le.getMessage()));
606      return le.getResultCode();
607    }
608
609
610    // If an LDIF file was provided, then use it to populate the server.
611    if (ldifFileArgument.isPresent())
612    {
613      final File ldifFile = ldifFileArgument.getValue();
614      try
615      {
616        final int numEntries = directoryServer.importFromLDIF(true,
617             ldifFile.getAbsolutePath());
618        out(INFO_MEM_DS_TOOL_ADDED_ENTRIES_FROM_LDIF.get(numEntries,
619             ldifFile.getAbsolutePath()));
620      }
621      catch (final LDAPException le)
622      {
623        Debug.debugException(le);
624        err(ERR_MEM_DS_TOOL_ERROR_POPULATING_SERVER_INSTANCE.get(
625             ldifFile.getAbsolutePath(), le.getMessage()));
626        return le.getResultCode();
627      }
628    }
629
630
631    // Start the server.
632    try
633    {
634      if (! dontStartArgument.isPresent())
635      {
636        directoryServer.startListening();
637        out(INFO_MEM_DS_TOOL_LISTENING.get(directoryServer.getListenPort()));
638      }
639    }
640    catch (final Exception e)
641    {
642      Debug.debugException(e);
643      err(ERR_MEM_DS_TOOL_ERROR_STARTING_SERVER.get(
644           StaticUtils.getExceptionMessage(e)));
645      return ResultCode.LOCAL_ERROR;
646    }
647
648    return ResultCode.SUCCESS;
649  }
650
651
652
653  /**
654   * Creates a server configuration based on information provided with
655   * command line arguments.
656   *
657   * @return  The configuration that was created.
658   *
659   * @throws  LDAPException  If a problem is encountered while creating the
660   *                         configuration.
661   */
662  private InMemoryDirectoryServerConfig getConfig()
663          throws LDAPException
664  {
665    final List<DN> dnList = baseDNArgument.getValues();
666    final DN[] baseDNs = new DN[dnList.size()];
667    dnList.toArray(baseDNs);
668
669    final InMemoryDirectoryServerConfig serverConfig =
670         new InMemoryDirectoryServerConfig(baseDNs);
671
672
673    // If a listen port was specified, then update the configuration to use it.
674    int listenPort = 0;
675    if (portArgument.isPresent())
676    {
677      listenPort = portArgument.getValue();
678    }
679
680
681    // If schema should be used, then get it.
682    if (useDefaultSchemaArgument.isPresent())
683    {
684      serverConfig.setSchema(Schema.getDefaultStandardSchema());
685    }
686    else if (useSchemaFileArgument.isPresent())
687    {
688      final ArrayList<File> schemaFiles = new ArrayList<File>(10);
689      for (final File f : useSchemaFileArgument.getValues())
690      {
691        if (f.exists())
692        {
693          if (f.isFile())
694          {
695            schemaFiles.add(f);
696          }
697          else
698          {
699            for (final File subFile : f.listFiles())
700            {
701              if (subFile.isFile())
702              {
703                schemaFiles.add(subFile);
704              }
705            }
706          }
707        }
708        else
709        {
710          throw new LDAPException(ResultCode.PARAM_ERROR,
711               ERR_MEM_DS_TOOL_NO_SUCH_SCHEMA_FILE.get(f.getAbsolutePath()));
712        }
713      }
714
715      try
716      {
717        serverConfig.setSchema(Schema.getSchema(schemaFiles));
718      }
719      catch (final Exception e)
720      {
721        Debug.debugException(e);
722
723        final StringBuilder fileList = new StringBuilder();
724        final Iterator<File> fileIterator = schemaFiles.iterator();
725        while (fileIterator.hasNext())
726        {
727          fileList.append(fileIterator.next().getAbsolutePath());
728          if (fileIterator.hasNext())
729          {
730            fileList.append(", ");
731          }
732        }
733
734        throw new LDAPException(ResultCode.LOCAL_ERROR,
735             ERR_MEM_DS_TOOL_ERROR_READING_SCHEMA.get(
736                  fileList, StaticUtils.getExceptionMessage(e)),
737             e);
738      }
739    }
740    else
741    {
742      serverConfig.setSchema(null);
743    }
744
745
746    // If an additional bind DN and password are provided, then include them in
747    // the configuration.
748    if (additionalBindDNArgument.isPresent())
749    {
750      serverConfig.addAdditionalBindCredentials(
751           additionalBindDNArgument.getValue().toString(),
752           additionalBindPasswordArgument.getValue());
753    }
754
755
756    // If a maximum number of changelog entries was specified, then update the
757    // configuration with that.
758    if (maxChangeLogEntriesArgument.isPresent())
759    {
760      serverConfig.setMaxChangeLogEntries(
761           maxChangeLogEntriesArgument.getValue());
762    }
763
764
765    // If an access log file was specified, then create the appropriate log
766    // handler.
767    if (accessLogToStandardOutArgument.isPresent())
768    {
769      final StreamHandler handler = new StreamHandler(System.out,
770           new MinimalLogFormatter(null, false, false, true));
771      handler.setLevel(Level.INFO);
772      serverConfig.setAccessLogHandler(handler);
773    }
774    else if (accessLogFileArgument.isPresent())
775    {
776      final File logFile = accessLogFileArgument.getValue();
777      try
778      {
779        final FileHandler handler =
780             new FileHandler(logFile.getAbsolutePath(), true);
781        handler.setLevel(Level.INFO);
782        handler.setFormatter(new MinimalLogFormatter(null, false, false,
783             true));
784        serverConfig.setAccessLogHandler(handler);
785      }
786      catch (final Exception e)
787      {
788        Debug.debugException(e);
789        throw new LDAPException(ResultCode.LOCAL_ERROR,
790             ERR_MEM_DS_TOOL_ERROR_CREATING_LOG_HANDLER.get(
791                  logFile.getAbsolutePath(),
792                  StaticUtils.getExceptionMessage(e)),
793             e);
794      }
795    }
796
797
798    // If an LDAP debug log file was specified, then create the appropriate log
799    // handler.
800    if (ldapDebugLogToStandardOutArgument.isPresent())
801    {
802      final StreamHandler handler = new StreamHandler(System.out,
803           new MinimalLogFormatter(null, false, false, true));
804      handler.setLevel(Level.INFO);
805      serverConfig.setLDAPDebugLogHandler(handler);
806    }
807    else if (ldapDebugLogFileArgument.isPresent())
808    {
809      final File logFile = ldapDebugLogFileArgument.getValue();
810      try
811      {
812        final FileHandler handler =
813             new FileHandler(logFile.getAbsolutePath(), true);
814        handler.setLevel(Level.INFO);
815        handler.setFormatter(new MinimalLogFormatter(null, false, false,
816             true));
817        serverConfig.setLDAPDebugLogHandler(handler);
818      }
819      catch (final Exception e)
820      {
821        Debug.debugException(e);
822        throw new LDAPException(ResultCode.LOCAL_ERROR,
823             ERR_MEM_DS_TOOL_ERROR_CREATING_LOG_HANDLER.get(
824                  logFile.getAbsolutePath(),
825                  StaticUtils.getExceptionMessage(e)),
826             e);
827      }
828    }
829
830
831    // If SSL is to be used, then create the corresponding socket factories.
832    if (useSSLArgument.isPresent() || useStartTLSArgument.isPresent())
833    {
834      try
835      {
836        final KeyManager keyManager = new KeyStoreKeyManager(
837             keyStorePathArgument.getValue(),
838             keyStorePasswordArgument.getValue().toCharArray(),
839             keyStoreTypeArgument.getValue(), null);
840
841        final TrustManager trustManager;
842        if (trustStorePathArgument.isPresent())
843        {
844          final char[] password;
845          if (trustStorePasswordArgument.isPresent())
846          {
847            password = trustStorePasswordArgument.getValue().toCharArray();
848          }
849          else
850          {
851            password = null;
852          }
853
854          trustManager = new TrustStoreTrustManager(
855               trustStorePathArgument.getValue(), password,
856               trustStoreTypeArgument.getValue(), true);
857        }
858        else
859        {
860          trustManager = new TrustAllTrustManager();
861        }
862
863        final SSLUtil serverSSLUtil = new SSLUtil(keyManager, trustManager);
864
865        if (useSSLArgument.isPresent())
866        {
867          final SSLUtil clientSSLUtil = new SSLUtil(new TrustAllTrustManager());
868          serverConfig.setListenerConfigs(
869               InMemoryListenerConfig.createLDAPSConfig("LDAPS", null,
870                    listenPort, serverSSLUtil.createSSLServerSocketFactory(),
871                    clientSSLUtil.createSSLSocketFactory()));
872        }
873        else
874        {
875          serverConfig.setListenerConfigs(
876               InMemoryListenerConfig.createLDAPConfig("LDAP+StartTLS", null,
877                    listenPort, serverSSLUtil.createSSLSocketFactory()));
878        }
879      }
880      catch (final Exception e)
881      {
882        Debug.debugException(e);
883        throw new LDAPException(ResultCode.LOCAL_ERROR,
884             ERR_MEM_DS_TOOL_ERROR_INITIALIZING_SSL.get(
885                  StaticUtils.getExceptionMessage(e)),
886             e);
887      }
888    }
889    else
890    {
891      serverConfig.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig(
892           "LDAP", listenPort));
893    }
894
895
896    // If vendor name and/or vendor version values were provided, then configure
897    // them for use.
898    if (vendorNameArgument.isPresent())
899    {
900      serverConfig.setVendorName(vendorNameArgument.getValue());
901    }
902
903    if (vendorVersionArgument.isPresent())
904    {
905      serverConfig.setVendorVersion(vendorVersionArgument.getValue());
906    }
907
908
909    // If equality indexing is to be performed, then configure it.
910    if (equalityIndexArgument.isPresent())
911    {
912      serverConfig.setEqualityIndexAttributes(
913           equalityIndexArgument.getValues());
914    }
915
916    return serverConfig;
917  }
918
919
920
921  /**
922   * {@inheritDoc}
923   */
924  @Override()
925  public LinkedHashMap<String[],String> getExampleUsages()
926  {
927    final LinkedHashMap<String[],String> exampleUsages =
928         new LinkedHashMap<String[],String>(2);
929
930    final String[] example1Args =
931    {
932      "--baseDN", "dc=example,dc=com"
933    };
934    exampleUsages.put(example1Args, INFO_MEM_DS_TOOL_EXAMPLE_1.get());
935
936    final String[] example2Args =
937    {
938      "--baseDN", "dc=example,dc=com",
939      "--port", "1389",
940      "--ldifFile", "test.ldif",
941      "--accessLogFile", "access.log",
942      "--useDefaultSchema"
943    };
944    exampleUsages.put(example2Args, INFO_MEM_DS_TOOL_EXAMPLE_2.get());
945
946    return exampleUsages;
947  }
948
949
950
951  /**
952   * Retrieves the in-memory directory server instance that has been created by
953   * this tool.  It will only be valid after the {@link #doToolProcessing()}
954   * method has been called.
955   *
956   * @return  The in-memory directory server instance that has been created by
957   *          this tool, or {@code null} if the directory server instance has
958   *          not been successfully created.
959   */
960  public InMemoryDirectoryServer getDirectoryServer()
961  {
962    return directoryServer;
963  }
964
965
966
967  /**
968   * {@inheritDoc}
969   */
970  public void connectionCreationFailure(final Socket socket,
971                                        final Throwable cause)
972  {
973    err(ERR_MEM_DS_TOOL_ERROR_ACCEPTING_CONNECTION.get(
974         StaticUtils.getExceptionMessage(cause)));
975  }
976
977
978
979  /**
980   * {@inheritDoc}
981   */
982  public void connectionTerminated(
983                   final LDAPListenerClientConnection connection,
984                   final LDAPException cause)
985  {
986    err(ERR_MEM_DS_TOOL_CONNECTION_TERMINATED_BY_EXCEPTION.get(
987         StaticUtils.getExceptionMessage(cause)));
988  }
989}