forked from rosh/MQL5Book
137 lines
5 KiB
MQL5
137 lines
5 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| ServiceAccount.mq5 |
| |||
//| Copyright 2021, MetaQuotes Ltd. |
| |||
//| https://www.mql5.com |
| |||
//+------------------------------------------------------------------+
| |||
#property service
| |||
| |||
input long MasterAccount = 123456789;
| |||
input string Prefix = "!A_";
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| T type pair of values to byte array |
| |||
//+------------------------------------------------------------------+
| |||
template<typename T>
| |||
union ByteOverlay2
| |||
{
| |||
T values[2];
| |||
uchar bytes[sizeof(T) * 2];
| |||
ByteOverlay2(const T v1, const T v2) { values[0] = v1; values[1] = v2; }
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Common generator of a signature for given account numbers |
| |||
//+------------------------------------------------------------------+
| |||
string Cipher(const long data1, const long data2)
| |||
{
| |||
// TODO: replace the secret with your own hard to break byte set
| |||
// TODO: for CRYPT_AES128/CRYPT_AES256 use 16/32 bytes long array
| |||
const static uchar secret[] = {'S', 'E', 'C', 'R', 'E', 'T', '0'};
| |||
ByteOverlay2<long> bo(data1, data2);
| |||
// TODO: embed time limit into the cipher, for example, 1 day long:
| |||
// const long day = 60 * 60 * 24;
| |||
// ByteOverlay3<long> bo(TimeCurrent() / day * day, data1, data2);
| |||
// OR computer session long
| |||
// ByteOverlay3<long> bo((TimeLocal() - GetTickCount() / 1000), data1, data2);
| |||
uchar result[];
| |||
if(CryptEncode(CRYPT_DES, bo.bytes, secret, result) > 0)
| |||
{
| |||
uchar dummy[], text[];
| |||
if(CryptEncode(CRYPT_BASE64, result, dummy, text) > 0)
| |||
{
| |||
return CharArrayToString(text);
| |||
}
| |||
}
| |||
return NULL;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Lookup list of licensed accounts (single one in this example) |
| |||
//| and optional dependent accounts |
| |||
//+------------------------------------------------------------------+
| |||
bool CheckAccounts()
| |||
{
| |||
const long accounts[] = {MasterAccount}; // populate this array
| |||
for(int i = 0; i < ArraySize(accounts); ++i)
| |||
{
| |||
if(IsCurrentAccountAuthorizedByMaster(accounts[i])) return true;
| |||
}
| |||
return false;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Example of validation of current account against master account |
| |||
//+------------------------------------------------------------------+
| |||
bool IsCurrentAccountAuthorizedByMaster(const long data)
| |||
{
| |||
const long a = AccountInfoInteger(ACCOUNT_LOGIN);
| |||
if(a == data) return true;
| |||
const string s = Cipher(data, a);
| |||
if(a != 0 && GlobalVariableGet(Prefix + s) == a)
| |||
{
| |||
Print("Sub-License is active: ", s);
| |||
return true;
| |||
}
| |||
return false;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Service program start function |
| |||
//+------------------------------------------------------------------+
| |||
void OnStart()
| |||
{
| |||
static long account = 0; // previous account
| |||
| |||
// example of check-up in an external program
| |||
if(CheckAccounts())
| |||
{
| |||
// act on success as appropriate
| |||
}
| |||
| |||
for( ; !IsStopped(); )
| |||
{
| |||
// account must be logged in, connected, and not in investor mode
| |||
const bool c = TerminalInfoInteger(TERMINAL_CONNECTED)
| |||
&& AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);
| |||
const long a = c ? AccountInfoInteger(ACCOUNT_LOGIN) : 0;
| |||
| |||
if(account != a)
| |||
{
| |||
if(a != 0)
| |||
{
| |||
if(account != 0) // previous and current accounts are authorized
| |||
{
| |||
const string signature = Cipher(account, a);
| |||
PrintFormat("Account %I64d registered by %I64d: %s", a, account, signature);
| |||
// save info about logged accounts binding
| |||
if(StringLen(signature) > 0)
| |||
{
| |||
GlobalVariableTemp(Prefix + signature);
| |||
GlobalVariableSet(Prefix + signature, a);
| |||
}
| |||
}
| |||
else
| |||
{
| |||
PrintFormat("New account %I64d detected", a);
| |||
}
| |||
// remember current account
| |||
account = a;
| |||
}
| |||
}
| |||
| |||
Sleep(1000);
| |||
}
| |||
}
| |||
//+------------------------------------------------------------------+
| |||
/*
| |||
example output (adjust master account number in inputs)
| |||
| |||
[launch service instance]
| |||
New account 123456789 detected
| |||
[change account]
| |||
Account 5555555 registered by 123456789: jdVKxUswBiNlZzDAnV3yxw==
| |||
[re-launch service instance]
| |||
Sub-License is active: jdVKxUswBiNlZzDAnV3yxw==
| |||
Account 123456789 registered by 5555555: ZWcwwJ1d8seN1UrFSzAGIw==
| |||
*/
| |||
//+------------------------------------------------------------------+
|