diff --git a/msfrpcd b/msfrpcd index e3c5103c22..d0c57775e6 100755 --- a/msfrpcd +++ b/msfrpcd @@ -4,52 +4,161 @@ # $Id$ # # This user interface listens on a port and provides clients that connect to -# it with an RPC interface to the Metasploit Framework. +# it with an RPC or JSON-RPC interface to the Metasploit Framework. # # $Revision$ # -msfbase = __FILE__ -while File.symlink?(msfbase) - msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) +RPC_TYPE = 'Msg' + +@localconf = "#{ENV['HOME']}/.msf4" +@ws_tag = 'msf-ws' +@ws_rpc_tag = 'msf-json-rpc' +@ws_conf_full_path = nil +@ws_conf = "#{@ws_rpc_tag}.ru" +@ws_ssl_key_default = "#{@localconf}/#{@ws_tag}-key.pem" +@ws_ssl_cert_default = "#{@localconf}/#{@ws_tag}-cert.pem" +@ws_log = "#{@localconf}/logs/#{@ws_rpc_tag}.log" +@ws_rpc_pid = "#{@localconf}/#{@ws_rpc_tag}.pid" +@ws_tag = 'production' + + +def start_json_rpc_service(conf:, address:, port:, ssl:, ssl_key:, ssl_cert:, ssl_disable_verify:, daemonize:) + unless File.file?(conf) + $stdout.puts "[-] No MSF JSON-RPC web service configuration found at #{conf}, not starting" + return false + end + + # check if MSF JSON-RPC web service is already started + if File.file?(@ws_rpc_pid) + ws_pid = Msf::Util::ServiceHelper.tail(@ws_rpc_pid) + if ws_pid.nil? || !Msf::Util::ServiceHelper.process_active?(ws_pid.to_i) + $stdout.puts "[-] MSF JSON-RPC web service PID file found, but no active process running as PID #{ws_pid}" + $stdout.puts "[*] Deleting MSF JSON-RPC web service PID file #{@ws_rpc_pid}" + File.delete(@ws_rpc_pid) + else + $stdout.puts "[*] MSF JSON-RPC web service is already running as PID #{ws_pid}" + return false + end + end + + # attempt to start MSF JSON-RPC service + thin_cmd = Msf::Util::ServiceHelper.thin_cmd(conf: conf, + address: address, + port: port, + ssl: ssl, + ssl_key: ssl_key, + ssl_cert: ssl_cert, + ssl_disable_verify: ssl_disable_verify, + env: @ws_tag, + daemonize: daemonize, + log: @ws_log, + pid: @ws_rpc_pid, + tag: @ws_rpc_tag) + Msf::Util::ServiceHelper.run_cmd("#{thin_cmd} start") end -$:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'msfenv' +def stop_json_rpc_service(conf:, address:, port:, ssl:, ssl_key:, ssl_cert:, ssl_disable_verify:, daemonize:) + ws_pid = Msf::Util::ServiceHelper.tail(@ws_rpc_pid) + $stdout.puts '' + if ws_pid.nil? || !Msf::Util::ServiceHelper.process_active?(ws_pid.to_i) + $stdout.puts '[*] MSF JSON-RPC web service is no longer running' + if File.file?(@ws_rpc_pid) + $stdout.puts "[*] Deleting MSF JSON-RPC web service PID file #{@ws_rpc_pid}" + File.delete(@ws_rpc_pid) + end + else + $stdout.puts "[*] Stopping MSF JSON-RPC web service PID #{ws_pid}" + thin_cmd = Msf::Util::ServiceHelper.thin_cmd(conf: conf, + address: address, + port: port, + ssl: ssl, + ssl_key: ssl_key, + ssl_cert: ssl_cert, + ssl_disable_verify: ssl_disable_verify, + env: @ws_tag, + daemonize: daemonize, + log: @ws_log, + pid: @ws_rpc_pid, + tag: @ws_rpc_tag) + Msf::Util::ServiceHelper.run_cmd("#{thin_cmd} stop") + end +end -$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] +def start_rpc_service(opts, frameworkOpts, foreground) + # Fork into the background if requested + begin + if foreground + $stdout.puts "[*] #{RPC_TYPE.upcase}RPC ready at #{Time.now}." + else + $stderr.puts "[*] #{RPC_TYPE.upcase}RPC backgrounding at #{Time.now}..." + exit(0) if Process.fork() + end + rescue ::NotImplementedError + $stderr.puts "[-] Background mode is not available on this platform" + end -require 'rex/parser/arguments' + # Create an instance of the framework + $framework = Msf::Simple::Framework.create(frameworkOpts) -# Declare the argument parser for msfrpcd -arguments = Rex::Parser::Arguments.new( - "-a" => [ true, "Bind to this IP address" ], - "-p" => [ true, "Bind to this port instead of 55553" ], - "-U" => [ true, "Specify the username to access msfrpcd" ], - "-P" => [ true, "Specify the password to access msfrpcd" ], - "-u" => [ true, "URI for Web server" ], - "-t" => [ true, "Token Timeout (default 300 seconds" ], - "-S" => [ false, "Disable SSL on the RPC socket" ], - "-f" => [ false, "Run the daemon in the foreground" ], - "-n" => [ false, "Disable database" ], - "-h" => [ false, "Help banner" ]) - -opts = { - 'RunInForeground' => true, - 'SSL' => true, - 'ServerHost' => '0.0.0.0', - 'ServerPort' => 55553, - 'ServerType' => 'Msg', - 'TokenTimeout' => 300, -} - -foreground = false -frameworkOpts = {} + # Run the plugin instance in the foreground. + begin + $framework.plugins.load("#{RPC_TYPE.downcase}rpc", opts).run + rescue ::Interrupt + $stderr.puts "[*] Shutting down" + end +end -# Parse command line arguments. -arguments.parse(ARGV) { |opt, idx, val| - case opt +if $PROGRAM_NAME == __FILE__ + msfbase = __FILE__ + while File.symlink?(msfbase) + msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) + end + + $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) + require 'msfenv' + + $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] + + require 'rex/parser/arguments' + + opts = { + 'RunInForeground' => true, + 'SSL' => true, + 'ServerHost' => '0.0.0.0', + 'ServerPort' => 55553, + 'ServerType' => RPC_TYPE, + 'TokenTimeout' => 300, + } + + # Declare the argument parser for msfrpcd + arguments = Rex::Parser::Arguments.new( + "-a" => [ true, "Bind to this IP address (default: #{opts['ServerHost']})" ], + "-p" => [ true, "Bind to this port (default: #{opts['ServerPort']})" ], + "-U" => [ true, "Specify the username to access msfrpcd" ], + "-P" => [ true, "Specify the password to access msfrpcd" ], + "-u" => [ true, "URI for Web server" ], + "-t" => [ true, "Token Timeout seconds (default: #{opts['TokenTimeout']})" ], + "-S" => [ false, "Disable SSL on the RPC socket" ], + "-f" => [ false, "Run the daemon in the foreground" ], + "-n" => [ false, "Disable database" ], + "-j" => [ false, "(JSON-RPC) Start JSON-RPC server" ], + "-k" => [ false, "(JSON-RPC) Path to private key (default: #{@ws_ssl_key_default})" ], + "-c" => [ false, "(JSON-RPC) Path to certificate (default: #{@ws_ssl_cert_default})" ], + "-v" => [ false, "(JSON-RPC) SSL enable verify (optional) client cert requests" ], + "-h" => [ false, "Help banner" ]) + + foreground = false + json_rpc = false + ssl_enable_verify = false + ws_ssl_key = @ws_ssl_key_default + ws_ssl_cert = @ws_ssl_cert_default + frameworkOpts = {} + + # Parse command line arguments. + arguments.parse(ARGV) { |opt, idx, val| + case opt when "-a" opts['ServerHost'] = val when "-S" @@ -68,47 +177,67 @@ arguments.parse(ARGV) { |opt, idx, val| opts['URI'] = val when "-n" frameworkOpts['DisableDatabase'] = true + when "-j" + json_rpc = true + when "-k" + ws_ssl_key = val + when "-c" + ws_ssl_cert = val + when "-v" + ssl_enable_verify = true when "-h" print("\nUsage: #{File.basename(__FILE__)} \n" + arguments.usage) exit + end + } + + $0 = "msfrpcd" + + require 'msf/base' + require 'msf/ui' + require 'msf/util/service_helper' + + begin + if json_rpc + + if !File.file?(@ws_ssl_key_default) || !File.file?(@ws_ssl_cert_default) + $stdout.puts "[-] It doesn't appear msfdb has been run; please run 'msfdb init' first." + abort + end + + $stderr.puts "[*] JSON-RPC starting on #{opts['ServerHost']}:#{opts['ServerPort']} (#{opts['SSL'] ? "SSL" : "NO SSL"})..." + $stderr.puts "[*] URI: /api//json-rpc" + $stderr.puts "[*] JSON-RPC server log: #{@ws_log}" unless foreground + + ws_conf_full_path = File.expand_path(File.join(File.dirname(msfbase), @ws_conf)) + + start_json_rpc_service(conf: ws_conf_full_path, + address: opts['ServerHost'], + port: opts['ServerPort'], + ssl: opts['SSL'], + ssl_key: ws_ssl_key, + ssl_cert: ws_ssl_cert, + ssl_disable_verify: !ssl_enable_verify, + daemonize: !foreground) + else + unless opts['Pass'] + $stderr.puts "[-] Error: a password must be specified (-P)" + exit(0) + end + + $stderr.puts "[*] #{RPC_TYPE.upcase}RPC starting on #{opts['ServerHost']}:#{opts['ServerPort']} (#{opts['SSL'] ? "SSL" : "NO SSL"}):#{opts['ServerType']}..." + $stderr.puts "[*] URI: #{opts['URI']}" if opts['URI'] + + start_rpc_service(opts, frameworkOpts, foreground) + end + rescue ::Interrupt + stop_json_rpc_service(conf: ws_conf_full_path, + address: opts['ServerHost'], + port: opts['ServerPort'], + ssl: opts['SSL'], + ssl_key: ws_ssl_key, + ssl_cert: ws_ssl_cert, + ssl_disable_verify: !ssl_enable_verify, + daemonize: !foreground) if json_rpc end -} - -unless opts['Pass'] - $stderr.puts "[-] Error: a password must be specified (-P)" - exit(0) -end - -$0 = "msfrpcd" - -rpctype = 'MSG' - -$stderr.puts "[*] #{rpctype}RPC starting on #{opts['ServerHost']}:#{opts['ServerPort']} (#{opts['SSL'] ? "SSL" : "NO SSL"}):#{opts['ServerType']}..." - -$stderr.puts "[*] URI: #{opts['URI']}" if opts['URI'] - -require 'msf/base' -require 'msf/ui' - - -# Fork into the background if requested -begin - if foreground - $stdout.puts "[*] #{rpctype}RPC ready at #{Time.now}." - else - $stderr.puts "[*] #{rpctype}RPC backgrounding at #{Time.now}..." - exit(0) if Process.fork() - end -rescue ::NotImplementedError - $stderr.puts "[-] Background mode is not available on this platform" -end - -# Create an instance of the framework -$framework = Msf::Simple::Framework.create(frameworkOpts) - -# Run the plugin instance in the foreground. -begin - $framework.plugins.load("#{rpctype.downcase}rpc", opts).run -rescue ::Interrupt - $stderr.puts "[*] Shutting down" -end +end \ No newline at end of file