New licensing system

By Wouter Spruit and Balder Viëtor with minor edits by me
This commit is contained in:
Thulinma 2016-09-27 11:18:42 +02:00
parent d0c81bb9a0
commit 2062d83858
10 changed files with 252 additions and 24 deletions

View file

@ -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 #

View file

@ -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,6 +308,13 @@ 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
@ -314,6 +322,12 @@ int main_loop(int argc, char ** argv){
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
@ -325,6 +339,7 @@ int main_loop(int argc, char ** argv){
//start push checking thread
tthread::thread pushThread(Controller::pushCheckLoop, 0);
//start main loop
while (Controller::conf.is_active){/*LTS*/
Controller::conf.serveThreadedSocket(Controller::handleAPIConnection);
@ -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");

View file

@ -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")){

View 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
}
}
}

View 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
}

View file

@ -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;

View file

@ -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.

View file

@ -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);

View file

@ -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);

View file

@ -34,14 +34,6 @@ void Controller::uplinkConnection(void * np) {
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;
while (Controller::conf.is_active) {
@ -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;