# User Puppet provider for AIX. It uses standard commands to manage users:
# mkuser, rmuser, lsuser, chuser
#
# Notes:
# - AIX users can have expiry date defined with minute granularity,
# but Puppet does not allow it. There is a ticket open for that (#5431)
#
# - AIX maximum password age is in WEEKs, not days
#
# See https://puppet.com/docs/puppet/latest/provider_development.html
# for more information
require 'puppet/provider/aix_object'
require 'puppet/util/posix'
require 'tempfile'
require 'date'
Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
desc "User management for AIX."
defaultfor :operatingsystem => :aix
confine :operatingsystem => :aix
# Commands that manage the element
commands :list => "/usr/sbin/lsuser"
commands :add => "/usr/bin/mkuser"
commands :delete => "/usr/sbin/rmuser"
commands :modify => "/usr/bin/chuser"
commands :chpasswd => "/bin/chpasswd"
# Provider features
has_features :manages_aix_lam
has_features :manages_homedir, :manages_passwords, :manages_shell
has_features :manages_expiry, :manages_password_age
has_features :manages_local_users_and_groups
class << self
def group_provider
@group_provider ||= Puppet::Type.type(:group).provider(:aix)
end
# Define some Puppet Property => AIX Attribute (and vice versa)
# conversion functions here.
def gid_to_pgrp(provider, gid)
group = group_provider.find(gid, provider.ia_module_args)
group[:name]
end
def pgrp_to_gid(provider, pgrp)
group = group_provider.find(pgrp, provider.ia_module_args)
group[:gid]
end
def expiry_to_expires(expiry)
return '0' if expiry == "0000-00-00" || expiry.to_sym == :absent
DateTime.parse(expiry, "%Y-%m-%d %H:%M")
.strftime("%m%d%H%M%y")
end
def expires_to_expiry(provider, expires)
return :absent if expires == '0'
unless (match_obj = /\A(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)\z/.match(expires))
#TRANSLATORS 'AIX' is the name of an operating system and should not be translated
Puppet.warning(_("Could not convert AIX expires date '%{expires}' on %{class_name}[%{resource_name}]") % { expires: expires, class_name: provider.resource.class.name, resource_name: provider.resource.name })
return :absent
end
month, day, year = match_obj[1], match_obj[2], match_obj[-1]
return "20#{year}-#{month}-#{day}"
end
# We do some validation before-hand to ensure the value's an Array,
# a String, etc. in the property. This routine does a final check to
# ensure our value doesn't have whitespace before we convert it to
# an attribute.
def groups_property_to_attribute(groups)
if groups =~ /\s/
raise ArgumentError, _("Invalid value %{groups}: Groups must be comma separated!") % { groups: groups }
end
groups
end
# We do not directly use the groups attribute value because that will
# always include the primary group, even if our user is not one of its
# members. Instead, we retrieve our property value by parsing the etc/group file,
# which matches what we do on our other POSIX platforms like Linux and Solaris.
#
# See https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.files/group_security.htm
def groups_attribute_to_property(provider, _groups)
Puppet::Util::POSIX.groups_of(provider.resource[:name]).join(',')
end
end
mapping puppet_property: :comment,
aix_attribute: :gecos
mapping puppet_property: :expiry,
aix_attribute: :expires,
property_to_attribute: method(:expiry_to_expires),
attribute_to_property: method(:expires_to_expiry)
mapping puppet_property: :gid,
aix_attribute: :pgrp,
property_to_attribute: method(:gid_to_pgrp),
attribute_to_property: method(:pgrp_to_gid)
mapping puppet_property: :groups,
property_to_attribute: method(:groups_property_to_attribute),
attribute_to_property: method(:groups_attribute_to_property)
mapping puppet_property: :home
mapping puppet_property: :shell
numeric_mapping puppet_property: :uid,
aix_attribute: :id
numeric_mapping puppet_property: :password_max_age,
aix_attribute: :maxage
numeric_mapping puppet_property: :password_min_age,
aix_attribute: :minage
numeric_mapping puppet_property: :password_warn_days,
aix_attribute: :pwdwarntime
# Now that we have all of our mappings, let's go ahead and make
# the resource methods (property getters + setters for our mapped
# properties + a getter for the attributes property).
mk_resource_methods
# Setting the primary group (pgrp attribute) on AIX causes both the
# current and new primary groups to be included in our user's groups,
# which is undesirable behavior. Thus, this custom setter resets the
# 'groups' property back to its previous value after setting the primary
# group.
def gid=(value)
old_pgrp = gid
cur_groups = groups
set(:gid, value)
begin
self.groups = cur_groups
rescue Puppet::Error => detail
raise Puppet::Error, _("Could not reset the groups property back to %{cur_groups} after setting the primary group on %{resource}[%{name}]. This means that the previous primary group of %{old_pgrp} and the new primary group of %{new_pgrp} have been added to %{cur_groups}. You will need to manually reset the groups property if this is undesirable behavior. Detail: %{detail}") % { cur_groups: cur_groups, resource: @resource.class.name, name: @resource.name, old_pgrp: old_pgrp, new_pgrp: value, detail: detail }, detail.backtrace
end
end
# Helper function that parses the password from the given
# password filehandle. This is here to make testing easier
# for #password since we cannot configure Mocha to mock out
# a method and have it return a block's value, meaning we
# cannot test #password directly (not in a simple and obvious
# way, at least).
# @api private
def parse_password(f)
# From the docs, a user stanza is formatted as (newlines are explicitly
# stated here for clarity):
# <user>:\n
# <attribute1>=<value1>\n
# <attribute2>=<value2>\n
#
# First, find our user stanza
stanza = f.each_line.find { |line| line =~ /\A#{@resource[:name]}:/ }
return :absent unless stanza
# Now find the password line, if it exists. Note our call to each_line here
# will pick up right where we left off.
match_obj = nil
f.each_line.find do |line|
# Break if we find another user stanza. This means our user
# does not have a password.
break if line =~ /^\S+:$/
match_obj = /password\s+=\s+(\S+)/.match(line)
end
return :absent unless match_obj
match_obj[1]
end
#- **password**
# The user's password, in whatever encrypted format the local machine
# requires. Be sure to enclose any value that includes a dollar sign ($)
# in single quotes ('). Requires features manages_passwords.
#
# Retrieve the password parsing the /etc/security/passwd file.
def password
# AIX reference indicates this file is ASCII
# https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.files/passwd_security.htm
Puppet::FileSystem.open("/etc/security/passwd", nil, "r:ASCII") do |f|
parse_password(f)
end
end
def password=(value)
user = @resource[:name]
begin
# Puppet execute does not support strings as input, only files.
# The password is expected to be in an encrypted format given -e is specified:
# https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.cmds1/chpasswd.htm
# /etc/security/passwd is specified as an ASCII file per the AIX documentation
tempfile = nil
tempfile = Tempfile.new("puppet_#{user}_pw", :encoding => Encoding::ASCII)
tempfile << "#{user}:#{value}\n"
tempfile.close()
# Options '-e', '-c', use encrypted password and clear flags
# Must receive "user:enc_password" as input
# command, arguments = {:failonfail => true, :combine => true}
# Fix for bugs #11200 and #10915
cmd = [self.class.command(:chpasswd), *ia_module_args, '-e', '-c']
execute_options = {
:failonfail => false,
:combine => true,
:stdinfile => tempfile.path
}
output = execute(cmd, execute_options)
# chpasswd can return 1, even on success (at least on AIX 6.1); empty output
# indicates success
if output != ""
raise Puppet::ExecutionFailure, "chpasswd said #{output}"
end
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not set password on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace
ensure
if tempfile
# Extra close will noop. This is in case the write to our tempfile
# fails.
tempfile.close()
tempfile.delete()
end
end
end
def create
super
# We specify the 'groups' AIX attribute in AixObject's create method
# when creating our user. However, this does not always guarantee that
# our 'groups' property is set to the right value. For example, the
# primary group will always be included in the 'groups' property. This is
# bad if we're explicitly managing the 'groups' property under inclusive
# membership, and we are not specifying the primary group in the 'groups'
# property value.
#
# Setting the groups property here a second time will ensure that our user is
# created and in the right state. Note that this is an idempotent operation,
# so if AixObject's create method already set it to the right value, then this
# will noop.
if (groups = @resource.should(:groups))
self.groups = groups
end
if (password = @resource.should(:password))
self.password = password
end
end
# Lists all instances of the given object, taking in an optional set
# of ia_module arguments. Returns an array of hashes, each hash
# having the schema
# {
# :name => <object_name>
# :home => <object_home>
# }
def list_all_homes(ia_module_args = [])
cmd = [command(:list), '-c', *ia_module_args, '-a', 'home', 'ALL']
parse_aix_objects(execute(cmd)).to_a.map do |object|
name = object[:name]
home = object[:attributes].delete(:home)
{ name: name, home: home }
end
rescue => e
Puppet.debug("Could not list home of all users: #{e.message}")
{}
end
# Deletes this instance resource
def delete
homedir = home
super
return unless @resource.managehome?
if !Puppet::Util.absolute_path?(homedir) || File.realpath(homedir) == '/' || Puppet::FileSystem.symlink?(homedir)
Puppet.debug("Can not remove home directory '#{homedir}' of user '#{@resource[:name]}'. Please make sure the path is not relative, symlink or '/'.")
return
end
affected_home = list_all_homes.find { |info| info[:home].start_with?(File.realpath(homedir)) }
if affected_home
Puppet.debug("Can not remove home directory '#{homedir}' of user '#{@resource[:name]}' as it would remove the home directory '#{affected_home[:home]}' of user '#{affected_home[:name]}' also.")
return
end
FileUtils.remove_entry_secure(homedir, true)
end
def deletecmd
[self.class.command(:delete), '-p'] + ia_module_args + [@resource[:name]]
end
# UNSUPPORTED
#- **profile_membership**
# Whether specified roles should be treated as the only roles
# of which the user is a member or whether they should merely
# be treated as the minimum membership list. Valid values are
# `inclusive`, `minimum`.
# UNSUPPORTED
#- **profiles**
# The profiles the user has. Multiple profiles should be
# specified as an array. Requires features manages_solaris_rbac.
# UNSUPPORTED
#- **project**
# The name of the project associated with a user Requires features
# manages_solaris_rbac.
# UNSUPPORTED
#- **role_membership**
# Whether specified roles should be treated as the only roles
# of which the user is a member or whether they should merely
# be treated as the minimum membership list. Valid values are
# `inclusive`, `minimum`.
# UNSUPPORTED
#- **roles**
# The roles the user has. Multiple roles should be
# specified as an array. Requires features manages_roles.
# UNSUPPORTED
#- **key_membership**
# Whether specified key value pairs should be treated as the only
# attributes
# of the user or whether they should merely
# be treated as the minimum list. Valid values are `inclusive`,
# `minimum`.
# UNSUPPORTED
#- **keys**
# Specify user attributes in an array of keyvalue pairs Requires features
# manages_solaris_rbac.
# UNSUPPORTED
#- **allowdupe**
# Whether to allow duplicate UIDs. Valid values are `true`, `false`.
# UNSUPPORTED
#- **auths**
# The auths the user has. Multiple auths should be
# specified as an array. Requires features manages_solaris_rbac.
# UNSUPPORTED
#- **auth_membership**
# Whether specified auths should be treated as the only auths
# of which the user is a member or whether they should merely
# be treated as the minimum membership list. Valid values are
# `inclusive`, `minimum`.
# UNSUPPORTED
end
Anons79 File Manager Version 1.0, Coded By Anons79
Email: [email protected]