Сервисы
Сервисом является MQL-программа с единственным обработчиком OnStart и директивой #property service.
Напомним, что после успешной компиляции сервиса требуется создать и настроить его экземпляр (один или несколько) с помощью команды Добавить сервис в контекстном меню Навигатора терминала.
В качестве примера сервиса решим небольшую прикладную проблему, которая часто возникает у разработчиков MQL-программ. Многие из них практикуют привязку своих программ к номеру счета пользователя. Речь здесь не обязательно идет о платном продукте, а может касаться распространения среди друзей и знакомых для сбора статистики или удачных настроек. При этом пользователь может помимо рабочего реального счета регистрировать демо-счета. Время существования таких счетов, как правило, ограничено, и потому обновлять ради них привязку каждую пару недель довольно неудобно. Для этого надо править исходный код, компилировать и отсылать программу заново.
Вместо этого мы можем разработать сервис, который будет регистрировать в глобальных переменных (или файлах) номера счетов, к которым произошло успешное подключение из данного терминала.
Технология привязки основывается на попарном шифровании (или, как вариант, хешировании) номеров счетов: прежнего счета логина и нового счета логина. Предыдущий счет должен быть мастер-счетом (на который "выписана" условная привязка), чтобы общая подпись пары расширяла права пользования продуктом на новый счет. В качестве ключа используется секрет, известный только внутри программ (предполагается, что все они поставляются в закрытом, откомпилированном виде). Результатом операции будет строка в формате Base64. В реализации применены функции MQL5 API, часть которых еще предстоит изучить, в частности, получение номера счета через AccountInfoInteger и шифрование функцией CryptEncode. Проверка связи с сервером выполняется известной нам функцией TerminalInfoInteger (см. раздел Проверка сетевых подключений).
Сервис не обязан знать, какие счета являются мастер-счетами, а какие дополнительными, — ему достаточно особым образом "подписывать" пары любых последовательно залогиненных счетов. А вот уже конкретная прикладная программа должна дополнить процесс проверки своей "лицензии": помимо сравнения текущего счета с мастер-счетом следует повторить алгоритм сервиса: составить пару [мастер-счет;текущий счет], подсчитать для неё зашифрованную "подпись" и проверить, есть ли она среди глобальных переменных.
Украсть такую лицензию путем переноса на другой компьютер получится, только если подключаться к тому же счету в режиме торговли (не инвестора). Недобросовестный пользователь, конечно, может создавать демо-счета для других людей. Поэтому желательно усовершенствовать защиту. В текущей реализации глобальная переменная просто делается временной, то есть удаляется вместе с окончанием сеанса терминала, но это не предотвращает её возможное копирование.
В качестве дополнительных мер можно, например, шифровать в "подписи" время её создания и предусмотреть истечение прав каждые сутки (или с другой периодичностью). Другой вариант — генерация случайного числа при запуске сервиса и добавление его в подписываемую информацию наравне с номерами счетов. Это число известно только внутри сервиса, но он может транслировать его заинтересованным MQL-программам на графики с помощью функции EventChartCustom. Таким образом, "подпись" продолжит действовать в данном экземпляре терминала вплоть до завершения сеанса. В каждом сеансе будет генерироваться и рассылаться новое случайное число, поэтому оно не подойдет для других терминалов. Наконец, самым простым и удобным вариантом будет, вероятно, добавление в "подпись" времени старта системы: (TimeLocal() - GetTickCount() / 1000) или производного от него.
Из различных типов MQL-программ только некоторые продолжают выполняться между переключениями счетов и позволяют реализовать данную схему защиты. Поскольку требуется единообразным образом защитить MQL-программы любых типов, включая индикаторы и эксперты (которые перезагружаются при смене счета), имеет смысл поручить эту задачу сервису. Тогда сервис, постоянно работающий с момента загрузки терминала и до его закрытия, будет контролировать логины и генерировать уполномочивающие "подписи".
Исходный код сервиса приведен в файле MQL5/Services/MQL5Book/p5/ServiceAccount.mq5. Во входных параметрах задается мастер-счет и префикс глобальных переменных, в которых будут сохраняться "подписи". В реальных программах списки мастер-счетов должны быть зашиты в исходном коде, а вместо глобальных переменных лучше использовать файлы в папке Common, чтобы охватить также и тестер.
#property service
|
Главная функция сервиса выполняет свою работу следующим образом: в бесконечном цикле с паузами в 1 секунду отслеживаем смены счетов и сохраняем последний номер, создаем для пары "подпись" и записываем её в глобальную переменную. Созданием подписи занимается функция Cipher.
void OnStart()
|
Функция Cipher использует специальное объединение ByteOverlay2, чтобы представить пару номеров счетов (типа long) в виде байтового массива, который передается для шифрования в CryptEncode (здесь выбран метод шифрования CRYPT_DES, но его можно заменить на CRYPT_AES128, CRYPT_AES256 или просто хеширование CRYPT_HASH_SHA256 (с секретом в качестве соли), если восстановление информации из подписи не требуется).
template<typename T>
|
Далее любая программа в терминале может проверить, нет ли в глобальных переменных "лицензии" для текущего счета. Это делается с помощью функций CheckAccounts и IsCurrentAccountAuthorizedByMaster. Они приведены в сервисе просто для демонстрации.
Функция CheckAccounts выполняет проверку по всем мастер-счетам, зашитым в код, не совпадает ли один из них с текущим.
bool CheckAccounts()
|
IsCurrentAccountAuthorizedByMaster принимает в качестве параметра номер одного мастер-счета, воссоздает для него "подпись" в паре с текущим счетом и анализирует совпадения.
bool IsCurrentAccountAuthorizedByMaster(const long data)
|
Предположим, что программам разрешено выполняться на счете 123456789, и он в данный момент активен. При запуске сервис среагирует записью в журнале:
New account 123456789 detected |
Если затем сменить номер счета, например, на 5555555, получим следующую сигнатуру:
Account 5555555 registered by 123456789: jdVKxUswBiNlZzDAnV3yxw== |
Если остановить и снова запустить сервис, увидим проверку счета 5555555 в действии (вызов функции CheckAccounts встроен для демонстрации в начало OnStart).
Sub-License is active: jdVKxUswBiNlZzDAnV3yxw==
|
Лицензия сработала для нового счета. Если переключиться обратно, сгенерируется "пропуск" с текущего счета на предыдущий (это следствие того, что сервис не знает, какие счета являются основными, а какие временными, и такая "подпись", скорее всего, не потребуется в программах).
Для опосредованной авторизации нового счета потребуется снова войти в мастер-счет и только затем переключиться на новый: это создаст другую глобальную переменную с зашифрованной парой [мастер-счет; новый счет].
Данная версия сервиса не проверяет, чтобы мастер-счет был реальным, а зависимый — демонстрационным. Каждое из этих ограничений можно добавить.