001    /*
002     * Copyright 2011-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2011-2013 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     */
021    package com.unboundid.ldap.listener;
022    
023    
024    
025    import java.io.File;
026    import java.io.OutputStream;
027    import java.io.Serializable;
028    import java.net.Socket;
029    import java.util.ArrayList;
030    import java.util.Iterator;
031    import java.util.LinkedHashMap;
032    import java.util.List;
033    import java.util.logging.FileHandler;
034    import java.util.logging.Level;
035    import java.util.logging.StreamHandler;
036    import javax.net.ssl.KeyManager;
037    import javax.net.ssl.TrustManager;
038    
039    import com.unboundid.ldap.sdk.DN;
040    import com.unboundid.ldap.sdk.LDAPException;
041    import com.unboundid.ldap.sdk.ResultCode;
042    import com.unboundid.ldap.sdk.Version;
043    import com.unboundid.ldap.sdk.schema.Schema;
044    import com.unboundid.util.CommandLineTool;
045    import com.unboundid.util.Debug;
046    import com.unboundid.util.MinimalLogFormatter;
047    import com.unboundid.util.NotMutable;
048    import com.unboundid.util.StaticUtils;
049    import com.unboundid.util.ThreadSafety;
050    import com.unboundid.util.ThreadSafetyLevel;
051    import com.unboundid.util.args.ArgumentException;
052    import com.unboundid.util.args.ArgumentParser;
053    import com.unboundid.util.args.BooleanArgument;
054    import com.unboundid.util.args.DNArgument;
055    import com.unboundid.util.args.IntegerArgument;
056    import com.unboundid.util.args.FileArgument;
057    import com.unboundid.util.args.StringArgument;
058    import com.unboundid.util.ssl.KeyStoreKeyManager;
059    import com.unboundid.util.ssl.SSLUtil;
060    import com.unboundid.util.ssl.TrustAllTrustManager;
061    import com.unboundid.util.ssl.TrustStoreTrustManager;
062    
063    import 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)
175    public 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    }