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 }