source: trunk/eraser6/Eraser.Manager/Plugins.cs @ 1446

Revision 1446, 12.6 KB checked in by lowjoel, 5 years ago (diff)

Do not explicitly allow or disallow a plugin based on whether the plugin is checked in the plugin settings list view. This is because signed plugins load automatically; if the signature changes or if root certificates change the plugin may then be a valid signed assembly. This also fixes the "double error message on startup" where users are greeted with the "invalid unused space and file erasure methods as well as PRNG" will be given the "plugins will only be loaded on restart" message (which makes no sense to the user)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2009 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.Text;
25using System.IO;
26using System.Reflection;
27using System.Windows.Forms;
28using System.Runtime.InteropServices;
29using System.Security.Cryptography;
30using System.Security.Cryptography.X509Certificates;
31using Eraser.Util;
32
33namespace Eraser.Manager.Plugin
34{
35    /// <summary>
36    /// The plugins host interface which is used for communicating with the host
37    /// program.
38    /// </summary>
39    /// <remarks>Remember to call Load to load the plugins into memory, otherwise
40    /// they will never be loaded.</remarks>
41    public abstract class Host : IDisposable
42    {
43        #region IDisposable members
44        protected virtual void Dispose(bool disposing)
45        {
46        }
47
48        /// <summary>
49        /// Cleans up resources used by the host. Also unloads all loaded plugins.
50        /// </summary>
51        public void Dispose()
52        {
53            Dispose(true);
54            GC.SuppressFinalize(this);
55        }
56        #endregion
57
58        /// <summary>
59        /// Getter that retrieves the global plugin host instance.
60        /// </summary>
61        public static Host Instance
62        {
63            get { return ManagerLibrary.Instance.Host; }
64        }
65
66        /// <summary>
67        /// Retrieves the list of currently loaded plugins.
68        /// </summary>
69        /// <remarks>The returned list is read-only</remarks>
70        public abstract ICollection<PluginInstance> Plugins
71        {
72            get;
73        }
74
75        /// <summary>
76        /// Loads all plugins into memory.
77        /// </summary>
78        public abstract void Load();
79
80        /// <summary>
81        /// The plugin loaded event.
82        /// </summary>
83        public EventHandler<PluginLoadedEventArgs> PluginLoaded { get; set; }
84
85        /// <summary>
86        /// Event callback executor for the OnPluginLoad Event
87        /// </summary>
88        internal void OnPluginLoaded(object sender, PluginLoadedEventArgs e)
89        {
90            if (PluginLoaded != null)
91                PluginLoaded(sender, e);
92        }
93
94        /// <summary>
95        /// Loads a plugin.
96        /// </summary>
97        /// <param name="filePath">The absolute or relative file path to the
98        /// DLL.</param>
99        public abstract void LoadPlugin(string filePath);
100    }
101
102    /// <summary>
103    /// Event argument for the plugin loaded event.
104    /// </summary>
105    public class PluginLoadedEventArgs : EventArgs
106    {
107        /// <summary>
108        /// Constructor.
109        /// </summary>
110        /// <param name="instance">The plugin instance of the recently loaded plugin.</param>
111        public PluginLoadedEventArgs(PluginInstance instance)
112        {
113            Instance = instance;
114        }
115
116        /// <summary>
117        /// The <see cref="PluginInstance"/> object representing the newly loaded plugin.
118        /// </summary>
119        public PluginInstance Instance { get; private set; }
120    }
121
122    /// <summary>
123    /// The default plugins host implementation.
124    /// </summary>
125    internal class DefaultHost : Host
126    {
127        /// <summary>
128        /// Constructor. Loads all plugins in the Plugins folder.
129        /// </summary>
130        public DefaultHost()
131        {
132        }
133
134        public override void Load()
135        {
136            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
137            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency;
138            string pluginsFolder = Path.Combine(
139                Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location
140                PLUGINSFOLDER //Plugins folder
141            );
142
143            foreach (string fileName in Directory.GetFiles(pluginsFolder))
144            {
145                FileInfo file = new FileInfo(fileName);
146                if (file.Extension.Equals(".dll"))
147                    try
148                    {
149                        LoadPlugin(file.FullName);
150                    }
151                    catch (BadImageFormatException)
152                    {
153                    }
154                    catch (FileLoadException)
155                    {
156                    }
157            }
158        }
159
160        protected override void Dispose(bool disposing)
161        {
162            if (disposing)
163            {
164                //Unload all the plugins. This will cause all the plugins to execute
165                //the cleanup code.
166                foreach (PluginInstance plugin in plugins)
167                    if (plugin.Plugin != null)
168                        plugin.Plugin.Dispose();
169            }
170        }
171
172        /// <summary>
173        /// The path to the folder containing the plugins.
174        /// </summary>
175        public const string PLUGINSFOLDER = "Plugins";
176
177        public override ICollection<PluginInstance> Plugins
178        {
179            get { return plugins.AsReadOnly(); }
180        }
181
182        public override void LoadPlugin(string filePath)
183        {
184            //Create the PluginInstance structure
185            Assembly reflectAssembly = Assembly.ReflectionOnlyLoadFrom(filePath);
186            PluginInstance instance = new PluginInstance(reflectAssembly, null);
187            Type typePlugin = null;
188
189            //Iterate over every exported type, checking if it implements IPlugin
190            foreach (Type type in instance.Assembly.GetExportedTypes())
191            {
192                //Check for an implementation of IPlugin
193                Type typeInterface = type.GetInterface("Eraser.Manager.Plugin.IPlugin", true);
194                if (typeInterface != null)
195                {
196                    typePlugin = type;
197                    break;
198                }
199            }
200
201            //If the typePlugin type is empty the assembly doesn't implement IPlugin; we
202            //aren't interested.
203            if (typePlugin == null)
204                return;
205
206            //OK this assembly is a plugin
207            lock (plugins)
208                plugins.Add(instance);
209
210            //The plugin was not disabled or it was loaded for the first time. Check
211            //the plugin for the presence of a valid signature.
212            IDictionary<Guid, bool> approvals = ManagerLibrary.Settings.PluginApprovals;
213            if ((reflectAssembly.GetName().GetPublicKey().Length == 0 ||
214                !MsCorEEApi.VerifyStrongName(filePath) ||
215                instance.AssemblyAuthenticode == null) &&
216                !approvals.ContainsKey(instance.AssemblyInfo.Guid))
217            {
218                return;
219            }
220
221            //Load the plugin
222            instance.Assembly = Assembly.LoadFrom(filePath);
223
224            //See if the plugin belongs to us (same signature) and if the assembly
225            //declares an IsCore attribute.
226            if (reflectAssembly.GetName().GetPublicKey().Length ==
227                Assembly.GetExecutingAssembly().GetName().GetPublicKey().Length)
228            {
229                bool sameKey = true;
230                byte[] reflectAssemblyKey = reflectAssembly.GetName().GetPublicKey();
231                byte[] thisAssemblyKey = Assembly.GetExecutingAssembly().GetName().GetPublicKey();
232                for (int i = 0, j = reflectAssemblyKey.Length; i != j; ++i)
233                    if (reflectAssemblyKey[i] != thisAssemblyKey[i])
234                    {
235                        sameKey = false;
236                        break;
237                    }
238
239                //Check for the IsCore attribute.
240                if (sameKey)
241                {
242                    object[] attr = instance.Assembly.GetCustomAttributes(typeof(CoreAttribute), true);
243                    if (attr.Length != 0)
244                        instance.IsCore = true;
245                }
246            }
247
248            //See if the user disabled this plugin (users cannot disable Core plugins)
249            if (approvals.ContainsKey(instance.AssemblyInfo.Guid) &&
250                !approvals[instance.AssemblyInfo.Guid] && !instance.IsCore)
251            {
252                return;
253            }
254
255            //Initialize the plugin
256            IPlugin pluginInterface = (IPlugin)Activator.CreateInstance(
257                instance.Assembly.GetType(typePlugin.ToString()));
258            pluginInterface.Initialize(this);
259            instance.Plugin = pluginInterface;
260
261            //And broadcast the plugin load event
262            OnPluginLoaded(this, new PluginLoadedEventArgs(instance));
263        }
264
265        private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
266        {
267            lock (plugins)
268                foreach (PluginInstance instance in plugins)
269                    if (instance.Assembly.FullName == args.Name)
270                        return instance.Assembly;
271            return null;
272        }
273
274        private Assembly ResolveReflectionDependency(object sender, ResolveEventArgs args)
275        {
276            return Assembly.ReflectionOnlyLoad(args.Name);
277        }
278
279        private List<PluginInstance> plugins = new List<PluginInstance>();
280    }
281
282    /// <summary>
283    /// Structure holding the instance values of the plugin like handle and path.
284    /// </summary>
285    public class PluginInstance
286    {
287        /// <summary>
288        /// Constructor
289        /// </summary>
290        /// <param name="assembly">The assembly representing this plugin.</param>
291        /// <param name="path">The path to the ass</param>
292        /// <param name="plugin"></param>
293        internal PluginInstance(Assembly assembly, IPlugin plugin)
294        {
295            Assembly = assembly;
296            Plugin = plugin;
297            IsCore = false;
298
299            //Verify the certificate in the assembly.
300            if (WintrustApi.VerifyAuthenticode(assembly.Location))
301            {
302                X509Certificate2 cert = new X509Certificate2(
303                    X509Certificate.CreateFromSignedFile(assembly.Location));
304                AssemblyAuthenticode = cert;
305            }
306        }
307
308        /// <summary>
309        /// Gets the Assembly this plugin instance came from.
310        /// </summary>
311        public Assembly Assembly
312        {
313            get
314            {
315                return assembly;
316            }
317            internal set
318            {
319                assembly = value;
320
321                AssemblyInfo info = new AssemblyInfo();
322                info.Version = assembly.GetName().Version;
323                IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(assembly);
324                foreach (CustomAttributeData attr in attributes)
325                    if (attr.Constructor.DeclaringType == typeof(GuidAttribute))
326                        info.Guid = new Guid((string)attr.ConstructorArguments[0].Value);
327                    else if (attr.Constructor.DeclaringType == typeof(AssemblyCompanyAttribute))
328                        info.Author = (string)attr.ConstructorArguments[0].Value;
329
330                this.AssemblyInfo = info;
331            }
332        }
333
334        /// <summary>
335        /// Gets the attributes of the assembly, loading from reflection-only sources.
336        /// </summary>
337        public AssemblyInfo AssemblyInfo { get; private set; }
338
339        /// <summary>
340        /// The Authenticode signature used for signing the assembly.
341        /// </summary>
342        public X509Certificate2 AssemblyAuthenticode { get; private set; }
343
344        /// <summary>
345        /// Gets whether the plugin is required for the functioning of Eraser (and
346        /// therefore cannot be disabled.)
347        /// </summary>
348        public bool IsCore { get; internal set; }
349
350        /// <summary>
351        /// Gets the IPlugin interface which the plugin exposed. This may be null
352        /// if the plugin was not loaded.
353        /// </summary>
354        public IPlugin Plugin { get; internal set; }
355
356        /// <summary>
357        /// Gets whether this particular plugin is currently loaded in memory.
358        /// </summary>
359        public bool Loaded
360        {
361            get { return Plugin != null; }
362        }
363
364        private Assembly assembly;
365    }
366
367    /// <summary>
368    /// Reflection-only information retrieved from the assembly.
369    /// </summary>
370    public struct AssemblyInfo
371    {
372        /// <summary>
373        /// The GUID of the assembly.
374        /// </summary>
375        public Guid Guid { get; set; }
376
377        /// <summary>
378        /// The publisher of the assembly.
379        /// </summary>
380        public string Author { get; set; }
381
382        /// <summary>
383        /// The version of the assembly.
384        /// </summary>
385        public Version Version { get; set; }
386
387        public override bool Equals(object obj)
388        {
389            if (!(obj is AssemblyInfo))
390                return false;
391            return Equals((AssemblyInfo)obj);
392        }
393
394        public bool Equals(AssemblyInfo other)
395        {
396            return Guid == other.Guid;
397        }
398
399        public static bool operator ==(AssemblyInfo assembly1, AssemblyInfo assembly2)
400        {
401            return assembly1.Equals(assembly2);
402        }
403
404        public static bool operator !=(AssemblyInfo assembly1, AssemblyInfo assembly2)
405        {
406            return !assembly1.Equals(assembly2);
407        }
408
409        public override int GetHashCode()
410        {
411            return Guid.GetHashCode();
412        }
413    }
414
415    /// <summary>
416    /// Basic plugin interface which allows for the main program to utilize the
417    /// functions in the DLL
418    /// </summary>
419    public interface IPlugin : IDisposable
420    {
421        /// <summary>
422        /// Initializer.
423        /// </summary>
424        /// <param name="host">The host object which can be used for two-way
425        /// communication with the program.</param>
426        void Initialize(Host host);
427
428        /// <summary>
429        /// The name of the plug-in, used for descriptive purposes in the UI
430        /// </summary>
431        string Name
432        {
433            get;
434        }
435
436        /// <summary>
437        /// The author of the plug-in, used for display in the UI and for users
438        /// to contact the author about bugs. Must be in the format:
439        ///     (.+) \<([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)\.([a-zA-Z0-9]+)\>
440        /// </summary>
441        /// <example>Joel Low <joel@joelsplace.sg></example>
442        string Author
443        {
444            get;
445        }
446
447        /// <summary>
448        /// Determines whether the plug-in is configurable.
449        /// </summary>
450        bool Configurable
451        {
452            get;
453        }
454
455        /// <summary>
456        /// Fulfil a request to display the settings for this plug-in.
457        /// </summary>
458        /// <param name="parent">The parent control which the settings dialog should
459        /// be parented with.</param>
460        void DisplaySettings(Control parent);
461    }
462
463    /// <summary>
464    /// Declares that the entity referenced is a core plugin and cannot be unloaded.
465    /// Only plugins signed with the same signature as the Manager library will be
466    /// considered to be safe and therefore checked for this attribute.
467    /// </summary>
468    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
469    public sealed class CoreAttribute : Attribute
470    {
471    }
472}
Note: See TracBrowser for help on using the repository browser.