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 <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24 #include <webserver/ServerWebServer.h>
25 #include <webserver/ServerWebHandler.h>
26 #include <webserver/ServerWebSettingsHandler.h>
27 #include <webserver/ServerWebAppletHandler.h>
28 #include <server/ServerChannelManager.h>
29 #include <server/ServerCommon.h>
30 #include <webserver/ServerWebServerUtil.h>
31 #include <server/ScorchedServer.h>
32 #include <net/NetMessagePool.h>
33 #include <common/OptionsScorched.h>
34 #include <common/Logger.h>
35 #include <common/LoggerI.h>
36 #include <common/Defines.h>
37 
38 ServerWebServer *ServerWebServer::instance_ = 0;
39 
instance()40 ServerWebServer *ServerWebServer::instance()
41 {
42 	if (!instance_)
43 	{
44 		instance_ = new ServerWebServer();
45 	}
46 	return instance_;
47 }
48 
ServerWebServer()49 ServerWebServer::ServerWebServer() :
50 	netServer_(new NetServerHTTPProtocolRecv),
51 	logger_(0), asyncTimer_(0)
52 {
53 	sendThread_ = SDL_CreateThread(ServerWebServer::sendThreadFunc, 0);
54 	if (sendThread_ == 0)
55 	{
56 		Logger::log(S3D::formatStringBuffer("ServerWebServer: Failed to create thread"));
57 	}
58 
59 	addRequestHandler("/players", new ServerWebHandler::PlayerHandler());
60 	addThrededRequestHandler("/playersthreaded", new ServerWebHandler::PlayerHandlerThreaded());
61 	addRequestHandler("/logs", new ServerWebHandler::LogHandler());
62 	addRequestHandler("/logfile", new ServerWebHandler::LogFileHandler());
63 	addRequestHandler("/game", new ServerWebHandler::GameHandler());
64 	addRequestHandler("/server", new ServerWebHandler::ServerHandler());
65 	addRequestHandler("/talk", new ServerWebAppletHandler::AppletHtmlHandler());
66 	addRequestHandler("/banned", new ServerWebHandler::BannedHandler());
67 	addRequestHandler("/mods", new ServerWebHandler::ModsHandler());
68 	addRequestHandler("/sessions", new ServerWebHandler::SessionsHandler());
69 	addRequestHandler("/account", new ServerWebHandler::AccountHandler());
70 	addRequestHandler("/stats", new ServerWebHandler::StatsHandler());
71 	addRequestHandler("/settingsmain", new ServerWebSettingsHandler::SettingsMainHandler());
72 	addRequestHandler("/settingsall", new ServerWebSettingsHandler::SettingsAllHandler());
73 	addRequestHandler("/settingslandscape", new ServerWebSettingsHandler::SettingsLandscapeHandler());
74 	addRequestHandler("/settingsplayers", new ServerWebSettingsHandler::SettingsPlayersHandler());
75 	addRequestHandler("/settingsmod", new ServerWebSettingsHandler::SettingsModHandler());
76 	addRequestHandler("/Applet.jar", new ServerWebAppletHandler::AppletFileHandler());
77 	addAsyncRequestHandler("/appletstream", new ServerWebAppletHandler::AppletAsyncHandler());
78 	addRequestHandler("/action", new ServerWebAppletHandler::AppletActionHandler());
79 }
80 
~ServerWebServer()81 ServerWebServer::~ServerWebServer()
82 {
83 }
84 
sendThreadFunc(void *)85 int ServerWebServer::sendThreadFunc(void *)
86 {
87 	while (true)
88 	{
89 		SDL_Delay(100);
90 
91 		// Process the threaded queue
92 		instance_->processQueue(instance_->threadedQueue_, false);
93 	}
94 	return 1;
95 }
96 
start(int port)97 void ServerWebServer::start(int port)
98 {
99 	Logger::log(S3D::formatStringBuffer("Starting management web server on port %i", port));
100 	netServer_.setMessageHandler(this);
101 	netServer_.start(port);
102 
103 	if (0 != strcmp(ScorchedServer::instance()->getOptionsGame().
104 		getServerFileLogger(), "none"))
105 	{
106 		logger_ = new FileLogger(
107 			S3D::formatStringBuffer("ServerWeb-%i-",
108 				ScorchedServer::instance()->getOptionsGame().getPortNo()));
109 	}
110 }
111 
addRequestHandler(const char * url,ServerWebServerI * handler)112 void ServerWebServer::addRequestHandler(const char *url,
113 	ServerWebServerI *handler)
114 {
115 	HandlerEntry entry = { handler, 0 };
116 	handlers_[url] = entry;
117 }
118 
addThrededRequestHandler(const char * url,ServerWebServerI * handler)119 void ServerWebServer::addThrededRequestHandler(const char *url,
120 	ServerWebServerI *handler)
121 {
122 	HandlerEntry entry = { handler, HandlerEntry::eThreaded };
123 	handlers_[url] = entry;
124 }
125 
addAsyncRequestHandler(const char * url,ServerWebServerI * handler)126 void ServerWebServer::addAsyncRequestHandler(const char *url,
127 	ServerWebServerI *handler)
128 {
129 	HandlerEntry entry = { handler, HandlerEntry::eAsync };
130 	handlers_[url] = entry;
131 }
132 
processMessages()133 void ServerWebServer::processMessages()
134 {
135 	// Check if any delayed messages should be sent
136 	while (!delayedMessages_.empty())
137 	{
138 		unsigned int theTime = (unsigned int) time(0);
139 		std::pair<unsigned int, NetMessage *> &delayedMessage =
140 			delayedMessages_.front();
141 		if (delayedMessage.first <= theTime)
142 		{
143 			// Get the message
144 			NetMessage *message = delayedMessage.second;
145 			delayedMessages_.pop_front();
146 
147 			// Send this message now
148 			netServer_.sendMessageDest(message->getBuffer(), message->getDestinationId());
149 			NetMessagePool::instance()->addToPool(message);
150 		}
151 		else break;
152 	}
153 
154 	// Check if any non-delayed messages should be processed
155 	netServer_.processMessages();
156 
157 	// Check if anything needs to be done for the async processing
158 	unsigned int theTime = (unsigned int) time(0);
159 	if (theTime != asyncTimer_)
160 	{
161 		asyncTimer_ = theTime;
162 		processQueue(asyncQueue_, true);
163 	}
164 }
165 
processMessage(NetMessage & message)166 void ServerWebServer::processMessage(NetMessage &message)
167 {
168 	if (message.getMessageType() == NetMessage::BufferMessage)
169 	{
170 		// We have received a request for the web server
171 
172 		// Add a NULL to the end of the buffer so we can
173 		// do string searches on it and they dont run out of
174 		// the buffer.
175 		message.getBuffer().addToBuffer("");
176 		const char *buffer = message.getBuffer().getBuffer();
177 
178 		// Check it is a GET
179 		bool ok = false;
180 		bool get = (strstr(buffer, "GET ") == buffer);
181 		bool post = (strstr(buffer, "POST ") == buffer);
182 		if (get || post)
183 		{
184 			std::map<std::string, std::string> fields;
185 			std::map<std::string, NetMessage *> parts;
186 
187 			// Get POST query fields if any
188 			if (post)
189 			{
190 				// Find the end of the header
191 				char *headerend = (char *) strstr(buffer, "\r\n\r\n");
192 				if (headerend)
193 				{
194 					// Try to find the multipart POST information
195 					// in the header only
196 					// (So make the headerend a null to bound the search)
197 					headerend[0] = '\0';
198 					const char *findStr = "Content-Type: multipart/form-data; boundary=";
199 					const char *multipart = strstr(buffer, findStr);
200 					headerend[0] = '\r';
201 					if (multipart)
202 					{
203 						// We have a multipart message
204 						// Get the boundry type
205 						const char *boundryStart = multipart + strlen(findStr);
206 						char *boundrysep = (char *) strstr(boundryStart, "\r\n");
207 						if (boundrysep)
208 						{
209 							// Get the multi-part boundry
210 							boundrysep[0] = '\0';
211 							std::string boundry = boundryStart;
212 							boundrysep[0] = '\r';
213 
214 							// Extract the multi-part data from after the header
215 							headerend += 4; // Skip past /r/n/r/n
216 							int headersize = headerend - buffer;
217 							int sizeleft = message.getBuffer().getBufferUsed() - headersize;
218 
219 							ServerWebServerUtil::extractMultiPartPost(headerend, boundry.c_str(), sizeleft, parts);
220 						}
221 					}
222 					else
223 					{
224 						// Extract the query fields from after the header
225 						headerend += 4; // Skip past /r/n/r/n
226 						ServerWebServerUtil::extractQueryFields(fields, headerend);
227 					}
228 				}
229 			}
230 
231 			// Check it has a url
232 			const char *url = buffer + (get?4:5);
233 			char *eol = (char *) strchr(url, ' ');
234 			if (eol)
235 			{
236 				*eol = '\0';
237 				if (*url)
238 				{
239 					// Get GET query fields if any
240 					char *sep = (char *) strchr(url, '?');
241 					if (sep)
242 					{
243 						*sep = '\0'; sep++;
244 						ServerWebServerUtil::extractQueryFields(fields, sep);
245 					}
246 
247 					// Add ip address into fields
248 					fields["ipaddress"] =
249 						NetInterface::getIpName(message.getIpAddress());
250 
251 					// Log info
252 					if (logger_)
253 					{
254 						time_t t = time(0);
255 						std::string f;
256 						std::map<std::string, std::string>::iterator itor;
257 						for (itor = fields.begin();
258 							itor != fields.end();
259 							++itor)
260 						{
261 							if (0 != strcmp((*itor).first.c_str(), "password"))
262 							{
263 								f += S3D::formatStringBuffer("%s=%s ",
264 									(*itor).first.c_str(),
265 									(*itor).second.c_str());
266 							}
267 						}
268 
269 						std::string username;
270 						if (fields.find("sid") != fields.end())
271 						{
272 							unsigned int sid = (unsigned int) atoi(fields["sid"].c_str());
273 							ServerAdminSessions::SessionParams *session =
274 								ScorchedServer::instance()->getServerAdminSessions().getSession(sid);
275 							if (session)
276 							{
277 								username = session->credentials.username;
278 							}
279 						}
280 
281 						LoggerInfo info(
282 							S3D::formatStringBuffer("%u %s http://%s [%s]",
283 							message.getDestinationId(),
284 							username.c_str(), url, f.c_str()),
285 							ctime(&t));
286 						logger_->logMessage(info);
287 					}
288 
289 					// Process request
290 					const char *ipaddress = NetInterface::getIpName(message.getIpAddress());
291 					ok = processRequest(message.getDestinationId(), ipaddress, url, fields, parts);
292 				}
293 			}
294 
295 			// Add any message parts back to the pool
296 			std::map<std::string, NetMessage *>::iterator partitor;
297 			for (partitor = parts.begin();
298 				partitor != parts.end();
299 				++partitor)
300 			{
301 				NetMessage *newMessage = (*partitor).second;
302 				NetMessagePool::instance()->addToPool(newMessage);
303 			}
304 		}
305 
306 		if (!ok)
307 		{
308 			// Disconnect the client
309 			netServer_.disconnectClient(message.getDestinationId());
310 		}
311 	}
312 	else if (message.getMessageType() == NetMessage::SentMessage)
313 	{
314 		// Check if this is a sync or async destination
315 		if (asyncQueue_.hasEntry(message.getDestinationId()))
316 		{
317 			// Its an async destination do nothing
318 		}
319 		else
320 		{
321 			// Its a sync destination
322 			// Once a sync message has been fully sent close the connection
323 			netServer_.disconnectClient(message.getDestinationId());
324 		}
325 	}
326 	else if (message.getMessageType() == NetMessage::DisconnectMessage)
327 	{
328 		// Remove any async processes we may be processing for this
329 		// destination
330 		asyncQueue_.removeEntry(message.getDestinationId());
331 	}
332 }
333 
processRequest(unsigned int destinationId,const char * ip,const char * url,std::map<std::string,std::string> & fields,std::map<std::string,NetMessage * > & parts)334 bool ServerWebServer::processRequest(
335 	unsigned int destinationId,
336 	const char *ip,
337 	const char *url,
338 	std::map<std::string, std::string> &fields,
339 	std::map<std::string, NetMessage *> &parts)
340 {
341 	bool delayed = false; // Set delayed on authentication failure
342 	std::string text;
343 	if (0 == strcmp(url, "/"))
344 	{
345 		// We have requested the login page
346 		// Have the login credentials been supplied
347 		if (validateUser(ip, url, fields, delayed))
348 		{
349 			// Yes, and credentials are correct
350 			// Show the starting (players) page
351 			ServerWebServerUtil::getHtmlRedirect(
352 				S3D::formatStringBuffer("/players?sid=%s", fields["sid"].c_str()), text);
353 		}
354 		else
355 		{
356 			// No, or credentials are not correct
357 			// Show the login page after a delay
358 			if (!ServerWebServerUtil::getHtmlTemplate(
359 				0, "login.html", fields, text)) return false;
360 		}
361 	}
362 	else
363 	{
364 		// A "normal" page has been requested
365 		// Check the session is valid
366 		unsigned int sid = validateSession(ip, url, fields);
367 		if (sid)
368 		{
369 			// The session is valid, show the page
370 			std::map<std::string, HandlerEntry>::iterator itor =
371 				handlers_.find(url);
372 			if (itor == handlers_.end())
373 			{
374 				ServerWebServerUtil::getHtmlNotFound(text);
375 			}
376 			else
377 			{
378 				ServerWebServerI *handler = itor->second.handler->createCopy();
379 				ServerWebServerQueueEntry *entry = new ServerWebServerQueueEntry(
380 						destinationId, sid, url, handler, fields, parts);
381 
382 				if (itor->second.flags == HandlerEntry::eAsync)
383 				{
384 					asyncQueue_.addEntry(entry);
385 				}
386 				else if (itor->second.flags == HandlerEntry::eThreaded)
387 				{
388 					threadedQueue_.addEntry(entry);
389 				}
390 				else
391 				{
392 					normalQueue_.addEntry(entry);
393 					if (!processQueue(normalQueue_, false)) return false;
394 				}
395 				return true;
396 			}
397 		}
398 		else
399 		{
400 			if (handlers_.find(url) == handlers_.end())
401 			{
402 				// The session is invalid,
403 				// but the page does not exist show the 404 page.
404 				// This is for cases where the browser asks for stupid files
405 				// from the webserver that must fail for the browser to continue.
406 				ServerWebServerUtil::getHtmlNotFound(text);
407 			}
408 			else
409 			{
410 				// The session is invalid show the login page after a delay
411 				ServerWebServerUtil::getHtmlRedirect("/", text);
412 				delayed = true;
413 			}
414 		}
415 	}
416 
417 	// Check the text is not empty
418 	if (text.empty()) return false;
419 
420 	// Generate the message to send
421 	NetMessage *message = NetMessagePool::instance()->getFromPool(
422 		NetMessage::BufferMessage, destinationId, 0, 0);
423 	message->getBuffer().addDataToBuffer(text.data(), text.size()); // No null
424 	if (delayed)
425 	{
426 		// Generate an outgoing message, that will be sent after a time delay
427 		unsigned int delayedTime = (unsigned int) time(0) + 5;
428 		std::pair<unsigned int, NetMessage *> delayedMessage(delayedTime, message);
429 		delayedMessages_.push_back(delayedMessage);
430 	}
431 	else
432 	{
433 		// Send this message now
434 		netServer_.sendMessageDest(message->getBuffer(), message->getDestinationId());
435 		NetMessagePool::instance()->addToPool(message);
436 	}
437 
438 	return true;
439 }
440 
validateSession(const char * ip,const char * url,std::map<std::string,std::string> & fields)441 unsigned int ServerWebServer::validateSession(
442 	const char *ip,
443 	const char *url,
444 	std::map<std::string, std::string> &fields)
445 {
446 	// Hack for silly java 6.0 applets
447 	if (strcmp(url, "/Applet.jar") == 0)
448 	{
449 		ServerAdminSessions::SessionParams *session =
450 			ScorchedServer::instance()->getServerAdminSessions().getFirstSession();
451 		if (session)
452 		{
453 			return session->sid;
454 		}
455 	}
456 
457 	// Check this sid is valid
458 	if (fields.find("sid") != fields.end())
459 	{
460 		unsigned int sid = (unsigned int) atoi(fields["sid"].c_str());
461 		ServerAdminSessions::SessionParams *params =
462 			ScorchedServer::instance()->getServerAdminSessions().getSession(sid);
463 		if (params) return sid;
464 	}
465 
466 	return 0;
467 }
468 
validateUser(const char * ip,const char * url,std::map<std::string,std::string> & fields,bool & delayed)469 bool ServerWebServer::validateUser(
470 	const char *ip,
471 	const char *url,
472 	std::map<std::string, std::string> &fields,
473 	bool &delayed)
474 {
475 	// Create a session for the user
476 	unsigned int sid = ScorchedServer::instance()->getServerAdminSessions().login(
477 		fields["name"].c_str(),
478 		fields["password"].c_str(),
479 		ip);
480 	if (sid != 0)
481 	{
482 		// Set the sid for use in the html templates
483 		fields["sid"] = S3D::formatStringBuffer("%u", sid);
484 
485 		ServerAdminSessions::SessionParams *adminSession =
486 			ScorchedServer::instance()->getServerAdminSessions().getSession(sid);
487 
488 		ScorchedServer::instance()->getServerChannelManager().sendText(
489 			ChannelText("info",
490 				"ADMIN_WEB_LOGIN",
491 				"server admin \"{0}\" logged in",
492 				adminSession->credentials.username.c_str()),
493 			true);
494 
495 		return true;
496 	}
497 	else
498 	{
499 		if (fields["name"] != "" && fields["password"] != "")
500 		{
501 			Logger::log(S3D::formatStringBuffer("Failed login for server admin \"%s\", via web, ip \"%s\"",
502 				fields["name"].c_str(), ip));
503 			delayed = true;
504 		}
505 	}
506 
507 	return false;
508 }
509 
processQueue(ServerWebServerQueue & queue,bool keepEntries)510 bool ServerWebServer::processQueue(ServerWebServerQueue &queue, bool keepEntries)
511 {
512 	bool result = true;
513 	std::list<ServerWebServerQueueEntry *> keptEntries;
514 
515 	// Process queue
516 	ServerWebServerQueueEntry *entry = 0;
517 	while ((entry = queue.getEntry()) != 0)
518 	{
519 		bool keepEntry = keepEntries;
520 
521 		// Get the session for the user
522 		ServerAdminSessions::SessionParams *session =
523 			ScorchedServer::instance()->getServerAdminSessions().getSession(
524 			entry->getSid());
525 		entry->getRequest().setSession(session);
526 
527 		// Call handler
528 		std::string resultText;
529 		if (session &&
530 			entry->getHandler()->processRequest(
531 			entry->getRequest(), resultText))
532 		{
533 			if (!resultText.empty())
534 			{
535 				// It has generated some text
536 				// Generate the message to send
537 				NetMessage *message = NetMessagePool::instance()->getFromPool(
538 					NetMessage::BufferMessage, entry->getDestinationId(), 0, 0);
539 				message->getBuffer().addDataToBuffer(resultText.data(), resultText.size()); // No null
540 
541 				// Send this message now
542 				netServer_.sendMessageDest(message->getBuffer(), message->getDestinationId());
543 				NetMessagePool::instance()->addToPool(message);
544 
545 				// Update the session time so we don't timeout
546 				ScorchedServer::instance()->getServerAdminSessions().getSession(entry->getSid());
547 			}
548 			else
549 			{
550 				result = false;
551 			}
552 		}
553 		else
554 		{
555 			keepEntry = false;
556 			result = false;
557 		}
558 
559 		// Tidy queue entry
560 		if (keepEntry) keptEntries.push_back(entry);
561 		else delete entry;
562 	}
563 
564 	// Keep entries
565 	while (!keptEntries.empty())
566 	{
567 		entry = keptEntries.front();
568 		keptEntries.pop_front();
569 		queue.addEntry(entry);
570 	}
571 
572 	return result;
573 }
574