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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 028 package org.opends.server.util.args; 029 030 import org.opends.messages.Message; 031 import static org.opends.messages.ToolMessages.*; 032 import org.opends.server.tools.LDAPConnection; 033 import org.opends.server.tools.LDAPConnectionOptions; 034 import org.opends.server.tools.SSLConnectionFactory; 035 import org.opends.server.tools.SSLConnectionException; 036 import org.opends.server.tools.LDAPConnectionException; 037 import static org.opends.server.util.ServerConstants.MAX_LINE_WIDTH; 038 import static org.opends.server.util.StaticUtils.wrapText; 039 import org.opends.server.util.cli.LDAPConnectionConsoleInteraction; 040 import org.opends.server.admin.client.cli.SecureConnectionCliArgs; 041 import org.opends.server.types.OpenDsException; 042 043 import java.util.LinkedList; 044 import java.util.LinkedHashSet; 045 import java.util.concurrent.atomic.AtomicInteger; 046 import java.io.PrintStream; 047 048 /** 049 * Creates an argument parser pre-populated with arguments for specifying 050 * information for openning and LDAPConnection an LDAP connection. 051 */ 052 public class LDAPConnectionArgumentParser extends ArgumentParser { 053 054 private SecureConnectionCliArgs args; 055 056 /** 057 * Creates a new instance of this argument parser with no arguments. 058 * Unnamed trailing arguments will not be allowed. 059 * 060 * @param mainClassName The fully-qualified name of the Java 061 * class that should be invoked to launch 062 * the program with which this argument 063 * parser is associated. 064 * @param toolDescription A human-readable description for the 065 * tool, which will be included when 066 * displaying usage information. 067 * @param longArgumentsCaseSensitive Indicates whether long arguments should 068 * @param argumentGroup Group to which LDAP arguments will be 069 * added to the parser. May be null to 070 * indicate that arguments should be 071 * added to the default group 072 */ 073 public LDAPConnectionArgumentParser(String mainClassName, 074 Message toolDescription, 075 boolean longArgumentsCaseSensitive, 076 ArgumentGroup argumentGroup) { 077 super(mainClassName, toolDescription, longArgumentsCaseSensitive); 078 addLdapConnectionArguments(argumentGroup); 079 } 080 081 /** 082 * Creates a new instance of this argument parser with no arguments that may 083 * or may not be allowed to have unnamed trailing arguments. 084 * 085 * @param mainClassName The fully-qualified name of the Java 086 * class that should be invoked to launch 087 * the program with which this argument 088 * parser is associated. 089 * @param toolDescription A human-readable description for the 090 * tool, which will be included when 091 * displaying usage information. 092 * @param longArgumentsCaseSensitive Indicates whether long arguments should 093 * be treated in a case-sensitive manner. 094 * @param allowsTrailingArguments Indicates whether this parser allows 095 * unnamed trailing arguments to be 096 * provided. 097 * @param minTrailingArguments The minimum number of unnamed trailing 098 * arguments that must be provided. A 099 * value less than or equal to zero 100 * indicates that no minimum will be 101 * enforced. 102 * @param maxTrailingArguments The maximum number of unnamed trailing 103 * arguments that may be provided. A 104 * value less than or equal to zero 105 * indicates that no maximum will be 106 * enforced. 107 * @param trailingArgsDisplayName The display name that should be used 108 * as a placeholder for unnamed trailing 109 * arguments in the generated usage 110 * information. 111 * @param argumentGroup Group to which LDAP arguments will be 112 * added to the parser. May be null to 113 * indicate that arguments should be 114 * added to the default group 115 */ 116 public LDAPConnectionArgumentParser(String mainClassName, 117 Message toolDescription, 118 boolean longArgumentsCaseSensitive, 119 boolean allowsTrailingArguments, 120 int minTrailingArguments, 121 int maxTrailingArguments, 122 String trailingArgsDisplayName, 123 ArgumentGroup argumentGroup) { 124 super(mainClassName, toolDescription, longArgumentsCaseSensitive, 125 allowsTrailingArguments, minTrailingArguments, maxTrailingArguments, 126 trailingArgsDisplayName); 127 addLdapConnectionArguments(argumentGroup); 128 } 129 130 /** 131 * Indicates whether or not the user has indicated that they would like 132 * to perform a remote operation based on the arguments. 133 * 134 * @return true if the user wants to perform a remote operation; 135 * false otherwise 136 */ 137 public boolean connectionArgumentsPresent() { 138 return args != null && args.argumentsPresent(); 139 } 140 141 /** 142 * Creates a new LDAPConnection and invokes a connect operation using 143 * information provided in the parsed set of arguments that were provided 144 * by the user. 145 * 146 * @param out stream to write messages 147 * @param err stream to write messages 148 * @return LDAPConnection created by this class from parsed arguments 149 * @throws LDAPConnectionException if there was a problem connecting 150 * to the server indicated by the input arguments 151 * @throws ArgumentException if there was a problem processing the input 152 * arguments 153 */ 154 public LDAPConnection connect(PrintStream out, PrintStream err) 155 throws LDAPConnectionException, ArgumentException 156 { 157 return connect(this.args, out, err); 158 } 159 160 161 /** 162 * Creates a new LDAPConnection and invokes a connect operation using 163 * information provided in the parsed set of arguments that were provided 164 * by the user. 165 * 166 * @param args with which to connect 167 * @param out stream to write messages 168 * @param err stream to write messages 169 * @return LDAPConnection created by this class from parsed arguments 170 * @throws LDAPConnectionException if there was a problem connecting 171 * to the server indicated by the input arguments 172 * @throws ArgumentException if there was a problem processing the input 173 * arguments 174 */ 175 private LDAPConnection connect(SecureConnectionCliArgs args, 176 PrintStream out, PrintStream err) 177 throws LDAPConnectionException, ArgumentException 178 { 179 // If both a bind password and bind password file were provided, then return 180 // an error. 181 if (args.bindPasswordArg.isPresent() && 182 args.bindPasswordFileArg.isPresent()) 183 { 184 Message message = ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get( 185 args.bindPasswordArg.getLongIdentifier(), 186 args.bindPasswordFileArg.getLongIdentifier()); 187 err.println(wrapText(message, MAX_LINE_WIDTH)); 188 throw new ArgumentException(message); 189 } 190 191 192 // If both a key store password and key store password file were provided, 193 // then return an error. 194 if (args.keyStorePasswordArg.isPresent() && 195 args.keyStorePasswordFileArg.isPresent()) 196 { 197 Message message = ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get( 198 args.keyStorePasswordArg.getLongIdentifier(), 199 args.keyStorePasswordFileArg.getLongIdentifier()); 200 throw new ArgumentException(message); 201 } 202 203 204 // If both a trust store password and trust store password file were 205 // provided, then return an error. 206 if (args.trustStorePasswordArg.isPresent() && 207 args.trustStorePasswordFileArg.isPresent()) 208 { 209 Message message = ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get( 210 args.trustStorePasswordArg.getLongIdentifier(), 211 args.trustStorePasswordFileArg.getLongIdentifier()); 212 err.println(wrapText(message, MAX_LINE_WIDTH)); 213 throw new ArgumentException(message); 214 } 215 216 // Create the LDAP connection options object, which will be used to 217 // customize the way that we connect to the server and specify a set of 218 // basic defaults. 219 LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions(); 220 connectionOptions.setVersionNumber(3); 221 222 223 // See if we should use SSL or StartTLS when establishing the connection. 224 // If so, then make sure only one of them was specified. 225 if (args.useSSLArg.isPresent()) 226 { 227 if (args.useStartTLSArg.isPresent()) 228 { 229 Message message = ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get( 230 args.useSSLArg.getLongIdentifier(), 231 args.useSSLArg.getLongIdentifier()); 232 err.println(wrapText(message, MAX_LINE_WIDTH)); 233 throw new ArgumentException(message); 234 } 235 else 236 { 237 connectionOptions.setUseSSL(true); 238 } 239 } 240 else if (args.useStartTLSArg.isPresent()) 241 { 242 connectionOptions.setStartTLS(true); 243 } 244 245 246 // If we should blindly trust any certificate, then install the appropriate 247 // SSL connection factory. 248 if (args.useSSLArg.isPresent() || args.useStartTLSArg.isPresent()) 249 { 250 try 251 { 252 String clientAlias; 253 if (args.certNicknameArg.isPresent()) 254 { 255 clientAlias = args.certNicknameArg.getValue(); 256 } 257 else 258 { 259 clientAlias = null; 260 } 261 262 SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory(); 263 sslConnectionFactory.init(args.trustAllArg.isPresent(), 264 args.keyStorePathArg.getValue(), 265 args.keyStorePasswordArg.getValue(), 266 clientAlias, 267 args.trustStorePathArg.getValue(), 268 args.trustStorePasswordArg.getValue()); 269 270 connectionOptions.setSSLConnectionFactory(sslConnectionFactory); 271 } 272 catch (SSLConnectionException sce) 273 { 274 Message message = 275 ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL.get(sce.getMessage()); 276 err.println(wrapText(message, MAX_LINE_WIDTH)); 277 } 278 } 279 280 281 // If one or more SASL options were provided, then make sure that one of 282 // them was "mech" and specified a valid SASL mechanism. 283 if (args.saslOptionArg.isPresent()) 284 { 285 String mechanism = null; 286 LinkedList<String> options = new LinkedList<String>(); 287 288 for (String s : args.saslOptionArg.getValues()) 289 { 290 int equalPos = s.indexOf('='); 291 if (equalPos <= 0) 292 { 293 Message message = ERR_LDAP_CONN_CANNOT_PARSE_SASL_OPTION.get(s); 294 err.println(wrapText(message, MAX_LINE_WIDTH)); 295 throw new ArgumentException(message); 296 } 297 else 298 { 299 String name = s.substring(0, equalPos); 300 301 if (name.equalsIgnoreCase("mech")) 302 { 303 mechanism = s; 304 } 305 else 306 { 307 options.add(s); 308 } 309 } 310 } 311 312 if (mechanism == null) 313 { 314 Message message = ERR_LDAP_CONN_NO_SASL_MECHANISM.get(); 315 err.println(wrapText(message, MAX_LINE_WIDTH)); 316 throw new ArgumentException(message); 317 } 318 319 connectionOptions.setSASLMechanism(mechanism); 320 321 for (String option : options) 322 { 323 connectionOptions.addSASLProperty(option); 324 } 325 } 326 return connect( 327 args.hostNameArg.getValue(), 328 args.portArg.getIntValue(), 329 args.bindDnArg.getValue(), 330 getPasswordValue(args.bindPasswordArg, args.bindPasswordFileArg), 331 connectionOptions, out, err); 332 } 333 334 /** 335 * Creates a connection using a console interaction that will be used 336 * to potientially interact with the user to prompt for necessary 337 * information for establishing the connection. 338 * 339 * @param ui user interaction for prompting the user 340 * @param out stream to write messages 341 * @param err stream to write messages 342 * @return LDAPConnection created by this class from parsed arguments 343 * @throws LDAPConnectionException if there was a problem connecting 344 * to the server indicated by the input arguments 345 */ 346 public LDAPConnection connect(LDAPConnectionConsoleInteraction ui, 347 PrintStream out, PrintStream err) 348 throws LDAPConnectionException 349 { 350 LDAPConnection connection = null; 351 try { 352 ui.run(); 353 LDAPConnectionOptions options = new LDAPConnectionOptions(); 354 options.setVersionNumber(3); 355 connection = connect( 356 ui.getHostName(), 357 ui.getPortNumber(), 358 ui.getBindDN(), 359 ui.getBindPassword(), 360 ui.populateLDAPOptions(options), out, err); 361 } catch (OpenDsException e) { 362 err.println(e.getMessageObject()); 363 } 364 return connection; 365 } 366 367 368 /** 369 * Creates a connection from information provided. 370 * 371 * @param host of the server 372 * @param port of the server 373 * @param bindDN with which to connect 374 * @param bindPw with which to connect 375 * @param options with which to connect 376 * @param out stream to write messages 377 * @param err stream to write messages 378 * @return LDAPConnection created by this class from parsed arguments 379 * @throws LDAPConnectionException if there was a problem connecting 380 * to the server indicated by the input arguments 381 */ 382 public LDAPConnection connect(String host, int port, 383 String bindDN, String bindPw, 384 LDAPConnectionOptions options, 385 PrintStream out, 386 PrintStream err) 387 throws LDAPConnectionException 388 { 389 390 // Attempt to connect and authenticate to the Directory Server. 391 AtomicInteger nextMessageID = new AtomicInteger(1); 392 393 LDAPConnection connection = new LDAPConnection( 394 host, port, options, out, err); 395 396 connection.connectToHost(bindDN, bindPw, nextMessageID); 397 398 return connection; 399 } 400 401 /** 402 * Gets the arguments associated with this parser. 403 * 404 * @return arguments for this parser. 405 */ 406 public SecureConnectionCliArgs getArguments() { 407 return args; 408 } 409 410 /** 411 * Commodity method that retrieves the password value analyzing the contents 412 * of a string argument and of a file based argument. It assumes that the 413 * arguments have already been parsed and validated. 414 * @param bindPwdArg the string argument. 415 * @param bindPwdFileArg the file based argument. 416 * @return the password value. 417 */ 418 public static String getPasswordValue(StringArgument bindPwdArg, 419 FileBasedArgument bindPwdFileArg) 420 { 421 String pwd = bindPwdArg.getValue(); 422 if ((pwd == null) && bindPwdFileArg.isPresent()) 423 { 424 pwd = bindPwdFileArg.getValue(); 425 } 426 return pwd; 427 } 428 429 private void addLdapConnectionArguments(ArgumentGroup argGroup) { 430 args = new SecureConnectionCliArgs(); 431 try { 432 LinkedHashSet<Argument> argSet = args.createGlobalArguments(); 433 for (Argument arg : argSet) { 434 addArgument(arg, argGroup); 435 } 436 } 437 catch (ArgumentException ae) { 438 ae.printStackTrace(); // Should never happen 439 } 440 441 } 442 443 }