1 // Copyright (C) 2011, 2014, 2015, 2020 Ben Asselstine
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 // 02110-1301, USA.
17
18 #include <iostream>
19 #include <fstream>
20 #include <sstream>
21 #include <list>
22
23 #include "gamehost-server.h"
24
25 #include "network-server.h"
26 #include "xmlhelper.h"
27 #include "Configuration.h"
28 #include "ucompose.hpp"
29 #include "gamelist.h"
30 #include "recently-played-game-list.h"
31 #include "recently-played-game.h"
32 #include "hosted-game.h"
33 #include "gamelist-client.h"
34 #include "advertised-game.h"
35 #include "GameScenario.h"
36 #include "profile.h"
37 #include "File.h"
38
39 #define debug(x) {std::cerr<<__FILE__<<": "<<__LINE__<<": "<<x<<std::endl<<std::flush;}
40 //#define debug(x)
41
42 struct HostGameRequest
43 {
44 Glib::TimeVal created_on;
45 Profile *profile;
46 Glib::ustring scenario_id;
47 };
48
49
50 GamehostServer * GamehostServer::s_instance = 0;
51
getInstance()52 GamehostServer* GamehostServer::getInstance()
53 {
54 if (s_instance == 0)
55 s_instance = new GamehostServer();
56
57 return s_instance;
58 }
59
deleteInstance()60 void GamehostServer::deleteInstance()
61 {
62 if (s_instance)
63 delete s_instance;
64
65 s_instance = 0;
66 }
67
GamehostServer()68 GamehostServer::GamehostServer()
69 {
70 Timing::instance().timer_registered.connect
71 (sigc::mem_fun(*this, &GamehostServer::on_timer_registered));
72 Gamelist::getInstance()->load();
73 Gamelist::getInstance()->pruneGames();
74 Gamelist::getInstance()->pingGames();
75 }
76
~GamehostServer()77 GamehostServer::~GamehostServer()
78 {
79 if (network_server.get() != NULL)
80 {
81 if (network_server->isListening())
82 network_server->stop();
83 }
84 }
85
reload()86 void GamehostServer::reload()
87 {
88 Gamelist::getInstance()->load();
89 }
90
isListening()91 bool GamehostServer::isListening()
92 {
93 if (network_server.get() != NULL)
94 return network_server->isListening();
95 else
96 return false;
97 }
98
start(int port)99 void GamehostServer::start(int port)
100 {
101 if (network_server.get() != NULL && network_server->isListening())
102 return;
103 network_server.reset(new NetworkServer());
104 network_server->port_in_use.connect
105 (sigc::mem_fun(port_in_use, &sigc::signal<void, int>::emit));
106 network_server->got_message.connect
107 (sigc::mem_fun(this, &GamehostServer::onGotMessage));
108 network_server->connection_lost.connect
109 (sigc::hide(sigc::mem_fun(this, &GamehostServer::onConnectionLost)));
110 network_server->connection_made.connect
111 (sigc::hide(sigc::mem_fun(this, &GamehostServer::onConnectionMade)));
112
113 network_server->startListening(port);
114
115 }
116
sendList(void * conn)117 void GamehostServer::sendList(void *conn)
118 {
119 std::ostringstream os;
120 XML_Helper helper(&os);
121 RecentlyPlayedGameList *l;
122 if (network_server->is_local_connection(conn))
123 l = Gamelist::getInstance()->getList(false);
124 else
125 l = Gamelist::getInstance()->getList(true);
126
127 l->save(&helper);
128 network_server->send(conn, GHS_MESSAGE_GAME_LIST, os.str());
129 delete l;
130 }
131
unhost(void * conn,Glib::ustring profile_id,Glib::ustring scenario_id,Glib::ustring & err)132 void GamehostServer::unhost(void *conn, Glib::ustring profile_id, Glib::ustring scenario_id, Glib::ustring &err)
133 {
134 HostedGame *g = Gamelist::getInstance()->findGameByScenarioId(scenario_id);
135 if (!g)
136 {
137 err = _("no such game with that scenario id");
138 return;
139 }
140 if (g->getAdvertisedGame()->getProfileId() != profile_id &&
141 network_server->is_local_connection(conn) == false)
142 {
143 err = _("permission denied");
144 return;
145 }
146 if (kill (g->getPid(), SIGQUIT) != 0)
147 {
148 err = _("could not kill process");
149 return;
150 }
151 Glib::spawn_close_pid(g->getPid());
152 Gamelist::getInstance()->remove(g);
153
154 //now we unadvertise it.
155 GamelistClient *gsc = GamelistClient ::getInstance();
156 gsc->client_connected.connect
157 (sigc::bind(sigc::mem_fun(*this, &GamehostServer::on_connected_to_gamelist_server_for_advertising_removal), g->getAdvertisedGame()->getId()));
158 gsc->start(Configuration::s_gamelist_server_hostname,
159 Configuration::s_gamelist_server_port, g->getAdvertisedGame()->getProfile());
160 delete g;
161 return;
162 }
163
on_connected_to_gamelist_server_for_advertising_removal(Glib::ustring scenario_id)164 void GamehostServer::on_connected_to_gamelist_server_for_advertising_removal(Glib::ustring scenario_id)
165 {
166 GamelistClient *gsc = GamelistClient::getInstance();
167 gsc->received_advertising_removal_response.connect
168 (sigc::hide(sigc::hide(sigc::mem_fun(*this, &GamehostServer::on_advertising_removal_response_received))));
169 gsc->request_advertising_removal(scenario_id);
170 }
171
on_advertising_removal_response_received()172 void GamehostServer::on_advertising_removal_response_received()
173 {
174 GamelistClient::deleteInstance();
175 return;
176 }
177
run_game(GameScenario * game_scenario,Glib::Pid * child_pid,guint32 port,Glib::ustring & err)178 void GamehostServer::run_game(GameScenario *game_scenario, Glib::Pid *child_pid, guint32 port, Glib::ustring &err)
179 {
180 Glib::ustring lordsawar = Glib::find_program_in_path(PACKAGE);
181 if (lordsawar == "")
182 {
183 err = _("couldn't find lordsawar binary in path!");
184 return;
185 }
186
187 Glib::ustring tmpfile = File::get_tmp_file();
188 tmpfile += SAVE_EXT;
189 game_scenario->saveGame(tmpfile);
190
191 std::list<Glib::ustring> argv;
192 argv.push_back(lordsawar);
193 argv.push_back(tmpfile);
194 argv.push_back("--host");
195 argv.push_back("--port");
196 argv.push_back(String::ucompose("%1", port));
197
198 //run lordsawar <file> --host --port <port>
199 Glib::spawn_async (File::getCacheDir (), argv,
200 Glib::SPAWN_STDOUT_TO_DEV_NULL |
201 Glib::SPAWN_STDERR_TO_DEV_NULL,
202 sigc::mem_fun(*this, &GamehostServer::on_child_setup),
203 child_pid);
204 }
205
waitForGameToBeConnectable(guint32 port)206 bool GamehostServer::waitForGameToBeConnectable(guint32 port)
207 {
208 Glib::RefPtr<Gio::SocketClient>client = Gio::SocketClient::create();
209 while (1)
210 {
211 Glib::RefPtr<Gio::SocketConnection> sock;
212 try
213 {
214 sock = client->connect_to_host ("127.0.0.1", port);
215 }
216 catch (Glib::Exception &ex)
217 {
218 ;
219 }
220 if (sock)
221 {
222 sock.reset();
223 break;
224 }
225 Glib::usleep(1000000);
226 }
227 client.reset();
228 return true;
229 }
230
host(GameScenario * game_scenario,Profile * profile,Glib::ustring & err)231 HostedGame * GamehostServer::host(GameScenario *game_scenario, Profile *profile, Glib::ustring &err)
232 {
233 guint32 port = get_free_port();
234 Glib::Pid child_pid;
235 run_game(game_scenario, &child_pid, port, err);
236 if (err != "")
237 return NULL;
238
239 //now we wait to see if everything is okay.
240
241 bool success = waitForGameToBeConnectable(port);
242 if (!success)
243 {
244 err = _("Game couldn't be setup properly.");
245 kill (child_pid, SIGQUIT);
246 Glib::spawn_close_pid(child_pid);
247 return NULL;
248 }
249
250 //now we add an entry to the gamelist.
251 HostedGame *g = new HostedGame(new AdvertisedGame(game_scenario, profile));
252 g->setPid((guint32) child_pid);
253 g->getAdvertisedGame()->fillData(getHostname(), port);
254 if (Gamelist::getInstance()->add(g) == false)
255 {
256 err = _("could not add game to list.");
257 kill (g->getPid(), SIGQUIT);
258 Glib::spawn_close_pid(g->getPid());
259 delete g;
260 return NULL;
261 }
262
263 //now we advertise it.
264 GamelistClient *gsc = GamelistClient ::getInstance();
265 gsc->client_connected.connect
266 (sigc::bind(sigc::mem_fun(*this, &GamehostServer::on_connected_to_gamelist_server_for_advertising), g));
267 gsc->start(Configuration::s_gamelist_server_hostname,
268 Configuration::s_gamelist_server_port, profile);
269 return g;
270 }
271
get_free_port()272 guint32 GamehostServer::get_free_port()
273 {
274 Glib::RefPtr<Gio::SocketListener> l = Gio::SocketListener::create();
275 guint32 port = l->add_any_inet_port();
276 l.reset();
277 //gosh i hope this gets unbound before we run our program.
278 return port;
279 }
280
on_child_setup()281 void GamehostServer::on_child_setup()
282 {
283 return;
284 }
285
on_connected_to_gamelist_server_for_advertising(HostedGame * game)286 void GamehostServer::on_connected_to_gamelist_server_for_advertising(HostedGame *game)
287 {
288 GamelistClient *gsc = GamelistClient::getInstance();
289 //okay, fashion the recently played game to go over the wire.
290 RecentlyPlayedNetworkedGame *g =
291 new RecentlyPlayedNetworkedGame(*game->getAdvertisedGame());
292 gsc->received_advertising_response.connect
293 (sigc::hide(sigc::hide(sigc::mem_fun(*this, &GamehostServer::on_advertising_response_received))));
294 gsc->request_advertising(g);
295 }
296
on_advertising_response_received()297 void GamehostServer::on_advertising_response_received()
298 {
299 GamelistClient::deleteInstance();
300 return;
301 }
302
get_profile_and_scenario_id(Glib::ustring payload,Profile ** profile,Glib::ustring & scenario_id,Glib::ustring & err)303 void GamehostServer::get_profile_and_scenario_id(Glib::ustring payload, Profile **profile, Glib::ustring &scenario_id, Glib::ustring &err)
304 {
305 bool broken = false;
306 Glib::ustring match = "</" + Profile::d_tag + ">";
307 size_t pos = payload.find(match);
308 if (pos == Glib::ustring::npos)
309 {
310 err = _("malformed host new game message");
311 return;
312 }
313 std::istringstream is(payload.substr(0, pos + match.length()));
314 //get the profile that wants to host a game
315 XML_Helper helper(&is);
316 helper.registerTag
317 (Profile::d_tag, sigc::bind(sigc::mem_fun(this,
318 &GamehostServer::loadProfile),
319 profile));
320 if (!helper.parseXML())
321 broken = true;
322 helper.close();
323 if (broken)
324 {
325 err = _("Could not parse profile information.");
326 return;
327 }
328
329 //now get the scenario id that tags on the end.
330 scenario_id = String::utrim(payload.substr(pos + match.length()));
331 }
332
loadProfile(Glib::ustring tag,XML_Helper * helper,Profile ** profile)333 bool GamehostServer::loadProfile(Glib::ustring tag, XML_Helper *helper, Profile **profile)
334 {
335 if (tag == Profile::d_tag)
336 {
337 *profile = new Profile(helper);
338 return true;
339 }
340 return false;
341 }
342
343
onGotMessage(void * conn,int type,Glib::ustring payload)344 bool GamehostServer::onGotMessage(void *conn, int type, Glib::ustring payload)
345 {
346 debug("got message of type " << type);
347 switch (GhsMessageType(type))
348 {
349 case GHS_MESSAGE_HOST_NEW_GAME:
350 {
351 Glib::ustring err = "";
352 Profile *profile = NULL; //take it from payload
353 Glib::ustring scenario_id;
354 get_profile_and_scenario_id(payload, &profile, scenario_id, err);
355 if (err != "")
356 {
357 network_server->send(conn, GHS_MESSAGE_COULD_NOT_HOST_GAME,
358 "{???} " + err);
359 return true;
360 }
361 if (is_member(profile->getId()) == false &&
362 network_server->is_local_connection(conn) == false)
363 {
364 delete profile;
365 err = _("Not authorized to host on this server.");
366 network_server->send(conn, GHS_MESSAGE_COULD_NOT_HOST_GAME,
367 scenario_id + " " + err);
368 return true;
369 }
370 if (add_to_profiles_awaiting_maps(profile, scenario_id) == false)
371 {
372 delete profile;
373 err = _("Server too busy. Try again later.");
374 network_server->send(conn, GHS_MESSAGE_COULD_NOT_HOST_GAME,
375 scenario_id + " " + err);
376 return true;
377 }
378
379 network_server->send(conn, GHS_MESSAGE_AWAITING_MAP, scenario_id);
380
381 }
382 break;
383 case GHS_MESSAGE_SENDING_MAP:
384 {
385 Glib::ustring err = "";
386 Glib::ustring tmpfile = File::get_tmp_file();
387 std::ofstream f(tmpfile.c_str());
388 f << payload;
389 f.close();
390 bool broken = false;
391 GameScenario *game_scenario = new GameScenario(tmpfile, broken);
392 File::erase(tmpfile);
393 if (broken)
394 {
395 err = _("Could not read map file.");
396 network_server->send(conn, GHS_MESSAGE_COULD_NOT_READ_MAP,
397 "{???} " + err);
398 return true;
399 }
400 game_scenario->setPlayMode(GameScenario::NETWORKED);
401 //go get associated profile.
402 Profile *profile =
403 remove_from_profiles_awaiting_maps(game_scenario->getId());
404 if (!profile)
405 {
406 err = _("protocol error.");
407 network_server->send(conn, GHS_MESSAGE_COULD_NOT_START_GAME,
408 game_scenario->getId()+ " " + err);
409 return true;
410 }
411
412 HostedGame *g = host(game_scenario, profile, err);
413 if (err != "")
414 network_server->send(conn, GHS_MESSAGE_COULD_NOT_START_GAME,
415 game_scenario->getId()+ " " + err);
416 else
417 network_server->send
418 (conn, GHS_MESSAGE_GAME_HOSTED,
419 String::ucompose("%1 %2", game_scenario->getId(),
420 g->getAdvertisedGame()->getPort()));
421
422 Gamelist::getInstance()->save();
423 delete game_scenario;
424 delete profile;
425 }
426 break;
427 case GHS_MESSAGE_UNHOST_GAME:
428 {
429 size_t pos;
430 Glib::ustring err;
431 pos = payload.find(' ');
432 if (pos == Glib::ustring::npos)
433 return false;
434 unhost(conn, payload.substr(0, pos), payload.substr(pos + 1), err);
435 if (err != "")
436 network_server->send(conn, GHS_MESSAGE_COULD_NOT_UNHOST_GAME,
437 payload.substr(pos + 1) + " " + err);
438 else
439 network_server->send(conn, GHS_MESSAGE_GAME_UNHOSTED,
440 payload.substr(pos + 1));
441 Gamelist::getInstance()->save();
442 }
443 break;
444 case GHS_MESSAGE_REQUEST_GAME_LIST:
445 sendList(conn);
446 break;
447 case GHS_MESSAGE_REQUEST_RELOAD:
448 if (network_server->is_local_connection(conn))
449 {
450 Gamelist::getInstance()->load();
451 network_server->send(conn, GHS_MESSAGE_RELOADED, "");
452 }
453 else
454 network_server->send(conn, GHS_MESSAGE_COULD_NOT_RELOAD,
455 _("permission denied"));
456 break;
457 case GHS_MESSAGE_REQUEST_TERMINATION:
458 if (network_server->is_local_connection(conn))
459 {
460 terminate_request_received.emit();
461 Gtk::Main::quit();
462 }
463 break;
464 case GHS_MESSAGE_GAME_LIST:
465 case GHS_MESSAGE_COULD_NOT_RELOAD:
466 case GHS_MESSAGE_RELOADED:
467 case GHS_MESSAGE_AWAITING_MAP:
468 case GHS_MESSAGE_GAME_UNHOSTED:
469 case GHS_MESSAGE_COULD_NOT_HOST_GAME:
470 case GHS_MESSAGE_COULD_NOT_UNHOST_GAME:
471 case GHS_MESSAGE_COULD_NOT_GET_GAME_LIST:
472 case GHS_MESSAGE_GAME_HOSTED:
473 case GHS_MESSAGE_COULD_NOT_READ_MAP:
474 case GHS_MESSAGE_COULD_NOT_START_GAME:
475 break;
476 //faulty client
477 break;
478 }
479 return true;
480 }
481
onConnectionMade()482 void GamehostServer::onConnectionMade()
483 {
484 debug("connection made");
485 Gamelist::getInstance()->pruneGames();
486 Gamelist::getInstance()->pingGames();
487 cleanup_old_profiles_awaiting_maps();
488 }
489
onConnectionLost()490 void GamehostServer::onConnectionLost()
491 {
492 debug("connection lost");
493 }
494
on_timer_registered(Timing::timer_slot s,int msecs_interval)495 sigc::connection GamehostServer::on_timer_registered(Timing::timer_slot s,
496 int msecs_interval)
497 {
498 return Glib::signal_timeout().connect(s, msecs_interval);
499 }
500
add_to_profiles_awaiting_maps(Profile * profile,Glib::ustring scenario_id)501 bool GamehostServer::add_to_profiles_awaiting_maps(Profile *profile, Glib::ustring scenario_id)
502 {
503 if (host_game_requests.size() > (guint32) TOO_MANY_PROFILES_AWAITING_MAPS &&
504 TOO_MANY_PROFILES_AWAITING_MAPS != -1)
505 return false;
506 HostGameRequest* request = new HostGameRequest();
507 request->profile = profile;
508 request->scenario_id = scenario_id;
509 Glib::TimeVal now;
510 now.assign_current_time();
511 request->created_on = now;
512 host_game_requests.push_back(request);
513 return true;
514 }
515
cleanup_old_profiles_awaiting_maps(int stale)516 void GamehostServer::cleanup_old_profiles_awaiting_maps(int stale)
517 {
518 Glib::TimeVal now;
519 now.assign_current_time();
520 for (std::list<HostGameRequest*>::iterator i = host_game_requests.begin();
521 i != host_game_requests.end(); i++)
522 {
523 if ((*i)->created_on.as_double() + stale < now.as_double())
524 {
525 delete (*i)->profile;
526 delete (*i);
527 i = host_game_requests.erase(i);
528 }
529 }
530 }
531
remove_from_profiles_awaiting_maps(Glib::ustring scenario_id)532 Profile *GamehostServer::remove_from_profiles_awaiting_maps(Glib::ustring scenario_id)
533 {
534 for (std::list<HostGameRequest*>::iterator i = host_game_requests.begin();
535 i != host_game_requests.end(); i++)
536 {
537 if ((*i)->scenario_id == scenario_id)
538 {
539 Profile *profile = (*i)->profile;
540 delete (*i);
541 host_game_requests.erase(i);
542 return profile;
543 }
544 }
545 return NULL;
546 }
547
is_member(Glib::ustring profile_id)548 bool GamehostServer::is_member(Glib::ustring profile_id)
549 {
550 if (members.empty())
551 return true;
552 Glib::ustring id = String::utrim(profile_id);
553 for (std::list<Glib::ustring>::iterator i = members.begin(); i != members.end();
554 i++)
555 {
556 if (id == *i)
557 return true;
558 }
559 return false;
560 }
561
load_members_from_file(Glib::ustring file)562 std::list<Glib::ustring> GamehostServer::load_members_from_file(Glib::ustring file)
563 {
564 char buffer[1024];
565 std::list<Glib::ustring> members;
566 std::ifstream f(file.c_str());
567 if (f.is_open() == false)
568 return members;
569 while (!f.eof())
570 {
571 f.getline(buffer, sizeof(buffer));
572 Glib::ustring line = buffer;
573 size_t pos = line.find('#');
574 Glib::ustring trimmed_line;
575 if (pos == Glib::ustring::npos)
576 trimmed_line =String::utrim(line);
577 else
578 trimmed_line = String::utrim(line.substr(pos));
579 if (trimmed_line != "")
580 members.push_back(trimmed_line);
581 }
582 f.close();
583 return members;
584 }
585 // End of file
586