source: branches/eraser6/Manager/DirectExecutor.cs @ 208

Revision 208, 10.9 KB checked in by lowjoel, 6 years ago (diff)

Implemented secure folder removal.

Line 
1using System;
2using System.Collections.Generic;
3using System.Collections.Specialized;
4using System.Text;
5using System.Threading;
6using System.IO;
7
8using Eraser.Util;
9
10namespace Eraser.Manager
11{
12    /// <summary>
13    /// The DirectExecutor class is used by the Eraser GUI directly when the program
14    /// is run without the help of a Service.
15    /// </summary>
16    public class DirectExecutor : Executor, IDisposable
17    {
18        public DirectExecutor()
19        {
20            thread = new Thread(delegate()
21            {
22                this.Main();
23            });
24
25            thread.Start();
26            Thread.Sleep(0);
27        }
28
29        void IDisposable.Dispose()
30        {
31            thread.Abort();
32            schedulerInterrupt.Set();
33        }
34
35        public override void AddTask(ref Task task)
36        {
37            lock (unusedIdsLock)
38            {
39                if (unusedIds.Count != 0)
40                {
41                    task.ID = unusedIds[0];
42                    unusedIds.RemoveAt(0);
43                }
44                else
45                    task.ID = ++nextId;
46            }
47
48            //Add the task to the set of tasks
49            lock (tasksLock)
50            {
51                tasks.Add(task.ID, task);
52
53                //If the task is scheduled to run now, break the waiting thread and
54                //run it immediately
55                if (task.Schedule == Schedule.RunNow)
56                {
57                    scheduledTasks.Add(DateTime.Now, task);
58                    schedulerInterrupt.Set();
59                }
60                //If the task is scheduled, add the next execution time to the list
61                //of schduled tasks.
62                else if (task.Schedule != Schedule.RunOnRestart)
63                {
64                    scheduledTasks.Add((task.Schedule as RecurringSchedule).NextRun, task);
65                }
66            }
67        }
68
69        public override bool DeleteTask(uint taskId)
70        {
71            lock (tasksLock)
72            {
73                if (!tasks.ContainsKey(taskId))
74                    return false;
75
76                lock (unusedIdsLock)
77                    unusedIds.Add(taskId);
78                tasks.Remove(taskId);
79            }
80            return true;
81        }
82
83        public override Task GetTask(uint taskId)
84        {
85            lock (tasksLock)
86            {
87                if (!tasks.ContainsKey(taskId))
88                    return null;
89                return tasks[taskId];
90            }
91        }
92
93        /// <summary>
94        /// The thread entry point for this object. This object operates on a queue
95        /// and hence the thread will sequentially execute tasks.
96        /// </summary>
97        private void Main()
98        {
99            //The waiting thread will utilize a polling loop to check for new
100            //scheduled tasks. This will be checked every 30 seconds. However,
101            //when the thread is waiting for a new task, it can be interrupted.
102            while (thread.ThreadState != ThreadState.AbortRequested)
103            {
104                //Check for a new task
105                Task task = null;
106                lock (tasksLock)
107                {
108                    if (scheduledTasks.Count != 0 &&
109                        (scheduledTasks.Values[0].Schedule == Schedule.RunNow ||
110                         scheduledTasks.Keys[0] <= DateTime.Now))
111                    {
112                        task = scheduledTasks.Values[0];
113                        scheduledTasks.RemoveAt(0);
114                    }
115                }
116
117                if (task != null)
118                {
119                    try
120                    {
121                        //Run the task
122                        foreach (Task.ErasureTarget target in task.Entries)
123                            try
124                            {
125                                if (target is Task.UnusedSpace)
126                                    EraseUnusedSpace(task, (Task.UnusedSpace)target);
127                                else if (target is Task.FilesystemObject)
128                                    EraseFilesystemObject(task, (Task.FilesystemObject)target);
129                                else
130                                    throw new ArgumentException("Unknown erasure target.");
131                            }
132                            catch (FatalException)
133                            {
134                                throw;
135                            }
136                            catch (Exception e)
137                            {
138                                task.LogEntry(new LogEntry(e.Message, LogLevel.ERROR));
139                            }
140                    }
141                    catch (FatalException e)
142                    {
143                        task.LogEntry(new LogEntry(e.Message, LogLevel.FATAL));
144                    }
145
146                    //If the task is a recurring task, reschedule it since we are done.
147                    if (task.Schedule is RecurringSchedule)
148                        ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
149                }
150
151                //Wait for half a minute to check for the next scheduled task.
152                schedulerInterrupt.WaitOne(30000, false);
153            }
154        }
155
156        /// <summary>
157        /// Executes a unused space erase.
158        /// </summary>
159        /// <param name="target">The target of the unused space erase.</param>
160        private void EraseUnusedSpace(Task task, Task.UnusedSpace target)
161        {
162            throw new NotImplementedException("Unused space erasures are not "+
163                "currently implemented");
164        }
165
166        /// <summary>
167        /// Erases a file or folder on the volume.
168        /// </summary>
169        /// <param name="target">The target of the erasure.</param>
170        private void EraseFilesystemObject(Task task, Task.FilesystemObject target)
171        {
172            List<string> paths = target.GetPaths();
173            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
174
175            //Get the erasure method if the user specified he wants the default.
176            ErasureMethod method = target.Method;
177            if (method == ErasureMethodManager.Default)
178                method = ErasureMethodManager.GetInstance(Globals.Settings.DefaultFileErasureMethod);
179
180            //Iterate over every path, and erase the path.
181            for (int i = 0; i < paths.Count; ++i)
182            {
183                //Update the task progress
184                eventArgs.overallProgress = (i * 100) / paths.Count;
185                eventArgs.currentItemName = paths[i];
186                eventArgs.currentItemProgress = 0;
187                eventArgs.totalPasses = method.Passes;
188                task.OnProgressChanged(eventArgs);
189
190                //Make sure the file does not have any attributes which may
191                //affect the erasure process
192                FileInfo info = new FileInfo(paths[i]);
193                if ((info.Attributes & FileAttributes.Compressed) != 0 ||
194                    (info.Attributes & FileAttributes.Encrypted) != 0 ||
195                    (info.Attributes & FileAttributes.SparseFile) != 0 ||
196                    (info.Attributes & FileAttributes.ReparsePoint) != 0)
197                {
198                    //Log the error
199                    throw new ArgumentException("Compressed, encrypted, or sparse" +
200                        "files cannot be erased with Eraser.");
201                }
202
203                //Remove the read-only flag, if it is set.
204                if ((info.Attributes & FileAttributes.ReadOnly) != 0)
205                    info.Attributes &= ~FileAttributes.ReadOnly;
206
207                //Create the file stream, and call the erasure method
208                //to write to the stream.
209                using (FileStream strm = new FileStream(info.FullName,
210                    FileMode.Open, FileAccess.Write, FileShare.None,
211                    8, FileOptions.WriteThrough))
212                {
213                    //Set the end of the stream after the wrap-round the cluster size
214                    uint clusterSize = Drives.GetDriveClusterSize(info.Directory.Root.FullName);
215                    long roundUpFileLength = strm.Length % clusterSize;
216                    if (roundUpFileLength != 0)
217                        strm.SetLength(strm.Length + (clusterSize - roundUpFileLength));
218
219                    //Then erase the file.
220                    method.Erase(strm, PRNGManager.GetInstance(Globals.Settings.ActivePRNG),
221                        delegate(float currentProgress, int currentPass)
222                        {
223                            eventArgs.currentPass = currentPass;
224                            eventArgs.currentItemProgress = (int)
225                                ((float)currentProgress * 100.0);
226                            eventArgs.overallProgress = (int)
227                                (((i + currentProgress) / (float)paths.Count) * 100);
228                            task.OnProgressChanged(eventArgs);
229                        }
230                    );
231
232                    //Set the length of the file to 0.
233                    strm.Seek(0, SeekOrigin.Begin);
234                    strm.SetLength(0);
235                }
236
237                //Remove the file.
238                RemoveFile(info);
239            }
240
241            //If the user requested a folder removal, do it.
242            if (target is Task.Folder)
243            {
244                Task.Folder fldr = (Task.Folder)target;
245                if (fldr.DeleteIfEmpty)
246                    RemoveFolder(new DirectoryInfo(fldr.Path));
247            }
248        }
249
250        /// <summary>
251        /// Securely removes files.
252        /// </summary>
253        /// <param name="info">The FileInfo object representing the file.</param>
254        private void RemoveFile(FileInfo info)
255        {
256            //Set the date of the file to be invalid to prevent forensic
257            //detection
258            info.CreationTime = info.LastWriteTime = info.LastAccessTime =
259                new DateTime(1800, 1, 1, 0, 0, 0);
260            info.Attributes = FileAttributes.Normal;
261            info.Attributes = FileAttributes.NotContentIndexed;
262
263            //Rename the file a few times to erase the record from the MFT.
264            for (int i = 0; i < FilenameErasePasses; ++i)
265            {
266                //Get a random file name
267                PRNG prng = PRNGManager.GetInstance(Globals.Settings.ActivePRNG);
268                byte[] newFileNameAry = new byte[info.Name.Length];
269                prng.NextBytes(newFileNameAry);
270
271                //Validate the name
272                const string validFileNameChars = "0123456789abcdefghijklmnopqrs" +
273                    "tuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
274                for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
275                    newFileNameAry[j] = (byte)validFileNameChars[
276                        (int)newFileNameAry[j] % validFileNameChars.Length];
277
278                //Rename the file.
279                string newPath = info.DirectoryName + Path.DirectorySeparatorChar +
280                    (new System.Text.UTF8Encoding()).GetString(newFileNameAry);
281                info.MoveTo(newPath);
282            }
283
284            //Then delete the file.
285            info.Delete();
286        }
287
288        private void RemoveFolder(DirectoryInfo info)
289        {
290            foreach (FileInfo file in info.GetFiles())
291                RemoveFile(file);
292            foreach (DirectoryInfo dir in info.GetDirectories())
293                RemoveFolder(dir);
294
295            //Then clean up this folder.
296            for (int i = 0; i < FilenameErasePasses; ++i)
297            {
298                //Get a random file name
299                PRNG prng = PRNGManager.GetInstance(Globals.Settings.ActivePRNG);
300                byte[] newFileNameAry = new byte[info.Name.Length];
301                prng.NextBytes(newFileNameAry);
302
303                //Validate the name
304                const string validFileNameChars = "0123456789abcdefghijklmnopqrs" +
305                    "tuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
306                for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
307                    newFileNameAry[j] = (byte)validFileNameChars[
308                        (int)newFileNameAry[j] % validFileNameChars.Length];
309
310                //Rename the folder.
311                string newPath = info.Parent.FullName + Path.DirectorySeparatorChar +
312                    (new System.Text.UTF8Encoding()).GetString(newFileNameAry);
313                info.MoveTo(newPath);
314            }
315
316            //Remove the folder
317            info.Delete();
318        }
319
320        /// <summary>
321        /// The thread object.
322        /// </summary>
323        private Thread thread;
324
325        /// <summary>
326        /// The lock preventing concurrent access for the tasks list and the
327        /// tasks queue.
328        /// </summary>
329        private object tasksLock = new object();
330
331        /// <summary>
332        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
333        /// </summary>
334        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
335
336        /// <summary>
337        /// The queue of tasks. This queue is executed when the first element's
338        /// timestamp (the key) has been past. This list assumes that all tasks
339        /// are sorted by timestamp, smallest one first.
340        /// </summary>
341        private SortedList<DateTime, Task> scheduledTasks =
342            new SortedList<DateTime, Task>();
343
344        /// <summary>
345        /// The list of task IDs for recycling.
346        /// </summary>
347        private List<uint> unusedIds = new List<uint>();
348
349        /// <summary>
350        /// Lock preventing concurrent access for the IDs.
351        /// </summary>
352        private object unusedIdsLock = new object();
353
354        /// <summary>
355        /// Incrementing ID. This value is incremented by one every time an ID
356        /// is required by no unused IDs remain.
357        /// </summary>
358        private uint nextId = 0;
359
360        /// <summary>
361        /// An automatically reset event allowing the addition of new tasks to
362        /// interrupt the thread's sleeping state waiting for the next recurring
363        /// task to be due.
364        /// </summary>
365        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
366    }
367}
Note: See TracBrowser for help on using the repository browser.