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.tools.status;
028
029import static com.forgerock.opendj.cli.ArgumentConstants.*;
030import static com.forgerock.opendj.cli.CliMessages.*;
031import static com.forgerock.opendj.cli.Utils.*;
032import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
033import static org.forgerock.util.Utils.*;
034
035import static org.opends.messages.AdminToolMessages.*;
036import static org.opends.messages.QuickSetupMessages.INFO_ERROR_READING_SERVER_CONFIGURATION;
037import static org.opends.messages.QuickSetupMessages.INFO_NOT_AVAILABLE_LABEL;
038
039import java.io.File;
040import java.io.InputStream;
041import java.io.OutputStream;
042import java.io.PrintStream;
043import java.net.URI;
044import java.security.GeneralSecurityException;
045import java.security.cert.CertificateException;
046import java.security.cert.X509Certificate;
047import java.util.HashSet;
048import java.util.Set;
049import java.util.TreeSet;
050import java.util.concurrent.TimeUnit;
051
052import javax.naming.AuthenticationException;
053import javax.naming.NamingException;
054import javax.naming.ldap.InitialLdapContext;
055import javax.net.ssl.KeyManager;
056import javax.net.ssl.SSLException;
057import javax.net.ssl.TrustManager;
058
059import org.forgerock.i18n.LocalizableMessage;
060import org.forgerock.i18n.LocalizableMessageBuilder;
061import org.forgerock.i18n.slf4j.LocalizedLogger;
062import org.forgerock.opendj.config.LDAPProfile;
063import org.forgerock.opendj.config.client.ManagementContext;
064import org.forgerock.opendj.config.client.ldap.LDAPManagementContext;
065import org.forgerock.opendj.config.server.ConfigException;
066import org.forgerock.opendj.ldap.AuthorizationException;
067import org.forgerock.opendj.ldap.Connection;
068import org.forgerock.opendj.ldap.LDAPConnectionFactory;
069import org.forgerock.opendj.ldap.LdapException;
070import org.forgerock.opendj.ldap.ResultCode;
071import org.forgerock.opendj.ldap.SSLContextBuilder;
072import org.forgerock.opendj.ldap.TrustManagers;
073import org.forgerock.util.Options;
074import org.forgerock.util.time.Duration;
075import org.opends.admin.ads.util.ApplicationTrustManager;
076import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
077import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
078import org.opends.guitools.controlpanel.datamodel.BaseDNTableModel;
079import org.opends.guitools.controlpanel.datamodel.ConfigReadException;
080import org.opends.guitools.controlpanel.datamodel.ConnectionHandlerDescriptor;
081import org.opends.guitools.controlpanel.datamodel.ConnectionHandlerTableModel;
082import org.opends.guitools.controlpanel.datamodel.ConnectionProtocolPolicy;
083import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
084import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
085import org.opends.guitools.controlpanel.util.ControlPanelLog;
086import org.opends.guitools.controlpanel.util.Utilities;
087import org.opends.server.admin.client.cli.SecureConnectionCliArgs;
088import org.opends.server.types.DN;
089import org.opends.server.types.InitializationException;
090import org.opends.server.types.NullOutputStream;
091import org.opends.server.types.OpenDsException;
092import org.opends.server.util.BuildVersion;
093import org.opends.server.util.StaticUtils;
094import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
095
096import com.forgerock.opendj.cli.ArgumentException;
097import com.forgerock.opendj.cli.CliConstants;
098import com.forgerock.opendj.cli.ClientException;
099import com.forgerock.opendj.cli.ConsoleApplication;
100import com.forgerock.opendj.cli.ReturnCode;
101import com.forgerock.opendj.cli.TableBuilder;
102import com.forgerock.opendj.cli.TextTablePrinter;
103
104/**
105 * The class used to provide some CLI interface to display status.
106 * This class basically is in charge of parsing the data provided by the
107 * user in the command line.
108 */
109public class StatusCli extends ConsoleApplication
110{
111  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
112
113  private boolean displayMustAuthenticateLegend;
114  private boolean displayMustStartLegend;
115
116  /** Prefix for log files. */
117  public static final String LOG_FILE_PREFIX = "opendj-status-";
118  /** Suffix for log files. */
119  public static final String LOG_FILE_SUFFIX = ".log";
120
121  private ApplicationTrustManager interactiveTrustManager;
122  private boolean useInteractiveTrustManager;
123
124  /** The argument parser. */
125  private StatusCliArgumentParser argParser;
126
127  /**
128   * Constructor for the status cli object.
129   *
130   * @param out
131   *          The print stream to use for standard output.
132   * @param err
133   *          The print stream to use for standard error.
134   * @param in
135   *          The input stream to use for standard input.
136   */
137  public StatusCli(PrintStream out, PrintStream err, InputStream in)
138  {
139    super(out, err);
140  }
141
142  /**
143   * The main method for the status CLI tool.
144   *
145   * @param args The command-line arguments provided to this program.
146   */
147
148  public static void main(String[] args)
149  {
150    int retCode = mainCLI(args, true, System.out, System.err, System.in);
151    if(retCode != 0)
152    {
153      System.exit(retCode);
154    }
155  }
156
157  /**
158   * Parses the provided command-line arguments and uses that information to
159   * run the status tool.
160   *
161   * @param args the command-line arguments provided to this program.
162   *
163   * @return The return code.
164   */
165
166  public static int mainCLI(String[] args)
167  {
168    return mainCLI(args, true, System.out, System.err, System.in);
169  }
170
171  /**
172   * Parses the provided command-line arguments and uses that information to run
173   * the status tool.
174   *
175   * @param args
176   *          The command-line arguments provided to this program.
177   * @param initializeServer
178   *          Indicates whether to initialize the server.
179   * @param outStream
180   *          The output stream to use for standard output, or {@code null}
181   *          if standard output is not needed.
182   * @param errStream
183   *          The output stream to use for standard error, or {@code null}
184   *          if standard error is not needed.
185   * @param inStream
186   *          The input stream to use for standard input.
187   * @return The return code.
188   */
189  public static int mainCLI(String[] args, boolean initializeServer,
190      OutputStream outStream, OutputStream errStream, InputStream inStream)
191  {
192    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
193    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
194
195    try {
196      ControlPanelLog.initLogFileHandler(
197              File.createTempFile(LOG_FILE_PREFIX, LOG_FILE_SUFFIX));
198      ControlPanelLog.initPackage("org.opends.server.tools.status");
199    } catch (Throwable t) {
200      System.err.println("Unable to initialize log");
201      t.printStackTrace();
202    }
203
204    final StatusCli statusCli = new StatusCli(out, err, inStream);
205    int retCode = statusCli.execute(args);
206    if (retCode == 0)
207    {
208      ControlPanelLog.closeAndDeleteLogFile();
209    }
210    return retCode;
211  }
212
213  /**
214   * Parses the provided command-line arguments and uses that information to run
215   * the status CLI.
216   *
217   * @param args
218   *          The command-line arguments provided to this program.
219   * @return The return code of the process.
220   */
221  public int execute(String[] args) {
222    argParser = new StatusCliArgumentParser(StatusCli.class.getName());
223    try {
224      argParser.initializeGlobalArguments(getOutputStream());
225    } catch (ArgumentException ae) {
226      println(ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
227      return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get();
228    }
229
230    try
231    {
232      argParser.getSecureArgsList().initArgumentsWithConfiguration();
233    }
234    catch (ConfigException ce)
235    {
236      // Ignore.
237    }
238
239    // Validate user provided data
240    try {
241      argParser.parseArguments(args);
242    } catch (ArgumentException ae) {
243      argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
244      return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get();
245    }
246
247    //  If we should just display usage or version information,
248    // then print it and exit.
249    if (argParser.usageOrVersionDisplayed()) {
250      return ReturnCode.SUCCESS.get();
251    }
252
253    // Checks the version - if upgrade required, the tool is unusable
254    try
255    {
256      BuildVersion.checkVersionMismatch();
257    }
258    catch (InitializationException e)
259    {
260      println(e.getMessageObject());
261      return 1;
262    }
263
264    int v = argParser.validateGlobalOptions(getErrorStream());
265    if (v != ReturnCode.SUCCESS.get()) {
266      println(LocalizableMessage.raw(argParser.getUsage()));
267      return v;
268    }
269
270    final ControlPanelInfo controlInfo = ControlPanelInfo.getInstance();
271    controlInfo.setTrustManager(getTrustManager());
272    controlInfo.setConnectTimeout(argParser.getConnectTimeout());
273    controlInfo.regenerateDescriptor();
274
275    if (controlInfo.getServerDescriptor().getStatus() == ServerDescriptor.ServerStatus.STARTED)
276    {
277      String bindDn = null;
278      String bindPwd = null;
279
280      ManagementContext mContext = null;
281
282      // This is done because we do not need to ask the user about these
283      // parameters. We force their presence in the
284      // LDAPConnectionConsoleInteraction, this done, it will not prompt
285      // the user for them.
286      final SecureConnectionCliArgs secureArgsList = argParser.getSecureArgsList();
287      controlInfo.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
288      int port = CliConstants.DEFAULT_ADMINISTRATION_CONNECTOR_PORT;
289      controlInfo.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
290      String ldapUrl = controlInfo.getURLToConnect();
291      try
292      {
293        final URI uri = new URI(ldapUrl);
294        port = uri.getPort();
295      }
296      catch (Throwable t)
297      {
298        logger.error(LocalizableMessage.raw("Error parsing url: " + ldapUrl));
299      }
300      secureArgsList.hostNameArg.setPresent(true);
301      secureArgsList.portArg.setPresent(true);
302      secureArgsList.hostNameArg.addValue(secureArgsList.hostNameArg.getDefaultValue());
303      secureArgsList.portArg.addValue(Integer.toString(port));
304      // We already know if SSL or StartTLS can be used.  If we cannot
305      // use them we will not propose them in the connection parameters
306      // and if none of them can be used we will just not ask for the
307      // protocol to be used.
308      final LDAPConnectionConsoleInteraction ci =
309          new LDAPConnectionConsoleInteraction(this, argParser.getSecureArgsList());
310      try
311      {
312        ci.run(false);
313      }
314      catch (ArgumentException e)
315      {
316        argParser.displayMessageAndUsageReference(getErrStream(), e.getMessageObject());
317        return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get();
318      }
319      try
320      {
321        if (argParser.isInteractive())
322        {
323          bindDn = ci.getBindDN();
324          bindPwd = ci.getBindPassword();
325        }
326        else
327        {
328          bindDn = argParser.getBindDN();
329          bindPwd = argParser.getBindPassword();
330        }
331        if (bindPwd != null && !bindPwd.isEmpty())
332        {
333          mContext = getManagementContextFromConnection(ci);
334          interactiveTrustManager = ci.getTrustManager();
335          controlInfo.setTrustManager(interactiveTrustManager);
336          useInteractiveTrustManager = true;
337        }
338      } catch (ClientException e) {
339        println(e.getMessageObject());
340        return ReturnCode.CLIENT_SIDE_PARAM_ERROR.get();
341      } finally {
342        closeSilently(mContext);
343      }
344
345      if (mContext != null)
346      {
347        InitialLdapContext ctx = null;
348        try {
349          ctx = Utilities.getAdminDirContext(controlInfo, bindDn, bindPwd);
350          controlInfo.setDirContext(ctx);
351          controlInfo.regenerateDescriptor();
352          writeStatus(controlInfo);
353
354          if (!controlInfo.getServerDescriptor().getExceptions().isEmpty()) {
355            return ReturnCode.ERROR_INITIALIZING_SERVER.get();
356          }
357        } catch (NamingException ne) {
358          // This should not happen but this is useful information to
359          // diagnose the error.
360          println();
361          println(INFO_ERROR_READING_SERVER_CONFIGURATION.get(ne));
362          return ReturnCode.ERROR_INITIALIZING_SERVER.get();
363        } catch (ConfigReadException cre) {
364          // This should not happen but this is useful information to
365          // diagnose the error.
366          println();
367          println(cre.getMessageObject());
368          return ReturnCode.ERROR_INITIALIZING_SERVER.get();
369        } finally {
370          StaticUtils.close(ctx);
371        }
372      } else {
373        // The user did not provide authentication: just display the
374        // information we can get reading the config file.
375        writeStatus(controlInfo);
376        return ReturnCode.ERROR_USER_CANCELLED.get();
377      }
378    } else {
379      writeStatus(controlInfo);
380    }
381
382    return ReturnCode.SUCCESS.get();
383  }
384
385  private void writeStatus(ControlPanelInfo controlInfo)
386  {
387    if (controlInfo.getServerDescriptor() == null)
388    {
389      controlInfo.regenerateDescriptor();
390    }
391    writeStatus(controlInfo.getServerDescriptor());
392    int period = argParser.getRefreshPeriod();
393    boolean first = true;
394    while (period > 0)
395    {
396      long timeToSleep = period * 1000;
397      if (!first)
398      {
399        long t1 = System.currentTimeMillis();
400        controlInfo.regenerateDescriptor();
401        long t2 = System.currentTimeMillis();
402
403        timeToSleep = timeToSleep - t2 + t1;
404      }
405
406      if (timeToSleep > 0)
407      {
408        StaticUtils.sleep(timeToSleep);
409      }
410      println();
411      println(LocalizableMessage.raw("          ---------------------"));
412      println();
413      writeStatus(controlInfo.getServerDescriptor());
414      first = false;
415    }
416  }
417
418  private void writeStatus(ServerDescriptor desc)
419  {
420    LocalizableMessage[] labels =
421      {
422        INFO_SERVER_STATUS_LABEL.get(),
423        INFO_CONNECTIONS_LABEL.get(),
424        INFO_HOSTNAME_LABEL.get(),
425        INFO_ADMINISTRATIVE_USERS_LABEL.get(),
426        INFO_INSTALLATION_PATH_LABEL.get(),
427        INFO_OPENDS_VERSION_LABEL.get(),
428        INFO_JAVA_VERSION_LABEL.get(),
429        INFO_CTRL_PANEL_ADMIN_CONNECTOR_LABEL.get()
430      };
431    int labelWidth = 0;
432    LocalizableMessage title = INFO_SERVER_STATUS_TITLE.get();
433    if (!isScriptFriendly())
434    {
435      for (LocalizableMessage label : labels)
436      {
437        labelWidth = Math.max(labelWidth, label.length());
438      }
439      println();
440      println(centerTitle(title));
441    }
442    writeStatusContents(desc, labelWidth);
443    writeCurrentConnectionContents(desc, labelWidth);
444    if (!isScriptFriendly())
445    {
446      println();
447    }
448
449    title = INFO_SERVER_DETAILS_TITLE.get();
450    if (!isScriptFriendly())
451    {
452      println(centerTitle(title));
453    }
454    writeHostnameContents(desc, labelWidth);
455    writeAdministrativeUserContents(desc, labelWidth);
456    writeInstallPathContents(desc, labelWidth);
457    boolean sameInstallAndInstance = desc.sameInstallAndInstance();
458    if (!sameInstallAndInstance)
459    {
460      writeInstancePathContents(desc, labelWidth);
461    }
462    writeVersionContents(desc, labelWidth);
463    writeJavaVersionContents(desc, labelWidth);
464    writeAdminConnectorContents(desc, labelWidth);
465    if (!isScriptFriendly())
466    {
467      println();
468    }
469
470    writeListenerContents(desc);
471    if (!isScriptFriendly())
472    {
473      println();
474    }
475
476    writeBaseDNContents(desc);
477
478    writeErrorContents(desc);
479
480    if (!isScriptFriendly())
481    {
482      if (displayMustStartLegend)
483      {
484        println();
485        println(INFO_NOT_AVAILABLE_SERVER_DOWN_CLI_LEGEND.get());
486      }
487      else if (displayMustAuthenticateLegend)
488      {
489        println();
490        println(INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_CLI_LEGEND.get());
491      }
492    }
493    println();
494  }
495
496  /**
497   * Writes the status contents displaying with what is specified in the
498   * provided ServerDescriptor object.
499   *
500   * @param desc
501   *          The ServerStatusDescriptor object.
502   */
503  private void writeStatusContents(ServerDescriptor desc, int maxLabelWidth)
504  {
505    writeLabelValue(INFO_SERVER_STATUS_LABEL.get(), getStatus(desc).toString(), maxLabelWidth);
506  }
507
508  private LocalizableMessage getStatus(ServerDescriptor desc)
509  {
510    switch (desc.getStatus())
511    {
512    case STARTED:
513      return INFO_SERVER_STARTED_LABEL.get();
514    case STOPPED:
515      return INFO_SERVER_STOPPED_LABEL.get();
516    case STARTING:
517      return INFO_SERVER_STARTING_LABEL.get();
518    case STOPPING:
519      return INFO_SERVER_STOPPING_LABEL.get();
520    case NOT_CONNECTED_TO_REMOTE:
521      return INFO_SERVER_NOT_CONNECTED_TO_REMOTE_STATUS_LABEL.get();
522    case UNKNOWN:
523      return INFO_SERVER_UNKNOWN_STATUS_LABEL.get();
524    default:
525      throw new IllegalStateException("Unknown status: "+desc.getStatus());
526    }
527  }
528
529  /**
530   * Writes the current connection contents displaying with what is specified in
531   * the provided ServerDescriptor object.
532   *
533   * @param desc
534   *          The ServerDescriptor object.
535   */
536  private void writeCurrentConnectionContents(ServerDescriptor desc, int maxLabelWidth)
537  {
538    writeLabelValue(INFO_CONNECTIONS_LABEL.get(), getNbConnection(desc), maxLabelWidth);
539  }
540
541  private String getNbConnection(ServerDescriptor desc)
542  {
543    if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED)
544    {
545      final int nConn = desc.getOpenConnections();
546      if (nConn >= 0)
547      {
548        return String.valueOf(nConn);
549      }
550      else if (!desc.isAuthenticated() || !desc.getExceptions().isEmpty())
551      {
552        return getNotAvailableBecauseAuthenticationIsRequiredText();
553      }
554      else
555      {
556        return getNotAvailableText();
557      }
558    }
559    return getNotAvailableBecauseServerIsDownText();
560  }
561
562  /**
563   * Writes the host name contents.
564   *
565   * @param desc
566   *          The ServerDescriptor object.
567   * @param maxLabelWidth
568   *          The maximum label width of the left label.
569   */
570  private void writeHostnameContents(ServerDescriptor desc, int maxLabelWidth)
571  {
572    writeLabelValue(INFO_HOSTNAME_LABEL.get(), desc.getHostname(), maxLabelWidth);
573  }
574
575  /**
576   * Writes the administrative user contents displaying with what is specified
577   * in the provided ServerStatusDescriptor object.
578   *
579   * @param desc
580   *          The ServerStatusDescriptor object.
581   * @param maxLabelWidth
582   *          The maximum label width of the left label.
583   */
584  private void writeAdministrativeUserContents(ServerDescriptor desc, int maxLabelWidth)
585  {
586    Set<DN> administrators = desc.getAdministrativeUsers();
587    if (!administrators.isEmpty())
588    {
589      TreeSet<DN> ordered = new TreeSet<>(administrators);
590      for (DN dn : ordered)
591      {
592        writeLabelValue(INFO_ADMINISTRATIVE_USERS_LABEL.get(), dn.toString(), maxLabelWidth);
593      }
594    }
595    else
596    {
597      writeLabelValue(INFO_ADMINISTRATIVE_USERS_LABEL.get(), getErrorText(desc), maxLabelWidth);
598    }
599  }
600
601  private String getErrorText(ServerDescriptor desc)
602  {
603    if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED
604        && (!desc.isAuthenticated() || !desc.getExceptions().isEmpty()))
605    {
606      return getNotAvailableBecauseAuthenticationIsRequiredText();
607    }
608    return getNotAvailableText();
609  }
610
611  /**
612   * Writes the install path contents displaying with what is specified in the
613   * provided ServerDescriptor object.
614   *
615   * @param desc
616   *          The ServerDescriptor object.
617   * @param maxLabelWidth
618   *          The maximum label width of the left label.
619   */
620  private void writeInstallPathContents(ServerDescriptor desc, int maxLabelWidth)
621  {
622    writeLabelValue(INFO_INSTALLATION_PATH_LABEL.get(), desc.getInstallPath(), maxLabelWidth);
623  }
624
625  /**
626   * Writes the instance path contents displaying with what is specified in the
627   * provided ServerDescriptor object.
628   *
629   * @param desc
630   *          The ServerDescriptor object.
631   * @param maxLabelWidth
632   *          The maximum label width of the left label.
633   */
634  private void writeInstancePathContents(ServerDescriptor desc, int maxLabelWidth)
635  {
636    writeLabelValue(INFO_CTRL_PANEL_INSTANCE_PATH_LABEL.get(), desc.getInstancePath(), maxLabelWidth);
637  }
638
639  /**
640   * Updates the server version contents displaying with what is specified in
641   * the provided ServerDescriptor object. This method must be called from the
642   * event thread.
643   *
644   * @param desc
645   *          The ServerDescriptor object.
646   */
647  private void writeVersionContents(ServerDescriptor desc, int maxLabelWidth)
648  {
649    writeLabelValue(INFO_OPENDS_VERSION_LABEL.get(), desc.getOpenDSVersion(), maxLabelWidth);
650  }
651
652  /**
653   * Updates the java version contents displaying with what is specified in the
654   * provided ServerDescriptor object. This method must be called from the event
655   * thread.
656   *
657   * @param desc
658   *          The ServerDescriptor object.
659   * @param maxLabelWidth
660   *          The maximum label width of the left label.
661   */
662  private void writeJavaVersionContents(ServerDescriptor desc, int maxLabelWidth)
663  {
664    writeLabelValue(INFO_JAVA_VERSION_LABEL.get(), getJavaVersion(desc), maxLabelWidth);
665  }
666
667  private String getJavaVersion(ServerDescriptor desc)
668  {
669    if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED)
670    {
671      if (!desc.isAuthenticated() || !desc.getExceptions().isEmpty())
672      {
673        return getNotAvailableBecauseAuthenticationIsRequiredText();
674      }
675      return desc.getJavaVersion();
676    }
677    return getNotAvailableBecauseServerIsDownText();
678  }
679
680  /**
681   * Updates the admin connector contents displaying with what is specified in
682   * the provided ServerDescriptor object. This method must be called from the
683   * event thread.
684   *
685   * @param desc
686   *          The ServerDescriptor object.
687   * @param maxLabelWidth
688   *          The maximum label width of the left label.
689   */
690  private void writeAdminConnectorContents(ServerDescriptor desc, int maxLabelWidth)
691  {
692    ConnectionHandlerDescriptor adminConnector = desc.getAdminConnector();
693    LocalizableMessage text = adminConnector != null
694        ? INFO_CTRL_PANEL_ADMIN_CONNECTOR_DESCRIPTION.get(adminConnector.getPort())
695        : INFO_NOT_AVAILABLE_SHORT_LABEL.get();
696    writeLabelValue(INFO_CTRL_PANEL_ADMIN_CONNECTOR_LABEL.get(), text.toString(), maxLabelWidth);
697  }
698
699  /**
700   * Writes the listeners contents displaying with what is specified in the
701   * provided ServerDescriptor object.
702   *
703   * @param desc
704   *          The ServerDescriptor object.
705   */
706  private void writeListenerContents(ServerDescriptor desc)
707  {
708    if (!isScriptFriendly())
709    {
710      LocalizableMessage title = INFO_LISTENERS_TITLE.get();
711      println(centerTitle(title));
712    }
713
714    Set<ConnectionHandlerDescriptor> allHandlers = desc.getConnectionHandlers();
715    if (allHandlers.isEmpty())
716    {
717      if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED)
718      {
719        if (!desc.isAuthenticated())
720        {
721          println(INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_CLI_LABEL.get());
722        }
723        else
724        {
725          println(INFO_NO_LISTENERS_FOUND.get());
726        }
727      }
728      else
729      {
730        println(INFO_NO_LISTENERS_FOUND.get());
731      }
732    }
733    else
734    {
735      ConnectionHandlerTableModel connHandlersTableModel =
736        new ConnectionHandlerTableModel(false);
737      connHandlersTableModel.setData(allHandlers);
738      writeConnectionHandlersTableModel(connHandlersTableModel, desc);
739    }
740  }
741
742  /**
743   * Writes the base DN contents displaying with what is specified in the
744   * provided ServerDescriptor object.
745   *
746   * @param desc
747   *          The ServerDescriptor object.
748   */
749  private void writeBaseDNContents(ServerDescriptor desc)
750  {
751    LocalizableMessage title = INFO_DATABASES_TITLE.get();
752    if (!isScriptFriendly())
753    {
754      println(centerTitle(title));
755    }
756
757    Set<BaseDNDescriptor> replicas = new HashSet<>();
758    Set<BackendDescriptor> bs = desc.getBackends();
759    for (BackendDescriptor backend: bs)
760    {
761      if (!backend.isConfigBackend())
762      {
763        replicas.addAll(backend.getBaseDns());
764      }
765    }
766    if (replicas.isEmpty())
767    {
768      if (desc.getStatus() == ServerDescriptor.ServerStatus.STARTED)
769      {
770        if (!desc.isAuthenticated())
771        {
772          println(INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_CLI_LABEL.get());
773        }
774        else
775        {
776          println(INFO_NO_DBS_FOUND.get());
777        }
778      }
779      else
780      {
781        println(INFO_NO_DBS_FOUND.get());
782      }
783    }
784    else
785    {
786      BaseDNTableModel baseDNTableModel = new BaseDNTableModel(true, false);
787      baseDNTableModel.setData(replicas, desc.getStatus(), desc.isAuthenticated());
788
789      writeBaseDNTableModel(baseDNTableModel, desc);
790    }
791  }
792
793  /**
794   * Writes the error label contents displaying with what is specified in the
795   * provided ServerDescriptor object.
796   *
797   * @param desc
798   *          The ServerDescriptor object.
799   */
800  private void writeErrorContents(ServerDescriptor desc)
801  {
802    for (OpenDsException ex : desc.getExceptions())
803    {
804      LocalizableMessage errorMsg = ex.getMessageObject();
805      if (errorMsg != null)
806      {
807        println();
808        println(errorMsg);
809      }
810    }
811  }
812
813  /**
814   * Returns the not available text explaining that the data is not available
815   * because the server is down.
816   *
817   * @return the text.
818   */
819  private String getNotAvailableBecauseServerIsDownText()
820  {
821    displayMustStartLegend = true;
822    return INFO_NOT_AVAILABLE_SERVER_DOWN_CLI_LABEL.get().toString();
823  }
824
825  /**
826   * Returns the not available text explaining that the data is not available
827   * because authentication is required.
828   *
829   * @return the text.
830   */
831  private String getNotAvailableBecauseAuthenticationIsRequiredText()
832  {
833    displayMustAuthenticateLegend = true;
834    return INFO_NOT_AVAILABLE_AUTHENTICATION_REQUIRED_CLI_LABEL.get().toString();
835  }
836
837  /**
838   * Returns the not available text explaining that the data is not available.
839   *
840   * @return the text.
841   */
842  private String getNotAvailableText()
843  {
844    return INFO_NOT_AVAILABLE_LABEL.get().toString();
845  }
846
847  /**
848   * Writes the contents of the provided table model simulating a table layout
849   * using text.
850   *
851   * @param tableModel
852   *          The connection handler table model.
853   * @param desc
854   *          The Server Status descriptor.
855   */
856  private void writeConnectionHandlersTableModel(
857      ConnectionHandlerTableModel tableModel,
858      ServerDescriptor desc)
859  {
860    if (isScriptFriendly())
861    {
862      for (int i=0; i<tableModel.getRowCount(); i++)
863      {
864        // Get the host name, it can be multivalued.
865        String[] hostNames = getHostNames(tableModel, i);
866        for (String hostName : hostNames)
867        {
868          println(LocalizableMessage.raw("-"));
869          for (int j=0; j<tableModel.getColumnCount(); j++)
870          {
871            LocalizableMessageBuilder line = new LocalizableMessageBuilder();
872            line.append(tableModel.getColumnName(j)).append(": ");
873            if (j == 0)
874            {
875              // It is the hostName
876              line.append(getCellValue(hostName, desc));
877            }
878            else
879            {
880              line.append(getCellValue(tableModel.getValueAt(i, j), desc));
881            }
882            println(line.toMessage());
883          }
884        }
885      }
886    }
887    else
888    {
889      TableBuilder table = new TableBuilder();
890      for (int i=0; i< tableModel.getColumnCount(); i++)
891      {
892        table.appendHeading(LocalizableMessage.raw(tableModel.getColumnName(i)));
893      }
894      for (int i=0; i<tableModel.getRowCount(); i++)
895      {
896        // Get the host name, it can be multivalued.
897        String[] hostNames = getHostNames(tableModel, i);
898        for (String hostName : hostNames)
899        {
900          table.startRow();
901          for (int j=0; j<tableModel.getColumnCount(); j++)
902          {
903            if (j == 0)
904            {
905              // It is the hostName
906              table.appendCell(getCellValue(hostName, desc));
907            }
908            else
909            {
910              table.appendCell(getCellValue(tableModel.getValueAt(i, j), desc));
911            }
912          }
913        }
914      }
915      TextTablePrinter printer = new TextTablePrinter(getOutputStream());
916      printer.setColumnSeparator(LIST_TABLE_SEPARATOR);
917      table.print(printer);
918    }
919  }
920
921  private String[] getHostNames(ConnectionHandlerTableModel tableModel, int row)
922  {
923   String v = (String)tableModel.getValueAt(row, 0);
924   String htmlTag = "<html>";
925   if (v.toLowerCase().startsWith(htmlTag))
926   {
927     v = v.substring(htmlTag.length());
928   }
929   return v.split("<br>");
930  }
931
932  private String getCellValue(Object v, ServerDescriptor desc)
933  {
934    if (v != null)
935    {
936      if (v instanceof String)
937      {
938        return (String) v;
939      }
940      else if (v instanceof Integer)
941      {
942        int nEntries = ((Integer)v).intValue();
943        if (nEntries >= 0)
944        {
945          return String.valueOf(nEntries);
946        }
947        else if (!desc.isAuthenticated() || !desc.getExceptions().isEmpty())
948        {
949          return getNotAvailableBecauseAuthenticationIsRequiredText();
950        }
951        else
952        {
953          return getNotAvailableText();
954        }
955      }
956      else
957      {
958        throw new IllegalStateException("Unknown object type: "+v);
959      }
960    }
961    return getNotAvailableText();
962  }
963
964  /**
965   * Writes the contents of the provided base DN table model. Every base DN is
966   * written in a block containing pairs of labels and values.
967   *
968   * @param tableModel
969   *          The TableModel.
970   * @param desc
971   *          The Server Status descriptor.
972   */
973  private void writeBaseDNTableModel(BaseDNTableModel tableModel, ServerDescriptor desc)
974  {
975    boolean isRunning = desc.getStatus() == ServerDescriptor.ServerStatus.STARTED;
976
977    int labelWidth = 0;
978    int labelWidthWithoutReplicated = 0;
979    LocalizableMessage[] labels = new LocalizableMessage[tableModel.getColumnCount()];
980    for (int i=0; i<tableModel.getColumnCount(); i++)
981    {
982      LocalizableMessage header = LocalizableMessage.raw(tableModel.getColumnName(i));
983      labels[i] = new LocalizableMessageBuilder(header).append(":").toMessage();
984      labelWidth = Math.max(labelWidth, labels[i].length());
985      if (i != 4 && i != 5)
986      {
987        labelWidthWithoutReplicated =
988          Math.max(labelWidthWithoutReplicated, labels[i].length());
989      }
990    }
991
992    LocalizableMessage replicatedLabel = INFO_BASEDN_REPLICATED_LABEL.get();
993    for (int i=0; i<tableModel.getRowCount(); i++)
994    {
995      if (isScriptFriendly())
996      {
997        println(LocalizableMessage.raw("-"));
998      }
999      else if (i > 0)
1000      {
1001        println();
1002      }
1003      for (int j=0; j<tableModel.getColumnCount(); j++)
1004      {
1005        Object v = tableModel.getValueAt(i, j);
1006        String value = getValue(desc, isRunning, v);
1007
1008        boolean doWrite = true;
1009        boolean isReplicated =
1010          replicatedLabel.toString().equals(
1011              String.valueOf(tableModel.getValueAt(i, 3)));
1012        if (j == 4 || j == 5)
1013        {
1014          // If the suffix is not replicated we do not have to display these lines
1015          doWrite = isReplicated;
1016        }
1017        if (doWrite)
1018        {
1019          writeLabelValue(labels[j], value,
1020              isReplicated?labelWidth:labelWidthWithoutReplicated);
1021        }
1022      }
1023    }
1024  }
1025
1026  private String getValue(ServerDescriptor desc, boolean isRunning, Object v)
1027  {
1028    if (v != null)
1029    {
1030      if (v == BaseDNTableModel.NOT_AVAILABLE_SERVER_DOWN)
1031      {
1032        return getNotAvailableBecauseServerIsDownText();
1033      }
1034      else if (v == BaseDNTableModel.NOT_AVAILABLE_AUTHENTICATION_REQUIRED)
1035      {
1036        return getNotAvailableBecauseAuthenticationIsRequiredText();
1037      }
1038      else if (v == BaseDNTableModel.NOT_AVAILABLE)
1039      {
1040        return getNotAvailableText(desc, isRunning);
1041      }
1042      else if (v instanceof String)
1043      {
1044        return (String) v;
1045      }
1046      else if (v instanceof LocalizableMessage)
1047      {
1048        return ((LocalizableMessage) v).toString();
1049      }
1050      else if (v instanceof Integer)
1051      {
1052        final int nEntries = ((Integer) v).intValue();
1053        if (nEntries >= 0)
1054        {
1055          return String.valueOf(nEntries);
1056        }
1057        return getNotAvailableText(desc, isRunning);
1058      }
1059      else
1060      {
1061        throw new IllegalStateException("Unknown object type: " + v);
1062      }
1063    }
1064    return "";
1065  }
1066
1067  private String getNotAvailableText(ServerDescriptor desc, boolean isRunning)
1068  {
1069    if (!isRunning)
1070    {
1071      return getNotAvailableBecauseServerIsDownText();
1072    }
1073    if (!desc.isAuthenticated() || !desc.getExceptions().isEmpty())
1074    {
1075      return getNotAvailableBecauseAuthenticationIsRequiredText();
1076    }
1077    return getNotAvailableText();
1078  }
1079
1080  private void writeLabelValue(final LocalizableMessage label, final String value, final int maxLabelWidth)
1081  {
1082    final LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
1083    buf.append(label);
1084
1085    int extra = maxLabelWidth - label.length();
1086    for (int i = 0; i<extra; i++)
1087    {
1088      buf.append(" ");
1089    }
1090    buf.append(" ").append(value);
1091    println(buf.toMessage());
1092  }
1093
1094  private LocalizableMessage centerTitle(final LocalizableMessage text)
1095  {
1096    if (text.length() <= MAX_LINE_WIDTH - 8)
1097    {
1098      final LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
1099      int extra = Math.min(10,
1100          (MAX_LINE_WIDTH - 8 - text.length()) / 2);
1101      for (int i=0; i<extra; i++)
1102      {
1103        buf.append(" ");
1104      }
1105      buf.append("--- ").append(text).append(" ---");
1106      return buf.toMessage();
1107    }
1108    return text;
1109  }
1110
1111  /**
1112   * Returns the trust manager to be used by this application.
1113   *
1114   * @return the trust manager to be used by this application.
1115   */
1116  private ApplicationTrustManager getTrustManager()
1117  {
1118    if (useInteractiveTrustManager)
1119    {
1120      return interactiveTrustManager;
1121    }
1122    return argParser.getTrustManager();
1123  }
1124
1125  /** {@inheritDoc} */
1126  @Override
1127  public boolean isAdvancedMode()
1128  {
1129    return false;
1130  }
1131
1132  /** {@inheritDoc} */
1133  @Override
1134  public boolean isInteractive() {
1135    return argParser.isInteractive();
1136  }
1137
1138  /** {@inheritDoc} */
1139  @Override
1140  public boolean isMenuDrivenMode() {
1141    return true;
1142  }
1143
1144  /** {@inheritDoc} */
1145  @Override
1146  public boolean isQuiet() {
1147    return false;
1148  }
1149
1150  /** {@inheritDoc} */
1151  @Override
1152  public boolean isScriptFriendly() {
1153    return argParser.isScriptFriendly();
1154  }
1155
1156  /** {@inheritDoc} */
1157  @Override
1158  public boolean isVerbose() {
1159    return true;
1160  }
1161
1162  /** FIXME Common code with DSConfigand tools*. This method needs to be moved. */
1163  private ManagementContext getManagementContextFromConnection(
1164      final LDAPConnectionConsoleInteraction ci) throws ClientException
1165  {
1166    // Interact with the user though the console to get
1167    // LDAP connection information
1168    final String hostName = getHostNameForLdapUrl(ci.getHostName());
1169    final Integer portNumber = ci.getPortNumber();
1170    final String bindDN = ci.getBindDN();
1171    final String bindPassword = ci.getBindPassword();
1172    TrustManager trustManager = ci.getTrustManager();
1173    final KeyManager keyManager = ci.getKeyManager();
1174
1175    // This connection should always be secure. useSSL = true.
1176    Connection connection = null;
1177    final Options options = Options.defaultOptions();
1178    options.set(CONNECT_TIMEOUT, new Duration((long) ci.getConnectTimeout(), TimeUnit.MILLISECONDS));
1179    LDAPConnectionFactory factory = null;
1180    while (true)
1181    {
1182      try
1183      {
1184        final SSLContextBuilder sslBuilder = new SSLContextBuilder();
1185        sslBuilder.setTrustManager(trustManager == null ? TrustManagers.trustAll() : trustManager);
1186        sslBuilder.setKeyManager(keyManager);
1187        options.set(SSL_USE_STARTTLS, ci.useStartTLS());
1188        options.set(SSL_CONTEXT, sslBuilder.getSSLContext());
1189
1190        factory = new LDAPConnectionFactory(hostName, portNumber, options);
1191        connection = factory.getConnection();
1192        connection.bind(bindDN, bindPassword.toCharArray());
1193        break;
1194      }
1195      catch (LdapException e)
1196      {
1197        if (ci.isTrustStoreInMemory() && e.getCause() instanceof SSLException
1198            && e.getCause().getCause() instanceof CertificateException)
1199        {
1200          String authType = null;
1201          if (trustManager instanceof ApplicationTrustManager)
1202          { // FIXME use PromptingTrustManager
1203            ApplicationTrustManager appTrustManager =
1204                (ApplicationTrustManager) trustManager;
1205            authType = appTrustManager.getLastRefusedAuthType();
1206            X509Certificate[] cert = appTrustManager.getLastRefusedChain();
1207
1208            if (ci.checkServerCertificate(cert, authType, hostName))
1209            {
1210              // If the certificate is trusted, update the trust manager.
1211              trustManager = ci.getTrustManager();
1212              // Try to connect again.
1213              continue;
1214            }
1215          }
1216        }
1217        if (e.getCause() instanceof SSLException)
1218        {
1219          LocalizableMessage message =
1220              ERR_FAILED_TO_CONNECT_NOT_TRUSTED.get(hostName, portNumber);
1221          throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
1222              message);
1223        }
1224        if (e.getCause() instanceof AuthorizationException)
1225        {
1226          throw new ClientException(ReturnCode.AUTH_METHOD_NOT_SUPPORTED,
1227              ERR_SIMPLE_BIND_NOT_SUPPORTED.get());
1228        }
1229        else if (e.getCause() instanceof AuthenticationException
1230            || e.getResult().getResultCode() == ResultCode.INVALID_CREDENTIALS)
1231        {
1232          // Status Cli must not fail when un-authenticated.
1233          return null;
1234        }
1235        throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
1236            ERR_FAILED_TO_CONNECT.get(hostName, portNumber));
1237      }
1238      catch (GeneralSecurityException e)
1239      {
1240        LocalizableMessage message =
1241            ERR_FAILED_TO_CONNECT.get(hostName, portNumber);
1242        throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR, message);
1243      }
1244      finally
1245      {
1246        closeSilently(factory, connection);
1247      }
1248    }
1249
1250    return LDAPManagementContext.newManagementContext(connection, LDAPProfile.getInstance());
1251  }
1252}