2005-05-22 07:14:16 +00:00
|
|
|
require 'find'
|
2005-07-09 21:18:49 +00:00
|
|
|
require 'msf/core'
|
2005-05-22 07:14:16 +00:00
|
|
|
|
|
|
|
module Msf
|
|
|
|
|
|
|
|
###
|
|
|
|
#
|
|
|
|
# ModuleSet
|
|
|
|
# ---------
|
|
|
|
#
|
|
|
|
# A module set contains zero or more named module classes of an arbitrary
|
|
|
|
# type.
|
|
|
|
#
|
|
|
|
###
|
|
|
|
class ModuleSet < Hash
|
2005-07-13 18:06:12 +00:00
|
|
|
|
|
|
|
include Framework::Offspring
|
|
|
|
|
2005-05-22 07:58:02 +00:00
|
|
|
def initialize(type = nil)
|
2005-05-22 07:46:41 +00:00
|
|
|
self.module_type = type
|
|
|
|
|
|
|
|
# Hashes that convey the supported architectures and platforms for a
|
|
|
|
# given module
|
|
|
|
self.mod_arch_hash = {}
|
|
|
|
self.mod_platform_hash = {}
|
2005-07-11 05:15:30 +00:00
|
|
|
self.mod_sorted = nil
|
2005-07-13 18:06:12 +00:00
|
|
|
self.mod_ranked = nil
|
2005-07-14 06:34:58 +00:00
|
|
|
self.mod_extensions = []
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
# Create an instance of the supplied module by its name
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def create(name)
|
2005-07-14 06:34:58 +00:00
|
|
|
klass = self[name]
|
|
|
|
instance = nil
|
|
|
|
|
|
|
|
# If the klass is valid for this name, try to create it
|
|
|
|
if (klass)
|
|
|
|
instance = klass.new
|
|
|
|
end
|
|
|
|
|
|
|
|
# Notify any general subscribers of the creation event
|
|
|
|
if (instance)
|
|
|
|
self.framework.events.on_module_created(instance)
|
|
|
|
end
|
|
|
|
|
|
|
|
return instance
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
# Enumerates each module class in the set
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:46:41 +00:00
|
|
|
def each_module(opts = {}, &block)
|
2005-07-11 05:15:30 +00:00
|
|
|
mod_sorted = self.sort if (mod_sorted == nil)
|
2005-07-13 18:06:12 +00:00
|
|
|
|
|
|
|
each_module_list(mod_sorted, opts, &block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def each_module_ranked(opts = {}, &block)
|
|
|
|
mod_ranked = rank_modules if (mod_ranked == nil)
|
|
|
|
|
|
|
|
each_module_list(mod_ranked, opts, &block)
|
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# Custom each_module filtering if an advanced set supports doing extended
|
|
|
|
# filtering. Returns true if the entry should be filtered.
|
|
|
|
#
|
|
|
|
def each_module_filter(opts, name, entry)
|
|
|
|
return false
|
|
|
|
end
|
2005-07-11 05:15:30 +00:00
|
|
|
|
2005-07-13 18:06:12 +00:00
|
|
|
#
|
|
|
|
# Dummy placeholder to relcalculate aliases and other fun things
|
|
|
|
#
|
|
|
|
def recalculate
|
|
|
|
end
|
|
|
|
|
|
|
|
attr_reader :module_type
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
#
|
|
|
|
# Enumerates the modules in the supplied array with possible limiting
|
|
|
|
# factors.
|
|
|
|
#
|
|
|
|
def each_module_list(ary, opts, &block)
|
|
|
|
ary.each { |entry|
|
2005-07-11 05:15:30 +00:00
|
|
|
name, mod = entry
|
|
|
|
|
2005-05-22 07:46:41 +00:00
|
|
|
# Filter out incompatible architectures
|
2005-07-13 18:06:12 +00:00
|
|
|
if (opts['Arch'])
|
2005-05-22 07:46:41 +00:00
|
|
|
if (!mod_arch_hash[mod])
|
|
|
|
mod_arch_hash[mod] = mod.new.arch
|
|
|
|
end
|
|
|
|
|
2005-07-13 18:06:12 +00:00
|
|
|
next if ((mod_arch_hash[mod] & opts['Arch']).empty? == true)
|
2005-05-22 07:46:41 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Filter out incompatible platforms
|
2005-07-13 18:06:12 +00:00
|
|
|
if (opts['Platform'])
|
2005-05-22 07:46:41 +00:00
|
|
|
if (!mod_platform_hash[mod])
|
|
|
|
mod_platform_hash[mod] = mod.new.platform
|
|
|
|
end
|
|
|
|
|
2005-07-13 18:06:12 +00:00
|
|
|
next if ((mod_platform_hash[mod] & opts['Platform']).empty? == true)
|
2005-05-22 07:46:41 +00:00
|
|
|
end
|
|
|
|
|
2005-07-12 05:39:44 +00:00
|
|
|
# Custom filtering
|
|
|
|
next if (each_module_filter(opts, name, entry) == true)
|
|
|
|
|
2005-07-10 00:49:12 +00:00
|
|
|
block.call(name, mod)
|
2005-05-22 07:46:41 +00:00
|
|
|
}
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-12 05:39:44 +00:00
|
|
|
#
|
2005-07-13 18:06:12 +00:00
|
|
|
# Ranks modules based on their constant rank value, if they have one.
|
2005-07-12 05:39:44 +00:00
|
|
|
#
|
2005-07-13 18:06:12 +00:00
|
|
|
def rank_modules
|
|
|
|
mod_ranked = self.sort { |a, b|
|
|
|
|
a_name, a_mod = a
|
|
|
|
b_name, b_mod = b
|
|
|
|
|
|
|
|
# Extract the ranking between the two modules
|
2005-07-13 21:09:07 +00:00
|
|
|
a_rank = a[1].const_defined?('Rank') ? a[1].const_get('Rank') : NormalRanking
|
|
|
|
b_rank = b[1].const_defined?('Rank') ? b[1].const_get('Rank') : NormalRanking
|
2005-07-13 18:06:12 +00:00
|
|
|
|
|
|
|
# Compare their relevant rankings. Since we want highest to lowest,
|
|
|
|
# we compare b_rank to a_rank in terms of higher/lower precedence
|
|
|
|
b_rank <=> a_rank
|
|
|
|
}
|
2005-07-12 05:39:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
#
|
2005-07-10 00:16:48 +00:00
|
|
|
# Adds a module with a the supplied name
|
2005-07-13 18:06:12 +00:00
|
|
|
#
|
2005-07-10 00:16:48 +00:00
|
|
|
def add_module(module_class, name)
|
2005-07-13 18:06:12 +00:00
|
|
|
# Duplicate the module class so that we can operate on a
|
|
|
|
# framework-specific copy of it.
|
|
|
|
dup = module_class.dup
|
|
|
|
|
2005-07-10 19:21:40 +00:00
|
|
|
# Set the module's name so that it can be referenced when
|
|
|
|
# instances are created.
|
2005-07-13 18:06:12 +00:00
|
|
|
dup.framework = framework
|
|
|
|
dup.refname = name
|
2005-07-10 19:21:40 +00:00
|
|
|
|
2005-07-13 18:06:12 +00:00
|
|
|
self[name] = dup
|
2005-07-14 06:34:58 +00:00
|
|
|
|
|
|
|
# Notify the framework that a module was loaded
|
|
|
|
framework.events.on_module_load(name, dup)
|
2005-07-11 05:15:30 +00:00
|
|
|
|
|
|
|
# Invalidate the sorted array
|
2005-07-13 18:06:12 +00:00
|
|
|
invalidate_cache
|
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# Invalidates the sorted and ranked module caches.
|
|
|
|
#
|
|
|
|
def invalidate_cache
|
2005-07-11 05:15:30 +00:00
|
|
|
mod_sorted = nil
|
2005-07-13 18:06:12 +00:00
|
|
|
mod_ranked = nil
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-09 19:35:29 +00:00
|
|
|
attr_writer :module_type
|
2005-05-22 07:46:41 +00:00
|
|
|
attr_accessor :mod_arch_hash, :mod_platform_hash
|
2005-07-13 18:06:12 +00:00
|
|
|
attr_accessor :mod_sorted, :mod_ranked
|
2005-07-14 06:34:58 +00:00
|
|
|
attr_accessor :mod_extensions
|
2005-05-22 07:14:16 +00:00
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
###
|
|
|
|
#
|
|
|
|
# ModuleManager
|
|
|
|
# -------------
|
|
|
|
#
|
|
|
|
# Upper management decided to throw in some middle management
|
|
|
|
# because the modules were getting out of hand. This bad boy
|
|
|
|
# takes care of the work of managing the interaction with
|
|
|
|
# modules in terms of loading and instantiation.
|
|
|
|
#
|
2005-05-22 07:23:25 +00:00
|
|
|
# TODO:
|
|
|
|
#
|
|
|
|
# - add reload support
|
|
|
|
# - add unload support
|
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
###
|
2005-05-22 07:58:02 +00:00
|
|
|
class ModuleManager < ModuleSet
|
2005-05-22 07:14:16 +00:00
|
|
|
|
2005-07-09 21:18:49 +00:00
|
|
|
require 'msf/core/payload_set'
|
2005-07-09 00:24:02 +00:00
|
|
|
|
2005-07-13 18:06:12 +00:00
|
|
|
include Framework::Offspring
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
def initialize(framework)
|
2005-05-22 07:14:16 +00:00
|
|
|
self.module_paths = []
|
|
|
|
self.module_history = {}
|
|
|
|
self.module_history_mtime = {}
|
2005-05-22 07:58:02 +00:00
|
|
|
self.module_sets = {}
|
2005-07-14 06:34:58 +00:00
|
|
|
self.framework = framework
|
2005-05-22 07:14:16 +00:00
|
|
|
|
|
|
|
MODULE_TYPES.each { |type|
|
2005-07-09 00:24:02 +00:00
|
|
|
case type
|
|
|
|
when MODULE_PAYLOAD
|
|
|
|
instance = PayloadSet.new(self)
|
|
|
|
else
|
|
|
|
instance = ModuleSet.new(type)
|
|
|
|
end
|
|
|
|
|
|
|
|
self.module_sets[type] = instance
|
2005-07-13 18:06:12 +00:00
|
|
|
|
|
|
|
# Set the module set's framework reference
|
2005-07-14 06:34:58 +00:00
|
|
|
instance.framework = framework
|
2005-05-22 07:14:16 +00:00
|
|
|
}
|
2005-05-22 07:58:02 +00:00
|
|
|
|
|
|
|
super
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-05-22 07:23:25 +00:00
|
|
|
#
|
|
|
|
# Accessors by module type
|
|
|
|
#
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:23:25 +00:00
|
|
|
# Returns the set of loaded encoder module classes
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def encoders
|
2005-05-22 07:58:02 +00:00
|
|
|
return module_sets[MODULE_ENCODER]
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:23:25 +00:00
|
|
|
# Returns the set of loaded exploit module classes
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def exploits
|
2005-05-22 07:58:02 +00:00
|
|
|
return module_sets[MODULE_EXPLOIT]
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:25:15 +00:00
|
|
|
# Returns the set of loaded nop module classes
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:25:15 +00:00
|
|
|
def nops
|
2005-05-22 20:28:21 +00:00
|
|
|
return module_sets[MODULE_NOP]
|
2005-05-22 07:25:15 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:23:25 +00:00
|
|
|
# Returns the set of loaded payload module classes
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:23:25 +00:00
|
|
|
def payloads
|
2005-05-22 07:58:02 +00:00
|
|
|
return module_sets[MODULE_PAYLOAD]
|
2005-05-22 07:23:25 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:23:25 +00:00
|
|
|
# Returns the set of loaded recon module classes
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def recon
|
2005-05-22 07:58:02 +00:00
|
|
|
return module_sets[MODULE_RECON]
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
##
|
2005-05-22 07:14:16 +00:00
|
|
|
#
|
|
|
|
# Module path management
|
|
|
|
#
|
2005-07-14 06:34:58 +00:00
|
|
|
##
|
2005-05-22 07:14:16 +00:00
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
# Adds a path to be searched for new modules
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def add_module_path(path)
|
2005-06-04 08:23:16 +00:00
|
|
|
path.sub!(/#{File::SEPARATOR}$/, '')
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
# Make sure the path is a valid directory before we try to rock the
|
|
|
|
# house
|
|
|
|
if (File.directory?(path) == false)
|
|
|
|
raise NameError, "The path supplied is not a valid directory.",
|
|
|
|
caller
|
|
|
|
end
|
|
|
|
|
2005-05-22 07:14:16 +00:00
|
|
|
module_paths << path
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
return load_modules(path)
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
# Removes a path from which to search for modules
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def remove_module_path(path)
|
|
|
|
module_paths.delete(path)
|
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
def register_type_extension(type, ext)
|
|
|
|
end
|
|
|
|
|
2005-05-22 07:14:16 +00:00
|
|
|
protected
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
# Load all of the modules from the supplied module path (independent of
|
|
|
|
# module type)
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def load_modules(path)
|
|
|
|
loaded = {}
|
2005-07-09 00:24:02 +00:00
|
|
|
recalc = {}
|
2005-07-14 06:34:58 +00:00
|
|
|
counts = {}
|
2005-05-22 07:14:16 +00:00
|
|
|
|
|
|
|
Find.find(path) { |file|
|
|
|
|
|
|
|
|
# If the file doesn't end in the expected extension...
|
|
|
|
next if (!file.match(/\.rb$/))
|
|
|
|
|
|
|
|
# If the file on disk hasn't changed with what we have stored in the
|
|
|
|
# cache, then there's no sense in loading it
|
|
|
|
if (!has_module_file_changed?(file))
|
|
|
|
dlog("Cached module from file #{file} has not changed.", 'core',
|
|
|
|
LEV_1)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Substitute the base path
|
|
|
|
path_base = file.sub(path + File::SEPARATOR, '')
|
|
|
|
|
2005-07-10 00:49:12 +00:00
|
|
|
# Derive the name from the path with the exclusion of the .rb
|
|
|
|
name = path_base.match(/^(.+?)#{File::SEPARATOR}(.*)(.rb?)$/)[2]
|
|
|
|
|
2005-07-09 23:48:41 +00:00
|
|
|
# Chop off the file name
|
2005-07-10 00:49:12 +00:00
|
|
|
path_base.sub!(/(.+)(#{File::SEPARATOR}.+)(.rb?)$/, '\1')
|
2005-05-22 07:14:16 +00:00
|
|
|
|
2005-07-09 23:48:41 +00:00
|
|
|
# Extract the module's namespace from its path
|
|
|
|
mod = mod_from_name(path_base)
|
|
|
|
type = path_base.match(/^(.+?)#{File::SEPARATOR}+?/)[1].sub(/s$/, '')
|
2005-05-22 07:14:16 +00:00
|
|
|
|
|
|
|
# Get the module and grab the current number of constants
|
2005-07-09 23:48:41 +00:00
|
|
|
old_constants = mod.constants
|
2005-05-22 07:14:16 +00:00
|
|
|
|
|
|
|
# Load the file
|
|
|
|
begin
|
|
|
|
if (!load(file))
|
|
|
|
elog("Failed to load from file #{file}.")
|
|
|
|
next
|
|
|
|
end
|
|
|
|
rescue LoadError
|
|
|
|
elog("LoadError: #{$!}.")
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
added = mod.constants - old_constants
|
|
|
|
|
|
|
|
if (added.length > 1)
|
|
|
|
elog("Loaded file contained more than one class (#{file}).")
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
# If nothing was added, check to see if there's anything
|
|
|
|
# in the cache
|
|
|
|
if (added.empty?)
|
|
|
|
if (module_history[file])
|
|
|
|
added = module_history[file]
|
|
|
|
else
|
|
|
|
elog("Loaded #{file} but no classes were added.")
|
|
|
|
next
|
|
|
|
end
|
|
|
|
else
|
|
|
|
added = mod.const_get(added[0])
|
|
|
|
end
|
|
|
|
|
|
|
|
ilog("Loaded #{type} module #{added} from #{file}.", 'core', LEV_1)
|
|
|
|
|
|
|
|
# Do some processing on the loaded module to get it into the
|
|
|
|
# right associations
|
2005-07-10 00:16:48 +00:00
|
|
|
on_module_load(added, type, name)
|
2005-05-22 07:14:16 +00:00
|
|
|
|
2005-07-09 00:24:02 +00:00
|
|
|
# Set this module type as needing recalculation
|
|
|
|
recalc[type] = true
|
|
|
|
|
2005-05-22 07:14:16 +00:00
|
|
|
# Append the added module to the hash of file->module
|
|
|
|
loaded[file] = added
|
2005-07-14 06:34:58 +00:00
|
|
|
|
|
|
|
# The number of loaded modules this round
|
|
|
|
counts[type] = (counts[type]) ? (counts[type] + 1) : 1
|
2005-05-22 07:14:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Cache the loaded file mtimes
|
|
|
|
loaded.each_key {|file|
|
|
|
|
module_history_mtime[file] = File.new(file).mtime
|
|
|
|
}
|
|
|
|
|
|
|
|
# Cache the loaded file module associations
|
|
|
|
module_history.update(loaded)
|
|
|
|
|
2005-07-09 00:24:02 +00:00
|
|
|
# Perform any required recalculations for the individual module types
|
|
|
|
# that actually had load changes
|
|
|
|
recalc.each_key { |key|
|
|
|
|
module_sets[key].recalculate
|
|
|
|
}
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
# Return per-module loaded counts
|
|
|
|
return counts
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
# Checks to see if the supplied file has changed (if it's even in the
|
|
|
|
# cache)
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def has_module_file_changed?(file)
|
|
|
|
return (module_history_mtime[file] != File.new(file).mtime)
|
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
# Returns the module object that is associated with the supplied module
|
|
|
|
# name
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
def mod_from_name(name)
|
2005-07-09 23:48:41 +00:00
|
|
|
obj = Msf
|
|
|
|
|
|
|
|
name.split(File::SEPARATOR).each { |m|
|
|
|
|
# Up-case the first letter and any prefixed by _
|
|
|
|
m.gsub!(/^[a-z]/) { |s| s.upcase }
|
|
|
|
m.gsub!(/(_[a-z])/) { |s| s[1..1].upcase }
|
2005-05-22 07:14:16 +00:00
|
|
|
|
|
|
|
begin
|
|
|
|
obj = obj.const_get(m)
|
|
|
|
rescue NameError
|
2005-07-09 23:48:41 +00:00
|
|
|
obj = obj.const_set(m, ::Module.new)
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
end
|
|
|
|
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-05-22 07:14:16 +00:00
|
|
|
# Called when a module is initially loaded such that it can be
|
|
|
|
# categorized accordingly
|
2005-07-14 06:34:58 +00:00
|
|
|
#
|
2005-07-10 00:16:48 +00:00
|
|
|
def on_module_load(mod, type, name)
|
2005-07-09 00:24:02 +00:00
|
|
|
# Payload modules require custom loading as the individual files
|
|
|
|
# may not directly contain a logical payload that a user would
|
|
|
|
# reference, such as would be the case with a payload stager or
|
|
|
|
# stage. As such, when payload modules are loaded they are handed
|
|
|
|
# off to a special payload set. The payload set, in turn, will
|
|
|
|
# automatically create all the permutations after all the payload
|
|
|
|
# modules have been loaded.
|
|
|
|
if (type != MODULE_PAYLOAD)
|
|
|
|
# Add the module class to the list of modules and add it to the
|
|
|
|
# type separated set of module classes
|
2005-07-10 00:16:48 +00:00
|
|
|
add_module(mod, name)
|
2005-07-09 00:24:02 +00:00
|
|
|
end
|
2005-07-11 05:15:30 +00:00
|
|
|
|
2005-07-10 00:16:48 +00:00
|
|
|
module_sets[type].add_module(mod, name)
|
2005-05-22 07:14:16 +00:00
|
|
|
end
|
|
|
|
|
2005-05-22 07:58:02 +00:00
|
|
|
attr_accessor :modules, :module_sets
|
2005-05-22 07:14:16 +00:00
|
|
|
attr_accessor :module_paths
|
|
|
|
attr_accessor :module_history, :module_history_mtime
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|