1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections;
22 using System.Collections.Generic;
23 using System.Diagnostics;
24 using System.IO;
25 using System.Reflection;
26 using System.Runtime.Remoting;
27 using System.Text;
28 using System.Windows.Forms;
29 
30 using KeePass.App;
31 using KeePass.App.Configuration;
32 using KeePass.Plugins;
33 using KeePass.Resources;
34 using KeePass.UI;
35 using KeePass.Util;
36 
37 using KeePassLib;
38 using KeePassLib.Cryptography;
39 using KeePassLib.Delegates;
40 using KeePassLib.Interfaces;
41 using KeePassLib.Native;
42 using KeePassLib.Utility;
43 
44 namespace KeePass.Plugins
45 {
46 	internal sealed class PluginManager : IEnumerable<PluginInfo>
47 	{
48 		private List<PluginInfo> m_vPlugins = new List<PluginInfo>();
49 		private IPluginHost m_host = null;
50 
51 		private static string g_strUserDir = string.Empty;
52 		internal static string UserDirectory
53 		{
54 			get { return g_strUserDir; }
55 		}
56 
Initialize(IPluginHost host)57 		public void Initialize(IPluginHost host)
58 		{
59 			Debug.Assert(host != null);
60 			m_host = host;
61 		}
62 
IEnumerable.GetEnumerator()63 		IEnumerator IEnumerable.GetEnumerator()
64 		{
65 			return m_vPlugins.GetEnumerator();
66 		}
67 
GetEnumerator()68 		public IEnumerator<PluginInfo> GetEnumerator()
69 		{
70 			return m_vPlugins.GetEnumerator();
71 		}
72 
LoadAllPlugins()73 		internal void LoadAllPlugins()
74 		{
75 			string[] vExclNames = new string[] {
76 				AppDefs.FileNames.Program, AppDefs.FileNames.XmlSerializers,
77 				AppDefs.FileNames.NativeLib32, AppDefs.FileNames.NativeLib64,
78 				AppDefs.FileNames.ShInstUtil
79 			};
80 
81 			string strAppDir = UrlUtil.GetFileDirectory(WinUtil.GetExecutable(),
82 				false, true);
83 			LoadAllPlugins(strAppDir, SearchOption.TopDirectoryOnly, vExclNames);
84 			g_strUserDir = strAppDir; // Preliminary, see below
85 
86 			if(WinUtil.IsAppX)
87 			{
88 				string str = UrlUtil.EnsureTerminatingSeparator(
89 					AppConfigSerializer.AppDataDirectory, false) + AppDefs.PluginsDir;
90 				LoadAllPlugins(str, SearchOption.AllDirectories, vExclNames);
91 
92 				g_strUserDir = str;
93 			}
94 			else if(!NativeLib.IsUnix())
95 			{
96 				string str = UrlUtil.EnsureTerminatingSeparator(strAppDir,
97 					false) + AppDefs.PluginsDir;
98 				LoadAllPlugins(str, SearchOption.AllDirectories, vExclNames);
99 
100 				g_strUserDir = str;
101 			}
102 			else // Unix
103 			{
104 				try
105 				{
106 					DirectoryInfo diPlgRoot = new DirectoryInfo(strAppDir);
107 					foreach(DirectoryInfo diSub in diPlgRoot.GetDirectories())
108 					{
109 						if(diSub == null) { Debug.Assert(false); continue; }
110 
111 						if(string.Equals(diSub.Name, AppDefs.PluginsDir,
112 							StrUtil.CaseIgnoreCmp))
113 						{
114 							LoadAllPlugins(diSub.FullName, SearchOption.AllDirectories,
115 								vExclNames);
116 
117 							g_strUserDir = diSub.FullName;
118 						}
119 					}
120 				}
121 				catch(Exception) { Debug.Assert(false); }
122 			}
123 		}
124 
LoadAllPlugins(string strDir, SearchOption so, string[] vExclNames)125 		public void LoadAllPlugins(string strDir, SearchOption so, string[] vExclNames)
126 		{
127 			Debug.Assert(m_host != null);
128 
129 			try
130 			{
131 				if(!Directory.Exists(strDir)) return; // No assert
132 
133 				List<string> lDlls = UrlUtil.GetFilePaths(strDir, "*.dll", so);
134 				FilterList(lDlls, vExclNames);
135 
136 				List<string> lExes = UrlUtil.GetFilePaths(strDir, "*.exe", so);
137 				FilterList(lExes, vExclNames);
138 
139 				List<string> lPlgxs = UrlUtil.GetFilePaths(strDir, "*." +
140 					PlgxPlugin.PlgxExtension, so);
141 				FilterList(lPlgxs, vExclNames);
142 
143 				FilterLists(lDlls, lExes, lPlgxs);
144 
145 				LoadPlugins(lDlls, null, null, true);
146 				LoadPlugins(lExes, null, null, true);
147 
148 				if(lPlgxs.Count != 0)
149 				{
150 					OnDemandStatusDialog dlgStatus = new OnDemandStatusDialog(true, null);
151 					dlgStatus.StartLogging(PwDefs.ShortProductName, false);
152 
153 					try
154 					{
155 						foreach(string strFile in lPlgxs)
156 							PlgxPlugin.Load(strFile, dlgStatus);
157 					}
158 					finally { dlgStatus.EndLogging(); }
159 				}
160 			}
161 			catch(Exception) { Debug.Assert(false); } // Path access violation
162 		}
163 
LoadPlugin(string strFilePath, string strTypeName, string strDisplayFilePath, bool bSkipCacheFile)164 		public void LoadPlugin(string strFilePath, string strTypeName,
165 			string strDisplayFilePath, bool bSkipCacheFile)
166 		{
167 			if(strFilePath == null) throw new ArgumentNullException("strFilePath");
168 
169 			List<string> l = new List<string>();
170 			l.Add(strFilePath);
171 
172 			LoadPlugins(l, strTypeName, strDisplayFilePath, bSkipCacheFile);
173 		}
174 
LoadPlugins(List<string> lFiles, string strTypeName, string strDisplayFilePath, bool bSkipCacheFiles)175 		private void LoadPlugins(List<string> lFiles, string strTypeName,
176 			string strDisplayFilePath, bool bSkipCacheFiles)
177 		{
178 			string strCacheRoot = UrlUtil.EnsureTerminatingSeparator(
179 				PlgxCache.GetCacheRoot(), false);
180 
181 			foreach(string strFile in lFiles)
182 			{
183 				if(bSkipCacheFiles && strFile.StartsWith(strCacheRoot,
184 					StrUtil.CaseIgnoreCmp))
185 					continue;
186 
187 				FileVersionInfo fvi = null;
188 				try
189 				{
190 					fvi = FileVersionInfo.GetVersionInfo(strFile);
191 
192 					if((fvi == null) || (fvi.ProductName == null) ||
193 						(fvi.ProductName != AppDefs.PluginProductName))
194 					{
195 						continue;
196 					}
197 				}
198 				catch(Exception) { continue; }
199 
200 				Exception exShowStd = null;
201 				try
202 				{
203 					string strHash = Convert.ToBase64String(CryptoUtil.HashSha256(
204 						strFile), Base64FormattingOptions.None);
205 
206 					PluginInfo pi = new PluginInfo(strFile, fvi, strDisplayFilePath);
207 					pi.Interface = CreatePluginInstance(pi.FilePath, strTypeName);
208 
209 					CheckCompatibility(strHash, pi.Interface);
210 					// CheckCompatibilityRefl(strFile);
211 
212 					if(!pi.Interface.Initialize(m_host))
213 						continue; // Fail without error
214 
215 					m_vPlugins.Add(pi);
216 				}
217 				catch(BadImageFormatException exBif)
218 				{
219 					if(Is1xPlugin(strFile))
220 						MessageService.ShowWarning(KPRes.PluginIncompatible +
221 							MessageService.NewLine + strFile + MessageService.NewParagraph +
222 							KPRes.Plugin1x + MessageService.NewParagraph + KPRes.Plugin1xHint);
223 					else exShowStd = exBif;
224 				}
225 				catch(Exception exLoad)
226 				{
227 					if(Program.CommandLineArgs[AppDefs.CommandLineOptions.Debug] != null)
228 						MessageService.ShowWarningExcp(strFile, exLoad);
229 					else exShowStd = exLoad;
230 				}
231 
232 				if(exShowStd != null)
233 					ShowLoadError(strFile, exShowStd, null);
234 			}
235 		}
236 
ShowLoadError(string strPath, Exception ex, IStatusLogger slStatus)237 		internal static void ShowLoadError(string strPath, Exception ex,
238 			IStatusLogger slStatus)
239 		{
240 			if(string.IsNullOrEmpty(strPath)) { Debug.Assert(false); return; }
241 
242 			if(slStatus != null)
243 				slStatus.SetText(KPRes.PluginLoadFailed, LogStatusType.Info);
244 
245 			string strMsg = KPRes.PluginIncompatible + MessageService.NewLine +
246 				strPath + MessageService.NewParagraph + KPRes.PluginUpdateHint;
247 			if(NativeLib.IsUnix())
248 				strMsg += MessageService.NewParagraph + KPRes.PluginMonoComplete;
249 
250 			bool bShowExcp = (Program.CommandLineArgs[
251 				AppDefs.CommandLineOptions.Debug] != null);
252 			string strExcp = ((ex != null) ? StrUtil.FormatException(ex).Trim() : null);
253 
254 			VistaTaskDialog vtd = new VistaTaskDialog();
255 			vtd.Content = strMsg;
256 			vtd.ExpandedByDefault = ((strExcp != null) && bShowExcp);
257 			vtd.ExpandedInformation = strExcp;
258 			vtd.WindowTitle = PwDefs.ShortProductName;
259 			vtd.SetIcon(VtdIcon.Warning);
260 
261 			if(!vtd.ShowDialog())
262 			{
263 				if(!bShowExcp) MessageService.ShowWarning(strMsg);
264 				else MessageService.ShowWarningExcp(strPath, ex);
265 			}
266 		}
267 
UnloadAllPlugins()268 		public void UnloadAllPlugins()
269 		{
270 			foreach(PluginInfo plugin in m_vPlugins)
271 			{
272 				Debug.Assert(plugin.Interface != null);
273 				if(plugin.Interface != null)
274 				{
275 					try { plugin.Interface.Terminate(); }
276 					catch(Exception) { Debug.Assert(false); }
277 				}
278 			}
279 
280 			m_vPlugins.Clear();
281 		}
282 
CreatePluginInstance(string strFilePath, string strTypeName)283 		private static Plugin CreatePluginInstance(string strFilePath,
284 			string strTypeName)
285 		{
286 			Debug.Assert(strFilePath != null);
287 			if(strFilePath == null) throw new ArgumentNullException("strFilePath");
288 
289 			string strType;
290 			if(string.IsNullOrEmpty(strTypeName))
291 			{
292 				strType = UrlUtil.GetFileName(strFilePath);
293 				strType = UrlUtil.StripExtension(strType) + "." +
294 					UrlUtil.StripExtension(strType) + "Ext";
295 			}
296 			else strType = strTypeName + "." + strTypeName + "Ext";
297 
298 			ObjectHandle oh = Activator.CreateInstanceFrom(strFilePath, strType);
299 
300 			Plugin plugin = (oh.Unwrap() as Plugin);
301 			if(plugin == null) throw new FileLoadException();
302 			return plugin;
303 		}
304 
Is1xPlugin(string strFile)305 		private static bool Is1xPlugin(string strFile)
306 		{
307 			try
308 			{
309 				byte[] pbFile = File.ReadAllBytes(strFile);
310 				byte[] pbSig = StrUtil.Utf8.GetBytes("KpCreateInstance");
311 				string strData = MemUtil.ByteArrayToHexString(pbFile);
312 				string strSig = MemUtil.ByteArrayToHexString(pbSig);
313 
314 				return (strData.IndexOf(strSig) >= 0);
315 			}
316 			catch(Exception) { Debug.Assert(false); }
317 
318 			return false;
319 		}
320 
FilterList(List<string> l, string[] vExclNames)321 		private static void FilterList(List<string> l, string[] vExclNames)
322 		{
323 			if((l == null) || (vExclNames == null)) { Debug.Assert(false); return; }
324 
325 			for(int i = l.Count - 1; i >= 0; --i)
326 			{
327 				string strName = UrlUtil.GetFileName(l[i]);
328 				if(string.IsNullOrEmpty(strName))
329 				{
330 					Debug.Assert(false);
331 					l.RemoveAt(i);
332 					continue;
333 				}
334 
335 				// Ignore satellite assemblies
336 				if(strName.EndsWith(".resources.dll", StrUtil.CaseIgnoreCmp))
337 				{
338 					l.RemoveAt(i);
339 					continue;
340 				}
341 
342 				foreach(string strExcl in vExclNames)
343 				{
344 					if(string.IsNullOrEmpty(strExcl)) { Debug.Assert(false); continue; }
345 
346 					if(strName.Equals(strExcl, StrUtil.CaseIgnoreCmp))
347 					{
348 						l.RemoveAt(i);
349 						break;
350 					}
351 				}
352 			}
353 		}
354 
FilterLists(List<string> lDlls, List<string> lExes, List<string> lPlgxs)355 		private static void FilterLists(List<string> lDlls, List<string> lExes,
356 			List<string> lPlgxs)
357 		{
358 			bool bPreferDll = Program.IsStableAssembly();
359 
360 			for(int i = lDlls.Count - 1; i >= 0; --i)
361 			{
362 				string strDllPre = UrlUtil.StripExtension(lDlls[i]);
363 
364 				for(int j = lPlgxs.Count - 1; j >= 0; --j)
365 				{
366 					string strPlgxPre = UrlUtil.StripExtension(lPlgxs[j]);
367 
368 					if(string.Equals(strDllPre, strPlgxPre, StrUtil.CaseIgnoreCmp))
369 					{
370 						if(bPreferDll) lPlgxs.RemoveAt(j);
371 						else lDlls.RemoveAt(i);
372 
373 						break;
374 					}
375 				}
376 			}
377 		}
378 
CheckRefs(Module m, int iMdTokenType, GAction<Module, int> fCheck)379 		private static void CheckRefs(Module m, int iMdTokenType,
380 			GAction<Module, int> fCheck)
381 		{
382 			if((m == null) || (fCheck == null)) { Debug.Assert(false); return; }
383 			if((iMdTokenType & 0x00FFFFFF) != 0)
384 			{
385 				Debug.Assert(false); // Not a valid MetadataTokenType
386 				return;
387 			}
388 			if((iMdTokenType < 0) || (iMdTokenType == 0x7F000000))
389 			{
390 				Debug.Assert(false); // Loop below would need to be adjusted
391 				return;
392 			}
393 
394 			try
395 			{
396 				// https://msdn.microsoft.com/en-us/library/ms404456(v=vs.100).aspx
397 				// https://docs.microsoft.com/en-us/dotnet/standard/metadata-and-self-describing-components
398 				int s = iMdTokenType | 1; // RID = 0 <=> 'nil token'
399 				int e = iMdTokenType | 0x00FFFFFF;
400 
401 				for(int i = s; i <= e; ++i) fCheck(m, i);
402 			}
403 			catch(ArgumentOutOfRangeException) { } // End of metadata table
404 			catch(ArgumentException) { Debug.Assert(false); }
405 			// Other exceptions indicate an unresolved reference
406 		}
407 
CheckTypeRef(Module m, int iMdToken)408 		private static void CheckTypeRef(Module m, int iMdToken)
409 		{
410 			// ResolveType should throw exception for unresolvable token
411 			// if(m.ResolveType(iMdToken) == null) { Debug.Assert(false); }
412 
413 			// ResolveType should throw exception for unresolvable token
414 			Type t = m.ResolveType(iMdToken);
415 			if(t == null) { Debug.Assert(false); return; }
416 
417 			if(t.Assembly == typeof(PluginManager).Assembly)
418 			{
419 				if(t.IsNotPublic || t.IsNestedPrivate || t.IsNestedAssembly ||
420 					t.IsNestedFamANDAssem)
421 					throw new UnauthorizedAccessException("Ref.: " + t.ToString() + ".");
422 			}
423 		}
424 
CheckMemberRef(Module m, int iMdToken)425 		private static void CheckMemberRef(Module m, int iMdToken)
426 		{
427 			// ResolveMember should throw exception for unresolvable token
428 			// if(m.ResolveMember(iMdToken) == null) { Debug.Assert(false); }
429 
430 			// ResolveMember should throw exception for unresolvable token
431 			MemberInfo mi = m.ResolveMember(iMdToken);
432 			if(mi == null) { Debug.Assert(false); return; }
433 
434 			if(mi.Module == typeof(PluginManager).Module)
435 			{
436 				MethodBase mb = (mi as MethodBase);
437 				if(mb != null)
438 				{
439 					if(mb.IsPrivate || mb.IsAssembly || mb.IsFamilyAndAssembly)
440 						ThrowRefAccessExcp(mb);
441 					return;
442 				}
443 
444 				FieldInfo fi = (mi as FieldInfo);
445 				if(fi != null)
446 				{
447 					if(fi.IsPrivate || fi.IsAssembly || fi.IsFamilyAndAssembly)
448 						ThrowRefAccessExcp(fi);
449 					return;
450 				}
451 
452 				Debug.Assert(false); // Unknown member reference type
453 			}
454 		}
455 
ThrowRefAccessExcp(MemberInfo mi)456 		private static void ThrowRefAccessExcp(MemberInfo mi)
457 		{
458 			string str = "Ref.: ";
459 
460 			try
461 			{
462 				Type t = mi.DeclaringType;
463 				if(t != null) str += t.ToString() + " -> ";
464 			}
465 			catch(Exception) { Debug.Assert(false); }
466 
467 			throw new MemberAccessException(str + mi.ToString() + ".");
468 		}
469 
CheckCompatibilityPriv(Plugin p)470 		private static void CheckCompatibilityPriv(Plugin p)
471 		{
472 			// When trying to resolve a non-existing token, Mono
473 			// terminates the whole process with a SIGABRT instead
474 			// of just throwing an ArgumentOutOfRangeException
475 			if(MonoWorkarounds.IsRequired(9604)) return;
476 
477 			Assembly asm = p.GetType().Assembly;
478 			if(asm == typeof(PluginManager).Assembly) { Debug.Assert(false); return; }
479 
480 			foreach(Module m in asm.GetModules())
481 			{
482 				// MetadataTokenType.TypeRef = 0x01000000
483 				CheckRefs(m, 0x01000000, CheckTypeRef);
484 
485 				// MetadataTokenType.MemberRef = 0x0A000000
486 				CheckRefs(m, 0x0A000000, CheckMemberRef);
487 			}
488 		}
489 
CheckCompatibility(string strHash, Plugin p)490 		private static void CheckCompatibility(string strHash, Plugin p)
491 		{
492 			AceApplication aceApp = Program.Config.Application;
493 			// bool? ob = aceApp.GetPluginCompat(strHash);
494 			// if(ob.HasValue) return ob.Value;
495 			if(aceApp.IsPluginCompat(strHash)) return;
496 
497 			CheckCompatibilityPriv(p);
498 
499 			aceApp.SetPluginCompat(strHash);
500 		}
501 
502 		/* private static void CheckCompatibilityRefl(string strFile)
503 		{
504 			ResolveEventHandler eh = delegate(object sender, ResolveEventArgs e)
505 			{
506 				string strName = e.Name;
507 				if(strName.Equals("KeePass", StrUtil.CaseIgnoreCmp) ||
508 					strName.StartsWith("KeePass,", StrUtil.CaseIgnoreCmp))
509 					return Assembly.ReflectionOnlyLoadFrom(WinUtil.GetExecutable());
510 
511 				return Assembly.ReflectionOnlyLoad(strName);
512 			};
513 
514 			AppDomain d = AppDomain.CurrentDomain;
515 			d.ReflectionOnlyAssemblyResolve += eh;
516 			try
517 			{
518 				Assembly asm = Assembly.ReflectionOnlyLoadFrom(strFile);
519 				asm.GetTypes();
520 			}
521 			finally { d.ReflectionOnlyAssemblyResolve -= eh; }
522 		} */
523 
AddMenuItems(PluginMenuType t, ToolStripItemCollection c, ToolStripItem tsiPrev)524 		internal void AddMenuItems(PluginMenuType t, ToolStripItemCollection c,
525 			ToolStripItem tsiPrev)
526 		{
527 			if(c == null) { Debug.Assert(false); return; }
528 
529 			List<ToolStripItem> l = new List<ToolStripItem>();
530 			foreach(PluginInfo pi in m_vPlugins)
531 			{
532 				if(pi == null) { Debug.Assert(false); continue; }
533 
534 				Plugin p = pi.Interface;
535 				if(p == null) { Debug.Assert(false); continue; }
536 
537 				ToolStripMenuItem tsmi = p.GetMenuItem(t);
538 				if(tsmi != null)
539 				{
540 					// string strTip = tsmi.ToolTipText;
541 					// if((strTip == null) || (strTip == tsmi.Text))
542 					//	strTip = string.Empty;
543 					// if(strTip.Length != 0) strTip += MessageService.NewParagraph;
544 					// strTip += KPRes.Plugin + ": " + pi.Name;
545 					// tsmi.ToolTipText = strTip;
546 
547 					l.Add(tsmi);
548 				}
549 			}
550 			if(l.Count == 0) return;
551 
552 			int iPrev = ((tsiPrev != null) ? c.IndexOf(tsiPrev) : -1);
553 			if(iPrev < 0) { Debug.Assert(false); iPrev = c.Count - 1; }
554 			int iIns = iPrev + 1;
555 
556 			l.Sort(PluginManager.CompareToolStripItems);
557 			if((iPrev >= 0) && (iPrev < c.Count) && !(c[iPrev] is ToolStripSeparator))
558 				l.Insert(0, new ToolStripSeparator());
559 			if((iIns < c.Count) && !(c[iIns] is ToolStripSeparator))
560 				l.Add(new ToolStripSeparator());
561 
562 			if(iIns == c.Count) c.AddRange(l.ToArray());
563 			else
564 			{
565 				for(int i = 0; i < l.Count; ++i)
566 					c.Insert(iIns + i, l[i]);
567 			}
568 		}
569 
CompareToolStripItems(ToolStripItem x, ToolStripItem y)570 		private static int CompareToolStripItems(ToolStripItem x,
571 			ToolStripItem y)
572 		{
573 			return string.Compare(x.Text, y.Text, StrUtil.CaseIgnoreCmp);
574 		}
575 	}
576 }
577