summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/http_server
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/network/http_server')
-rw-r--r--lib/puppet/network/http_server/mongrel.rb139
-rw-r--r--lib/puppet/network/http_server/webrick.rb163
2 files changed, 302 insertions, 0 deletions
diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb
new file mode 100644
index 000000000..ba4b048b3
--- /dev/null
+++ b/lib/puppet/network/http_server/mongrel.rb
@@ -0,0 +1,139 @@
+#!/usr/bin/env ruby
+# File: 06-11-14-mongrel_xmlrpc.rb
+# Author: Manuel Holtgrewe <purestorm at ggnore.net>
+#
+# 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/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:
+#
+# <pre>
+# handler = XmlRpcHandler.new
+# handler.xmlrpc_server.add_handler("my.add") { |a, b| a.to_i + b.to_i }
+# </pre>
+module Puppet::Network
+ class HTTPServer::Mongrel < ::Mongrel::HttpHandler
+ include Puppet::Daemon
+ attr_reader :xmlrpc_server
+
+ def initialize(handlers)
+ # 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"]
+ if dn = params[Puppet[:ssl_client_header]] and dn.include?("/CN=")
+ client = dn.sub("/CN=", '')
+ valid = (params[Puppet[:ssl_client_verify_header]] == 'SUCCESS')
+ else
+ client = Resolv.getname(ip)
+ 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
+
diff --git a/lib/puppet/network/http_server/webrick.rb b/lib/puppet/network/http_server/webrick.rb
new file mode 100644
index 000000000..2cf85b7b6
--- /dev/null
+++ b/lib/puppet/network/http_server/webrick.rb
@@ -0,0 +1,163 @@
+require 'puppet'
+require 'puppet/daemon'
+require 'webrick'
+require 'webrick/https'
+require 'fcntl'
+
+require 'puppet/sslcertificates/support'
+require 'puppet/network/xmlrpc/webrick_servlet'
+require 'puppet/network/http_server'
+require 'puppet/network/client'
+
+module Puppet
+ class ServerError < RuntimeError; end
+ module Network
+ # The old-school, pure ruby webrick server, which is the default serving
+ # mechanism.
+ class HTTPServer::WEBrick < WEBrick::HTTPServer
+ include Puppet::Daemon
+ include Puppet::SSLCertificates::Support
+
+ # Read the CA cert and CRL and populate an OpenSSL::X509::Store
+ # with them, with flags appropriate for checking client
+ # certificates for revocation
+ def x509store
+ if Puppet[:cacrl] == 'none'
+ # No CRL, no store needed
+ return nil
+ end
+ unless File.exist?(Puppet[:cacrl])
+ raise Puppet::Error, "Could not find CRL"
+ end
+ crl = OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl]))
+ store = OpenSSL::X509::Store.new
+ store.purpose = OpenSSL::X509::PURPOSE_ANY
+ store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK
+ unless self.ca_cert
+ raise Puppet::Error, "No CA certificate"
+ end
+
+ store.add_file(Puppet[:localcacert])
+ store.add_crl(crl)
+ return store
+ end
+
+ # Set up the http log.
+ def httplog
+ args = []
+
+ # yuck; separate http logs
+ file = nil
+ Puppet.settings.use(:main, :ssl, Puppet[:name])
+ if Puppet[:name] == "puppetmasterd"
+ file = Puppet[:masterhttplog]
+ else
+ file = Puppet[:httplog]
+ end
+
+ # open the log manually to prevent file descriptor leak
+ file_io = open(file, "a+")
+ file_io.sync
+ file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+
+ args << file_io
+ if Puppet[:debug]
+ args << WEBrick::Log::DEBUG
+ end
+
+ log = WEBrick::Log.new(*args)
+
+
+ return log
+ end
+
+ # Create our server, yo.
+ def initialize(hash = {})
+ Puppet.info "Starting server for Puppet version %s" % Puppet.version
+
+ if handlers = hash[:Handlers]
+ handler_instances = setup_handlers(handlers)
+ else
+ raise ServerError, "A server must have handlers"
+ end
+
+ unless self.read_cert
+ if ca = handler_instances.find { |handler| handler.is_a?(Puppet::Network::Handler.ca) }
+ request_cert(ca)
+ else
+ raise Puppet::Error, "No certificate and no CA; cannot get cert"
+ end
+ end
+
+ setup_webrick(hash)
+
+ super(hash)
+
+ # make sure children don't inherit the sockets
+ listeners.each { |sock|
+ sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+ }
+
+ Puppet.info "Listening on port %s" % hash[:Port]
+
+ # this creates a new servlet for every connection,
+ # but all servlets have the same list of handlers
+ # thus, the servlets can have their own state -- passing
+ # around the requests and such -- but the handlers
+ # have a global state
+
+ # mount has to be called after the server is initialized
+ servlet = Puppet::Network::XMLRPC::WEBrickServlet.new(
+ handler_instances)
+ self.mount("/RPC2", servlet)
+ end
+
+ # Create a ca client to set up our cert for us.
+ def request_cert(ca)
+ client = Puppet::Network::Client.ca.new(:CA => ca)
+ unless client.request_cert
+ raise Puppet::Error, "Could get certificate"
+ end
+ end
+
+ # Create all of our handler instances.
+ def setup_handlers(handlers)
+ unless handlers.is_a?(Hash)
+ raise ServerError, "Handlers must have arguments"
+ end
+
+ handlers.collect { |handler, args|
+ hclass = nil
+ unless hclass = Handler.handler(handler)
+ raise ServerError, "Invalid handler %s" % handler
+ end
+ hclass.new(args)
+ }
+ end
+
+ # Handle all of the many webrick arguments.
+ def setup_webrick(hash)
+ hash[:Port] ||= Puppet[:masterport]
+ hash[:Logger] ||= self.httplog
+ hash[:AccessLog] ||= [
+ [ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
+ [ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ]
+ ]
+
+ hash[:SSLCertificateStore] = x509store
+ hash[:SSLCertificate] = self.cert
+ hash[:SSLPrivateKey] = self.key
+ hash[:SSLStartImmediately] = true
+ hash[:SSLEnable] = true
+ hash[:SSLCACertificateFile] = Puppet[:localcacert]
+ hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER
+ hash[:SSLCertName] = nil
+
+ if addr = Puppet[:bindaddress] and addr != ""
+ hash[:BindAddress] = addr
+ end
+ end
+ end
+ end
+end
+