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