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