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    }