diff --git a/lib/metasploit/framework/command/console.rb b/lib/metasploit/framework/command/console.rb index 61ebe9dc30e7..b0b4a7f0caf6 100644 --- a/lib/metasploit/framework/command/console.rb +++ b/lib/metasploit/framework/command/console.rb @@ -84,6 +84,7 @@ def driver_options driver_options['DisableDatabase'] = options.database.disable driver_options['HistFile'] = options.console.histfile driver_options['LocalOutput'] = options.console.local_output + driver_options['Logger'] = options.console.logger driver_options['ModulePath'] = options.modules.path driver_options['Plugins'] = options.console.plugins driver_options['RealReadline'] = options.console.real_readline diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb index 70aba2ce828b..7089c56778af 100644 --- a/lib/metasploit/framework/parsed_options/console.rb +++ b/lib/metasploit/framework/parsed_options/console.rb @@ -11,6 +11,7 @@ def options options.console.commands = [] options.console.confirm_exit = false options.console.histfile = nil + options.console.logger = nil options.console.local_output = nil options.console.plugins = [] options.console.quiet = false @@ -43,6 +44,10 @@ def option_parser options.console.histfile = file end + option_parser.on('-l', '--logger STRING', "Specify a logger to use (#{Rex::Logging::LogSinkFactory.available_sinks.join(', ')})") do |logger| + options.console.logger = logger + end + option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do options.console.real_readline = true end diff --git a/lib/msf/base/logging.rb b/lib/msf/base/logging.rb index 286e903e568c..f86da7da54cf 100644 --- a/lib/msf/base/logging.rb +++ b/lib/msf/base/logging.rb @@ -16,13 +16,16 @@ class Logging # Initialize logging. # + # @param log_sink_name [string] Log sink name. # @return [void] - def self.init + def self.init(log_sink_name = nil) if (! @@initialized) @@initialized = true - f = Rex::Logging::Sinks::Flatfile.new( - Msf::Config.log_directory + File::SEPARATOR + "framework.log") + log_sink ||= Rex::Logging::LogSinkFactory.new( + log_sink_name, + Msf::Config.log_directory + File::SEPARATOR + "framework.log" + ) # Register each known log source [ @@ -30,7 +33,7 @@ def self.init Msf::LogSource, 'base', ].each { |src| - register_log_source(src, f) + register_log_source(src, log_sink) } end end @@ -80,7 +83,7 @@ def self.session_logging_enabled? # @return [void] def self.start_session_log(session) if (log_source_registered?(session.log_source) == false) - f = Rex::Logging::Sinks::TimestampFlatfile.new( + f = Rex::Logging::Sinks::TimestampColorlessFlatfile.new( Msf::Config.session_log_directory + File::SEPARATOR + "#{session.log_file_name}.log") register_log_source(session.log_source, f) diff --git a/lib/msf/base/simple/framework.rb b/lib/msf/base/simple/framework.rb index 67211fb41c65..48ab768e6723 100644 --- a/lib/msf/base/simple/framework.rb +++ b/lib/msf/base/simple/framework.rb @@ -85,6 +85,7 @@ def self.create(opts = {}) # @option opts [#call] 'OnCreateProc' Proc to call after {#init_simplified}. Will be passed `framework`. # @option opts [String] 'ConfigDirectory' Directory where configuration is saved. The `~/.msf4` directory. # @option opts [Boolean] 'DisableLogging' (false) `true` to disable `Msf::Logging.init` + # @option opts [String] 'Logger' (Flatfile) Will default to logging to `~/.msf4`. # @option opts [Boolean] 'DeferModuleLoads' (false) `true` to disable `framework.init_module_paths`. # @return [Msf::Simple::Framework] `framework` def self.simplify(framework, opts) @@ -110,7 +111,10 @@ def self.simplify(framework, opts) # Initialize configuration and logging Msf::Config.init - Msf::Logging.init unless opts['DisableLogging'] + unless opts['DisableLogging'] + log_sink_name = opts['Logger'] + Msf::Logging.init(log_sink_name) + end # Load the configuration framework.load_config diff --git a/lib/msf/core/web_services/framework_extension.rb b/lib/msf/core/web_services/framework_extension.rb index 4497a19656a5..92822de3aca2 100644 --- a/lib/msf/core/web_services/framework_extension.rb +++ b/lib/msf/core/web_services/framework_extension.rb @@ -17,6 +17,8 @@ module Msf::WebServices # MSF_WS_DATA_SERVICE_CERT - Certificate file matching the remote data server's certificate. # Needed when using self-signed SSL certificates. # MSF_WS_DATA_SERVICE_SKIP_VERIFY - (Boolean) Skip validating authenticity of server's certificate. + # MSF_WS_DATA_SERVICE_LOGGER - (String) The logger that framework will use. By default logs will be + # placed in ``~/.msf4/logs` module FrameworkExtension FALSE_VALUES = [nil, false, 0, '0', 'f', 'false', 'off', 'no'].to_set @@ -37,21 +39,22 @@ def self.registered(app) @@framework = nil # Create simplified instance of the framework app.set :framework, Proc.new { - @@framework ||= - begin - framework = Msf::Simple::Framework.create + @@framework ||= begin + init_framework_opts = { + 'Logger' => ENV.fetch('MSF_WS_DATA_SERVICE_LOGGER', nil) + } + framework = Msf::Simple::Framework.create(init_framework_opts) - if !app.settings.data_service_url.nil? && !app.settings.data_service_url.empty? - framework_db_connect_http_data_service(framework: framework, - data_service_url: app.settings.data_service_url, - api_token: app.settings.data_service_api_token, - cert: app.settings.data_service_cert, - skip_verify: app.settings.data_service_skip_verify) - end - - framework - end + if !app.settings.data_service_url.nil? && !app.settings.data_service_url.empty? + framework_db_connect_http_data_service(framework: framework, + data_service_url: app.settings.data_service_url, + api_token: app.settings.data_service_api_token, + cert: app.settings.data_service_cert, + skip_verify: app.settings.data_service_skip_verify) + end + framework + end } end @@ -90,4 +93,4 @@ def self.to_bool(value) !FALSE_VALUES.include?(value) end end -end \ No newline at end of file +end diff --git a/lib/rex/logging/log_dispatcher.rb b/lib/rex/logging/log_dispatcher.rb index aceedf658841..ddac0de9c9d4 100644 --- a/lib/rex/logging/log_dispatcher.rb +++ b/lib/rex/logging/log_dispatcher.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- require 'rex/sync' require 'rex/logging/log_sink' +require 'rex/logging/log_sink_factory' module Rex module Logging diff --git a/lib/rex/logging/log_sink.rb b/lib/rex/logging/log_sink.rb index 9408cfc27a33..47da7357ec52 100644 --- a/lib/rex/logging/log_sink.rb +++ b/lib/rex/logging/log_sink.rb @@ -37,7 +37,3 @@ def get_current_timestamp end end - -require 'rex/logging/sinks/flatfile' -require 'rex/logging/sinks/stderr' -require 'rex/logging/sinks/timestamp_flatfile' diff --git a/lib/rex/logging/log_sink_factory.rb b/lib/rex/logging/log_sink_factory.rb new file mode 100644 index 000000000000..fd4d9c067282 --- /dev/null +++ b/lib/rex/logging/log_sink_factory.rb @@ -0,0 +1,43 @@ +# -*- coding: binary -*- + +require 'rex/logging/sinks/stream' +require 'rex/logging/sinks/flatfile' +require 'rex/logging/sinks/timestamp_colorless_flatfile' +require 'rex/logging/sinks/stderr' +require 'rex/logging/sinks/stdout' +require 'rex/logging/sinks/stdout_without_timestamps' + +module Rex + module Logging + + ### + # + # LogSinkFactory can instantiate a LogSink based on the given name. + # + ### + module LogSinkFactory + # Creates a new log sink of the given name. If no name is provided, a default + # Flatfile log sink is chosen + # + # @param [String] name The name of the required log sink within Rex::Logging::Sinks + # @param [Array] attrs The attributes to use with the given log sink + # @return [Rex::Logging::LogSink] The newly created log sink + def self.new(name = nil, *attrs) + name ||= Rex::Logging::Sinks::Flatfile.name.demodulize + raise NameError unless available_sinks.include?(name.to_sym) + + log_sink = Rex::Logging::Sinks.const_get(name) + log_sink.new(*attrs) + rescue NameError + raise Rex::ArgumentError, "Could not find logger #{name}, expected one of #{available_sinks.join(', ')}" + end + + # Returns a list of the available sinks that can be created by this factory + # + # @return [Array] The available sinks that can be created by this factory + def self.available_sinks + Rex::Logging::Sinks.constants - [Rex::Logging::Sinks::Stream.name.demodulize.to_sym] + end + end + end +end diff --git a/lib/rex/logging/sinks/flatfile.rb b/lib/rex/logging/sinks/flatfile.rb index e08cbc3dae29..8391916cda8c 100644 --- a/lib/rex/logging/sinks/flatfile.rb +++ b/lib/rex/logging/sinks/flatfile.rb @@ -9,48 +9,16 @@ module Sinks # file on disk. # ### -class Flatfile - - include Rex::Logging::LogSink +class Flatfile < Rex::Logging::Sinks::Stream # # Creates a flatfile log sink instance that will be configured to log to # the supplied file path. # def initialize(file) - self.fd = File.new(file, "a") - end - - def cleanup # :nodoc: - fd.close + super(File.new(file, 'a')) end - def log(sev, src, level, msg) # :nodoc: - if (sev == LOG_RAW) - fd.write(msg) - else - code = 'i' - - case sev - when LOG_DEBUG - code = 'd' - when LOG_ERROR - code = 'e' - when LOG_INFO - code = 'i' - when LOG_WARN - code = 'w' - end - fd.write("[#{get_current_timestamp}] [#{code}(#{level})] #{src}: #{msg}\n") - end - - fd.flush - end - -protected - - attr_accessor :fd # :nodoc: - end end end end diff --git a/lib/rex/logging/sinks/stderr.rb b/lib/rex/logging/sinks/stderr.rb index 5e50faf4cc31..f5aefdfe0389 100644 --- a/lib/rex/logging/sinks/stderr.rb +++ b/lib/rex/logging/sinks/stderr.rb @@ -7,38 +7,15 @@ module Sinks # # This class implements the LogSink interface and backs it against stderr ### -class Stderr - - include Rex::Logging::LogSink +class Stderr < Rex::Logging::Sinks::Stream # - # Writes log data to stderr + # Creates a log sink instance that will be configured to log to stderr # - - def log(sev, src, level, msg) # :nodoc: - if (sev == LOG_RAW) - $stderr.write(msg) - else - code = 'i' - - case sev - when LOG_DEBUG - code = 'd' - when LOG_ERROR - code = 'e' - when LOG_INFO - code = 'i' - when LOG_WARN - code = 'w' - end - $stderr.write("[#{get_current_timestamp}] [#{code}(#{level})] #{src}: #{msg}\n") - end - - $stderr.flush + def initialize(*_attrs) + super($stderr) end -protected - end end end end diff --git a/lib/rex/logging/sinks/stdout.rb b/lib/rex/logging/sinks/stdout.rb new file mode 100644 index 000000000000..a4c5c473b2ec --- /dev/null +++ b/lib/rex/logging/sinks/stdout.rb @@ -0,0 +1,21 @@ +# -*- coding: binary -*- +module Rex +module Logging +module Sinks + +### +# +# This class implements the LogSink interface and backs it against stdout +### +class Stdout < Rex::Logging::Sinks::Stream + + # + # Creates a log sink instance that will be configured to log to stdout + # + def initialize(*_attrs) + super($stdout) + end + +end + +end end end diff --git a/lib/rex/logging/sinks/stdout_without_timestamps.rb b/lib/rex/logging/sinks/stdout_without_timestamps.rb new file mode 100644 index 000000000000..a9acbe27f230 --- /dev/null +++ b/lib/rex/logging/sinks/stdout_without_timestamps.rb @@ -0,0 +1,37 @@ +# -*- coding: binary -*- +module Rex +module Logging +module Sinks + +### +# +# This class implements the LogSink interface and backs it against stdout +### +class StdoutWithoutTimestamps < Rex::Logging::Sinks::Stream + + # + # Creates a log sink instance that will be configured to log to stdout + # + def initialize(*_attrs) + super($stdout) + end + + # + # Writes log data to a stream + # + # + # Writes log data to a stream + # + def log(sev, src, level, msg) # :nodoc: + if sev == LOG_RAW + stream.write(msg) + else + stream.write("[#{log_code_for(sev)}(#{level})] #{src}: #{msg}\n") + end + + stream.flush + end + +end + +end end end diff --git a/lib/rex/logging/sinks/stream.rb b/lib/rex/logging/sinks/stream.rb new file mode 100644 index 000000000000..452e9d184e41 --- /dev/null +++ b/lib/rex/logging/sinks/stream.rb @@ -0,0 +1,61 @@ +# -*- coding: binary -*- +module Rex +module Logging +module Sinks + +### +# +# This class implements the LogSink interface and backs it against a stream +### +class Stream + + include Rex::Logging::LogSink + + def initialize(stream) + @stream = stream + end + + # + # Writes log data to a stream + # + def log(sev, src, level, msg) # :nodoc: + if sev == LOG_RAW + stream.write(msg) + else + stream.write("[#{get_current_timestamp}] [#{log_code_for(sev)}(#{level})] #{src}: #{msg}\n") + end + + stream.flush + end + + def cleanup # :nodoc: + stream.close + end + + protected + + attr_accessor :stream # :nodoc: + + # + # This method returns the corresponding log code for the given severity + # + def log_code_for(sev) + code = 'i' + + case sev + when LOG_DEBUG + code = 'd' + when LOG_ERROR + code = 'e' + when LOG_INFO + code = 'i' + when LOG_WARN + code = 'w' + end + + code + end + +end + +end end end diff --git a/lib/rex/logging/sinks/timestamp_colorless_flatfile.rb b/lib/rex/logging/sinks/timestamp_colorless_flatfile.rb new file mode 100644 index 000000000000..5c42b4465742 --- /dev/null +++ b/lib/rex/logging/sinks/timestamp_colorless_flatfile.rb @@ -0,0 +1,24 @@ +# -*- coding: binary -*- +module Rex +module Logging +module Sinks + +### +# +# This class implements the LogSink interface and backs it against a +# file on disk. The logged messages will have their colors and trailing +# whitespace removed +# +### +class TimestampColorlessFlatfile < Flatfile + def log(sev, src, level, msg) # :nodoc: + return unless msg.present? + if sev == LOG_RAW + msg = msg.gsub(/\x1b\[[0-9;]*[mG]/,'').gsub(/[\x01-\x02]/, ' ').gsub(/\s+$/,'') + msg = "[#{get_current_timestamp}] #{msg}\n" + end + super(sev, src, level, msg) + end +end + +end end end diff --git a/lib/rex/logging/sinks/timestamp_flatfile.rb b/lib/rex/logging/sinks/timestamp_flatfile.rb deleted file mode 100644 index 20295f9a612e..000000000000 --- a/lib/rex/logging/sinks/timestamp_flatfile.rb +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: binary -*- -module Rex -module Logging -module Sinks - -### -# -# This class implements the LogSink interface and backs it against a -# file on disk with a Timestamp. -# -### -class TimestampFlatfile < Flatfile - - def log(sev, src, level, msg) # :nodoc: - return unless msg.present? - msg = msg.gsub(/\x1b\[[0-9;]*[mG]/,'').gsub(/[\x01-\x02]/, ' ').gsub(/\s+$/,'') - fd.write("[#{get_current_timestamp}] #{msg}\n") - fd.flush - end -end - -end end end