Module: Lich::Common::EAccess

Defined in:
documented/common/eaccess.rb

Overview

Module for handling EAccess related operations

Examples:

Usage

Lich::Common::EAccess.download_pem

Constant Summary collapse

PACKET_SIZE =

The size of the packet for reading data

8192

Class Method Summary collapse

Class Method Details

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

Authenticates a user with the given 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:

  • (String, Array)

    The authentication result or error message



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

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?
     = 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/
          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

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)



33
34
35
36
37
38
39
40
41
42
43
44
# File 'documented/common/eaccess.rb', line 33

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

Returns:

  • (String)

    The path to the PEM file



19
20
21
# File 'documented/common/eaccess.rb', line 19

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

.pem_exist?Boolean

Checks if the PEM file exists

Returns:

  • (Boolean)

    True if the PEM file exists, false otherwise



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

def self.pem_exist?
  File.exist? pem
end

.read(conn) ⇒ String

Reads data from the connection

Parameters:

  • conn (TCPSocket)

    The connection to read from

Returns:

  • (String)

    The data read from the connection



180
181
182
# File 'documented/common/eaccess.rb', line 180

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



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'documented/common/eaccess.rb', line 65

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 connection’s peer certificate

Parameters:

  • conn (OpenSSL::SSL::SSLSocket)

    The SSL connection to verify

Returns:

  • (Boolean)

    True if the certificates match, false otherwise

Raises:

  • (StandardError)

    If the certificates do not match and downloading fails



50
51
52
53
54
55
56
57
58
59
# File 'documented/common/eaccess.rb', line 50

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