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}