diff --git a/ext/project_data.yaml b/ext/project_data.yaml index ee3fc71f5..4bff38686 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -1,46 +1,46 @@ --- project: 'puppet' author: 'Puppet Labs' email: 'info@puppetlabs.com' homepage: 'https://github.com/puppetlabs/puppet' summary: 'Puppet, an automated configuration management tool' description: 'Puppet, an automated configuration management tool' version_file: 'lib/puppet/version.rb' # files and gem_files are space separated lists files: '[A-Z]* install.rb bin lib conf man examples ext tasks spec' # The gem specification bits only work on Puppet >= 3.0rc, NOT 2.7.x and earlier gem_files: '[A-Z]* install.rb bin lib conf man examples ext tasks spec' gem_test_files: 'spec/**/*' gem_executables: 'puppet' gem_default_executables: 'puppet' gem_forge_project: 'puppet' gem_runtime_dependencies: facter: ['> 1.6', '< 3'] hiera: '~> 1.0' rgen: '~> 0.6.5' json_pure: gem_rdoc_options: - --title - "Puppet - Configuration Management" - --main - README.md - --line-numbers gem_platform_dependencies: x86-mingw32: gem_runtime_dependencies: # Pinning versions that require native extensions ffi: '1.9.0' sys-admin: '1.5.6' win32-api: '1.4.8' win32-dir: '~> 0.4.3' win32-eventlog: '~> 0.5.3' win32-process: '~> 0.6.5' - win32-security: '~> 0.1.4' + win32-security: '~> 0.2.5' win32-service: '0.7.2' win32-taskscheduler: '~> 0.2.2' win32console: '1.3.2' windows-api: '~> 0.4.2' windows-pr: '~> 1.2.2' minitar: '~> 0.5.4' bundle_platforms: x86-mingw32: mingw diff --git a/lib/puppet/util/windows/adsi.rb b/lib/puppet/util/windows/adsi.rb index f621c798f..0b7bfd076 100644 --- a/lib/puppet/util/windows/adsi.rb +++ b/lib/puppet/util/windows/adsi.rb @@ -1,394 +1,394 @@ module Puppet::Util::Windows::ADSI require 'ffi' class << self extend FFI::Library def connectable?(uri) begin !! connect(uri) rescue false end end def connect(uri) begin WIN32OLE.connect(uri) rescue Exception => e raise Puppet::Error.new( "ADSI connection error: #{e}", e ) end end def create(name, resource_type) Puppet::Util::Windows::ADSI.connect(computer_uri).Create(resource_type, name) end def delete(name, resource_type) Puppet::Util::Windows::ADSI.connect(computer_uri).Delete(resource_type, name) end # taken from winbase.h MAX_COMPUTERNAME_LENGTH = 31 def computer_name unless @computer_name max_length = MAX_COMPUTERNAME_LENGTH + 1 # NULL terminated FFI::MemoryPointer.new(max_length * 2) do |buffer| # wide string FFI::MemoryPointer.new(:dword, 1) do |buffer_size| buffer_size.write_dword(max_length) # length in TCHARs if GetComputerNameW(buffer, buffer_size) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to get computer name") end @computer_name = buffer.read_wide_string(buffer_size.read_dword) end end end @computer_name end def computer_uri(host = '.') "WinNT://#{host}" end def wmi_resource_uri( host = '.' ) "winmgmts:{impersonationLevel=impersonate}!//#{host}/root/cimv2" end # @api private def sid_uri_safe(sid) return sid_uri(sid) if sid.kind_of?(Win32::Security::SID) begin sid = Win32::Security::SID.new(Win32::Security::SID.string_to_sid(sid)) sid_uri(sid) - rescue Win32::Security::SID::Error - return nil + rescue SystemCallError + nil end end def sid_uri(sid) raise Puppet::Error.new( "Must use a valid SID object" ) if !sid.kind_of?(Win32::Security::SID) "WinNT://#{sid.to_s}" end def uri(resource_name, resource_type, host = '.') "#{computer_uri(host)}/#{resource_name},#{resource_type}" end def wmi_connection connect(wmi_resource_uri) end def execquery(query) wmi_connection.execquery(query) end def sid_for_account(name) Puppet.deprecation_warning "Puppet::Util::Windows::ADSI.sid_for_account is deprecated and will be removed in 3.0, use Puppet::Util::Windows::SID.name_to_sid instead." Puppet::Util::Windows::SID.name_to_sid(name) end ffi_convention :stdcall # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724295(v=vs.85).aspx # BOOL WINAPI GetComputerName( # _Out_ LPTSTR lpBuffer, # _Inout_ LPDWORD lpnSize # ); ffi_lib :kernel32 attach_function_private :GetComputerNameW, [:lpwstr, :lpdword], :win32_bool end class User extend Enumerable attr_accessor :native_user attr_reader :name, :sid def initialize(name, native_user = nil) @name = name @native_user = native_user end def self.parse_name(name) if name =~ /\// raise Puppet::Error.new( "Value must be in DOMAIN\\user style syntax" ) end matches = name.scan(/((.*)\\)?(.*)/) domain = matches[0][1] || '.' account = matches[0][2] return account, domain end def native_user @native_user ||= Puppet::Util::Windows::ADSI.connect(self.class.uri(*self.class.parse_name(@name))) end def sid @sid ||= Puppet::Util::Windows::SID.octet_string_to_sid_object(native_user.objectSID) end def self.uri(name, host = '.') if sid_uri = Puppet::Util::Windows::ADSI.sid_uri_safe(name) then return sid_uri end host = '.' if ['NT AUTHORITY', 'BUILTIN', Socket.gethostname].include?(host) Puppet::Util::Windows::ADSI.uri(name, 'user', host) end def uri self.class.uri(sid.account, sid.domain) end def self.logon(name, password) Puppet::Util::Windows::User.password_is?(name, password) end def [](attribute) native_user.Get(attribute) end def []=(attribute, value) native_user.Put(attribute, value) end def commit begin native_user.SetInfo unless native_user.nil? rescue Exception => e raise Puppet::Error.new( "User update failed: #{e}", e ) end self end def password_is?(password) self.class.logon(name, password) end def add_flag(flag_name, value) flag = native_user.Get(flag_name) rescue 0 native_user.Put(flag_name, flag | value) commit end def password=(password) native_user.SetPassword(password) commit fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 add_flag("UserFlags", fADS_UF_DONT_EXPIRE_PASSWD) end def groups # WIN32OLE objects aren't enumerable, so no map groups = [] native_user.Groups.each {|g| groups << g.Name} rescue nil groups end def add_to_groups(*group_names) group_names.each do |group_name| Puppet::Util::Windows::ADSI::Group.new(group_name).add_member_sids(sid) end end alias add_to_group add_to_groups def remove_from_groups(*group_names) group_names.each do |group_name| Puppet::Util::Windows::ADSI::Group.new(group_name).remove_member_sids(sid) end end alias remove_from_group remove_from_groups def set_groups(desired_groups, minimum = true) return if desired_groups.nil? or desired_groups.empty? desired_groups = desired_groups.split(',').map(&:strip) current_groups = self.groups # First we add the user to all the groups it should be in but isn't groups_to_add = desired_groups - current_groups add_to_groups(*groups_to_add) # Then we remove the user from all groups it is in but shouldn't be, if # that's been requested groups_to_remove = current_groups - desired_groups remove_from_groups(*groups_to_remove) unless minimum end def self.create(name) # Windows error 1379: The specified local group already exists. raise Puppet::Error.new( "Cannot create user if group '#{name}' exists." ) if Puppet::Util::Windows::ADSI::Group.exists? name new(name, Puppet::Util::Windows::ADSI.create(name, 'user')) end def self.exists?(name) Puppet::Util::Windows::ADSI::connectable?(User.uri(*User.parse_name(name))) end def self.delete(name) Puppet::Util::Windows::ADSI.delete(name, 'user') end def self.each(&block) wql = Puppet::Util::Windows::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"') users = [] wql.each do |u| users << new(u.name) end users.each(&block) end end class UserProfile def self.delete(sid) begin Puppet::Util::Windows::ADSI.wmi_connection.Delete("Win32_UserProfile.SID='#{sid}'") rescue => e # http://social.technet.microsoft.com/Forums/en/ITCG/thread/0f190051-ac96-4bf1-a47f-6b864bfacee5 # Prior to Vista SP1, there's no builtin way to programmatically # delete user profiles (except for delprof.exe). So try to delete # but warn if we fail raise e unless e.message.include?('80041010') Puppet.warning "Cannot delete user profile for '#{sid}' prior to Vista SP1" end end end class Group extend Enumerable attr_accessor :native_group attr_reader :name def initialize(name, native_group = nil) @name = name @native_group = native_group end def uri self.class.uri(name) end def self.uri(name, host = '.') if sid_uri = Puppet::Util::Windows::ADSI.sid_uri_safe(name) then return sid_uri end Puppet::Util::Windows::ADSI.uri(name, 'group', host) end def native_group @native_group ||= Puppet::Util::Windows::ADSI.connect(uri) end def commit begin native_group.SetInfo unless native_group.nil? rescue Exception => e raise Puppet::Error.new( "Group update failed: #{e}", e ) end self end def self.name_sid_hash(names) return [] if names.nil? or names.empty? sids = names.map do |name| sid = Puppet::Util::Windows::SID.name_to_sid_object(name) raise Puppet::Error.new( "Could not resolve username: #{name}" ) if !sid [sid.to_s, sid] end Hash[ sids ] end def add_members(*names) Puppet.deprecation_warning('Puppet::Util::Windows::ADSI::Group#add_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#add_member_sids') sids = self.class.name_sid_hash(names) add_member_sids(*sids.values) end alias add_member add_members def remove_members(*names) Puppet.deprecation_warning('Puppet::Util::Windows::ADSI::Group#remove_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#remove_member_sids') sids = self.class.name_sid_hash(names) remove_member_sids(*sids.values) end alias remove_member remove_members def add_member_sids(*sids) sids.each do |sid| native_group.Add(Puppet::Util::Windows::ADSI.sid_uri(sid)) end end def remove_member_sids(*sids) sids.each do |sid| native_group.Remove(Puppet::Util::Windows::ADSI.sid_uri(sid)) end end def members # WIN32OLE objects aren't enumerable, so no map members = [] native_group.Members.each {|m| members << m.Name} members end def member_sids sids = [] native_group.Members.each do |m| sids << Puppet::Util::Windows::SID.octet_string_to_sid_object(m.objectSID) end sids end def set_members(desired_members) return if desired_members.nil? or desired_members.empty? current_hash = Hash[ self.member_sids.map { |sid| [sid.to_s, sid] } ] desired_hash = self.class.name_sid_hash(desired_members) # First we add all missing members members_to_add = (desired_hash.keys - current_hash.keys).map { |sid| desired_hash[sid] } add_member_sids(*members_to_add) # Then we remove all extra members members_to_remove = (current_hash.keys - desired_hash.keys).map { |sid| current_hash[sid] } remove_member_sids(*members_to_remove) end def self.create(name) # Windows error 2224: The account already exists. raise Puppet::Error.new( "Cannot create group if user '#{name}' exists." ) if Puppet::Util::Windows::ADSI::User.exists? name new(name, Puppet::Util::Windows::ADSI.create(name, 'group')) end def self.exists?(name) Puppet::Util::Windows::ADSI.connectable?(Group.uri(name)) end def self.delete(name) Puppet::Util::Windows::ADSI.delete(name, 'group') end def self.each(&block) wql = Puppet::Util::Windows::ADSI.execquery( 'select name from win32_group where localaccount = "TRUE"' ) groups = [] wql.each do |g| groups << new(g.name) end groups.each(&block) end end end diff --git a/spec/unit/util/windows/sid_spec.rb b/spec/unit/util/windows/sid_spec.rb index 5a3d788c7..50f333a8d 100755 --- a/spec/unit/util/windows/sid_spec.rb +++ b/spec/unit/util/windows/sid_spec.rb @@ -1,166 +1,166 @@ #!/usr/bin/env ruby require 'spec_helper' describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? do if Puppet.features.microsoft_windows? require 'puppet/util/windows' end let(:subject) { Puppet::Util::Windows::SID } let(:sid) { Win32::Security::SID::LocalSystem } let(:invalid_sid) { 'bogus' } let(:unknown_sid) { 'S-0-0-0' } let(:unknown_name) { 'chewbacca' } context "#octet_string_to_sid_object" do it "should properly convert an array of bytes for the local Administrator SID" do host = '.' username = 'Administrator' admin = WIN32OLE.connect("WinNT://#{host}/#{username},user") converted = subject.octet_string_to_sid_object(admin.objectSID) converted.should == Win32::Security::SID.new(username, host) converted.should be_an_instance_of Win32::Security::SID end it "should properly convert an array of bytes for a well-known SID" do bytes = [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] converted = subject.octet_string_to_sid_object(bytes) converted.should == Win32::Security::SID.new('SYSTEM') converted.should be_an_instance_of Win32::Security::SID end it "should raise an error for non-array input" do expect { subject.octet_string_to_sid_object(invalid_sid) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for an empty byte array" do expect { subject.octet_string_to_sid_object([]) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for a malformed byte array" do expect { invalid_octet = [1] subject.octet_string_to_sid_object(invalid_octet) - }.to raise_error(Win32::Security::SID::Error, /No mapping between account names and security IDs was done./) + }.to raise_error(SystemCallError, /No mapping between account names and security IDs was done./) end end context "#name_to_sid" do it "should return nil if the account does not exist" do subject.name_to_sid(unknown_name).should be_nil end it "should accept unqualified account name" do subject.name_to_sid('SYSTEM').should == sid end it "should be case-insensitive" do subject.name_to_sid('SYSTEM').should == subject.name_to_sid('system') end it "should be leading and trailing whitespace-insensitive" do subject.name_to_sid('SYSTEM').should == subject.name_to_sid(' SYSTEM ') end it "should accept domain qualified account names" do subject.name_to_sid('NT AUTHORITY\SYSTEM').should == sid end it "should be the identity function for any sid" do subject.name_to_sid(sid).should == sid end end context "#name_to_sid_object" do it "should return nil if the account does not exist" do subject.name_to_sid_object(unknown_name).should be_nil end it "should return a Win32::Security::SID instance for any valid sid" do subject.name_to_sid_object(sid).should be_an_instance_of(Win32::Security::SID) end it "should accept unqualified account name" do subject.name_to_sid_object('SYSTEM').to_s.should == sid end it "should be case-insensitive" do subject.name_to_sid_object('SYSTEM').should == subject.name_to_sid_object('system') end it "should be leading and trailing whitespace-insensitive" do subject.name_to_sid_object('SYSTEM').should == subject.name_to_sid_object(' SYSTEM ') end it "should accept domain qualified account names" do subject.name_to_sid_object('NT AUTHORITY\SYSTEM').to_s.should == sid end end context "#sid_to_name" do it "should return nil if given a sid for an account that doesn't exist" do subject.sid_to_name(unknown_sid).should be_nil end it "should accept a sid" do subject.sid_to_name(sid).should == "NT AUTHORITY\\SYSTEM" end end context "#sid_ptr_to_string" do it "should raise if given an invalid sid" do expect { subject.sid_ptr_to_string(nil) }.to raise_error(Puppet::Error, /Invalid SID/) end it "should yield a valid sid pointer" do string = nil subject.string_to_sid_ptr(sid) do |ptr| string = subject.sid_ptr_to_string(ptr) end string.should == sid end end context "#string_to_sid_ptr" do it "should yield sid_ptr" do ptr = nil subject.string_to_sid_ptr(sid) do |p| ptr = p end ptr.should_not be_nil end it "should raise on an invalid sid" do expect { subject.string_to_sid_ptr(invalid_sid) }.to raise_error(Puppet::Error, /Failed to convert string SID/) end end context "#valid_sid?" do it "should return true for a valid SID" do subject.valid_sid?(sid).should be_true end it "should return false for an invalid SID" do subject.valid_sid?(invalid_sid).should be_false end it "should raise if the conversion fails" do subject.expects(:string_to_sid_ptr).with(sid). raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Windows::Error::ERROR_ACCESS_DENIED)) expect { subject.string_to_sid_ptr(sid) {|ptr| } }.to raise_error(Puppet::Util::Windows::Error, /Failed to convert string SID: #{sid}/) end end end