// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Classes:  NativeObjectSecurity class
**
**
===========================================================*/

using Microsoft.Win32;
using System;
using System.Collections;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace System.Security.AccessControl
{
    using FileNotFoundException = System.IO.FileNotFoundException;
    using System.Globalization;
    using System.Diagnostics.Contracts;


    public abstract class NativeObjectSecurity : CommonObjectSecurity
    {
        #region Private Members
        
        private readonly ResourceType _resourceType;
        private ExceptionFromErrorCode _exceptionFromErrorCode = null;
        private object _exceptionContext = null;
        private readonly uint ProtectedDiscretionaryAcl       = 0x80000000;
        private readonly uint ProtectedSystemAcl                 = 0x40000000;
        private readonly uint UnprotectedDiscretionaryAcl   = 0x20000000;
        private readonly uint UnprotectedSystemAcl             = 0x10000000;



        #endregion

        #region Delegates

        [System.Security.SecuritySafeCritical]  // auto-generated
        internal protected delegate System.Exception ExceptionFromErrorCode( int errorCode, string name, SafeHandle handle, object context );

        #endregion

        #region Constructors

        protected NativeObjectSecurity( bool isContainer, ResourceType resourceType )
            : base( isContainer )
        {
            _resourceType = resourceType;
        }

        protected NativeObjectSecurity(bool isContainer, ResourceType resourceType, ExceptionFromErrorCode exceptionFromErrorCode, object exceptionContext)
            : this(isContainer, resourceType)
        {
            _exceptionContext = exceptionContext;
            _exceptionFromErrorCode = exceptionFromErrorCode;
        }

        [System.Security.SecurityCritical]  // auto-generated
        internal NativeObjectSecurity( ResourceType resourceType, CommonSecurityDescriptor securityDescriptor )
            : this( resourceType, securityDescriptor, null )
        {
        }

        [System.Security.SecurityCritical]  // auto-generated
        internal NativeObjectSecurity( ResourceType resourceType, CommonSecurityDescriptor securityDescriptor, ExceptionFromErrorCode exceptionFromErrorCode )
            : base( securityDescriptor )
        {
            _resourceType = resourceType;
            _exceptionFromErrorCode = exceptionFromErrorCode;
        }
        
        [System.Security.SecuritySafeCritical]  // auto-generated
        protected NativeObjectSecurity( bool isContainer, ResourceType resourceType, string name, AccessControlSections includeSections, ExceptionFromErrorCode exceptionFromErrorCode, object exceptionContext )
            : this( resourceType, CreateInternal( resourceType, isContainer, name, null, includeSections, true, exceptionFromErrorCode, exceptionContext ), exceptionFromErrorCode)
        {
        }

        protected NativeObjectSecurity( bool isContainer, ResourceType resourceType, string name, AccessControlSections includeSections )
            : this( isContainer, resourceType, name, includeSections, null, null )
        {
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        protected NativeObjectSecurity( bool isContainer, ResourceType resourceType, SafeHandle handle, AccessControlSections includeSections, ExceptionFromErrorCode exceptionFromErrorCode, object exceptionContext )
            : this( resourceType, CreateInternal( resourceType, isContainer, null, handle, includeSections, false, exceptionFromErrorCode, exceptionContext ), exceptionFromErrorCode)
        {
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        protected NativeObjectSecurity( bool isContainer, ResourceType resourceType, SafeHandle handle, AccessControlSections includeSections )
            : this( isContainer, resourceType, handle, includeSections, null, null )
        {
        }

        #endregion

        #region Private Methods

        [System.Security.SecurityCritical]  // auto-generated
        private static CommonSecurityDescriptor CreateInternal( ResourceType resourceType, bool isContainer, string name, SafeHandle handle, AccessControlSections includeSections, bool createByName, ExceptionFromErrorCode exceptionFromErrorCode, object exceptionContext )
        {
            int error;
            RawSecurityDescriptor rawSD;

            if ( createByName && name == null )
            {
                throw new ArgumentNullException( "name" );
            }
            else if ( !createByName && handle == null )
            {
                throw new ArgumentNullException( "handle" );
            }

            error = Win32.GetSecurityInfo( resourceType, name, handle, includeSections, out rawSD );

            if ( error != Win32Native.ERROR_SUCCESS )
            {
                System.Exception exception = null;

                if ( exceptionFromErrorCode != null )
                {
                    exception = exceptionFromErrorCode( error, name, handle, exceptionContext );
                }

                if ( exception == null )
                {
                    if ( error == Win32Native.ERROR_ACCESS_DENIED )
                    {
                        exception = new UnauthorizedAccessException();
                    }
                    else if ( error == Win32Native.ERROR_INVALID_OWNER )
                    {
                        exception = new InvalidOperationException( Environment.GetResourceString( "AccessControl_InvalidOwner" ) );
                    }
                    else if ( error == Win32Native.ERROR_INVALID_PRIMARY_GROUP )
                    {
                        exception = new InvalidOperationException( Environment.GetResourceString( "AccessControl_InvalidGroup" ));
                    }
                    else if ( error == Win32Native.ERROR_INVALID_PARAMETER )
                    {
                        exception = new InvalidOperationException( Environment.GetResourceString( "AccessControl_UnexpectedError", error ));
                    }
                    else if ( error == Win32Native.ERROR_INVALID_NAME )
                    {
                        exception = new ArgumentException(
                            Environment.GetResourceString( "Argument_InvalidName" ),
                            "name" );
                    }
                    else if ( error == Win32Native.ERROR_FILE_NOT_FOUND )
                    {
                        exception = ( name == null ? new FileNotFoundException() : new FileNotFoundException( name ));
                    }
                    else if ( error == Win32Native.ERROR_NO_SECURITY_ON_OBJECT )
                    {
                        exception = new NotSupportedException( Environment.GetResourceString( "AccessControl_NoAssociatedSecurity" ));
                    }
                    else
                    {
                        Contract.Assert( false, string.Format( CultureInfo.InvariantCulture, "Win32GetSecurityInfo() failed with unexpected error code {0}", error ));
                        exception = new InvalidOperationException( Environment.GetResourceString( "AccessControl_UnexpectedError", error ));
                    }
                }

                throw exception;
            }

            return new CommonSecurityDescriptor( isContainer, false /* isDS */, rawSD, true );
        }

        //
        // Attempts to persist the security descriptor onto the object
        //

        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        private void Persist( string name, SafeHandle handle, AccessControlSections includeSections, object exceptionContext )
        {
            WriteLock();

            try
            {
                int error;
                SecurityInfos securityInfo = 0;

                SecurityIdentifier owner = null, group = null;
                SystemAcl sacl = null;
                DiscretionaryAcl dacl = null;

                if (( includeSections & AccessControlSections.Owner ) != 0 && _securityDescriptor.Owner != null )
                {
                    securityInfo |= SecurityInfos.Owner;
                    owner = _securityDescriptor.Owner;
                }

                if (( includeSections & AccessControlSections.Group ) != 0 && _securityDescriptor.Group != null )
                {
                    securityInfo |= SecurityInfos.Group;
                    group = _securityDescriptor.Group;
                }

                if (( includeSections & AccessControlSections.Audit ) != 0 )
                {
                    securityInfo |= SecurityInfos.SystemAcl;
                    if ( _securityDescriptor.IsSystemAclPresent && 
                         _securityDescriptor.SystemAcl != null && 
                         _securityDescriptor.SystemAcl.Count > 0 )
                    {
                        sacl = _securityDescriptor.SystemAcl;
                    }
                    else
                    {
                        sacl = null;
                    }

                    if (( _securityDescriptor.ControlFlags & ControlFlags.SystemAclProtected ) != 0 )
                    {
                       securityInfo =  (SecurityInfos)((uint)securityInfo | ProtectedSystemAcl);
                    }
                    else
                    {
                        securityInfo =  (SecurityInfos)((uint)securityInfo | UnprotectedSystemAcl);
                    }
                }

                if (( includeSections & AccessControlSections.Access ) != 0 && _securityDescriptor.IsDiscretionaryAclPresent )
                {
                    securityInfo |= SecurityInfos.DiscretionaryAcl;

                    // if the DACL is in fact a crafted replaced for NULL replacement, then we will persist it as NULL
                    if (_securityDescriptor.DiscretionaryAcl.EveryOneFullAccessForNullDacl)
                    {
                        dacl = null;
                    }
                    else
                    {
                        dacl = _securityDescriptor.DiscretionaryAcl;
                    }

                    if (( _securityDescriptor.ControlFlags & ControlFlags.DiscretionaryAclProtected ) != 0 )
                    {
                        securityInfo =  (SecurityInfos)((uint)securityInfo | ProtectedDiscretionaryAcl);
                    }
                    else
                    {
                        securityInfo =  (SecurityInfos)((uint)securityInfo | UnprotectedDiscretionaryAcl);
                    }
                }

                if ( securityInfo == 0 )
                {
                    //
                    // Nothing to persist
                    //

                    return;
                }

                error = Win32.SetSecurityInfo( _resourceType, name, handle, securityInfo, owner, group, sacl, dacl );

                if ( error != Win32Native.ERROR_SUCCESS )
                {
                    System.Exception exception = null;

                    if ( _exceptionFromErrorCode != null )
                    {
                        exception = _exceptionFromErrorCode( error, name, handle, exceptionContext );
                    }

                    if ( exception == null )
                    {
                        if ( error == Win32Native.ERROR_ACCESS_DENIED )
                        {
                            exception = new UnauthorizedAccessException();
                        }
                        else if ( error == Win32Native.ERROR_INVALID_OWNER )
                        {
                            exception = new InvalidOperationException( Environment.GetResourceString( "AccessControl_InvalidOwner" ) );
                        }
                        else if ( error == Win32Native.ERROR_INVALID_PRIMARY_GROUP )
                        {
                            exception = new InvalidOperationException( Environment.GetResourceString( "AccessControl_InvalidGroup" ) );
                        }
                        else if ( error == Win32Native.ERROR_INVALID_NAME )
                        {
                            exception = new ArgumentException(
                                Environment.GetResourceString( "Argument_InvalidName" ),
                                "name" );
                        }
                        else if ( error == Win32Native.ERROR_INVALID_HANDLE )
                        {
                            exception = new NotSupportedException( Environment.GetResourceString( "AccessControl_InvalidHandle" ));
                        }
                        else if ( error == Win32Native.ERROR_FILE_NOT_FOUND )
                        {
                            exception = new FileNotFoundException();
                        }
                        else if (error == Win32Native.ERROR_NO_SECURITY_ON_OBJECT)
                        {
                            exception = new NotSupportedException(Environment.GetResourceString("AccessControl_NoAssociatedSecurity"));
                        }
                        else
                        {
                            Contract.Assert( false, string.Format( CultureInfo.InvariantCulture, "Unexpected error code {0}", error ));
                            exception = new InvalidOperationException( Environment.GetResourceString( "AccessControl_UnexpectedError", error ));
                        }
                    }

                    throw exception;
                }
                
                //
                // Everything goes well, let us clean the modified flags.
                // We are in proper write lock, so just go ahead
                //
                
                this.OwnerModified = false;
                this.GroupModified = false;
                this.AccessRulesModified = false;
                this.AuditRulesModified = false;            
            }
            finally
            {
                WriteUnlock();
            }
        }

        #endregion

        #region Protected Methods

        //
        // Persists the changes made to the object
        // by calling the underlying Windows API
        //
        // This overloaded method takes a name of an existing object
        //

        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        protected sealed override void Persist( string name, AccessControlSections includeSections )
        {
            Persist(name, includeSections, _exceptionContext);
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        protected void Persist( string name, AccessControlSections includeSections, object exceptionContext )
        {
            if (name == null)
            {
                throw new ArgumentNullException( "name" );
            }
            Contract.EndContractBlock();

            Persist( name, null, includeSections, exceptionContext );
        }

        //
        // Persists the changes made to the object
        // by calling the underlying Windows API
        //
        // This overloaded method takes a handle to an existing object
        //
        [System.Security.SecuritySafeCritical]  // auto-generated
        protected sealed override void Persist( SafeHandle handle, AccessControlSections includeSections )
        {
            Persist(handle, includeSections, _exceptionContext);
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        protected void Persist( SafeHandle handle, AccessControlSections includeSections, object exceptionContext )
        {
            if (handle == null)
            {
                throw new ArgumentNullException( "handle" );
            }
            Contract.EndContractBlock();

            Persist( null, handle, includeSections, exceptionContext );
        }

        #endregion
    }
}
