#!/usr/bin/env ruby

require 'vendor/scgi'
require 'drb'
require 'set'
include SCGI


class SCGIFixed
  alias_method :env, :env_table
end


class NitroProcessor < SCGIProcessor
    def process_request(request, body, socket)
        return if socket.closed?
        cgi = SCGIFixed.new(request, body, socket)
        begin
            # Nitro *is* thread safe!
            synchronize do
                Nitro::Cgi.process($server, cgi, cgi.stdinput, cgi.stdoutput)
            end
        rescue IOError
            @log.error("received IOError #$! when handling client.  Your web server doesn't like me.")
        rescue Object => nitro_error
            @log.error("calling Dispatcher.dispatch", nitro_error)
        end
    end
end


class NitroController
    attr_reader :restart_requested
    
    def initialize(config)
        @config = config
        @restart_requested = false
        
        if not File.exist? @config
            STDERR.puts("No configuration file given and #{config} not found.")
            exit 1
        end

        settings = YAML.load_file(@config)
        ENV["NITRO_ENV"] = settings[:env]

        require(settings[:run] || 'run')
        $server = Server.new.start
        
        @nitro = NitroProcessor.new(settings)
        @log = SCGI::LogFactory.instance.create(settings[:log_file] || "log/scgi.log")

        @log.info("Running in #{@nitro.settings[:env]} mode on #{@nitro.settings[:host]}:#{@nitro.settings[:port]}")
    end

    def run
        secure_drb_methods(["status","reconfigure","shutdown","restart"])
        DRb.start_service(@nitro.settings[:control_url], self)
        trap("INT") { @nitro.shutdown }
        @nitro.listen
    end
    
    def status(password)
        verify_password(password)
        @nitro.status
    end
    
    def reconfigure(password)
        verify_password(password)
        @log.info("Reconfiguring via #@config...")
        @nitro.configure(YAML.load_file(@config))
        @log.info("Done.")
        return true
    end

    def shutdown(password, force = false)
        verify_password(password)
        @nitro.shutdown(force)
    end
        
    def restart(password, force = true)
        verify_password(password)
        @restart_requested = true
        @log.info("Restart requested, running #{__FILE__} #{ARGV[0]} as command.")
        @nitro.shutdown(force)
    end
    
    private

    def verify_password(password)
        image = @nitro.settings[:password]
        pc = password.crypt(image[0..2])
        if pc != image
            raise "Authentication failed"
        end
    end
    
    # DRb only allows blacklist exclusion, which is super bad.  What we
    # really need is "ALLOW_ONLY_METHODS".  This method does that by listing
    # all the methods this object has, then removing the methods listed in
    # allowed_methods.  What remains is a block list for everything else which
    # we configure in DRb::DRbServer::INSECURE_METHODS.
    def secure_drb_methods(allowed_methods)
        all_methods = self.methods
        bad_methods = all_methods - allowed_methods
        
        DRb::DRbServer::INSECURE_METHOD << bad_methods.to_a
        DRb::DRbServer::INSECURE_METHOD.flatten!
    end
end


config = ARGV[0] || ENV["SCGI_CONFIG"] || "conf/scgi.yaml"
control = NitroController.new(config)
control.run

# check to see if a restart was requested rather than a shutdown, then start up the new one
if control.restart_requested
    exec "ruby", __FILE__, config
end
