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 2014-2015 ForgeRock AS
026 */
027
028package org.opends.server.admin.client.cli;
029
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.IOException;
035import java.io.OutputStream;
036import java.io.PrintStream;
037import java.util.Collection;
038import java.util.Set;
039
040import org.forgerock.i18n.LocalizableMessage;
041import org.forgerock.i18n.LocalizableMessageBuilder;
042import org.forgerock.i18n.slf4j.LocalizedLogger;
043import org.opends.admin.ads.util.ApplicationTrustManager;
044
045import com.forgerock.opendj.cli.Argument;
046import com.forgerock.opendj.cli.ArgumentException;
047import com.forgerock.opendj.cli.ArgumentGroup;
048import com.forgerock.opendj.cli.BooleanArgument;
049import com.forgerock.opendj.cli.ClientException;
050import com.forgerock.opendj.cli.CommonArguments;
051import com.forgerock.opendj.cli.ConsoleApplication;
052import com.forgerock.opendj.cli.FileBasedArgument;
053import com.forgerock.opendj.cli.StringArgument;
054import com.forgerock.opendj.cli.SubCommandArgumentParser;
055
056/**
057 * This is a commodity class that can be used to check the arguments required to
058 * establish a secure connection in the command line. It can be used to generate
059 * an ApplicationTrustManager object based on the options provided by the user
060 * in the command line.
061 */
062public abstract class SecureConnectionCliParser extends SubCommandArgumentParser
063{
064  /**
065   * Logger.
066   */
067  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
068
069  /**
070   * The 'showUsage' global argument.
071   */
072  protected BooleanArgument showUsageArg;
073
074  /**
075   * The 'verbose' global argument.
076   */
077  protected BooleanArgument verboseArg;
078
079  /**
080   * The secure args list object.
081   */
082  protected SecureConnectionCliArgs secureArgsList ;
083
084  /**
085   * Argument indicating a properties file argument.
086   */
087  protected StringArgument propertiesFileArg;
088
089  /**
090   * The argument which should be used to indicate that we will not
091   * look for properties file.
092   */
093  protected BooleanArgument noPropertiesFileArg;
094
095  /**
096   * Creates a new instance of this argument parser with no arguments.
097   *
098   * @param mainClassName
099   *          The fully-qualified name of the Java class that should
100   *          be invoked to launch the program with which this
101   *          argument parser is associated.
102   * @param toolDescription
103   *          A human-readable description for the tool, which will be
104   *          included when displaying usage information.
105   * @param longArgumentsCaseSensitive
106   *          Indicates whether subcommand and long argument names
107   *          should be treated in a case-sensitive manner.
108   */
109  protected SecureConnectionCliParser(String mainClassName,
110      LocalizableMessage toolDescription, boolean longArgumentsCaseSensitive)
111  {
112    super(mainClassName, toolDescription, longArgumentsCaseSensitive);
113  }
114
115  /**
116   * Get the bindDN which has to be used for the command.
117   *
118   * @return The bindDN specified by the command line argument, or the
119   *         default value, if not specified.
120   */
121  public String getBindDN()
122  {
123    return secureArgsList.getBindDN();
124  }
125
126
127  /**
128   * Returns the Administrator UID provided in the command-line.
129   * @return the Administrator UID provided in the command-line.
130   */
131  public String getAdministratorUID()
132  {
133    return secureArgsList.getAdministratorUID();
134  }
135
136  /**
137   * Get the password which has to be used for the command.
138   *
139   * @param dn
140   *          The user DN for which to password could be asked.
141   * @param out
142   *          The input stream to used if we have to prompt to the
143   *          user.
144   * @param err
145   *          The error stream to used if we have to prompt to the
146   *          user.
147   * @param pwdArg
148   *          The password StringArgument argument.
149   * @param fileArg
150   *          The password FileBased argument.
151   * @return The password stored into the specified file on by the
152   *         command line argument, or prompts it if not specified.
153   */
154  protected String getBindPassword(String dn, OutputStream out,
155      OutputStream err, StringArgument pwdArg, FileBasedArgument fileArg)
156  {
157    if (fileArg.isPresent())
158    {
159      return fileArg.getValue();
160    }
161
162    String bindPasswordValue = pwdArg.isPresent() ? pwdArg.getValue() : null;
163    if (bindPasswordValue == null || "-".equals(bindPasswordValue))
164    {
165      // Read the password from the STDin.
166      try
167      {
168        return readPassword(dn, out);
169      }
170      catch (Exception ex)
171      {
172        logger.traceException(ex);
173        try
174        {
175          err.write(wrapText(ex.getMessage(), MAX_LINE_WIDTH).getBytes());
176          err.write(LINE_SEPARATOR.getBytes());
177        }
178        catch (IOException e)
179        {
180          // Nothing to do.
181        }
182      }
183    }
184    return bindPasswordValue;
185  }
186
187  private String readPassword(String dn, OutputStream out) throws IOException,
188      ClientException
189  {
190    out.write(INFO_LDAPAUTH_PASSWORD_PROMPT.get(dn).toString().getBytes());
191    out.flush();
192    char[] pwChars = ConsoleApplication.readPassword();
193    return new String(pwChars);
194  }
195
196  /**
197   * Gets the password which has to be used for the command.
198   *
199   * @param dn
200   *          The user DN for which to password could be asked.
201   * @param out
202   *          The input stream to used if we have to prompt to the
203   *          user.
204   * @param err
205   *          The error stream to used if we have to prompt to the
206   *          user.
207   * @return The password stored into the specified file on by the
208   *         command line argument, or prompts it if not specified.
209   */
210  public String getBindPassword(String dn, OutputStream out, OutputStream err)
211  {
212    return getBindPassword(dn, out, err, secureArgsList.bindPasswordArg,
213        secureArgsList.bindPasswordFileArg);
214  }
215
216  /**
217   * Gets the password which has to be used for the command without prompting
218   * the user.  If no password was specified, return null.
219   *
220   * @return The password stored into the specified file on by the
221   *         command line argument, or null it if not specified.
222   */
223  public String getBindPassword()
224  {
225    return getBindPassword(secureArgsList.bindPasswordArg,
226        secureArgsList.bindPasswordFileArg);
227  }
228
229  /**
230   * Initialize Global option.
231   *
232   * @param outStream
233   *          The output stream used for the usage.
234   * @param alwaysSSL If true, always use the SSL connection type. In this case,
235   * the arguments useSSL and startTLS are not present.
236   *
237   * @throws ArgumentException
238   *           If there is a problem with any of the parameters used
239   *           to create this argument.
240   * @return a ArrayList with the options created.
241   */
242  protected Set<Argument> createGlobalArguments(OutputStream outStream, boolean alwaysSSL) throws ArgumentException
243  {
244    secureArgsList = new SecureConnectionCliArgs(alwaysSSL);
245    Set<Argument> set = secureArgsList.createGlobalArguments();
246
247    showUsageArg = CommonArguments.getShowUsage();
248    setUsageArgument(showUsageArg, outStream);
249    set.add(showUsageArg);
250
251    verboseArg = CommonArguments.getVerbose();
252    set.add(verboseArg);
253
254    propertiesFileArg = CommonArguments.getPropertiesFile();
255    setFilePropertiesArgument(propertiesFileArg);
256    set.add(propertiesFileArg);
257
258    noPropertiesFileArg = CommonArguments.getNoPropertiesFile();
259    setNoPropertiesFileArgument(noPropertiesFileArg);
260    set.add(noPropertiesFileArg);
261
262    return set;
263  }
264
265  /**
266   * Initialize the global options with the provided set of arguments.
267   * @param args the arguments to use to initialize the global options.
268   * @throws ArgumentException if there is a conflict with the provided
269   * arguments.
270   */
271  protected void initializeGlobalArguments(Collection<Argument> args)
272  throws ArgumentException
273  {
274    initializeGlobalArguments(args, null);
275  }
276
277
278  /**
279   * Initialize the global options with the provided set of arguments.
280   * @param args the arguments to use to initialize the global options.
281   * @param argGroup to which args will be added
282   * @throws ArgumentException if there is a conflict with the provided
283   * arguments.
284   */
285  protected void initializeGlobalArguments(
286          Collection<Argument> args,
287          ArgumentGroup argGroup)
288  throws ArgumentException
289  {
290    for (Argument arg : args)
291    {
292      addGlobalArgument(arg, argGroup);
293    }
294
295    // Set the propertiesFile argument
296    setFilePropertiesArgument(propertiesFileArg);
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
303   *         the default value, if not specified.
304   */
305  public String getHostName()
306  {
307    return secureArgsList.getHostName();
308  }
309
310  /**
311   * Get the port which has to be used for the command.
312   *
313   * @return The port specified by the command line argument, or the
314   *         default value, if not specified.
315   */
316  public String getPort()
317  {
318    return secureArgsList.getPort();
319  }
320
321  /**
322   * Indication if provided global options are validate.
323   *
324   * @param buf the LocalizableMessageBuilder to write the error messages.
325   * @return return code.
326   */
327  public int validateGlobalOptions(LocalizableMessageBuilder buf)
328  {
329    int ret = secureArgsList.validateGlobalOptions(buf) ;
330
331    // Couldn't have at the same time properties file arg and
332    // propertiesFileArg
333    if (noPropertiesFileArg.isPresent()
334        && propertiesFileArg.isPresent())
335    {
336      LocalizableMessage message = ERR_TOOL_CONFLICTING_ARGS.get(
337          noPropertiesFileArg.getLongIdentifier(), propertiesFileArg
338              .getLongIdentifier());
339      if (buf.length() > 0)
340      {
341        buf.append(LINE_SEPARATOR);
342      }
343      buf.append(message);
344      return CONFLICTING_ARGS.get();
345    }
346
347    return ret;
348  }
349  /**
350   * Indication if provided global options are validate.
351   *
352   * @param err the stream to be used to print error message.
353   * @return return code.
354   */
355  public int validateGlobalOptions(PrintStream err)
356  {
357    LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
358    int returnValue = validateGlobalOptions(buf);
359    printWrappedText(err, buf.toString());
360    return returnValue;
361  }
362
363  /**
364   * Indicate if the verbose mode is required.
365   *
366   * @return True if verbose mode is required
367   */
368  public boolean isVerbose()
369  {
370    return verboseArg.isPresent();
371  }
372
373
374  /**
375   * Indicate if the SSL mode is required.
376   *
377   * @return True if SSL mode is required
378   */
379  public boolean useSSL()
380  {
381    return secureArgsList.useSSL();
382  }
383
384  /**
385   * Indicate if the startTLS mode is required.
386   *
387   * @return True if startTLS mode is required
388   */
389  public boolean useStartTLS()
390  {
391    return secureArgsList.useStartTLS();
392  }
393
394  /**
395   * Handle TrustStore.
396   *
397   * @return The trustStore manager to be used for the command.
398   */
399  public ApplicationTrustManager getTrustManager()
400  {
401    return secureArgsList.getTrustManager();
402  }
403
404  /**
405   * Returns the timeout to be used to connect in milliseconds.  The method
406   * must be called after parsing the arguments.
407   * @return the timeout to be used to connect in milliseconds.  Returns
408   * {@code 0} if there is no timeout.
409   * @throws IllegalStateException if the method is called before
410   * parsing the arguments.
411   */
412  public int getConnectTimeout()throws IllegalStateException
413  {
414    try
415    {
416      return secureArgsList.connectTimeoutArg.getIntValue();
417    }
418    catch (ArgumentException ae)
419    {
420      throw new IllegalStateException("Argument parser is not parsed: "+ae, ae);
421    }
422  }
423}