/************************************************************************************
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create an emulator which will provide a server for players to *
* continue playing a game similar to the one they used to play. We are basing *
* it on the final publish of the game prior to end-game events. *
* *
* This file is part of Holocore. *
* *
* -------------------------------------------------------------------------------- *
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see . *
* *
***********************************************************************************/
package services.admin;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import intents.PlayerEventIntent;
import resources.common.BCrypt;
import resources.config.ConfigFile;
import resources.control.Service;
import resources.server_info.Config;
import resources.server_info.Log;
import services.admin.http.HttpServer;
import services.admin.http.HttpServer.HttpServerCallback;
import services.admin.http.HttpSession;
import services.admin.http.HttpSocket;
import services.admin.http.HttpSocket.HttpRequest;
import services.admin.http.HttpsServer;
import utilities.ThreadUtilities;
public class OnlineInterfaceService extends Service implements HttpServerCallback {
private static final String TAG = "OnlineInterfaceService";
private static final String GET_USER_SQL = "SELECT password, banned FROM users WHERE LOWER(username) = LOWER(?)";
private final WebserverData data;
private final WebserverHandler handler;
private final Runnable dataCollectionRunnable;
private final PreparedStatement getUser;
private ScheduledExecutorService executor;
private HttpsServer httpsServer;
private HttpServer httpServer;
private boolean authorized;
public OnlineInterfaceService() {
data = new WebserverData();
handler = new WebserverHandler(data);
dataCollectionRunnable = () -> collectData();
getUser = getLocalDatabase().prepareStatement(GET_USER_SQL);
authorized = false;
}
@Override
public boolean initialize() {
Config network = getConfig(ConfigFile.NETWORK);
authorized = !network.getString("HTTPS-KEYSTORE-PASSWORD", "").isEmpty();
if (!authorized)
return super.initialize();
httpServer = new HttpServer(getBindAddr(network, "HTTP-BIND-ADDR", "BIND-ADDR"), network.getInt("HTTP-PORT", 8080));
httpsServer = new HttpsServer(getBindAddr(network, "HTTPS-BIND-ADDR", "BIND-ADDR"), network.getInt("HTTPS-PORT", 8081));
httpServer.setMaxConnections(network.getInt("HTTP-MAX-CONNECTIONS", 2));
httpsServer.setMaxConnections(network.getInt("HTTPS-MAX-CONNECTIONS", 5));
if (!httpsServer.initialize(network)) {
Log.e(this, "Failed to initialize HTTPS server! Incorrect password?");
httpServer.stop();
httpsServer.stop();
super.initialize();
return false;
}
executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("ServerInterface-DataCollection"));
registerForIntent(PlayerEventIntent.class, pei -> handlePlayerEventIntent(pei));
return super.initialize();
}
@Override
public boolean start() {
if (authorized) {
httpServer.setServerCallback(this);
httpsServer.setServerCallback(this);
httpServer.start();
httpsServer.start();
executor.scheduleAtFixedRate(dataCollectionRunnable, 0, 1, TimeUnit.SECONDS);
Log.i(this, "Web server is now online.");
}
return super.start();
}
@Override
public boolean stop() {
if (authorized) {
httpServer.stop();
httpsServer.stop();
executor.shutdownNow();
try {
executor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(this, e);
}
}
return super.stop();
}
@Override
public void onSocketCreated(HttpSocket socket) {
Log.i(TAG, "Received connection from: %s:%d [%s]", socket.getInetAddress(), socket.getPort(), socket.isSecure() ? "secure" : "insecure");
}
@Override
public void onRequestReceived(HttpSocket socket, HttpRequest request) {
try {
if (request.getType().equals("POST")) {
String [] variables = request.getBody().split("&");
if (variables.length == 2) {
Map varMap = new HashMap<>();
for (String str : variables) {
String [] var = str.split("=");
if (var.length == 2)
varMap.put(var[0], var[1]);
}
if (varMap.containsKey("username") && varMap.containsKey("password"))
login(socket, varMap.get("username"), varMap.get("password"));
}
}
if (!socket.isSecure()) {
try {
socket.redirect(new URL(request.getURI().getPath()).toString());
} catch (MalformedURLException e) {
Log.w(this, "Malformed URL: " + request.getURI().getPath());
}
return;
}
handler.handleRequest(socket, request);
} catch (IOException e) {
Log.e(this, e);
}
}
private void handlePlayerEventIntent(PlayerEventIntent pei){
switch (pei.getEvent()) {
case PE_ZONE_IN_CLIENT:
data.addOnlinePlayer(pei.getPlayer());
break;
case PE_LOGGED_OUT:
data.removeOnlinePlayer(pei.getPlayer());
break;
default:
break;
}
}
private void collectData() {
data.updateResourceUsage();
}
private InetAddress getBindAddr(Config c, String firstTry, String secondTry) {
String t = firstTry;
try {
if (c.containsKey(firstTry))
return InetAddress.getByName(c.getString(firstTry, "127.0.0.1"));
t = secondTry;
if (c.containsKey(secondTry))
return InetAddress.getByName(c.getString(secondTry, "127.0.0.1"));
} catch (UnknownHostException e) {
Log.e(this, "Unknown host for IP: " + t);
}
return null;
}
private void login(HttpSocket socket, String username, String password) {
HttpSession session = socket.getSession();
if (session == null)
return;
synchronized (getUser) {
try {
getUser.setString(1, username);
boolean prevAuthenticated = session.isAuthenticated();
session.setAuthenticated(false);
try (ResultSet cursor = getUser.executeQuery()) {
session.setAuthenticated(cursor.next() && isUserValid(cursor, password));
if (session.isAuthenticated()) {
if (!prevAuthenticated)
Log.i(TAG, "[%s] Successfully logged in to online interface", username);
} else {
Log.w(TAG, "[%s] Failed to login to online interface. Incorrect user/pass", username);
}
}
} catch (SQLException e) {
Log.e(this, e);
}
}
}
private boolean isUserValid(ResultSet set, String password) throws SQLException {
if (password.isEmpty())
return false;
if (set.getBoolean("banned"))
return false;
String psqlPass = set.getString("password");
if (psqlPass.length() != 60 && !psqlPass.startsWith("$2"))
return psqlPass.equals(password);
password = BCrypt.hashpw(BCrypt.hashpw(password, psqlPass), psqlPass);
return psqlPass.equals(password);
}
}