Module: Lich::Common::EAccess

Defined in:
lib/common/eaccess.rb

Constant Summary collapse

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

pp PEM

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 specified credentials.

Examples:

 = EAccess.auth(password: "secret", account: "user123")

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 hashes.

Raises:

  • (StandardError)

    if authentication fails or the response is invalid.



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
176
177
178
179
180
# File 'lib/common/eaccess.rb', line 91

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:

EAccess.download_pem("example.com", 1234)

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 or the PEM file cannot be written.



27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/common/eaccess.rb', line 27

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.

Returns:

  • (Boolean)

    true if the PEM file exists, false otherwise.



15
16
17
# File 'lib/common/eaccess.rb', line 15

def self.pem_exist?
  File.exist? PEM
end

.read(conn) ⇒ String

Reads data from the connection.

Examples:

data = EAccess.read(conn)

Parameters:

  • conn (TCPSocket)

    the connection to read from.

Returns:

  • (String)

    the data read from the connection.

Raises:

  • (IOError)

    if the read operation fails.



189
190
191
# File 'lib/common/eaccess.rb', line 189

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:

ssl_socket = EAccess.socket("example.com", 1234)

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 file does not exist or the connection fails.



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

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:

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 downloading fails.



47
48
49
50
51
52
53
54
55
56
# File 'lib/common/eaccess.rb', line 47

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