/*
* IDK Game Server by Steve Winfield
* https://github.com/WinfieldSteve
*/
package org.stevewinfield.placeholder.services;
import com.google.common.io.BaseEncoding;
import org.apache.log4j.Logger;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.magicwerk.brownies.collections.GapList;
import org.stevewinfield.suja.idk.Bootloader;
import org.stevewinfield.suja.idk.IDK;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
public class PlaceholderNetwork {
private static final Logger logger = Logger.getLogger(PlaceholderNetwork.class);
public boolean isAuthenticated() {
return authenticated;
}
public String getLastBuildNumber() {
return lastBuildNumber;
}
public boolean needsUpdate() {
return !IDK.BUILD_NUMBER.equals(lastBuildNumber);
}
public boolean isConnected() {
return connected;
}
public PlaceholderNetwork(final String uniqueKey, final byte[] prefixArray, final byte[] saltArray) {
logger.info("Connecting to Mirror Servers..");
this.prefix = new String(prefixArray);
this.salt = new String(saltArray);
this.uniqueKey = uniqueKey;
this.pluginVersions = new ConcurrentHashMap<String, Long>();
if (this.updateConnectionList()) {
this.connected = true;
this.authenticated = this.authenticateIDK();
return;
}
logger.error("Connection failed, try to get in touch with an IDK administrator.");
Bootloader.exitServer();
}
public boolean authenticateIDK() {
final ServerResult response = this.requestServer(this.authServer, "confirm");
if (response == null) {
return false;
}
this.authenticated = true;
return true;
}
public boolean loadPlugins() {
return loadPlugins(true);
}
public boolean loadPlugins(final boolean showLog) {
final ServerResult response = this.requestServer(this.pluginServer, "confirm");
if (response == null) {
return false;
}
final JSONObject plugins = (JSONObject) response.getResponse().get("plugins");
int counter = 0;
for (final Object plugin : plugins.keySet()) {
JSONObject pluginObject = (JSONObject)plugins.get((String)plugin);
if ((!pluginVersions.containsKey((String) plugin) || pluginVersions.get((String) plugin) < (Long)pluginObject.get("version")) && Bootloader.getPluginManager().addPlugin((String) plugin, new String(BaseEncoding.base64().decode((String)pluginObject.get("script"))), showLog, true)) {
pluginVersions.put((String) plugin, (Long) pluginObject.get("version"));
counter++;
}
}
if (showLog) {
logger.info(counter + " plugin(s) external loaded from Placeholder Network.");
}
return true;
}
public boolean updateConnectionList() {
final ServerResult response = this.requestServer("", "expo-connection");
if (response == null) {
return false;
}
final JSONObject servers = (JSONObject) response.getResponse().get("servers");
if (!servers.containsKey("auth") || !servers.containsKey("updates") || !servers.containsKey("plugins") || !servers.containsKey("analytics")) {
return false;
}
this.authServer = (String) servers.get("auth");
this.updateServer = (String) servers.get("updates");
this.pluginServer = (String) servers.get("plugins");
this.analyticsServer = (String) servers.get("analytics");
return true;
}
public ServerResult requestServer(final String serverURL, final String path) {
return this.requestServer(serverURL, path, new GapList<Entry<String, String>>());
}
public ServerResult requestServer(final String serverURL, final String path, final List<Entry<String, String>> parameters) {
final String randomToken = Bootloader.getHashedString(("rd" + (new Random().nextInt())).getBytes());
try {
String parameterString = "";
if (path.startsWith("confirm")) {
parameters.add(new AbstractMap.SimpleEntry<>("token", randomToken));
parameters.add(new AbstractMap.SimpleEntry<>("key", uniqueKey));
parameters.add(new AbstractMap.SimpleEntry<>("host", InetAddress.getLocalHost().toString()));
parameters.add(new AbstractMap.SimpleEntry<>("v", "2"));
}
for (final Entry<String, String> parameter : parameters) {
parameterString += "&".concat(parameter.getKey()).concat("=").concat(parameter.getValue());
}
if (parameterString.length() > 0) {
parameterString = parameterString.substring(1);
}
final URL url = new URL("http" + (serverURL.isEmpty() ? "s" : "") + ":".concat("" + SLASH + SLASH).concat(serverURL).concat(!serverURL.isEmpty() ? "." : "").concat(prefix).concat("" + SLASH).concat(path).concat("?").concat(parameterString));
final URLConnection conn = url.openConnection();
final BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
final StringBuilder result = new StringBuilder();
while ((inputLine = br.readLine()) != null) {
result.append(inputLine);
}
br.close();
final JSONObject array = (JSONObject) ((new JSONParser()).parse(result.toString()));
if (!((String) array.get("responseCode")).startsWith("200")) {
return null;
}
final ServerResult serverResult = new ServerResult((String) array.get("responseCode"), (JSONObject) array.get("response"));
if (path.startsWith("confirm")) {
final int serverId = Integer.valueOf(serverURL.split("-")[2]);
final String hash = this.getNetworkHash(this.getAuthenticationData(randomToken));
if (hash.equals(this.getNetworkHash(IDK.PLACEHOLDER_NETWORK_SALT)) || !hash.equals(serverResult.getResponse().get("hashcheck")) || serverId != (Long) serverResult.getResponse().get("serverId")) {
return null;
}
}
return serverResult;
} catch (final MalformedURLException e) {
logger.error("MalformedURLException", e);
} catch (final IOException e) {
logger.error("IOException", e);
} catch (final ParseException e) {
logger.error("ParseException", e);
}
return null;
}
public boolean analyticsPing(final GapList<Entry<String, String>> parameters) {
if (this.requestServer(analyticsServer, "confirm", parameters) != null) {
return true;
}
this.connected = false;
this.authenticated = false;
return false;
}
public String getNetworkHash(final byte[] dig) {
return Bootloader.getHashedString(dig);
}
public byte[] getAuthenticationData(final String randomToken) {
return uniqueKey.concat(randomToken).concat(salt).getBytes();
}
private final char SLASH = 47;
private final String prefix;
private final String salt;
private final String uniqueKey;
private boolean authenticated;
private boolean connected;
private String updateServer;
private String authServer;
private String pluginServer;
private String analyticsServer;
private String lastBuildNumber;
private ConcurrentHashMap<String, Long> pluginVersions;
}