/*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*/
package silentium.authserver;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Set;
import javolution.util.FastSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import silentium.authserver.GameServerTable.GameServerInfo;
import silentium.authserver.configs.MainConfig;
import silentium.authserver.network.GameServerPacketHandler;
import silentium.authserver.network.GameServerPacketHandler.GameServerState;
import silentium.authserver.network.loginserverpackets.InitLS;
import silentium.authserver.network.loginserverpackets.KickPlayer;
import silentium.authserver.network.loginserverpackets.LoginServerFail;
import silentium.authserver.network.serverpackets.ServerBasePacket;
import silentium.commons.crypt.NewCrypt;
import silentium.commons.utils.Util;
/**
* @author -Wooden-
* @author KenM
*
* @rework Ashe<br>
* Date: 26/08/2012<br>
* Time: 00:03
*/
public class GameServerThread extends Thread {
protected static final Logger _log = LoggerFactory.getLogger(GameServerThread.class.getName());
private final Socket _connection;
private InputStream _in;
private OutputStream _out;
private final RSAPublicKey _publicKey;
private final RSAPrivateKey _privateKey;
private NewCrypt _blowfish;
private GameServerState _loginConnectionState = GameServerState.CONNECTED;
private final String _connectionIp;
private GameServerInfo _gsi;
/**
* Authed Clients on a GameServer
*/
private final Set<String> _accountsOnGameServer = new FastSet<>();
private String _connectionIPAddress;
@Override
public void run() {
_connectionIPAddress = _connection.getInetAddress().getHostAddress();
if (isBannedGameserverIP(_connectionIPAddress)) {
_log.info("GameServerRegistration: IP Address " + _connectionIPAddress + " is on Banned IP list.");
forceClose(LoginServerFail.REASON_IP_BANNED);
// ensure no further processing for this connection
return;
}
final InitLS startPacket = new InitLS(_publicKey.getModulus().toByteArray());
try {
sendPacket(startPacket);
int lengthHi = 0;
int lengthLo = 0;
int length = 0;
boolean checksumOk = false;
for (;;) {
lengthLo = _in.read();
lengthHi = _in.read();
length = lengthHi * 256 + lengthLo;
if (lengthHi < 0 || _connection.isClosed()) {
_log.debug("LoginServerThread: Login terminated the connection.");
break;
}
byte[] data = new byte[length - 2];
int receivedBytes = 0;
int newBytes = 0;
while (newBytes != -1 && receivedBytes < length - 2) {
newBytes = _in.read(data, 0, length - 2);
receivedBytes = receivedBytes + newBytes;
}
if (receivedBytes != length - 2) {
_log.warn("Incomplete Packet is sent to the server, closing connection.(LS)");
break;
}
// decrypt if we have a key
data = _blowfish.decrypt(data);
checksumOk = NewCrypt.verifyChecksum(data);
if (!checksumOk) {
_log.warn("Incorrect packet checksum, closing connection (LS)");
return;
}
if (MainConfig.PACKET_HANDLER_DEBUG) {
_log.debug("[C]\n" + Util.printData(data));
}
GameServerPacketHandler.handlePacket(data, this);
}
} catch (IOException e) {
final String serverName = getServerId() != -1 ? "[" + getServerId() + "] " + GameServerTable.getInstance().getServerNameById(getServerId()) : "(" + _connectionIPAddress + ")";
final String msg = "GameServer " + serverName + ": Connection lost: " + e.getMessage();
_log.info(msg);
} finally {
if (isAuthed()) {
_gsi.setDown();
_log.info("Server [" + getServerId() + "] " + GameServerTable.getInstance().getServerNameById(getServerId()) + " is now set as disconnected");
}
L2LoginServer.getInstance().getGameServerListener().removeGameServer(this);
L2LoginServer.getInstance().getGameServerListener().removeFloodProtection(_connectionIp);
}
}
/**
* @return Returns the connectionIpAddress.
*/
public String getConnectionIpAddress() {
return _connectionIPAddress;
}
/**
* @return Returns the isAuthed.
*/
public boolean isAuthed() {
if (_gsi == null) {
return false;
}
return _gsi.isAuthed();
}
/**
* @param gsi
*/
public void setGameServerInfo(final GameServerInfo gsi) {
_gsi = gsi;
}
/**
* @return
*/
public GameServerInfo getGameServerInfo() {
return _gsi;
}
/**
* @param ipAddress
* @return
*/
public static boolean isBannedGameserverIP(final String ipAddress) {
return false;
}
/**
* @param con
*/
public GameServerThread(final Socket con) {
_connection = con;
_connectionIp = con.getInetAddress().getHostAddress();
try {
_in = new BufferedInputStream(_connection.getInputStream());
_out = new BufferedOutputStream(_connection.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
final KeyPair pair = GameServerTable.getInstance().getKeyPair();
_privateKey = (RSAPrivateKey) pair.getPrivate();
_publicKey = (RSAPublicKey) pair.getPublic();
_blowfish = new NewCrypt("_;v.]05-31!|+-%xT!^[$\00");
start();
}
/**
*
* @param account
* @return
*/
public boolean hasAccountOnGameServer(final String account) {
return _accountsOnGameServer.contains(account);
}
/**
* Count accounts on game server
*
* @return
*/
public int getPlayerCount() {
return _accountsOnGameServer.size();
}
/**
* Attachs a GameServerInfo to this Thread<br>
* <ul>
* <li>Updates the GameServerInfo values based on GameServerAuth packet</li>
* <li><b>Sets the GameServerInfo as Authed</b></li>
* </ul>
*
* @param gsi The GameServerInfo to be attached.
* @param port
* @param hosts
* @param maxPlayers
*/
public void attachGameServerInfo(GameServerInfo gsi, int port, final String gameExternalHost, final String gameInternalHost, int maxPlayers) {
setGameServerInfo(gsi);
gsi.setGameServerThread(this);
gsi.setPort(port);
setGameHosts(gameExternalHost, gameInternalHost);
gsi.setMaxPlayers(maxPlayers);
gsi.setAuthed(true);
}
/**
* @param gameExternalHost
* @param gameInternalHost
*/
public void setGameHosts(final String gameExternalHost, final String gameInternalHost) {
final String oldInternal = _gsi.getInternalHost();
final String oldExternal = _gsi.getExternalHost();
_gsi.setExternalHost(gameExternalHost);
_gsi.setInternalIp(gameInternalHost);
if (!"*".equals(gameExternalHost)) {
try {
_gsi.setExternalIp(InetAddress.getByName(gameExternalHost).getHostAddress());
} catch (UnknownHostException e) {
_log.warn("Couldn't resolve hostname \"" + gameExternalHost + "\"");
}
} else {
_gsi.setExternalIp(_connectionIp);
}
if (!"*".equals(gameInternalHost)) {
try {
_gsi.setInternalIp(InetAddress.getByName(gameInternalHost).getHostAddress());
} catch (UnknownHostException e) {
_log.warn("Couldn't resolve hostname \"" + gameInternalHost + "\"");
}
} else {
_gsi.setInternalIp(_connectionIp);
}
String internalIP = "not found", externalIP = "not found";
if (oldInternal == null|| !oldInternal.equalsIgnoreCase(gameInternalHost)) {
internalIP = gameInternalHost;
}
if (oldExternal == null|| !oldExternal.equalsIgnoreCase(gameExternalHost)) {
externalIP = gameExternalHost;
}
_log.info("Hooked gameserver: [" + getServerId() + "] " + GameServerTable.getInstance().getServerNameById(getServerId()));
_log.info("Internal/External IP(s): " + internalIP + "/" + externalIP);
}
/**
* @param account
*/
public void kickPlayer(final String account) {
sendPacket(new KickPlayer(account));
}
/**
* @param reason
*/
public void forceClose(final int reason) {
sendPacket(new LoginServerFail(reason));
try {
_connection.close();
} catch (IOException e) {
_log.info("GameServerThread: Failed disconnecting banned server, server already disconnected.");
}
}
/**
* @param sl
*/
public void sendPacket(final ServerBasePacket sl) {
try {
byte[] data = sl.getContent();
NewCrypt.appendChecksum(data);
if (MainConfig.PACKET_HANDLER_DEBUG) {
_log.info("[S] " + sl.getClass().getSimpleName() + ":\n" + Util.printData(data));
}
_blowfish.crypt(data, 0, data.length);
int len = data.length + 2;
synchronized (_out) {
_out.write(len & 0xff);
_out.write((len >> 8) & 0xff);
_out.write(data);
_out.flush();
}
} catch (IOException e) {
_log.error("IOException while sending packet " + sl.getClass().getSimpleName());
}
}
/**
* Return server ID
*
* @return int
*/
public int getServerId() {
if (getGameServerInfo() != null) {
return getGameServerInfo().getId();
}
return -1;
}
/**
* @return _privateKey
*/
public RSAPrivateKey getPrivateKey() {
return _privateKey;
}
/**
* @param blowfish
*/
public void SetBlowFish(NewCrypt blowfish) {
_blowfish = blowfish;
}
/**
* @return _loginConnectionState
*/
public GameServerState getLoginConnectionState() {
return _loginConnectionState;
}
/**
* @param state
*/
public void setLoginConnectionState(GameServerState state) {
_loginConnectionState = state;
}
/**
* @param account
*/
public void addAccountOnGameServer(String account) {
_accountsOnGameServer.add(account);
}
/**
* @param account
*/
public void removeAccountOnGameServer(String account) {
_accountsOnGameServer.remove(account);
}
}