001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * Copyright 2014-2015 ForgeRock AS. 024 */ 025package org.forgerock.opendj.server.setup.cli; 026 027import static com.forgerock.opendj.cli.Utils.filterExitCode; 028import static com.forgerock.opendj.cli.Utils.LINE_SEPARATOR; 029import static com.forgerock.opendj.cli.Utils.checkJavaVersion; 030import static com.forgerock.opendj.cli.CliMessages.*; 031import static com.forgerock.opendj.cli.CliConstants.*; 032 033import java.io.PrintStream; 034import java.util.ArrayList; 035import java.util.Collection; 036import java.util.HashSet; 037import java.util.LinkedHashSet; 038import java.util.Set; 039 040import org.forgerock.i18n.LocalizableMessage; 041import org.forgerock.i18n.LocalizableMessageBuilder; 042import org.forgerock.i18n.slf4j.LocalizedLogger; 043 044import com.forgerock.opendj.cli.Argument; 045import com.forgerock.opendj.cli.ArgumentException; 046import com.forgerock.opendj.cli.BooleanArgument; 047import com.forgerock.opendj.cli.ClientException; 048import com.forgerock.opendj.cli.CommonArguments; 049import com.forgerock.opendj.cli.ConsoleApplication; 050import com.forgerock.opendj.cli.FileBasedArgument; 051import com.forgerock.opendj.cli.IntegerArgument; 052import com.forgerock.opendj.cli.ReturnCode; 053import com.forgerock.opendj.cli.StringArgument; 054import com.forgerock.opendj.cli.SubCommand; 055import com.forgerock.opendj.cli.SubCommandArgumentParser; 056import com.forgerock.opendj.cli.Utils; 057 058/** 059 * This class implements the new CLI for OpenDJ3 setup. 060 */ 061public final class SetupCli extends ConsoleApplication { 062 063 /** 064 * Setup's logger. 065 */ 066 private static final LocalizedLogger LOG = LocalizedLogger.getLoggerForThisClass(); 067 068 /** 069 * TODO remove that after implementation in config. 070 * 071 * @return The installation path. 072 */ 073 static String getInstallationPath() { 074 return "/home/violette/OpenDJ-3.0.0/"; 075 } 076 077 /** 078 * TODO remove that after implementation in config. 079 * 080 * @return The instance path. 081 */ 082 static String getInstancePath() { 083 return "/home/violette/OpenDJ-3.0.0/"; 084 } 085 086 087 private SubCommandArgumentParser argParser; 088 089 private BooleanArgument cli; 090 private BooleanArgument addBaseEntry; 091 private BooleanArgument skipPortCheck; 092 private BooleanArgument enableWindowsService; 093 private BooleanArgument doNotStart; 094 private BooleanArgument enableStartTLS; 095 private BooleanArgument generateSelfSignedCertificate; 096 private StringArgument hostName; 097 private BooleanArgument usePkcs11; 098 private FileBasedArgument directoryManagerPwdFile; 099 private FileBasedArgument keyStorePasswordFile; 100 private IntegerArgument ldapPort; 101 private IntegerArgument adminConnectorPort; 102 private IntegerArgument ldapsPort; 103 private IntegerArgument jmxPort; 104 private IntegerArgument sampleData; 105 private StringArgument baseDN; 106 private StringArgument importLDIF; 107 private StringArgument rejectedImportFile; 108 private StringArgument skippedImportFile; 109 private StringArgument directoryManagerDN; 110 private StringArgument directoryManagerPwdString; 111 private StringArgument useJavaKeyStore; 112 private StringArgument useJCEKS; 113 private StringArgument usePkcs12; 114 private StringArgument keyStorePassword; 115 private StringArgument certNickname; 116 private IntegerArgument connectTimeout; 117 private BooleanArgument acceptLicense; 118 119 /** Sub-commands. */ 120 private SubCommand createDirectoryServer; 121 private SubCommand createProxy; 122 123 /** Register the global arguments. */ 124 private BooleanArgument noPrompt; 125 private BooleanArgument quietMode; 126 private BooleanArgument verbose; 127 private StringArgument propertiesFile; 128 private BooleanArgument noPropertiesFile; 129 private BooleanArgument showUsage; 130 131 private SetupCli() { 132 // Nothing to do. 133 } 134 135 /** To allow tests. */ 136 SetupCli(PrintStream out, PrintStream err) { 137 super(out, err); 138 } 139 140 /** 141 * The main method for setup tool. 142 * 143 * @param args 144 * The command-line arguments provided to this program. 145 */ 146 public static void main(final String[] args) { 147 final int retCode = new SetupCli().run(args); 148 System.exit(filterExitCode(retCode)); 149 } 150 151 /** Create the command-line argument parser for use with this program. */ 152 int run(final String[] args) { 153 // TODO Activate logger when the instance/installation path will be resolved. 154 // SetupLog.initLogFileHandler(); 155 156 try { 157 checkJavaVersion(); 158 } catch (ClientException e) { 159 errPrintln(e.getMessageObject()); 160 return ReturnCode.JAVA_VERSION_INCOMPATIBLE.get(); 161 } 162 163 try { 164 argParser = new SubCommandArgumentParser("setup", INFO_SETUP_DESCRIPTION.get(), true); 165 initializeArguments(); 166 } catch (ArgumentException e) { 167 final LocalizableMessage message = ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage()); 168 errPrintln(message); 169 return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get(); 170 } 171 172 // Parse the command-line arguments provided to this program. 173 try { 174 argParser.parseArguments(args); 175 176 if (argParser.usageOrVersionDisplayed()) { 177 // If we should just display usage or version information, then print it and exit. 178 return ReturnCode.SUCCESS.get(); 179 } 180 } catch (final ArgumentException e) { 181 final LocalizableMessage message = ERR_ERROR_PARSING_ARGS.get(e.getMessage()); 182 errPrintln(message); 183 return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get(); 184 } 185 186 // Verifying provided informations. 187 try { 188 final LinkedHashSet<LocalizableMessage> errorMessages = new LinkedHashSet<>(); 189 checkServerPassword(errorMessages); 190 checkProvidedPorts(errorMessages); 191 checkImportDataArguments(errorMessages); 192 checkSecurityArguments(errorMessages); 193 if (errorMessages.size() > 0) { 194 throw new ArgumentException(ERR_CANNOT_INITIALIZE_ARGS.get( 195 getMessageFromCollection(errorMessages, LINE_SEPARATOR))); 196 } 197 } catch (final ArgumentException e) { 198 errPrintln(e.getMessageObject()); 199 return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get(); 200 } 201 202 // Starts setup process. 203 try { 204 fillSetupSettings(); 205 runSetupInstallation(); 206 } catch (ClientException ex) { 207 return ex.getReturnCode(); 208 } catch (Exception ex) { 209 // TODO 210 //println(Style.ERROR, LocalizableMessage.raw("...?")); 211 return ReturnCode.ERROR_UNEXPECTED.get(); 212 } 213 return ReturnCode.SUCCESS.get(); 214 } 215 216 /** 217 * Initialize setup's arguments by default. 218 * 219 * @throws ArgumentException 220 * If an exception occurs during the initialization of the arguments. 221 */ 222 private void initializeArguments() throws ArgumentException { 223 // Options. 224 acceptLicense = addGlobal(CommonArguments.getAcceptLicense()); 225 cli = addGlobal(CommonArguments.getCLI()); 226 baseDN = addGlobal(CommonArguments.getBaseDN()); 227 addBaseEntry = addGlobal(CommonArguments.getAddBaseEntry()); 228 importLDIF = addGlobal(CommonArguments.getLDIFFile(INFO_DESCRIPTION_IMPORTLDIF.get())); 229 rejectedImportFile = addGlobal(CommonArguments.getRejectedImportLdif()); 230 skippedImportFile = addGlobal(CommonArguments.getSkippedImportFile()); 231 sampleData = addGlobal(CommonArguments.getSampleData()); 232 ldapPort = addGlobal(CommonArguments.getLDAPPort(DEFAULT_LDAP_PORT)); 233 ldapsPort = addGlobal(CommonArguments.getLDAPSPort(DEFAULT_LDAPS_PORT)); 234 adminConnectorPort = addGlobal(CommonArguments.getAdminLDAPPort(DEFAULT_ADMIN_PORT)); 235 jmxPort = addGlobal(CommonArguments.getJMXPort(DEFAULT_JMX_PORT)); 236 skipPortCheck = addGlobal(CommonArguments.getSkipPortCheck()); 237 directoryManagerDN = addGlobal(CommonArguments.getRootDN()); 238 directoryManagerPwdString = addGlobal(CommonArguments.getRootDNPwd()); 239 directoryManagerPwdFile = addGlobal(CommonArguments.getRootDNPwdFile()); 240 enableWindowsService = addGlobal(CommonArguments.getEnableWindowsService()); 241 doNotStart = addGlobal(CommonArguments.getDoNotStart()); 242 enableStartTLS = addGlobal(CommonArguments.getEnableTLS()); 243 generateSelfSignedCertificate = addGlobal(CommonArguments.getGenerateSelfSigned()); 244 hostName = addGlobal(CommonArguments.getHostName(Utils.getDefaultHostName())); 245 usePkcs11 = addGlobal(CommonArguments.getUsePKCS11Keystore()); 246 useJavaKeyStore = addGlobal(CommonArguments.getUseJavaKeyStore()); 247 useJCEKS = addGlobal(CommonArguments.getUseJCEKS()); 248 usePkcs12 = addGlobal(CommonArguments.getUsePKCS12KeyStore()); 249 keyStorePassword = addGlobal(CommonArguments.getKeyStorePassword()); 250 keyStorePasswordFile = addGlobal(CommonArguments.getKeyStorePasswordFile()); 251 certNickname = addGlobal(CommonArguments.getCertNickName()); 252 connectTimeout = CommonArguments.getConnectTimeOut(); 253 254 // Utility Input Output Options. 255 noPrompt = addGlobal(CommonArguments.getNoPrompt()); 256 quietMode = addGlobal(CommonArguments.getQuiet()); 257 verbose = addGlobal(CommonArguments.getVerbose()); 258 propertiesFile = addGlobal(CommonArguments.getPropertiesFile()); 259 noPropertiesFile = addGlobal(CommonArguments.getNoPropertiesFile()); 260 showUsage = addGlobal(CommonArguments.getShowUsage()); 261 262 //Sub-commands && their arguments 263 final ArrayList<SubCommand> subCommandList = new ArrayList<>(2); 264 createDirectoryServer = new SubCommand(argParser, "create-directory-server", 265 INFO_SETUP_SUBCOMMAND_CREATE_DIRECTORY_SERVER.get()); 266 // TODO to complete. 267 createProxy = new SubCommand(argParser, "create-proxy", 268 INFO_SETUP_SUBCOMMAND_CREATE_PROXY.get()); 269 subCommandList.add(createDirectoryServer); 270 subCommandList.add(createProxy); 271 272 argParser.setUsageGroupArgument(showUsage, subCommandList); 273 274 // Register the global arguments. 275 argParser.addArgument(showUsage); 276 argParser.setUsageArgument(showUsage, getOutputStream()); 277 argParser.addArgument(noPropertiesFile); 278 argParser.setNoPropertiesFileArgument(noPropertiesFile); 279 argParser.addArgument(propertiesFile); 280 argParser.setFilePropertiesArgument(propertiesFile); 281 argParser.addArgument(quietMode); 282 argParser.addArgument(verbose); 283 argParser.addArgument(noPrompt); 284 argParser.addArgument(acceptLicense); 285 } 286 287 private <A extends Argument> A addGlobal(A arg) throws ArgumentException { 288 argParser.addGlobalArgument(arg); 289 return arg; 290 } 291 292 /** {@inheritDoc} */ 293 @Override 294 public boolean isInteractive() { 295 return !noPrompt.isPresent(); 296 } 297 298 /** {@inheritDoc} */ 299 @Override 300 public boolean isQuiet() { 301 return quietMode.isPresent(); 302 } 303 304 /** 305 * Automatically accepts the license if it's present. 306 * 307 * @return {@code true} if license is accepted by default. 308 */ 309 private boolean isAcceptLicense() { 310 return acceptLicense.isPresent(); 311 } 312 313 /** {@inheritDoc} */ 314 @Override 315 public boolean isVerbose() { 316 return verbose.isPresent(); 317 } 318 319 /** 320 * Returns whether the command was launched in CLI mode or not. 321 * 322 * @return <CODE>true</CODE> if the command was launched to use CLI mode and <CODE>false</CODE> otherwise. 323 */ 324 public boolean isCli() { 325 return cli.isPresent(); 326 } 327 328 /** 329 * Returns whether the command was launched to setup proxy or not. 330 * 331 * @return <CODE>true</CODE> if the command was launched to setup a proxy and <CODE>false</CODE> otherwise. 332 */ 333 public boolean isCreateProxy() { 334 return argParser.getSubCommand("create-proxy") != null; 335 } 336 337 /** 338 * Checks that there are no conflicts with the provided ports (like if the user provided the same port for different 339 * protocols). 340 * 341 * @param errorMessages 342 * the list of messages to which we add the error messages describing the problems encountered during the 343 * execution of the checking. 344 */ 345 private void checkProvidedPorts(final Collection<LocalizableMessage> errorMessages) { 346 // Check that the provided ports do not match. 347 try { 348 final Set<Integer> ports = new HashSet<>(); 349 ports.add(ldapPort.getIntValue()); 350 351 checkPortArgument(adminConnectorPort, ports, errorMessages); 352 353 if (jmxPort.isPresent()) { 354 checkPortArgument(jmxPort, ports, errorMessages); 355 } 356 if (ldapsPort.isPresent()) { 357 checkPortArgument(ldapsPort, ports, errorMessages); 358 } 359 } catch (ArgumentException ae) { 360 LOG.error(LocalizableMessage.raw("Unexpected error. " 361 + "Assuming that it is caused by a previous parsing issue: " + ae, ae)); 362 } 363 } 364 365 private void checkPortArgument(IntegerArgument portArg, final Set<Integer> ports, 366 final Collection<LocalizableMessage> errorMessages) throws ArgumentException { 367 if (ports.contains(portArg.getIntValue())) { 368 errorMessages.add(ERR_PORT_ALREADY_SPECIFIED.get(portArg.getIntValue())); 369 } else { 370 ports.add(portArg.getIntValue()); 371 } 372 } 373 374 /** 375 * Checks that there are no conflicts with the import data arguments. 376 * 377 * @param errorMessages 378 * the list of messages to which we add the error messages describing the problems encountered during the 379 * execution of the checking. 380 */ 381 private void checkImportDataArguments(final Collection<LocalizableMessage> errorMessages) { 382 // Make sure that the user didn't provide conflicting arguments. 383 if (addBaseEntry.isPresent()) { 384 if (importLDIF.isPresent()) { 385 errorMessages.add(ERR_TOOL_CONFLICTING_ARGS.get(addBaseEntry.getLongIdentifier(), 386 importLDIF.getLongIdentifier())); 387 } else if (sampleData.isPresent()) { 388 errorMessages.add(ERR_TOOL_CONFLICTING_ARGS.get(addBaseEntry.getLongIdentifier(), 389 sampleData.getLongIdentifier())); 390 } 391 } else if (importLDIF.isPresent() && sampleData.isPresent()) { 392 errorMessages.add(ERR_TOOL_CONFLICTING_ARGS.get(importLDIF.getLongIdentifier(), 393 sampleData.getLongIdentifier())); 394 } 395 396 if (rejectedImportFile.isPresent() && addBaseEntry.isPresent()) { 397 errorMessages.add(ERR_TOOL_CONFLICTING_ARGS.get(addBaseEntry.getLongIdentifier(), 398 rejectedImportFile.getLongIdentifier())); 399 } else if (rejectedImportFile.isPresent() && sampleData.isPresent()) { 400 errorMessages.add(ERR_TOOL_CONFLICTING_ARGS.get(rejectedImportFile.getLongIdentifier(), 401 sampleData.getLongIdentifier())); 402 } 403 404 if (skippedImportFile.isPresent() && addBaseEntry.isPresent()) { 405 errorMessages.add(ERR_TOOL_CONFLICTING_ARGS.get(addBaseEntry.getLongIdentifier(), 406 skippedImportFile.getLongIdentifier())); 407 } else if (skippedImportFile.isPresent() && sampleData.isPresent()) { 408 errorMessages.add(ERR_TOOL_CONFLICTING_ARGS.get(skippedImportFile.getLongIdentifier(), 409 sampleData.getLongIdentifier())); 410 } 411 412 if (noPrompt.isPresent() && !baseDN.isPresent() && baseDN.getDefaultValue() == null) { 413 final Argument[] args = { importLDIF, addBaseEntry, sampleData }; 414 for (final Argument arg : args) { 415 if (arg.isPresent()) { 416 errorMessages.add(ERR_ARGUMENT_NO_BASE_DN_SPECIFIED.get("--" + arg.getLongIdentifier())); 417 } 418 } 419 } 420 } 421 422 /** 423 * Checks that there are no conflicts with the security arguments. If we are in no prompt mode, check that all the 424 * information required has been provided (but not if this information is valid: we do not try to open the keystores 425 * or to check that the LDAPS port is in use). 426 * 427 * @param errorMessages 428 * the list of messages to which we add the error messages describing the problems encountered during the 429 * execution of the checking. 430 */ 431 private void checkSecurityArguments(final Collection<LocalizableMessage> errorMessages) { 432 final boolean certificateRequired = ldapsPort.isPresent() || enableStartTLS.isPresent(); 433 434 int certificateType = 0; 435 if (generateSelfSignedCertificate.isPresent()) { 436 certificateType++; 437 } 438 if (useJavaKeyStore.isPresent()) { 439 certificateType++; 440 } 441 if (useJCEKS.isPresent()) { 442 certificateType++; 443 } 444 if (usePkcs11.isPresent()) { 445 certificateType++; 446 } 447 if (usePkcs12.isPresent()) { 448 certificateType++; 449 } 450 451 if (certificateType > 1) { 452 errorMessages.add(ERR_SEVERAL_CERTIFICATE_TYPE_SPECIFIED.get()); 453 } 454 455 if (certificateRequired && noPrompt.isPresent() && certificateType == 0) { 456 errorMessages.add(ERR_CERTIFICATE_REQUIRED_FOR_SSL_OR_STARTTLS.get()); 457 } 458 459 if (certificateType == 1) { 460 if (!generateSelfSignedCertificate.isPresent()) { 461 // Check that we have only a password. 462 if (keyStorePassword.isPresent() && keyStorePasswordFile.isPresent()) { 463 final LocalizableMessage message = ERR_TWO_CONFLICTING_ARGUMENTS.get( 464 keyStorePassword.getLongIdentifier(), keyStorePasswordFile.getLongIdentifier()); 465 errorMessages.add(message); 466 } 467 468 // Check that we have one password in no prompt mode. 469 if (noPrompt.isPresent() && !keyStorePassword.isPresent() && !keyStorePasswordFile.isPresent()) { 470 final LocalizableMessage message = ERR_NO_KEYSTORE_PASSWORD.get( 471 keyStorePassword.getLongIdentifier(), keyStorePasswordFile.getLongIdentifier()); 472 errorMessages.add(message); 473 } 474 } 475 if (noPrompt.isPresent() && !ldapsPort.isPresent() && !enableStartTLS.isPresent()) { 476 final LocalizableMessage message = ERR_SSL_OR_STARTTLS_REQUIRED.get(ldapsPort.getLongIdentifier(), 477 enableStartTLS.getLongIdentifier()); 478 errorMessages.add(message); 479 } 480 } 481 } 482 483 /** 484 * Checks that there are no conflicts with the directory manager passwords. If we are in no prompt mode, check that 485 * the password was provided. 486 * 487 * @param errorMessages 488 * the list of messages to which we add the error messages describing the problems encountered during the 489 * execution of the checking. 490 */ 491 private void checkServerPassword(Collection<LocalizableMessage> errorMessages) { 492 if (directoryManagerPwdString.isPresent() && directoryManagerPwdFile.isPresent()) { 493 errorMessages.add(ERR_TWO_CONFLICTING_ARGUMENTS.get( 494 directoryManagerPwdString.getLongIdentifier(), directoryManagerPwdFile.getLongIdentifier())); 495 } 496 497 if (noPrompt.isPresent() && !directoryManagerPwdString.isPresent() 498 && !directoryManagerPwdFile.isPresent()) { 499 errorMessages.add(ERR_NO_ROOT_PASSWORD.get(directoryManagerPwdString.getLongIdentifier(), 500 directoryManagerPwdFile.getLongIdentifier())); 501 } 502 } 503 504 505 /** 506 * This is a helper method that gets a LocalizableMessage representation of the elements in the Collection of 507 * Messages. The LocalizableMessage will display the different elements separated by the separator String. 508 * TODO move this function. 509 * @param col 510 * the collection containing the messages. 511 * @param separator 512 * the separator String to be used. 513 * @return the message representation for the collection; LocalizableMessage.EMPTY if null. 514 */ 515 private static LocalizableMessage getMessageFromCollection(final Collection<LocalizableMessage> col, 516 final String separator) { 517 if (col == null || col.isEmpty()) { 518 return LocalizableMessage.EMPTY; 519 } else { 520 LocalizableMessageBuilder mb = null; 521 for (final LocalizableMessage m : col) { 522 if (mb == null) { 523 mb = new LocalizableMessageBuilder(m); 524 } else { 525 mb.append(separator).append(m); 526 } 527 } 528 return mb.toMessage(); 529 } 530 } 531 532 /** 533 * Fills the setup components according to the arguments provided by the user. 534 * @throws ArgumentException 535 */ 536 private void fillSetupSettings() throws ArgumentException { 537 // TODO ... 538 } 539 540 /** 541 * Launches the setup process. 542 * @throws ClientException 543 */ 544 private void runSetupInstallation() throws ClientException { 545 // TODO move that function to another class. 546 } 547}