require 'puppet/provider/nameservice/objectadd' require 'date' require 'puppet/util/libuser' require 'time' require 'puppet/error' Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameService::ObjectAdd do desc "User management via `useradd` and its ilk. Note that you will need to install Ruby's shadow password library (often known as `ruby-libshadow`) if you wish to manage user passwords." commands :add => "useradd", :delete => "userdel", :modify => "usermod", :password => "chage" options :home, :flag => "-d", :method => :dir options :comment, :method => :gecos options :groups, :flag => "-G" options :password_min_age, :flag => "-m", :method => :sp_min options :password_max_age, :flag => "-M", :method => :sp_max options :password_warn_days, :flag => "-W", :method => :sp_warn options :password, :method => :sp_pwdp options :expiry, :method => :sp_expire, :munge => proc { |value| if value == :absent '' else case Facter.value(:operatingsystem) when 'Solaris' # Solaris uses %m/%d/%Y for useradd/usermod expiry_year, expiry_month, expiry_day = value.split('-') [expiry_month, expiry_day, expiry_year].join('/') else value end end }, :unmunge => proc { |value| if value == -1 :absent else # Expiry is days after 1970-01-01 (Date.new(1970,1,1) + value).strftime('%Y-%m-%d') end } optional_commands :localadd => "luseradd", :localdelete => "luserdel", :localmodify => "lusermod", :localpassword => "lchage" has_feature :libuser if Puppet.features.libuser? def exists? return !!localuid if @resource.forcelocal? super end def uid return localuid if @resource.forcelocal? get(:uid) end def finduser(key, value) passwd_file = "/etc/passwd" passwd_keys = ['account', 'password', 'uid', 'gid', 'gecos', 'directory', 'shell'] index = passwd_keys.index(key) File.open(passwd_file) do |f| f.each_line do |line| user = line.split(":") if user[index] == value f.close return user end end end false end def local_username finduser('uid', @resource.uid) end def localuid user = finduser('account', resource[:name]) return user[2] if user false end def shell=(value) check_valid_shell set("shell", value) end verify :gid, "GID must be an integer" do |value| value.is_a? Integer end verify :groups, "Groups must be comma-separated" do |value| value !~ /\s/ end has_features :manages_homedir, :allows_duplicates, :manages_expiry has_features :system_users unless %w{HP-UX Solaris}.include? Facter.value(:operatingsystem) has_features :manages_passwords, :manages_password_age if Puppet.features.libshadow? has_features :manages_shell def check_allow_dup # We have to manually check for duplicates when using libuser # because by default duplicates are allowed. This check is # to ensure consistent behaviour of the useradd provider when # using both useradd and luseradd if (!@resource.allowdupe?) && @resource.forcelocal? if @resource.should(:uid) && finduser('uid', @resource.should(:uid).to_s) raise(Puppet::Error, "UID #{@resource.should(:uid).to_s} already exists, use allowdupe to force user creation") end elsif @resource.allowdupe? && (!@resource.forcelocal?) return ["-o"] end [] end def check_valid_shell unless File.exists?(@resource.should(:shell)) raise(Puppet::Error, "Shell #{@resource.should(:shell)} must exist") end unless File.executable?(@resource.should(:shell).to_s) raise(Puppet::Error, "Shell #{@resource.should(:shell)} must be executable") end end def check_manage_home cmd = [] if @resource.managehome? && (!@resource.forcelocal?) cmd << "-m" elsif (!@resource.managehome?) && Facter.value(:osfamily) == 'RedHat' cmd << "-M" end cmd end def check_system_users if self.class.system_users? && resource.system? ["-r"] else [] end end def add_properties cmd = [] # validproperties is a list of properties in undefined order # sort them to have a predictable command line in tests Puppet::Type.type(:user).validproperties.sort.each do |property| value = get_value_for_property(property) next if value.nil? # the value needs to be quoted, mostly because -c might # have spaces in it cmd << flag(property) << munge(property, value) end cmd end def get_value_for_property(property) return nil if property == :ensure return nil if property_manages_password_age?(property) return nil if property == :groups and @resource.forcelocal? return nil if property == :expiry and @resource.forcelocal? value = @resource.should(property) return nil if !value || value == "" value end def has_sensitive_data?(property = nil) #Check for sensitive values? properties = property ? [property] : Puppet::Type.type(:user).validproperties properties.any? do |prop| p = @resource.parameter(prop) p && p.respond_to?(:is_sensitive) && p.is_sensitive end end def addcmd if @resource.forcelocal? cmd = [command(:localadd)] @custom_environment = Puppet::Util::Libuser.getenv else cmd = [command(:add)] end if (!@resource.should(:gid)) && Puppet::Util.gid(@resource[:name]) cmd += ["-g", @resource[:name]] end cmd += add_properties cmd += check_allow_dup cmd += check_manage_home cmd += check_system_users cmd << @resource[:name] end def modifycmd(param, value) if @resource.forcelocal? case param when :groups, :expiry cmd = [command(:modify)] else cmd = [command(property_manages_password_age?(param) ? :localpassword : :localmodify)] end @custom_environment = Puppet::Util::Libuser.getenv else cmd = [command(property_manages_password_age?(param) ? :password : :modify)] end cmd << flag(param) << value cmd += check_allow_dup if param == :uid cmd << @resource[:name] cmd end def deletecmd if @resource.forcelocal? cmd = [command(:localdelete)] @custom_environment = Puppet::Util::Libuser.getenv else cmd = [command(:delete)] end # Solaris `userdel -r` will fail if the homedir does not exist. if @resource.managehome? && (('Solaris' != Facter.value(:operatingsystem)) || Dir.exist?(Dir.home(@resource[:name]))) cmd << '-r' end cmd << @resource[:name] end def passcmd if @resource.forcelocal? cmd = command(:localpassword) @custom_environment = Puppet::Util::Libuser.getenv else cmd = command(:password) end age_limits = [:password_min_age, :password_max_age, :password_warn_days].select { |property| @resource.should(property) } if age_limits.empty? nil else [cmd, age_limits.collect { |property| [flag(property), @resource.should(property)]}, @resource[:name]].flatten end end [:expiry, :password_min_age, :password_max_age, :password_warn_days, :password].each do |shadow_property| define_method(shadow_property) do if Puppet.features.libshadow? if ent = Shadow::Passwd.getspnam(@canonical_name) method = self.class.option(shadow_property, :method) return unmunge(shadow_property, ent.send(method)) end end :absent end end def create if @resource[:shell] check_valid_shell end super if @resource.forcelocal? && self.groups? set(:groups, @resource[:groups]) end if @resource.forcelocal? && @resource[:expiry] set(:expiry, @resource[:expiry]) end end def groups? !!@resource[:groups] end def property_manages_password_age?(property) property.to_s =~ /password_.+_age|password_warn_days/ end end