diff --git a/lib/puppet/network/http_server.rb b/lib/puppet/network/http_server.rb new file mode 100644 index 000000000..e3826a654 --- /dev/null +++ b/lib/puppet/network/http_server.rb @@ -0,0 +1,3 @@ +# Just a stub, so we can correctly scope other classes. +module Puppet::Network::HTTPServer # :nodoc: +end diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb new file mode 100644 index 000000000..ce0401ad2 --- /dev/null +++ b/lib/puppet/network/http_server/mongrel.rb @@ -0,0 +1,130 @@ +#!/usr/bin/env ruby +# File: 06-11-14-mongrel_xmlrpc.rb +# Author: Manuel Holtgrewe +# +# Copyright (c) 2006 Manuel Holtgrewe, 2007 Luke Kanies +# +# This file is based heavily on a file retrieved from +# http://ttt.ggnore.net/2006/11/15/xmlrpc-with-mongrel-and-ruby-off-rails/ + +require 'rubygems' +require 'mongrel' +require 'xmlrpc/server' +require 'puppet/network/xmlrpc/server' +require 'puppet/network/http_server' +require 'puppet/network/client_request' +require 'puppet/network/handler' + +require 'resolv' + +# This handler can be hooked into Mongrel to accept HTTP requests. After +# checking whether the request itself is sane, the handler forwards it +# to an internal instance of XMLRPC::BasicServer to process it. +# +# You can access the server by calling the Handler's "xmlrpc_server" +# attribute accessor method and add XMLRPC handlers there. For example: +# +#
+# handler = XmlRpcHandler.new
+# handler.xmlrpc_server.add_handler("my.add") { |a, b| a.to_i + b.to_i }
+# 
+module Puppet::Network + class HTTPServer::Mongrel < ::Mongrel::HttpHandler + attr_reader :xmlrpc_server + + def initialize(handlers) + if Puppet[:debug] + $mongrel_debug_client = true + Puppet.debug 'Mongrel client debugging enabled. [$mongrel_debug_client = true].' + end + # Create a new instance of BasicServer. We are supposed to subclass it + # but that does not make sense since we would not introduce any new + # behaviour and we have to subclass Mongrel::HttpHandler so our handler + # works for Mongrel. + @xmlrpc_server = Puppet::Network::XMLRPCServer.new + handlers.each do |name| + unless handler = Puppet::Network::Handler.handler(name) + raise ArgumentError, "Invalid handler #{name}" + end + @xmlrpc_server.add_handler(handler.interface, handler.new({})) + end + end + + # This method produces the same results as XMLRPC::CGIServer.serve + # from Ruby's stdlib XMLRPC implementation. + def process(request, response) + # Make sure this has been a POST as required for XMLRPC. + request_method = request.params[Mongrel::Const::REQUEST_METHOD] || Mongrel::Const::GET + if request_method != "POST" + response.start(405) { |head, out| out.write("Method Not Allowed") } + return + end + + # Make sure the user has sent text/xml data. + request_mime = request.params["CONTENT_TYPE"] || "text/plain" + if parse_content_type(request_mime).first != "text/xml" + response.start(400) { |head, out| out.write("Bad Request") } + return + end + + # Make sure there is data in the body at all. + length = request.params[Mongrel::Const::CONTENT_LENGTH].to_i + if length <= 0 + response.start(411) { |head, out| out.write("Length Required") } + return + end + + # Check the body to be valid. + if request.body.nil? or request.body.size != length + response.start(400) { |head, out| out.write("Bad Request") } + return + end + + info = client_info(request) + + # All checks above passed through + response.start(200) do |head, out| + head["Content-Type"] = "text/xml; charset=utf-8" + begin + out.write(@xmlrpc_server.process(request.body, info)) + rescue => detail + puts detail.backtrace + raise + end + end + end + + private + + def client_info(request) + params = request.params + ip = params["HTTP_X_FORWARDED_FOR"] ? params["HTTP_X_FORWARDED_FOR"].split(',').last.strip : params["REMOTE_ADDR"] + # JJM #906 The following dn.match regular expression is forgiving + # enough to match the two Distinguished Name string contents + # coming from Apache, Pound or other reverse SSL proxies. + if dn = params[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) + client = dn_matchdata[1].to_str + valid = (params[Puppet[:ssl_client_verify_header]] == 'SUCCESS') + else + begin + client = Resolv.getname(ip) + rescue => detail + Puppet.err "Could not resolve #{ip}: #{detail}" + client = "unknown" + end + valid = false + end + + info = Puppet::Network::ClientRequest.new(client, ip, valid) + + info + end + + # Taken from XMLRPC::ParseContentType + def parse_content_type(str) + a, *b = str.split(";") + return a.strip, *b + end + end +end +