New licensing system
By Wouter Spruit and Balder Viëtor with minor edits by me
This commit is contained in:
parent
d0c81bb9a0
commit
2062d83858
10 changed files with 252 additions and 24 deletions
|
@ -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 #
|
||||
|
|
|
@ -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<tthread::mutex> guard(Controller::logMutex);
|
||||
Controller::Storage.removeMember("log");
|
||||
|
|
|
@ -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")){
|
||||
|
|
189
src/controller/controller_license.cpp
Normal file
189
src/controller/controller_license.cpp
Normal file
|
@ -0,0 +1,189 @@
|
|||
#include "controller_license.h"
|
||||
#include "controller_storage.h"
|
||||
#include <iostream>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/encryption.h>
|
||||
#include <mist/encode.h>
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
17
src/controller/controller_license.h
Normal file
17
src/controller/controller_license.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
#include <mist/json.h>
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <mist/tinythread.h>
|
||||
|
||||
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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue