File: principal.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (201 lines) | stat: -rw-r--r-- 8,846 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
require_relative '../../../puppet/util/windows'

module Puppet::Util::Windows::SID
  class Principal
    extend FFI::Library
    attr_reader :account, :sid_bytes, :sid, :domain, :domain_account, :account_type

    def initialize(account, sid_bytes, sid, domain, account_type)
      # This is only ever called from lookup_account_sid which has already
      # removed the potential for passing in an account like host\user
      @account = account
      @sid_bytes = sid_bytes
      @sid = sid
      @domain = domain
      @account_type = account_type
      # When domain is available and it is a Domain principal, use domain only
      #   otherwise if domain is available then combine it with parsed account
      #   otherwise when the domain is not available, use the account value directly
      # WinNT naming standard https://msdn.microsoft.com/en-us/library/windows/desktop/aa746534(v=vs.85).aspx
      if (domain && !domain.empty? && @account_type == :SidTypeDomain)
        @domain_account = @domain
      elsif (domain && !domain.empty?)
        @domain_account =  "#{domain}\\#{@account}"
      else
        @domain_account = account
      end
    end

    # added for backward compatibility
    def ==(compare)
      compare.is_a?(Puppet::Util::Windows::SID::Principal) &&
        @sid_bytes == compare.sid_bytes
    end

    # returns authority qualified account name
    # prefer to compare Principal instances with == operator or by #sid
    def to_s
      @domain_account
    end

    # = 8 + max sub identifiers (15) * 4
    MAXIMUM_SID_BYTE_LENGTH = 68

    ERROR_INVALID_PARAMETER   = 87
    ERROR_INSUFFICIENT_BUFFER = 122

    def self.lookup_account_name(system_name = nil, sanitize = true, account_name)
      account_name = sanitize_account_name(account_name) if sanitize
      system_name_ptr = FFI::Pointer::NULL
      begin
        if system_name
          system_name_wide = Puppet::Util::Windows::String.wide_string(system_name)
          system_name_ptr = FFI::MemoryPointer.from_wide_string(system_name_wide)
        end

        FFI::MemoryPointer.from_string_to_wide_string(account_name) do |account_name_ptr|
          FFI::MemoryPointer.new(:byte, MAXIMUM_SID_BYTE_LENGTH) do |sid_ptr|
            FFI::MemoryPointer.new(:dword, 1) do |sid_length_ptr|
              FFI::MemoryPointer.new(:dword, 1) do |domain_length_ptr|
                FFI::MemoryPointer.new(:uint32, 1) do |name_use_enum_ptr|

                sid_length_ptr.write_dword(MAXIMUM_SID_BYTE_LENGTH)
                success = LookupAccountNameW(system_name_ptr, account_name_ptr, sid_ptr, sid_length_ptr,
                  FFI::Pointer::NULL, domain_length_ptr, name_use_enum_ptr)
                last_error = FFI.errno

                if (success == FFI::WIN32_FALSE && last_error != ERROR_INSUFFICIENT_BUFFER)
                  raise Puppet::Util::Windows::Error.new(_('Failed to call LookupAccountNameW with account: %{account_name}') % { account_name: account_name}, last_error)
                end

                FFI::MemoryPointer.new(:lpwstr, domain_length_ptr.read_dword) do |domain_ptr|
                  if LookupAccountNameW(system_name_ptr, account_name_ptr,
                      sid_ptr, sid_length_ptr,
                      domain_ptr, domain_length_ptr, name_use_enum_ptr) == FFI::WIN32_FALSE
                  raise Puppet::Util::Windows::Error.new(_('Failed to call LookupAccountNameW with account: %{account_name}') % { account_name: account_name} )
                  end

                  # with a SID returned, loop back through lookup_account_sid to retrieve official name
                  # necessary when accounts like . or '' are passed in
                  return lookup_account_sid(
                    system_name,
                    sid_ptr.read_bytes(sid_length_ptr.read_dword).unpack('C*'))
                  end
                end
              end
            end
          end
        end
      ensure
        system_name_ptr.free if system_name_ptr != FFI::Pointer::NULL
      end
    end

    def self.lookup_account_sid(system_name = nil, sid_bytes)
      system_name_ptr = FFI::Pointer::NULL
      if (sid_bytes.nil? || (!sid_bytes.is_a? Array) || (sid_bytes.length == 0))
        #TRANSLATORS `lookup_account_sid` is a variable name and should not be translated
        raise Puppet::Util::Windows::Error.new(_('Byte array for lookup_account_sid must not be nil and must be at least 1 byte long'))
      end

      begin
        if system_name
          system_name_wide = Puppet::Util::Windows::String.wide_string(system_name)
          system_name_ptr = FFI::MemoryPointer.from_wide_string(system_name_wide)
        end

        FFI::MemoryPointer.new(:byte, sid_bytes.length) do |sid_ptr|
          FFI::MemoryPointer.new(:dword, 1) do |name_length_ptr|
            FFI::MemoryPointer.new(:dword, 1) do |domain_length_ptr|
              FFI::MemoryPointer.new(:uint32, 1) do |name_use_enum_ptr|

                sid_ptr.write_array_of_uchar(sid_bytes)

                if Puppet::Util::Windows::SID.IsValidSid(sid_ptr) == FFI::WIN32_FALSE
                  raise Puppet::Util::Windows::Error.new(_('Byte array for lookup_account_sid is invalid: %{sid_bytes}') % { sid_bytes: sid_bytes }, ERROR_INVALID_PARAMETER)
                end

                success = LookupAccountSidW(system_name_ptr, sid_ptr, FFI::Pointer::NULL, name_length_ptr,
                  FFI::Pointer::NULL, domain_length_ptr, name_use_enum_ptr)
                last_error = FFI.errno

                if (success == FFI::WIN32_FALSE && last_error != ERROR_INSUFFICIENT_BUFFER)
                  raise Puppet::Util::Windows::Error.new(_('Failed to call LookupAccountSidW with bytes: %{sid_bytes}') % { sid_bytes: sid_bytes}, last_error)
                end

                FFI::MemoryPointer.new(:lpwstr, name_length_ptr.read_dword) do |name_ptr|
                  FFI::MemoryPointer.new(:lpwstr, domain_length_ptr.read_dword) do |domain_ptr|
                    if LookupAccountSidW(system_name_ptr, sid_ptr, name_ptr, name_length_ptr,
                        domain_ptr, domain_length_ptr, name_use_enum_ptr) == FFI::WIN32_FALSE
                     raise Puppet::Util::Windows::Error.new(_('Failed to call LookupAccountSidW with bytes: %{sid_bytes}') % { sid_bytes: sid_bytes} )
                    end

                    return new(
                      name_ptr.read_wide_string(name_length_ptr.read_dword),
                      sid_bytes,
                      Puppet::Util::Windows::SID.sid_ptr_to_string(sid_ptr),
                      domain_ptr.read_wide_string(domain_length_ptr.read_dword),
                      SID_NAME_USE[name_use_enum_ptr.read_uint32])
                  end
                end
              end
            end
          end
        end
      ensure
        system_name_ptr.free if system_name_ptr != FFI::Pointer::NULL
      end
    end

    # Sanitize the given account name for lookup to avoid known issues
    def self.sanitize_account_name(account_name)
      return account_name unless account_name.start_with?('APPLICATION PACKAGE AUTHORITY\\')
      account_name.split('\\').last
    end
    private_class_method :sanitize_account_name

    ffi_convention :stdcall

    # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379601(v=vs.85).aspx
    SID_NAME_USE = enum(
      :SidTypeUser, 1,
      :SidTypeGroup, 2,
      :SidTypeDomain, 3,
      :SidTypeAlias, 4,
      :SidTypeWellKnownGroup, 5,
      :SidTypeDeletedAccount, 6,
      :SidTypeInvalid, 7,
      :SidTypeUnknown, 8,
      :SidTypeComputer, 9,
      :SidTypeLabel, 10
    )

    # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379159(v=vs.85).aspx
    # BOOL WINAPI LookupAccountName(
    #   _In_opt_  LPCTSTR       lpSystemName,
    #   _In_      LPCTSTR       lpAccountName,
    #   _Out_opt_ PSID          Sid,
    #   _Inout_   LPDWORD       cbSid,
    #   _Out_opt_ LPTSTR        ReferencedDomainName,
    #   _Inout_   LPDWORD       cchReferencedDomainName,
    #   _Out_     PSID_NAME_USE peUse
    # );
    ffi_lib :advapi32
    attach_function_private :LookupAccountNameW,
      [:lpcwstr, :lpcwstr, :pointer, :lpdword, :lpwstr, :lpdword, :pointer], :win32_bool

    # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379166(v=vs.85).aspx
    # BOOL WINAPI LookupAccountSid(
    #   _In_opt_  LPCTSTR       lpSystemName,
    #   _In_      PSID          lpSid,
    #   _Out_opt_ LPTSTR        lpName,
    #   _Inout_   LPDWORD       cchName,
    #   _Out_opt_ LPTSTR        lpReferencedDomainName,
    #   _Inout_   LPDWORD       cchReferencedDomainName,
    #   _Out_     PSID_NAME_USE peUse
    # );
    ffi_lib :advapi32
    attach_function_private :LookupAccountSidW,
      [:lpcwstr, :pointer, :lpwstr, :lpdword, :lpwstr, :lpdword, :pointer], :win32_bool
  end
end