class Net::SSH::Config

option to Net::SSH.start. (They are, by default.)
whether the OpenSSH configuration files are read by passing the :config
Note that you will never need to use this class directly–you can control
* FingerprintHash => :fingerprint_hash
* NumberOfPasswordPrompts => :number_of_password_prompts
* UserKnownHostsFile => :user_known_hosts_file
* User => :user
* StrictHostKeyChecking => :verify_host_key
* RekeyLimit => :rekey_limit
* PubKeyAuthentication => maps to the :auth_methods option
* ProxyJump => maps to the :proxy option
* ProxyCommand => maps to the :proxy option
* PreferredAuthentications => maps to the :auth_methods option
* Port => :port
* PasswordAuthentication => maps to the :auth_methods option password
* Macs => maps to the :hmac option
* CheckHostIP => :check_host_ip
* IdentitiesOnly => :keys_only
* IdentityAgent => :identity_agent
* IdentityFile => maps to the :keys option
* HostName => :host_name
* HostKeyAlias => :host_key_alias
* HostKeyAlgorithms => maps to :host_key option
* HostBasedAuthentication => maps to the :auth_methods option
* GlobalKnownHostsFile => :global_known_hosts_file
* ForwardAgent => :forward_agent
* ConnectTimeout => maps to the :timeout option
* CompressionLevel => :compression_level
* Compression => :compression
* Ciphers => maps to the :encryption option
* CertificateFile => maps to the :keycerts option
* KbdInteractiveAuthentication => maps to the :auth_methods keyboard-interactive
* ChallengeResponseAuthentication => maps to the :auth_methods option challenge-response (then coleasced into keyboard-interactive)
Only a subset of OpenSSH configuration options are understood:
and so forth).
some extent) from OpenSSH configuration files (~/.ssh/config, /etc/ssh_config,
understands. This lets Net::SSH scripts read their configuration (to
and translates that syntax into the configuration syntax that Net::SSH
The Net::SSH::Config class is used to parse OpenSSH configuration files,

def default_auth_methods

def default_auth_methods
  @@default_auth_methods.clone
end

def default_files

to parse by default.
Returns an array of locations of OpenSSH configuration files
def default_files
  @@default_files.clone
end

def eval_match_conditions(condition, host, settings)

def eval_match_conditions(condition, host, settings)
  # Not using `\s` for whitespace matching as canonical
  # ssh_config parser implementation (OpenSSH) has specific character set.
  # Ref: https://github.com/openssh/openssh-portable/blob/2581333d564d8697837729b3d07d45738eaf5a54/misc.c#L237-L239
  conditions = condition.split(/[ \t\r\n]+|(?<!=)=(?!=)/).reject(&:empty?)
  return true if conditions == ["all"]
  conditions = conditions.each_slice(2)
  condition_matches = []
  conditions.each do |(kind, exprs)|
    exprs = unquote(exprs)
    case kind.downcase
    when "all"
      raise "all cannot be mixed with other conditions"
    when "host"
      if exprs.start_with?('!')
        negated = true
        exprs = exprs[1..-1]
      else
        negated = false
      end
      condition_met = false
      exprs.split(",").each do |expr|
        condition_met = condition_met || host =~ pattern2regex(expr)
      end
      condition_matches << (true && negated ^ condition_met)
      # else
      # warn "net-ssh: Unsupported expr in Match block: #{kind}"
    end
  end
  !condition_matches.empty? && condition_matches.all?
end

def expandable_default_files

Filters default_files down to the files that are expandable.
def expandable_default_files
  default_files.keep_if do |path|
    File.expand_path(path)
    true
  rescue ArgumentError
    false
  end
end

def for(host, files = expandable_default_files)

recognized by Net::SSH, and returns them.
#default_files), translates the resulting hash into the options
given +files+ (defaulting to the list of files returned by
Loads the configuration data for the given +host+ from all of the
def for(host, files = expandable_default_files)
  translate(files.inject({}) { |settings, file|
    load(file, host, settings)
  })
end

def included_file_paths(base_dir, config_paths)

def included_file_paths(base_dir, config_paths)
  tokenize_config_value(config_paths).flat_map do |path|
    Dir.glob(File.expand_path(path, base_dir)).select { |f| File.file?(f) }
  end
end

def interpret_size(size)

Converts the given size into an integer number of bytes.
def interpret_size(size)
  case size
  when /k$/i then size.to_i * 1024
  when /m$/i then size.to_i * 1024 * 1024
  when /g$/i then size.to_i * 1024 * 1024 * 1024
  else size.to_i
  end
end

def load(path, host, settings = {}, base_dir = nil)

options.)
#translate for how to convert the OpenSSH options into Net::SSH
ones. Returns a hash containing the OpenSSH options. (See
that hash, with existing values taking precedence over newly parsed
given +host+. If +settings+ is given, the options are merged into
Load the OpenSSH configuration settings in the given +file+ for the
def load(path, host, settings = {}, base_dir = nil)
  file = File.expand_path(path)
  base_dir ||= File.dirname(file)
  return settings unless File.readable?(file)
  globals = {}
  block_matched = false
  block_seen = false
  IO.foreach(file) do |line|
    next if line =~ /^\s*(?:#.*)?$/
    if line =~ /^\s*(\S+)\s*=(.*)$/
      key, value = $1, $2
    else
      key, value = line.strip.split(/\s+/, 2)
    end
    # silently ignore malformed entries
    next if value.nil?
    key.downcase!
    value = unquote(value)
    value = case value.strip
            when /^\d+$/ then value.to_i
            when /^no$/i then false
            when /^yes$/i then true
            else value
            end
    if key == 'host'
      # Support "Host host1 host2 hostN".
      # See http://github.com/net-ssh/net-ssh/issues#issue/6
      negative_hosts, positive_hosts = value.to_s.split(/\s+/).partition { |h| h.start_with?('!') }
      # Check for negative patterns first. If the host matches, that overrules any other positive match.
      # The host substring code is used to strip out the starting "!" so the regexp will be correct.
      negative_matched = negative_hosts.any? { |h| host =~ pattern2regex(h[1..-1]) }
      if negative_matched
        block_matched = false
      else
        block_matched = positive_hosts.any? { |h| host =~ pattern2regex(h) }
      end
      block_seen = true
      settings[key] = host
    elsif key == 'match'
      block_matched = eval_match_conditions(value, host, settings)
      block_seen = true
    elsif !block_seen
      case key
      when 'identityfile', 'certificatefile'
        (globals[key] ||= []) << value
      when 'include'
        included_file_paths(base_dir, value).each do |file_path|
          globals = load(file_path, host, globals, base_dir)
        end
      else
        globals[key] = value unless settings.key?(key)
      end
    elsif block_matched
      case key
      when 'identityfile', 'certificatefile'
        (settings[key] ||= []) << value
      when 'include'
        included_file_paths(base_dir, value).each do |file_path|
          settings = load(file_path, host, settings, base_dir)
        end
      else
        settings[key] = value unless settings.key?(key)
      end
    end
    # ProxyCommand and ProxyJump override each other so they need to be tracked togeather
    %w[proxyjump proxycommand].each do |proxy_key|
      if (proxy_value = settings.delete(proxy_key))
        settings['proxy'] ||= [proxy_key, proxy_value]
      end
    end
  end
  globals.merge(settings) do |key, oldval, newval|
    case key
    when 'identityfile', 'certificatefile'
      oldval + newval
    else
      newval
    end
  end
end

def merge_challenge_response_with_keyboard_interactive(hash)

def merge_challenge_response_with_keyboard_interactive(hash)
  if hash[:auth_methods].include?('challenge-response')
    hash[:auth_methods].delete('challenge-response')
    (hash[:auth_methods] << 'keyboard-interactive').uniq!
  end
  hash
end

def pattern2regex(pattern)

host names.
Converts an ssh_config pattern into a regex for matching against
def pattern2regex(pattern)
  tail = pattern
  prefix = String.new
  while !tail.empty? do
    head, sep, tail = tail.partition(/[\*\?]/)
    prefix = prefix + Regexp.quote(head)
    case sep
    when '*'
      prefix += '.*'
    when '?'
      prefix += '.'
    when ''
    else
      fail "Unpexpcted sep:#{sep}"
    end
  end
  Regexp.new("^" + prefix + "$", true)
end

def setup_proxy(type, value)

def setup_proxy(type, value)
  case type
  when 'proxycommand'
    if value !~ /^none$/
      require 'net/ssh/proxy/command'
      Net::SSH::Proxy::Command.new(value)
    end
  when 'proxyjump'
    require 'net/ssh/proxy/jump'
    Net::SSH::Proxy::Jump.new(value)
  end
end

def tokenize_config_value(str)

A token is a word or a quoted sequence of words, separated by whitespaces.
Tokenize string into tokens.
def tokenize_config_value(str)
  str.scan(/([^"\s]+)?(?:"([^"]+)")?\s*/).map(&:join)
end

def translate(settings)

the returned hash will have Symbols for keys.
+settings+ hash must have Strings for keys, all downcased, and
a hash of Net::SSH options. Unrecognized options are ignored. The
Given a hash of OpenSSH configuration options, converts them into
def translate(settings)
  auth_methods = default_auth_methods.clone
  (auth_methods << 'challenge-response').uniq!
  ret = settings.each_with_object({ auth_methods: auth_methods }) do |(key, value), hash|
    translate_config_key(hash, key.to_sym, value, settings)
  end
  merge_challenge_response_with_keyboard_interactive(ret)
end

def translate_config_key(hash, key, value, settings)

def translate_config_key(hash, key, value, settings)
  case key
  when :stricthostkeychecking
    hash[:verify_host_key] = translate_verify_host_key(value)
  when :ciphers
    hash[:encryption] = value.split(/,/)
  when :hostbasedauthentication
    if value
      (hash[:auth_methods] << "hostbased").uniq!
    else
      hash[:auth_methods].delete("hostbased")
    end
  when :hostkeyalgorithms
    hash[:host_key] = value.split(/,/)
  when :hostname
    hash[:host_name] = value.gsub(/%h/, settings['host'])
  when :macs
    hash[:hmac] = value.split(/,/)
  when :serveralivecountmax
    hash[:keepalive_maxcount] = value.to_i if value
  when :serveraliveinterval
    translate_keepalive(hash, value)
  when :passwordauthentication
    if value
      (hash[:auth_methods] << 'password').uniq!
    else
      hash[:auth_methods].delete('password')
    end
  when :challengeresponseauthentication
    if value
      (hash[:auth_methods] << 'challenge-response').uniq!
    else
      hash[:auth_methods].delete('challenge-response')
    end
  when :kbdinteractiveauthentication
    if value
      (hash[:auth_methods] << 'keyboard-interactive').uniq!
    else
      hash[:auth_methods].delete('keyboard-interactive')
    end
  when :preferredauthentications
    hash[:auth_methods] = value.split(/,/) # TODO we should place to preferred_auth_methods rather than auth_methods
  when :proxy
    if (proxy = setup_proxy(*value))
      hash[:proxy] = proxy
    end
  when :pubkeyauthentication
    if value
      (hash[:auth_methods] << 'publickey').uniq!
    else
      hash[:auth_methods].delete('publickey')
    end
  when :rekeylimit
    hash[:rekey_limit] = interpret_size(value)
  when :sendenv
    multi_send_env = value.to_s.split(/\s+/)
    hash[:send_env] = multi_send_env.map { |e| Regexp.new pattern2regex(e).source, false }
  when :setenv
    hash[:set_env] = Shellwords.split(value.to_s).map { |e| e.split '=', 2 }.to_h
  when :numberofpasswordprompts
    hash[:number_of_password_prompts] = value.to_i
  when *TRANSLATE_CONFIG_KEY_RENAME_MAP.keys
    hash[TRANSLATE_CONFIG_KEY_RENAME_MAP[key]] = value
  end
end

def translate_keepalive(hash, value)

def translate_keepalive(hash, value)
  if value && value.to_i > 0
    hash[:keepalive] = true
    hash[:keepalive_interval] = value.to_i
  else
    hash[:keepalive] = false
  end
end

def translate_verify_host_key(value)

def translate_verify_host_key(value)
  case value
  when false
    :never
  when true
    :always
  when 'accept-new'
    :accept_new
  end
end

def unquote(string)

def unquote(string)
  string =~ /^"(.*)"$/ ? Regexp.last_match(1) : string
end