summaryrefslogtreecommitdiff
path: root/lib/chessh/ssh/daemon.ex
blob: b341833bc27e5d2c1a0bb777fcfe17099fd5243c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
defmodule Chessh.SSH.Daemon do
  alias Chessh.{Repo, PlayerSession, Utils}
  alias Chessh.Auth.PasswordAuthenticator
  use GenServer
  import Ecto.Query

  require Logger

  def start_link(_) do
    GenServer.start_link(__MODULE__, %{
      pid: nil
    })
  end

  def init(state) do
    GenServer.cast(self(), :start)
    {:ok, state}
  end

  def pwd_authenticate(username, password, {ip, _port}) do
    [jail_timeout_ms, jail_attempt_threshold] =
      Application.get_env(:chessh, RateLimits)
      |> Keyword.take([:jail_timeout_ms, :jail_attempt_threshold])
      |> Keyword.values()

    rateId = "failed_password_attempts:#{Enum.join(Tuple.to_list(ip), ".")}"

    case PasswordAuthenticator.authenticate(
           String.Chars.to_string(username),
           String.Chars.to_string(password)
         ) do
      false ->
        case Hammer.check_rate_inc(rateId, jail_timeout_ms, jail_attempt_threshold, 1) do
          {:allow, _count} ->
            false

          {:deny, _limit} ->
            :disconnect
        end

      x ->
        if PlayerSession.player_within_concurrent_sessions_and_satisfies(username, fn _player ->
             x
           end),
           do: true,
           else: :disconnect
    end
  end

  def pwd_authenticate(username, password, inet, _address),
    do: pwd_authenticate(username, password, inet)

  def handle_cast(:start, state) do
    port = Application.fetch_env!(:chessh, :port)
    key_dir = String.to_charlist(Application.fetch_env!(:chessh, :key_dir))
    max_sessions = Application.fetch_env!(:chessh, :max_sessions)

    case :ssh.daemon(
           port,
           # shell: fn _username, _peer -> Process.sleep(5000) end,
           system_dir: key_dir,
           pwdfun: &pwd_authenticate/4,
           key_cb: Chessh.SSH.ServerKey,
           ssh_cli: {Chessh.SSH.Cli, []},
           #           connectfun: &on_connect/3,
           disconnectfun: &on_disconnect/1,
           id_string: :random,
           subsystems: [],
           parallel_login: true,
           max_sessions: max_sessions
         ) do
      {:ok, pid} ->
        Process.link(pid)
        {:noreply, %{state | pid: pid}, :hibernate}

      {:error, err} ->
        raise inspect(err)
    end

    {:noreply, state}
  end

  def handle_info(_, state), do: {:noreply, state}

  #  defp on_connect(username, _inet, _method) do
  #    Logger.debug("#{inspect(self())} connected and is authenticated as #{username}")
  #
  #    case Repo.get_by(Player, username: String.Chars.to_string(username)) do
  #      nil ->
  #        nil
  #
  #      player ->
  #        Repo.insert(%PlayerSession{
  #          login: DateTime.utc_now(),
  #          node_id: System.fetch_env!("NODE_ID"),
  #          player: player,
  #          process: pid_to_str(self())
  #        })
  #    end
  #  end

  defp on_disconnect(_reason) do
    Logger.debug("#{inspect(self())} disconnected")

    Repo.delete_all(
      from(p in PlayerSession,
        where: p.node_id == ^System.fetch_env!("NODE_ID"),
        where: p.process == ^Utils.pid_to_str(self())
      )
    )
  end
end