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 }