00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 #include "config.hpp"
00016 #include "filesystem.hpp"
00017 #include "log.hpp"
00018 #include "network.hpp"
00019 #include "publish_campaign.hpp"
00020 #include "util.hpp"
00021 #include "serialization/binary_wml.hpp"
00022 #include "serialization/parser.hpp"
00023 #include "game_config.hpp"
00024
00025 #include "SDL.h"
00026
00027 #include <iostream>
00028 #include <map>
00029 #include <algorithm>
00030
00031
00032
00033 #if !(defined(_WIN32))
00034 #include <errno.h>
00035 #include <stdio.h>
00036 #include <unistd.h>
00037 #include <sys/wait.h>
00038 #include <sys/types.h>
00039 #endif
00040
00041 #define LOG_CS lg::err(lg::network, false)
00042
00043 namespace {
00044
00045 config construct_message(const std::string& msg)
00046 {
00047 config cfg;
00048 cfg.add_child("message")["message"] = msg;
00049 return cfg;
00050 }
00051
00052 config construct_error(const std::string& msg)
00053 {
00054 config cfg;
00055 cfg.add_child("error")["message"] = msg;
00056 LOG_CS << "ERROR: "<<msg<<"\n";
00057 return cfg;
00058 }
00059
00060 class campaign_server
00061 {
00062 public:
00063 explicit campaign_server(const std::string& cfgfile,size_t min_thread = 10,size_t max_thread = 0);
00064 void run();
00065 private:
00066
00067
00068
00069
00070 void fire(const std::string& hook, const std::string& addon);
00071 int load_config();
00072 const config& campaigns() const { return *cfg_.child("campaigns"); }
00073 config& campaigns() { return *cfg_.child("campaigns"); }
00074 config cfg_;
00075 const std::string file_;
00076 const network::manager net_manager_;
00077 const network::server_manager server_manager_;
00078 std::map<std::string, std::string> hooks_;
00079
00080 };
00081
00082 void campaign_server::fire(const std::string& hook, const std::string& addon)
00083 {
00084 const std::map<std::string, std::string>::const_iterator itor = hooks_.find(hook);
00085 if(itor == hooks_.end()) return;
00086
00087 const std::string& script = itor->second;
00088 if(script == "") return;
00089
00090 #if (defined(_WIN32))
00091 LOG_CS << "ERROR: Tried to execute a script on a not supporting platform\n";
00092 return;
00093 #else
00094 pid_t childpid;
00095
00096 if((childpid = fork()) == -1) {
00097 LOG_CS << "ERROR fork failed while updating campaign " << addon << "\n";
00098 return;
00099 }
00100
00101 if(childpid == 0) {
00102
00103
00104
00105
00106 execlp(script.c_str(), script.c_str(), addon.c_str(), (char *)NULL);
00107
00108
00109 std::cerr << "ERROR exec failed for addon " << addon
00110 << " with errno = " << errno << "\n";
00111 exit(errno);
00112
00113 } else {
00114 return;
00115 }
00116
00117 #endif
00118 }
00119
00120 int campaign_server::load_config()
00121 {
00122 scoped_istream stream = istream_file(file_);
00123 read(cfg_, *stream);
00124 return lexical_cast_default<int>(cfg_["port"], 15003);
00125 }
00126
00127 campaign_server::campaign_server(const std::string& cfgfile,size_t min_thread,size_t max_thread)
00128 : file_(cfgfile), net_manager_(min_thread,max_thread), server_manager_(load_config())
00129 {
00130 if(cfg_.child("campaigns") == NULL) {
00131 cfg_.add_child("campaigns");
00132 }
00133
00134
00135 hooks_.insert(std::make_pair(std::string("hook_post_upload"), cfg_["hook_post_upload"]));
00136 hooks_.insert(std::make_pair(std::string("hook_post_erase"), cfg_["hook_post_erase"]));
00137 }
00138
00139 void find_translations(const config& cfg, config& campaign)
00140 {
00141 const config::child_list& dirs = cfg.get_children("dir");
00142 for(config::child_list::const_iterator i = dirs.begin(); i != dirs.end(); ++i) {
00143 if((const t_string)(**i)["name"] == "LC_MESSAGES") {
00144 config* language = &campaign.add_child("translation");
00145 (*language)["language"] = cfg["name"];
00146 }
00147 else {
00148 find_translations(**i, campaign);
00149 }
00150 }
00151 }
00152
00153
00154
00155
00156
00157
00158
00159 std::string check_python_scripts(config &data, std::string filename)
00160 {
00161 std::vector<config *> python_scripts = find_scripts(data, ".py");
00162 if (!python_scripts.empty()) {
00163
00164 config old_campaign;
00165 scoped_istream stream = istream_file(filename);
00166 read_compressed(old_campaign, *stream);
00167 std::vector<config *> old_scripts = find_scripts(old_campaign, ".py");
00168 std::string script_names = "";
00169 std::vector<config *>::iterator i, j;
00170
00171 for (i = python_scripts.begin(); i != python_scripts.end(); ++i) {
00172 bool already = false;
00173
00174 for (j = old_scripts.begin(); j != old_scripts.end(); ++j) {
00175 if ((**i)["contents"] != (**j)["contents"]) continue;
00176 already = true;
00177 break;
00178 }
00179 if (!already) {
00180 script_names += "\n" + (**i)["name"];
00181 (**i)["name"] += ".unchecked";
00182 }
00183 }
00184 if (script_names != "")
00185 return "\nScripts awaiting approval:\n" + script_names;
00186 }
00187 return "";
00188 }
00189
00190
00191
00192
00193
00194 std::string validate_all_python_scripts(config &data)
00195 {
00196 std::vector<config *> python_scripts = find_scripts(data, ".py.unchecked");
00197 if (!python_scripts.empty()) {
00198
00199 std::string script_names = "";
00200 std::vector<config *>::iterator i;
00201
00202 for (i = python_scripts.begin(); i != python_scripts.end(); ++i) {
00203 std::string name = (**i)["name"];
00204 name.resize(name.length() - 10);
00205 (**i)["name"] = name;
00206 script_names += "\n" + name;
00207 }
00208 return script_names;
00209 }
00210 return "";
00211 }
00212
00213
00214 void add_license(config &data)
00215 {
00216 config *dir = data.find_child("dir", "name", data["campaign_name"]);
00217
00218 if (!dir) return;
00219
00220
00221 if (dir->find_child("file", "name", "COPYING.txt")) return;
00222 if (dir->find_child("file", "name", "COPYING")) return;
00223 if (dir->find_child("file", "name", "copying.txt")) return;
00224 if (dir->find_child("file", "name", "Copying.txt")) return;
00225 if (dir->find_child("file", "name", "COPYING.TXT")) return;
00226
00227
00228 std::string contents = read_file("data/COPYING.txt");
00229 config ©ing = dir->add_child("file");
00230 copying["name"] = "COPYING.txt";
00231 copying["contents"] = contents;
00232
00233 if (contents.empty()) {
00234 std::cerr << "Could not find " << "data/COPYING.txt" <<
00235 ", path is \"" << game_config::path << "\"\n";
00236 }
00237 }
00238
00239 void campaign_server::run()
00240 {
00241 for(int increment = 0; ; ++increment) {
00242 try {
00243
00244 if((increment%(60*10*2)) == 0) {
00245 scoped_ostream cfgfile = ostream_file(file_);
00246 write(*cfgfile, cfg_);
00247 }
00248
00249 network::process_send_queue();
00250
00251 network::connection sock = network::accept_connection();
00252 if(sock) {
00253 LOG_CS << "received connection from " << network::ip_address(sock) << "\n";
00254 }
00255
00256 config data;
00257 while((sock = network::receive_data(data)) != network::null_connection) {
00258 if(const config* req = data.child("request_campaign_list")) {
00259 LOG_CS << "sending campaign list to " << network::ip_address(sock) << "\n";
00260 time_t epoch = time(NULL);
00261 config campaign_list;
00262 (campaign_list)["timestamp"] = lexical_cast<std::string>(epoch);
00263 if((const t_string)(*req)["times_relative_to"] != "now") {
00264 epoch = 0;
00265 }
00266 int before_flag = 0;
00267 time_t before = epoch;
00268 if((const t_string)(*req)["before"] != "") {
00269 try {
00270 before = before + lexical_cast<time_t>((*req)["before"]);
00271 before_flag = 1;
00272 }
00273 catch(bad_lexical_cast) {
00274 }
00275 }
00276 int after_flag = 0;
00277 time_t after = epoch;
00278 if((const t_string)(*req)["after"] != "") {
00279 try {
00280 after = after + lexical_cast<time_t>((*req)["after"]);
00281 after_flag = 1;
00282 }
00283 catch(bad_lexical_cast) {
00284 }
00285 }
00286 config::child_list cmps = campaigns().get_children("campaign");
00287 for(config::child_list::iterator i = cmps.begin(); i != cmps.end(); ++i) {
00288 if((const t_string)(*req)["name"] != "" && (*req)["name"] != (**i)["name"]) continue;
00289 if(before_flag && ((const t_string)(**i)["timestamp"] == "" || lexical_cast_default<time_t>((**i)["timestamp"],0) >= before)) continue;
00290 if(after_flag && ((const t_string)(**i)["timestamp"] == "" || lexical_cast_default<time_t>((**i)["timestamp"],0) <= after)) continue;
00291 int found = 1;
00292 if((const t_string)(*req)["language"] != "") {
00293 found = 0;
00294 config::child_list translation = (**i).get_children("translation");
00295 for(config::child_list::iterator j = translation.begin(); j != translation.end(); ++j) {
00296 if((*req)["language"] == (**j)["language"]) {
00297 found = 1;
00298 break;
00299 }
00300 }
00301 }
00302 if(found == 0) continue;
00303 campaign_list.add_child("campaign", (**i));
00304 }
00305 cmps = campaign_list.get_children("campaign");
00306 for(config::child_list::iterator j = cmps.begin(); j != cmps.end(); ++j) {
00307 (**j)["passphrase"] = "";
00308 (**j)["upload_ip"] = "";
00309 }
00310
00311 config response;
00312 response.add_child("campaigns",campaign_list);
00313
00314 network::send_data(response, sock, true);
00315 } else if(const config* req = data.child("request_campaign")) {
00316 LOG_CS << "sending campaign '" << (*req)["name"] << "' to " << network::ip_address(sock) << "\n";
00317 config* const campaign = campaigns().find_child("campaign","name",(*req)["name"]);
00318 if(campaign == NULL) {
00319
00320 network::send_data(construct_error("Add-on '" + (*req)["name"] + "'not found."), sock, true);
00321 } else {
00322 config cfg;
00323 scoped_istream stream = istream_file((*campaign)["filename"]);
00324 read_compressed(cfg, *stream);
00325 add_license(cfg);
00326
00327 network::send_data(cfg, sock, true);
00328
00329 const int downloads = lexical_cast_default<int>((*campaign)["downloads"],0)+1;
00330 (*campaign)["downloads"] = lexical_cast<std::string>(downloads);
00331 }
00332
00333 } else if(data.child("request_terms") != NULL) {
00334 LOG_CS << "sending terms " << network::ip_address(sock) << "\n";
00335
00336 network::send_data(construct_message("All add-ons uploaded to this server must be licensed under the terms of the GNU General Public License (GPL). By uploading content to this server, you certify that you have the right to place the content under the conditions of the GPL, and choose to do so."), sock, true);
00337 LOG_CS << " Done\n";
00338 } else if(config* upload = data.child("upload")) {
00339 LOG_CS << "uploading campaign '" << (*upload)["name"] << "' from " << network::ip_address(sock) << ".\n";
00340 config* data = upload->child("data");
00341 config* campaign = campaigns().find_child("campaign","name",(*upload)["name"]);
00342 if(data == NULL) {
00343
00344 LOG_CS << "Upload aborted no data.\n";
00345 network::send_data(construct_error("No add-on data was supplied."), sock, true);
00346 } else if(campaign_name_legal((*upload)["name"]) == false) {
00347
00348 LOG_CS << "Upload aborted invalid name.\n";
00349 network::send_data(construct_error("The name of the add-on is invalid"), sock, true);
00350 } else if(check_names_legal(*data) == false) {
00351
00352 LOG_CS << "Upload aborted invalid file name.\n";
00353 network::send_data(construct_error("The add-on contains an illegal file or directory name."), sock, true);
00354 } else if(campaign != NULL && (*campaign)["passphrase"] != (*upload)["passphrase"]) {
00355
00356
00357
00358 LOG_CS << "Upload is admin upload.\n";
00359 if(campaigns()["master_password"] != ""
00360 && campaigns()["master_password"] == (*upload)["passphrase"]) {
00361
00362 std::string message = "Add-on accepted.";
00363
00364 std::string filename = (*campaign)["filename"];
00365 (*data)["title"] = (*campaign)["title"];
00366 (*data)["name"] = "";
00367 (*data)["campaign_name"] = (*campaign)["name"];
00368 (*data)["author"] = (*campaign)["author"];
00369 (*data)["description"] = (*campaign)["description"];
00370 (*data)["version"] = (*campaign)["version"];
00371 (*data)["timestamp"] = (*campaign)["timestamp"];
00372 (*data)["icon"] = (*campaign)["icon"];
00373 (*data)["translate"] = (*campaign)["translate"];
00374 (*campaign).clear_children("translation");
00375 find_translations(*data, *campaign);
00376
00377 scoped_ostream campaign_file = ostream_file(filename);
00378 write_compressed(*campaign_file, *data);
00379
00380 (*campaign)["size"] = lexical_cast<std::string>(
00381 file_size(filename));
00382 scoped_ostream cfgfile = ostream_file(file_);
00383 write(*cfgfile, cfg_);
00384
00385 network::send_data(construct_message(message), sock, true);
00386
00387 } else {
00388
00389 LOG_CS << "Upload aborted invalid passphrase.\n";
00390 network::send_data(construct_error("The add-on already exists, and your passphrase was incorrect."), sock, true);
00391 }
00392 } else {
00393 LOG_CS << "Upload is owner upload.\n";
00394 std::string message = "Add-on accepted.";
00395 if(campaign == NULL) {
00396 campaign = &campaigns().add_child("campaign");
00397 }
00398
00399 (*campaign)["title"] = (*upload)["title"];
00400 (*campaign)["name"] = (*upload)["name"];
00401 (*campaign)["filename"] = (*upload)["name"];
00402 (*campaign)["passphrase"] = (*upload)["passphrase"];
00403 (*campaign)["author"] = (*upload)["author"];
00404 (*campaign)["description"] = (*upload)["description"];
00405 (*campaign)["version"] = (*upload)["version"];
00406 (*campaign)["icon"] = (*upload)["icon"];
00407 (*campaign)["translate"] = (*upload)["translate"];
00408 (*campaign)["dependencies"] = (*upload)["dependencies"];
00409 (*campaign)["upload_ip"] = network::ip_address(sock);
00410
00411 if((*campaign)["downloads"].empty()) {
00412 (*campaign)["downloads"] = "0";
00413 }
00414 (*campaign)["timestamp"] = lexical_cast<std::string>(time(NULL));
00415
00416 const int uploads = lexical_cast_default<int>((*campaign)["uploads"],0) + 1;
00417 (*campaign)["uploads"] = lexical_cast<std::string>(uploads);
00418
00419 std::string filename = (*campaign)["filename"];
00420 (*data)["title"] = (*campaign)["title"];
00421 (*data)["name"] = "";
00422 (*data)["campaign_name"] = (*campaign)["name"];
00423 (*data)["author"] = (*campaign)["author"];
00424 (*data)["description"] = (*campaign)["description"];
00425 (*data)["version"] = (*campaign)["version"];
00426 (*data)["timestamp"] = (*campaign)["timestamp"];
00427 (*data)["icon"] = (*campaign)["icon"];
00428 (*campaign).clear_children("translation");
00429 find_translations(*data, *campaign);
00430
00431
00432 if ((*campaign)["python"] != "allowed") {
00433 message += check_python_scripts(*data, filename);
00434 }
00435 scoped_ostream campaign_file = ostream_file(filename);
00436 write_compressed(*campaign_file, *data);
00437
00438 (*campaign)["size"] = lexical_cast<std::string>(
00439 file_size(filename));
00440 scoped_ostream cfgfile = ostream_file(file_);
00441 write(*cfgfile, cfg_);
00442
00443 network::send_data(construct_message(message), sock, true);
00444
00445 fire("hook_post_upload", (*upload)["name"]);
00446 }
00447 } else if(const config* erase = data.child("delete")) {
00448 LOG_CS << "deleting campaign '" << (*erase)["name"] << "' requested from " << network::ip_address(sock) << "\n";
00449 config* const campaign = campaigns().find_child("campaign","name",(*erase)["name"]);
00450 if(campaign == NULL) {
00451
00452 network::send_data(construct_error("The add-on does not exist."), sock, true);
00453 continue;
00454 }
00455
00456 if((*campaign)["passphrase"] != (*erase)["passphrase"]
00457 && (campaigns()["master_password"] == ""
00458 || campaigns()["master_password"] != (*erase)["passphrase"])) {
00459
00460
00461 network::send_data(construct_error("The passphrase is incorrect."), sock, true);
00462 continue;
00463 }
00464
00465
00466 write_file((*campaign)["filename"],"");
00467 remove((*campaign)["filename"].c_str());
00468
00469 const config::child_list& campaigns_list = campaigns().get_children("campaign");
00470 const size_t index = std::find(campaigns_list.begin(),campaigns_list.end(),campaign) - campaigns_list.begin();
00471 campaigns().remove_child("campaign",index);
00472 scoped_ostream cfgfile = ostream_file(file_);
00473 write(*cfgfile, cfg_);
00474
00475 network::send_data(construct_message("Add-on deleted."), sock, true);
00476
00477 fire("hook_post_erase", (*erase)["name"]);
00478
00479 } else if(const config* cpass = data.child("change_passphrase")) {
00480 config* campaign = campaigns().find_child("campaign","name",(*cpass)["name"]);
00481 if(campaign == NULL) {
00482
00483 network::send_data(construct_error("No add-on with that name exists."), sock, true);
00484 } else if((*campaign)["passphrase"] != (*cpass)["passphrase"]) {
00485
00486 network::send_data(construct_error("Your old passphrase was incorrect."), sock, true);
00487 } else if((const t_string)(*cpass)["new_passphrase"] == "") {
00488
00489 network::send_data(construct_error("No new passphrase was supplied."), sock, true);
00490 } else {
00491 (*campaign)["passphrase"] = (*cpass)["new_passphrase"];
00492 scoped_ostream cfgfile = ostream_file(file_);
00493 write(*cfgfile, cfg_);
00494
00495 network::send_data(construct_message("Passphrase changed."), sock, true);
00496 }
00497 } else if(const config* cvalidate = data.child("validate_scripts")) {
00498 config* campaign = campaigns().find_child("campaign","name",(*cvalidate)["name"]);
00499 if(campaign == NULL) {
00500
00501 network::send_data(construct_error(
00502 "No add-on with that name exists."), sock, true);
00503 } else if(campaigns()["master_password"] == "") {
00504
00505 network::send_data(construct_error(
00506 "Sever does not allow scripts."), sock, true);
00507 } else if (campaigns()["master_password"] != (*cvalidate)["master_password"]) {
00508
00509 network::send_data(construct_error(
00510 "Password was incorrect."), sock, true);
00511 } else {
00512
00513 config campaign_file;
00514 scoped_istream stream = istream_file((*campaign)["filename"]);
00515 read_compressed(campaign_file, *stream);
00516 std::string scripts = validate_all_python_scripts(campaign_file);
00517 if (!scripts.empty()) {
00518
00519 scoped_ostream ostream = ostream_file((*campaign)["filename"]);
00520 write_compressed(*ostream, campaign_file);
00521
00522
00523 network::send_data(construct_message("The following scripts have been validated: " +
00524 scripts), sock, true);
00525 } else {
00526
00527 network::send_data(construct_message("No unchecked scripts found!"), sock, true);
00528 }
00529 }
00530 }
00531 }
00532 } catch(network::error& e) {
00533 if(!e.socket) {
00534 LOG_CS << "fatal network error\n";
00535 break;
00536 } else {
00537 LOG_CS <<"client disconnect : "<<e.message<<" " << network::ip_address(e.socket) << "\n";
00538 e.disconnect();
00539 }
00540 } catch(config::error& ) {
00541 LOG_CS << "error in receiving data...\n";
00542 }
00543
00544 SDL_Delay(20);
00545 }
00546 }
00547
00548 }
00549
00550 int main(int argc, char**argv)
00551 {
00552 lg::timestamps(true);
00553 try {
00554 printf("argc %d argv[0] %s 1 %s\n",argc,argv[0],argv[1]);
00555 if(argc >= 2 && atoi(argv[1])){
00556 campaign_server("server.cfg",atoi(argv[1])).run();
00557 }else {
00558 campaign_server("server.cfg").run();
00559 }
00560 } catch(config::error& ) {
00561 std::cerr << "Could not parse config file\n";
00562 return 1;
00563 } catch(io_exception& ) {
00564 std::cerr << "File I/O error\n";
00565 return 2;
00566 } catch(network::error& e) {
00567 std::cerr << "Aborted with network error: " << e.message << '\n';
00568 return 3;
00569 }
00570 return 0;
00571 }