/lib/net/ssh/known_hosts.rb

https://github.com/tecknicaltom/metasploit-framework · Ruby · 132 lines · 66 code · 21 blank · 45 comment · 11 complexity · 53868548ba342d257b3d9e28893ad74d MD5 · raw file

  1. require 'strscan'
  2. require 'net/ssh/buffer'
  3. module Net; module SSH
  4. # Searches an OpenSSH-style known-host file for a given host, and returns all
  5. # matching keys. This is used to implement host-key verification, as well as
  6. # to determine what key a user prefers to use for a given host.
  7. #
  8. # This is used internally by Net::SSH, and will never need to be used directly
  9. # by consumers of the library.
  10. class KnownHosts
  11. class <<self
  12. # Searches all known host files (see KnownHosts.hostfiles) for all keys
  13. # of the given host. Returns an array of keys found.
  14. def search_for(host, options={})
  15. search_in(hostfiles(options), host)
  16. end
  17. # Search for all known keys for the given host, in every file given in
  18. # the +files+ array. Returns the list of keys.
  19. def search_in(files, host)
  20. files.map { |file| KnownHosts.new(file).keys_for(host) }.flatten
  21. end
  22. # Looks in the given +options+ hash for the :user_known_hosts_file and
  23. # :global_known_hosts_file keys, and returns an array of all known
  24. # hosts files. If the :user_known_hosts_file key is not set, the
  25. # default is returned (~/.ssh/known_hosts and ~/.ssh/known_hosts2). If
  26. # :global_known_hosts_file is not set, the default is used
  27. # (/etc/ssh/known_hosts and /etc/ssh/known_hosts2).
  28. #
  29. # If you only want the user known host files, you can pass :user as
  30. # the second option.
  31. def hostfiles(options, which=:all)
  32. files = []
  33. if which == :all || which == :user
  34. files += Array(options[:user_known_hosts_file] || %w(~/.ssh/known_hosts ~/.ssh/known_hosts2))
  35. end
  36. if which == :all || which == :global
  37. files += Array(options[:global_known_hosts_file] || %w(/etc/ssh/known_hosts /etc/ssh/known_hosts2))
  38. end
  39. return files
  40. end
  41. # Looks in all user known host files (see KnownHosts.hostfiles) and tries to
  42. # add an entry for the given host and key to the first file it is able
  43. # to.
  44. def add(host, key, options={})
  45. hostfiles(options, :user).each do |file|
  46. begin
  47. KnownHosts.new(file).add(host, key)
  48. return
  49. rescue SystemCallError
  50. # try the next hostfile
  51. end
  52. end
  53. end
  54. end
  55. # The host-key file name that this KnownHosts instance will use to search
  56. # for keys.
  57. attr_reader :source
  58. # Instantiate a new KnownHosts instance that will search the given known-hosts
  59. # file. The path is expanded file File.expand_path.
  60. def initialize(source)
  61. @source = File.expand_path(source)
  62. end
  63. # Returns an array of all keys that are known to be associatd with the
  64. # given host. The +host+ parameter is either the domain name or ip address
  65. # of the host, or both (comma-separated). Additionally, if a non-standard
  66. # port is being used, it may be specified by putting the host (or ip, or
  67. # both) in square brackets, and appending the port outside the brackets
  68. # after a colon. Possible formats for +host+, then, are;
  69. #
  70. # "net.ssh.test"
  71. # "1.2.3.4"
  72. # "net.ssh.test,1.2.3.4"
  73. # "[net.ssh.test]:5555"
  74. # "[1,2,3,4]:5555"
  75. # "[net.ssh.test]:5555,[1.2.3.4]:5555
  76. def keys_for(host)
  77. keys = []
  78. return keys unless File.readable?(source)
  79. entries = host.split(/,/)
  80. File.open(source) do |file|
  81. scanner = StringScanner.new("")
  82. file.each_line do |line|
  83. scanner.string = line
  84. scanner.skip(/\s*/)
  85. next if scanner.match?(/$|#/)
  86. hostlist = scanner.scan(/\S+/).split(/,/)
  87. next unless entries.all? { |entry| hostlist.include?(entry) }
  88. scanner.skip(/\s*/)
  89. type = scanner.scan(/\S+/)
  90. next unless %w(ssh-rsa ssh-dss).include?(type)
  91. scanner.skip(/\s*/)
  92. blob = scanner.rest.unpack("m*").first
  93. keys << Net::SSH::Buffer.new(blob).read_key
  94. end
  95. end
  96. keys
  97. end
  98. # Tries to append an entry to the current source file for the given host
  99. # and key. If it is unable to (because the file is not writable, for
  100. # instance), an exception will be raised.
  101. def add(host, key)
  102. # Forget that. No way I want this thing writing to my known_hosts file.
  103. # Some day, make this configurable. Until that day, off by default.
  104. return
  105. File.open(source, "a") do |file|
  106. blob = [Net::SSH::Buffer.from(:key, key).to_s].pack("m*").gsub(/\s/, "")
  107. file.puts "#{host} #{key.ssh_type} #{blob}"
  108. end
  109. end
  110. end
  111. end; end