summaryrefslogtreecommitdiffstats
path: root/lib/puppet/server
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/server')
-rw-r--r--lib/puppet/server/ca.rb159
-rwxr-xr-xlib/puppet/server/filebucket.rb277
-rwxr-xr-xlib/puppet/server/fileserver.rb264
-rw-r--r--lib/puppet/server/master.rb92
-rw-r--r--lib/puppet/server/servlet.rb112
5 files changed, 904 insertions, 0 deletions
diff --git a/lib/puppet/server/ca.rb b/lib/puppet/server/ca.rb
new file mode 100644
index 000000000..65074c3f6
--- /dev/null
+++ b/lib/puppet/server/ca.rb
@@ -0,0 +1,159 @@
+require 'openssl'
+require 'puppet'
+require 'puppet/sslcertificates'
+require 'xmlrpc/server'
+
+# Much of this was taken from QuickCert:
+# http://segment7.net/projects/ruby/QuickCert/
+
+module Puppet
+class Server
+ class CAError < Puppet::Error; end
+ class CA
+ attr_reader :ca
+
+ def self.interface
+ XMLRPC::Service::Interface.new("puppetca") { |iface|
+ iface.add_method("array getcert(csr)")
+ }
+ end
+
+ Puppet::Server.addhandler(:CA, self)
+
+ def autosign?(hostname)
+ # simple values are easy
+ asign = Puppet[:autosign]
+ if asign == true or asign == false
+ return asign
+ end
+
+ # we only otherwise know how to handle files
+ unless asign =~ /^\//
+ raise Puppet::Error, "Invalid autosign value %s" %
+ asign
+ end
+
+ unless FileTest.exists?(asign)
+ Puppet.warning "Autosign is enabled but %s is missing" % asign
+ return false
+ end
+ File.open(asign) { |f|
+ f.each { |line|
+ line.chomp!
+ if line =~ /^[.\w-]+$/ and line == hostname
+ Puppet.info "%s exactly matched %s" % [hostname, line]
+ return true
+ else
+ begin
+ rx = Regexp.new(line)
+ rescue => detail
+ Puppet.err(
+ "Could not create regexp out of autosign line %s: %s" %
+ [line, detail]
+ )
+ next
+ end
+
+ if hostname =~ rx
+ Puppet.info "%s matched %s" % [hostname, line]
+ return true
+ end
+ end
+ }
+ }
+
+ return false
+ end
+
+ def initialize(hash = {})
+ @ca = Puppet::SSLCertificates::CA.new()
+ end
+
+ # our client sends us a csr, and we either store it for later signing,
+ # or we sign it right away
+ def getcert(csrtext, request = nil)
+ # okay, i need to retrieve the hostname from the csr, and then
+ # verify that i get the same hostname through reverse lookup or
+ # something
+
+ Puppet.info "Someone's trying for a cert"
+ csr = OpenSSL::X509::Request.new(csrtext)
+
+ subject = csr.subject
+
+ nameary = subject.to_a.find { |ary|
+ ary[0] == "CN"
+ }
+
+ if nameary.nil?
+ Puppet.err "Invalid certificate request"
+ return "invalid"
+ end
+
+ hostname = nameary[1]
+
+ unless @ca
+ Puppet.notice "Host %s asked for signing from non-CA master" % hostname
+ return ""
+ end
+
+ # okay, we're now going to store the public key if we don't already
+ # have it
+ public_key = csr.public_key
+ unless FileTest.directory?(Puppet[:publickeydir])
+ Puppet.recmkdir(Puppet[:publickeydir])
+ end
+ pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.'))
+
+ if FileTest.exists?(pkeyfile)
+ currentkey = File.open(pkeyfile) { |k| k.read }
+ unless currentkey == public_key.to_s
+ raise Puppet::Error, "public keys for %s differ" % hostname
+ end
+ else
+ File.open(pkeyfile, "w", 0644) { |f|
+ f.print public_key.to_s
+ }
+ end
+ unless FileTest.directory?(Puppet[:certdir])
+ Puppet.recmkdir(Puppet[:certdir], 0770)
+ end
+ certfile = File.join(Puppet[:certdir], [hostname, "pem"].join("."))
+
+ #puts hostname
+ #puts certfile
+
+ unless FileTest.directory?(Puppet[:csrdir])
+ Puppet.recmkdir(Puppet[:csrdir], 0770)
+ end
+ # first check to see if we already have a signed cert for the host
+ cert, cacert = ca.getclientcert(hostname)
+ if cert and cacert
+ Puppet.info "Retrieving existing certificate for %s" % hostname
+ Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class]
+ return [cert.to_pem, cacert.to_pem]
+ elsif @ca
+ if self.autosign?(hostname)
+ # okay, we don't have a signed cert
+ # if we're a CA and autosign is turned on, then go ahead and sign
+ # the csr and return the results
+ Puppet.info "Signing certificate for %s" % hostname
+ cert, cacert = @ca.sign(csr)
+ Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class]
+ return [cert.to_pem, cacert.to_pem]
+ else # just write out the csr for later signing
+ if @ca.getclientcsr(hostname)
+ Puppet.info "Not replacing existing request from %s" % hostname
+ else
+ Puppet.info "Storing certificate request for %s" % hostname
+ @ca.storeclientcsr(csr)
+ end
+ return ["", ""]
+ end
+ else
+ raise "huh?"
+ end
+ end
+ end
+end
+end
diff --git a/lib/puppet/server/filebucket.rb b/lib/puppet/server/filebucket.rb
new file mode 100755
index 000000000..f0e717146
--- /dev/null
+++ b/lib/puppet/server/filebucket.rb
@@ -0,0 +1,277 @@
+#!/usr/bin/ruby -w
+
+#--------------------
+# accept and serve files
+#
+# $Id$
+
+
+require 'webrick'
+#require 'webrick/https'
+require 'xmlrpc/server'
+require 'xmlrpc/client'
+#require 'webrick/httpstatus'
+require 'facter'
+require 'digest/md5'
+require 'base64'
+
+class BucketError < RuntimeError; end
+
+module FileBucket
+ DEFAULTPORT = 8139
+ # this doesn't work for relative paths
+ def FileBucket.mkdir(dir)
+ if FileTest.exist?(dir)
+ return false
+ else
+ tmp = dir.sub(/^\//,'')
+ path = [File::SEPARATOR]
+ tmp.split(File::SEPARATOR).each { |dir|
+ path.push dir
+ unless FileTest.exist?(File.join(path))
+ Dir.mkdir(File.join(path))
+ end
+ }
+ return true
+ end
+ end
+
+ def FileBucket.paths(base,md5)
+ return [
+ File.join(base, md5),
+ File.join(base, md5, "contents"),
+ File.join(base, md5, "paths")
+ ]
+ end
+
+ #---------------------------------------------------------------
+ class Bucket
+ def initialize(hash)
+ # build our AST
+
+ if hash.include?(:ConflictCheck)
+ @conflictchk = hash[:ConflictCheck]
+ hash.delete(:ConflictCheck)
+ else
+ @conflictchk = true
+ end
+
+ if hash.include?(:Path)
+ @bucket = hash[:Path]
+ hash.delete(:Path)
+ else
+ if defined? Puppet
+ @bucket = Puppet[:bucketdir]
+ else
+ @bucket = File.expand_path("~/.filebucket")
+ end
+ end
+
+ # XXX this should really be done using Puppet::Type instances...
+ FileBucket.mkdir(@bucket)
+ end
+
+ # accept a file from a client
+ def addfile(string,path)
+ #puts "entering addfile"
+ contents = Base64.decode64(string)
+ #puts "string is decoded"
+
+ md5 = Digest::MD5.hexdigest(contents)
+ #puts "md5 is made"
+
+ bpath, bfile, pathpath = FileBucket.paths(@bucket,md5)
+
+ # if it's a new directory...
+ if FileBucket.mkdir(bpath)
+ # ...then just create the file
+ #puts "creating file"
+ File.open(bfile, File::WRONLY|File::CREAT) { |of|
+ of.print contents
+ }
+ #puts "File is created"
+ else # if the dir already existed...
+ # ...we need to verify that the contents match the existing file
+ if @conflictchk
+ unless FileTest.exists?(bfile)
+ raise(BucketError,
+ "No file at %s for sum %s" % [bfile,md5], caller)
+ end
+
+ curfile = ""
+ File.open(bfile) { |of|
+ curfile = of.read
+ }
+
+ # if the contents don't match, then we've found a conflict
+ # unlikely, but quite bad
+ if curfile != contents
+ raise(BucketError,
+ "Got passed new contents for sum %s" % md5, caller)
+ end
+ end
+ #puts "Conflict check is done"
+ end
+
+ # in either case, add the passed path to the list of paths
+ paths = nil
+ addpath = false
+ if FileTest.exists?(pathpath)
+ File.open(pathpath) { |of|
+ paths = of.readlines
+ }
+
+ # unless our path is already there...
+ unless paths.include?(path)
+ addpath = true
+ end
+ else
+ addpath = true
+ end
+ #puts "Path is checked"
+
+ # if it's a new file, or if our path isn't in the file yet, add it
+ if addpath
+ File.open(pathpath, File::WRONLY|File::CREAT|File::APPEND) { |of|
+ of.puts path
+ }
+ #puts "Path is added"
+ end
+
+ return md5
+ end
+
+ def getfile(md5)
+ bpath, bfile, bpaths = FileBucket.paths(@bucket,md5)
+
+ unless FileTest.exists?(bfile)
+ return false
+ end
+
+ contents = nil
+ File.open(bfile) { |of|
+ contents = of.read
+ }
+
+ return Base64.encode64(contents)
+ end
+
+ private
+
+ def on_init
+ @default_namespace = 'urn:filebucket-server'
+ add_method(self, 'addfile', 'string', 'path')
+ add_method(self, 'getfile', 'md5')
+ end
+
+ def cert(filename)
+ OpenSSL::X509::Certificate.new(File.open(File.join(@dir, filename)) { |f|
+ f.read
+ })
+ end
+
+ def key(filename)
+ OpenSSL::PKey::RSA.new(File.open(File.join(@dir, filename)) { |f|
+ f.read
+ })
+ end
+
+ end
+ #---------------------------------------------------------------
+
+ class BucketWebserver < WEBrick::HTTPServer
+ def initialize(hash)
+ unless hash.include?(:Port)
+ hash[:Port] = FileBucket::DEFAULTPORT
+ end
+ servlet = XMLRPC::WEBrickServlet.new
+ @bucket = FileBucket::Bucket.new(hash)
+ #puts @bucket
+ servlet.add_handler("bucket",@bucket)
+ super
+
+ self.mount("/RPC2", servlet)
+ end
+ end
+
+ class BucketClient < XMLRPC::Client
+ @@methods = [ :addfile, :getfile ]
+
+ @@methods.each { |method|
+ self.send(:define_method,method) { |*args|
+ begin
+ call("bucket.%s" % method.to_s,*args)
+ rescue => detail
+ #puts detail
+ end
+ }
+ }
+
+ def initialize(hash)
+ hash[:Path] ||= "/RPC2"
+ hash[:Server] ||= "localhost"
+ hash[:Port] ||= FileBucket::DEFAULTPORT
+ super(hash[:Server],hash[:Path],hash[:Port])
+ end
+ end
+
+ class Dipper
+ def initialize(hash)
+ if hash.include?(:Server)
+ @bucket = FileBucket::BucketClient.new(
+ :Server => hash[:Server]
+ )
+ elsif hash.include?(:Bucket)
+ @bucket = hash[:Bucket]
+ elsif hash.include?(:Path)
+ @bucket = FileBucket::Bucket.new(
+ :Path => hash[:Path]
+ )
+ end
+ end
+
+ def backup(file)
+ unless FileTest.exists?(file)
+ raise(BucketError, "File %s does not exist" % file, caller)
+ end
+ contents = File.open(file) { |of| of.read }
+
+ string = Base64.encode64(contents)
+ #puts "string is created"
+
+ sum = @bucket.addfile(string,file)
+ #puts "file %s is added" % file
+ return sum
+ end
+
+ def restore(file,sum)
+ restore = true
+ if FileTest.exists?(file)
+ contents = File.open(file) { |of| of.read }
+
+ cursum = Digest::MD5.hexdigest(contents)
+
+ # if the checksum has changed...
+ # this might be extra effort
+ if cursum == sum
+ restore = false
+ end
+ end
+
+ if restore
+ #puts "Restoring %s" % file
+ newcontents = Base64.decode64(@bucket.getfile(sum))
+ newsum = Digest::MD5.hexdigest(newcontents)
+ File.open(file,File::WRONLY|File::TRUNC) { |of|
+ of.print(newcontents)
+ }
+ #puts "Done"
+ return newsum
+ else
+ return nil
+ end
+
+ end
+ end
+ #---------------------------------------------------------------
+end
diff --git a/lib/puppet/server/fileserver.rb b/lib/puppet/server/fileserver.rb
new file mode 100755
index 000000000..43e08655f
--- /dev/null
+++ b/lib/puppet/server/fileserver.rb
@@ -0,0 +1,264 @@
+require 'puppet'
+require 'cgi'
+
+module Puppet
+class Server
+ class FileServerError < Puppet::Error; end
+ class FileServer
+ attr_accessor :local
+
+ #CHECKPARAMS = %w{checksum type mode owner group}
+ CHECKPARAMS = [:mode, :type, :owner, :group, :checksum]
+
+ def self.interface
+ XMLRPC::Service::Interface.new("fileserver") { |iface|
+ iface.add_method("string describe(string)")
+ iface.add_method("string list(string, boolean)")
+ iface.add_method("string retrieve(string)")
+ }
+ end
+
+ Puppet::Server.addhandler(:FileServer, self)
+
+ def check(dir)
+ unless FileTest.exists?(dir)
+ Puppet.notice "File source %s does not exist" % dir
+ return nil
+ end
+
+ obj = nil
+ unless obj = Puppet::Type::PFile[dir]
+ obj = Puppet::Type::PFile.new(
+ :name => dir,
+ :check => CHECKPARAMS
+ )
+ end
+ # we should really have a timeout here -- we don't
+ # want to actually check on every connection, maybe no more
+ # than every 60 seconds or something
+ #@files[mount].evaluate
+ obj.evaluate
+
+ return obj
+ end
+
+ def describe(file)
+ mount, path = splitpath(file)
+
+ subdir = nil
+ unless subdir = subdir(mount, path)
+ Puppet.notice "Could not find subdirectory %s" %
+ "//%s/%s" % [mount, path]
+ return ""
+ end
+
+ obj = nil
+ unless obj = self.check(subdir)
+ return ""
+ end
+
+ desc = []
+ CHECKPARAMS.each { |check|
+ if state = obj.state(check)
+ unless state.is
+ Puppet.notice "Manually retrieving info for %s" % check
+ state.retrieve
+ end
+ desc << state.is
+ else
+ if check == "checksum" and obj.state(:type).is == "file"
+ Puppet.notice "File %s does not have data for %s" %
+ [obj.name, check]
+ end
+ desc << nil
+ end
+ }
+
+ return desc.join("\t")
+ end
+
+ def initialize(hash = {})
+ @mounts = {}
+ @files = {}
+
+ if hash[:Local]
+ @local = hash[:Local]
+ else
+ @local = false
+ end
+ end
+
+ def list(dir, recurse = false, sum = "md5")
+ mount, path = splitpath(dir)
+
+ subdir = nil
+ unless subdir = subdir(mount, path)
+ Puppet.notice "Could not find subdirectory %s" %
+ "//%s/%s" % [mount, path]
+ return ""
+ end
+
+ obj = nil
+ unless FileTest.exists?(subdir)
+ return ""
+ end
+
+ #rmdir = File.dirname(File.join(@mounts[mount], path))
+ rmdir = nameswap(dir, mount)
+ desc = self.reclist(rmdir, subdir, recurse)
+
+ if desc.length == 0
+ Puppet.notice "Got no information on //%s/%s" %
+ [mount, path]
+ return ""
+ end
+
+ desc.collect { |sub|
+ sub.join("\t")
+ }.join("\n")
+ end
+
+ def mount(dir, name)
+ if @mounts.include?(name)
+ if @mounts[name] != dir
+ raise FileServerError, "%s is already mounted at %s" %
+ [@mounts[name], name]
+ else
+ # it's already mounted; no problem
+ return
+ end
+ end
+
+ unless name =~ %r{^\w+$}
+ raise FileServerError, "Invalid name format '%s'" % name
+ end
+
+ unless FileTest.exists?(dir)
+ raise FileServerError, "%s does not exist" % dir
+ end
+
+ if FileTest.directory?(dir)
+ if FileTest.readable?(dir)
+ Puppet.info "Mounting %s at %s" % [dir, name]
+ @mounts[name] = dir
+ else
+ raise FileServerError, "%s is not readable" % dir
+ end
+ else
+ raise FileServerError, "%s is not a directory" % dir
+ end
+ end
+
+ # recursive listing function
+ def reclist(root, path, recurse)
+ #desc = [obj.name.sub(%r{#{root}/?}, '')]
+ name = path.sub(root, '')
+ if name == ""
+ name = "/"
+ end
+
+ if name == path
+ raise Puppet::FileServerError, "Could not match %s in %s" %
+ [root, path]
+ end
+
+ desc = [name]
+ ftype = File.stat(path).ftype
+
+ desc << ftype
+ if recurse.is_a?(Integer)
+ recurse -= 1
+ end
+
+ ary = [desc]
+ if recurse == true or (recurse.is_a?(Integer) and recurse > -1)
+ if ftype == "directory"
+ Dir.entries(path).each { |child|
+ next if child =~ /^\.\.?$/
+ self.reclist(root, File.join(path, child), recurse).each { |cobj|
+ ary << cobj
+ }
+ }
+ end
+ end
+
+ return ary.reject { |c| c.nil? }
+ end
+
+ def retrieve(file)
+ mount, path = splitpath(file)
+
+ unless (@mounts.include?(mount))
+ # FIXME I really need some better way to pass and handle xmlrpc errors
+ raise FileServerError, "%s not mounted" % mount
+ end
+
+ fpath = nil
+ if path
+ fpath = File.join(@mounts[mount], path)
+ else
+ fpath = @mounts[mount]
+ end
+
+ unless FileTest.exists?(fpath)
+ return ""
+ end
+
+ str = File.read(fpath)
+
+ if @local
+ return str
+ else
+ return CGI.escape(str)
+ end
+ end
+
+ private
+
+ def nameswap(name, mount)
+ name.sub(/\/#{mount}/, @mounts[mount]).gsub(%r{//}, '/').sub(
+ %r{/$}, ''
+ )
+ #Puppet.info "Swapped %s to %s" % [name, newname]
+ #newname
+ end
+
+ def splitpath(dir)
+ # the dir is based on one of the mounts
+ # so first retrieve the mount path
+ mount = nil
+ path = nil
+ if dir =~ %r{/(\w+)/?}
+ mount = $1
+ path = dir.sub(%r{/#{mount}/?}, '')
+
+ unless @mounts.include?(mount)
+ raise FileServerError, "%s not mounted" % mount
+ end
+ else
+ raise FileServerError, "Invalid path '%s'" % dir
+ end
+
+ if path == ""
+ path = nil
+ end
+ return mount, path
+ end
+
+ def subdir(mount, dir)
+ basedir = @mounts[mount]
+
+ dirname = nil
+ if dir
+ dirname = File.join(basedir, dir.split("/").join(File::SEPARATOR))
+ else
+ dirname = basedir
+ end
+
+ return dirname
+ end
+ end
+end
+end
+
+# $Id$
diff --git a/lib/puppet/server/master.rb b/lib/puppet/server/master.rb
new file mode 100644
index 000000000..f3f0411e9
--- /dev/null
+++ b/lib/puppet/server/master.rb
@@ -0,0 +1,92 @@
+require 'openssl'
+require 'puppet'
+require 'puppet/parser/interpreter'
+require 'puppet/sslcertificates'
+require 'xmlrpc/server'
+
+module Puppet
+class Server
+ class MasterError < Puppet::Error; end
+ class Master
+ attr_accessor :ast, :local
+ attr_reader :ca
+
+ def self.interface
+ XMLRPC::Service::Interface.new("puppetmaster") { |iface|
+ iface.add_method("string getconfig(string)")
+ }
+ end
+
+ Puppet::Server.addhandler(:Master, self)
+
+ def initialize(hash = {})
+
+ # build our AST
+ @file = hash[:File] || Puppet[:manifest]
+ @parser = Puppet::Parser::Parser.new()
+ @parser.file = @file
+ @ast = @parser.parse
+ hash.delete(:File)
+
+ if hash[:Local]
+ @local = hash[:Local]
+ else
+ @local = false
+ end
+
+ if hash.include?(:CA) and hash[:CA]
+ @ca = Puppet::SSLCertificates::CA.new()
+ else
+ @ca = nil
+ end
+ end
+
+ def getconfig(facts, request = nil)
+ if request
+ #Puppet.warning request.inspect
+ end
+ if @local
+ # we don't need to do anything, since we should already
+ # have raw objects
+ Puppet.debug "Our client is local"
+ else
+ Puppet.debug "Our client is remote"
+
+ # XXX this should definitely be done in the protocol, somehow
+ begin
+ facts = Marshal::load(CGI.unescape(facts))
+ rescue => detail
+ puts "AAAAA"
+ puts detail
+ exit
+ end
+ end
+
+ Puppet.debug("Creating interpreter")
+
+ begin
+ interpreter = Puppet::Parser::Interpreter.new(
+ :ast => @ast,
+ :facts => facts
+ )
+ rescue => detail
+ return detail.to_s
+ end
+
+ Puppet.debug("Running interpreter")
+ begin
+ retobjects = interpreter.run()
+ rescue => detail
+ Puppet.err detail.to_s
+ return ""
+ end
+
+ if @local
+ return retobjects
+ else
+ return CGI.escape(Marshal::dump(retobjects))
+ end
+ end
+ end
+end
+end
diff --git a/lib/puppet/server/servlet.rb b/lib/puppet/server/servlet.rb
new file mode 100644
index 000000000..4db9f6c0e
--- /dev/null
+++ b/lib/puppet/server/servlet.rb
@@ -0,0 +1,112 @@
+require 'xmlrpc/server'
+
+module Puppet
+class Server
+ class ServletError < RuntimeError; end
+ class Servlet < XMLRPC::WEBrickServlet
+ attr_accessor :request
+
+ # this is just a duplicate of the normal method; it's here for
+ # debugging when i need it
+ def self.get_instance(server, *options)
+ self.new(server, *options)
+ end
+
+ def initialize(server, handlers)
+ #Puppet.info server.inspect
+
+ # the servlet base class does not consume any arguments
+ # and its BasicServer base class only accepts a 'class_delim'
+ # option which won't change in Puppet at all
+ # thus, we don't need to pass any args to our base class,
+ # and we can consume them all ourselves
+ super()
+
+ handlers.each { |handler|
+ Puppet.debug "adding handler for %s" % handler.class
+ self.add_handler(handler.class.interface, handler)
+ }
+
+ @request = nil
+ self.set_service_hook { |obj, *args|
+ #raise "crap!"
+ if @request
+ args.push @request
+ #obj.call(args, @request)
+ end
+ begin
+ obj.call(*args)
+ rescue => detail
+ Puppet.warning obj.inspect
+ Puppet.err "Could not call: %s" % detail.to_s
+ end
+ }
+ end
+
+ def service(request, response)
+ @request = request
+ if @request.client_cert
+ Puppet.info "client cert is %s" % @request.client_cert
+ end
+ if @request.server_cert
+ Puppet.info "server cert is %s" % @request.server_cert
+ end
+ #p @request
+ begin
+ super
+ rescue => detail
+ Puppet.err "Could not service request: %s: %s" %
+ [detail.class, detail]
+ end
+ @request = nil
+ end
+
+ private
+
+ # this is pretty much just a copy of the original method but with more
+ # feedback
+ def dispatch(methodname, *args)
+ #Puppet.warning "dispatch on %s called with %s" %
+ # [methodname, args.inspect]
+ for name, obj in @handler
+ if obj.kind_of? Proc
+ unless methodname == name
+ Puppet.debug "obj is proc but %s != %s" %
+ [methodname, name]
+ next
+ end
+ else
+ unless methodname =~ /^#{name}(.+)$/
+ Puppet.debug "methodname did not match"
+ next
+ end
+ unless obj.respond_to? $1
+ Puppet.debug "methodname does not respond to %s" % $1
+ next
+ end
+ obj = obj.method($1)
+ end
+
+ if check_arity(obj, args.size)
+ if @service_hook.nil?
+ return obj.call(*args)
+ else
+ return @service_hook.call(obj, *args)
+ end
+ else
+ Puppet.debug "arity is incorrect"
+ end
+ end
+
+ if @default_handler.nil?
+ raise XMLRPC::FaultException.new(
+ ERR_METHOD_MISSING,
+ "Method #{methodname} missing or wrong number of parameters!"
+ )
+ else
+ @default_handler.call(methodname, *args)
+ end
+ end
+ end
+end
+end