001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.util.cli; 018 019import static org.opends.messages.ToolMessages.*; 020 021import static com.forgerock.opendj.cli.Utils.*; 022 023import java.io.PrintStream; 024import java.util.LinkedList; 025import java.util.Set; 026import java.util.concurrent.atomic.AtomicInteger; 027 028import javax.net.ssl.SSLException; 029 030import org.forgerock.i18n.LocalizableMessage; 031import org.opends.server.admin.client.cli.SecureConnectionCliArgs; 032import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler; 033import org.opends.server.tools.LDAPConnection; 034import org.opends.server.tools.LDAPConnectionException; 035import org.opends.server.tools.LDAPConnectionOptions; 036import org.opends.server.tools.SSLConnectionException; 037import org.opends.server.tools.SSLConnectionFactory; 038import org.opends.server.types.OpenDsException; 039 040import com.forgerock.opendj.cli.Argument; 041import com.forgerock.opendj.cli.ArgumentException; 042import com.forgerock.opendj.cli.ArgumentGroup; 043import com.forgerock.opendj.cli.ArgumentParser; 044import com.forgerock.opendj.cli.ClientException; 045import com.forgerock.opendj.cli.ConsoleApplication; 046import com.forgerock.opendj.cli.FileBasedArgument; 047import com.forgerock.opendj.cli.StringArgument; 048 049/** 050 * Creates an argument parser pre-populated with arguments for specifying 051 * information for opening and LDAPConnection an LDAP connection. 052 */ 053public class LDAPConnectionArgumentParser extends ArgumentParser 054{ 055 056 private SecureConnectionCliArgs args; 057 058 /** 059 * Creates a new instance of this argument parser with no arguments. Unnamed 060 * trailing arguments will not be allowed. 061 * 062 * @param mainClassName 063 * The fully-qualified name of the Java class that should be invoked 064 * to launch the program with which this argument parser is 065 * associated. 066 * @param toolDescription 067 * A human-readable description for the tool, which will be included 068 * when displaying usage information. 069 * @param longArgumentsCaseSensitive 070 * Indicates whether long arguments should 071 * @param argumentGroup 072 * Group to which LDAP arguments will be added to the parser. May be 073 * null to indicate that arguments should be added to the default 074 * group 075 * @param alwaysSSL 076 * If true, always use the SSL connection type. In this case, the 077 * arguments useSSL and startTLS are not present. 078 */ 079 public LDAPConnectionArgumentParser(String mainClassName, LocalizableMessage toolDescription, 080 boolean longArgumentsCaseSensitive, ArgumentGroup argumentGroup, boolean alwaysSSL) 081 { 082 super(mainClassName, toolDescription, longArgumentsCaseSensitive); 083 addLdapConnectionArguments(argumentGroup, alwaysSSL); 084 setVersionHandler(new DirectoryServerVersionHandler()); 085 } 086 087 /** 088 * Indicates whether or not the user has indicated that they would like to 089 * perform a remote operation based on the arguments. 090 * 091 * @return true if the user wants to perform a remote operation; false 092 * otherwise 093 */ 094 public boolean connectionArgumentsPresent() 095 { 096 return args != null && args.argumentsPresent(); 097 } 098 099 /** 100 * Creates a new LDAPConnection and invokes a connect operation using 101 * information provided in the parsed set of arguments that were provided by 102 * the user. 103 * 104 * @param out 105 * stream to write messages 106 * @param err 107 * stream to write error messages 108 * @return LDAPConnection created by this class from parsed arguments 109 * @throws LDAPConnectionException 110 * if there was a problem connecting to the server indicated by the 111 * input arguments 112 * @throws ArgumentException 113 * if there was a problem processing the input arguments 114 */ 115 public LDAPConnection connect(PrintStream out, PrintStream err) throws LDAPConnectionException, ArgumentException 116 { 117 return connect(this.args, out, err); 118 } 119 120 /** 121 * Creates a new LDAPConnection and invokes a connect operation using 122 * information provided in the parsed set of arguments that were provided by 123 * the user. 124 * 125 * @param args 126 * with which to connect 127 * @param out 128 * stream to write messages 129 * @param err 130 * stream to write error messages 131 * @return LDAPConnection created by this class from parsed arguments 132 * @throws LDAPConnectionException 133 * if there was a problem connecting to the server indicated by the 134 * input arguments 135 * @throws ArgumentException 136 * if there was a problem processing the input arguments 137 */ 138 private LDAPConnection connect(SecureConnectionCliArgs args, PrintStream out, PrintStream err) 139 throws LDAPConnectionException, ArgumentException 140 { 141 throwIfArgumentsConflict(args.getBindPasswordArg(), args.getBindPasswordFileArg()); 142 throwIfArgumentsConflict(args.getKeyStorePasswordArg(), args.getKeyStorePasswordFileArg()); 143 throwIfArgumentsConflict(args.getTrustStorePasswordArg(), args.getTrustStorePasswordFileArg()); 144 throwIfArgumentsConflict(args.getUseSSLArg(), args.getUseStartTLSArg()); 145 146 // Create the LDAP connection options object, which will be used to 147 // customize the way that we connect to the server and specify a set of 148 // basic defaults. 149 LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions(); 150 connectionOptions.setVersionNumber(3); 151 152 // See if we should use SSL or StartTLS when establishing the connection. 153 // If so, then make sure only one of them was specified. 154 if (args.getUseSSLArg().isPresent()) 155 { 156 connectionOptions.setUseSSL(true); 157 } 158 else if (args.getUseStartTLSArg().isPresent()) 159 { 160 connectionOptions.setStartTLS(true); 161 } 162 163 // If we should blindly trust any certificate, then install the appropriate 164 // SSL connection factory. 165 if (args.getUseSSLArg().isPresent() || args.getUseStartTLSArg().isPresent()) 166 { 167 try 168 { 169 String clientAlias; 170 if (args.getCertNicknameArg().isPresent()) 171 { 172 clientAlias = args.getCertNicknameArg().getValue(); 173 } 174 else 175 { 176 clientAlias = null; 177 } 178 179 SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory(); 180 sslConnectionFactory.init(args.getTrustAllArg().isPresent(), 181 args.getKeyStorePathArg().getValue(), 182 args.getKeyStorePasswordArg().getValue(), 183 clientAlias, 184 args.getTrustStorePathArg().getValue(), 185 args.getTrustStorePasswordArg().getValue()); 186 connectionOptions.setSSLConnectionFactory(sslConnectionFactory); 187 } 188 catch (SSLConnectionException sce) 189 { 190 printWrappedText(err, ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL.get(sce.getMessage())); 191 } 192 } 193 194 // If one or more SASL options were provided, then make sure that one of 195 // them was "mech" and specified a valid SASL mechanism. 196 if (args.getSaslOptionArg().isPresent()) 197 { 198 String mechanism = null; 199 LinkedList<String> options = new LinkedList<>(); 200 201 for (String s : args.getSaslOptionArg().getValues()) 202 { 203 int equalPos = s.indexOf('='); 204 if (equalPos <= 0) 205 { 206 printAndThrowException(err, ERR_LDAP_CONN_CANNOT_PARSE_SASL_OPTION.get(s)); 207 } 208 else 209 { 210 String name = s.substring(0, equalPos); 211 if ("mech".equalsIgnoreCase(name)) 212 { 213 mechanism = s; 214 } 215 else 216 { 217 options.add(s); 218 } 219 } 220 } 221 222 if (mechanism == null) 223 { 224 printAndThrowException(err, ERR_LDAP_CONN_NO_SASL_MECHANISM.get()); 225 } 226 227 connectionOptions.setSASLMechanism(mechanism); 228 for (String option : options) 229 { 230 connectionOptions.addSASLProperty(option); 231 } 232 } 233 234 int timeout = args.getConnectTimeoutArg().getIntValue(); 235 236 final String passwordValue = getPasswordValue( 237 args.getBindPasswordArg(), args.getBindPasswordFileArg(), args.getBindDnArg(), out, err); 238 return connect( 239 args.getHostNameArg().getValue(), 240 args.getPortArg().getIntValue(), 241 args.getBindDnArg().getValue(), 242 passwordValue, 243 connectionOptions, timeout, out, err); 244 } 245 246 private void printAndThrowException(PrintStream err, LocalizableMessage message) throws ArgumentException 247 { 248 printWrappedText(err, message); 249 throw new ArgumentException(message); 250 } 251 252 /** 253 * Creates a connection using a console interaction that will be used to 254 * potentially interact with the user to prompt for necessary information for 255 * establishing the connection. 256 * 257 * @param ui 258 * user interaction for prompting the user 259 * @param out 260 * stream to write messages 261 * @param err 262 * stream to write error messages 263 * @return LDAPConnection created by this class from parsed arguments 264 * @throws LDAPConnectionException 265 * if there was a problem connecting to the server 266 * @throws ArgumentException 267 * if there was a problem indicated by the input arguments 268 */ 269 public LDAPConnection connect(LDAPConnectionConsoleInteraction ui, PrintStream out, PrintStream err) 270 throws LDAPConnectionException, ArgumentException 271 { 272 try 273 { 274 ui.run(); 275 LDAPConnectionOptions options = new LDAPConnectionOptions(); 276 options.setVersionNumber(3); 277 return connect(ui.getHostName(), ui.getPortNumber(), ui.getBindDN(), 278 ui.getBindPassword(), ui.populateLDAPOptions(options), ui.getConnectTimeout(), out, err); 279 } 280 catch (OpenDsException e) 281 { 282 err.println(isSSLException(e) ? 283 ERR_TASKINFO_LDAP_EXCEPTION_SSL.get(ui.getHostName(), ui.getPortNumber()) : e.getMessageObject()); 284 return null; 285 } 286 } 287 288 private boolean isSSLException(Exception e) 289 { 290 return e.getCause() != null 291 && e.getCause().getCause() != null 292 && e.getCause().getCause() instanceof SSLException; 293 } 294 295 /** 296 * Creates a connection from information provided. 297 * 298 * @param host 299 * of the server 300 * @param port 301 * of the server 302 * @param bindDN 303 * with which to connect 304 * @param bindPw 305 * with which to connect 306 * @param options 307 * with which to connect 308 * @param out 309 * stream to write messages 310 * @param err 311 * stream to write error messages 312 * @return LDAPConnection created by this class from parsed arguments 313 * @throws LDAPConnectionException 314 * if there was a problem connecting to the server indicated by the 315 * input arguments 316 */ 317 public LDAPConnection connect(String host, int port, String bindDN, String bindPw, LDAPConnectionOptions options, 318 PrintStream out, PrintStream err) throws LDAPConnectionException 319 { 320 return connect(host, port, bindDN, bindPw, options, 0, out, err); 321 } 322 323 /** 324 * Creates a connection from information provided. 325 * 326 * @param host 327 * of the server 328 * @param port 329 * of the server 330 * @param bindDN 331 * with which to connect 332 * @param bindPw 333 * with which to connect 334 * @param options 335 * with which to connect 336 * @param timeout 337 * the timeout to establish the connection in milliseconds. Use 338 * {@code 0} to express no timeout 339 * @param out 340 * stream to write messages 341 * @param err 342 * stream to write error messages 343 * @return LDAPConnection created by this class from parsed arguments 344 * @throws LDAPConnectionException 345 * if there was a problem connecting to the server indicated by the 346 * input arguments 347 */ 348 public LDAPConnection connect(String host, int port, String bindDN, String bindPw, LDAPConnectionOptions options, 349 int timeout, PrintStream out, PrintStream err) throws LDAPConnectionException 350 { 351 // Attempt to connect and authenticate to the Directory Server. 352 AtomicInteger nextMessageID = new AtomicInteger(1); 353 LDAPConnection connection = new LDAPConnection(host, port, options, out, err); 354 connection.connectToHost(bindDN, bindPw, nextMessageID, timeout); 355 return connection; 356 } 357 358 /** 359 * Gets the arguments associated with this parser. 360 * 361 * @return arguments for this parser. 362 */ 363 public SecureConnectionCliArgs getArguments() 364 { 365 return args; 366 } 367 368 /** 369 * Commodity method that retrieves the password value analyzing the contents 370 * of a string argument and of a file based argument. It assumes that the 371 * arguments have already been parsed and validated. If the string is a dash, 372 * or no password is available, it will prompt for it on the command line. 373 * 374 * @param bindPwdArg 375 * the string argument for the password. 376 * @param bindPwdFileArg 377 * the file based argument for the password. 378 * @param bindDnArg 379 * the string argument for the bindDN. 380 * @param out 381 * stream to write message. 382 * @param err 383 * stream to write error message. 384 * @return the password value. 385 */ 386 public static String getPasswordValue(StringArgument bindPwdArg, FileBasedArgument bindPwdFileArg, 387 StringArgument bindDnArg, PrintStream out, PrintStream err) 388 { 389 try 390 { 391 return getPasswordValue(bindPwdArg, bindPwdFileArg, bindDnArg.getValue(), out, err); 392 } 393 catch (Exception ex) 394 { 395 printWrappedText(err, ex.getMessage()); 396 return null; 397 } 398 } 399 400 /** 401 * Commodity method that retrieves the password value analyzing the contents 402 * of a string argument and of a file based argument. It assumes that the 403 * arguments have already been parsed and validated. If the string is a dash, 404 * or no password is available, it will prompt for it on the command line. 405 * 406 * @param bindPassword 407 * the string argument for the password. 408 * @param bindPasswordFile 409 * the file based argument for the password. 410 * @param bindDNValue 411 * the string value for the bindDN. 412 * @param out 413 * stream to write message. 414 * @param err 415 * stream to write error message. 416 * @return the password value. 417 * @throws ClientException 418 * if the password cannot be read 419 */ 420 public static String getPasswordValue(StringArgument bindPassword, FileBasedArgument bindPasswordFile, 421 String bindDNValue, PrintStream out, PrintStream err) throws ClientException 422 { 423 String bindPasswordValue = bindPassword.getValue(); 424 if ("-".equals(bindPasswordValue) 425 || (!bindPasswordFile.isPresent() && bindDNValue != null && bindPasswordValue == null)) 426 { 427 // read the password from the stdin. 428 out.print(INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDNValue)); 429 char[] pwChars = ConsoleApplication.readPassword(); 430 // As per rfc 4513(section-5.1.2) a client should avoid sending 431 // an empty password to the server. 432 while (pwChars.length == 0) 433 { 434 printWrappedText(err, INFO_LDAPAUTH_NON_EMPTY_PASSWORD.get()); 435 out.print(INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDNValue)); 436 pwChars = ConsoleApplication.readPassword(); 437 } 438 return new String(pwChars); 439 } 440 else if (bindPasswordValue == null) 441 { 442 // Read from file if it exists. 443 return bindPasswordFile.getValue(); 444 } 445 return bindPasswordValue; 446 } 447 448 private void addLdapConnectionArguments(ArgumentGroup argGroup, boolean alwaysSSL) 449 { 450 args = new SecureConnectionCliArgs(alwaysSSL); 451 try 452 { 453 Set<Argument> argSet = args.createGlobalArguments(); 454 for (Argument arg : argSet) 455 { 456 addArgument(arg, argGroup); 457 } 458 } 459 catch (ArgumentException ae) 460 { 461 ae.printStackTrace(); // Should never happen 462 } 463 } 464}