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