require_relative '../../../puppet/util/windows'

require 'ffi'

module Puppet::Util::Windows::User
  extend Puppet::Util::Windows::String
  extend FFI::Library

  def admin?
    return false unless check_token_membership

    # if Vista or later, check for unrestricted process token
    elevated_supported = Puppet::Util::Windows::Process.supports_elevated_security?
    return elevated_supported ? Puppet::Util::Windows::Process.elevated_security? : true
  end
  module_function :admin?

  # The name of the account in all locales is `LocalSystem`. `.\LocalSystem` or `ComputerName\LocalSystem' can also be used.
  # This account is not recognized by the security subsystem, so you cannot specify its name in a call to the `LookupAccountName` function.
  # https://docs.microsoft.com/en-us/windows/win32/services/localsystem-account
  def localsystem?(name)
    ["LocalSystem", ".\\LocalSystem", "#{Puppet::Util::Windows::ADSI.computer_name}\\LocalSystem"].any?{ |s| s.casecmp(name) == 0 }
  end
  module_function :localsystem?

  # Check if a given user is one of the default system accounts
  # These accounts do not have a password and all checks done through logon attempt will fail
  # https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/local-accounts#default-local-system-accounts
  def default_system_account?(name)
    user_sid = Puppet::Util::Windows::SID.name_to_sid(name)
    [Puppet::Util::Windows::SID::LocalSystem, Puppet::Util::Windows::SID::NtLocal, Puppet::Util::Windows::SID::NtNetwork].include?(user_sid)
  end
  module_function :default_system_account?

  # https://msdn.microsoft.com/en-us/library/windows/desktop/ee207397(v=vs.85).aspx
  SECURITY_MAX_SID_SIZE = 68

  # https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
  # These error codes indicate successful authentication but failure to
  # logon for a separate reason
  ERROR_ACCOUNT_RESTRICTION = 1327
  ERROR_INVALID_LOGON_HOURS = 1328
  ERROR_INVALID_WORKSTATION = 1329
  ERROR_ACCOUNT_DISABLED    = 1331

  def check_token_membership
    is_admin = false
    FFI::MemoryPointer.new(:byte, SECURITY_MAX_SID_SIZE) do |sid_pointer|
      FFI::MemoryPointer.new(:dword, 1) do |size_pointer|
        size_pointer.write_uint32(SECURITY_MAX_SID_SIZE)

        if CreateWellKnownSid(:WinBuiltinAdministratorsSid, FFI::Pointer::NULL, sid_pointer, size_pointer) == FFI::WIN32_FALSE
          raise Puppet::Util::Windows::Error.new(_("Failed to create administrators SID"))
        end
      end

      if IsValidSid(sid_pointer) == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error.new(_("Invalid SID"))
      end

      FFI::MemoryPointer.new(:win32_bool, 1) do |ismember_pointer|
        if CheckTokenMembership(FFI::Pointer::NULL_HANDLE, sid_pointer, ismember_pointer) == FFI::WIN32_FALSE
          raise Puppet::Util::Windows::Error.new(_("Failed to check membership"))
        end

        # Is administrators SID enabled in calling thread's access token?
        is_admin = ismember_pointer.read_win32_bool
      end
    end

    is_admin
  end
  module_function :check_token_membership

  def password_is?(name, password, domain = '.')
    begin
      logon_user(name, password, domain) { |token| }
    rescue Puppet::Util::Windows::Error => detail

      authenticated_error_codes = Set[
        ERROR_ACCOUNT_RESTRICTION,
        ERROR_INVALID_LOGON_HOURS,
        ERROR_INVALID_WORKSTATION,
        ERROR_ACCOUNT_DISABLED,
      ]

      return authenticated_error_codes.include?(detail.code)
    end
  end
  module_function :password_is?

  def logon_user(name, password, domain = '.', &block)
    fLOGON32_PROVIDER_DEFAULT = 0
    fLOGON32_LOGON_INTERACTIVE = 2
    fLOGON32_LOGON_NETWORK = 3

    token = nil
    begin
      FFI::MemoryPointer.new(:handle, 1) do |token_pointer|
        #try logon using network else try logon using interactive mode
        if logon_user_by_logon_type(name, domain, password, fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE
          if logon_user_by_logon_type(name, domain, password, fLOGON32_LOGON_INTERACTIVE, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE
            raise Puppet::Util::Windows::Error.new(_("Failed to logon user %{name}") % {name: name.inspect})
          end
        end

        yield token = token_pointer.read_handle
      end
    ensure
      FFI::WIN32.CloseHandle(token) if token
    end

    # token has been closed by this point
    true
  end
  module_function :logon_user

  def self.logon_user_by_logon_type(name, domain, password, logon_type, logon_provider, token)
    LogonUserW(wide_string(name), wide_string(domain), password.nil? ? FFI::Pointer::NULL : wide_string(password), logon_type, logon_provider, token)
  end

  private_class_method :logon_user_by_logon_type

  def load_profile(user, password)
    logon_user(user, password) do |token|
      FFI::MemoryPointer.from_string_to_wide_string(user) do |lpUserName|
        pi = PROFILEINFO.new
        pi[:dwSize] = PROFILEINFO.size
        pi[:dwFlags] = 1 # PI_NOUI - prevents display of profile error msgs
        pi[:lpUserName] = lpUserName

        # Load the profile. Since it doesn't exist, it will be created
        if LoadUserProfileW(token, pi.pointer) == FFI::WIN32_FALSE
          raise Puppet::Util::Windows::Error.new(_("Failed to load user profile %{user}") % { user: user.inspect })
        end

        Puppet.debug("Loaded profile for #{user}")

        if UnloadUserProfile(token, pi[:hProfile]) == FFI::WIN32_FALSE
          raise Puppet::Util::Windows::Error.new(_("Failed to unload user profile %{user}") % { user: user.inspect })
        end
      end
    end
  end
  module_function :load_profile

  def get_rights(name)
    user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
    return "" unless user_info

    rights = []
    rights_pointer = FFI::MemoryPointer.new(:pointer)
    number_of_rights = FFI::MemoryPointer.new(:ulong)
    sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)

    new_lsa_policy_handle do |policy_handle|
      result = LsaEnumerateAccountRights(policy_handle.read_pointer, sid_pointer, rights_pointer, number_of_rights)
      check_lsa_nt_status_and_raise_failures(result, "LsaEnumerateAccountRights")
    end

    number_of_rights.read_ulong.times do |index|
      right = LSA_UNICODE_STRING.new(rights_pointer.read_pointer + index * LSA_UNICODE_STRING.size)
      rights << right[:Buffer].read_arbitrary_wide_string_up_to
    end

    result = LsaFreeMemory(rights_pointer.read_pointer)
    check_lsa_nt_status_and_raise_failures(result, "LsaFreeMemory")

    rights.join(",")
  end
  module_function :get_rights

  def set_rights(name, rights)
    rights_pointer = new_lsa_unicode_strings_pointer(rights)
    user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
    sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)

    new_lsa_policy_handle do |policy_handle|
      result = LsaAddAccountRights(policy_handle.read_pointer, sid_pointer, rights_pointer, rights.size)
      check_lsa_nt_status_and_raise_failures(result, "LsaAddAccountRights")
    end
  end
  module_function :set_rights

  def remove_rights(name, rights)
    rights_pointer = new_lsa_unicode_strings_pointer(rights)
    user_info = Puppet::Util::Windows::SID.name_to_principal(name.sub(/^\.\\/, "#{Puppet::Util::Windows::ADSI.computer_name}\\"))
    sid_pointer = FFI::MemoryPointer.new(:byte, user_info.sid_bytes.length).write_array_of_uchar(user_info.sid_bytes)

    new_lsa_policy_handle do |policy_handle|
      result = LsaRemoveAccountRights(policy_handle.read_pointer, sid_pointer, false, rights_pointer, rights.size)
      check_lsa_nt_status_and_raise_failures(result, "LsaRemoveAccountRights")
    end
  end
  module_function :remove_rights

  # ACCESS_MASK flags for Policy Objects
  # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lsad/b61b7268-987a-420b-84f9-6c75f8dc8558
  POLICY_VIEW_LOCAL_INFORMATION   = 0x00000001
  POLICY_VIEW_AUDIT_INFORMATION   = 0x00000002
  POLICY_GET_PRIVATE_INFORMATION  = 0x00000004
  POLICY_TRUST_ADMIN              = 0x00000008
  POLICY_CREATE_ACCOUNT           = 0x00000010
  POLICY_CREATE_SECRET            = 0x00000020
  POLICY_CREATE_PRIVILEGE         = 0x00000040
  POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080
  POLICY_SET_AUDIT_REQUIREMENTS   = 0x00000100
  POLICY_AUDIT_LOG_ADMIN          = 0x00000200
  POLICY_SERVER_ADMIN             = 0x00000400
  POLICY_LOOKUP_NAMES             = 0x00000800
  POLICY_NOTIFICATION             = 0x00001000

  def self.new_lsa_policy_handle
    access = 0
    access |= POLICY_LOOKUP_NAMES
    access |= POLICY_CREATE_ACCOUNT
    policy_handle = FFI::MemoryPointer.new(:pointer)

    result = LsaOpenPolicy(nil, LSA_OBJECT_ATTRIBUTES.new, access, policy_handle)
    check_lsa_nt_status_and_raise_failures(result, "LsaOpenPolicy")

    begin
      yield policy_handle
    ensure
      result = LsaClose(policy_handle.read_pointer)
      check_lsa_nt_status_and_raise_failures(result, "LsaClose")
    end
  end
  private_class_method :new_lsa_policy_handle

  def self.new_lsa_unicode_strings_pointer(strings)
    lsa_unicode_strings_pointer = FFI::MemoryPointer.new(LSA_UNICODE_STRING, strings.size)

    strings.each_with_index do |string, index|
      lsa_string = LSA_UNICODE_STRING.new(lsa_unicode_strings_pointer + index * LSA_UNICODE_STRING.size)
      lsa_string[:Buffer] = FFI::MemoryPointer.from_string(wide_string(string))
      lsa_string[:Length] = string.length * 2
      lsa_string[:MaximumLength] = lsa_string[:Length] + 2
    end

    lsa_unicode_strings_pointer
  end
  private_class_method :new_lsa_unicode_strings_pointer

  # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d
  def self.check_lsa_nt_status_and_raise_failures(status, method_name)
    error_code = LsaNtStatusToWinError(status)

    error_reason = case error_code.to_s(16)
    when '0' # ERROR_SUCCESS
      return # Method call succeded
    when '2' # ERROR_FILE_NOT_FOUND
      return # No rights/privilleges assigned to given user
    when '5' # ERROR_ACCESS_DENIED
      "Access is denied. Please make sure that puppet is running as administrator."
    when '521' # ERROR_NO_SUCH_PRIVILEGE
      "One or more of the given rights/privilleges are incorrect."
    when '6ba' # RPC_S_SERVER_UNAVAILABLE
      "The RPC server is unavailable or given domain name is invalid."
    end

    raise Puppet::Error.new("Calling `#{method_name}` returned 'Win32 Error Code 0x%08X'. #{error_reason}" % error_code)
  end
  private_class_method :check_lsa_nt_status_and_raise_failures

  ffi_convention :stdcall

  # https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx
  # BOOL LogonUser(
  #   _In_      LPTSTR lpszUsername,
  #   _In_opt_  LPTSTR lpszDomain,
  #   _In_opt_  LPTSTR lpszPassword,
  #   _In_      DWORD dwLogonType,
  #   _In_      DWORD dwLogonProvider,
  #   _Out_     PHANDLE phToken
  # );
  ffi_lib :advapi32
  attach_function_private :LogonUserW,
    [:lpwstr, :lpwstr, :lpwstr, :dword, :dword, :phandle], :win32_bool

  # https://msdn.microsoft.com/en-us/library/windows/desktop/bb773378(v=vs.85).aspx
  # typedef struct _PROFILEINFO {
  #   DWORD  dwSize;
  #   DWORD  dwFlags;
  #   LPTSTR lpUserName;
  #   LPTSTR lpProfilePath;
  #   LPTSTR lpDefaultPath;
  #   LPTSTR lpServerName;
  #   LPTSTR lpPolicyPath;
  #   HANDLE hProfile;
  # } PROFILEINFO, *LPPROFILEINFO;
  # technically
  # NOTE: that for structs, buffer_* (lptstr alias) cannot be used
  class PROFILEINFO < FFI::Struct
    layout :dwSize, :dword,
           :dwFlags, :dword,
           :lpUserName, :pointer,
           :lpProfilePath, :pointer,
           :lpDefaultPath, :pointer,
           :lpServerName, :pointer,
           :lpPolicyPath, :pointer,
           :hProfile, :handle
  end

  # https://msdn.microsoft.com/en-us/library/windows/desktop/bb762281(v=vs.85).aspx
  # BOOL WINAPI LoadUserProfile(
  #   _In_     HANDLE hToken,
  #   _Inout_  LPPROFILEINFO lpProfileInfo
  # );
  ffi_lib :userenv
  attach_function_private :LoadUserProfileW,
    [:handle, :pointer], :win32_bool

  # https://msdn.microsoft.com/en-us/library/windows/desktop/bb762282(v=vs.85).aspx
  # BOOL WINAPI UnloadUserProfile(
  #   _In_  HANDLE hToken,
  #   _In_  HANDLE hProfile
  # );
  ffi_lib :userenv
  attach_function_private :UnloadUserProfile,
    [:handle, :handle], :win32_bool

  # https://msdn.microsoft.com/en-us/library/windows/desktop/aa376389(v=vs.85).aspx
  # BOOL WINAPI CheckTokenMembership(
  #   _In_opt_  HANDLE TokenHandle,
  #   _In_      PSID SidToCheck,
  #   _Out_     PBOOL IsMember
  # );
  ffi_lib :advapi32
  attach_function_private :CheckTokenMembership,
    [:handle, :pointer, :pbool], :win32_bool

  # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379650(v=vs.85).aspx
  WELL_KNOWN_SID_TYPE = enum(
    :WinNullSid                                   , 0,
    :WinWorldSid                                  , 1,
    :WinLocalSid                                  , 2,
    :WinCreatorOwnerSid                           , 3,
    :WinCreatorGroupSid                           , 4,
    :WinCreatorOwnerServerSid                     , 5,
    :WinCreatorGroupServerSid                     , 6,
    :WinNtAuthoritySid                            , 7,
    :WinDialupSid                                 , 8,
    :WinNetworkSid                                , 9,
    :WinBatchSid                                  , 10,
    :WinInteractiveSid                            , 11,
    :WinServiceSid                                , 12,
    :WinAnonymousSid                              , 13,
    :WinProxySid                                  , 14,
    :WinEnterpriseControllersSid                  , 15,
    :WinSelfSid                                   , 16,
    :WinAuthenticatedUserSid                      , 17,
    :WinRestrictedCodeSid                         , 18,
    :WinTerminalServerSid                         , 19,
    :WinRemoteLogonIdSid                          , 20,
    :WinLogonIdsSid                               , 21,
    :WinLocalSystemSid                            , 22,
    :WinLocalServiceSid                           , 23,
    :WinNetworkServiceSid                         , 24,
    :WinBuiltinDomainSid                          , 25,
    :WinBuiltinAdministratorsSid                  , 26,
    :WinBuiltinUsersSid                           , 27,
    :WinBuiltinGuestsSid                          , 28,
    :WinBuiltinPowerUsersSid                      , 29,
    :WinBuiltinAccountOperatorsSid                , 30,
    :WinBuiltinSystemOperatorsSid                 , 31,
    :WinBuiltinPrintOperatorsSid                  , 32,
    :WinBuiltinBackupOperatorsSid                 , 33,
    :WinBuiltinReplicatorSid                      , 34,
    :WinBuiltinPreWindows2000CompatibleAccessSid  , 35,
    :WinBuiltinRemoteDesktopUsersSid              , 36,
    :WinBuiltinNetworkConfigurationOperatorsSid   , 37,
    :WinAccountAdministratorSid                   , 38,
    :WinAccountGuestSid                           , 39,
    :WinAccountKrbtgtSid                          , 40,
    :WinAccountDomainAdminsSid                    , 41,
    :WinAccountDomainUsersSid                     , 42,
    :WinAccountDomainGuestsSid                    , 43,
    :WinAccountComputersSid                       , 44,
    :WinAccountControllersSid                     , 45,
    :WinAccountCertAdminsSid                      , 46,
    :WinAccountSchemaAdminsSid                    , 47,
    :WinAccountEnterpriseAdminsSid                , 48,
    :WinAccountPolicyAdminsSid                    , 49,
    :WinAccountRasAndIasServersSid                , 50,
    :WinNTLMAuthenticationSid                     , 51,
    :WinDigestAuthenticationSid                   , 52,
    :WinSChannelAuthenticationSid                 , 53,
    :WinThisOrganizationSid                       , 54,
    :WinOtherOrganizationSid                      , 55,
    :WinBuiltinIncomingForestTrustBuildersSid     , 56,
    :WinBuiltinPerfMonitoringUsersSid             , 57,
    :WinBuiltinPerfLoggingUsersSid                , 58,
    :WinBuiltinAuthorizationAccessSid             , 59,
    :WinBuiltinTerminalServerLicenseServersSid    , 60,
    :WinBuiltinDCOMUsersSid                       , 61,
    :WinBuiltinIUsersSid                          , 62,
    :WinIUserSid                                  , 63,
    :WinBuiltinCryptoOperatorsSid                 , 64,
    :WinUntrustedLabelSid                         , 65,
    :WinLowLabelSid                               , 66,
    :WinMediumLabelSid                            , 67,
    :WinHighLabelSid                              , 68,
    :WinSystemLabelSid                            , 69,
    :WinWriteRestrictedCodeSid                    , 70,
    :WinCreatorOwnerRightsSid                     , 71,
    :WinCacheablePrincipalsGroupSid               , 72,
    :WinNonCacheablePrincipalsGroupSid            , 73,
    :WinEnterpriseReadonlyControllersSid          , 74,
    :WinAccountReadonlyControllersSid             , 75,
    :WinBuiltinEventLogReadersGroup               , 76,
    :WinNewEnterpriseReadonlyControllersSid       , 77,
    :WinBuiltinCertSvcDComAccessGroup             , 78,
    :WinMediumPlusLabelSid                        , 79,
    :WinLocalLogonSid                             , 80,
    :WinConsoleLogonSid                           , 81,
    :WinThisOrganizationCertificateSid            , 82,
    :WinApplicationPackageAuthoritySid            , 83,
    :WinBuiltinAnyPackageSid                      , 84,
    :WinCapabilityInternetClientSid               , 85,
    :WinCapabilityInternetClientServerSid         , 86,
    :WinCapabilityPrivateNetworkClientServerSid   , 87,
    :WinCapabilityPicturesLibrarySid              , 88,
    :WinCapabilityVideosLibrarySid                , 89,
    :WinCapabilityMusicLibrarySid                 , 90,
    :WinCapabilityDocumentsLibrarySid             , 91,
    :WinCapabilitySharedUserCertificatesSid       , 92,
    :WinCapabilityEnterpriseAuthenticationSid     , 93,
    :WinCapabilityRemovableStorageSid             , 94
  )

  # https://msdn.microsoft.com/en-us/library/windows/desktop/aa446585(v=vs.85).aspx
  # BOOL WINAPI CreateWellKnownSid(
  #   _In_       WELL_KNOWN_SID_TYPE WellKnownSidType,
  #   _In_opt_   PSID DomainSid,
  #   _Out_opt_  PSID pSid,
  #   _Inout_    DWORD *cbSid
  # );
  ffi_lib :advapi32
  attach_function_private :CreateWellKnownSid,
    [WELL_KNOWN_SID_TYPE, :pointer, :pointer, :lpdword], :win32_bool

  # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379151(v=vs.85).aspx
  # BOOL WINAPI IsValidSid(
  #   _In_  PSID pSid
  # );
  ffi_lib :advapi32
  attach_function_private :IsValidSid,
    [:pointer], :win32_bool

  # https://docs.microsoft.com/en-us/windows/win32/api/lsalookup/ns-lsalookup-lsa_object_attributes
  # typedef struct _LSA_OBJECT_ATTRIBUTES {
  #   ULONG               Length;
  #   HANDLE              RootDirectory;
  #   PLSA_UNICODE_STRING ObjectName;
  #   ULONG               Attributes;
  #   PVOID               SecurityDescriptor;
  #   PVOID               SecurityQualityOfService;
  # } LSA_OBJECT_ATTRIBUTES, *PLSA_OBJECT_ATTRIBUTES;
  class LSA_OBJECT_ATTRIBUTES < FFI::Struct
    layout :Length, :ulong,
      :RootDirectory, :handle,
      :ObjectName, :plsa_unicode_string,
      :Attributes, :ulong,
      :SecurityDescriptor, :pvoid,
      :SecurityQualityOfService, :pvoid
  end

  # https://docs.microsoft.com/en-us/windows/win32/api/lsalookup/ns-lsalookup-lsa_unicode_string
  # typedef struct _LSA_UNICODE_STRING {
  #   USHORT Length;
  #   USHORT MaximumLength;
  #   PWSTR  Buffer;
  # } LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;
  class LSA_UNICODE_STRING < FFI::Struct
    layout :Length, :ushort,
      :MaximumLength, :ushort,
      :Buffer, :pwstr
  end

  # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaenumerateaccountrights
  # https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/user-rights-assignment
  # NTSTATUS LsaEnumerateAccountRights(
  #   LSA_HANDLE          PolicyHandle,
  #   PSID                AccountSid,
  #   PLSA_UNICODE_STRING *UserRights,
  #   PULONG              CountOfRights
  # );
  ffi_lib :advapi32
  attach_function_private :LsaEnumerateAccountRights,
    [:lsa_handle, :psid, :plsa_unicode_string, :pulong], :ntstatus

  # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaaddaccountrights
  # NTSTATUS LsaAddAccountRights(
  #   LSA_HANDLE          PolicyHandle,
  #   PSID                AccountSid,
  #   PLSA_UNICODE_STRING UserRights,
  #   ULONG               CountOfRights
  # );
  ffi_lib :advapi32
  attach_function_private :LsaAddAccountRights,
    [:lsa_handle, :psid, :plsa_unicode_string, :ulong], :ntstatus

  # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaremoveaccountrights
  # NTSTATUS LsaRemoveAccountRights(
  #   LSA_HANDLE          PolicyHandle,
  #   PSID                AccountSid,
  #   BOOLEAN             AllRights,
  #   PLSA_UNICODE_STRING UserRights,
  #   ULONG               CountOfRights
  # );
  ffi_lib :advapi32
  attach_function_private :LsaRemoveAccountRights,
    [:lsa_handle, :psid, :bool, :plsa_unicode_string, :ulong], :ntstatus

  # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaopenpolicy
  # NTSTATUS LsaOpenPolicy(
  #   PLSA_UNICODE_STRING    SystemName,
  #   PLSA_OBJECT_ATTRIBUTES ObjectAttributes,
  #   ACCESS_MASK            DesiredAccess,
  #   PLSA_HANDLE            PolicyHandle
  # );
  ffi_lib :advapi32
  attach_function_private :LsaOpenPolicy,
    [:plsa_unicode_string, :plsa_object_attributes, :access_mask, :plsa_handle], :ntstatus

  # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaclose
  # NTSTATUS LsaClose(
  #   LSA_HANDLE ObjectHandle
  # );
  ffi_lib :advapi32
  attach_function_private :LsaClose,
    [:lsa_handle], :ntstatus

  # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsafreememory
  # NTSTATUS LsaFreeMemory(
  #   PVOID Buffer
  # );
  ffi_lib :advapi32
  attach_function_private :LsaFreeMemory,
    [:pvoid], :ntstatus

  # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsantstatustowinerror
  # ULONG LsaNtStatusToWinError(
  #   NTSTATUS Status
  # );
  ffi_lib :advapi32
  attach_function_private :LsaNtStatusToWinError,
    [:ntstatus], :ulong
end
