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