From 220e61d5361cd0a1632f9b76b22817343b04a789 Mon Sep 17 00:00:00 2001 From: maltewitt Date: Fri, 22 Jul 2016 10:59:05 +0200 Subject: [PATCH 1/4] First check in of UrlAcl lib --- .vscode/settings.json | 5 + libraries/url_acl.rb | 272 ++++++++++++++++++++++++++++++++++++++++++ providers/http_acl.rb | 11 +- 3 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 libraries/url_acl.rb diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..90febc01 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +// Place your settings in this file to overwrite default and user settings. +{ + // Controls the rendering size of tabs in characters. Accepted values: "auto", 2, 4, 6, etc. If set to "auto", the value will be guessed when a file is opened. + "editor.tabSize": 2 +} \ No newline at end of file diff --git a/libraries/url_acl.rb b/libraries/url_acl.rb new file mode 100644 index 00000000..110bcc33 --- /dev/null +++ b/libraries/url_acl.rb @@ -0,0 +1,272 @@ +# +# Author:: Malte Witt () +# Cookbook Name:: windows +# Library:: UrlAcl +# +# Copyright:: 2011-2015, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'ffi' + +module Windows + module UrlAcl + module HttpApi + extend FFI::Library + ffi_lib 'httpapi' + + HTTP_INITIALIZE_CONFIG = 2 + + enum :http_service_config_query_type, [:http_service_config_query_exact, 0, + :http_service_config_next, + :http_service_config_max] + + enum :http_service_config_id, [:http_service_config_ip_listen_list, 0, + :http_service_config_ssl_cert_info, + :http_service_config_urlacl_info, + :http_service_config_timeout, + :http_service_config_cache, + :http_service_config_ssl_sni_cert_info, + :http_service_config_max] + + class HTTPApiVersion < FFI::Struct + layout :http_api_major_version, :ushort, + :http_api_minor_version, :ushort + end + + class HTTPServiceConfigURLACLKey < FFI::Struct + layout :url_prefix, :pointer + end + + class HTTPServiceConfigURLACLParam < FFI::Struct + layout :string_security_descriptor, :pointer + end + + class HTTPServiceConfigURLACLQuery < FFI::Struct + layout :query_desc, :http_service_config_query_type, + :key_desc, HTTPServiceConfigURLACLKey, + :token, :uint + end + + class HTTPServiceConfigURLACLSet < FFI::Struct + layout :key_desc, HTTPServiceConfigURLACLKey, + :param_desc, HTTPServiceConfigURLACLParam + end + + attach_function( + :http_initialize, + :HttpInitialize, + [HTTPApiVersion.by_value, :ulong, :pointer], + :ulong + ) + attach_function( + :http_terminate, + :HttpTerminate, + [:ulong, :pointer], + :ulong + ) + attach_function( + :http_query_service_configuration, + :HttpQueryServiceConfiguration, + [:pointer, :http_service_config_id, :pointer, :ulong, :pointer, :ulong, :pointer, :pointer], + :ulong + ) + end + + module Advapi32 + extend FFI::Library + ffi_lib 'advapi32' + + attach_function( + :convert_string_sid_to_sid, + :ConvertStringSidToSidW, + [:pointer, :pointer], + :int + ) + + attach_function( + :lookup_account_sid, + :LookupAccountSidW, + [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], + :int + ) + end + + module Kernel32 + extend FFI::Library + ffi_lib 'kernel32' + + attach_function( + :local_free, + :LocalFree, + [:pointer], + :pointer + ) + end + + module ErrorCodes + ERROR_SUCCESS = 0, + ERROR_INVALID_FUNCTION = 1, + ERROR_FILE_NOT_FOUND = 2, + ERROR_INVALID_HANDLE = 6, + ERROR_INSUFFICIENT_BUFFER = 122 + end + + def get_urlacl(url) + hresult = initialize_http + begin + security_descriptor = get_security_descriptor(url) + sids = get_sids(security_descriptor) + sids.map { |sid| get_user(sid) } + ensure + terminate_http + end + end + + private + + def initialize_http + api_version = HttpApi::HTTPApiVersion.new + api_version[:http_api_major_version] = 1 + api_version[:http_api_minor_version] = 0 + + hresult = HttpApi.http_initialize(api_version, HttpApi::HTTP_INITIALIZE_CONFIG, nil) + + raise "Unexcpected HRESULT #{hresult} [HttpApi.http_initialize]" \ + unless 0 == hresult + end + + def terminate_http + HttpApi.http_terminate(HttpApi::HTTP_INITIALIZE_CONFIG, nil) + end + + def get_security_descriptor(url) + url_utf16 = url.encode(Encoding::UTF_16LE) + ptr_url_utf16 = FFI::MemoryPointer.new(:ushort, url_utf16.length + 1, true). + put_bytes(0, url_utf16) + + input_config_info = HttpApi::HTTPServiceConfigURLACLQuery.new + input_config_info[:query_desc] = :http_service_config_query_exact + input_config_info[:key_desc][:url_prefix] = ptr_url_utf16 + input_config_info[:token] = 0 + ptr_return_length = FFI::MemoryPointer.new(:int, 1) + + # First request to get needed buffer_size. Expecting ERROR_INSUFFICIENT_BUFFER + # or URL not found + hresult = HttpApi.http_query_service_configuration( + nil, + :http_service_config_urlacl_info, + input_config_info.to_ptr, + input_config_info.size, + nil, + 0, + ptr_return_length, + nil) + + return "" if 2 == hresult + + raise "Unexcpected HRESULT #{hresult} [HttpApi.http_query_service_configuration]" \ + unless ErrorCodes::ERROR_INSUFFICIENT_BUFFER == hresult + + buffer_size = ptr_return_length.read_int + ptr_output_config_info = FFI::MemoryPointer.new(:char, buffer_size) + + # Second request is expected to succeed + hresult = HttpApi.http_query_service_configuration( + nil, + :http_service_config_urlacl_info, + input_config_info.to_ptr, + input_config_info.size, + ptr_output_config_info, + buffer_size, + ptr_return_length, + nil) + + raise "Unexcpected HRESULT #{hresult} [HttpApi.http_query_service_configuration]" \ + unless 0 == hresult + + output_config_info = HttpApi::HTTPServiceConfigURLACLSet.new(ptr_output_config_info) + read_utf16(output_config_info[:param_desc][:string_security_descriptor]) + end + + def read_utf16(mem_pointer) + index = 0 + wchar = 0 + result = [] + begin + wchar = mem_pointer.get_uint16(index*2) + result.push(wchar) unless (0 == wchar) + index += 1 + end until 0 == wchar + + result.pack("s*"). + force_encoding(Encoding::UTF_16LE). + encode(Encoding::UTF_8) + end + + def get_sids(security_descriptor) + match_data = security_descriptor.scan(/\([^\)]+\)/) + sids = [] + match_data.each do |sd| + sid_string = sd.match(/\(A;;G\w{1};;;([^\)]+)/)[1].encode(Encoding::UTF_16LE) + sids.push(sid_string) + end + return sids + end + + def get_user(sid_string) + ptr_string_sid = FFI::MemoryPointer.new(:ushort, sid_string.length + 1). + put_bytes(0, sid_string) + ptr_ptr_sid = FFI::MemoryPointer.new(:pointer, 1) + success = Advapi32.convert_string_sid_to_sid(ptr_string_sid, ptr_ptr_sid) + + raise "Advapi32.convert_string_sid_to_sid failed" unless 0 != success + + ptr_cch_name = FFI::MemoryPointer.new(:uint, 1) + ptr_cch_domain = FFI::MemoryPointer.new(:uint, 1) + ptr_sid_name_use = FFI::MemoryPointer.new(:int, 1) + + # First call to get needed buffer sizes + Advapi32.lookup_account_sid( + nil, + ptr_ptr_sid.read_pointer, + nil, + ptr_cch_name, + nil, + ptr_cch_domain, + ptr_sid_name_use) + + ptr_name = FFI::MemoryPointer.new(:ushort, ptr_cch_name.read_uint) + ptr_domain = FFI::MemoryPointer.new(:ushort, ptr_cch_domain.read_uint) + success = Advapi32.lookup_account_sid( + nil, + ptr_ptr_sid.read_pointer, + ptr_name, + ptr_cch_name, + ptr_domain, + ptr_cch_domain, + ptr_sid_name_use) + + raise "Advapi32.lookup_account_sid failed" unless 0 != success + + name = read_utf16(ptr_name) + domain = read_utf16(ptr_domain) + domain = nil if domain.empty? + + return :domain => domain, :user => name + ensure + ptr_sid = ptr_ptr_sid.read_pointer + Kernel32.local_free(ptr_sid) unless ptr_sid.null? + end + end +end diff --git a/providers/http_acl.rb b/providers/http_acl.rb index b0f9ed59..60500aa1 100644 --- a/providers/http_acl.rb +++ b/providers/http_acl.rb @@ -23,6 +23,7 @@ include Chef::Mixin::ShellOut include Windows::Helper +include Windows::UrlAcl # Support whyrun def whyrun_supported? @@ -71,14 +72,14 @@ def load_current_resource private def getCurrentAcl - cmd = shell_out!("#{@command} http show urlacl url=#{@current_resource.url}") - Chef::Log.debug "netsh reports: #{cmd.stdout}" + users = get_urlacl(@current_resource.url) - m = cmd.stdout.scan(/User:\s*(.+)/) - if m.empty? + if users.empty? @current_resource.exists = false else - @current_resource.user(m[0][0].chomp) + # TODO: URL might be registered for more than one user. + # For now .first will do for most cases (I've never seen more than one user registered) + @current_resource.user(users.first[:user]) @current_resource.exists = true end end From 3de5c4a051963bfbf8025890d1328fd0e9b1ccbc Mon Sep 17 00:00:00 2001 From: maltewitt Date: Fri, 22 Jul 2016 12:16:53 +0200 Subject: [PATCH 2/4] Working for domain and non-domain users now. --- providers/http_acl.rb | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/providers/http_acl.rb b/providers/http_acl.rb index 60500aa1..96e9724a 100644 --- a/providers/http_acl.rb +++ b/providers/http_acl.rb @@ -34,7 +34,8 @@ def whyrun_supported? raise 'No user property set' if @new_resource.user.nil? || @new_resource.user.empty? if @current_resource.exists - needsChange = (@current_resource.user.casecmp(@new_resource.user) != 0) + new_username = to_domain_notation(@new_resource.user) + needsChange = (@current_resource.user.casecmp(new_username) != 0) if needsChange converge_by("Changing #{@current_resource.url}") do @@ -71,6 +72,19 @@ def load_current_resource private +# If no domain is given either by x\y notation in user or by specifying domain seperately +# the result will be "user", otherwise it will be "domain\user" +def to_domain_notation(user, domain = nil) + part_array = user.split('\\') + if part_array.length == 2 + part_array.shift if part_array[0].nil? || part_array[0].empty? + else + part_array.unshift(domain) unless domain.nil? + end + + return part_array.join('\\') +end + def getCurrentAcl users = get_urlacl(@current_resource.url) @@ -79,7 +93,9 @@ def getCurrentAcl else # TODO: URL might be registered for more than one user. # For now .first will do for most cases (I've never seen more than one user registered) - @current_resource.user(users.first[:user]) + user = users.first + username = to_domain_notation(user[:user], user[:domain]) + @current_resource.user(username) @current_resource.exists = true end end From 339d4dbc1958f102a4de9c9f09bf9b4bed33b785 Mon Sep 17 00:00:00 2001 From: maltewitt Date: Fri, 22 Jul 2016 12:30:25 +0200 Subject: [PATCH 3/4] Commited by accident --- .vscode/settings.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 90febc01..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -// Place your settings in this file to overwrite default and user settings. -{ - // Controls the rendering size of tabs in characters. Accepted values: "auto", 2, 4, 6, etc. If set to "auto", the value will be guessed when a file is opened. - "editor.tabSize": 2 -} \ No newline at end of file From ff3962da9ff0873e38523cfcc900327d4658980e Mon Sep 17 00:00:00 2001 From: maltewitt Date: Fri, 22 Jul 2016 14:50:44 +0200 Subject: [PATCH 4/4] Fixed RuboCop Issues --- libraries/url_acl.rb | 123 +++++++++++++++++++++--------------------- providers/http_acl.rb | 6 +-- 2 files changed, 63 insertions(+), 66 deletions(-) diff --git a/libraries/url_acl.rb b/libraries/url_acl.rb index 110bcc33..a1303257 100644 --- a/libraries/url_acl.rb +++ b/libraries/url_acl.rb @@ -25,7 +25,7 @@ module HttpApi extend FFI::Library ffi_lib 'httpapi' - HTTP_INITIALIZE_CONFIG = 2 + HTTP_INITIALIZE_CONFIG = 2 unless defined? HTTP_INITIALIZE_CONFIG enum :http_service_config_query_type, [:http_service_config_query_exact, 0, :http_service_config_next, @@ -115,25 +115,25 @@ module Kernel32 end module ErrorCodes - ERROR_SUCCESS = 0, - ERROR_INVALID_FUNCTION = 1, - ERROR_FILE_NOT_FOUND = 2, - ERROR_INVALID_HANDLE = 6, - ERROR_INSUFFICIENT_BUFFER = 122 + ERROR_SUCCESS = 0 unless defined? ERROR_SUCCESS + ERROR_INVALID_FUNCTION = 1 unless defined? ERROR_INVALID_FUNCTION + ERROR_FILE_NOT_FOUND = 2 unless defined? ERROR_FILE_NOT_FOUND + ERROR_INVALID_HANDLE = 6 unless defined? ERROR_INVALID_HANDLE + ERROR_INSUFFICIENT_BUFFER = 122 unless defined? ERROR_INSUFFICIENT_BUFFER end def get_urlacl(url) - hresult = initialize_http + initialize_http begin - security_descriptor = get_security_descriptor(url) - sids = get_sids(security_descriptor) - sids.map { |sid| get_user(sid) } + security_descriptor = get_security_descriptor(url) + sids = get_sids(security_descriptor) + sids.map { |sid| get_user(sid) } ensure - terminate_http + terminate_http end end - private + private def initialize_http api_version = HttpApi::HTTPApiVersion.new @@ -152,8 +152,7 @@ def terminate_http def get_security_descriptor(url) url_utf16 = url.encode(Encoding::UTF_16LE) - ptr_url_utf16 = FFI::MemoryPointer.new(:ushort, url_utf16.length + 1, true). - put_bytes(0, url_utf16) + ptr_url_utf16 = FFI::MemoryPointer.new(:ushort, url_utf16.length + 1, true).put_bytes(0, url_utf16) input_config_info = HttpApi::HTTPServiceConfigURLACLQuery.new input_config_info[:query_desc] = :http_service_config_query_exact @@ -164,16 +163,16 @@ def get_security_descriptor(url) # First request to get needed buffer_size. Expecting ERROR_INSUFFICIENT_BUFFER # or URL not found hresult = HttpApi.http_query_service_configuration( - nil, - :http_service_config_urlacl_info, - input_config_info.to_ptr, - input_config_info.size, - nil, - 0, - ptr_return_length, - nil) + nil, + :http_service_config_urlacl_info, + input_config_info.to_ptr, + input_config_info.size, + nil, + 0, + ptr_return_length, + nil) - return "" if 2 == hresult + return '' if 2 == hresult raise "Unexcpected HRESULT #{hresult} [HttpApi.http_query_service_configuration]" \ unless ErrorCodes::ERROR_INSUFFICIENT_BUFFER == hresult @@ -183,14 +182,14 @@ def get_security_descriptor(url) # Second request is expected to succeed hresult = HttpApi.http_query_service_configuration( - nil, - :http_service_config_urlacl_info, - input_config_info.to_ptr, - input_config_info.size, - ptr_output_config_info, - buffer_size, - ptr_return_length, - nil) + nil, + :http_service_config_urlacl_info, + input_config_info.to_ptr, + input_config_info.size, + ptr_output_config_info, + buffer_size, + ptr_return_length, + nil) raise "Unexcpected HRESULT #{hresult} [HttpApi.http_query_service_configuration]" \ unless 0 == hresult @@ -203,34 +202,32 @@ def read_utf16(mem_pointer) index = 0 wchar = 0 result = [] - begin - wchar = mem_pointer.get_uint16(index*2) - result.push(wchar) unless (0 == wchar) - index += 1 - end until 0 == wchar - - result.pack("s*"). - force_encoding(Encoding::UTF_16LE). - encode(Encoding::UTF_8) + loop do + wchar = mem_pointer.get_uint16(2 * index) + break if 0 == wchar + result.push(wchar) + index += 1 + end + + result.pack('s*').force_encoding(Encoding::UTF_16LE).encode(Encoding::UTF_8) end def get_sids(security_descriptor) - match_data = security_descriptor.scan(/\([^\)]+\)/) + match_data = security_descriptor.scan(/\([^\)]+\)/) sids = [] match_data.each do |sd| - sid_string = sd.match(/\(A;;G\w{1};;;([^\)]+)/)[1].encode(Encoding::UTF_16LE) - sids.push(sid_string) + sid_string = sd.match(/\(A;;G\w{1};;;([^\)]+)/)[1].encode(Encoding::UTF_16LE) + sids.push(sid_string) end - return sids + sids end def get_user(sid_string) - ptr_string_sid = FFI::MemoryPointer.new(:ushort, sid_string.length + 1). - put_bytes(0, sid_string) + ptr_string_sid = FFI::MemoryPointer.new(:ushort, sid_string.length + 1).put_bytes(0, sid_string) ptr_ptr_sid = FFI::MemoryPointer.new(:pointer, 1) success = Advapi32.convert_string_sid_to_sid(ptr_string_sid, ptr_ptr_sid) - raise "Advapi32.convert_string_sid_to_sid failed" unless 0 != success + raise 'Advapi32.convert_string_sid_to_sid failed' unless 0 != success ptr_cch_name = FFI::MemoryPointer.new(:uint, 1) ptr_cch_domain = FFI::MemoryPointer.new(:uint, 1) @@ -238,32 +235,32 @@ def get_user(sid_string) # First call to get needed buffer sizes Advapi32.lookup_account_sid( - nil, - ptr_ptr_sid.read_pointer, - nil, - ptr_cch_name, - nil, - ptr_cch_domain, - ptr_sid_name_use) + nil, + ptr_ptr_sid.read_pointer, + nil, + ptr_cch_name, + nil, + ptr_cch_domain, + ptr_sid_name_use) ptr_name = FFI::MemoryPointer.new(:ushort, ptr_cch_name.read_uint) ptr_domain = FFI::MemoryPointer.new(:ushort, ptr_cch_domain.read_uint) success = Advapi32.lookup_account_sid( - nil, - ptr_ptr_sid.read_pointer, - ptr_name, - ptr_cch_name, - ptr_domain, - ptr_cch_domain, - ptr_sid_name_use) + nil, + ptr_ptr_sid.read_pointer, + ptr_name, + ptr_cch_name, + ptr_domain, + ptr_cch_domain, + ptr_sid_name_use) - raise "Advapi32.lookup_account_sid failed" unless 0 != success + raise 'Advapi32.lookup_account_sid failed' unless 0 != success name = read_utf16(ptr_name) domain = read_utf16(ptr_domain) domain = nil if domain.empty? - return :domain => domain, :user => name + return { domain: domain, user: name } ensure ptr_sid = ptr_ptr_sid.read_pointer Kernel32.local_free(ptr_sid) unless ptr_sid.null? diff --git a/providers/http_acl.rb b/providers/http_acl.rb index 96e9724a..0c73e0d9 100644 --- a/providers/http_acl.rb +++ b/providers/http_acl.rb @@ -81,8 +81,8 @@ def to_domain_notation(user, domain = nil) else part_array.unshift(domain) unless domain.nil? end - - return part_array.join('\\') + + part_array.join('\\') end def getCurrentAcl @@ -91,7 +91,7 @@ def getCurrentAcl if users.empty? @current_resource.exists = false else - # TODO: URL might be registered for more than one user. + # TODO: URL might be registered for more than one user. # For now .first will do for most cases (I've never seen more than one user registered) user = users.first username = to_domain_notation(user[:user], user[:domain])