~ubuntu-branches/ubuntu/trusty/puppet/trusty

« back to all changes in this revision

Viewing changes to .pc/CVE-2011-3872.patch/lib/puppet/util/settings.rb

  • Committer: Package Import Robot
  • Author(s): Stig Sandbeck Mathisen
  • Date: 2011-10-22 14:08:22 UTC
  • mfrom: (1.1.25) (3.1.32 sid)
  • Revision ID: package-import@ubuntu.com-20111022140822-odxde5lohc45yhuz
Tags: 2.7.6-1
* New upstream release (CVE-2011-3872)
* Remove cherry-picked "groupadd_aix_warning" patch
* Install all new manpages

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
require 'puppet'
2
 
require 'sync'
3
 
require 'getoptlong'
4
 
require 'puppet/external/event-loop'
5
 
require 'puppet/util/cacher'
6
 
require 'puppet/util/loadedfile'
7
 
 
8
 
# The class for handling configuration files.
9
 
class Puppet::Util::Settings
10
 
  include Enumerable
11
 
  include Puppet::Util::Cacher
12
 
 
13
 
  require 'puppet/util/settings/setting'
14
 
  require 'puppet/util/settings/file_setting'
15
 
  require 'puppet/util/settings/boolean_setting'
16
 
 
17
 
  attr_accessor :file
18
 
  attr_reader :timer
19
 
 
20
 
  ReadOnly = [:run_mode, :name]
21
 
 
22
 
  # Retrieve a config value
23
 
  def [](param)
24
 
    value(param)
25
 
  end
26
 
 
27
 
  # Set a config value.  This doesn't set the defaults, it sets the value itself.
28
 
  def []=(param, value)
29
 
    set_value(param, value, :memory)
30
 
  end
31
 
 
32
 
  # Generate the list of valid arguments, in a format that GetoptLong can
33
 
  # understand, and add them to the passed option list.
34
 
  def addargs(options)
35
 
    # Add all of the config parameters as valid options.
36
 
    self.each { |name, setting|
37
 
      setting.getopt_args.each { |args| options << args }
38
 
    }
39
 
 
40
 
    options
41
 
  end
42
 
 
43
 
  # Generate the list of valid arguments, in a format that OptionParser can
44
 
  # understand, and add them to the passed option list.
45
 
  def optparse_addargs(options)
46
 
    # Add all of the config parameters as valid options.
47
 
    self.each { |name, setting|
48
 
      options << setting.optparse_args
49
 
    }
50
 
 
51
 
    options
52
 
  end
53
 
 
54
 
  # Is our parameter a boolean parameter?
55
 
  def boolean?(param)
56
 
    param = param.to_sym
57
 
    !!(@config.include?(param) and @config[param].kind_of? BooleanSetting)
58
 
  end
59
 
 
60
 
  # Remove all set values, potentially skipping cli values.
61
 
  def clear(exceptcli = false)
62
 
    @sync.synchronize do
63
 
      unsafe_clear(exceptcli)
64
 
    end
65
 
  end
66
 
 
67
 
  # Remove all set values, potentially skipping cli values.
68
 
  def unsafe_clear(exceptcli = false)
69
 
    @values.each do |name, values|
70
 
      @values.delete(name) unless exceptcli and name == :cli
71
 
    end
72
 
 
73
 
    # Don't clear the 'used' in this case, since it's a config file reparse,
74
 
    # and we want to retain this info.
75
 
    @used = [] unless exceptcli
76
 
 
77
 
    @cache.clear
78
 
  end
79
 
 
80
 
  # This is mostly just used for testing.
81
 
  def clearused
82
 
    @cache.clear
83
 
    @used = []
84
 
  end
85
 
 
86
 
  # Do variable interpolation on the value.
87
 
  def convert(value, environment = nil)
88
 
    return value unless value
89
 
    return value unless value.is_a? String
90
 
    newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value|
91
 
      varname = $2 || $1
92
 
      if varname == "environment" and environment
93
 
        environment
94
 
      elsif pval = self.value(varname, environment)
95
 
        pval
96
 
      else
97
 
        raise Puppet::DevError, "Could not find value for #{value}"
98
 
      end
99
 
    end
100
 
 
101
 
    newval
102
 
  end
103
 
 
104
 
  # Return a value's description.
105
 
  def description(name)
106
 
    if obj = @config[name.to_sym]
107
 
      obj.desc
108
 
    else
109
 
      nil
110
 
    end
111
 
  end
112
 
 
113
 
  def each
114
 
    @config.each { |name, object|
115
 
      yield name, object
116
 
    }
117
 
  end
118
 
 
119
 
  # Iterate over each section name.
120
 
  def eachsection
121
 
    yielded = []
122
 
    @config.each do |name, object|
123
 
      section = object.section
124
 
      unless yielded.include? section
125
 
        yield section
126
 
        yielded << section
127
 
      end
128
 
    end
129
 
  end
130
 
 
131
 
  # Return an object by name.
132
 
  def setting(param)
133
 
    param = param.to_sym
134
 
    @config[param]
135
 
  end
136
 
 
137
 
  # Handle a command-line argument.
138
 
  def handlearg(opt, value = nil)
139
 
    @cache.clear
140
 
    value &&= munge_value(value)
141
 
    str = opt.sub(/^--/,'')
142
 
 
143
 
    bool = true
144
 
    newstr = str.sub(/^no-/, '')
145
 
    if newstr != str
146
 
      str = newstr
147
 
      bool = false
148
 
    end
149
 
    str = str.intern
150
 
 
151
 
    if @config[str].is_a?(Puppet::Util::Settings::BooleanSetting)
152
 
      if value == "" or value.nil?
153
 
        value = bool
154
 
      end
155
 
    end
156
 
 
157
 
    set_value(str, value, :cli)
158
 
  end
159
 
 
160
 
  def include?(name)
161
 
    name = name.intern if name.is_a? String
162
 
    @config.include?(name)
163
 
  end
164
 
 
165
 
  # check to see if a short name is already defined
166
 
  def shortinclude?(short)
167
 
    short = short.intern if name.is_a? String
168
 
    @shortnames.include?(short)
169
 
  end
170
 
 
171
 
  # Create a new collection of config settings.
172
 
  def initialize
173
 
    @config = {}
174
 
    @shortnames = {}
175
 
 
176
 
    @created = []
177
 
    @searchpath = nil
178
 
 
179
 
    # Mutex-like thing to protect @values
180
 
    @sync = Sync.new
181
 
 
182
 
    # Keep track of set values.
183
 
    @values = Hash.new { |hash, key| hash[key] = {} }
184
 
 
185
 
    # And keep a per-environment cache
186
 
    @cache = Hash.new { |hash, key| hash[key] = {} }
187
 
 
188
 
    # The list of sections we've used.
189
 
    @used = []
190
 
  end
191
 
 
192
 
  # NOTE: ACS ahh the util classes. . .sigh
193
 
  # as part of a fix for 1183, I pulled the logic for the following 5 methods out of the executables and puppet.rb
194
 
  # They probably deserve their own class, but I don't want to do that until I can refactor environments
195
 
  # its a little better than where they were
196
 
 
197
 
  # Prints the contents of a config file with the available config settings, or it
198
 
  # prints a single value of a config setting.
199
 
  def print_config_options
200
 
    env = value(:environment)
201
 
    val = value(:configprint)
202
 
    if val == "all"
203
 
      hash = {}
204
 
      each do |name, obj|
205
 
        val = value(name,env)
206
 
        val = val.inspect if val == ""
207
 
        hash[name] = val
208
 
      end
209
 
      hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val|
210
 
        puts "#{name} = #{val}"
211
 
      end
212
 
    else
213
 
      val.split(/\s*,\s*/).sort.each do |v|
214
 
        if include?(v)
215
 
          #if there is only one value, just print it for back compatibility
216
 
          if v == val
217
 
            puts value(val,env)
218
 
            break
219
 
          end
220
 
          puts "#{v} = #{value(v,env)}"
221
 
        else
222
 
          puts "invalid parameter: #{v}"
223
 
          return false
224
 
        end
225
 
      end
226
 
    end
227
 
    true
228
 
  end
229
 
 
230
 
  def generate_config
231
 
    puts to_config
232
 
    true
233
 
  end
234
 
 
235
 
  def generate_manifest
236
 
    puts to_manifest
237
 
    true
238
 
  end
239
 
 
240
 
  def print_configs
241
 
    return print_config_options if value(:configprint) != ""
242
 
    return generate_config if value(:genconfig)
243
 
    generate_manifest if value(:genmanifest)
244
 
  end
245
 
 
246
 
  def print_configs?
247
 
    (value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true
248
 
  end
249
 
 
250
 
  # Return a given object's file metadata.
251
 
  def metadata(param)
252
 
    if obj = @config[param.to_sym] and obj.is_a?(FileSetting)
253
 
      return [:owner, :group, :mode].inject({}) do |meta, p|
254
 
        if v = obj.send(p)
255
 
          meta[p] = v
256
 
        end
257
 
        meta
258
 
      end
259
 
    else
260
 
      nil
261
 
    end
262
 
  end
263
 
 
264
 
  # Make a directory with the appropriate user, group, and mode
265
 
  def mkdir(default)
266
 
    obj = get_config_file_default(default)
267
 
 
268
 
    Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do
269
 
      mode = obj.mode || 0750
270
 
      Dir.mkdir(obj.value, mode)
271
 
    end
272
 
  end
273
 
 
274
 
  # Figure out the section name for the run_mode.
275
 
  def run_mode
276
 
    Puppet.run_mode.name
277
 
  end
278
 
 
279
 
  # Return all of the parameters associated with a given section.
280
 
  def params(section = nil)
281
 
    if section
282
 
      section = section.intern if section.is_a? String
283
 
      @config.find_all { |name, obj|
284
 
        obj.section == section
285
 
      }.collect { |name, obj|
286
 
        name
287
 
      }
288
 
    else
289
 
      @config.keys
290
 
    end
291
 
  end
292
 
 
293
 
  # Parse the configuration file.  Just provides
294
 
  # thread safety.
295
 
  def parse
296
 
    raise "No :config setting defined; cannot parse unknown config file" unless self[:config]
297
 
 
298
 
    @sync.synchronize do
299
 
      unsafe_parse(self[:config])
300
 
    end
301
 
 
302
 
    # Create a timer so that this file will get checked automatically
303
 
    # and reparsed if necessary.
304
 
    set_filetimeout_timer
305
 
  end
306
 
 
307
 
  # Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly.
308
 
  def unsafe_parse(file)
309
 
    return unless FileTest.exist?(file)
310
 
    begin
311
 
      data = parse_file(file)
312
 
    rescue => details
313
 
      puts details.backtrace if Puppet[:trace]
314
 
      Puppet.err "Could not parse #{file}: #{details}"
315
 
      return
316
 
    end
317
 
 
318
 
    unsafe_clear(true)
319
 
 
320
 
    metas = {}
321
 
    data.each do |area, values|
322
 
      metas[area] = values.delete(:_meta)
323
 
      values.each do |key,value|
324
 
        set_value(key, value, area, :dont_trigger_handles => true, :ignore_bad_settings => true )
325
 
      end
326
 
    end
327
 
 
328
 
    # Determine our environment, if we have one.
329
 
    if @config[:environment]
330
 
      env = self.value(:environment).to_sym
331
 
    else
332
 
      env = "none"
333
 
    end
334
 
 
335
 
    # Call any hooks we should be calling.
336
 
    settings_with_hooks.each do |setting|
337
 
      each_source(env) do |source|
338
 
        if value = @values[source][setting.name]
339
 
          # We still have to use value to retrieve the value, since
340
 
          # we want the fully interpolated value, not $vardir/lib or whatever.
341
 
          # This results in extra work, but so few of the settings
342
 
          # will have associated hooks that it ends up being less work this
343
 
          # way overall.
344
 
          setting.handle(self.value(setting.name, env))
345
 
          break
346
 
        end
347
 
      end
348
 
    end
349
 
 
350
 
    # We have to do it in the reverse of the search path,
351
 
    # because multiple sections could set the same value
352
 
    # and I'm too lazy to only set the metadata once.
353
 
    searchpath.reverse.each do |source|
354
 
      source = run_mode if source == :run_mode
355
 
      source = @name if (@name && source == :name)
356
 
      if meta = metas[source]
357
 
        set_metadata(meta)
358
 
      end
359
 
    end
360
 
  end
361
 
 
362
 
  # Create a new setting.  The value is passed in because it's used to determine
363
 
  # what kind of setting we're creating, but the value itself might be either
364
 
  # a default or a value, so we can't actually assign it.
365
 
  def newsetting(hash)
366
 
    klass = nil
367
 
    hash[:section] = hash[:section].to_sym if hash[:section]
368
 
    if type = hash[:type]
369
 
      unless klass = {:setting => Setting, :file => FileSetting, :boolean => BooleanSetting}[type]
370
 
        raise ArgumentError, "Invalid setting type '#{type}'"
371
 
      end
372
 
      hash.delete(:type)
373
 
    else
374
 
      case hash[:default]
375
 
      when true, false, "true", "false"
376
 
        klass = BooleanSetting
377
 
      when /^\$\w+\//, /^\//, /^\w:\//
378
 
        klass = FileSetting
379
 
      when String, Integer, Float # nothing
380
 
        klass = Setting
381
 
      else
382
 
        raise ArgumentError, "Invalid value '#{hash[:default].inspect}' for #{hash[:name]}"
383
 
      end
384
 
    end
385
 
    hash[:settings] = self
386
 
    setting = klass.new(hash)
387
 
 
388
 
    setting
389
 
  end
390
 
 
391
 
  # This has to be private, because it doesn't add the settings to @config
392
 
  private :newsetting
393
 
 
394
 
  # Iterate across all of the objects in a given section.
395
 
  def persection(section)
396
 
    section = section.to_sym
397
 
    self.each { |name, obj|
398
 
      if obj.section == section
399
 
        yield obj
400
 
      end
401
 
    }
402
 
  end
403
 
 
404
 
  # Cache this in an easily clearable way, since we were
405
 
  # having trouble cleaning it up after tests.
406
 
  cached_attr(:file) do
407
 
    if path = self[:config] and FileTest.exist?(path)
408
 
      Puppet::Util::LoadedFile.new(path)
409
 
    end
410
 
  end
411
 
 
412
 
  # Reparse our config file, if necessary.
413
 
  def reparse
414
 
    if file and file.changed?
415
 
      Puppet.notice "Reparsing #{file.file}"
416
 
      parse
417
 
      reuse
418
 
    end
419
 
  end
420
 
 
421
 
  def reuse
422
 
    return unless defined?(@used)
423
 
    @sync.synchronize do # yay, thread-safe
424
 
      new = @used
425
 
      @used = []
426
 
      self.use(*new)
427
 
    end
428
 
  end
429
 
 
430
 
  # The order in which to search for values.
431
 
  def searchpath(environment = nil)
432
 
    if environment
433
 
      [:cli, :memory, environment, :run_mode, :main, :mutable_defaults]
434
 
    else
435
 
      [:cli, :memory, :run_mode, :main, :mutable_defaults]
436
 
    end
437
 
  end
438
 
 
439
 
  # Get a list of objects per section
440
 
  def sectionlist
441
 
    sectionlist = []
442
 
    self.each { |name, obj|
443
 
      section = obj.section || "puppet"
444
 
      sections[section] ||= []
445
 
      sectionlist << section unless sectionlist.include?(section)
446
 
      sections[section] << obj
447
 
    }
448
 
 
449
 
    return sectionlist, sections
450
 
  end
451
 
 
452
 
  def service_user_available?
453
 
    return @service_user_available if defined?(@service_user_available)
454
 
 
455
 
    return @service_user_available = false unless user_name = self[:user]
456
 
 
457
 
    user = Puppet::Type.type(:user).new :name => self[:user], :audit => :ensure
458
 
 
459
 
    @service_user_available = user.exists?
460
 
  end
461
 
 
462
 
  def legacy_to_mode(type, param)
463
 
    if not defined?(@app_names)
464
 
      require 'puppet/util/command_line'
465
 
      command_line = Puppet::Util::CommandLine.new
466
 
      @app_names = Puppet::Util::CommandLine::LegacyName.inject({}) do |hash, pair|
467
 
        app, legacy = pair
468
 
        command_line.require_application app
469
 
        hash[legacy.to_sym] = Puppet::Application.find(app).run_mode.name
470
 
        hash
471
 
      end
472
 
    end
473
 
    if new_type = @app_names[type]
474
 
      Puppet.warning "You have configuration parameter $#{param} specified in [#{type}], which is a deprecated section. I'm assuming you meant [#{new_type}]"
475
 
      return new_type
476
 
    end
477
 
    type
478
 
  end
479
 
 
480
 
  def set_value(param, value, type, options = {})
481
 
    param = param.to_sym
482
 
    unless setting = @config[param]
483
 
      if options[:ignore_bad_settings]
484
 
        return
485
 
      else
486
 
        raise ArgumentError,
487
 
          "Attempt to assign a value to unknown configuration parameter #{param.inspect}"
488
 
      end
489
 
    end
490
 
    value = setting.munge(value) if setting.respond_to?(:munge)
491
 
    setting.handle(value) if setting.respond_to?(:handle) and not options[:dont_trigger_handles]
492
 
    if ReadOnly.include? param and type != :mutable_defaults
493
 
      raise ArgumentError,
494
 
        "You're attempting to set configuration parameter $#{param}, which is read-only."
495
 
    end
496
 
    type = legacy_to_mode(type, param)
497
 
    @sync.synchronize do # yay, thread-safe
498
 
      @values[type][param] = value
499
 
      @cache.clear
500
 
 
501
 
      clearused
502
 
 
503
 
      # Clear the list of environments, because they cache, at least, the module path.
504
 
      # We *could* preferentially just clear them if the modulepath is changed,
505
 
      # but we don't really know if, say, the vardir is changed and the modulepath
506
 
      # is defined relative to it. We need the defined?(stuff) because of loading
507
 
      # order issues.
508
 
      Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment)
509
 
    end
510
 
 
511
 
    value
512
 
  end
513
 
 
514
 
  # Set a bunch of defaults in a given section.  The sections are actually pretty
515
 
  # pointless, but they help break things up a bit, anyway.
516
 
  def setdefaults(section, defs)
517
 
    section = section.to_sym
518
 
    call = []
519
 
    defs.each { |name, hash|
520
 
      if hash.is_a? Array
521
 
        unless hash.length == 2
522
 
          raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription"
523
 
        end
524
 
        tmp = hash
525
 
        hash = {}
526
 
        [:default, :desc].zip(tmp).each { |p,v| hash[p] = v }
527
 
      end
528
 
      name = name.to_sym
529
 
      hash[:name] = name
530
 
      hash[:section] = section
531
 
      raise ArgumentError, "Parameter #{name} is already defined" if @config.include?(name)
532
 
      tryconfig = newsetting(hash)
533
 
      if short = tryconfig.short
534
 
        if other = @shortnames[short]
535
 
          raise ArgumentError, "Parameter #{other.name} is already using short name '#{short}'"
536
 
        end
537
 
        @shortnames[short] = tryconfig
538
 
      end
539
 
      @config[name] = tryconfig
540
 
 
541
 
      # Collect the settings that need to have their hooks called immediately.
542
 
      # We have to collect them so that we can be sure we're fully initialized before
543
 
      # the hook is called.
544
 
      call << tryconfig if tryconfig.call_on_define
545
 
    }
546
 
 
547
 
    call.each { |setting| setting.handle(self.value(setting.name)) }
548
 
  end
549
 
 
550
 
  # Create a timer to check whether the file should be reparsed.
551
 
  def set_filetimeout_timer
552
 
    return unless timeout = self[:filetimeout] and timeout = Integer(timeout) and timeout > 0
553
 
    timer = EventLoop::Timer.new(:interval => timeout, :tolerance => 1, :start? => true) { self.reparse }
554
 
  end
555
 
 
556
 
  # Convert the settings we manage into a catalog full of resources that model those settings.
557
 
  def to_catalog(*sections)
558
 
    sections = nil if sections.empty?
559
 
 
560
 
    catalog = Puppet::Resource::Catalog.new("Settings")
561
 
 
562
 
    @config.values.find_all { |value| value.is_a?(FileSetting) }.each do |file|
563
 
      next unless (sections.nil? or sections.include?(file.section))
564
 
      next unless resource = file.to_resource
565
 
      next if catalog.resource(resource.ref)
566
 
 
567
 
      catalog.add_resource(resource)
568
 
    end
569
 
 
570
 
    add_user_resources(catalog, sections)
571
 
 
572
 
    catalog
573
 
  end
574
 
 
575
 
  # Convert our list of config settings into a configuration file.
576
 
  def to_config
577
 
    str = %{The configuration file for #{Puppet[:name]}.  Note that this file
578
 
is likely to have unused configuration parameters in it; any parameter that's
579
 
valid anywhere in Puppet can be in any config file, even if it's not used.
580
 
 
581
 
Every section can specify three special parameters: owner, group, and mode.
582
 
These parameters affect the required permissions of any files specified after
583
 
their specification.  Puppet will sometimes use these parameters to check its
584
 
own configured state, so they can be used to make Puppet a bit more self-managing.
585
 
 
586
 
Generated on #{Time.now}.
587
 
 
588
 
}.gsub(/^/, "# ")
589
 
 
590
 
#         Add a section heading that matches our name.
591
 
if @config.include?(:run_mode)
592
 
  str += "[#{self[:run_mode]}]\n"
593
 
    end
594
 
    eachsection do |section|
595
 
      persection(section) do |obj|
596
 
        str += obj.to_config + "\n" unless ReadOnly.include? obj.name or obj.name == :genconfig
597
 
      end
598
 
    end
599
 
 
600
 
    return str
601
 
  end
602
 
 
603
 
  # Convert to a parseable manifest
604
 
  def to_manifest
605
 
    catalog = to_catalog
606
 
    catalog.resource_refs.collect do |ref|
607
 
      catalog.resource(ref).to_manifest
608
 
    end.join("\n\n")
609
 
  end
610
 
 
611
 
  # Create the necessary objects to use a section.  This is idempotent;
612
 
  # you can 'use' a section as many times as you want.
613
 
  def use(*sections)
614
 
    sections = sections.collect { |s| s.to_sym }
615
 
    @sync.synchronize do # yay, thread-safe
616
 
      sections = sections.reject { |s| @used.include?(s) }
617
 
 
618
 
      return if sections.empty?
619
 
 
620
 
      begin
621
 
        catalog = to_catalog(*sections).to_ral
622
 
      rescue => detail
623
 
        puts detail.backtrace if Puppet[:trace]
624
 
        Puppet.err "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}"
625
 
 
626
 
        # We need some way to get rid of any resources created during the catalog creation
627
 
        # but not cleaned up.
628
 
        return
629
 
      end
630
 
 
631
 
      catalog.host_config = false
632
 
      catalog.apply do |transaction|
633
 
        if transaction.any_failed?
634
 
          report = transaction.report
635
 
          failures = report.logs.find_all { |log| log.level == :err }
636
 
          raise "Got #{failures.length} failure(s) while initializing: #{failures.collect { |l| l.to_s }.join("; ")}"
637
 
        end
638
 
      end
639
 
 
640
 
      sections.each { |s| @used << s }
641
 
      @used.uniq!
642
 
    end
643
 
  end
644
 
 
645
 
  def valid?(param)
646
 
    param = param.to_sym
647
 
    @config.has_key?(param)
648
 
  end
649
 
 
650
 
  def uninterpolated_value(param, environment = nil)
651
 
    param = param.to_sym
652
 
    environment &&= environment.to_sym
653
 
 
654
 
    # See if we can find it within our searchable list of values
655
 
    val = catch :foundval do
656
 
      each_source(environment) do |source|
657
 
        # Look for the value.  We have to test the hash for whether
658
 
        # it exists, because the value might be false.
659
 
        @sync.synchronize do
660
 
          throw :foundval, @values[source][param] if @values[source].include?(param)
661
 
        end
662
 
      end
663
 
      throw :foundval, nil
664
 
    end
665
 
 
666
 
    # If we didn't get a value, use the default
667
 
    val = @config[param].default if val.nil?
668
 
 
669
 
    val
670
 
  end
671
 
 
672
 
  # Find the correct value using our search path.  Optionally accept an environment
673
 
  # in which to search before the other configuration sections.
674
 
  def value(param, environment = nil)
675
 
    param = param.to_sym
676
 
    environment &&= environment.to_sym
677
 
 
678
 
    # Short circuit to nil for undefined parameters.
679
 
    return nil unless @config.include?(param)
680
 
 
681
 
    # Yay, recursion.
682
 
    #self.reparse unless [:config, :filetimeout].include?(param)
683
 
 
684
 
    # Check the cache first.  It needs to be a per-environment
685
 
    # cache so that we don't spread values from one env
686
 
    # to another.
687
 
    if cached = @cache[environment||"none"][param]
688
 
      return cached
689
 
    end
690
 
 
691
 
    val = uninterpolated_value(param, environment)
692
 
 
693
 
    if param == :code
694
 
      # if we interpolate code, all hell breaks loose.
695
 
      return val
696
 
    end
697
 
 
698
 
    # Convert it if necessary
699
 
    val = convert(val, environment)
700
 
 
701
 
    # And cache it
702
 
    @cache[environment||"none"][param] = val
703
 
    val
704
 
  end
705
 
 
706
 
  # Open a file with the appropriate user, group, and mode
707
 
  def write(default, *args, &bloc)
708
 
    obj = get_config_file_default(default)
709
 
    writesub(default, value(obj.name), *args, &bloc)
710
 
  end
711
 
 
712
 
  # Open a non-default file under a default dir with the appropriate user,
713
 
  # group, and mode
714
 
  def writesub(default, file, *args, &bloc)
715
 
    obj = get_config_file_default(default)
716
 
    chown = nil
717
 
    if Puppet.features.root?
718
 
      chown = [obj.owner, obj.group]
719
 
    else
720
 
      chown = [nil, nil]
721
 
    end
722
 
 
723
 
    Puppet::Util::SUIDManager.asuser(*chown) do
724
 
      mode = obj.mode || 0640
725
 
      args << "w" if args.empty?
726
 
 
727
 
      args << mode
728
 
 
729
 
      # Update the umask to make non-executable files
730
 
      Puppet::Util.withumask(File.umask ^ 0111) do
731
 
        File.open(file, *args) do |file|
732
 
          yield file
733
 
        end
734
 
      end
735
 
    end
736
 
  end
737
 
 
738
 
  def readwritelock(default, *args, &bloc)
739
 
    file = value(get_config_file_default(default).name)
740
 
    tmpfile = file + ".tmp"
741
 
    sync = Sync.new
742
 
    raise Puppet::DevError, "Cannot create #{file}; directory #{File.dirname(file)} does not exist" unless FileTest.directory?(File.dirname(tmpfile))
743
 
 
744
 
    sync.synchronize(Sync::EX) do
745
 
      File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf|
746
 
        rf.lock_exclusive do
747
 
          if File.exist?(tmpfile)
748
 
            raise Puppet::Error, ".tmp file already exists for #{file}; Aborting locked write. Check the .tmp file and delete if appropriate"
749
 
          end
750
 
 
751
 
          # If there's a failure, remove our tmpfile
752
 
          begin
753
 
            writesub(default, tmpfile, *args, &bloc)
754
 
          rescue
755
 
            File.unlink(tmpfile) if FileTest.exist?(tmpfile)
756
 
            raise
757
 
          end
758
 
 
759
 
          begin
760
 
            File.rename(tmpfile, file)
761
 
          rescue => detail
762
 
            Puppet.err "Could not rename #{file} to #{tmpfile}: #{detail}"
763
 
            File.unlink(tmpfile) if FileTest.exist?(tmpfile)
764
 
          end
765
 
        end
766
 
      end
767
 
    end
768
 
  end
769
 
 
770
 
  private
771
 
 
772
 
  def get_config_file_default(default)
773
 
    obj = nil
774
 
    unless obj = @config[default]
775
 
      raise ArgumentError, "Unknown default #{default}"
776
 
    end
777
 
 
778
 
    raise ArgumentError, "Default #{default} is not a file" unless obj.is_a? FileSetting
779
 
 
780
 
    obj
781
 
  end
782
 
 
783
 
  # Create the transportable objects for users and groups.
784
 
  def add_user_resources(catalog, sections)
785
 
    return unless Puppet.features.root?
786
 
    return unless self[:mkusers]
787
 
 
788
 
    @config.each do |name, setting|
789
 
      next unless setting.respond_to?(:owner)
790
 
      next unless sections.nil? or sections.include?(setting.section)
791
 
 
792
 
      if user = setting.owner and user != "root" and catalog.resource(:user, user).nil?
793
 
        resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present})
794
 
        resource[:gid] = self[:group] if self[:group]
795
 
        catalog.add_resource resource
796
 
      end
797
 
      if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil?
798
 
        catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present})
799
 
      end
800
 
    end
801
 
  end
802
 
 
803
 
  # Yield each search source in turn.
804
 
  def each_source(environment)
805
 
    searchpath(environment).each do |source|
806
 
      # Modify the source as necessary.
807
 
      source = self.run_mode if source == :run_mode
808
 
      yield source
809
 
    end
810
 
  end
811
 
 
812
 
  # Return all settings that have associated hooks; this is so
813
 
  # we can call them after parsing the configuration file.
814
 
  def settings_with_hooks
815
 
    @config.values.find_all { |setting| setting.respond_to?(:handle) }
816
 
  end
817
 
 
818
 
  # Extract extra setting information for files.
819
 
  def extract_fileinfo(string)
820
 
    result = {}
821
 
    value = string.sub(/\{\s*([^}]+)\s*\}/) do
822
 
      params = $1
823
 
      params.split(/\s*,\s*/).each do |str|
824
 
        if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/
825
 
          param, value = $1.intern, $2
826
 
          result[param] = value
827
 
          raise ArgumentError, "Invalid file option '#{param}'" unless [:owner, :mode, :group].include?(param)
828
 
 
829
 
          if param == :mode and value !~ /^\d+$/
830
 
            raise ArgumentError, "File modes must be numbers"
831
 
          end
832
 
        else
833
 
          raise ArgumentError, "Could not parse '#{string}'"
834
 
        end
835
 
      end
836
 
      ''
837
 
    end
838
 
    result[:value] = value.sub(/\s*$/, '')
839
 
    result
840
 
  end
841
 
 
842
 
  # Convert arguments into booleans, integers, or whatever.
843
 
  def munge_value(value)
844
 
    # Handle different data types correctly
845
 
    return case value
846
 
      when /^false$/i; false
847
 
      when /^true$/i; true
848
 
      when /^\d+$/i; Integer(value)
849
 
      when true; true
850
 
      when false; false
851
 
      else
852
 
        value.gsub(/^["']|["']$/,'').sub(/\s+$/, '')
853
 
    end
854
 
  end
855
 
 
856
 
  # This method just turns a file in to a hash of hashes.
857
 
  def parse_file(file)
858
 
    text = read_file(file)
859
 
 
860
 
    result = Hash.new { |names, name|
861
 
      names[name] = {}
862
 
    }
863
 
 
864
 
    count = 0
865
 
 
866
 
    # Default to 'main' for the section.
867
 
    section = :main
868
 
    result[section][:_meta] = {}
869
 
    text.split(/\n/).each { |line|
870
 
      count += 1
871
 
      case line
872
 
      when /^\s*\[(\w+)\]\s*$/
873
 
        section = $1.intern # Section names
874
 
        # Add a meta section
875
 
        result[section][:_meta] ||= {}
876
 
      when /^\s*#/; next # Skip comments
877
 
      when /^\s*$/; next # Skip blanks
878
 
      when /^\s*(\w+)\s*=\s*(.*?)\s*$/ # settings
879
 
        var = $1.intern
880
 
 
881
 
        # We don't want to munge modes, because they're specified in octal, so we'll
882
 
        # just leave them as a String, since Puppet handles that case correctly.
883
 
        if var == :mode
884
 
          value = $2
885
 
        else
886
 
          value = munge_value($2)
887
 
        end
888
 
 
889
 
        # Check to see if this is a file argument and it has extra options
890
 
        begin
891
 
          if value.is_a?(String) and options = extract_fileinfo(value)
892
 
            value = options[:value]
893
 
            options.delete(:value)
894
 
            result[section][:_meta][var] = options
895
 
          end
896
 
          result[section][var] = value
897
 
        rescue Puppet::Error => detail
898
 
          detail.file = file
899
 
          detail.line = line
900
 
          raise
901
 
        end
902
 
      else
903
 
        error = Puppet::Error.new("Could not match line #{line}")
904
 
        error.file = file
905
 
        error.line = line
906
 
        raise error
907
 
      end
908
 
    }
909
 
 
910
 
    result
911
 
  end
912
 
 
913
 
  # Read the file in.
914
 
  def read_file(file)
915
 
    begin
916
 
      return File.read(file)
917
 
    rescue Errno::ENOENT
918
 
      raise ArgumentError, "No such file #{file}"
919
 
    rescue Errno::EACCES
920
 
      raise ArgumentError, "Permission denied to file #{file}"
921
 
    end
922
 
  end
923
 
 
924
 
  # Set file metadata.
925
 
  def set_metadata(meta)
926
 
    meta.each do |var, values|
927
 
      values.each do |param, value|
928
 
        @config[var].send(param.to_s + "=", value)
929
 
      end
930
 
    end
931
 
  end
932
 
end