1 ////////////////////////////////////////////////////////////////////////////////
2 //    Scorched3D (c) 2000-2011
3 //
4 //    This file is part of Scorched3D.
5 //
6 //    Scorched3D is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    Scorched3D is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License along
17 //    with this program; if not, write to the Free Software Foundation, Inc.,
18 //    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 ////////////////////////////////////////////////////////////////////////////////
20 
21 #include <server/ServerConnectAuthHandler.h>
22 #include <server/ServerConnectHandler.h>
23 #include <server/ServerChannelManager.h>
24 #include <server/ServerBanned.h>
25 #include <server/ScorchedServer.h>
26 #include <server/ServerCommon.h>
27 #include <server/ServerDestinations.h>
28 #include <server/ServerSimulator.h>
29 #include <server/ServerAuthHandler.h>
30 #include <server/ServerState.h>
31 #include <engine/SaveGame.h>
32 #include <tank/TankModelStore.h>
33 #include <tank/TankColorGenerator.h>
34 #include <target/TargetContainer.h>
35 #include <tank/TankDeadContainer.h>
36 #include <tank/TankAvatar.h>
37 #include <tank/TankState.h>
38 #include <tank/TankScore.h>
39 #include <tankai/TankAIAdder.h>
40 #include <tankai/TankAIStrings.h>
41 #include <tankai/TankAIStore.h>
42 #include <common/Defines.h>
43 #include <common/FileLines.h>
44 #include <common/OptionsScorched.h>
45 #include <common/StatsLogger.h>
46 #include <common/Logger.h>
47 #include <net/NetInterface.h>
48 #include <simactions/TankAddSimAction.h>
49 #include <coms/ComsConnectAcceptMessage.h>
50 #include <coms/ComsConnectMessage.h>
51 #include <coms/ComsMessageSender.h>
52 #ifndef S3D_SERVER
53 #include <client/ClientParams.h>
54 #endif
55 
ServerConnectAuthHandler(ComsMessageHandler & comsMessageHandler)56 ServerConnectAuthHandler::ServerConnectAuthHandler(ComsMessageHandler &comsMessageHandler)
57 {
58 	comsMessageHandler.addHandler(
59 		ComsConnectAuthMessage::ComsConnectAuthMessageType,
60 		this);
61 }
62 
~ServerConnectAuthHandler()63 ServerConnectAuthHandler::~ServerConnectAuthHandler()
64 {
65 	std::list<AuthMessage *>::iterator itor;
66 	for (itor = authMessages_.begin();
67 		itor != authMessages_.end();
68 		++itor)
69 	{
70 		delete *itor;
71 	}
72 	authMessages_.clear();
73 }
74 
processMessage(NetMessage & netMessage,const char * messageType,NetBufferReader & reader)75 bool ServerConnectAuthHandler::processMessage(
76 	NetMessage &netMessage,
77 	const char *messageType, NetBufferReader &reader)
78 {
79 	// Decode the auth connect message
80 	AuthMessage *authMessage = new AuthMessage();
81 	authMessage->destinationId = netMessage.getDestinationId();
82 	authMessage->ipAddress = netMessage.getIpAddress();
83 	if (!authMessage->message.readMessage(reader))
84 	{
85 		ServerCommon::kickDestination(netMessage.getDestinationId(),
86 			"Invalid auth message format");
87 		delete authMessage;
88 		return true;
89 	}
90 
91 	// Record the auth message till later
92 	authMessages_.push_back(authMessage);
93 	return true;
94 }
95 
processMessages()96 void ServerConnectAuthHandler::processMessages()
97 {
98 	// Only process the new auth requests once there are no more outstanding
99 	// tank addition requests
100 	// Just makes some of the checks easier
101 	while (!authMessages_.empty())
102 	{
103 		// Check that there are no outstanding tank addition requests
104 		if (TankAddSimAction::TankAddSimActionCount > 0) break;
105 
106 		// Otherwise process the message
107 		AuthMessage *message = authMessages_.back();
108 		processMessageInternal(message->destinationId, message->ipAddress, message->message);
109 		authMessages_.pop_back();
110 		delete message;
111 	}
112 
113 	while (!aiAdditions_.empty())
114 	{
115 		// Check that there are no outstanding tank addition requests
116 		if (TankAddSimAction::TankAddSimActionCount > 0) break;
117 
118 		processAIInternal(aiAdditions_);
119 		aiAdditions_.clear();
120 	}
121 }
122 
processMessageInternal(unsigned int destinationId,unsigned int ipAddress,ComsConnectAuthMessage & message)123 void ServerConnectAuthHandler::processMessageInternal(
124 	unsigned int destinationId,
125 	unsigned int ipAddress,
126 	ComsConnectAuthMessage &message)
127 {
128 	// Check for acceptance bassed on standard checks
129 	if (!ServerConnectHandler::checkStandardParams(destinationId, ipAddress)) return;
130 
131 	// Check that this message has come from a destination that is still connected
132 	// This may happen if the client disconnects very early on in the initial setup
133 	ServerDestination *serverDestination =
134 		ScorchedServer::instance()->getServerDestinations().getDestination(destinationId);
135 	if (!serverDestination)
136 	{
137 		return;
138 	}
139 
140 	// Check player availability
141 	if (message.getNoPlayers() >
142 		(unsigned int)
143 		(ScorchedServer::instance()->getOptionsGame().getNoMaxPlayers() -
144 		ScorchedServer::instance()->getTargetContainer().getNoOfTanks()))
145 	{
146 		std::string kickMessage =
147 			S3D::formatStringBuffer(
148 			"--------------------------------------------------\n"
149 			"This server is full, you cannot join!\n"
150 			"--------------------------------------------------");
151 		ServerCommon::serverLog("Server full, kicking");
152 		ServerCommon::kickDestination(destinationId, kickMessage);
153 		return;
154 	}
155 
156 	// Auth handler, make sure that only prefered players can connect
157 	// DO FIRST AS THE HANDLER MAY ALTER THE MESSAGE
158 	ServerAuthHandler *authHandler =
159 		ScorchedServer::instance()->getAuthHandler();
160 	if (authHandler)
161 	{
162 		std::string resultMessage;
163 		if (!authHandler->authenticateUser(message, resultMessage))
164 		{
165 			std::string kickMessage =
166 				S3D::formatStringBuffer(
167 				"--------------------------------------------------\n"
168 				"%s"
169 				"Connection failed.\n"
170 				"--------------------------------------------------",
171 				resultMessage.c_str());
172 			ServerCommon::kickDestination(destinationId, kickMessage);
173 			return;
174 		}
175 	}
176 
177 	std::string uniqueId = message.getUniqueId();
178 	if (!uniqueId.c_str()[0]) // No ID
179 	{
180 		// Generate the players unique id (if we need to)
181 		//
182 		// This will only actualy generate an id when using a persistent
183 		// stats logger as this is the only time we can be sure of not
184 		// giving out duplicate ids.
185 		//
186 		uniqueId = StatsLogger::instance()->allocateId();
187 	}
188 	std::string SUid = message.getSUI(); //request for SUI
189 
190 	// Get compat ver
191 	unsigned int compatVer = message.getCompatabilityVer();
192 	// TODO send back proper version message
193 
194 	// Check if this unique id has been banned
195 	// Do after auth handler as this may update uniqueid
196 	ServerBanned::BannedType type =
197 		ScorchedServer::instance()->getBannedPlayers().getBanned(uniqueId.c_str(), SUid.c_str());
198 	if (type == ServerBanned::Banned)
199 	{
200 		ServerCommon::kickDestination(destinationId,
201 			"Banned uniqueid/suid connection from destination");
202 		Logger::log(S3D::formatStringBuffer("UniqueId: %s, Suid: %s",
203 			uniqueId.c_str(), SUid.c_str()));
204 		return;
205 	}
206 
207 	// Check that this unique id has not already connected (if unique ids are in use)
208 	if (!ScorchedServer::instance()->getOptionsGame().getAllowSameUniqueId())
209 	{
210 		std::map<unsigned int, Tank *> &playingTanks =
211 			ScorchedServer::instance()->getTargetContainer().getTanks();
212 		std::map<unsigned int, Tank *>::iterator playingItor;
213 		for (playingItor = playingTanks.begin();
214 			playingItor != playingTanks.end();
215 			++playingItor)
216 		{
217 			Tank *current = (*playingItor).second;
218 			if (current->getDestinationId() != 0)
219 			{
220 				if (uniqueId.c_str()[0])
221 				{
222 					if (0 == strcmp(current->getUniqueId(), uniqueId.c_str()))
223 					{
224 						ServerCommon::kickDestination(destinationId,
225 							"Duplicate uniqueid connection from destination");
226 						Logger::log(S3D::formatStringBuffer("UniqueId: %s, Suid: %s",
227 							uniqueId.c_str(), SUid.c_str()));
228 						return;
229 					}
230 				}
231 				if (SUid.c_str()[0])
232 				{
233 					if (0 == strcmp(current->getSUI(), SUid.c_str()))
234 					{
235 						ServerCommon::kickDestination(destinationId,
236 							"Duplicate SUI connection from destination");
237 						Logger::log(S3D::formatStringBuffer("UniqueId: %s, Suid: %s",
238 							uniqueId.c_str(), SUid.c_str()));
239 						return;
240 					}
241 				}
242 			}
243 		}
244 	}
245 
246 	// Send the connection accepted message to the client
247 	ComsConnectAcceptMessage acceptMessage(
248 		destinationId,
249 		ScorchedServer::instance()->getOptionsGame().getServerName(),
250 		ScorchedServer::instance()->getOptionsGame().getPublishAddress(),
251 		uniqueId.c_str());
252 	if (!ComsMessageSender::sendToSingleClient(acceptMessage, destinationId))
253 	{
254 		ServerCommon::kickDestination(destinationId,
255 			"Failed to send accept to client");
256 		return;
257 	}
258 
259 	const char *savedGame = "";
260 #ifndef S3D_SERVER
261 	savedGame = ClientParams::instance()->getSaveFile();
262 #endif
263 
264 	if (savedGame[0])
265 	{
266 		SaveGame::loadTargets(savedGame);
267 		ScorchedServer::instance()->getServerState().setState(ServerState::ServerNewLevelState);
268 	}
269 	else
270 	{
271 		// Add all the new tanks
272 		for (unsigned int i=0; i<message.getNoPlayers(); i++)
273 		{
274 			addNextTank(destinationId,
275 				ipAddress,
276 				uniqueId.c_str(),
277 				SUid.c_str(),
278 				message.getHostDesc(),
279 				false);
280 		}
281 
282 		// For the single player game
283 		// Add a spectator that will always remain a spectator
284 		// this is so if we only have computer players we still
285 		// send messages to them
286 	#ifndef S3D_SERVER
287 		{
288 			addNextTank(destinationId,
289 				ipAddress,
290 				uniqueId.c_str(),
291 				SUid.c_str(),
292 				message.getHostDesc(),
293 				true);
294 		}
295 	#endif
296 	}
297 }
298 
addNextTank(unsigned int destinationId,unsigned int ipAddress,const char * sentUniqueId,const char * sentSUI,const char * sentHostDesc,bool extraSpectator)299 void ServerConnectAuthHandler::addNextTank(unsigned int destinationId,
300 	unsigned int ipAddress,
301 	const char *sentUniqueId,
302 	const char *sentSUI,
303 	const char *sentHostDesc,
304 	bool extraSpectator)
305 {
306 	// The player has connected
307 	unsigned int tankId = 0;
308 	LangString playerName;
309 	if (extraSpectator)
310 	{
311 		tankId = TargetID::SPEC_TANK_ID;
312 		playerName = LANG_STRING("Spectator");
313 	}
314 	else
315 	{
316 		playerName = LANG_STRING(ScorchedServer::instance()->getTankAIStrings().getPlayerName());
317 		std::set<unsigned int> takenPlayerIds;
318 		tankId = TankAIAdder::getNextTankId(
319 			sentUniqueId,
320 			ScorchedServer::instance()->getContext(),
321 			takenPlayerIds);
322 	}
323 
324 	// Make sure host desc does not contain \"
325 	for (char *h=(char *)sentHostDesc; *h; h++) if (*h == '\"') *h=' ';
326 
327 	// Use the stats name if stats are enabled and the player has one
328 	std::list<std::string> aliases  =
329 		StatsLogger::instance()->getAliases(sentUniqueId);
330 	if (!aliases.empty())
331 	{
332 		LangString alias = LANG_STRING(aliases.front());
333 		playerName = alias;
334 	}
335 
336 	// Add this tank
337 	TankAddSimAction *simAction = new TankAddSimAction(tankId, destinationId,
338 		sentUniqueId, sentSUI, sentHostDesc, ipAddress, playerName, "");
339 	if (sentUniqueId[0])
340 	{
341 		ScorchedServer::instance()->getTankDeadContainer().getDeadTank(
342 			simAction, sentUniqueId);
343 	}
344 	else if (sentSUI[0])
345 	{
346 		ScorchedServer::instance()->getTankDeadContainer().getDeadTank(
347 			simAction, sentSUI);
348 	}
349 
350 	ScorchedServer::instance()->getServerSimulator().addSimulatorAction(simAction);
351 }
352 
processAIInternal(std::list<std::string> & aiAdditions)353 void ServerConnectAuthHandler::processAIInternal(std::list<std::string> &aiAdditions)
354 {
355 	std::set<std::string> takenUniqueIds;
356 	std::set<unsigned int> takenPlayerIds;
357 	std::list<std::string>::iterator itor;
358 	for (itor = aiAdditions.begin(); itor != aiAdditions.end(); ++itor)
359 	{
360 		std::string &aiName = *itor;
361 		TankAI *ai = ScorchedServer::instance()->getTankAIs().getAIByName(aiName.c_str());
362 		if (!ai)
363 		{
364 			Logger::log(S3D::formatStringBuffer("Failed to find a tank ai called \"%s\"",
365 				aiName.c_str()));
366 			return;
367 		}
368 
369 		// Tank Name
370 		LangString newname =
371 			LANG_STRING(ScorchedServer::instance()->getOptionsGame().getBotNamePrefix());
372 		newname += LANG_STRING(ScorchedServer::instance()->getTankAIStrings().getAIPlayerName(
373 			ScorchedServer::instance()->getContext()));
374 
375 		// Unique Id
376 		char uniqueId[256];
377 		for (int i=1;;i++)
378 		{
379 			snprintf(uniqueId, 256, "%s - computer - %i", aiName.c_str(), i);
380 			if (takenUniqueIds.find(uniqueId) == takenUniqueIds.end() && !uniqueIdTaken(uniqueId)) break;
381 		}
382 		takenUniqueIds.insert(uniqueId);
383 		unsigned int playerId = TankAIAdder::getNextTankId(
384 			uniqueId, ScorchedServer::instance()->getContext(), takenPlayerIds);
385 		takenPlayerIds.insert(playerId);
386 
387 		// Add this new tank
388 		TankAddSimAction *simAction = new TankAddSimAction(playerId, 0,
389 			uniqueId, "", "AI", 0, newname, aiName);
390 		ScorchedServer::instance()->getServerSimulator().addSimulatorAction(simAction);
391 	}
392 }
393 
uniqueIdTaken(const std::string & uniqueId)394 bool ServerConnectAuthHandler::uniqueIdTaken(const std::string &uniqueId)
395 {
396 	std::map<unsigned int, Tank *> &playingTanks =
397 		ScorchedServer::instance()->getTargetContainer().getTanks();
398 	std::map<unsigned int, Tank *>::iterator playingItor;
399 	for (playingItor = playingTanks.begin();
400 		playingItor != playingTanks.end();
401 		++playingItor)
402 	{
403 		Tank *current = (*playingItor).second;
404 		if (0 == strcmp(current->getUniqueId(), uniqueId.c_str()))
405 		{
406 			return true;
407 		}
408 	}
409 	return false;
410 }