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 }