//////////////////////////////////////////////////////////////////////////////// // Scorched3D (c) 2000-2003 // // This file is part of Scorched3D. // // Scorched3D 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 2 of the License, or // (at your option) any later version. // // Scorched3D 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 Scorched3D; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ServerConnectHandler *ServerConnectHandler::instance_ = 0; ServerConnectHandler *ServerConnectHandler::instance() { if (!instance_) { instance_ = new ServerConnectHandler; } return instance_; } ServerConnectHandler::ServerConnectHandler() { ScorchedServer::instance()->getComsMessageHandler().addHandler( "ComsConnectMessage", this); } ServerConnectHandler::~ServerConnectHandler() { } bool ServerConnectHandler::processMessage(unsigned int destinationId, const char *messageType, NetBufferReader &reader) { unsigned int ipAddress = 0; // Only do this on the server, the client can have all bots if (OptionsParam::instance()->getDedicatedServer()) { // First things, first // Check we can actually accept the connection if (ScorchedServer::instance()->getOptionsGame().getNoMaxPlayers() <= ScorchedServer::instance()->getTankContainer().getNoOfTanks()) { ServerCommon::sendString(destinationId, "--------------------------------------------------\n" "This server is full, you cannot join!\n" "--------------------------------------------------", false); ServerCommon::serverLog("Server full, kicking"); ServerCommon::kickDestination(destinationId, true); return true; } // Get the ip address ipAddress = ScorchedServer::instance()->getNetInterface(). getIpAddress(destinationId); } // Decode the connect message ComsConnectMessage message; // Check the player protocol versions are the same (correct) if (!message.readMessage(reader) || (0 != strcmp(message.getProtocolVersion(), ScorchedProtocolVersion))) { ServerCommon::sendString(destinationId, formatString( "--------------------------------------------------\n" "The version of Scorched you are running\n" "does not match the server's version.\n" "This server is running Scorched build %s (%s).\n" "You are running Scorched build %s (%s).\n" "New versions can be downloaded from\n" "http://www.scorched3d.co.uk\n" "Connection failed.\n" "--------------------------------------------------", ScorchedVersion, ScorchedProtocolVersion, message.getVersion(), message.getProtocolVersion()), false); Logger::log( formatString( "Player connected with out of date version \"%s(%s)\"", message.getVersion(), message.getProtocolVersion())); ServerCommon::kickDestination(destinationId, true); return true; } // Check for no players when a dedicated server is being used if (message.getNoPlayers() > 1 && OptionsParam::instance()->getDedicatedServer()) { ServerCommon::serverLog( formatString( "Client connecting with %u players", message.getNoPlayers())); ServerCommon::kickDestination(destinationId, true); return true; } // Check player availability if (message.getNoPlayers() > (unsigned int) (ScorchedServer::instance()->getOptionsGame().getNoMaxPlayers() - ScorchedServer::instance()->getTankContainer().getNoOfTanks())) { ServerCommon::sendString(destinationId, formatString( "--------------------------------------------------\n" "This server is full, you cannot join!\n" "--------------------------------------------------")); ServerCommon::serverLog("Server full, kicking"); ServerCommon::kickDestination(destinationId, true); return true; } // Check for a password (if any) if (ScorchedServer::instance()->getOptionsGame().getServerPassword()[0]) { if (0 != strcmp(message.getPassword(), ScorchedServer::instance()->getOptionsGame().getServerPassword())) { ServerCommon::sendString(destinationId, "--------------------------------------------------\n" "This server is running a password protected game.\n" "Your supplied password does not match.\n" "Connection failed.\n" "--------------------------------------------------", false); Logger::log( "Player connected with an invalid password"); ServerCommon::kickDestination(destinationId, true); return true; } } std::string uniqueId = message.getUniqueId(); if (!uniqueId.c_str()[0]) // No ID { // Generate the players unique id (if we need to) // // This will only actualy generate an id when using a persistent // stats logger as this is the only time we can be sure of not // giving out duplicate ids. // uniqueId = StatsLogger::instance()->allocateId(); } // Auth handler, make sure that only prefered players can connect ServerAuthHandler *authHandler = ScorchedServerUtil::instance()->getAuthHandler(); if (authHandler) { std::string resultMessage; if (!authHandler->authenticateUser(uniqueId, message.getUserName(), message.getPassword(), resultMessage)) { ServerCommon::sendString(destinationId, formatString( "--------------------------------------------------\n" "%s" "Connection failed.\n" "--------------------------------------------------", resultMessage.c_str()), false); Logger::log(formatString("User failed authentication \"%s\" [%s]", message.getUserName(), uniqueId.c_str())); ServerCommon::kickDestination(destinationId, true); return true; } } // Check if this unique id has been banned if (uniqueId.c_str()[0]) { ServerBanned::BannedType type = ScorchedServerUtil::instance()->bannedPlayers.getBanned(ipAddress, uniqueId.c_str()); if (type == ServerBanned::Banned) { Logger::log(formatString("Banned uniqueid connection from destination \"%i\"", destinationId)); ServerCommon::kickDestination(destinationId); return true; } } // Check that this unique id has not already connected (if unique ids are in use) if (uniqueId.c_str()[0] && !ScorchedServer::instance()->getOptionsGame().getAllowSameUniqueId()) { std::map &playingTanks = ScorchedServer::instance()->getTankContainer().getPlayingTanks(); std::map::iterator playingItor; for (playingItor = playingTanks.begin(); playingItor != playingTanks.end(); playingItor++) { Tank *current = (*playingItor).second; if (0 == strcmp(current->getUniqueId(), uniqueId.c_str())) { Logger::log(formatString("Duplicate uniqueid connection from destination \"%i\"", destinationId)); ServerCommon::kickDestination(destinationId); return true; } } } // Send the connection accepted message to the client ComsConnectAcceptMessage acceptMessage( destinationId, ScorchedServer::instance()->getOptionsGame().getServerName(), ScorchedServer::instance()->getOptionsGame().getPublishAddress(), uniqueId.c_str()); // Add the connection gif if (OptionsParam::instance()->getDedicatedServer()) { const char *fileName = getSettingsFile(formatString("icon-%i.gif", ScorchedServer::instance()->getOptionsGame().getPortNo())); FILE *in = fopen(fileName, "rb"); if (in) { acceptMessage.getServerGif().reset(); unsigned char readBuf[512]; while (unsigned int size = fread(readBuf, sizeof(unsigned char), 512, in)) { acceptMessage.getServerGif().addDataToBuffer(readBuf, size); } fclose(in); } } if (!ComsMessageSender::sendToSingleClient(acceptMessage, destinationId)) { Logger::log(formatString( "Failed to send accept to client \"%i\"", destinationId)); ServerCommon::kickDestination(destinationId); return true; } // Send all current tanks to the new client std::map::iterator itor; std::map &tankList = ScorchedServer::instance()->getTankContainer().getPlayingTanks(); for (itor = tankList.begin(); itor != tankList.end(); itor++) { Tank *tank = (*itor).second; ComsAddPlayerMessage oldPlayerMessage( tank->getPlayerId(), tank->getName(), tank->getColor(), tank->getModelContainer().getTankModelName(), tank->getModelContainer().getTankTypeName(), tank->getDestinationId(), tank->getTeam(), ""); oldPlayerMessage.setPlayerIconName(tank->getAvatar().getName()); oldPlayerMessage.getPlayerIcon().addDataToBuffer( tank->getAvatar().getFile().getBuffer(), tank->getAvatar().getFile().getBufferUsed()); ComsMessageSender::sendToSingleClient(oldPlayerMessage, destinationId); } // Add all the new tanks for (unsigned int i=0; igetDedicatedServer()) { addNextTank(destinationId, ipAddress, uniqueId.c_str(), message.getHostDesc(), true); } // Send the state of all the currently connect clients ComsPlayerStateMessage comsPlayerStateMessage(true); if (!ComsMessageSender::sendToSingleClient( comsPlayerStateMessage, destinationId)) return false; return true; } void ServerConnectHandler::addNextTank(unsigned int destinationId, unsigned int ipAddress, const char *sentUniqueId, const char *sentHostDesc, bool extraSpectator) { // The player has connected Vector color; unsigned int tankId = 0; const char *playerName = ""; if (extraSpectator) { tankId = 1; playerName = "Spectator"; color = Vector(0.7f, 0.7f, 0.7f); } else { playerName = TankAIStrings::instance()->getPlayerName(); color = TankColorGenerator::instance()->getNextColor( ScorchedServer::instance()->getTankContainer().getPlayingTanks()); tankId = TankAIAdder::getNextTankId( ScorchedServer::instance()->getContext()); } // Make sure host desc does not contain \" for (char *h=(char *)sentHostDesc; *h; h++) if (*h == '\"') *h=' '; // Create the new tank Tank *tank = new Tank( ScorchedServer::instance()->getContext(), tankId, destinationId, playerName, color, "Random", "none"); // The model, this will be turned into a "proper" model in the player choice dialog tank->setUniqueId(sentUniqueId); tank->setIpAddress(ipAddress); tank->setHostDesc(sentHostDesc); tank->getState().setSpectator(true); tank->getState().setLoading(true); tank->getState().setInitializing(true); // Use the stats name if stats are enabled and the player has one std::list aliases = StatsLogger::instance()->getAliases(tank); if (!aliases.empty()) { std::string alias = aliases.front(); tank->setName(alias.c_str()); } // Add the tank to the list of tanks ScorchedServer::instance()->getTankContainer().addTank(tank); // Tell the clients to create this tank ComsAddPlayerMessage addPlayerMessage( tank->getPlayerId(), tank->getName(), tank->getColor(), tank->getModelContainer().getTankModelName(), tank->getModelContainer().getTankTypeName(), tank->getDestinationId(), tank->getTeam(), ""); ComsMessageSender::sendToAllConnectedClients(addPlayerMessage); // Tell this computer that a new tank has connected if (OptionsParam::instance()->getDedicatedServer()) { // Add to dialog Logger::log(formatString("Player connected dest=\"%i\" id=\"%i\" name=\"%s\" unique=[%s]", tank->getDestinationId(), tank->getPlayerId(), tank->getName(), tank->getUniqueId())); ServerCommon::sendString(0, formatString("Player connected \"%s\"", tank->getName())); } // Add this tank to stats StatsLogger::instance()->tankConnected(tank); // Check if admin muted if (ipAddress != 0) { ServerBanned::BannedType type = ScorchedServerUtil::instance()->bannedPlayers.getBanned(ipAddress, tank->getUniqueId()); if (type == ServerBanned::Muted) { tank->getState().setMuted(true); ServerCommon::sendStringAdmin(formatString("Player admin muted \"%s\"", tank->getName())); } else if (type == ServerBanned::Flagged) { ServerCommon::sendStringAdmin(formatString("Player admin flagged \"%s\"", tank->getName())); } } }