diff --git a/lib/puppet/network/http_pool.rb b/lib/puppet/network/http_pool.rb index 99f09a90c..69574d8fd 100644 --- a/lib/puppet/network/http_pool.rb +++ b/lib/puppet/network/http_pool.rb @@ -1,92 +1,95 @@ require 'puppet/sslcertificates/support' require 'net/https' +module Puppet::Network +end + # Manage Net::HTTP instances for keep-alive. module Puppet::Network::HttpPool # This handles reading in the key and such-like. extend Puppet::SSLCertificates::Support @http_cache = {} # Clear our http cache, closing all connections. def self.clear_http_instances @http_cache.each do |name, connection| connection.finish if connection.started? end @http_cache.clear @cert = nil @key = nil end # Make sure we set the driver up when we read the cert in. def self.read_cert if val = super # This calls read_cert from the Puppet::SSLCertificates::Support module. # Clear out all of our connections, since they previously had no cert and now they # should have them. clear_http_instances return val else return false end end # Use cert information from a Puppet client to set up the http object. def self.cert_setup(http) # Just no-op if we don't have certs. return false unless (defined?(@cert) and @cert) or self.read_cert store = OpenSSL::X509::Store.new store.add_file Puppet[:localcacert] store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT http.cert_store = store http.ca_file = Puppet[:localcacert] http.cert = self.cert http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.key = self.key end # Retrieve a cached http instance of caching is enabled, else return # a new one. def self.http_instance(host, port, reset = false) # We overwrite the uninitialized @http here with a cached one. key = "%s:%s" % [host, port] # Return our cached instance if we've got a cache, as long as we're not # resetting the instance. return @http_cache[key] if ! reset and @http_cache[key] # Clean up old connections if we have them. if http = @http_cache[key] @http_cache.delete(key) http.finish if http.started? end args = [host, port] if Puppet[:http_proxy_host] == "none" args << nil << nil else args << Puppet[:http_proxy_host] << Puppet[:http_proxy_port] end http = Net::HTTP.new(*args) # Pop open the http client a little; older versions of Net::HTTP(s) didn't # give us a reader for ca_file... Grr... class << http; attr_accessor :ca_file; end http.use_ssl = true http.read_timeout = 120 http.open_timeout = 120 # JJM Configurable fix for #896. if Puppet[:http_enable_post_connection_check] http.enable_post_connection_check = true else http.enable_post_connection_check = false end cert_setup(http) @http_cache[key] = http return http end end diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb index d340f3d63..6b2325d29 100644 --- a/lib/puppet/network/http_server/mongrel.rb +++ b/lib/puppet/network/http_server/mongrel.rb @@ -1,151 +1,152 @@ #!/usr/bin/env ruby # File: 06-11-14-mongrel_xmlrpc.rb # Author: Manuel Holtgrewe # # Copyright (c) 2006 Manuel Holtgrewe, 2007 Luke Kanies # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # 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 'puppet/daemon' 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 include Puppet::Daemon 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, args| unless handler = Puppet::Network::Handler.handler(name) raise ArgumentError, "Invalid handler %s" % name end @xmlrpc_server.add_handler(handler.interface, handler.new(args)) 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" then 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" then 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 then 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 then 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["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 %s: %s" % [ip, detail] client = "unknown" end valid = false end info = Puppet::Network::ClientRequest.new(client, ip, valid) return info end # Taken from XMLRPC::ParseContentType def parse_content_type(str) a, *b = str.split(";") return a.strip, *b end end end