diff --git a/lib/puppet/provider/exec/posix.rb b/lib/puppet/provider/exec/posix.rb index 92dbd8c98..157d0f28d 100644 --- a/lib/puppet/provider/exec/posix.rb +++ b/lib/puppet/provider/exec/posix.rb @@ -1,112 +1,115 @@ Puppet::Type.type(:exec).provide :posix do include Puppet::Util::Execution confine :feature => :posix defaultfor :feature => :posix - desc "Execute external binaries directly, on POSIX systems. -This does not pass through a shell, or perform any interpolation, but -only directly calls the command with the arguments given." + desc <<-EOT + Executes external binaries directly, without passing through a shell or + performing any interpolation. This is a safer and more predictable way + to execute most commands, but prevents the use of globbing and shell + built-ins (including control logic like "for" and "if" statements). + EOT def run(command, check = false) output = nil status = nil dir = nil checkexe(command) if dir = resource[:cwd] unless File.directory?(dir) if check dir = nil else self.fail "Working directory '#{dir}' does not exist" end end end dir ||= Dir.pwd debug "Executing#{check ? " check": ""} '#{command}'" begin # Do our chdir Dir.chdir(dir) do environment = {} environment[:PATH] = resource[:path].join(":") if resource[:path] if envlist = resource[:environment] envlist = [envlist] unless envlist.is_a? Array envlist.each do |setting| if setting =~ /^(\w+)=((.|\n)+)$/ env_name = $1 value = $2 if environment.include?(env_name) || environment.include?(env_name.to_sym) warning "Overriding environment setting '#{env_name}' with '#{value}'" end environment[env_name] = value else warning "Cannot understand environment setting #{setting.inspect}" end end end withenv environment do Timeout::timeout(resource[:timeout]) do output, status = Puppet::Util::SUIDManager. run_and_capture([command], resource[:user], resource[:group]) end # The shell returns 127 if the command is missing. if status.exitstatus == 127 raise ArgumentError, output end end end rescue Errno::ENOENT => detail self.fail detail.to_s end return output, status end # Verify that we have the executable def checkexe(command) exe = extractexe(command) if resource[:path] if Puppet.features.posix? and !File.exists?(exe) withenv :PATH => resource[:path].join(File::PATH_SEPARATOR) do exe = which(exe) || raise(ArgumentError,"Could not find command '#{exe}'") end elsif Puppet.features.microsoft_windows? and !File.exists?(exe) resource[:path].each do |path| [".exe", ".ps1", ".bat", ".com", ""].each do |extension| file = File.join(path, exe+extension) return if File.exists?(file) end end end end raise ArgumentError, "Could not find command '#{exe}'" unless File.exists?(exe) unless File.executable?(exe) raise ArgumentError, "'#{exe}' is not executable" end end def extractexe(command) # easy case: command was quoted if command =~ /^"([^"]+)"/ $1 else command.split(/ /)[0] end end def validatecmd(command) exe = extractexe(command) # if we're not fully qualified, require a path self.fail "'#{command}' is not qualified and no path was specified. Please qualify the command or specify a path." if File.expand_path(exe) != exe and resource[:path].nil? end end diff --git a/lib/puppet/provider/exec/shell.rb b/lib/puppet/provider/exec/shell.rb index 98f309e8f..ad2171005 100644 --- a/lib/puppet/provider/exec/shell.rb +++ b/lib/puppet/provider/exec/shell.rb @@ -1,17 +1,26 @@ Puppet::Type.type(:exec).provide :shell, :parent => :posix do include Puppet::Util::Execution confine :feature => :posix - desc "Execute external binaries directly, on POSIX systems. -passing through a shell so that shell built ins are available." + desc <<-EOT + Passes the provided command through `/bin/sh`; only available on + POSIX systems. This allows the use of shell globbing and built-ins, and + does not require that the path to a command be fully-qualified. Although + this can be more convenient than the `posix` provider, it also means that + you need to be more careful with escaping; as ever, with great power comes + etc. etc. + + This provider closely resembles the behavior of the `exec` type + in Puppet 0.25.x. + EOT def run(command, check = false) command = %Q{/bin/sh -c "#{command.gsub(/"/,'\"')}"} super(command, check) end def validatecmd(command) true end end