Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/msfdb
Views: 11655
#!/usr/bin/env ruby require 'fileutils' require 'io/console' require 'json' require 'net/http' require 'net/https' require 'open3' require 'optparse' require 'rex/socket' require 'rex/text' require 'securerandom' require 'uri' require 'yaml' require 'pg' include Rex::Text::Color msfbase = __FILE__ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] require 'msfdb_helpers/pg_ctlcluster' require 'msfdb_helpers/pg_ctl' require 'msfdb_helpers/standalone' require 'msfenv' @script_name = File.basename(__FILE__) @framework = File.expand_path(File.dirname(__FILE__)) @localconf = Msf::Config.config_directory @db = "#{@localconf}/db" @db_conf = "#{@localconf}/database.yml" @pg_cluster_conf_root = "#{@localconf}/.local/etc/postgresql" @db_driver = nil @ws_tag = 'msf-ws' @ws_conf = File.join(@framework, "#{@ws_tag}.ru") @ws_ssl_key_default = "#{@localconf}/#{@ws_tag}-key.pem" @ws_ssl_cert_default = "#{@localconf}/#{@ws_tag}-cert.pem" @ws_log = "#{@localconf}/logs/#{@ws_tag}.log" @ws_pid = "#{@localconf}/#{@ws_tag}.pid" @current_user = ENV['LOGNAME'] || ENV['USERNAME'] || ENV['USER'] @msf_ws_user = (@current_user || "msfadmin").to_s.strip @ws_generated_ssl = false @ws_api_token = nil @components = %w(database webservice) @environments = %w(production development) @options = { # When the component value is nil, the user has not yet specified a specific component # It will later be defaulted to a more sane value component: nil, debug: false, msf_db_name: 'msf', msf_db_user: 'msf', msftest_db_name: 'msftest', msftest_db_user: 'msftest', db_host: '127.0.0.1', db_port: 5433, db_pool: 200, address: 'localhost', port: 5443, daemon: true, ssl: true, ssl_cert: @ws_ssl_cert_default, ssl_key: @ws_ssl_key_default, ssl_disable_verify: true, ws_env: ENV['RACK_ENV'] || 'production', retry_max: 10, retry_delay: 5.0, ws_user: nil, add_data_service: false, data_service_name: nil, use_defaults: false, delete_existing_data: true } def supports_color? return true if Rex::Compat.is_windows term = Rex::Compat.getenv('TERM') term and term.match(/(?:vt10[03]|xterm(?:-color)?|linux|screen|rxvt)/i) != nil end class String def bold substitute_colors("%bld#{self}%clr") end def underline substitute_colors("%und#{self}%clr") end def red substitute_colors("%red#{self}%clr") end def green substitute_colors("%grn#{self}%clr") end def blue substitute_colors("%blu#{self}%clr") end def cyan substitute_colors("%cya#{self}%clr") end end def pw_gen SecureRandom.base64(32) end def tail(file) begin File.readlines(file).last.to_s.strip rescue nil end end def status_db update_db_port case @db_driver.status when DatabaseStatus::RUNNING puts "Database started" when DatabaseStatus::INACTIVE puts "Database found, but is not running" when DatabaseStatus::NEEDS_INIT puts "Database found, but needs initialized" when DatabaseStatus::NOT_FOUND puts "No database found" end end def start_db case @db_driver.status when DatabaseStatus::NOT_FOUND print_error 'No database found.' return when DatabaseStatus::NEEDS_INIT print_error 'Has the database been initialized with "msfdb init" or "msfdb init --component database"?' return end update_db_port db_started = @db_driver.start if !db_started last_log = tail("#{@db}/log") puts last_log if last_log =~ /not compatible/ puts 'Please attempt to upgrade the database manually using pg_upgrade.' end print_error 'Your database may be corrupt. Try reinitializing.' end end def stop_db update_db_port @db_driver.stop end def restart_db @db_driver.restart end def init_db case @db_driver.status when DatabaseStatus::RUNNING puts 'Existing database running' return when DatabaseStatus::INACTIVE puts 'Existing database found, attempting to start it' @db_driver.start return end if @db_driver.exists? && !@options[:delete_existing_data] if !load_db_config puts 'Failed to load existing database config. Please reinit and overwrite the file.' return end end # Generate new database passwords if not already assigned @msf_pass ||= pw_gen @msftest_pass ||= pw_gen @db_driver.init(@msf_pass, @msftest_pass) write_db_config puts 'Creating initial database schema' Dir.chdir(@framework) do @db_driver.run_cmd('bundle exec rake db:migrate') end puts 'Database initialization successful'.green.bold.to_s end def load_db_config if File.file?(@db_conf) config = YAML.load(File.read(@db_conf)) production = config['production'] if production.nil? puts "No production section found in database config #{@db_conf}." return false end test = config['test'] if test.nil? puts "No test section found in database config #{@db_conf}." return false end # get values for development and production @options[:msf_db_name] = production['database'] @options[:msf_db_user] = production['username'] @msf_pass = production['password'] @options[:db_port] = production['port'] @options[:db_pool] = production['pool'] # get values for test @options[:msftest_db_name] = test['database'] @options[:msftest_db_user] = test['username'] @msftest_pass = test['password'] return true end return false end def write_db_config # Write a default database config file Dir.mkdir(@localconf) unless File.directory?(@localconf) File.open(@db_conf, 'w') do |f| f.puts <<~EOF development: &pgsql adapter: postgresql database: #{@options[:msf_db_name]} username: #{@options[:msf_db_user]} password: #{@msf_pass} host: #{@options[:db_host]} port: #{@options[:db_port]} pool: #{@options[:db_pool]} production: &production <<: *pgsql test: <<: *pgsql database: #{@options[:msftest_db_name]} username: #{@options[:msftest_db_user]} password: #{@msftest_pass} EOF end File.chmod(0640, @db_conf) end def update_db_port if File.file?(@db_conf) config = begin YAML.load_file(@db_conf, aliases: true) || {} rescue ArgumentError YAML.load_file(@db_conf) || {} end if config["production"] && config["production"]["port"] port = config["production"]["port"] if port != @options[:db_port] puts "Using database port #{port} found in #{@db_conf}" @options[:db_port] = port end end end end def ask_yn(question, default: nil) loop do print "#{'[?]'.blue.bold} #{question} [#{default}]: " input = STDIN.gets.strip input = input.empty? ? default : input case input when /^[Yy]/ return true when /^[Nn]/ return false else puts 'Please answer yes or no.' end end end def ask_value(question, default) return default if @options[:use_defaults] print "#{'[?]'.blue.bold} #{question} [#{default}]: " input = STDIN.gets.strip if input.nil? || input.empty? return default else return input end end def ask_password(question) print "#{'[?]'.blue.bold} #{question}: " input = STDIN.noecho(&:gets).chomp print "\n" if input.nil? || input.empty? return pw_gen else return input end end def print_error(error) puts "#{'[!]'.red.bold} #{error}" end def delete_db stop_web_service @db_driver.delete end def reinit_db delete_db init_db end def print_webservice_removal_prompt $stderr.puts "#{'[WARNING]'.red} The remote web service is being removed. Does this impact you? React here: https://github.com/rapid7/metasploit-framework/issues/18439" end class WebServicePIDStatus RUNNING = 0 INACTIVE = 1 NO_PID_FILE = 2 end class DatabaseStatus RUNNING = 0 INACTIVE = 1 NOT_FOUND = 2 NEEDS_INIT = 3 end def web_service_pid File.file?(@ws_pid) ? tail(@ws_pid) : nil end def web_service_pid_status if File.file?(@ws_pid) ws_pid = tail(@ws_pid) if ws_pid.nil? || !process_active?(ws_pid.to_i) WebServicePIDStatus::INACTIVE else WebServicePIDStatus::RUNNING end else WebServicePIDStatus::NO_PID_FILE end end def status_web_service ws_pid = web_service_pid status = web_service_pid_status if status == WebServicePIDStatus::RUNNING puts "MSF web service is running as PID #{ws_pid}" elsif status == WebServicePIDStatus::INACTIVE puts "MSF web service is not running: PID file found at #{@ws_pid}, but no active process running as PID #{ws_pid}" elsif status == WebServicePIDStatus::NO_PID_FILE puts "MSF web service is not running: no PID file found at #{@ws_pid}" end end def init_web_service if web_service_pid_status == WebServicePIDStatus::RUNNING puts "MSF web service is already running as PID #{web_service_pid}" return false end unless @options[:use_defaults] if @options[:ws_user].nil? @msf_ws_user = ask_value('Initial MSF web service account username?', @msf_ws_user) else @msf_ws_user = @options[:ws_user] end end if @options[:use_defaults] @msf_ws_pass = pw_gen elsif @options[:ws_pass].nil? @msf_ws_pass = ask_password('Initial MSF web service account password? (Leave blank for random password)') else @msf_ws_pass = @options[:ws_pass] end if should_generate_web_service_ssl && @options[:delete_existing_data] generate_web_service_ssl(key: @options[:ssl_key], cert: @options[:ssl_cert]) end if start_web_service(expect_auth: false) if add_web_service_workspace && add_web_service_user output_web_service_information else puts 'Failed to complete MSF web service configuration, please reinitialize.' stop_web_service end end end def start_web_service_daemon(expect_auth:) if @db_driver.run_cmd("#{thin_cmd} start") == 0 # wait until web service is online retry_count = 0 response_data = web_service_online_check(expect_auth: expect_auth) is_online = response_data[:state] != :offline while !is_online && retry_count < @options[:retry_max] retry_count += 1 if @options[:debug] puts "MSF web service doesn't appear to be online. Sleeping #{@options[:retry_delay]}s until check #{retry_count}/#{@options[:retry_max]}" end sleep(@options[:retry_delay]) response_data = web_service_online_check(expect_auth: expect_auth) is_online = response_data[:state] != :offline end if response_data[:state] == :online puts "#{'success'.green.bold}" puts 'MSF web service started and online' return true elsif response_data[:state] == :error puts "#{'failed'.red.bold}" print_error 'MSF web service failed and returned the following message:' puts "#{response_data[:message].nil? || response_data[:message].empty? ? "No message returned." : response_data[:message]}" elsif response_data[:state] == :offline puts "#{'failed'.red.bold}" print_error 'A connection with the web service was refused.' end puts "Please see #{@ws_log} for additional webservice details." return false else puts "#{'failed'.red.bold}" puts 'Failed to start MSF web service' return false end end def start_web_service(expect_auth: true) unless File.file?(@ws_conf) puts "No MSF web service configuration found at #{@ws_conf}, not starting" return false end # check if MSF web service is already started ws_pid = web_service_pid status = web_service_pid_status if status == WebServicePIDStatus::RUNNING puts "MSF web service is already running as PID #{ws_pid}" return false elsif status == WebServicePIDStatus::INACTIVE puts "MSF web service PID file found, but no active process running as PID #{ws_pid}" puts "Deleting MSF web service PID file #{@ws_pid}" File.delete(@ws_pid) end print 'Attempting to start MSF web service...' unless File.file?(@options[:ssl_key]) puts "#{'failed'.red.bold}" print_error "The SSL Key needed for the webservice to connect to the database could not be found at #{@options[:ssl_key]}." print_error 'Has the webservice been initialized with "msfdb init" or "msfdb init --component webservice"?' return false end if @options[:daemon] start_web_service_daemon(expect_auth: expect_auth) else puts thin_cmd system "#{thin_cmd} start" end end def stop_web_service ws_pid = web_service_pid status = web_service_pid_status if status == WebServicePIDStatus::RUNNING puts "Stopping MSF web service PID #{ws_pid}" @db_driver.run_cmd("#{thin_cmd} stop") else puts 'MSF web service is no longer running' if status == WebServicePIDStatus::INACTIVE puts "Deleting MSF web service PID file #{@ws_pid}" File.delete(@ws_pid) end end end def restart_web_service stop_web_service start_web_service end def delete_web_service stop_web_service File.delete(@ws_pid) if web_service_pid_status == WebServicePIDStatus::INACTIVE if @options[:delete_existing_data] File.delete(@options[:ssl_key]) if File.file?(@options[:ssl_key]) File.delete(@options[:ssl_cert]) if File.file?(@options[:ssl_cert]) end end def reinit_web_service delete_web_service init_web_service end def generate_web_service_ssl(key:, cert:) @ws_generated_ssl = true if (File.file?(key) || File.file?(cert)) && !@options[:delete_existing_data] return end puts 'Generating SSL key and certificate for MSF web service' @ssl_key, @ssl_cert, @ssl_extra_chain_cert = Rex::Socket::Ssl.ssl_generate_certificate # write PEM format key and certificate mode = 'wb' mode_int = 0600 File.open(key, mode) { |f| f.write(@ssl_key.to_pem) } File.chmod(mode_int, key) File.open(cert, mode) { |f| f.write(@ssl_cert.to_pem) } File.chmod(mode_int, cert) end def web_service_online_check(expect_auth:) msf_version_uri = get_web_service_uri(path: '/api/v1/msf/version') response_data = http_request(uri: msf_version_uri, method: :get, skip_verify: skip_ssl_verify?, cert: get_ssl_cert) if !response_data[:exception].nil? && response_data[:exception].is_a?(Errno::ECONNREFUSED) response_data[:state] = :offline elsif !response_data[:exception].nil? && response_data[:exception].is_a?(OpenSSL::OpenSSLError) response_data[:state] = :error response_data[:message] = 'Detected an SSL issue. Please set the same options used to initialize the web service or reinitialize.' elsif !response_data[:response].nil? && response_data[:response].dig(:error, :code) == 401 if expect_auth response_data[:state] = :online else response_data[:state] = :error response_data[:message] = 'MSF web service expects authentication. If you wish to reinitialize the web service account you will need to reinitialize the database.' end elsif !response_data[:response].nil? && !response_data[:response].dig(:data, :metasploit_version).nil? response_data[:state] = :online else response_data[:state] = :error end puts "web_service_online: expect_auth=#{expect_auth}, response_msg=#{response_data}" if @options[:debug] response_data end def add_web_service_workspace(name: 'default') # Send request to create new workspace workspace_data = { name: name } workspaces_uri = get_web_service_uri(path: '/api/v1/workspaces') response_data = http_request(uri: workspaces_uri, data: workspace_data, method: :post, skip_verify: skip_ssl_verify?, cert: get_ssl_cert) response = response_data[:response] puts "add_web_service_workspace: add workspace response=#{response}" if @options[:debug] if response.nil? || response.dig(:data, :name) != name print_error "Error creating MSF web service workspace '#{name}'" return false end return true end def add_web_service_user puts "Creating MSF web service user #{@msf_ws_user}" # Generate new web service user password cred_data = { username: @msf_ws_user, password: @msf_ws_pass } # Send request to create new admin user user_data = cred_data.merge({ admin: true }) user_uri = get_web_service_uri(path: '/api/v1/users') response_data = http_request(uri: user_uri, data: user_data, method: :post, skip_verify: skip_ssl_verify?, cert: get_ssl_cert) response = response_data[:response] puts "add_web_service_user: create user response=#{response}" if @options[:debug] if response.nil? || response.dig(:data, :username) != @msf_ws_user print_error "Error creating MSF web service user #{@msf_ws_user}" return false end puts "\n#{' ############################################################'.cyan}" print "#{' ## '.cyan}" print"#{'MSF Web Service Credentials'.cyan.bold.underline}" puts"#{' ##'.cyan}" puts "#{' ## ##'.cyan}" puts "#{' ## Please store these credentials securely. ##'.cyan}" puts "#{' ## You will need them to connect to the webservice. ##'.cyan}" puts "#{' ############################################################'.cyan}" puts "\n#{'MSF web service username'.cyan.bold}: #{@msf_ws_user}" puts "#{'MSF web service password'.cyan.bold}: #{@msf_ws_pass}" # Send request to create new API token for the user generate_token_uri = get_web_service_uri(path: '/api/v1/auth/generate-token') response_data = http_request(uri: generate_token_uri, data: cred_data, method: :post, skip_verify: skip_ssl_verify?, cert: get_ssl_cert) response = response_data[:response] puts "add_web_service_user: generate token response=#{response}" if @options[:debug] if response.nil? || (@ws_api_token = response.dig(:data, :token)).nil? print_error "Error creating MSF web service user API token" return false end puts "#{'MSF web service user API token'.cyan.bold}: #{@ws_api_token}" return true end def output_web_service_information puts "\n\n" puts 'MSF web service configuration complete' if @options[:add_data_service] data_service_name = @options[:data_service_name] || "local-#{@options[:ssl] ? 'https' : 'http'}-data-service" puts "The web service has been configured as your default data service in msfconsole with the name \"#{data_service_name}\"" else puts "No data service has been configured in msfconsole." end puts '' puts 'If needed, manually reconnect to the data service in msfconsole using the command:' puts "#{get_db_connect_command}" puts '' puts 'The username and password are credentials for the API account:' puts "#{get_web_service_uri(path: '/api/v1/auth/account')}" puts '' if @options[:add_data_service] persist_data_service end end def run_msfconsole_command(cmd) # Attempts to run a the metasploit command first with the default env settings, and once again with the path set # to the current directory. This ensures that it works in an environment such as bundler # @msf_command holds the initial common part of commands (msfconsole -qx) and takes the optional specific commands as arguments (#{cmd}) msf_command = "msfconsole -qx '#{cmd}'" if @db_driver.run_cmd(msf_command) != 0 # attempt to execute msfconsole in the current working directory if @db_driver.run_cmd(msf_command, env: {'PATH' => ".:#{ENV["PATH"]}"}) != 0 puts 'Failed to run msfconsole' end end end def persist_data_service puts 'Persisting http web data service credentials in msfconsole' # execute msfconsole commands to add and persist the data service connection cmd = "#{get_db_connect_command}; db_save; exit" run_msfconsole_command(cmd) end def get_db_connect_command data_service_name = "local-#{@options[:ssl] ? 'https' : 'http'}-data-service" if !@options[:data_service_name].nil? data_service_name = @options[:data_service_name] end # build db_remove and db_connect command based on install options connect_cmd = "db_connect" connect_cmd << " --name #{data_service_name}" connect_cmd << " --token #{@ws_api_token}" connect_cmd << " --cert #{@options[:ssl_cert]}" if @options[:ssl] connect_cmd << " --skip-verify" if skip_ssl_verify? connect_cmd << " #{get_web_service_uri}" connect_cmd end def get_web_service_uri(path: nil) uri_class = @options[:ssl] ? URI::HTTPS : URI::HTTP uri_class.build({host: get_web_service_host, port: @options[:port], path: path}) end def get_web_service_host # user specified any address INADDR_ANY (0.0.0.0), return a routable address @options[:address] == '0.0.0.0' ? 'localhost' : @options[:address] end def skip_ssl_verify? @ws_generated_ssl || @options[:ssl_disable_verify] end def get_ssl_cert @options[:ssl] ? @options[:ssl_cert] : nil end # TODO: In the future this can be replaced by Msf::WebServices::HttpDBManagerService def thin_cmd server_opts = "--rackup #{@ws_conf.shellescape} --address #{@options[:address].shellescape} --port #{@options[:port]}" ssl_opts = @options[:ssl] ? "--ssl --ssl-key-file #{@options[:ssl_key].shellescape} --ssl-cert-file #{@options[:ssl_cert].shellescape}" : '' ssl_opts << ' --ssl-disable-verify' if skip_ssl_verify? adapter_opts = "--environment #{@options[:ws_env]}" daemon_opts = "--daemonize --log #{@ws_log.shellescape} --pid #{@ws_pid.shellescape} --tag #{@ws_tag}" if @options[:daemon] all_opts = [server_opts, ssl_opts, adapter_opts, daemon_opts].reject(&:blank?).join(' ') "thin #{all_opts}" end def process_active?(pid) begin Process.kill(0, pid) true rescue Errno::ESRCH false end end def http_request(uri:, query: nil, data: nil, method: :get, headers: nil, skip_verify: false, cert: nil) all_headers = { 'User-Agent': @script_name } all_headers.merge!(headers) unless headers.nil? query_str = (!query.nil? && !query.empty?) ? URI.encode_www_form(query.compact) : nil uri.query = query_str http = Net::HTTP.new(uri.host, uri.port) if uri.is_a?(URI::HTTPS) http.use_ssl = true if skip_verify http.verify_mode = OpenSSL::SSL::VERIFY_NONE else # https://stackoverflow.com/questions/22093042/implementing-https-certificate-pubkey-pinning-with-ruby http.verify_mode = OpenSSL::SSL::VERIFY_PEER user_passed_cert = OpenSSL::X509::Certificate.new(File.read(cert)) http.verify_callback = lambda do |preverify_ok, cert_store| server_cert = cert_store.chain[0] return true unless server_cert.to_der == cert_store.current_cert.to_der same_public_key?(server_cert, user_passed_cert) end end end begin response_data = { response: nil } case method when :get request = Net::HTTP::Get.new(uri.request_uri, initheader=all_headers) when :post request = Net::HTTP::Post.new(uri.request_uri, initheader=all_headers) else raise Exception, "Request method #{method} is not handled" end request.content_type = 'application/json' unless data.nil? json_body = data.to_json request.body = json_body end response = http.request(request) unless response.body.nil? || response.body.empty? response_data[:response] = JSON.parse(response.body, symbolize_names: true) end rescue => e response_data[:exception] = e puts "Problem with HTTP #{method} request #{uri.request_uri}, message: #{e.message}" if @options[:debug] end response_data end # Tells us whether the private keys on the passed certificates match # and use the same algo def same_public_key?(ref_cert, actual_cert) pkr, pka = ref_cert.public_key, actual_cert.public_key # First check if the public keys use the same crypto... return false unless pkr.class == pka.class # ...and then - that they have the same contents return false unless pkr.to_pem == pka.to_pem true end def parse_args(args) subtext = <<~USAGE Commands: init initialize the component reinit delete and reinitialize the component delete delete and stop the component status check component status start start the component stop stop the component restart restart the component USAGE parser = OptionParser.new do |opts| opts.banner = "Usage: #{@script_name} [options] <command>" opts.separator('Manage a Metasploit Framework database and web service') opts.separator('') opts.separator('General Options:') opts.on('--component COMPONENT', @components + ['all'], 'Component used with provided command (default: database)', " (#{@components.join(', ')})") { |component| @options[:component] = component.to_sym } opts.on('-d', '--debug', 'Enable debug output') { |d| @options[:debug] = d } opts.on('-h', '--help', 'Show this help message') { puts opts exit } opts.on('--use-defaults', 'Accept all defaults and do not prompt for options during an init') { |d| @options[:use_defaults] = d } opts.separator('') opts.separator('Database Options:') opts.on('--msf-db-name NAME', "Database name (default: #{@options[:msf_db_name]})") { |n| @options[:msf_db_name] = n } opts.on('--msf-db-user-name USER', "Database username (default: #{@options[:msf_db_user]})") { |u| @options[:msf_db_user] = u } opts.on('--msf-test-db-name NAME', "Test database name (default: #{@options[:msftest_db_name]})") { |n| @options[:msftest_db_name] = n } opts.on('--msf-test-db-user-name USER', "Test database username (default: #{@options[:msftest_db_user]})") { |u| @options[:msftest_db_user] = u } opts.on('--db-port PORT', Integer, "Database port (default: #{@options[:db_port]})") { |p| @options[:db_port] = p } opts.on('--db-pool MAX', Integer, "Database connection pool size (default: #{@options[:db_pool]})") { |m| @options[:db_pool] = m } opts.on('--connection-string URI', 'Use a pre-existing database cluster for initialization', 'Example: --connection-string=postgresql://postgres:mysecretpassword@localhost:5432/postgres') { |c| @connection_string = c } opts.separator('') opts.separator('Web Service Options:') opts.on('-a', '--address ADDRESS', "Bind to host address (default: #{@options[:address]})") { |a| @options[:address] = a } opts.on('-p', '--port PORT', Integer, "Web service port (default: #{@options[:port]})") { |p| @options[:port] = p } opts.on('--[no-]daemon', 'Enable daemon') { |d| @options[:daemon] = d } opts.on('--[no-]ssl', "Enable SSL (default: #{@options[:ssl]})") { |s| @options[:ssl] = s } opts.on('--ssl-key-file PATH', "Path to private key (default: #{@options[:ssl_key]})") { |p| @options[:ssl_key] = p } opts.on('--ssl-cert-file PATH', "Path to certificate (default: #{@options[:ssl_cert]})") { |p| @options[:ssl_cert] = p } opts.on('--[no-]ssl-disable-verify', "Disables (optional) client cert requests (default: #{@options[:ssl_disable_verify]})") { |v| @options[:ssl_disable_verify] = v } opts.on('--environment ENV', @environments, "Web service framework environment (default: #{@options[:ws_env]})", " (#{@environments.join(', ')})") { |e| @options[:ws_env] = e } opts.on('--retry-max MAX', Integer, "Maximum number of web service connect attempts (default: #{@options[:retry_max]})") { |m| @options[:retry_max] = m } opts.on('--retry-delay DELAY', Float, "Delay in seconds between web service connect attempts (default: #{@options[:retry_delay]})") { |d| @options[:retry_delay] = d } opts.on('--user USER', 'Initial web service admin username') { |u| @options[:ws_user] = u } opts.on('--pass PASS', 'Initial web service admin password') { |p| @options[:ws_pass] = p } opts.on('--[no-]msf-data-service NAME', 'Local msfconsole data service connection name') { |n| if !n @options[:add_data_service] = false else @options[:add_data_service] = true @options[:data_service_name] = n end } opts.separator('') opts.separator(subtext) end parser.parse!(args) if args.length != 1 puts parser abort end @options end def invoke_command(commands, component, command) method = commands[component][command] if !method.nil? send(method) else print_error "Error: unrecognized command '#{command}' for #{component}" end end def installed?(cmd) !Msf::Util::Helper.which(cmd).nil? end def has_requirements(postgresql_cmds) ret_val = true other_cmds = %w(bundle thin) missing_msg = "Missing requirement: %<name>s does not appear to be installed or '%<prog>s' is not in the environment path" postgresql_cmds.each do |cmd| next unless Msf::Util::Helper.which(cmd).nil? puts missing_msg % { name: 'PostgreSQL', prog: cmd } ret_val = false end other_cmds.each do |cmd| if Msf::Util::Helper.which(cmd).nil? puts missing_msg % { name: "'#{cmd}'", prog: cmd } ret_val = false end end ret_val end def should_generate_web_service_ssl @options[:ssl] && ((!File.file?(@options[:ssl_key]) || !File.file?(@options[:ssl_cert])) || (@options[:ssl_key] == @ws_ssl_key_default && @options[:ssl_cert] == @ws_ssl_cert_default)) end def prompt_for_component(command) if command == :status || command == :delete return :all end if command == :stop && web_service_pid_status != WebServicePIDStatus::RUNNING return :database end if @options[:add_data_service] == true :all else :database end end def prompt_for_deletion(command) destructive_operations = [:reinit, :delete] if destructive_operations.include? command @options[:delete_existing_data] = should_delete end end def should_delete return true if @options[:use_defaults] ask_yn("Would you like to delete your existing data and configurations?") end if $PROGRAM_NAME == __FILE__ # Bomb out if we're root if !Gem.win_platform? && Process.uid.zero? puts "Please run #{@script_name} as a non-root user" abort end # map component commands to methods commands = { database: { init: :init_db, reinit: :reinit_db, delete: :delete_db, status: :status_db, start: :start_db, stop: :stop_db, restart: :restart_db }, webservice: { init: :init_web_service, reinit: :reinit_web_service, delete: :delete_web_service, status: :status_web_service, start: :start_web_service, stop: :stop_web_service, restart: :restart_web_service } } parse_args(ARGV) update_db_port if @connection_string @db_driver = MsfdbHelpers::Standalone.new(options: @options, db_conf: @db_conf, connection_string: @connection_string) elsif installed?('pg_ctl') && has_requirements(MsfdbHelpers::PgCtl.requirements) @db_driver = MsfdbHelpers::PgCtl.new(db_path: @db, options: @options, localconf: @localconf, db_conf: @db_conf) elsif installed?('pg_ctlcluster') && has_requirements(MsfdbHelpers::PgCtlcluster.requirements) @db_driver = MsfdbHelpers::PgCtlcluster.new(db_path: @db, options: @options, localconf: @localconf, db_conf: @db_conf) else print_error('You need to have postgres installed or specify a database with --connection-string') abort end command = ARGV[0].to_sym if @options[:component].nil? @options[:component] = prompt_for_component(command) end prompt_for_deletion(command) if @options[:component] == :all @components.each { |component| if component == :webservice 3.times { print_webservice_removal_prompt } end puts '====================================================================' puts "Running the '#{command}' command for the #{component}:" invoke_command(commands, component.to_sym, command) puts '====================================================================' puts } else puts "Running the '#{command}' command for the #{@options[:component]}:" if @options[:component] == :webservice 3.times { print_webservice_removal_prompt } end invoke_command(commands, @options[:component], command) end end