summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/http/webrick.rb
blob: 52aec1bf14d66c9dfe07bf177c0cb7f9c9c8c574 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
require 'webrick'
require 'webrick/https'
require 'puppet/network/http/webrick/rest'
require 'puppet/network/xmlrpc/webrick_servlet'
require 'thread'

require 'puppet/ssl/certificate'
require 'puppet/ssl/certificate_revocation_list'

class Puppet::Network::HTTP::WEBrick
  def initialize(args = {})
    @listening = false
    @mutex = Mutex.new
  end

  def self.class_for_protocol(protocol)
    return Puppet::Network::HTTP::WEBrickREST if protocol.to_sym == :rest
    raise "Unknown protocol [#{protocol}]."
  end

  def listen(args = {})
    raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty?
    raise ArgumentError, ":address must be specified." unless args[:address]
    raise ArgumentError, ":port must be specified." unless args[:port]

    @protocols = args[:protocols]
    @xmlrpc_handlers = args[:xmlrpc_handlers]

    arguments = {:BindAddress => args[:address], :Port => args[:port]}
    arguments.merge!(setup_logger)
    arguments.merge!(setup_ssl)

    @server = WEBrick::HTTPServer.new(arguments)
    @server.listeners.each { |l| l.start_immediately = false }

    setup_handlers

    @mutex.synchronize do
      raise "WEBrick server is already listening" if @listening
      @listening = true
      @thread = Thread.new {
        @server.start { |sock|
          raise "Client disconnected before connection could be established" unless IO.select([sock],nil,nil,6.2)
          sock.accept
          @server.run(sock)
        }
      }
      sleep 0.1 until @server.status == :Running
    end
  end

  def unlisten
    @mutex.synchronize do
      raise "WEBrick server is not listening" unless @listening
      @server.shutdown
      @thread.join
      @server = nil
      @listening = false
    end
  end

  def listening?
    @mutex.synchronize do
      @listening
    end
  end

  # Configure our http log file.
  def setup_logger
    # Make sure the settings are all ready for us.
    Puppet.settings.use(:main, :ssl, Puppet[:name])

    if Puppet.run_mode.master?
      file = Puppet[:masterhttplog]
    else
      file = Puppet[:httplog]
    end

    # open the log manually to prevent file descriptor leak
    file_io = ::File.open(file, "a+")
    file_io.sync
    file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)

    args = [file_io]
    args << WEBrick::Log::DEBUG if Puppet::Util::Log.level == :debug

    logger = WEBrick::Log.new(*args)
    return :Logger => logger, :AccessLog => [
      [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
      [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT ]
    ]
  end

  # Add all of the ssl cert information.
  def setup_ssl
    results = {}

    # Get the cached copy.  We know it's been generated, too.
    host = Puppet::SSL::Host.localhost

    raise Puppet::Error, "Could not retrieve certificate for #{host.name} and not running on a valid certificate authority" unless host.certificate

    results[:SSLPrivateKey] = host.key.content
    results[:SSLCertificate] = host.certificate.content
    results[:SSLStartImmediately] = true
    results[:SSLEnable] = true

    raise Puppet::Error, "Could not find CA certificate" unless Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME)

    results[:SSLCACertificateFile] = Puppet[:localcacert]
    results[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER

    results[:SSLCertificateStore] = host.ssl_store

    results
  end

  private

  def setup_handlers
    # Set up the new-style protocols.
    klass = self.class.class_for_protocol(:rest)
    @server.mount('/', klass, :this_value_is_apparently_necessary_but_unused)

    # And then set up xmlrpc, if configured.
    @server.mount("/RPC2", xmlrpc_servlet) if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty?
  end

  # Create our xmlrpc servlet, which provides backward compatibility.
  def xmlrpc_servlet
    handlers = @xmlrpc_handlers.collect { |handler|
      unless hclass = Puppet::Network::Handler.handler(handler)
        raise "Invalid xmlrpc handler #{handler}"
      end
      hclass.new({})
    }
    Puppet::Network::XMLRPC::WEBrickServlet.new handlers
  end
end