metasploit-framework/msfupdate

316 lines
9.9 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env ruby
# -*- coding: binary -*-
2013-10-10 20:25:00 +08:00
#
# $Id$
2013-10-10 20:25:00 +08:00
#
# This keeps the framework up-to-date
#
# $Revision$
2013-10-10 20:25:00 +08:00
#
msfbase = __FILE__
2012-10-04 06:06:38 +08:00
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
2013-11-16 05:24:35 +08:00
class Msfupdate
2013-11-16 07:15:15 +08:00
attr_reader :stdin
2013-11-16 05:24:35 +08:00
attr_reader :stdout
attr_reader :stderr
def initialize(msfbase_dir, stdin = $stdin, stdout = $stdout, stderr = $stderr)
2013-11-16 05:24:35 +08:00
@msfbase_dir = msfbase_dir
2013-11-16 07:15:15 +08:00
@stdin = stdin
2013-11-16 05:24:35 +08:00
@stdout = stdout
@stderr = stderr
end
def usage(io = stdout)
help = "usage: msfupdate [options...]\n"
help << "Options:\n"
help << "-h, --help show help\n"
help << " --git-remote REMOTE git remote to use (default upstream)\n" if git?
help << " --git-branch BRANCH git branch to use (default master)\n" if git?
help << " --offline-file FILE offline update file to use\n" if binary_install?
io.print help
2013-11-16 01:02:16 +08:00
end
2013-11-16 05:24:35 +08:00
def parse_args(args)
begin
# GetoptLong uses ARGV, but we want to use the args parameter
# Copy args into ARGV, then restore ARGV after GetoptLong
real_args = ARGV.clone
ARGV.clear
args.each { |arg| ARGV << arg }
2013-11-16 01:02:16 +08:00
2013-11-16 05:24:35 +08:00
require 'getoptlong'
opts = GetoptLong.new(
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--git-remote', GetoptLong::REQUIRED_ARGUMENT],
['--git-branch', GetoptLong::REQUIRED_ARGUMENT],
['--offline-file', GetoptLong::REQUIRED_ARGUMENT]
2013-11-16 05:24:35 +08:00
)
2013-11-16 05:24:35 +08:00
begin
opts.each do |opt, arg|
case opt
when '--help'
usage
maybe_wait_and_exit
2013-11-16 05:24:35 +08:00
when '--git-remote'
@git_remote = arg
when '--git-branch'
@git_branch = arg
when '--offline-file'
@offline_file = File.expand_path(arg)
end
end
rescue GetoptLong::Error
stderr.puts "#{$PROGRAM_NAME}: try 'msfupdate --help' for more information"
maybe_wait_and_exit 0x20
2013-11-16 05:24:35 +08:00
end
2013-11-16 05:24:35 +08:00
# Handle the old wait/nowait argument behavior
if ARGV[0] == 'wait' || ARGV[0] == 'nowait'
@actually_wait = (ARGV.shift == 'wait')
end
2013-11-16 05:24:35 +08:00
ensure
# Restore the original ARGV value
ARGV.clear
real_args.each { |arg| ARGV << arg }
2013-11-16 05:24:35 +08:00
end
end
def validate_args
valid = true
2017-05-27 13:34:12 +08:00
if binary_install? || apt?
if @git_branch
stderr.puts "[-] ERROR: git-branch is not supported on this installation"
valid = false
end
if @git_remote
stderr.puts "[-] ERROR: git-remote is not supported on this installation"
valid = false
end
end
2017-05-27 13:34:12 +08:00
if apt? || git?
if @offline_file
stderr.puts "[-] ERROR: offline-file option is not supported on this installation"
valid = false
end
end
valid
end
2013-11-16 05:24:35 +08:00
def apt?
2016-04-20 20:11:34 +08:00
File.exist?(File.expand_path(File.join(@msfbase_dir, '.apt')))
2013-11-16 05:24:35 +08:00
end
2013-11-16 05:24:35 +08:00
# Are you an installer, or did you get here via a source checkout?
def binary_install?
2016-04-20 20:11:34 +08:00
File.exist?(File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))) && !apt?
2013-11-16 05:24:35 +08:00
end
2013-11-16 05:24:35 +08:00
def git?
File.directory?(File.join(@msfbase_dir, ".git"))
end
2013-01-15 06:26:42 +08:00
2013-11-16 05:24:35 +08:00
def run!
validate_args || maybe_wait_and_exit(0x13)
2013-11-16 05:24:35 +08:00
stderr.puts "[*]"
stderr.puts "[*] Attempting to update the Metasploit Framework..."
stderr.puts "[*]"
stderr.puts ""
# Bail right away, no waiting around for consoles.
unless Process.uid.zero? || File.stat(@msfbase_dir).owned?
2013-11-16 05:24:35 +08:00
stderr.puts "[-] ERROR: User running msfupdate does not own the Metasploit installation"
stderr.puts "[-] Please run msfupdate as the same user who installed Metasploit."
maybe_wait_and_exit 0x10
end
2013-11-16 05:24:35 +08:00
Dir.chdir(@msfbase_dir) do
2017-05-26 23:02:51 +08:00
if apt?
2017-05-26 23:10:20 +08:00
stderr.puts "[-] ERROR: msfupdate is not supported on Kali Linux."
stderr.puts "[-] Please run 'apt update; apt install metasploit-framework' instead."
2013-11-16 05:24:35 +08:00
elsif binary_install?
update_binary_install!
2017-05-26 23:02:51 +08:00
elsif git?
update_git!
2013-11-16 05:24:35 +08:00
else
raise "Cannot determine checkout type: `#{@msfbase_dir}'"
2013-11-16 05:24:35 +08:00
end
end
end
# We could also do this by running `git config --global user.name` and `git config --global user.email`
2017-04-01 11:30:43 +08:00
# and check the output of those. (it's a bit quieter)
2017-04-01 06:41:42 +08:00
def git_globals_okay?
2017-04-01 11:30:43 +08:00
output = ''
begin
output = `git config --list`
rescue Errno::ENOENT
stderr.puts '[-] ERROR: Failed to check git settings, git not found'
2017-04-01 06:41:42 +08:00
return false
end
2017-04-01 11:30:43 +08:00
unless output.include? 'user.name'
2017-04-01 06:41:42 +08:00
stderr.puts '[-] ERROR: user.name is not set in your global git configuration'
stderr.puts '[-] Set it by running: \'git config --global user.name "NAME HERE"\''
2017-04-01 11:30:43 +08:00
stderr.puts ''
2017-04-01 06:41:42 +08:00
return false
end
2017-04-01 11:30:43 +08:00
unless output.include? 'user.email'
2017-04-01 06:41:42 +08:00
stderr.puts '[-] ERROR: user.email is not set in your global git configuration'
stderr.puts '[-] Set it by running: \'git config --global user.email "email@example.com"\''
2017-04-01 11:30:43 +08:00
stderr.puts ''
2017-04-01 06:41:42 +08:00
return false
end
true
end
2013-11-16 05:24:35 +08:00
def update_git!
####### Since we're Git, do it all that way #######
stdout.puts "[*] Checking for updates via git"
stdout.puts "[*] Note: Updating from bleeding edge"
out = `git remote show upstream` # Actually need the output for this one.
add_git_upstream unless $?.success? &&
out =~ %r{(https|git|git@github\.com):(//github\.com/)?(rapid7/metasploit-framework\.git)}
2013-11-16 05:24:35 +08:00
remote = @git_remote || "upstream"
branch = @git_branch || "master"
# This will save local changes in a stash, but won't
# attempt to reapply them. If the user wants them back
# they can always git stash pop them, and that presumes
# they know what they're doing when they're editing local
# checkout, which presumes they're not using msfupdate
# to begin with.
#
# Note, this requires at least user.name and user.email
2017-04-01 06:41:42 +08:00
# to be configured in the global git config. Installers
# will be told to set them if they aren't already set.
# Checks user.name and user.email
global_status = git_globals_okay?
maybe_wait_and_exit(1) unless global_status
2017-04-01 06:41:42 +08:00
# We shouldn't get here if the globals dont check out
2013-11-16 05:24:35 +08:00
committed = system("git", "diff", "--quiet", "HEAD")
if committed.nil?
stderr.puts "[-] ERROR: Failed to run git"
stderr.puts ""
stderr.puts "[-] If you used a binary installer, make sure you run the symlink in"
stderr.puts "[-] /usr/local/bin instead of running this file directly (e.g.: ./msfupdate)"
stderr.puts "[-] to ensure a proper environment."
maybe_wait_and_exit 1
elsif !committed
2013-11-16 05:24:35 +08:00
system("git", "stash")
stdout.puts "[*] Stashed local changes to avoid merge conflicts."
stdout.puts "[*] Run `git stash pop` to reapply local changes."
end
2013-11-16 05:24:35 +08:00
system("git", "reset", "HEAD", "--hard")
system("git", "checkout", branch)
system("git", "fetch", remote)
system("git", "merge", "#{remote}/#{branch}")
stdout.puts "[*] Updating gems..."
begin
require 'bundler'
rescue LoadError
stderr.puts '[*] Installing bundler'
system('gem', 'install', 'bundler')
Gem.clear_paths
require 'bundler'
end
2013-11-16 05:24:35 +08:00
Bundler.with_clean_env do
if File::exist? "Gemfile.local"
system("bundle", "install", "--gemfile", "Gemfile.local")
else
system("bundle", "install")
end
2013-11-16 05:24:35 +08:00
end
end
2013-11-16 05:24:35 +08:00
def update_binary_install!
update_script = File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))
product_key = File.expand_path(File.join(@msfbase_dir, "..", "engine", "license", "product.key"))
2016-04-20 20:11:34 +08:00
if File.exist? product_key
2013-11-16 05:24:35 +08:00
if File.readable? product_key
if @offline_file
2013-11-16 05:24:35 +08:00
system("ruby", update_script, @offline_file)
else
system("ruby", update_script)
end
else
2013-11-16 05:24:35 +08:00
stdout.puts "[-] ERROR: Failed to update Metasploit installation"
stdout.puts ""
stdout.puts "[-] You must be able to read the product key for the"
stdout.puts "[-] Metasploit installation in order to run msfupdate."
stdout.puts "[-] Usually, this means you must be root (EUID 0)."
maybe_wait_and_exit 10
end
else
2013-11-16 05:24:35 +08:00
stdout.puts "[-] ERROR: Failed to update Metasploit installation"
stdout.puts ""
stdout.puts "[-] In order to update your Metasploit installation,"
stdout.puts "[-] you must first register it through the UI, here:"
stderr.puts "[-] https://localhost:3790"
stderr.puts "[-] (Note: Metasploit Community Edition is totally"
stderr.puts "[-] free and takes just a few seconds to register!)"
maybe_wait_and_exit 11
end
end
2013-11-16 05:24:35 +08:00
# Adding an upstream enables msfupdate to pull updates from
# Rapid7's metasploit-framework repo instead of the repo
# the user originally cloned or forked.
def add_git_upstream
stdout.puts "[*] Attempting to add remote 'upstream' to your local git repository."
system("git", "remote", "add", "upstream", "git://github.com/rapid7/metasploit-framework.git")
stdout.puts "[*] Added remote 'upstream' to your local git repository."
end
# This only exits if you actually pass a wait option, otherwise
# just returns nil. This is likely unexpected, revisit this.
def maybe_wait_and_exit(exit_code = 0)
2013-11-16 05:24:35 +08:00
if @actually_wait
stdout.puts ""
stdout.puts "[*] Please hit enter to exit"
stdout.puts ""
stdin.readline
end
exit exit_code
2013-11-16 05:24:35 +08:00
end
2013-11-16 05:24:35 +08:00
def apt_upgrade_available(package)
require 'open3'
installed = nil
upgrade = nil
::Open3.popen3({ 'LANG' => 'en_US.UTF-8' }, "apt-cache", "policy", package) do |_stdin, stdout, _stderr|
2013-11-16 05:24:35 +08:00
stdout.each do |line|
installed = $1 if line =~ /Installed: ([\w\-+.:~]+)$/
upgrade = $1 if line =~ /Candidate: ([\w\-+.:~]+)$/
break if installed && upgrade
end
end
if installed && installed != upgrade
upgrade
else
nil
end
end
end
2013-01-15 06:26:42 +08:00
2013-11-16 05:24:35 +08:00
if __FILE__ == $PROGRAM_NAME
cli = Msfupdate.new(File.dirname(msfbase))
cli.parse_args(ARGV.dup)
cli.run!
cli.maybe_wait_and_exit
2013-11-16 05:24:35 +08:00
end