Mirror of metasploit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

msfupdate 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. #!/usr/bin/env ruby
  2. # -*- coding: binary -*-
  3. #
  4. # $Id$
  5. #
  6. # This keeps the framework up-to-date
  7. #
  8. # $Revision$
  9. #
  10. msfbase = __FILE__
  11. while File.symlink?(msfbase)
  12. msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
  13. end
  14. require 'backports'
  15. class Msfupdate
  16. attr_reader :stdin
  17. attr_reader :stdout
  18. attr_reader :stderr
  19. def initialize(msfbase_dir, stdin = $stdin, stdout = $stdout, stderr = $stderr)
  20. @msfbase_dir = msfbase_dir
  21. @stdin = stdin
  22. @stdout = stdout
  23. @stderr = stderr
  24. end
  25. def usage(io = stdout)
  26. help = "usage: msfupdate [options...]\n"
  27. help << "Options:\n"
  28. help << "-h, --help show help\n"
  29. help << " --git-remote REMOTE git remote to use (default upstream)\n" if git?
  30. help << " --git-branch BRANCH git branch to use (default master)\n" if git?
  31. help << " --offline-file FILE offline update file to use\n" if binary_install?
  32. io.print help
  33. end
  34. def parse_args(args)
  35. begin
  36. # GetoptLong uses ARGV, but we want to use the args parameter
  37. # Copy args into ARGV, then restore ARGV after GetoptLong
  38. real_args = ARGV.clone
  39. ARGV.clear
  40. args.each { |arg| ARGV << arg }
  41. require 'getoptlong'
  42. opts = GetoptLong.new(
  43. ['--help', '-h', GetoptLong::NO_ARGUMENT],
  44. ['--git-remote', GetoptLong::REQUIRED_ARGUMENT],
  45. ['--git-branch', GetoptLong::REQUIRED_ARGUMENT],
  46. ['--offline-file', GetoptLong::REQUIRED_ARGUMENT]
  47. )
  48. begin
  49. opts.each do |opt, arg|
  50. case opt
  51. when '--help'
  52. usage
  53. maybe_wait_and_exit
  54. when '--git-remote'
  55. @git_remote = arg
  56. when '--git-branch'
  57. @git_branch = arg
  58. when '--offline-file'
  59. @offline_file = File.expand_path(arg)
  60. end
  61. end
  62. rescue GetoptLong::Error
  63. stderr.puts "#{$PROGRAM_NAME}: try 'msfupdate --help' for more information"
  64. maybe_wait_and_exit 0x20
  65. end
  66. # Handle the old wait/nowait argument behavior
  67. if ARGV[0] == 'wait' || ARGV[0] == 'nowait'
  68. @actually_wait = (ARGV.shift == 'wait')
  69. end
  70. ensure
  71. # Restore the original ARGV value
  72. ARGV.clear
  73. real_args.each { |arg| ARGV << arg }
  74. end
  75. end
  76. def validate_args
  77. valid = true
  78. if binary_install? || apt?
  79. if @git_branch
  80. stderr.puts "[-] ERROR: git-branch is not supported on this installation"
  81. valid = false
  82. end
  83. if @git_remote
  84. stderr.puts "[-] ERROR: git-remote is not supported on this installation"
  85. valid = false
  86. end
  87. end
  88. if apt? || git?
  89. if @offline_file
  90. stderr.puts "[-] ERROR: offline-file option is not supported on this installation"
  91. valid = false
  92. end
  93. end
  94. valid
  95. end
  96. def apt?
  97. File.exist?(File.expand_path(File.join(@msfbase_dir, '.apt')))
  98. end
  99. # Are you an installer, or did you get here via a source checkout?
  100. def binary_install?
  101. File.exist?(File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))) && !apt?
  102. end
  103. def git?
  104. File.directory?(File.join(@msfbase_dir, ".git"))
  105. end
  106. def run!
  107. validate_args || maybe_wait_and_exit(0x13)
  108. stderr.puts "[*]"
  109. stderr.puts "[*] Attempting to update the Metasploit Framework..."
  110. stderr.puts "[*]"
  111. stderr.puts ""
  112. # Bail right away, no waiting around for consoles.
  113. unless Process.uid.zero? || File.stat(@msfbase_dir).owned?
  114. stderr.puts "[-] ERROR: User running msfupdate does not own the Metasploit installation"
  115. stderr.puts "[-] Please run msfupdate as the same user who installed Metasploit."
  116. maybe_wait_and_exit 0x10
  117. end
  118. Dir.chdir(@msfbase_dir) do
  119. if apt?
  120. stderr.puts "[-] ERROR: msfupdate is not supported on Kali Linux."
  121. stderr.puts "[-] Please run 'apt update; apt install metasploit-framework' instead."
  122. elsif binary_install?
  123. update_binary_install!
  124. elsif git?
  125. update_git!
  126. else
  127. raise "Cannot determine checkout type: `#{@msfbase_dir}'"
  128. end
  129. end
  130. end
  131. # We could also do this by running `git config --global user.name` and `git config --global user.email`
  132. # and check the output of those. (it's a bit quieter)
  133. def git_globals_okay?
  134. output = ''
  135. begin
  136. output = `git config --list`
  137. rescue Errno::ENOENT
  138. stderr.puts '[-] ERROR: Failed to check git settings, git not found'
  139. return false
  140. end
  141. unless output.include? 'user.name'
  142. stderr.puts '[-] ERROR: user.name is not set in your global git configuration'
  143. stderr.puts '[-] Set it by running: \'git config --global user.name "NAME HERE"\''
  144. stderr.puts ''
  145. return false
  146. end
  147. unless output.include? 'user.email'
  148. stderr.puts '[-] ERROR: user.email is not set in your global git configuration'
  149. stderr.puts '[-] Set it by running: \'git config --global user.email "email@example.com"\''
  150. stderr.puts ''
  151. return false
  152. end
  153. true
  154. end
  155. def update_git!
  156. ####### Since we're Git, do it all that way #######
  157. stdout.puts "[*] Checking for updates via git"
  158. stdout.puts "[*] Note: Updating from bleeding edge"
  159. out = `git remote show upstream` # Actually need the output for this one.
  160. add_git_upstream unless $?.success? &&
  161. out =~ %r{(https|git|git@github\.com):(//github\.com/)?(rapid7/metasploit-framework\.git)}
  162. remote = @git_remote || "upstream"
  163. branch = @git_branch || "master"
  164. # This will save local changes in a stash, but won't
  165. # attempt to reapply them. If the user wants them back
  166. # they can always git stash pop them, and that presumes
  167. # they know what they're doing when they're editing local
  168. # checkout, which presumes they're not using msfupdate
  169. # to begin with.
  170. #
  171. # Note, this requires at least user.name and user.email
  172. # to be configured in the global git config. Installers
  173. # will be told to set them if they aren't already set.
  174. # Checks user.name and user.email
  175. global_status = git_globals_okay?
  176. maybe_wait_and_exit(1) unless global_status
  177. # We shouldn't get here if the globals dont check out
  178. committed = system("git", "diff", "--quiet", "HEAD")
  179. if committed.nil?
  180. stderr.puts "[-] ERROR: Failed to run git"
  181. stderr.puts ""
  182. stderr.puts "[-] If you used a binary installer, make sure you run the symlink in"
  183. stderr.puts "[-] /usr/local/bin instead of running this file directly (e.g.: ./msfupdate)"
  184. stderr.puts "[-] to ensure a proper environment."
  185. maybe_wait_and_exit 1
  186. elsif !committed
  187. system("git", "stash")
  188. stdout.puts "[*] Stashed local changes to avoid merge conflicts."
  189. stdout.puts "[*] Run `git stash pop` to reapply local changes."
  190. end
  191. system("git", "reset", "HEAD", "--hard")
  192. system("git", "checkout", branch)
  193. system("git", "fetch", remote)
  194. system("git", "merge", "#{remote}/#{branch}")
  195. stdout.puts "[*] Updating gems..."
  196. begin
  197. require 'bundler'
  198. rescue LoadError
  199. stderr.puts '[*] Installing bundler'
  200. system('gem', 'install', 'bundler')
  201. Gem.clear_paths
  202. require 'bundler'
  203. end
  204. Bundler.with_clean_env do
  205. if File::exist? "Gemfile.local"
  206. system("bundle", "install", "--gemfile", "Gemfile.local")
  207. else
  208. system("bundle", "install")
  209. end
  210. end
  211. end
  212. def update_binary_install!
  213. update_script = File.expand_path(File.join(@msfbase_dir, "..", "engine", "update.rb"))
  214. product_key = File.expand_path(File.join(@msfbase_dir, "..", "engine", "license", "product.key"))
  215. if File.exist? product_key
  216. if File.readable? product_key
  217. if @offline_file
  218. system("ruby", update_script, @offline_file)
  219. else
  220. system("ruby", update_script)
  221. end
  222. else
  223. stdout.puts "[-] ERROR: Failed to update Metasploit installation"
  224. stdout.puts ""
  225. stdout.puts "[-] You must be able to read the product key for the"
  226. stdout.puts "[-] Metasploit installation in order to run msfupdate."
  227. stdout.puts "[-] Usually, this means you must be root (EUID 0)."
  228. maybe_wait_and_exit 10
  229. end
  230. else
  231. stdout.puts "[-] ERROR: Failed to update Metasploit installation"
  232. stdout.puts ""
  233. stdout.puts "[-] In order to update your Metasploit installation,"
  234. stdout.puts "[-] you must first register it through the UI, here:"
  235. stderr.puts "[-] https://localhost:3790"
  236. stderr.puts "[-] (Note: Metasploit Community Edition is totally"
  237. stderr.puts "[-] free and takes just a few seconds to register!)"
  238. maybe_wait_and_exit 11
  239. end
  240. end
  241. # Adding an upstream enables msfupdate to pull updates from
  242. # Rapid7's metasploit-framework repo instead of the repo
  243. # the user originally cloned or forked.
  244. def add_git_upstream
  245. stdout.puts "[*] Attempting to add remote 'upstream' to your local git repository."
  246. system("git", "remote", "add", "upstream", "git://github.com/rapid7/metasploit-framework.git")
  247. stdout.puts "[*] Added remote 'upstream' to your local git repository."
  248. end
  249. # This only exits if you actually pass a wait option, otherwise
  250. # just returns nil. This is likely unexpected, revisit this.
  251. def maybe_wait_and_exit(exit_code = 0)
  252. if @actually_wait
  253. stdout.puts ""
  254. stdout.puts "[*] Please hit enter to exit"
  255. stdout.puts ""
  256. stdin.readline
  257. end
  258. exit exit_code
  259. end
  260. def apt_upgrade_available(package)
  261. require 'open3'
  262. installed = nil
  263. upgrade = nil
  264. ::Open3.popen3({ 'LANG' => 'en_US.UTF-8' }, "apt-cache", "policy", package) do |_stdin, stdout, _stderr|
  265. stdout.each do |line|
  266. installed = $1 if line =~ /Installed: ([\w\-+.:~]+)$/
  267. upgrade = $1 if line =~ /Candidate: ([\w\-+.:~]+)$/
  268. break if installed && upgrade
  269. end
  270. end
  271. if installed && installed != upgrade
  272. upgrade
  273. else
  274. nil
  275. end
  276. end
  277. end
  278. if __FILE__ == $PROGRAM_NAME
  279. cli = Msfupdate.new(File.dirname(msfbase))
  280. cli.parse_args(ARGV.dup)
  281. cli.run!
  282. cli.maybe_wait_and_exit
  283. end