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