source: branches/eraser6/pluginsRewrite/Eraser.DefaultPlugins/ErasureTargets/FileSystemObjectErasureTarget.cs @ 2360

Revision 2360, 13.4 KB checked in by lowjoel, 2 years ago (diff)

Move the CopyPlausibleDeniabilityFile? function from the FileSystem? class to the FileSystemObjectErasureTarget? base class, since that's the only place this setting should be needed.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26
27using System.Runtime.Serialization;
28using System.Runtime.InteropServices;
29using System.Security.Permissions;
30using System.IO;
31
32using Eraser.Manager;
33using Eraser.Util;
34using Eraser.Util.ExtensionMethods;
35
36namespace Eraser.DefaultPlugins
37{
38    /// <summary>
39    /// Class representing a tangible object (file/folder) to be erased.
40    /// </summary>
41    [Serializable]
42    public abstract class FileSystemObjectErasureTarget : ErasureTarget
43    {
44        #region Serialization code
45        protected FileSystemObjectErasureTarget(SerializationInfo info, StreamingContext context)
46            : base(info, context)
47        {
48            Path = (string)info.GetValue("Path", typeof(string));
49        }
50
51        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
52        public override void GetObjectData(SerializationInfo info, StreamingContext context)
53        {
54            base.GetObjectData(info, context);
55            info.AddValue("Path", Path);
56        }
57        #endregion
58
59        /// <summary>
60        /// Constructor.
61        /// </summary>
62        protected FileSystemObjectErasureTarget()
63            : base()
64        {
65        }
66
67        /// <summary>
68        /// The Progress of this target's erasure. This property must be set
69        /// before <see cref="FileSystemObjectErasureTarget.Execute"/> is called.
70        /// </summary>
71        protected new SteppedProgressManager Progress
72        {
73            get
74            {
75                return (SteppedProgressManager)base.Progress;
76            }
77            set
78            {
79                base.Progress = value;
80            }
81        }
82
83        /// <summary>
84        /// Retrieves the list of files/folders to erase as a list.
85        /// </summary>
86        /// <returns>A list containing the paths to all the files to be erased.</returns>
87        protected abstract List<StreamInfo> GetPaths();
88
89        /// <summary>
90        /// Gets all files in the provided directory.
91        /// </summary>
92        /// <param name="info">The directory to look files in.</param>
93        /// <returns>A list of files found in the directory.</returns>
94        /// <remarks>This function does not recurse into directories which are
95        /// reparse points.</remarks>
96        protected static FileInfo[] GetFiles(DirectoryInfo info)
97        {
98            List<FileInfo> result = new List<FileInfo>();
99            if (info.Exists && (info.Attributes & FileAttributes.ReparsePoint) == 0)
100            {
101                try
102                {
103                    foreach (DirectoryInfo dir in info.GetDirectories())
104                        result.AddRange(GetFiles(dir));
105                    result.AddRange(info.GetFiles());
106                }
107                catch (UnauthorizedAccessException e)
108                {
109                    Logger.Log(S._("Could not erase files and subfolders in {0} because {1}",
110                        info.FullName, e.Message), LogLevel.Error);
111                }
112            }
113
114            return result.ToArray();
115        }
116
117        /// <summary>
118        /// Adds ADSes of the given file to the list, forcing the open handles to the
119        /// files closed if necessary.
120        /// </summary>
121        /// <param name="file">The file to look for ADSes</param>
122        protected static StreamInfo[] GetPathADSes(FileInfo file)
123        {
124            try
125            {
126                return file.GetADSes().ToArray();
127            }
128            catch (FileNotFoundException)
129            {
130            }
131            catch (SharingViolationException)
132            {
133                //The system cannot open the file, try to force the file handle to close.
134                if (!ManagerLibrary.Settings.ForceUnlockLockedFiles)
135                    throw;
136
137                StringBuilder processStr = new StringBuilder();
138                foreach (OpenHandle handle in OpenHandle.Close(file.FullName))
139                {
140                    try
141                    {
142                        processStr.AppendFormat(
143                            System.Globalization.CultureInfo.InvariantCulture,
144                            "{0}, ", handle.Process.MainModule.FileName);
145                    }
146                    catch (System.ComponentModel.Win32Exception)
147                    {
148                        processStr.AppendFormat(
149                            System.Globalization.CultureInfo.InvariantCulture,
150                            "Process ID {0}, ", handle.Process.Id);
151                    }
152                }
153
154                if (processStr.Length == 0)
155                    return GetPathADSes(file);
156                else
157                    throw;
158            }
159            catch (UnauthorizedAccessException e)
160            {
161                //The system cannot read the file, assume no ADSes for lack of
162                //more information.
163                Logger.Log(e.Message, LogLevel.Error);
164            }
165
166            return new StreamInfo[0];
167        }
168
169        /// <summary>
170        /// The path to the file or folder referred to by this object.
171        /// </summary>
172        public string Path { get; set; }
173
174        public sealed override ErasureMethod EffectiveMethod
175        {
176            get
177            {
178                if (Method != ErasureMethodRegistrar.Default)
179                    return base.EffectiveMethod;
180
181                return ManagerLibrary.Instance.ErasureMethodRegistrar[
182                    ManagerLibrary.Settings.DefaultFileErasureMethod];
183            }
184        }
185
186        public override string UIText
187        {
188            get
189            {
190                return Path;
191            }
192        }
193
194        /// <summary>
195        /// Erases the streams returned by the <see cref="GetPaths"/> function.
196        /// </summary>
197        /// <remarks>The <see cref="Progress"/> property must be defined prior
198        /// to the execution of this function.</remarks>
199        public override void Execute()
200        {
201            //Retrieve the list of files to erase.
202            List<StreamInfo> paths = GetPaths();
203            long dataTotal = paths.Sum(x => x.Length);
204
205            //Set the event's current target status.
206            if (Progress == null)
207                throw new InvalidOperationException("The Progress property must not be null.");
208            Task.Progress.Steps.Add(new SteppedProgressManagerStep(Progress,
209                1.0f / Task.Targets.Count));
210
211            //Iterate over every path, and erase the path.
212            for (int i = 0; i < paths.Count; ++i)
213            {
214                ProgressManager step = new ProgressManager();
215                Progress.Steps.Add(new SteppedProgressManagerStep(step,
216                    dataTotal == 0 ? 0.0f : paths[i].Length / (float)dataTotal,
217                    S._("Erasing files...")));
218                EraseStream(paths[i], step);
219                step.MarkComplete();
220            }
221        }
222
223        /// <summary>
224        /// Erases the provided stream, and updates progress using the provided
225        /// progress manager.
226        /// </summary>
227        /// <param name="info">The information regarding the stream that needs erasure.</param>
228        /// <param name="progress">The progress manager for the erasure of the current
229        /// stream.</param>
230        protected void EraseStream(StreamInfo info, ProgressManager progress)
231        {
232            //Check that the file exists - we do not want to bother erasing nonexistant files
233            if (!info.Exists)
234            {
235                Logger.Log(S._("The file {0} was not erased as the file does not exist.",
236                    info.FileName), LogLevel.Notice);
237                return;
238            }
239
240            //Get the filesystem provider to handle the secure file erasures
241            FileSystem fsManager = ManagerLibrary.Instance.FileSystemRegistrar[
242                VolumeInfo.FromMountPoint(info.DirectoryName)];
243
244            bool isReadOnly = false;
245
246            try
247            {
248                //Update the task progress
249                ErasureMethod method = EffectiveMethod;
250                OnProgressChanged(this, new ProgressChangedEventArgs(progress,
251                    new TaskProgressChangedEventArgs(info.FullName, 0, method.Passes)));
252
253                //Remove the read-only flag, if it is set.
254                if (isReadOnly = info.IsReadOnly)
255                    info.IsReadOnly = false;
256
257                //Define the callback function for progress reporting.
258                ErasureMethodProgressFunction callback =
259                    delegate(long lastWritten, long totalData, int currentPass)
260                    {
261                        if (Task.Canceled)
262                            throw new OperationCanceledException(S._("The task was cancelled."));
263
264                        progress.Total = totalData;
265                        progress.Completed += lastWritten;
266                        OnProgressChanged(this, new ProgressChangedEventArgs(progress,
267                            new TaskProgressChangedEventArgs(info.FullName, currentPass, method.Passes)));
268                    };
269
270                TryEraseStream(fsManager, method, info, callback);
271
272                //Remove the file.
273                FileInfo fileInfo = info.File;
274                if (fileInfo != null)
275                    fsManager.DeleteFile(fileInfo);
276                progress.MarkComplete();
277            }
278            catch (UnauthorizedAccessException)
279            {
280                Logger.Log(S._("The file {0} could not be erased because the file's " +
281                    "permissions prevent access to the file.", info.FullName), LogLevel.Error);
282            }
283            catch (SharingViolationException e)
284            {
285                Logger.Log(e.Message, LogLevel.Error);
286            }
287            finally
288            {
289                //Re-set the read-only flag if the file exists (i.e. there was an error)
290                if (isReadOnly && info.Exists && !info.IsReadOnly)
291                    info.IsReadOnly = isReadOnly;
292            }
293        }
294
295        /// <summary>
296        /// Attempts to erase a stream, trying to close all open handles if a process has
297        /// a lock on the file.
298        /// </summary>
299        /// <param name="fsManager">The file system provider used to erase the stream.</param>
300        /// <param name="method">The erasure method to use to erase the stream.</param>
301        /// <param name="info">The stream to erase.</param>
302        /// <param name="callback">The erasure progress callback.</param>
303        private void TryEraseStream(FileSystem fsManager, ErasureMethod method, StreamInfo info,
304            ErasureMethodProgressFunction callback)
305        {
306            for (int i = 0; ; ++i)
307            {
308                try
309                {
310                    //Make sure the file does not have any attributes which may affect
311                    //the erasure process
312                    if ((info.Attributes & FileAttributes.Compressed) != 0 ||
313                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
314                        (info.Attributes & FileAttributes.SparseFile) != 0)
315                    {
316                        //Log the error
317                        Logger.Log(S._("The file {0} could not be erased because the file was " +
318                            "either compressed, encrypted or a sparse file.", info.FullName),
319                            LogLevel.Error);
320                        return;
321                    }
322
323                    //Do not erase reparse points, as they will cause other references to the file
324                    //to be to garbage.
325                    if ((info.Attributes & FileAttributes.ReparsePoint) == 0)
326                        fsManager.EraseFileSystemObject(info, method, callback);
327                    else
328                        Logger.Log(S._("The file {0} is a hard link or a symbolic link thus the " +
329                            "contents of the file was not erased.", LogLevel.Notice));
330                    return;
331                }
332                catch (SharingViolationException)
333                {
334                    if (!ManagerLibrary.Settings.ForceUnlockLockedFiles)
335                        throw;
336
337                    //Try closing all open handles. If it succeeds, we can run the erase again.
338                    //To prevent Eraser from deadlocking, we will only attempt this once. Some
339                    //programs may be aggressive and keep a handle open in a tight loop.
340                    List<OpenHandle> remainingHandles = OpenHandle.Close(info.FullName);
341                    if (i == 0 && remainingHandles.Count == 0)
342                        continue;
343
344                    //Either we could not close all instances, or we already tried twice. Report
345                    //the error.
346                    StringBuilder processStr = new StringBuilder();
347                    foreach (OpenHandle handle in remainingHandles)
348                    {
349                        try
350                        {
351                            processStr.AppendFormat(
352                                System.Globalization.CultureInfo.InvariantCulture,
353                                "{0}, ", handle.Process.MainModule.FileName);
354                        }
355                        catch (System.ComponentModel.Win32Exception)
356                        {
357                            processStr.AppendFormat(
358                                System.Globalization.CultureInfo.InvariantCulture,
359                                "Process ID {0}, ", handle.Process.Id);
360                        }
361                    }
362
363                    throw new SharingViolationException(S._(
364                        "Could not force closure of file \"{0}\" {1}", info.FileName,
365                        S._("(locked by {0})",
366                            processStr.ToString().Remove(processStr.Length - 2)).Trim()),
367                        info.FileName);
368                }
369            }
370        }
371
372        /// <summary>
373        /// Writes a file for plausible deniability over the current stream.
374        /// </summary>
375        /// <param name="stream">The stream to write the data to.</param>
376        protected static void CopyPlausibleDeniabilityFile(Stream stream)
377        {
378            //Get the template file to copy
379            FileInfo shadowFileInfo;
380            {
381                string shadowFile = null;
382                List<string> entries = new List<string>(
383                    ManagerLibrary.Settings.PlausibleDeniabilityFiles);
384                Prng prng = Host.Instance.Prngs.ActivePrng;
385                do
386                {
387                    if (entries.Count == 0)
388                        throw new FatalException(S._("Plausible deniability was selected, " +
389                            "but no decoy files were found. The current file has been only " +
390                            "replaced with random data."));
391
392                    //Get an item from the list of files, and then check that the item exists.
393                    int index = prng.Next(entries.Count - 1);
394                    shadowFile = entries[index];
395                    if (File.Exists(shadowFile) || Directory.Exists(shadowFile))
396                    {
397                        if ((File.GetAttributes(shadowFile) & FileAttributes.Directory) != 0)
398                        {
399                            DirectoryInfo dir = new DirectoryInfo(shadowFile);
400                            FileInfo[] files = dir.GetFiles("*", SearchOption.AllDirectories);
401                            entries.Capacity += files.Length;
402                            foreach (FileInfo f in files)
403                                entries.Add(f.FullName);
404                        }
405                        else
406                            shadowFile = entries[index];
407                    }
408                    else
409                        shadowFile = null;
410
411                    entries.RemoveAt(index);
412                }
413                while (string.IsNullOrEmpty(shadowFile));
414                shadowFileInfo = new FileInfo(shadowFile);
415            }
416
417            //Dump the copy (the first 4MB, or less, depending on the file size and size of
418            //the original file)
419            long amountToCopy = Math.Min(stream.Length,
420                Math.Min(4 * 1024 * 1024, shadowFileInfo.Length));
421            using (FileStream shadowFileStream = shadowFileInfo.OpenRead())
422            {
423                while (stream.Position < amountToCopy)
424                {
425                    byte[] buf = new byte[524288];
426                    int bytesRead = shadowFileStream.Read(buf, 0, buf.Length);
427
428                    //Stop bothering if the input stream is at the end
429                    if (bytesRead == 0)
430                        break;
431
432                    //Dump the read contents onto the file to be deleted
433                    stream.Write(buf, 0,
434                        (int)Math.Min(bytesRead, amountToCopy - stream.Position));
435                }
436            }
437        }
438    }
439}
Note: See TracBrowser for help on using the repository browser.