001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.extensions; 028 029 030 031 import java.io.File; 032 import java.io.FileInputStream; 033 import java.io.FileOutputStream; 034 import java.io.InputStream; 035 import java.io.IOException; 036 import java.io.OutputStream; 037 import java.security.MessageDigest; 038 import java.util.Arrays; 039 import java.util.Date; 040 import java.util.HashMap; 041 import java.util.HashSet; 042 import java.util.Iterator; 043 import java.util.LinkedHashMap; 044 import java.util.LinkedList; 045 import java.util.List; 046 import java.util.TreeSet; 047 import java.util.TreeMap; 048 import java.util.zip.Deflater; 049 import java.util.zip.GZIPInputStream; 050 import java.util.zip.GZIPOutputStream; 051 import java.util.zip.ZipEntry; 052 import java.util.zip.ZipInputStream; 053 import java.util.zip.ZipOutputStream; 054 import java.util.concurrent.ConcurrentHashMap; 055 import java.util.concurrent.CopyOnWriteArrayList; 056 import javax.crypto.Mac; 057 058 import org.opends.messages.Message; 059 import org.opends.messages.MessageBuilder; 060 import org.opends.server.admin.Configuration; 061 import org.opends.server.api.AlertGenerator; 062 import org.opends.server.api.ClientConnection; 063 import org.opends.server.api.ConfigAddListener; 064 import org.opends.server.api.ConfigChangeListener; 065 import org.opends.server.api.ConfigDeleteListener; 066 import org.opends.server.api.ConfigHandler; 067 import org.opends.server.config.ConfigEntry; 068 import org.opends.server.config.ConfigException; 069 import org.opends.server.core.AddOperation; 070 import org.opends.server.core.DeleteOperation; 071 import org.opends.server.core.DirectoryServer; 072 import org.opends.server.core.ModifyOperation; 073 import org.opends.server.core.ModifyDNOperation; 074 import org.opends.server.core.SearchOperation; 075 import org.opends.server.loggers.debug.DebugTracer; 076 import org.opends.server.protocols.asn1.ASN1OctetString; 077 import org.opends.server.schema.GeneralizedTimeSyntax; 078 import org.opends.server.tools.LDIFModify; 079 import org.opends.server.types.*; 080 import org.opends.server.util.DynamicConstants; 081 import org.opends.server.util.LDIFException; 082 import org.opends.server.util.LDIFReader; 083 import org.opends.server.util.LDIFWriter; 084 import org.opends.server.util.TimeThread; 085 086 import static org.opends.server.config.ConfigConstants.*; 087 import static org.opends.server.extensions.ExtensionsConstants.*; 088 import static org.opends.server.loggers.ErrorLogger.*; 089 import static org.opends.server.loggers.debug.DebugLogger.*; 090 import static org.opends.messages.ConfigMessages.*; 091 import static org.opends.server.util.ServerConstants.*; 092 import static org.opends.server.util.StaticUtils.*; 093 094 095 /** 096 * This class defines a simple configuration handler for the Directory Server 097 * that will read the server configuration from an LDIF file. 098 */ 099 public class ConfigFileHandler 100 extends ConfigHandler 101 implements AlertGenerator 102 { 103 /** 104 * The tracer object for the debug logger. 105 */ 106 private static final DebugTracer TRACER = getTracer(); 107 108 109 110 /** 111 * The fully-qualified name of this class. 112 */ 113 private static final String CLASS_NAME = 114 "org.opends.server.extensions.ConfigFileHandler"; 115 116 117 118 /** 119 * The set of supported control OIDs for this backend. 120 */ 121 private static final HashSet<String> SUPPORTED_CONTROLS = 122 new HashSet<String>(0); 123 124 125 126 /** 127 * The set of supported feature OIDs for this backend. 128 */ 129 private static final HashSet<String> SUPPORTED_FEATURES = 130 new HashSet<String>(0); 131 132 133 134 /** 135 * The privilege array containing both the CONFIG_READ and CONFIG_WRITE 136 * privileges. 137 */ 138 private static final Privilege[] CONFIG_READ_AND_WRITE = 139 { 140 Privilege.CONFIG_READ, 141 Privilege.CONFIG_WRITE 142 }; 143 144 145 146 // Indicates whether to maintain a configuration archive. 147 private boolean maintainConfigArchive; 148 149 // Indicates whether to start using the last known good configuration. 150 private boolean useLastKnownGoodConfig; 151 152 // A SHA-1 digest of the last known configuration. This should only be 153 // incorrect if the server configuration file has been manually edited with 154 // the server online, which is a bad thing. 155 private byte[] configurationDigest; 156 157 // The mapping that holds all of the configuration entries that have been read 158 // from the LDIF file. 159 private ConcurrentHashMap<DN,ConfigEntry> configEntries; 160 161 // The reference to the configuration root entry. 162 private ConfigEntry configRootEntry; 163 164 // The set of base DNs for this config handler backend. 165 private DN[] baseDNs; 166 167 // The maximum config archive size to maintain. 168 private int maxConfigArchiveSize; 169 170 // The write lock used to ensure that only one thread can apply a 171 // configuration update at any given time. 172 private Object configLock; 173 174 // The path to the configuration file. 175 private String configFile; 176 177 // The instance root directory for the Directory Server. 178 private String serverRoot; 179 180 181 182 /** 183 * Creates a new instance of this config file handler. No initialization 184 * should be performed here, as all of that work should be done in the 185 * <CODE>initializeConfigHandler</CODE> method. 186 */ 187 public ConfigFileHandler() 188 { 189 super(); 190 } 191 192 193 194 /** 195 * {@inheritDoc} 196 */ 197 @Override() 198 public void initializeConfigHandler(String configFile, boolean checkSchema) 199 throws InitializationException 200 { 201 // Initialize the config lock. 202 configLock = new Object(); 203 204 205 // Determine whether we should try to start using the last known good 206 // configuration. If so, then only do so if such a file exists. If it 207 // doesn't exist, then fall back on the active configuration file. 208 this.configFile = configFile; 209 DirectoryEnvironmentConfig envConfig = 210 DirectoryServer.getEnvironmentConfig(); 211 useLastKnownGoodConfig = envConfig.useLastKnownGoodConfiguration(); 212 File f = null; 213 if (useLastKnownGoodConfig) 214 { 215 f = new File(configFile + ".startok"); 216 if (! f.exists()) 217 { 218 logError(WARN_CONFIG_FILE_NO_STARTOK_FILE.get(f.getAbsolutePath(), 219 configFile)); 220 useLastKnownGoodConfig = false; 221 f = new File(configFile); 222 } 223 else 224 { 225 logError(NOTE_CONFIG_FILE_USING_STARTOK_FILE.get(f.getAbsolutePath(), 226 configFile)); 227 } 228 } 229 else 230 { 231 f = new File(configFile); 232 } 233 234 try 235 { 236 if (! f.exists()) 237 { 238 Message message = ERR_CONFIG_FILE_DOES_NOT_EXIST.get( 239 f.getAbsolutePath()); 240 throw new InitializationException(message); 241 } 242 } 243 catch (InitializationException ie) 244 { 245 if (debugEnabled()) 246 { 247 TRACER.debugCaught(DebugLogLevel.ERROR, ie); 248 } 249 250 throw ie; 251 } 252 catch (Exception e) 253 { 254 if (debugEnabled()) 255 { 256 TRACER.debugCaught(DebugLogLevel.ERROR, e); 257 } 258 259 Message message = ERR_CONFIG_FILE_CANNOT_VERIFY_EXISTENCE.get( 260 f.getAbsolutePath(), String.valueOf(e)); 261 throw new InitializationException(message); 262 } 263 264 265 // Check to see if a configuration archive exists. If not, then create one. 266 // If so, then check whether the current configuration matches the last 267 // configuration in the archive. If it doesn't, then archive it. 268 maintainConfigArchive = envConfig.maintainConfigArchive(); 269 maxConfigArchiveSize = envConfig.getMaxConfigArchiveSize(); 270 if (maintainConfigArchive & (! useLastKnownGoodConfig)) 271 { 272 try 273 { 274 configurationDigest = calculateConfigDigest(); 275 } 276 catch (DirectoryException de) 277 { 278 throw new InitializationException(de.getMessageObject(), de.getCause()); 279 } 280 281 File archiveDirectory = new File(f.getParent(), CONFIG_ARCHIVE_DIR_NAME); 282 if (archiveDirectory.exists()) 283 { 284 try 285 { 286 byte[] lastDigest = getLastConfigDigest(archiveDirectory); 287 if (! Arrays.equals(configurationDigest, lastDigest)) 288 { 289 writeConfigArchive(); 290 } 291 } catch (Exception e) {} 292 } 293 else 294 { 295 writeConfigArchive(); 296 } 297 } 298 299 300 301 // Fixme -- Should we add a hash or signature check here? 302 303 304 // See if there is a config changes file. If there is, then try to apply 305 // the changes contained in it. 306 File changesFile = new File(f.getParent(), CONFIG_CHANGES_NAME); 307 try 308 { 309 if (changesFile.exists()) 310 { 311 applyChangesFile(f, changesFile); 312 if (maintainConfigArchive) 313 { 314 configurationDigest = calculateConfigDigest(); 315 writeConfigArchive(); 316 } 317 } 318 } 319 catch (Exception e) 320 { 321 if (debugEnabled()) 322 { 323 TRACER.debugCaught(DebugLogLevel.ERROR, e); 324 } 325 326 Message message = ERR_CONFIG_UNABLE_TO_APPLY_STARTUP_CHANGES.get( 327 changesFile.getAbsolutePath(), String.valueOf(e)); 328 throw new InitializationException(message, e); 329 } 330 331 332 // We will use the LDIF reader to read the configuration file. Create an 333 // LDIF import configuration to do this and then get the reader. 334 LDIFReader reader; 335 try 336 { 337 LDIFImportConfig importConfig = new LDIFImportConfig(f.getAbsolutePath()); 338 339 // FIXME -- Should we support encryption or compression for the config? 340 341 reader = new LDIFReader(importConfig); 342 } 343 catch (Exception e) 344 { 345 if (debugEnabled()) 346 { 347 TRACER.debugCaught(DebugLogLevel.ERROR, e); 348 } 349 350 Message message = ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get( 351 f.getAbsolutePath(), String.valueOf(e)); 352 throw new InitializationException(message, e); 353 } 354 355 356 // Read the first entry from the configuration file. 357 Entry entry; 358 try 359 { 360 entry = reader.readEntry(checkSchema); 361 } 362 catch (LDIFException le) 363 { 364 if (debugEnabled()) 365 { 366 TRACER.debugCaught(DebugLogLevel.ERROR, le); 367 } 368 369 try 370 { 371 reader.close(); 372 } 373 catch (Exception e) 374 { 375 if (debugEnabled()) 376 { 377 TRACER.debugCaught(DebugLogLevel.ERROR, e); 378 } 379 } 380 381 Message message = ERR_CONFIG_FILE_INVALID_LDIF_ENTRY.get( 382 le.getLineNumber(), f.getAbsolutePath(), String.valueOf(le)); 383 throw new InitializationException(message, le); 384 } 385 catch (Exception e) 386 { 387 if (debugEnabled()) 388 { 389 TRACER.debugCaught(DebugLogLevel.ERROR, e); 390 } 391 392 try 393 { 394 reader.close(); 395 } 396 catch (Exception e2) 397 { 398 if (debugEnabled()) 399 { 400 TRACER.debugCaught(DebugLogLevel.ERROR, e2); 401 } 402 } 403 404 Message message = 405 ERR_CONFIG_FILE_READ_ERROR.get(f.getAbsolutePath(), 406 String.valueOf(e)); 407 throw new InitializationException(message, e); 408 } 409 410 411 // Make sure that the provide LDIF file is not empty. 412 if (entry == null) 413 { 414 try 415 { 416 reader.close(); 417 } 418 catch (Exception e) 419 { 420 if (debugEnabled()) 421 { 422 TRACER.debugCaught(DebugLogLevel.ERROR, e); 423 } 424 } 425 426 Message message = ERR_CONFIG_FILE_EMPTY.get(f.getAbsolutePath()); 427 throw new InitializationException(message); 428 } 429 430 431 // Make sure that the DN of this entry is equal to the config root DN. 432 try 433 { 434 DN configRootDN = DN.decode(DN_CONFIG_ROOT); 435 if (! entry.getDN().equals(configRootDN)) 436 { 437 Message message = ERR_CONFIG_FILE_INVALID_BASE_DN.get( 438 f.getAbsolutePath(), entry.getDN().toString(), 439 DN_CONFIG_ROOT); 440 throw new InitializationException(message); 441 } 442 } 443 catch (InitializationException ie) 444 { 445 if (debugEnabled()) 446 { 447 TRACER.debugCaught(DebugLogLevel.ERROR, ie); 448 } 449 450 try 451 { 452 reader.close(); 453 } 454 catch (Exception e) 455 { 456 if (debugEnabled()) 457 { 458 TRACER.debugCaught(DebugLogLevel.ERROR, e); 459 } 460 } 461 462 throw ie; 463 } 464 catch (Exception e) 465 { 466 if (debugEnabled()) 467 { 468 TRACER.debugCaught(DebugLogLevel.ERROR, e); 469 } 470 471 try 472 { 473 reader.close(); 474 } 475 catch (Exception e2) 476 { 477 if (debugEnabled()) 478 { 479 TRACER.debugCaught(DebugLogLevel.ERROR, e2); 480 } 481 } 482 483 // This should not happen, so we can use a generic error here. 484 Message message = ERR_CONFIG_FILE_GENERIC_ERROR.get(f.getAbsolutePath(), 485 String.valueOf(e)); 486 throw new InitializationException(message, e); 487 } 488 489 490 // Convert the entry to a configuration entry and put it in the config 491 // hash. 492 configEntries = new ConcurrentHashMap<DN,ConfigEntry>(); 493 configRootEntry = new ConfigEntry(entry, null); 494 configEntries.put(entry.getDN(), configRootEntry); 495 496 497 // Iterate through the rest of the configuration file and process the 498 // remaining entries. 499 while (true) 500 { 501 // Read the next entry from the configuration. 502 try 503 { 504 entry = reader.readEntry(checkSchema); 505 } 506 catch (LDIFException le) 507 { 508 if (debugEnabled()) 509 { 510 TRACER.debugCaught(DebugLogLevel.ERROR, le); 511 } 512 513 try 514 { 515 reader.close(); 516 } 517 catch (Exception e) 518 { 519 if (debugEnabled()) 520 { 521 TRACER.debugCaught(DebugLogLevel.ERROR, e); 522 } 523 } 524 525 Message message = ERR_CONFIG_FILE_INVALID_LDIF_ENTRY.get( 526 le.getLineNumber(), f.getAbsolutePath(), 527 String.valueOf(le)); 528 throw new InitializationException(message, le); 529 } 530 catch (Exception e) 531 { 532 if (debugEnabled()) 533 { 534 TRACER.debugCaught(DebugLogLevel.ERROR, e); 535 } 536 537 try 538 { 539 reader.close(); 540 } 541 catch (Exception e2) 542 { 543 if (debugEnabled()) 544 { 545 TRACER.debugCaught(DebugLogLevel.ERROR, e2); 546 } 547 } 548 549 Message message = ERR_CONFIG_FILE_READ_ERROR.get(f.getAbsolutePath(), 550 String.valueOf(e)); 551 throw new InitializationException(message, e); 552 } 553 554 555 // If the entry is null, then we have reached the end of the configuration 556 // file. 557 if (entry == null) 558 { 559 try 560 { 561 reader.close(); 562 } 563 catch (Exception e) 564 { 565 if (debugEnabled()) 566 { 567 TRACER.debugCaught(DebugLogLevel.ERROR, e); 568 } 569 } 570 571 break; 572 } 573 574 575 // Make sure that the DN of the entry read doesn't already exist. 576 DN entryDN = entry.getDN(); 577 if (configEntries.containsKey(entryDN)) 578 { 579 try 580 { 581 reader.close(); 582 } 583 catch (Exception e) 584 { 585 if (debugEnabled()) 586 { 587 TRACER.debugCaught(DebugLogLevel.ERROR, e); 588 } 589 } 590 591 Message message = ERR_CONFIG_FILE_DUPLICATE_ENTRY.get( 592 entryDN.toString(), 593 String.valueOf(reader.getLastEntryLineNumber()), 594 f.getAbsolutePath()); 595 throw new InitializationException(message); 596 } 597 598 599 // Make sure that the parent DN of the entry read does exist. 600 DN parentDN = entryDN.getParent(); 601 if (parentDN == null) 602 { 603 try 604 { 605 reader.close(); 606 } 607 catch (Exception e) 608 { 609 if (debugEnabled()) 610 { 611 TRACER.debugCaught(DebugLogLevel.ERROR, e); 612 } 613 } 614 615 Message message = ERR_CONFIG_FILE_UNKNOWN_PARENT.get( 616 entryDN.toString(), 617 reader.getLastEntryLineNumber(), 618 f.getAbsolutePath()); 619 throw new InitializationException(message); 620 } 621 622 ConfigEntry parentEntry = configEntries.get(parentDN); 623 if (parentEntry == null) 624 { 625 try 626 { 627 reader.close(); 628 } 629 catch (Exception e) 630 { 631 if (debugEnabled()) 632 { 633 TRACER.debugCaught(DebugLogLevel.ERROR, e); 634 } 635 } 636 637 Message message = ERR_CONFIG_FILE_NO_PARENT.get(entryDN.toString(), 638 reader.getLastEntryLineNumber(), 639 f.getAbsolutePath(), parentDN.toString()); 640 throw new InitializationException(message); 641 } 642 643 644 // Create the new configuration entry, add it as a child of the provided 645 // parent entry, and put it into the entry has. 646 try 647 { 648 ConfigEntry configEntry = new ConfigEntry(entry, parentEntry); 649 parentEntry.addChild(configEntry); 650 configEntries.put(entryDN, configEntry); 651 } 652 catch (Exception e) 653 { 654 // This should not happen. 655 if (debugEnabled()) 656 { 657 TRACER.debugCaught(DebugLogLevel.ERROR, e); 658 } 659 660 try 661 { 662 reader.close(); 663 } 664 catch (Exception e2) 665 { 666 if (debugEnabled()) 667 { 668 TRACER.debugCaught(DebugLogLevel.ERROR, e2); 669 } 670 } 671 672 Message message = ERR_CONFIG_FILE_GENERIC_ERROR.get(f.getAbsolutePath(), 673 String.valueOf(e)); 674 throw new InitializationException(message, e); 675 } 676 } 677 678 679 // Determine the appropriate server root. If it's not defined in the 680 // environment config, then try to figure it out from the location of the 681 // configuration file. 682 File rootFile = envConfig.getServerRoot(); 683 if (rootFile == null) 684 { 685 try 686 { 687 File configDirFile = f.getParentFile(); 688 if ((configDirFile != null) && 689 configDirFile.getName().equals(CONFIG_DIR_NAME)) 690 { 691 serverRoot = configDirFile.getParentFile().getAbsolutePath(); 692 } 693 694 if (serverRoot == null) 695 { 696 Message message = ERR_CONFIG_CANNOT_DETERMINE_SERVER_ROOT.get( 697 ENV_VAR_INSTANCE_ROOT); 698 throw new InitializationException(message); 699 } 700 } 701 catch (InitializationException ie) 702 { 703 if (debugEnabled()) 704 { 705 TRACER.debugCaught(DebugLogLevel.ERROR, ie); 706 } 707 708 throw ie; 709 } 710 catch (Exception e) 711 { 712 if (debugEnabled()) 713 { 714 TRACER.debugCaught(DebugLogLevel.ERROR, e); 715 } 716 717 Message message = 718 ERR_CONFIG_CANNOT_DETERMINE_SERVER_ROOT.get(ENV_VAR_INSTANCE_ROOT); 719 throw new InitializationException(message); 720 } 721 } 722 else 723 { 724 serverRoot = rootFile.getAbsolutePath(); 725 } 726 727 728 // Register with the Directory Server as an alert generator. 729 DirectoryServer.registerAlertGenerator(this); 730 731 732 // Register with the Directory Server as the backend that should be used 733 // when accessing the configuration. 734 baseDNs = new DN[] { configRootEntry.getDN() }; 735 736 try 737 { 738 // Set a backend ID for the config backend. Try to avoid potential 739 // conflict with user backend identifiers. 740 setBackendID("__config.ldif__"); 741 742 DirectoryServer.registerBaseDN(configRootEntry.getDN(), this, true); 743 } 744 catch (Exception e) 745 { 746 if (debugEnabled()) 747 { 748 TRACER.debugCaught(DebugLogLevel.ERROR, e); 749 } 750 751 Message message = ERR_CONFIG_CANNOT_REGISTER_AS_PRIVATE_SUFFIX.get( 752 String.valueOf(configRootEntry.getDN()), getExceptionMessage(e)); 753 throw new InitializationException(message, e); 754 } 755 } 756 757 758 759 /** 760 * Calculates a SHA-1 digest of the current configuration file. 761 * 762 * @return The calculated configuration digest. 763 * 764 * @throws DirectoryException If a problem occurs while calculating the 765 * digest. 766 */ 767 private byte[] calculateConfigDigest() 768 throws DirectoryException 769 { 770 InputStream inputStream = null; 771 try 772 { 773 MessageDigest sha1Digest = 774 MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1); 775 inputStream = new FileInputStream(configFile); 776 byte[] buffer = new byte[8192]; 777 while (true) 778 { 779 int bytesRead = inputStream.read(buffer); 780 if (bytesRead < 0) 781 { 782 break; 783 } 784 785 sha1Digest.update(buffer, 0, bytesRead); 786 } 787 return sha1Digest.digest(); 788 } 789 catch (Exception e) 790 { 791 Message message = ERR_CONFIG_CANNOT_CALCULATE_DIGEST.get( 792 configFile, stackTraceToSingleLineString(e)); 793 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 794 message, e); 795 } 796 finally 797 { 798 if (inputStream != null) 799 { 800 try 801 { 802 inputStream.close(); 803 } 804 catch (IOException e) { 805 // ignore; 806 } 807 } 808 } 809 } 810 811 812 813 /** 814 * Looks at the existing archive directory, finds the latest archive file, 815 * and calculates a SHA-1 digest of that file. 816 * 817 * @return The calculated digest of the most recent archived configuration 818 * file. 819 * 820 * @throws DirectoryException If a problem occurs while calculating the 821 * digest. 822 */ 823 private byte[] getLastConfigDigest(File archiveDirectory) 824 throws DirectoryException 825 { 826 int latestCounter = 0; 827 long latestTimestamp = -1; 828 String latestFileName = null; 829 for (String name : archiveDirectory.list()) 830 { 831 if (! name.startsWith("config-")) 832 { 833 continue; 834 } 835 836 int dotPos = name.indexOf('.', 7); 837 if (dotPos < 0) 838 { 839 continue; 840 } 841 842 int dashPos = name.indexOf('-', 7); 843 if (dashPos < 0) 844 { 845 try 846 { 847 ASN1OctetString ts = new ASN1OctetString(name.substring(7, dotPos)); 848 long timestamp = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(ts); 849 if (timestamp > latestTimestamp) 850 { 851 latestFileName = name; 852 latestTimestamp = timestamp; 853 latestCounter = 0; 854 continue; 855 } 856 } 857 catch (Exception e) 858 { 859 continue; 860 } 861 } 862 else 863 { 864 try 865 { 866 ASN1OctetString ts = new ASN1OctetString(name.substring(7, dashPos)); 867 long timestamp = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(ts); 868 int counter = Integer.parseInt(name.substring(dashPos+1, dotPos)); 869 870 if (timestamp > latestTimestamp) 871 { 872 latestFileName = name; 873 latestTimestamp = timestamp; 874 latestCounter = counter; 875 continue; 876 } 877 else if ((timestamp == latestTimestamp) && (counter > latestCounter)) 878 { 879 latestFileName = name; 880 latestTimestamp = timestamp; 881 latestCounter = counter; 882 continue; 883 } 884 } 885 catch (Exception e) 886 { 887 continue; 888 } 889 } 890 } 891 892 if (latestFileName == null) 893 { 894 return null; 895 } 896 File latestFile = new File(archiveDirectory, latestFileName); 897 898 try 899 { 900 MessageDigest sha1Digest = 901 MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1); 902 GZIPInputStream inputStream = 903 new GZIPInputStream(new FileInputStream(latestFile)); 904 byte[] buffer = new byte[8192]; 905 while (true) 906 { 907 int bytesRead = inputStream.read(buffer); 908 if (bytesRead < 0) 909 { 910 break; 911 } 912 913 sha1Digest.update(buffer, 0, bytesRead); 914 } 915 916 return sha1Digest.digest(); 917 } 918 catch (Exception e) 919 { 920 Message message = ERR_CONFIG_CANNOT_CALCULATE_DIGEST.get( 921 latestFile.getAbsolutePath(), stackTraceToSingleLineString(e)); 922 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 923 message, e); 924 } 925 } 926 927 928 929 /** 930 * Applies the updates in the provided changes file to the content in the 931 * specified source file. The result will be written to a temporary file, the 932 * current source file will be moved out of place, and then the updated file 933 * will be moved into the place of the original file. The changes file will 934 * also be renamed so it won't be applied again. 935 * <BR><BR> 936 * If any problems are encountered, then the config initialization process 937 * will be aborted. 938 * 939 * @param sourceFile The LDIF file containing the source data. 940 * @param changesFile The LDIF file containing the changes to apply. 941 * 942 * @throws IOException If a problem occurs while performing disk I/O. 943 * 944 * @throws LDIFException If a problem occurs while trying to interpret the 945 * data. 946 */ 947 private void applyChangesFile(File sourceFile, File changesFile) 948 throws IOException, LDIFException 949 { 950 // Create the appropriate LDIF readers and writer. 951 LDIFImportConfig importConfig = 952 new LDIFImportConfig(sourceFile.getAbsolutePath()); 953 importConfig.setValidateSchema(false); 954 LDIFReader sourceReader = new LDIFReader(importConfig); 955 956 importConfig = new LDIFImportConfig(changesFile.getAbsolutePath()); 957 importConfig.setValidateSchema(false); 958 LDIFReader changesReader = new LDIFReader(importConfig); 959 960 String tempFile = changesFile.getAbsolutePath() + ".tmp"; 961 LDIFExportConfig exportConfig = 962 new LDIFExportConfig(tempFile, ExistingFileBehavior.OVERWRITE); 963 LDIFWriter targetWriter = new LDIFWriter(exportConfig); 964 965 966 // Apply the changes and make sure there were no errors. 967 LinkedList<Message> errorList = new LinkedList<Message>(); 968 boolean successful = LDIFModify.modifyLDIF(sourceReader, changesReader, 969 targetWriter, errorList); 970 971 try 972 { 973 sourceReader.close(); 974 } catch (Exception e) {} 975 976 try 977 { 978 changesReader.close(); 979 } catch (Exception e) {} 980 981 try 982 { 983 targetWriter.close(); 984 } catch (Exception e) {} 985 986 if (! successful) 987 { 988 // FIXME -- Log each error message and throw an exception. 989 for (Message s : errorList) 990 { 991 Message message = ERR_CONFIG_ERROR_APPLYING_STARTUP_CHANGE.get(s); 992 logError(message); 993 } 994 995 Message message = ERR_CONFIG_UNABLE_TO_APPLY_CHANGES_FILE.get(); 996 throw new LDIFException(message); 997 } 998 999 1000 // Move the current config file out of the way and replace it with the 1001 // updated version. 1002 File oldSource = new File(sourceFile.getAbsolutePath() + ".prechanges"); 1003 if (oldSource.exists()) 1004 { 1005 oldSource.delete(); 1006 } 1007 sourceFile.renameTo(oldSource); 1008 new File(tempFile).renameTo(sourceFile); 1009 1010 // Move the changes file out of the way so it doesn't get applied again. 1011 File newChanges = new File(changesFile.getAbsolutePath() + ".applied"); 1012 if (newChanges.exists()) 1013 { 1014 newChanges.delete(); 1015 } 1016 changesFile.renameTo(newChanges); 1017 } 1018 1019 1020 1021 /** 1022 * {@inheritDoc} 1023 */ 1024 @Override() 1025 public void finalizeConfigHandler() 1026 { 1027 try 1028 { 1029 DirectoryServer.deregisterBaseDN(configRootEntry.getDN()); 1030 } 1031 catch (Exception e) 1032 { 1033 if (debugEnabled()) 1034 { 1035 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1036 } 1037 } 1038 } 1039 1040 1041 1042 /** 1043 * {@inheritDoc} 1044 */ 1045 @Override() 1046 public void finalizeBackend() 1047 { 1048 // No implementation is required. 1049 } 1050 1051 1052 1053 /** 1054 * {@inheritDoc} 1055 */ 1056 @Override() 1057 public ConfigEntry getConfigRootEntry() 1058 throws ConfigException 1059 { 1060 return configRootEntry; 1061 } 1062 1063 1064 1065 /** 1066 * {@inheritDoc} 1067 */ 1068 @Override() 1069 public ConfigEntry getConfigEntry(DN entryDN) 1070 throws ConfigException 1071 { 1072 return configEntries.get(entryDN); 1073 } 1074 1075 1076 1077 /** 1078 * {@inheritDoc} 1079 */ 1080 @Override() 1081 public String getServerRoot() 1082 { 1083 return serverRoot; 1084 } 1085 1086 1087 1088 /** 1089 * {@inheritDoc} 1090 */ 1091 @Override() 1092 public void configureBackend(Configuration cfg) 1093 throws ConfigException 1094 { 1095 // No action is required. 1096 } 1097 1098 1099 1100 /** 1101 * {@inheritDoc} 1102 */ 1103 @Override() 1104 public void initializeBackend() 1105 throws ConfigException, InitializationException 1106 { 1107 // No action is required, since all initialization was performed in the 1108 // initializeConfigHandler method. 1109 } 1110 1111 1112 1113 /** 1114 * {@inheritDoc} 1115 */ 1116 @Override() 1117 public DN[] getBaseDNs() 1118 { 1119 return baseDNs; 1120 } 1121 1122 1123 1124 /** 1125 * {@inheritDoc} 1126 */ 1127 @Override() 1128 public long getEntryCount() 1129 { 1130 return configEntries.size(); 1131 } 1132 1133 1134 1135 /** 1136 * {@inheritDoc} 1137 */ 1138 @Override() 1139 public boolean isLocal() 1140 { 1141 // The configuration information will always be local. 1142 return true; 1143 } 1144 1145 1146 1147 /** 1148 * {@inheritDoc} 1149 */ 1150 @Override() 1151 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 1152 { 1153 // All searches in this backend will always be considered indexed. 1154 return true; 1155 } 1156 1157 1158 1159 /** 1160 * {@inheritDoc} 1161 */ 1162 @Override() 1163 public ConditionResult hasSubordinates(DN entryDN) 1164 throws DirectoryException 1165 { 1166 ConfigEntry baseEntry = configEntries.get(entryDN); 1167 if(baseEntry == null) 1168 { 1169 return ConditionResult.UNDEFINED; 1170 } 1171 else if(baseEntry.hasChildren()) 1172 { 1173 return ConditionResult.TRUE; 1174 } 1175 else 1176 { 1177 return ConditionResult.FALSE; 1178 } 1179 } 1180 1181 1182 1183 /** 1184 * {@inheritDoc} 1185 */ 1186 @Override() 1187 public long numSubordinates(DN entryDN, boolean subtree) 1188 throws DirectoryException 1189 { 1190 ConfigEntry baseEntry = configEntries.get(entryDN); 1191 if (baseEntry == null) 1192 { 1193 return -1; 1194 } 1195 1196 if(!subtree) 1197 { 1198 return baseEntry.getChildren().size(); 1199 } 1200 else 1201 { 1202 long count = 0; 1203 for(ConfigEntry child : baseEntry.getChildren().values()) 1204 { 1205 count += numSubordinates(child.getDN(), true); 1206 count ++; 1207 } 1208 return count; 1209 } 1210 } 1211 1212 1213 1214 /** 1215 * {@inheritDoc} 1216 */ 1217 @Override() 1218 public Entry getEntry(DN entryDN) 1219 throws DirectoryException 1220 { 1221 ConfigEntry configEntry = configEntries.get(entryDN); 1222 if (configEntry == null) 1223 { 1224 return null; 1225 } 1226 1227 return configEntry.getEntry().duplicate(true); 1228 } 1229 1230 1231 1232 /** 1233 * {@inheritDoc} 1234 */ 1235 @Override() 1236 public boolean entryExists(DN entryDN) 1237 throws DirectoryException 1238 { 1239 return configEntries.containsKey(entryDN); 1240 } 1241 1242 1243 1244 /** 1245 * {@inheritDoc} 1246 */ 1247 @Override() 1248 public void addEntry(Entry entry, AddOperation addOperation) 1249 throws DirectoryException 1250 { 1251 Entry e = entry.duplicate(false); 1252 1253 // If there is an add operation, then make sure that the associated user has 1254 // both the CONFIG_READ and CONFIG_WRITE privileges. 1255 if (addOperation != null) 1256 { 1257 ClientConnection clientConnection = addOperation.getClientConnection(); 1258 if (! (clientConnection.hasAllPrivileges(CONFIG_READ_AND_WRITE, 1259 addOperation))) 1260 { 1261 Message message = ERR_CONFIG_FILE_ADD_INSUFFICIENT_PRIVILEGES.get(); 1262 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 1263 message); 1264 } 1265 } 1266 1267 1268 // Grab the config lock to ensure that only one config update may be in 1269 // progress at any given time. 1270 synchronized (configLock) 1271 { 1272 // Make sure that the target DN does not already exist. If it does, then 1273 // fail. 1274 DN entryDN = e.getDN(); 1275 if (configEntries.containsKey(entryDN)) 1276 { 1277 Message message = 1278 ERR_CONFIG_FILE_ADD_ALREADY_EXISTS.get(String.valueOf(entryDN)); 1279 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); 1280 } 1281 1282 1283 // Make sure that the entry's parent exists. If it does not, then fail. 1284 DN parentDN = entryDN.getParent(); 1285 if (parentDN == null) 1286 { 1287 // The entry DN doesn't have a parent. This is not allowed. 1288 Message message = 1289 ERR_CONFIG_FILE_ADD_NO_PARENT_DN.get(String.valueOf(entryDN)); 1290 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 1291 } 1292 1293 ConfigEntry parentEntry = configEntries.get(parentDN); 1294 if (parentEntry == null) 1295 { 1296 // The parent entry does not exist. This is not allowed. 1297 Message message = ERR_CONFIG_FILE_ADD_NO_PARENT.get( 1298 String.valueOf(entryDN), 1299 String.valueOf(parentDN)); 1300 1301 // Get the matched DN, if possible. 1302 DN matchedDN = null; 1303 parentDN = parentDN.getParent(); 1304 while (parentDN != null) 1305 { 1306 if (configEntries.containsKey(parentDN)) 1307 { 1308 matchedDN = parentDN; 1309 break; 1310 } 1311 1312 parentDN = parentDN.getParent(); 1313 } 1314 1315 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, 1316 matchedDN, null); 1317 } 1318 1319 1320 // Encapsulate the provided entry in a config entry. 1321 ConfigEntry newEntry = new ConfigEntry(e, parentEntry); 1322 1323 1324 // See if the parent entry has any add listeners. If so, then iterate 1325 // through them and make sure the new entry is acceptable. 1326 CopyOnWriteArrayList<ConfigAddListener> addListeners = 1327 parentEntry.getAddListeners(); 1328 MessageBuilder unacceptableReason = new MessageBuilder(); 1329 for (ConfigAddListener l : addListeners) 1330 { 1331 if (! l.configAddIsAcceptable(newEntry, unacceptableReason)) 1332 { 1333 Message message = ERR_CONFIG_FILE_ADD_REJECTED_BY_LISTENER. 1334 get(String.valueOf(entryDN), String.valueOf(parentDN), 1335 String.valueOf(unacceptableReason)); 1336 throw new DirectoryException( 1337 ResultCode.UNWILLING_TO_PERFORM, message); 1338 1339 } 1340 } 1341 1342 1343 // At this point, we will assume that everything is OK and proceed with 1344 // the add. 1345 try 1346 { 1347 parentEntry.addChild(newEntry); 1348 configEntries.put(entryDN, newEntry); 1349 writeUpdatedConfig(); 1350 } 1351 catch (ConfigException ce) 1352 { 1353 if (debugEnabled()) 1354 { 1355 TRACER.debugCaught(DebugLogLevel.ERROR, ce); 1356 } 1357 1358 Message message = ERR_CONFIG_FILE_ADD_FAILED. 1359 get(String.valueOf(entryDN), String.valueOf(parentDN), 1360 getExceptionMessage(ce)); 1361 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 1362 message); 1363 } 1364 1365 1366 // Notify all the add listeners that the entry has been added. 1367 ResultCode resultCode = ResultCode.SUCCESS; 1368 LinkedList<Message> messages = new LinkedList<Message>(); 1369 for (ConfigAddListener l : addListeners) 1370 { 1371 ConfigChangeResult result = l.applyConfigurationAdd(newEntry); 1372 if (result.getResultCode() != ResultCode.SUCCESS) 1373 { 1374 if (resultCode == ResultCode.SUCCESS) 1375 { 1376 resultCode = result.getResultCode(); 1377 } 1378 1379 messages.addAll(result.getMessages()); 1380 } 1381 1382 handleConfigChangeResult(result, newEntry.getDN(), 1383 l.getClass().getName(), 1384 "applyConfigurationAdd"); 1385 } 1386 1387 if (resultCode != ResultCode.SUCCESS) 1388 { 1389 MessageBuilder buffer = new MessageBuilder(); 1390 if (! messages.isEmpty()) 1391 { 1392 Iterator<Message> iterator = messages.iterator(); 1393 buffer.append(iterator.next()); 1394 while (iterator.hasNext()) 1395 { 1396 buffer.append(". "); 1397 buffer.append(iterator.next()); 1398 } 1399 } 1400 1401 Message message = 1402 ERR_CONFIG_FILE_ADD_APPLY_FAILED.get(String.valueOf(buffer)); 1403 throw new DirectoryException(resultCode, message); 1404 } 1405 } 1406 } 1407 1408 1409 1410 /** 1411 * {@inheritDoc} 1412 */ 1413 @Override() 1414 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) 1415 throws DirectoryException 1416 { 1417 // If there is a delete operation, then make sure that the associated user 1418 // has both the CONFIG_READ and CONFIG_WRITE privileges. 1419 if (deleteOperation != null) 1420 { 1421 ClientConnection clientConnection = deleteOperation.getClientConnection(); 1422 if (! (clientConnection.hasAllPrivileges(CONFIG_READ_AND_WRITE, 1423 deleteOperation))) 1424 { 1425 Message message = ERR_CONFIG_FILE_DELETE_INSUFFICIENT_PRIVILEGES.get(); 1426 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 1427 message); 1428 } 1429 } 1430 1431 1432 // Grab the config lock to ensure that only one config update may be in 1433 // progress at any given time. 1434 synchronized (configLock) 1435 { 1436 // Get the target entry. If it does not exist, then fail. 1437 ConfigEntry entry = configEntries.get(entryDN); 1438 if (entry == null) 1439 { 1440 // Try to find the matched DN if possible. 1441 DN matchedDN = null; 1442 if (entryDN.isDescendantOf(configRootEntry.getDN())) 1443 { 1444 DN parentDN = entryDN.getParent(); 1445 while (parentDN != null) 1446 { 1447 if (configEntries.containsKey(parentDN)) 1448 { 1449 matchedDN = parentDN; 1450 break; 1451 } 1452 1453 parentDN = parentDN.getParent(); 1454 } 1455 } 1456 1457 Message message = 1458 ERR_CONFIG_FILE_DELETE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)); 1459 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, 1460 matchedDN, null); 1461 } 1462 1463 1464 // If the entry has children, then fail. 1465 if (entry.hasChildren()) 1466 { 1467 Message message = 1468 ERR_CONFIG_FILE_DELETE_HAS_CHILDREN.get(String.valueOf(entryDN)); 1469 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, 1470 message); 1471 } 1472 1473 1474 // Get the parent entry. If there isn't one, then it must be the config 1475 // root, which we won't allow. 1476 ConfigEntry parentEntry = entry.getParent(); 1477 if (parentEntry == null) 1478 { 1479 Message message = 1480 ERR_CONFIG_FILE_DELETE_NO_PARENT.get(String.valueOf(entryDN)); 1481 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1482 } 1483 1484 1485 // Get the delete listeners from the parent and make sure that they are 1486 // all OK with the delete. 1487 CopyOnWriteArrayList<ConfigDeleteListener> deleteListeners = 1488 parentEntry.getDeleteListeners(); 1489 MessageBuilder unacceptableReason = new MessageBuilder(); 1490 for (ConfigDeleteListener l : deleteListeners) 1491 { 1492 if (! l.configDeleteIsAcceptable(entry, unacceptableReason)) 1493 { 1494 Message message = ERR_CONFIG_FILE_DELETE_REJECTED. 1495 get(String.valueOf(entryDN), String.valueOf(parentEntry.getDN()), 1496 String.valueOf(unacceptableReason)); 1497 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1498 message); 1499 } 1500 } 1501 1502 1503 // At this point, we will assume that everything is OK and proceed with 1504 // the delete. 1505 try 1506 { 1507 parentEntry.removeChild(entryDN); 1508 configEntries.remove(entryDN); 1509 writeUpdatedConfig(); 1510 } 1511 catch (ConfigException ce) 1512 { 1513 if (debugEnabled()) 1514 { 1515 TRACER.debugCaught(DebugLogLevel.ERROR, ce); 1516 } 1517 1518 Message message = ERR_CONFIG_FILE_DELETE_FAILED. 1519 get(String.valueOf(entryDN), String.valueOf(parentEntry.getDN()), 1520 getExceptionMessage(ce)); 1521 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 1522 message); 1523 } 1524 1525 1526 // Notify all the delete listeners that the entry has been removed. 1527 ResultCode resultCode = ResultCode.SUCCESS; 1528 LinkedList<Message> messages = new LinkedList<Message>(); 1529 for (ConfigDeleteListener l : deleteListeners) 1530 { 1531 ConfigChangeResult result = l.applyConfigurationDelete(entry); 1532 if (result.getResultCode() != ResultCode.SUCCESS) 1533 { 1534 if (resultCode == ResultCode.SUCCESS) 1535 { 1536 resultCode = result.getResultCode(); 1537 } 1538 1539 messages.addAll(result.getMessages()); 1540 } 1541 1542 handleConfigChangeResult(result, entry.getDN(), 1543 l.getClass().getName(), 1544 "applyConfigurationDelete"); 1545 } 1546 1547 if (resultCode != ResultCode.SUCCESS) 1548 { 1549 StringBuilder buffer = new StringBuilder(); 1550 if (! messages.isEmpty()) 1551 { 1552 Iterator<Message> iterator = messages.iterator(); 1553 buffer.append(iterator.next()); 1554 while (iterator.hasNext()) 1555 { 1556 buffer.append(". "); 1557 buffer.append(iterator.next()); 1558 } 1559 } 1560 1561 Message message = 1562 ERR_CONFIG_FILE_DELETE_APPLY_FAILED.get(String.valueOf(buffer)); 1563 throw new DirectoryException(resultCode, message); 1564 } 1565 } 1566 } 1567 1568 1569 1570 /** 1571 * {@inheritDoc} 1572 */ 1573 @Override() 1574 public void replaceEntry(Entry entry, ModifyOperation modifyOperation) 1575 throws DirectoryException 1576 { 1577 Entry e = entry.duplicate(false); 1578 1579 // If there is a modify operation, then make sure that the associated user 1580 // has both the CONFIG_READ and CONFIG_WRITE privileges. Also, if the 1581 // operation targets the set of root privileges then make sure the user has 1582 // the PRIVILEGE_CHANGE privilege. 1583 if (modifyOperation != null) 1584 { 1585 ClientConnection clientConnection = modifyOperation.getClientConnection(); 1586 if (! (clientConnection.hasAllPrivileges(CONFIG_READ_AND_WRITE, 1587 modifyOperation))) 1588 { 1589 Message message = ERR_CONFIG_FILE_MODIFY_INSUFFICIENT_PRIVILEGES.get(); 1590 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 1591 message); 1592 } 1593 1594 AttributeType privType = 1595 DirectoryServer.getAttributeType(ATTR_DEFAULT_ROOT_PRIVILEGE_NAME, 1596 true); 1597 for (Modification m : modifyOperation.getModifications()) 1598 { 1599 if (m.getAttribute().getAttributeType().equals(privType)) 1600 { 1601 if (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, 1602 modifyOperation)) 1603 { 1604 Message message = 1605 ERR_CONFIG_FILE_MODIFY_PRIVS_INSUFFICIENT_PRIVILEGES.get(); 1606 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 1607 message); 1608 } 1609 1610 break; 1611 } 1612 } 1613 } 1614 1615 1616 // Grab the config lock to ensure that only one config update may be in 1617 // progress at any given time. 1618 synchronized (configLock) 1619 { 1620 // Get the DN of the target entry for future reference. 1621 DN entryDN = e.getDN(); 1622 1623 1624 // Get the target entry. If it does not exist, then fail. 1625 ConfigEntry currentEntry = configEntries.get(entryDN); 1626 if (currentEntry == null) 1627 { 1628 // Try to find the matched DN if possible. 1629 DN matchedDN = null; 1630 if (entryDN.isDescendantOf(configRootEntry.getDN())) 1631 { 1632 DN parentDN = entryDN.getParent(); 1633 while (parentDN != null) 1634 { 1635 if (configEntries.containsKey(parentDN)) 1636 { 1637 matchedDN = parentDN; 1638 break; 1639 } 1640 1641 parentDN = parentDN.getParent(); 1642 } 1643 } 1644 1645 Message message = 1646 ERR_CONFIG_FILE_MODIFY_NO_SUCH_ENTRY.get(String.valueOf(entryDN)); 1647 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, 1648 matchedDN, null); 1649 } 1650 1651 1652 // If the structural class is different between the current entry and the 1653 // new entry, then reject the change. 1654 if (! currentEntry.getEntry().getStructuralObjectClass().equals( 1655 entry.getStructuralObjectClass())) 1656 { 1657 Message message = ERR_CONFIG_FILE_MODIFY_STRUCTURAL_CHANGE_NOT_ALLOWED. 1658 get(String.valueOf(entryDN)); 1659 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 1660 } 1661 1662 1663 // Create a new config entry to use for the validation testing. 1664 ConfigEntry newEntry = new ConfigEntry(e, currentEntry.getParent()); 1665 1666 1667 // See if there are any config change listeners registered for this entry. 1668 // If there are, then make sure they are all OK with the change. 1669 CopyOnWriteArrayList<ConfigChangeListener> changeListeners = 1670 currentEntry.getChangeListeners(); 1671 MessageBuilder unacceptableReason = new MessageBuilder(); 1672 for (ConfigChangeListener l : changeListeners) 1673 { 1674 if (! l.configChangeIsAcceptable(newEntry, unacceptableReason)) 1675 { 1676 Message message = ERR_CONFIG_FILE_MODIFY_REJECTED_BY_CHANGE_LISTENER. 1677 get(String.valueOf(entryDN), String.valueOf(unacceptableReason)); 1678 throw new DirectoryException( 1679 ResultCode.UNWILLING_TO_PERFORM, message); 1680 } 1681 } 1682 1683 1684 // At this point, it looks like the change is acceptable, so apply it. 1685 // We'll just overwrite the core entry in the current config entry so that 1686 // we keep all the registered listeners, references to the parent and 1687 // children, and other metadata. 1688 currentEntry.setEntry(e); 1689 writeUpdatedConfig(); 1690 1691 1692 // Notify all the change listeners of the update. 1693 ResultCode resultCode = ResultCode.SUCCESS; 1694 LinkedList<Message> messages = new LinkedList<Message>(); 1695 for (ConfigChangeListener l : changeListeners) 1696 { 1697 ConfigChangeResult result = l.applyConfigurationChange(currentEntry); 1698 if (result.getResultCode() != ResultCode.SUCCESS) 1699 { 1700 if (resultCode == ResultCode.SUCCESS) 1701 { 1702 resultCode = result.getResultCode(); 1703 } 1704 1705 messages.addAll(result.getMessages()); 1706 } 1707 1708 handleConfigChangeResult(result, currentEntry.getDN(), 1709 l.getClass().getName(), 1710 "applyConfigurationChange"); 1711 } 1712 1713 if (resultCode != ResultCode.SUCCESS) 1714 { 1715 MessageBuilder buffer = new MessageBuilder(); 1716 if (! messages.isEmpty()) 1717 { 1718 Iterator<Message> iterator = messages.iterator(); 1719 buffer.append(iterator.next()); 1720 while (iterator.hasNext()) 1721 { 1722 buffer.append(". "); 1723 buffer.append(iterator.next()); 1724 } 1725 } 1726 1727 Message message = 1728 ERR_CONFIG_FILE_MODIFY_APPLY_FAILED.get(String.valueOf(buffer)); 1729 throw new DirectoryException(resultCode, message); 1730 } 1731 } 1732 } 1733 1734 1735 1736 /** 1737 * {@inheritDoc} 1738 */ 1739 @Override() 1740 public void renameEntry(DN currentDN, Entry entry, 1741 ModifyDNOperation modifyDNOperation) 1742 throws DirectoryException 1743 { 1744 // If there is a modify DN operation, then make sure that the associated 1745 // user has both the CONFIG_READ and CONFIG_WRITE privileges. 1746 if (modifyDNOperation != null) 1747 { 1748 ClientConnection clientConnection = 1749 modifyDNOperation.getClientConnection(); 1750 if (! (clientConnection.hasAllPrivileges(CONFIG_READ_AND_WRITE, 1751 modifyDNOperation))) 1752 { 1753 Message message = ERR_CONFIG_FILE_MODDN_INSUFFICIENT_PRIVILEGES.get(); 1754 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 1755 message); 1756 } 1757 } 1758 1759 1760 // Modify DN operations will not be allowed in the configuration, so this 1761 // will always throw an exception. 1762 Message message = ERR_CONFIG_FILE_MODDN_NOT_ALLOWED.get(); 1763 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 1764 } 1765 1766 1767 1768 /** 1769 * {@inheritDoc} 1770 */ 1771 @Override() 1772 public void search(SearchOperation searchOperation) 1773 throws DirectoryException 1774 { 1775 // Make sure that the associated user has the CONFIG_READ privilege. 1776 ClientConnection clientConnection = searchOperation.getClientConnection(); 1777 if (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, searchOperation)) 1778 { 1779 Message message = ERR_CONFIG_FILE_SEARCH_INSUFFICIENT_PRIVILEGES.get(); 1780 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 1781 message); 1782 } 1783 1784 1785 // First, get the base DN for the search and make sure that it exists. 1786 DN baseDN = searchOperation.getBaseDN(); 1787 ConfigEntry baseEntry = configEntries.get(baseDN); 1788 if (baseEntry == null) 1789 { 1790 Message message = ERR_CONFIG_FILE_SEARCH_NO_SUCH_BASE.get( 1791 String.valueOf(baseDN)); 1792 DN matchedDN = null; 1793 if (baseDN.isDescendantOf(configRootEntry.getDN())) 1794 { 1795 DN parentDN = baseDN.getParent(); 1796 while (parentDN != null) 1797 { 1798 if (configEntries.containsKey(parentDN)) 1799 { 1800 matchedDN = parentDN; 1801 break; 1802 } 1803 1804 parentDN = parentDN.getParent(); 1805 } 1806 } 1807 1808 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, 1809 matchedDN, null); 1810 } 1811 1812 1813 // Get the scope for the search and perform the remainder of the processing 1814 // accordingly. Also get the filter since we will need it in all cases. 1815 SearchScope scope = searchOperation.getScope(); 1816 SearchFilter filter = searchOperation.getFilter(); 1817 switch (scope) 1818 { 1819 case BASE_OBJECT: 1820 // We are only interested in the base entry itself. See if it matches 1821 // and if so then return the entry. 1822 Entry e = baseEntry.getEntry().duplicate(true); 1823 if (filter.matchesEntry(e)) 1824 { 1825 searchOperation.returnEntry(e, null); 1826 } 1827 break; 1828 1829 1830 case SINGLE_LEVEL: 1831 // We are only interested in entries immediately below the base entry. 1832 // Iterate through them and return the ones that match the filter. 1833 for (ConfigEntry child : baseEntry.getChildren().values()) 1834 { 1835 e = child.getEntry().duplicate(true); 1836 if (filter.matchesEntry(e)) 1837 { 1838 if (! searchOperation.returnEntry(e, null)) 1839 { 1840 break; 1841 } 1842 } 1843 } 1844 break; 1845 1846 1847 case WHOLE_SUBTREE: 1848 // We are interested in the base entry and all its children. Use a 1849 // recursive process to achieve this. 1850 searchSubtree(baseEntry, filter, searchOperation); 1851 break; 1852 1853 1854 case SUBORDINATE_SUBTREE: 1855 // We are not interested in the base entry, but we want to check out all 1856 // of its children. Use a recursive process to achieve this. 1857 for (ConfigEntry child : baseEntry.getChildren().values()) 1858 { 1859 if (! searchSubtree(child, filter, searchOperation)) 1860 { 1861 break; 1862 } 1863 } 1864 break; 1865 1866 1867 default: 1868 // The user provided an invalid scope. 1869 Message message = 1870 ERR_CONFIG_FILE_SEARCH_INVALID_SCOPE.get(String.valueOf(scope)); 1871 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 1872 } 1873 } 1874 1875 1876 1877 /** 1878 * Performs a subtree search starting at the provided base entry, returning 1879 * all entries anywhere in that subtree that match the provided filter. 1880 * 1881 * @param baseEntry The base entry below which to perform the search. 1882 * @param filter The filter to use to identify matching entries. 1883 * @param searchOperation The search operation to use to return entries to 1884 * the client. 1885 * 1886 * @return <CODE>true</CODE> if the search should continue, or 1887 * <CODE>false</CODE> if it should stop for some reason (e.g., the 1888 * time limit or size limit has been reached). 1889 * 1890 * @throws DirectoryException If a problem occurs during processing. 1891 */ 1892 private boolean searchSubtree(ConfigEntry baseEntry, SearchFilter filter, 1893 SearchOperation searchOperation) 1894 throws DirectoryException 1895 { 1896 Entry e = baseEntry.getEntry().duplicate(true); 1897 if (filter.matchesEntry(e)) 1898 { 1899 if (! searchOperation.returnEntry(e, null)) 1900 { 1901 return false; 1902 } 1903 } 1904 1905 for (ConfigEntry child : baseEntry.getChildren().values()) 1906 { 1907 if (! searchSubtree(child, filter, searchOperation)) 1908 { 1909 return false; 1910 } 1911 } 1912 1913 return true; 1914 } 1915 1916 1917 1918 /** 1919 * {@inheritDoc} 1920 */ 1921 @Override() 1922 public void writeUpdatedConfig() 1923 throws DirectoryException 1924 { 1925 // FIXME -- This needs support for encryption. 1926 1927 1928 // Calculate an archive for the current server configuration file and see if 1929 // it matches what we expect. If not, then the file has been manually 1930 // edited with the server online which is a bad thing. In that case, we'll 1931 // copy the current config off to the side before writing the new config 1932 // so that the manual changes don't get lost but also don't get applied. 1933 // Also, send an admin alert notifying administrators about the problem. 1934 if (maintainConfigArchive) 1935 { 1936 try 1937 { 1938 byte[] currentDigest = calculateConfigDigest(); 1939 if (! Arrays.equals(configurationDigest, currentDigest)) 1940 { 1941 File existingCfg = new File(configFile); 1942 File newConfigFile = new File(existingCfg.getParent(), 1943 "config.manualedit-" + 1944 TimeThread.getGMTTime() + ".ldif"); 1945 int counter = 2; 1946 while (newConfigFile.exists()) 1947 { 1948 newConfigFile = new File(newConfigFile.getAbsolutePath() + "." + 1949 counter++); 1950 } 1951 1952 FileInputStream inputStream = new FileInputStream(existingCfg); 1953 FileOutputStream outputStream = new FileOutputStream(newConfigFile); 1954 byte[] buffer = new byte[8192]; 1955 while (true) 1956 { 1957 int bytesRead = inputStream.read(buffer); 1958 if (bytesRead < 0) 1959 { 1960 break; 1961 } 1962 1963 outputStream.write(buffer, 0, bytesRead); 1964 } 1965 1966 inputStream.close(); 1967 outputStream.close(); 1968 1969 Message message = WARN_CONFIG_MANUAL_CHANGES_DETECTED.get( 1970 configFile, newConfigFile.getAbsolutePath()); 1971 logError(message); 1972 1973 DirectoryServer.sendAlertNotification(this, 1974 ALERT_TYPE_MANUAL_CONFIG_EDIT_HANDLED, message); 1975 } 1976 } 1977 catch (Exception e) 1978 { 1979 if (debugEnabled()) 1980 { 1981 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1982 } 1983 1984 Message message = ERR_CONFIG_MANUAL_CHANGES_LOST.get( 1985 configFile, stackTraceToSingleLineString(e)); 1986 logError(message); 1987 1988 DirectoryServer.sendAlertNotification(this, 1989 ALERT_TYPE_MANUAL_CONFIG_EDIT_HANDLED, message); 1990 } 1991 } 1992 1993 1994 // Write the new configuration to a temporary file. 1995 String tempConfig = configFile + ".tmp"; 1996 try 1997 { 1998 LDIFExportConfig exportConfig = 1999 new LDIFExportConfig(tempConfig, ExistingFileBehavior.OVERWRITE); 2000 2001 // FIXME -- Add all the appropriate configuration options. 2002 writeLDIF(exportConfig); 2003 } 2004 catch (Exception e) 2005 { 2006 if (debugEnabled()) 2007 { 2008 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2009 } 2010 2011 Message message = ERR_CONFIG_FILE_WRITE_CANNOT_EXPORT_NEW_CONFIG.get( 2012 String.valueOf(tempConfig), stackTraceToSingleLineString(e)); 2013 logError(message); 2014 2015 DirectoryServer.sendAlertNotification(this, 2016 ALERT_TYPE_CANNOT_WRITE_CONFIGURATION, message); 2017 return; 2018 } 2019 2020 2021 // Delete the previous version of the configuration and rename the new one. 2022 try 2023 { 2024 File actualConfig = new File(configFile); 2025 File tmpConfig = new File(tempConfig); 2026 renameFile(tmpConfig, actualConfig); 2027 } 2028 catch (Exception e) 2029 { 2030 if (debugEnabled()) 2031 { 2032 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2033 } 2034 2035 Message message = ERR_CONFIG_FILE_WRITE_CANNOT_RENAME_NEW_CONFIG. 2036 get(String.valueOf(tempConfig), String.valueOf(configFile), 2037 stackTraceToSingleLineString(e)); 2038 logError(message); 2039 2040 DirectoryServer.sendAlertNotification(this, 2041 ALERT_TYPE_CANNOT_WRITE_CONFIGURATION, message); 2042 return; 2043 } 2044 2045 configurationDigest = calculateConfigDigest(); 2046 2047 2048 // Try to write the archive for the new configuration. 2049 if (maintainConfigArchive) 2050 { 2051 writeConfigArchive(); 2052 } 2053 } 2054 2055 2056 2057 /** 2058 * Writes the current configuration to the configuration archive. This will 2059 * be a best-effort attempt. 2060 */ 2061 private void writeConfigArchive() 2062 { 2063 if (! maintainConfigArchive) 2064 { 2065 return; 2066 } 2067 2068 // Determine the path to the directory that will hold the archived 2069 // configuration files. 2070 File configDirectory = new File(configFile).getParentFile(); 2071 File archiveDirectory = new File(configDirectory, CONFIG_ARCHIVE_DIR_NAME); 2072 2073 2074 // If the archive directory doesn't exist, then create it. 2075 if (! archiveDirectory.exists()) 2076 { 2077 try 2078 { 2079 if (! archiveDirectory.mkdirs()) 2080 { 2081 Message message = ERR_CONFIG_FILE_CANNOT_CREATE_ARCHIVE_DIR_NO_REASON. 2082 get(archiveDirectory.getAbsolutePath()); 2083 logError(message); 2084 2085 DirectoryServer.sendAlertNotification(this, 2086 ALERT_TYPE_CANNOT_WRITE_CONFIGURATION, message); 2087 return; 2088 } 2089 } 2090 catch (Exception e) 2091 { 2092 if (debugEnabled()) 2093 { 2094 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2095 } 2096 2097 Message message = ERR_CONFIG_FILE_CANNOT_CREATE_ARCHIVE_DIR. 2098 get(archiveDirectory.getAbsolutePath(), 2099 stackTraceToSingleLineString(e)); 2100 logError(message); 2101 2102 DirectoryServer.sendAlertNotification(this, 2103 ALERT_TYPE_CANNOT_WRITE_CONFIGURATION, message); 2104 return; 2105 } 2106 } 2107 2108 2109 // Determine the appropriate name to use for the current configuration. 2110 File archiveFile; 2111 try 2112 { 2113 String timestamp = TimeThread.getGMTTime(); 2114 archiveFile = new File(archiveDirectory, "config-" + timestamp + ".gz"); 2115 if (archiveFile.exists()) 2116 { 2117 int counter = 2; 2118 archiveFile = new File(archiveDirectory, 2119 "config-" + timestamp + "-" + counter + ".gz"); 2120 2121 while (archiveFile.exists()) 2122 { 2123 counter++; 2124 archiveFile = new File(archiveDirectory, 2125 "config-" + timestamp + "-" + counter + ".gz"); 2126 } 2127 } 2128 } 2129 catch (Exception e) 2130 { 2131 if (debugEnabled()) 2132 { 2133 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2134 } 2135 2136 Message message = ERR_CONFIG_FILE_CANNOT_WRITE_CONFIG_ARCHIVE.get( 2137 stackTraceToSingleLineString(e)); 2138 logError(message); 2139 2140 DirectoryServer.sendAlertNotification(this, 2141 ALERT_TYPE_CANNOT_WRITE_CONFIGURATION, message); 2142 return; 2143 } 2144 2145 2146 // Copy the current configuration to the new configuration file. 2147 byte[] buffer = new byte[8192]; 2148 FileInputStream inputStream = null; 2149 GZIPOutputStream outputStream = null; 2150 try 2151 { 2152 inputStream = new FileInputStream(configFile); 2153 outputStream = new GZIPOutputStream(new FileOutputStream(archiveFile)); 2154 2155 int bytesRead = inputStream.read(buffer); 2156 while (bytesRead > 0) 2157 { 2158 outputStream.write(buffer, 0, bytesRead); 2159 bytesRead = inputStream.read(buffer); 2160 } 2161 } 2162 catch (Exception e) 2163 { 2164 if (debugEnabled()) 2165 { 2166 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2167 } 2168 2169 Message message = ERR_CONFIG_FILE_CANNOT_WRITE_CONFIG_ARCHIVE.get( 2170 stackTraceToSingleLineString(e)); 2171 logError(message); 2172 2173 DirectoryServer.sendAlertNotification(this, 2174 ALERT_TYPE_CANNOT_WRITE_CONFIGURATION, message); 2175 return; 2176 } 2177 finally 2178 { 2179 try 2180 { 2181 inputStream.close(); 2182 } catch (Exception e) {} 2183 2184 try 2185 { 2186 outputStream.close(); 2187 } catch (Exception e) {} 2188 } 2189 2190 2191 // If we should enforce a maximum number of archived configurations, then 2192 // see if there are any old ones that we need to delete. 2193 if (maxConfigArchiveSize > 0) 2194 { 2195 String[] archivedFileList = archiveDirectory.list(); 2196 int numToDelete = archivedFileList.length - maxConfigArchiveSize; 2197 if (numToDelete > 0) 2198 { 2199 TreeSet<String> archiveSet = new TreeSet<String>(); 2200 for (String name : archivedFileList) 2201 { 2202 if (! name.startsWith("config-")) 2203 { 2204 continue; 2205 } 2206 2207 // Simply ordering by filename should work, even when there are 2208 // timestamp conflicts, because the dash comes before the period in 2209 // the ASCII character set. 2210 archiveSet.add(name); 2211 } 2212 2213 Iterator<String> iterator = archiveSet.iterator(); 2214 for (int i=0; ((i < numToDelete) && iterator.hasNext()); i++) 2215 { 2216 File f = new File(archiveDirectory, iterator.next()); 2217 try 2218 { 2219 f.delete(); 2220 } catch (Exception e) {} 2221 } 2222 } 2223 } 2224 } 2225 2226 2227 2228 /** 2229 * {@inheritDoc} 2230 */ 2231 @Override() 2232 public void writeSuccessfulStartupConfig() 2233 { 2234 if (useLastKnownGoodConfig) 2235 { 2236 // The server was started with the "last known good" configuration, so we 2237 // shouldn't overwrite it with something that is probably bad. 2238 return; 2239 } 2240 2241 2242 String startOKFilePath = configFile + ".startok"; 2243 String tempFilePath = startOKFilePath + ".tmp"; 2244 String oldFilePath = startOKFilePath + ".old"; 2245 2246 2247 // Copy the current config file to a temporary file. 2248 File tempFile = new File(tempFilePath); 2249 FileInputStream inputStream = null; 2250 try 2251 { 2252 inputStream = new FileInputStream(configFile); 2253 2254 FileOutputStream outputStream = null; 2255 try 2256 { 2257 outputStream = new FileOutputStream(tempFilePath, false); 2258 2259 try 2260 { 2261 byte[] buffer = new byte[8192]; 2262 while (true) 2263 { 2264 int bytesRead = inputStream.read(buffer); 2265 if (bytesRead < 0) 2266 { 2267 break; 2268 } 2269 2270 outputStream.write(buffer, 0, bytesRead); 2271 } 2272 } 2273 catch (Exception e) 2274 { 2275 if (debugEnabled()) 2276 { 2277 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2278 } 2279 2280 logError(ERR_STARTOK_CANNOT_WRITE.get(configFile, tempFilePath, 2281 getExceptionMessage(e))); 2282 return; 2283 } 2284 } 2285 catch (Exception e) 2286 { 2287 if (debugEnabled()) 2288 { 2289 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2290 } 2291 2292 logError(ERR_STARTOK_CANNOT_OPEN_FOR_WRITING.get(tempFilePath, 2293 getExceptionMessage(e))); 2294 return; 2295 } 2296 finally 2297 { 2298 try 2299 { 2300 outputStream.close(); 2301 } 2302 catch (Exception e) 2303 { 2304 if (debugEnabled()) 2305 { 2306 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2307 } 2308 } 2309 } 2310 } 2311 catch (Exception e) 2312 { 2313 if (debugEnabled()) 2314 { 2315 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2316 } 2317 2318 logError(ERR_STARTOK_CANNOT_OPEN_FOR_READING.get(configFile, 2319 getExceptionMessage(e))); 2320 return; 2321 } 2322 finally 2323 { 2324 try 2325 { 2326 inputStream.close(); 2327 } 2328 catch (Exception e) 2329 { 2330 if (debugEnabled()) 2331 { 2332 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2333 } 2334 } 2335 } 2336 2337 2338 // If a ".startok" file already exists, then move it to an ".old" file. 2339 File oldFile = new File(oldFilePath); 2340 try 2341 { 2342 if (oldFile.exists()) 2343 { 2344 oldFile.delete(); 2345 } 2346 } 2347 catch (Exception e) 2348 { 2349 if (debugEnabled()) 2350 { 2351 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2352 } 2353 } 2354 2355 File startOKFile = new File(startOKFilePath); 2356 try 2357 { 2358 if (startOKFile.exists()) 2359 { 2360 startOKFile.renameTo(oldFile); 2361 } 2362 } 2363 catch (Exception e) 2364 { 2365 if (debugEnabled()) 2366 { 2367 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2368 } 2369 } 2370 2371 2372 // Rename the temp file to the ".startok" file. 2373 try 2374 { 2375 tempFile.renameTo(startOKFile); 2376 } catch (Exception e) 2377 { 2378 if (debugEnabled()) 2379 { 2380 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2381 } 2382 2383 logError(ERR_STARTOK_CANNOT_RENAME.get(tempFilePath, startOKFilePath, 2384 getExceptionMessage(e))); 2385 return; 2386 } 2387 2388 2389 // Remove the ".old" file if there is one. 2390 try 2391 { 2392 if (oldFile.exists()) 2393 { 2394 oldFile.delete(); 2395 } 2396 } 2397 catch (Exception e) 2398 { 2399 if (debugEnabled()) 2400 { 2401 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2402 } 2403 } 2404 } 2405 2406 2407 2408 /** 2409 * {@inheritDoc} 2410 */ 2411 @Override() 2412 public HashSet<String> getSupportedControls() 2413 { 2414 return SUPPORTED_CONTROLS; 2415 } 2416 2417 2418 2419 /** 2420 * {@inheritDoc} 2421 */ 2422 @Override() 2423 public HashSet<String> getSupportedFeatures() 2424 { 2425 return SUPPORTED_FEATURES; 2426 } 2427 2428 2429 2430 /** 2431 * {@inheritDoc} 2432 */ 2433 @Override() 2434 public boolean supportsLDIFExport() 2435 { 2436 // TODO We would need export-ldif to initialize this backend. 2437 return false; 2438 } 2439 2440 2441 2442 /** 2443 * {@inheritDoc} 2444 */ 2445 @Override() 2446 public void exportLDIF(LDIFExportConfig exportConfig) 2447 throws DirectoryException 2448 { 2449 // TODO We would need export-ldif to initialize this backend. 2450 writeLDIF(exportConfig); 2451 } 2452 2453 2454 2455 /** 2456 * Writes the current configuration to LDIF with the provided export 2457 * configuration. 2458 * 2459 * @param exportConfig The configuration to use for the export. 2460 * 2461 * @throws DirectoryException If a problem occurs while writing the LDIF. 2462 */ 2463 private void writeLDIF(LDIFExportConfig exportConfig) 2464 throws DirectoryException 2465 { 2466 LDIFWriter writer; 2467 try 2468 { 2469 writer = new LDIFWriter(exportConfig); 2470 writer.writeComment(INFO_CONFIG_FILE_HEADER.get(), 80); 2471 writeEntryAndChildren(writer, configRootEntry); 2472 } 2473 catch (Exception e) 2474 { 2475 if (debugEnabled()) 2476 { 2477 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2478 } 2479 2480 Message message = ERR_CONFIG_LDIF_WRITE_ERROR.get(String.valueOf(e)); 2481 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2482 message, e); 2483 } 2484 2485 try 2486 { 2487 writer.close(); 2488 } 2489 catch (Exception e) 2490 { 2491 if (debugEnabled()) 2492 { 2493 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2494 } 2495 2496 Message message = ERR_CONFIG_FILE_CLOSE_ERROR.get(String.valueOf(e)); 2497 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2498 message, e); 2499 } 2500 } 2501 2502 2503 2504 /** 2505 * Writes the provided entry and any children that it may have to the provided 2506 * LDIF writer. 2507 * 2508 * @param writer The LDIF writer to use to write the entry and its 2509 * children. 2510 * @param configEntry The configuration entry to write, along with its 2511 * children. 2512 * 2513 * @throws DirectoryException If a problem occurs while attempting to write 2514 * the entry or one of its children. 2515 */ 2516 private void writeEntryAndChildren(LDIFWriter writer, ConfigEntry configEntry) 2517 throws DirectoryException 2518 { 2519 try 2520 { 2521 // Write the entry itself to LDIF. 2522 writer.writeEntry(configEntry.getEntry()); 2523 } 2524 catch (Exception e) 2525 { 2526 if (debugEnabled()) 2527 { 2528 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2529 } 2530 2531 Message message = ERR_CONFIG_FILE_WRITE_ERROR.get( 2532 configEntry.getDN().toString(), String.valueOf(e)); 2533 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2534 message, e); 2535 } 2536 2537 2538 // See if the entry has any children. If so, then iterate through them and 2539 // write them and their children. We'll copy the entries into a tree map 2540 // so that we have a sensible order in the resulting LDIF. 2541 TreeMap<DN,ConfigEntry> childMap = 2542 new TreeMap<DN,ConfigEntry>(configEntry.getChildren()); 2543 for (ConfigEntry childEntry : childMap.values()) 2544 { 2545 writeEntryAndChildren(writer, childEntry); 2546 } 2547 } 2548 2549 2550 2551 /** 2552 * {@inheritDoc} 2553 */ 2554 @Override() 2555 public boolean supportsLDIFImport() 2556 { 2557 return false; 2558 } 2559 2560 2561 2562 /** 2563 * {@inheritDoc} 2564 */ 2565 @Override() 2566 public LDIFImportResult importLDIF(LDIFImportConfig importConfig) 2567 throws DirectoryException 2568 { 2569 Message message = ERR_CONFIG_FILE_UNWILLING_TO_IMPORT.get(); 2570 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 2571 } 2572 2573 2574 2575 /** 2576 * {@inheritDoc} 2577 */ 2578 @Override() 2579 public boolean supportsBackup() 2580 { 2581 // We do support an online backup mechanism for the configuration. 2582 return true; 2583 } 2584 2585 2586 2587 /** 2588 * {@inheritDoc} 2589 */ 2590 @Override() 2591 public boolean supportsBackup(BackupConfig backupConfig, 2592 StringBuilder unsupportedReason) 2593 { 2594 // We should support online backup for the configuration in any form. This 2595 // implementation does not support incremental backups, but in this case 2596 // even if we're asked to do an incremental we'll just do a full backup 2597 // instead. So the answer to this should always be "true". 2598 return true; 2599 } 2600 2601 2602 2603 /** 2604 * {@inheritDoc} 2605 */ 2606 @Override() 2607 public void createBackup(BackupConfig backupConfig) 2608 throws DirectoryException 2609 { 2610 // Get the properties to use for the backup. We don't care whether or not 2611 // it's incremental, so there's no need to get that. 2612 String backupID = backupConfig.getBackupID(); 2613 BackupDirectory backupDirectory = backupConfig.getBackupDirectory(); 2614 boolean compress = backupConfig.compressData(); 2615 boolean encrypt = backupConfig.encryptData(); 2616 boolean hash = backupConfig.hashData(); 2617 boolean signHash = backupConfig.signHash(); 2618 2619 2620 // Create a hash map that will hold the extra backup property information 2621 // for this backup. 2622 HashMap<String,String> backupProperties = new HashMap<String,String>(); 2623 2624 2625 // Get the crypto manager and use it to obtain references to the message 2626 // digest and/or MAC to use for hashing and/or signing. 2627 CryptoManager cryptoManager = DirectoryServer.getCryptoManager(); 2628 Mac mac = null; 2629 MessageDigest digest = null; 2630 String digestAlgorithm = null; 2631 String macKeyID = null; 2632 2633 if (hash) 2634 { 2635 if (signHash) 2636 { 2637 try 2638 { 2639 macKeyID = cryptoManager.getMacEngineKeyEntryID(); 2640 backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID); 2641 2642 mac = cryptoManager.getMacEngine(macKeyID); 2643 } 2644 catch (Exception e) 2645 { 2646 if (debugEnabled()) 2647 { 2648 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2649 } 2650 2651 Message message = ERR_CONFIG_BACKUP_CANNOT_GET_MAC.get( 2652 macKeyID, stackTraceToSingleLineString(e)); 2653 throw new DirectoryException( 2654 DirectoryServer.getServerErrorResultCode(), message, 2655 e); 2656 } 2657 } 2658 else 2659 { 2660 digestAlgorithm = cryptoManager.getPreferredMessageDigestAlgorithm(); 2661 backupProperties.put(BACKUP_PROPERTY_DIGEST_ALGORITHM, digestAlgorithm); 2662 2663 try 2664 { 2665 digest = cryptoManager.getPreferredMessageDigest(); 2666 } 2667 catch (Exception e) 2668 { 2669 if (debugEnabled()) 2670 { 2671 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2672 } 2673 2674 Message message = ERR_CONFIG_BACKUP_CANNOT_GET_DIGEST.get( 2675 digestAlgorithm, stackTraceToSingleLineString(e)); 2676 throw new DirectoryException( 2677 DirectoryServer.getServerErrorResultCode(), message, 2678 e); 2679 } 2680 } 2681 } 2682 2683 2684 // Create an output stream that will be used to write the archive file. At 2685 // its core, it will be a file output stream to put a file on the disk. If 2686 // we are to encrypt the data, then that file output stream will be wrapped 2687 // in a cipher output stream. The resulting output stream will then be 2688 // wrapped by a zip output stream (which may or may not actually use 2689 // compression). 2690 String filename = null; 2691 OutputStream outputStream; 2692 try 2693 { 2694 filename = CONFIG_BACKUP_BASE_FILENAME + backupID; 2695 File archiveFile = new File(backupDirectory.getPath() + File.separator + 2696 filename); 2697 if (archiveFile.exists()) 2698 { 2699 int i=1; 2700 while (true) 2701 { 2702 archiveFile = new File(backupDirectory.getPath() + File.separator + 2703 filename + "." + i); 2704 if (archiveFile.exists()) 2705 { 2706 i++; 2707 } 2708 else 2709 { 2710 filename = filename + "." + i; 2711 break; 2712 } 2713 } 2714 } 2715 2716 outputStream = new FileOutputStream(archiveFile, false); 2717 backupProperties.put(BACKUP_PROPERTY_ARCHIVE_FILENAME, filename); 2718 } 2719 catch (Exception e) 2720 { 2721 if (debugEnabled()) 2722 { 2723 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2724 } 2725 2726 Message message = ERR_CONFIG_BACKUP_CANNOT_CREATE_ARCHIVE_FILE. 2727 get(String.valueOf(filename), backupDirectory.getPath(), 2728 stackTraceToSingleLineString(e)); 2729 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2730 message, e); 2731 } 2732 2733 2734 // If we should encrypt the data, then wrap the output stream in a cipher 2735 // output stream. 2736 if (encrypt) 2737 { 2738 try 2739 { 2740 outputStream 2741 = cryptoManager.getCipherOutputStream(outputStream); 2742 } 2743 catch (Exception e) 2744 { 2745 if (debugEnabled()) 2746 { 2747 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2748 } 2749 2750 Message message = ERR_CONFIG_BACKUP_CANNOT_GET_CIPHER.get( 2751 stackTraceToSingleLineString(e)); 2752 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2753 message, e); 2754 } 2755 } 2756 2757 2758 // Wrap the file output stream in a zip output stream. 2759 ZipOutputStream zipStream = new ZipOutputStream(outputStream); 2760 2761 Message message = ERR_CONFIG_BACKUP_ZIP_COMMENT.get( 2762 DynamicConstants.PRODUCT_NAME, 2763 backupID); 2764 zipStream.setComment(message.toString()); 2765 2766 if (compress) 2767 { 2768 zipStream.setLevel(Deflater.DEFAULT_COMPRESSION); 2769 } 2770 else 2771 { 2772 zipStream.setLevel(Deflater.NO_COMPRESSION); 2773 } 2774 2775 2776 // This may seem a little weird, but in this context, we only have access to 2777 // this class as a backend and not as the config handler. We need it as a 2778 // config handler to determine the path to the config file, so we can get 2779 // that from the Directory Server object. 2780 String configFile = null; 2781 try 2782 { 2783 configFile = 2784 ((ConfigFileHandler) DirectoryServer.getConfigHandler()).configFile; 2785 } 2786 catch (Exception e) 2787 { 2788 if (debugEnabled()) 2789 { 2790 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2791 } 2792 2793 message = ERR_CONFIG_BACKUP_CANNOT_DETERMINE_CONFIG_FILE_LOCATION. 2794 get(getExceptionMessage(e)); 2795 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2796 message, e); 2797 } 2798 2799 2800 // Read the Directory Server configuration file and put it in the archive. 2801 byte[] buffer = new byte[8192]; 2802 FileInputStream inputStream = null; 2803 try 2804 { 2805 File f = new File(configFile); 2806 2807 ZipEntry zipEntry = new ZipEntry(f.getName()); 2808 zipStream.putNextEntry(zipEntry); 2809 2810 inputStream = new FileInputStream(f); 2811 while (true) 2812 { 2813 int bytesRead = inputStream.read(buffer); 2814 if (bytesRead < 0 || backupConfig.isCancelled()) 2815 { 2816 break; 2817 } 2818 2819 if (hash) 2820 { 2821 if (signHash) 2822 { 2823 mac.update(buffer, 0, bytesRead); 2824 } 2825 else 2826 { 2827 digest.update(buffer, 0, bytesRead); 2828 } 2829 } 2830 2831 zipStream.write(buffer, 0, bytesRead); 2832 } 2833 2834 inputStream.close(); 2835 zipStream.closeEntry(); 2836 } 2837 catch (Exception e) 2838 { 2839 if (debugEnabled()) 2840 { 2841 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2842 } 2843 2844 try 2845 { 2846 inputStream.close(); 2847 } catch (Exception e2) {} 2848 2849 try 2850 { 2851 zipStream.close(); 2852 } catch (Exception e2) {} 2853 2854 message = ERR_CONFIG_BACKUP_CANNOT_BACKUP_CONFIG_FILE.get( 2855 configFile, stackTraceToSingleLineString(e)); 2856 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2857 message, e); 2858 } 2859 2860 2861 // If an archive directory exists, then add its contents as well. 2862 try 2863 { 2864 File archiveDirectory = new File(new File(configFile).getParent(), 2865 CONFIG_ARCHIVE_DIR_NAME); 2866 if (archiveDirectory.exists()) 2867 { 2868 for (File archiveFile : archiveDirectory.listFiles()) 2869 { 2870 ZipEntry zipEntry = new ZipEntry(CONFIG_ARCHIVE_DIR_NAME + 2871 File.separator + 2872 archiveFile.getName()); 2873 zipStream.putNextEntry(zipEntry); 2874 inputStream = new FileInputStream(archiveFile); 2875 while (true) 2876 { 2877 int bytesRead = inputStream.read(buffer); 2878 if (bytesRead < 0 || backupConfig.isCancelled()) 2879 { 2880 break; 2881 } 2882 2883 if (hash) 2884 { 2885 if (signHash) 2886 { 2887 mac.update(buffer, 0, bytesRead); 2888 } 2889 else 2890 { 2891 digest.update(buffer, 0, bytesRead); 2892 } 2893 } 2894 2895 zipStream.write(buffer, 0, bytesRead); 2896 } 2897 2898 inputStream.close(); 2899 zipStream.closeEntry(); 2900 } 2901 } 2902 } 2903 catch (Exception e) 2904 { 2905 if (debugEnabled()) 2906 { 2907 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2908 } 2909 2910 try 2911 { 2912 inputStream.close(); 2913 } catch (Exception e2) {} 2914 2915 try 2916 { 2917 zipStream.close(); 2918 } catch (Exception e2) {} 2919 2920 message = ERR_CONFIG_BACKUP_CANNOT_BACKUP_ARCHIVED_CONFIGS.get( 2921 configFile, stackTraceToSingleLineString(e)); 2922 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2923 message, e); 2924 } 2925 2926 2927 // We're done writing the file, so close the zip stream (which should also 2928 // close the underlying stream). 2929 try 2930 { 2931 zipStream.close(); 2932 } 2933 catch (Exception e) 2934 { 2935 if (debugEnabled()) 2936 { 2937 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2938 } 2939 2940 message = ERR_CONFIG_BACKUP_CANNOT_CLOSE_ZIP_STREAM.get( 2941 filename, backupDirectory.getPath(), getExceptionMessage(e)); 2942 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2943 message, e); 2944 } 2945 2946 2947 // Get the digest or MAC bytes if appropriate. 2948 byte[] digestBytes = null; 2949 byte[] macBytes = null; 2950 if (hash) 2951 { 2952 if (signHash) 2953 { 2954 macBytes = mac.doFinal(); 2955 } 2956 else 2957 { 2958 digestBytes = digest.digest(); 2959 } 2960 } 2961 2962 2963 // Create the backup info structure for this backup and add it to the backup 2964 // directory. 2965 // FIXME -- Should I use the date from when I started or finished? 2966 BackupInfo backupInfo = new BackupInfo(backupDirectory, backupID, 2967 new Date(), false, compress, 2968 encrypt, digestBytes, macBytes, 2969 null, backupProperties); 2970 2971 try 2972 { 2973 backupDirectory.addBackup(backupInfo); 2974 backupDirectory.writeBackupDirectoryDescriptor(); 2975 } 2976 catch (Exception e) 2977 { 2978 if (debugEnabled()) 2979 { 2980 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2981 } 2982 2983 message = ERR_CONFIG_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get( 2984 backupDirectory.getDescriptorPath(), stackTraceToSingleLineString(e)); 2985 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 2986 message, e); 2987 } 2988 2989 // Remove the backup if this operation was cancelled since the 2990 // backup may be incomplete 2991 if (backupConfig.isCancelled()) 2992 { 2993 removeBackup(backupDirectory, backupID); 2994 } 2995 2996 } 2997 2998 2999 3000 /** 3001 * {@inheritDoc} 3002 */ 3003 @Override() 3004 public void removeBackup(BackupDirectory backupDirectory, 3005 String backupID) 3006 throws DirectoryException 3007 { 3008 // NYI 3009 } 3010 3011 3012 3013 /** 3014 * {@inheritDoc} 3015 */ 3016 @Override() 3017 public boolean supportsRestore() 3018 { 3019 // We will provide a restore, but only for offline operations. 3020 return true; 3021 } 3022 3023 3024 3025 /** 3026 * {@inheritDoc} 3027 */ 3028 @Override() 3029 public void restoreBackup(RestoreConfig restoreConfig) 3030 throws DirectoryException 3031 { 3032 // First, make sure that the requested backup exists. 3033 BackupDirectory backupDirectory = restoreConfig.getBackupDirectory(); 3034 String backupPath = backupDirectory.getPath(); 3035 String backupID = restoreConfig.getBackupID(); 3036 BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID); 3037 if (backupInfo == null) 3038 { 3039 Message message = 3040 ERR_CONFIG_RESTORE_NO_SUCH_BACKUP.get(backupID, backupPath); 3041 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3042 message); 3043 } 3044 3045 3046 // Read the backup info structure to determine the name of the file that 3047 // contains the archive. Then make sure that file exists. 3048 String backupFilename = 3049 backupInfo.getBackupProperty(BACKUP_PROPERTY_ARCHIVE_FILENAME); 3050 if (backupFilename == null) 3051 { 3052 Message message = 3053 ERR_CONFIG_RESTORE_NO_BACKUP_FILE.get(backupID, backupPath); 3054 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3055 message); 3056 } 3057 3058 File backupFile = new File(backupPath + File.separator + backupFilename); 3059 try 3060 { 3061 if (! backupFile.exists()) 3062 { 3063 Message message = 3064 ERR_CONFIG_RESTORE_NO_SUCH_FILE.get(backupID, backupFile.getPath()); 3065 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3066 message); 3067 } 3068 } 3069 catch (DirectoryException de) 3070 { 3071 throw de; 3072 } 3073 catch (Exception e) 3074 { 3075 Message message = ERR_CONFIG_RESTORE_CANNOT_CHECK_FOR_ARCHIVE.get( 3076 backupID, backupFile.getPath(), stackTraceToSingleLineString(e)); 3077 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3078 message, e); 3079 } 3080 3081 3082 // If the backup is hashed, then we need to get the message digest to use 3083 // to verify it. 3084 byte[] unsignedHash = backupInfo.getUnsignedHash(); 3085 MessageDigest digest = null; 3086 if (unsignedHash != null) 3087 { 3088 String digestAlgorithm = 3089 backupInfo.getBackupProperty(BACKUP_PROPERTY_DIGEST_ALGORITHM); 3090 if (digestAlgorithm == null) 3091 { 3092 Message message = ERR_CONFIG_RESTORE_UNKNOWN_DIGEST.get(backupID); 3093 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3094 message); 3095 } 3096 3097 try 3098 { 3099 digest = DirectoryServer.getCryptoManager().getMessageDigest( 3100 digestAlgorithm); 3101 } 3102 catch (Exception e) 3103 { 3104 Message message = 3105 ERR_CONFIG_RESTORE_CANNOT_GET_DIGEST.get(backupID, digestAlgorithm); 3106 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3107 message, e); 3108 } 3109 } 3110 3111 3112 // If the backup is signed, then we need to get the MAC to use to verify it. 3113 byte[] signedHash = backupInfo.getSignedHash(); 3114 Mac mac = null; 3115 if (signedHash != null) 3116 { 3117 String macKeyID = 3118 backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID); 3119 if (macKeyID == null) 3120 { 3121 Message message = ERR_CONFIG_RESTORE_UNKNOWN_MAC.get(backupID); 3122 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3123 message); 3124 } 3125 3126 try 3127 { 3128 mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID); 3129 } 3130 catch (Exception e) 3131 { 3132 Message message = ERR_CONFIG_RESTORE_CANNOT_GET_MAC.get( 3133 backupID, macKeyID); 3134 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3135 message, e); 3136 } 3137 } 3138 3139 3140 // Create the input stream that will be used to read the backup file. At 3141 // its core, it will be a file input stream. 3142 InputStream inputStream; 3143 try 3144 { 3145 inputStream = new FileInputStream(backupFile); 3146 } 3147 catch (Exception e) 3148 { 3149 Message message = ERR_CONFIG_RESTORE_CANNOT_OPEN_BACKUP_FILE.get( 3150 backupID, backupFile.getPath(), stackTraceToSingleLineString(e)); 3151 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3152 message, e); 3153 } 3154 3155 // If the backup is encrypted, then we need to wrap the file input stream 3156 // in a cipher input stream. 3157 if (backupInfo.isEncrypted()) 3158 { 3159 try 3160 { 3161 inputStream = DirectoryServer.getCryptoManager() 3162 .getCipherInputStream(inputStream); 3163 } 3164 catch (Exception e) 3165 { 3166 Message message = ERR_CONFIG_RESTORE_CANNOT_GET_CIPHER.get( 3167 backupFile.getPath(), stackTraceToSingleLineString(e)); 3168 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3169 message, e); 3170 } 3171 } 3172 3173 // Now wrap the resulting input stream in a zip stream so that we can read 3174 // its contents. We don't need to worry about whether to use compression or 3175 // not because it will be handled automatically. 3176 ZipInputStream zipStream = new ZipInputStream(inputStream); 3177 3178 3179 // Determine whether we should actually do the restore, or if we should just 3180 // try to verify the archive. If we are going to actually do the restore, 3181 // then create a directory and move the existing config files there so that 3182 // they can be restored in case something goes wrong. 3183 String configFilePath = 3184 ((ConfigFileHandler) DirectoryServer.getConfigHandler()).configFile; 3185 File configFile = new File(configFilePath); 3186 File configDir = configFile.getParentFile(); 3187 String configDirPath = configDir.getPath(); 3188 String backupDirPath = null; 3189 File configBackupDir = null; 3190 boolean verifyOnly = restoreConfig.verifyOnly(); 3191 if (! verifyOnly) 3192 { 3193 // Create a new directory to hold the current config files. 3194 try 3195 { 3196 if (configDir.exists()) 3197 { 3198 String configBackupDirPath = configDirPath + ".save"; 3199 backupDirPath = configBackupDirPath; 3200 configBackupDir = new File(backupDirPath); 3201 if (configBackupDir.exists()) 3202 { 3203 int i=2; 3204 while (true) 3205 { 3206 backupDirPath = configBackupDirPath + i; 3207 configBackupDir = new File(backupDirPath); 3208 if (configBackupDir.exists()) 3209 { 3210 i++; 3211 } 3212 else 3213 { 3214 break; 3215 } 3216 } 3217 } 3218 3219 configBackupDir.mkdirs(); 3220 moveFile(configFile, configBackupDir); 3221 3222 File archiveDirectory = new File(configDir, CONFIG_ARCHIVE_DIR_NAME); 3223 if (archiveDirectory.exists()) 3224 { 3225 File archiveBackupPath = new File(configBackupDir, 3226 CONFIG_ARCHIVE_DIR_NAME); 3227 archiveDirectory.renameTo(archiveBackupPath); 3228 } 3229 } 3230 } 3231 catch (Exception e) 3232 { 3233 Message message = ERR_CONFIG_RESTORE_CANNOT_BACKUP_EXISTING_CONFIG. 3234 get(backupID, configDirPath, String.valueOf(backupDirPath), 3235 getExceptionMessage(e)); 3236 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3237 message, e); 3238 } 3239 3240 3241 // Create a new directory to hold the restored config files. 3242 try 3243 { 3244 configDir.mkdirs(); 3245 } 3246 catch (Exception e) 3247 { 3248 // Try to restore the previous config directory if possible. This will 3249 // probably fail in this case, but try anyway. 3250 if (configBackupDir != null) 3251 { 3252 try 3253 { 3254 configBackupDir.renameTo(configDir); 3255 Message message = 3256 NOTE_CONFIG_RESTORE_RESTORED_OLD_CONFIG.get(configDirPath); 3257 logError(message); 3258 } 3259 catch (Exception e2) 3260 { 3261 Message message = ERR_CONFIG_RESTORE_CANNOT_RESTORE_OLD_CONFIG.get( 3262 configBackupDir.getPath()); 3263 logError(message); 3264 } 3265 } 3266 3267 3268 Message message = ERR_CONFIG_RESTORE_CANNOT_CREATE_CONFIG_DIRECTORY.get( 3269 backupID, configDirPath, getExceptionMessage(e)); 3270 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3271 message, e); 3272 } 3273 } 3274 3275 3276 // Read through the archive file an entry at a time. For each entry, update 3277 // the digest or MAC if necessary, and if we're actually doing the restore, 3278 // then write the files out into the config directory. 3279 byte[] buffer = new byte[8192]; 3280 while (true) 3281 { 3282 ZipEntry zipEntry; 3283 try 3284 { 3285 zipEntry = zipStream.getNextEntry(); 3286 } 3287 catch (Exception e) 3288 { 3289 // Tell the user where the previous config was archived. 3290 if (configBackupDir != null) 3291 { 3292 Message message = ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED.get( 3293 configBackupDir.getPath()); 3294 logError(message); 3295 } 3296 3297 Message message = ERR_CONFIG_RESTORE_CANNOT_GET_ZIP_ENTRY.get( 3298 backupID, backupFile.getPath(), stackTraceToSingleLineString(e)); 3299 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3300 message, e); 3301 } 3302 3303 if (zipEntry == null) 3304 { 3305 break; 3306 } 3307 3308 3309 // Get the filename for the zip entry and update the digest or MAC as 3310 // necessary. 3311 String fileName = zipEntry.getName(); 3312 if (digest != null) 3313 { 3314 digest.update(getBytes(fileName)); 3315 } 3316 if (mac != null) 3317 { 3318 mac.update(getBytes(fileName)); 3319 } 3320 3321 3322 // If we're doing the restore, then create the output stream to write the 3323 // file. 3324 OutputStream outputStream = null; 3325 if (! verifyOnly) 3326 { 3327 File restoreFile = new File(configDirPath + File.separator + fileName); 3328 File parentDir = restoreFile.getParentFile(); 3329 3330 try 3331 { 3332 if (! parentDir.exists()) 3333 { 3334 parentDir.mkdirs(); 3335 } 3336 3337 outputStream = new FileOutputStream(restoreFile); 3338 } 3339 catch (Exception e) 3340 { 3341 // Tell the user where the previous config was archived. 3342 if (configBackupDir != null) 3343 { 3344 Message message = ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED.get( 3345 configBackupDir.getPath()); 3346 logError(message); 3347 } 3348 3349 Message message = ERR_CONFIG_RESTORE_CANNOT_CREATE_FILE. 3350 get(backupID, restoreFile.getAbsolutePath(), 3351 stackTraceToSingleLineString(e)); 3352 throw new DirectoryException( 3353 DirectoryServer.getServerErrorResultCode(), message, 3354 e); 3355 } 3356 } 3357 3358 3359 // Read the contents of the file and update the digest or MAC as 3360 // necessary. If we're actually restoring it, then write it into the 3361 // new config directory. 3362 try 3363 { 3364 while (true) 3365 { 3366 int bytesRead = zipStream.read(buffer); 3367 if (bytesRead < 0) 3368 { 3369 // We've reached the end of the entry. 3370 break; 3371 } 3372 3373 3374 // Update the digest or MAC if appropriate. 3375 if (digest != null) 3376 { 3377 digest.update(buffer, 0, bytesRead); 3378 } 3379 3380 if (mac != null) 3381 { 3382 mac.update(buffer, 0, bytesRead); 3383 } 3384 3385 3386 // Write the data to the output stream if appropriate. 3387 if (outputStream != null) 3388 { 3389 outputStream.write(buffer, 0, bytesRead); 3390 } 3391 } 3392 3393 3394 // We're at the end of the file so close the output stream if we're 3395 // writing it. 3396 if (outputStream != null) 3397 { 3398 outputStream.close(); 3399 } 3400 } 3401 catch (Exception e) 3402 { 3403 // Tell the user where the previous config was archived. 3404 if (configBackupDir != null) 3405 { 3406 Message message = ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED.get( 3407 configBackupDir.getPath()); 3408 logError(message); 3409 } 3410 3411 Message message = ERR_CONFIG_RESTORE_CANNOT_PROCESS_ARCHIVE_FILE.get( 3412 backupID, fileName, stackTraceToSingleLineString(e)); 3413 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3414 message, e); 3415 } 3416 } 3417 3418 3419 // Close the zip stream since we don't need it anymore. 3420 try 3421 { 3422 zipStream.close(); 3423 } 3424 catch (Exception e) 3425 { 3426 Message message = ERR_CONFIG_RESTORE_ERROR_ON_ZIP_STREAM_CLOSE.get( 3427 backupID, backupFile.getPath(), getExceptionMessage(e)); 3428 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3429 message, e); 3430 } 3431 3432 3433 // At this point, we should be done with the contents of the ZIP file and 3434 // the restore should be complete. If we were generating a digest or MAC, 3435 // then make sure it checks out. 3436 if (digest != null) 3437 { 3438 byte[] calculatedHash = digest.digest(); 3439 if (Arrays.equals(calculatedHash, unsignedHash)) 3440 { 3441 Message message = NOTE_CONFIG_RESTORE_UNSIGNED_HASH_VALID.get(); 3442 logError(message); 3443 } 3444 else 3445 { 3446 // Tell the user where the previous config was archived. 3447 if (configBackupDir != null) 3448 { 3449 Message message = ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED.get( 3450 configBackupDir.getPath()); 3451 logError(message); 3452 } 3453 3454 Message message = 3455 ERR_CONFIG_RESTORE_UNSIGNED_HASH_INVALID.get(backupID); 3456 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3457 message); 3458 } 3459 } 3460 3461 if (mac != null) 3462 { 3463 byte[] calculatedSignature = mac.doFinal(); 3464 if (Arrays.equals(calculatedSignature, signedHash)) 3465 { 3466 Message message = NOTE_CONFIG_RESTORE_SIGNED_HASH_VALID.get(); 3467 logError(message); 3468 } 3469 else 3470 { 3471 // Tell the user where the previous config was archived. 3472 if (configBackupDir != null) 3473 { 3474 Message message = ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED.get( 3475 configBackupDir.getPath()); 3476 logError(message); 3477 } 3478 3479 Message message = ERR_CONFIG_RESTORE_SIGNED_HASH_INVALID.get( 3480 configBackupDir.getPath()); 3481 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 3482 message); 3483 } 3484 } 3485 3486 3487 // If we are just verifying the archive, then we're done. 3488 if (verifyOnly) 3489 { 3490 Message message = 3491 NOTE_CONFIG_RESTORE_VERIFY_SUCCESSFUL.get(backupID, backupPath); 3492 logError(message); 3493 return; 3494 } 3495 3496 3497 // If we've gotten here, then the archive was restored successfully. Get 3498 // rid of the temporary copy we made of the previous config directory and 3499 // exit. 3500 if (configBackupDir != null) 3501 { 3502 recursiveDelete(configBackupDir); 3503 } 3504 3505 Message message = NOTE_CONFIG_RESTORE_SUCCESSFUL.get(backupID, backupPath); 3506 logError(message); 3507 } 3508 3509 3510 3511 /** 3512 * {@inheritDoc} 3513 */ 3514 public DN getComponentEntryDN() 3515 { 3516 return configRootEntry.getDN(); 3517 } 3518 3519 3520 3521 /** 3522 * {@inheritDoc} 3523 */ 3524 public String getClassName() 3525 { 3526 return CLASS_NAME; 3527 } 3528 3529 3530 3531 /** 3532 * {@inheritDoc} 3533 */ 3534 public LinkedHashMap<String,String> getAlerts() 3535 { 3536 LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>(); 3537 3538 alerts.put(ALERT_TYPE_CANNOT_WRITE_CONFIGURATION, 3539 ALERT_DESCRIPTION_CANNOT_WRITE_CONFIGURATION); 3540 alerts.put(ALERT_TYPE_MANUAL_CONFIG_EDIT_HANDLED, 3541 ALERT_DESCRIPTION_MANUAL_CONFIG_EDIT_HANDLED); 3542 alerts.put(ALERT_TYPE_MANUAL_CONFIG_EDIT_LOST, 3543 ALERT_DESCRIPTION_MANUAL_CONFIG_EDIT_LOST); 3544 3545 return alerts; 3546 } 3547 3548 3549 3550 /** 3551 * Examines the provided result and logs a message if appropriate. If the 3552 * result code is anything other than {@code SUCCESS}, then it will log an 3553 * error message. If the operation was successful but admin action is 3554 * required, then it will log a warning message. If no action is required but 3555 * messages were generated, then it will log an informational message. 3556 * 3557 * @param result The config change result object that 3558 * @param entryDN The DN of the entry that was added, deleted, or 3559 * modified. 3560 * @param className The name of the class for the object that generated the 3561 * provided result. 3562 * @param methodName The name of the method that generated the provided 3563 * result. 3564 */ 3565 public void handleConfigChangeResult(ConfigChangeResult result, DN entryDN, 3566 String className, String methodName) 3567 { 3568 if (result == null) 3569 { 3570 Message message = ERR_CONFIG_CHANGE_NO_RESULT. 3571 get(String.valueOf(className), String.valueOf(methodName), 3572 String.valueOf(entryDN)); 3573 logError(message); 3574 return; 3575 } 3576 3577 ResultCode resultCode = result.getResultCode(); 3578 boolean adminActionRequired = result.adminActionRequired(); 3579 List<Message> messages = result.getMessages(); 3580 3581 MessageBuilder messageBuffer = new MessageBuilder(); 3582 if (messages != null) 3583 { 3584 for (Message s : messages) 3585 { 3586 if (messageBuffer.length() > 0) 3587 { 3588 messageBuffer.append(" "); 3589 } 3590 messageBuffer.append(s); 3591 } 3592 } 3593 3594 3595 if (resultCode != ResultCode.SUCCESS) 3596 { 3597 Message message = ERR_CONFIG_CHANGE_RESULT_ERROR. 3598 get(String.valueOf(className), String.valueOf(methodName), 3599 String.valueOf(entryDN), String.valueOf(resultCode), 3600 adminActionRequired, messageBuffer.toString()); 3601 logError(message); 3602 } 3603 else if (adminActionRequired) 3604 { 3605 Message message = WARN_CONFIG_CHANGE_RESULT_ACTION_REQUIRED. 3606 get(String.valueOf(className), String.valueOf(methodName), 3607 String.valueOf(entryDN), messageBuffer.toString()); 3608 logError(message); 3609 } 3610 else if (messageBuffer.length() > 0) 3611 { 3612 Message message = INFO_CONFIG_CHANGE_RESULT_MESSAGES. 3613 get(String.valueOf(className), String.valueOf(methodName), 3614 String.valueOf(entryDN), messageBuffer.toString()); 3615 logError(message); 3616 } 3617 } 3618 3619 3620 3621 /** 3622 * {@inheritDoc} 3623 */ 3624 public void preloadEntryCache() throws UnsupportedOperationException { 3625 throw new UnsupportedOperationException("Operation not supported."); 3626 } 3627 }