/*
 * SecurityOps.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

#if !NATIVE
#error "This file cannot be compiled or used properly with native code disabled."
#endif

using System;
using System.Runtime.InteropServices;
using System.Security;

#if !NET_40
using System.Security.Permissions;
#endif

using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Constants;

namespace Eagle._Components.Private
{
#if NET_40
    [SecurityCritical()]
#else
    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
#endif
    [ObjectId("447e6857-c104-440a-9988-e04b1ec09066")]
    internal static class SecurityOps
    {
        #region Private Constants
#if WINDOWS
        private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////
        // Required Native APIs used via P/Invoke
        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Unsafe Native Methods Class
        [SuppressUnmanagedCodeSecurity()]
        [ObjectId("6d48f680-28c0-45e9-9469-0c8769dffd93")]
        private static class UnsafeNativeMethods
        {
            //
            // NOTE: Currently, this constant is only used with the WinTrust API; however,
            //       it is needed even when the WINDOWS compile-time option is disabled.
            //
            internal const uint ERROR_SUCCESS = 0;

            ///////////////////////////////////////////////////////////////////////////////////////////

#if WINDOWS
#if !MONO
            [DllImport(DllName.MsCorEe, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
            [return: MarshalAs(UnmanagedType.U1)]
            internal static extern bool StrongNameSignatureVerificationEx(
                [MarshalAs(UnmanagedType.LPWStr)] string filePath,
                [MarshalAs(UnmanagedType.U1)] bool forceVerification,
                [MarshalAs(UnmanagedType.U1)] ref bool wasVerified
            );
#endif

            ///////////////////////////////////////////////////////////////////////////////////////////

            #region WinTrust API
            #region Constants
            internal static readonly Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 =
                new Guid("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}");

            internal const uint WTD_UI_ALL = 1;
            internal const uint WTD_UI_NONE = 2;

            internal const uint WTD_REVOKE_NONE = 0x0;
            internal const uint WTD_REVOKE_WHOLECHAIN = 0x1;

            internal const uint WTD_CHOICE_FILE = 1;

            internal const uint WTD_UICONTEXT_EXECUTE = 0;
            internal const uint WTD_UICONTEXT_INSTALL = 1;

            internal const uint WTD_STATEACTION_IGNORE = 0x0;

            internal const uint WTD_SAFER_FLAG = 0x100;
            #endregion

            ///////////////////////////////////////////////////////////////////////////////////////////////

            #region Structures
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            [ObjectId("5c7cdbf0-c943-49b7-87fe-471813fa88d5")]
            internal struct WINTRUST_FILE_INFO
            {
                public /* DWORD */ uint cbStruct;
                public /* LPCWSTR */ string pcwszFilePath;
                public /* HANDLE */ IntPtr hFile;
                public /* LPGUID */ IntPtr pgKnownSubject;
            }

            ///////////////////////////////////////////////////////////////////////////////////////////////

            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            [ObjectId("7cb4009e-d4c5-403c-a246-16751dfacb6b")]
            internal struct WINTRUST_DATA
            {
                public /* DWORD */ uint cbStruct;
                public /* LPVOID */ IntPtr pPolicyCallbackData;
                public /* LPVOID */ IntPtr pSIPClientData;
                public /* DWORD */ uint dwUIChoice;
                public /* DWORD */ uint fdwRevocationChecks;
                public /* DWORD */ uint dwUnionChoice;
                public /* PWINTRUST_FILE_INFO */ IntPtr pFile;
                public /* DWORD */ uint dwStateAction;
                public /* HANDLE */ IntPtr hWVTStateData;
                public /* LPWSTR */ string pwszURLReference;
                public /* DWORD */ uint dwProvFlags;
                public /* DWORD */ uint dwUIContext;
            }
            #endregion

            ///////////////////////////////////////////////////////////////////////////////////////////////

            #region Functions
            [DllImport(DllName.WinTrust, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern int WinVerifyTrust(
                IntPtr hWnd,
                [MarshalAs(UnmanagedType.LPStruct)] Guid actionId,
                ref WINTRUST_DATA pData
            );
            #endregion
            #endregion

            ///////////////////////////////////////////////////////////////////////////////////////////

            //
            // NOTE: For use with Windows NT and Windows 2000 RTM+
            //       (does not work properly with Vista).
            //
            [DllImport(DllName.AdvPack, CallingConvention = CallingConvention.Winapi)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool IsNTAdmin(uint reserved1, ref uint reserved2);

            //
            // NOTE: Imported by ordinal to support Windows 2000 SP4+, 
            //       Windows XP Home/Pro RTM+, and Vista (and hopefully 
            //       Windows Server 2008).
            //
            [DllImport(DllName.Shell32, EntryPoint = "#680", CallingConvention = CallingConvention.Winapi)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool IsUserAnAdmin();
#endif

            ///////////////////////////////////////////////////////////////////////////////////////////

#if UNIX
            [DllImport(DllName.LibC, CallingConvention = CallingConvention.Cdecl)]
            internal static extern int getuid(); /* NOTE: *POSIX* Cannot fail. */
#endif
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Public "Trust" Methods
        public static bool IsFileTrusted(
            string fileName,
            IntPtr fileHandle,
            bool userInterface,
            bool userPrompt,
            bool revocation,
            bool install
            )
        {
            int returnValue = (int)UnsafeNativeMethods.ERROR_SUCCESS + 1; /* !SUCCESS */
            Result error = null;

            if ((IsFileTrusted(
                    fileName, fileHandle, userInterface, userPrompt, revocation,
                    install, ref returnValue, ref error) == ReturnCode.Ok) &&
                (returnValue == UnsafeNativeMethods.ERROR_SUCCESS))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode IsFileTrusted(
            string fileName,
            IntPtr fileHandle,
            bool userInterface,
            bool userPrompt,
            bool revocation,
            bool install,
            ref int returnValue,
            ref Result error
            )
        {
            if (!String.IsNullOrEmpty(fileName))
            {
#if WINDOWS
                if (PlatformOps.IsWindowsOperatingSystem())
                {
                    try
                    {
                        UnsafeNativeMethods.WINTRUST_FILE_INFO file =
                            new UnsafeNativeMethods.WINTRUST_FILE_INFO();

                        file.cbStruct = (uint)Marshal.SizeOf(
                            typeof(UnsafeNativeMethods.WINTRUST_FILE_INFO));

                        file.pcwszFilePath = fileName;
                        file.hFile = fileHandle;
                        file.pgKnownSubject = IntPtr.Zero;

                        IntPtr pFile = IntPtr.Zero;

                        try
                        {
                            pFile = Marshal.AllocCoTaskMem((int)file.cbStruct);

                            if (pFile != IntPtr.Zero)
                            {
                                Marshal.StructureToPtr(file, pFile, false);

                                UnsafeNativeMethods.WINTRUST_DATA winTrustData =
                                    new UnsafeNativeMethods.WINTRUST_DATA();

                                winTrustData.cbStruct = (uint)Marshal.SizeOf(
                                    typeof(UnsafeNativeMethods.WINTRUST_DATA));

                                winTrustData.pPolicyCallbackData = IntPtr.Zero;
                                winTrustData.pSIPClientData = IntPtr.Zero;

                                winTrustData.dwUIChoice = userInterface && userPrompt ?
                                    UnsafeNativeMethods.WTD_UI_ALL :
                                    UnsafeNativeMethods.WTD_UI_NONE;

                                winTrustData.fdwRevocationChecks = revocation ?
                                    UnsafeNativeMethods.WTD_REVOKE_WHOLECHAIN :
                                    UnsafeNativeMethods.WTD_REVOKE_NONE;

                                winTrustData.dwUnionChoice =
                                    UnsafeNativeMethods.WTD_CHOICE_FILE;

                                winTrustData.pFile = pFile;

                                winTrustData.dwStateAction =
                                    UnsafeNativeMethods.WTD_STATEACTION_IGNORE;

                                winTrustData.hWVTStateData = IntPtr.Zero;
                                winTrustData.pwszURLReference = null;

                                winTrustData.dwProvFlags =
                                    UnsafeNativeMethods.WTD_SAFER_FLAG;

                                winTrustData.dwUIContext = install ?
                                    UnsafeNativeMethods.WTD_UICONTEXT_INSTALL :
                                    UnsafeNativeMethods.WTD_UICONTEXT_EXECUTE;

                                returnValue = UnsafeNativeMethods.WinVerifyTrust(userInterface ?
                                    WindowOps.GetInteractiveHandle() : INVALID_HANDLE_VALUE,
                                    UnsafeNativeMethods.WINTRUST_ACTION_GENERIC_VERIFY_V2,
                                    ref winTrustData);

                                return ReturnCode.Ok;
                            }
                            else
                            {
                                error = "out of memory";
                            }
                        }
                        finally
                        {
                            if (pFile != IntPtr.Zero)
                            {
                                Marshal.FreeCoTaskMem(pFile);
                                pFile = IntPtr.Zero;
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = "not supported on this operating system";
                }
#else
                error = "not implemented";
#endif
            }
            else
            {
                error = "invalid file name";
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: IsFileTrusted: file {1} trust failure, userInterface = {2}, " +
                "revocation = {3}, install = {4}, returnValue = {5}, error = {6}{7}",
                GlobalState.GetCurrentThreadId(), FormatOps.WrapOrNull(fileName),
                userInterface, revocation, install, returnValue,
                FormatOps.WrapOrNull(error), Environment.NewLine),
                typeof(SecurityOps).Name);

            return ReturnCode.Error;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Public StrongName Methods
        public static bool IsStrongNameVerified(
            string fileName,
            bool force
            )
        {
            bool returnValue = false;
            bool verified = false;
            Result error = null;

            if ((IsStrongNameVerified(fileName, force, ref returnValue,
                    ref verified, ref error) == ReturnCode.Ok) &&
                returnValue && verified)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private StrongName Methods
        private static ReturnCode IsStrongNameVerified(
            string fileName,
            bool force,
            ref bool returnValue,
            ref bool verified,
            ref Result error
            )
        {
            if (!String.IsNullOrEmpty(fileName))
            {
#if WINDOWS && !MONO
                if (PlatformOps.IsWindowsOperatingSystem())
                {
                    try
                    {
                        returnValue = UnsafeNativeMethods.StrongNameSignatureVerificationEx(
                            fileName, force, ref verified);

                        return ReturnCode.Ok;
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = "not supported on this operating system";
                }
#else
                error = "not implemented";
#endif
            }
            else
            {
                error = "invalid file name";
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: IsStrongNameVerified: file {1} verification failure, " +
                "force = {2}, returnValue = {3}, verified = {4}, error = {5}{6}",
                GlobalState.GetCurrentThreadId(), FormatOps.WrapOrNull(fileName),
                force, returnValue, verified, FormatOps.WrapOrNull(error),
                Environment.NewLine),
                typeof(SecurityOps).Name);

            return ReturnCode.Error;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Public Platform Abstraction Methods
        public static bool IsAdministrator()
        {
            bool administrator = false;

            return (IsAdministrator(ref administrator) == ReturnCode.Ok) && administrator;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode IsAdministrator(
            ref bool administrator,
            ref Result error
            )
        {
#if WINDOWS
            if (PlatformOps.IsWindowsOperatingSystem())
                return WindowsIsAdministrator(ref administrator, ref error);
#endif

#if UNIX
            if (PlatformOps.IsUnixOperatingSystem())
                return UnixIsAdministrator(ref administrator, ref error);
#endif

            error = "unknown operating system";
            return ReturnCode.Error;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Platform Abstraction Methods
        private static ReturnCode IsAdministrator(
            ref bool administrator
            )
        {
            Result error = null;

            return IsAdministrator(ref administrator, ref error);
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Windows-Specific Methods
#if WINDOWS
        private static ReturnCode WindowsIsAdministrator(
            ref bool administrator,
            ref Result error
            )
        {
            try
            {
                //
                // NOTE: Are we running on Windows 2000 SP4 or higher?
                //
                if (PlatformOps.CheckVersion(PlatformID.Win32NT, 5, 0, 4, 0))
                {
                    //
                    // HACK: Use a "documented" function for Windows
                    //       2000 SP4+, Windows XP, and Vista (this
                    //       function used to be undocumented).
                    //
                    administrator = UnsafeNativeMethods.IsUserAnAdmin();
                }
                else
                {
                    //
                    // HACK: Use a different undocumented function for 
                    //       Windows NT and Windows 2000 RTM to SP3.
                    //
                    uint reserved2 = 0;

                    administrator = UnsafeNativeMethods.IsNTAdmin(0, ref reserved2);
                }

                return ReturnCode.Ok;
            }
            catch (Exception e)
            {
                error = e;
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: WindowsIsAdministrator: administrator = {1}, error = {2}{3}",
                GlobalState.GetCurrentThreadId(), administrator,
                FormatOps.WrapOrNull(error), Environment.NewLine),
                typeof(SecurityOps).Name);

            return ReturnCode.Error;
        }
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Unix-Specific Methods
#if UNIX
        private static ReturnCode UnixIsAdministrator(
            ref bool administrator,
            ref Result error
            )
        {
            try
            {
                administrator = (UnsafeNativeMethods.getuid() == 0);

                return ReturnCode.Ok;
            }
            catch (Exception e)
            {
                error = e;
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: UnixIsAdministrator: administrator = {1}, error = {2}{3}",
                GlobalState.GetCurrentThreadId(), administrator,
                FormatOps.WrapOrNull(error), Environment.NewLine),
                typeof(SecurityOps).Name);

            return ReturnCode.Error;
        }
#endif
        #endregion
    }
}
