Module: Lich::Common::Authentication::EAccess

Defined in:
documented/common/authentication/eaccess.rb

Defined Under Namespace

Classes: AuthenticationError

Constant Summary collapse

PACKET_SIZE =
8192

Class Method Summary collapse

Class Method Details

.auth(password:, account:, character: nil, game_code: nil, legacy: false) ⇒ Array<Hash>

Authenticates a user with the provided credentials.

Parameters:

  • password (String)

    the user's password

  • account (String)

    the user's account name

  • character (String, nil) (defaults to: nil)

    the character name (optional)

  • game_code (String, nil) (defaults to: nil)

    the game code (optional)

  • legacy (Boolean) (defaults to: false)

    whether to use legacy authentication (default: false)

Returns:

  • (Array<Hash>)

    login information for the authenticated user

Raises:

  • AuthenticationError if authentication fails



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'documented/common/authentication/eaccess.rb', line 94

def self.auth(password:, account:, character: nil, game_code: nil, legacy: false)
  # Set Account module state
  if defined?(Lich::Common::Account)
    Lich::Common::Account.name = 
    Lich::Common::Account.game_code = game_code
    Lich::Common::Account.character = character
  end

  conn = EAccess.socket()
  begin
    # it is vitally important to verify self-signed certs
    # because there is no chain-of-trust for them
    EAccess.verify_pem(conn)
    conn.puts "K\n"
    hashkey = EAccess.read(conn)
    # pp "hash=%s" % hashkey
    password = password.split('').map { |c| c.getbyte(0) }
    hashkey = hashkey.split('').map { |c| c.getbyte(0) }
    password.each_index { |i| password[i] = ((password[i] - 32) ^ hashkey[i]) + 32 }
    password = password.map { |c| c.chr }.join
    conn.puts "A\t#{}\t#{password}\n"
    response = EAccess.read(conn)
    unless /KEY\t(?<key>.*)\t/.match(response)
      error_code = response.split(/\s+/).last
      raise AuthenticationError, error_code
    end
    # pp "A:response=%s" % response
    conn.puts "M\n"
    response = EAccess.read(conn)
    raise StandardError, response unless response =~ /^M\t/
    # pp "M:response=%s" % response

    unless legacy
      conn.puts "F\t#{game_code}\n"
      response = EAccess.read(conn)
      raise StandardError, response unless response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
      if defined?(Lich::Common::Account)
        Lich::Common::Account.subscription = response
      end
      # pp "F:response=%s" % response
      conn.puts "G\t#{game_code}\n"
      EAccess.read(conn)
      # pp "G:response=%s" % response
      conn.puts "P\t#{game_code}\n"
      EAccess.read(conn)
      # pp "P:response=%s" % response
      conn.puts "C\n"
      response = EAccess.read(conn)
      # pp "C:response=%s" % response
      if defined?(Lich::Common::Account)
        Lich::Common::Account.members = response
      end
      char_entry = response.sub(/^C\t[0-9]+\t[0-9]+\t[0-9]+\t[0-9]+[\t\n]/, '')
                           .scan(/[^\t]+\t[^\t^\n]+/)
                           .find { |c| c.split("\t")[1] == character }
      unless char_entry
        raise AuthenticationError, "CHARACTER_NOT_FOUND"
      end
      char_code = char_entry.split("\t")[0]
      conn.puts "L\t#{char_code}\tSTORM\n"
      response = EAccess.read(conn)
      raise StandardError, response unless response =~ /^L\t/
      # pp "L:response=%s" % response
       = response.sub(/^L\tOK\t/, '')
                           .split("\t")
                           .map { |kv|
                             k, v = kv.split("=")
                             [k.downcase, v]
                           }.to_h
    else
       = Array.new
      for game in response.sub(/^M\t/, '').scan(/[^\t]+\t[^\t^\n]+/)
        game_code, game_name = game.split("\t")
        # pp "M:response = %s" % response
        conn.puts "N\t#{game_code}\n"
        response = EAccess.read(conn)
        if response =~ /STORM/
          conn.puts "F\t#{game_code}\n"
          response = EAccess.read(conn)
          if response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
            if defined?(Lich::Common::Account)
              Lich::Common::Account.subscription = response
            end
            conn.puts "G\t#{game_code}\n"
            EAccess.read(conn)
            conn.puts "P\t#{game_code}\n"
            EAccess.read(conn)
            conn.puts "C\n"
            response = EAccess.read(conn)
            if defined?(Lich::Common::Account)
              Lich::Common::Account.members = response
            end
            for code_name in response.sub(/^C\t[0-9]+\t[0-9]+\t[0-9]+\t[0-9]+[\t\n]/, '').scan(/[^\t]+\t[^\t^\n]+/)
              char_code, char_name = code_name.split("\t")
              hash = { :game_code => "#{game_code}", :game_name => "#{game_name}",
                      :char_code => "#{char_code}", :char_name => "#{char_name}" }
              .push(hash)
            end
          end
        end
      end
    end
    return 
  ensure
    conn&.close unless conn&.closed?
  end
end

.download_pem(hostname = "eaccess.play.net", port = 7910) ⇒ void

This method returns an undefined value.

Downloads the PEM file from the specified hostname and port.

Parameters:

  • hostname (String) (defaults to: "eaccess.play.net")

    the hostname to connect to (default: "eaccess.play.net")

  • port (Integer) (defaults to: 7910)

    the port to connect to (default: 7910)



40
41
42
43
44
45
46
47
48
49
50
51
# File 'documented/common/authentication/eaccess.rb', line 40

def self.download_pem(hostname = "eaccess.play.net", port = 7910)
  # Create an OpenSSL context
  ctx = OpenSSL::SSL::SSLContext.new
  # Get remote TCP socket
  sock = TCPSocket.new(hostname, port)
  # pass that socket to OpenSSL
  ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
  # establish connection, if possible
  ssl.connect
  # write the .pem to disk
  File.write(pem, ssl.peer_cert)
end

.pemString

Returns the path to the PEM file used for SSL connections.

Returns:

  • (String)

    path to the PEM file



26
27
28
# File 'documented/common/authentication/eaccess.rb', line 26

def self.pem
  @pem ||= File.join(DATA_DIR, "simu.pem")
end

.pem_exist?Boolean

Checks if the PEM file exists on the filesystem.

Returns:

  • (Boolean)

    true if the PEM file exists, false otherwise



32
33
34
# File 'documented/common/authentication/eaccess.rb', line 32

def self.pem_exist?
  File.exist? pem
end

.read(conn) ⇒ String

Reads data from the given connection up to the defined packet size.

Parameters:

  • conn (TCPSocket)

    the connection to read from

Returns:

  • (String)

    the data read from the connection



205
206
207
# File 'documented/common/authentication/eaccess.rb', line 205

def self.read(conn)
  conn.sysread(PACKET_SIZE)
end

.socket(hostname = "eaccess.play.net", port = 7910) ⇒ OpenSSL::SSL::SSLSocket

Establishes a secure socket connection to the specified hostname and port.

Parameters:

  • hostname (String) (defaults to: "eaccess.play.net")

    the hostname to connect to (default: "eaccess.play.net")

  • port (Integer) (defaults to: 7910)

    the port to connect to (default: 7910)

Returns:

  • (OpenSSL::SSL::SSLSocket)

    the established SSL socket



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'documented/common/authentication/eaccess.rb', line 72

def self.socket(hostname = "eaccess.play.net", port = 7910)
  download_pem unless pem_exist?
  socket = TCPSocket.open(hostname, port)
  cert_store              = OpenSSL::X509::Store.new
  ssl_context             = OpenSSL::SSL::SSLContext.new
  ssl_context.cert_store  = cert_store
  ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
  cert_store.add_file(pem) if pem_exist?
  ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
  ssl_socket.sync_close = true
  EAccess.verify_pem(ssl_socket.connect)
  return ssl_socket
end

.verify_pem(conn) ⇒ Boolean

Verifies the PEM certificate against the stored PEM file.

Parameters:

  • conn (OpenSSL::SSL::SSLSocket)

    the SSL connection to verify

Returns:

  • (Boolean)

    true if the certificate matches, false otherwise

Raises:

  • AuthenticationError if the certificate does not match



57
58
59
60
61
62
63
64
65
66
# File 'documented/common/authentication/eaccess.rb', line 57

def self.verify_pem(conn)
  # return if conn.peer_cert.to_s = File.read(pem)
  if !(conn.peer_cert.to_s == File.read(pem))
    Lich.log "Exception, \nssl peer certificate did not match #{pem}\nwas:\n#{conn.peer_cert}"
    download_pem
  else
    return true
  end
  #     fail Exception, "\nssl peer certificate did not match #{pem}\nwas:\n#{conn.peer_cert}"
end