Автор: Alexander
Vaga WEB-сайт: http://icq2000cc.hobi.ru
Перед рассмотрением работы обработчика AuthorizePart надо немного поговорить
и о протоколе.
Перед тем, как подключиться к ICQ-серверу и начать работать мы должны пройти
авторизацию на Authorization Server. Его адрес - login.icq.com:5190.
Необходимо:
- соединиться с Authorization Server;
- передать ему пакет с UINом и паролем;
- получить от него IP-адрес и порт основного сервера и Cookie (256 байт
случайных данных). Cookie - это будет наш пропуск при последующем (после
авторизации) коннекте к основному рабочему серверу;
- разьединиться с Authorization Server.
Именно к Authorization Server инициируется соединение в процедуре icq_Login.
Сервер отвечает нам маленьким пакетом:
| FLAP |
| Command Start |
2A |
| Channel ID |
01 |
| Sequence Number |
XX XX |
| Data Field Length |
00 04 |
| Data |
00 00 00 01 |
| |
В нем только лишь 00 00 00 01. Для нас - это сигнал начать передачу пакета с
авторизационными данными (с UINом и паролем).
Сейчас уже пора разобраться и с форматом блока данных FLAP-пакета.
Можно сказать, что показанный выше пакет совсем не имеет никакой структуры:
просто DWORD и все. В большинстве случаев в FLAP-пакете размещены данные,
которые упакованы еще в один протокол: т.н. SNAC. В этом случае пакет данных
выглядит так:
| FLAP |
| Command Start |
2A |
| Channel ID |
02 |
| Sequence Number |
word |
| Data Field Length |
word |
| SNAC |
| Family ID |
word |
| SubType ID |
word |
| Flags[0] |
byte |
| Flags[1] |
byte |
| Request ID |
dword |
| SNAC Data |
variable |
| |
|
| |
- SNAC
- SNAC - это обычное содержимое блока данных FLAP-пакета в основной рабочей
фазе соединения. Т.е. SNACи посылаются только через Сhannel ID = 2.
В любом FLAP-пакете может находиться только один пакет SNAC.
Прием (анализ) и передача SCACов - это то основное, что предстоит делать,
чтобы реализовать все функции ICQ-клиента. Будь то передача списка контактов,
или изменение нашего статуса, или получение и передача сообщений, или запрос
информации о любом клиенте, для любого запроса и ответа на него есть свой SNAC
(FamilyID, SubTypeID). Из сказанного видно, что вся смысловая информация
помещена в SNACи. И UINы, и никнэймы, и и-мэйлы с хоумпэйджами. Конечно же они
не просто так накиданы в SNACи. Они там размещены в юнитах, которые называются
TLV.
- TLV
- TLV дословно означает - "Type, Length, Value" ("Тип, Длина, Значение"). Его
структура такая:
| TLV |
| (T)ype code |
word |
| (L)ength code |
word |
| (V)alue field |
variable length |
| |
В TLV упаковывается все, что используется в ICQ-протоколе: текстовые строки,
байты, слова, двойные слова, другие массивы и т.д. и т.п.. На тип содеожимого
TLV указывает Type code. Чаще всего TLV располагаются внутри SNACов, но это не
является обязательным условием. Они могут также напрямую использоваться в блоке
данных FLAP-пакета. Именно напрямую (т.е. без использования SNACов) TLV
задействованы на этапе авторизации.
Этот механизм мы и рассмотрим именно сейчас, т.к. мы соединены уже с
Authorization Server и получили от него добро в виде DWORD=00000001 на передачу
нашего UINа и пароля.
procedure TForm1.AuthorizePart(p:PPack); var ss : string; T : integer; tmp : PPack; begin // позиционируемся на начало блока данных, пропустив заголовок PacketGoto(p,sizeof(FLAP_HDR)); // если FLAP-данные содержат лишь 00000001, // то это самое начало сессии if (swap(p^.Len)=4)and (swap(p^.SNAC.FamilyID)=0)and (swap(p^.SNAC.SubTypeID)=1) then begin M(Memo,'< Authorize Server CONNECT'); // каждый раз, когда начинается новая TCP-сессия, // присваиваем SEQ случайное начальное значение SEQ := random($7FFF); // в ответ надо передать пакет с UINом и паролем // создаем объект-пакет типа PPack: в нем формируется // FLAP-заголовок с Chanel_ID=1 tmp := CreatePacket(1,SEQ); // сначала надо вставить такой же DWORD=00000001 // (еще надо помнить о порядке следования байтов в DWORD !!!) PacketAppend32(tmp,DSwap(1)); // далее в поле данных добавляются несколько TLV // это наш UIN - TLV(1) TLVAppendStr(tmp,$1,s(UIN)); // и закодированный пароль - TLV(2) TLVAppendStr(tmp,$2,Calc_Pass(PASSWORD)); // описывать содержимое других TLV особого смысла нет TLVAppendStr(tmp,$3, 'ICQ Inc. - Product of ICQ (TM).2000a.4.31.1.3143.85'); TLVAppendWord(tmp,$16,$010A); TLVAppendWord(tmp,$17,$0004); // 4 - для ICQ2000a TLVAppendWord(tmp,$18,$001F); TLVAppendWord(tmp,$19,$0001); TLVAppendWord(tmp,$1A,$0C47); TLVAppendDWord(tmp,$14,$00000055); TLVAppendStr(tmp,$0F,'en'); TLVAppendStr(tmp,$0E,'us'); // посылаем пакет через ClientSocket // (здесь tmp-пакет будет также и удален) PacketSend(tmp); M(Memo,'> Auth Request (Login)');
end else // на это сервер ответит так: // его ответ содержит TLV(1) - т.е. наш UIN if (TLVReadStr(p,ss)=1)and(ss=s(UIN))then begin // если это так, то считаем следующий TLV T := TLVReadStr(p,ss); case T of // если это TLV(5) - значит это адрес и порт основного сервера 5: g>begin // BOS-IP:PORT M(Memo,'< Auth Responce (COOKIE)'); // запоминаем и адрес и порт WorkAddress := copy(ss,1,pos(':',ss)-1); WorkPort := strtoint(copy(ss,pos(':',ss)+1, length(ss)-pos(':',ss))); // за ними должен быть и TLV(6) - т.н. COOKIE (256 байт) // принимаем его прямо в переменную sCOOKIE // (он пригодится при коннекте к основному серверу) if (TLVReadStr(p,sCOOKIE)=6) then begin; // COOKIE получен и значит пора разъединяться // формируем пустой пакет с Channel_ID=4 tmp:=CreatePacket(4,SEQ); // ChID=4 // который и передаем PacketSend(tmp); // закрываем свой ClientSocket OfflineDiscconnect1Click(self); // говорим себе, что авторизация пройдена isAuth := false; // настраиваем ClientSocket на адрес:порт // основного (BOS) сервера CLI.Address := WorkAddress; CLI.Host := ''; CLI.Port := WorkPort; M(Memo,''); M(Memo,'>>> Connecting to BOS: '+ss); // и коннектимся к нему CLI.Open; { ******************************************* } { в этом месте заканчивается этап авторизации } { ******************************************* } end; end; // а, например, в случае неверного UINа или пароля // мы получим TLV(4) и TLV(8) 4,8: begin M(Memo,'< Auth ERROR'); M(Memo,'TLV($'+inttohex(T,2)+') ERROR'); M(Memo,'STRING: '+ss); if pos('http://',ss)>0 then begin // и даже можем загрузить в браузер присланный нам URL // с описанием ошибки // Web.Navigate(ss); // {это навигатор с панели компонентов Делфи} end; TLVReadStr(p,ss); M(Memo,ss); // конечно же закрываем ClientSocket OfflineDiscconnect1Click(self); M(Memo,''); end; end; end; end;
|
После успешного прохождения авторизации, мы подключаемся к основному рабочему
серверу ICQ. Т.к. флажек isAuth уже сброшен, то диспетчер MainTTimer все пакеты
будет направлять на обработчик WorkPart. Его построение во многом схоже с
только, что рассмотренным обработчиком AuthorizePart.
В таком случае продолжим... |