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
unstable
HD Moore 2009-11-14 21:41:38 +00:00
parent 240a8444b0
commit 291aad8cc1
2 changed files with 213 additions and 35 deletions

View File

@ -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

View File

@ -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 <user:pass>@<host:port>/<database>")
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 <user:pass>@<host:port>/<database>")
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 <user:pass>@<host:port>/<database>")
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 <user:pass>@<host:port>/<database>")
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 = []