File: Configuration\StdRegProviderWrapper.cs
Project: ndp\cdf\src\WCF\Tools\WsatConfig\WsatConfig.csproj (WsatConfig)
//------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------
 
namespace Microsoft.Tools.ServiceModel.WsatConfig
{
    using System;
    using System.IO;
    using System.Management;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.AccessControl;
    using System.Security.Principal;
    using Microsoft.Win32;
 
    class StdRegProviderWrapper : IDisposable
    {
        ManagementClass regClassInstance;
        string subKey;
        uint hiveValue;
        
        bool ensuredSubKeyExists;
        bool ensuredReadAccess;
        bool ensuredWriteAccess;
 
        RegistryExceptionHelper registryExceptionHelper;
 
        static class StdRegProvMethods
        {
            public const string GetDwordValue = "GetDWORDValue";
            public const string GetStringValue = "GetStringValue";
            public const string GetMultiStringValue = "GetMultiStringValue";
            public const string SetDwordValue = "SetDWORDValue";
            public const string SetStringValue = "SetStringValue";
            public const string SetMultiStringValue = "SetMultiStringValue";
            public const string EnumKey = "EnumKey";
            public const string CreateRegistryKey = "CreateKey";
            public const string CheckAccess = "CheckAccess";
        }
 
        static class InputParameters
        {
            public const string DefKey = "hDefKey";
            public const string SubKeyName = "sSubKeyName";
            public const string ValueName = "sValueName";
            public const string AccessPermission = "uRequired";
 
            public const string DwordValueKey = "uValue";
            public const string StringValueKey = "sValue";
        }
 
        static class OutputParameters
        {
            public const string IsAccessGranted = "bGranted";
            public const string SubKeyNames = "sNames";
            public const string ReturnValue = "ReturnValue";
        }
 
        public StdRegProviderWrapper(RegistryHive registryHive, string subKey, string machineName)
        {
            switch (registryHive)
            {
                case RegistryHive.ClassesRoot:
                    this.hiveValue = 0x80000000;
                    break;
                case RegistryHive.CurrentUser:
                    this.hiveValue = 0x80000001;
                    break;
                case RegistryHive.LocalMachine:
                    this.hiveValue = 0x80000002;
                    break;
                default:
                    // We do not support other values here
                    throw new ArgumentException("remoteHive");
            }
 
            registryExceptionHelper = new RegistryExceptionHelper(machineName, registryHive, subKey);
 
            try
            {
                ConnectionOptions co = null;
 
                if (Utilities.IsLocalMachineName(machineName))
                {
                    machineName = "localhost";
                }
                else
                {
                    co = new ConnectionOptions();
                    co.Authentication = AuthenticationLevel.PacketPrivacy;
                    co.Impersonation = ImpersonationLevel.Impersonate;
                }
 
                ManagementScope managementScope = new ManagementScope("\\\\" + machineName + "\\root\\DEFAULT", co);
                ManagementPath managementPath = new ManagementPath("StdRegProv");
                ObjectGetOptions options = new ObjectGetOptions(new ManagementNamedValueCollection(), TimeSpan.FromSeconds(15), false);
                this.regClassInstance = new ManagementClass(managementScope, managementPath, options);
                this.subKey = subKey;
            }
            catch (ManagementException e)
            {
                throw registryExceptionHelper.CreateRegistryAccessException(e);
            }
            catch (COMException e) // for RPC_S_SERVER_UNAVAILABLE sort of errors
            {
                throw registryExceptionHelper.CreateRegistryAccessException(e);
            }
        }
 
        StdRegProviderWrapper(uint hiveValue, string subKey, ManagementClass regClassInstance)
        {
            this.hiveValue = hiveValue;
            this.subKey = subKey;
            this.registryExceptionHelper = new RegistryExceptionHelper(subKey);
            this.regClassInstance = new ManagementClass(regClassInstance.Path, regClassInstance.Options);
        }
 
        internal StdRegProviderWrapper OpenKey(string subKey)
        {
            string s = this.subKey;
            RegistryExceptionHelper.EnsureEndsWithSlash(ref s);
 
            s += subKey;
 
            return new StdRegProviderWrapper(this.hiveValue, s, regClassInstance);
        }
 
        internal uint ReadUInt32(string name, uint defaultValue)
        {
            return (uint)DoReadData(name, InputParameters.DwordValueKey, defaultValue, StdRegProvMethods.GetDwordValue);
        }
 
        internal string ReadString(string name, string defaultValue)
        {
            return (string)DoReadData(name, InputParameters.StringValueKey, defaultValue, StdRegProvMethods.GetStringValue);
        }
 
        internal string[] ReadMultiString(string name, string[] defaultValue)
        {
            return (string[])DoReadData(name, InputParameters.StringValueKey, defaultValue, StdRegProvMethods.GetMultiStringValue);
        }
 
        internal void WriteUInt32(string name, uint value)
        {
            DoWriteData(name, InputParameters.DwordValueKey, value, StdRegProvMethods.SetDwordValue);
        }
 
        internal void WriteString(string name, string value)
        {
            DoWriteData(name, InputParameters.StringValueKey, value, StdRegProvMethods.SetStringValue);
        }
 
        internal void WriteMultiString(string name, string[] value)
        {
            DoWriteData(name, InputParameters.StringValueKey, value, StdRegProvMethods.SetMultiStringValue);
        }
 
        object DoReadData(string name, string valueKey, object defaultValue, string readMethod)
        {
            EnsureReadAccess();
 
            try
            {
                ManagementBaseObject inParams = regClassInstance.GetMethodParameters(readMethod);
 
                inParams[InputParameters.DefKey] = this.hiveValue;
                inParams[InputParameters.SubKeyName] = subKey;
                inParams[InputParameters.ValueName] = name;
 
                ManagementBaseObject outParams = regClassInstance.InvokeMethod(readMethod,
                                                                                inParams, null);
                uint ret = (uint)outParams[OutputParameters.ReturnValue];
                if (ret == 0) // zero means success
                {
                    return outParams[valueKey];
                }
                return defaultValue;
            }
#pragma warning suppress 56500
            catch (Exception e)
            {
                // MSDN does not have a spec of possible exceptions for the APIs used above.
                // To be safe, we should be a bit more generic in catching exceptions
                if (Utilities.IsCriticalException(e))
                {
                    throw;
                }
                throw registryExceptionHelper.CreateRegistryAccessException(name, e);
            }
        }
 
        void DoWriteData(string name, string valueKey, object value, string writeMethod)
        {
            EnsureSubKeyExists();
            EnsureWriteAccess();
 
            try
            {
                ManagementBaseObject inParams = regClassInstance.GetMethodParameters(writeMethod);
 
                inParams[InputParameters.DefKey] = this.hiveValue;
                inParams[InputParameters.SubKeyName] = subKey;
                inParams[InputParameters.ValueName] = name;
                inParams[valueKey] = value;
 
                ManagementBaseObject outParams = regClassInstance.InvokeMethod(writeMethod,
                                                                                inParams, null);
                uint ret = (uint)outParams[OutputParameters.ReturnValue];
                if (ret != 0) // zero means success
                {
                    string registryKey = this.subKey;
                    RegistryExceptionHelper.EnsureEndsWithSlash(ref registryKey);
                    registryKey += name;
 
                    registryExceptionHelper.CreateRegistryWriteException(registryKey, null);
                }
            }
#pragma warning suppress 56500
            catch (Exception e)
            {
                // MSDN does not have a spec of possible exceptions for the APIs used above.
                // To be safe, we should be a bit more generic in catching exceptions
                if (Utilities.IsCriticalException(e))
                {
                    throw;
                }
                throw registryExceptionHelper.CreateRegistryAccessException(name, e);
            }
        }
 
        void EnsureSubKeyExists()
        {
            try
            {
                if (!ensuredSubKeyExists)
                {
                    ManagementBaseObject inParams = regClassInstance.GetMethodParameters(StdRegProvMethods.CreateRegistryKey);
                    inParams[InputParameters.DefKey] = this.hiveValue;
                    inParams[InputParameters.SubKeyName] = this.subKey;
 
                    ManagementBaseObject outParams = regClassInstance.InvokeMethod(StdRegProvMethods.CreateRegistryKey, inParams, null);
                    uint ret = (uint)outParams[OutputParameters.ReturnValue];
                    if (ret != 0) // zero means success
                    {
                        throw registryExceptionHelper.CreateRegistryAccessException(ret);
                    }
                    ensuredSubKeyExists = true;
                }
            }
#pragma warning suppress 56500
            catch (Exception e)
            {
                // MSDN does not have a spec of possible exceptions for the APIs used above.
                // To be safe, we should be a bit more generic in catching exceptions
                if (Utilities.IsCriticalException(e))
                {
                    throw;
                }
                throw registryExceptionHelper.CreateRegistryAccessException(e);
            }
        }
 
        const uint ERROR_ACCESS_DENIED = 5;
 
        bool CheckRegistryAccess(UInt32 accessPermission, out bool isAccessGranted)
        {
            ManagementBaseObject inParams = null;
 
            try
            {
                inParams = regClassInstance.GetMethodParameters(StdRegProvMethods.CheckAccess);
            }
#pragma warning suppress 56500
            catch (Exception e)
            {
                // MSDN does not have a spec of possible exceptions for the APIs used above.
                // To be safe, we should be a bit more generic in catching exceptions
                if (Utilities.IsCriticalException(e))
                {
                    throw;
                }
                throw registryExceptionHelper.CreateRegistryAccessException(e);
            }
 
            inParams[InputParameters.DefKey] = this.hiveValue;
            inParams[InputParameters.SubKeyName] = this.subKey;
            inParams[InputParameters.AccessPermission] = accessPermission;
 
            ManagementBaseObject outParams = regClassInstance.InvokeMethod(StdRegProvMethods.CheckAccess, inParams, null);
            uint ret = (uint)outParams[OutputParameters.ReturnValue];
            isAccessGranted = (bool)outParams[OutputParameters.IsAccessGranted];
            return ret == 0 || ret == ERROR_ACCESS_DENIED;
        }
 
        const UInt32 KEY_QUERY_VALUE = 0x01;
        const UInt32 KEY_SET_VALUE = 0x02;
 
        void EnsureReadAccess()
        {
            if (!ensuredReadAccess)
            {
                bool accessGranted = false;
                if (CheckRegistryAccess(KEY_QUERY_VALUE, out accessGranted))
                {
                    if (!accessGranted)
                    {
                        throw registryExceptionHelper.CreateRegistryAccessException(null);
                    }
                    ensuredReadAccess = true;
                }
            }
        }
 
        void EnsureWriteAccess()
        {
            if (!ensuredWriteAccess)
            {
                bool accessGranted = false;
                if (CheckRegistryAccess(KEY_QUERY_VALUE | KEY_SET_VALUE, out accessGranted))
                {
                    if (!accessGranted)
                    {
                        throw registryExceptionHelper.CreateRegistryWriteException(null);
                    }
                    ensuredWriteAccess = true;
                }
            }
        }
 
        internal void AdjustRegKeyPermission()
        {
            try
            {
                RegistryKey regKey;
                regKey = Registry.LocalMachine.OpenSubKey(
                    WsatKeys.WsatRegKey,
                    RegistryKeyPermissionCheck.ReadWriteSubTree,
                    RegistryRights.FullControl);
 
                if (regKey != null)
                {
                    using (regKey)
                    {
                        // NetworkService always needs access to the WS-AT key
                        // On some platforms, it doesn't inherit this permission from the parent MSDTC key
                        RegistryAccessRule rule = new RegistryAccessRule(
                            new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null),
                            RegistryRights.ReadKey,
                            InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                            PropagationFlags.None,
                            AccessControlType.Allow);
 
                        // Ensure the authenticated users have read access to the WS-AT key
                        // there is a key under the WS-AT key named OleTxUpgradeEnabled that requires the permission
                        RegistryAccessRule rule2 = new RegistryAccessRule(
                            new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null),
                            RegistryRights.ReadKey,
                            InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                            PropagationFlags.None,
                            AccessControlType.Allow);
 
                        RegistrySecurity registrySecurity = regKey.GetAccessControl();
                        registrySecurity.AddAccessRule(rule);
                        registrySecurity.AddAccessRule(rule2);
                        regKey.SetAccessControl(registrySecurity);
                    }
                }
            }
            catch (SecurityException e)
            {
                throw registryExceptionHelper.CreateRegistryWriteException(e);
            }
            catch (ObjectDisposedException e)
            {
                throw registryExceptionHelper.CreateRegistryWriteException(e);
            }
            catch (ArgumentNullException e)
            {
                throw registryExceptionHelper.CreateRegistryWriteException(e);
            }
            catch (ArgumentException e)
            {
                throw registryExceptionHelper.CreateRegistryWriteException(e);
            }
            catch (UnauthorizedAccessException e)
            {
                throw registryExceptionHelper.CreateRegistryWriteException(e);
            }        
        }
 
        public void Dispose()
        {
            if (regClassInstance != null)
            {
                regClassInstance.Dispose();
                regClassInstance = null;
            }
        }
    }
}