diff options
| author | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2023-10-01 20:41:48 -0600 |
|---|---|---|
| committer | Elizabeth Hunt <elizabeth.hunt@simponic.xyz> | 2023-10-01 20:41:48 -0600 |
| commit | ffa3d553f693494c218a0244c946ba83dfbf7cd1 (patch) | |
| tree | 092a824dad118c37960ab67da98cdbc445a35d2b /lib | |
| download | tronglessh-main.tar.gz tronglessh-main.zip | |
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/ssh/conn_cli_manager.ex | 143 | ||||
| -rw-r--r-- | lib/ssh/daemon.ex | 43 | ||||
| -rw-r--r-- | lib/ssh/screen.ex | 18 | ||||
| -rw-r--r-- | lib/ssh/session.ex | 3 | ||||
| -rw-r--r-- | lib/ssh/tui.ex | 25 | ||||
| -rw-r--r-- | lib/trongle_ssh.ex | 11 |
6 files changed, 243 insertions, 0 deletions
diff --git a/lib/ssh/conn_cli_manager.ex b/lib/ssh/conn_cli_manager.ex new file mode 100644 index 0000000..19360f8 --- /dev/null +++ b/lib/ssh/conn_cli_manager.ex @@ -0,0 +1,143 @@ +defmodule TrongleSSH.SSH.ConnectionCLIManager do + alias TrongleSSH.SSH.{Session, Tui} + alias IO.ANSI + require Logger + + @behaviour :ssh_server_channel + @session_closed_message [ + ANSI.clear(), + ["This session has been closed."] + ] + + defmodule State do + defstruct channel_id: nil, + width: nil, + height: nil, + client_pid: nil, + connection_ref: nil, + session: %Session{} + end + + def init([%State{} = init_state]) do + {:ok, init_state} + end + + def handle_msg({:ssh_channel_up, channel_id, connection_ref}, %State{} = state) do + connected_user = + :ssh.connection_info(connection_ref) + |> Keyword.fetch!(:user) + |> String.Chars.to_string() + + {:ok, + %State{ + state + | channel_id: channel_id, + connection_ref: connection_ref, + session: %Session{username: connected_user} + }} + end + + def handle_msg( + {:EXIT, client_pid, _reason}, + %State{client_pid: client_pid, channel_id: channel_id} = state + ) do + send(client_pid, :quit) + {:stop, channel_id, state} + end + + def handle_msg( + {:send_data, data}, + %State{connection_ref: connection_ref, channel_id: channel_id} = state + ) do + :ssh_connection.send(connection_ref, channel_id, data) + {:ok, state} + end + + def handle_msg( + :session_closed, + %State{connection_ref: connection_ref, channel_id: channel_id} = state + ) do + :ssh_connection.send(connection_ref, channel_id, @session_closed_message) + {:stop, channel_id, state} + end + + def handle_msg(msg, term) do + Logger.debug("Unknown msg #{inspect(msg)}, #{inspect(term)}") + end + + def handle_ssh_msg( + {:ssh_cm, _connection_handler, {:data, _channel_id, _type, data}}, + %State{} = state + ) do + send(state.client_pid, {:data, data}) + {:ok, state} + end + + def handle_ssh_msg( + {:ssh_cm, connection_handler, + {:pty, channel_id, want_reply?, {_term, width, height, _pixwidth, _pixheight, _opts}}}, + %State{} = state + ) do + :ssh_connection.reply_request(connection_handler, want_reply?, :success, channel_id) + + {:ok, + %{ + state + | width: width, + height: height + }} + end + + def handle_ssh_msg( + {:ssh_cm, connection_handler, {:env, channel_id, want_reply?, _var, _value}}, + state + ) do + :ssh_connection.reply_request(connection_handler, want_reply?, :failure, channel_id) + {:ok, state} + end + + def handle_ssh_msg( + {:ssh_cm, _connection_handler, + {:window_change, _channel_id, width, height, _pixwidth, _pixheight}}, + %State{client_pid: client_pid} = state + ) do + send(client_pid, {:resize, {width, height}}) + + {:ok, + %State{ + state + | width: width, + height: height + }} + end + + def handle_ssh_msg( + {:ssh_cm, connection_handler, {:shell, channel_id, want_reply?}}, + %State{width: width, height: height, session: session} = state + ) do + :ssh_connection.reply_request(connection_handler, want_reply?, :success, channel_id) + + {:ok, client_pid} = + GenServer.start_link(Tui, [ + %Tui.State{ + connection_manager_pid: self(), + width: width, + height: height, + session: session + } + ]) + + send(client_pid, :refresh) + {:ok, %State{state | client_pid: client_pid}} + end + + def handle_ssh_msg( + msg, + %State{channel_id: channel_id} = state + ) do + Logger.debug("unknown ssh #{inspect(msg)}") + {:stop, channel_id, state} + end + + def terminate(_reason, _state), do: :ok +end diff --git a/lib/ssh/daemon.ex b/lib/ssh/daemon.ex new file mode 100644 index 0000000..34cd4b9 --- /dev/null +++ b/lib/ssh/daemon.ex @@ -0,0 +1,43 @@ +defmodule TrongleSSH.SSH.Daemon do + alias TrongleSSH.SSH.ConnectionCLIManager + use GenServer + require Logger + + defmodule State do + defstruct daemon_pid: nil + end + + def start_link(_) do + GenServer.start_link(__MODULE__, %State{}) + end + + def init(%State{} = state) do + send(self(), :start) + + {:ok, state} + end + + def handle_info(:start, %State{} = state) do + port = Application.fetch_env!(:tronglessh, :ssh_port) + key_dir = String.to_charlist(Application.fetch_env!(:tronglessh, :key_dir)) + + case :ssh.daemon( + port, + system_dir: key_dir, + ssh_cli: {ConnectionCLIManager, [%ConnectionCLIManager.State{}]}, + no_auth_needed: true, + id_string: :random, + parallel_login: true, + subsystems: [] + ) do + {:ok, daemon_pid} -> + Logger.info("SSH server started on port #{port}, on #{inspect(daemon_pid)}") + {:noreply, %State{state | daemon_pid: daemon_pid}} + + {:error, err} -> + raise inspect(err) + end + end + + def handle_info(_, state), do: {:ok, state} +end diff --git a/lib/ssh/screen.ex b/lib/ssh/screen.ex new file mode 100644 index 0000000..8f0c7bd --- /dev/null +++ b/lib/ssh/screen.ex @@ -0,0 +1,18 @@ +defmodule TrongleSSH.SSH.Screen do + @callback render(width :: integer(), height :: integer(), state :: any()) :: any() + @callback input(width :: integer(), height :: integer(), action :: any(), state :: any()) :: + any() + + defmacro __using__(_) do + quote do + @behaviour TrongleSSH.SSH.Screen + use GenServer + + def handle_info({:render, width, height}, state), + do: {:noreply, render(width, height, state)} + + def handle_info({:input, width, height, action}, state), + do: {:noreply, input(width, height, action, state)} + end + end +end diff --git a/lib/ssh/session.ex b/lib/ssh/session.ex new file mode 100644 index 0000000..d662e59 --- /dev/null +++ b/lib/ssh/session.ex @@ -0,0 +1,3 @@ +defmodule TrongleSSH.SSH.Session do + defstruct username: "" +end diff --git a/lib/ssh/tui.ex b/lib/ssh/tui.ex new file mode 100644 index 0000000..4fbbcf5 --- /dev/null +++ b/lib/ssh/tui.ex @@ -0,0 +1,25 @@ +defmodule TrongleSSH.SSH.Tui do + alias TrongleSSH.SSH.Session + + defmodule State do + defstruct connection_manager_pid: nil, + width: nil, + height: nil, + session: %Session{} + end + + use TrongleSSH.SSH.Screen + + def render(width, height, %State{connection_manager_pid: connection_manager_pid} = state) do + send( + connection_manager_pid, + {:send_to_ssh, render_state(width, height, state)} + ) + + state + end + + def input(width, height, action, %State{} = state) do + state + end +end diff --git a/lib/trongle_ssh.ex b/lib/trongle_ssh.ex new file mode 100644 index 0000000..a872149 --- /dev/null +++ b/lib/trongle_ssh.ex @@ -0,0 +1,11 @@ +defmodule TrongleSSH.Application do + use Application + + def start(_, _) do + children = [ + TrongleSSH.SSH.Daemon + ] + + Supervisor.start_link(children, strategy: :one_for_one, name: TrongleSSH.Supervisor) + end +end |
