require 'xmlrpc/server' require 'puppet/network/authorization' require 'puppet/network/xmlrpc/processor' module Puppet::Network::XMLRPC class ServletError < RuntimeError; end class WEBrickServlet < ::XMLRPC::WEBrickServlet include Puppet::Network::XMLRPCProcessor # This is a hackish way to avoid an auth message every time we have a # normal operation def self.log(msg) unless defined? @logs @logs = {} end if @logs.include?(msg) @logs[msg] += 1 else Puppet.info msg @logs[msg] = 1 end end # Accept a list of handlers and register them all. def initialize(handlers) # 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() setup_processor() # Set up each of the passed handlers. handlers.each do |handler| add_handler(handler.class.interface, handler) end end # Handle the actual request. We can't use the super() method, because # we need to pass a ClientRequest object to process() so we can do # authorization. It's the only way to stay thread-safe. def service(request, response) if @valid_ip raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip } end if request.request_method != "POST" raise WEBrick::HTTPStatus::MethodNotAllowed, "unsupported method `#{request.request_method}'." end if parse_content_type(request['Content-type']).first != "text/xml" raise WEBrick::HTTPStatus::BadRequest end length = (request['Content-length'] || 0).to_i raise WEBrick::HTTPStatus::LengthRequired unless length > 0 data = request.body if data.nil? or data.size != length raise WEBrick::HTTPStatus::BadRequest end resp = process(data, client_request(request)) if resp.nil? or resp.size <= 0 raise WEBrick::HTTPStatus::InternalServerError end response.status = 200 response['Content-Length'] = resp.size response['Content-Type'] = "text/xml; charset=utf-8" response.body = resp end private # Generate a ClientRequest object for later validation. def client_request(request) if peer = request.peeraddr client = peer[2] clientip = peer[3] else raise ::XMLRPC::FaultException.new( ERR_UNCAUGHT_EXCEPTION, "Could not retrieve client information" ) end # If they have a certificate (which will almost always be true) # then we get the hostname from the cert, instead of via IP # info valid = false if cert = request.client_cert nameary = cert.subject.to_a.find { |ary| ary[0] == "CN" } if nameary.nil? Puppet.warning "Could not retrieve server name from cert" else unless client == nameary[1] Puppet.debug "Overriding %s with cert name %s" % [client, nameary[1]] client = nameary[1] end valid = true end end info = Puppet::Network::ClientRequest.new(client, clientip, valid) return info end end end # $Id: webrick_servlet.rb 2259 2007-03-06 19:03:05Z luke $