diff --git a/CMakeLists.txt b/CMakeLists.txt index db538b66..dd96b076 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,9 @@ endif() if (NOT DEFINED NOUPDATE ) add_definitions(-DUPDATER=1) endif() +if (NOT DEFINED PERPETUAL ) + add_definitions(-DLICENSING=1) +endif() if (DEFINED NOAUTH ) add_definitions(-DNOAUTH=1) endif() @@ -498,6 +501,7 @@ set(controllerHeaders ${SOURCE_DIR}/src/controller/controller_capabilities.h ${SOURCE_DIR}/src/controller/controller_streams.h ${SOURCE_DIR}/src/controller/controller_push.h + ${SOURCE_DIR}/src/controller/controller_license.h ) ######################################## @@ -515,6 +519,7 @@ set(controllerSources ${SOURCE_DIR}/src/controller/controller_uplink.cpp ${SOURCE_DIR}/src/controller/controller_api.cpp ${SOURCE_DIR}/src/controller/controller_push.cpp + ${SOURCE_DIR}/src/controller/controller_license.cpp ) ######################################## # MistController - Build # diff --git a/src/controller/controller.cpp b/src/controller/controller.cpp index 1f1c5569..1cd8072b 100644 --- a/src/controller/controller.cpp +++ b/src/controller/controller.cpp @@ -55,6 +55,7 @@ #include "controller_updater.h" #include "controller_limits.h" #include "controller_uplink.h" +#include "controller_license.h" /*LTS-END*/ #include "controller_api.h" #include "controller_push.h" @@ -307,15 +308,28 @@ int main_loop(int argc, char ** argv){ Controller::Log("CONF", "Controller started"); Controller::conf.activate();//activate early, so threads aren't killed. - + //Generate instanceId once per boot. + if (Controller::instanceId == ""){ + srand(time(NULL)); + do{ + Controller::instanceId += (char)(64 + rand() % 62); + }while(Controller::instanceId.size() < 16); + } + /*LTS-START*/ #ifdef UPDATER if (Controller::conf.getBool("update")){ Controller::CheckUpdates(); } #endif + #ifdef LICENSING + Controller::initLicense(); + Controller::checkLicense(); + //start license checking thread + tthread::thread licenseThread(Controller::licenseLoop, 0); + #endif /*LTS-END*/ - + //start stats thread tthread::thread statsThread(Controller::SharedMemStats, &Controller::conf); //start monitoring thread @@ -324,6 +338,7 @@ int main_loop(int argc, char ** argv){ tthread::thread uplinkThread(Controller::uplinkConnection, 0);/*LTS*/ //start push checking thread tthread::thread pushThread(Controller::pushCheckLoop, 0); + //start main loop while (Controller::conf.is_active){/*LTS*/ @@ -339,6 +354,11 @@ int main_loop(int argc, char ** argv){ shutdown_reason = "restart (on request)"; } /*LTS-START*/ + #ifdef LICENSING + if (!Controller::isLicensed()){ + shutdown_reason = "no valid license"; + } + #endif if(Triggers::shouldTrigger("SYSTEM_STOP")){ if (!Triggers::doTrigger("SYSTEM_STOP", shutdown_reason)){ Controller::conf.is_active = true; @@ -359,8 +379,13 @@ int main_loop(int argc, char ** argv){ //join all joinable threads statsThread.join(); monitorThread.join(); - uplinkThread.join();/*LTS*/ - pushThread.join();/*LTS*/ + /*LTS-START*/ + uplinkThread.join(); + pushThread.join(); + #ifdef LICENSING + licenseThread.join(); + #endif + /*LTS-END*/ //write config tthread::lock_guard guard(Controller::logMutex); Controller::Storage.removeMember("log"); diff --git a/src/controller/controller_api.cpp b/src/controller/controller_api.cpp index 46b2ac50..9a4d2069 100644 --- a/src/controller/controller_api.cpp +++ b/src/controller/controller_api.cpp @@ -15,6 +15,7 @@ #include "controller_updater.h" #include "controller_limits.h" #include "controller_push.h" +#include "controller_license.h" /*LTS-END*/ ///\brief Check the submitted configuration and handle things accordingly. @@ -512,6 +513,11 @@ int Controller::handleAPIConnection(Socket::Connection & conn){ if (!Request.isMember("minimal") || Response.isMember("config")){ Response["config"] = Controller::Storage["config"]; Response["config"]["version"] = PACKAGE_VERSION; + /*LTS-START*/ + #ifdef LICENSING + Response["config"]["license"] = getLicense(); + #endif + /*LTS-END*/ //add required data to the current unix time to the config, for syncing reasons Response["config"]["time"] = Util::epoch(); if ( !Response["config"].isMember("serverid")){ diff --git a/src/controller/controller_license.cpp b/src/controller/controller_license.cpp new file mode 100644 index 00000000..a0d724ac --- /dev/null +++ b/src/controller/controller_license.cpp @@ -0,0 +1,189 @@ +#include "controller_license.h" +#include "controller_storage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Controller{ + + static JSON::Value currentLicense; + + const JSON::Value & getLicense(){ + return currentLicense; + } + + //PACKAGE_VERSION = MistServer version + //RELEASE = OS + user_ID + + void initLicense(){ + if (Storage.isMember("license") && Storage.isMember("license_id")){ + INFO_MSG("Reading license from storage") + readLicense(Storage["license_id"].asInt(), Storage["license"].asStringRef()); + } + } + bool isLicensed(){ + uint64_t now = Util::epoch(); +#if DEBUG >= DLVL_DEVEL + INFO_MSG("Verifying license against %llu:", now); + std::cout << currentLicense.toPrettyString() << std::endl; +#endif + //The loop below is timechecker loop + if (!currentLicense.isMember("valid_from") || !currentLicense.isMember("valid_till")){ +#if DEBUG >= DLVL_DEVEL + INFO_MSG("Accepting license despite lack of date range, because early build"); +#endif + return true;//temporary + } + if (now < currentLicense["valid_from"].asInt() || now > currentLicense["valid_till"].asInt()){ + return false;//license is expired + } + if (RELEASE != currentLicense["release"].asStringRef() || PACKAGE_VERSION != currentLicense["version"].asStringRef()){ + FAIL_MSG("Could not verify license"); + return false; + } + //everything seems okay + return true; + } + + bool checkLicense(){ + updateLicense(); + if (!currentLicense.isMember("interval")){ + currentLicense["interval"] = 3600ll; + } + INFO_MSG("Checking license time"); + if(!isLicensed()){ + FAIL_MSG("License expired, shutting down"); + kill(getpid(), SIGINT); + return false; + } + return true; + } + + void parseKey(std::string key, char * newKey, unsigned int len){ + memset(newKey, 0, len); + for (size_t i = 0; i < key.size() && i < (len << 1); ++i){ + char c = key[i]; + newKey[i>>1] |= ((c&15) + (((c&64)>>6) | ((c&64)>>3))) << ((~i&1) << 2); + } + } + + void updateLicense(){ + INFO_MSG("Running license updater"); + JSON::Value response; + + HTTP::Parser http; + Socket::Connection updrConn("releases.mistserver.org", 80, true); + if ( !updrConn){ + WARN_MSG("Failed to reach licensing server"); + return; + } + + //Sending request to server. + //http.url = "/licensing.php" + //also see statics at start function. + http.url = "/license.php?release="+Encodings::URL::encode(RELEASE)+"&version="+Encodings::URL::encode(PACKAGE_VERSION)+"&iid="+Encodings::URL::encode(instanceId)+"&lid="+currentLicense["lic_id"].asString(); + long long currID = currentLicense["lic_id"].asInt(); + http.method = "GET"; + http.SetHeader("Host", "releases.mistserver.org"); + http.SetHeader("X-Version", PACKAGE_VERSION); + if (currID){ + char aesKey[16]; + if (strlen(SUPER_SECRET) >= 32){ + parseKey(SUPER_SECRET SUPER_SECRET + 7,aesKey,16); + }else{ + parseKey("4E56721C67306E1F473156F755FF5570",aesKey,16); + } + for (unsigned int i = 0; i < 8; ++i){ + aesKey[15-i] = ((currID >> (i*8)) + aesKey[15-i]) & 0xFF; + } + char ivec[16]; + memset(ivec, 0, 16); + http.SetHeader("X-IRDGAF", Encodings::Base64::encode(Encryption::AES_Crypt(RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec))); + } + updrConn.SendNow(http.BuildRequest()); + http.Clean(); + unsigned int startTime = Util::epoch(); + while ((Util::epoch() - startTime < 10) && (updrConn || updrConn.Received().size())){ + if (updrConn.spool() || updrConn.Received().size()){ + if ( *(updrConn.Received().get().rbegin()) != '\n'){ + std::string tmp = updrConn.Received().get(); + updrConn.Received().get().clear(); + if (updrConn.Received().size()){ + updrConn.Received().get().insert(0, tmp); + }else{ + updrConn.Received().append(tmp); + } + continue; + } + if (http.Read(updrConn.Received().get())){ + response = JSON::fromString(http.body); + break; //break out of while loop + } + } + } + updrConn.close(); + + //read license + readLicense(response["lic_id"].asInt(), response["license"].asStringRef()); + + } + + void readLicense(uint64_t licID, const std::string & input){ + char aesKey[16]; + if (strlen(SUPER_SECRET) >= 32){ + parseKey(SUPER_SECRET SUPER_SECRET + 7,aesKey,16); + }else{ + parseKey("4E56721C67306E1F473156F755FF5570",aesKey,16); + } + for (unsigned int i = 0; i < 8; ++i){ + aesKey[15-i] = ((licID >> (i*8)) + aesKey[15-i]) & 0xFF; + } + std::string cipher = Encodings::Base64::decode(input); + std::string deCrypted; + //magic ivecs, they are empty. It's secretly 16 times \0. + char ivec[16]; + memset(ivec, 0, 16); + deCrypted = Encryption::AES_Crypt(cipher.c_str(), cipher.size(), aesKey, ivec); + //get time stamps and license. + + //verify checksum + if (deCrypted.size() < 33 || Secure::md5(deCrypted.substr(32)) != deCrypted.substr(0,32)){ + FAIL_MSG("Could not decode license"); + Storage.removeMember("license"); + return; + } + currentLicense = JSON::fromString(deCrypted.substr(32)); + if (RELEASE != currentLicense["release"].asStringRef() || PACKAGE_VERSION != currentLicense["version"].asStringRef()){ + FAIL_MSG("Could not verify license"); + return; + } + + //Store license here. + if (currentLicense["store"].asBool()){ + if (Storage["license"].asStringRef() != input){ + Storage["license"] = input; + Storage["license_id"] = (long long)licID; + INFO_MSG("Stored license for offline use"); + } + } + } + + void licenseLoop(void * np){ + unsigned long now = Util::epoch(); + while (conf.is_active){ + if (Util::epoch() - now > currentLicense["interval"].asInt()){ + if (checkLicense()){ + now = Util::epoch(); + } + } + Util::wait(2000);//wait at least 2 seconds + } + } +} diff --git a/src/controller/controller_license.h b/src/controller/controller_license.h new file mode 100644 index 00000000..4017daaa --- /dev/null +++ b/src/controller/controller_license.h @@ -0,0 +1,17 @@ + +#include + +namespace Controller{ + + const JSON::Value & getLicense(); + void initLicense(); + bool isLicensed(); //checks/verifies license time + bool checkLicense(); //Call from Mainloop. + void updateLicense(); //retrieves update from license server + void licenseLoop(void * np); + void readLicense(uint64_t licId, const std::string & input); //checks/interprets license + + + + +} diff --git a/src/controller/controller_storage.cpp b/src/controller/controller_storage.cpp index a245f4cf..47900b2c 100644 --- a/src/controller/controller_storage.cpp +++ b/src/controller/controller_storage.cpp @@ -11,7 +11,7 @@ ///\brief Holds everything unique to the controller. namespace Controller { - + std::string instanceId; ///instanceId (previously uniqId) is first set in controller.cpp before licensing or update calls. Util::Config conf; JSON::Value Storage; ///< Global storage of data. tthread::mutex configMutex; diff --git a/src/controller/controller_storage.h b/src/controller/controller_storage.h index 37dc1782..54b17b39 100644 --- a/src/controller/controller_storage.h +++ b/src/controller/controller_storage.h @@ -4,6 +4,7 @@ #include namespace Controller { + extern std::string instanceId; ///global storage of instanceId (previously uniqID) for updater extern Util::Config conf;///< Global storage of configuration. extern JSON::Value Storage; ///< Global storage of data. extern tthread::mutex logMutex;///< Mutex for log thread. diff --git a/src/controller/controller_updater.cpp b/src/controller/controller_updater.cpp index 2552f8d9..d3bc39a7 100644 --- a/src/controller/controller_updater.cpp +++ b/src/controller/controller_updater.cpp @@ -17,9 +17,10 @@ #include "controller_connectors.h" #include "controller_updater.h" + namespace Controller { JSON::Value updates; - std::string uniqId; + std::string readFile(std::string filename){ std::ifstream file(filename.c_str()); @@ -69,13 +70,6 @@ namespace Controller { JSON::Value CheckUpdateInfo(){ JSON::Value ret; - if (uniqId == ""){ - srand(time(NULL)); - do{ - char meh = 64 + rand() % 62; - uniqId += meh; - }while(uniqId.size() < 16); - } //initialize connection HTTP::Parser http; @@ -88,7 +82,7 @@ namespace Controller { } //retrieve update information - http.url = "/getsums.php?verinfo=1&rel=" RELEASE "&pass=" SHARED_SECRET "&uniqId=" + uniqId; + http.url = "/getsums.php?verinfo=1&rel=" RELEASE "&pass=" SHARED_SECRET "&iid=" + instanceId; http.method = "GET"; http.SetHeader("Host", "releases.mistserver.org"); http.SetHeader("X-Version", PACKAGE_VERSION); diff --git a/src/controller/controller_updater.h b/src/controller/controller_updater.h index 96e6e0be..550e04af 100644 --- a/src/controller/controller_updater.h +++ b/src/controller/controller_updater.h @@ -9,7 +9,6 @@ namespace Controller { extern JSON::Value updates; - extern std::string uniqId; std::string readFile(std::string filename); bool writeFile(std::string filename, std::string & contents); diff --git a/src/controller/controller_uplink.cpp b/src/controller/controller_uplink.cpp index e4d752a3..33ce732b 100644 --- a/src/controller/controller_uplink.cpp +++ b/src/controller/controller_uplink.cpp @@ -33,14 +33,6 @@ void Controller::uplinkConnection(void * np) { if (!uplink_port) { return; } - - if (uniqId == ""){ - srand(time(NULL)); - do{ - char meh = 64 + rand() % 62; - uniqId += meh; - }while(uniqId.size() < 16); - } unsigned long long lastSend = Util::epoch() - 5; Socket::Connection uplink; @@ -119,7 +111,7 @@ void Controller::uplinkConnection(void * np) { it->removeMember("name"); } data["config"] = Controller::Storage["config"]; - data["config"]["uniq"] = uniqId; + data["config"]["uniq"] = instanceId; data["config"]["version"] = PACKAGE_VERSION; Controller::checkCapable(capabilities); data["capabilities"] = capabilities;