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 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.guitools.uninstaller;
018
019import static org.forgerock.util.Utils.*;
020import static org.opends.admin.ads.util.PreferredConnection.Type.*;
021import static org.opends.messages.AdminToolMessages.*;
022import static org.opends.messages.QuickSetupMessages.*;
023
024import static com.forgerock.opendj.cli.ArgumentConstants.*;
025import static com.forgerock.opendj.cli.Utils.*;
026
027import java.io.BufferedReader;
028import java.io.File;
029import java.io.FileReader;
030import java.io.IOException;
031import java.net.URI;
032import java.util.Collections;
033import java.util.HashSet;
034import java.util.LinkedHashSet;
035import java.util.Set;
036
037import javax.naming.NamingException;
038import javax.net.ssl.TrustManager;
039
040import org.forgerock.i18n.LocalizableMessage;
041import org.forgerock.i18n.LocalizableMessageBuilder;
042import org.forgerock.i18n.slf4j.LocalizedLogger;
043import org.opends.admin.ads.ADSContext;
044import org.opends.admin.ads.ServerDescriptor;
045import org.opends.admin.ads.TopologyCache;
046import org.opends.admin.ads.TopologyCacheException;
047import org.opends.admin.ads.util.ApplicationTrustManager;
048import org.opends.admin.ads.util.ConnectionWrapper;
049import org.opends.admin.ads.util.PreferredConnection.Type;
050import org.opends.guitools.controlpanel.datamodel.ConnectionProtocolPolicy;
051import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
052import org.opends.quicksetup.Application;
053import org.opends.quicksetup.ApplicationException;
054import org.opends.quicksetup.Configuration;
055import org.opends.quicksetup.Constants;
056import org.opends.quicksetup.Installation;
057import org.opends.quicksetup.ProgressStep;
058import org.opends.quicksetup.Step;
059import org.opends.quicksetup.UserDataException;
060import org.opends.quicksetup.event.ProgressUpdateEvent;
061import org.opends.quicksetup.event.ProgressUpdateListener;
062import org.opends.quicksetup.util.PlainTextProgressMessageFormatter;
063import org.opends.quicksetup.util.ServerController;
064import org.opends.quicksetup.util.Utils;
065import org.opends.server.admin.client.cli.SecureConnectionCliArgs;
066import org.opends.server.types.HostPort;
067import org.opends.server.util.StaticUtils;
068import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
069
070import com.forgerock.opendj.cli.ArgumentException;
071import com.forgerock.opendj.cli.ClientException;
072import com.forgerock.opendj.cli.ConsoleApplication;
073import com.forgerock.opendj.cli.IntegerArgument;
074import com.forgerock.opendj.cli.Menu;
075import com.forgerock.opendj.cli.MenuBuilder;
076import com.forgerock.opendj.cli.MenuResult;
077import com.forgerock.opendj.cli.ReturnCode;
078import com.forgerock.opendj.cli.StringArgument;
079
080/**
081 * The class used to provide some CLI interface in the uninstall.
082 *
083 * This class basically is in charge of parsing the data provided by the user
084 * in the command line and displaying messages asking the user for information.
085 *
086 * Once the user has provided all the required information it calls Uninstaller
087 * and launches it.
088 */
089public class UninstallCliHelper extends ConsoleApplication {
090  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
091
092  private UninstallerArgumentParser parser;
093  private LDAPConnectionConsoleInteraction ci;
094  private ControlPanelInfo info;
095
096  private boolean forceNonInteractive;
097  private Type connectionType = LDAPS;
098
099  /** Default constructor. */
100  public UninstallCliHelper()
101  {
102    // Nothing to do.
103  }
104
105  /**
106   * Creates a UserData based in the arguments provided. It asks user for
107   * additional information if what is provided in the arguments is not enough.
108   *
109   * @param args
110   *          the ArgumentParser with the allowed arguments of the command line.
111   *          The code assumes that the arguments have already been parsed.
112   * @return the UserData object with what the user wants to uninstall and null
113   *         if the user cancels the uninstallation.
114   * @throws UserDataException
115   *           if there is an error with the data in the arguments.
116   * @throws ClientException
117   *           If there is an error processing data in non-interactive mode and
118   *           an error must be thrown (not in force on error mode).
119   */
120  public UninstallUserData createUserData(UninstallerArgumentParser args)
121      throws UserDataException, ClientException
122  {
123    parser = args;
124    UninstallUserData userData = new UninstallUserData();
125    try
126    {
127      boolean isInteractive;
128      boolean isQuiet;
129      boolean isVerbose;
130      boolean isCanceled = false;
131
132      /* Step 1: analyze the arguments. */
133
134      isInteractive = args.isInteractive();
135
136      isQuiet = args.isQuiet();
137
138      isVerbose = args.isVerbose();
139
140      userData.setQuiet(isQuiet);
141      userData.setVerbose(isVerbose);
142      userData.setForceOnError(args.isForceOnError());
143      userData.setTrustManager(args.getTrustManager());
144
145      userData.setConnectTimeout(getConnectTimeout());
146
147      /* Step 2: check that the provided parameters are compatible. */
148      LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
149      int v = args.validateGlobalOptions(buf);
150      if (v != ReturnCode.SUCCESS.get())
151      {
152        throw new UserDataException(null, buf.toMessage());
153      }
154
155      /* Step 3: If this is an interactive uninstall ask for confirmation to
156       * delete the different parts of the installation if the user did not
157       * specify anything to delete.  If we are not in interactive mode
158       * check that the user specified something to be deleted.
159       */
160      Set<String> outsideDbs;
161      Set<String> outsideLogs;
162      Configuration config =
163        Installation.getLocal().getCurrentConfiguration();
164      try {
165        outsideDbs = config.getOutsideDbs();
166      } catch (IOException ioe) {
167        outsideDbs = Collections.emptySet();
168        logger.info(LocalizableMessage.raw("error determining outside databases", ioe));
169      }
170
171      try {
172        outsideLogs = config.getOutsideLogs();
173      } catch (IOException ioe) {
174        outsideLogs = Collections.emptySet();
175        logger.info(LocalizableMessage.raw("error determining outside logs", ioe));
176      }
177
178      boolean somethingSpecifiedToDelete =
179        args.removeAll() ||
180        args.removeBackupFiles() ||
181        args.removeDatabases() ||
182        args.removeLDIFFiles() ||
183        args.removeConfigurationFiles() ||
184        args.removeLogFiles() ||
185        args.removeServerLibraries();
186
187      if (somethingSpecifiedToDelete)
188      {
189        userData.setRemoveBackups(args.removeAll() || args.removeBackupFiles());
190        userData.setRemoveConfigurationAndSchema(args.removeAll() ||
191            args.removeConfigurationFiles());
192        userData.setRemoveDatabases(args.removeAll() || args.removeDatabases());
193        userData.setRemoveLDIFs(args.removeAll() || args.removeLDIFFiles());
194        userData.setRemoveLibrariesAndTools(args.removeAll() ||
195            args.removeServerLibraries());
196        userData.setRemoveLogs(args.removeAll() || args.removeLogFiles());
197
198        userData.setExternalDbsToRemove(outsideDbs);
199        userData.setExternalLogsToRemove(outsideLogs);
200      }
201      else if (!isInteractive)
202      {
203        throw new UserDataException(null,
204           ERR_CLI_UNINSTALL_NOTHING_TO_BE_UNINSTALLED_NON_INTERACTIVE.get());
205      }
206      else
207      {
208        isCanceled = askWhatToDelete(userData, outsideDbs, outsideLogs);
209      }
210      String adminUid = args.getAdministratorUID();
211      if (adminUid == null && !args.isInteractive())
212      {
213        adminUid = args.getDefaultAdministratorUID();
214      }
215      userData.setAdminUID(adminUid);
216      userData.setAdminPwd(args.getBindPassword());
217      String referencedHostName = args.getReferencedHostName();
218      if (referencedHostName == null && !args.isInteractive())
219      {
220        referencedHostName = args.getDefaultReferencedHostName();
221      }
222      try
223      {
224        UninstallData d = new UninstallData(Installation.getLocal());
225        userData.setReplicationServer(
226            referencedHostName+":"+d.getReplicationServerPort());
227      }
228      catch (Throwable t)
229      {
230        logger.error(LocalizableMessage.raw("Could not create UninstallData: "+t, t));
231        userData.setReplicationServer(
232            referencedHostName+":8989");
233      }
234      info = ControlPanelInfo.getInstance();
235      info.setTrustManager(userData.getTrustManager());
236      info.setConnectTimeout(getConnectTimeout());
237      info.regenerateDescriptor();
238      info.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
239      String adminConnectorUrl = info.getAdminConnectorURL();
240      if (adminConnectorUrl == null)
241      {
242        logger.warn(LocalizableMessage.raw(
243        "Error retrieving a valid LDAP URL in conf file."));
244        if (!parser.isInteractive())
245        {
246          LocalizableMessage msg = ERR_COULD_NOT_FIND_VALID_LDAPURL.get();
247          throw new ClientException(ReturnCode.APPLICATION_ERROR, msg);
248        }
249      }
250      userData.setLocalServerUrl(adminConnectorUrl);
251      userData.setReferencedHostName(referencedHostName);
252
253      /*
254       * Step 4: check if server is running.  Depending if it is running and the
255       * OS we are running, ask for authentication information.
256       */
257      if (!isCanceled)
258      {
259        isCanceled = checkServerState(userData);
260        if (isCanceled && !userData.isForceOnError())
261        {
262          logger.info(LocalizableMessage.raw("User cancelled uninstall."));
263          userData = null;
264        }
265      }
266
267      if (userData != null && !args.isQuiet())
268      {
269        println();
270      }
271    }
272    catch (Throwable t)
273    {
274      logger.warn(LocalizableMessage.raw("Exception: "+t, t));
275      if (t instanceof UserDataException)
276      {
277        throw (UserDataException)t;
278      }
279      else if (t instanceof ClientException)
280      {
281        throw (ClientException)t;
282      }
283      else
284      {
285        throw new IllegalStateException("Unexpected error: "+t, t);
286      }
287    }
288    logger.info(LocalizableMessage.raw("Successfully created user data"));
289    return userData;
290  }
291
292  /**
293   * Commodity method used to ask the user to confirm the deletion of certain
294   * parts of the server.  It updates the provided UserData object
295   * accordingly.  Returns <CODE>true</CODE> if the user cancels and <CODE>
296   * false</CODE> otherwise.
297   * @param userData the UserData object to be updated.
298   * @param outsideDbs the set of relative paths of databases located outside
299   * the installation path of the server.
300   * @param outsideLogs the set of relative paths of log files located outside
301   * the installation path of the server.
302   * @return <CODE>true</CODE> if the user cancels and <CODE>false</CODE>
303   * otherwise.
304   */
305  private boolean askWhatToDelete(UninstallUserData userData,
306      Set<String> outsideDbs, Set<String> outsideLogs) throws UserDataException
307  {
308    boolean cancelled = false;
309    final int REMOVE_ALL = 1;
310    final int SPECIFY_TO_REMOVE = 2;
311    int[] indexes = {REMOVE_ALL, SPECIFY_TO_REMOVE};
312    LocalizableMessage[] msgs = new LocalizableMessage[] {
313        INFO_CLI_UNINSTALL_REMOVE_ALL.get(),
314        INFO_CLI_UNINSTALL_SPECIFY_WHAT_REMOVE.get()
315      };
316
317    MenuBuilder<Integer> builder = new MenuBuilder<>(this);
318    builder.setPrompt(INFO_CLI_UNINSTALL_WHAT_TO_DELETE.get());
319
320    for (int i=0; i<indexes.length; i++)
321    {
322      builder.addNumberedOption(msgs[i], MenuResult.success(indexes[i]));
323    }
324
325    builder.addQuitOption();
326
327    builder.setDefault(LocalizableMessage.raw(String.valueOf(REMOVE_ALL)),
328        MenuResult.success(REMOVE_ALL));
329
330    builder.setMaxTries(CONFIRMATION_MAX_TRIES);
331
332    Menu<Integer> menu = builder.toMenu();
333    int choice;
334    try
335    {
336      MenuResult<Integer> m = menu.run();
337      if (m.isSuccess())
338      {
339        choice = m.getValue();
340      }
341      else if (m.isQuit())
342      {
343        choice = REMOVE_ALL;
344        cancelled = true;
345      }
346      else
347      {
348        // Should never happen.
349        throw new RuntimeException();
350      }
351    }
352    catch (ClientException ce)
353    {
354      logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
355      throw new UserDataException(null, ce.getMessageObject(), ce);
356    }
357
358    if (cancelled)
359    {
360      // Nothing to do
361    }
362    else if (choice == REMOVE_ALL)
363    {
364      userData.setRemoveBackups(true);
365      userData.setRemoveConfigurationAndSchema(true);
366      userData.setRemoveDatabases(true);
367      userData.setRemoveLDIFs(true);
368      userData.setRemoveLibrariesAndTools(true);
369      userData.setRemoveLogs(true);
370
371      userData.setExternalDbsToRemove(outsideDbs);
372      userData.setExternalLogsToRemove(outsideLogs);
373    }
374    else
375    {
376      boolean somethingSelected = false;
377      while (!somethingSelected && !cancelled)
378      {
379        println();
380//      Ask for confirmation for the different items
381        msgs = new LocalizableMessage [] {
382                INFO_CLI_UNINSTALL_CONFIRM_LIBRARIES_BINARIES.get(),
383                INFO_CLI_UNINSTALL_CONFIRM_DATABASES.get(),
384                INFO_CLI_UNINSTALL_CONFIRM_LOGS.get(),
385                INFO_CLI_UNINSTALL_CONFIRM_CONFIGURATION_SCHEMA.get(),
386                INFO_CLI_UNINSTALL_CONFIRM_BACKUPS.get(),
387                INFO_CLI_UNINSTALL_CONFIRM_LDIFS.get(),
388                INFO_CLI_UNINSTALL_CONFIRM_OUTSIDEDBS.get(
389                        joinAsString(Constants.LINE_SEPARATOR, outsideDbs)),
390                INFO_CLI_UNINSTALL_CONFIRM_OUTSIDELOGS.get(
391                        joinAsString(Constants.LINE_SEPARATOR, outsideLogs)
392                )
393        };
394
395        boolean[] answers = new boolean[msgs.length];
396        try
397        {
398          for (int i=0; i<msgs.length; i++)
399          {
400            boolean ignore = (i == 6 && outsideDbs.isEmpty())
401                || (i == 7 && outsideLogs.isEmpty());
402            if (!ignore)
403            {
404              answers[i] = askConfirmation(msgs[i], true, logger);
405            }
406            else
407            {
408              answers[i] = false;
409            }
410          }
411        }
412        catch (ClientException ce)
413        {
414          throw new UserDataException(null, ce.getMessageObject(), ce);
415        }
416
417        if (!cancelled)
418        {
419          for (int i=0; i<answers.length; i++)
420          {
421            switch (i)
422            {
423            case 0:
424              userData.setRemoveLibrariesAndTools(answers[i]);
425              break;
426
427            case 1:
428              userData.setRemoveDatabases(answers[i]);
429              break;
430
431            case 2:
432              userData.setRemoveLogs(answers[i]);
433              break;
434
435            case 3:
436              userData.setRemoveConfigurationAndSchema(answers[i]);
437              break;
438
439            case 4:
440              userData.setRemoveBackups(answers[i]);
441              break;
442
443            case 5:
444              userData.setRemoveLDIFs(answers[i]);
445              break;
446
447            case 6:
448              if (answers[i])
449              {
450                userData.setExternalDbsToRemove(outsideDbs);
451              }
452              break;
453
454            case 7:
455              if (answers[i])
456              {
457                userData.setExternalLogsToRemove(outsideLogs);
458              }
459              break;
460            }
461          }
462          if (userData.getExternalDbsToRemove().isEmpty() &&
463              userData.getExternalLogsToRemove().isEmpty() &&
464              !userData.getRemoveLibrariesAndTools() &&
465              !userData.getRemoveDatabases() &&
466              !userData.getRemoveConfigurationAndSchema() &&
467              !userData.getRemoveBackups() &&
468              !userData.getRemoveLDIFs() &&
469              !userData.getRemoveLogs())
470          {
471            somethingSelected = false;
472            println();
473            printErrorMessage(
474                ERR_CLI_UNINSTALL_NOTHING_TO_BE_UNINSTALLED.get());
475          }
476          else
477          {
478            somethingSelected = true;
479          }
480        }
481      }
482    }
483
484    return cancelled;
485  }
486
487  /**
488   * Commodity method used to ask the user (when necessary) if the server must
489   * be stopped or not. It also prompts (if required) for authentication.
490   *
491   * @param userData
492   *          the UserData object to be updated with the authentication of the
493   *          user.
494   * @return <CODE>true</CODE> if the user wants to continue with uninstall and
495   *         <CODE>false</CODE> otherwise.
496   * @throws UserDataException
497   *           if there is a problem with the data provided by the user (in the
498   *           particular case where we are on non-interactive uninstall and
499   *           some data is missing or not valid).
500   * @throws ClientException
501   *           If there is an error processing data in non-interactive mode and
502   *           an error must be thrown (not in force on error mode).
503   */
504  private boolean checkServerState(UninstallUserData userData)
505  throws UserDataException, ClientException
506  {
507    boolean cancelled = false;
508    boolean interactive = parser.isInteractive();
509    boolean forceOnError = parser.isForceOnError();
510    UninstallData conf = null;
511    try
512    {
513      conf = new UninstallData(Installation.getLocal());
514    }
515    catch (Throwable t)
516    {
517      logger.warn(LocalizableMessage.raw("Error processing task: "+t, t));
518      throw new UserDataException(Step.CONFIRM_UNINSTALL,
519          getThrowableMsg(INFO_BUG_MSG.get(), t));
520    }
521    logger.info(LocalizableMessage.raw("interactive: "+interactive));
522    logger.info(LocalizableMessage.raw("forceOnError: "+forceOnError));
523    logger.info(LocalizableMessage.raw("conf.isADS(): "+conf.isADS()));
524    logger.info(LocalizableMessage.raw("conf.isReplicationServer(): "+
525        conf.isReplicationServer()));
526    logger.info(LocalizableMessage.raw("conf.isServerRunning(): "+conf.isServerRunning()));
527    if (conf.isADS() && conf.isReplicationServer())
528    {
529      if (conf.isServerRunning())
530      {
531        if (interactive)
532        {
533          try
534          {
535            println();
536            if (confirmToUpdateRemote())
537            {
538              cancelled = !askForAuthenticationIfNeeded(userData);
539              if (cancelled)
540              {
541                /* Ask for confirmation to stop server */
542                println();
543                cancelled = !confirmToStopServer();
544              }
545              else
546              {
547                cancelled = !updateUserUninstallDataWithRemoteServers(userData);
548                if (cancelled)
549                {
550                  println();
551                  /* Ask for confirmation to stop server */
552                  cancelled = !confirmToStopServer();
553                }
554              }
555            }
556            else
557            {
558              /* Ask for confirmation to stop server */
559              cancelled = !confirmToStopServer();
560            }
561          }
562          catch (ClientException ce)
563          {
564            throw new UserDataException(null, ce.getMessageObject(), ce);
565          }
566        }
567        else
568        {
569          boolean errorWithRemote =
570            !updateUserUninstallDataWithRemoteServers(userData);
571          cancelled = errorWithRemote && !parser.isForceOnError();
572          logger.info(LocalizableMessage.raw("Non interactive mode.  errorWithRemote: "+
573              errorWithRemote));
574        }
575      }
576      else if (interactive)
577      {
578        println();
579        try
580        {
581          if (confirmToUpdateRemoteAndStart())
582          {
583            boolean startWorked = startServer(userData.isQuiet());
584            // Ask for authentication if needed, etc.
585            if (startWorked)
586            {
587              cancelled = !askForAuthenticationIfNeeded(userData);
588              if (cancelled)
589              {
590                println();
591                /* Ask for confirmation to stop server */
592                cancelled = !confirmToStopServer();
593              }
594              else
595              {
596                cancelled =
597                  !updateUserUninstallDataWithRemoteServers(userData);
598                if (cancelled)
599                {
600                  println();
601                  /* Ask for confirmation to stop server */
602                  cancelled = !confirmToStopServer();
603                }
604              }
605              userData.setStopServer(true);
606            }
607            else
608            {
609              userData.setStopServer(false);
610              println();
611              /* Ask for confirmation to delete files */
612              cancelled = !confirmDeleteFiles();
613            }
614          }
615          else
616          {
617            println();
618            /* Ask for confirmation to delete files */
619            cancelled = !confirmDeleteFiles();
620          }
621        }
622        catch (ClientException ce)
623        {
624          throw new UserDataException(null, ce.getMessageObject(), ce);
625        }
626      }
627      else
628      {
629        boolean startWorked = startServer(userData.isQuiet());
630        // Ask for authentication if needed, etc.
631        if (startWorked)
632        {
633          userData.setStopServer(true);
634          boolean errorWithRemote =
635            !updateUserUninstallDataWithRemoteServers(userData);
636          cancelled = errorWithRemote && !parser.isForceOnError();
637        }
638        else
639        {
640          cancelled  = !forceOnError;
641          userData.setStopServer(false);
642        }
643      }
644      if (!cancelled || parser.isForceOnError())
645      {
646        /* During all the confirmations, the server might be stopped. */
647        userData.setStopServer(
648            Installation.getLocal().getStatus().isServerRunning());
649        logger.info(LocalizableMessage.raw("Must stop the server after confirmations? "+
650            userData.getStopServer()));
651      }
652    }
653    else if (conf.isServerRunning())
654    {
655      try
656      {
657        if (interactive)
658        {
659          println();
660          /* Ask for confirmation to stop server */
661          cancelled = !confirmToStopServer();
662        }
663
664        if (!cancelled)
665        {
666          /* During all the confirmations, the server might be stopped. */
667          userData.setStopServer(
668              Installation.getLocal().getStatus().isServerRunning());
669          logger.info(LocalizableMessage.raw("Must stop the server after confirmations? "+
670              userData.getStopServer()));
671        }
672      }
673      catch (ClientException ce)
674      {
675        throw new UserDataException(null, ce.getMessageObject(), ce);
676      }
677    }
678    else
679    {
680      userData.setStopServer(false);
681      if (interactive)
682      {
683        println();
684        /* Ask for confirmation to delete files */
685        try
686        {
687          cancelled = !confirmDeleteFiles();
688        }
689        catch (ClientException ce)
690        {
691          throw new UserDataException(null, ce.getMessageObject(), ce);
692        }
693      }
694    }
695    logger.info(LocalizableMessage.raw("cancelled: "+cancelled));
696    return cancelled;
697  }
698
699  /**
700   *  Ask for confirmation to stop server.
701   *  @return <CODE>true</CODE> if the user wants to continue and stop the
702   *  server.  <CODE>false</CODE> otherwise.
703   *  @throws ClientException if the user reached the confirmation limit.
704   */
705  private boolean confirmToStopServer() throws ClientException
706  {
707    return askConfirmation(INFO_CLI_UNINSTALL_CONFIRM_STOP.get(), true, logger);
708  }
709
710  /**
711   *  Ask for confirmation to delete files.
712   *  @return <CODE>true</CODE> if the user wants to continue and delete the
713   *  files.  <CODE>false</CODE> otherwise.
714   *  @throws ClientException if the user reached the confirmation limit.
715   */
716  private boolean confirmDeleteFiles() throws ClientException
717  {
718    return askConfirmation(INFO_CLI_UNINSTALL_CONFIRM_DELETE_FILES.get(), true,
719        logger);
720  }
721
722  /**
723   *  Ask for confirmation to update configuration on remote servers.
724   *  @return <CODE>true</CODE> if the user wants to continue and stop the
725   *  server.  <CODE>false</CODE> otherwise.
726   *  @throws ClientException if the user reached the confirmation limit.
727   */
728  private boolean confirmToUpdateRemote() throws ClientException
729  {
730    return askConfirmation(INFO_CLI_UNINSTALL_CONFIRM_UPDATE_REMOTE.get(), true,
731        logger);
732  }
733
734  /**
735   *  Ask for confirmation to update configuration on remote servers.
736   *  @return <CODE>true</CODE> if the user wants to continue and stop the
737   *  server.  <CODE>false</CODE> otherwise.
738   *  @throws ClientException if the user reached the confirmation limit.
739   */
740  private boolean confirmToUpdateRemoteAndStart() throws ClientException
741  {
742    return askConfirmation(
743        INFO_CLI_UNINSTALL_CONFIRM_UPDATE_REMOTE_AND_START.get(), true, logger);
744  }
745
746  /**
747   *  Ask for confirmation to provide again authentication.
748   *  @return <CODE>true</CODE> if the user wants to provide authentication
749   *  again.  <CODE>false</CODE> otherwise.
750   *  @throws ClientException if the user reached the confirmation limit.
751   */
752  private boolean promptToProvideAuthenticationAgain() throws ClientException
753  {
754    return askConfirmation(
755        INFO_UNINSTALL_CONFIRM_PROVIDE_AUTHENTICATION_AGAIN.get(), true, logger);
756  }
757
758  /**
759   * Ask for data required to update configuration on remote servers. If all the
760   * data is provided and validated, we assume that the user wants to update the
761   * remote servers.
762   *
763   * @return <CODE>true</CODE> if the user wants to continue and update the
764   *         remote servers. <CODE>false</CODE> otherwise.
765   * @throws UserDataException
766   *           if there is a problem with the information provided by the user.
767   * @throws ClientException
768   *           If there is an error processing data.
769   */
770  private boolean askForAuthenticationIfNeeded(UninstallUserData userData)
771  throws UserDataException, ClientException
772  {
773    boolean accepted = true;
774    String uid = userData.getAdminUID();
775    String pwd = userData.getAdminPwd();
776
777    boolean couldConnect = false;
778
779    while (!couldConnect && accepted)
780    {
781      // This is done because we do not need to ask the user about these parameters.
782      // If we force their presence the class LDAPConnectionConsoleInteraction will not prompt the user for them.
783      SecureConnectionCliArgs secureArgsList = parser.getSecureArgsList();
784
785      StringArgument hostNameArg = secureArgsList.getHostNameArg();
786      hostNameArg.setPresent(true);
787      hostNameArg.clearValues();
788      hostNameArg.addValue(hostNameArg.getDefaultValue());
789
790      IntegerArgument portArg = secureArgsList.getPortArg();
791      portArg.setPresent(true);
792      portArg.clearValues();
793      portArg.addValue(portArg.getDefaultValue());
794
795      StringArgument bindDnArg = secureArgsList.getBindDnArg();
796      bindDnArg.clearValues();
797      if (uid != null)
798      {
799        bindDnArg.addValue(ADSContext.getAdministratorDN(uid));
800        bindDnArg.setPresent(true);
801      }
802      else
803      {
804        bindDnArg.setPresent(false);
805      }
806
807      StringArgument bindPasswordArg = secureArgsList.getBindPasswordArg();
808      bindPasswordArg.clearValues();
809      if (pwd != null)
810      {
811        bindPasswordArg.addValue(pwd);
812        bindPasswordArg.setPresent(true);
813      }
814      else
815      {
816        bindPasswordArg.setPresent(false);
817      }
818
819      if (ci == null)
820      {
821        ci = new LDAPConnectionConsoleInteraction(this, parser.getSecureArgsList());
822        ci.setDisplayLdapIfSecureParameters(true);
823      }
824
825      try
826      {
827        ci.run(false);
828        userData.setAdminUID(ci.getAdministratorUID());
829        userData.setAdminPwd(ci.getBindPassword());
830
831        info.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
832        String adminConnectorUrl = info.getAdminConnectorURL();
833        if (adminConnectorUrl == null)
834        {
835          logger.warn(LocalizableMessage.raw("Error retrieving a valid Administration Connector URL in conf file."));
836          LocalizableMessage msg = ERR_COULD_NOT_FIND_VALID_LDAPURL.get();
837          throw new ClientException(ReturnCode.APPLICATION_ERROR, msg);
838        }
839        try
840        {
841          URI uri = new URI(adminConnectorUrl);
842          int port = uri.getPort();
843          portArg.clearValues();
844          portArg.addValue(String.valueOf(port));
845          ci.setPortNumber(port);
846        }
847        catch (Throwable t)
848        {
849          logger.error(LocalizableMessage.raw("Error parsing url: "+adminConnectorUrl));
850        }
851        updateTrustManager(userData, ci);
852
853        info.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
854        adminConnectorUrl = info.getAdminConnectorURL();
855        if (adminConnectorUrl == null)
856        {
857          logger.warn(LocalizableMessage.raw(
858         "Error retrieving a valid Administration Connector URL in conf file."));
859          LocalizableMessage msg = ERR_COULD_NOT_FIND_VALID_LDAPURL.get();
860          throw new ClientException(ReturnCode.APPLICATION_ERROR, msg);
861        }
862
863        userData.setLocalServerUrl(adminConnectorUrl);
864        couldConnect = true;
865      }
866      catch (ArgumentException e)
867      {
868        parser.displayMessageAndUsageReference(getErrStream(), e.getMessageObject());
869      }
870      catch (ClientException e) {
871        printErrorMessage(e.getMessageObject());
872        println();
873      }
874
875      if (!couldConnect)
876      {
877        try
878        {
879          accepted = promptToProvideAuthenticationAgain();
880          if (accepted)
881          {
882            uid = null;
883            pwd = null;
884          }
885        }
886        catch (ClientException ce)
887        {
888          throw new UserDataException(null, ce.getMessageObject(), ce);
889        }
890      }
891    }
892
893    if (accepted)
894    {
895      String referencedHostName = parser.getReferencedHostName();
896      while (referencedHostName == null)
897      {
898        println();
899        referencedHostName = askForReferencedHostName(userData.getHostName());
900      }
901      try
902      {
903        UninstallData d = new UninstallData(Installation.getLocal());
904        userData.setReplicationServer(
905            referencedHostName+":"+d.getReplicationServerPort());
906        userData.setReferencedHostName(referencedHostName);
907      }
908      catch (Throwable t)
909      {
910        logger.error(LocalizableMessage.raw("Could not create UninstallData: "+t, t));
911      }
912    }
913    userData.setUpdateRemoteReplication(accepted);
914    return accepted;
915  }
916
917  private String askForReferencedHostName(String defaultHostName)
918  {
919    String s = defaultHostName;
920    try
921    {
922      s = readInput(INFO_UNINSTALL_CLI_REFERENCED_HOSTNAME_PROMPT.get(),
923          defaultHostName);
924    }
925    catch (ClientException ce)
926    {
927      logger.warn(LocalizableMessage.raw("Error reading input: %s", ce), ce);
928    }
929    return s;
930  }
931
932  private boolean startServer(boolean suppressOutput)
933  {
934    logger.info(LocalizableMessage.raw("startServer, suppressOutput: " + suppressOutput));
935    boolean serverStarted = false;
936    Application application = new Application()
937    {
938      @Override
939      public String getInstallationPath()
940      {
941        return Installation.getLocal().getRootDirectory().getAbsolutePath();
942      }
943      @Override
944      public String getInstancePath()
945      {
946        String installPath =  getInstallationPath();
947
948        // look for <installPath>/lib/resource.loc
949        String instancePathFileName = installPath + File.separator + "lib"
950        + File.separator + "resource.loc";
951        File f = new File(instancePathFileName);
952        if (!f.exists())
953        {
954          return installPath;
955        }
956
957        // Read the first line and close the file.
958        try (BufferedReader reader = new BufferedReader(new FileReader(instancePathFileName)))
959        {
960          String line = reader.readLine();
961          return new File(line).getAbsolutePath();
962        }
963        catch (Exception e)
964        {
965          return installPath;
966        }
967      }
968      @Override
969      public ProgressStep getCurrentProgressStep()
970      {
971        return UninstallProgressStep.NOT_STARTED;
972      }
973      @Override
974      public Integer getRatio(ProgressStep step)
975      {
976        return 0;
977      }
978      @Override
979      public LocalizableMessage getSummary(ProgressStep step)
980      {
981        return null;
982      }
983      @Override
984      public boolean isFinished()
985      {
986        return false;
987      }
988      @Override
989      public boolean isCancellable()
990      {
991        return false;
992      }
993      @Override
994      public void cancel()
995      {
996        // no-op
997      }
998      @Override
999      public void run()
1000      {
1001        // no-op
1002      }
1003    };
1004    application.setProgressMessageFormatter(
1005        new PlainTextProgressMessageFormatter());
1006    if (!suppressOutput)
1007    {
1008      application.addProgressUpdateListener(
1009          new ProgressUpdateListener() {
1010            @Override
1011            public void progressUpdate(ProgressUpdateEvent ev) {
1012              System.out.print(ev.getNewLogs().toString());
1013              System.out.flush();
1014            }
1015          });
1016    }
1017    ServerController controller = new ServerController(application,
1018        Installation.getLocal());
1019    try
1020    {
1021      if (suppressOutput)
1022      {
1023        controller.startServer(true);
1024      }
1025      else
1026      {
1027        println();
1028        controller.startServer(false);
1029        println();
1030      }
1031      serverStarted = Installation.getLocal().getStatus().isServerRunning();
1032      logger.info(LocalizableMessage.raw("server started successfully. serverStarted: "+
1033          serverStarted));
1034    }
1035    catch (ApplicationException ae)
1036    {
1037      logger.warn(LocalizableMessage.raw("ApplicationException: "+ae, ae));
1038      if (!suppressOutput)
1039      {
1040        printErrorMessage(ae.getMessageObject());
1041      }
1042    }
1043    catch (Throwable t)
1044    {
1045      logger.error(LocalizableMessage.raw("Unexpected error: "+t, t));
1046      throw new IllegalStateException("Unexpected error: "+t, t);
1047    }
1048    return serverStarted;
1049  }
1050
1051  /**
1052   * Updates the contents of the UninstallUserData while trying to connect to
1053   * the remote servers. It returns <CODE>true</CODE> if we could connect to the
1054   * remote servers and all the presented certificates were accepted and
1055   * <CODE>false</CODE> otherwise. continue if
1056   *
1057   * @param userData
1058   *          the user data to be updated.
1059   * @return <CODE>true</CODE> if we could connect to the remote servers and all
1060   *         the presented certificates were accepted and <CODE>false</CODE>
1061   *         otherwise.
1062   * @throws UserDataException
1063   *           if were are not in interactive mode and not in force on error
1064   *           mode and the operation must be stopped.
1065   * @throws ClientException
1066   *           If there is an error processing data in non-interactive mode and
1067   *           an error must be thrown (not in force on error mode).
1068   */
1069  private boolean updateUserUninstallDataWithRemoteServers(
1070      UninstallUserData userData) throws UserDataException, ClientException
1071  {
1072    boolean accepted = false;
1073    boolean interactive = parser.isInteractive();
1074    boolean forceOnError = parser.isForceOnError();
1075
1076    boolean exceptionOccurred = true;
1077
1078    LocalizableMessage exceptionMsg = null;
1079
1080    logger.info(LocalizableMessage.raw("Updating user data with remote servers."));
1081
1082    ConnectionWrapper conn = null;
1083    try
1084    {
1085      info.setTrustManager(userData.getTrustManager());
1086      info.setConnectTimeout(getConnectTimeout());
1087      String host = "localhost";
1088      int port = 389;
1089      String adminUid = userData.getAdminUID();
1090      String pwd = userData.getAdminPwd();
1091      String dn = ADSContext.getAdministratorDN(adminUid);
1092
1093      info.setConnectionPolicy(ConnectionProtocolPolicy.USE_ADMIN);
1094      String adminConnectorUrl = info.getAdminConnectorURL();
1095      try
1096      {
1097        URI uri = new URI(adminConnectorUrl);
1098        host = uri.getHost();
1099        port = uri.getPort();
1100      }
1101      catch (Throwable t)
1102      {
1103        logger.error(LocalizableMessage.raw("Error parsing url: "+adminConnectorUrl));
1104      }
1105      conn = new ConnectionWrapper(new HostPort(host, port), connectionType, dn, pwd,
1106          getConnectTimeout(), userData.getTrustManager());
1107
1108      ADSContext adsContext = new ADSContext(conn);
1109      if (interactive && userData.getTrustManager() == null)
1110      {
1111        // This is required when the user did  connect to the server using SSL
1112        // or Start TLS in interactive mode.  In this case
1113        // LDAPConnectionInteraction.run does not initialize the keystore and
1114        // the trust manager is null.
1115        forceTrustManagerInitialization();
1116        updateTrustManager(userData, ci);
1117      }
1118      logger.info(LocalizableMessage.raw("Reloading topology"));
1119      TopologyCache cache = new TopologyCache(adsContext,
1120          userData.getTrustManager(), getConnectTimeout());
1121      cache.getFilter().setSearchMonitoringInformation(false);
1122      cache.reloadTopology();
1123
1124      accepted = handleTopologyCache(cache, userData);
1125
1126      exceptionOccurred = false;
1127    }
1128    catch (NamingException ne)
1129    {
1130      logger.warn(LocalizableMessage.raw("Error connecting to server: "+ne, ne));
1131      if (isCertificateException(ne))
1132      {
1133        String details = ne.getMessage() != null ?
1134            ne.getMessage() : ne.toString();
1135        exceptionMsg = INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE.get(details);
1136      }
1137      else
1138      {
1139        exceptionMsg = getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), ne);
1140      }
1141    } catch (TopologyCacheException te)
1142    {
1143      logger.warn(LocalizableMessage.raw("Error connecting to server: "+te, te));
1144      exceptionMsg = Utils.getMessage(te);
1145    } catch (ClientException ce)
1146    {
1147      throw ce;
1148    } catch (Throwable t)
1149    {
1150      logger.warn(LocalizableMessage.raw("Error connecting to server: "+t, t));
1151      exceptionMsg = getThrowableMsg(INFO_BUG_MSG.get(), t);
1152    }
1153    finally
1154    {
1155      StaticUtils.close(conn);
1156    }
1157    if (exceptionOccurred)
1158    {
1159      if (!interactive)
1160      {
1161        if (forceOnError)
1162        {
1163          println();
1164          printErrorMessage(ERR_UNINSTALL_ERROR_UPDATING_REMOTE_FORCE.get(
1165              "--" + parser.getSecureArgsList().getAdminUidArg().getLongIdentifier(),
1166              "--" + OPTION_LONG_BINDPWD,
1167              "--" + OPTION_LONG_BINDPWD_FILE,
1168              exceptionMsg));
1169        }
1170        else
1171        {
1172          println();
1173          throw new UserDataException(null,
1174              ERR_UNINSTALL_ERROR_UPDATING_REMOTE_NO_FORCE.get(
1175                  "--" + parser.getSecureArgsList().getAdminUidArg().getLongIdentifier(),
1176                  "--" + OPTION_LONG_BINDPWD,
1177                  "--" + OPTION_LONG_BINDPWD_FILE,
1178                  "--" + parser.forceOnErrorArg.getLongIdentifier(),
1179                  exceptionMsg));
1180        }
1181      }
1182      else
1183      {
1184        try
1185        {
1186          accepted = askConfirmation(
1187              ERR_UNINSTALL_NOT_UPDATE_REMOTE_PROMPT.get(),
1188              false, logger);
1189        }
1190        catch (ClientException ce)
1191        {
1192          throw new UserDataException(null, ce.getMessageObject(), ce);
1193        }
1194      }
1195    }
1196    userData.setUpdateRemoteReplication(accepted);
1197    logger.info(LocalizableMessage.raw("accepted: "+accepted));
1198    return accepted;
1199  }
1200
1201  /**
1202   * Method that interacts with the user depending on what errors where
1203   * encountered in the TopologyCache object.  This method assumes that the
1204   * TopologyCache has been reloaded.
1205   * Returns <CODE>true</CODE> if the user accepts all the problems encountered
1206   * and <CODE>false</CODE> otherwise.
1207   * @param userData the user data.
1208   * @throws UserDataException if there is an error with the information
1209   * provided by the user when we are in non-interactive mode.
1210   * @throws ClientException if there is an error processing data in
1211   * non-interactive mode and an error must be thrown (not in force on error
1212   * mode).
1213   */
1214  private boolean handleTopologyCache(TopologyCache cache,
1215      UninstallUserData userData) throws UserDataException, ClientException
1216  {
1217    boolean returnValue;
1218    boolean stopProcessing = false;
1219    boolean reloadTopologyCache = false;
1220
1221    logger.info(LocalizableMessage.raw("Handle topology cache."));
1222
1223    Set<TopologyCacheException> exceptions = new HashSet<>();
1224    /* Analyze if we had any exception while loading servers.  For the moment
1225     * only throw the exception found if the user did not provide the
1226     * Administrator DN and this caused a problem authenticating in one server
1227     * or if there is a certificate problem.
1228     */
1229    Set<ServerDescriptor> servers = cache.getServers();
1230    userData.setRemoteServers(servers);
1231    for (ServerDescriptor server : servers)
1232    {
1233      TopologyCacheException e = server.getLastException();
1234      if (e != null)
1235      {
1236        exceptions.add(e);
1237      }
1238    }
1239    Set<LocalizableMessage> exceptionMsgs = new LinkedHashSet<>();
1240    /* Check the exceptions and see if we throw them or not. */
1241    for (TopologyCacheException e : exceptions)
1242    {
1243      logger.info(LocalizableMessage.raw("Analyzing exception: "+e, e));
1244      if (stopProcessing)
1245      {
1246        break;
1247      }
1248      switch (e.getType())
1249      {
1250      case NOT_GLOBAL_ADMINISTRATOR:
1251        println();
1252        printErrorMessage(INFO_NOT_GLOBAL_ADMINISTRATOR_PROVIDED.get());
1253        stopProcessing = true;
1254        break;
1255      case GENERIC_CREATING_CONNECTION:
1256        if (isCertificateException(e.getCause()))
1257        {
1258          if (isInteractive())
1259          {
1260            println();
1261            stopProcessing = true;
1262            if (ci.promptForCertificateConfirmation(e.getCause(),
1263                e.getTrustManager(), e.getLdapUrl(), logger))
1264            {
1265              reloadTopologyCache = true;
1266              updateTrustManager(userData, ci);
1267            }
1268          }
1269          else
1270          {
1271            exceptionMsgs.add(
1272                INFO_ERROR_READING_CONFIG_LDAP_CERTIFICATE_SERVER.get(
1273                e.getHostPort(), e.getCause().getMessage()));
1274          }
1275        }
1276        else
1277        {
1278          exceptionMsgs.add(Utils.getMessage(e));
1279        }
1280        break;
1281      default:
1282        exceptionMsgs.add(Utils.getMessage(e));
1283      }
1284    }
1285    if (isInteractive())
1286    {
1287      if (!stopProcessing && !exceptionMsgs.isEmpty())
1288      {
1289        println();
1290        try
1291        {
1292          returnValue = askConfirmation(
1293            ERR_UNINSTALL_READING_REGISTERED_SERVERS_CONFIRM_UPDATE_REMOTE.get(
1294                Utils.getMessageFromCollection(exceptionMsgs,
1295                  Constants.LINE_SEPARATOR)), true, logger);
1296        }
1297        catch (ClientException ce)
1298        {
1299          throw new UserDataException(null, ce.getMessageObject(), ce);
1300        }
1301      }
1302      else if (reloadTopologyCache)
1303      {
1304       returnValue = updateUserUninstallDataWithRemoteServers(userData);
1305      }
1306      else
1307      {
1308        returnValue = !stopProcessing;
1309      }
1310    }
1311    else
1312    {
1313      logger.info(LocalizableMessage.raw("exceptionMsgs: "+exceptionMsgs));
1314      if (!exceptionMsgs.isEmpty())
1315      {
1316        if (parser.isForceOnError())
1317        {
1318          LocalizableMessage msg = Utils.getMessageFromCollection(exceptionMsgs,
1319              Constants.LINE_SEPARATOR);
1320          println();
1321          printErrorMessage(msg);
1322          returnValue = false;
1323        }
1324        else
1325        {
1326          LocalizableMessage msg =
1327            ERR_UNINSTALL_ERROR_UPDATING_REMOTE_NO_FORCE.get(
1328              "--" + parser.getSecureArgsList().getAdminUidArg().getLongIdentifier(),
1329              "--" + OPTION_LONG_BINDPWD,
1330              "--" + OPTION_LONG_BINDPWD_FILE,
1331              "--" + parser.forceOnErrorArg.getLongIdentifier(),
1332              Utils.getMessageFromCollection(exceptionMsgs, Constants.LINE_SEPARATOR));
1333          throw new ClientException(ReturnCode.APPLICATION_ERROR, msg);
1334        }
1335      }
1336      else
1337      {
1338        returnValue = true;
1339      }
1340    }
1341    logger.info(LocalizableMessage.raw("Return value: "+returnValue));
1342    return returnValue;
1343  }
1344
1345  @Override
1346  public boolean isAdvancedMode() {
1347    return false;
1348  }
1349
1350  @Override
1351  public boolean isInteractive() {
1352    return !forceNonInteractive && parser.isInteractive();
1353  }
1354
1355  @Override
1356  public boolean isMenuDrivenMode() {
1357    return true;
1358  }
1359
1360  @Override
1361  public boolean isQuiet() {
1362    return false;
1363  }
1364
1365  @Override
1366  public boolean isScriptFriendly() {
1367    return false;
1368  }
1369
1370  @Override
1371  public boolean isVerbose() {
1372    return true;
1373  }
1374
1375  /**
1376   * Commodity method to update the user data with the trust manager in the
1377   * LDAPConnectionConsoleInteraction object.
1378   * @param userData the user data to be updated.
1379   * @param ci the LDAPConnectionConsoleInteraction object to be used to update
1380   * the user data object.
1381   */
1382   private void updateTrustManager(UninstallUserData userData,
1383       LDAPConnectionConsoleInteraction ci)
1384   {
1385     ApplicationTrustManager trust = null;
1386     TrustManager t = ci.getTrustManager();
1387     if (t != null)
1388     {
1389       if (t instanceof ApplicationTrustManager)
1390       {
1391         trust = (ApplicationTrustManager)t;
1392       }
1393       else
1394       {
1395         trust = new ApplicationTrustManager(ci.getKeyStore());
1396       }
1397     }
1398     userData.setTrustManager(trust);
1399   }
1400
1401   /** Forces the initialization of the trust manager in the LDAPConnectionInteraction object. */
1402   private void forceTrustManagerInitialization()
1403   {
1404     forceNonInteractive = true;
1405     try
1406     {
1407       ci.initializeTrustManagerIfRequired();
1408     }
1409     catch (ArgumentException ae)
1410     {
1411       logger.warn(LocalizableMessage.raw("Error initializing trust store: "+ae, ae));
1412     }
1413     forceNonInteractive = false;
1414   }
1415
1416   private void printErrorMessage(LocalizableMessage msg)
1417   {
1418     super.println(msg);
1419     logger.warn(LocalizableMessage.raw(msg));
1420   }
1421
1422   /**
1423    * Returns the timeout to be used to connect in milliseconds.  The method
1424    * must be called after parsing the arguments.
1425    * @return the timeout to be used to connect in milliseconds.  Returns
1426    * {@code 0} if there is no timeout.
1427    * @throw {@code IllegalStateException} if the method is called before
1428    * parsing the arguments.
1429    */
1430   private int getConnectTimeout()
1431   {
1432     try
1433     {
1434       return parser.getSecureArgsList().getConnectTimeoutArg().getIntValue();
1435     }
1436     catch (ArgumentException ae)
1437     {
1438       throw new IllegalStateException("Argument parser is not parsed: "+ae,
1439           ae);
1440     }
1441   }
1442}