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    }