Module: Lich::Common::EAccess

Defined in:
documented/common/eaccess.rb

Overview

Handles EAccess related operations

This module provides methods for downloading and verifying PEM files, as well as authenticating users.

Examples:

Authenticating a user

Lich::Common::EAccess.auth(password: "password", account: "account_name")

Constant Summary collapse

PEM =

The path to the PEM file used for SSL connections.

File.join(DATA_DIR, "simu.pem")
PACKET_SIZE =

pp PEM The size of packets to read from the connection.

8192

Class Method Summary collapse

Class Method Details

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

Authenticates a user with the given credentials

Examples:

Authenticating a user

 = Lich::Common::EAccess.auth(password: "password", account: "account_name")

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:

  • (String, Array<Hash>)

    An error message or an array of login information

Raises:

  • (StandardError)

    if authentication fails



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
# File 'documented/common/eaccess.rb', line 100

def self.auth(password:, account:, character: nil, game_code: nil, legacy: false)
  Account.name = 
  Account.game_code = game_code
  Account.character = character
  conn = EAccess.socket()
  # 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)
    eaccess_error = "Error(%s)" % response.split(/\s+/).last
    return eaccess_error
  end
  # pp "A:response=%s" % response
  conn.puts "M\n"
  response = EAccess.read(conn)
  fail StandardError, response unless response =~ /^M\t/
  # pp "M:response=%s" % response

  unless legacy
    conn.puts "F\t#{game_code}\n"
    response = EAccess.read(conn)
    fail StandardError, response unless response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
    Account.subscription = response
    # 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
    Account.members = response
    char_code = 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 }
                        .split("\t")[0]
    conn.puts "L\t#{char_code}\tSTORM\n"
    response = EAccess.read(conn)
    fail StandardError, response unless response =~ /^L\t/
    # pp "L:response=%s" % response
    conn.close unless conn.closed?
     = Hash[response.sub(/^L\tOK\t/, '')
                              .split("\t")
                              .map { |kv|
                        k, v = kv.split("=")
                        [k.downcase, v]
                      }]
  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/
          Account.subscription = response
          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)
          Account.members = response
          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
  conn.close unless conn.closed?
  return 
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

Examples:

Downloading the PEM file

Lich::Common::EAccess.download_pem

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)

Raises:

  • (StandardError)

    if the connection fails



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

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(EAccess::PEM, ssl.peer_cert)
end

.pem_exist?Boolean

Checks if the PEM file exists

Examples:

Checking for PEM existence

exists = Lich::Common::EAccess.pem_exist?

Returns:

  • (Boolean)

    true if the PEM file exists, false otherwise



28
29
30
# File 'documented/common/eaccess.rb', line 28

def self.pem_exist?
  File.exist? PEM
end

.read(conn) ⇒ String

Reads data from the connection

Examples:

Reading data from a connection

data = Lich::Common::EAccess.read(conn)

Parameters:

  • conn (TCPSocket)

    The connection to read from

Returns:

  • (String)

    The data read from the connection



196
197
198
# File 'documented/common/eaccess.rb', line 196

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

Examples:

Creating a secure socket

ssl_socket = Lich::Common::EAccess.socket

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

Raises:

  • (StandardError)

    if the PEM verification fails



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'documented/common/eaccess.rb', line 76

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(EAccess::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

Examples:

Verifying a PEM certificate

Lich::Common::EAccess.verify_pem(conn)

Parameters:

  • conn (OpenSSL::SSL::SSLSocket)

    The SSL connection to verify

Returns:

  • (Boolean)

    true if the certificate matches, false otherwise

Raises:

  • (StandardError)

    if the certificate does not match and download fails



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

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