[
  {
    "path": ".gitignore",
    "content": "/_build\n/deps\nerl_crash.dump\n*.ez\n*.swp\n*.kate-swp\n.kateproject.d\n.zedstate\n.directory\n.elixir_ls\n\n/config/prod.exs\n\n/rel\n/doc\n"
  },
  {
    "path": ".tool-versions",
    "content": "elixir 1.8.1-otp-21\nerlang 21.2.5\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: elixir\nelixir:\n  - 1.8\notp_release:\n    21.0\nsudo: false\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Rubén Caro\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "----\nThe actively maintained version of SSHEx is now in https://github.com/witchtails/sshex\n\nThis repo has been archived.\n----\n\n# SSHEx\n[![Build Status](https://travis-ci.org/rubencaro/sshex.svg?branch=master)](https://travis-ci.org/rubencaro/sshex)\n[![Hex Version](http://img.shields.io/hexpm/v/sshex.svg?style=flat)](https://hex.pm/packages/sshex)\n[![Hex Version](http://img.shields.io/hexpm/dt/sshex.svg?style=flat)](https://hex.pm/packages/sshex)\n\nSimple SSH helpers for Elixir.\n\nLibrary to unify helpers already used on several applications. It uses low level Erlang [ssh library](http://www.erlang.org/doc/man/ssh.html).\n\nThe only purpose of these helpers is to avoid repetitive patterns seen when working with SSH from Elixir. It doesn't mean to hide anything from the venerable code underneath. If there's an ugly crash from `:ssh` it will come back as `{:error, reason}`.\n\n## Installation\n\nJust add `{:sshex, \"2.2.1\"}` to your deps on `mix.exs` and run `mix deps.get`\n\n## Use\n\nThen assuming `:ssh` application is already started with `:ssh.start` (hence it is listed on deps), you should acquire an SSH connection using `SSHEx.connect/1` like this:\n\n1.You can use `ssh-copy-id myuser@123.123.123.123.123` to copy ssh key to remote host and then connect using this line:\n```elixir\n{:ok, conn} = SSHEx.connect ip: '123.123.123.123', user: 'myuser'\n```\n2.You can supply the password:\n```elixir\n{:ok, conn} = SSHEx.connect ip: '123.123.123.123', user: 'myuser', password: 'your-password'\n```\n\nThen you can use the acquired `conn` with the `cmd!/4` helper like this:\n\n```elixir\nSSHEx.cmd! conn, 'mkdir -p /path/to/newdir'\nres = SSHEx.cmd! conn, 'ls /some/path'\n```\n\nThis is meant to run commands which you don't care about the return code. `cmd!/3` will return the output of the command only, and __will raise any errors__. If you want to check the status code, and control errors too, you can use `run/3` like this:\n\n```elixir\n{:ok, _, 0} = SSHEx.run conn, 'rm -fr /something/to/delete'\n{:ok, res, 0} = SSHEx.run conn, 'ls /some/path'\n{:error, reason} = SSHEx.run failing_conn, 'ls /some/path'\n```\n\nYou can pass the option `:separate_streams` to get separated stdout and stderr. Like this:\n\n```elixir\n{:ok, stdout, stderr, 2} = SSHEx.run conn, 'ls /nonexisting/path', separate_streams: true\n```\n\nYou will be reusing the same SSH connection all over.\n\n\n## Streaming\n\nYou can use `SSHEx` to run some command and create a [`Stream`](http://elixir-lang.org/docs/stable/elixir/Stream.html), so you can lazily process an arbitrarily long output as it arrives. Internally `Stream.resource/3` is used to create the `Stream`, and every response from `:ssh` is emitted so it can be easily matched with a simple `case`.\n\nYou just have to use `stream/3` like this:\n\n```elixir\nstr = SSHEx.stream conn, 'somecommand'\n\nEnum.each(str, fn(x)->\n  case x do\n    {:stdout, row}    -> process_stdout(row)\n    {:stderr, row}    -> process_stderr(row)\n    {:status, status} -> process_exit_status(status)\n    {:error, reason}  -> process_error(reason)\n  end\nend)\n```\n\n## Alternative keys\n\nTo use alternative keys you should save them somewhere on disk and then set the `:user_dir` option for `SSHEx.connect/4`. See [ssh library docs](http://www.erlang.org/doc/man/ssh.html) for more options.\n\n\n## TODOs\n\n* Add tunnelling helpers [*](http://erlang.org/pipermail/erlang-questions/2014-June/079481.html)\n\n## Changelog\n\n### 2.2.1\n\n* Add new possible exit_signal message format\n\n### 2.2.0\n\n* Add ability to specify ssh key and known hosts\n* Make it Elixir string friendly\n\n### 2.1.2\n\n* Remove Elixir 1.4 warnings\n\n### 2.1.1\n\n* Close channel after stream\n\n### 2.1.0\n\n* Add `connect/1` to improve testability by easier mocking\n\n### 2.0.1\n\n* Avoid some Elixir 1.2.0 warnings\n* Adjust the SSH flow control window to handle long outputs (fixes #4).\n\n### 2.0.0\n\nBackwards incompatible changes:\n* Remove every `raise`, get clean controlled `{:error, reason}` responses\n* Put every optional parameter under a unique Keyword list\n\n### 1.3.1\n\n* Fix Elixir version requested. Use >= 1.0 now.\n\n### 1.3\n\n* Support streaming via `stream/3`\n* Stop using global mocks (i.e. `:meck`)\n\n### 1.2\n\n* Uniform `raise` behaviour on `:ssh` errors.\n* Document and test `:ssh` error handling\n\n### 1.1\n\n* Add support for separate stdout/stderr responses.\n\n### 1.0\n\n* Initial release\n"
  },
  {
    "path": "lib/sshex/configurable_client_keys.ex",
    "content": "defmodule SSHEx.ConfigurableClientKeys do\n  @moduledoc ~S\"\"\"\n  Provides public key behavior for SSH clients.\n  \n  valid options: \n    - `key`: `IO.device` providing the ssh key (required)\n    - `known_hosts`: `IO.device` providing the known hosts list (required)\n    - `accept_hosts`: `boolean` silently accept and add new hosts to the known hosts. By default only known hosts will be accepted. \n  `\n  SSHEx.connect(\n      ip: to_charlist(hostname), \n      user: to_charlist(username), \n      key_cb: {SSHEx.ConfigurableClientKeys, [\n        key: <IO.device>,\n        known_hosts: <IO.device> ]}\n      )\n  `\n  A convenience method is provided that can take filenames instead of IO devices\n\n  `\n  cb_module = SSHEx.ConfigurableClientKeys.get_cb_module(key_file: \"path/to/keyfile\", known_hosts_file: \"path_to_known_hostsFile\", accept_hosts: false)\n  SSHEx.connect(\n      ip: to_charlist(hostname), \n      user: to_charlist(username), \n      key_cb: cb_module\n      )\n  `\n\n  \"\"\"\n\n  @behaviour :ssh_client_key_api\n\n\n  @spec add_host_key(hostname :: charlist, key :: :public_key.public_key , opts :: list) :: :ok | {:error, term}\n  def add_host_key(hostname, key, opts) do  \n    case accept_hosts(opts) do\n      true -> \n        opts\n        |> known_hosts\n        |> IO.read(:all)\n        |> :public_key.ssh_decode(:known_hosts)\n        |> (fn decoded -> decoded ++ [{key, [{:hostnames, [hostname]}]}] end).()\n        |> :public_key.ssh_encode(:known_hosts)\n        |> (fn encoded -> IO.write(known_hosts(opts), encoded) end).()\n      _ -> \n        message = \n          \"\"\"\n          Error: unknown fingerprint found for #{inspect hostname} #{inspect key}.\n          You either need to add a known good fingerprint to your known hosts file for this host,\n          *or* pass the accept_hosts option to your client key callback\n          \"\"\"        \n        {:error, message}\n    end    \n  end\n\n  @spec is_host_key(key :: :public_key.public_key, hostname :: charlist, alg :: :ssh_client_key_api.algorithm, opts :: list) :: boolean\n  def is_host_key(key, hostname, _alg, opts) do\n    opts\n    |> known_hosts    \n    |> IO.read(:all)\n    |> :public_key.ssh_decode(:known_hosts)\n    |> has_fingerprint(key, hostname)\n  end\n\n  @spec user_key(alg :: :ssh_client_key_api.algorithm, opts :: list) :: {:error, term} | {:ok, :public_key.private_key}\n  def user_key(_alg, opts) do\n    material =\n      opts\n      |> key\n      |> IO.read(:all)\n      |> :public_key.pem_decode\n      |> List.first\n      |> :public_key.pem_entry_decode\n    {:ok, material}\n  end\n\n  @spec get_cb_module(opts :: list) :: {atom, list}\n  def get_cb_module(opts) do\n    opts = \n      opts\n      |> Keyword.put(:key, File.open!(opts[:key_file]))\n      |> Keyword.put(:known_hosts, File.open!(opts[:known_hosts_file]))   \n    {__MODULE__, opts}\n  end\n\n  @spec key(opts :: list) :: IO.device\n  defp key(opts) do\n    cb_opts(opts)[:key]\n  end\n\n  @spec accept_hosts(opts :: list) :: boolean\n  defp accept_hosts(opts) do\n    cb_opts(opts)[:accept_hosts]\n  end\n\n  @spec known_hosts(opts :: list) :: IO.device  \n  defp known_hosts(opts) do\n    cb_opts(opts)[:known_hosts]\n  end\n\n  @spec cb_opts(opts :: list) :: list\n  defp cb_opts(opts) do\n    opts[:key_cb_private]\n  end\n\n  defp has_fingerprint(fingerprints, key, hostname) do \n    Enum.any?(fingerprints, \n      fn {k, v} -> (k == key) && (Enum.member?(v[:hostnames], hostname)) end\n      )\n  end\nend\n"
  },
  {
    "path": "lib/sshex/helpers.ex",
    "content": "defmodule SSHEx.Helpers do\n  @moduledoc \"\"\"\n    require SSHEx.Helpers, as: H  # the cool way\n  \"\"\"\n  @doc \"\"\"\n    Convenience to get environment bits. Avoid all that repetitive\n    `Application.get_env( :myapp, :blah, :blah)` noise.\n  \"\"\"\n  def env(key, default \\\\ nil), do: env(Mix.Project.get!.project[:app], key, default)\n  def env(app, key, default), do: Application.get_env(app, key, default)\n\n  @doc \"\"\"\n  Spit to output any passed variable, with location information.\n  If `sample` option is given, it should be a float between 0.0 and 1.0.\n  Output will be produced randomly with that probability.\n  Given `opts` will be fed straight into `inspect`. Any option accepted by it should work.\n  \"\"\"\n  defmacro spit(obj \\\\ \"\", opts \\\\ []) do\n    quote do\n      opts = unquote(opts)\n      obj = unquote(obj)\n      opts = Keyword.put(opts, :env, __ENV__)\n\n      SSHEx.Helpers.maybe_spit(obj, opts, opts[:sample])\n      obj  # chainable\n    end\n  end\n\n  @doc false\n  def maybe_spit(obj, opts, nil), do: do_spit(obj, opts)\n  def maybe_spit(obj, opts, prob) when is_float(prob) do\n    if :rand.uniform <= prob, do: do_spit(obj, opts)\n  end\n\n  defp do_spit(obj, opts) do\n    %{file: file, line: line} = opts[:env]\n    name = Process.info(self())[:registered_name]\n    chain = [ :bright, :red, \"\\n\\n#{file}:#{line}\", :normal, \"\\n     #{inspect self()}\", :green,\" #{name}\"]\n\n    msg = inspect(obj, opts)\n    chain = chain ++ [:red, \"\\n\\n#{msg}\"]\n\n    (chain ++ [\"\\n\\n\", :reset]) |> IO.ANSI.format(true) |> IO.puts\n  end\n\n  @doc \"\"\"\n    Print to stdout a _TODO_ message, with location information.\n  \"\"\"\n  defmacro todo(msg \\\\ \"\") do\n    quote do\n      %{file: file, line: line} = __ENV__\n      [ :yellow, \"\\nTODO: #{file}:#{line} #{unquote(msg)}\\n\", :reset]\n      |> IO.ANSI.format(true)\n      |> IO.puts\n      :todo\n    end\n  end\n\n  @doc \"\"\"\n    Apply given defaults to given Keyword. Returns merged Keyword.\n\n    The inverse of `Keyword.merge`, best suited to apply some defaults in a\n    chainable way.\n\n    Ex:\n      kw = gather_data\n        |> transform_data\n        |> H.defaults(k1: 1234, k2: 5768)\n        |> here_i_need_defaults\n\n    Instead of:\n      kw1 = gather_data\n        |> transform_data\n      kw = [k1: 1234, k2: 5768]\n        |> Keyword.merge(kw1)\n        |> here_i_need_defaults\n\n  \"\"\"\n  def defaults(args, defs) do\n    defs |> Keyword.merge(args)\n  end\n\n  def convert_values(args) do\n    Enum.map(args, fn {k, v} -> {k, convert_value(v)} end)\n  end\n\n  def convert_value(v) when is_binary(v) do\n    String.to_charlist(v)\n  end\n\n  def convert_value(v), do: v\n\nend\n"
  },
  {
    "path": "lib/sshex.ex",
    "content": "require SSHEx.Helpers, as: H\n\ndefmodule SSHEx do\n\n  @moduledoc \"\"\"\n    Module to deal with SSH connections. It uses low level erlang\n    [ssh library](http://www.erlang.org/doc/man/ssh.html).\n\n    :ssh.start # just in case\n    {:ok, conn} = SSHEx.connect ip: '123.123.123.123', user: 'myuser'\n  \"\"\"\n\n  @doc \"\"\"\n    Establish a connection with given options. Uses `:ssh.connect/4` for that.\n\n    Recognised options are `ip` (mandatory), `port` and `negotiation_timeout`.\n    Any other option is passed to `:ssh.connect/4` as is\n    (so be careful if you use binaries and `:ssh` expects char lists...).\n    See [its reference](http://erlang.org/doc/man/ssh.html#connect-4) for available options.\n\n    Default values exist for some options, which are:\n    * `port`: 22\n    * `negotiation_timeout`: 5000\n    * `silently_accept_hosts`: `true`\n\n    Returns `{:ok, connection}`, or `{:error, reason}`.\n  \"\"\"\n  def connect(opts) do\n    opts =\n      opts\n      |> H.convert_values\n      |> H.defaults(port: 22,\n                    negotiation_timeout: 5000,\n                    silently_accept_hosts: true,\n                    ssh_module: :ssh)\n\n    own_keys = [:ip, :port, :negotiation_timeout, :ssh_module]\n\n    ssh_opts = opts |> Enum.filter(fn({k,_})-> not (k in own_keys) end)\n\n    opts[:ssh_module].connect(opts[:ip], opts[:port], ssh_opts, opts[:negotiation_timeout])\n  end\n\n  @doc \"\"\"\n    Gets an open SSH connection reference (as returned by `:ssh.connect/4`),\n    and a command to execute.\n\n    Optionally it gets a `channel_timeout` for the underlying SSH channel opening,\n    and an `exec_timeout` for the execution itself. Both default to 5000ms.\n\n    Returns `{:ok,data,status}` on success. Otherwise `{:error, details}`.\n\n    If `:separate_streams` is `true` then the response on success looks like `{:ok,stdout,stderr,status}`.\n\n    Ex:\n\n    ```\n    {:ok, _, 0} = SSHEx.run conn, 'rm -fr /something/to/delete'\n    {:ok, res, 0} = SSHEx.run conn, 'ls /some/path'\n    {:error, reason} = SSHEx.run failing_conn, 'ls /some/path'\n    {:ok, stdout, stderr, 2} = SSHEx.run conn, 'ls /nonexisting/path', separate_streams: true\n    ```\n  \"\"\"\n  def run(conn, cmd, opts \\\\ []) do\n    opts =\n      opts\n      |> H.convert_values\n      |> H.defaults(connection_module: :ssh_connection,\n                    channel_timeout: 5000,\n                    exec_timeout: 5000)\n    cmd = H.convert_value(cmd)\n    case open_channel_and_exec(conn, cmd, opts) do\n      {:error, r} -> {:error, r}\n      chn -> get_response(conn, chn, opts[:exec_timeout], \"\", \"\", nil, false, opts)\n    end\n  end\n\n  @doc \"\"\"\n    Convenience function to run `run/3` and get output string straight from it,\n    like `:os.cmd/1`.\n\n    See `run/3` for options.\n\n    Returns `response` only if `run/3` return value matches `{:ok, response, _}`,\n    or returns `{stdout, stderr}` if `run/3` returns `{:ok, stdout, stderr, _}`.\n    Raises any `{:error, details}` returned by `run/3`. Note return status from\n    `cmd` is also ignored.\n\n    Ex:\n\n    ```\n        SSHEx.cmd! conn, 'mkdir -p /path/to/newdir'\n        res = SSHEx.cmd! conn, 'ls /some/path'\n    ```\n  \"\"\"\n  def cmd!(conn, cmd, opts \\\\ []) do\n    case run(conn, cmd, opts) do\n      {:ok, response, _} -> response\n      {:ok, stdout, stderr, _} -> {stdout, stderr}\n      any -> raise inspect(any)\n    end\n  end\n\n  @doc \"\"\"\n    Gets an open SSH connection reference (as returned by `:ssh.connect/4`),\n    and a command to execute.\n\n    See `run/3` for options.\n\n    Returns a `Stream` that you can use to lazily retrieve each line of output\n    for the given command.\n\n    Each iteration of the stream will read from the underlying connection and\n    return one of these:\n\n    * `{:stdout,row}`\n    * `{:stderr,row}`\n    * `{:status,status}`\n    * `{:error,reason}`\n\n    Keep in mind that rows may not be received in order.\n\n    Ex:\n    ```\n      {:ok, conn} = :ssh.connect('123.123.123.123', 22,\n                    [ {:user,'myuser'}, {:silently_accept_hosts, true} ], 5000)\n\n      str = SSHEx.stream conn, 'somecommand'\n\n      Stream.each(str, fn(x)->\n        case x do\n          {:stdout,row}    -> process_stdout(row)\n          {:stderr,row}    -> process_stderr(row)\n          {:status,status} -> process_exit_status(status)\n          {:error,reason}  -> process_error(row)\n        end\n      end)\n    ```\n  \"\"\"\n  def stream(conn, cmd, opts \\\\ []) do\n    opts =\n      opts\n      |> H.convert_values\n      |> H.defaults(connection_module: :ssh_connection,\n                    channel_timeout: 5000,\n                    exec_timeout: 5000)\n\n    cmd = H.convert_value(cmd)\n    start_fun = fn-> open_channel_and_exec(conn,cmd,opts) end\n\n    next_fun = fn(input)->\n      case input do\n        :halt_next -> {:halt, 'Halt requested on previous iteration'}\n        {:error, _} = x -> {[x], :halt_next} # emit error, then halt\n        chn -> do_stream_next(conn, chn, opts)\n      end\n    end\n\n    after_fun = fn(channel) ->\n      :ok = opts[:connection_module].close(conn, channel)\n    end\n\n    Stream.resource start_fun, next_fun, after_fun\n  end\n\n  # Actual mapping of `:ssh` responses into streamable chunks\n  #\n  defp do_stream_next(conn, channel, opts) do\n    case receive_and_parse_response(conn, channel, opts[:connection_module], opts[:exec_timeout]) do\n      {:loop, {_, _, \"\", \"\", nil, false}} -> {[], channel}\n      {:loop, {_, _,  x, \"\", nil, false}} -> {[ {:stdout,x} ], channel}\n      {:loop, {_, _, \"\",  x, nil, false}} -> {[ {:stderr,x} ], channel}\n      {:loop, {_, _, \"\", \"\",   x, false}} -> {[ {:status,x} ], channel}\n      {:loop, {_, _, \"\", \"\", nil, true }} -> {:halt, channel}\n      {:error, _} = x -> {[x], :halt_next} # emit error, then halt\n    end\n  end\n\n  # Try to get the channel, and then execute the given command.\n  # Just a DRY to call internal `open_channel/3` and `exec/5`.\n  #\n  defp open_channel_and_exec(conn, cmd, opts) do\n    case open_channel(conn, opts[:channel_timeout], opts[:connection_module]) do\n      {:error, r} -> {:error, r}\n      {:ok, chn} -> exec(chn, conn, cmd, opts[:exec_timeout], opts[:connection_module])\n    end\n  end\n\n  # Try to get the channel\n  #\n  defp open_channel(conn, channel_timeout, connection_module) do\n    connection_module.session_channel(conn, channel_timeout)\n  end\n\n  # Execute the given command. Map every error to `{:error,reason}`.\n  #\n  defp exec(channel, conn, cmd, exec_timeout, connection_module) do\n    case connection_module.exec(conn, channel, cmd, exec_timeout) do\n      :success -> channel\n      :failure -> {:error, \"Could not exec '#{cmd}'!\"}\n      any -> any  # {:error, reason}\n    end\n  end\n\n  # Loop until all data is received. Return read data and the exit_status.\n  #\n  defp get_response(conn, channel, timeout, stdout, stderr, status, closed, opts) do\n\n    # if we got status and closed, then we are done\n    parsed = case {status, closed} do\n      {st, true} when not is_nil(st) -> format_response({:ok, stdout, stderr, status}, opts)\n      _ -> receive_and_parse_response(conn, channel, opts[:connection_module],\n                                      timeout, stdout, stderr, status, closed)\n    end\n\n    # tail recursion\n    case parsed do\n      {:loop, {ch, tout, out, err, st, cl}} -> # loop again, still things missing\n        get_response(conn, ch, tout, out, err, st, cl, opts)\n      x -> x\n    end\n  end\n\n  # Parse ugly response\n  #\n  defp receive_and_parse_response(conn, chn, connection_module, tout,\n                                  stdout \\\\ \"\", stderr \\\\ \"\", status \\\\ nil, closed \\\\ false) do\n    response = receive do\n      {:ssh_cm, ^conn, res} -> res\n    after\n      tout -> {:error, \"Timeout. Did not receive data for #{tout}ms.\"}\n    end\n\n    # call adjust_window to allow more data income, but only when needed\n    case response do\n      {:data, ^chn, _, new_data} -> connection_module.adjust_window(conn, chn, byte_size(new_data))\n      _ -> :ok\n    end\n\n    case response do\n      {:data, ^chn, 1, new_data} ->       {:loop, {chn, tout, stdout, stderr <> new_data, status, closed}}\n      {:data, ^chn, 0, new_data} ->       {:loop, {chn, tout, stdout <> new_data, stderr, status, closed}}\n      {:eof, ^chn} ->                     {:loop, {chn, tout, stdout, stderr, status, closed}}\n      {:exit_signal, ^chn, _, _} ->       {:loop, {chn, tout, stdout, stderr, status, closed}}\n      {:exit_signal, ^chn, _, _, _} ->    {:loop, {chn, tout, stdout, stderr, status, closed}}\n      {:exit_status, ^chn, new_status} -> {:loop, {chn, tout, stdout, stderr, new_status, closed}}\n      {:closed, ^chn} ->                  {:loop, {chn, tout, stdout, stderr, status, true}}\n      any -> any # {:error, reason}\n    end\n  end\n\n  # Format response for given raw response and given options\n  #\n  defp format_response(raw, opts) do\n    case opts[:separate_streams] do\n      true -> raw\n      _ -> {:ok, stdout, stderr, status} = raw\n           {:ok, stdout <> stderr, status}\n    end\n  end\n\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule SSHEx.Mixfile do\n  use Mix.Project\n\n  def project do\n    [app: :sshex,\n     version: \"2.2.1\",\n     elixir: \">= 1.0.0\",\n     package: package(),\n     deps: deps(),\n     description: \"Simple SSH helpers for Elixir\" ]\n  end\n\n  def application do\n    [ applications: [:ssh] ]\n  end\n\n  defp package do\n    [maintainers: [\"Rubén Caro\"],\n     licenses: [\"MIT\"],\n     links: %{github: \"https://github.com/rubencaro/sshex\"}]\n  end\n\n  defp deps, do: [{:ex_doc, \">= 0.0.0\", only: :dev}]\nend\n"
  },
  {
    "path": "test/configurable_client_keys_test.exs",
    "content": "defmodule SSHEx.ConfigurableClientKeysTest do\n  use ExUnit.Case\n\n  alias SSHEx.ConfigurableClientKeys\n\n    @key \"\"\"\n  -----BEGIN RSA PRIVATE KEY-----\n  MIIEpQIBAAKCAQEAr7CylYwuNUCYjOV7uj0X8ZRVVKlwhKtavL0vtmCiSes/TQ+u\n  bQb7787djy4fILh/9ALsvOs6mzmAI9Ye6CxwG2nPhbweD76K/92cAAvbcwWFhKTS\n  6q2MY6XhATcORqQmYhi6JToYkz51JFeG0k38TwyiIBaLe4yKTCnZ6F0tIB9szdR8\n  pNOoZTMDAjXRDA1T0Y1wgxXn5dCFR4ywDcphRTu18FWhulruyPGQjRjFRzZCF8rO\n  PYRCVBaWCIQ9Guj6VnAOaPH3tIkkdTxAeMigflCsCbFttbKLSVbi+woQrOnpQt1G\n  T9YPaZed9vuXXJMI5IzUacoveyqr/X5cOfdVjwIDAQABAoIBAQCWJHRJt1WZ7s0v\n  w8H8A8/dhT1zL6ZXyrStjSQkQOsQLrmXGqqexBQz+V6AyRKS/PlkR8eXH5OjKf2n\n  IoqhMbDQzJkrmfs6y0SwqutxYrC02GglVlJledD7K7xhNHK/zfJ7bNRPkhmEZCDp\n  4N74BOt1hr9amsmy2QUrV6zAljhFNQtGXlMIzjSwkhAO5RyVDdNUIuXVbK4vYQ8n\n  60LAFUqrNMU0H5P7N1I9Wv1XdVroQ23bZkkoQ2YNg3+Xt8/ma1R8ImliODq593EF\n  EWDdtcm6gUeKfZSmu3V3XLX2w03/xng9PiQJWhDyGiiqylLWw61V72BOfk8Lsq03\n  bCI8HP5pAoGBANmMlI31H1E6zNrJ5/1+2IETRLJhwhCE5SJ14UgHpGjzWpCRVoi1\n  KY+N6FfUgReMn6efFtJj59cKUxkODDr4yt+8mfM0evfIUJumQ56czDv5l2m3dnm1\n  0JOX4jIwetFv0ROrbk7EONkg4BYLV3OFlekB+iNR0cW3mtPM/x35hvbbAoGBAM6+\n  Icf5pDwvU9tqgBlbYdN9D8IJa2kTC2818XAeEBcnDsQNrpKNGfG7jcOb7Os7Qs4q\n  ArIrfaWKH5OnRv8sxEJzvR1yQ7zfN0qL5+FgI95/5GmNzmiIOMIRltszuqvXhEbf\n  VfxoEOrYidiliI1P2oSkaSHnKh06vTcX/Iuve3hdAoGAPLahFuUb8l2Iol7K4dIu\n  tgccmvPxZw7Pq8heMO4BElEoK0SEc+6rRKcD+s8Rn/Lc87jQc7LyFu+ItWtYOnUI\n  mVxXUqqIzvIWnPnP0UpNLUfA2/4ZkGoPZcFznTIudJjSLr0fMdhNTTuBjmVn6JOV\n  fMvSdVz2QEm3afjCEil7YxUCgYEAkSPH4XUvyJTNQTfGUIbn6apdurIUNwMIvv1W\n  z4g7cZWY9yhHy1jFwwARqSa5L/c9kjDKDb0ci2+pdWY1IIWUDrbkKF0Ekv79+Ra5\n  Jm7xH44Xk8bbBmXDuvLQPnlVbrhxg7Pc0MNaRRTZyT+E2vgZh49Iw2VfGoAXQCtV\n  v9blTn0CgYEAoaLnpcDDIAJrSEuYJlmMkCSOLnDaUJ2Gvk7h8J3AKUJqaZKDrYtp\n  LkmEnkNn9pRguHw5O4t2A2/MPTMMPl9okxWUxmFol6vrLcVWJ7fHKnAgN4VeVdmV\n  3wC+maU88MNIdY/eZWowKv/3ZzENQAJYVSOoDKRM5prZ4UMml4xIv4c=\n  -----END RSA PRIVATE KEY-----\n  \"\"\"\n\n  @decoded_pem {\n    :RSAPrivateKey, \n    :\"two-prime\", \n    22178836200351380318740579128076760436035138298677133998095994045880250237512489621454049374968347715235055783881967698436999743499552314220245159073073380722782484638674394343203468456842721969160490509408462854393345405217052033027002266498826680781860593217442724766608969408399340485759397900671701217164071787627996052566922654086785056460976416517428062469453531934201888286810352377976660566632576939435504625681508553755882812006916212863287750386635961610258614389401711948452227087543533788958800504728999838598664499003820759527927048804714300161960621149036100364182740694238727122433513404946091683042703, 65537, 18953722005479038672989358915211180275890260321558970411086292300953891314102903798293741601596842249220589427161410575497215994540174656492260412045190058045697523798132914292381351875465619868574569967505985612493829380502486125604518301719636021034677605693414631216007271459728426119381823980696705221015479522156363868435684783617296463247438499300802671603744372814426304362919053721052072690042881679754394100902833620569116419541999627788678348085500419601780112784642580771348412671226267444389048117048054825692970232535861820613163424630322678634426098569280437755714074716898258095743415742430656778272361, 152768202594113506003906892512748290305531150912607635622225249629386697488954984355599970491458110778989746505533403503662866670470953760749589017642309508310049856672577159050375653128905845977073586607530375373027518020058386877444130805997536867700689067133764524765224528717473435950253382932149817505499, 145179663200449145917521205971590191715095399605877657860535867727166023541655152176425320419506590078056361653718878871175026174125734643334617196310933880264431271402500332782840931546131335362589195212219385352510583753009500018046055463721898551203113792493296805078356511882223907692461773372201488840797, 42634396225740208200122939165023822110993251906428332934533161660153542229168052609425568156747621132302706312254237302317680568273093737646062272192469000823821839244113039031865521701141155727614567329168722486117358203562383020102433014048475659707426385673383785616612854269230955697241777527641182266133, 101920611626859098746040147787461939524540705867934527984274451657219304776355522780797909077026392768989962056944197903228585062565435177154621093200325875415203905670958966858503264102811489825554515502983073693999716921620063267013762696345283281845431778671957714037110407177460667546919659598114321682045, 113504902981879519219294186506942993139718372538463522557610536232722856450238191548259358573620555616453272575244746754527476128589876390856908911294135573670756100331272684920761429283802284667615556476745952741230523406386621965212672405641392988569738039218761014896354457746693861804733309700621570981767, \n    :asn1_NOVALUE\n  }  \n\n  @known_hosts \"\"\"\ngithub.com,192.30.252.128 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n\"\"\"\n\n  @host_key {\n    :RSAPublicKey, \n    21634204163197213132817109123906975906368888521544012567769262995559431966147970056259890368935740096079275379887017970430632559083119648736096672444000478892100121400122505155635695213610246722639150597148186404829574795017869184029845276838222700401896051725788665083080114314875103545837696279553436341967388240785773957395421170074137268759810304409727303757139265883118481355074238232002946450070460602471201997377623196017810991617729908588802783067540409316213603919494955068312762445623851275603318105356333614840694877780018045461415298887693169943421044958989561462337777142733008105081260079805978461159529, \n    35\n  }\n\n  @tmp_dir \"./test/temp\"\n\n  setup_all do\n    File.mkdir_p!(@tmp_dir)\n    on_exit(fn -> File.rm_rf!(@tmp_dir) end)\n  end\n\n  \n  setup do\n    {:ok, known_hosts} =  StringIO.open(@known_hosts)\n    {:ok, key} = StringIO.open(@key)\n    %{\n      known_hosts: known_hosts,\n      key: key\n    }\n  end\n\n  test \"add_host_key writes an entry to known hosts if accept_hosts is true\" do\n    {:ok, known_hosts} = StringIO.open(\"\")\n    ConfigurableClientKeys.add_host_key(\n      \"example.com\", \n      @host_key, \n      [key_cb_private: [accept_hosts: true, known_hosts: known_hosts]]\n      )\n    result = StringIO.flush(known_hosts)\n    assert result =~ \"example.com\"\n  end\n\n  test \"add_host_key returns an error if accept_hosts is false\" do\n    {:ok, known_hosts} = StringIO.open(\"\")\n    result = ConfigurableClientKeys.add_host_key(\n      \"example.com\", \n      @host_key, \n      [key_cb_private: [accept_hosts: false, known_hosts: known_hosts]])    \n    assert {:error, _message} = result  \n  end  \n\n  test \"is_host_key returns true if host and key match known hosts entry\", %{known_hosts: known_hosts} do\n    result = ConfigurableClientKeys.is_host_key(\n      @host_key,\n      'github.com', \n      nil,\n      [key_cb_private: [accept_hosts: false, known_hosts: known_hosts]])\n    assert result\n  end\n\n  test \"is_host_key returns false if host and key do not match known hosts entry\", %{known_hosts: known_hosts} do\n    result = ConfigurableClientKeys.is_host_key(\n      @host_key,\n      'other.com', \n      nil,\n      [key_cb_private: [accept_hosts: false, known_hosts: known_hosts]])\n    refute result    \n  end  \n\n  test \"user key returns the contents of the key option\", %{key: key} do\n    result = ConfigurableClientKeys.user_key(\n      nil,\n      [key_cb_private: [key: key]]\n    )\n    assert result == {:ok, @decoded_pem}    \n  end\n\n  test \"get_cb_module returns IO devices for the specified files\" do\n    key_file = Path.join(@tmp_dir, \"./foo\")\n    known_hosts_file = Path.join(@tmp_dir, \"./bar\")\n    File.touch(key_file)\n    File.touch(known_hosts_file)\n    {_module, opts} = ConfigurableClientKeys.get_cb_module([key_file: key_file, known_hosts_file: known_hosts_file ])\n    assert opts[:key_file]\n    assert opts[:known_hosts_file]\n  end\n\nend"
  },
  {
    "path": "test/sshex_test.exs",
    "content": "defmodule SSHExTest do\n  use ExUnit.Case\n\n  test \"connect\" do\n    opts = [ip: '123.123.123.123',\n      user: 'myuser',\n      ssh_module: AllOKMock]\n\n    assert SSHEx.connect(opts) == {:ok, :mocked}\n  end\n\n  test \"Plain `cmd!`\" do\n    # send mocked response sequence to the mailbox\n    mocked_data = \"output\"\n    status = 123 # any would do\n    send_regular_sequence mocked_data, status\n\n    # actually test it\n    assert SSHEx.cmd!(:mocked, 'somecommand', connection_module: AllOKMock) == mocked_data\n  end\n\n  test \"String `cmd!`\" do\n    # send mocked response sequence to the mailbox\n    mocked_data = \"output\"\n    status = 123 # any would do\n    send_regular_sequence mocked_data, status\n\n    # actually test it\n    assert SSHEx.cmd!(:mocked, \"somecommand\", connection_module: AllOKMock) == mocked_data\n  end\n\n  test \"Plain `run`\" do\n    # send mocked response sequence to the mailbox\n    mocked_data = \"output\"\n    status = 123 # any would do\n    send_regular_sequence mocked_data, status\n\n    assert SSHEx.run(:mocked, 'somecommand', connection_module: AllOKMock) == {:ok, mocked_data, status}\n  end\n\n  test \"String `run`\" do\n    # send mocked response sequence to the mailbox\n    mocked_data = \"output\"\n    status = 123 # any would do\n    send_regular_sequence mocked_data, status\n\n    assert SSHEx.run(:mocked, \"somecommand\", connection_module: AllOKMock) == {:ok, mocked_data, status}\n  end\n\n  test \"Stream long response\" do\n    lines = [\"some\", \"long\", \"output\", \"sequence\"]\n    send_long_sequence(lines)\n    response = Enum.map(lines,&( {:stdout,&1} )) ++ [\n      {:stderr,\"mockederror\"},\n      {:status, 0}\n    ]\n\n    str = SSHEx.stream :mocked, 'somecommand', connection_module: AllOKMock\n    assert Enum.to_list(str) == response\n  end\n\n  test \"Separate streams\" do\n    # send mocked response sequence to the mailbox\n    mocked_stdout = \"output\"\n    mocked_stderr = \"something failed\"\n    send_separated_sequence mocked_stdout, mocked_stderr\n\n    # actually test it\n    res = SSHEx.run :mocked, 'failingcommand', connection_module: AllOKMock, separate_streams: true\n    assert res == {:ok, mocked_stdout, mocked_stderr, 2}\n  end\n\n  test \"`:ssh` error message when `run`\" do\n    send self(), {:ssh_cm, :mocked, {:error, :reason}}\n    assert SSHEx.run(:mocked, 'somecommand', connection_module: AllOKMock) == {:error, :reason}\n  end\n\n  test \"`:ssh` error message when `cmd!`\" do\n    send self(), {:ssh_cm, :mocked, {:error, :reason}}\n    assert_raise RuntimeError, \"{:error, :reason}\", fn ->\n      SSHEx.cmd!(:mocked, 'somecommand', connection_module: AllOKMock)\n    end\n  end\n\n  test \"`:ssh` error message while `stream`\" do\n    lines = [\"some\", \"long\", \"output\", \"sequence\"]\n    send_long_sequence(lines, error: true)\n    response = Enum.map(lines,&( {:stdout,&1} )) ++ [ {:error, :reason} ]\n\n    str = SSHEx.stream :mocked, 'somecommand', connection_module: AllOKMock\n    assert Enum.to_list(str) == response\n  end\n\n  test \"`:ssh_connection.exec` failure\" do\n    assert SSHEx.run(:mocked, 'somecommand', connection_module: ExecFailureMock) == {:error, \"Could not exec 'somecommand'!\"}\n\n    str = SSHEx.stream(:mocked, 'somecommand', connection_module: ExecFailureMock)\n    assert Enum.to_list(str) == [error: \"Could not exec 'somecommand'!\"]\n\n    assert_raise RuntimeError, \"{:error, \\\"Could not exec 'somecommand'!\\\"}\", fn ->\n      SSHEx.cmd!(:mocked, 'somecommand', connection_module: ExecFailureMock)\n    end\n  end\n\n  test \"`:ssh_connection.exec` error\" do\n    assert SSHEx.run(:mocked, 'somecommand', connection_module: ExecErrorMock) == {:error, :reason}\n\n    str = SSHEx.stream(:mocked, 'somecommand', connection_module: ExecErrorMock)\n    assert Enum.to_list(str) == [error: :reason]\n\n    assert_raise RuntimeError, \"{:error, :reason}\", fn ->\n      SSHEx.cmd!(:mocked, 'somecommand', connection_module: ExecErrorMock)\n    end\n  end\n\n  test \"`:ssh_connection.session_channel` error\" do\n    assert SSHEx.run(:mocked, 'somecommand', connection_module: SessionChannelErrorMock) == {:error, :reason}\n\n    str = SSHEx.stream(:mocked, 'somecommand', connection_module: SessionChannelErrorMock)\n    assert Enum.to_list(str) == [error: :reason]\n\n    assert_raise RuntimeError, \"{:error, :reason}\", fn ->\n      SSHEx.cmd!(:mocked, 'somecommand', connection_module: SessionChannelErrorMock)\n    end\n  end\n\n  test \"receive only from given connection\" do\n    # send mocked response sequence to the mailbox for 2 different connections\n    status = 123 # any would do\n    mocked_data1 = \"output1\"\n    send_regular_sequence mocked_data1, status, conn: :mocked1\n    mocked_data2 = \"output2\"\n    send_regular_sequence mocked_data2, status, conn: :mocked2\n\n    # check that we only receive for the one we want\n    assert SSHEx.cmd!(:mocked2, 'somecommand', connection_module: AllOKMock) == mocked_data2\n  end\n\n  defp send_long_sequence(lines, opts \\\\ []) do\n    for l <- lines do\n      send self(), {:ssh_cm, :mocked, {:data, :mocked, 0, l}}\n    end\n\n    if opts[:error], do: send(self(), {:ssh_cm, :mocked, {:error, :reason}})\n\n    send self(), {:ssh_cm, :mocked, {:data, :mocked, 1, \"mockederror\"}}\n    send self(), {:ssh_cm, :mocked, {:eof, :mocked}}\n    send self(), {:ssh_cm, :mocked, {:exit_status, :mocked, 0}}\n    send self(), {:ssh_cm, :mocked, {:closed, :mocked}}\n  end\n\n  defp send_regular_sequence(mocked_data, status, opts \\\\ []) do\n    conn = opts[:conn] || :mocked\n    send self(), {:ssh_cm, conn, {:data, conn, 0, mocked_data}}\n    send self(), {:ssh_cm, conn, {:eof, conn}}\n    send self(), {:ssh_cm, conn, {:exit_status, conn, status}}\n    send self(), {:ssh_cm, conn, {:closed, conn}}\n  end\n\n  defp send_separated_sequence(mocked_stdout, mocked_stderr) do\n    send self(), {:ssh_cm, :mocked, {:data, :mocked, 0, mocked_stdout}}\n    send self(), {:ssh_cm, :mocked, {:data, :mocked, 1, mocked_stderr}}\n    send self(), {:ssh_cm, :mocked, {:eof, :mocked}}\n    send self(), {:ssh_cm, :mocked, {:exit_status, :mocked, 2}}\n    send self(), {:ssh_cm, :mocked, {:closed, :mocked}}\n  end\n\nend\n\ndefmodule AllOKMock do\n  def connect(_,_,_,_), do: {:ok, :mocked}\n  def session_channel(conn,_), do: {:ok, conn}\n  def exec(_,_,_,_), do: :success\n  def adjust_window(_,_,_), do: :ok\n  def close(_, _), do: :ok\nend\n\ndefmodule ExecFailureMock do\n  def session_channel(_,_), do: {:ok, :mocked}\n  def exec(_,_,_,_), do: :failure\n  def adjust_window(_,_,_), do: :ok\n  def close(_, _), do: :ok\nend\n\ndefmodule ExecErrorMock do\n  def session_channel(_,_), do: {:ok, :mocked}\n  def exec(_,_,_,_), do: {:error, :reason}\n  def adjust_window(_,_,_), do: :ok\n  def close(_, _), do: :ok\nend\n\ndefmodule SessionChannelErrorMock do\n  def session_channel(_,_), do: {:error, :reason}\n  def exec(_,_,_,_), do: :success\n  def adjust_window(_,_,_), do: :ok\n  def close(_, _), do: :ok\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "ExUnit.start()\n"
  }
]