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 * 024 * Copyright 2007-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.admin.client.cli; 028 029import static com.forgerock.opendj.cli.ArgumentConstants.*; 030import static com.forgerock.opendj.cli.CliMessages.*; 031import static com.forgerock.opendj.cli.ReturnCode.*; 032import static com.forgerock.opendj.cli.Utils.*; 033 034import java.io.File; 035import java.io.FileInputStream; 036import java.io.IOException; 037import java.net.InetAddress; 038import java.security.KeyStore; 039import java.security.KeyStoreException; 040import java.security.NoSuchAlgorithmException; 041import java.security.cert.CertificateException; 042import java.util.ArrayList; 043import java.util.LinkedHashSet; 044import java.util.List; 045import java.util.Set; 046 047import org.forgerock.i18n.LocalizableMessage; 048import org.forgerock.i18n.LocalizableMessageBuilder; 049import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1; 050import org.forgerock.i18n.slf4j.LocalizedLogger; 051import org.forgerock.opendj.config.server.ConfigException; 052import org.opends.admin.ads.util.ApplicationTrustManager; 053import org.opends.server.admin.AdministrationConnector; 054import org.opends.server.admin.server.ServerManagementContext; 055import org.opends.server.admin.std.server.AdministrationConnectorCfg; 056import org.opends.server.admin.std.server.FileBasedTrustManagerProviderCfg; 057import org.opends.server.admin.std.server.RootCfg; 058import org.opends.server.admin.std.server.TrustManagerProviderCfg; 059import org.opends.server.core.DirectoryServer; 060 061import com.forgerock.opendj.cli.Argument; 062import com.forgerock.opendj.cli.ArgumentException; 063import com.forgerock.opendj.cli.BooleanArgument; 064import com.forgerock.opendj.cli.CliConstants; 065import com.forgerock.opendj.cli.CommonArguments; 066import com.forgerock.opendj.cli.FileBasedArgument; 067import com.forgerock.opendj.cli.IntegerArgument; 068import com.forgerock.opendj.cli.StringArgument; 069 070/** 071 * This is a commodity class that can be used to check the arguments required to 072 * establish a secure connection in the command line. It can be used to generate 073 * an ApplicationTrustManager object based on the options provided by the user 074 * in the command line. 075 */ 076public final class SecureConnectionCliArgs 077{ 078 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 079 080 /** The 'hostName' global argument. */ 081 public StringArgument hostNameArg; 082 /** The 'port' global argument. */ 083 public IntegerArgument portArg; 084 /** The 'bindDN' global argument. */ 085 public StringArgument bindDnArg; 086 /** The 'adminUID' global argument. */ 087 public StringArgument adminUidArg; 088 /** The 'bindPasswordFile' global argument. */ 089 public FileBasedArgument bindPasswordFileArg; 090 /** The 'bindPassword' global argument. */ 091 public StringArgument bindPasswordArg; 092 /** The 'trustAllArg' global argument. */ 093 public BooleanArgument trustAllArg; 094 /** The 'trustStore' global argument. */ 095 public StringArgument trustStorePathArg; 096 /** The 'trustStorePassword' global argument. */ 097 public StringArgument trustStorePasswordArg; 098 /** The 'trustStorePasswordFile' global argument. */ 099 public FileBasedArgument trustStorePasswordFileArg; 100 /** The 'keyStore' global argument. */ 101 public StringArgument keyStorePathArg; 102 /** The 'keyStorePassword' global argument. */ 103 public StringArgument keyStorePasswordArg; 104 /** The 'keyStorePasswordFile' global argument. */ 105 public FileBasedArgument keyStorePasswordFileArg; 106 /** The 'certNicknameArg' global argument. */ 107 public StringArgument certNicknameArg; 108 /** The 'useSSLArg' global argument. */ 109 public BooleanArgument useSSLArg; 110 /** The 'useStartTLSArg' global argument. */ 111 public BooleanArgument useStartTLSArg; 112 /** Argument indicating a SASL option. */ 113 public StringArgument saslOptionArg; 114 /** Argument to specify the connection timeout. */ 115 public IntegerArgument connectTimeoutArg; 116 117 /** Private container for global arguments. */ 118 private Set<Argument> argList; 119 120 /** The trust manager. */ 121 private ApplicationTrustManager trustManager; 122 123 private boolean configurationInitialized; 124 125 /** Defines if the CLI always use the SSL connection type. */ 126 private boolean alwaysSSL; 127 128 /** 129 * Creates a new instance of secure arguments. 130 * 131 * @param alwaysSSL 132 * If true, always use the SSL connection type. In this case, the 133 * arguments useSSL and startTLS are not present. 134 */ 135 public SecureConnectionCliArgs(boolean alwaysSSL) 136 { 137 this.alwaysSSL = alwaysSSL; 138 } 139 140 /** 141 * Indicates whether or not any of the arguments are present. 142 * 143 * @return boolean where true indicates that at least one of the arguments is 144 * present 145 */ 146 public boolean argumentsPresent() 147 { 148 if (argList != null) 149 { 150 for (Argument arg : argList) 151 { 152 if (arg.isPresent()) 153 { 154 return true; 155 } 156 } 157 } 158 return false; 159 } 160 161 /** 162 * Get the admin UID which has to be used for the command. 163 * 164 * @return The admin UID specified by the command line argument, or the 165 * default value, if not specified. 166 */ 167 public String getAdministratorUID() 168 { 169 if (adminUidArg.isPresent()) 170 { 171 return adminUidArg.getValue(); 172 } 173 return adminUidArg.getDefaultValue(); 174 } 175 176 /** 177 * Tells whether this parser uses the Administrator UID (instead of the bind 178 * DN) or not. 179 * 180 * @return {@code true} if this parser uses the Administrator UID and 181 * {@code false} otherwise. 182 */ 183 public boolean useAdminUID() 184 { 185 return !adminUidArg.isHidden(); 186 } 187 188 /** 189 * Get the bindDN which has to be used for the command. 190 * 191 * @return The bindDN specified by the command line argument, or the default 192 * value, if not specified. 193 */ 194 public String getBindDN() 195 { 196 if (bindDnArg.isPresent()) 197 { 198 return bindDnArg.getValue(); 199 } 200 return bindDnArg.getDefaultValue(); 201 } 202 203 /** 204 * Initialize Global option. 205 * 206 * @throws ArgumentException 207 * If there is a problem with any of the parameters used to create 208 * this argument. 209 * @return a ArrayList with the options created. 210 */ 211 public Set<Argument> createGlobalArguments() throws ArgumentException 212 { 213 argList = new LinkedHashSet<>(); 214 215 useSSLArg = CommonArguments.getUseSSL(); 216 if (!alwaysSSL) 217 { 218 argList.add(useSSLArg); 219 } 220 else 221 { 222 // simulate that the useSSL arg has been given in the CLI 223 useSSLArg.setPresent(true); 224 } 225 226 useStartTLSArg = CommonArguments.getStartTLS(); 227 if (!alwaysSSL) 228 { 229 argList.add(useStartTLSArg); 230 } 231 232 String defaultHostName; 233 try 234 { 235 defaultHostName = InetAddress.getLocalHost().getHostName(); 236 } 237 catch (Exception e) 238 { 239 defaultHostName = "Unknown (" + e + ")"; 240 } 241 hostNameArg = CommonArguments.getHostName(defaultHostName); 242 argList.add(hostNameArg); 243 244 portArg = CommonArguments.getPort(AdministrationConnector.DEFAULT_ADMINISTRATION_CONNECTOR_PORT, 245 alwaysSSL ? INFO_DESCRIPTION_ADMIN_PORT.get() : INFO_DESCRIPTION_PORT.get()); 246 argList.add(portArg); 247 248 bindDnArg = CommonArguments.getBindDN(CliConstants.DEFAULT_ROOT_USER_DN); 249 argList.add(bindDnArg); 250 251 // It is up to the classes that required admin UID to make this argument 252 // visible and add it. 253 adminUidArg = new StringArgument("adminUID", 'I', OPTION_LONG_ADMIN_UID, false, false, true, 254 INFO_ADMINUID_PLACEHOLDER.get(), CliConstants.GLOBAL_ADMIN_UID, 255 null, INFO_DESCRIPTION_ADMIN_UID.get()); 256 adminUidArg.setPropertyName(OPTION_LONG_ADMIN_UID); 257 adminUidArg.setHidden(true); 258 259 bindPasswordArg = CommonArguments.getBindPassword(); 260 argList.add(bindPasswordArg); 261 262 bindPasswordFileArg = CommonArguments.getBindPasswordFile(); 263 argList.add(bindPasswordFileArg); 264 265 saslOptionArg = CommonArguments.getSASL(); 266 argList.add(saslOptionArg); 267 268 trustAllArg = CommonArguments.getTrustAll(); 269 argList.add(trustAllArg); 270 271 trustStorePathArg = CommonArguments.getTrustStorePath(); 272 argList.add(trustStorePathArg); 273 274 trustStorePasswordArg = CommonArguments.getTrustStorePassword(); 275 argList.add(trustStorePasswordArg); 276 277 trustStorePasswordFileArg = CommonArguments.getTrustStorePasswordFile(); 278 argList.add(trustStorePasswordFileArg); 279 280 keyStorePathArg = CommonArguments.getKeyStorePath(); 281 argList.add(keyStorePathArg); 282 283 keyStorePasswordArg = CommonArguments.getKeyStorePassword(); 284 argList.add(keyStorePasswordArg); 285 286 keyStorePasswordFileArg = CommonArguments.getKeyStorePasswordFile(); 287 argList.add(keyStorePasswordFileArg); 288 289 certNicknameArg = CommonArguments.getCertNickName(); 290 argList.add(certNicknameArg); 291 292 connectTimeoutArg = CommonArguments.getConnectTimeOut(); 293 connectTimeoutArg.setHidden(false); 294 argList.add(connectTimeoutArg); 295 296 return argList; 297 } 298 299 /** 300 * Get the host name which has to be used for the command. 301 * 302 * @return The host name specified by the command line argument, or the 303 * default value, if not specified. 304 */ 305 public String getHostName() 306 { 307 if (hostNameArg.isPresent()) 308 { 309 return hostNameArg.getValue(); 310 } 311 return hostNameArg.getDefaultValue(); 312 } 313 314 /** 315 * Get the port which has to be used for the command. 316 * 317 * @return The port specified by the command line argument, or the default 318 * value, if not specified. 319 */ 320 public String getPort() 321 { 322 if (portArg.isPresent()) 323 { 324 return portArg.getValue(); 325 } 326 return portArg.getDefaultValue(); 327 } 328 329 /** 330 * Indication if provided global options are validate. 331 * 332 * @param buf 333 * the LocalizableMessageBuilder to write the error messages. 334 * @return return code. 335 */ 336 public int validateGlobalOptions(LocalizableMessageBuilder buf) 337 { 338 List<LocalizableMessage> errors = new ArrayList<>(); 339 340 addIfArgsAreConflicting(errors, bindPasswordArg, bindPasswordFileArg); 341 342 // Couldn't have at the same time trustAll and trustStore related args 343 addIfArgsAreConflicting(errors, trustAllArg, trustStorePathArg); 344 addIfArgsAreConflicting(errors, trustAllArg, trustStorePasswordArg); 345 addIfArgsAreConflicting(errors, trustAllArg, trustStorePasswordFileArg); 346 347 addIfArgsAreConflicting(errors, trustStorePasswordArg, trustStorePasswordFileArg); 348 349 checkIfPathArgumentIsReadable(errors, trustStorePathArg, ERR_CANNOT_READ_TRUSTSTORE); 350 checkIfPathArgumentIsReadable(errors, keyStorePathArg, ERR_CANNOT_READ_KEYSTORE); 351 352 addIfArgsAreConflicting(errors, useStartTLSArg, useSSLArg); 353 354 if (!errors.isEmpty()) 355 { 356 for (LocalizableMessage error : errors) 357 { 358 if (buf.length() > 0) 359 { 360 buf.append(LINE_SEPARATOR); 361 } 362 buf.append(error); 363 } 364 return CONFLICTING_ARGS.get(); 365 } 366 367 return SUCCESS.get(); 368 } 369 370 private void addIfArgsAreConflicting(List<LocalizableMessage> errors, Argument arg1, Argument arg2) 371 { 372 if (arg1.isPresent() && arg2.isPresent()) 373 { 374 errors.add(ERR_TOOL_CONFLICTING_ARGS.get(arg1.getLongIdentifier(), arg2.getLongIdentifier())); 375 } 376 } 377 378 private void checkIfPathArgumentIsReadable(List<LocalizableMessage> errors, StringArgument pathArg, Arg1<Object> msg) 379 { 380 if (pathArg.isPresent() && !canRead(pathArg.getValue())) 381 { 382 errors.add(msg.get(pathArg.getValue())); 383 } 384 } 385 386 /** 387 * Indicate if the SSL mode is required. 388 * 389 * @return True if SSL mode is required 390 */ 391 public boolean useSSL() 392 { 393 return useSSLArg.isPresent() || alwaysSSL(); 394 } 395 396 /** 397 * Indicate if the startTLS mode is required. 398 * 399 * @return True if startTLS mode is required 400 */ 401 public boolean useStartTLS() 402 { 403 return useStartTLSArg.isPresent(); 404 } 405 406 /** 407 * Indicate if the SSL mode is always used. 408 * 409 * @return True if SSL mode is always used. 410 */ 411 public boolean alwaysSSL() 412 { 413 return alwaysSSL; 414 } 415 416 /** 417 * Handle TrustStore. 418 * 419 * @return The trustStore manager to be used for the command. 420 */ 421 public ApplicationTrustManager getTrustManager() 422 { 423 if (trustManager == null) 424 { 425 KeyStore truststore = null; 426 if (trustAllArg.isPresent()) 427 { 428 // Running a null TrustManager will force createLdapsContext and 429 // createStartTLSContext to use a bindTrustManager. 430 return null; 431 } 432 else if (trustStorePathArg.isPresent()) 433 { 434 try (final FileInputStream fos = new FileInputStream(trustStorePathArg.getValue())) 435 { 436 String trustStorePasswordStringValue = null; 437 if (trustStorePasswordArg.isPresent()) 438 { 439 trustStorePasswordStringValue = trustStorePasswordArg.getValue(); 440 } 441 else if (trustStorePasswordFileArg.isPresent()) 442 { 443 trustStorePasswordStringValue = trustStorePasswordFileArg.getValue(); 444 } 445 446 if (trustStorePasswordStringValue != null) 447 { 448 trustStorePasswordStringValue = System.getProperty("javax.net.ssl.trustStorePassword"); 449 } 450 451 char[] trustStorePasswordValue = null; 452 if (trustStorePasswordStringValue != null) 453 { 454 trustStorePasswordValue = trustStorePasswordStringValue.toCharArray(); 455 } 456 457 truststore = KeyStore.getInstance(KeyStore.getDefaultType()); 458 truststore.load(fos, trustStorePasswordValue); 459 } 460 catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) 461 { 462 // Nothing to do: if this occurs we will systematically refuse the 463 // certificates. Maybe we should avoid this and be strict, but we 464 // are in a best effort mode. 465 logger.warn(LocalizableMessage.raw("Error with the truststore"), e); 466 } 467 } 468 trustManager = new ApplicationTrustManager(truststore); 469 } 470 return trustManager; 471 } 472 473 /** 474 * Returns {@code true} if we can read on the provided path and {@code false} 475 * otherwise. 476 * 477 * @param path 478 * the path. 479 * @return {@code true} if we can read on the provided path and {@code false} 480 * otherwise. 481 */ 482 private boolean canRead(String path) 483 { 484 final File file = new File(path); 485 return file.exists() && file.canRead(); 486 } 487 488 /** 489 * Returns the absolute path of the trust store file that appears on the 490 * config. Returns {@code null} if the trust store is not defined or it does 491 * not exist. 492 * 493 * @return the absolute path of the trust store file that appears on the 494 * config. 495 * @throws ConfigException 496 * if there is an error reading the configuration. 497 */ 498 public String getTruststoreFileFromConfig() throws ConfigException 499 { 500 String truststoreFileAbsolute = null; 501 TrustManagerProviderCfg trustManagerCfg = null; 502 AdministrationConnectorCfg administrationConnectorCfg = null; 503 504 boolean couldInitializeConfig = configurationInitialized; 505 // Initialization for admin framework 506 if (!configurationInitialized) 507 { 508 couldInitializeConfig = initializeConfiguration(); 509 } 510 if (couldInitializeConfig) 511 { 512 // Get the Directory Server configuration handler and use it. 513 RootCfg root = ServerManagementContext.getInstance().getRootConfiguration(); 514 administrationConnectorCfg = root.getAdministrationConnector(); 515 516 String trustManagerStr = administrationConnectorCfg.getTrustManagerProvider(); 517 trustManagerCfg = root.getTrustManagerProvider(trustManagerStr); 518 if (trustManagerCfg instanceof FileBasedTrustManagerProviderCfg) 519 { 520 FileBasedTrustManagerProviderCfg fileBasedTrustManagerCfg = (FileBasedTrustManagerProviderCfg) trustManagerCfg; 521 String truststoreFile = fileBasedTrustManagerCfg.getTrustStoreFile(); 522 // Check the file 523 if (truststoreFile.startsWith(File.separator)) 524 { 525 truststoreFileAbsolute = truststoreFile; 526 } 527 else 528 { 529 truststoreFileAbsolute = DirectoryServer.getInstanceRoot() + File.separator + truststoreFile; 530 } 531 File f = new File(truststoreFileAbsolute); 532 if (!f.exists() || !f.canRead() || f.isDirectory()) 533 { 534 truststoreFileAbsolute = null; 535 } 536 else 537 { 538 // Try to get the canonical path. 539 try 540 { 541 truststoreFileAbsolute = f.getCanonicalPath(); 542 } 543 catch (Throwable t) 544 { 545 // We can ignore this error. 546 } 547 } 548 } 549 } 550 return truststoreFileAbsolute; 551 } 552 553 /** 554 * Returns the admin port from the configuration. 555 * 556 * @return the admin port from the configuration. 557 * @throws ConfigException 558 * if an error occurs reading the configuration. 559 */ 560 public int getAdminPortFromConfig() throws ConfigException 561 { 562 // Initialization for admin framework 563 boolean couldInitializeConfiguration = configurationInitialized; 564 if (!configurationInitialized) 565 { 566 couldInitializeConfiguration = initializeConfiguration(); 567 } 568 if (couldInitializeConfiguration) 569 { 570 RootCfg root = ServerManagementContext.getInstance().getRootConfiguration(); 571 return root.getAdministrationConnector().getListenPort(); 572 } 573 else 574 { 575 return AdministrationConnector.DEFAULT_ADMINISTRATION_CONNECTOR_PORT; 576 } 577 } 578 579 private boolean initializeConfiguration() 580 { 581 // check if the initialization is required 582 try 583 { 584 ServerManagementContext.getInstance().getRootConfiguration().getAdministrationConnector(); 585 } 586 catch (java.lang.Throwable th) 587 { 588 try 589 { 590 DirectoryServer.bootstrapClient(); 591 DirectoryServer.initializeJMX(); 592 DirectoryServer.getInstance().initializeConfiguration(); 593 } 594 catch (Exception ex) 595 { 596 // do nothing 597 return false; 598 } 599 } 600 configurationInitialized = true; 601 return true; 602 } 603 604 /** 605 * Returns the port to be used according to the configuration and the 606 * arguments provided by the user. This method should be called after the 607 * arguments have been parsed. 608 * 609 * @return the port to be used according to the configuration and the 610 * arguments provided by the user. 611 */ 612 public int getPortFromConfig() 613 { 614 int portNumber; 615 if (alwaysSSL()) 616 { 617 portNumber = AdministrationConnector.DEFAULT_ADMINISTRATION_CONNECTOR_PORT; 618 // Try to get the port from the config file 619 try 620 { 621 portNumber = getAdminPortFromConfig(); 622 } 623 catch (ConfigException ex) 624 { 625 // Nothing to do 626 } 627 } 628 else 629 { 630 portNumber = CliConstants.DEFAULT_SSL_PORT; 631 } 632 return portNumber; 633 } 634 635 /** 636 * Updates the default values of the port and the trust store with what is 637 * read in the configuration. 638 * 639 * @throws ConfigException 640 * if there is an error reading the configuration. 641 */ 642 public void initArgumentsWithConfiguration() throws ConfigException 643 { 644 portArg.setDefaultValue(String.valueOf(getPortFromConfig())); 645 646 String truststoreFileAbsolute = getTruststoreFileFromConfig(); 647 if (truststoreFileAbsolute != null) 648 { 649 trustStorePathArg.setDefaultValue(truststoreFileAbsolute); 650 } 651 } 652}