diff --git a/lib/puppet/parser/functions/generate.rb b/lib/puppet/parser/functions/generate.rb index cf9166f89..f53d8e5c3 100644 --- a/lib/puppet/parser/functions/generate.rb +++ b/lib/puppet/parser/functions/generate.rb @@ -1,37 +1,37 @@ # Runs an external command and returns the results Puppet::Parser::Functions::newfunction(:generate, :type => :rvalue, :doc => "Calls an external command on the Puppet master and returns the results of the command. Any arguments are passed to the external command as arguments. If the generator does not exit with return code of 0, the generator is considered to have failed and a parse error is thrown. Generators can only have file separators, alphanumerics, dashes, and periods in them. This function will attempt to protect you from malicious generator calls (e.g., those with '..' in them), but it can never be entirely safe. No subshell is used to execute generators, so all shell metacharacters are passed directly to the generator.") do |args| raise Puppet::ParseError, "Generators must be fully qualified" unless Puppet::Util.absolute_path?(args[0]) if Puppet.features.microsoft_windows? valid = args[0] =~ /^[a-z]:(?:[\/\\][\w.-]+)+$/i else - valid = args[0] =~ /^[-\/\w.]+$/ + valid = args[0] =~ /^[-\/\w.+]+$/ end unless valid raise Puppet::ParseError, "Generators can only contain alphanumerics, file separators, and dashes" end if args[0] =~ /\.\./ raise Puppet::ParseError, "Can not use generators with '..' in them." end begin Dir.chdir(File.dirname(args[0])) { Puppet::Util::Execution.execute(args) } rescue Puppet::ExecutionFailure => detail raise Puppet::ParseError, "Failed to execute generator #{args[0]}: #{detail}" end end diff --git a/spec/unit/agent_backward_compatibility_spec.rb b/spec/unit/agent_backward_compatibility_spec.rb old mode 100644 new mode 100755 index 4f2acfd01..9301e2611 --- a/spec/unit/agent_backward_compatibility_spec.rb +++ b/spec/unit/agent_backward_compatibility_spec.rb @@ -1,152 +1,152 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/agent' ############################################################################ # NOTE # ############################################################################ # # # This entire spec is only here for backwards compatibility from 2.7.12+ # # with 2.7.10 and 2.7.11. The entire file should be able to be removed # # for the 3.x series. # # # # For more info, see the comments on the #handle_2_7_10_disabled_lockfile # # method in pidlock.rb # # # # --cprice 2012-03-01 # ############################################################################ class AgentTestClient def run # no-op end def stop # no-op end end describe Puppet::Agent do include PuppetSpec::Files let(:agent) { Puppet::Agent.new(AgentTestClient) } describe "in order to be backwards-compatibility with versions 2.7.10 and 2.7.11" do describe "when the 2.7.10/2.7.11 'disabled' lockfile exists" do # the "normal" lockfile let(:lockfile_path) { tmpfile("agent_spec_lockfile") } # the 2.7.10/2.7.11 "disabled" lockfile # (can't use PuppetSpec::Files.tmpfile here because we need the ".disabled" file to have *exactly* the same # path/name as the original file, plus the ".disabled" suffix.) let(:disabled_lockfile_path) { lockfile_path + ".disabled" } # some regexes to match log messages - let(:warning_regex) { /^Found special lockfile '#{disabled_lockfile_path}'.*renaming/ } + let(:warning_regex) { /^Found special lockfile '#{Regexp.escape(disabled_lockfile_path)}'.*renaming/ } let(:disabled_regex) { /^Skipping run of .*; administratively disabled/ } before(:each) do # create the 2.7.10 "disable" lockfile. FileUtils.touch(disabled_lockfile_path) # stub in our temp lockfile path. AgentTestClient.expects(:lockfile_path).returns lockfile_path end after(:each) do # manually clean up the files that we didn't create via PuppetSpec::Files.tmpfile begin File.unlink(disabled_lockfile_path) rescue Errno::ENOENT # some of the tests expect for the agent code to take care of deleting this file, # so it may (validly) not exist. end end describe "when the 'regular' lockfile also exists" do # the logic here is that if a 'regular' lockfile already exists, then there is some state that the # current version of puppet is responsible for dealing with. All of the tests in this block are # simply here to make sure that our backwards-compatibility hack does *not* interfere with this. # # Even if the ".disabled" lockfile exists--it can be dealt with at another time, when puppet is # in *exactly* the state that we want it to be in (mostly meaning that the 'regular' lockfile # does not exist.) before(:each) do # create the "regular" lockfile FileUtils.touch(lockfile_path) end it "should be recognized as 'disabled'" do agent.should be_disabled end it "should not try to start a new agent run" do AgentTestClient.expects(:new).never Puppet.expects(:notice).with(regexp_matches(disabled_regex)) agent.run end it "should not delete the 2.7.10/2.7.11 lockfile" do agent.run File.exists?(disabled_lockfile_path).should == true end it "should not print the warning message" do Puppet.expects(:warning).with(regexp_matches(warning_regex)).never agent.run end end describe "when the 'regular' lockfile does not exist" do # this block of tests is for actually testing the backwards compatibility hack. This # is where we're in a clean state and we know it's safe(r) to muck with the lockfile # situation. it "should recognize that the agent is disabled" do agent.should be_disabled end describe "when an agent run is requested" do it "should not try to start a new agent run" do AgentTestClient.expects(:new).never Puppet.expects(:notice).with(regexp_matches(disabled_regex)) agent.run end it "should warn, remove the 2.7.10/2.7.11 lockfile, and create the 'normal' lockfile" do Puppet.expects(:warning).with(regexp_matches(warning_regex)) agent.run File.exists?(disabled_lockfile_path).should == false File.exists?(lockfile_path).should == true end end describe "when running --enable" do it "should recognize that the agent is disabled" do agent.should be_disabled end it "should warn and clean up the 2.7.10/2.7.11 lockfile" do Puppet.expects(:warning).with(regexp_matches(warning_regex)) agent.enable File.exists?(disabled_lockfile_path).should == false File.exists?(lockfile_path).should == false end end end end end end diff --git a/spec/unit/parser/functions/generate_spec.rb b/spec/unit/parser/functions/generate_spec.rb index fe779c832..71e2ff4c0 100755 --- a/spec/unit/parser/functions/generate_spec.rb +++ b/spec/unit/parser/functions/generate_spec.rb @@ -1,114 +1,120 @@ #!/usr/bin/env rspec require 'spec_helper' describe "the generate function" do include PuppetSpec::Files before :all do Puppet::Parser::Functions.autoloader.loadall end let(:scope) { Puppet::Parser::Scope.new } it "should exist" do Puppet::Parser::Functions.function("generate").should == "function_generate" end it "accept a fully-qualified path as a command" do command = File.expand_path('/command/foo') Dir.expects(:chdir).with(File.dirname(command)).returns("yay") scope.function_generate([command]).should == "yay" end it "should not accept a relative path as a command" do lambda { scope.function_generate(["command"]) }.should raise_error(Puppet::ParseError) end it "should not accept a command containing illegal characters" do lambda { scope.function_generate([File.expand_path('/##/command')]) }.should raise_error(Puppet::ParseError) end it "should not accept a command containing spaces" do lambda { scope.function_generate([File.expand_path('/com mand')]) }.should raise_error(Puppet::ParseError) end it "should not accept a command containing '..'" do command = File.expand_path("/command/../") lambda { scope.function_generate([command]) }.should raise_error(Puppet::ParseError) end it "should execute the generate script with the correct working directory" do command = File.expand_path("/command") Dir.expects(:chdir).with(File.dirname(command)).returns("yay") scope.function_generate([command]).should == 'yay' end describe "on Windows", :as_platform => :windows do it "should accept lower-case drive letters" do command = 'd:/command/foo' Dir.expects(:chdir).with(File.dirname(command)).returns("yay") scope.function_generate([command]).should == 'yay' end it "should accept upper-case drive letters" do command = 'D:/command/foo' Dir.expects(:chdir).with(File.dirname(command)).returns("yay") scope.function_generate([command]).should == 'yay' end it "should accept forward and backslashes in the path" do command = 'D:\command/foo\bar' Dir.expects(:chdir).with(File.dirname(command)).returns("yay") scope.function_generate([command]).should == 'yay' end it "should reject colons when not part of the drive letter" do lambda { scope.function_generate(['C:/com:mand']) }.should raise_error(Puppet::ParseError) end it "should reject root drives" do lambda { scope.function_generate(['C:/']) }.should raise_error(Puppet::ParseError) end end describe "on non-Windows", :as_platform => :posix do it "should reject backslashes" do lambda { scope.function_generate(['/com\\mand']) }.should raise_error(Puppet::ParseError) end + + it "should accept plus and dash" do + command = "/var/folders/9z/9zXImgchH8CZJh6SgiqS2U+++TM/-Tmp-/foo" + Dir.expects(:chdir).with(File.dirname(command)).returns("yay") + scope.function_generate([command]).should == 'yay' + end end let :command do cmd = tmpfile('function_generate') if Puppet.features.microsoft_windows? cmd += '.bat' text = '@echo off' + "\n" + 'echo a-%1 b-%2' else text = '#!/bin/sh' + "\n" + 'echo a-$1 b-$2' end File.open(cmd, 'w') {|fh| fh.puts text } File.chmod 0700, cmd cmd end it "should call generator with no arguments" do scope.function_generate([command]).should == "a- b-\n" end it "should call generator with one argument" do scope.function_generate([command, 'one']).should == "a-one b-\n" end it "should call generator with wo arguments" do scope.function_generate([command, 'one', 'two']).should == "a-one b-two\n" end it "should fail if generator is not absolute" do expect { scope.function_generate(['boo']) }.to raise_error Puppet::ParseError end it "should fail if generator fails" do expect { scope.function_generate(['/boo']) }.to raise_error Puppet::ParseError end end