summaryrefslogtreecommitdiff
path: root/lib/chef/util
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/util')
-rw-r--r--lib/chef/util/file_edit.rb132
-rw-r--r--lib/chef/util/windows.rb56
-rw-r--r--lib/chef/util/windows/net_group.rb101
-rw-r--r--lib/chef/util/windows/net_use.rb121
-rw-r--r--lib/chef/util/windows/net_user.rb198
-rw-r--r--lib/chef/util/windows/volume.rb59
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