From 291aad8cc133dd3dbc5f2cd43cf8b2ef1c21f0b8 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sat, 14 Nov 2009 21:41:38 +0000 Subject: [PATCH] Fixes #516 and fixes #515. This patch makes database support a little more user-friendly. The db commands now accept -h and --help, the db_drivers command will indicate how to install support for a given database, the db_create/db_destroy commands will indicate what tools are required to use them, and the postgres driver will now roperly handle sameuser authentication over a unix domain socket with postgres-pr git-svn-id: file:///home/svn/framework3/trunk@7516 4d416f70-5f16-0410-b530-b9f4589650da --- lib/msf/core/db_manager.rb | 66 ++++--- lib/msf/ui/console/command_dispatcher/db.rb | 182 ++++++++++++++++++-- 2 files changed, 213 insertions(+), 35 deletions(-) diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 9bd22afb41..0951445688 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -14,28 +14,28 @@ class DBManager # Provides :framework and other accessors include Framework::Offspring - + # Returns true if we are ready to load/store data attr_accessor :active - + # Returns true if the prerequisites have been installed attr_accessor :usable - + # Returns the list of usable database drivers attr_accessor :drivers - + # Returns the active driver attr_accessor :driver - + # Stores the error message for why the db was not loaded attr_accessor :error - + def initialize(framework) - + self.framework = framework @usable = false @active = false - + # # Prefer our local copy of active_record and active_support # @@ -43,75 +43,93 @@ class DBManager if(File.directory?(dir_ar) and not $:.include?(dir_ar)) $:.unshift(dir_ar) end - + dir_as = File.join(Msf::Config.data_directory, 'msfweb', 'vendor', 'rails', 'activesupport', 'lib') if(File.directory?(dir_as) and not $:.include?(dir_as)) $:.unshift(dir_as) end - + # Load ActiveRecord if it is available - begin + begin require 'rubygems' require 'active_record' require 'active_support' require 'msf/core/db_objects' @usable = true - + rescue ::Exception => e self.error = e elog("DB is not enabled due to load error: #{e}") return end - + # # Determine what drivers are available # initialize_drivers end - + + # + # # - # - # def initialize_drivers self.drivers = [] tdrivers = %W{ sqlite3 mysql postgresql } tdrivers.each do |driver| begin ActiveRecord::Base.establish_connection(:adapter => driver) + if(self.respond_to?("driver_check_#{driver}")) + self.send("driver_check_#{driver}") + end ActiveRecord::Base.remove_connection self.drivers << driver rescue ::Exception end end - + if(not self.drivers.empty?) self.driver = self.drivers[0] end end - + + # Verify that sqlite3 is ready + def driver_check_sqlite3 + require 'sqlite3' + end + + # Verify that mysql is ready + def driver_check_mysql + require 'mysql' + end + # # Connects this instance to a database # def connect(opts={}) return false if not @usable - + nopts = opts.dup if (nopts['port']) nopts['port'] = nopts['port'].to_i end - - + + begin + # Configure the database adapter ActiveRecord::Base.establish_connection(nopts) + + # Try to query for hosts (triggers a real connection + Host.find(:first) rescue ::Exception => e + self.error = e elog("DB.connect threw an exception: #{e}") return false end - + @active = true end - + # # Disconnects a database session # @@ -119,6 +137,7 @@ class DBManager begin ActiveRecord::Base.remove_connection rescue ::Exception => e + self.error = e elog("DB.disconnect threw an exception: #{e}") end @active = false @@ -126,3 +145,4 @@ class DBManager end end + diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index 481fd1dcb4..0574747e5e 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -867,7 +867,7 @@ class Db def cmd_db_driver(*args) if(args[0]) - if(args[0] == "-h") + if(args[0] == "-h" || args[0] == "--help") print_status("Usage: db_driver [driver-name]") return end @@ -887,6 +887,25 @@ class Db print_status("No Active Driver") end print_status(" Available: #{framework.db.drivers.join(", ")}") + print_line("") + + if ! framework.db.drivers.include?('sqlite3') + print_status(" DB Support: Enable the sqlite3 driver with the following command:") + print_status(" $ gem install sqlite3-ruby") + print_line("") + end + + if ! framework.db.drivers.include?('mysql') + print_status(" DB Support: Enable the mysql driver with the following command:") + print_status(" $ gem install mysql") + print_line("") + end + + if ! framework.db.drivers.include?('postgresql') + print_status(" DB Support: Enable the postgresql driver with the following command:") + print_status(" $ gem install postgres-pr") + print_line("") + end end def cmd_db_driver_tabs(str, words) @@ -905,6 +924,12 @@ class Db def cmd_db_destroy(*args) return if not db_check_driver + + if(args[0] and (args[0] == "-h" || args[0] == "--help")) + print_status("Usage: db_destroy") + return + end + meth = "db_destroy_#{framework.db.driver}" if(self.respond_to?(meth)) self.send(meth, *args) @@ -925,6 +950,12 @@ class Db def cmd_db_disconnect(*args) return if not db_check_driver + + if(args[0] and (args[0] == "-h" || args[0] == "--help")) + print_status("Usage: db_disconnect") + return + end + meth = "db_disconnect_#{framework.db.driver}" if(self.respond_to?(meth)) self.send(meth, *args) @@ -934,6 +965,21 @@ class Db end + def db_find_tools(tools) + found = true + missed = [] + tools.each do |name| + if(! Rex::FileUtils.find_full_path(name)) + missed << name + end + end + if(not missed.empty?) + print_error("This database command requires the following tools to be installed: #{missed.join(", ")}") + return + end + true + end + # # Database management: SQLite3 @@ -953,18 +999,23 @@ class Db # def db_connect_sqlite3(*args) + if(args[0] and (args[0] == "-h" || args[0] == "--help")) + print_status("Usage: db_connect [database-file-path]") + return + end + info = db_parse_db_uri_sqlite3(args[0]) opts = { 'adapter' => 'sqlite3' } opts['dbfile'] = info[:path] - if (not File.exists?(opts['dbfile'])) + if (not ::File.exists?(opts['dbfile'])) print_error("The specified database does not exist") return end if (not framework.db.connect(opts)) - raise RuntimeError.new("Failed to connect to the database") + raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end print_status("Successfully connected to the database") @@ -977,6 +1028,11 @@ class Db def db_create_sqlite3(*args) cmd_db_disconnect() + if(args[0] and (args[0] == "-h" || args[0] == "--help")) + print_status("Usage: db_create [database-file-path]") + return + end + info = db_parse_db_uri_sqlite3(args[0]) opts = { 'adapter' => 'sqlite3' } @@ -1002,7 +1058,7 @@ class Db end if (not framework.db.connect(opts)) - raise RuntimeError.new("Failed to connect to the database") + raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end print_status("Successfully connected to the database") @@ -1016,6 +1072,7 @@ class Db cmd_db_disconnect() info = db_parse_db_uri_sqlite3(args[0]) begin + print_status("Deleting #{info[:path]}...") File.unlink(info[:path]) rescue Errno::ENOENT print_error("The specified database does not exist") @@ -1045,6 +1102,15 @@ class Db # Connect to an existing MySQL database # def db_connect_mysql(*args) + if(args[0] == nil or args[0] == "-h" or args[0] == "--help") + print_status(" Usage: db_connect @/") + print_status("Examples:") + print_status(" db_connect user@metasploit3") + print_status(" db_connect user:pass@192.168.0.2/metasploit3") + print_status(" db_connect user:pass@192.168.0.2:1500/metasploit3") + return + end + info = db_parse_db_uri_mysql(args[0]) opts = { 'adapter' => 'mysql' } @@ -1054,6 +1120,8 @@ class Db opts['host'] = info[:host] if (info[:host]) opts['port'] = info[:port] if (info[:port]) + opts['host'] ||= 'localhost' + # This is an ugly hack for a broken MySQL adapter: # http://dev.rubyonrails.org/ticket/3338 if (opts['host'].strip.downcase == 'localhost') @@ -1061,7 +1129,7 @@ class Db end if (not framework.db.connect(opts)) - raise RuntimeError.new("Failed to connect to the database") + raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end end @@ -1071,6 +1139,17 @@ class Db def db_create_mysql(*args) cmd_db_disconnect() + if(args[0] == nil or args[0] == "-h" or args[0] == "--help") + print_status(" Usage: db_create @/") + print_status("Examples:") + print_status(" db_create user@metasploit3") + print_status(" db_create user:pass@192.168.0.2/metasploit3") + print_status(" db_create user:pass@192.168.0.2:1500/metasploit3") + return + end + + return if ! db_find_tools(%W{mysqladmin mysql}) + info = db_parse_db_uri_mysql(args[0]) opts = { 'adapter' => 'mysql' } @@ -1117,7 +1196,7 @@ class Db system("mysqladmin #{cargs} drop #{info[:name]} >/dev/null 2>&1") system("mysqladmin #{cargs} create #{info[:name]}") - psql = File.popen("mysql #{cargs} #{info[:name]}", "w") + psql = ::IO.popen("mysql #{cargs} #{info[:name]}", "w") psql.write(fd.read) psql.close fd.close @@ -1125,7 +1204,7 @@ class Db print_status("Database creation complete (check for errors)") if (not framework.db.connect(opts)) - raise RuntimeError.new("Failed to connect to the database") + raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end end @@ -1137,6 +1216,8 @@ class Db cmd_db_disconnect() + return if ! db_find_tools(%W{mysqladmin}) + info = db_parse_db_uri_mysql(args[0]) argv = [] @@ -1195,6 +1276,15 @@ class Db # Connect to an existing Postgres database # def db_connect_postgresql(*args) + if(args[0] == nil or args[0] == "-h" or args[0] == "--help") + print_status(" Usage: db_connect @/") + print_status("Examples:") + print_status(" db_connect user@metasploit3") + print_status(" db_connect user:pass@192.168.0.2/metasploit3") + print_status(" db_connect user:pass@192.168.0.2:1500/metasploit3") + return + end + info = db_parse_db_uri_postgresql(args[0]) opts = { 'adapter' => 'postgresql' } @@ -1204,8 +1294,36 @@ class Db opts['host'] = info[:host] if (info[:host]) opts['port'] = info[:port] if (info[:port]) + opts['pass'] ||= '' + + # Do a little legwork to find the real database socket + if(! opts['host']) + while(true) + done = false + dirs = %W{ /var/run/postgresql /tmp } + dirs.each do |dir| + if(::File.directory?(dir)) + d = ::Dir.new(dir) + d.entries.grep(/^\.s\.PGSQL.(\d+)$/).each do |ent| + opts['port'] = ent.split('.')[-1].to_i + opts['host'] = dir + done = true + break + end + end + break if done + end + break + end + end + + # Default to loopback + if(! opts['host']) + opts['host'] = '127.0.0.1' + end + if (not framework.db.connect(opts)) - raise RuntimeError.new("Failed to connect to the database") + raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end end @@ -1215,6 +1333,17 @@ class Db def db_create_postgresql(*args) cmd_db_disconnect() + if(args[0] == nil or args[0] == "-h" or args[0] == "--help") + print_status(" Usage: db_create @/") + print_status("Examples:") + print_status(" db_create user@metasploit3") + print_status(" db_create user:pass@192.168.0.2/metasploit3") + print_status(" db_create user:pass@192.168.0.2:1500/metasploit3") + return + end + + return if ! db_find_tools(%W{psql dropdb createdb}) + info = db_parse_db_uri_postgresql(args[0]) opts = { 'adapter' => 'postgresql' } argv = [] @@ -1252,21 +1381,48 @@ class Db cargs = argv.map{|c| "'#{c}' "}.join - sql = File.join(Msf::Config.install_root, "data", "sql", "postgres.sql") - fd = File.open(sql, 'r') + sql = ::File.join(Msf::Config.install_root, "data", "sql", "postgres.sql") + fd = ::File.open(sql, 'r') system("dropdb #{cargs} #{info[:name]} >/dev/null 2>&1") system("createdb #{cargs} #{info[:name]}") - psql = File.popen("psql -q " + cargs + info[:name], "w") + psql = ::IO.popen("psql -q " + cargs + info[:name], "w") psql.write(fd.read) psql.close fd.close print_status("Database creation complete (check for errors)") + opts['pass'] ||= '' + + # Do a little legwork to find the real database socket + if(! opts['host']) + while(true) + done = false + dirs = %W{ /var/run/postgresql /tmp } + dirs.each do |dir| + if(::File.directory?(dir)) + d = ::Dir.new(dir) + d.entries.grep(/^\.s\.PGSQL.(\d+)$/).each do |ent| + opts['port'] = ent.split('.')[-1].to_i + opts['host'] = dir + done = true + break + end + end + break if done + end + break + end + end + + # Default to loopback + if(! opts['host']) + opts['host'] = '127.0.0.1' + end if (not framework.db.connect(opts)) - raise RuntimeError.new("Failed to connect to the database") + raise RuntimeError.new("Failed to connect to the database: #{framework.db.error}") end end @@ -1277,6 +1433,8 @@ class Db cmd_db_disconnect() + return if ! db_find_tools(%W{dropdb}) + info = db_parse_db_uri_postgresql(args[0]) argv = []