[PHP] Для тех, кто интересуется ICQ протоколом.

Discussion in 'PHP' started by Deathdreams, 12 Dec 2010.

  1. Deathdreams

    Deathdreams Elder - Старейшина

    Joined:
    8 Nov 2008
    Messages:
    342
    Likes Received:
    116
    Reputations:
    5
    Всем привет, на днях решил вспомнить пыхыпы и написать немного о ICQ протоколе и с чем его едят.

    За основу своих трудов взял несколько готовых ICQ классов (рабочих) и пару пачек пельменей.

    Разберу основные моменты (реализацию проксей не сделал правда - времени мало), но конечный код можно будет использовать, как минимум, для проверки номеров на валидность.

    1. Начало.
    Для начала зададим параметры подключения:
    Code:
    $uin = "122211"; # номер ICQ
    $password = "bazzinga"; # соответственно пароль
    
    Далее установим соединение с сервером авторизации (lоgin.iсq.соm):
    Code:
    $socket = @fsockopen("login.icq.com", 5190);
    
    Q: А почему бы для скорости не сделать случайную выборку сервера?
    A: На личном опыте убедился, что быстрее сервера авторизации, чем этот - нет. Да и в инете полно дохлых. Поэтому это реализуется только при наличие работающих и быстрых серверов.

    2. Первые FLAP и SNAC.
    После создания подключения, считаем ответ сервера - FLAP и SNAC (о них можно почитать в гугле). Эта функция чтения ответа сервера будет постоянно, но выделять как отдельную функцию не стал.
    Code:
    $FLAP = @fread($socket, 6);	
    list(, $length) = @unpack("n", substr($FLAP, 4, 2));
    $SNAC = @fread($socket, $length); 
    
    3. Подготовка данных перед отправкой на сервер.
    Теперь перед нами стоит задача, сделать наши данные такие, чтобы их смог понять сервер авторизации (UIN просто пакуем,а пароль - XOR'им).
    Выделять XOR пароля в отдельную функцию тоже не стал:
    Code:
    $data = base64_decode("8yaBxDmG25Jxo7nmU3qVfA==");
    for ($i=0; $i<strlen($password); $i++) @$out .= chr(ord($data[$i]) ^ ord($password[$i]));
    
    Теперь у нас всё готово, осталось данные упаковать в FLAP и SNAC:
    Code:
    $SNAC = base64_decode('AAAAAQ==').pack("nna*", 0x01, strlen($uin), $uin).pack("nna*", 0x02, strlen($out), $out).base64_decode('AAMACElDUUJhc2ljABYAAgEKABcAAgABABgAAgAAABkAAgAAABoAAgABABQABAAAAFUADwACZW4ADgACdXM=');
    $FLAP = pack("CCnn", 0x2A, 1, $random=rand(0x0000, 0xFFFF), strlen($SNAC)); $random++;
    
    Я сделал за вас всю грязную работу, упаковав постоянные данные в base64, также убрал возможности смены версии клиента, поставив кошерную, ибо сервер ругается ссылками на ICQ7, если там поставить свой :\
    И, собсна, сама отправка:
    Code:
    @fwrite($socket, $FLAP.$SNAC);
    
    4. Прием ответа.
    Для считывания ответа сервера и данных и BOS-сервере (BOS-сервер -- сервер, на который перенаправляет нас центр авторизации при верно введенном UIN'e+пароле. Собственно так мы и будем определять, верные данные отправили или нет.)
    Code:
    $FLAP = @fread($socket, 6);
    list(, $length) = @unpack("n", substr($FLAP, 4, 2));	
    $SNAC = @fread($socket, $length);	
    	while (strlen($SNAC) > 0) {
    		list(, $type, $length) = unpack("n2", substr($SNAC, 0, 4));
    		$TLV[$type] = substr($SNAC, 4, $length);
    		$SNAC = substr($SNAC, 4+$length);
    				}
    
    У нас образовался массив $TLV, (Type, Length, Value - ничто иное, как указание числового идентификатора типа данных, их длины, и, собственно, самих данных (с)destym)

    В своём труде я добавил парочку проверок для блокировку IP (из-за частых подключений), неверный UIN/пароль и на кривой сервер авторизации.

    Также, если вы хотите сделать из этого чеккер, в результате собственных наблюдений установил, что задержку между UIN'ами нужно делать примерно так:
    Code:
    sleep(rand(4,5));
    
    Это позволяет не схватить временный бан IP при использовании только одного сервера авторизации.

    Конечный код:

    PHP:
    $uin "UIN";
    $password "PWD";
    $socket = @fsockopen("login.icq.com"5190);
        if (
    $socket
        {
            
    $start getmicrotime();
            
    $FLAP = @fread($socket6);    
            list(, 
    $length) = @unpack("n"substr($FLAP42));
            
    $SNAC = @fread($socket$length); 
            
    $data base64_decode("8yaBxDmG25Jxo7nmU3qVfA==");
            for (
    $i=0$i<strlen($password); $i++) @$out .= chr(ord($data[$i]) ^ ord($password[$i]));
            
    $SNAC base64_decode('AAAAAQ==').pack("nna*"0x01strlen($uin), $uin).pack("nna*"0x02strlen($out), $out).base64_decode('AAMACElDUUJhc2ljABYAAgEKABcAAgABABgAAgAAABkAAgAAABoAAgABABQABAAAAFUADwACZW4ADgACdXM=');
            
    $FLAP pack("CCnn"0x2A1$random=rand(0x00000xFFFF), strlen($SNAC)); $random++;
            @
    fwrite($socket$FLAP.$SNAC);
            
    $FLAP = @fread($socket6);
            list(, 
    $length) = @unpack("n"substr($FLAP42));    
            
    $SNAC = @fread($socket$length);    
            while (
    strlen($SNAC) > 0) {
                list(, 
    $type$length) = unpack("n2"substr($SNAC04));
                
    $TLV[$type] = substr($SNAC4$length);
                
    $SNAC substr($SNAC4+$length);
                        }
            echo 
    "<br>"; echo $uin.":".$password." - ";
            if ( @
    trim($TLV[0x04]) == 'http://www.aol.com?ccode=us&lang=en' $debug 'Слишком быстро...'
            else if( @
    strstr$TLV[0x04], 'MISMATCH_PASSWD' ) ) $debug 'Неверный пароль.';
            else if (!(@
    $TLV[0x05] && @$TLV[0x06])) $debug 'Ошибка при авторизации: не выдан BOS-сервер (слишком много переподключений или выбран корявый сервер).'
            else 
    $debug 'Пароль соответствует UIN\'у.';

            @
    fclose($socket); echo "<b>".$debug."</b>""  (time: ".substr((getmicrotime()-$start), 04)." s.)""<br><br>";
        } else echo 
    $debug 'Невозможно создать подключение с сервером :(';

    function 
    getmicrotime()
    {
    $mt explode(" "microtime() );
    return ((float)
    $mt[0] + (float)$mt[1]);
    }
    5. Выведение номера в On-line.
    Для выведения номера в онлайн, придётся сделать следующиее:
    - Закрыть подключение с сервером авторизации
    - Подключиться к BOS-серверу
    - Отправить Cookies
    - Отправить пакет для готовности работы
    - Выполнить необходимые действия и завершить работу закрыванием соединения
    Реализация:
    Code:
    			if( $debug == 'Пароль соответствует UIN\'у.') # Проверяем на валидность UIN
    			{
    				echo '<br>Подключаемся...'; # Выведем сообщение, что всё ОК и сейчас подключимся
    				list( $server, $port ) = explode( ':', $TLV[0x05] );
    				$socket = @fsockopen($server, $port); # Создаем подключение с выданным нам BOS-сервером
    				if( !$socket ) die('<b>BOS-сервер  ( </b>'.$TLV[0x05].'<b> )  недоступен.Попробуйте ещё раз.</b>'); # Выведем сообщение, если BOS-сервер недоступен
    				$SNAC = base64_decode("AAAAAQ==").pack("nna*", 0x06, strlen($TLV[0x06]), $TLV[0x06]); # Отдаём запакованые Cookies
    				$FLAP = pack("CCnn", 0x2A, 1, $random, strlen($SNAC)); $random++; # Создаем FLAP для этих данных
    				@fwrite($socket, $FLAP.$SNAC); # Отправляем данные серверу
    				$SNAC = base64_decode("AAEAAgAAAAAAAgABAAMBEAKKAAIAAQEBAooAAwABARACigAVAAEBEAKKAAQAAQEQAooABgABARACigAJAAEBEAKKAAoAAQEQAoo="); # Опять делаю всю грязную работу, т.е. отсылаем серверу пакет о готовности работы
    				$FLAP = pack("CCnn", 0x2A, 2, $random, strlen($SNAC)); # Создаем FLAP для этих данных
    				@fwrite($socket, $FLAP.$SNAC); # Отправляем данные серверу
    				/*  Цикл  */
    
    					for( $i=0; $i <= 3; $i++) # Цикл для работы с номером
    					{
    						sleep(1);
    					}
    
    				/*  Конец цикла  */
    				@fclose($socket); # Закрываем соединение, работа с ICQ номером завершена.
    			}
    
    Конечный код, с выведением номера в онлайн:
    PHP:
    $uin "UIN";
    $password "PWD";
    $socket = @fsockopen("login.icq.com"5190);
        if (
    $socket
        {
            
    $start getmicrotime();
            
    $FLAP = @fread($socket6);    
            list(, 
    $length) = @unpack("n"substr($FLAP42));
            
    $SNAC = @fread($socket$length); 
            
    $data base64_decode("8yaBxDmG25Jxo7nmU3qVfA==");
            for (
    $i=0$i<strlen($password); $i++) @$out .= chr(ord($data[$i]) ^ ord($password[$i]));
            
    $SNAC base64_decode('AAAAAQ==').pack("nna*"0x01strlen($uin), $uin).pack("nna*"0x02strlen($out), $out).base64_decode('AAMACElDUUJhc2ljABYAAgEKABcAAgABABgAAgAAABkAAgAAABoAAgABABQABAAAAFUADwACZW4ADgACdXM=');
            
    $FLAP pack("CCnn"0x2A1$random=rand(0x00000xFFFF), strlen($SNAC)); $random++;
            @
    fwrite($socket$FLAP.$SNAC);
            
    $FLAP = @fread($socket6);
            list(, 
    $length) = @unpack("n"substr($FLAP42));    
            
    $SNAC = @fread($socket$length);    
            while (
    strlen($SNAC) > 0) {
                list(, 
    $type$length) = unpack("n2"substr($SNAC04));
                
    $TLV[$type] = substr($SNAC4$length);
                
    $SNAC substr($SNAC4+$length);
                        }
            echo 
    "<br>"; echo $uin.":".$password." - ";
            if ( @
    trim($TLV[0x04]) == 'http://www.aol.com?ccode=us&lang=en' $debug 'Слишком быстро...'
            else if( @
    strstr$TLV[0x04], 'MISMATCH_PASSWD' ) ) $debug 'Неверный пароль.';
            else if (!(@
    $TLV[0x05] && @$TLV[0x06])) $debug 'Ошибка при авторизации: не выдан BOS-сервер (слишком много переподключений или выбран корявый сервер).'
            else 
    $debug 'Пароль соответствует UIN\'у.';

            @
    fclose($socket); echo "<b>".$debug."</b>""(time: ".substr((getmicrotime()-$start), 04)." s.)""<br><br>";
                if( 
    $debug == 'Пароль соответствует UIN\'у.'# Проверяем на валидность UIN
                
    {
                    echo 
    '<br>Подключаемся...'# Выведем сообщение, что всё ОК и сейчас подключимся
                    
    list( $server$port ) = explode':'$TLV[0x05] );
                    
    $socket = @fsockopen($server$port); # Создаем подключение с выданным нам BOS-сервером
                    
    if( !$socket ) die('<b>BOS-сервер  ( </b>'.$TLV[0x05].'<b> )  недоступен.Попробуйте ещё раз.</b>'); # Выведем сообщение, если BOS-сервер недоступен
                    
    $SNAC base64_decode("AAAAAQ==").pack("nna*"0x06strlen($TLV[0x06]), $TLV[0x06]); # Отдаём запакованые Cookies
                    
    $FLAP pack("CCnn"0x2A1$randomstrlen($SNAC)); $random++; # Создаем FLAP для этих данных
                    
    @fwrite($socket$FLAP.$SNAC); # Отправляем данные серверу
                    
    $SNAC base64_decode("AAEAAgAAAAAAAgABAAMBEAKKAAIAAQEBAooAAwABARACigAVAAEBEAKKAAQAAQEQAooABgABARACigAJAAEBEAKKAAoAAQEQAoo="); # Опять делаю всю грязную работу, т.е. отсылаем серверу пакет о готовности работы
                    
    $FLAP pack("CCnn"0x2A2$randomstrlen($SNAC)); # Создаем FLAP для этих данных
                    
    @fwrite($socket$FLAP.$SNAC); # Отправляем данные серверу
                    /*  Цикл  */

                        
    for( $i=0$i <= 3$i++) # Цикл для работы с номером
                        
    {
                            
    sleep(1);
                        }

                    
    /*  Конец цикла  */
                    
    @fclose($socket); # Закрываем соединение, работа с ICQ номером завершена.

                
    }
        } else echo 
    $debug 'Невозможно создать подключение с сервером :(';

    function 
    getmicrotime()
    {
    $mt explode(" "microtime() );
    return ((float)
    $mt[0] + (float)$mt[1]);
    }
    Написать данное введение мне помогли 3 известных класса: [http://kanicq.ru/invisible/icqlib.rar], [http://destym.ru/sources/mlCQ.phps], [http://wip.asminog.com/releases/WebIcqPro1.4.8b.zip]

    P.S. Не откажусь от вашей критики :)
     
    #1 Deathdreams, 12 Dec 2010
    Last edited: 12 Dec 2010
    5 people like this.
  2. AnGeI

    AnGeI Elder - Старейшина

    Joined:
    8 Dec 2008
    Messages:
    395
    Likes Received:
    79
    Reputations:
    16
    будет скрипт, который выводит онлайн номер?
     
  3. Deathdreams

    Deathdreams Elder - Старейшина

    Joined:
    8 Nov 2008
    Messages:
    342
    Likes Received:
    116
    Reputations:
    5
    Достаточно серверу отправить дополнительные данные и можно выводить в онлайн.
    Только зачем ?
    Для этого есть громосткие классы.
     
  4. Deathdreams

    Deathdreams Elder - Старейшина

    Joined:
    8 Nov 2008
    Messages:
    342
    Likes Received:
    116
    Reputations:
    5
    [UPD]
    Добавил функцию выведения номера в онлайн (по просьбе читающих)

    P.s. придётся поудалять пробелы, которые создаёт форум :(
     
    #4 Deathdreams, 12 Dec 2010
    Last edited: 12 Dec 2010
    1 person likes this.
  5. Gifts

    Gifts Green member

    Joined:
    25 Apr 2008
    Messages:
    2,494
    Likes Received:
    807
    Reputations:
    614
    Deathdreams чем оправдано использование @?
    Пожалуйста, не используйте магические переменные, если это, конечно, возможно
    Обрамление повторно используемого кода в функции - все же плюс, чем минус
     
    _________________________
  6. Deathdreams

    Deathdreams Elder - Старейшина

    Joined:
    8 Nov 2008
    Messages:
    342
    Likes Received:
    116
    Reputations:
    5
    Мб скрипт будет использоваться не только в личных целях ?
    Хотелось показать весь процесс авторизации поэтапно.
    Да, я согласен с вами, но зрительно воспринимается больше такой метод.
     
  7. Gifts

    Gifts Green member

    Joined:
    25 Apr 2008
    Messages:
    2,494
    Likes Received:
    807
    Reputations:
    614
    Deathdreams
    про это-то и спрашиваю, как вы предлагаете локализовывать проблему пользователям? При вашем коде - потребуется заменять все знаки @ на пропуски, а вдруг там где нить используется этот знак, не только для заглушения ошибок. Не лучше ли использовать error_reporting(0);

    Зрительно лучше воспринимается абстрактный код, например:
    PHP:
    function do_authorise()
    {
    connect();
    send_loginpassword();
    return 
    check_answer();
    }

    function 
    mainloop()
    {
    receive_message();
    dispatch_message();
    }

    if 
    do_authorise()
    {
    mainloop();
    }
     
    _________________________
  8. Deathdreams

    Deathdreams Elder - Старейшина

    Joined:
    8 Nov 2008
    Messages:
    342
    Likes Received:
    116
    Reputations:
    5
    Всегда пренебрежительно относился к error_reporting(0);
    Ставил "@" там, где могут произойти ошибки на стороне сокета или принятых данных... Ну, если вчитаться, это можно легко заметить.
    Ставлю при открытии сокета (т.к. сервер регулируется пользователем), ставлю при принятии TLV от сервера (а вдруг он пустой или вообще массив ?).

    З.Ы. Неужели нет интересующихся ICQ протоколом людей ? о_О
    Зря писал что ли :(
     
  9. R1no

    R1no New Member

    Joined:
    10 Jan 2011
    Messages:
    5
    Likes Received:
    0
    Reputations:
    0
    Интересующиеся есть :) Очень интересует, как поставить xstatus с текстом на основе библиотеки WebIcqPro :rolleyes:
     
  10. php_casper

    php_casper New Member

    Joined:
    6 Jun 2010
    Messages:
    64
    Likes Received:
    1
    Reputations:
    1
    R1no, у меня вообще скачаная с офф сайта последняя версия не работала(( Щас использую туже либу, но исправленную и выковыряную со steelbot
     
  11. R1no

    R1no New Member

    Joined:
    10 Jan 2011
    Messages:
    5
    Likes Received:
    0
    Reputations:
    0
    В steelbot хорошая допиленная библиотека, но она тоже не позволяет поставить текст в xStatus. В ссылках, которые тс дал, я не увидел методов для установки текста в xStatus(
    Может кто-то знает такую библиотеку или в состоянии доделать это в WebIcqPro ?
     
  12. < OryPeЦ >

    < OryPeЦ > Banned

    Joined:
    10 Jan 2011
    Messages:
    0
    Likes Received:
    1
    Reputations:
    -5
    качал версию, не работала выкидывала ошибку.
    ща розобралса юзаю.