diff --git a/data/sql/sqlite.sql b/data/sql/sqlite.sql new file mode 100644 index 0000000000..e712ecfbe0 --- /dev/null +++ b/data/sql/sqlite.sql @@ -0,0 +1,22 @@ +drop table hosts; + +create table hosts ( +'id' INTEGER PRIMARY KEY NOT NULL, +'address' VARCHAR(16) UNIQUE, +'comm' VARCHAR(255), +'name' VARCHAR(255), +'state' VARCHAR(255), +'desc' VARCHAR(1024) +); + +drop table services; + +create table services ( +'id' INTEGER PRIMARY KEY NOT NULL, +'host_id' INTEGER, +'port' INTEGER NOT NULL, +'proto' VARCHAR(16) NOT NULL, +'state' VARCHAR(255), +'name' VARCHAR(255), +'desc' VARCHAR(1024) +); diff --git a/data/sql/sqlite3.db b/data/sql/sqlite3.db new file mode 100644 index 0000000000..3569c91601 Binary files /dev/null and b/data/sql/sqlite3.db differ diff --git a/lib/msf/core.rb b/lib/msf/core.rb index e107cd0d38..525400fa2e 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -23,16 +23,19 @@ end # General require 'msf/core/constants' require 'msf/core/exceptions' -require 'msf/core/event_dispatcher' require 'msf/core/data_store' require 'msf/core/option_container' # Framework context and core classes require 'msf/core/framework' -require 'msf/core/session_manager' -require 'msf/core/session' +require 'msf/core/db_manager' +require 'msf/core/event_dispatcher' +require 'msf/core/module_manager' require 'msf/core/plugin_manager' +require 'msf/core/session' +require 'msf/core/session_manager' + # Wrappers require 'msf/core/encoded_payload' diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb new file mode 100644 index 0000000000..5977efaa6f --- /dev/null +++ b/lib/msf/core/db.rb @@ -0,0 +1,145 @@ +module Msf + +### +# +# The states that a host can be in. +# +### +module HostState + # + # The host is alive. + # + Alive = "alive" + # + # The host is dead. + # + Dead = "down" + # + # The host state is unknown. + # + Unknown = "unknown" +end + +### +# +# The states that a service can be in. +# +### +module ServiceState + # + # The service is alive. + # + Up = "up" + # + # The service is dead. + # + Dead = "down" + # + # The service state is unknown. + # + Unknown = "unknown" +end + + +### +# +# The DB module ActiveRecord definitions for the DBManager +# +### + +class DBManager + + # + # Reports a host as being in a given state by address. + # + def report_host_state(mod, addr, state, context = nil) + + # TODO: use the current thread's Comm to find the host + comm = '' + host = get_host(context, addr, comm) + + ostate = host.state + host.state = state + framework.events.on_db_host_state(context, host, ostate) + end + + # + # This method reports a host's service state. + # + def report_service_state(mod, addr, proto, port, state, context = nil) + + # TODO: use the current thread's Comm to find the host + comm = '' + host = get_host(context, addr, comm) + port = get_service(context, host, proto, port) + + ostate = port.state + port.state = state + framework.events.on_db_service_state(context, host, servcice, ostate) + end + + + # + # This method iterates the hosts table calling the supplied block with the + # host instance of each entry. + # TODO: use the find() block syntax instead + # + def each_host(&block) + hosts.each do |host| + block.call(host) + end + end + + # + # This methods returns a list of all hosts in the database + # + def hosts + Host.find(:all) + end + + # + # This method iterates the services table calling the supplied block with the + # host and service instances of each entry. + # TODO: use the find() block syntax instead + # + def each_service(&block) + services.each do |service| + block.call(service) + end + end + + # + # This methods returns a list of all services in the database + # + def services + Service.find(:all) + end + + def get_host(context, address, comm='') + host = Host.find(:first, :conditions => [ "address = ? and comm = ?", address, comm]) + if (not host) + host = Host.create(:address => address, :comm => comm, :state => HostState::Unknown) + framework.events.on_db_host(context, host) + end + + return host + end + + + def get_service(host, proto, port) + port = Service.find(:first, :conditions => [ "host_id = ? and proto = ? and port = ?", host.id, proto, port]) + if (not port) + port = Service.create( + :host => host, + :proto => proto, + :port => port, + :state => ServiceState::Unknown + ) + framework.events.on_db_service(context, host, port) + end + return port + end + +end + +end diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb new file mode 100644 index 0000000000..6464c225d4 --- /dev/null +++ b/lib/msf/core/db_manager.rb @@ -0,0 +1,71 @@ +require 'msf/core' +require 'msf/core/db' + +module Msf + +### +# +# The db module provides persistent storage and events +# +### + +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 + + def initialize(framework) + + self.framework = framework + @usable = false + @active = false + + begin + require 'rubygems' + require_gem 'activerecord' + + require 'msf/core/db_objects' + + @usable = true + rescue ::Exception => e + elog("DBManager is not enabled due to load error: #{e.to_s}") + end + end + + # + # Connects this instance to a database + # + def connect(opts={}) + + return false if not @usable + + begin + ActiveRecord::Base.establish_connection(opts) + rescue ::Exception => e + elog("DB.connect threw an exception: #{e.to_s}") + return false + end + + @active = true + end + + # + # Disconnects a database session + # + def disconnect + begin + ActiveRecord::Base.remove_connection + rescue ::Exception => e + elog("DB.disconnect threw an exception: #{e.to_s}") + end + @active = false + end + +end +end diff --git a/lib/msf/core/db_objects.rb b/lib/msf/core/db_objects.rb new file mode 100644 index 0000000000..c5418a6dec --- /dev/null +++ b/lib/msf/core/db_objects.rb @@ -0,0 +1,22 @@ +module Msf + + +## +# +# This module defines all of the DB database tables +# and creates ActiveRecord objects for each one of them +# +## + +class DBManager + +# Host object definition +class Host < ActiveRecord::Base +end + +# Service object definition +class Service < ActiveRecord::Base +end + +end +end diff --git a/lib/msf/core/event_dispatcher.rb b/lib/msf/core/event_dispatcher.rb index f4fa1dcba0..f30f3db6d3 100644 --- a/lib/msf/core/event_dispatcher.rb +++ b/lib/msf/core/event_dispatcher.rb @@ -31,10 +31,14 @@ end ### class EventDispatcher - def initialize + include Framework::Offspring + + def initialize(framework) + self.framework = framework self.general_event_subscribers = [] self.exploit_event_subscribers = [] self.session_event_subscribers = [] + self.db_event_subscribers = [] self.subscribers_rwlock = Rex::ReadWriteLock.new end @@ -59,6 +63,24 @@ class EventDispatcher remove_event_subscriber(general_event_subscribers, subscriber) end + # + # This method adds a db event subscriber. db event subscribers + # receive notifications when events occur that pertain to db changes. + # The subscriber provided must implement the dbEvents module methods in + # some form. + # + def add_db_subscriber(subscriber) + add_event_subscriber(db_event_subscribers, subscriber) + end + + # + # Removes a db event subscriber. + # + def remove_db_subscriber(subscriber) + remove_event_subscriber(db_event_subscribers, subscriber) + end + + # # This method adds an exploit event subscriber. Exploit event subscribers # receive notifications when events occur that pertain to exploits, such as @@ -126,68 +148,49 @@ class EventDispatcher } } end + + # + # Capture incoming events and pass them off to the subscribers + # + def method_missing(name, *args) - - ## - # - # Exploit events - # - ## - - # - # Called when an exploit succeeds. This notifies the registered exploit - # event subscribers. - # - def on_exploit_success(exploit, session = nil) - subscribers_rwlock.synchronize_read { - exploit_event_subscribers.each { |subscriber| - subscriber.on_exploit_success(exploit, session) - } - } + # Prevent this from hooking events prior to it actually loading + return false if not subscribers_rwlock + + subscribers_rwlock.synchronize_read do + case name + + # Exploit events + when /^on_exploit/ + exploit_event_subscribers.each do |subscriber| + next if not subscriber.respond_to?(name) + subscriber.send(name, *args) + end + + # Session events + when /^on_session/ + session_event_subscribers.each do |subscriber| + next if not subscriber.respond_to?(name) + subscriber.send(name, *args) + end + + # db events + when /^on_db/ + # Only process these events if the db is active + if (framework.db.active) + db_event_subscribers.each do |subscriber| + next if not subscriber.respond_to?(name) + subscriber.send(name, *args) + end + end + # Everything else + else + elog("Event dispatcher received an unhandled event: #{name}") + end + end end - - # - # Called when an exploit fails. This notifies the registered exploit - # event subscribers. - # - def on_exploit_failure(exploit, reason) - subscribers_rwlock.synchronize_read { - exploit_event_subscribers.each { |subscriber| - subscriber.on_exploit_failure(exploit, reason) - } - } - end - - ## - # - # Session events - # - ## - - # - # Called when a new session is opened. This notifies all the registered - # session event subscribers. - # - def on_session_open(session) - subscribers_rwlock.synchronize_read { - session_event_subscribers.each { |subscriber| - subscriber.on_session_open(session) - } - } - end - - # - # Called when a new session is closed. This notifies all the registered - # session event subscribers. - # - def on_session_close(session) - subscribers_rwlock.synchronize_read { - session_event_subscribers.each { |subscriber| - subscriber.on_session_close(session) - } - } - end - + + protected # diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb index d115e22e25..f3786837c2 100644 --- a/lib/msf/core/framework.rb +++ b/lib/msf/core/framework.rb @@ -15,7 +15,7 @@ class Framework # Major = 3 Minor = 0 - Release = "-alpha-r3" + Release = "-alpha-r4" Version = "#{Major}.#{Minor}#{Release}" Revision = "$Revision$" @@ -34,17 +34,19 @@ class Framework require 'msf/core/module_manager' require 'msf/core/session_manager' + require 'msf/core/db_manager' # # Creates an instance of the framework context. # def initialize() - self.events = EventDispatcher.new + self.events = EventDispatcher.new(self) self.modules = ModuleManager.new(self) self.sessions = SessionManager.new(self) self.datastore = DataStore.new self.jobs = Rex::JobContainer.new self.plugins = PluginManager.new(self) + self.db = DBManager.new(self) end # @@ -125,7 +127,12 @@ class Framework # unloading of plugins. # attr_reader :plugins - + # + # The framework instance's db manager. The db manager + # maintains the database db and handles db events + # + attr_reader :db + protected attr_writer :events # :nodoc: @@ -135,7 +142,7 @@ protected attr_writer :auxmgr # :nodoc: attr_writer :jobs # :nodoc: attr_writer :plugins # :nodoc: - + attr_writer :db # :nodoc: end end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 0cca144938..3deb55d40a 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -840,7 +840,7 @@ class Core # Tab completion for the unload command # def cmd_unload_tabs(str, words) - framework.plugins.each { k.name } + framework.plugins.each { |k| k.name } end # diff --git a/plugins/db_sqlite3.rb b/plugins/db_sqlite3.rb new file mode 100644 index 0000000000..5ad7fbd028 --- /dev/null +++ b/plugins/db_sqlite3.rb @@ -0,0 +1,113 @@ +require 'fileutils' + +module Msf + +### +# +# This class intializes the database db with a shiny new +# SQLite3 database instance. +# +### + +class Plugin::DBSQLite3 < Msf::Plugin + + ### + # + # This class implements a sample console command dispatcher. + # + ### + class ConsoleCommandDispatcher + include Msf::Ui::Console::CommandDispatcher + + # + # The dispatcher's name. + # + def name + "DBDispatcher" + end + + # + # Returns the hash of commands supported by this dispatcher. + # + def commands + { + "db_hosts" => "List all hosts in the database db", + "db_services" => "List all services in the database db", + "db_insert" => "Insert a new host into the db", + "db_test" => "Test", + } + end + + def cmd_db_hosts(*args) + framework.db.each_host do |host| + print_status("Host: #{host.address}") + end + end + + def cmd_db_services(*args) + framework.db.each_service do |host, service| + print_status("Service: host=#{host.address} port=#{service.port} port=#{service.proto}") + end + end + + def cmd_db_insert(*args) + print_status("Inserting #{args.length.to_s} hosts...") + args.each do |address| + framework.db.get_host(nil, address) + end + end + + def cmd_db_test(*args) + framework.db.get_host(nil, "1.2.3.4") + framework.db.get_host(nil, "1.2.3.5") + framework.db.get_host(nil, "1.2.3.6") + framework.db.each_host do |host| + print_status("Host: #{host.address}") + end + end + end + + + def initialize(framework, opts) + super + + odb = File.join(Msf::Config.install_root, "data", "sql", "sqlite3.db") + ndb = File.join(Msf::Config.install_root, "current.db") + + if (File.exists?(ndb)) + File.unlink(ndb) + end + + FileUtils.copy(odb, ndb) + + if (not framework.db.connect("adapter" => "sqlite3", "dbfile" => ndb)) + print_status("Failed to connect to the database :(") + return + end + + add_console_dispatcher(ConsoleCommandDispatcher) + end + + def cleanup + remove_console_dispatcher('DBDispatcher') + end + + # + # This method returns a short, friendly name for the plugin. + # + def name + "db_sqlite3" + end + + # + # This method returns a brief description of the plugin. It should be no + # more than 60 characters, but there are no hard limits. + # + def desc + "Loads a new SQLite3 db and intializes it" + end + +protected + +end +end