Files
2025-02-07 19:13:59 +03:30

634 lines
22 KiB
Plaintext

//+------------------------------------------------------------------+
//| License.mqh |
//| Copyright 2015, MetaQuotes Software Corp. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
#property strict
#define LICENSED_TRADE_MODES {ACCOUNT_TRADE_MODE_DEMO,ACCOUNT_TRADE_MODE_REAL}//{ACCOUNT_TRADE_MODE_CONTEST,ACCOUNT_TRADE_MODE_DEMO}
#define LICENSED_PRIVATE_KEY "Activation Key E"
#define LICENSED_EXPIRY_DAYS 30
#include "Setting.mqh"
#include "FileSave.mqh"
#include "Translations/English.mqh"
long InpUserId = 123456;
datetime InpExpiry = D'2020.01.01';
class License
{
private:
CFileSaving m_fileSave;
string g_logMessages;
protected:
string m_mqlName;
string m_account;
string m_userid;
string m_expiry;
public:
License();
~License();
void Initialization();
bool CheckTradeModes();
bool VerifyLicenseWeb();
bool CheckAccountMarginMode();
private:
string IniFileName(const bool Log) const;
bool IniFileDelete(void) {return FileDelete(m_fileSave.DirectoryLogFile());}
string LoadPathSetting() {return ("Licence\\"+m_mqlName+"\\" +IniFileName(false));}
bool SaveSettingsOnDisk();
bool LoadSettingsFromDisk();
bool FileGen();
string Hash(string data);
string KeyGen( string data );
bool CheckIsFile() { return FileIsExist(LicencePath());}
bool CreateLicenseFile();
bool VerifyLicenseFile();
string LicencePath();
bool LoadDataFile( string &data );
string SanitizeFileName(string fileName);
long Current_Account_Mode() {return AccountInfoInteger(ACCOUNT_TRADE_MODE);}
bool LoadDataServer( string &data );
};
License::License()
{
}
License::~License()
{
}
void License::Initialization()
{
m_mqlName = MQLInfoString(MQL_PROGRAM_NAME);
m_account = IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN));
IniFileDelete();
string txt = "\n"+__FUNCTION__+":: "+TRANSLATION_EXPERT_START;
m_fileSave.IniFileSave(txt,false);
m_fileSave.IniFileSaveRun(TRANSLATION_EXPERT_START,false);
if (!LoadSettingsFromDisk()) {SaveSettingsOnDisk();}
}
string License::IniFileName(const bool Log) const
{
string name;
name=m_mqlName;
if(Log) {name+="_Log";}
else {name+="_Setttings";}
name+="_Ini.txt";
return(name);
}
bool License::SaveSettingsOnDisk()
{
Sets.LoginUserID = IntegerToString(InpUserId);
Sets.LoginExpiry = TimeToString(InpExpiry,TIME_DATE);
ResetLastError();
string txt = TRANSLATION_TRYING_TO_SAVE_FILE + IniFileName(false)+TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+ txt,false);
m_fileSave.IniFileSaveRun(txt,false);
int fh;
// Save to new format only.
fh = FileOpen(LoadPathSetting(), FILE_CSV | FILE_WRITE);
if (fh == INVALID_HANDLE)
{
ResetLastError();
string txt = " ⚠ "+ TRANSLATION_LICENSE_FILED_TO_OPEN_ + TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return false;
}
//--- Sets
FileWrite(fh, "LoginUserID");
FileWrite(fh, Sets.LoginUserID);
FileWrite(fh, "LoginExpiry");
FileWrite(fh, Sets.LoginExpiry);
FileClose(fh);
ResetLastError();
txt = TRANSLATION_SAVE_DONE + IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+ txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return true;
}
bool License::LoadSettingsFromDisk()
{
ResetLastError();
string txt = TRANSLATION_LICENSE_TRYING_TO_LOAD + IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+ txt,false);
m_fileSave.IniFileSaveRun(txt,false);
int fh;
if (!FileIsExist(LoadPathSetting()))
{
ResetLastError();
string txt = " ⚠ "+ TRANSLATION_LICENSE_NO_SETTINGS + IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return false;
}
fh = FileOpen(LoadPathSetting(), FILE_CSV | FILE_READ);
if (fh == INVALID_HANDLE)
{
ResetLastError();
string txt =" ⚠ "+ TRANSLATION_LICENSE_FAILED_TO_OPEN_FILE_READING+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return false;
}
while (!FileIsEnding(fh))
{
string var_name = FileReadString(fh);
string var_content = FileReadString(fh);
if (var_name == "LoginUserID")
Sets.LoginUserID = (var_content);
else if (var_name == "LoginExpiry")
Sets.LoginExpiry = (var_content);
}
FileClose(fh);
ResetLastError();
txt = TRANSLATION_LICENSE_LOADED_DONE + IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+ txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return true;
}
string License::Hash(string data)
{
uchar dataCharArray[];
StringToCharArray(data, dataCharArray);
uchar combinedCharArray[];
int combinedSize = ArraySize(dataCharArray);
ArrayResize(combinedCharArray, combinedSize);
ArrayCopy(combinedCharArray, dataCharArray, 0, 0, ArraySize(dataCharArray));
// Generate SHA-256 hash
uchar hashResult[];
if (!CryptEncode(CRYPT_HASH_SHA256, combinedCharArray, combinedCharArray, hashResult)) {
ResetLastError();
string txt = TRANSLATION_LICENSE_ERROR_HASH + TRANSLATION_MESSAGE_ERROR +IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return "";
}
// Base64 encode the hash result
uchar base64Result[];
if (!CryptEncode(CRYPT_BASE64, hashResult, hashResult, base64Result)) {
ResetLastError();
string txt = TRANSLATION_LICENSE_ERROR_BASE + TRANSLATION_MESSAGE_ERROR +IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return "";
}
// Convert Base64 result to string for the final passcode
string result = CharArrayToString(base64Result);
return result;
}
string License::KeyGen( string data ) {
string keyString = data + m_mqlName;
return Hash( keyString );
}
bool License::FileGen() {
// Step 1: Ensure directory structure exists by creating a dummy file temporarily.
string licenceDirPath = "Licence\\"+m_mqlName+"\\dummy.txt";
int dummyHandle = FileOpen(licenceDirPath, FILE_WRITE | FILE_ANSI);
if (dummyHandle == INVALID_HANDLE) {
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_CREATE_DIRECTORY+m_mqlName+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return false;
}
FileClose(dummyHandle);
FileDelete(licenceDirPath); // Delete the dummy file afterward
// Step 2: Proceed to create the actual licence file
string licencePath = LicencePath();
int handle = FileOpen(licencePath, FILE_WRITE | FILE_BIN | FILE_ANSI);
if (handle == INVALID_HANDLE) {
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_NO_CREATE_FILE+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return false;
}
// Combine signature and data into one line
string data_hash = Hash(m_account + "_" + Sets.LoginExpiry + "_" + Sets.LoginUserID + "_" + m_mqlName + "_" + LICENSED_PRIVATE_KEY);
string signature = KeyGen(data_hash);
string contents = signature + data_hash + "\n" + Sets.LoginExpiry;
// Print(licencePath); // show name file
// Print(contents); // show hahs
FileWriteString(handle, contents);
FileFlush(handle);
FileClose(handle);
ResetLastError();
string txt = TRANSLATION_LICENSE_CREATE_LICENSE_FILE+TRANSLATION_MESSAGE_ERROR+ IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return true;
}
bool License::CreateLicenseFile()
{
// Check if the file exists and verify if it matches current inputs
if(CheckIsFile())
{
if(VerifyLicenseFile()) {return true;}
else
{
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_RETRY+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
FileDelete(LicencePath());
Alert(TRANSLATION_LICENSE_RETRY+".");
return false;
}
}
if(FileGen())
{
if(CheckIsFile())
{
ResetLastError();
string txt =TRANSLATION_LICENSE_TO_CREATE+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
return true;
}
}
return false;
}
bool License::VerifyLicenseFile()
{
string licencePath = LicencePath();
int maxAttempts = 2; // Allow one retry
int attempt = 0;
while (attempt < maxAttempts)
{
attempt++;
// Attempt to open the file
int handle = FileOpen(licencePath, FILE_READ | FILE_BIN | FILE_ANSI);
if (handle == INVALID_HANDLE)
{
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_FAILET_TO_OPEN_FILE+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt, false);
m_fileSave.IniFileSaveRun(txt, false);
Alert(TRANSLATION_LICENSE_FAILET_TO_OPEN_FILE+".");
return false; // Cannot open file, no retry possible
}
// Read the file contents
string fileContents = FileReadString(handle);
FileClose(handle);
// Generate the expected data hash
string data_hash = Hash(m_account + "_" + Sets.LoginExpiry + "_" + Sets.LoginUserID + "_" + m_mqlName + "_" + LICENSED_PRIVATE_KEY);
string signature = KeyGen(data_hash);
string expectedContents = signature + data_hash + "\n" + Sets.LoginExpiry;
// Check if the file contents match the expected format and data
if (fileContents == expectedContents)
{
return true; // Verification succeeded
}
// Log the error for this attempt
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_NO_VERIFY_INPUTS+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt, false);
m_fileSave.IniFileSaveRun(txt, false);
// Retry if more attempts are allowed
if (attempt < maxAttempts)
{
ResetLastError();
string txt =" ⚠ "+TRANSLATION_LICENSE_FAILED_RETRY_CREATE+IntegerToString(attempt+1)+TRANSLATION_LICENSE_FAILED_RETRY_CREATE+
IntegerToString(maxAttempts)+")"+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt, false);
m_fileSave.IniFileSaveRun(txt, false);
Sleep(500); // Add a small delay before retrying
continue;
}
// After maximum attempts, show alert and fail
ResetLastError();
txt = " ⚠ "+TRANSLATION_LICENSE_FAILED_RETRY_ERROR1+"\n"+TRANSLATION_LICENSE_FAILED_RETRY_ERROR2+"\n"+
TRANSLATION_LICENSE_FAILED_RETRY_ERROR3+"\n"+TRANSLATION_LICENSE_FAILED_RETRY_ERROR4+"\n"+TRANSLATION_LICENSE_ERROR_404_2+
TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt, false);
m_fileSave.IniFileSaveRun(txt, false);
Alert(TRANSLATION_LICENSE_FAILED_RETRY_ERROR1+"\n"+TRANSLATION_LICENSE_FAILED_RETRY_ERROR2+"\n"+
TRANSLATION_LICENSE_FAILED_RETRY_ERROR3+"\n"+TRANSLATION_LICENSE_FAILED_RETRY_ERROR4+"\n"+TRANSLATION_LICENSE_ERROR_404_2+".");
return false;
}
return false;
}
// bool License::VerifyLicenseFile()
// {
// string licencePath = LicencePath();
// int handle = FileOpen(licencePath, FILE_READ | FILE_BIN | FILE_ANSI);
// if(handle == INVALID_HANDLE)
// {
// ResetLastError();
// string txt = __FUNCTION__+":: ⚠ "+TRANSLATION_LICENSE_FAILET_TO_OPEN_FILE+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
// m_fileSave.IniFileSave(txt,false);
// m_fileSave.IniFileSaveRun(txt,false);
// Alert(TRANSLATION_LICENSE_FAILET_TO_OPEN_FILE+".");
// return false;
// }
// // Read the file contents
// string fileContents = FileReadString(handle);
// FileClose(handle);
// // Generate the expected data hash
// string data_hash = Hash(m_account + "_" + Sets.LoginExpiry + "_" + Sets.LoginUserID + "_" + m_mqlName + "_" + LICENSED_PRIVATE_KEY);
// string signature = KeyGen(data_hash);
// string expectedContents = signature + data_hash + "\n" + Sets.LoginExpiry;
// // Check if the file contents match the expected format and data
// if (fileContents == expectedContents) {return true;}
// ResetLastError();
// string txt = __FUNCTION__+":: ⚠ "+TRANSLATION_LICENSE_NO_VERIFY_INPUTS+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
// m_fileSave.IniFileSave(txt,false);
// m_fileSave.IniFileSaveRun(txt,false);
// Alert(TRANSLATION_LICENSE_NO_VERIFY_INPUTS+".");
// return false;
// }
bool License::LoadDataFile( string &data ) {
string licencePath = LicencePath();
int handle = FileOpen( licencePath, FILE_READ | FILE_BIN | FILE_ANSI );
if ( handle == INVALID_HANDLE ) {
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_NO_OPEN_FILE+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
Alert(TRANSLATION_LICENSE_NO_OPEN_FILE+".");
return ( false );
}
int len = ( int )FileSize( handle );
data = FileReadString( handle, len );
FileClose( handle );
return true;
}
string License::LicencePath()
{
string rawHash = Hash(m_mqlName + "_" + m_account);
string sanitizedHash = SanitizeFileName(rawHash);
return ("Licence\\" + m_mqlName + "\\" + sanitizedHash + ".lic");
}
string License::SanitizeFileName(string fileName)
{
// List of invalid characters to replace/remove
string invalidChars = "\\/:*?\"<>|";
// Replace invalid characters with an underscore (_)
for (int i = 0; i < StringLen(invalidChars); i++) {
// Extract each invalid character as a string
string invalidChar = StringSubstr(invalidChars, i, 1);
// Update fileName directly
StringReplace(fileName, invalidChar, "_");
}
return fileName;
}
// -- check trade mode user
bool License::CheckTradeModes(){
int validModes[] = LICENSED_TRADE_MODES;
for (int i = ArraySize(validModes) - 1; i >= 0; i--) {
if (Current_Account_Mode() == validModes[i]) {
return true;
}
}
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_AUTHORIZED+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
Alert(TRANSLATION_LICENSE_AUTHORIZED+".");
return false; // Return false if no valid mode was found
}
bool License::LoadDataServer( string &data )
{
string headers = "";
char postData[];
char resultData[];
string resultHeaders;
int timeout = 10000; // 10 seconds standard timeout
int delayThreshold = 5000;
string rawHash = Hash(m_mqlName + "_" + m_account);
string mRegistration = SanitizeFileName(rawHash);
string url = "https://raw.githubusercontent.com";
// string api = StringFormat( "%s/amirghadiri1987/License/blob/main/%s.txt", url, mRegistration );
string api = StringFormat( "%s/amirghadiri1987/License/refs/heads/main/%s.lic", url, mRegistration );
// Start tracking the time for feedback
datetime startTime = TimeLocal();
ResetLastError();
int response = WebRequest( "GET", api, headers, timeout, postData, resultData, resultHeaders );
// Add this code to handle 303 redirect but it creates more problems
// if (response==303) {
// int locStart = StringFind(resultHeaders, "Location: ", 0)+10;
// int locEnd = StringFind(resultHeaders, "\r", locStart);
// api = StringSubstr(resultHeaders, locStart, locEnd-locStart);
// ResetLastError();
// response = WebRequest( "GET", api, headers, timeout, postData, resultData, resultHeaders );
// errorCode = GetLastError();
//}
if ((TimeLocal() - startTime) > delayThreshold / 1000) {
Comment("The license check is taking longer than expected. Please wait...");
}
data = CharArrayToString( resultData );
switch ( response ) {
case -1:
{
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_ERROR_RESPONSE+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
ResetLastError();
txt = " ⚠ "+TRANSLATION_LICENSE_ADD_URL + url + TRANSLATION_LICENSE_ADD_URL2+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
Alert(TRANSLATION_LICENSE_ADD_URL + url + TRANSLATION_LICENSE_ADD_URL2+".");
Comment("");
return false;
}
break;
case 200:
//--- Success
Comment("");
return true;
break;
case 404:
{
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_ERROR_404+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
Alert(TRANSLATION_LICENSE_ERROR_404+"\n"+TRANSLATION_LICENSE_ERROR_404_2+".");
Comment("");
return false;
}
break;
default:
{
ResetLastError();
string txt = TRANSLATION_LICENSE_ERROE_UNEXPECTED+ IntegerToString(response)+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+":: "+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
Comment("");
return false;
}
break;
}
Comment(""); // Clear any comment on exit
return false;
}
bool License::VerifyLicenseWeb()
{
// Check if license file creation was successful
if (!CreateLicenseFile())
{
return false;
}
// Load data from server and file
string data_server, data_file;
if (!LoadDataServer(data_server))
{
return false;
}
if (!LoadDataFile(data_file))
{
return false;
}
// Parse server data
int pos_Server = StringFind(data_server, "\n");
if (pos_Server <= 0)
{
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_INVALID_LICENSE_SERVER+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
Alert(TRANSLATION_LICENSE_INVALID_LICENSE_SERVER+".");
return false;
}
// Parse file data
int pos_File = StringFind(data_file, "\n");
if (pos_File <= 0)
{
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_INVALID_LICENSE_FILE+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
Alert(TRANSLATION_LICENSE_INVALID_LICENSE_FILE+".");
return false;
}
string signature_Server = StringSubstr( data_server, 0, pos_Server );
string expiry_Server = StringSubstr( data_server, pos_Server + 1 );
string signature_File = StringSubstr( data_file, 0, pos_File );
string expiry_File = StringSubstr( data_file, pos_File + 1 );
Sets.SHowExpiry = expiry_Server;
// Validate signatures and expiry dates
if (signature_Server != signature_File || expiry_Server != expiry_File) {
ResetLastError();
string txt = " ⚠ "+TRANSLATION_LICENSE_VERIFY_LICENSE+TRANSLATION_MESSAGE_ERROR+IntegerToString(GetLastError());
m_fileSave.IniFileSave(__FUNCTION__+"::"+txt,false);
m_fileSave.IniFileSaveRun(txt,false);
Alert(TRANSLATION_LICENSE_VERIFY_LICENSE);
return false;
}
// All checks passed
return true;
}
bool License::CheckAccountMarginMode()
{
if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
{
ResetLastError();
string txt = TRANSLATION_TERMINAL_MARGIN_MODE_HEDGE + TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError());
m_fileSave.IniFileSaveRun(txt,false);
m_fileSave.IniFileSave(__FUNCTION__+":: "+ txt, false);
Alert(TRANSLATION_TERMINAL_MARGIN_MODE_HEDGE+".");
return false;
}
return true;
}