diff options
Diffstat (limited to 'lib/chef/util')
-rw-r--r-- | lib/chef/util/file_edit.rb | 132 | ||||
-rw-r--r-- | lib/chef/util/windows.rb | 56 | ||||
-rw-r--r-- | lib/chef/util/windows/net_group.rb | 101 | ||||
-rw-r--r-- | lib/chef/util/windows/net_use.rb | 121 | ||||
-rw-r--r-- | lib/chef/util/windows/net_user.rb | 198 | ||||
-rw-r--r-- | lib/chef/util/windows/volume.rb | 59 |
6 files changed, 667 insertions, 0 deletions
diff --git a/lib/chef/util/file_edit.rb b/lib/chef/util/file_edit.rb new file mode 100644 index 0000000000..ce37bdcdbf --- /dev/null +++ b/lib/chef/util/file_edit.rb @@ -0,0 +1,132 @@ +# +# Author:: Nuo Yan (<nuo@opscode.com>) +# Copyright:: Copyright (c) 2009 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# 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 'fileutils' +require 'tempfile' + +class Chef + class Util + class FileEdit + + private + + attr_accessor :original_pathname, :contents, :file_edited + + public + + def initialize(filepath) + @original_pathname = filepath + @file_edited = false + + raise ArgumentError, "File doesn't exist" unless File.exist? @original_pathname + raise ArgumentError, "File is blank" unless (@contents = File.new(@original_pathname).readlines).length > 0 + end + + #search the file line by line and match each line with the given regex + #if matched, replace the whole line with newline. + def search_file_replace_line(regex, newline) + search_match(regex, newline, 'r', 1) + end + + #search the file line by line and match each line with the given regex + #if matched, replace the match (all occurances) with the replace parameter + def search_file_replace(regex, replace) + search_match(regex, replace, 'r', 2) + end + + #search the file line by line and match each line with the given regex + #if matched, delete the line + def search_file_delete_line(regex) + search_match(regex, " ", 'd', 1) + end + + #search the file line by line and match each line with the given regex + #if matched, delete the match (all occurances) from the line + def search_file_delete(regex) + search_match(regex, " ", 'd', 2) + end + + #search the file line by line and match each line with the given regex + #if matched, insert newline after each matching line + def insert_line_after_match(regex, newline) + search_match(regex, newline, 'i', 1) + end + + #search the file line by line and match each line with the given regex + #if not matched, insert newline at the end of the file + def insert_line_if_no_match(regex, newline) + search_match(regex, newline, 'i', 2) + end + + #Make a copy of old_file and write new file out (only if file changed) + def write_file + + # file_edited is false when there was no match in the whole file and thus no contents have changed. + if file_edited + backup_pathname = original_pathname + ".old" + FileUtils.cp(original_pathname, backup_pathname, :preserve => true) + File.open(original_pathname, "w") do |newfile| + contents.each do |line| + newfile.puts(line) + end + newfile.flush + end + end + self.file_edited = false + end + + private + + #helper method to do the match, replace, delete, and insert operations + #command is the switch of delete, replace, and insert ('d', 'r', 'i') + #method is to control operation on whole line or only the match (1 for line, 2 for match) + def search_match(regex, replace, command, method) + + #convert regex to a Regexp object (if not already is one) and store it in exp. + exp = Regexp.new(regex) + + #loop through contents and do the appropriate operation depending on 'command' and 'method' + new_contents = [] + + contents.each do |line| + if line.match(exp) + self.file_edited = true + case + when command == 'r' + new_contents << ((method == 1) ? replace : line.gsub!(exp, replace)) + when command == 'd' + if method == 2 + new_contents << line.gsub!(exp, "") + end + when command == 'i' + new_contents << line + new_contents << replace unless method == 2 + end + else + new_contents << line + end + end + if command == 'i' && method == 2 && ! file_edited + new_contents << replace + self.file_edited = true + end + + self.contents = new_contents + end + end + end +end diff --git a/lib/chef/util/windows.rb b/lib/chef/util/windows.rb new file mode 100644 index 0000000000..cba2c2a1b7 --- /dev/null +++ b/lib/chef/util/windows.rb @@ -0,0 +1,56 @@ +# +# Author:: Doug MacEachern (<dougm@vmware.com>) +# Copyright:: Copyright (c) 2010 VMware, Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# +#requires: gem install windows-pr +require 'windows/api' +require 'windows/error' +require 'windows/handle' +require 'windows/unicode' +require 'windows/msvcrt/buffer' +require 'windows/msvcrt/string' +require 'windows/network/management' + +class Chef + class Util + class Windows + protected + + include ::Windows::Error + include ::Windows::Unicode + include ::Windows::MSVCRT::Buffer + include ::Windows::MSVCRT::String + include ::Windows::Network::Management + + PTR_SIZE = 4 #XXX 64-bit + + def lpwstr_to_s(buffer, offset) + str = 0.chr * (256 * 2) #XXX unhardcode this length (*2 for WCHAR) + wcscpy str, buffer[offset*PTR_SIZE,PTR_SIZE].unpack('L')[0] + wide_to_multi str + end + + def dword_to_i(buffer, offset) + buffer[offset*PTR_SIZE,PTR_SIZE].unpack('i')[0] || 0 + end + + #return pointer for use with pack('L') + def str_to_ptr(v) + [v].pack('p*').unpack('L')[0] + end + end + end +end diff --git a/lib/chef/util/windows/net_group.rb b/lib/chef/util/windows/net_group.rb new file mode 100644 index 0000000000..9da0dc6557 --- /dev/null +++ b/lib/chef/util/windows/net_group.rb @@ -0,0 +1,101 @@ +# +# Author:: Doug MacEachern (<dougm@vmware.com>) +# Copyright:: Copyright (c) 2010 VMware, Inc. +# License:: Apache License, Version 2.0 +# +# 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 'chef/util/windows' + +#wrapper around a subset of the NetGroup* APIs. +#nothing Chef specific, but not complete enough to be its own gem, so util for now. +class Chef::Util::Windows::NetGroup < Chef::Util::Windows + + private + + def pack_str(s) + [str_to_ptr(s)].pack('L') + end + + def modify_members(members, func) + buffer = 0.chr * (members.size * PTR_SIZE) + members.each_with_index do |member,offset| + buffer[offset*PTR_SIZE,PTR_SIZE] = pack_str(multi_to_wide(member)) + end + rc = func.call(nil, @name, 3, buffer, members.size) + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + end + + public + + def initialize(groupname) + @name = multi_to_wide(groupname) + end + + def local_get_members + group_members = [] + handle = 0.chr * PTR_SIZE + rc = ERROR_MORE_DATA + + while rc == ERROR_MORE_DATA + ptr = 0.chr * PTR_SIZE + nread = 0.chr * PTR_SIZE + total = 0.chr * PTR_SIZE + + rc = NetLocalGroupGetMembers.call(nil, @name, 1, ptr, -1, + nread, total, handle) + if (rc == NERR_Success) || (rc == ERROR_MORE_DATA) + ptr = ptr.unpack('L')[0] + nread = nread.unpack('i')[0] + members = 0.chr * (nread * (PTR_SIZE * 3)) #nread * sizeof(LOCALGROUP_MEMBERS_INFO_1) + memcpy(members, ptr, members.size) + + #3 pointer fields in LOCALGROUP_MEMBERS_INFO_1, offset 2*PTR_SIZE is lgrmi1_name + nread.times do |i| + offset = (i * 3) + 2 + member = lpwstr_to_s(members, offset) + group_members << member + end + NetApiBufferFree(ptr) + else + raise ArgumentError, get_last_error(rc) + end + end + group_members + end + + def local_add + rc = NetLocalGroupAdd.call(nil, 0, pack_str(@name), nil) + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + end + + def local_set_members(members) + modify_members(members, NetLocalGroupSetMembers) + end + + def local_add_members(members) + modify_members(members, NetLocalGroupAddMembers) + end + + def local_delete + rc = NetLocalGroupDel.call(nil, @name) + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + end +end diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb new file mode 100644 index 0000000000..1979e095bd --- /dev/null +++ b/lib/chef/util/windows/net_use.rb @@ -0,0 +1,121 @@ +# +# Author:: Doug MacEachern (<dougm@vmware.com>) +# Copyright:: Copyright (c) 2010 VMware, Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# + +#the Win32 Volume APIs do not support mapping network drives. not supported by WMI either. +#see also: WNetAddConnection2 and WNetAddConnection3 +#see also cmd.exe: net use /? + +require 'chef/util/windows' + +class Chef::Util::Windows::NetUse < Chef::Util::Windows + + private + + USE_NOFORCE = 0 + USE_FORCE = 1 + USE_LOTS_OF_FORCE = 2 #every windows API should support this flag + + USE_INFO_2 = [ + [:local, nil], + [:remote, nil], + [:password, nil], + [:status, 0], + [:asg_type, 0], + [:refcount, 0], + [:usecount, 0], + [:username, nil], + [:domainname, nil] + ] + + USE_INFO_2_TEMPLATE = + USE_INFO_2.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join + + SIZEOF_USE_INFO_2 = #sizeof(USE_INFO_2) + USE_INFO_2.inject(0){|sum,item| + sum + (item[1].class == Fixnum ? 4 : PTR_SIZE) + } + + def use_info_2(args) + USE_INFO_2.collect { |field| + args.include?(field[0]) ? args[field[0]] : field[1] + } + end + + def use_info_2_pack(use) + use.collect { |v| + v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v)) + }.pack(USE_INFO_2_TEMPLATE) + end + + def use_info_2_unpack(buffer) + use = Hash.new + USE_INFO_2.each_with_index do |field,offset| + use[field[0]] = field[1].class == Fixnum ? + dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset) + end + use + end + + public + + def initialize(localname) + @localname = localname + @name = multi_to_wide(localname) + end + + def add(args) + if args.class == String + remote = args + args = Hash.new + args[:remote] = remote + end + args[:local] ||= @localname + use = use_info_2(args) + buffer = use_info_2_pack(use) + rc = NetUseAdd.call(nil, 2, buffer, nil) + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + end + + def get_info + ptr = 0.chr * PTR_SIZE + rc = NetUseGetInfo.call(nil, @name, 2, ptr) + + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + + ptr = ptr.unpack('L')[0] + buffer = 0.chr * SIZEOF_USE_INFO_2 + memcpy(buffer, ptr, buffer.size) + NetApiBufferFree(ptr) + use_info_2_unpack(buffer) + end + + def device + get_info()[:remote] + end + #XXX should we use some FORCE here? + def delete + rc = NetUseDel.call(nil, @name, USE_NOFORCE) + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + end +end diff --git a/lib/chef/util/windows/net_user.rb b/lib/chef/util/windows/net_user.rb new file mode 100644 index 0000000000..97d8f33834 --- /dev/null +++ b/lib/chef/util/windows/net_user.rb @@ -0,0 +1,198 @@ +# +# Author:: Doug MacEachern (<dougm@vmware.com>) +# Copyright:: Copyright (c) 2010 VMware, Inc. +# License:: Apache License, Version 2.0 +# +# 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 'chef/util/windows' + +#wrapper around a subset of the NetUser* APIs. +#nothing Chef specific, but not complete enough to be its own gem, so util for now. +class Chef::Util::Windows::NetUser < Chef::Util::Windows + + private + + LogonUser = Windows::API.new('LogonUser', 'SSSLLP', 'I', 'advapi32') + + DOMAIN_GROUP_RID_USERS = 0x00000201 + + UF_SCRIPT = 0x000001 + UF_ACCOUNTDISABLE = 0x000002 + UF_PASSWD_CANT_CHANGE = 0x000040 + UF_NORMAL_ACCOUNT = 0x000200 + UF_DONT_EXPIRE_PASSWD = 0x010000 + + #[:symbol_name, default_val] + #default_val duals as field type + #array index duals as structure offset + USER_INFO_3 = [ + [:name, nil], + [:password, nil], + [:password_age, 0], + [:priv, 0], #"The NetUserAdd and NetUserSetInfo functions ignore this member" + [:home_dir, nil], + [:comment, nil], + [:flags, UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_NORMAL_ACCOUNT], + [:script_path, nil], + [:auth_flags, 0], + [:full_name, nil], + [:user_comment, nil], + [:parms, nil], + [:workstations, nil], + [:last_logon, 0], + [:last_logoff, 0], + [:acct_expires, -1], + [:max_storage, -1], + [:units_per_week, 0], + [:logon_hours, nil], + [:bad_pw_count, 0], + [:num_logons, 0], + [:logon_server, nil], + [:country_code, 0], + [:code_page, 0], + [:user_id, 0], + [:primary_group_id, DOMAIN_GROUP_RID_USERS], + [:profile, nil], + [:home_dir_drive, nil], + [:password_expired, 0] + ] + + USER_INFO_3_TEMPLATE = + USER_INFO_3.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join + + SIZEOF_USER_INFO_3 = #sizeof(USER_INFO_3) + USER_INFO_3.inject(0){|sum,item| + sum + (item[1].class == Fixnum ? 4 : PTR_SIZE) + } + + def user_info_3(args) + USER_INFO_3.collect { |field| + args.include?(field[0]) ? args[field[0]] : field[1] + } + end + + def user_info_3_pack(user) + user.collect { |v| + v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v)) + }.pack(USER_INFO_3_TEMPLATE) + end + + def user_info_3_unpack(buffer) + user = Hash.new + USER_INFO_3.each_with_index do |field,offset| + user[field[0]] = field[1].class == Fixnum ? + dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset) + end + user + end + + def set_info(args) + user = user_info_3(args) + buffer = user_info_3_pack(user) + rc = NetUserSetInfo.call(nil, @name, 3, buffer, nil) + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + end + + public + + def initialize(username) + @username = username + @name = multi_to_wide(username) + end + + LOGON32_PROVIDER_DEFAULT = 0 + LOGON32_LOGON_NETWORK = 3 + #XXX for an extra painful alternative, see: http://support.microsoft.com/kb/180548 + def validate_credentials(passwd) + token = 0.chr * PTR_SIZE + res = LogonUser.call(@username, nil, passwd, + LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, token) + if res == 0 + return false + end + ::Windows::Handle::CloseHandle.call(token.unpack('L')[0]) + return true + end + + def get_info + ptr = 0.chr * PTR_SIZE + rc = NetUserGetInfo.call(nil, @name, 3, ptr) + + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + + ptr = ptr.unpack('L')[0] + buffer = 0.chr * SIZEOF_USER_INFO_3 + memcpy(buffer, ptr, buffer.size) + NetApiBufferFree(ptr) + user_info_3_unpack(buffer) + end + + def add(args) + user = user_info_3(args) + buffer = user_info_3_pack(user) + + rc = NetUserAdd.call(nil, 3, buffer, rc) + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + + #usri3_primary_group_id: + #"When you call the NetUserAdd function, this member must be DOMAIN_GROUP_RID_USERS" + NetLocalGroupAddMembers(nil, multi_to_wide("Users"), 3, buffer[0,PTR_SIZE], 1) + end + + def user_modify(&proc) + user = get_info + user[:last_logon] = user[:units_per_week] = 0 #ignored as per USER_INFO_3 doc + user[:logon_hours] = nil #PBYTE field; \0 == no changes + proc.call(user) + set_info(user) + end + + def update(args) + user_modify do |user| + args.each do |key,val| + user[key] = val + end + end + end + + def delete + rc = NetUserDel.call(nil, @name) + if rc != NERR_Success + raise ArgumentError, get_last_error(rc) + end + end + + def disable_account + user_modify do |user| + user[:flags] |= UF_ACCOUNTDISABLE + end + end + + def enable_account + user_modify do |user| + user[:flags] &= ~UF_ACCOUNTDISABLE + end + end + + def check_enabled + (get_info()[:flags] & UF_ACCOUNTDISABLE) != 0 + end +end diff --git a/lib/chef/util/windows/volume.rb b/lib/chef/util/windows/volume.rb new file mode 100644 index 0000000000..11f8e080b3 --- /dev/null +++ b/lib/chef/util/windows/volume.rb @@ -0,0 +1,59 @@ +# +# Author:: Doug MacEachern (<dougm@vmware.com>) +# Copyright:: Copyright (c) 2010 VMware, Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# + +#simple wrapper around Volume APIs. might be possible with WMI, but possibly more complex. + +require 'chef/util/windows' +require 'windows/volume' + +class Chef::Util::Windows::Volume < Chef::Util::Windows + + private + include Windows::Volume + #XXX not defined in the current windows-pr release + DeleteVolumeMountPoint = + Windows::API.new('DeleteVolumeMountPoint', 'S', 'B') unless defined? DeleteVolumeMountPoint + + public + + def initialize(name) + name += "\\" unless name =~ /\\$/ #trailing slash required + @name = name + end + + def device + buffer = 0.chr * 256 + if GetVolumeNameForVolumeMountPoint(@name, buffer, buffer.size) + return buffer[0,buffer.size].unpack("Z*")[0] + else + raise ArgumentError, get_last_error + end + end + + def delete + unless DeleteVolumeMountPoint.call(@name) + raise ArgumentError, get_last_error + end + end + + def add(device) + unless SetVolumeMountPoint(@name, device) + raise ArgumentError, get_last_error + end + end +end |