mirror of
https://github.com/danog/MadelineProto.git
synced 2024-11-27 06:34:41 +01:00
456 lines
17 KiB
PHP
456 lines
17 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Conversion module.
|
|
*
|
|
* This file is part of MadelineProto.
|
|
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU Affero General Public License for more details.
|
|
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* @author Daniil Gentili <daniil@daniil.it>
|
|
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
|
|
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
|
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
|
*/
|
|
|
|
namespace danog\MadelineProto;
|
|
|
|
use danog\MadelineProto\MTProtoTools\Crypt;
|
|
use PDO;
|
|
use Webmozart\Assert\Assert;
|
|
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
use function stream_get_contents;
|
|
|
|
final class Conversion
|
|
{
|
|
/**
|
|
* Prepare API instance.
|
|
*
|
|
* @param array<int, string> $authorization Authorization info
|
|
*/
|
|
public static function importAuthorization(array $authorization, int $main_dc_id, string $session, ?SettingsAbstract $settings = null): API
|
|
{
|
|
$settingsFull = new Settings;
|
|
if ($settings) {
|
|
$settingsFull->merge($settings);
|
|
}
|
|
$settings = $settingsFull;
|
|
$settings->getLogger()->setLevel(Logger::ULTRA_VERBOSE);
|
|
$settings->getAuth()->setPfs(true);
|
|
$MadelineProto = new API($session, $settings);
|
|
$MadelineProto->help->getConfig();
|
|
$MadelineProto->logger('About to import auth!', Logger::FATAL_ERROR);
|
|
$MadelineProto->importAuthorization($authorization, $main_dc_id);
|
|
return $MadelineProto;
|
|
}
|
|
public static function telethon(string $session, string $new_session, $settings = [])
|
|
{
|
|
if (!\extension_loaded('sqlite3')) {
|
|
throw new Exception(['extension', 'sqlite3']);
|
|
}
|
|
if (!isset(\pathinfo($session)['extension'])) {
|
|
$session .= '.session';
|
|
}
|
|
$session = Tools::absolute($session);
|
|
$sqlite = new PDO("sqlite:$session");
|
|
$sqlite->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
$sessions = $sqlite->query('SELECT * FROM sessions')->fetchAll();
|
|
|
|
$dcs = [];
|
|
foreach ($sessions as $dc) {
|
|
$dcs[$dc['dc_id']] = $dc['auth_key'];
|
|
}
|
|
|
|
return self::importAuthorization($dcs, $dc['dc_id'], $new_session, $settings);
|
|
}
|
|
|
|
public static function pyrogram(string $session, string $new_session, $settings = [])
|
|
{
|
|
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
|
if (!isset(\pathinfo($session)['extension'])) {
|
|
$session .= '.session';
|
|
}
|
|
$session = Tools::absolute($session);
|
|
$session = \json_decode(\file_get_contents($session), true);
|
|
$session['auth_key'] = \base64_decode(\implode('', $session['auth_key']));
|
|
Assert::notFalse($session['auth_key']);
|
|
Assert::integer($session['dc_id']);
|
|
|
|
$settings['connection_settings']['all']['test_mode'] = $session['test_mode'];
|
|
|
|
return self::importAuthorization([$session['dc_id'] => $session['auth_key']], $session['dc_id'], $new_session, $settings);
|
|
}
|
|
|
|
public static function zerobias($session, $new_session, $settings = [])
|
|
{
|
|
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
|
if (\is_string($session)) {
|
|
$session = \json_decode($session, true);
|
|
}
|
|
$dc = $session['dc'];
|
|
$key = \hex2bin($session["dc$dc".'_auth_key']);
|
|
|
|
Assert::integer($dc);
|
|
Assert::string($key);
|
|
return self::importAuthorization([$dc => $key], $dc, $new_session, $settings);
|
|
}
|
|
|
|
public static function tdesktop_md5($data)
|
|
{
|
|
$result = '';
|
|
foreach (\str_split(\md5($data), 2) as $byte) {
|
|
$result .= \strrev($byte);
|
|
}
|
|
|
|
return \strtoupper($result);
|
|
}
|
|
|
|
const FILEOPTION_SAFE = 1;
|
|
const FILEOPTION_USER = 2;
|
|
public static $tdesktop_base_path;
|
|
public static $tdesktop_user_base_path;
|
|
public static $tdesktop_key;
|
|
|
|
public static function tdesktop_fopen($fileName, $options = self::FILEOPTION_SAFE|self::FILEOPTION_USER)
|
|
{
|
|
$name = ($options & self::FILEOPTION_USER ? self::$tdesktop_user_base_path : self::$tdesktop_base_path).$fileName;
|
|
$totry = [];
|
|
foreach (['0', '1', 's'] as $x) {
|
|
if (\file_exists($name.$x)) {
|
|
$totry[] = \fopen($name.$x, 'rb');
|
|
}
|
|
}
|
|
foreach ($totry as $fp) {
|
|
if (\stream_get_contents($fp, 4) !== 'TDF$') {
|
|
Logger::log('Wrong magic', Logger::ERROR);
|
|
continue;
|
|
}
|
|
$versionBytes = \stream_get_contents($fp, 4);
|
|
$version = Tools::unpackSignedInt($versionBytes);
|
|
Logger::log("TDesktop version: $version");
|
|
$data = \stream_get_contents($fp);
|
|
$md5 = \substr($data, -16);
|
|
$data = \substr($data, 0, -16);
|
|
|
|
$length = \pack('l', \strlen($data));
|
|
$length = Magic::$BIG_ENDIAN ? \strrev($length) : $length;
|
|
|
|
if (\md5($data.$length.$versionBytes.'TDF$', true) !== $md5) {
|
|
Logger::log('Wrong MD5', Logger::ERROR);
|
|
}
|
|
|
|
$res = \fopen('php://memory', 'rw+b');
|
|
\fwrite($res, $data);
|
|
\fseek($res, 0);
|
|
|
|
return $res;
|
|
}
|
|
|
|
throw new Exception("Could not open $fileName");
|
|
}
|
|
|
|
public static function tdesktop_fopen_encrypted($fileName, $options = 3)
|
|
{
|
|
$f = self::tdesktop_fopen($fileName, $options);
|
|
$data = self::tdesktop_read_bytearray($f);
|
|
$res = self::tdesktop_decrypt($data, self::$tdesktop_key);
|
|
$length = \unpack('V', \stream_get_contents($res, 4))[1];
|
|
|
|
if ($length > \fstat($res)['size'] || $length < 4) {
|
|
throw new Exception('Wrong length');
|
|
}
|
|
|
|
return $res;
|
|
}
|
|
|
|
public static function tdesktop_read_bytearray($fp, bool $asString = false)
|
|
{
|
|
$length = Tools::unpackSignedInt(\strrev(\stream_get_contents($fp, 4)));
|
|
$data = $length > 0 ? \stream_get_contents($fp, $length) : '';
|
|
if ($asString) {
|
|
return $data;
|
|
}
|
|
$res = \fopen('php://memory', 'rw+b');
|
|
\fwrite($res, $data);
|
|
\fseek($res, 0);
|
|
|
|
return $res;
|
|
}
|
|
|
|
public static function tdesktop_decrypt($data, $auth_key)
|
|
{
|
|
$message_key = \stream_get_contents($data, 16);
|
|
$encrypted_data = \stream_get_contents($data);
|
|
|
|
[$aes_key, $aes_iv] = Crypt::oldAesCalculate($message_key, $auth_key, false);
|
|
$decrypted_data = Crypt::igeDecrypt($encrypted_data, $aes_key, $aes_iv);
|
|
|
|
if ($message_key != \substr(\sha1($decrypted_data, true), 0, 16)) {
|
|
throw new SecurityException('msg_key mismatch');
|
|
}
|
|
|
|
$res = \fopen('php://memory', 'rw+b');
|
|
\fwrite($res, $decrypted_data);
|
|
\fseek($res, 0);
|
|
|
|
return $res;
|
|
}
|
|
|
|
const dbiKey = 0x00;
|
|
const dbiUser = 0x01;
|
|
const dbiDcOptionOldOld = 0x02;
|
|
const dbiChatSizeMax = 0x03;
|
|
const dbiMutePeer = 0x04;
|
|
const dbiSendKey = 0x05;
|
|
const dbiAutoStart = 0x06;
|
|
const dbiStartMinimized = 0x07;
|
|
const dbiSoundNotify = 0x08;
|
|
const dbiWorkMode = 0x09;
|
|
const dbiSeenTrayTooltip = 0x0a;
|
|
const dbiDesktopNotify = 0x0b;
|
|
const dbiAutoUpdate = 0x0c;
|
|
const dbiLastUpdateCheck = 0x0d;
|
|
const dbiWindowPosition = 0x0e;
|
|
const dbiConnectionTypeOld = 0x0f;
|
|
// 0x10 reserved
|
|
const dbiDefaultAttach = 0x11;
|
|
const dbiCatsAndDogs = 0x12;
|
|
const dbiReplaceEmojis = 0x13;
|
|
const dbiAskDownloadPath = 0x14;
|
|
const dbiDownloadPathOld = 0x15;
|
|
const dbiScale = 0x16;
|
|
const dbiEmojiTabOld = 0x17;
|
|
const dbiRecentEmojiOldOld = 0x18;
|
|
const dbiLoggedPhoneNumber = 0x19;
|
|
const dbiMutedPeers = 0x1a;
|
|
// 0x1b reserved
|
|
const dbiNotifyView = 0x1c;
|
|
const dbiSendToMenu = 0x1d;
|
|
const dbiCompressPastedImage = 0x1e;
|
|
const dbiLangOld = 0x1f;
|
|
const dbiLangFileOld = 0x20;
|
|
const dbiTileBackground = 0x21;
|
|
const dbiAutoLock = 0x22;
|
|
const dbiDialogLastPath = 0x23;
|
|
const dbiRecentEmojiOld = 0x24;
|
|
const dbiEmojiVariantsOld = 0x25;
|
|
const dbiRecentStickers = 0x26;
|
|
const dbiDcOptionOld = 0x27;
|
|
const dbiTryIPv6 = 0x28;
|
|
const dbiSongVolume = 0x29;
|
|
const dbiWindowsNotificationsOld = 0x30;
|
|
const dbiIncludeMuted = 0x31;
|
|
const dbiMegagroupSizeMax = 0x32;
|
|
const dbiDownloadPath = 0x33;
|
|
const dbiAutoDownload = 0x34;
|
|
const dbiSavedGifsLimit = 0x35;
|
|
const dbiShowingSavedGifsOld = 0x36;
|
|
const dbiAutoPlay = 0x37;
|
|
const dbiAdaptiveForWide = 0x38;
|
|
const dbiHiddenPinnedMessages = 0x39;
|
|
const dbiRecentEmoji = 0x3a;
|
|
const dbiEmojiVariants = 0x3b;
|
|
const dbiDialogsMode = 0x40;
|
|
const dbiModerateMode = 0x41;
|
|
const dbiVideoVolume = 0x42;
|
|
const dbiStickersRecentLimit = 0x43;
|
|
const dbiNativeNotifications = 0x44;
|
|
const dbiNotificationsCount = 0x45;
|
|
const dbiNotificationsCorner = 0x46;
|
|
const dbiThemeKey = 0x47;
|
|
const dbiDialogsWidthRatioOld = 0x48;
|
|
const dbiUseExternalVideoPlayer = 0x49;
|
|
const dbiDcOptions = 0x4a;
|
|
const dbiMtpAuthorization = 0x4b;
|
|
const dbiLastSeenWarningSeenOld = 0x4c;
|
|
const dbiAuthSessionSettings = 0x4d;
|
|
const dbiLangPackKey = 0x4e;
|
|
const dbiConnectionType = 0x4f;
|
|
const dbiStickersFavedLimit = 0x50;
|
|
const dbiSuggestStickersByEmoji = 0x51;
|
|
|
|
const dbiEncryptedWithSalt = 333;
|
|
const dbiEncrypted = 444;
|
|
|
|
// 500-600 reserved
|
|
|
|
const dbiVersion = 666;
|
|
|
|
public static function tdesktop(string $session, string $new_session, $settings = [])
|
|
{
|
|
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
|
$settings['old_session_key'] ??= 'data';
|
|
$settings['old_session_passcode'] ??= '';
|
|
|
|
if (\basename($session) !== 'tdata') {
|
|
$session .= DIRECTORY_SEPARATOR.'tdata';
|
|
}
|
|
|
|
[$part_one_md5, $part_two_md5] = \str_split(self::tdesktop_md5($settings['old_session_key']), 16);
|
|
self::$tdesktop_base_path = $session.DIRECTORY_SEPARATOR;
|
|
self::$tdesktop_user_base_path = self::$tdesktop_base_path.$part_one_md5.DIRECTORY_SEPARATOR;
|
|
|
|
$data = self::tdesktop_fopen('map');
|
|
|
|
$salt = self::tdesktop_read_bytearray($data, true);
|
|
$encryptedKey = self::tdesktop_read_bytearray($data);
|
|
|
|
if (\strlen($salt)) {
|
|
$keyIterCount = \strlen($settings['old_session_passcode']) ? 4000 : 4;
|
|
$passKey = \openssl_pbkdf2($settings['old_session_passcode'], $salt, 256, $keyIterCount);
|
|
|
|
self::$tdesktop_key = \stream_get_contents(
|
|
self::tdesktop_read_bytearray(
|
|
self::tdesktop_decrypt($encryptedKey, $passKey),
|
|
),
|
|
);
|
|
} else {
|
|
$key = 'key_'.$settings['old_session_key'];
|
|
$data = self::tdesktop_fopen($key, self::FILEOPTION_SAFE);
|
|
|
|
$salt = self::tdesktop_read_bytearray($data, true);
|
|
if (\strlen($salt) !== 32) {
|
|
throw new Exception('Length of salt is wrong!');
|
|
}
|
|
|
|
$encryptedKey = self::tdesktop_read_bytearray($data);
|
|
$encryptedInfo = self::tdesktop_read_bytearray($data);
|
|
|
|
$hash = \hash('sha512', $salt.$settings['old_session_passcode'].$salt, true);
|
|
$iterCount = \strlen($settings['old_session_passcode']) ? 100000 : 1;
|
|
|
|
$passKey = \openssl_pbkdf2($hash, $salt, 256, $iterCount, 'sha512');
|
|
|
|
$key = self::tdesktop_read_bytearray(self::tdesktop_decrypt($encryptedKey, $passKey), true);
|
|
$info = self::tdesktop_read_bytearray(self::tdesktop_decrypt($encryptedInfo, $key));
|
|
|
|
self::$tdesktop_key = $key;
|
|
|
|
$count = Tools::unpackSignedInt(\strrev(\stream_get_contents($info, 4)));
|
|
Logger::log("Number of accounts: $count");
|
|
for ($i = 0; $i != $count; $i++) {
|
|
$idx = Tools::unpackSignedInt(\strrev(\stream_get_contents($info, 4)));
|
|
if ($idx >= 0) {
|
|
$dataName = $settings['old_session_key'];
|
|
if ($idx > 0) {
|
|
$dataName = $settings['old_session_key'].'#'.($idx+1);
|
|
}
|
|
[$part_one_md5] = \str_split(self::tdesktop_md5($dataName), 16);
|
|
}
|
|
}
|
|
}
|
|
|
|
$main = self::tdesktop_fopen_encrypted($part_one_md5, self::FILEOPTION_SAFE);
|
|
|
|
$auth_keys = [];
|
|
|
|
while (!\feof($main)) {
|
|
$magic = Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)));
|
|
switch ($magic) {
|
|
case self::dbiDcOptionOldOld:
|
|
\stream_get_contents($main, 4);
|
|
self::tdesktop_read_bytearray($main);
|
|
self::tdesktop_read_bytearray($main);
|
|
\stream_get_contents($main, 4);
|
|
break;
|
|
case self::dbiDcOptionOld:
|
|
\stream_get_contents($main, 8);
|
|
self::tdesktop_read_bytearray($main);
|
|
\stream_get_contents($main, 4);
|
|
break;
|
|
case self::dbiDcOptions:
|
|
self::tdesktop_read_bytearray($main);
|
|
break;
|
|
case self::dbiUser:
|
|
\stream_get_contents($main, 4);
|
|
$main_dc_id = Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)));
|
|
break;
|
|
case self::dbiKey:
|
|
$auth_keys[Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)))] = \stream_get_contents($main, 256);
|
|
break;
|
|
case self::dbiMtpAuthorization:
|
|
$main = self::tdesktop_read_bytearray($main);
|
|
//stream_get_contents($main, 4);
|
|
$user_id = Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)));
|
|
$main_dc_id = Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)));
|
|
$length = Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)));
|
|
for ($x = 0; $x < $length; $x++) {
|
|
$dc = Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)));
|
|
$auth_key = \stream_get_contents($main, 256);
|
|
if ($dc <= 5) {
|
|
$auth_keys[$dc] = $auth_key;
|
|
}
|
|
}
|
|
break 2;
|
|
case self::dbiAutoDownload:
|
|
\stream_get_contents($main, 12);
|
|
break;
|
|
case self::dbiDialogsMode:
|
|
\stream_get_contents($main, 8);
|
|
break;
|
|
case self::dbiAuthSessionSettings:
|
|
self::tdesktop_read_bytearray($main);
|
|
break;
|
|
case self::dbiConnectionTypeOld:
|
|
switch (Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)))) {
|
|
case 2:
|
|
case 3:
|
|
self::tdesktop_read_bytearray($main);
|
|
\stream_get_contents($main, 4);
|
|
self::tdesktop_read_bytearray($main);
|
|
self::tdesktop_read_bytearray($main);
|
|
break;
|
|
}
|
|
break;
|
|
case self::dbiConnectionType:
|
|
\stream_get_contents($main, 8);
|
|
self::tdesktop_read_bytearray($main);
|
|
\stream_get_contents($main, 4);
|
|
self::tdesktop_read_bytearray($main);
|
|
self::tdesktop_read_bytearray($main);
|
|
break;
|
|
case self::dbiThemeKey:
|
|
case self::dbiLangPackKey:
|
|
case self::dbiMutePeer:
|
|
\stream_get_contents($main, 8);
|
|
break;
|
|
case self::dbiWindowPosition:
|
|
\stream_get_contents($main, 24);
|
|
break;
|
|
case self::dbiLoggedPhoneNumber:
|
|
self::tdesktop_read_bytearray($main);
|
|
break;
|
|
case self::dbiMutedPeers:
|
|
$length = Tools::unpackSignedInt(\strrev(\stream_get_contents($main, 4)));
|
|
for ($x = 0; $x < $length; $x++) {
|
|
\stream_get_contents($main, 8);
|
|
}
|
|
// no break
|
|
case self::dbiDownloadPathOld:
|
|
self::tdesktop_read_bytearray($main);
|
|
break;
|
|
case self::dbiDialogLastPath:
|
|
self::tdesktop_read_bytearray($main);
|
|
break;
|
|
case self::dbiDownloadPath:
|
|
self::tdesktop_read_bytearray($main);
|
|
self::tdesktop_read_bytearray($main);
|
|
break;
|
|
default:
|
|
throw new \Exception("Unknown type $magic");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return self::importAuthorization($auth_keys, $main_dc_id, $new_session, $settings);
|
|
}
|
|
}
|