summaryrefslogtreecommitdiffstats
path: root/lib/puppet/provider/user/aix.rb
blob: 032d2b536935a6d5c01190e0e2ac1e80f006ccfc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#
# 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  http://projects.puppetlabs.com/projects/puppet/wiki/Development_Provider_Development
# for more information
#
# Author::    Hector Rivas Gandara <keymon@gmail.com>
#
require 'puppet/provider/aixobject'
require 'tempfile'
require 'date'

Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do
  desc "User management for AIX! Users are managed with mkuser, rmuser, chuser, lsuser"

  # This will the the default provider for this platform
  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 :lsgroup   => "/usr/sbin/lsgroup"
  commands :chpasswd  => "/bin/chpasswd"

  # Provider features
  has_features :manages_aix_lam
  has_features :manages_homedir, :manages_passwords
  has_features :manages_expiry,  :manages_password_age

  # Attribute verification (TODO)
  #verify :gid, "GID must be an string or int of a valid group" do |value|
  #  value.is_a? String || value.is_a? Integer
  #end
  #
  #verify :groups, "Groups must be comma-separated" do |value|
  #  value !~ /\s/
  #end

  # User attributes to ignore from AIX output.
  def self.attribute_ignore
    []
  end

  # AIX attributes to properties mapping.
  # 
  # Valid attributes to be managed by this provider.
  # It is a list with of hash
  #  :aix_attr      AIX command attribute name
  #  :puppet_prop   Puppet propertie name
  #  :to            Method to adapt puppet property to aix command value. Optional.
  #  :from          Method to adapt aix command value to puppet property. Optional
  self.attribute_mapping = [
    #:name => :name,
    {:aix_attr => :pgrp,       :puppet_prop => :gid,
        :to => :gid_to_attr, :from => :gid_from_attr},
    {:aix_attr => :id,         :puppet_prop => :uid},
    {:aix_attr => :groups,     :puppet_prop => :groups},
    {:aix_attr => :home,       :puppet_prop => :home},
    {:aix_attr => :shell,      :puppet_prop => :shell},
    {:aix_attr => :expires,    :puppet_prop => :expiry,
        :to => :expiry_to_attr, :from => :expiry_from_attr},
    {:aix_attr => :maxage,     :puppet_prop => :password_max_age},
    {:aix_attr => :minage,     :puppet_prop => :password_min_age},
    {:aix_attr => :attributes, :puppet_prop => :attributes},
  ]
  
  #--------------
  # Command definition
  
  # Return the IA module arguments based on the resource param ia_load_module
  def get_ia_module_args
    if @resource[:ia_load_module]
      ["-R", @resource[:ia_load_module].to_s]
    else
      []
    end
  end
  
  # List groups and Ids
  def lsgroupscmd(value=@resource[:name])
    [command(:lsgroup)] +
      self.get_ia_module_args +
      ["-a", "id", value]
  end

  def lscmd(value=@resource[:name])
    [self.class.command(:list)] + self.get_ia_module_args + [ value]
  end

  def lsallcmd()
    lscmd("ALL")
  end

  def addcmd(extra_attrs = [])
    # Here we use the @resource.to_hash to get the list of provided parameters
    # Puppet does not call to self.<parameter>= method if it does not exists.
    #
    # It gets an extra list of arguments to add to the user.
    [self.class.command(:add)] + self.get_ia_module_args +
      self.hash2args(@resource.to_hash) +
      extra_attrs + [@resource[:name]]
  end

  # Get modify command. Set translate=false if no mapping must be used.
  # Needed for special properties like "attributes"
  def modifycmd(hash = property_hash)
    args = self.hash2args(hash)
    return nil if args.empty?
    
    [self.class.command(:modify)] + self.get_ia_module_args +
      args + [@resource[:name]]
  end

  def deletecmd
    [self.class.command(:delete)] + self.get_ia_module_args + [@resource[:name]]
  end

  #--------------
  # We overwrite the create function to change the password after creation.
  def create
    super
    # Reset the password if needed
    self.password = @resource[:password] if @resource[:password]
  end 

  
  def get_arguments(key, value, mapping, objectinfo)
    # In the case of attributes, return a list of key=vlaue
    if key == :attributes
      raise Puppet::Error, "Attributes must be a list of pairs key=value on #{@resource.class.name}[#{@resource.name}]" \
        unless value and value.is_a? Hash
      return value.select { |k,v| true }.map { |pair| pair.join("=") }
    end
        
    super(key, value, mapping, objectinfo)
  end
  
  # Get the groupname from its id
  def self.groupname_by_id(gid)
    groupname=nil
    execute(lsgroupscmd("ALL")).each { |entry|
      attrs = self.parse_attr_list(entry, nil)
      if attrs and attrs.include? :id and gid == attrs[:id].to_i
        groupname = entry.split(" ")[0]
      end
    }
    groupname
  end

  # Get the groupname from its id
  def groupid_by_name(groupname)
    attrs = self.parse_attr_list(execute(lsgroupscmd(groupname)).split("\n")[0], nil)
    attrs ? attrs[:id].to_i : nil
  end

  # Check that a group exists and is valid
  def verify_group(value)
    if value.is_a? Integer or value.is_a? Fixnum  
      groupname = self.groupname_by_id(value)
      raise ArgumentError, "AIX group must be a valid existing group" unless groupname
    else 
      raise ArgumentError, "AIX group must be a valid existing group" unless groupid_by_name(value)
      groupname = value
    end
    groupname
  end
  
  # The user's primary group.  Can be specified numerically or by name.
  def gid_to_attr(value)
    verify_group(value)
  end

  # Get the group gid from its name
  def gid_from_attr(value)
    groupid_by_name(value)
  end

  # The expiry date for this user. Must be provided in
  # a zero padded YYYY-MM-DD HH:MM format 
  def expiry_to_attr(value)
    # For chuser the expires parameter is a 10-character string in the MMDDhhmmyy format
    # that is,"%m%d%H%M%y"
    newdate = '0'
    if value.is_a? String and value!="0000-00-00"
      d = DateTime.parse(value, "%Y-%m-%d %H:%M")
      newdate = d.strftime("%m%d%H%M%y")
    end
    newdate
  end
  
  def expiry_from_attr(value)
    if value =~ /(..)(..)(..)(..)(..)/
      #d= DateTime.parse("20#{$5}-#{$1}-#{$2} #{$3}:#{$4}")
      #expiry_date = d.strftime("%Y-%m-%d %H:%M")
      #expiry_date = d.strftime("%Y-%m-%d")
      expiry_date = "20#{$5}-#{$1}-#{$2}"
    else
      Puppet.warn("Could not convert AIX expires date '#{value}' on #{@resource.class.name}[#{@resource.name}]") \
        unless value == '0'
      expiry_date = :absent
    end
    expiry_date
  end

  #--------------------------------
  # Getter and Setter
  # When the provider is initialized, create getter/setter methods for each
  # property our resource type supports.
  # If setter or getter already defined it will not be overwritten

  #- **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 directly the /etc/security/passwd
  def password
    password = :absent
    user = @resource[:name]
    f = File.open("/etc/security/passwd", 'r')
    # Skip to the user
    f.each { |l| break if l  =~ /^#{user}:\s*$/ }
    if ! f.eof?
      f.each { |l|
        # If there is a new user stanza, stop
        break if l  =~ /^\S*:\s*$/ 
        # If the password= entry is found, return it
        if l  =~ /^\s*password\s*=\s*(.*)$/
          password = $1; break;
        end
      }
    end
    f.close()
    return password
  end 

  def password=(value)
    user = @resource[:name]
    
    # Puppet execute does not support strings as input, only files.
    tmpfile = Tempfile.new('puppet_#{user}_pw')
    tmpfile << "#{user}:#{value}\n"
    tmpfile.close()

    # Options '-e', '-c', use encrypted password and clear flags
    # Must receibe "user:enc_password" as input
    # command, arguments = {:failonfail => true, :combine => true}
    cmd = [self.class.command(:chpasswd),"-R", self.class.ia_module,
           '-e', '-c', user]
    begin
      execute(cmd, {:failonfail => true, :combine => true, :stdinfile => tmpfile.path })
    rescue Puppet::ExecutionFailure  => detail
      raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}"
    ensure
      tmpfile.delete()
    end
  end 

  def filter_attributes(hash)
    # Return only not managed attributtes.
    hash.select {
        |k,v| !self.class.attribute_mapping_from.include?(k) and
                !self.class.attribute_ignore.include?(k)
      }.inject({}) {
        |hash, array| hash[array[0]] = array[1]; hash
      }
  end

  def attributes
    filter_attributes(getosinfo(refresh = false))
  end

  def attributes=(attr_hash)
    #self.class.validate(param, value)
    param = :attributes
    cmd = modifycmd({param => filter_attributes(attr_hash)})
    if cmd 
      begin
        execute(cmd)
      rescue Puppet::ExecutionFailure  => detail
        raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}"
      end
    end
  end

  #- **comment**
  #    A description of the user.  Generally is a user's full name.
  #def comment=(value)
  #end
  #
  #def comment
  #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_solaris_rbac.
  # 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