//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Configuration { using System.Collections; using System.Configuration; using System.Configuration.Internal; using System.Web; using System.Web.Util; using System.Security; using System.IO; using System.Web.Hosting; using System.Runtime.InteropServices; using System.Reflection; using System.Collections.Specialized; using System.Xml; using System.Security.Principal; using System.Threading; using System.Globalization; using System.Security.Permissions; [SecurityPermission(SecurityAction.Demand, Unrestricted = true)] internal sealed class RemoteWebConfigurationHost : DelegatingConfigHost { private const string KEY_MACHINE = "MACHINE"; private static object s_version = new object(); private string _Server; private string _Username; private string _Domain; private string _Password; private WindowsIdentity _Identity; private Hashtable _PathMap; // configPath -> configFile private string _ConfigPath; ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// internal RemoteWebConfigurationHost() {} public override void Init(IInternalConfigRoot configRoot, params object[] hostInitParams) { throw ExceptionUtil.UnexpectedError("RemoteWebConfigurationHost::Init"); } public override void InitForConfiguration(ref string locationSubPath, out string configPath, out string locationConfigPath, IInternalConfigRoot root, params object[] hostInitConfigurationParams) { WebLevel webLevel = (WebLevel) hostInitConfigurationParams[0]; // ConfigurationFileMap fileMap = (ConfigurationFileMap) hostInitConfigurationParams[1]; string path = (string) hostInitConfigurationParams[2]; string site = (string) hostInitConfigurationParams[3]; if (locationSubPath == null) { locationSubPath = (string) hostInitConfigurationParams[4]; } string server = (string) hostInitConfigurationParams[5]; string userName = (string) hostInitConfigurationParams[6]; string password = (string) hostInitConfigurationParams[7]; IntPtr tokenHandle = (IntPtr) hostInitConfigurationParams[8]; configPath = null; locationConfigPath = null; _Server = server; _Username = GetUserNameFromFullName(userName); _Domain = GetDomainFromFullName(userName); _Password = password; _Identity = (tokenHandle == IntPtr.Zero) ? null : new WindowsIdentity(tokenHandle); //CreateWindowsIdentity(username, domain, password, tokenHandle); _PathMap = new Hashtable(StringComparer.OrdinalIgnoreCase); #if !FEATURE_PAL // FEATURE_PAL does not have WindowsImpersonationContext, COM objects // // Send the path arguments to the server for parsing, // and retreive the normalized paths and path mapping // from config paths to file paths. // string filePaths; try { WindowsImpersonationContext wiContext = (_Identity != null) ? _Identity.Impersonate() : null; try { IRemoteWebConfigurationHostServer remoteSrv = RemoteWebConfigurationHost.CreateRemoteObject(server, _Username, _Domain, password); //(IRemoteWebConfigurationHostServer) Activator.CreateInstance(type); try { filePaths = remoteSrv.GetFilePaths((int) webLevel, path, site, locationSubPath); } finally { // Release COM objects while (Marshal.ReleaseComObject(remoteSrv) > 0) { } } } finally { if (wiContext != null) wiContext.Undo(); } } catch { // Wrap finally clause with a try to avoid exception clauses being run // while the thread is impersonated. throw; } if (filePaths == null) { throw ExceptionUtil.UnexpectedError("RemoteWebConfigurationHost::InitForConfiguration"); } // // Format of filePaths: // appPath < appSiteName < appSiteID < configPath < locationConfigPath [< configPath < fileName]+ // string[] parts = filePaths.Split(RemoteWebConfigurationHostServer.FilePathsSeparatorParams); if (parts.Length < 7 || (parts.Length - 5) % 2 != 0) { throw ExceptionUtil.UnexpectedError("RemoteWebConfigurationHost::InitForConfiguration"); } // convert empty strings to nulls for (int i = 0; i < parts.Length; i++) { if (parts[i].Length == 0) { parts[i] = null; } } // get config paths string appPath = parts[0]; string appSiteName = parts[1]; string appSiteID = parts[2]; configPath = parts[3]; locationConfigPath = parts[4]; _ConfigPath = configPath; // Create a WebConfigurationFileMap to be used when we later initialize our delegating WebConfigurationHost WebConfigurationFileMap configFileMap = new WebConfigurationFileMap(); VirtualPath appPathVirtualPath = VirtualPath.CreateAbsoluteAllowNull(appPath); configFileMap.Site = appSiteID; // populate the configpath->physical path mapping for (int i = 5; i < parts.Length; i += 2) { string configPathTemp = parts[i]; string physicalFilePath = parts[i+1]; _PathMap.Add(configPathTemp, physicalFilePath); // Update the WebConfigurationFileMap if (WebConfigurationHost.IsMachineConfigPath(configPathTemp)) { configFileMap.MachineConfigFilename = physicalFilePath; } else { string vPathString; bool isRootApp; if (WebConfigurationHost.IsRootWebConfigPath(configPathTemp)) { vPathString = null; isRootApp = false; } else { VirtualPath vPath; string dummy; WebConfigurationHost.GetSiteIDAndVPathFromConfigPath(configPathTemp, out dummy, out vPath); vPathString = VirtualPath.GetVirtualPathString(vPath); isRootApp = (vPath == appPathVirtualPath); } configFileMap.VirtualDirectories.Add(vPathString, new VirtualDirectoryMapping(Path.GetDirectoryName(physicalFilePath), isRootApp)); } } #else // !FEATURE_PAL: set dummy config path string appPath = null; _ConfigPath = configPath; #endif // !FEATURE_PAL // Delegate to a WebConfigurationHost for unhandled methods. WebConfigurationHost webConfigurationHost = new WebConfigurationHost(); webConfigurationHost.Init(root, true, new UserMapPath(configFileMap, /*pathsAreLocal*/ false), null, appPath, appSiteName, appSiteID); Host = webConfigurationHost; } // config path support public override bool IsConfigRecordRequired(string configPath) { // a record is required for every part of the config path return configPath.Length <= _ConfigPath.Length; } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override string GetStreamName(string configPath) { return (string) _PathMap[configPath]; } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override object GetStreamVersion(string streamName) { #if FEATURE_PAL // FEATURE_PAL: singelton version return s_version; #else // for now, assume it is the same // return s_version; bool exists; long size, createDate, lastWriteDate; WindowsImpersonationContext wiContext = null; try { if (_Identity != null) { wiContext = _Identity.Impersonate(); } try { ////////////////////////////////////////////////////////////////// // Step 3: Get the type and create the object on the remote server IRemoteWebConfigurationHostServer remoteSrv = CreateRemoteObject(_Server, _Username, _Domain, _Password); try { ////////////////////////////////////////////////////////////////// // Step 4: Call the API remoteSrv.GetFileDetails(streamName, out exists, out size, out createDate, out lastWriteDate); } finally { while (Marshal.ReleaseComObject(remoteSrv) > 0) { } // release the COM object } } finally { if (wiContext != null) { wiContext.Undo(); // revert impersonation } } } catch { // Wrap finally clause with a try to avoid exception clauses being run // while the thread is impersonated. throw; } return new FileDetails(exists, size, DateTime.FromFileTimeUtc(createDate), DateTime.FromFileTimeUtc(lastWriteDate)); #endif // FEATURE_PAL } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override Stream OpenStreamForRead(string streamName) { RemoteWebConfigurationHostStream rcs = new RemoteWebConfigurationHostStream(false, _Server, streamName, null, _Username, _Domain, _Password, _Identity); if (rcs == null || rcs.Length < 1) return null; return rcs; } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override Stream OpenStreamForWrite(string streamName, string templateStreamName, ref object writeContext) { RemoteWebConfigurationHostStream rcs = new RemoteWebConfigurationHostStream(true, _Server, streamName, templateStreamName, _Username, _Domain, _Password, _Identity); writeContext = rcs; return rcs; } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override void DeleteStream(string StreamName) { // } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override void WriteCompleted(string streamName, bool success, object writeContext) { if (success) { RemoteWebConfigurationHostStream rcs = (RemoteWebConfigurationHostStream)writeContext; rcs.FlushForWriteCompleted(); } } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override bool IsFile(string StreamName) { return false; } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override bool PrefetchAll(string configPath, string StreamName) { return true; } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override bool PrefetchSection(string sectionGroupName, string sectionName) { return true; } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override void GetRestrictedPermissions(IInternalConfigRecord configRecord, out PermissionSet permissionSet, out bool isHostReady) { WebConfigurationHost.StaticGetRestrictedPermissions(configRecord, out permissionSet, out isHostReady); } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// public override bool IsRemote { get { return true; } } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// // Encrypt/decrypt support public override string DecryptSection(string encryptedXmlString, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedConfigSection) { return CallEncryptOrDecrypt(false, encryptedXmlString, protectionProvider, protectedConfigSection); } public override string EncryptSection(string clearTextXmlString, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedConfigSection) { return CallEncryptOrDecrypt(true, clearTextXmlString, protectionProvider, protectedConfigSection); } private string CallEncryptOrDecrypt(bool doEncrypt, string xmlString, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedConfigSection) { #if !FEATURE_PAL // FEATURE_PAL has no COM objects => no encryption // ROTORTODO: COM Objects are not implemented. // CORIOLISTODO: COM Objects are not implemented. ProviderSettings ps; NameValueCollection nvc; string [] paramKeys; string [] paramValues; string returnString = null; string typeName; WindowsImpersonationContext wiContext = null; //////////////////////////////////////////////////////////// // Step 1: Create list of parameters for the protection provider typeName = protectionProvider.GetType().AssemblyQualifiedName; ps = protectedConfigSection.Providers[protectionProvider.Name]; if (ps == null) throw ExceptionUtil.ParameterInvalid("protectionProvider"); nvc = ps.Parameters; if (nvc == null) nvc = new NameValueCollection(); paramKeys = nvc.AllKeys; paramValues = new string[paramKeys.Length]; for(int iter = 0; iter 0) { } // release the COM object } } finally { if (wiContext != null) wiContext.Undo(); // revert impersonation } } catch { } return returnString; #else // !FEATURE_PAL throw new NotImplementedException("ROTORTODO"); #endif // !FEATURE_PAL } /////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////// private static string GetUserNameFromFullName(string fullUserName) { if (string.IsNullOrEmpty(fullUserName)) return null; if (fullUserName.Contains("@")) { return fullUserName; } string[] splitted = fullUserName.Split(new char[] { '\\' }); if (splitted.Length == 1) return fullUserName; else return splitted[1]; } private static string GetDomainFromFullName(string fullUserName) { if (string.IsNullOrEmpty(fullUserName)) return null; if (fullUserName.Contains("@")) return null; string[] splitted = fullUserName.Split(new char[] { '\\' }); if (splitted.Length == 1) return "."; return splitted[0]; } // impersonation support: create an identity from credentials #if OLD_WAY private static WindowsIdentity CreateWindowsIdentity(string userName, string password, IntPtr tokenHandle) { ////////////////////////////////////////////////////////////////// // Step 0: Most common case: check if no credentials are supplied if (string.IsNullOrEmpty(userName) && tokenHandle == IntPtr.Zero && string.IsNullOrEmpty(password)) return null; ////////////////////////////////////////////////////////////////// // Step 1: Make sure that either username & password OR token is supplied if ((string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password)) || (!string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(password))) throw ExceptionUtil.ParameterNullOrEmpty("password"); if (!string.IsNullOrEmpty(userName) && tokenHandle != IntPtr.Zero) throw ExceptionUtil.ParameterInvalid("tokenHandle"); ////////////////////////////////////////////////////////////////// // Step 2: Create token if not supplied if (!string.IsNullOrEmpty(userName)) tokenHandle = CreateUserToken(userName, password); ////////////////////////////////////////////////////////////////// // Step 3: Create a windows identity from the token WindowsIdentity wi = new WindowsIdentity(tokenHandle); if (!string.IsNullOrEmpty(userName) && tokenHandle != IntPtr.Zero) // Close the handle if we created it. UnsafeNativeMethods.CloseHandle(tokenHandle); return wi; } private static IntPtr CreateUserToken(string fullUserName, string password) { string userName, domain; if (fullUserName.Contains("@")) { userName = fullUserName; domain = null; } else { string[] splitted = fullUserName.Split(new char[] { '\\' }); if (splitted.Length == 1) { userName = fullUserName; domain = "."; } else { userName = splitted[1]; domain = splitted[0]; } } //This parameter causes LogonUser to create a primary token. const int LOGON32_PROVIDER_DEFAULT = 0; const int LOGON32_LOGON_INTERACTIVE = 2; const int LOGON32_LOGON_NETWORK = 3; const int LOGON32_LOGON_BATCH = 4; const int LOGON32_LOGON_SERVICE = 5; const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8; int[] Logon32Values = new int[] { LOGON32_LOGON_INTERACTIVE, LOGON32_LOGON_BATCH, LOGON32_LOGON_SERVICE, LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_LOGON_NETWORK}; IntPtr tokenHandle = IntPtr.Zero; int lastError = 0; if (UnsafeNativeMethods.LogonUser(userName, domain, password, Logon32Values[0], LOGON32_PROVIDER_DEFAULT, ref tokenHandle) != 0) return tokenHandle; lastError = Marshal.GetHRForLastWin32Error(); for (int iter = 1; iter < Logon32Values.Length; iter++) if (UnsafeNativeMethods.LogonUser(userName, domain, password, Logon32Values[iter], LOGON32_PROVIDER_DEFAULT, ref tokenHandle) != 0) return tokenHandle; Marshal.ThrowExceptionForHR(lastError); return IntPtr.Zero; } #endif internal static IRemoteWebConfigurationHostServer CreateRemoteObject(string server, string username, string domain, string password) { #if !FEATURE_PAL // FEATURE_PAL has no COM objects try { if (string.IsNullOrEmpty(username)) return CreateRemoteObjectUsingGetTypeFromCLSID(server); if (IntPtr.Size == 8) return CreateRemoteObjectOn64BitPlatform(server, username, domain, password); return CreateRemoteObjectOn32BitPlatform(server, username, domain, password); } catch (COMException ex) { if ((uint)ex.ErrorCode == 0x80040154) throw new Exception(SR.GetString(SR.Make_sure_remote_server_is_enabled_for_config_access)); throw; } } private static IRemoteWebConfigurationHostServer CreateRemoteObjectUsingGetTypeFromCLSID(string server) { Type type = Type.GetTypeFromCLSID(typeof(RemoteWebConfigurationHostServer).GUID, server, true); return (IRemoteWebConfigurationHostServer)Activator.CreateInstance(type); } private static IRemoteWebConfigurationHostServer CreateRemoteObjectOn32BitPlatform(string server, string username, string domain, string password) { MULTI_QI [] amqi = new MULTI_QI[1]; IntPtr guidbuf = IntPtr.Zero; COAUTHINFO ca = null; IntPtr captr = IntPtr.Zero; COSERVERINFO cs = null; Guid clsid = typeof(RemoteWebConfigurationHostServer).GUID; int hr = 0; COAUTHIDENTITY ci = null; IntPtr ciptr = IntPtr.Zero; try { guidbuf = Marshal.AllocCoTaskMem(16); Marshal.StructureToPtr(typeof(IRemoteWebConfigurationHostServer).GUID, guidbuf, false); amqi[0] = new MULTI_QI(guidbuf); ci = new COAUTHIDENTITY(username, domain, password); ciptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(ci)); Marshal.StructureToPtr(ci, ciptr, false); ca = new COAUTHINFO(RpcAuthent.WinNT, RpcAuthor.None, null, /*RpcLevel.Connect*/ RpcLevel.Default, RpcImpers.Impersonate, ciptr); captr = Marshal.AllocCoTaskMem(Marshal.SizeOf(ca)); Marshal.StructureToPtr(ca, captr, false); cs = new COSERVERINFO(server, captr); hr = UnsafeNativeMethods.CoCreateInstanceEx(ref clsid, IntPtr.Zero, (int)ClsCtx.RemoteServer, cs, 1, amqi); if ((uint)hr == 0x80040154) throw new Exception(SR.GetString(SR.Make_sure_remote_server_is_enabled_for_config_access)); if (hr < 0) Marshal.ThrowExceptionForHR(hr); if (amqi[0].hr < 0) Marshal.ThrowExceptionForHR(amqi[0].hr); hr = UnsafeNativeMethods.CoSetProxyBlanket(amqi[0].pItf, RpcAuthent.WinNT, RpcAuthor.None, null, /*RpcLevel.Connect*/ RpcLevel.Default, RpcImpers.Impersonate, ciptr, 0); if (hr < 0) Marshal.ThrowExceptionForHR(hr); return (IRemoteWebConfigurationHostServer)Marshal.GetObjectForIUnknown(amqi[0].pItf); } finally { if (amqi[0].pItf != IntPtr.Zero) { Marshal.Release(amqi[0].pItf); amqi[0].pItf = IntPtr.Zero; } amqi[0].piid = IntPtr.Zero; if (captr != IntPtr.Zero) { Marshal.DestroyStructure(captr, typeof(COAUTHINFO)); Marshal.FreeCoTaskMem(captr); } if (ciptr != IntPtr.Zero) { Marshal.DestroyStructure(ciptr, typeof(COAUTHIDENTITY)); Marshal.FreeCoTaskMem(ciptr); } if (guidbuf != IntPtr.Zero) Marshal.FreeCoTaskMem(guidbuf); } #else // !FEATURE_PAL throw new NotSupportedException(); #endif // !FEATURE_PAL } private static IRemoteWebConfigurationHostServer CreateRemoteObjectOn64BitPlatform(string server, string username, string domain, string password) { MULTI_QI_X64[] amqi = new MULTI_QI_X64[1]; IntPtr guidbuf = IntPtr.Zero; COAUTHINFO_X64 ca = null; IntPtr captr = IntPtr.Zero; COSERVERINFO_X64 cs = null; Guid clsid = typeof(RemoteWebConfigurationHostServer).GUID; int hr = 0; COAUTHIDENTITY_X64 ci = null; IntPtr ciptr = IntPtr.Zero; try { guidbuf = Marshal.AllocCoTaskMem(16); Marshal.StructureToPtr(typeof(IRemoteWebConfigurationHostServer).GUID, guidbuf, false); amqi[0] = new MULTI_QI_X64(guidbuf); ci = new COAUTHIDENTITY_X64(username, domain, password); ciptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(ci)); Marshal.StructureToPtr(ci, ciptr, false); ca = new COAUTHINFO_X64(RpcAuthent.WinNT, RpcAuthor.None, null, /*RpcLevel.Connect*/ RpcLevel.Default, RpcImpers.Impersonate, ciptr); captr = Marshal.AllocCoTaskMem(Marshal.SizeOf(ca)); Marshal.StructureToPtr(ca, captr, false); cs = new COSERVERINFO_X64(server, captr); hr = UnsafeNativeMethods.CoCreateInstanceEx(ref clsid, IntPtr.Zero, (int)ClsCtx.RemoteServer, cs, 1, amqi); if ((uint)hr == 0x80040154) throw new Exception(SR.GetString(SR.Make_sure_remote_server_is_enabled_for_config_access)); if (hr < 0) Marshal.ThrowExceptionForHR(hr); if (amqi[0].hr < 0) Marshal.ThrowExceptionForHR(amqi[0].hr); hr = UnsafeNativeMethods.CoSetProxyBlanket(amqi[0].pItf, RpcAuthent.WinNT, RpcAuthor.None, null, /*RpcLevel.Connect*/ RpcLevel.Default, RpcImpers.Impersonate, ciptr, 0); if (hr < 0) Marshal.ThrowExceptionForHR(hr); return (IRemoteWebConfigurationHostServer)Marshal.GetObjectForIUnknown(amqi[0].pItf); } finally { if (amqi[0].pItf != IntPtr.Zero) { Marshal.Release(amqi[0].pItf); amqi[0].pItf = IntPtr.Zero; } amqi[0].piid = IntPtr.Zero; if (captr != IntPtr.Zero) { Marshal.DestroyStructure(captr, typeof(COAUTHINFO_X64)); Marshal.FreeCoTaskMem(captr); } if (ciptr != IntPtr.Zero) { Marshal.DestroyStructure(ciptr, typeof(COAUTHIDENTITY_X64)); Marshal.FreeCoTaskMem(ciptr); } if (guidbuf != IntPtr.Zero) Marshal.FreeCoTaskMem(guidbuf); } } } }