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