Skip to content

Instantly share code, notes, and snippets.

@ryanwinchester
Created January 9, 2023 16:57
Show Gist options
  • Save ryanwinchester/7266e8c1ad21afa45e45f0a658d86612 to your computer and use it in GitHub Desktop.
Save ryanwinchester/7266e8c1ad21afa45e45f0a658d86612 to your computer and use it in GitHub Desktop.
Elixir (clunky) Telnet Client
defmodule TelnetClient do
use GenServer
require Logger
@doc """
Starts a connection to the telnet server.
"""
def start_link({host, port}) do
GenServer.start_link(__MODULE__, {host, port}, name: __MODULE__)
end
@doc """
Log in to the telnet server.
"""
@spec login(iodata(), iodata()) :: :ok
def login(user, pass) do
GenServer.cast(__MODULE__, {:login, user, pass})
end
@doc """
Send a message to the telnet server.
"""
@spec send_message(iodata()) :: :ok
def send_message(message) do
GenServer.cast(__MODULE__, {:send, to_charlist(message)})
end
@doc """
Quits the telnet client.
"""
@spec quit() :: :ok
def quit do
GenServer.stop(__MODULE__, :normal)
end
## Callbacks
@impl GenServer
def init({host, port}) do
{:ok, socket} = :gen_tcp.connect(to_charlist(host), port, [])
{:ok, %{state: :connected, socket: socket, login: nil}}
end
@impl GenServer
def handle_cast({:login, user, pass}, state) do
send_message(user)
{:noreply, %{state | state: :login, login: {user, pass}}}
end
@impl GenServer
def handle_call({:send, msg}, _from, %{state: :login} = state) do
{:reply, {:error, :not_logged_in}, state}
end
def handle_call({:send, msg}, %{state: :ready, socket: socket} = state) do
:ok = :gen_tcp.send(socket, [msg, '\r\n'])
{:noreply, state}
end
@impl GenServer
def handle_info({:tcp, _socket, 'user\r\n'}, %{state: :login} = state) do
Logger.debug("USER ACCEPTED")
{:noreply, state}
end
def handle_info({:tcp, _socket, 'user\r\nPassword: '}, %{state: :login, login: {_user, pass}} = state) do
Logger.debug("SENDING PASS 1")
send_message(pass)
{:noreply, state}
end
def handle_info({:tcp, _socket, 'Password: '}, %{state: :login, login: {_user, pass}} = state) do
Logger.debug("SENDING PASS 2")
send_message(pass)
{:noreply, state}
end
def handle_info({:tcp, _socket, message}, %{state: :login} = state) do
message = to_string(message)
cond do
String.ends_with?(message, ":~$ \e[6n") ->
Logger.debug("LOGGED IN")
{:noreply, %{state | state: :ready, login: nil}}
true ->
IO.inspect(message)
{:noreply, state}
end
end
def handle_info({:tcp, _socket, msg}, state) do
Logger.debug("SERVER: #{msg}")
IO.inspect(msg)
{:noreply, state}
end
def handle_info({:tcp_closed, _socket}, state) do
Logger.debug("Closing TCP and shutting down")
{:stop, :normal, state}
end
def handle_info(msg, state) do
Logger.debug("HUH?")
Logger.debug(inspect(msg))
{:noreply, state}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment