From 93cac8e9713f2e1162abd52331839ec04ccf109f Mon Sep 17 00:00:00 2001 From: Obique PSWG Date: Mon, 24 Aug 2015 17:27:45 -0500 Subject: [PATCH] Basic HTTPS server --- res/webserver/favicon.ico | Bin 0 -> 1150 bytes res/webserver/index.html | 81 ++++++ res/webserver/log.html | 1 + res/webserver/online_players.html | 0 res/webserver/server_info.html | 12 + server.keystore | Bin 0 -> 2252 bytes src/resources/control/Manager.java | 36 +-- src/resources/control/ServerManager.java | 222 --------------- .../control/ServerPublicInterface.java | 191 ------------- src/resources/control/Service.java | 4 +- src/services/CoreManager.java | 7 +- .../admin/OnlineInterfaceService.java | 153 ++++++++++ src/services/admin/WebserverData.java | 60 ++++ src/services/admin/WebserverHandler.java | 205 ++++++++++++++ src/services/admin/http/HttpImageType.java | 7 + src/services/admin/http/HttpServer.java | 196 +++++++++++++ src/services/admin/http/HttpSocket.java | 263 ++++++++++++++++++ src/services/admin/http/HttpStatusCode.java | 48 ++++ src/services/admin/http/HttpsServer.java | 81 ++++++ src/services/player/LoginService.java | 2 +- 20 files changed, 1124 insertions(+), 445 deletions(-) create mode 100644 res/webserver/favicon.ico create mode 100644 res/webserver/index.html create mode 100644 res/webserver/log.html create mode 100644 res/webserver/online_players.html create mode 100644 res/webserver/server_info.html create mode 100644 server.keystore delete mode 100644 src/resources/control/ServerManager.java delete mode 100644 src/resources/control/ServerPublicInterface.java create mode 100644 src/services/admin/OnlineInterfaceService.java create mode 100644 src/services/admin/WebserverData.java create mode 100644 src/services/admin/WebserverHandler.java create mode 100644 src/services/admin/http/HttpImageType.java create mode 100644 src/services/admin/http/HttpServer.java create mode 100644 src/services/admin/http/HttpSocket.java create mode 100644 src/services/admin/http/HttpStatusCode.java create mode 100644 src/services/admin/http/HttpsServer.java diff --git a/res/webserver/favicon.ico b/res/webserver/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5d39b59fef6347c161d83ce639f12138a2b39761 GIT binary patch literal 1150 zcmZ{kA#Xxa428RF$#4U~5(LSDAPWM6Kp-9hfk0po2n2>K2m}T}GG2l~AP^V?0)gR2 zz<$bp$dc}UWnCUGfu-wR={fB=_hupw;U}Aw@cT_Zow2+wES!)oL{*7mGy;&d%3vw=ZcNe^akktJ-KZ zbT}O9U@*|hWMUb(=iTsJiNv}@B|MOJEEd}9_2}1pK5x0-@0&mFC%b@ME|FYy{=X(<8QTEb_eM9yFmwd z<}!y}@sRUov(eM(q=&=7?t?pFCRlbx5AOp!{b>F3Mwx|vG=V3BXK%0`aNq-Y_Jp4v z?gLM#qk~;I!%GjrZ1)gP!ISX^zqm7Wp#eYfhy8pe-f{m#(0pNk_H-UH{(;9+=Hrde zbgbv#-31K0I?ny0j(*-ZyQkl8k^|lB!p;P9c?Y~dG~dHIhrda@BJdL3_xApYM82Oz Zz8*#5M1+4PH=j8C9f!X7<=P7d>@R=-FeCr~ literal 0 HcmV?d00001 diff --git a/res/webserver/index.html b/res/webserver/index.html new file mode 100644 index 000000000..fafa95f75 --- /dev/null +++ b/res/webserver/index.html @@ -0,0 +1,81 @@ + + + Game Server Diagnostics + + + + + + +
+
+
+ Memory Usage:
+ +
+ +
+ +
+ + diff --git a/res/webserver/log.html b/res/webserver/log.html new file mode 100644 index 000000000..0b63a57ff --- /dev/null +++ b/res/webserver/log.html @@ -0,0 +1 @@ +${LOG} diff --git a/res/webserver/online_players.html b/res/webserver/online_players.html new file mode 100644 index 000000000..e69de29bb diff --git a/res/webserver/server_info.html b/res/webserver/server_info.html new file mode 100644 index 000000000..b3397edab --- /dev/null +++ b/res/webserver/server_info.html @@ -0,0 +1,12 @@ + +
+${ONLINE_PLAYERS} +
diff --git a/server.keystore b/server.keystore new file mode 100644 index 0000000000000000000000000000000000000000..45136856e289bcf561a29a88872453e8405ff659 GIT binary patch literal 2252 zcmc&#`8O1d8lKI_I<{-g$TyZuV#by=vJE5K$P($BkugnV-;yOmW?W=1%h+x?mg%G5AF}o56^SnbKdtn?|Yv2VC7&1004lF0Q?)!5MppR zG5CnuyR@pn001r!iUK)62|{^KL4gn;5+n=+aseO|$Ra>U^H23c`a$rLT>Nm;#Y>O) zjgy=(kMPJ0gQCd8lvI*fOQh@_0_~wji%U|VcYAuM5*O=m5Qmi6hGfv)z~a?c!# zBnB;;UYMri`dT|bSvy;6?Ktct4pUF6-+kxWv)B|fawT!T$?ukra$P!R6s3+XBM%0N?l9Ex9`uxc$;|5Y$qc2i#4^RWo)uL zF!tb?Av&r|4srZAf20{HXfVQk(kjEFM|JI~m=RT$o~gJq7N^S{uumAXLReVDGE3qU@p`&yX0imclU7#U=B z<~e+qINuBQvp6}~$Ceq)MooE2YbukL^yjJ%_+EQCu7TEcnNUbz z!NMJ}(_U~lasRJu{=f`!JN&Q-SsT(YG1a_sIzcEmj^MJa^3_eMPGVABFQUyC21qp) zZk+Y!Tc;&Gtel^M;u?Px)~;GOKO`Mu1bRG;r~s2@-To?aZks7Dj6d4WS<$@FF1I0{k?&@mG*-$ zo|LFpnFT3FL#f|0~aw zOJ53#UdU&we$%tU=!oT(x^Hdbe~~!DJpy>z1L}o;R@1(r_Y^IufzZw(3o4kp_=pBSu<2C7LZ> z4ECssik_QjwCbSQ`VDWWR_TM-{6$QLvEXi+oY26-)tlE!cd8JkWp`3GBjMF__Z-=C zGUFHW;v4E(+w|ri>qwu)o0LMWy(^i1wqo2l`WN47W(IT*Aw;7)fgIufi*7)aTSGh3 zcPVF}EbPo%KmRFcNabPldIQ!&IoEdimh=pn9$}1Zq+#ve#4aAI4A|i(TBr%B>f8?H zei1|9*0s~tAJ&P)Y;djiF(KO*T6FFL`jTTseA;$nYshXR{D?35*XU7jAye0s(ogh?msIdLk7}0b-XbcU zN`lD!c)@bHH)T<^e%fjAGK|*z^}xr))o@!-VUDBmi+Jq8%@rK}P4bP~Wj7uxgLUvP zU6nJc3;TVl1ojPaUXX3`&zuD>HPQvlmkc{uVX8V7c^0q5Nj?DS6eaLY# z7tZHgnO)96gJs=54>*}0*i5f&3}AaYhQ|b4Ti4AxL25OI28<tuh@Rl@cEiM*n`4#5G}h*v_LTrp@dl*sQ&K2bh% zVmQ$+An-cTKNJl+>UgArMWOaysXBmx1A%&D{*^N>q-`)YYIQy&*YEH5cRtaaAaR_se{pl-( zpd(|tCFgY^3#*6#&7U^vQT2ej=%Ac&v-_~EZR3cAI_Wra2OQe3N>XlfL|@80=g!b! zk%)c6HIg1FZ1EVTF$fF<0L$f3awyrORmuuN#35p_6#Fm^popTn3)N$PV#X$&H2Bgs8}E1e)j^nZP61s`AxGWB{6BMXm2W5*I^A$HD^h88 zLlMh!3RqjP6)rutnbPB9Vf6>3K<=b+sJt2NGY74#U_}k4Kk0*P^V0PV`>Q`WEFp#( z%Qz_a1ge6dAs!n(9C`e))jXl*^PhCxac7^cNz;jRbD8kW=`X$$voZ^kKL)yQjlE(p zrR?&lfv(o7zI_?b7(+i6>u1}d4%MQEN>f!{hqU0+WE&P-lOPvQoSr6UG*FBf<6v{r z#{)#8RPQ>fVN{VHw=DuwY*#XYpNs#p7L_Y%NVB9aD|f%ScqX;ncXcYLS63iHgf=iF bUDE`tiq|4kCY2zSZbyU1^CTnswFCbGeJ0e* literal 0 HcmV?d00001 diff --git a/src/resources/control/Manager.java b/src/resources/control/Manager.java index 6b1b01749..c4219e4e4 100644 --- a/src/resources/control/Manager.java +++ b/src/resources/control/Manager.java @@ -37,7 +37,6 @@ import java.util.List; */ public abstract class Manager extends Service { - private static final ServerManager serverManager = ServerManager.getInstance(); private List children; public Manager() { @@ -52,19 +51,13 @@ public abstract class Manager extends Service { */ @Override public boolean initialize() { - boolean success = super.initialize(), cSuccess = true; - long start = 0, end = 0; + boolean success = super.initialize(); synchronized (children) { for (Service child : children) { - if (!success) - break; - start = System.nanoTime(); - cSuccess = child.initialize(); - end = System.nanoTime(); - serverManager.setServiceInitTime(child, (end-start)/1E6, cSuccess); - if (!cSuccess) { + if (!child.initialize()) { System.err.println(child.getClass().getSimpleName() + " failed to initialize!"); success = false; + break; } } } @@ -79,19 +72,13 @@ public abstract class Manager extends Service { */ @Override public boolean start() { - boolean success = super.start(), cSuccess = true; - long start = 0, end = 0; + boolean success = super.start(); synchronized (children) { for (Service child : children) { - if (!success) - break; - start = System.nanoTime(); - cSuccess = child.start(); - end = System.nanoTime(); - serverManager.setServiceStartTime(child, (end-start)/1E6, cSuccess); - if (!cSuccess) { + if (!child.start()) { System.err.println(child.getClass().getSimpleName() + " failed to start!"); success = false; + break; } } } @@ -129,15 +116,10 @@ public abstract class Manager extends Service { */ @Override public boolean terminate() { - boolean success = super.terminate(), cSuccess = true; - long start = 0, end = 0; + boolean success = super.terminate(); synchronized (children) { for (Service child : children) { - start = System.nanoTime(); - cSuccess = child.terminate(); - end = System.nanoTime(); - serverManager.setServiceTerminateTime(child, (end-start)/1E6, cSuccess); - if (!cSuccess) + if (!child.terminate()) success = false; } } @@ -174,7 +156,6 @@ public abstract class Manager extends Service { if (s == child || s.equals(child)) return; } - serverManager.addChild(this, s); children.add(s); } } @@ -187,7 +168,6 @@ public abstract class Manager extends Service { if (s == null) return; synchronized (children) { - serverManager.removeChild(this, s); children.remove(s); } } diff --git a/src/resources/control/ServerManager.java b/src/resources/control/ServerManager.java deleted file mode 100644 index a1cb20412..000000000 --- a/src/resources/control/ServerManager.java +++ /dev/null @@ -1,222 +0,0 @@ -/*********************************************************************************** -* 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 resources.control; - -import intents.server.ServerStatusIntent; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import resources.control.Service; - -/** - * Might be best to keep this protected, from a server security standpoint - */ -final class ServerManager implements IntentReceiver { - - private static final ServerManager INSTANCE = new ServerManager(); - - private final Map serviceStats; - private ServerStatus status; - - private ServerManager() { - serviceStats = new HashMap(); - status = ServerStatus.OFFLINE; - ServerPublicInterface.initialize(this); - IntentManager.getInstance().registerForIntent(ServerStatusIntent.TYPE, this); - } - - public static final ServerManager getInstance() { - return INSTANCE; - } - - public void onIntentReceived(Intent i) { - if (i instanceof ServerStatusIntent) - processServerStatusIntent((ServerStatusIntent) i); - } - - public boolean initialize() { - ServerPublicInterface.initialize(this); - return true; - } - - public boolean terminate() { - ServerPublicInterface.terminate(); - return true; - } - - public void addChild(Service parent, Service child) { - getServiceStats(parent).addChild(getServiceStats(child)); - } - - public void removeChild(Service parent, Service child) { - getServiceStats(parent).removeChild(getServiceStats(child)); - } - - public void setServiceInitTime(Service service, double timeMilliseconds, boolean success) { - getServiceStats(service).addInitTime(timeMilliseconds, success); - } - - public void setServiceStartTime(Service service, double timeMilliseconds, boolean success) { - getServiceStats(service).addStartTime(timeMilliseconds, success); - } - - public void setServiceTerminateTime(Service service, double timeMilliseconds, boolean success) { - getServiceStats(service).addTerminateTime(timeMilliseconds, success); - } - - private void processServerStatusIntent(ServerStatusIntent i) { - status = i.getStatus(); - } - - public byte [] serializeControlTimes() { - synchronized (serviceStats) { - int size = 0; - for (ServiceStats stats : serviceStats.values()) - size += stats.getSerializeSize(); - ByteBuffer data = ByteBuffer.allocate(size); - for (ServiceStats stats : serviceStats.values()) - data.put(stats.serialize()); - return data.array(); - } - } - - public ServerStatus getServerStatus() { - return status; - } - - private ServiceStats getServiceStats(Service service) { - synchronized (serviceStats) { - ServiceStats stats = serviceStats.get(service.getClass().getName()); - if (stats == null) { - stats = new ServiceStats(service); - serviceStats.put(service.getClass().getName(), stats); - } - return stats; - } - } - - private static class ServiceStats { - private final Service service; - private final List initTimes; - private final List startTimes; - private final List terminateTimes; - private final List children; - - public ServiceStats(Service service) { - this.service = service; - initTimes = new ArrayList(); - startTimes = new ArrayList(); - terminateTimes = new ArrayList(); - children = new ArrayList(); - } - - public void addChild(ServiceStats service) { - children.add(service); - } - - public void removeChild(ServiceStats service) { - children.remove(service); - } - - public void addInitTime(double time, boolean success) { - initTimes.add(new ServiceControlTime(time, success)); - } - - public void addStartTime(double time, boolean success) { - startTimes.add(new ServiceControlTime(time, success)); - } - - public void addTerminateTime(double time, boolean success) { - terminateTimes.add(new ServiceControlTime(time, success)); - } - - public int getSerializeSize() { - return 2 + service.getClass().getName().length() + 9*3; - } - - public byte [] serialize() { - String name = service.getClass().getName(); - ByteBuffer data = ByteBuffer.allocate(getSerializeSize()); - data.putShort((short) name.length()); - data.put(name.getBytes(Charset.forName("UTF-8"))); - serializeLastControlTime(data, initTimes); - serializeLastControlTime(data, startTimes); - serializeLastControlTime(data, terminateTimes); - return data.array(); - } - - private void serializeLastControlTime(ByteBuffer data, List times) { - if (initTimes.size() > times.size() || times.size() == 0) - serializeControlTime(data, null); - else - serializeControlTime(data, times.get(initTimes.size()-1)); - } - - private void serializeControlTime(ByteBuffer data, ServiceControlTime time) { - if (time == null) { - data.put((byte) 0); - data.putDouble(Double.NaN); - } else { - data.put((byte) (time.isSuccess() ? 1 : 0)); - data.putDouble(time.getTime()); - } - } - - public String toString() { - return "ServiceStats[" + service.getClass().getName() + "]"; - } - } - - private static class ServiceControlTime { - private final double time; - private final boolean success; - - public ServiceControlTime(double time, boolean success) { - this.time = time; - this.success = success; - } - - public double getTime() { - return time; - } - - public boolean isSuccess() { - return success; - } - - public String toString() { - return "[" + (isSuccess()?"Success":"Failure") + " in " + getTime() + "ms]"; - } - } - -} diff --git a/src/resources/control/ServerPublicInterface.java b/src/resources/control/ServerPublicInterface.java deleted file mode 100644 index d27de8456..000000000 --- a/src/resources/control/ServerPublicInterface.java +++ /dev/null @@ -1,191 +0,0 @@ -/*********************************************************************************** -* 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 resources.control; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; - -import resources.config.ConfigFile; -import resources.network.UDPServer; -import resources.network.UDPServer.UDPCallback; -import resources.network.UDPServer.UDPPacket; -import resources.server_info.Config; -import resources.server_info.DataManager; - -final class ServerPublicInterface { - - private static final int PORT = 44452; - private static final int PACKET_SIZE = 1024; - - private static ServerPublicInterface publicInterface = null; - - private ServerManager serverManager; - private UDPServer server; - private boolean initialized = false; - private boolean terminated = false; - - private ServerPublicInterface() { - serverManager = null; - } - - public static final void initialize(ServerManager instance) { - if (publicInterface == null) - publicInterface = new ServerPublicInterface(); - publicInterface.initializeInterface(instance); - } - - public static final void terminate() { - publicInterface.terminateInterface(); - } - - private void initializeInterface(ServerManager instance) { - if (!initialized) { - serverManager = instance; - server = createUdpServer(); - initialized = true; - terminated = false; - } - } - - private void terminateInterface() { - if (!terminated) { - serverManager = null; - if (server != null) - server.close(); - initialized = false; - terminated = true; - } - } - - private void onReceivedPacket(UDPPacket packet) { - if (packet.getData().length == 0) - return; - InetAddress sender = packet.getAddress(); - int port = packet.getPort(); - DataInputStream dis = new DataInputStream(new ByteArrayInputStream(packet.getData())); - try { - while (dis.available() > 0) { - processPacket(sender, port, dis); - } - } catch (IOException e) { - - } - } - - private void processPacket(InetAddress sender, int port, DataInputStream is) throws IOException { - RequestType type = getType(is.readByte()); - switch (type) { - case PING_PONG: processPing(sender, port, is); break; - case STATUS: processServerStatus(sender, port, is); break; - case CONTROL_TIMES: processControlTimes(sender, port, is); break; - default: - break; - } - } - - private void processPing(InetAddress sender, int port, DataInputStream is) { - server.send(port, sender, new byte[]{RequestType.PING_PONG.getType()}); - } - - private void processServerStatus(InetAddress sender, int port, DataInputStream is) { - ServerStatus status = serverManager.getServerStatus(); - ByteBuffer data = ByteBuffer.allocate(3 + status.name().length()); - data.put(RequestType.STATUS.getType()); - data.putShort((short) status.name().length()); - data.put(status.name().getBytes()); - server.send(port, sender, data.array()); - } - - private void processControlTimes(InetAddress sender, int port, DataInputStream is) { - byte [] controlTimes = serverManager.serializeControlTimes(); - byte [] timePacket = new byte[controlTimes.length+1]; - timePacket[0] = RequestType.CONTROL_TIMES.getType(); - System.arraycopy(controlTimes, 0, timePacket, 1, controlTimes.length); - server.send(port, sender, timePacket); - } - - private UDPServer createUdpServer() { - UDPServer server = null; - InetAddress bindAddr = getBindAddr(); - try { - if (bindAddr == null) - server = new UDPServer(PORT, PACKET_SIZE); - else - server = new UDPServer(bindAddr, PORT, PACKET_SIZE); - server.setCallback(new UDPCallback() { - public void onReceivedPacket(UDPPacket packet) { - ServerPublicInterface.this.onReceivedPacket(packet); - } - }); - } catch (SocketException e) { - // Keep it quiet - nobody needs to know this class exists - } - return server; - } - - private InetAddress getBindAddr() { - Config c = DataManager.getInstance().getConfig(ConfigFile.NETWORK); - try { - if (c.containsKey("BIND-ADDR")) - return InetAddress.getByName(c.getString("BIND-ADDR", "127.0.0.1")); - if (c.containsKey("INTERFACE-BIND-ADDR")) - return InetAddress.getByName(c.getString("INTERFACE-BIND-ADDR", "127.0.0.1")); - } catch (UnknownHostException e) { - - } - return null; - } - - private RequestType getType(byte b) { - for (RequestType type : RequestType.values()) - if (type.getType() == b) - return type; - return RequestType.UNKNOWN; - } - - public enum RequestType { - PING_PONG (0x00), - STATUS (0x01), - CONTROL_TIMES (0x02), - UNKNOWN (0xFF); - - private byte type; - - RequestType(int type) { - this.type = (byte) type; - } - - public byte getType() { return type; } - } - -} diff --git a/src/resources/control/Service.java b/src/resources/control/Service.java index 49fba0b06..7ea7c0de3 100644 --- a/src/resources/control/Service.java +++ b/src/resources/control/Service.java @@ -50,7 +50,7 @@ public abstract class Service implements IntentReceiver { */ public boolean initialize() { IntentManager.getInstance().initialize(); - return DataManager.getInstance().isInitialized() && ServerManager.getInstance().initialize(); + return DataManager.getInstance().isInitialized(); } /** @@ -78,7 +78,7 @@ public abstract class Service implements IntentReceiver { * @return TRUE if termination was successful, FALSE otherwise */ public boolean terminate() { - return ServerManager.getInstance().terminate(); + return true; } /** diff --git a/src/services/CoreManager.java b/src/services/CoreManager.java index a5e567a34..d96797291 100644 --- a/src/services/CoreManager.java +++ b/src/services/CoreManager.java @@ -55,6 +55,7 @@ import resources.control.Intent; import resources.control.Manager; import resources.control.ServerStatus; import resources.server_info.Config; +import services.admin.OnlineInterfaceService; import services.galaxy.GalacticManager; import utilities.ThreadUtilities; @@ -63,6 +64,8 @@ public class CoreManager extends Manager { private static final int galaxyId = 1; private final ScheduledExecutorService shutdownService; + + private OnlineInterfaceService onlineInterfaceService; private EngineManager engineManager; private GalacticManager galacticManager; private PrintStream packetOutput; @@ -76,9 +79,11 @@ public class CoreManager extends Manager { shutdownRequested = false; galaxy = getGalaxy(); if (galaxy != null) { + onlineInterfaceService = new OnlineInterfaceService(); engineManager = new EngineManager(galaxy); galacticManager = new GalacticManager(galaxy); - + + addChildService(onlineInterfaceService); addChildService(engineManager); addChildService(galacticManager); } diff --git a/src/services/admin/OnlineInterfaceService.java b/src/services/admin/OnlineInterfaceService.java new file mode 100644 index 000000000..29eab83b5 --- /dev/null +++ b/src/services/admin/OnlineInterfaceService.java @@ -0,0 +1,153 @@ +package services.admin; + +import intents.PlayerEventIntent; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import resources.config.ConfigFile; +import resources.control.Intent; +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.HttpSocket; +import services.admin.http.HttpSocket.HttpRequest; +import services.admin.http.HttpStatusCode; +import services.admin.http.HttpsServer; +import utilities.ThreadUtilities; + +public class OnlineInterfaceService extends Service implements HttpServerCallback { + + private static final String TAG = "OnlineInterfaceService"; + + private final WebserverData data; + private final WebserverHandler handler; + private final Runnable dataCollectionRunnable; + private ScheduledExecutorService executor; + private HttpsServer httpsServer; + private HttpServer httpServer; + private boolean authorized; + + public OnlineInterfaceService() { + data = new WebserverData(); + handler = new WebserverHandler(data); + dataCollectionRunnable = () -> collectData(); + 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)) { + System.err.println("Failed to initialize HTTPS server! Incorrect password?"); + httpServer.stop(); + httpsServer.stop(); + super.initialize(); + return false; + } + executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("ServerInterface-DataCollection")); + registerForIntent(PlayerEventIntent.TYPE); + 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); + } + return super.start(); + } + + @Override + public boolean stop() { + if (authorized) { + httpServer.stop(); + httpsServer.stop(); + executor.shutdownNow(); + try { + executor.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + 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("GET")) { + socket.send(HttpStatusCode.METHOD_NOT_ALLOWED); + return; + } + if (!socket.isSecure()) { + socket.redirect(new URL("https", httpsServer.getBindAddress().getHostName(), httpsServer.getBindPort(), request.getURI().getPath()).toString()); + return; + } + handler.handleRequest(socket, request); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onIntentReceived(Intent i) { + if (i instanceof PlayerEventIntent) { + PlayerEventIntent pei = (PlayerEventIntent) i; + switch (pei.getEvent()) { + case PE_ZONE_IN: + data.addOnlinePlayer(pei.getPlayer()); + break; + case PE_LOGGED_OUT: + data.removeOnlinePlayer(pei.getPlayer()); + break; + default: + break; + } + } + } + + private void collectData() { + Runtime r = Runtime.getRuntime(); + double memUsage = 1 - ((double) r.totalMemory() / r.maxMemory()); + data.addMemoryUsageData(memUsage); + } + + 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) { + System.err.println("NetworkListenerService: Unknown host for IP: " + t); + } + return null; + } + +} diff --git a/src/services/admin/WebserverData.java b/src/services/admin/WebserverData.java new file mode 100644 index 000000000..5ce3c162e --- /dev/null +++ b/src/services/admin/WebserverData.java @@ -0,0 +1,60 @@ +package services.admin; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import resources.player.Player; + +class WebserverData { + + private static final Charset ASCII = Charset.forName("ASCII"); + + private final double [] memoryUsage; + private final Set onlinePlayers; + + public WebserverData() { + memoryUsage = new double[60 * 5]; + onlinePlayers = new HashSet<>(); + } + + public void addMemoryUsageData(double percent) { + for (int i = 1; i < memoryUsage.length; i++) + memoryUsage[i-1] = memoryUsage[i]; + memoryUsage[memoryUsage.length-1] = percent; + } + + public void addOnlinePlayer(Player player) { + onlinePlayers.add(player); + } + + public void removeOnlinePlayer(Player player) { + onlinePlayers.remove(player); + } + + public double [] getMemoryUsage() { + return Arrays.copyOf(memoryUsage, memoryUsage.length); + } + + public Set getOnlinePlayers() { + return Collections.unmodifiableSet(onlinePlayers); + } + + public String getLog() throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("log.txt")), ASCII)); + StringBuilder builder = new StringBuilder(); + while (reader.ready()) { + builder.append(reader.readLine() + System.lineSeparator()); + } + reader.close(); + return builder.toString(); + } + +} diff --git a/src/services/admin/WebserverHandler.java b/src/services/admin/WebserverHandler.java new file mode 100644 index 000000000..6cb36926c --- /dev/null +++ b/src/services/admin/WebserverHandler.java @@ -0,0 +1,205 @@ +package services.admin; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import resources.player.Player; +import resources.server_info.Log; +import services.admin.http.HttpImageType; +import services.admin.http.HttpSocket; +import services.admin.http.HttpSocket.HttpRequest; +import services.admin.http.HttpStatusCode; + +class WebserverHandler { + + private static final String TAG = "WebserverHandler"; + private static final Charset ASCII = Charset.forName("ASCII"); + + private final WebserverData data; + private final Pattern variablePattern; + + public WebserverHandler(WebserverData data) { + this.data = data; + variablePattern = Pattern.compile("\\$\\{.+\\}"); // Looks for variables: ${VAR_NAME} + } + + public void handleRequest(HttpSocket socket, HttpRequest request) throws IOException { + Log.i(TAG, "Requested: " + request.getURI()); + String file = request.getURI().toASCIIString(); + if (file.contains("?")) + file = file.substring(0, file.indexOf('?')); + switch (file) { + case "/memory_usage.png": + socket.send(createMemoryUsage(), HttpImageType.PNG); + break; + default: { + byte [] response = parseFile(file); + if (response == null) + socket.send(HttpStatusCode.NOT_FOUND, request.getURI() + " is not found!"); + else + socket.send(getFileType(file), response); + break; + } + } + } + + private BufferedImage createMemoryUsage() { + double [] memoryUsage = data.getMemoryUsage(); + final int graphHeight = 300; + BufferedImage image = new BufferedImage(900, graphHeight + 25, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = image.createGraphics(); + int prevX = -1; + int prevY = -1; + Font font = g.getFont(); + g.setFont(font.deriveFont(Math.min(image.getHeight()/10.0f, 25.0f))); + RenderingHints rh = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g.setRenderingHints(rh); + g.setColor(Color.WHITE); + g.drawRect(0, 0, image.getWidth()-1, graphHeight-1); + g.drawLine(0, image.getHeight()-1, image.getWidth(), image.getHeight()-1); + for (int i = 1; i < 10; i++) { + g.drawLine(0, (int) ((i/10.0)*graphHeight), 10, (int) ((i/10.0)*graphHeight)); + } + String inUse = getMemoryInUse(); + int width = g.getFontMetrics().stringWidth(inUse); + g.drawString("Current: " + (int) (memoryUsage[memoryUsage.length-1] * 100) + "%", 5, image.getHeight() - 5); + g.drawString(inUse, image.getWidth()-width - 5, image.getHeight() - 5); + g.setColor(Color.RED); + int start = 0; + for (double d : memoryUsage) { + if (d == 0) + start++; + else + break; + } + for (int i = start; i < memoryUsage.length; i++) { + int x = (int) ((double) i / memoryUsage.length * image.getWidth()); + int y = (int) ((1-memoryUsage[i]) * graphHeight); + if (prevX != -1 && prevY != -1) + g.drawLine(prevX, prevY, x, y); + prevX = x; + prevY = y; + } + return image; + } + + private String getMemoryInUse() { + double memory = Runtime.getRuntime().totalMemory(); + String [] types = new String[]{"B", "KB", "MB", "GB"}; + String type = types[0]; + for (int i = 0; i < types.length && memory >= 1024; i++) { + memory /= 1024; + type = types[i]; + } + return String.format("In-Use: %.2f%s", memory, type); + } + + private String getFileType(String filepath) { + if (!filepath.contains(".")) + return "text/html"; + String type = filepath.substring(filepath.lastIndexOf('.')+1).toLowerCase(Locale.US); + switch (type) { + case "png": + case "gif": + case "jpg": + case "jpeg": + case "ico": + return "image/" + type; + default: + return "text/" + type; + } + } + + private byte [] parseFile(String filepath) throws IOException { + File file = new File("res/webserver" + filepath); + if (file.isDirectory()) + file = new File(file, "index.html"); + String type = getFileType(filepath); + if (!verifyPath(file)) + return null; + if (type.equalsIgnoreCase("text/html")) + return parseHtmlFile(file).getBytes(ASCII); + try (InputStream is = new FileInputStream(file)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(is.available()); + byte [] buffer = new byte[Math.min(1024, is.available())]; + while (is.available() > 0) { + if (is.available() > buffer.length && buffer.length < 1024) + buffer = new byte[Math.min(1024, is.available())]; + int len = is.read(buffer); + baos.write(buffer, 0, len); + } + return baos.toByteArray(); + } + } + + private boolean verifyPath(File file) { + File parent = new File("res/webserver"); + if (!file.getAbsolutePath().startsWith(parent.getAbsolutePath())) + return false; + if (!file.isFile()) + return false; + return true; + } + + private String parseHtmlFile(File file) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) { + String line = null; + StringBuilder builder = new StringBuilder(); + while (reader.ready()) { + line = reader.readLine(); + if (line == null) + break; + Matcher matcher = variablePattern.matcher(line); + while (matcher.find()) { + String var = matcher.group(); + var = var.substring(2, var.length()-1); + line = line.replaceAll("\\$\\{"+var+"\\}", getVariable(var)); + matcher.reset(line); + } + builder.append(line + System.lineSeparator()); + } + return builder.toString(); + } + } + + private String getVariable(String var) throws IOException { + var = var.toLowerCase(Locale.US); + switch (var) { + case "log": + return data.getLog().replace("\n", "\n
"); + case "online_players": { + Set players = data.getOnlinePlayers(); + StringBuilder ret = new StringBuilder("Online Players: ["+players.size()+"]
"); + ret.append(""); + for (Player p : players) { + ret.append(""); + ret.append(String.format("", p.getUsername())); + ret.append(String.format("", p.getUserId())); + ret.append(String.format("", p.getCharacterName())); + ret.append(String.format("", p.getCreatureObject().getObjectId())); + ret.append(""); + } + ret.append("
UsernameUser IDCharacterCharacter ID
%s%d%s%d
"); + return ret.toString(); + } + default: + return ""; + } + } + +} diff --git a/src/services/admin/http/HttpImageType.java b/src/services/admin/http/HttpImageType.java new file mode 100644 index 000000000..025018dec --- /dev/null +++ b/src/services/admin/http/HttpImageType.java @@ -0,0 +1,7 @@ +package services.admin.http; + +public enum HttpImageType { + GIF, + PNG, + JPG +} diff --git a/src/services/admin/http/HttpServer.java b/src/services/admin/http/HttpServer.java new file mode 100644 index 000000000..b5f824e1d --- /dev/null +++ b/src/services/admin/http/HttpServer.java @@ -0,0 +1,196 @@ +package services.admin.http; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import resources.server_info.Log; +import services.admin.http.HttpSocket.HttpRequest; +import utilities.ThreadUtilities; + +public class HttpServer { + + private static final String TAG = "HttpServer"; + + private final InetAddress addr; + private final int port; + + private ExecutorService executor; + private ServerSocket serverSocket; + private AtomicInteger currentConnections; + private HttpServerCallback callback; + private int maxConnections; + private boolean secure; + + protected HttpServer(InetAddress addr, int port, boolean secure) { + this.addr = addr; + this.port = port; + this.currentConnections = new AtomicInteger(0); + this.callback = null; + this.maxConnections = 2; + this.secure = secure; + } + + public HttpServer(InetAddress addr, int port) { + this(addr, port, false); + } + + public void start() { + executor = Executors.newCachedThreadPool(ThreadUtilities.newThreadFactory(getThreadFactoryName())); + try { + serverSocket = createSocket(); + startAcceptThread(serverSocket, secure); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void stop() { + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + if (executor != null) { + executor.shutdownNow(); + try { + executor.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void setServerCallback(HttpServerCallback callback) { + this.callback = callback; + } + + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + + public final InetAddress getBindAddress() { + return addr; + } + + public final int getBindPort() { + return port; + } + + protected String getThreadFactoryName() { + return "HttpServer-%d"; + } + + protected ServerSocket createSocket() throws IOException { + return new ServerSocket(port, 0, addr); + } + + private void startAcceptThread(ServerSocket serverSocket, boolean secure) { + executor.submit(() -> { + try { + acceptThread(serverSocket, secure); + } catch (Throwable t) { + t.printStackTrace(); + } + }); + } + + private void startSocketThread(HttpSocket socket) { + executor.submit(() -> { + try { + socketThread(socket); + } catch (Throwable t) { + t.printStackTrace(); + } + try { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + synchronized (currentConnections) { + currentConnections.decrementAndGet(); + } + }); + } + + private void acceptThread(ServerSocket serverSocket, boolean secure) { + Log.i(TAG, "Now listening over HTTP%s. Address: %s:%d", secure?"S":"", serverSocket.getInetAddress(), serverSocket.getLocalPort()); + while (serverSocket.isBound() && !serverSocket.isClosed()) { + try { + Socket socket = serverSocket.accept(); + HttpSocket httpSocket = new HttpSocket(socket, secure); + if (!httpSocket.isOpened()) { + httpSocket.close(); + continue; + } + if (currentConnections.get() >= maxConnections) { + httpSocket.send(HttpStatusCode.SERVICE_UNAVAILABLE, "The server has reached it's maximum connection limit: " + maxConnections); + socket.close(); + } else { + if (currentConnections.incrementAndGet() > maxConnections) { + socket.close(); + currentConnections.decrementAndGet(); + } else { + startSocketThread(httpSocket); + } + } + } catch (SocketException e) { + if (serverSocket.isClosed()) + break; + e.printStackTrace(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + Log.i(TAG, "No longer listening over HTTP%s!", secure?"S":""); + } + + private void socketThread(HttpSocket socket) throws IOException { + onSocketCreated(socket); + HttpRequest request; + while (socket.isOpened() && !socket.isClosed()) { + request = socket.waitForRequest(); + if (request == null) + break; + if (request.getType() == null || request.getURI() == null || request.getHttpVersion() == null) { + socket.send(HttpStatusCode.BAD_REQUEST); + continue; + } + onRequestReceived(socket, request); + } + } + + private void onSocketCreated(HttpSocket socket) { + if (callback == null) + return; + try { + callback.onSocketCreated(socket); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + private void onRequestReceived(HttpSocket socket, HttpRequest request) { + if (callback == null) + return; + try { + callback.onRequestReceived(socket, request); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public interface HttpServerCallback { + void onSocketCreated(HttpSocket socket); + void onRequestReceived(HttpSocket socket, HttpRequest request); + } + +} diff --git a/src/services/admin/http/HttpSocket.java b/src/services/admin/http/HttpSocket.java new file mode 100644 index 000000000..68ec22c99 --- /dev/null +++ b/src/services/admin/http/HttpSocket.java @@ -0,0 +1,263 @@ +package services.admin.http; + +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import javax.imageio.ImageIO; + +import resources.server_info.Log; + +public class HttpSocket implements Closeable { + + private static final Charset ASCII = Charset.forName("ASCII"); + + private final Socket socket; + private final BufferedReader reader; + private final BufferedWriter writer; + private final OutputStream rawOutputStream; + private final boolean secure; + + public HttpSocket(Socket socket, boolean secure) { + this.socket = socket; + this.reader = createBufferedReader(); + this.writer = createBufferedWriter(); + this.rawOutputStream = getOutputStream(); + this.secure = secure; + } + + /** + * Waits for the next HTTP request to come in, then returns the request + * sent by the client - or null if the client closes the connection. + * @return the HttpRequest sent by the client, or null if the connection is + * closed + */ + public HttpRequest waitForRequest() { + while (!socket.isClosed()) { + String line = readLine(); + boolean hasData = line != null && !line.isEmpty(); + String [] req = null; + Map params = new HashMap<>(); + while (line != null && !line.isEmpty()) { + if (req == null) + req = line.split(" ", 3); + String [] parts = line.split(": ", 2); + if (parts.length == 2) + params.put(parts[0], parts[1]); + hasData = !line.isEmpty(); + line = readLine(); + } + if (hasData) { + String type = null; + URI uri = null; + String version = null; + if (req.length >= 1) + type = req[0]; + if (req.length >= 2) + uri = URI.create(req[1]); + if (req.length >= 3) + version = req[2]; + return new HttpRequest(type, uri, version, params); + } + if (line == null) + break; + } + return null; + } + + public void redirect(String url) throws IOException { + Map params = new HashMap<>(); + params.put("Location", url); + send(HttpStatusCode.MOVED_PERMANENTLY, params, "text/html", ""); + } + + public void send(HttpStatusCode code) throws IOException { + send(code, ""); + } + + public void send(String response) throws IOException { + send(HttpStatusCode.OK, response); + } + + public void send(String contentType, byte [] response) throws IOException { + send(HttpStatusCode.OK, new HashMap<>(), contentType, response); + } + + public void send(BufferedImage image, HttpImageType type) throws IOException { + String contentType = "image"; + switch (type) { + case GIF: + contentType = "image/gif"; + break; + case JPG: + contentType = "image/jpeg"; + break; + case PNG: + contentType = "image/png"; + break; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(image.getWidth() * image.getHeight() * 3); + if (!ImageIO.write(image, type.name().toLowerCase(Locale.US), baos)) + throw new IllegalArgumentException("Cannot write image with type: " + type); + send(HttpStatusCode.OK, new HashMap<>(), contentType, baos.toByteArray()); + } + + public void send(HttpStatusCode code, String response) throws IOException { + if (code == HttpStatusCode.OK && response.isEmpty()) + code = HttpStatusCode.NO_CONTENT; + send(code, new HashMap<>(), "text/html", response); + } + + private void send(HttpStatusCode code, Map params, String contentType, String response) throws IOException { + params.put("Content-Length", Integer.toString(response.length())); + params.put("Content-Type", contentType); + sendHeader(code, params); + writer.write(response); + writer.flush(); + } + + private void send(HttpStatusCode code, Map params, String contentType, byte [] data) throws IOException { + params.put("Content-Length", Integer.toString(data.length)); + params.put("Content-Type", contentType); + sendHeader(code, params); + writer.flush(); + rawOutputStream.write(data); + rawOutputStream.flush(); + } + + private void sendHeader(HttpStatusCode code, Map params) throws IOException { + write("HTTP/1.1 %d %s", code.getCode(), code.getName()); + for (Entry param : params.entrySet()) + write(param.getKey() + ": " + param.getValue()); + writer.newLine(); + } + + private String readLine() { + try { + return reader.readLine(); + } catch (Exception e) { + return null; + } + } + + @Override + public void close() throws IOException { + socket.close(); + } + + public boolean isOpened() { + return reader != null && writer != null; + } + + public boolean isClosed() { + return socket.isClosed(); + } + + public boolean isSecure() { + return secure; + } + + public InetAddress getInetAddress() { + return socket.getInetAddress(); + } + + public int getPort() { + return socket.getPort(); + } + + private void write(String str, Object ... args) throws IOException { + writer.write(String.format(str, args) + System.lineSeparator()); + Log.d("HttpSocket", str, args); + } + + private BufferedReader createBufferedReader() { + try { + return new BufferedReader(new InputStreamReader(socket.getInputStream(), ASCII)); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private BufferedWriter createBufferedWriter() { + try { + return new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), ASCII)); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private OutputStream getOutputStream() { + try { + return socket.getOutputStream(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + public static class HttpRequest { + private final String type; + private final URI uri; + private final String httpVersion; + private final Map params; + + private HttpRequest(String requestType, URI uri, String httpVersion, Map params) { + this.type = requestType; + this.uri = uri; + this.httpVersion = httpVersion; + this.params = params; + } + + /** + * Gets the type of HTTP request sent. It could be one of: GET, POST, + * HEAD. + * @return the type of HTTP request + */ + public String getType() { + return type; + } + + /** + * Gets the requested URI sent + * @return the requested URI + */ + public URI getURI() { + return uri; + } + + /** + * Gets the HTTP version of the client, typical format is: HTTP/1.1 + * @return the HTTP version of the client + */ + public String getHttpVersion() { + return httpVersion; + } + + /** + * Gets all the paramters sent in the HTTP request, such as: + * "User-Agent", "Accept", etc. + * @return the HTTP request parameters + */ + public Map getParams() { + return Collections.unmodifiableMap(params); + } + } + +} diff --git a/src/services/admin/http/HttpStatusCode.java b/src/services/admin/http/HttpStatusCode.java new file mode 100644 index 000000000..0f9de5cfc --- /dev/null +++ b/src/services/admin/http/HttpStatusCode.java @@ -0,0 +1,48 @@ +package services.admin.http; + +public enum HttpStatusCode { + /** The request is OK (this is the standard response for successful HTTP requests) */ + OK (200, "OK"), + /** The request has been successfully processed, but is not returning any content */ + NO_CONTENT (204, "No Content"), + /** The requested page has moved to a new URL */ + MOVED_PERMANENTLY (301, "Moved Permanently"), + /** The requested page has moved temporarily to a new URL */ + FOUND (302, "Found"), + /** The requested page can be found under a different URL */ + SEE_OTHER (303, "See Other"), + /** The requested page has moved temporarily to a new URL */ + TEMPORARY_REDIRECT (307, "Temporary Redirect"), + /** The request cannot be fulfilled due to bad syntax */ + BAD_REQUEST (400, "Bad Request"), + /** The request was a legal request, but the server is refusing to respond to it. + For use when authentication is possible but has failed or not yet been provided */ + UNAUTHORIZED (401, "Unauthorized"), + /** The request was a legal request, but the server is refusing to respond to it */ + FORBIDDEN (403, "Forbidden"), + /** The requested page could not be found but may be available again in the future */ + NOT_FOUND (404, "Not Found"), + /** A request was made of a page using a request method not supported by that page */ + METHOD_NOT_ALLOWED (405, "Method Not Allowed"), + /** A generic error message, given when no more specific message is suitable */ + INTERNAL_ERROR (500, "Internal Error"), + /** The server is currently unavailable (overloaded or down) */ + SERVICE_UNAVAILABLE (503, ""); + + private int code; + private String name; + + HttpStatusCode(int code, String name) { + this.code = code; + this.name = name; + } + + public int getCode() { + return code; + } + + public String getName() { + return name; + } + +} diff --git a/src/services/admin/http/HttpsServer.java b/src/services/admin/http/HttpsServer.java new file mode 100644 index 000000000..449c57022 --- /dev/null +++ b/src/services/admin/http/HttpsServer.java @@ -0,0 +1,81 @@ +package services.admin.http; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocketFactory; + +import resources.server_info.Config; +import resources.server_info.Log; + +public class HttpsServer extends HttpServer { + + private static final String SERVER_KEYSTORE = "server.keystore"; + + private SSLContext sslContext; + private SSLServerSocketFactory sslServerSocketFactory; + + public HttpsServer(InetAddress addr, int port) { + super(addr, port, true); + } + + public boolean initialize(Config config) { + String pass = config.getString("HTTPS-KEYSTORE-PASSWORD", ""); + System.setProperty("javax.net.ssl.keyStore", SERVER_KEYSTORE); + System.setProperty("javax.net.ssl.keyStorePassword", pass); + try { + sslContext = SSLContext.getInstance("TLSv1.2"); + KeyManager [] managers = createKeyManagers(pass.toCharArray()); + if (managers == null) + return false; + sslContext.init(managers, null, null); + sslServerSocketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + protected String getThreadFactoryName() { + return "HttpsServer-%d"; + } + + protected ServerSocket createSocket() throws IOException { + try { + return sslServerSocketFactory.createServerSocket(getBindPort(), 0, getBindAddress()); + } catch (IOException e) { + System.err.println("Failed to start HTTPS server!"); + Log.e("HttpsServer", "Failed to start HTTPS server!"); + e.printStackTrace(); + } + return null; + } + + private KeyManager [] createKeyManagers(char [] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException { + KeyStore ks = KeyStore.getInstance("JKS"); + InputStream ksIs = new FileInputStream(SERVER_KEYSTORE); + try { + ks.load(ksIs, password); + } catch (IOException e) { + return null; // Password invalid + } finally { + ksIs.close(); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, password); + return kmf.getKeyManagers(); + } + +} diff --git a/src/services/player/LoginService.java b/src/services/player/LoginService.java index ad63d82f2..903dd82a5 100644 --- a/src/services/player/LoginService.java +++ b/src/services/player/LoginService.java @@ -228,7 +228,7 @@ public class LoginService extends Service { String message = "Sorry, you're banned!"; sendPacket(player.getNetworkId(), new ErrorMessage(type, message, false)); System.err.println("[" + id.getUsername() + "] Can't login - Banned! IP: " + id.getAddress() + ":" + id.getPort()); - Log.i("LoginService", "%s cannot login due to a ban, from %s:%d", id.getAddress(), id.getPort()); + Log.i("LoginService", "%s cannot login due to a ban, from %s:%d", player.getUsername(), id.getAddress(), id.getPort()); player.setPlayerState(PlayerState.DISCONNECTED); new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_BANNED).broadcast(); }