summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerdar Sutay <serdar@opscode.com>2014-08-13 09:05:27 -0700
committerSerdar Sutay <serdar@opscode.com>2014-08-15 16:42:33 -0700
commit62806a8363e22d3f5fa26a4e081b9aaaeab12480 (patch)
treec5cfd1c331d42e0c6d797e46860a7d1761fe2ead
parent128c2be3ac24e99504890d5e20a5392ad0931f01 (diff)
downloadchef-62806a8363e22d3f5fa26a4e081b9aaaeab12480.tar.gz
Merge pull request #1800 from opscode/sersut/chef#1634
DSCL User Provider Rewrite Conflicts: .gitignore CHANGELOG.md RELEASE_NOTES.md chef.gemspec chef/lib/chef/provider/user/dscl.rb chef/spec/unit/provider/user/dscl_spec.rb spec/functional/resource/user/useradd_spec.rb spec/unit/provider/user/useradd_spec.rb
-rw-r--r--.gitignore4
-rw-r--r--CHANGELOG.md9
-rw-r--r--RELEASE_NOTES.md58
-rw-r--r--chef/chef.gemspec1
-rw-r--r--chef/lib/chef/exceptions.rb1
-rw-r--r--chef/lib/chef/provider/user/dscl.rb740
-rw-r--r--chef/lib/chef/resource/user.rb18
-rw-r--r--chef/spec/data/mac_users/10.7-8.plist.xml559
-rw-r--r--chef/spec/data/mac_users/10.7-8.shadow.xml11
-rw-r--r--chef/spec/data/mac_users/10.7.plist.xml559
-rw-r--r--chef/spec/data/mac_users/10.7.shadow.xml11
-rw-r--r--chef/spec/data/mac_users/10.8.plist.xml559
-rw-r--r--chef/spec/data/mac_users/10.8.shadow.xml21
-rw-r--r--chef/spec/data/mac_users/10.9.plist.xml560
-rw-r--r--chef/spec/data/mac_users/10.9.shadow.xml21
-rw-r--r--chef/spec/functional/resource/user/dscl_spec.rb199
-rw-r--r--chef/spec/unit/provider/user/dscl_spec.rb989
17 files changed, 3851 insertions, 469 deletions
diff --git a/.gitignore b/.gitignore
index 82b8f311cc..a71e3f5c84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,7 @@ binstubs/
# RVM and RBENV ruby version files
.rbenv-version
.rvmrc
+
+# Vagrant
+Vagrantfile
+.vagrant/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4425d3330c..b9550fc4d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,14 +2,19 @@
## 10.34.0
+* [**Phil Dibowitz**](https://github.com/jaymzh):
+ 'group' provider on OSX properly uses 'dscl' to determine existing groups
+
+
+
* `options` attribute of mount resource now supports lazy evaluation. (CHEF-5163)
* Fix OS X service provider actions that don't require the service label
to work when there is no plist. (backport CHEF-5223)
* Set Net::HTTP open_timeout. (backport Chef-1585)
* Fix RPM package version detection (backport Issue 1554)
* Support for single letter environments.
-* [**Phil Dibowitz**](https://github.com/jaymzh):
- 'group' provider on OSX properly uses 'dscl' to determine existing groups
+* Add password setting support for Mac 10.7, 10.8 and 10.9 to the dscl user provider.
+
## 10.32.2
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index c813e2d2fa..0116cafa1f 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,14 +1,52 @@
-<!---
-This file is reset every time a new release is done. The contents of this file are for the currently unreleased version.
+# Chef Client 10.34.0 Release Notes:
-Example Note:
+## DSCL user provider now supports Mac OS X 10.7 and above.
-## Example Heading
-Details about the thing that changed that needs to get included in the Release Notes in markdown.
--->
-# Chef Client 10.x Release Notes:
+DSCL user provider in Chef has supported setting passwords only on Mac OS X 10.6. In this release, Mac OS X versions 10.7 and above are now supported. Support for Mac OS X 10.6 is dropped from the dscl provider since this version is EOLed by Apple.
-* Backport CHEF-5223 Fix to 10-stable
-* 'group' provider on OSX properly uses 'dscl' to determine existing groups
+In order to support configuring passwords for the users using shadow hashes two new attributes `salt` & `iterations` are added to the user resource. These attributes are required to make the new [SALTED-SHA512-PBKDF2](http://en.wikipedia.org/wiki/PBKDF2) style shadow hashes used in Mac OS X versions 10.8 and above.
-# Chef Client 10.x Breaking Changes:
+User resource on Mac supports setting password both using plain-text password or using the shadow hash. You can simply set the `password` attribute to the plain text password to configure the password for the user. However this is not ideal since including plain text passwords in cookbooks (even if they are private) is not a good idea. In order to set passwords using shadow hash you can follow the instructions below based on your Mac OS X version.
+
+### Mac OS X 10.7
+
+10.7 calculates the password hash using **SALTED-SHA512**. Stored shadow hash length is 68 bytes; first 4 bytes being salt and the next 64 bytes being the shadow hash itself. You can use below code in order to calculate password hashes to be used in `password` attribute on Mac OS X 10.7:
+
+```
+password = "my_awesome_password"
+salt = OpenSSL::Random.random_bytes(4)
+encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + password)
+shadow_hash = salt.unpack('H*').first + encoded_password
+
+# You can use this value in your recipes as below:
+
+user "my_awesome_user" do
+ password "c9b3bd....d843" # Length: 136
+end
+```
+### Mac OS X 10.8 and above
+
+10.7 calculates the password hash using **SALTED-SHA512-PBKDF2**. Stored shadow hash length is 128 bytes. In addition to the shadow hash value, `salt` (32 bytes) and `iterations` (integer) is stored on the system. You can use below code in order to calculate password hashes on Mac OS X 10.8 and above:
+
+```
+password = "my_awesome_password"
+salt = OpenSSL::Random.random_bytes(32)
+iterations = 25000 # Any value above 20k should be fine.
+
+shadow_hash = OpenSSL::PKCS5::pbkdf2_hmac(
+ password,
+ salt,
+ iterations,
+ 128,
+ OpenSSL::Digest::SHA512.new
+).unpack('H*').first
+salt_value = salt.unpack('H*').first
+
+# You can use this value in your recipes as below:
+
+user "my_awesome_user" do
+ password "cbd1a....fc843" # Length: 256
+ salt "bd1a....fc83" # Length: 64
+ iterations 25000
+end
+```
diff --git a/chef/chef.gemspec b/chef/chef.gemspec
index eabb93fa54..c5fb15cbc2 100644
--- a/chef/chef.gemspec
+++ b/chef/chef.gemspec
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
# 1.9.2+), so we have to add an additional pin. 1.16 is chosen just becuase
# it's the version I had when I tested.
s.add_dependency "mime-types", "~> 1.16"
+ s.add_dependency 'plist', '~> 3.1.0'
s.add_dependency "bunny", ">= 0.6.0", "< 0.8.0"
# The JSON gem reliably releases breaking changes as a patch release
diff --git a/chef/lib/chef/exceptions.rb b/chef/lib/chef/exceptions.rb
index 38b2e8fa79..0725cd2551 100644
--- a/chef/lib/chef/exceptions.rb
+++ b/chef/lib/chef/exceptions.rb
@@ -60,6 +60,7 @@ class Chef
class RequestedUIDUnavailable < RuntimeError; end
class InvalidHomeDirectory < ArgumentError; end
class DsclCommandFailed < RuntimeError; end
+ class PlistUtilCommandFailed < RuntimeError; end
class UserIDNotFound < ArgumentError; end
class GroupIDNotFound < ArgumentError; end
class ConflictingMembersInGroup < ArgumentError; end
diff --git a/chef/lib/chef/provider/user/dscl.rb b/chef/lib/chef/provider/user/dscl.rb
index 5e8071394f..8f56859fe4 100644
--- a/chef/lib/chef/provider/user/dscl.rb
+++ b/chef/lib/chef/provider/user/dscl.rb
@@ -6,9 +6,9 @@
# 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.
@@ -16,239 +16,282 @@
# limitations under the License.
#
-require 'chef/mixin/shell_out'
+require 'mixlib/shellout'
require 'chef/provider/user'
require 'openssl'
+require 'plist'
class Chef
class Provider
class User
+ #
+ # The most tricky bit of this provider is the way it deals with user passwords.
+ # Mac OS X has different password shadow calculations based on the version.
+ # < 10.7 => password shadow calculation format SALTED-SHA1
+ # => stored in: /var/db/shadow/hash/#{guid}
+ # => shadow binary length 68 bytes
+ # => First 4 bytes salt / Next 64 bytes shadow value
+ # = 10.7 => password shadow calculation format SALTED-SHA512
+ # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist
+ # => shadow binary length 68 bytes
+ # => First 4 bytes salt / Next 64 bytes shadow value
+ # > 10.7 => password shadow calculation format SALTED-SHA512-PBKDF2
+ # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist
+ # => shadow binary length 128 bytes
+ # => Salt / Iterations are stored seperately in the same file
+ #
+ # This provider only supports Mac OSX versions 10.7 and above
class Dscl < Chef::Provider::User
- include Chef::Mixin::ShellOut
-
- NFS_HOME_DIRECTORY = %r{^NFSHomeDirectory: (.*)$}
- AUTHENTICATION_AUTHORITY = %r{^AuthenticationAuthority: (.*)$}
-
- def dscl(*args)
- shell_out("dscl . -#{args.join(' ')}")
- end
- def safe_dscl(*args)
- result = dscl(*args)
- return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
- raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") unless result.exitstatus == 0
- raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
- return result.stdout
- end
+ def define_resource_requirements
+ super
- # This is handled in providers/group.rb by Etc.getgrnam()
- # def user_exists?(user)
- # users = safe_dscl("list /Users")
- # !! ( users =~ Regexp.new("\n#{user}\n") )
- # end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { mac_osx_version_less_than_10_7? == false }
+ a.failure_message(Chef::Exceptions::User, "Chef::Provider::User::Dscl only supports Mac OS X versions 10.7 and above.")
+ end
- # get a free UID greater than 200
- def get_free_uid(search_limit=1000)
- uid = nil; next_uid_guess = 200
- users_uids = safe_dscl("list /Users uid")
- while(next_uid_guess < search_limit + 200)
- if users_uids =~ Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n")
- next_uid_guess += 1
- else
- uid = next_uid_guess
- break
- end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/bin/dscl") }
+ a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{@new_resource}!")
end
- return uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
- end
- def uid_used?(uid)
- return false unless uid
- users_uids = safe_dscl("list /Users uid")
- !! ( users_uids =~ Regexp.new("#{Regexp.escape(uid.to_s)}\n") )
- end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ::File.exists?("/usr/bin/plutil") }
+ a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{@new_resource}!")
+ end
- def set_uid
- @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
- if uid_used?(@new_resource.uid)
- raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
+ requirements.assert(:create, :modify, :manage) do |a|
+ a.assertion do
+ if @new_resource.password && mac_osx_version_greater_than_10_7?
+ # SALTED-SHA512 password shadow hashes are not supported on 10.8 and above.
+ !salted_sha512?(@new_resource.password)
+ else
+ true
+ end
+ end
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512 passwords are not supported on Mac 10.8 and above. \
+If you want to set the user password using shadow info make sure you specify a SALTED-SHA512-PBKDF2 shadow hash \
+in 'password', with the associated 'salt' and 'iterations'.")
end
- safe_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
- end
- def modify_home
- return safe_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory") if (@new_resource.home.nil? || @new_resource.home.empty?)
- if @new_resource.supports[:manage_home]
- validate_home_dir_specification!
-
- if (@current_resource.home == @new_resource.home) && !new_home_exists?
- ditto_home
- elsif !current_home_exists? && !new_home_exists?
- ditto_home
- elsif current_home_exists?
- move_home
+ requirements.assert(:create, :modify, :manage) do |a|
+ a.assertion do
+ if @new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(@new_resource.password)
+ # salt and iterations should be specified when
+ # SALTED-SHA512-PBKDF2 password shadow hash is given
+ !@new_resource.salt.nil? && !@new_resource.iterations.nil?
+ else
+ true
+ end
end
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hash is given without associated \
+'salt' and 'iterations'. Please specify 'salt' and 'iterations' in order to set the user password using shadow hash.")
end
- safe_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
- end
- def osx_shadow_hash?(string)
- return !! ( string =~ /^[[:xdigit:]]{1240}$/ )
- end
+ requirements.assert(:create, :modify, :manage) do |a|
+ a.assertion do
+ if @new_resource.password && !mac_osx_version_greater_than_10_7?
+ # On 10.7 SALTED-SHA512-PBKDF2 is not supported
+ !salted_sha512_pbkdf2?(@new_resource.password)
+ else
+ true
+ end
+ end
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hashes are not supported on \
+Mac OS X version 10.7. Please specify a SALTED-SHA512 shadow hash in 'password' attribute to set the \
+user password using shadow hash.")
+ end
- def osx_salted_sha1?(string)
- return !! ( string =~ /^[[:xdigit:]]{48}$/ )
end
- def guid
- safe_dscl("read /Users/#{@new_resource.username} GeneratedUID").gsub(/GeneratedUID: /,"").strip
- end
+ def load_current_resource
+ @current_resource = Chef::Resource::User.new(@new_resource.username)
+ @current_resource.username(@new_resource.username)
- def shadow_hash_set?
- user_data = safe_dscl("read /Users/#{@new_resource.username}")
- if user_data =~ /AuthenticationAuthority: / && user_data =~ /ShadowHash/
- true
- else
- false
- end
- end
+ user_info = read_user_info
+ if user_info
+ @current_resource.uid(dscl_get(user_info, :uid))
+ @current_resource.gid(dscl_get(user_info, :gid))
+ @current_resource.home(dscl_get(user_info, :home))
+ @current_resource.shell(dscl_get(user_info, :shell))
+ @current_resource.comment(dscl_get(user_info, :comment))
+ @authentication_authority = dscl_get(user_info, :auth_authority)
- def modify_password
- if @new_resource.password
- shadow_hash = nil
-
- Chef::Log.debug("#{new_resource} updating password")
- if osx_shadow_hash?(@new_resource.password)
- shadow_hash = @new_resource.password.upcase
- else
- if osx_salted_sha1?(@new_resource.password)
- salted_sha1 = @new_resource.password.upcase
+ if @new_resource.password && dscl_get(user_info, :password) == "********"
+ # A password is set. Let's get the password information from shadow file
+ shadow_hash_binary = dscl_get(user_info, :shadow_hash)
+
+ # Calling shell_out directly since we want to give an input stream
+ shadow_hash_xml = convert_binary_plist_to_xml(shadow_hash_binary.string)
+ shadow_hash = Plist::parse_xml(shadow_hash_xml)
+
+ if shadow_hash["SALTED-SHA512"]
+ # Convert the shadow value from Base64 encoding to hex before consuming them
+ @password_shadow_conversion_algorithm = "SALTED-SHA512"
+ @current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack('H*').first)
+ elsif shadow_hash["SALTED-SHA512-PBKDF2"]
+ @password_shadow_conversion_algorithm = "SALTED-SHA512-PBKDF2"
+ # Convert the entropy from Base64 encoding to hex before consuming them
+ @current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first)
+ @current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"])
+ # Convert the salt from Base64 encoding to hex before consuming them
+ @current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack('H*').first)
else
- hex_salt = ""
- OpenSSL::Random.random_bytes(10).each_byte { |b| hex_salt << b.to_i.to_s(16) }
- hex_salt = hex_salt.slice(0...8)
- salt = [hex_salt].pack("H*")
- sha1 = ::OpenSSL::Digest::SHA1.hexdigest(salt+@new_resource.password)
- salted_sha1 = (hex_salt+sha1).upcase
+ raise(Chef::Exceptions::User,"Unknown shadow_hash format: #{shadow_hash.keys.join(' ')}")
end
- shadow_hash = String.new("00000000"*155)
- shadow_hash[168] = salted_sha1
- end
-
- ::File.open("/var/db/shadow/hash/#{guid}",'w',0600) do |output|
- output.puts shadow_hash
- end
-
- unless shadow_hash_set?
- safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';ShadowHash;'")
end
+
+ convert_group_name if @new_resource.gid
+ else
+ @user_exists = false
+ Chef::Log.debug("#{@new_resource} user does not exist")
end
- end
- def load_current_resource
- super
- raise Chef::Exceptions::User, "Could not find binary /usr/bin/dscl for #{@new_resource}" unless ::File.exists?("/usr/bin/dscl")
+ @current_resource
end
+ #
+ # Provider Actions
+ #
+
def create_user
dscl_create_user
dscl_create_comment
- set_uid
+ dscl_set_uid
dscl_set_gid
- modify_home
+ dscl_set_home
dscl_set_shell
- modify_password
+ set_password
end
-
+
def manage_user
dscl_create_user if diverged?(:username)
dscl_create_comment if diverged?(:comment)
- set_uid if diverged?(:uid)
+ dscl_set_uid if diverged?(:uid)
dscl_set_gid if diverged?(:gid)
- modify_home if diverged?(:home)
+ dscl_set_home if diverged?(:home)
dscl_set_shell if diverged?(:shell)
- modify_password if diverged?(:password)
+ set_password if diverged_password?
end
-
+
+ #
+ # Action Helpers
+ #
+
+ #
+ # Create a user using dscl
+ #
def dscl_create_user
- safe_dscl("create /Users/#{@new_resource.username}")
+ run_dscl("create /Users/#{@new_resource.username}")
end
-
+
+ #
+ # Saves the specified Chef user `comment` into RealName attribute
+ # of Mac user.
+ #
def dscl_create_comment
- safe_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
+ run_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
end
-
- def dscl_set_gid
- safe_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
- end
-
- def dscl_set_shell
- if @new_resource.password || ::File.exists?("#{@new_resource.shell}")
- safe_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
- else
- safe_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
+
+ #
+ # Sets the user id for the user using dscl.
+ # If a `uid` is not specified, it finds the next available one starting
+ # from 200 if `system` is set, 500 otherwise.
+ #
+ def dscl_set_uid
+ @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
+
+ if uid_used?(@new_resource.uid)
+ raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
end
+
+ run_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
end
-
- def remove_user
- if @new_resource.supports[:manage_home]
- user_info = safe_dscl("read /Users/#{@new_resource.username}")
- if nfs_home_match = user_info.match(NFS_HOME_DIRECTORY)
- #nfs_home = safe_dscl("read /Users/#{@new_resource.username} NFSHomeDirectory")
- #nfs_home.gsub!(/NFSHomeDirectory: /,"").gsub!(/\n$/,"")
- nfs_home = nfs_home_match[1]
- FileUtils.rm_rf(nfs_home)
+
+ #
+ # Find the next available uid on the system. starting with 200 if `system` is set,
+ # 500 otherwise.
+ #
+ def get_free_uid(search_limit=1000)
+ uid = nil
+ base_uid = @new_resource.system ? 200 : 500
+ next_uid_guess = base_uid
+ users_uids = run_dscl("list /Users uid")
+ while(next_uid_guess < search_limit + base_uid)
+ if users_uids =~ Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n")
+ next_uid_guess += 1
+ else
+ uid = next_uid_guess
+ break
end
end
- # remove the user from its groups
- groups = []
- Etc.group do |group|
- groups << group.name if group.mem.include?(@new_resource.username)
- end
- groups.each do |group_name|
- safe_dscl("delete /Groups/#{group_name} GroupMembership '#{@new_resource.username}'")
- end
- # remove user account
- safe_dscl("delete /Users/#{@new_resource.username}")
+ return uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
end
- def locked?
- user_info = safe_dscl("read /Users/#{@new_resource.username}")
- if auth_authority_md = AUTHENTICATION_AUTHORITY.match(user_info)
- !!(auth_authority_md[1] =~ /DisabledUser/ )
- else
- false
- end
- end
-
- def check_lock
- return @locked = locked?
+ #
+ # Returns true if uid is in use by a different account, false otherwise.
+ #
+ def uid_used?(uid)
+ return false unless uid
+ users_uids = run_dscl("list /Users uid")
+ !! ( users_uids =~ Regexp.new("#{Regexp.escape(uid.to_s)}\n") )
end
- def lock_user
- safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
+ #
+ # Sets the group id for the user using dscl. Fails if a group doesn't
+ # exist on the system with given group id.
+ #
+ def dscl_set_gid
+ unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
+ begin
+ possible_gid = run_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
+ rescue Chef::Exceptions::DsclCommandFailed => e
+ raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
+ end
+ @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
+ end
+ run_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
end
-
- def unlock_user
- auth_info = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority")
- auth_string = auth_info.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip#.gsub!(/[; ]*$/,"")
- safe_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
+
+ #
+ # Sets the home directory for the user. If `:manage_home` is set home
+ # directory is managed (moved / created) for the user.
+ #
+ def dscl_set_home
+ if @new_resource.home.nil? || @new_resource.home.empty?
+ run_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory")
+ return
+ end
+
+ if @new_resource.supports[:manage_home]
+ validate_home_dir_specification!
+
+ if (@current_resource.home == @new_resource.home) && !new_home_exists?
+ ditto_home
+ elsif !current_home_exists? && !new_home_exists?
+ ditto_home
+ elsif current_home_exists?
+ move_home
+ end
+ end
+ run_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
end
-
+
def validate_home_dir_specification!
unless @new_resource.home =~ /^\//
- raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
+ raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
end
end
-
+
def current_home_exists?
::File.exist?("#{@current_resource.home}")
end
-
+
def new_home_exists?
- ::File.exist?("#{@new_resource.home}")
+ ::File.exist?("#{@new_resource.home}")
end
-
+
def ditto_home
skel = "/System/Library/User Template/English.lproj"
raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
@@ -258,7 +301,7 @@ class Chef
def move_home
Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
-
+
src = @current_resource.home
FileUtils.mkdir_p(@new_resource.home)
files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
@@ -266,14 +309,365 @@ class Chef
::FileUtils.rmdir(src)
::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
end
-
+
+ #
+ # Sets the shell for the user using dscl.
+ #
+ def dscl_set_shell
+ if @new_resource.shell || ::File.exists?("#{@new_resource.shell}")
+ run_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
+ else
+ run_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
+ end
+ end
+
+ #
+ # Sets the password for the user based on given password parameters.
+ # Chef supports specifying plain-text passwords and password shadow
+ # hash data.
+ #
+ def set_password
+ # Return if there is no password to set
+ return if @new_resource.password.nil?
+
+ shadow_info = prepare_password_shadow_info
+
+ # Shadow info is saved as binary plist. Convert the info to binary plist.
+ shadow_info_binary = StringIO.new
+ command = Mixlib::ShellOut.new("plutil -convert binary1 -o - -",
+ :input => shadow_info.to_plist, :live_stream => shadow_info_binary)
+ command.run_command
+
+ # Replace the shadow info in user's plist
+ user_info = read_user_info
+ dscl_set(user_info, :shadow_hash, shadow_info_binary)
+
+ #
+ # Before saving the user's plist file we need to wait for dscl to
+ # update its caches and flush them to disk. In order to achieve this
+ # we need to wait first for our changes to get into the dscl cache
+ # and then flush the cache to disk before saving password into the
+ # plist file. 3 seconds is the minimum experimental value for dscl
+ # cache to be updated. We can get rid of this sleep when we find a
+ # trigger to update dscl cache.
+ #
+ sleep 3
+ shell_out("dscacheutil '-flushcache'")
+ save_user_info(user_info)
+ end
+
+ #
+ # Prepares the password shadow info based on the platform version.
+ #
+ def prepare_password_shadow_info
+ shadow_info = { }
+ entropy = nil
+ salt = nil
+ iterations = nil
+
+ if mac_osx_version_10_7?
+ hash_value = if salted_sha512?(@new_resource.password)
+ @new_resource.password
+ else
+ # Create a random 4 byte salt
+ salt = OpenSSL::Random.random_bytes(4)
+ encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + @new_resource.password)
+ hash_value = salt.unpack('H*').first + encoded_password
+ end
+
+ shadow_info["SALTED-SHA512"] = StringIO.new
+ shadow_info["SALTED-SHA512"].string = convert_to_binary(hash_value)
+ shadow_info
+ else
+ if salted_sha512_pbkdf2?(@new_resource.password)
+ entropy = convert_to_binary(@new_resource.password)
+ salt = convert_to_binary(@new_resource.salt)
+ iterations = @new_resource.iterations
+ else
+ salt = OpenSSL::Random.random_bytes(32)
+ iterations = @new_resource.iterations # Use the default if not specified by the user
+
+ entropy = OpenSSL::PKCS5::pbkdf2_hmac(
+ @new_resource.password,
+ salt,
+ iterations,
+ 128,
+ OpenSSL::Digest::SHA512.new
+ )
+ end
+
+ pbkdf_info = { }
+ pbkdf_info["entropy"] = StringIO.new
+ pbkdf_info["entropy"].string = entropy
+ pbkdf_info["salt"] = StringIO.new
+ pbkdf_info["salt"].string = salt
+ pbkdf_info["iterations"] = iterations
+
+ shadow_info["SALTED-SHA512-PBKDF2"] = pbkdf_info
+ end
+
+ shadow_info
+ end
+
+ #
+ # Removes the user from the system after removing user from his groups
+ # and deleting home directory if needed.
+ #
+ def remove_user
+ if @new_resource.supports[:manage_home]
+ # Remove home directory
+ FileUtils.rm_rf(@current_resource.home)
+ end
+
+ # Remove the user from its groups
+ run_dscl("list /Groups").each_line do |group|
+ if member_of_group?(group.chomp)
+ run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{@new_resource.username}'")
+ end
+ end
+
+ # Remove user account
+ run_dscl("delete /Users/#{@new_resource.username}")
+ end
+
+ #
+ # Locks the user.
+ #
+ def lock_user
+ run_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
+ end
+
+ #
+ # Unlocks the user
+ #
+ def unlock_user
+ auth_string = @authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip
+ run_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
+ end
+
+ #
+ # Returns true if the user is locked, false otherwise.
+ #
+ def locked?
+ if @authentication_authority
+ !!(@authentication_authority =~ /DisabledUser/ )
+ else
+ false
+ end
+ end
+
+ #
+ # This is the interface base User provider requires to provide idempotency.
+ #
+ def check_lock
+ return @locked = locked?
+ end
+
+ #
+ # Helper functions
+ #
+
+ #
+ # Returns true if the system state and desired state is different for
+ # given attribute.
+ #
def diverged?(parameter)
parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
end
-
+
def parameter_updated?(parameter)
not (@new_resource.send(parameter) == @current_resource.send(parameter))
end
+
+ #
+ # We need a special check function for password since we support both
+ # plain text and shadow hash data.
+ #
+ # Checks if password needs update based on platform version and the
+ # type of the password specified.
+ #
+ def diverged_password?
+ return false if @new_resource.password.nil?
+
+ # Dscl provider supports both plain text passwords and shadow hashes.
+ if mac_osx_version_10_7?
+ if salted_sha512?(@new_resource.password)
+ diverged?(:password)
+ else
+ !salted_sha512_password_match?
+ end
+ else
+ # When a system is upgraded to a version 10.7+ shadow hashes of the users
+ # will be updated when the user logs in. So it's possible that we will have
+ # SALTED-SHA512 password in the current_resource. In that case we will force
+ # password to be updated.
+ return true if salted_sha512?(@current_resource.password)
+
+ if salted_sha512_pbkdf2?(@new_resource.password)
+ diverged?(:password) || diverged?(:salt) || diverged?(:iterations)
+ else
+ !salted_sha512_pbkdf2_password_match?
+ end
+ end
+ end
+
+ #
+ # Returns true if user is member of the specified group, false otherwise.
+ #
+ def member_of_group?(group_name)
+ membership_info = ""
+ begin
+ membership_info = run_dscl("read /Groups/#{group_name}")
+ rescue Chef::Exceptions::DsclCommandFailed
+ # Raised if the group doesn't contain any members
+ end
+ # Output is something like:
+ # GroupMembership: root admin etc
+ members = membership_info.split(" ")
+ members.shift # Get rid of GroupMembership: string
+ members.include?(@new_resource.username)
+ end
+
+ #
+ # DSCL Helper functions
+ #
+
+ # A simple map of Chef's terms to DSCL's terms.
+ DSCL_PROPERTY_MAP = {
+ :uid => "generateduid",
+ :gid => "gid",
+ :home => "home",
+ :shell => "shell",
+ :comment => "realname",
+ :password => "passwd",
+ :auth_authority => "authentication_authority",
+ :shadow_hash => "ShadowHashData"
+ }.freeze
+
+ # Directory where the user plist files are stored for versions 10.7 and above
+ USER_PLIST_DIRECTORY = "/var/db/dslocal/nodes/Default/users".freeze
+
+ #
+ # Reads the user plist and returns a hash keyed with DSCL properties specified
+ # in DSCL_PROPERTY_MAP. Return nil if the user is not found.
+ #
+ def read_user_info
+ user_info = nil
+
+ begin
+ user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
+ user_plist_info = run_plutil("convert xml1 -o - #{user_plist_file}")
+ user_info = Plist::parse_xml(user_plist_info)
+ rescue Chef::Exceptions::PlistUtilCommandFailed
+ end
+
+ user_info
+ end
+
+ #
+ # Saves the given hash keyed with DSCL properties specified
+ # in DSCL_PROPERTY_MAP to the disk.
+ #
+ def save_user_info(user_info)
+ user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
+ Plist::Emit.save_plist(user_info, user_plist_file)
+ run_plutil("convert binary1 #{user_plist_file}")
+ end
+
+ #
+ # Sets a value in user information hash using Chef attributes as keys.
+ #
+ def dscl_set(user_hash, key, value)
+ raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key)
+ user_hash[DSCL_PROPERTY_MAP[key]] = [ value ]
+ user_hash
+ end
+
+ #
+ # Gets a value from user information hash using Chef attributes as keys.
+ #
+ def dscl_get(user_hash, key)
+ raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key)
+ # DSCL values are set as arrays
+ value = user_hash[DSCL_PROPERTY_MAP[key]]
+ value.nil? ? value : value.first
+ end
+
+ #
+ # System Helpets
+ #
+
+ def mac_osx_version
+ # This provider will only be invoked on node[:platform] == "mac_os_x"
+ # We do not check or assert that here.
+ node[:platform_version]
+ end
+
+ def mac_osx_version_10_7?
+ mac_osx_version.start_with?("10.7.")
+ end
+
+ def mac_osx_version_less_than_10_7?
+ versions = mac_osx_version.split(".")
+ # Make integer comparison in order not to report 10.10 less than 10.7
+ (versions[0].to_i <= 10 && versions[1].to_i < 7)
+ end
+
+ def mac_osx_version_greater_than_10_7?
+ versions = mac_osx_version.split(".")
+ # Make integer comparison in order not to report 10.10 less than 10.7
+ (versions[0].to_i >= 10 && versions[1].to_i > 7)
+ end
+
+ def run_dscl(*args)
+ result = shell_out("dscl . -#{args.join(' ')}")
+ return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
+ raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") unless result.exitstatus == 0
+ raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
+ result.stdout
+ end
+
+ def run_plutil(*args)
+ result = shell_out("plutil -#{args.join(' ')}")
+ raise(Chef::Exceptions::PlistUtilCommandFailed,"plutil error: #{result.inspect}") unless result.exitstatus == 0
+ result.stdout
+ end
+
+ def convert_binary_plist_to_xml(binary_plist_string)
+ Mixlib::ShellOut.new("plutil -convert xml1 -o - -", :input => binary_plist_string).run_command.stdout
+ end
+
+ def convert_to_binary(string)
+ string.unpack('a2'*(string.size/2)).collect { |i| i.hex.chr }.join
+ end
+
+ def salted_sha512?(string)
+ !!(string =~ /^[[:xdigit:]]{136}$/)
+ end
+
+ def salted_sha512_password_match?
+ # Salt is included in the first 4 bytes of shadow data
+ salt = @current_resource.password.slice(0,8)
+ shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + @new_resource.password)
+ @current_resource.password == salt + shadow
+ end
+
+ def salted_sha512_pbkdf2?(string)
+ !!(string =~ /^[[:xdigit:]]{256}$/)
+ end
+
+ def salted_sha512_pbkdf2_password_match?
+ salt = convert_to_binary(@current_resource.salt)
+
+ OpenSSL::PKCS5::pbkdf2_hmac(
+ @new_resource.password,
+ salt,
+ @current_resource.iterations,
+ 128,
+ OpenSSL::Digest::SHA512.new
+ ).unpack('H*').first == @current_resource.password
+ end
+
end
end
end
diff --git a/chef/lib/chef/resource/user.rb b/chef/lib/chef/resource/user.rb
index 4d8c4ac11b..529ee18a48 100644
--- a/chef/lib/chef/resource/user.rb
+++ b/chef/lib/chef/resource/user.rb
@@ -44,6 +44,8 @@ class Chef
:manage_home => false,
:non_unique => false
}
+ @iterations = 27855
+ @salt = nil
@allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock)
end
@@ -105,6 +107,22 @@ class Chef
)
end
+ def salt(arg=nil)
+ set_or_return(
+ :salt,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def iterations(arg=nil)
+ set_or_return(
+ :iterations,
+ arg,
+ :kind_of => [ Integer ]
+ )
+ end
+
def system(arg=nil)
set_or_return(
:system,
diff --git a/chef/spec/data/mac_users/10.7-8.plist.xml b/chef/spec/data/mac_users/10.7-8.plist.xml
new file mode 100644
index 0000000000..4ed294eb38
--- /dev/null
+++ b/chef/spec/data/mac_users/10.7-8.plist.xml
@@ -0,0 +1,559 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>KerberosKeys</key>
+ <array>
+ <data>
+ MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCBxHUxawMNiov49kfZn
+ M38ddgXFivE9SNpYgPamy+6prKJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEVB
+ OUJFMDZCQzExQjAxODdEMzQ1MkI3QTA5NjE3QjBCOTI2OTY4RXZhZ3JhbnQw
+ Y6EbMBmgAwIBEaESBBBD9mGbvFTNIUKAvAbnjh8ookQwQqADAgEDoTsEOUxL
+ REM6U0hBMS40RUE5QkUwNkJDMTFCMDE4N0QzNDUyQjdBMDk2MTdCMEI5MjY5
+ NjhFdmFncmFudDBroSMwIaADAgEQoRoEGG4TEFIf416UH7MvFW7sAXC8ArC6
+ AhbCraJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEVBOUJFMDZCQzExQjAxODdE
+ MzQ1MkI3QTA5NjE3QjBCOTI2OTY4RXZhZ3JhbnQ=
+ </data>
+ </array>
+ <key>ShadowHashData</key>
+ <array>
+ <data>
+ YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50
+ cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIDqTC0mXYAboOwN/M0lPfwd6Ry+CAa0
+ rMHtf+Iq689r61NE0PRC5ZD/oE1nkHXaOvsRnkG3K16vCO5KpUaTciZG1Rnu
+ BIQ964o+l3Qo0z9iXoOIeRPlwTtwA1lhXgCte8PnoMmK/D4Z0TYCckVPjTOp
+ IU0vvovmjR+YIbJmiTEjZk8QIPmU7y9zt8VZTr0VUzAJdrIHM84OJNZZeD2H
+ 89gcu7apEZugCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA
+ AAAAAOo=
+ </data>
+ </array>
+ <key>_writers_hint</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_jpegphoto</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_passwd</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_picture</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>authentication_authority</key>
+ <array>
+ <string>;ShadowHash;HASHLIST:&lt;SALTED-SHA512-PBKDF2&gt;</string>
+ <string>;Kerberosv5;;vagrant@LKDC:SHA1.4EA9BE06BC11B0187D3452B7A09617B0B926968E;LKDC:SHA1.4EA9BE06BC11B0187D3452B7A09617B0B926968E</string>
+ </array>
+ <key>generateduid</key>
+ <array>
+ <string>11112222-3333-4444-AAAA-BBBBCCCCDDDD</string>
+ </array>
+ <key>gid</key>
+ <array>
+ <string>80</string>
+ </array>
+ <key>home</key>
+ <array>
+ <string>/Users/vagrant</string>
+ </array>
+ <key>jpegphoto</key>
+ <array>
+ <data>
+ /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw
+ bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs
+ AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA
+ AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH
+ MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk
+ AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH
+ ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv
+ ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo
+ AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA
+ AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv
+ S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD
+ Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa
+ AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA
+ AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp
+ RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE
+ 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg
+ AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA
+ QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo
+ AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E
+ FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy
+ AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA
+ RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl
+ AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA
+ bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg
+ AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA
+ bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs
+ AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA
+ RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv
+ AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD
+ wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl
+ AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A
+ ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH
+ AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA
+ bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs
+ AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E
+ RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn
+ BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA
+ ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz
+ AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo
+ dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN
+ AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A
+ ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA
+ /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH
+ BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA
+ QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O
+ Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A
+ AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE
+ AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX
+ GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3
+ eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI
+ ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB
+ AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR
+ BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico
+ KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
+ 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0
+ 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ
+ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i
+ 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
+ By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU
+ UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9
+ piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s
+ fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/
+ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL
+ /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc
+ 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b
+ /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o
+ 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB
+ k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A
+ tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI
+ TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm
+ Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF
+ FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
+ AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR
+ WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q==
+ </data>
+ </array>
+ <key>name</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>passwd</key>
+ <array>
+ <string>********</string>
+ </array>
+ <key>passwordpolicyoptions</key>
+ <array>
+ <data>
+ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
+ WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
+ IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
+ LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp
+ bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr
+ ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt
+ MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8
+ L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgo8L2Rp
+ Y3Q+CjwvcGxpc3Q+Cg==
+ </data>
+ </array>
+ <key>realname</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>shell</key>
+ <array>
+ <string>/bin/bash</string>
+ </array>
+ <key>uid</key>
+ <array>
+ <string>501</string>
+ </array>
+</dict>
+</plist>
diff --git a/chef/spec/data/mac_users/10.7-8.shadow.xml b/chef/spec/data/mac_users/10.7-8.shadow.xml
new file mode 100644
index 0000000000..8c3b6dd3d7
--- /dev/null
+++ b/chef/spec/data/mac_users/10.7-8.shadow.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>SALTED-SHA512</key>
+ <data>
+ b3XXGQRB+sw0KR676h/HVrJC1P6bz/FBvMuE8ZeeJ+U5U5qjH599zJLAzqlZ6hjhi3IO
+ NY5/vjz76qVhRW9roAiTejA=
+ </data>
+</dict>
+</plist>
diff --git a/chef/spec/data/mac_users/10.7.plist.xml b/chef/spec/data/mac_users/10.7.plist.xml
new file mode 100644
index 0000000000..5c7a98fada
--- /dev/null
+++ b/chef/spec/data/mac_users/10.7.plist.xml
@@ -0,0 +1,559 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>KerberosKeys</key>
+ <array>
+ <data>
+ MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCCBdluECwTg7Fe5bsZ+
+ kxWTdvLPPtNGBCZOK2+aEFrkBaJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuREZG
+ QTkxRjM1QjUxNjMzMDNDMDc5RTk5ODc2NDAzMEQwOTU2QUYyNnZhZ3JhbnQw
+ Y6EbMBmgAwIBEaESBBAHZXv8koch6fiOdgRkDXyjokQwQqADAgEDoTsEOUxL
+ REM6U0hBMS5ERkZBOTFGMzVCNTE2MzMwM0MwNzlFOTk4NzY0MDMwRDA5NTZB
+ RjI2dmFncmFudDBroSMwIaADAgEQoRoEGKs+5dPs07zLf/0Vhu+YWCXZ6iwg
+ NLpkqKJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuREZGQTkxRjM1QjUxNjMzMDND
+ MDc5RTk5ODc2NDAzMEQwOTU2QUYyNnZhZ3JhbnQ=
+ </data>
+ </array>
+ <key>ShadowHashData</key>
+ <array>
+ <data>
+ YnBsaXN0MDDRAQJdU0FMVEVELVNIQTUxMk8QRG911xkEQfrMNCkeu+ofx1ay
+ QtT+m8/xQbzLhPGXniflOVOaox+ffcySwM6pWeoY4YtyDjWOf748++qlYUVv
+ a6AIk3owCAsZAAAAAAAAAQEAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAGA=
+ </data>
+ </array>
+ <key>_writers_LinkedIdentity</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_hint</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_jpegphoto</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_passwd</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_picture</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>authentication_authority</key>
+ <array>
+ <string>;ShadowHash;HASHLIST:&lt;SALTED-SHA512&gt;</string>
+ <string>;Kerberosv5;;vagrant@LKDC:SHA1.DFFA91F35B5163303C079E998764030D0956AF26;LKDC:SHA1.DFFA91F35B5163303C079E998764030D0956AF26</string>
+ </array>
+ <key>generateduid</key>
+ <array>
+ <string>11112222-3333-4444-AAAA-BBBBCCCCDDDD</string>
+ </array>
+ <key>gid</key>
+ <array>
+ <string>80</string>
+ </array>
+ <key>home</key>
+ <array>
+ <string>/Users/vagrant</string>
+ </array>
+ <key>jpegphoto</key>
+ <array>
+ <data>
+ /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw
+ bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs
+ AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA
+ AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH
+ MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk
+ AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH
+ ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv
+ ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo
+ AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA
+ AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv
+ S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD
+ Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa
+ AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA
+ AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp
+ RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE
+ 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg
+ AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA
+ QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo
+ AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E
+ FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy
+ AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA
+ RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl
+ AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA
+ bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg
+ AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA
+ bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs
+ AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA
+ RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv
+ AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD
+ wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl
+ AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A
+ ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH
+ AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA
+ bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs
+ AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E
+ RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn
+ BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA
+ ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz
+ AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo
+ dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN
+ AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A
+ ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA
+ /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH
+ BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA
+ QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O
+ Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A
+ AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE
+ AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX
+ GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3
+ eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI
+ ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB
+ AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR
+ BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico
+ KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
+ 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0
+ 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ
+ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i
+ P8K43v0P5UlMZ7LDPDcW6ywSJLGejKcipa8gtL65sLjzLaV4z3HVT9RXb6d4
+ mt7grFegW0398fcP+H40hHUUUgIZQykEEZBHeloAKKKKACiiigAooooAKKKK
+ ACiiigAooooAKKKKACiiigAooooAKKKguLmC1tzLcSpDGO7H/OaAJ6K4y48W
+ gXyi2tvMtwfmLnDN9PSuisNVs9RjzBJiQfeibhh+Hf8ACgC3cgHT5wQCDG2Q
+ fpX4u/CD9rf4h/C+aHRtXkfxt4PjbaLG/mP2i2XP/LGc5YADorbl7AL1r9o7
+ j/jxn/65n+VfzYS/8fMn+8f51+qeG+W4XHUcXSxEFKPub/8Ab2z3T9D8p8S8
+ zxWBq4Srh5uMvf2/7d3WzXkz99/hb8cPh38XtCW48J6yn9ppGGutHvMRXtv6
+ 5TJ3L/tIWX3zxXrtfgD8JPhh8TviF4ut9Q+Hlvd6ZDZTgv4nluHtLSxYd1mX
+ 5nkGfuRbm+lftD4C1HXfDvw+0vRfGHiW48batBFtudbeyS2aZvaNf4QOAxO4
+ 9W5r57jHh7BZXX5cPXUr/Z3lH1a0++z8nufQ8G8R43NaHNiKDjb7X2Zeiev3
+ XXpseu0VBb3MF1biW3lSaM91NT18Yfankuk/FDQfHPgxNb8B6zYa1ojna17a
+ ybnjb+5Ihw0T/wCy4Bqkzs8hd2Z3JyWJyTX4a+HPF3ijwL4/bxB4O13UPDus
+ K5DTWr/LMufuSocrKn+y4Ir76+Fv7YXhzXfs+j/FO1tvB+sHCLrtorNplwfW
+ VOXtifX5o/da/Rc/8O8bg71cL+9h2+0vl19Vr5I/OOHvEbBYxqli/wB1Pv8A
+ Zfz6fPTzPs6jt61HFLDPY293bTwXVpcIJLe4t5VlimQ9GR1JVgfUE0/mvzux
+ +jppq6F70lL6elHagBPyo7elHejvQM1tP1m905wI3MkPeJ+V/D0ru9O1yy1D
+ CK3k3B/5ZOeT9D3ry/tR0YH360CPaaK8607xJd2m2K6zdQDjJPzj6Hv+NdzZ
+ 39rfweZbSq/95Twy/UUgLlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS
+ MyohZ2CqBkknAFYmo69Z2AZFb7Rcj/lmh6fU9q4S/wBWvNRc+dJiLPyxJwo/
+ x/GgDrNR8TwQho7BRcydPMP3B/jXE3V3c3lwZrmV5X9+g+g7VXzj2PrS8EZP
+ FMBvtT1ZkkDozI68gqcEU0gjP86Tv9aBnWWPiWXyWttQ/eIyFRMo+YccZHQ/
+ pXwD8MP2PdC0iSDWvivd2/inVc+Yvh+xkZdOgPXE0gw9wR3Vdqf71fadHQ5r
+ 1MvzvG4GlUp4efKp2u1vpfZ7rfpqeRmGR4LHVadTEQ5nC9k9tbbrrt10IoIY
+ LTTbaxs7e2srC2Ty7a1toVihgX+6iKAqj2AqXtSVla7r2ieFvDjav4j1S00f
+ TRwstw3Mp/uxqPmkb2UH8K86EJ1JqMVeT6LVtnpznClByk0orvokv0N62u7i
+ yuRLbTPE4646H6jvV3U/i74Q8M3MVh4p1SGw1Z03rZwRvNMU/vtGgLIuSOWx
+ nPFfEXjj9ojWNT87TvAcE/hzTzlW1S4UG+lHqi8rAD6/M/uK8d8JPJL4xvbi
+ eSWe5lgZ5ZpXLySMWXJZjyT7k1+g5XwBXnTdbGPkX8q+L5vZfi/Q/PM08QKE
+ aipYKPO/5n8PyWjfrovU+P5/+P2b/fP86irqfG/hfUvBXxe8SeE9XXGoaVqE
+ ttK23Ak2sdrj/ZZcMPYiuWr97pVI1IRlF3TV0fz/AFacqc5QkrNOzPpr4GfE
+ bxb4B8GyDQdQEmlm/czaReZks5OFzhc5jb/aQg/WvvvwJ8ZvCPjeSGwkk/4R
+ rxG/A02/lGyZv+mE3Cv/ALp2t7GvzM8Af8iHL/1+yf8AoK128dvJeXcFlBbT
+ XtzO4WG2hiMkkrdgqgEk/SviOIuE8BmUpTkuSf8AMv1Wz+evmj7vhzizH5bC
+ MIPmh/K/0e6+Wnkz9V2VkdlYMrDhlIwRTfzr5Ut/E3xQ+CX7Nlz4x8foniHQ
+ bW7tbW38O3FyDqduk0mzd9p5CbRyIn389Ste2fDz4peBPipob3ngvW0vbmJN
+ 15pNyvk6hZ/9dISclf8AbQsnvX4hj8jr4eMqsGqlJO3PHWN9N+268uibP3DA
+ Z/h8TONKadOq1fklpK3l32fn3SO/780d6Xr3/Wkrxj3Be1JS/likoAU9KfFL
+ LBOssMjxSA5DKcGmUn50Adrp3in7sOornt5yD+Y/w/KuximingWWGRJY26Mp
+ yDXjXOenWuL8V/Fzw58MWYahqMlzrRXcmiWJElxJ6GQH5Yl/2nwfQGt8LhK2
+ JqKnRi5SfRf1+OxzYrF0cNSdWtNRiur/AK/A+naK5vwdr0nij4VeHfEktqtl
+ JqenxXTW6ybxEXUNt3YGcZxnFdJWVWnKnNwlutDWlUjUgpx2auvmFFFFQWFF
+ FFABRRRQAUUV4D8evjfbfBHQ/Cmpajo17qmk6tqD2l3NYyL9otFEe4SJG3yy
+ e67lPoc104PB1sVWVGjHmk9l30ucuMxlHCUXWrStFbv52PcL3UbTT4d1zKFY
+ jKoOWb6CuG1LxHd3ZaODNrbnsp+Zvqf6CvOvCnjfwv8AETwqfEfhHxDZ+JNP
+ JHnyRMfOt2P8E8bfPE3swx6E10fFZVaU6U3CpFqS3T0a9UbUa1OtBVKclKL2
+ a1T+Yd6KPyrk/F3jjwv4F0lbnxLqaWs0i7rawhXzbu5/3Igc4/2mwvvVUKFW
+ tUVOlFyk9ktWLEYilQpupVkoxW7bsjq6O9fK+hftaeBrj4n3nhvxlpl34It9
+ yHT9Wkm+02xVlyFudq5ibP8AGoZPp1r6lhlgudOt7y0uLe8sbhBJbXVtKssM
+ yf3kdSVYe4NduZZPjcvmo4mm4327P0aun566HDlmdYHMIuWGqKVt+69U7P8A
+ Akyce1LwTx1pM0qqzyBEVmduiqMk15h6YnQ1HPNBa6bc315cW1lY2yeZc3Vz
+ MsUMCf3ndiFUe5NeXeOvjH4R8DtLY+b/AMJJ4hQY/s2wlG2E/wDTaYZVP90Z
+ b2Ffm58ZfGXxR+IOpvdeKdU/tDwvFIXtNJ0pDFY2Y7F4cku4/wCejlifUV9n
+ w9wTjczanP8Ad0+73f8AhXX1dl2bPiuIuOMFlicIfvKnZbL1fT0V33sfUfxR
+ /bE0LR/tOjfCmzt/FWqDKN4gv42GnQHpmGM4a4I7M21P96vmXR/FPiTxra3f
+ iLxdrmoeItblvJAbq8kzsXC4SNRhY0HZVAFfP4IIyCCD0INexeAf+RCk/wCv
+ 2T+S1+1Zbw1gMqo2w8Pe6yesn8+i8lZeR+JZjxNmGbV74ifu9IrSK+X6u78z
+ ta6zwd/yM0//AF6t/wChLXJnpXt3wO+HWp/EHx1rENldR2FtZWAae5kjLKGe
+ RQicdyFcj/dNGZ4inQws6lR2iupeWYepXxUKdNXk+g79vb4c/wBm/Erw78Tr
+ CDbaaxENP1RlXgXMS5iYn1eIFfpBX58V+/nx0+HafFH9lzxX4TWJZNTktTca
+ UxxlbqL54sHtuI2E/wB12r8BXR45njkVkkUkMrDBBHUEV4/h1m/1vLPYyfvU
+ tPl9n9V8j1vEfJ/qmae2ivdq6/P7X6P5n1L+zt8Orj4lWd/pltr+j6StndNL
+ eRvKJL7yyF+aK3zlx1+c4UY5r9FfBnw/8KeArBk8OacFvZE2z6pdESXk/wBX
+ x8i/7KAD61+JVpd3mna1aalpt5eabqdrIJLW8tJmimgYfxI6kFT9DX218Lv2
+ x9V082+jfFuym16yGEXxJpsCi9jHTNxAMLOPV02v3IauDjnh/OcYnPDVOan1
+ gtH/APbej26Js7+A+IMmwbUMTT5anSb1X/2vqvm0j6A/a0/5MP8AEIH/AEGN
+ N/8AR5r8p7G+vtK16z1XSr+90rVbSQSWt7ZTtDPA3qjqQR/XvX6f/tJ+I/D3
+ i3/gnHrOv+Fdc0zxFos+sad5d5Yzb1z5/KsPvIwyMqwBHpX5b11eGlJxympC
+ as+eSaf+GOjX6HL4l1oyzenUpyuuSLTT85apo+5/hd+2TqFobfR/i9Yy6va8
+ KnibS7cC6QetxbjCy+7x7W7lWr700TW9F8TeErTX/Der6dr+h3IHkX1jMJIm
+ OM7T3Vh3VgGHpX4SV2Hgjx/4y+HHis6z4L1670S7fi5iXD212v8Admhb5JB9
+ Rn0IrHiDw4wmKvVwbVOfb7L+X2flp5G3DviTi8JaljF7SHf7S+f2vnr5n7fY
+ 70vavkv4XftbeDfF722j+PobbwB4kchFvDIW0q7Y8cOfmtyf7r5XPRhX1hcT
+ QWelSajeXVraaakQle8mmVYAhHD+ZnaVPYg89s1+MZnk+My+t7LE03F9Oz9H
+ s/kfteV51gsxo+1w9RSS37r1XQf+VYviHxLoHhLw7/aviXVrXSLM5ERlOZJz
+ /djjHzOfoMepFfP3jj9ouxtPO074fW0eqXPKnWr6I/Zk94Yjgyf7z4X2NfKm
+ r6vq3iDxFNq+u6le6xqkv37m7k3vj+6Oyr/sqAPavrcj4BxWKtUxX7uHb7T+
+ XT56+R8jnnH+Fw16eEXtJ9/sr5/a+Wnme7eOP2hde1kzad4Khn8LaU2Va/kw
+ dQnHqpGVgB/2ct/tCvng5aaSRmeSWRi8kkjFndj1LMeSfc0UV+t5blOEy+n7
+ PDw5V17v1e7/AE6H5JmWbYvH1faYibk+nZei2X9XP1/+E3/JsXgD/sAWv/op
+ a9Crz34S/wDJsPgD/sAWv/opa9Cr+ccx/wB7q/4n+Z/R+W/7pS/wr8kFFFFc
+ R2hRRRQAUUUUAFfn7/wUE/5Ij4B/7Dkv/og1+gVfn7/wUE/5Ij4B/wCw5L/6
+ INfVcEf8jzD+r/JnynHH/IixHovzR+YfhzxJ4h8H+MbfxD4U1vUvDutw8JeW
+ MuxyP7rjo6HurAg+lffXws/bF0rVWtdD+LFjFoOpMRHH4i0yBmspm6Dz4Blo
+ ST/Em5OfurX511Ys/wDkNWP/AF8x/wDoYr98zvhvAZrC2Ih7y2ktJL5/o7ry
+ P5+yPiTMMqqXw89OsXrF/L9VZ+Z+mPjr9oq7lln0z4fWr2EQJR9bv4QZ294Y
+ TkJ7M+W7gCvmO6ubq/1a41C/urm/1Cdt091cymSWU+rMeT/Smy/8fMn+8f51
+ H+PNcGVZLg8up8mHhbu+r9X+m3ZHqZrnWMzGpz4id+y6L0X6792eJ+N/+SmX
+ n/XCH/0Cul+Gnxj8ffCfUifCmrB9Hkk33WhX4M1hcep2ZzG3+3GVP1rmvG//
+ ACUy8/64Q/8AoFcpX0VXCUcTh/ZVoKUWtU1dHzFLF1sNiPa0ZuMk9GnZn6se
+ B/2q/hr4s8I3Fxq0eqeFfEdrDvn0UxG5+084JtpVADrnGQ+1lByc9a8s8d/H
+ bxT4shuNO0USeEvD7gq0NtNm7uF/6azDoD/dTA9zXxj4A/5Hub/ryk/mtewZ
+ z/8AWr43DcFZVgsS6sIXe6UndR9P83d+Z9riONs2x2FVKpUstnZWcvX/ACVl
+ 3QgAVcAADPQU4Eq2VJU+opMcUV9KfOHJa14O0vVWknt9ulX7cmSJP3ch/wBt
+ P6rg/WrHhTS7zR/DM1jfJGswu3ZTG+5XUhcMD+B6810tFW6knHlZkqUVLmW4
+ vb0r9RP2cPBX/CI/s3afd3MIj1XXD/aFwSOQjDEK/TZhsdi7V+fXwu8HP48+
+ Ovh7w3tY2c1wJL5hn5bdPnk57EqCoPqwr9f0RIoUjjRY40UKiKMBQOgA7Cvy
+ zxHzTlp08JF7+8/Tp+N/uP1Xw3yvmqVMZJbe6vV7/hp8x1fiL+1z8Of+Ff8A
+ 7YmtXFpB5WieIh/a1jtHyq0jHz09OJQ5wOiulft1Xx7+2r8OP+Ez/ZQk8S2U
+ Hm6z4TmN8hVcs1q+FuF+gASQn0iNfL8B5v8AUc1gpP3anuv57P7/AMGz6jj7
+ J/r2UzcV71P3l8t1934pH400UUV/SB/NRPDdXVtZXtrb3VzBaXmz7Zbxyssd
+ zsO5PMUHDFTyCRkHpUPB6cH0pKKVkF29wopc8YPIpKYAQCCCAQRgg969m8Ba
+ tq138N7jRLvVtTutFsb4NZafNdO9vblkydiE4XJ54HHbFeM16v8ADv8A5F3V
+ /wDr8T/0CufFRThdrY6sJKSqaPc9AzRTXdY03SMqr6k/kK9Av/hh400j9nPx
+ H8Utb0ibRvDOk2iXEcV4DHd3wZ0QeXGeVX5wdz4z2B615FfE0qPL7SSXM0lf
+ q3sl3PZo4atW5uSLfKm3bolq2+xwPOKWs/TdV0/V7Iz6ddJcIo/eJjbJH/vK
+ eR9envV/vW7TWjME76o/UT9m7xcnij9mTS7OR1/tDQ2OnTqODsUAxNj02FVz
+ 3KtXvtfmv+yz4y/4R/8AaAfw/cS7NP8AEFv5GCcKLiPLxH8R5ifVxX6UV/Pf
+ GOWfU80qJL3Ze8vnv+Nz+heDsz+u5XTbfvR91/Lb8LBRRRXy59SFFFFABRRR
+ QAV+Vv7fPj9NS+KHhf4c2bo8Wj2xv9QK8kTzDEaH0Kxru9xKPSv1D1TU7LRf
+ DOo6xqU62unWNrJc3UzdI441Lux9gATX883xA8YXvj/42eJ/GWob1uNX1CS5
+ EbHPlITiOPPoiBVHsor9K8M8q9vmEsTJaU1p/ien5X/A/M/E/NvYZfHCxetR
+ 6/4Vr+dvxOPqzZ/8hqx/6+Y//QxVapYJBDfwTEFhHKrlQcZwQcfpX70z8BTP
+ paX/AI+JiSFVSSzMcADPUnsK4HWfHVjZl4NIVNTuhwZmyIEPt3c/TA964TXf
+ EOq69LIbiZUsN2VtYMrGv+8OrH3PFc7XHSwyteR21cW3pEtXt7d6jqct7fTG
+ 4upMbnIA4HAAA6AdhVWiiutHG3c7fwB/yPM3/Xk/81r2DvXj/gD/AJHqb/ry
+ f+a17BXn4n+IelhP4YdqPzoorA6gooxV/StNvNa8Tado+nxGe/vrlLe3jz95
+ 3YKo/M0pSUU29kOMXJqK3Puj9kbwV9k8J6347u4cT37/AGHT2Yc+ShBkYezO
+ FX6xGvsquf8ACnh2z8JfDbRPDVgB9l060SBWC48wgfM5HqzZY+5NdBX81Z7m
+ Tx+OqV+jenotF+B/SuRZasBgadDqlr6vV/iFVb6ytNS0W806/gjurG6geC4g
+ kGVkjdSrKR6EEirVFeUm07o9ZpNWZ/PN8U/A118Nv2g/Ffgq68xhpl8yW0j9
+ ZYGw8Mh92jZCfQkiuAr9Lv2+vhxlPC3xTsIOR/xKNXKj/ekt3OP+2qlj/wBM
+ x6V+aNf1LwzmyzHLaVe+trP1Wj/z+Z/KvE+UvLcyq0Oid1/heq+7b1QUUUV7
+ x4AUUUUAFfSn7PXw18V/E2fXdM8MQ2IW3u4mvry8uAkVqrIcMVB3ueOig+5F
+ fNdd18OdfvvD3xQtLnTtQvNKvJh5cF3aTGKWKQcoQw9TkEHg55BrjzCFaeHm
+ qMkpdG1dfddf132O3LqlGGJg6ybjfVJ2f32f9dtz9oPhn+zv4I+H0ttql3Gf
+ FXimPkalfxjZA3/TCLlY+3zctx96qH7XH/KOb4pf9g6L/wBKYa8Z+G37WV7Z
+ m30j4nWbX1uMIuvafBiRfeeAdf8Aej9PuV6f+05r2i+Jv+CYPxK1nw/qljrG
+ lz6ZEYrm0mEiH/SITjI6EZ5B5HevwSrgM1o55hp468r1I2lvH4lt0Xpp6H9A
+ UsflNbI8TDA2janO8dpfC9+r9bv1PxQgnntb1Lm1nltrlD8ksTbWX8f6V6Ro
+ /j4ErBr0WD0F7AnH/A0H81/KvMu9B6Gv6EnTjPc/nanVlB3R9Q6Tqk2n6xpm
+ t6VcgT280d1Z3EZyNysGRh+IBr9lvCfiG08WfDXQ/Ellj7PqNmk4UHPlsR8y
+ H3VsqfcV+Jehf8iLov8A15J/Kv0U/ZH8Y/bvh9rXgm6lzcaXN9rslY8mCU/O
+ oHosnJ/661+U+ImWe2wUcRFa03r6PT87fifrPhzmnscY8PJ6VFp6rX8r/gfY
+ NFFFfih+2hRRRQAUUUUAfHP7bPxC/wCER/ZNPhmzn8rVvFdz9jUKcMLWPDzs
+ PY/u4z7Smvxtr6r/AGxfiF/wnH7Y+q6daT+bo/hmMaVbBW+UyqSbhsevmEp7
+ iNa+VK/pXgfKfqOU00170/efz2+5W+Z/MvHWbfX83qNP3Ye6vlv+N/kFFFFf
+ Xnx4qkq4ZSQw6EU5ni2M0pWHAyX6KPc+n1Fdx4A+Gfjj4oeJH03wVoU2prEw
+ F5qEreTY2XvLO3yqf9kZY9hX6I/Cz9lPwL4Ee11jxa0HxC8WR4dGurfbplm/
+ X91bt/rGB/jlz7IK+Yz7i3L8qTjVlzT/AJVq/n2Xr8kz6jh/hHMc2d6UbQ/m
+ e3y7v0+dj8vLuyvrAWRv7K9sVvIBPZNcW7xi6iPSSMsBvXjquRVav3R8WeFv
+ DXjzwlJoHjPQ7DxJpDcrBdpzAf70LjDRMOxQj8RxX5//ABR/Y88Q6J9o1j4V
+ 3dz4v0kZZtCvGVdTgHUiJ+EuQPT5ZPZq8PIvEbAY2Xs8SvZSe13eL/7e0s/V
+ JefQ93PvDjH4GPtMO/ax62VpL/t3W/ybfkfMvgD/AJHqb/ryf+a17Aa8i8Cx
+ TW3xKvrS5hntLy3tZI7i3njaOWFgVyrowBU+xFeu19liPjPjsIrQDsaKD60d
+ qwOgK+qf2UvBX9t/Ge88WXcW6x0GD9wSOGuZQVX67U3n2JU18rd6/WL4GeCv
+ +EH/AGcNDsJ4fJ1S9X7fqIIwwllAIU+6oEQ+6mvjeOc0+qZbKEX71T3V6dfw
+ 0+Z9nwNlf1vMozkvdp+8/Xp+OvyPX6KKK/BT97CiiigDg/if4Gs/iV8A/FPg
+ i9aONdUsWjgmdciCcYeGTHfbIqNjvjFfhh8Svg/8QPhN4jFh4z0Kezhkcra6
+ jD+8s7rv+7lAwTjnacMO4Ff0F1maxoukeIfDd1o+u6ZY6xpVymy4tLyBZYpB
+ 6FWBFfY8K8YV8mbhy81OTu1s790/6+R8ZxXwbQzlKfNy1Iqye6a7Nf1bzP5u
+ qK/QP9qT9lbwp8PPhzqHxJ8D6jPpmlRXMcd1oVzmVVMsgQGGQncACfuvnvhu
+ gr8/K/fsmznDZnhlXoPTbXRp9v8Ahj+fs6yXE5XiXh8Qtd9HdNd/w66hRRRX
+ qnkhSqzpIskTFJUYMjDswOQfzpKKAPovTr9NU0Cy1GPgXEQdh/dbow/76BrQ
+ M9z/AMItrehpeX1vo+sweRqtpBOUjukDBhuXpuBAIbGRjrivM/h7qO63v9Hk
+ blD9ptwfQ4Dj88H8TXpFeTVppSaa/rdHtUKjlFST/rZnj2s+CNRsA9xppfVb
+ MclQuJ4x7qPvD3X8q4jOQfbg+1fTIODkZFc7rXhjStbDSTRm0vyOLuAAMf8A
+ fHR/x5966aeJe0jkq4PrEu6F/wAiLov/AF5R/wAq9n+DHjL/AIQf9ovw9rE0
+ vladLN9j1DJwvky/KzH2U7X/AOACvIdPtmsvD9hZSOsr28CxF1GA2O4Bq53r
+ zcZhoYmjOlPaSa+89TA4mphqsKsN4tP7j9wKK8l+B/jH/hNv2bvD+pTS+bqV
+ rH9hvyTlvNiAXcfdl2P/AMCr1qv5jxmFnhq86M94tr7j+nsHioYmhCtDaST+
+ 8KKKK5jpCvPPix45g+G37Oni3xpMY/M02wZrRH6SXDYSFD7GRkB9ia9Dr82/
+ 2/PiF5en+EvhhZT/ADSsdX1VVPO0bo4FPsT5zEH+6hr3eGsq/tHMqVBrRu79
+ Fq/8jweJs2WXZZVxF9UrL1ei/wAz80rm5nvNRuLu6mkuLqeRpJpXOWdmOSxP
+ ckkmoKKK/qZK2h/KbberCj8jz0PIPsfUe1FFMD9DPg5+1n4Qj8NaX4N8e6Fp
+ Pw+itVEVnqWh2nl6Qe2ZYFy1ux7uNynvivt6Ce3u9Mtr6yuba+sLmPzLW6tZ
+ llhnX+8jqSrD3Br8Fa9M+Gvxf8ffCfVTJ4Q1cDSZJN93oV8pm0+59SY8/u2/
+ 24yrfWvy3iLw2o4hyrYGXLJ6uL+F+j3T+9eh+qcN+JVbDKNHGx54LRSW6Xps
+ 19z9T9ozXJ+LvG/hbwNpK3XifVFtJJF3W1jCvm3dz/uRDnH+02F96+Urr9qb
+ WvFHw4sZ/Cfh2PwlfzqyXt3c3Au2hkU4YWwwAF7h3BYenevBrq5ur/VrjUL+
+ 6utQ1C4bdPdXUpkllPqzHk/yFfLZN4d4mpLmxr5Euis5P56pL735Lc+szjxG
+ w1OHLgVzt9Wmkvlo2/uXqeg/E/4gxfEnxxbawfC2j6LJao0UF75YfUp4zgbZ
+ 5xjevAwmCF7GvOPeijvX6zg8HRwlGNGirRjstf1PyXGYytiq0q1V3lLd7fkF
+ FBo4rqOY9a+CHgr/AITr9ozQ9Mni87S7R/t2ogjKmGIg7T7MxRP+BV+s9fKX
+ 7KHgr+xvg9feL7qLbfa5Pttyw5W2iJUY9Nz7z7hVNfVtfg3HOafW8ycIv3af
+ u/Pr+OnyP3rgXK/qmWqpJe9U975dPw1+YUUUV8YfZhRRRQAUUUUAfKX7af8A
+ yYH4j/7CFl/6UJX4qV+1f7af/JgfiP8A7CFl/wClCV+Klfv3hh/yKJf43+UT
+ +ffFL/kbx/wL85BRRRX6Mfm4UUUUAaWj6idI8U2OojJSKT96B/FGeGH5HP4V
+ 9DHGcq25DyrDowPQ/lXzP1HPIr27wbqX9o+BYI5G3XFk32eTPUgcofxXj8K5
+ MVDRSO3Bz1cTqaDRRXEegA6UUfrQaAPrn9knxl/ZvxQ1fwZdTYttXt/tFmrH
+ pPECSAP9qPcT/wBcxX6EV+LXhnXrzwv8QdF8R2B/0rTryO4QZwH2tkqfYjIP
+ sTX7K6TqdnrfhfTtY0+TzrC+to7i3f8AvI6hlP5EV+LeImWexxkcTFaVFr6r
+ /gW/E/afDrM/bYKWGk9YPT0f/Bv+BoUUUV+eH6IFfhh+1hd3N1+3/wDEP7TP
+ JP5NzBFFuP3EW2iwo9AP6n1r9z6/Cn9qj/k//wCJP/X9D/6TQ1+meFiX9p1f
+ 8D/9KifmHiq2sspf41/6TI+faKKK/eD8DCiiigYUV3Ph7whHrXhCe9nuZrOd
+ 5ito4XcuF4YsvcE8ZB7VzuraFqeiThb+3xCxxHcxndE/0bsfY4NQqkW7X1NH
+ SkoqVtD1XwR/yTS0/wCvib/0KurrlfBH/JNLTr/x8Tf+hV1Wa82p8bPVo/Av
+ QKKKKgsO9b3hjw/eeK/iHo3hzTwfteo3aQIduQgY8ufZRlj7A1g9q+xv2R/B
+ X23xrrPjq7izBpsf2OwYjgzyDMjD3WMgfSWvJzzMlgMDUrvdLT1ei/E9bI8t
+ ePx1Ogtm9fRav8D7q0jSrPQ/Cmm6Lp0fk2Fjapb26eiIoUZ98DrWjRRX81yk
+ 5NtvVn9LRiopJLRBRRRUlBRRRQAUUUUAfKf7aKO37AXiUqjMFvrIsQM7R9oQ
+ ZPoMkD8a/FOv6MvGfhbTvG/wp8Q+EdWXOn6tYSWsrbcmPcpAcf7SnDD3Ar+e
+ XxDoWo+F/Hms+G9Xi8jVNLvZbS6TsJI3Ktj1GRwe4r9x8LMdTlg6uG+1GXN8
+ mkv0/I/CvFXA1I42lifsyjy/NNv8b/mY9FISFUsegGTV+/02/wBMliS/tpLc
+ SoHhc8pICMgqw4PB6da/Urn5VZlGjtRR2pgFdj4H1H7F42W0dsQX6eScngOO
+ UP55H41x1KrOkiyRMUlRgyMOzA5B/OpnHmi0VCXLJM+lqOtU9Ov01TQLPUY+
+ BcRB2Ufwt0YfgQaufyryWrHtJpq6DvR2oooGH41+kP7KvjH+3fgRceGrmXdf
+ aBc+WgJ5NvKS8Z/BvMX2AWvzer3P9nfxj/wiP7TOkJPL5em6wP7NusngGQjy
+ 2/CQIM9gWr5ji/LPruWVIpe9H3l8v81dH0/CGZ/Uszpyb92Xuv5/5OzP1Ooo
+ or+ej+hgr8Kf2qP+T/8A4k/9f0P/AKTQ1+4Wta5o/hzw1dazr+qWGjaVbJvn
+ u7ydYo4x7sTj8O9fg58f/FOheNf2wvHPifw1eNqGh314htLkxNH5oWGNCwVg
+ CBuU4yASMHAr9R8LKNT6/Vqcr5eW1+l7rS/c/K/FatT+oUqfMubnvbraz1t2
+ PHqKKK/cz8JYU5I3lnjhjx5kjhEz0yTgfzptWbL/AJDlj/19R/8AoYoGj6Gt
+ LOPTtJtdPhGIraIRD3x1P4nJ/Gp2VZIHjkRJYnGHjdQysPcHrUsv/H1J/vn+
+ dR9ulePe57dkirZWNppuni0sYRb2wkZ1jBJCljkgZ7Z7dqtUUAEsAAST0Aob
+ BK2gUfyqkupWL682lx3Mct+sZkeKM7vLAx94jgHnp1q7RYE09hyqzyqkas7s
+ cKqjJJPYV+vfwp8Gr4D+A3h/w6yKt9Hbia/I/iuJPmk574J2g+iivz6/Z08F
+ /wDCYftJ6ZPcReZpWij+0LrI+VmQjyl/GQqcdwrV+pNfkniPmnNUp4OL295+
+ vT8Lv5o/XPDfK7U6mMkt/dXp1/Gy+TCiiivy4/UgooooAKKKKACiiigAr8kv
+ 27Phz/YHx90r4gWMGzTvEtt5V4VXhbuBQpJ7DfH5ZHqUc1+tteFftIfDj/hZ
+ 37I3ifQ7aDz9as4v7S0gBcsbiEFgi+7oXj/4HX1HB+b/ANnZpTqN2jL3Zej/
+ AMnZ/I+W4yyf+0cqqU0ryj70fVf5q6+Z+DMn/HvJ/umvo6OGG58NWttdQRXN
+ s9rFvilXcrfIO39etfOMn/HvJ/umvpWwjlntdKtbeGa5upoIkhghjLySsUXh
+ VGST9K/pHFuyTP5rwSu2jznWfAJ+e40GTPc2U78/8Ac/yb8682mjkt7+S0uY
+ 3t7uP/WQyLtdfqP61+lHgf8AZ11XURFqXj65m0GxOGXSLVwb2Uekj8rCPYbn
+ +le8eIPg38LfE/w4h8Kar4L0oaTbg/Y5bRfJvLRj1kjuBmTeep3Fge4NfDY3
+ xFy/CVlSV6ndxtZfPaXy08+h91gvDjMcXRdXSn2Ur3fy6fPXy6n4uUV9V/FP
+ 9k7xt4KS61nwVJcfELwtGC7xwwhdUs0HUyQLxKo/vxZPHKivlJWVgdpzglWH
+ QqR1BHYj0PNfZ5Zm2EzCl7XDVFJfivVbr5nxOZ5Ti8ureyxNNxf4P0ezXoep
+ fD3Ud1tf6PI3KH7Tbj2PDgfjg/ia9Hr560bUW0nxTY6iM7IpP3o/vRnhx+R/
+ SvoY4yNrBlIyrDuD0P5U8TC0r9ysJO8LdhO/vR60Ud65zqD605HaOdZEdkdS
+ GVlOCpHQg+tNo7UAfsL8MvFyeOfgX4c8S71a5ubULeAfwzp8kox2G5SR7EV5
+ J+0T+0ZpnwM0PTLODST4g8WarFJJZWjS+XDAikL5sxHzYJOFVR821uVxmvL/
+ ANkLxjsvvEfgS6l+WRRqNgpP8Q2pMo+o8sgf7LGvgb9pT4hf8LJ/bC8V6zbz
+ +fo9lN/ZmlMGypggJXcp/uu/mSD/AH6/Isp4Np1eIKtCrG9KHveqfwr+uzP1
+ zN+M6lHh+lXpStVn7vo18T/rujkviT8XvH/xZ8SjUfGmuz30UblrXT4v3dpa
+ 9v3cQ4BxxuOWPcmvNKKK/b8Ph6VCmqdKKjFbJaI/DMRiateo6lWTlJ7tu7Ci
+ iitjEKs2X/Icsf8Ar5j/APQxVarNl/yHLH/r6j/9DFDBbn0lL/x9Sf75/nUf
+ 50+X/j5k/wB8/wA6ZXjo90zdT1jTNItRLqF0kRI+SJfmlf6KOfxOB715ZrXj
+ XUtSV7exDaVYtwQjZmkH+0/b6L+del6v4f0rW483sBS5C4S7h+WVB6Z/iHsf
+ 0rynWvCWqaMHnCjUNPH/AC8wKcoP9teq/Xke9dWHVPrucWJdXpsaHw+AHjmY
+ AAD7E5/8eWvYO9eP/D8g+OJiCCPsL4IPutfSHgDwpceOPjJ4f8LwbwL66VZ3
+ XrHCvzSv+CBj9cVhj60KSlUm7JK79Eb5bRnV5acFdt2Xqz9Af2YfBX/CM/s9
+ x63cw+XqfiGQXbEjDCBcrCv0ILOP+ulfSFQWttb2Wm29naRJb2sESxQxIMKi
+ KMKo9gABU9fy9meOnjcXUry3k7/5L5LQ/qTLMBDBYSnQjtFW+fV/N6hRRRXC
+ d4UUUUAFFFFABRRRQAUUUUAfhP8AtQfDf/hW/wC154n0u1g+z6Jqrf2ppW1c
+ KsM5YsgHYJIJEA9FHrX0L+zp8cPg/pumWOgazpVv8PvGjRJbtr99OZ7bUSAF
+ AFw3Nrn/AJ5sAn+0a91/bm+HP/CTfs42Pjmxg36p4Xuc3BUcvZzFUfp12uI2
+ 9l3n1r8hCAVIIBBGCCODX9BZTTo8S5DCnXm04+62nbVd+jurOzXpZ6n885vU
+ rcNZ/OpQgmn7yur6Pt1VndXT/wAj98dpAQ8FXQPGykFXU9GUjgg9iODTec1+
+ Pfws+PvxC+FDQ2GlXqa/4TD5k8O6s7PbqO5gcfPbt/ufL6qa+w9T/bR+HkPw
+ wh1PR/DniW/8WzAr/YF2FhitXA+9LdLlXjz08tdzei1+b5p4fZtha6hSh7WL
+ 2a/9uT+H1vbzP0rKvEXKcVQc60vZSS1T1/8AAWt/S1/I+vLi6t9P0u51O9vL
+ XTbC0Xzbi9uZ1hht1H8TyMQFHuT9K/MX9pX4lfBfx3r7f8IP4X/tPxYsg+1+
+ NbcmyguADynlYzd57SuFx1BavGfiR8W/HvxX1dZvGGsmXTYpC9nolmph0+1/
+ 3Ys/O3+25Zj6ivNq/QeE+Anl1WOJxFRuoukW0l5N6OXpovJn53xbx9/aVN4a
+ hTSp95JNv06R9dX5oOo55Fe3eDdS/tDwLBG7brmzb7PKSeSByh/FePwrxGux
+ 8Daj9i8araO2IL9PJOTwHHKH88j8a/Q8RDmh6H55hp8tT1PZqKKO1eaeqFFH
+ tRQMv2HivWvBMl54m8PXItNXs7KcQSldwXfG0Z4+jHHocGvlkdBX0Hr3/Ii6
+ 1/15P/KvnwdK6sHTinKaWrsr+S2/N/ecONqTajBvRXdvN2v+S+4KKKK7jgCi
+ ijnsGY9gqkk/gKACrVgM6/p49bqL/wBDFVFZXjV0ZWVhkMDkEVd07/kYtO/6
+ +4v/AEMUnsNbn0fL/wAfMh/2j/Oo6fJ/x8yH/aNMrx0e4B9e9KCQcg4PsaTt
+ RTEZcOi6bbeJH1W1tltbt42jlEXyxuCQclegbjqK+9/2RPBWItf8fXkXLf8A
+ Eu04sO3DzMP/ACGoI9HFfEtnaXN/q1rY2cL3F5czLDBEg5d2IVVHuSQK/Yvw
+ J4VtvBPwh0DwvbbCLC0VJXXpJKfmkf8A4E5Y/jXwfiDmroYBUE/eqaf9urf9
+ F95974e5Sq+Pddr3aev/AG89v1f3HW0UUV+Hn7iFFFFABRRRQAUUUUAFFFFA
+ BRRRQBl63o+n+IfBuraBq0AudL1KzktLuI/xxyIUYfkTX89HjzwjqHgL4yeJ
+ fBuqBjeaTfyWxcrjzVB+SQD0dCrj2YV/RXX5cft8fDj7D438NfFCwt9tvqUf
+ 9masyrx58aloXPqWjDr9IRX6V4aZv9Xx8sNJ+7UWn+Jbfer/AIH5n4nZP9Yw
+ EcVFe9Tev+F7/c7fifndRRRX70fgNgooooAKVWdJFkjYpKjBkb0YHIP50lFA
+ H0Xp1+mq6BZ6lHwLiIOw/ut0YfmDVyvN/h7qO62v9HkblD9otx7HhwPxwfxN
+ ekV5VSHLJo9ilPngmHeijtRUGpl67/yImtf9eUn8q+fB0FfQeu/8iNrX/XlJ
+ /KvnwdBXbhPhZ52N+JBRRRXWcYV9p/sNfDr/AISr9qa+8bXsHmaR4Rs90JYA
+ q19cKyRjB/uRea3sWSvip3WOF5HO1EUsx9AOTX7kfsofDhvhx+xf4bt7238j
+ X9bB1nVg33lknAKRnv8AJEIkx2KmvieP82+pZTKMX71T3V6P4vw0+aPuPD7K
+ PrubRnJe7T95+q2/HX5M89+Mv7FfgPx415rvgJ4Ph54ukJkdLeHOmXj8n97A
+ MeWxOPnjx3yrV+Y/jT4WePvhP8TNM0nx54eudIaW+jWzv0Pm2V786/6qcDaT
+ gg7Dhxnla/dXxv8AEXwf8O/D39o+K9ZgsN4P2e1X95c3JH8McQ+Zj74wO5Ff
+ n78Wf2ifEHxH0q98O6bplpoHg2f5ZYLmJLi7u1BBBdiCsXIBAT5h/fr5HgbO
+ c9qWhKPPR7ydrej1b9LP1R9jx1kuRQvOMuSt2jrf1WiXrdejPnqX/j6k/wB4
+ /wA6Z29aPxor9MPzMPWiiigD6V/Zd8Ff8JJ+0B/b11D5mm+HohckkZU3DZWE
+ fUYdx7xiv0srwz9njwV/wh37NmlNcReXqmsf8TG7yPmUOB5aeoxGF47MWr3O
+ v574wzT69mc2n7sfdXy3+93P6F4Pyv6jlkE170vefz2+5WCiiivlz6gKKKKA
+ CiiigAooooAKKKKACiiigAryz41/D6L4ofsyeLPB5RGvrm0Mumu2Bsuo/nhO
+ ewLKFJ/us3rXqdFb4bETw9aNWm7Si016owxWGp4ijOlUV4yTT9GfzVyxSwXM
+ kM0bwzRsVkjdcMrA4IIPQio6+qP2wfhz/wAIH+2DqmoWcBi0TxMn9q2hA+VZ
+ WJFwmfXzMvjsJFr5Xr+r8sx8MbhKeIhtJJ/8D5PQ/krNMBUwWLqYee8G1/k/
+ mtQoooruOEKKKKANLRtROk+KbHUeSkUn70f3ozw4/I5/CvoY4zlSGU4Kt/eB
+ 5Br5n6jnkV7b4N1L+0PAsCO265sm+zyknkgDKH8V4/CuTFQ0Ujtwc7PlOqoo
+ oriPQMzXP+RH1r/ryk/lXz2Ogr6E1z/kR9a/68pP5V89joK7cJ8LPOxnxIKK
+ KK6zjPT/AINeENO8bftJeGdJ165tbHwrazf2n4hurqQRxR2VuRJIrMem9tkY
+ 9fMr9IPiT+1nI5n0j4W2iiMZQ+INQg4+sEDdfZpMDn7pr85/AGmiLQbvVJky
+ 91II4cj+BDkn8W7/AOzXoHU18pm+SYbH4uNXELmUFZR6X6t93su2mzPrsnzr
+ FYDBypYd8rm7uXW3RJ9Fv567lzUtS1LWfENxq+s6je6xq1wf315eTGSV/bJ6
+ D0UYA7CqdHr2or0oxUUklZI89tyd3uw9qO1FB6GmIK9I+EvgxvHnx98P+H3j
+ aSwM/n6gewt4/mcE9t2AgPqwrzev0A/ZI8Ff2f4A1jxzdw7bnVJPslixHIgj
+ b52B9Gk4P/XIV4HE+afUMuqVU/eei9X/AJb/ACPf4Yyv6/mNOk17q1fov89v
+ mfYCqqoFUBVAwABgAUtFFfzkf0cFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF
+ FFFAHyP+2d8OP+E2/ZHutes4PN1rwpKdRhKrlmtiNtyvsNmJD/1xFfi/X9KN
+ 1a299plzZXkMdzaXETRTxSLlZEYEMpHcEEiv59fi54CuPhn+0b4s8FzCQw6f
+ fN9ikfrLbPh4XJ9TGy59Dkdq/bPC7N+ehUwUnrH3l6Pf7nr8z8Q8U8n5K9PG
+ wWkvdfqtvvWnyPOKKKK/WD8kCiiigArsfA2o/YvGq2jtiC/TyTk8CQcofzyP
+ xrjqVWdJFkiYpKjBkb0YHIP51M480WioT5ZJn0tQKp6dfpqnh+z1GPhbiIOw
+ /ut0YfgQa6zwz4U8R+MtfbTfDGlT6rcJjz5AQkFsP70sp+VB9efQGvFrVYUY
+ OdSSilu3ol8z3qFKdaajTTbeyWrfojjdbOPBOs/9eUn8q+eh0FfrN4W/Zz8J
+ 6foNx/wnDjxjfXMDRTWsbPBZQhhg7MEO7Y6OSMHkLXzj8T/2ONc0o3Gr/Ce/
+ m8UaaMu3h/UJVXUIh1xDKcJcD0Vtrn/arwMv47yepiHQ9pbtJq0X8+nq0l5n
+ u5lwJnFPDqv7O/eKd5L5dfk2+58T1LDBLdXsNrbqWnmkEcYHqTgUt1bXVhrF
+ 1p2oWl3p+o2rmO5tLqFopoGH8LowBU/UV2vgHTvtPiifUnXMVlH+79DK4IH4
+ hcn8RX3EppQ5j4aFNufKz1W1tYbDS7axgA8m2iEaY74HX8Tk/jU9H40nO8fS
+ vKPYQUveiigYe+KKKKBGnoukXuv+MNM0PTo/Nv7+6jtoF7bnYKM+3PJ7Cv2T
+ 8N6DZeF/AOj+HdOGLPTrRLeIkYLbRgsfcnJPuTXwX+yb4L/tb4saj4xu4t1l
+ osHlWpYcNcSgjI9dse/PoXU1+h9fjPiJmntcXHCxekNX6v8AyX5s/aPDrK/Z
+ YSWKktZ6L0X+b/JBRRRX5yfowUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU
+ UAFfm/8At9fDnztI8LfFKwgy9uf7J1cqP4GLPA59AG8xST/fQV+kFcR8SPBV
+ l8RfgV4o8FX5RYdWsHhjkcZEMo+aKTH+xIqN/wABr3OG82eXZjSxHROz9Ho/
+ 8/U8LiXKVmWW1cP1auvVar/L0P53qu6dYy6nrltp8DxRzzsVjaQ4XOCeT26U
+ up6be6P4k1DSNSt3tNRsbmS2uoH+9FIjFXU+4IIrT8J/8lK0b/rv/wCytX9S
+ ua5OZH8rKHv8sjKvtPvdMv8A7LqFtLaz9g44ceqnow+lU6+kLq1tb/T2tL63
+ hu7dv+Wcq5APqO4PuK801jwBcRu02gu96h5+xyn96P8Acbo/0OD9axp4lPSW
+ htVwso6x1R51RSsrJIyOpV1JDKeoI6ikrpOVnvfwL1f4ax+Ln0f4q6vqmkaC
+ 0olsprcbbcyH7yXEgy8cZwDuUeuSK/WTR7LRtP8ABun2/hqDSrfw4ybrH+y9
+ ptZBj7ysvDH1JJb1r8IK9U+Gnxm8ffCnU93hfVt+kO4a60W+BmsrjnJzGT8j
+ dfmTBycnNfn/ABjwfXzX95RrNNfZfw+q7Pzd/kfoPBnGVDKX7OtRTT+0viXk
+ +68tPmfsy33OxpSPXkV88/Df9pr4a/EDTlttVvYPAPiVY90tjqtx/o0uBljB
+ cHhuhOxsNgVgeOv2i0iefTPh7aebICVbXNQh+Ue8EB6+zycdwtfj1HhPNqmJ
+ eH9i1Jbt6Jed9n8r36H7LW4uymnhViPbKSeyWsn5W3Xzsu53/wAbfC3wf8Qe
+ CEuvi1DbW90Iium6laNs1gccCAqC0i/7Lho/pX5/6bo+n6FaXFhpU97d2P2q
+ SSG4vIkjuJUJ+UyKhKhgoAwpIra1HUNQ1jXrjVNYv7zVdTnOZru7lMkj+2T0
+ HsMAdhVTvX7Rw3kdTK8N7KVZzv0+yv8ACunn33sj8W4kzunmmJ9rGioW6/af
+ +J7P7tNrsPek/wCWg+lL3qhqOo2Wk6Y1/qMxtrRCFaTYW+Y/dXjucHGcZr6F
+ anzzdi/39axdT8Q6XpN3FazzGa+kkVFtYcM67iBluyjnvz7V53rXjm/vd9vp
+ avpVoeDJnM8g+vRB7Dn3rj7H/kOWR6k3UZJPJJ3jk+tdUMM95HHUxa2ifSDA
+ rIynkg4yBTakl/4+ZP8AeP8AOvYfgN4K/wCE2/aR0W0uIvN0rT2/tC/BGQUi
+ IKqfUM5RSPQn0ry8Zi4YbDzrT2imz18FhJ4rEQow3k0vvP0H+C3gr/hBP2dt
+ B0iaLytTnj+2aiCMN58oBKn3Vdqf8Ar1Wiiv5lxeJnia86095Nt/M/pvCYWG
+ GoQow2ikl8gooornOgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
+ D8df23fhx/wiX7UUXi6xg8vSPFdubhiowq3ce1Jx/wACBjkz3Lt6V8peE/8A
+ kpWjf9d//ZWr9pf2rfhz/wALF/Y71+O0g87W9DH9r6dtHzMYlbzUHc7ojIAv
+ dtvpX4g2l3cWOoxXdpKYLqI5jkABKnBGRnvzX9GcB5t9fyiMJP3qfuv06P7t
+ Pkz+buPco+oZxKcV7tT3l6/aX36+jR71q2t6ZokAbULjZKwzHbxjdLJ9F7D3
+ OBXlOteMdU1ZXt4CdM09uDFE/wC8cf7b9fwGB9a5V3eSd5ZXeWVzl5HYszH1
+ JPWm19jToRjq9WfHVcTKe2iCiiitznCiiigAIDAhgGB6gius0XxhqmkqkE5O
+ qaeOBDM/zoP9h+o+hyK5QdaSplFSVmVGbi7pn0FpOt6ZrcG7T590yjMltINs
+ qf8AAe49xkVroju+1FLt1OOw9T6V80I7xzpLE7xSoco6MVZT6gjpX0B4fv7u
+ /wDh9pM15N5sskO6VtoBkbcRubHU4A59q4K9Hk1R6WHxHO7M1yAOjBj3K9Pz
+ 719Ffs3WlnqPjLx9p2pWVlqenXOhQx3NneQLNDOv2j7rowIYfX8K+c6+k/2Y
+ /wDkpfjT/sCw/wDpQK+U4vbWTV2uy/8ASon1vCEVLOaCa3b/APSWYvxR/Y40
+ rURc6z8JL2HQb7ln8N6lOTZSnri3nOWhPokm5P8AaWvhHVPDniDwh8TIPD3i
+ rRdS8O65BcxmSzvoTG5G8fMp6Oh7MpIPrX7h319Y6VodxqeqX1npem24zPd3
+ cojiT6se/sMk9ga+OvjJ8XPCnjrww3hTS/C+n+ItPik3Qa3rNsQ9s4Od9mvD
+ xngfOxAP9w18twVxVnFaaoVIOrBbyejj6yekvR+8+59Pxvwnk1CDr05qlUe0
+ VqpeiWsfX4fI+epf+PmT/fP86/Rr9lXwV/YPwQufFF3Ft1DX590RYcrbRkqn
+ 03MXb3BWvgXwh4cvPGPxR0Pw1ZFvtOo3iwmTbny1Jy8h9lUMx9ga/ZDTNOtN
+ I8O2GlafEILGyt0t7eIdERFCqPwAFaeIuaeyw0MJF6z1fov83+RPhzlftcTP
+ FyWkNF6vf7l+Zdooor8cP2QKKKKACiiigAooooAKKKKACiiigAooooAKKKKA
+ CiiigAooooAQgMpDAEEYIPevwP8A2gfh0fhd+1d4q8MwwGHSGn+2aRxhTazZ
+ dFHqEO6PPrGa/fGvgX9vL4cf2z8HtC+JFhb7r7QJ/smpMq8taTMAjE+iS4AH
+ /TZjX3vh3m/1PNFSk/dq+78/s/5fM+A8Rsn+uZW6sV71L3vl9r/P5H5Q0UUV
+ /Q5/OgUUUUAFFdT4P8D+MPiB4wj0DwT4c1PxLqzYLxWsfyQKTgPNIcJEmeNz
+ kD0zX6U/Bz9hbw9ootdd+MF3B4t1cYdNAtGZdNtz1xK3DXDDjrtTI+6w5rwM
+ 74mwGVQvXn73SK1k/l09XZH0GR8MZhmsrUIe71k9Ir59fRXZ+dfhX4Y+OfGX
+ gDxF4u0PQZ28I6HYT3uo63dHybQJChd44nb/AF0uBwiZ5IyRXAA5UH1r98Pj
+ rZ2em/sE/FGx0+0trCxt/CN5HBb28QjjiUQMAqquAAPQV+B6/wCrX6Vw8J8R
+ 1M5p1asoKKjKyW+lur7/ACR3cXcN08mqUaUZuTlG7e2t7aLsLXu/hT/km2i+
+ 8B/9DavCK938Kf8AJNdG/wCuB/8AQ2r6TFfCj5vB/G/Q6DHNekfDX4ht8N9U
+ 8SalBpK6xf3+nx2tpHLKY4Y2WTeXkI+YjHQLyT3Feb96K8fGYOjiqMqNZXi9
+ 16O/T0PcweLq4WtGtRdpR2fyt1Ol8VeMPE3jbXFv/E+qy6i8Zzb2yr5dtbD0
+ iiHyr9eWPc1zVH+TUkUUk9zFBBG8s0jBI0RSWZicAAdya0o0adGmoU4qMVsl
+ okZ1q1StUc6knKT3b1bPsv8AZF8FfaPEeu+PLuEGKzT7Bp7MP+WrANKw9Cqb
+ V+kjV95Vwnwz8Hx+BPgf4e8NBUFzbWwa8Zed87/PIc9xuJA9gK7uv514kzT6
+ /mFSsn7uy9Ft9+/zP6L4byv+z8up0Wvetd+r3+7b5BRRRXhHuhRRRQAUUUUA
+ FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNzbW95p89peW8N1azIUmh
+ mQOkikYKsp4II7GpqKabTuDVz4O+Mf7EPhbxOLrXPhdPb+D9cbLtpU2Tp9wf
+ RMZaAk+m5ewVetfmX44+HvjL4ceMX0LxpoN9od+MmLzlzHOoON8cgysi+6k1
+ /RLXNeK/B3hfxz4Rn0Hxdoen6/pMv3oLuPdtP95W6ow7MpBHrX6Hw/4h43BW
+ p4n95D/yZfPr8/vR+dcQ+HWCxt6mG/dz/wDJX6rp6r7mfzmUV+hnxi/YZ1fS
+ vteu/CO8k13Txl30G+kAuohySIpDhZR6K2G93NfAGo6bqOj65daZq1jeaZqV
+ tIY7i1uoWiliYdVZWAIPsa/acoz3BZnT58NO/ddV6r+kfiWb5DjssqcmJhbs
+ +j9H/TNTwv4u8VeCfFsGveD/ABDqvhvV4iMXFjMV3gHO2RD8siequCPav0g+
+ Dv7dul34tdC+M9jDoF7gIviXTomNlKcYzPFy0BPdl3Jk/wAAr8vqKxzrhvAZ
+ pC2Ihr0ktJL5/o7ryNsk4lx+VTvh56dYvWL+X6qz8z96/jjqWnax+wF8UNT0
+ m/stU0258JXr293aTrLDMphb5ldSQw9wa/BJf9Wv0r0PwL478YeGrLU/B2ia
+ /eWfhXxLBJp2saS4EttLHMux3SNsiOYA8SJg565rI1nwfqmjo80QOpaev/Le
+ FTvQf7adR9RkV5vCvD7yWFWjKfMpO6ezta2vn8z0uLOIVnc6VeMHFxVmt1e9
+ 9PL5HK17v4T/AOSa6N/1wP8A6G1eDgggEEEHoQa948KD/i2ujf8AXA/+htX0
+ uL+FHzWDfvv0Ogorq/CXgjxR468RDTPDGkXWpzjHmug2xQg/xO5+VR9Tz2zX
+ 3V8Nv2XPDnh0W+qeN5IfFGsDDCzAP2KE+hB5l/4Fhf8AZ718lnPEmBy2P72V
+ 5fyrf/gfM+xybhvHZlL91G0f5nov+D8j5C+HfwX8b/Ei5jm0ux/s/RN2JNVv
+ QUgHrs7yH2Xj1Ir7++HHwI8EfDsQ3sVt/bviJME6pfICyN6xJyI/qMt/tGva
+ Ioo4LaOGGNIYY1CpGihVUAYAAHQCn1+P55xjjsxvBPkh2XX1fX8F5H7DkfBu
+ By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU
+ UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9
+ piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s
+ fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/
+ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL
+ /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc
+ 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b
+ /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o
+ 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB
+ k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A
+ tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI
+ TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm
+ Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF
+ FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
+ AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR
+ WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q==
+ </data>
+ </array>
+ <key>name</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>passwd</key>
+ <array>
+ <string>********</string>
+ </array>
+ <key>passwordpolicyoptions</key>
+ <array>
+ <data>
+ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
+ WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
+ IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
+ LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp
+ bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr
+ ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt
+ MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8
+ L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgo8L2Rp
+ Y3Q+CjwvcGxpc3Q+Cg==
+ </data>
+ </array>
+ <key>realname</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>shell</key>
+ <array>
+ <string>/bin/bash</string>
+ </array>
+ <key>uid</key>
+ <array>
+ <string>501</string>
+ </array>
+</dict>
+</plist>
diff --git a/chef/spec/data/mac_users/10.7.shadow.xml b/chef/spec/data/mac_users/10.7.shadow.xml
new file mode 100644
index 0000000000..8c3b6dd3d7
--- /dev/null
+++ b/chef/spec/data/mac_users/10.7.shadow.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>SALTED-SHA512</key>
+ <data>
+ b3XXGQRB+sw0KR676h/HVrJC1P6bz/FBvMuE8ZeeJ+U5U5qjH599zJLAzqlZ6hjhi3IO
+ NY5/vjz76qVhRW9roAiTejA=
+ </data>
+</dict>
+</plist>
diff --git a/chef/spec/data/mac_users/10.8.plist.xml b/chef/spec/data/mac_users/10.8.plist.xml
new file mode 100644
index 0000000000..4ed294eb38
--- /dev/null
+++ b/chef/spec/data/mac_users/10.8.plist.xml
@@ -0,0 +1,559 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>KerberosKeys</key>
+ <array>
+ <data>
+ MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCBxHUxawMNiov49kfZn
+ M38ddgXFivE9SNpYgPamy+6prKJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEVB
+ OUJFMDZCQzExQjAxODdEMzQ1MkI3QTA5NjE3QjBCOTI2OTY4RXZhZ3JhbnQw
+ Y6EbMBmgAwIBEaESBBBD9mGbvFTNIUKAvAbnjh8ookQwQqADAgEDoTsEOUxL
+ REM6U0hBMS40RUE5QkUwNkJDMTFCMDE4N0QzNDUyQjdBMDk2MTdCMEI5MjY5
+ NjhFdmFncmFudDBroSMwIaADAgEQoRoEGG4TEFIf416UH7MvFW7sAXC8ArC6
+ AhbCraJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEVBOUJFMDZCQzExQjAxODdE
+ MzQ1MkI3QTA5NjE3QjBCOTI2OTY4RXZhZ3JhbnQ=
+ </data>
+ </array>
+ <key>ShadowHashData</key>
+ <array>
+ <data>
+ YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50
+ cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIDqTC0mXYAboOwN/M0lPfwd6Ry+CAa0
+ rMHtf+Iq689r61NE0PRC5ZD/oE1nkHXaOvsRnkG3K16vCO5KpUaTciZG1Rnu
+ BIQ964o+l3Qo0z9iXoOIeRPlwTtwA1lhXgCte8PnoMmK/D4Z0TYCckVPjTOp
+ IU0vvovmjR+YIbJmiTEjZk8QIPmU7y9zt8VZTr0VUzAJdrIHM84OJNZZeD2H
+ 89gcu7apEZugCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA
+ AAAAAOo=
+ </data>
+ </array>
+ <key>_writers_hint</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_jpegphoto</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_passwd</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_picture</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>authentication_authority</key>
+ <array>
+ <string>;ShadowHash;HASHLIST:&lt;SALTED-SHA512-PBKDF2&gt;</string>
+ <string>;Kerberosv5;;vagrant@LKDC:SHA1.4EA9BE06BC11B0187D3452B7A09617B0B926968E;LKDC:SHA1.4EA9BE06BC11B0187D3452B7A09617B0B926968E</string>
+ </array>
+ <key>generateduid</key>
+ <array>
+ <string>11112222-3333-4444-AAAA-BBBBCCCCDDDD</string>
+ </array>
+ <key>gid</key>
+ <array>
+ <string>80</string>
+ </array>
+ <key>home</key>
+ <array>
+ <string>/Users/vagrant</string>
+ </array>
+ <key>jpegphoto</key>
+ <array>
+ <data>
+ /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw
+ bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs
+ AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA
+ AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH
+ MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk
+ AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH
+ ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv
+ ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo
+ AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA
+ AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv
+ S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD
+ Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa
+ AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA
+ AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp
+ RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE
+ 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg
+ AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA
+ QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo
+ AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E
+ FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy
+ AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA
+ RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl
+ AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA
+ bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg
+ AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA
+ bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs
+ AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA
+ RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv
+ AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD
+ wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl
+ AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A
+ ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH
+ AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA
+ bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs
+ AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E
+ RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn
+ BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA
+ ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz
+ AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo
+ dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN
+ AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A
+ ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA
+ /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH
+ BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA
+ QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O
+ Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A
+ AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE
+ AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX
+ GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3
+ eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI
+ ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB
+ AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR
+ BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico
+ KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
+ 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0
+ 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ
+ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i
+ P8K43v0P5UlMZ7LDPDcW6ywSJLGejKcipa8gtL65sLjzLaV4z3HVT9RXb6d4
+ mt7grFegW0398fcP+H40hHUUUgIZQykEEZBHeloAKKKKACiiigAooooAKKKK
+ ACiiigAooooAKKKKACiiigAooooAKKKguLmC1tzLcSpDGO7H/OaAJ6K4y48W
+ gXyi2tvMtwfmLnDN9PSuisNVs9RjzBJiQfeibhh+Hf8ACgC3cgHT5wQCDG2Q
+ fpX4u/CD9rf4h/C+aHRtXkfxt4PjbaLG/mP2i2XP/LGc5YADorbl7AL1r9o7
+ j/jxn/65n+VfzYS/8fMn+8f51+qeG+W4XHUcXSxEFKPub/8Ab2z3T9D8p8S8
+ zxWBq4Srh5uMvf2/7d3WzXkz99/hb8cPh38XtCW48J6yn9ppGGutHvMRXtv6
+ 5TJ3L/tIWX3zxXrtfgD8JPhh8TviF4ut9Q+Hlvd6ZDZTgv4nluHtLSxYd1mX
+ 5nkGfuRbm+lftD4C1HXfDvw+0vRfGHiW48batBFtudbeyS2aZvaNf4QOAxO4
+ 9W5r57jHh7BZXX5cPXUr/Z3lH1a0++z8nufQ8G8R43NaHNiKDjb7X2Zeiev3
+ XXpseu0VBb3MF1biW3lSaM91NT18Yfankuk/FDQfHPgxNb8B6zYa1ojna17a
+ ybnjb+5Ihw0T/wCy4Bqkzs8hd2Z3JyWJyTX4a+HPF3ijwL4/bxB4O13UPDus
+ K5DTWr/LMufuSocrKn+y4Ir76+Fv7YXhzXfs+j/FO1tvB+sHCLrtorNplwfW
+ VOXtifX5o/da/Rc/8O8bg71cL+9h2+0vl19Vr5I/OOHvEbBYxqli/wB1Pv8A
+ Zfz6fPTzPs6jt61HFLDPY293bTwXVpcIJLe4t5VlimQ9GR1JVgfUE0/mvzux
+ +jppq6F70lL6elHagBPyo7elHejvQM1tP1m905wI3MkPeJ+V/D0ru9O1yy1D
+ CK3k3B/5ZOeT9D3ry/tR0YH360CPaaK8607xJd2m2K6zdQDjJPzj6Hv+NdzZ
+ 39rfweZbSq/95Twy/UUgLlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS
+ MyohZ2CqBkknAFYmo69Z2AZFb7Rcj/lmh6fU9q4S/wBWvNRc+dJiLPyxJwo/
+ x/GgDrNR8TwQho7BRcydPMP3B/jXE3V3c3lwZrmV5X9+g+g7VXzj2PrS8EZP
+ FMBvtT1ZkkDozI68gqcEU0gjP86Tv9aBnWWPiWXyWttQ/eIyFRMo+YccZHQ/
+ pXwD8MP2PdC0iSDWvivd2/inVc+Yvh+xkZdOgPXE0gw9wR3Vdqf71fadHQ5r
+ 1MvzvG4GlUp4efKp2u1vpfZ7rfpqeRmGR4LHVadTEQ5nC9k9tbbrrt10IoIY
+ LTTbaxs7e2srC2Ty7a1toVihgX+6iKAqj2AqXtSVla7r2ieFvDjav4j1S00f
+ TRwstw3Mp/uxqPmkb2UH8K86EJ1JqMVeT6LVtnpznClByk0orvokv0N62u7i
+ yuRLbTPE4646H6jvV3U/i74Q8M3MVh4p1SGw1Z03rZwRvNMU/vtGgLIuSOWx
+ nPFfEXjj9ojWNT87TvAcE/hzTzlW1S4UG+lHqi8rAD6/M/uK8d8JPJL4xvbi
+ eSWe5lgZ5ZpXLySMWXJZjyT7k1+g5XwBXnTdbGPkX8q+L5vZfi/Q/PM08QKE
+ aipYKPO/5n8PyWjfrovU+P5/+P2b/fP86irqfG/hfUvBXxe8SeE9XXGoaVqE
+ ttK23Ak2sdrj/ZZcMPYiuWr97pVI1IRlF3TV0fz/AFacqc5QkrNOzPpr4GfE
+ bxb4B8GyDQdQEmlm/czaReZks5OFzhc5jb/aQg/WvvvwJ8ZvCPjeSGwkk/4R
+ rxG/A02/lGyZv+mE3Cv/ALp2t7GvzM8Af8iHL/1+yf8AoK128dvJeXcFlBbT
+ XtzO4WG2hiMkkrdgqgEk/SviOIuE8BmUpTkuSf8AMv1Wz+evmj7vhzizH5bC
+ MIPmh/K/0e6+Wnkz9V2VkdlYMrDhlIwRTfzr5Ut/E3xQ+CX7Nlz4x8foniHQ
+ bW7tbW38O3FyDqduk0mzd9p5CbRyIn389Ste2fDz4peBPipob3ngvW0vbmJN
+ 15pNyvk6hZ/9dISclf8AbQsnvX4hj8jr4eMqsGqlJO3PHWN9N+268uibP3DA
+ Z/h8TONKadOq1fklpK3l32fn3SO/780d6Xr3/Wkrxj3Be1JS/likoAU9KfFL
+ LBOssMjxSA5DKcGmUn50Adrp3in7sOornt5yD+Y/w/KuximingWWGRJY26Mp
+ yDXjXOenWuL8V/Fzw58MWYahqMlzrRXcmiWJElxJ6GQH5Yl/2nwfQGt8LhK2
+ JqKnRi5SfRf1+OxzYrF0cNSdWtNRiur/AK/A+naK5vwdr0nij4VeHfEktqtl
+ JqenxXTW6ybxEXUNt3YGcZxnFdJWVWnKnNwlutDWlUjUgpx2auvmFFFFQWFF
+ FFABRRRQAUUV4D8evjfbfBHQ/Cmpajo17qmk6tqD2l3NYyL9otFEe4SJG3yy
+ e67lPoc104PB1sVWVGjHmk9l30ucuMxlHCUXWrStFbv52PcL3UbTT4d1zKFY
+ jKoOWb6CuG1LxHd3ZaODNrbnsp+Zvqf6CvOvCnjfwv8AETwqfEfhHxDZ+JNP
+ JHnyRMfOt2P8E8bfPE3swx6E10fFZVaU6U3CpFqS3T0a9UbUa1OtBVKclKL2
+ a1T+Yd6KPyrk/F3jjwv4F0lbnxLqaWs0i7rawhXzbu5/3Igc4/2mwvvVUKFW
+ tUVOlFyk9ktWLEYilQpupVkoxW7bsjq6O9fK+hftaeBrj4n3nhvxlpl34It9
+ yHT9Wkm+02xVlyFudq5ibP8AGoZPp1r6lhlgudOt7y0uLe8sbhBJbXVtKssM
+ yf3kdSVYe4NduZZPjcvmo4mm4327P0aun566HDlmdYHMIuWGqKVt+69U7P8A
+ Akyce1LwTx1pM0qqzyBEVmduiqMk15h6YnQ1HPNBa6bc315cW1lY2yeZc3Vz
+ MsUMCf3ndiFUe5NeXeOvjH4R8DtLY+b/AMJJ4hQY/s2wlG2E/wDTaYZVP90Z
+ b2Ffm58ZfGXxR+IOpvdeKdU/tDwvFIXtNJ0pDFY2Y7F4cku4/wCejlifUV9n
+ w9wTjczanP8Ad0+73f8AhXX1dl2bPiuIuOMFlicIfvKnZbL1fT0V33sfUfxR
+ /bE0LR/tOjfCmzt/FWqDKN4gv42GnQHpmGM4a4I7M21P96vmXR/FPiTxra3f
+ iLxdrmoeItblvJAbq8kzsXC4SNRhY0HZVAFfP4IIyCCD0INexeAf+RCk/wCv
+ 2T+S1+1Zbw1gMqo2w8Pe6yesn8+i8lZeR+JZjxNmGbV74ifu9IrSK+X6u78z
+ ta6zwd/yM0//AF6t/wChLXJnpXt3wO+HWp/EHx1rENldR2FtZWAae5kjLKGe
+ RQicdyFcj/dNGZ4inQws6lR2iupeWYepXxUKdNXk+g79vb4c/wBm/Erw78Tr
+ CDbaaxENP1RlXgXMS5iYn1eIFfpBX58V+/nx0+HafFH9lzxX4TWJZNTktTca
+ UxxlbqL54sHtuI2E/wB12r8BXR45njkVkkUkMrDBBHUEV4/h1m/1vLPYyfvU
+ tPl9n9V8j1vEfJ/qmae2ivdq6/P7X6P5n1L+zt8Orj4lWd/pltr+j6StndNL
+ eRvKJL7yyF+aK3zlx1+c4UY5r9FfBnw/8KeArBk8OacFvZE2z6pdESXk/wBX
+ x8i/7KAD61+JVpd3mna1aalpt5eabqdrIJLW8tJmimgYfxI6kFT9DX218Lv2
+ x9V082+jfFuym16yGEXxJpsCi9jHTNxAMLOPV02v3IauDjnh/OcYnPDVOan1
+ gtH/APbej26Js7+A+IMmwbUMTT5anSb1X/2vqvm0j6A/a0/5MP8AEIH/AEGN
+ N/8AR5r8p7G+vtK16z1XSr+90rVbSQSWt7ZTtDPA3qjqQR/XvX6f/tJ+I/D3
+ i3/gnHrOv+Fdc0zxFos+sad5d5Yzb1z5/KsPvIwyMqwBHpX5b11eGlJxympC
+ as+eSaf+GOjX6HL4l1oyzenUpyuuSLTT85apo+5/hd+2TqFobfR/i9Yy6va8
+ KnibS7cC6QetxbjCy+7x7W7lWr700TW9F8TeErTX/Der6dr+h3IHkX1jMJIm
+ OM7T3Vh3VgGHpX4SV2Hgjx/4y+HHis6z4L1670S7fi5iXD212v8Admhb5JB9
+ Rn0IrHiDw4wmKvVwbVOfb7L+X2flp5G3DviTi8JaljF7SHf7S+f2vnr5n7fY
+ 70vavkv4XftbeDfF722j+PobbwB4kchFvDIW0q7Y8cOfmtyf7r5XPRhX1hcT
+ QWelSajeXVraaakQle8mmVYAhHD+ZnaVPYg89s1+MZnk+My+t7LE03F9Oz9H
+ s/kfteV51gsxo+1w9RSS37r1XQf+VYviHxLoHhLw7/aviXVrXSLM5ERlOZJz
+ /djjHzOfoMepFfP3jj9ouxtPO074fW0eqXPKnWr6I/Zk94Yjgyf7z4X2NfKm
+ r6vq3iDxFNq+u6le6xqkv37m7k3vj+6Oyr/sqAPavrcj4BxWKtUxX7uHb7T+
+ XT56+R8jnnH+Fw16eEXtJ9/sr5/a+Wnme7eOP2hde1kzad4Khn8LaU2Va/kw
+ dQnHqpGVgB/2ct/tCvng5aaSRmeSWRi8kkjFndj1LMeSfc0UV+t5blOEy+n7
+ PDw5V17v1e7/AE6H5JmWbYvH1faYibk+nZei2X9XP1/+E3/JsXgD/sAWv/op
+ a9Crz34S/wDJsPgD/sAWv/opa9Cr+ccx/wB7q/4n+Z/R+W/7pS/wr8kFFFFc
+ R2hRRRQAUUUUAFfn7/wUE/5Ij4B/7Dkv/og1+gVfn7/wUE/5Ij4B/wCw5L/6
+ INfVcEf8jzD+r/JnynHH/IixHovzR+YfhzxJ4h8H+MbfxD4U1vUvDutw8JeW
+ MuxyP7rjo6HurAg+lffXws/bF0rVWtdD+LFjFoOpMRHH4i0yBmspm6Dz4Blo
+ ST/Em5OfurX511Ys/wDkNWP/AF8x/wDoYr98zvhvAZrC2Ih7y2ktJL5/o7ry
+ P5+yPiTMMqqXw89OsXrF/L9VZ+Z+mPjr9oq7lln0z4fWr2EQJR9bv4QZ294Y
+ TkJ7M+W7gCvmO6ubq/1a41C/urm/1Cdt091cymSWU+rMeT/Smy/8fMn+8f51
+ H+PNcGVZLg8up8mHhbu+r9X+m3ZHqZrnWMzGpz4id+y6L0X6792eJ+N/+SmX
+ n/XCH/0Cul+Gnxj8ffCfUifCmrB9Hkk33WhX4M1hcep2ZzG3+3GVP1rmvG//
+ ACUy8/64Q/8AoFcpX0VXCUcTh/ZVoKUWtU1dHzFLF1sNiPa0ZuMk9GnZn6se
+ B/2q/hr4s8I3Fxq0eqeFfEdrDvn0UxG5+084JtpVADrnGQ+1lByc9a8s8d/H
+ bxT4shuNO0USeEvD7gq0NtNm7uF/6azDoD/dTA9zXxj4A/5Hub/ryk/mtewZ
+ z/8AWr43DcFZVgsS6sIXe6UndR9P83d+Z9riONs2x2FVKpUstnZWcvX/ACVl
+ 3QgAVcAADPQU4Eq2VJU+opMcUV9KfOHJa14O0vVWknt9ulX7cmSJP3ch/wBt
+ P6rg/WrHhTS7zR/DM1jfJGswu3ZTG+5XUhcMD+B6810tFW6knHlZkqUVLmW4
+ vb0r9RP2cPBX/CI/s3afd3MIj1XXD/aFwSOQjDEK/TZhsdi7V+fXwu8HP48+
+ Ovh7w3tY2c1wJL5hn5bdPnk57EqCoPqwr9f0RIoUjjRY40UKiKMBQOgA7Cvy
+ zxHzTlp08JF7+8/Tp+N/uP1Xw3yvmqVMZJbe6vV7/hp8x1fiL+1z8Of+Ff8A
+ 7YmtXFpB5WieIh/a1jtHyq0jHz09OJQ5wOiulft1Xx7+2r8OP+Ez/ZQk8S2U
+ Hm6z4TmN8hVcs1q+FuF+gASQn0iNfL8B5v8AUc1gpP3anuv57P7/AMGz6jj7
+ J/r2UzcV71P3l8t1934pH400UUV/SB/NRPDdXVtZXtrb3VzBaXmz7Zbxyssd
+ zsO5PMUHDFTyCRkHpUPB6cH0pKKVkF29wopc8YPIpKYAQCCCAQRgg969m8Ba
+ tq138N7jRLvVtTutFsb4NZafNdO9vblkydiE4XJ54HHbFeM16v8ADv8A5F3V
+ /wDr8T/0CufFRThdrY6sJKSqaPc9AzRTXdY03SMqr6k/kK9Av/hh400j9nPx
+ H8Utb0ibRvDOk2iXEcV4DHd3wZ0QeXGeVX5wdz4z2B615FfE0qPL7SSXM0lf
+ q3sl3PZo4atW5uSLfKm3bolq2+xwPOKWs/TdV0/V7Iz6ddJcIo/eJjbJH/vK
+ eR9envV/vW7TWjME76o/UT9m7xcnij9mTS7OR1/tDQ2OnTqODsUAxNj02FVz
+ 3KtXvtfmv+yz4y/4R/8AaAfw/cS7NP8AEFv5GCcKLiPLxH8R5ifVxX6UV/Pf
+ GOWfU80qJL3Ze8vnv+Nz+heDsz+u5XTbfvR91/Lb8LBRRRXy59SFFFFABRRR
+ QAV+Vv7fPj9NS+KHhf4c2bo8Wj2xv9QK8kTzDEaH0Kxru9xKPSv1D1TU7LRf
+ DOo6xqU62unWNrJc3UzdI441Lux9gATX883xA8YXvj/42eJ/GWob1uNX1CS5
+ EbHPlITiOPPoiBVHsor9K8M8q9vmEsTJaU1p/ien5X/A/M/E/NvYZfHCxetR
+ 6/4Vr+dvxOPqzZ/8hqx/6+Y//QxVapYJBDfwTEFhHKrlQcZwQcfpX70z8BTP
+ paX/AI+JiSFVSSzMcADPUnsK4HWfHVjZl4NIVNTuhwZmyIEPt3c/TA964TXf
+ EOq69LIbiZUsN2VtYMrGv+8OrH3PFc7XHSwyteR21cW3pEtXt7d6jqct7fTG
+ 4upMbnIA4HAAA6AdhVWiiutHG3c7fwB/yPM3/Xk/81r2DvXj/gD/AJHqb/ry
+ f+a17BXn4n+IelhP4YdqPzoorA6gooxV/StNvNa8Tado+nxGe/vrlLe3jz95
+ 3YKo/M0pSUU29kOMXJqK3Puj9kbwV9k8J6347u4cT37/AGHT2Yc+ShBkYezO
+ FX6xGvsquf8ACnh2z8JfDbRPDVgB9l060SBWC48wgfM5HqzZY+5NdBX81Z7m
+ Tx+OqV+jenotF+B/SuRZasBgadDqlr6vV/iFVb6ytNS0W806/gjurG6geC4g
+ kGVkjdSrKR6EEirVFeUm07o9ZpNWZ/PN8U/A118Nv2g/Ffgq68xhpl8yW0j9
+ ZYGw8Mh92jZCfQkiuAr9Lv2+vhxlPC3xTsIOR/xKNXKj/ekt3OP+2qlj/wBM
+ x6V+aNf1LwzmyzHLaVe+trP1Wj/z+Z/KvE+UvLcyq0Oid1/heq+7b1QUUUV7
+ x4AUUUUAFfSn7PXw18V/E2fXdM8MQ2IW3u4mvry8uAkVqrIcMVB3ueOig+5F
+ fNdd18OdfvvD3xQtLnTtQvNKvJh5cF3aTGKWKQcoQw9TkEHg55BrjzCFaeHm
+ qMkpdG1dfddf132O3LqlGGJg6ybjfVJ2f32f9dtz9oPhn+zv4I+H0ttql3Gf
+ FXimPkalfxjZA3/TCLlY+3zctx96qH7XH/KOb4pf9g6L/wBKYa8Z+G37WV7Z
+ m30j4nWbX1uMIuvafBiRfeeAdf8Aej9PuV6f+05r2i+Jv+CYPxK1nw/qljrG
+ lz6ZEYrm0mEiH/SITjI6EZ5B5HevwSrgM1o55hp468r1I2lvH4lt0Xpp6H9A
+ UsflNbI8TDA2janO8dpfC9+r9bv1PxQgnntb1Lm1nltrlD8ksTbWX8f6V6Ro
+ /j4ErBr0WD0F7AnH/A0H81/KvMu9B6Gv6EnTjPc/nanVlB3R9Q6Tqk2n6xpm
+ t6VcgT280d1Z3EZyNysGRh+IBr9lvCfiG08WfDXQ/Ellj7PqNmk4UHPlsR8y
+ H3VsqfcV+Jehf8iLov8A15J/Kv0U/ZH8Y/bvh9rXgm6lzcaXN9rslY8mCU/O
+ oHosnJ/661+U+ImWe2wUcRFa03r6PT87fifrPhzmnscY8PJ6VFp6rX8r/gfY
+ NFFFfih+2hRRRQAUUUUAfHP7bPxC/wCER/ZNPhmzn8rVvFdz9jUKcMLWPDzs
+ PY/u4z7Smvxtr6r/AGxfiF/wnH7Y+q6daT+bo/hmMaVbBW+UyqSbhsevmEp7
+ iNa+VK/pXgfKfqOU00170/efz2+5W+Z/MvHWbfX83qNP3Ye6vlv+N/kFFFFf
+ Xnx4qkq4ZSQw6EU5ni2M0pWHAyX6KPc+n1Fdx4A+Gfjj4oeJH03wVoU2prEw
+ F5qEreTY2XvLO3yqf9kZY9hX6I/Cz9lPwL4Ee11jxa0HxC8WR4dGurfbplm/
+ X91bt/rGB/jlz7IK+Yz7i3L8qTjVlzT/AJVq/n2Xr8kz6jh/hHMc2d6UbQ/m
+ e3y7v0+dj8vLuyvrAWRv7K9sVvIBPZNcW7xi6iPSSMsBvXjquRVav3R8WeFv
+ DXjzwlJoHjPQ7DxJpDcrBdpzAf70LjDRMOxQj8RxX5//ABR/Y88Q6J9o1j4V
+ 3dz4v0kZZtCvGVdTgHUiJ+EuQPT5ZPZq8PIvEbAY2Xs8SvZSe13eL/7e0s/V
+ JefQ93PvDjH4GPtMO/ax62VpL/t3W/ybfkfMvgD/AJHqb/ryf+a17Aa8i8Cx
+ TW3xKvrS5hntLy3tZI7i3njaOWFgVyrowBU+xFeu19liPjPjsIrQDsaKD60d
+ qwOgK+qf2UvBX9t/Ge88WXcW6x0GD9wSOGuZQVX67U3n2JU18rd6/WL4GeCv
+ +EH/AGcNDsJ4fJ1S9X7fqIIwwllAIU+6oEQ+6mvjeOc0+qZbKEX71T3V6dfw
+ 0+Z9nwNlf1vMozkvdp+8/Xp+OvyPX6KKK/BT97CiiigDg/if4Gs/iV8A/FPg
+ i9aONdUsWjgmdciCcYeGTHfbIqNjvjFfhh8Svg/8QPhN4jFh4z0Kezhkcra6
+ jD+8s7rv+7lAwTjnacMO4Ff0F1maxoukeIfDd1o+u6ZY6xpVymy4tLyBZYpB
+ 6FWBFfY8K8YV8mbhy81OTu1s790/6+R8ZxXwbQzlKfNy1Iqye6a7Nf1bzP5u
+ qK/QP9qT9lbwp8PPhzqHxJ8D6jPpmlRXMcd1oVzmVVMsgQGGQncACfuvnvhu
+ gr8/K/fsmznDZnhlXoPTbXRp9v8Ahj+fs6yXE5XiXh8Qtd9HdNd/w66hRRRX
+ qnkhSqzpIskTFJUYMjDswOQfzpKKAPovTr9NU0Cy1GPgXEQdh/dbow/76BrQ
+ M9z/AMItrehpeX1vo+sweRqtpBOUjukDBhuXpuBAIbGRjrivM/h7qO63v9Hk
+ blD9ptwfQ4Dj88H8TXpFeTVppSaa/rdHtUKjlFST/rZnj2s+CNRsA9xppfVb
+ MclQuJ4x7qPvD3X8q4jOQfbg+1fTIODkZFc7rXhjStbDSTRm0vyOLuAAMf8A
+ fHR/x5966aeJe0jkq4PrEu6F/wAiLov/AF5R/wAq9n+DHjL/AIQf9ovw9rE0
+ vladLN9j1DJwvky/KzH2U7X/AOACvIdPtmsvD9hZSOsr28CxF1GA2O4Bq53r
+ zcZhoYmjOlPaSa+89TA4mphqsKsN4tP7j9wKK8l+B/jH/hNv2bvD+pTS+bqV
+ rH9hvyTlvNiAXcfdl2P/AMCr1qv5jxmFnhq86M94tr7j+nsHioYmhCtDaST+
+ 8KKKK5jpCvPPix45g+G37Oni3xpMY/M02wZrRH6SXDYSFD7GRkB9ia9Dr82/
+ 2/PiF5en+EvhhZT/ADSsdX1VVPO0bo4FPsT5zEH+6hr3eGsq/tHMqVBrRu79
+ Fq/8jweJs2WXZZVxF9UrL1ei/wAz80rm5nvNRuLu6mkuLqeRpJpXOWdmOSxP
+ ckkmoKKK/qZK2h/KbberCj8jz0PIPsfUe1FFMD9DPg5+1n4Qj8NaX4N8e6Fp
+ Pw+itVEVnqWh2nl6Qe2ZYFy1ux7uNynvivt6Ce3u9Mtr6yuba+sLmPzLW6tZ
+ llhnX+8jqSrD3Br8Fa9M+Gvxf8ffCfVTJ4Q1cDSZJN93oV8pm0+59SY8/u2/
+ 24yrfWvy3iLw2o4hyrYGXLJ6uL+F+j3T+9eh+qcN+JVbDKNHGx54LRSW6Xps
+ 19z9T9ozXJ+LvG/hbwNpK3XifVFtJJF3W1jCvm3dz/uRDnH+02F96+Urr9qb
+ WvFHw4sZ/Cfh2PwlfzqyXt3c3Au2hkU4YWwwAF7h3BYenevBrq5ur/VrjUL+
+ 6utQ1C4bdPdXUpkllPqzHk/yFfLZN4d4mpLmxr5Euis5P56pL735Lc+szjxG
+ w1OHLgVzt9Wmkvlo2/uXqeg/E/4gxfEnxxbawfC2j6LJao0UF75YfUp4zgbZ
+ 5xjevAwmCF7GvOPeijvX6zg8HRwlGNGirRjstf1PyXGYytiq0q1V3lLd7fkF
+ FBo4rqOY9a+CHgr/AITr9ozQ9Mni87S7R/t2ogjKmGIg7T7MxRP+BV+s9fKX
+ 7KHgr+xvg9feL7qLbfa5Pttyw5W2iJUY9Nz7z7hVNfVtfg3HOafW8ycIv3af
+ u/Pr+OnyP3rgXK/qmWqpJe9U975dPw1+YUUUV8YfZhRRRQAUUUUAfKX7af8A
+ yYH4j/7CFl/6UJX4qV+1f7af/JgfiP8A7CFl/wClCV+Klfv3hh/yKJf43+UT
+ +ffFL/kbx/wL85BRRRX6Mfm4UUUUAaWj6idI8U2OojJSKT96B/FGeGH5HP4V
+ 9DHGcq25DyrDowPQ/lXzP1HPIr27wbqX9o+BYI5G3XFk32eTPUgcofxXj8K5
+ MVDRSO3Bz1cTqaDRRXEegA6UUfrQaAPrn9knxl/ZvxQ1fwZdTYttXt/tFmrH
+ pPECSAP9qPcT/wBcxX6EV+LXhnXrzwv8QdF8R2B/0rTryO4QZwH2tkqfYjIP
+ sTX7K6TqdnrfhfTtY0+TzrC+to7i3f8AvI6hlP5EV+LeImWexxkcTFaVFr6r
+ /gW/E/afDrM/bYKWGk9YPT0f/Bv+BoUUUV+eH6IFfhh+1hd3N1+3/wDEP7TP
+ JP5NzBFFuP3EW2iwo9AP6n1r9z6/Cn9qj/k//wCJP/X9D/6TQ1+meFiX9p1f
+ 8D/9KifmHiq2sspf41/6TI+faKKK/eD8DCiiigYUV3Ph7whHrXhCe9nuZrOd
+ 5ito4XcuF4YsvcE8ZB7VzuraFqeiThb+3xCxxHcxndE/0bsfY4NQqkW7X1NH
+ SkoqVtD1XwR/yTS0/wCvib/0KurrlfBH/JNLTr/x8Tf+hV1Wa82p8bPVo/Av
+ QKKKKgsO9b3hjw/eeK/iHo3hzTwfteo3aQIduQgY8ufZRlj7A1g9q+xv2R/B
+ X23xrrPjq7izBpsf2OwYjgzyDMjD3WMgfSWvJzzMlgMDUrvdLT1ei/E9bI8t
+ ePx1Ogtm9fRav8D7q0jSrPQ/Cmm6Lp0fk2Fjapb26eiIoUZ98DrWjRRX81yk
+ 5NtvVn9LRiopJLRBRRRUlBRRRQAUUUUAfKf7aKO37AXiUqjMFvrIsQM7R9oQ
+ ZPoMkD8a/FOv6MvGfhbTvG/wp8Q+EdWXOn6tYSWsrbcmPcpAcf7SnDD3Ar+e
+ XxDoWo+F/Hms+G9Xi8jVNLvZbS6TsJI3Ktj1GRwe4r9x8LMdTlg6uG+1GXN8
+ mkv0/I/CvFXA1I42lifsyjy/NNv8b/mY9FISFUsegGTV+/02/wBMliS/tpLc
+ SoHhc8pICMgqw4PB6da/Urn5VZlGjtRR2pgFdj4H1H7F42W0dsQX6eScngOO
+ UP55H41x1KrOkiyRMUlRgyMOzA5B/OpnHmi0VCXLJM+lqOtU9Ov01TQLPUY+
+ BcRB2Ufwt0YfgQaufyryWrHtJpq6DvR2oooGH41+kP7KvjH+3fgRceGrmXdf
+ aBc+WgJ5NvKS8Z/BvMX2AWvzer3P9nfxj/wiP7TOkJPL5em6wP7NusngGQjy
+ 2/CQIM9gWr5ji/LPruWVIpe9H3l8v81dH0/CGZ/Uszpyb92Xuv5/5OzP1Ooo
+ or+ej+hgr8Kf2qP+T/8A4k/9f0P/AKTQ1+4Wta5o/hzw1dazr+qWGjaVbJvn
+ u7ydYo4x7sTj8O9fg58f/FOheNf2wvHPifw1eNqGh314htLkxNH5oWGNCwVg
+ CBuU4yASMHAr9R8LKNT6/Vqcr5eW1+l7rS/c/K/FatT+oUqfMubnvbraz1t2
+ PHqKKK/cz8JYU5I3lnjhjx5kjhEz0yTgfzptWbL/AJDlj/19R/8AoYoGj6Gt
+ LOPTtJtdPhGIraIRD3x1P4nJ/Gp2VZIHjkRJYnGHjdQysPcHrUsv/H1J/vn+
+ dR9ulePe57dkirZWNppuni0sYRb2wkZ1jBJCljkgZ7Z7dqtUUAEsAAST0Aob
+ BK2gUfyqkupWL682lx3Mct+sZkeKM7vLAx94jgHnp1q7RYE09hyqzyqkas7s
+ cKqjJJPYV+vfwp8Gr4D+A3h/w6yKt9Hbia/I/iuJPmk574J2g+iivz6/Z08F
+ /wDCYftJ6ZPcReZpWij+0LrI+VmQjyl/GQqcdwrV+pNfkniPmnNUp4OL295+
+ vT8Lv5o/XPDfK7U6mMkt/dXp1/Gy+TCiiivy4/UgooooAKKKKACiiigAr8kv
+ 27Phz/YHx90r4gWMGzTvEtt5V4VXhbuBQpJ7DfH5ZHqUc1+tteFftIfDj/hZ
+ 37I3ifQ7aDz9as4v7S0gBcsbiEFgi+7oXj/4HX1HB+b/ANnZpTqN2jL3Zej/
+ AMnZ/I+W4yyf+0cqqU0ryj70fVf5q6+Z+DMn/HvJ/umvo6OGG58NWttdQRXN
+ s9rFvilXcrfIO39etfOMn/HvJ/umvpWwjlntdKtbeGa5upoIkhghjLySsUXh
+ VGST9K/pHFuyTP5rwSu2jznWfAJ+e40GTPc2U78/8Ac/yb8682mjkt7+S0uY
+ 3t7uP/WQyLtdfqP61+lHgf8AZ11XURFqXj65m0GxOGXSLVwb2Uekj8rCPYbn
+ +le8eIPg38LfE/w4h8Kar4L0oaTbg/Y5bRfJvLRj1kjuBmTeep3Fge4NfDY3
+ xFy/CVlSV6ndxtZfPaXy08+h91gvDjMcXRdXSn2Ur3fy6fPXy6n4uUV9V/FP
+ 9k7xt4KS61nwVJcfELwtGC7xwwhdUs0HUyQLxKo/vxZPHKivlJWVgdpzglWH
+ QqR1BHYj0PNfZ5Zm2EzCl7XDVFJfivVbr5nxOZ5Ti8ureyxNNxf4P0ezXoep
+ fD3Ud1tf6PI3KH7Tbj2PDgfjg/ia9Hr560bUW0nxTY6iM7IpP3o/vRnhx+R/
+ SvoY4yNrBlIyrDuD0P5U8TC0r9ysJO8LdhO/vR60Ud65zqD605HaOdZEdkdS
+ GVlOCpHQg+tNo7UAfsL8MvFyeOfgX4c8S71a5ubULeAfwzp8kox2G5SR7EV5
+ J+0T+0ZpnwM0PTLODST4g8WarFJJZWjS+XDAikL5sxHzYJOFVR821uVxmvL/
+ ANkLxjsvvEfgS6l+WRRqNgpP8Q2pMo+o8sgf7LGvgb9pT4hf8LJ/bC8V6zbz
+ +fo9lN/ZmlMGypggJXcp/uu/mSD/AH6/Isp4Np1eIKtCrG9KHveqfwr+uzP1
+ zN+M6lHh+lXpStVn7vo18T/rujkviT8XvH/xZ8SjUfGmuz30UblrXT4v3dpa
+ 9v3cQ4BxxuOWPcmvNKKK/b8Ph6VCmqdKKjFbJaI/DMRiateo6lWTlJ7tu7Ci
+ iitjEKs2X/Icsf8Ar5j/APQxVarNl/yHLH/r6j/9DFDBbn0lL/x9Sf75/nUf
+ 50+X/j5k/wB8/wA6ZXjo90zdT1jTNItRLqF0kRI+SJfmlf6KOfxOB715ZrXj
+ XUtSV7exDaVYtwQjZmkH+0/b6L+del6v4f0rW483sBS5C4S7h+WVB6Z/iHsf
+ 0rynWvCWqaMHnCjUNPH/AC8wKcoP9teq/Xke9dWHVPrucWJdXpsaHw+AHjmY
+ AAD7E5/8eWvYO9eP/D8g+OJiCCPsL4IPutfSHgDwpceOPjJ4f8LwbwL66VZ3
+ XrHCvzSv+CBj9cVhj60KSlUm7JK79Eb5bRnV5acFdt2Xqz9Af2YfBX/CM/s9
+ x63cw+XqfiGQXbEjDCBcrCv0ILOP+ulfSFQWttb2Wm29naRJb2sESxQxIMKi
+ KMKo9gABU9fy9meOnjcXUry3k7/5L5LQ/qTLMBDBYSnQjtFW+fV/N6hRRRXC
+ d4UUUUAFFFFABRRRQAUUUUAfhP8AtQfDf/hW/wC154n0u1g+z6Jqrf2ppW1c
+ KsM5YsgHYJIJEA9FHrX0L+zp8cPg/pumWOgazpVv8PvGjRJbtr99OZ7bUSAF
+ AFw3Nrn/AJ5sAn+0a91/bm+HP/CTfs42Pjmxg36p4Xuc3BUcvZzFUfp12uI2
+ 9l3n1r8hCAVIIBBGCCODX9BZTTo8S5DCnXm04+62nbVd+jurOzXpZ6n885vU
+ rcNZ/OpQgmn7yur6Pt1VndXT/wAj98dpAQ8FXQPGykFXU9GUjgg9iODTec1+
+ Pfws+PvxC+FDQ2GlXqa/4TD5k8O6s7PbqO5gcfPbt/ufL6qa+w9T/bR+HkPw
+ wh1PR/DniW/8WzAr/YF2FhitXA+9LdLlXjz08tdzei1+b5p4fZtha6hSh7WL
+ 2a/9uT+H1vbzP0rKvEXKcVQc60vZSS1T1/8AAWt/S1/I+vLi6t9P0u51O9vL
+ XTbC0Xzbi9uZ1hht1H8TyMQFHuT9K/MX9pX4lfBfx3r7f8IP4X/tPxYsg+1+
+ NbcmyguADynlYzd57SuFx1BavGfiR8W/HvxX1dZvGGsmXTYpC9nolmph0+1/
+ 3Ys/O3+25Zj6ivNq/QeE+Anl1WOJxFRuoukW0l5N6OXpovJn53xbx9/aVN4a
+ hTSp95JNv06R9dX5oOo55Fe3eDdS/tDwLBG7brmzb7PKSeSByh/FePwrxGux
+ 8Daj9i8araO2IL9PJOTwHHKH88j8a/Q8RDmh6H55hp8tT1PZqKKO1eaeqFFH
+ tRQMv2HivWvBMl54m8PXItNXs7KcQSldwXfG0Z4+jHHocGvlkdBX0Hr3/Ii6
+ 1/15P/KvnwdK6sHTinKaWrsr+S2/N/ecONqTajBvRXdvN2v+S+4KKKK7jgCi
+ ijnsGY9gqkk/gKACrVgM6/p49bqL/wBDFVFZXjV0ZWVhkMDkEVd07/kYtO/6
+ +4v/AEMUnsNbn0fL/wAfMh/2j/Oo6fJ/x8yH/aNMrx0e4B9e9KCQcg4PsaTt
+ RTEZcOi6bbeJH1W1tltbt42jlEXyxuCQclegbjqK+9/2RPBWItf8fXkXLf8A
+ Eu04sO3DzMP/ACGoI9HFfEtnaXN/q1rY2cL3F5czLDBEg5d2IVVHuSQK/Yvw
+ J4VtvBPwh0DwvbbCLC0VJXXpJKfmkf8A4E5Y/jXwfiDmroYBUE/eqaf9urf9
+ F95974e5Sq+Pddr3aev/AG89v1f3HW0UUV+Hn7iFFFFABRRRQAUUUUAFFFFA
+ BRRRQBl63o+n+IfBuraBq0AudL1KzktLuI/xxyIUYfkTX89HjzwjqHgL4yeJ
+ fBuqBjeaTfyWxcrjzVB+SQD0dCrj2YV/RXX5cft8fDj7D438NfFCwt9tvqUf
+ 9masyrx58aloXPqWjDr9IRX6V4aZv9Xx8sNJ+7UWn+Jbfer/AIH5n4nZP9Yw
+ EcVFe9Tev+F7/c7fifndRRRX70fgNgooooAKVWdJFkjYpKjBkb0YHIP50lFA
+ H0Xp1+mq6BZ6lHwLiIOw/ut0YfmDVyvN/h7qO62v9HkblD9otx7HhwPxwfxN
+ ekV5VSHLJo9ilPngmHeijtRUGpl67/yImtf9eUn8q+fB0FfQeu/8iNrX/XlJ
+ /KvnwdBXbhPhZ52N+JBRRRXWcYV9p/sNfDr/AISr9qa+8bXsHmaR4Rs90JYA
+ q19cKyRjB/uRea3sWSvip3WOF5HO1EUsx9AOTX7kfsofDhvhx+xf4bt7238j
+ X9bB1nVg33lknAKRnv8AJEIkx2KmvieP82+pZTKMX71T3V6P4vw0+aPuPD7K
+ PrubRnJe7T95+q2/HX5M89+Mv7FfgPx415rvgJ4Ph54ukJkdLeHOmXj8n97A
+ MeWxOPnjx3yrV+Y/jT4WePvhP8TNM0nx54eudIaW+jWzv0Pm2V786/6qcDaT
+ gg7Dhxnla/dXxv8AEXwf8O/D39o+K9ZgsN4P2e1X95c3JH8McQ+Zj74wO5Ff
+ n78Wf2ifEHxH0q98O6bplpoHg2f5ZYLmJLi7u1BBBdiCsXIBAT5h/fr5HgbO
+ c9qWhKPPR7ydrej1b9LP1R9jx1kuRQvOMuSt2jrf1WiXrdejPnqX/j6k/wB4
+ /wA6Z29aPxor9MPzMPWiiigD6V/Zd8Ff8JJ+0B/b11D5mm+HohckkZU3DZWE
+ fUYdx7xiv0srwz9njwV/wh37NmlNcReXqmsf8TG7yPmUOB5aeoxGF47MWr3O
+ v574wzT69mc2n7sfdXy3+93P6F4Pyv6jlkE170vefz2+5WCiiivlz6gKKKKA
+ CiiigAooooAKKKKACiiigAryz41/D6L4ofsyeLPB5RGvrm0Mumu2Bsuo/nhO
+ ewLKFJ/us3rXqdFb4bETw9aNWm7Si016owxWGp4ijOlUV4yTT9GfzVyxSwXM
+ kM0bwzRsVkjdcMrA4IIPQio6+qP2wfhz/wAIH+2DqmoWcBi0TxMn9q2hA+VZ
+ WJFwmfXzMvjsJFr5Xr+r8sx8MbhKeIhtJJ/8D5PQ/krNMBUwWLqYee8G1/k/
+ mtQoooruOEKKKKANLRtROk+KbHUeSkUn70f3ozw4/I5/CvoY4zlSGU4Kt/eB
+ 5Br5n6jnkV7b4N1L+0PAsCO265sm+zyknkgDKH8V4/CuTFQ0Ujtwc7PlOqoo
+ oriPQMzXP+RH1r/ryk/lXz2Ogr6E1z/kR9a/68pP5V89joK7cJ8LPOxnxIKK
+ KK6zjPT/AINeENO8bftJeGdJ165tbHwrazf2n4hurqQRxR2VuRJIrMem9tkY
+ 9fMr9IPiT+1nI5n0j4W2iiMZQ+INQg4+sEDdfZpMDn7pr85/AGmiLQbvVJky
+ 91II4cj+BDkn8W7/AOzXoHU18pm+SYbH4uNXELmUFZR6X6t93su2mzPrsnzr
+ FYDBypYd8rm7uXW3RJ9Fv567lzUtS1LWfENxq+s6je6xq1wf315eTGSV/bJ6
+ D0UYA7CqdHr2or0oxUUklZI89tyd3uw9qO1FB6GmIK9I+EvgxvHnx98P+H3j
+ aSwM/n6gewt4/mcE9t2AgPqwrzev0A/ZI8Ff2f4A1jxzdw7bnVJPslixHIgj
+ b52B9Gk4P/XIV4HE+afUMuqVU/eei9X/AJb/ACPf4Yyv6/mNOk17q1fov89v
+ mfYCqqoFUBVAwABgAUtFFfzkf0cFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF
+ FFFAHyP+2d8OP+E2/ZHutes4PN1rwpKdRhKrlmtiNtyvsNmJD/1xFfi/X9KN
+ 1a299plzZXkMdzaXETRTxSLlZEYEMpHcEEiv59fi54CuPhn+0b4s8FzCQw6f
+ fN9ikfrLbPh4XJ9TGy59Dkdq/bPC7N+ehUwUnrH3l6Pf7nr8z8Q8U8n5K9PG
+ wWkvdfqtvvWnyPOKKKK/WD8kCiiigArsfA2o/YvGq2jtiC/TyTk8CQcofzyP
+ xrjqVWdJFkiYpKjBkb0YHIP51M480WioT5ZJn0tQKp6dfpqnh+z1GPhbiIOw
+ /ut0YfgQa6zwz4U8R+MtfbTfDGlT6rcJjz5AQkFsP70sp+VB9efQGvFrVYUY
+ OdSSilu3ol8z3qFKdaajTTbeyWrfojjdbOPBOs/9eUn8q+eh0FfrN4W/Zz8J
+ 6foNx/wnDjxjfXMDRTWsbPBZQhhg7MEO7Y6OSMHkLXzj8T/2ONc0o3Gr/Ce/
+ m8UaaMu3h/UJVXUIh1xDKcJcD0Vtrn/arwMv47yepiHQ9pbtJq0X8+nq0l5n
+ u5lwJnFPDqv7O/eKd5L5dfk2+58T1LDBLdXsNrbqWnmkEcYHqTgUt1bXVhrF
+ 1p2oWl3p+o2rmO5tLqFopoGH8LowBU/UV2vgHTvtPiifUnXMVlH+79DK4IH4
+ hcn8RX3EppQ5j4aFNufKz1W1tYbDS7axgA8m2iEaY74HX8Tk/jU9H40nO8fS
+ vKPYQUveiigYe+KKKKBGnoukXuv+MNM0PTo/Nv7+6jtoF7bnYKM+3PJ7Cv2T
+ 8N6DZeF/AOj+HdOGLPTrRLeIkYLbRgsfcnJPuTXwX+yb4L/tb4saj4xu4t1l
+ osHlWpYcNcSgjI9dse/PoXU1+h9fjPiJmntcXHCxekNX6v8AyX5s/aPDrK/Z
+ YSWKktZ6L0X+b/JBRRRX5yfowUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU
+ UAFfm/8At9fDnztI8LfFKwgy9uf7J1cqP4GLPA59AG8xST/fQV+kFcR8SPBV
+ l8RfgV4o8FX5RYdWsHhjkcZEMo+aKTH+xIqN/wABr3OG82eXZjSxHROz9Ho/
+ 8/U8LiXKVmWW1cP1auvVar/L0P53qu6dYy6nrltp8DxRzzsVjaQ4XOCeT26U
+ up6be6P4k1DSNSt3tNRsbmS2uoH+9FIjFXU+4IIrT8J/8lK0b/rv/wCytX9S
+ ua5OZH8rKHv8sjKvtPvdMv8A7LqFtLaz9g44ceqnow+lU6+kLq1tb/T2tL63
+ hu7dv+Wcq5APqO4PuK801jwBcRu02gu96h5+xyn96P8Acbo/0OD9axp4lPSW
+ htVwso6x1R51RSsrJIyOpV1JDKeoI6ikrpOVnvfwL1f4ax+Ln0f4q6vqmkaC
+ 0olsprcbbcyH7yXEgy8cZwDuUeuSK/WTR7LRtP8ABun2/hqDSrfw4ybrH+y9
+ ptZBj7ysvDH1JJb1r8IK9U+Gnxm8ffCnU93hfVt+kO4a60W+BmsrjnJzGT8j
+ dfmTBycnNfn/ABjwfXzX95RrNNfZfw+q7Pzd/kfoPBnGVDKX7OtRTT+0viXk
+ +68tPmfsy33OxpSPXkV88/Df9pr4a/EDTlttVvYPAPiVY90tjqtx/o0uBljB
+ cHhuhOxsNgVgeOv2i0iefTPh7aebICVbXNQh+Ue8EB6+zycdwtfj1HhPNqmJ
+ eH9i1Jbt6Jed9n8r36H7LW4uymnhViPbKSeyWsn5W3Xzsu53/wAbfC3wf8Qe
+ CEuvi1DbW90Iium6laNs1gccCAqC0i/7Lho/pX5/6bo+n6FaXFhpU97d2P2q
+ SSG4vIkjuJUJ+UyKhKhgoAwpIra1HUNQ1jXrjVNYv7zVdTnOZru7lMkj+2T0
+ HsMAdhVTvX7Rw3kdTK8N7KVZzv0+yv8ACunn33sj8W4kzunmmJ9rGioW6/af
+ +J7P7tNrsPek/wCWg+lL3qhqOo2Wk6Y1/qMxtrRCFaTYW+Y/dXjucHGcZr6F
+ anzzdi/39axdT8Q6XpN3FazzGa+kkVFtYcM67iBluyjnvz7V53rXjm/vd9vp
+ avpVoeDJnM8g+vRB7Dn3rj7H/kOWR6k3UZJPJJ3jk+tdUMM95HHUxa2ifSDA
+ rIynkg4yBTakl/4+ZP8AeP8AOvYfgN4K/wCE2/aR0W0uIvN0rT2/tC/BGQUi
+ IKqfUM5RSPQn0ry8Zi4YbDzrT2imz18FhJ4rEQow3k0vvP0H+C3gr/hBP2dt
+ B0iaLytTnj+2aiCMN58oBKn3Vdqf8Ar1Wiiv5lxeJnia86095Nt/M/pvCYWG
+ GoQow2ikl8gooornOgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
+ D8df23fhx/wiX7UUXi6xg8vSPFdubhiowq3ce1Jx/wACBjkz3Lt6V8peE/8A
+ kpWjf9d//ZWr9pf2rfhz/wALF/Y71+O0g87W9DH9r6dtHzMYlbzUHc7ojIAv
+ dtvpX4g2l3cWOoxXdpKYLqI5jkABKnBGRnvzX9GcB5t9fyiMJP3qfuv06P7t
+ Pkz+buPco+oZxKcV7tT3l6/aX36+jR71q2t6ZokAbULjZKwzHbxjdLJ9F7D3
+ OBXlOteMdU1ZXt4CdM09uDFE/wC8cf7b9fwGB9a5V3eSd5ZXeWVzl5HYszH1
+ JPWm19jToRjq9WfHVcTKe2iCiiitznCiiigAIDAhgGB6gius0XxhqmkqkE5O
+ qaeOBDM/zoP9h+o+hyK5QdaSplFSVmVGbi7pn0FpOt6ZrcG7T590yjMltINs
+ qf8AAe49xkVroju+1FLt1OOw9T6V80I7xzpLE7xSoco6MVZT6gjpX0B4fv7u
+ /wDh9pM15N5sskO6VtoBkbcRubHU4A59q4K9Hk1R6WHxHO7M1yAOjBj3K9Pz
+ 719Ffs3WlnqPjLx9p2pWVlqenXOhQx3NneQLNDOv2j7rowIYfX8K+c6+k/2Y
+ /wDkpfjT/sCw/wDpQK+U4vbWTV2uy/8ASon1vCEVLOaCa3b/APSWYvxR/Y40
+ rURc6z8JL2HQb7ln8N6lOTZSnri3nOWhPokm5P8AaWvhHVPDniDwh8TIPD3i
+ rRdS8O65BcxmSzvoTG5G8fMp6Oh7MpIPrX7h319Y6VodxqeqX1npem24zPd3
+ cojiT6se/sMk9ga+OvjJ8XPCnjrww3hTS/C+n+ItPik3Qa3rNsQ9s4Od9mvD
+ xngfOxAP9w18twVxVnFaaoVIOrBbyejj6yekvR+8+59Pxvwnk1CDr05qlUe0
+ VqpeiWsfX4fI+epf+PmT/fP86/Rr9lXwV/YPwQufFF3Ft1DX590RYcrbRkqn
+ 03MXb3BWvgXwh4cvPGPxR0Pw1ZFvtOo3iwmTbny1Jy8h9lUMx9ga/ZDTNOtN
+ I8O2GlafEILGyt0t7eIdERFCqPwAFaeIuaeyw0MJF6z1fov83+RPhzlftcTP
+ FyWkNF6vf7l+Zdooor8cP2QKKKKACiiigAooooAKKKKACiiigAooooAKKKKA
+ CiiigAooooAQgMpDAEEYIPevwP8A2gfh0fhd+1d4q8MwwGHSGn+2aRxhTazZ
+ dFHqEO6PPrGa/fGvgX9vL4cf2z8HtC+JFhb7r7QJ/smpMq8taTMAjE+iS4AH
+ /TZjX3vh3m/1PNFSk/dq+78/s/5fM+A8Rsn+uZW6sV71L3vl9r/P5H5Q0UUV
+ /Q5/OgUUUUAFFdT4P8D+MPiB4wj0DwT4c1PxLqzYLxWsfyQKTgPNIcJEmeNz
+ kD0zX6U/Bz9hbw9ootdd+MF3B4t1cYdNAtGZdNtz1xK3DXDDjrtTI+6w5rwM
+ 74mwGVQvXn73SK1k/l09XZH0GR8MZhmsrUIe71k9Ir59fRXZ+dfhX4Y+OfGX
+ gDxF4u0PQZ28I6HYT3uo63dHybQJChd44nb/AF0uBwiZ5IyRXAA5UH1r98Pj
+ rZ2em/sE/FGx0+0trCxt/CN5HBb28QjjiUQMAqquAAPQV+B6/wCrX6Vw8J8R
+ 1M5p1asoKKjKyW+lur7/ACR3cXcN08mqUaUZuTlG7e2t7aLsLXu/hT/km2i+
+ 8B/9DavCK938Kf8AJNdG/wCuB/8AQ2r6TFfCj5vB/G/Q6DHNekfDX4ht8N9U
+ 8SalBpK6xf3+nx2tpHLKY4Y2WTeXkI+YjHQLyT3Feb96K8fGYOjiqMqNZXi9
+ 16O/T0PcweLq4WtGtRdpR2fyt1Ol8VeMPE3jbXFv/E+qy6i8Zzb2yr5dtbD0
+ iiHyr9eWPc1zVH+TUkUUk9zFBBG8s0jBI0RSWZicAAdya0o0adGmoU4qMVsl
+ okZ1q1StUc6knKT3b1bPsv8AZF8FfaPEeu+PLuEGKzT7Bp7MP+WrANKw9Cqb
+ V+kjV95Vwnwz8Hx+BPgf4e8NBUFzbWwa8Zed87/PIc9xuJA9gK7uv514kzT6
+ /mFSsn7uy9Ft9+/zP6L4byv+z8up0Wvetd+r3+7b5BRRRXhHuhRRRQAUUUUA
+ FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNzbW95p89peW8N1azIUmh
+ mQOkikYKsp4II7GpqKabTuDVz4O+Mf7EPhbxOLrXPhdPb+D9cbLtpU2Tp9wf
+ RMZaAk+m5ewVetfmX44+HvjL4ceMX0LxpoN9od+MmLzlzHOoON8cgysi+6k1
+ /RLXNeK/B3hfxz4Rn0Hxdoen6/pMv3oLuPdtP95W6ow7MpBHrX6Hw/4h43BW
+ p4n95D/yZfPr8/vR+dcQ+HWCxt6mG/dz/wDJX6rp6r7mfzmUV+hnxi/YZ1fS
+ vteu/CO8k13Txl30G+kAuohySIpDhZR6K2G93NfAGo6bqOj65daZq1jeaZqV
+ tIY7i1uoWiliYdVZWAIPsa/acoz3BZnT58NO/ddV6r+kfiWb5DjssqcmJhbs
+ +j9H/TNTwv4u8VeCfFsGveD/ABDqvhvV4iMXFjMV3gHO2RD8siequCPav0g+
+ Dv7dul34tdC+M9jDoF7gIviXTomNlKcYzPFy0BPdl3Jk/wAAr8vqKxzrhvAZ
+ pC2Ihr0ktJL5/o7ryNsk4lx+VTvh56dYvWL+X6qz8z96/jjqWnax+wF8UNT0
+ m/stU0258JXr293aTrLDMphb5ldSQw9wa/BJf9Wv0r0PwL478YeGrLU/B2ia
+ /eWfhXxLBJp2saS4EttLHMux3SNsiOYA8SJg565rI1nwfqmjo80QOpaev/Le
+ FTvQf7adR9RkV5vCvD7yWFWjKfMpO6ezta2vn8z0uLOIVnc6VeMHFxVmt1e9
+ 9PL5HK17v4T/AOSa6N/1wP8A6G1eDgggEEEHoQa948KD/i2ujf8AXA/+htX0
+ uL+FHzWDfvv0Ogorq/CXgjxR468RDTPDGkXWpzjHmug2xQg/xO5+VR9Tz2zX
+ 3V8Nv2XPDnh0W+qeN5IfFGsDDCzAP2KE+hB5l/4Fhf8AZ718lnPEmBy2P72V
+ 5fyrf/gfM+xybhvHZlL91G0f5nov+D8j5C+HfwX8b/Ei5jm0ux/s/RN2JNVv
+ QUgHrs7yH2Xj1Ir7++HHwI8EfDsQ3sVt/bviJME6pfICyN6xJyI/qMt/tGva
+ Ioo4LaOGGNIYY1CpGihVUAYAAHQCn1+P55xjjsxvBPkh2XX1fX8F5H7DkfBu
+ By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU
+ UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9
+ piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s
+ fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/
+ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL
+ /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc
+ 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b
+ /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o
+ 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB
+ k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A
+ tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI
+ TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm
+ Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF
+ FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
+ AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR
+ WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q==
+ </data>
+ </array>
+ <key>name</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>passwd</key>
+ <array>
+ <string>********</string>
+ </array>
+ <key>passwordpolicyoptions</key>
+ <array>
+ <data>
+ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
+ WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
+ IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
+ LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp
+ bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr
+ ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt
+ MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8
+ L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgo8L2Rp
+ Y3Q+CjwvcGxpc3Q+Cg==
+ </data>
+ </array>
+ <key>realname</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>shell</key>
+ <array>
+ <string>/bin/bash</string>
+ </array>
+ <key>uid</key>
+ <array>
+ <string>501</string>
+ </array>
+</dict>
+</plist>
diff --git a/chef/spec/data/mac_users/10.8.shadow.xml b/chef/spec/data/mac_users/10.8.shadow.xml
new file mode 100644
index 0000000000..c4b8ec8363
--- /dev/null
+++ b/chef/spec/data/mac_users/10.8.shadow.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>SALTED-SHA512-PBKDF2</key>
+ <dict>
+ <key>entropy</key>
+ <data>
+ 6kwtJl2AG6DsDfzNJT38HekcvggGtKzB7X/iKuvPa+tTRND0QuWQ/6BNZ5B1
+ 2jr7EZ5BtyterwjuSqVGk3ImRtUZ7gSEPeuKPpd0KNM/Yl6DiHkT5cE7cANZ
+ YV4ArXvD56DJivw+GdE2AnJFT40zqSFNL76L5o0fmCGyZokxI2Y=
+ </data>
+ <key>iterations</key>
+ <integer>39840</integer>
+ <key>salt</key>
+ <data>
+ +ZTvL3O3xVlOvRVTMAl2sgczzg4k1ll4PYfz2By7tqk=
+ </data>
+ </dict>
+</dict>
+</plist>
diff --git a/chef/spec/data/mac_users/10.9.plist.xml b/chef/spec/data/mac_users/10.9.plist.xml
new file mode 100644
index 0000000000..be3456ea6c
--- /dev/null
+++ b/chef/spec/data/mac_users/10.9.plist.xml
@@ -0,0 +1,560 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>KerberosKeys</key>
+ <array>
+ <data>
+ MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCAJxcIcjX3sMb98++d0
+ YvKqc351+CJJTMpyJO5mwWFMCaJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEEy
+ NDA4NDVBMjU0OUZCOEUwRjI4NEU1NkUyODE3NzU2RUU5Q0QyMnZhZ3JhbnQw
+ Y6EbMBmgAwIBEaESBBDzYvuM3CLsLOGCIX4FJ8vdokQwQqADAgEDoTsEOUxL
+ REM6U0hBMS40QTI0MDg0NUEyNTQ5RkI4RTBGMjg0RTU2RTI4MTc3NTZFRTlD
+ RDIydmFncmFudDBroSMwIaADAgEQoRoEGCkvuVvN92vqnm0cy+9GWNBoIEoW
+ XtUNx6JEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEEyNDA4NDVBMjU0OUZCOEUw
+ RjI4NEU1NkUyODE3NzU2RUU5Q0QyMnZhZ3JhbnQ=
+ </data>
+ </array>
+ <key>ShadowHashData</key>
+ <array>
+ <data>
+ YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50
+ cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIASYBqQ2xfL+LpICOY4L7DTudimwaGQ
+ R3v2gKshr7YGVGcTblXMIIpvdBVuPa8g+xM2nvS3uvoEfYA1n7RqSKStzNVI
+ 67M4UbCTR8yoQ0Gn+TonFHND+J+4Q/tGwAF9Jkr6SXa6rPlBuRW9HsHKJMML
+ PnWeAkA+AvWf5/9ZOKdjbE8QIO6VS+Ry/cYN34lIR4FDOZNiXwBq9uyBDAj0
+ mn5BOUahEYayCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA
+ AAAAAOo=
+ </data>
+ </array>
+ <key>_writers_hint</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_jpegphoto</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_passwd</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>_writers_picture</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>authentication_authority</key>
+ <array>
+ <string>;ShadowHash;HASHLIST:&lt;SALTED-SHA512-PBKDF2&gt;</string>
+ <string>;Kerberosv5;;vagrant@LKDC:SHA1.4A240845A2549FB8E0F284E56E2817756EE9CD22;LKDC:SHA1.4A240845A2549FB8E0F284E56E2817756EE9CD22</string>
+ </array>
+ <key>generateduid</key>
+ <array>
+ <string>11112222-3333-4444-AAAA-BBBBCCCCDDDD</string>
+ </array>
+ <key>gid</key>
+ <array>
+ <string>80</string>
+ </array>
+ <key>home</key>
+ <array>
+ <string>/Users/vagrant</string>
+ </array>
+ <key>jpegphoto</key>
+ <array>
+ <data>
+ /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw
+ bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs
+ AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA
+ AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH
+ MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk
+ AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH
+ ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv
+ ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo
+ AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA
+ AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv
+ S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD
+ Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa
+ AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA
+ AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp
+ RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE
+ 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg
+ AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA
+ QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo
+ AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E
+ FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy
+ AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA
+ RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl
+ AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA
+ bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg
+ AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA
+ bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs
+ AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA
+ RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv
+ AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD
+ wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl
+ AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A
+ ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH
+ AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA
+ bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs
+ AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E
+ RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn
+ BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA
+ ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz
+ AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo
+ dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN
+ AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A
+ ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA
+ /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH
+ BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA
+ QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O
+ Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A
+ AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE
+ AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX
+ GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3
+ eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI
+ ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB
+ AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR
+ BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico
+ KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
+ 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0
+ 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ
+ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i
+ 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
+ By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU
+ UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9
+ piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s
+ fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/
+ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL
+ /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc
+ 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b
+ /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o
+ 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB
+ k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A
+ tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI
+ TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm
+ Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF
+ FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
+ AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR
+ WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q==
+ </data>
+ </array>
+ <key>name</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>passwd</key>
+ <array>
+ <string>********</string>
+ </array>
+ <key>passwordpolicyoptions</key>
+ <array>
+ <data>
+ PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
+ WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
+ IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
+ LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp
+ bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr
+ ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt
+ MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8
+ L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtl
+ eT5wYXNzd29yZExhc3RTZXRUaW1lPC9rZXk+Cgk8ZGF0ZT4yMDE0LTAzLTA2
+ VDE4OjU0OjQ1WjwvZGF0ZT4KPC9kaWN0Pgo8L3BsaXN0Pgo=
+ </data>
+ </array>
+ <key>realname</key>
+ <array>
+ <string>vagrant</string>
+ </array>
+ <key>shell</key>
+ <array>
+ <string>/bin/bash</string>
+ </array>
+ <key>uid</key>
+ <array>
+ <string>501</string>
+ </array>
+</dict>
+</plist>
diff --git a/chef/spec/data/mac_users/10.9.shadow.xml b/chef/spec/data/mac_users/10.9.shadow.xml
new file mode 100644
index 0000000000..b8359d080a
--- /dev/null
+++ b/chef/spec/data/mac_users/10.9.shadow.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>SALTED-SHA512-PBKDF2</key>
+ <dict>
+ <key>entropy</key>
+ <data>
+ EmAakNsXy/i6SAjmOC+w07nYpsGhkEd79oCrIa+2BlRnE25VzCCKb3QVbj2v
+ IPsTNp70t7r6BH2ANZ+0akikrczVSOuzOFGwk0fMqENBp/k6JxRzQ/ifuEP7
+ RsABfSZK+kl2uqz5QbkVvR7ByiTDCz51ngJAPgL1n+f/WTinY2w=
+ </data>
+ <key>iterations</key>
+ <integer>34482</integer>
+ <key>salt</key>
+ <data>
+ 7pVL5HL9xg3fiUhHgUM5k2JfAGr27IEMCPSafkE5RqE=
+ </data>
+ </dict>
+</dict>
+</plist>
diff --git a/chef/spec/functional/resource/user/dscl_spec.rb b/chef/spec/functional/resource/user/dscl_spec.rb
new file mode 100644
index 0000000000..66ff0f4399
--- /dev/null
+++ b/chef/spec/functional/resource/user/dscl_spec.rb
@@ -0,0 +1,199 @@
+#
+# Copyright:: Copyright (c) 2014 Chef Software, 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 'spec_helper'
+require 'chef/mixin/shell_out'
+require 'functional/resource/base'
+
+metadata = {
+ :unix_only => true,
+ :requires_root => true,
+ :provider => {:user => Chef::Provider::User::Dscl}
+}
+
+describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metadata do
+ include Chef::Mixin::ShellOut
+
+ def clean_user
+ begin
+ shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'")
+ rescue Mixlib::ShellOut::ShellCommandFailed
+ # Raised when the user is already cleaned
+ end
+ end
+
+ def user_should_exist
+ shell_out("/usr/bin/dscl . -ls /Users").stdout.should include username
+ end
+
+ def check_password(pass)
+ # In order to test the password we use dscl passwd command since
+ # that's the only command that gets the user password from CLI.
+ shell_out("dscl . -passwd /Users/greatchef #{pass} new_password").exitstatus.should == 0
+ # Now reset the password back
+ shell_out("dscl . -passwd /Users/greatchef new_password #{pass}").exitstatus.should == 0
+ end
+
+ let(:node) do
+ n = Chef::Node.new
+ n.consume_external_attrs(ohai.data.dup, {})
+ n
+ end
+
+ let(:events) do
+ Chef::EventDispatch::Dispatcher.new
+ end
+
+ let(:run_context) do
+ Chef::RunContext.new(node, {}, events)
+ end
+
+ let(:username) do
+ "greatchef"
+ end
+
+ let(:uid) { nil }
+ let(:gid) { 20 }
+ let(:home) { nil }
+ let(:manage_home) { false }
+ let(:password) { "XXXYYYZZZ" }
+ let(:comment) { "Great Chef" }
+ let(:shell) { "/bin/bash" }
+ let(:salt) { nil }
+ let(:iterations) { nil }
+
+ let(:user_resource) do
+ r = Chef::Resource::User.new("TEST USER RESOURCE", run_context)
+ r.username(username)
+ r.uid(uid)
+ r.gid(gid)
+ r.home(home)
+ r.shell(shell)
+ r.comment(comment)
+ r.manage_home(manage_home)
+ r.password(password)
+ r.salt(salt)
+ r.iterations(iterations)
+ r
+ end
+
+ before do
+ clean_user
+ end
+
+ after(:each) do
+ clean_user
+ end
+
+ describe "action :create" do
+ it "should create the user" do
+ user_resource.run_action(:create)
+ user_should_exist
+ check_password(password)
+ end
+ end
+
+ describe "when user exists" do
+ before do
+ existing_resource = user_resource.dup
+ existing_resource.run_action(:create)
+ user_should_exist
+ end
+
+ describe "when password is updated" do
+ it "should update the password of the user" do
+ user_resource.password("mykitchen")
+ user_resource.run_action(:create)
+ check_password("mykitchen")
+ end
+ end
+ end
+
+ describe "when password is being set via shadow hash" do
+ let(:password) {
+ if node[:platform_version].start_with?("10.7.")
+ # On Mac 10.7 we only need to set the password
+ "c9b3bd1a0cde797eef0eff16c580dab996ba3a21961cccc\
+d0f5e65c61558243e50b1a490088bd4824e3b35562d383ca02260398\
+ef1979b302212ec1c5383d1d05fc8d843"
+ else
+ "c734b6e4787c3727bb35e29fdd92b97c\
+1de12df509577a045728255ec7c6c5f5\
+c18efa05ed02b682ffa7ebc05119900e\
+b1d4880833aa7a190afc13e2bf0936b8\
+20123e8c98f0f9bcac2a629d9163caac\
+9464a8c234f3919082400b4f939bb77b\
+c5adbbac718b7eb99463a7b679571e0f\
+1c9fef2ef08d0b9e9c2bcf644eed2ffc"
+ end
+ }
+
+ let(:iterations) { 25000 }
+ let(:salt) { "9e2e7d5ee473b496fd24cf0bbfcaedfcb291ee21740e570d1e917e874f8788ca" }
+
+ it "action :create should create the user" do
+ user_resource.run_action(:create)
+ user_should_exist
+ check_password("soawesome")
+ end
+
+ describe "when user exists" do
+ before do
+ existing_resource = user_resource.dup
+ existing_resource.run_action(:create)
+ user_should_exist
+ end
+
+ describe "when password is updated" do
+ it "should update the password of the user" do
+ user_resource.password("mykitchen")
+ user_resource.run_action(:create)
+ check_password("mykitchen")
+ end
+ end
+ end
+ end
+
+ describe "when a user is member of some groups" do
+ let(:groups) { ["staff", "operator"] }
+
+ before do
+ existing_resource = user_resource.dup
+ existing_resource.run_action(:create)
+
+ groups.each do |group|
+ shell_out!("/usr/bin/dscl . -append '/Groups/#{group}' GroupMembership #{username}")
+ end
+ end
+
+ after do
+ groups.each do |group|
+ # Do not raise an error when user is correctly removed
+ shell_out("/usr/bin/dscl . -delete '/Groups/#{group}' GroupMembership #{username}")
+ end
+ end
+
+ it ":remove action removes the user from the groups and deletes the user"do
+ user_resource.run_action(:remove)
+ groups.each do |group|
+ # Do not raise an error when group is empty
+ shell_out("dscl . read /Groups/staff GroupMembership").stdout.should_not include(group)
+ end
+ end
+ end
+
+end
diff --git a/chef/spec/unit/provider/user/dscl_spec.rb b/chef/spec/unit/provider/user/dscl_spec.rb
index a0a2ae64e5..3cb18f1979 100644
--- a/chef/spec/unit/provider/user/dscl_spec.rb
+++ b/chef/spec/unit/provider/user/dscl_spec.rb
@@ -6,9 +6,9 @@
# 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.
@@ -20,435 +20,856 @@ ShellCmdResult = Struct.new(:stdout, :stderr, :exitstatus)
require 'spec_helper'
require 'ostruct'
+require 'mixlib/shellout'
describe Chef::Provider::User::Dscl do
- before do
- @node = Chef::Node.new
- @events = Chef::EventDispatch::Dispatcher.new
- @run_context = Chef::RunContext.new(@node, {}, @events)
- @new_resource = Chef::Resource::User.new("toor")
- @provider = Chef::Provider::User::Dscl.new(@new_resource, @run_context)
- end
-
+ let(:node) {
+ node = Chef::Node.new
+ node.stub(:[]).with(:platform_version).and_return(mac_version)
+ node.stub(:[]).with(:platform).and_return("mac_os_x")
+ node
+ }
+
+ let(:events) {
+ Chef::EventDispatch::Dispatcher.new
+ }
+
+ let(:run_context) {
+ Chef::RunContext.new(node, {}, events)
+ }
+
+ let(:new_resource) {
+ r = Chef::Resource::User.new("toor")
+ r.password(password)
+ r.salt(salt)
+ r.iterations(iterations)
+ r
+ }
+
+ let(:provider) {
+ Chef::Provider::User::Dscl.new(new_resource, run_context)
+ }
+
+ let(:mac_version) {
+ "10.9.1"
+ }
+
+ let(:password) { nil }
+ let(:salt) { nil }
+ let(:iterations) { nil }
+
+ let(:salted_sha512_password) {
+ "0f543f021c63255e64e121a3585601b8ecfedf6d2\
+705ddac69e682a33db5dbcdb9b56a2520bc8fff63a\
+2ba6b7984c0737ff0b7949455071581f7affcd536d\
+402b6cdb097"
+ }
+
+ let(:salted_sha512_pbkdf2_password) {
+ "c734b6e4787c3727bb35e29fdd92b97c\
+1de12df509577a045728255ec7c6c5f5\
+c18efa05ed02b682ffa7ebc05119900e\
+b1d4880833aa7a190afc13e2bf0936b8\
+20123e8c98f0f9bcac2a629d9163caac\
+9464a8c234f3919082400b4f939bb77b\
+c5adbbac718b7eb99463a7b679571e0f\
+1c9fef2ef08d0b9e9c2bcf644eed2ffc"
+ }
+
+ let(:salted_sha512_pbkdf2_salt) {
+ "2d942d8364a9ccf2b8e5cb7ed1ff58f78\
+e29dbfee7f9db58859144d061fd0058"
+ }
+
+ let(:salted_sha512_pbkdf2_iterations) {
+ 25000
+ }
+
+ let(:vagrant_sha_512) {
+ "6f75d7190441facc34291ebbea1fc756b242d4f\
+e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\
+ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30"
+ }
+
+ let(:vagrant_sha_512_pbkdf2) {
+ "12601a90db17cbf\
+8ba4808e6382fb0d3b9d8a6c1a190477bf680ab21afb\
+6065467136e55cc208a6f74156e3daf20fb13369ef4b\
+7bafa047d80359fb46a48a4adccd548ebb33851b093\
+47cca84341a7f93a27147343f89fb843fb46c0017d2\
+64afa4976baacf941b915bd1ec1ca24c30b3e759e02\
+403e02f59fe7ff5938a7636c"
+ }
+
+ let(:vagrant_sha_512_pbkdf2_salt) {
+ "ee954be472fdc60ddf89484781433993625f006af6ec810c08f49a7e413946a1"
+ }
+
+ let(:vagrant_sha_512_pbkdf2_iterations) {
+ 34482
+ }
+
describe "when shelling out to dscl" do
it "should run dscl with the supplied cmd /Path args" do
shell_return = ShellCmdResult.new('stdout', 'err', 0)
- @provider.should_receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return)
- @provider.safe_dscl("cmd /Path args").should == 'stdout'
+ provider.should_receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return)
+ provider.run_dscl("cmd /Path args").should == 'stdout'
end
it "returns an empty string from delete commands" do
shell_return = ShellCmdResult.new('out', 'err', 23)
- @provider.should_receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return)
- @provider.safe_dscl("delete /Path args").should == ""
+ provider.should_receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return)
+ provider.run_dscl("delete /Path args").should == ""
end
it "should raise an exception for any other command" do
shell_return = ShellCmdResult.new('out', 'err', 23)
- @provider.should_receive(:shell_out).with('dscl . -cmd /Path arguments').and_return(shell_return)
- lambda { @provider.safe_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+ provider.should_receive(:shell_out).with('dscl . -cmd /Path arguments').and_return(shell_return)
+ lambda { provider.run_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
end
it "raises an exception when dscl reports 'no such key'" do
shell_return = ShellCmdResult.new("No such key: ", 'err', 23)
- @provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
- lambda { @provider.safe_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+ provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
+ lambda { provider.run_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+ end
+
+ it "raises an exception when dscl reports 'eDSRecordNotFound'" do
+ shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
+ provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
+ lambda { provider.run_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
end
end
describe "get_free_uid" do
before do
- @provider.stub!(:safe_dscl).and_return("\nwheel 200\nstaff 201\n")
+ provider.should_receive(:run_dscl).with("list /Users uid").and_return("\nwheel 200\nstaff 201\nbrahms 500\nchopin 501\n")
end
-
- it "should run safe_dscl with list /Users uid" do
- @provider.should_receive(:safe_dscl).with("list /Users uid")
- @provider.get_free_uid
+
+ describe "when resource is configured as system" do
+ before do
+ new_resource.system(true)
+ end
+
+ it "should return the first unused uid number on or above 500" do
+ provider.get_free_uid.should eq(202)
+ end
end
it "should return the first unused uid number on or above 200" do
- @provider.get_free_uid.should == 202
+ provider.get_free_uid.should eq(502)
end
-
+
it "should raise an exception when the search limit is exhausted" do
search_limit = 1
- lambda { @provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
+ lambda { provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
end
end
describe "uid_used?" do
- before do
- @provider.stub!(:safe_dscl).and_return("\naj 500\n")
+ it "should return false if not given any valid uid number" do
+ provider.uid_used?(nil).should be_false
end
- it "should run safe_dscl with list /Users uid" do
- @provider.should_receive(:safe_dscl).with("list /Users uid")
- @provider.uid_used?(500)
- end
-
- it "should return true for a used uid number" do
- @provider.uid_used?(500).should be_true
- end
+ describe "when called with a user id" do
+ before do
+ provider.should_receive(:run_dscl).with("list /Users uid").and_return("\naj 500\n")
+ end
- it "should return false for an unused uid number" do
- @provider.uid_used?(501).should be_false
- end
+ it "should return true for a used uid number" do
+ provider.uid_used?(500).should be_true
+ end
- it "should return false if not given any valid uid number" do
- @provider.uid_used?(nil).should be_false
+ it "should return false for an unused uid number" do
+ provider.uid_used?(501).should be_false
+ end
end
end
describe "when determining the uid to set" do
it "raises RequestedUIDUnavailable if the requested uid is already in use" do
- @provider.stub!(:uid_used?).and_return(true)
- @provider.should_receive(:get_free_uid).and_return(501)
- lambda { @provider.set_uid }.should raise_error(Chef::Exceptions::RequestedUIDUnavailable)
+ provider.stub(:uid_used?).and_return(true)
+ provider.should_receive(:get_free_uid).and_return(501)
+ lambda { provider.dscl_set_uid }.should raise_error(Chef::Exceptions::RequestedUIDUnavailable)
end
-
+
it "finds a valid, unused uid when none is specified" do
- @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
- @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 501")
- @provider.should_receive(:get_free_uid).and_return(501)
- @provider.set_uid
- @new_resource.uid.should == 501
+ provider.should_receive(:run_dscl).with("list /Users uid").and_return('')
+ provider.should_receive(:run_dscl).with("create /Users/toor UniqueID 501")
+ provider.should_receive(:get_free_uid).and_return(501)
+ provider.dscl_set_uid
+ new_resource.uid.should eq(501)
end
-
+
it "sets the uid specified in the resource" do
- @new_resource.uid(1000)
- @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 1000").and_return(true)
- @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
- @provider.set_uid
+ new_resource.uid(1000)
+ provider.should_receive(:run_dscl).with("create /Users/toor UniqueID 1000").and_return(true)
+ provider.should_receive(:run_dscl).with("list /Users uid").and_return('')
+ provider.dscl_set_uid
end
end
describe "when modifying the home directory" do
+ let(:current_resource) {
+ new_resource.dup
+ }
+
before do
- @new_resource.supports({ :manage_home => true })
- @new_resource.home('/Users/toor')
-
- @current_resource = @new_resource.dup
- @provider.current_resource = @current_resource
+ new_resource.supports({ :manage_home => true })
+ new_resource.home('/Users/toor')
+
+ provider.current_resource = current_resource
end
it "deletes the home directory when resource#home is nil" do
- @new_resource.instance_variable_set(:@home, nil)
- @provider.should_receive(:safe_dscl).with("delete /Users/toor NFSHomeDirectory").and_return(true)
- @provider.modify_home
+ new_resource.instance_variable_set(:@home, nil)
+ provider.should_receive(:run_dscl).with("delete /Users/toor NFSHomeDirectory").and_return(true)
+ provider.dscl_set_home
end
-
+
it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do
- @new_resource.home('epic-fail')
- lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::InvalidHomeDirectory)
+ new_resource.home('epic-fail')
+ lambda { provider.dscl_set_home }.should raise_error(Chef::Exceptions::InvalidHomeDirectory)
end
it "moves the users home to the new location if it exists and the target location is different" do
- @new_resource.supports(:manage_home => true)
-
+ new_resource.supports(:manage_home => true)
+
current_home = CHEF_SPEC_DATA + '/old_home_dir'
current_home_files = [current_home + '/my-dot-emacs', current_home + '/my-dot-vim']
- @current_resource.home(current_home)
- @new_resource.gid(23)
- ::File.stub!(:exists?).with('/old/home/toor').and_return(true)
- ::File.stub!(:exists?).with('/Users/toor').and_return(true)
-
+ current_resource.home(current_home)
+ new_resource.gid(23)
+ ::File.stub(:exists?).with('/old/home/toor').and_return(true)
+ ::File.stub(:exists?).with('/Users/toor').and_return(true)
+
FileUtils.should_receive(:mkdir_p).with('/Users/toor').and_return(true)
FileUtils.should_receive(:rmdir).with(current_home)
::Dir.should_receive(:glob).with("#{CHEF_SPEC_DATA}/old_home_dir/*",::File::FNM_DOTMATCH).and_return(current_home_files)
FileUtils.should_receive(:mv).with(current_home_files, "/Users/toor", :force => true)
FileUtils.should_receive(:chown_R).with('toor','23','/Users/toor')
-
- @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
- @provider.modify_home
+
+ provider.should_receive(:run_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
+ provider.dscl_set_home
end
it "should raise an exception when the systems user template dir (skel) cannot be found" do
- ::File.stub!(:exists?).and_return(false,false,false)
- lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::User)
+ ::File.stub(:exists?).and_return(false,false,false)
+ lambda { provider.dscl_set_home }.should raise_error(Chef::Exceptions::User)
end
it "should run ditto to copy any missing files from skel to the new home dir" do
::File.should_receive(:exists?).with("/System/Library/User\ Template/English.lproj").and_return(true)
FileUtils.should_receive(:chown_R).with('toor', '', '/Users/toor')
- @provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
- @provider.ditto_home
+ provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
+ provider.ditto_home
end
it "creates the user's NFSHomeDirectory and home directory" do
- @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'").and_return(true)
- @provider.should_receive(:ditto_home)
- @provider.modify_home
+ provider.should_receive(:run_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'").and_return(true)
+ provider.should_receive(:ditto_home)
+ provider.dscl_set_home
end
end
- describe "osx_shadow_hash?" do
- it "should return true when the string is a shadow hash" do
- @provider.osx_shadow_hash?("0"*8*155).should eql(true)
- end
+ describe "resource_requirements" do
+ let(:dscl_exists) { true }
+ let(:plutil_exists) { true }
- it "should return false otherwise" do
- @provider.osx_shadow_hash?("any other string").should eql(false)
+ before do
+ ::File.stub(:exists?).with("/usr/bin/dscl").and_return(dscl_exists)
+ ::File.stub(:exists?).with("/usr/bin/plutil").and_return(plutil_exists)
end
- end
- describe "when detecting the format of a password" do
- it "detects a OS X salted sha1" do
- @provider.osx_salted_sha1?("0"*48).should eql(true)
- @provider.osx_salted_sha1?("any other string").should eql(false)
+ def run_requirements
+ provider.define_resource_requirements
+ provider.action = :create
+ provider.process_resource_requirements
end
- end
- describe "guid" do
- it "should run safe_dscl with read /Users/user GeneratedUID to get the users GUID" do
- expected_uuid = "b398449e-cee0-45e0-80f8-b0b5b1bfdeaa"
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(expected_uuid + "\n")
- @provider.guid.should == expected_uuid
+ describe "when dscl doesn't exist" do
+ let(:dscl_exists) { false }
+
+ it "should raise an error" do
+ lambda { run_requirements }.should raise_error
+ end
end
- end
- describe "shadow_hash_set?" do
+ describe "when plutil doesn't exist" do
+ let(:plutil_exists) { false }
- it "should run safe_dscl with read /Users/user to see if the AuthenticationAuthority key exists" do
- @provider.should_receive(:safe_dscl).with("read /Users/toor")
- @provider.shadow_hash_set?
+ it "should raise an error" do
+ lambda { run_requirements }.should raise_error
+ end
end
- describe "when the user account has an AuthenticationAuthority key" do
- it "uses the shadow hash when there is a ShadowHash field in the AuthenticationAuthority key" do
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
- @provider.shadow_hash_set?.should be_true
+ describe "when on Mac 10.6" do
+ let(:mac_version) {
+ "10.6.5"
+ }
+
+ it "should raise an error" do
+ lambda { run_requirements }.should raise_error
end
+ end
+
+ describe "when on Mac 10.7" do
+ let(:mac_version) {
+ "10.7.5"
+ }
- it "does not use the shadow hash when there is no ShadowHash field in the AuthenticationAuthority key" do
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: \n")
- @provider.shadow_hash_set?.should be_false
+ describe "when password is SALTED-SHA512" do
+ let(:password) { salted_sha512_password }
+
+ it "should not raise an error" do
+ lambda { run_requirements }.should_not raise_error
+ end
end
+ describe "when password is SALTED-SHA512-PBKDF2" do
+ let(:password) { salted_sha512_pbkdf2_password }
+
+ it "should raise an error" do
+ lambda { run_requirements }.should raise_error
+ end
+ end
end
- describe "with no AuthenticationAuthority key in the user account" do
- it "does not use the shadow hash" do
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("")
- @provider.shadow_hash_set?.should eql(false)
+ [ "10.9", "10.10"].each do |version|
+ describe "when on Mac #{version}" do
+ let(:mac_version) {
+ "#{version}.2"
+ }
+
+ describe "when password is SALTED-SHA512" do
+ let(:password) { salted_sha512_password }
+
+ it "should raise an error" do
+ lambda { run_requirements }.should raise_error
+ end
+ end
+
+ describe "when password is SALTED-SHA512-PBKDF2" do
+ let(:password) { salted_sha512_pbkdf2_password }
+
+ describe "when salt and iteration is not set" do
+ it "should raise an error" do
+ lambda { run_requirements }.should raise_error
+ end
+ end
+
+ describe "when salt and iteration is set" do
+ let(:salt) { salted_sha512_pbkdf2_salt }
+ let(:iterations) { salted_sha512_pbkdf2_iterations }
+
+ it "should not raise an error" do
+ lambda { run_requirements }.should_not raise_error
+ end
+ end
+ end
end
end
end
- describe "when setting or modifying the user password" do
+ describe "load_current_resource" do
+ # set this to any of the user plist files under spec/data
+ let(:user_plist_file) { nil }
+
before do
- @new_resource.password("password")
- @output = StringIO.new
+ provider.should_receive(:shell_out).with("plutil -convert xml1 -o - /var/db/dslocal/nodes/Default/users/toor.plist") do
+ if user_plist_file.nil?
+ ShellCmdResult.new('Can not find the file', 'Sorry!!', 1)
+ else
+ ShellCmdResult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.plist.xml")), "", 0)
+ end
+ end
+
+ if !user_plist_file.nil?
+ provider.should_receive(:convert_binary_plist_to_xml).and_return(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.shadow.xml")))
+ end
end
- describe "when using a salted sha1 for the password" do
- before do
- @new_resource.password("F"*48)
- end
-
- it "should write a shadow hash file with the expected salted sha1" do
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
- File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
- expected_salted_sha1 = @new_resource.password
- expected_shadow_hash = "00000000"*155
- expected_shadow_hash[168] = expected_salted_sha1
- @provider.modify_password
- @output.string.strip.should == expected_shadow_hash
- end
- end
-
- describe "when given a shadow hash file for the password" do
- it "should write the shadow hash file directly to /var/db/shadow/hash/GUID" do
- shadow_hash = '0123456789ABCDE0123456789ABCDEF' * 40
- raise 'oops' unless shadow_hash.size == 1240
- @new_resource.password shadow_hash
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
- File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
- @provider.modify_password
- @output.string.strip.should == shadow_hash
- end
- end
-
- describe "when given a string for the password" do
- it "should output a salted sha1 and shadow hash file from the specified password" do
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
- File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
- @new_resource.password("password")
- OpenSSL::Random.stub!(:random_bytes).and_return("\377\377\377\377\377\377\377\377")
- expected_salted_sha1 = "F"*8+"SHA1-"*8
- expected_shadow_hash = "00000000"*155
- expected_shadow_hash[168] = expected_salted_sha1
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
- @provider.modify_password
- @output.string.strip.should match(/^0{168}(FFFFFFFF1C1AA7935D4E1190AFEC92343F31F7671FBF126D)0{1071}$/)
- end
- end
-
- it "should write the output directly to the shadow hash file at /var/db/shadow/hash/GUID" do
- shadow_file = StringIO.new
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
- File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
- @provider.modify_password
- shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
- end
-
- it "should run safe_dscl append /Users/user AuthenticationAuthority ;ShadowHash; when no shadow hash set" do
- shadow_file = StringIO.new
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
- File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority:\n")
- @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';ShadowHash;'")
- @provider.modify_password
- shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
+ describe "when user is not there" do
+ it "shouldn't raise an error" do
+ lambda { provider.load_current_resource }.should_not raise_error
+ end
+
+ it "should set @user_exists" do
+ provider.load_current_resource
+ provider.instance_variable_get(:@user_exists).should be_false
+ end
+
+ it "should set username" do
+ provider.load_current_resource
+ provider.current_resource.username.should eq("toor")
+ end
+ end
+
+ describe "when user is there" do
+ let(:password) { "something" } # Load password during load_current_resource
+
+ describe "on 10.7" do
+ let(:mac_version) {
+ "10.7.5"
+ }
+
+ let(:user_plist_file) { "10.7" }
+
+ it "collects the user data correctly" do
+ provider.load_current_resource
+ provider.current_resource.comment.should eq("vagrant")
+ provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
+ provider.current_resource.gid.should eq("80")
+ provider.current_resource.home.should eq("/Users/vagrant")
+ provider.current_resource.shell.should eq("/bin/bash")
+ provider.current_resource.password.should eq(vagrant_sha_512)
+ end
+
+ describe "when a plain password is set that is same" do
+ let(:password) { "vagrant" }
+
+ it "diverged_password? should report false" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_false
+ end
+ end
+
+ describe "when a plain password is set that is different" do
+ let(:password) { "not_vagrant" }
+
+ it "diverged_password? should report true" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_true
+ end
+ end
+
+ describe "when iterations change" do
+ let(:password) { vagrant_sha_512 }
+ let(:iterations) { 12345 }
+
+ it "diverged_password? should report false" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_false
+ end
+ end
+
+ describe "when shadow hash changes" do
+ let(:password) { salted_sha512_password }
+
+ it "diverged_password? should report true" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_true
+ end
+ end
+
+ describe "when salt change" do
+ let(:password) { vagrant_sha_512 }
+ let(:salt) { "SOMETHINGRANDOM" }
+
+ it "diverged_password? should report false" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_false
+ end
+ end
+ end
+
+ describe "on 10.8" do
+ let(:mac_version) {
+ "10.8.3"
+ }
+
+ let(:user_plist_file) { "10.8" }
+
+ it "collects the user data correctly" do
+ provider.load_current_resource
+ provider.current_resource.comment.should eq("vagrant")
+ provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
+ provider.current_resource.gid.should eq("80")
+ provider.current_resource.home.should eq("/Users/vagrant")
+ provider.current_resource.shell.should eq("/bin/bash")
+ provider.current_resource.password.should eq("ea4c2d265d801ba0ec0dfccd\
+253dfc1de91cbe0806b4acc1ed7fe22aebcf6beb5344d0f442e590\
+ffa04d679075da3afb119e41b72b5eaf08ee4aa54693722646d5\
+19ee04843deb8a3e977428d33f625e83887913e5c13b70035961\
+5e00ad7bc3e7a0c98afc3e19d1360272454f8d33a9214d2fbe8b\
+e68d1f9821b26689312366")
+ provider.current_resource.salt.should eq("f994ef2f73b7c5594ebd1553300976b20733ce0e24d659783d87f3d81cbbb6a9")
+ provider.current_resource.iterations.should eq(39840)
+ end
+ end
+
+ describe "on 10.7 upgraded to 10.8" do
+ # In this scenario user password is still in 10.7 format
+ let(:mac_version) {
+ "10.8.3"
+ }
+
+ let(:user_plist_file) { "10.7-8" }
+
+ it "collects the user data correctly" do
+ provider.load_current_resource
+ provider.current_resource.comment.should eq("vagrant")
+ provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
+ provider.current_resource.gid.should eq("80")
+ provider.current_resource.home.should eq("/Users/vagrant")
+ provider.current_resource.shell.should eq("/bin/bash")
+ provider.current_resource.password.should eq("6f75d7190441facc34291ebbea1fc756b242d4f\
+e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\
+ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30")
+ end
+
+ describe "when a plain text password is set" do
+ it "reports password needs to be updated" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_true
+ end
+ end
+
+ describe "when a salted-sha512-pbkdf2 shadow is set" do
+ let(:password) { salted_sha512_pbkdf2_password }
+ let(:salt) { salted_sha512_pbkdf2_salt }
+ let(:iterations) { salted_sha512_pbkdf2_iterations }
+
+ it "reports password needs to be updated" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_true
+ end
+ end
+ end
+
+ describe "on 10.9" do
+ let(:mac_version) {
+ "10.9.1"
+ }
+
+ let(:user_plist_file) { "10.9" }
+
+ it "collects the user data correctly" do
+ provider.load_current_resource
+ provider.current_resource.comment.should eq("vagrant")
+ provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
+ provider.current_resource.gid.should eq("80")
+ provider.current_resource.home.should eq("/Users/vagrant")
+ provider.current_resource.shell.should eq("/bin/bash")
+ provider.current_resource.password.should eq(vagrant_sha_512_pbkdf2)
+ provider.current_resource.salt.should eq(vagrant_sha_512_pbkdf2_salt)
+ provider.current_resource.iterations.should eq(vagrant_sha_512_pbkdf2_iterations)
+ end
+
+ describe "when a plain password is set that is same" do
+ let(:password) { "vagrant" }
+
+ it "diverged_password? should report false" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_false
+ end
+ end
+
+ describe "when a plain password is set that is different" do
+ let(:password) { "not_vagrant" }
+
+ it "diverged_password? should report true" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_true
+ end
+ end
+
+ describe "when iterations change" do
+ let(:password) { vagrant_sha_512_pbkdf2 }
+ let(:salt) { vagrant_sha_512_pbkdf2_salt }
+ let(:iterations) { 12345 }
+
+ it "diverged_password? should report true" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_true
+ end
+ end
+
+ describe "when shadow hash changes" do
+ let(:password) { salted_sha512_pbkdf2_password }
+ let(:salt) { vagrant_sha_512_pbkdf2_salt }
+ let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
+
+ it "diverged_password? should report true" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_true
+ end
+ end
+
+ describe "when salt change" do
+ let(:password) { vagrant_sha_512_pbkdf2 }
+ let(:salt) { salted_sha512_pbkdf2_salt }
+ let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
+
+ it "diverged_password? should report true" do
+ provider.load_current_resource
+ provider.diverged_password?.should be_true
+ end
+ end
+ end
end
end
- describe "load_current_resource" do
- it "should raise an error if the required binary /usr/bin/dscl doesn't exist" do
- ::File.should_receive(:exists?).with("/usr/bin/dscl").and_return(false)
- lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::User)
+ describe "salted_sha512_pbkdf2?" do
+ it "should return true when the string is a salted_sha512_pbkdf2 hash" do
+ provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password).should be_true
end
- it "shouldn't raise an error if /usr/bin/dscl exists" do
- ::File.stub!(:exists?).and_return(true)
- lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::User)
+ it "should return false otherwise" do
+ provider.salted_sha512_pbkdf2?(salted_sha512_password).should be_false
+ provider.salted_sha512_pbkdf2?("any other string").should be_false
end
end
- describe "when the user does not yet exist and chef is creating it" do
- before do
- @new_resource.comment "#mockssuck"
- @new_resource.gid 1001
+ describe "salted_sha512?" do
+ it "should return true when the string is a salted_sha512_pbkdf2 hash" do
+ provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password).should be_true
+ end
+
+ it "should return false otherwise" do
+ provider.salted_sha512?(salted_sha512_pbkdf2_password).should be_false
+ provider.salted_sha512?("any other string").should be_false
end
-
- it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
- @provider.should_receive :dscl_create_user
- @provider.should_receive :dscl_create_comment
- @provider.should_receive :set_uid
- @provider.should_receive :dscl_set_gid
- @provider.should_receive :modify_home
- @provider.should_receive :dscl_set_shell
- @provider.should_receive :modify_password
- @provider.create_user
+ end
+
+ describe "prepare_password_shadow_info" do
+ describe "when on Mac 10.7" do
+ let(:mac_version) {
+ "10.7.1"
+ }
+
+ describe "when the password is plain text" do
+ let(:password) { "vagrant" }
+
+ it "password_shadow_info should have salted-sha-512 format" do
+ shadow_info = provider.prepare_password_shadow_info
+ shadow_info.should have_key("SALTED-SHA512")
+ info = shadow_info["SALTED-SHA512"].string.unpack('H*').first
+ provider.salted_sha512?(info).should be_true
+ end
+ end
+
+ describe "when the password is salted-sha-512" do
+ let(:password) { vagrant_sha_512 }
+
+ it "password_shadow_info should have salted-sha-512 format" do
+ shadow_info = provider.prepare_password_shadow_info
+ shadow_info.should have_key("SALTED-SHA512")
+ info = shadow_info["SALTED-SHA512"].string.unpack('H*').first
+ provider.salted_sha512?(info).should be_true
+ info.should eq(vagrant_sha_512)
+ end
+ end
end
- it "creates the user and sets the comment field" do
- @provider.should_receive(:safe_dscl).with("create /Users/toor").and_return(true)
- @provider.dscl_create_user
+ ["10.8", "10.9", "10.10"].each do |version|
+ describe "when on Mac #{version}" do
+ let(:mac_version) {
+ "#{version}.1"
+ }
+
+ describe "when the password is plain text" do
+ let(:password) { "vagrant" }
+
+ it "password_shadow_info should have salted-sha-512 format" do
+ shadow_info = provider.prepare_password_shadow_info
+ shadow_info.should have_key("SALTED-SHA512-PBKDF2")
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("entropy")
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("salt")
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("iterations")
+ info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first
+ provider.salted_sha512_pbkdf2?(info).should be_true
+ end
+ end
+
+ describe "when the password is salted-sha-512" do
+ let(:password) { vagrant_sha_512_pbkdf2 }
+ let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
+ let(:salt) { vagrant_sha_512_pbkdf2_salt }
+
+ it "password_shadow_info should have salted-sha-512 format" do
+ shadow_info = provider.prepare_password_shadow_info
+ shadow_info.should have_key("SALTED-SHA512-PBKDF2")
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("entropy")
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("salt")
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("iterations")
+ info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first
+ provider.salted_sha512_pbkdf2?(info).should be_true
+ info.should eq(vagrant_sha_512_pbkdf2)
+ end
+ end
+ end
end
-
- it "sets the comment field" do
- @provider.should_receive(:safe_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
- @provider.dscl_create_comment
+ end
+
+ describe "set_password" do
+ before do
+ new_resource.password("something")
end
- it "should run safe_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
- @provider.should_receive(:safe_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true)
- @provider.dscl_set_gid
+ it "should sleep and flush the dscl cache before saving the password" do
+ provider.should_receive(:prepare_password_shadow_info).and_return({ })
+ mock_shellout = double("Mock::Shellout")
+ mock_shellout.stub(:run_command)
+ Mixlib::ShellOut.should_receive(:new).and_return(mock_shellout)
+ provider.should_receive(:read_user_info)
+ provider.should_receive(:dscl_set)
+ provider.should_receive(:sleep).with(3)
+ provider.should_receive(:shell_out).with("dscacheutil '-flushcache'")
+ provider.should_receive(:save_user_info)
+ provider.set_password
end
+ end
+
+ describe "when the user does not yet exist and chef is creating it" do
+ context "with a numeric gid" do
+ before do
+ new_resource.comment "#mockssuck"
+ new_resource.gid 1001
+ end
+
+ it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
+ provider.should_receive :dscl_create_user
+ provider.should_receive :dscl_create_comment
+ provider.should_receive :dscl_set_uid
+ provider.should_receive :dscl_set_gid
+ provider.should_receive :dscl_set_home
+ provider.should_receive :dscl_set_shell
+ provider.should_receive :set_password
+ provider.create_user
+ end
+
+ it "creates the user and sets the comment field" do
+ provider.should_receive(:run_dscl).with("create /Users/toor").and_return(true)
+ provider.dscl_create_user
+ end
+
+ it "sets the comment field" do
+ provider.should_receive(:run_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
+ provider.dscl_create_comment
+ end
+
+ it "should run run_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
+ provider.should_receive(:run_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true)
+ provider.dscl_set_gid
+ end
- it "should run safe_dscl with create /Users/user UserShell to set the users login shell" do
- @provider.should_receive(:safe_dscl).with("create /Users/toor UserShell '/usr/bin/false'").and_return(true)
- @provider.dscl_set_shell
+ it "should run run_dscl with create /Users/user UserShell to set the users login shell" do
+ provider.should_receive(:run_dscl).with("create /Users/toor UserShell '/usr/bin/false'").and_return(true)
+ provider.dscl_set_shell
+ end
end
+ context "with a non-numeric gid" do
+ before do
+ new_resource.comment "#mockssuck"
+ new_resource.gid "newgroup"
+ end
+
+ it "should map the group name to a numeric ID when the group exists" do
+ provider.should_receive(:run_dscl).with("read /Groups/newgroup PrimaryGroupID").ordered.and_return("PrimaryGroupID: 1001\n")
+ provider.should_receive(:run_dscl).with("create /Users/toor PrimaryGroupID '1001'").ordered.and_return(true)
+ provider.dscl_set_gid
+ end
+
+ it "should raise an exception when the group does not exist" do
+ shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
+ provider.should_receive(:shell_out).with('dscl . -read /Groups/newgroup PrimaryGroupID').and_return(shell_return)
+ lambda { provider.dscl_set_gid }.should raise_error(Chef::Exceptions::GroupIDNotFound)
+ end
+ end
end
describe "when the user exists and chef is managing it" do
before do
- @current_resource = @new_resource.dup
- @provider.current_resource = @current_resource
-
- # These are all different from @current_resource
- @new_resource.username "mud"
- @new_resource.uid 2342
- @new_resource.gid 2342
- @new_resource.home '/Users/death'
- @new_resource.password 'goaway'
- end
-
+ current_resource = new_resource.dup
+ provider.current_resource = current_resource
+
+ # These are all different from current_resource
+ new_resource.username "mud"
+ new_resource.uid 2342
+ new_resource.gid 2342
+ new_resource.home '/Users/death'
+ new_resource.password 'goaway'
+ end
+
it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do
- @provider.should_receive :dscl_create_user
- @provider.should_receive :dscl_create_comment
- @provider.should_receive :set_uid
- @provider.should_receive :dscl_set_gid
- @provider.should_receive :modify_home
- @provider.should_receive :dscl_set_shell
- @provider.should_receive :modify_password
- @provider.create_user
+ provider.should_receive :dscl_create_user
+ provider.should_receive :dscl_create_comment
+ provider.should_receive :dscl_set_uid
+ provider.should_receive :dscl_set_gid
+ provider.should_receive :dscl_set_home
+ provider.should_receive :dscl_set_shell
+ provider.should_receive :set_password
+ provider.create_user
end
end
describe "when changing the gid" do
before do
- @current_resource = @new_resource.dup
- @provider.current_resource = @current_resource
-
- # This is different from @current_resource
- @new_resource.gid 2342
+ current_resource = new_resource.dup
+ provider.current_resource = current_resource
+
+ # This is different from current_resource
+ new_resource.gid 2342
end
-
+
it "sets the gid" do
- @provider.should_receive :dscl_set_gid
- @provider.manage_user
+ provider.should_receive :dscl_set_gid
+ provider.manage_user
end
end
- describe "when the user exists and chef is removing it" do
- it "removes the user's home directory when the resource is configured to manage home" do
- @new_resource.supports({ :manage_home => true })
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("NFSHomeDirectory: /Users/fuuuuuuuuuuuuu")
- @provider.should_receive(:safe_dscl).with("delete /Users/toor")
- FileUtils.should_receive(:rm_rf).with("/Users/fuuuuuuuuuuuuu")
- @provider.remove_user
- end
-
- it "removes the user from any group memberships" do
- Etc.stub(:group).and_yield(OpenStruct.new(:name => 'ragefisters', :mem => 'toor'))
- @provider.should_receive(:safe_dscl).with("delete /Users/toor")
- @provider.should_receive(:safe_dscl).with("delete /Groups/ragefisters GroupMembership 'toor'")
- @provider.remove_user
+ describe "when the user exists" do
+ before do
+ provider.should_receive(:shell_out).with("plutil -convert xml1 -o - /var/db/dslocal/nodes/Default/users/toor.plist") do
+ ShellCmdResult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/10.9.plist.xml")), "", 0)
+ end
+ provider.load_current_resource
+ end
+
+ describe "when Chef is removing the user" do
+ it "removes the user from the groups and deletes home directory when the resource is configured to manage home" do
+ new_resource.supports({ :manage_home => true })
+ provider.should_receive(:run_dscl).with("list /Groups").and_return("my_group\nyour_group\nreal_group\n")
+ provider.should_receive(:run_dscl).with("read /Groups/my_group").and_raise(Chef::Exceptions::DsclCommandFailed) # Empty group
+ provider.should_receive(:run_dscl).with("read /Groups/your_group").and_return("GroupMembership: not_you")
+ provider.should_receive(:run_dscl).with("read /Groups/real_group").and_return("GroupMembership: toor")
+ provider.should_receive(:run_dscl).with("delete /Groups/real_group GroupMembership 'toor'")
+ provider.should_receive(:run_dscl).with("delete /Users/toor")
+ FileUtils.should_receive(:rm_rf).with("/Users/vagrant")
+ provider.remove_user
+ end
end
- end
- describe "when discovering if a user is locked" do
-
- it "determines the user is not locked when dscl shows an AuthenticationAuthority without a DisabledUser field" do
- @provider.should_receive(:safe_dscl).with("read /Users/toor")
- @provider.should_not be_locked
+ describe "when user is not locked" do
+ it "determines the user as not locked" do
+ provider.should_not be_locked
+ end
end
- it "determines the user is locked when dscl shows an AuthenticationAuthority with a DisabledUser field" do
- @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\nAuthenticationAuthority: ;DisabledUser;\n")
- @provider.should be_locked
- end
+ describe "when user is locked" do
+ before do
+ auth_authority = provider.instance_variable_get(:@authentication_authority)
+ provider.instance_variable_set(:@authentication_authority, auth_authority + ";DisabledUser;")
+ end
+
+ it "determines the user as not locked" do
+ provider.should be_locked
+ end
- it "determines the user is not locked when dscl shows no AuthenticationAuthority" do
- @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\n")
- @provider.should_not be_locked
+ it "can unlock the user" do
+ provider.should_receive(:run_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2>'")
+ provider.unlock_user
+ end
end
end
describe "when locking the user" do
- it "should run safe_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
- @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'")
- @provider.lock_user
+ it "should run run_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
+ provider.should_receive(:run_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'")
+ provider.lock_user
end
end
- describe "when unlocking the user" do
- it "removes DisabledUser from the authentication string" do
- @provider.should_receive(:safe_dscl).with("read /Users/toor AuthenticationAuthority").and_return("\nAuthenticationAuthority: ;ShadowHash; ;DisabledUser;\n")
- @provider.should_receive(:safe_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;'")
- @provider.unlock_user
- end
- end
end