~ubuntu-branches/ubuntu/lucid/puppet/lucid-security

« back to all changes in this revision

Viewing changes to lib/puppet/provider/nameservice/directoryservice.rb

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2009-12-23 00:48:10 UTC
  • mfrom: (1.1.10 upstream) (3.1.7 squeeze)
  • Revision ID: james.westby@ubuntu.com-20091223004810-3i4oryds922g5n59
Tags: 0.25.1-3ubuntu1
* Merge from debian testing.  Remaining changes:
  - debian/rules:
    + Don't start puppet when first installing puppet.
  - debian/puppet.conf, lib/puppet/defaults.rb:
    + Move templates to /etc/puppet
  - lib/puppet/defaults.rb:
    + Fix /var/lib/puppet/state ownership.
  - man/man8/puppet.conf.8: 
    + Fix broken URL in manpage.
  - debian/control:
    + Update maintainer accordint to spec.
    + Puppetmaster Recommends -> Suggests
    + Created puppet-testsuite as a seperate. Allow the users to run puppet's 
      testsuite.
  - tests/Rakefile: Fix rakefile so that the testsuite can acutally be ran.

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
        attr_writer :macosx_version_major
31
31
    end
32
32
 
33
 
    
34
 
    # JJM 2007-07-24: Not yet sure what initvars() does.  I saw it in netinfo.rb
35
 
    # I do know, however, that it makes methods "work"  =)
36
 
    # e.g. addcmd isn't available if this method call isn't present.
37
 
    #
38
 
    # JJM: Also, where this method is defined seems to impact the visibility
39
 
    #   of methods.  If I put initvars after commands, confine and defaultfor,
40
 
    #   then getinfo is called from the parent class, not this class.
41
33
    initvars()
42
 
    
 
34
 
43
35
    commands :dscl => "/usr/bin/dscl"
44
36
    commands :dseditgroup => "/usr/sbin/dseditgroup"
45
37
    commands :sw_vers => "/usr/bin/sw_vers"
80
72
        :ip_address => 'IPAddress',
81
73
        :members => 'GroupMembership',
82
74
    }
83
 
    
 
75
 
84
76
    @@password_hash_dir = "/var/db/shadow/hash"
85
 
    
 
77
 
86
78
    def self.instances
87
79
        # JJM Class method that provides an array of instance objects of this
88
80
        #     type.
89
81
        # JJM: Properties are dependent on the Puppet::Type we're managine.
90
82
        type_property_array = [:name] + @resource_type.validproperties
91
 
        
 
83
 
92
84
        # Create a new instance of this Puppet::Type for each object present
93
85
        #    on the system.
94
86
        list_all_present.collect do |name_string|
95
87
            self.new(single_report(name_string, *type_property_array))
96
88
        end
97
89
    end
98
 
    
 
90
 
99
91
    def self.get_ds_path
100
92
        # JJM: 2007-07-24 This method dynamically returns the DS path we're concerned with.
101
93
        #      For example, if we're working with an user type, this will be /Users
102
94
        #      with a group type, this will be /Groups.
103
 
        #   @ds_path is an attribute of the class itself.  
 
95
        #   @ds_path is an attribute of the class itself.
104
96
        if defined? @ds_path
105
97
            return @ds_path
106
98
        end
110
102
        #       Puppet::Type this class object is providing for.
111
103
        return @resource_type.name.to_s.capitalize + "s"
112
104
    end
113
 
    
 
105
 
114
106
    def self.get_macosx_version_major
115
107
        if defined? @macosx_version_major
116
108
            return @macosx_version_major
117
109
        end
118
110
        begin
119
 
            product_version = Facter.value(:macosx_productversion)
120
 
            if product_version.nil?
121
 
                raise Puppet::Error, "Could not determine OS X version"
 
111
            # Make sure we've loaded all of the facts
 
112
            Facter.loadfacts
 
113
 
 
114
            if Facter.value(:macosx_productversion_major)
 
115
                product_version_major = Facter.value(:macosx_productversion_major)
 
116
            else
 
117
                # TODO: remove this code chunk once we require Facter 1.5.5 or higher.
 
118
                Puppet.warning("DEPRECATION WARNING: Future versions of the directoryservice provider will require Facter 1.5.5 or newer.")            
 
119
                product_version = Facter.value(:macosx_productversion)
 
120
                if product_version.nil?
 
121
                    fail("Could not determine OS X version from Facter")
 
122
                end
 
123
                product_version_major = product_version.scan(/(\d+)\.(\d+)./).join(".")
122
124
            end
123
 
            product_version_major = product_version.scan(/(\d+)\.(\d+)./).join(".")
124
125
            if %w{10.0 10.1 10.2 10.3}.include?(product_version_major)
125
 
                raise Puppet::Error, "%s is not supported by the directoryservice provider" % product_version_major
 
126
                fail("%s is not supported by the directoryservice provider" % product_version_major)
126
127
            end
127
128
            @macosx_version_major = product_version_major
128
129
            return @macosx_version_major
129
130
        rescue Puppet::ExecutionFailure => detail
130
 
            raise Puppet::Error, "Could not determine OS X version: %s" % detail
 
131
            fail("Could not determine OS X version: %s" % detail)
131
132
        end
132
133
    end
133
134
 
 
135
 
134
136
    def self.list_all_present
135
137
        # JJM: List all objects of this Puppet::Type already present on the system.
136
138
        begin
137
139
            dscl_output = execute(get_exec_preamble("-list"))
138
140
        rescue Puppet::ExecutionFailure => detail
139
 
            raise Puppet::Error, "Could not get %s list from DirectoryService" % [ @resource_type.name.to_s ]
 
141
           fail("Could not get %s list from DirectoryService" % [ @resource_type.name.to_s ])
140
142
        end
141
143
        return dscl_output.split("\n")
142
144
    end
143
 
    
 
145
 
144
146
    def self.parse_dscl_url_data(dscl_output)
145
147
        # we need to construct a Hash from the dscl -url output to match
146
148
        # that returned by the dscl -plist output for 10.5+ clients.
150
152
        #   b) if a line ends in a colon and the next line does start with
151
153
        #      a space, then the second line is a value of the first.
152
154
        #   c) (implied by (b)) keys don't start with spaces.
153
 
        
 
155
 
154
156
        dscl_plist = {}
155
157
        dscl_output.split("\n").inject([]) do |array, line|
156
158
          if line =~ /^\s+/   # it's a value
180
182
        end
181
183
        return dscl_plist
182
184
    end
183
 
    
 
185
 
184
186
    def self.parse_dscl_plist_data(dscl_output)
185
187
        return Plist.parse_xml(dscl_output)
186
188
    end
187
 
    
 
189
 
188
190
    def self.generate_attribute_hash(input_hash, *type_properties)
189
191
        attribute_hash = {}
190
192
        input_hash.keys().each do |key|
192
194
            next unless (@@ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? @@ds_to_ns_attribute_map[ds_attribute])
193
195
            ds_value = input_hash[key]
194
196
            case @@ds_to_ns_attribute_map[ds_attribute]
195
 
                when :members: 
 
197
                when :members
196
198
                    ds_value = ds_value # only members uses arrays so far
197
 
                when :gid, :uid:
 
199
                when :gid, :uid
198
200
                    # OS X stores objects like uid/gid as strings.
199
201
                    # Try casting to an integer for these cases to be
200
202
                    # consistent with the other providers and the group type
208
210
            end
209
211
            attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value
210
212
        end
211
 
        
 
213
 
212
214
        # NBK: need to read the existing password here as it's not actually
213
215
        # stored in the user record. It is stored at a path that involves the
214
 
        # UUID of the user record for non-Mobile local acccounts.    
 
216
        # UUID of the user record for non-Mobile local acccounts.
215
217
        # Mobile Accounts are out of scope for this provider for now
216
218
        if @resource_type.validproperties.include?(:password)
217
219
            attribute_hash[:password] = self.get_password(attribute_hash[:guid])
218
220
        end
219
221
        return attribute_hash
220
222
    end
221
 
    
 
223
 
222
224
    def self.single_report(resource_name, *type_properties)
223
225
        # JJM 2007-07-24:
224
226
        #     Given a the name of an object and a list of properties of that
225
227
        #     object, return all property values in a hash.
226
 
        #     
 
228
        #
227
229
        #     This class method returns nil if the object doesn't exist
228
230
        #     Otherwise, it returns a hash of the object properties.
229
 
        
 
231
 
230
232
        all_present_str_array = list_all_present()
231
 
        
 
233
 
232
234
        # NBK: shortcut the process if the resource is missing
233
235
        return nil unless all_present_str_array.include? resource_name
234
 
        
 
236
 
235
237
        dscl_vector = get_exec_preamble("-read", resource_name)
236
238
        begin
237
239
            dscl_output = execute(dscl_vector)
238
240
        rescue Puppet::ExecutionFailure => detail
239
 
            raise Puppet::Error, "Could not get report.  command execution failed."
 
241
            fail("Could not get report.  command execution failed.")
240
242
        end
241
 
        
 
243
 
242
244
        # Two code paths is ugly, but until we can drop 10.4 support we don't
243
245
        # have a lot of choice. Ultimately this should all be done using Ruby
244
246
        # to access the DirectoryService APIs directly, but that's simply not
249
251
        when "10.5", "10.6"
250
252
            dscl_plist = self.parse_dscl_plist_data(dscl_output)
251
253
        end
252
 
        
 
254
 
253
255
        return self.generate_attribute_hash(dscl_plist, *type_properties)
254
256
    end
255
 
    
 
257
 
256
258
    def self.get_exec_preamble(ds_action, resource_name = nil)
257
259
        # JJM 2007-07-24
258
260
        #     DSCL commands are often repetitive and contain the same positional
285
287
        #       e.g. 'dscl / -create /Users/mccune'
286
288
        return command_vector
287
289
    end
288
 
    
 
290
 
289
291
    def self.set_password(resource_name, guid, password_hash)
290
292
        password_hash_file = "#{@@password_hash_dir}/#{guid}"
291
293
        begin
292
294
            File.open(password_hash_file, 'w') { |f| f.write(password_hash)}
293
295
        rescue Errno::EACCES => detail
294
 
            raise Puppet::Error, "Could not write to password hash file: #{detail}"
 
296
            fail("Could not write to password hash file: #{detail}")
295
297
        end
296
 
        
 
298
 
297
299
        # NBK: For shadow hashes, the user AuthenticationAuthority must contain a value of
298
300
        # ";ShadowHash;". The LKDC in 10.5 makes this more interesting though as it
299
301
        # will dynamically generate ;Kerberosv5;;username@LKDC:SHA1 attributes if
302
304
        # use other custom AuthenticationAuthority attributes without stomping on them.
303
305
        #
304
306
        # There is a potential problem here in that we're only doing this when setting
305
 
        # the password, and the attribute could get modified at other times while the 
 
307
        # the password, and the attribute could get modified at other times while the
306
308
        # hash doesn't change and so this doesn't get called at all... but
307
309
        # without switching all the other attributes to merge instead of create I can't
308
310
        # see a simple enough solution for this that doesn't modify the user record
309
311
        # every single time. This should be a rather rare edge case. (famous last words)
310
 
        
 
312
 
311
313
        dscl_vector = self.get_exec_preamble("-merge", resource_name)
312
314
        dscl_vector << "AuthenticationAuthority" << ";ShadowHash;"
313
315
        begin
314
316
            dscl_output = execute(dscl_vector)
315
317
        rescue Puppet::ExecutionFailure => detail
316
 
            raise Puppet::Error, "Could not set AuthenticationAuthority."
 
318
            fail("Could not set AuthenticationAuthority.")
317
319
        end
318
320
    end
319
 
    
 
321
 
320
322
    def self.get_password(guid)
321
323
        password_hash = nil
322
324
        password_hash_file = "#{@@password_hash_dir}/#{guid}"
323
325
        if File.exists?(password_hash_file) and File.file?(password_hash_file)
324
326
            if not File.readable?(password_hash_file)
325
 
                raise Puppet::Error("Could not read password hash file at #{password_hash_file} for #{@resource[:name]}")
 
327
                fail("Could not read password hash file at #{password_hash_file} for #{@resource[:name]}")
326
328
            end
327
329
            f = File.new(password_hash_file)
328
330
            password_hash = f.read
333
335
 
334
336
    def ensure=(ensure_value)
335
337
        super
336
 
        # JJM: Modeled after nameservice/netinfo.rb, we need to
337
 
        #   loop over all valid properties for the type we're managing
338
 
        #   and call the method which sets that property value
339
 
        #   Like netinfo, dscl can't create everything at once, afaik.
 
338
        # We need to loop over all valid properties for the type we're
 
339
        # managing and call the method which sets that property value
 
340
        # dscl can't create everything at once unfortunately.
340
341
        if ensure_value == :present
341
342
            @resource.class.validproperties.each do |name|
342
343
                next if name == :ensure
352
353
                    next
353
354
                end
354
355
            end
355
 
        end 
 
356
        end
356
357
    end
357
 
    
 
358
 
358
359
    def password=(passphrase)
359
360
      exec_arg_vector = self.class.get_exec_preamble("-read", @resource.name)
360
361
      exec_arg_vector << @@ns_to_ds_attribute_map[:guid]
367
368
          guid = guid_plist["dsAttrTypeStandard:#{@@ns_to_ds_attribute_map[:guid]}"][0]
368
369
          self.class.set_password(@resource.name, guid, passphrase)
369
370
      rescue Puppet::ExecutionFailure => detail
370
 
          raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
 
371
          fail("Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail])
371
372
      end
372
373
    end
373
 
    
 
374
 
374
375
    # NBK: we override @parent.set as we need to execute a series of commands
375
376
    # to deal with array values, rather than the single command nameservice.rb
376
377
    # expects to be returned by modifycmd. Thus we don't bother defining modifycmd.
377
 
    
 
378
 
378
379
    def set(param, value)
379
380
        self.class.validate(param, value)
380
381
        current_members = @property_value_cache_hash[:members]
385
386
            if @resource[:auth_membership] and not current_members.nil?
386
387
                remove_unwanted_members(current_members, value)
387
388
             end
388
 
             
 
389
 
389
390
             # if they're not a member, make them one.
390
391
             add_members(current_members, value)
391
392
        else
398
399
            begin
399
400
                execute(exec_arg_vector)
400
401
            rescue Puppet::ExecutionFailure => detail
401
 
                raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
 
402
                fail("Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail])
402
403
            end
403
404
        end
404
405
    end
405
 
    
 
406
 
406
407
    # NBK: we override @parent.create as we need to execute a series of commands
407
408
    # to create objects with dscl, rather than the single command nameservice.rb
408
409
    # expects to be returned by addcmd. Thus we don't bother defining addcmd.
411
412
            info "already exists"
412
413
            return nil
413
414
        end
414
 
        
 
415
 
415
416
        # NBK: First we create the object with a known guid so we can set the contents
416
417
        # of the password hash if required
417
418
        # Shelling out sucks, but for a single use case it doesn't seem worth
419
420
        # This should be revisited if Puppet starts managing UUIDs for other platform
420
421
        # user records.
421
422
        guid = %x{/usr/bin/uuidgen}.chomp
422
 
        
 
423
 
423
424
        exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
424
425
        exec_arg_vector << @@ns_to_ds_attribute_map[:guid] << guid
425
426
        begin
426
427
          execute(exec_arg_vector)
427
428
        rescue Puppet::ExecutionFailure => detail
428
 
            raise Puppet::Error, "Could not set GeneratedUID for %s %s: %s" %
429
 
                [@resource.class.name, @resource.name, detail]
 
429
            fail("Could not set GeneratedUID for %s %s: %s" %
 
430
                [@resource.class.name, @resource.name, detail])
430
431
        end
431
 
        
 
432
 
432
433
        if value = @resource.should(:password) and value != ""
433
434
          self.class.set_password(@resource[:name], guid, value)
434
435
        end
435
 
        
 
436
 
436
437
        # Now we create all the standard properties
437
438
        Puppet::Type.type(@resource.class.name).validproperties.each do |property|
438
439
            next if property == :ensure
447
448
                    begin
448
449
                      execute(exec_arg_vector)
449
450
                    rescue Puppet::ExecutionFailure => detail
450
 
                        raise Puppet::Error, "Could not create %s %s: %s" %
451
 
                            [@resource.class.name, @resource.name, detail]
 
451
                        fail("Could not create %s %s: %s" %
 
452
                            [@resource.class.name, @resource.name, detail])
452
453
                    end
453
454
                end
454
455
            end
455
456
        end
456
457
    end
457
 
    
 
458
 
458
459
    def remove_unwanted_members(current_members, new_members)
459
460
        current_members.each do |member|
460
461
            if not new_members.include?(member)
462
463
                begin
463
464
                     execute(cmd)
464
465
                rescue Puppet::ExecutionFailure => detail
465
 
                     raise Puppet::Error, "Could not remove %s from group: %s, %s" % [member, @resource.name, detail]
 
466
                     fail("Could not remove %s from group: %s, %s" % [member, @resource.name, detail])
466
467
                end
467
468
             end
468
469
         end
469
470
    end
470
 
    
 
471
 
471
472
    def add_members(current_members, new_members)
472
473
        new_members.each do |new_member|
473
474
           if current_members.nil? or not current_members.include?(new_member)
475
476
               begin
476
477
                    execute(cmd)
477
478
               rescue Puppet::ExecutionFailure => detail
478
 
                    raise Puppet::Error, "Could not add %s to group: %s, %s" % [new_member, @resource.name, detail]
 
479
                    fail("Could not add %s to group: %s, %s" % [new_member, @resource.name, detail])
479
480
               end
480
481
           end
481
482
        end
482
483
    end
483
 
    
 
484
 
484
485
    def deletecmd
485
486
        # JJM: Like addcmd, only called when deleting the object itself
486
487
        #    Note, this isn't used to delete properties of the object,
487
488
        #    at least that's how I understand it...
488
489
        self.class.get_exec_preamble("-delete", @resource[:name])
489
490
    end
490
 
    
 
491
 
491
492
    def getinfo(refresh = false)
492
 
        # JJM 2007-07-24: 
 
493
        # JJM 2007-07-24:
493
494
        #      Override the getinfo method, which is also defined in nameservice.rb
494
 
        #      This method returns and sets @infohash, which looks like:
495
 
        #      (NetInfo provider, user type...)
496
 
        #       @infohash = {:comment=>"Jeff McCune", :home=>"/Users/mccune", 
497
 
        #       :shell=>"/bin/zsh", :password=>"********", :uid=>502, :gid=>502,
498
 
        #       :name=>"mccune"}
499
 
        #
 
495
        #      This method returns and sets @infohash
500
496
        # I'm not re-factoring the name "getinfo" because this method will be
501
497
        # most likely called by nameservice.rb, which I didn't write.
502
498
        if refresh or (! defined?(@property_value_cache_hash) or ! @property_value_cache_hash)
503
499
            # JJM 2007-07-24: OK, there's a bit of magic that's about to
504
500
            # happen... Let's see how strong my grip has become... =)
505
 
            # 
 
501
            #
506
502
            # self is a provider instance of some Puppet::Type, like
507
503
            # Puppet::Type::User::ProviderDirectoryservice for the case of the
508
504
            # user type and this provider.
509
 
            # 
 
505
            #
510
506
            # self.class looks like "user provider directoryservice", if that
511
507
            # helps you ...
512
 
            # 
 
508
            #
513
509
            # self.class.resource_type is a reference to the Puppet::Type class,
514
510
            # probably Puppet::Type::User or Puppet::Type::Group, etc...
515
 
            # 
 
511
            #
516
512
            # self.class.resource_type.validproperties is a class method,
517
513
            # returning an Array of the valid properties of that specific
518
514
            # Puppet::Type.
519
 
            # 
 
515
            #
520
516
            # So... something like [:comment, :home, :password, :shell, :uid,
521
517
            # :groups, :ensure, :gid]
522
 
            # 
 
518
            #
523
519
            # Ultimately, we add :name to the list, delete :ensure from the
524
520
            # list, then report on the remaining list. Pretty whacky, ehh?
525
521
            type_properties = [:name] + self.class.resource_type.validproperties
533
529
        return @property_value_cache_hash
534
530
    end
535
531
end
536
 
end
 
 
b'\\ No newline at end of file'
 
532
end