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     */
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>"--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)
187    public 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    }