#
#--
# Ronin - A Ruby platform designed for information security and data
# exploration tasks.
#
# Copyright (c) 2006-2008 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#++
#
require 'ronin/cache/extension_cache'
require 'ronin/cache/overlay'
require 'ronin/context'
module Ronin
module Cache
class Extension
include Context
# Extension file name
EXTENSION_FILE = 'extension.rb'
# Extension lib directory
LIB_DIR = 'lib'
contextify :extension
# Name of extension
attr_reader :name
# Paths of similar extensions
attr_reader :paths
# Dependency extensions
attr_reader :dependencies
#
# Creates a new Extension with the specified _name_. If a
# _block_ is given, it will be passed the newly created
# Extension.
#
# Extension.new('exploits')
#
# Extension.new('awesome') do |ext|
# ...
# end
#
def initialize(name,&block)
@name = name.to_s
@paths = []
@dependencies = {}
@setup = false
@toredown = true
@setup_blocks = []
@action_blocks = {}
@teardown_blocks = []
block.call(self) if block
end
#
# Returns the names of all extensions within the overlay cache.
#
def Extension.names
Overlay.cache.overlays.map { |overlay| overlay.extensions }.flatten.uniq
end
#
# Returns +true+ if an extension exists with the specified _name_,
# returns +false+ otherwise.
#
def Extension.exists?(name)
Extension.names.include?(name.to_s)
end
#
# Iterates through the extension names passing each to the specified
# _block_.
#
# Extension.each_name do |name|
# puts name
# end
#
def Extension.each_name(&block)
Extension.names.each(&block)
end
#
# Returns the paths of all extensions.
#
def Extension.paths
paths = []
Overlay.each { |repo| paths += repo.extension_paths }
return paths
end
#
# Iterates over the paths of all extensions with the specified
# _name_, passing each to the specified _block_.
#
def Extension.each_path(&block)
Extension.paths.each(&block)
end
#
# Returns the paths of all extensions with the specified _name_.
#
def Extension.paths_for(name)
Overlay.with_extension(name).map do |repo|
File.expand_path(File.join(repo.path,name))
end
end
#
# Iterates over the paths of all extensions with the specified
# _name_, passing each to the specified _block_.
#
def Extension.each_path_for(name,&block)
Extension.paths_for(name).each(&block)
end
#
# Adds the lib/ directory from within the specified _path_ to
# $LOAD_PATH, only if the lib/ directory exists within the
# specified _path_ and the directory has not already been
# added to $LOAD_PATH. If a _block_ is given, it will be called
# after $LOAD_PATH may or maynot have been modified.
#
def Extension.load_path(path,&block)
lib_dir = File.expand_path(File.join(path,LIB_DIR))
if File.directory?(lib_dir)
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
end
block.call if block
return nil
end
#
# Similar to load_path, but adds the lib/ directories from the
# paths of all extensions with the specified _name_ to $LOAD_PATH.
# If a _block_ is given, it will be called after $LOAD_PATH may or
# maynot have been modified.
#
def Extension.load_paths(name,&block)
Extension.each_path_for(name) do |path|
Extension.load_path(path)
end
block.call if block
return nil
end
#
# Loads an extension at the specified _path_ into a newly created
# Extension object. If a _block_ is given, it will be passed the
# newly created Extension object.
#
def Extension.load_from(path,&block)
Extension.new(File.basename(name)) do |ext|
ext.include_path(path,&block)
end
end
#
# Loads an extension at the specified _path_ into a newly created
# Extension object and then runs it with the specified _block_.
#
# Extension.run_from('lab/exploits') do |ext|
# puts ext.search('apache')
# end
#
def Extension.run_from(path,&block)
Extension.load_from(path) do |ext|
ext.run(&block)
end
end
#
# Loads all extensions with the specified _name_ into a newly created
# Extension object. If a _block_ is given, it will be passed the
# newly created Extension object.
#
# Extension.load('shellcode') do |ext|
# puts ext.search('moon_lander')
# end
#
def Extension.load(name,&block)
Extension.new(name) do |ext|
ext.include(name,&block)
end
end
#
# Loads all extensions with the specified _name_ into a newly created
# Extension object and then runs it with the specified _block_.
#
# Extension.run('exploits') do |ext|
# puts ext.search(:product => 'Apache')
# end
#
def Extension.run(name,&block)
Extension.load(name) do |ext|
ext.run(&block)
end
end
#
# Returns the current ExtensionCache.
#
def Extension.cache
@@cache ||= ExtensionCache.new
end
#
# Returns the extension with the specified _name_ from the extension
# cache. If no extension exists with the specified _name_ an
# ExtensionNotFound exception will be raised.
#
def Extension.[](name)
Extension.cache[name]
end
#
# Returns +true+ if the extension with the specified _name_ has been
# loaded into the extension cache, returns +false+ otherwise.
#
def Extension.loaded?(name)
Extension.cache.has_extension?(name)
end
#
# Includes all extensions of the specified _name_ into the extension.
# If a _block_ is given, it will be passed the newly created
# extension after the extensions of _name_ have been included.
#
def include(name,&block)
Extension.load_paths(name) do
Extension.each_path_for(name) do |path|
include_path(path)
end
end
block.call(self) if block
return self
end
#
# Includes the extension at the specified _path_ into the extension.
# If a _block_ is given, it will be passed the newly created
# extension.
#
def include_path(path,&block)
path = File.expand_path(path)
unless File.directory?(path)
raise(ExtensionNotFound,"extension #{path.dump} is not a valid extension",caller)
end
# add to the search paths
@paths << path
Extension.load_path(path) do
extension_file = File.join(path,EXTENSION_FILE)
if File.file?(extension_file)
# instance_eval the extension block
context_block = Extension.load_context_block(extension_file)
instance_eval(&context_block) if context_block
end
end
block.call(self) if block
return self
end
#
# Loads all similar extensions with the specified _name_ into a
# newly created Extension object and adds it to the extensions
# dependencies.
#
# depend 'shellcode'
#
def depend(name)
name = name.to_s
unless Extension.exists?(name)
raise(ExtensionNotFound,"extension #{name.dump} is not in the overlay cache",caller)
end
@dependencies[name] ||= Extension.load(name)
return self
end
#
# Returns +true+ if the extension has the dependency of the specified
# _name_, returns +false+ otherwise.
#
def depends_on?(name)
@dependencies.has_key?(name.to_s)
end
#
# Passes all the extension's dependencies and the extension itself to
# the specified _block_ using the given _options_.
#
# _options_ may include the following keys:
# :top_down:: Indicates that distribute will recurse through
# the extensions and their elements in a
# top-down manner. This is distributes default
# behavior.
# :bottom_up:: Indictates that distribute will recurse
# through the extensions and their dependencies
# in a bottom-up manner. Mutually exclusive with
# the :top_down option.
#
def distribute(options={},&block)
distribute_deps = lambda {
@dependencies.map { |ext|
ext.distribute(options,&block)
}.flatten
}
if options[:bottom_up]
return distribute_deps.call + [block.call(self)]
else
return [block.call(self)] + distribute_deps.call
end
end
#
# Returns +true+ if the app context has a public instance method
# of the matching _name_, returns +false+ otherwise.
#
# ext.has_method?(:console) # => true
#
def has_method?(name)
public_methods.include?(name.to_s)
end
#
# Returns an +Array+ of extensions that have the specified _method_.
# If a _block_ is given, it will be passed each extension with the
# specified _method_.
#
# ext.extensions_with_method(:console) # => [...]
#
# ext.extensions_with_method(:console) do |ext|
# ext.console(ARGV)
# end
#
def extensions_with_method(method,&block)
extensions = distribute { |ext|
ext if ext.has_method?(method)
}.compact
extensions.each(&block) if block
return extensions
end
#
# Calls the setup blocks of the extension's dependencies and the
# extension itself. If a _block_ is given, it will be passed the
# extension after it has been setup.
#
# ext.perform_setup # => Extension
#
# ext.perform_setup do |ext|
# puts "Extension #{ext} has been setup..."
# end
#
def perform_setup(&block)
unless @setup
distribute(:bottom_up => true) do |ext|
ext.instance_eval do
@setup_blocks.each do |setup_block|
setup_block.call(self) if setup_block
end
end
end
@setup = true
@toredown = false
end
block.call(self) if block
return self
end
#
# Returns +true+ if the extension has been setup, returns +false+
# otherwise.
#
def was_setup?
@setup == true
end
#
# Run the teardown blocks of the extension and it's dependencies.
# If a _block_ is given, it will be passed the extension before it
# has been tore down.
#
# ext.perform_teardown # => Extension
#
# ext.perform_teardown do |ext|
# puts "Extension #{ext} is being tore down..."
# end
#
def perform_teardown(&block)
block.call(self) if block
unless @toredown
distribute(:top_down => true) do |ext|
ext.instance_eval do
@teardown_blocks.each do |teardown_block|
teardown_block.call(self) if teardown_block
end
end
end
@toredown = true
@setup = false
end
return self
end
#
# Returns +true+ if the extension has been toredown, returns +false+
# otherwise.
#
def was_toredown?
@toredown == true
end
#
# Sets up the extension, passes the extension to the specified
# _block_ and then tears down the extension.
#
# ext.run do |ext|
# ext.console(ARGV)
# end
#
def run(&block)
perform_setup
block.call(self) if block
perform_teardown
return self
end
#
# Returns an +Array+ of the names of all actions defined in the
# extension.
#
# ext.actions # => [...]
#
def actions
@action_blocks.keys
end
#
# Returns +true+ if the extension has the action of the specified
# _name_, returns +false+ otherwise.
#
def has_action?(name)
@action_blocks.has_key?(name.to_sym)
end
#
# Runs the action of the specified _name_ with the given _args_.
# If no action of the specified name exists, then an UnknownAction
# exception will be raised.
#
def perform_action(name,*args)
name = name.to_s
unless has_action?(name)
raise(UnknownAction,"action #{name.dump} is not defined",caller)
end
return run do
@action_blocks[name.to_sym].call(*args)
end
end
#
# Find the specified _path_ from within all similar extensions.
# If a _block_ is given, it will be passed the full path if found.
#
# ext.find_path('data/test')
#
# ext.find_path('data/test') do |path|
# puts Dir[File.join(path,'*')]
# end
#
def find_path(path,&block)
@paths.each do |ext_path|
full_path = File.expand_path(File.join(ext_path,path))
if File.exists?(full_path)
block.call(full_path) if block
return full_path
end
end
return nil
end
#
# Find the specified file _path_ from within all similar extensions.
# If a _block_ is given, it will be passed the full file path if
# found.
#
# ext.find_file('data/test/file.xml')
#
# ext.find_file('data/test/file.xml') do |file|
# REXML::Document.new(open(file))
# ...
# end
#
def find_file(path,&block)
find_path(path) do |full_path|
if File.file?(full_path)
block.call(full_path) if block
return full_path
end
end
end
#
# Find the specified directory _path_ from within all similar
# extensions. If a _block_ is given, it will be passed the full
# directory path if found.
#
# ext.find_directory('data/test')
#
# ext.find_directory('data/test') do |dir|
# puts Dir[File.join(dir,'*')]
# end
#
def find_dir(path,&block)
find_path(path) do |full_path|
if File.directory?(full_path)
block.call(full_path) if block
return full_path
end
end
end
#
# Find the paths that match the given pattern from within all similar
# extensions. If a _block_ is given, it will be passed each matching
# full path.
#
# ext.glob_paths('data/*') # => [...]
#
# ext.glob_paths('data/*') do |path|
# puts path
# end
#
def glob_paths(pattern,&block)
full_paths = @paths.inject([]) do |paths,ext_path|
paths + Dir[File.join(ext_path,pattern)]
end
full_paths.each(&block) if block
return full_paths
end
#
# Find the file paths that match the given pattern from within all
# similar extensions. If a _block_ is given, it will be passed each
# matching full file path.
#
# ext.glob_files('data/*.xml') # => [...]
#
# ext.glob_files('data/*.xml') do |file|
# puts file
# end
#
def glob_files(pattern,&block)
full_paths = glob_paths(pattern).select do |path|
File.file?(path)
end
full_paths.each(&block) if block
return full_paths
end
#
# Find the directory paths that match the given pattern from within
# all similar extensions. If a _block_ is given, it will be passed
# each matching full directory path.
#
# ext.glob_dirs('builds/*') # => [...]
#
# ext.glob_dirs('builds/*') do |dir|
# puts dir
# end
#
def glob_dirs(pattern,&block)
full_paths = glob_paths(pattern).select do |path|
File.directory?(path)
end
full_paths.each(&block) if block
return full_paths
end
#
# Returns the name of the app context in string form.
#
def to_s
@name.to_s
end
protected
#
# Adds the specified _block_ to the list of blocks to run in order
# to properly setup the extension.
#
def setup(&block)
@setup_blocks << block if block
return self
end
#
# Defines a new action with the specified _name_ and the given
# _block_. If an action of the same _name_ has already been defined
# then an ActionRedefined exception will be raised.
#
def action(name,&block)
name = name.to_s
if has_action?(name)
raise(ActionRedefined,"action #{name.dump} previously defined",caller)
end
@action_blocks[name.to_sym] = block
return self
end
#
# Adds the specified _block_ to the list of blocks to run in order
# to properly tear-down the extension.
#
def teardown(&block)
@teardown_blocks << block if block
return self
end
#
# Provides transparent access to the performing of actions
# and extensions dependencies.
#
# ext.scan('localhost') # => Extension
#
# ext.shellcode # => Extension
#
# ext.shellcode do |dep|
# puts "#{ext} has the dependency #{dep}"
# end
#
def method_missing(sym,*args,&block)
if (args.length==0)
name = sym.to_s
if (has_action?(name) && block.nil?)
return perform_action(name,*args)
end
if depends_on?(name)
block.call(@dependencies[name]) if block
return @dependencies[name]
end
end
return super(sym,*args,&block)
end
end
end
end