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