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.setArgumentGroupName(
460             INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
461        keyStorePasswordArgument.addLongIdentifier("keyStorePIN");
462        keyStorePasswordArgument.addLongIdentifier("key-store-password");
463        keyStorePasswordArgument.addLongIdentifier("key-store-pin");
464        parser.addArgument(keyStorePasswordArgument);
465    
466        keyStoreTypeArgument = new StringArgument(null, "keyStoreType",
467             false, 1, "{type}", "The keystore type.", "JKS");
468        keyStoreTypeArgument.setArgumentGroupName(
469             INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
470        keyStoreTypeArgument.addLongIdentifier("keyStoreFormat");
471        keyStoreTypeArgument.addLongIdentifier("key-store-type");
472        keyStoreTypeArgument.addLongIdentifier("key-store-format");
473        parser.addArgument(keyStoreTypeArgument);
474    
475        trustStorePathArgument = new FileArgument('P', "trustStorePath", false, 1,
476             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
477             INFO_MEM_DS_TOOL_ARG_DESC_TRUST_STORE_PATH.get(), true, true, true,
478             false);
479        trustStorePathArgument.setArgumentGroupName(
480             INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
481        trustStorePathArgument.addLongIdentifier("trust-store-path");
482        parser.addArgument(trustStorePathArgument);
483    
484        trustStorePasswordArgument = new StringArgument('T', "trustStorePassword",
485             false, 1, INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PASSWORD.get(),
486             INFO_MEM_DS_TOOL_ARG_DESC_TRUST_STORE_PW.get());
487        trustStorePasswordArgument.setArgumentGroupName(
488             INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
489        trustStorePasswordArgument.addLongIdentifier("trustStorePIN");
490        trustStorePasswordArgument.addLongIdentifier("trust-store-password");
491        trustStorePasswordArgument.addLongIdentifier("trust-store-pin");
492        parser.addArgument(trustStorePasswordArgument);
493    
494        trustStoreTypeArgument = new StringArgument(null, "trustStoreType",
495             false, 1, "{type}", "The trust store type.", "JKS");
496        trustStoreTypeArgument.setArgumentGroupName(
497             INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
498        trustStoreTypeArgument.addLongIdentifier("trustStoreFormat");
499        trustStoreTypeArgument.addLongIdentifier("trust-store-type");
500        trustStoreTypeArgument.addLongIdentifier("trust-store-format");
501        parser.addArgument(trustStoreTypeArgument);
502    
503        dontStartArgument = new BooleanArgument(null, "dontStart",
504             INFO_MEM_DS_TOOL_ARG_DESC_DONT_START.get());
505        dontStartArgument.setArgumentGroupName(
506             INFO_MEM_DS_TOOL_GROUP_CONNECTIVITY.get());
507        dontStartArgument.setHidden(true);
508        dontStartArgument.addLongIdentifier("doNotStart");
509        dontStartArgument.addLongIdentifier("dont-start");
510        dontStartArgument.addLongIdentifier("do-not-start");
511        parser.addArgument(dontStartArgument);
512    
513        baseDNArgument = new DNArgument('b', "baseDN", true, 0,
514             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_BASE_DN.get(),
515             INFO_MEM_DS_TOOL_ARG_DESC_BASE_DN.get());
516        baseDNArgument.setArgumentGroupName(INFO_MEM_DS_TOOL_GROUP_DATA.get());
517        baseDNArgument.addLongIdentifier("base-dn");
518        parser.addArgument(baseDNArgument);
519    
520        ldifFileArgument = new FileArgument('l', "ldifFile", false, 1,
521             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
522             INFO_MEM_DS_TOOL_ARG_DESC_LDIF_FILE.get(), true, true, true, false);
523        ldifFileArgument.setArgumentGroupName(INFO_MEM_DS_TOOL_GROUP_DATA.get());
524        ldifFileArgument.addLongIdentifier("ldif-file");
525        parser.addArgument(ldifFileArgument);
526    
527        additionalBindDNArgument = new DNArgument('D', "additionalBindDN", false, 1,
528             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_BIND_DN.get(),
529             INFO_MEM_DS_TOOL_ARG_DESC_ADDITIONAL_BIND_DN.get());
530        additionalBindDNArgument.setArgumentGroupName(
531             INFO_MEM_DS_TOOL_GROUP_DATA.get());
532        additionalBindDNArgument.addLongIdentifier("additional-bind-dn");
533        parser.addArgument(additionalBindDNArgument);
534    
535        additionalBindPasswordArgument = new StringArgument('w',
536             "additionalBindPassword", false, 1,
537             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PASSWORD.get(),
538             INFO_MEM_DS_TOOL_ARG_DESC_ADDITIONAL_BIND_PW.get());
539        additionalBindPasswordArgument.setArgumentGroupName(
540             INFO_MEM_DS_TOOL_GROUP_DATA.get());
541        additionalBindPasswordArgument.addLongIdentifier(
542             "additional-bind-password");
543        parser.addArgument(additionalBindPasswordArgument);
544    
545        useDefaultSchemaArgument = new BooleanArgument('s', "useDefaultSchema",
546             INFO_MEM_DS_TOOL_ARG_DESC_USE_DEFAULT_SCHEMA.get());
547        useDefaultSchemaArgument.setArgumentGroupName(
548             INFO_MEM_DS_TOOL_GROUP_DATA.get());
549        useDefaultSchemaArgument.addLongIdentifier("use-default-schema");
550        parser.addArgument(useDefaultSchemaArgument);
551    
552        useSchemaFileArgument = new FileArgument('S', "useSchemaFile", false, 0,
553             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
554             INFO_MEM_DS_TOOL_ARG_DESC_USE_SCHEMA_FILE.get(), true, true, false,
555             false);
556        useSchemaFileArgument.setArgumentGroupName(
557             INFO_MEM_DS_TOOL_GROUP_DATA.get());
558        useSchemaFileArgument.addLongIdentifier("use-schema-file");
559        parser.addArgument(useSchemaFileArgument);
560    
561        equalityIndexArgument = new StringArgument('I', "equalityIndex", false, 0,
562             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_ATTR.get(),
563             INFO_MEM_DS_TOOL_ARG_DESC_EQ_INDEX.get());
564        equalityIndexArgument.setArgumentGroupName(
565             INFO_MEM_DS_TOOL_GROUP_DATA.get());
566        equalityIndexArgument.addLongIdentifier("equality-index");
567        parser.addArgument(equalityIndexArgument);
568    
569        maxChangeLogEntriesArgument = new IntegerArgument('c',
570             "maxChangeLogEntries", false, 1,
571             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_COUNT.get(),
572             INFO_MEM_DS_TOOL_ARG_DESC_MAX_CHANGELOG_ENTRIES.get(), 0,
573             Integer.MAX_VALUE, 0);
574        maxChangeLogEntriesArgument.setArgumentGroupName(
575             INFO_MEM_DS_TOOL_GROUP_DATA.get());
576        maxChangeLogEntriesArgument.addLongIdentifier("max-changelog-entries");
577        maxChangeLogEntriesArgument.addLongIdentifier("max-change-log-entries");
578        parser.addArgument(maxChangeLogEntriesArgument);
579    
580        vendorNameArgument = new StringArgument(null, "vendorName", false, 1,
581             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_VALUE.get(),
582             INFO_MEM_DS_TOOL_ARG_DESC_VENDOR_NAME.get());
583        vendorNameArgument.setArgumentGroupName(INFO_MEM_DS_TOOL_GROUP_DATA.get());
584        vendorNameArgument.addLongIdentifier("vendor-name");
585        parser.addArgument(vendorNameArgument);
586    
587        vendorVersionArgument = new StringArgument(null, "vendorVersion", false, 1,
588             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_VALUE.get(),
589             INFO_MEM_DS_TOOL_ARG_DESC_VENDOR_VERSION.get());
590        vendorVersionArgument.setArgumentGroupName(
591             INFO_MEM_DS_TOOL_GROUP_DATA.get());
592        vendorVersionArgument.addLongIdentifier("vendor-version");
593        parser.addArgument(vendorVersionArgument);
594    
595        accessLogToStandardOutArgument = new BooleanArgument('A',
596             "accessLogToStandardOut",
597             INFO_MEM_DS_TOOL_ARG_DESC_ACCESS_LOG_TO_STDOUT.get());
598        accessLogToStandardOutArgument.setArgumentGroupName(
599             INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
600        accessLogToStandardOutArgument.addLongIdentifier(
601             "access-log-to-standard-out");
602        parser.addArgument(accessLogToStandardOutArgument);
603    
604        accessLogFileArgument = new FileArgument('a', "accessLogFile", false, 1,
605             INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
606             INFO_MEM_DS_TOOL_ARG_DESC_ACCESS_LOG_FILE.get(), false, true, true,
607             false);
608        accessLogFileArgument.setArgumentGroupName(
609             INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
610        accessLogFileArgument.addLongIdentifier("access-log-format");
611        parser.addArgument(accessLogFileArgument);
612    
613        ldapDebugLogToStandardOutArgument = new BooleanArgument(null,
614             "ldapDebugLogToStandardOut",
615             INFO_MEM_DS_TOOL_ARG_DESC_LDAP_DEBUG_LOG_TO_STDOUT.get());
616        ldapDebugLogToStandardOutArgument.setArgumentGroupName(
617             INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
618        ldapDebugLogToStandardOutArgument.addLongIdentifier(
619             "ldap-debug-log-to-standard-out");
620        parser.addArgument(ldapDebugLogToStandardOutArgument);
621    
622        ldapDebugLogFileArgument = new FileArgument('d', "ldapDebugLogFile", false,
623             1, INFO_MEM_DS_TOOL_ARG_PLACEHOLDER_PATH.get(),
624             INFO_MEM_DS_TOOL_ARG_DESC_LDAP_DEBUG_LOG_FILE.get(), false, true, true,
625             false);
626        ldapDebugLogFileArgument.setArgumentGroupName(
627             INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
628        ldapDebugLogFileArgument.addLongIdentifier("ldap-debug-log-file");
629        parser.addArgument(ldapDebugLogFileArgument);
630    
631        codeLogFile = new FileArgument('C', "codeLogFile", false, 1, "{path}",
632             INFO_MEM_DS_TOOL_ARG_DESC_CODE_LOG_FILE.get(), false, true, true,
633             false);
634        codeLogFile.setArgumentGroupName(INFO_MEM_DS_TOOL_GROUP_LOGGING.get());
635        codeLogFile.addLongIdentifier("code-log-file");
636        parser.addArgument(codeLogFile);
637    
638        parser.addExclusiveArgumentSet(useDefaultSchemaArgument,
639             useSchemaFileArgument);
640        parser.addExclusiveArgumentSet(useSSLArgument, useStartTLSArgument);
641    
642        parser.addExclusiveArgumentSet(accessLogToStandardOutArgument,
643             accessLogFileArgument);
644        parser.addExclusiveArgumentSet(ldapDebugLogToStandardOutArgument,
645             ldapDebugLogFileArgument);
646    
647        parser.addDependentArgumentSet(additionalBindDNArgument,
648             additionalBindPasswordArgument);
649        parser.addDependentArgumentSet(additionalBindPasswordArgument,
650             additionalBindDNArgument);
651    
652        parser.addDependentArgumentSet(useSSLArgument, keyStorePathArgument);
653        parser.addDependentArgumentSet(useSSLArgument, keyStorePasswordArgument);
654        parser.addDependentArgumentSet(useStartTLSArgument, keyStorePathArgument);
655        parser.addDependentArgumentSet(useStartTLSArgument,
656             keyStorePasswordArgument);
657        parser.addDependentArgumentSet(keyStorePathArgument, useSSLArgument,
658             useStartTLSArgument);
659        parser.addDependentArgumentSet(keyStorePasswordArgument, useSSLArgument,
660             useStartTLSArgument);
661        parser.addDependentArgumentSet(trustStorePathArgument, useSSLArgument,
662             useStartTLSArgument);
663        parser.addDependentArgumentSet(trustStorePasswordArgument,
664             trustStorePathArgument);
665      }
666    
667    
668    
669      /**
670       * {@inheritDoc}
671       */
672      @Override()
673      public boolean supportsInteractiveMode()
674      {
675        return true;
676      }
677    
678    
679    
680      /**
681       * {@inheritDoc}
682       */
683      @Override()
684      public boolean defaultsToInteractiveMode()
685      {
686        return true;
687      }
688    
689    
690    
691      /**
692       * Indicates whether this tool supports the use of a properties file for
693       * specifying default values for arguments that aren't specified on the
694       * command line.
695       *
696       * @return  {@code true} if this tool supports the use of a properties file
697       *          for specifying default values for arguments that aren't specified
698       *          on the command line, or {@code false} if not.
699       */
700      @Override()
701      public boolean supportsPropertiesFile()
702      {
703        return true;
704      }
705    
706    
707    
708      /**
709       * {@inheritDoc}
710       */
711      @Override()
712      public ResultCode doToolProcessing()
713      {
714        // Create a base configuration.
715        final InMemoryDirectoryServerConfig serverConfig;
716        try
717        {
718          serverConfig = getConfig();
719        }
720        catch (final LDAPException le)
721        {
722          Debug.debugException(le);
723          err(ERR_MEM_DS_TOOL_ERROR_INITIALIZING_CONFIG.get(le.getMessage()));
724          return le.getResultCode();
725        }
726    
727    
728        // Create the server instance using the provided configuration, but don't
729        // start it yet.
730        try
731        {
732          directoryServer = new InMemoryDirectoryServer(serverConfig);
733        }
734        catch (final LDAPException le)
735        {
736          Debug.debugException(le);
737          err(ERR_MEM_DS_TOOL_ERROR_CREATING_SERVER_INSTANCE.get(le.getMessage()));
738          return le.getResultCode();
739        }
740    
741    
742        // If an LDIF file was provided, then use it to populate the server.
743        if (ldifFileArgument.isPresent())
744        {
745          final File ldifFile = ldifFileArgument.getValue();
746          try
747          {
748            final int numEntries = directoryServer.importFromLDIF(true,
749                 ldifFile.getAbsolutePath());
750            out(INFO_MEM_DS_TOOL_ADDED_ENTRIES_FROM_LDIF.get(numEntries,
751                 ldifFile.getAbsolutePath()));
752          }
753          catch (final LDAPException le)
754          {
755            Debug.debugException(le);
756            err(ERR_MEM_DS_TOOL_ERROR_POPULATING_SERVER_INSTANCE.get(
757                 ldifFile.getAbsolutePath(), le.getMessage()));
758            return le.getResultCode();
759          }
760        }
761    
762    
763        // Start the server.
764        try
765        {
766          if (! dontStartArgument.isPresent())
767          {
768            directoryServer.startListening();
769            out(INFO_MEM_DS_TOOL_LISTENING.get(directoryServer.getListenPort()));
770          }
771        }
772        catch (final Exception e)
773        {
774          Debug.debugException(e);
775          err(ERR_MEM_DS_TOOL_ERROR_STARTING_SERVER.get(
776               StaticUtils.getExceptionMessage(e)));
777          return ResultCode.LOCAL_ERROR;
778        }
779    
780        return ResultCode.SUCCESS;
781      }
782    
783    
784    
785      /**
786       * Creates a server configuration based on information provided with
787       * command line arguments.
788       *
789       * @return  The configuration that was created.
790       *
791       * @throws  LDAPException  If a problem is encountered while creating the
792       *                         configuration.
793       */
794      private InMemoryDirectoryServerConfig getConfig()
795              throws LDAPException
796      {
797        final List<DN> dnList = baseDNArgument.getValues();
798        final DN[] baseDNs = new DN[dnList.size()];
799        dnList.toArray(baseDNs);
800    
801        final InMemoryDirectoryServerConfig serverConfig =
802             new InMemoryDirectoryServerConfig(baseDNs);
803    
804    
805        // If a listen port was specified, then update the configuration to use it.
806        int listenPort = 0;
807        if (portArgument.isPresent())
808        {
809          listenPort = portArgument.getValue();
810        }
811    
812    
813        // If schema should be used, then get it.
814        if (useDefaultSchemaArgument.isPresent())
815        {
816          serverConfig.setSchema(Schema.getDefaultStandardSchema());
817        }
818        else if (useSchemaFileArgument.isPresent())
819        {
820          final ArrayList<File> schemaFiles = new ArrayList<File>(10);
821          for (final File f : useSchemaFileArgument.getValues())
822          {
823            if (f.exists())
824            {
825              if (f.isFile())
826              {
827                schemaFiles.add(f);
828              }
829              else
830              {
831                for (final File subFile : f.listFiles())
832                {
833                  if (subFile.isFile())
834                  {
835                    schemaFiles.add(subFile);
836                  }
837                }
838              }
839            }
840            else
841            {
842              throw new LDAPException(ResultCode.PARAM_ERROR,
843                   ERR_MEM_DS_TOOL_NO_SUCH_SCHEMA_FILE.get(f.getAbsolutePath()));
844            }
845          }
846    
847          try
848          {
849            serverConfig.setSchema(Schema.getSchema(schemaFiles));
850          }
851          catch (final Exception e)
852          {
853            Debug.debugException(e);
854    
855            final StringBuilder fileList = new StringBuilder();
856            final Iterator<File> fileIterator = schemaFiles.iterator();
857            while (fileIterator.hasNext())
858            {
859              fileList.append(fileIterator.next().getAbsolutePath());
860              if (fileIterator.hasNext())
861              {
862                fileList.append(", ");
863              }
864            }
865    
866            throw new LDAPException(ResultCode.LOCAL_ERROR,
867                 ERR_MEM_DS_TOOL_ERROR_READING_SCHEMA.get(
868                      fileList, StaticUtils.getExceptionMessage(e)),
869                 e);
870          }
871        }
872        else
873        {
874          serverConfig.setSchema(null);
875        }
876    
877    
878        // If an additional bind DN and password are provided, then include them in
879        // the configuration.
880        if (additionalBindDNArgument.isPresent())
881        {
882          serverConfig.addAdditionalBindCredentials(
883               additionalBindDNArgument.getValue().toString(),
884               additionalBindPasswordArgument.getValue());
885        }
886    
887    
888        // If a maximum number of changelog entries was specified, then update the
889        // configuration with that.
890        if (maxChangeLogEntriesArgument.isPresent())
891        {
892          serverConfig.setMaxChangeLogEntries(
893               maxChangeLogEntriesArgument.getValue());
894        }
895    
896    
897        // If an access log file was specified, then create the appropriate log
898        // handler.
899        if (accessLogToStandardOutArgument.isPresent())
900        {
901          final StreamHandler handler = new StreamHandler(System.out,
902               new MinimalLogFormatter(null, false, false, true));
903          handler.setLevel(Level.INFO);
904          serverConfig.setAccessLogHandler(handler);
905        }
906        else if (accessLogFileArgument.isPresent())
907        {
908          final File logFile = accessLogFileArgument.getValue();
909          try
910          {
911            final FileHandler handler =
912                 new FileHandler(logFile.getAbsolutePath(), true);
913            handler.setLevel(Level.INFO);
914            handler.setFormatter(new MinimalLogFormatter(null, false, false,
915                 true));
916            serverConfig.setAccessLogHandler(handler);
917          }
918          catch (final Exception e)
919          {
920            Debug.debugException(e);
921            throw new LDAPException(ResultCode.LOCAL_ERROR,
922                 ERR_MEM_DS_TOOL_ERROR_CREATING_LOG_HANDLER.get(
923                      logFile.getAbsolutePath(),
924                      StaticUtils.getExceptionMessage(e)),
925                 e);
926          }
927        }
928    
929    
930        // If an LDAP debug log file was specified, then create the appropriate log
931        // handler.
932        if (ldapDebugLogToStandardOutArgument.isPresent())
933        {
934          final StreamHandler handler = new StreamHandler(System.out,
935               new MinimalLogFormatter(null, false, false, true));
936          handler.setLevel(Level.INFO);
937          serverConfig.setLDAPDebugLogHandler(handler);
938        }
939        else if (ldapDebugLogFileArgument.isPresent())
940        {
941          final File logFile = ldapDebugLogFileArgument.getValue();
942          try
943          {
944            final FileHandler handler =
945                 new FileHandler(logFile.getAbsolutePath(), true);
946            handler.setLevel(Level.INFO);
947            handler.setFormatter(new MinimalLogFormatter(null, false, false,
948                 true));
949            serverConfig.setLDAPDebugLogHandler(handler);
950          }
951          catch (final Exception e)
952          {
953            Debug.debugException(e);
954            throw new LDAPException(ResultCode.LOCAL_ERROR,
955                 ERR_MEM_DS_TOOL_ERROR_CREATING_LOG_HANDLER.get(
956                      logFile.getAbsolutePath(),
957                      StaticUtils.getExceptionMessage(e)),
958                 e);
959          }
960        }
961    
962    
963        // If a code log file was specified, then update the configuration
964        // accordingly.
965        if (codeLogFile.isPresent())
966        {
967          serverConfig.setCodeLogDetails(codeLogFile.getValue().getAbsolutePath(),
968               true);
969        }
970    
971    
972        // If SSL is to be used, then create the corresponding socket factories.
973        if (useSSLArgument.isPresent() || useStartTLSArgument.isPresent())
974        {
975          try
976          {
977            final KeyManager keyManager = new KeyStoreKeyManager(
978                 keyStorePathArgument.getValue(),
979                 keyStorePasswordArgument.getValue().toCharArray(),
980                 keyStoreTypeArgument.getValue(), null);
981    
982            final TrustManager trustManager;
983            if (trustStorePathArgument.isPresent())
984            {
985              final char[] password;
986              if (trustStorePasswordArgument.isPresent())
987              {
988                password = trustStorePasswordArgument.getValue().toCharArray();
989              }
990              else
991              {
992                password = null;
993              }
994    
995              trustManager = new TrustStoreTrustManager(
996                   trustStorePathArgument.getValue(), password,
997                   trustStoreTypeArgument.getValue(), true);
998            }
999            else
1000            {
1001              trustManager = new TrustAllTrustManager();
1002            }
1003    
1004            final SSLUtil serverSSLUtil = new SSLUtil(keyManager, trustManager);
1005    
1006            if (useSSLArgument.isPresent())
1007            {
1008              final SSLUtil clientSSLUtil = new SSLUtil(new TrustAllTrustManager());
1009              serverConfig.setListenerConfigs(
1010                   InMemoryListenerConfig.createLDAPSConfig("LDAPS", null,
1011                        listenPort, serverSSLUtil.createSSLServerSocketFactory(),
1012                        clientSSLUtil.createSSLSocketFactory()));
1013            }
1014            else
1015            {
1016              serverConfig.setListenerConfigs(
1017                   InMemoryListenerConfig.createLDAPConfig("LDAP+StartTLS", null,
1018                        listenPort, serverSSLUtil.createSSLSocketFactory()));
1019            }
1020          }
1021          catch (final Exception e)
1022          {
1023            Debug.debugException(e);
1024            throw new LDAPException(ResultCode.LOCAL_ERROR,
1025                 ERR_MEM_DS_TOOL_ERROR_INITIALIZING_SSL.get(
1026                      StaticUtils.getExceptionMessage(e)),
1027                 e);
1028          }
1029        }
1030        else
1031        {
1032          serverConfig.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig(
1033               "LDAP", listenPort));
1034        }
1035    
1036    
1037        // If vendor name and/or vendor version values were provided, then configure
1038        // them for use.
1039        if (vendorNameArgument.isPresent())
1040        {
1041          serverConfig.setVendorName(vendorNameArgument.getValue());
1042        }
1043    
1044        if (vendorVersionArgument.isPresent())
1045        {
1046          serverConfig.setVendorVersion(vendorVersionArgument.getValue());
1047        }
1048    
1049    
1050        // If equality indexing is to be performed, then configure it.
1051        if (equalityIndexArgument.isPresent())
1052        {
1053          serverConfig.setEqualityIndexAttributes(
1054               equalityIndexArgument.getValues());
1055        }
1056    
1057        return serverConfig;
1058      }
1059    
1060    
1061    
1062      /**
1063       * {@inheritDoc}
1064       */
1065      @Override()
1066      public LinkedHashMap<String[],String> getExampleUsages()
1067      {
1068        final LinkedHashMap<String[],String> exampleUsages =
1069             new LinkedHashMap<String[],String>(2);
1070    
1071        final String[] example1Args =
1072        {
1073          "--baseDN", "dc=example,dc=com"
1074        };
1075        exampleUsages.put(example1Args, INFO_MEM_DS_TOOL_EXAMPLE_1.get());
1076    
1077        final String[] example2Args =
1078        {
1079          "--baseDN", "dc=example,dc=com",
1080          "--port", "1389",
1081          "--ldifFile", "test.ldif",
1082          "--accessLogFile", "access.log",
1083          "--useDefaultSchema"
1084        };
1085        exampleUsages.put(example2Args, INFO_MEM_DS_TOOL_EXAMPLE_2.get());
1086    
1087        return exampleUsages;
1088      }
1089    
1090    
1091    
1092      /**
1093       * Retrieves the in-memory directory server instance that has been created by
1094       * this tool.  It will only be valid after the {@link #doToolProcessing()}
1095       * method has been called.
1096       *
1097       * @return  The in-memory directory server instance that has been created by
1098       *          this tool, or {@code null} if the directory server instance has
1099       *          not been successfully created.
1100       */
1101      public InMemoryDirectoryServer getDirectoryServer()
1102      {
1103        return directoryServer;
1104      }
1105    
1106    
1107    
1108      /**
1109       * {@inheritDoc}
1110       */
1111      public void connectionCreationFailure(final Socket socket,
1112                                            final Throwable cause)
1113      {
1114        err(ERR_MEM_DS_TOOL_ERROR_ACCEPTING_CONNECTION.get(
1115             StaticUtils.getExceptionMessage(cause)));
1116      }
1117    
1118    
1119    
1120      /**
1121       * {@inheritDoc}
1122       */
1123      public void connectionTerminated(
1124                       final LDAPListenerClientConnection connection,
1125                       final LDAPException cause)
1126      {
1127        err(ERR_MEM_DS_TOOL_CONNECTION_TERMINATED_BY_EXCEPTION.get(
1128             StaticUtils.getExceptionMessage(cause)));
1129      }
1130    }