Авторизация ICQ

Больше
13 года 9 мес. назад - 13 года 9 мес. назад #1 от usbdrag
usbdrag создал тему: Авторизация ICQ
Добрый день.
Может кто-нибудь помочь с процедурой авторизации на сервере ICQ. Что-то я немного запутался. В документации по протоколу OSCAR написано следующее.
При подключении, с сервера приходит пакет вида: 0x2a 0x1 0xXX 0xXX 0x04 0x00 0x00 0x00 0x00 0x01. В ответ надо сформировать следующее:
0x00 0x00 0x00 0x01
0x01 "номер в асе"
0x02 "зашифрованнный пароль"
0x03 имя клиента
0x16 ид клиента
0x17 мажорная версия клиента
0x18 минорная версия клиента
0x19 еще какя-то версия клиента
0x1A номер билда
0x14 номер дистрибутива
0x0f "en"
0x0e "us"
Собираю пакет. Отправляю. А с сервера ответа нету.

Исходник на с++:
// MyICQ.cpp: определяет точку входа для консольного приложения.
//
#include "stdafx.h"

struct FlapHeader//6 bytes
{
	BYTE Sign;//Command Start always 0x2a
	BYTE ChID;//Channel ID
	WORD SeqNum;//Sequence Number
	WORD DFLen;//Data Field Length
};
struct FlapPacket
{
	FlapHeader Header;
	BYTE *Data;
};
struct SnacHeader
{
	WORD FamID;//Family ID
	WORD SubTypeID;//SubType ID
	BYTE Flags0;//Flags 0
	BYTE Flags1;//Flags 1
	DWORD ReqID;//Request ID
};
struct SnacPacket
{
	SnacHeader Header;
	BYTE Data[4096];
};
struct TLVPacket
{
	WORD TypeCode;
	WORD LengthCode;
	BYTE Data[4096];
};

HANDLE ReciveThreadHandle;
stack<FlapPacket> PacketQueue;
WORD Sequence;
bool EndWork;

void ReciveThread(void *Param);
void QueueThread(void *Param);

void AuthorizeInIcq(SOCKET Socket, FlapPacket *FirstPacket);
WORD SwapWord(WORD value);
DWORD SwapDWord(DWORD value);
void AddTLVStringToPacket(FlapPacket *inFlapPacket, WORD TypeCode, char *value);
void AddTLVWordToPacket(FlapPacket *inFlapPacket, WORD TypeCode, WORD value);
void AddTLVDWordToPacket(FlapPacket *inFlapPacket, WORD TypeCode, DWORD value);

int main(int argc, char* argv[])
{
	SOCKET ServerSock;
	sockaddr_in IcqServerAddress;

	//starting sockets
	WSADATA wsaData;
    int wsaret=WSAStartup(0x101,&wsaData);
	if(wsaret!=0)
    {
		cout<<"WSAStartup false"<<endl;
		cin.get();
        return 1;
    }

	//get ip of login.icq.com
	const char* const host = "login.icq.com";
	const in_addr* address;
    const hostent* host_info = 0 ;
    for( int attempt=0; (host_info==0) && (attempt<3); ++attempt )
		host_info = gethostbyname(host);
    if(host_info)
    {
		for( int i=0; host_info->h_addr_list[i]; ++i )
			address = (in_addr*)host_info->h_addr_list[i];
	}
	else
	{
		cout << "Cannot get ip of login.icq.com" << endl;
		return 1;
		cin.get();
	}

	//connecting to login.icq.com
	IcqServerAddress.sin_addr = *address;
	cout << "IP of login.icq.com: " << inet_ntoa( IcqServerAddress.sin_addr ) << endl ;
	IcqServerAddress.sin_family=AF_INET;
	IcqServerAddress.sin_port=htons(5190);
	ServerSock=socket(AF_INET, SOCK_STREAM, 0);
	if(connect(ServerSock,(sockaddr*)&(IcqServerAddress),sizeof(IcqServerAddress))==SOCKET_ERROR)
	{
		cout<<"Connection failed"<<endl;
		closesocket(ServerSock);
		ServerSock=0;
		WSACleanup();
		cin.get();
		return 1;
	}
	cout<<"Connected to login.icq.com"<<endl;

	//start analyse packets thread
	EndWork = false;
	ReciveThreadHandle = (HANDLE)_beginthread(QueueThread, 0, &ServerSock);
	//start recieve packets thread
	ReciveThreadHandle = (HANDLE)_beginthread(ReciveThread, 0, &ServerSock);
	while(!_kbhit())
	{
		Sleep(1);
	}
	EndWork = true;
	Sleep(10);

	//closing sockets and thread
	closesocket(ServerSock);
	WSACleanup();
	cout<<"Disconnected from login.icq.com";

	Sleep(1000);
	return 0;
}

void AuthorizeInIcq(SOCKET Socket, FlapPacket *FirstPacket)
{
	FlapPacket *AuthorizePacket = new FlapPacket;
	memset(AuthorizePacket, 0x00, sizeof(FlapPacket));
	char strUIN[] = "1234567";
	char strPassOpen[] = "xxxxxx";

	AuthorizePacket->Header.Sign = 0x2A;
	AuthorizePacket->Header.ChID = 0x01;
	Sequence = rand()%0x7FFF;
	AuthorizePacket->Header.SeqNum = Sequence;
	//copy original length and data
	AuthorizePacket->Header.DFLen = FirstPacket->Header.DFLen;
	WORD DataLen = SwapWord(AuthorizePacket->Header.DFLen);
	AuthorizePacket->Data = new BYTE[DataLen];
	memset(AuthorizePacket->Data, 0, DataLen);
	memcpy(AuthorizePacket->Data, FirstPacket->Data, DataLen);

	//create enrypted password
	char strPassEncr[32] = {0};//encrypted password
	BYTE PassTab[] = {0xF3,0x26,0x81,0xC4,0x39,0x86,0xDB,0x92,0x71,0xA3,0xB9,0xE6,0x53,0x7A,0x95,0x7C};
	for(unsigned int i=0; i<strlen(strPassOpen); i++)
		strPassEncr[i] = strPassOpen[i]^PassTab[i];

	//create FlapPacket with authorize information
	AddTLVStringToPacket(AuthorizePacket, 0x0001, strUIN);
	AddTLVStringToPacket(AuthorizePacket, 0x0002, strPassEncr);
	AddTLVStringToPacket(AuthorizePacket, 0x0003, "Self-made ICQ");
	AddTLVWordToPacket(AuthorizePacket, 0x0016, 0x010A);
	AddTLVWordToPacket(AuthorizePacket, 0x0017, 0x0007);
	AddTLVWordToPacket(AuthorizePacket, 0x0018, 0x0000);
	AddTLVWordToPacket(AuthorizePacket, 0x0019, 0x0001);
	AddTLVWordToPacket(AuthorizePacket, 0x001a, 0x04b5);
	AddTLVDWordToPacket(AuthorizePacket, 0x0014, 0x00000055);
	AddTLVStringToPacket(AuthorizePacket, 0x000f, "en");
	AddTLVStringToPacket(AuthorizePacket, 0x000e, "us");
	cout<<endl<<"<<====Out packet:"<<endl;
	for(unsigned int i=0; i<SwapWord(AuthorizePacket->Header.DFLen); i++)
		cout<<" 0x"<<hex<<(short int)AuthorizePacket->Data[i];
	cout<<endl<<endl;
	
	//send authorize information to server
	unsigned int SizeData = send(Socket, (const char*)AuthorizePacket, SwapWord(AuthorizePacket->Header.DFLen), 0);
	if(SizeData == SOCKET_ERROR)
	{
		cout<<"Error while sending authorize packet"<<endl;
		if(AuthorizePacket) delete AuthorizePacket;
		return;
	}
	cout<<"Authorize packet was sended"<<endl;

	if(AuthorizePacket) delete AuthorizePacket;
}

WORD SwapWord(WORD value)
{
	return ((value & 0x00FF) << 8) + ((value & 0xFF00) >> 8);
}

DWORD SwapDWord(DWORD value)
{
	DWORD Result=0;
	__asm
	{
		MOV EAX, value
		BSWAP EAX
		MOV Result,EAX
	};
	return Result;
}

void AddTLVStringToPacket(FlapPacket *inFlapPacket, WORD TypeCode, char *value)
{
	//create tlvpacket
	TLVPacket Result;
	memset((void*)&Result, 0, sizeof(TLVPacket));
	Result.TypeCode = SwapWord(TypeCode);
	Result.LengthCode = SwapWord(strlen(value));
	memcpy((void*)Result.Data, (void *)value, strlen(value));

	//insert tlv in the end
	WORD DataLen = SwapWord(inFlapPacket->Header.DFLen);
	WORD SizeOfTLV = 2 + 2 + SwapWord(Result.LengthCode);
	BYTE *Copy = new BYTE[DataLen];
	memcpy(Copy, inFlapPacket->Data, DataLen);
	inFlapPacket->Data = new BYTE[DataLen + SizeOfTLV];
	memcpy(inFlapPacket->Data, Copy, DataLen);
	memcpy(inFlapPacket->Data+DataLen, (void*)&Result, SizeOfTLV);
	inFlapPacket->Header.DFLen = SwapWord(DataLen + SizeOfTLV);
	delete [] Copy;
}

void AddTLVWordToPacket(FlapPacket *inFlapPacket, WORD TypeCode, WORD value)
{
	//create tlvpacket
	TLVPacket Result;
	memset((void*)&Result, 0, sizeof(TLVPacket));
	Result.TypeCode = SwapWord(TypeCode);
	Result.LengthCode = SwapWord(sizeof(WORD));
	WORD SwapValue = SwapWord(value);
	memcpy((void*)Result.Data, (void *)&SwapValue, sizeof(WORD));

	//insert tlv in the end
	WORD DataLen = SwapWord(inFlapPacket->Header.DFLen);
	WORD SizeOfTLV = 2 + 2 + SwapWord(Result.LengthCode);
	BYTE *Copy = new BYTE[DataLen];
	memcpy(Copy, inFlapPacket->Data, DataLen);
	inFlapPacket->Data = new BYTE[DataLen + SizeOfTLV];
	memcpy(inFlapPacket->Data, Copy, DataLen);
	memcpy(inFlapPacket->Data+DataLen, (void*)&Result, SizeOfTLV);
	inFlapPacket->Header.DFLen = SwapWord(DataLen + SizeOfTLV);
	delete [] Copy;
}

void AddTLVDWordToPacket(FlapPacket *inFlapPacket, WORD TypeCode, DWORD value)
{
	//create tlvpacket
	TLVPacket Result;
	memset((void*)&Result, 0, sizeof(TLVPacket));
	Result.TypeCode = SwapWord(TypeCode);
	Result.LengthCode = SwapWord(sizeof(DWORD));
	DWORD SwapValue = SwapDWord(value);
	memcpy((void*)Result.Data, (void *)&SwapValue, sizeof(DWORD));

	//insert tlv in the end
	WORD DataLen = SwapWord(inFlapPacket->Header.DFLen);
	WORD SizeOfTLV = 2 + 2 + SwapWord(Result.LengthCode);
	BYTE *Copy = new BYTE[DataLen];
	memcpy(Copy, inFlapPacket->Data, DataLen);
	inFlapPacket->Data = new BYTE[DataLen + SizeOfTLV];
	memcpy(inFlapPacket->Data, Copy, DataLen);
	memcpy(inFlapPacket->Data+DataLen, (void*)&Result, SizeOfTLV);
	inFlapPacket->Header.DFLen = SwapWord(DataLen + SizeOfTLV);
	delete [] Copy;
}

void ReciveThread(void *Param)
{
	//reciving first packet
	SOCKET *Socket = (SOCKET*)Param;
	unsigned char Buf[4096]={0};
	unsigned int SizeData;
	FlapPacket mFlapPacket;

	while(1)
	{
		memset((void*)&mFlapPacket, 0, sizeof(FlapPacket));
		if(EndWork) return;
		//get packet from login.icq.com
		SizeData = recv(*Socket, (char*)Buf, sizeof(Buf), 0);
		if(SizeData == SOCKET_ERROR)
		{
			cout<<"Recive thread closed"<<endl;
			EndWork = true;
			return;
		}
		//get flap header from first packet
		memcpy((void*)&mFlapPacket.Header, Buf, sizeof(FlapHeader));//read 6 bytes
		//get flap data size
		WORD DataLen = SwapWord(mFlapPacket.Header.DFLen);
		mFlapPacket.Data = new BYTE[DataLen];
		memset(mFlapPacket.Data, 0, DataLen);
		//get flap data
		memcpy((void*)mFlapPacket.Data, Buf+sizeof(FlapHeader), DataLen);

		//copy packet to queue
		PacketQueue.push(mFlapPacket);
	}
}

void QueueThread(void *Param)
{
	SOCKET *Socket = (SOCKET*)Param;
	while(1)
	{
		if(EndWork) return;
		if(PacketQueue.empty())
		{
			Sleep(1);
			continue;
		}
		//output source of packet
		BYTE *Mass = (BYTE*)&PacketQueue.top();
		cout<<endl<<"=====>>In packet:"<<endl;
		for(unsigned int i=0; i<6+SwapWord(PacketQueue.top().Header.DFLen); i++)
			cout<<" 0x"<<hex<<(unsigned short int)Mass[i];

		//is this first packet?
		if(PacketQueue.top().Data[0]==0x0 
			&& PacketQueue.top().Data[1]==0x0
			&& PacketQueue.top().Data[2]==0x0
			&& PacketQueue.top().Data[3]==0x1
			&& PacketQueue.top().Header.DFLen == 0x0400)
		{
			//do authorizing
			AuthorizeInIcq(*Socket, &PacketQueue.top());
			//PacketQueue.pop();
			//Sleep(1);
			//continue;
		}
		PacketQueue.pop();
		Sleep(1);
	}
}
Последнее редактирование: 13 года 9 мес. назад пользователем usbdrag.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад - 13 года 9 мес. назад #2 от cy6
cy6 ответил в теме RE: Авторизация ICQ
Лучшее средство для изучения протокола Oscar, это живая работающая программа.
В RnQ есть чудный инструмент - "Журнал событий" (который встроил еще Rejetto).
Открывать не пробовали? :)

Обмен с логин сервером состоит из следующей последовательности:
Connect to server
< SERVER FLAP (login) типа хэлло
> CLIENT FLAP (login) ответ превед
> CLIENT SNAC (17,06) засылаем UIN
< SERVER SNAC (17,07) получаем хэш для шифрования пароля
> CLIENT SNAC (17,02) засылаем параметры клиента, набор может быть разный, зависит от того какую версию ICQ клиента вы имитируете
< SERVER SNAC (17,03) получаем последнее слово логин сервера, если все ок, то это айпи+порт сервера и кукис, иначе код ошибки и даже ссылка где скачать клиент получше :laugh:

Если ответа с сервера нету (даже кода ошибки), это значит что вами отправлены неадекватные байты (не те, которые ожидает сервер), скорее всего даже формат пакетов неверный.

Первый вопрос, который бросается в глаза, просматривая исходник, учитывается ли у вас выравнивание структур в C++? :silly:
Последнее редактирование: 13 года 9 мес. назад пользователем cy6.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад - 13 года 9 мес. назад #3 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ
По поводу журнала событий. Посмотрел его уже после того как разместил свой пост. Там несколько по другому идет логирование, так как вы и написали:

25.06.2010 19:34:21.078> SERVER size:0010 (login flap)
2A 01 25 F2 00 04 00 00 00 01 *.%т......

25.06.2010 19:34:21.125> CLIENT size:0018 (login flap)
2A 01 67 EA 00 0C 00 00 00 01 80 03 00 04 00 10 *.gк......Ђ.....
00 00 ..

25.06.2010 19:34:21.234> (17,06) CLIENT size:0029 ref:00000001 Нет данных
2A 02 67 EB 00 17 00 17 00 06 00 00 00 00 00 01 *.gл............
00 01 00 09 xx xx xx xx xx xx xx xx xx ....xxxxxxxxx

Ответ-превед не понятен. 2A 01 67 EA 00 0C 00 00 00 01 80 03 00 04 00 10 00 00. Что внутри?
Выравнивание не учел похоже.
Последнее редактирование: 13 года 9 мес. назад пользователем usbdrag.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад #4 от cy6
cy6 ответил в теме RE: Авторизация ICQ
usbdrag писал(а):

Ответ-превед не понятен. 2A 01 67 EA 00 0C 00 00 00 01 80 03 00 04 00 10 00 00. Что внутри

Заголовок FLAP + DWORD 0x00000001 + TLV (0x8003, 0x00100000)
:)

Вы в курсе, кстати, что порядок байт может быть и LE и BE в разных случаях? :side:

Исходник скопипастил из среды и не обратил внимания что отступы поплыли. В коде все в норме. Или под выравниванием структур имелось в виду другое? Упаковка(pragma comment(push), pragma comment(pop))?


Это означает выравнивание структур, при которой размер структуры может быть совсем не такой, какой вы расчитывали, и даже хуже того, между полями могут оказаться лишние байты, если не вставить специфические (для конкретного компилятора) директивы, отменяющие всю эту радость. :silly:

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад - 13 года 9 мес. назад #5 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ
:blink:
Не знал.
Действительно отправляю на сервер пургу. :(
Проверка это показала.
BYTE *Mass1 = new BYTE[6+SwapWord(Packet->Header.DFLen)];
memcpy(Mass1, Packet, 6+SwapWord(Packet->Header.DFLen));
for(int i=0;i<6+SwapWord(Packet->Header.DFLen);i++)
  cout<<" 0x"<<(unsigned short int)Mass1[i];
cout<<endl;
delete [] Mass1;
Спасибо за помощь.
Последнее редактирование: 13 года 9 мес. назад пользователем usbdrag.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад - 13 года 9 мес. назад #6 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ
Сделал в тестовых целях авторизацию иначе. В итоге пакеты уходят на сервер, но результат тотже(в ответ ничего не приходит). После некоторого интервала меня отбрасывет сервером. Вставил проверку отправленного содержимого. Может, между посылками пакетов нужно выдержать паузу?
//начало куска======================================
unsigned char Buf[4096]={0};
FlapPacket mFlapPacket;
memset((void*)&mFlapPacket, 0, sizeof(FlapPacket));
//get packet from login.icq.com
int SizeData = recv(ServerSock, (char*)Buf, sizeof(Buf), 0);
if(SizeData == SOCKET_ERROR)
{
	cout<<"recv error 1"<<endl;
	cin.get();
	return 0;
}
cout<<"Recived packet"<<endl;
for(int i=0;i<SizeData;i++)
	cout<<" 0x"<<hex<<(unsigned short int)Buf[i];
cout<<endl;

AuthorizeInIcq(ServerSock);

memset(Buf, 0x00, 4096);
SizeData = recv(ServerSock, (char*)Buf, sizeof(Buf), 0);
if(SizeData == SOCKET_ERROR)
{
	if(WSAGetLastError()==10054) cout<<"Server closed connection";
	closesocket(ServerSock);
	WSACleanup();
	cin.get();
	return 0;
}
cout<<"Recived packet"<<endl;
for(int i=0;i<SizeData;i++)
        cout<<" 0x"<<hex<<(unsigned short int)Buf[i];
cout<<endl;
//конец куска======================================

void AuthorizeInIcq(SOCKET Socket)
{
	BYTE HelloPacket[18]={0x2a,0x01,0x65,0xea,0x00,0x0c,0x00,0x00,0x00,0x01,0x80,0x03,0x00,0x04,0x00,0x10,0x00};
	unsigned int SizeData = send(Socket, (const char*)HelloPacket, 18, 0);
	if(SizeData == SOCKET_ERROR)
	{
		cout<<"Error while sending packet"<<endl;
		return;
	}
	cout<<"Send packet"<<endl;
	for(int i=0;i<18;i++)
		cout<<" 0x"<<hex<<(unsigned short int)HelloPacket[i];
	cout<<endl;

	FlapPacket *Packet = new FlapPacket;
	memset(Packet, 0x00, sizeof(FlapPacket));
	char strUIN[] = "123456789";
	Packet->Header.Sign = 0x2A;
	Packet->Header.ChID = 0x02;
	Packet->Header.SeqNum = 0xeb65;//0x65eb
	Packet->Header.DFLen = 0x0a00;//0x00a0
	Packet->Data = new BYTE[10];
	memset(Packet->Data, 0, 10);
	Packet->Data[0]=0x00;
	Packet->Data[1]=0x17;
	Packet->Data[2]=0x00;
	Packet->Data[3]=0x06;
	Packet->Data[4]=0x00;
	Packet->Data[5]=0x00;
	Packet->Data[6]=0x00;
	Packet->Data[7]=0x00;
	Packet->Data[8]=0x00;
	Packet->Data[9]=0x01;
	AddTLVStringToPacket(Packet, 0x0001, strUIN);
	BYTE Mass[29]={0};
	memcpy(Mass, (void*)&Packet->Header, sizeof(FlapHeader));
	memcpy(Mass+6, (void*)Packet->Data, 23);

	SizeData = send(Socket, (const char*)Mass, 18, 0);
	if(SizeData == SOCKET_ERROR)
	{
		cout<<"Error while sending packet"<<endl;
		return;
	}
	cout<<"Send packet"<<endl;
	for(int i=0;i<29;i++)
		cout<<" 0x"<<hex<<(unsigned short int)Mass[i];
	cout<<endl;

	delete Packet;
}

Результат выполения:
IP of login.icq.com: 64.12.202.116
Connected to login.icq.com
Recived packet
0x2a 0x1 0x15 0x10 0x0 0x4 0x0 0x0 0x0 0x1
Send packet
0x2a 0x1 0x65 0xea 0x0 0xc 0x0 0x0 0x0 0x1 0x80 0x3 0x0 0x4 0x0 0x10 0x0 0x0
Send packet
0x2a 0x2 0x65 0xeb 0x0 0x17 0x0 0x17 0x0 0x6 0x0 0x0 0x0 0x0 0x0 0x1 0x0 0x1 0x
0 0x9 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39

Разобрался.
SizeData = send(Socket, (const char*)Mass, 18, 0); не 18, а 29 байт отправлять надо было
:woohoo:
Последнее редактирование: 13 года 9 мес. назад пользователем usbdrag.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад #7 от cy6
cy6 ответил в теме RE: Авторизация ICQ
:)

Кстати, если интересуют исходники именно C++, то лучше всего глянуть Miranda IM.
Она тоже пакеты писать в файл умеет, если в настройках задать.

Что мне там не понравилось, это то что стандартный модуль ICQ (не ICQ мод) не поддерживает поддержку privacy (уровень видимости как в кип и крысе). Ну и многие функции написаны очень тяжело старым недобрым процедурным методом (он же pure C). Такой код очень тяжело переварить, в отличии от ООП, где каждый уровень абстракции реализует только свою часть работы.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад - 13 года 9 мес. назад #8 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ
Исходники миранды я смотрел. Даже прикрутил icq_packet.h и icq_packet.cpp вырезая куски констант и функций из разных файлов. Но поковырявшись - понял не очень много и отсоединил их от проекта. Так же пробовал IcqKid2. Тоже не работает. Конектится, но в ответ с сервера идет идет ошибка с ссыкой на скачивание ICQ-клиента.
После всего, реально помогло это: icq2000cc.hobi.ru/page1.html
Но там старый тип авторизации, потому у меня и не работало.
А по поводу выравнивание структур - перегоняю структуру в массив по кускам и обратно. Может не красиво, но работает.

Еще вопрос появился. Как применить хеш к паролю?
Последнее редактирование: 13 года 9 мес. назад пользователем usbdrag.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад #9 от cy6
cy6 ответил в теме RE: Авторизация ICQ
usbdrag писал(а):

А по поводу выравнивание структур - перегоняю структуру в массив по кускам и обратно. Может не красиво, но работает.

Еще вопрос появился. Как применить хеш к паролю?


И зачем так мучатся со структурами? :silly:
#pragma pack(push, 1)

typedef struct _TWndMethod
{
	LPVOID Method;
	LPVOID Object;
} TWndMethod;

#pragma pack(pop)

Проверенный рабочий код, компилированный под C++ VS2008, директивами устанавливается выравнивание в 1 байт, а затем отменяется.

Из кода &RQ/R&Q (FAuthKey (наш "хэш") получен от сервера)
function TICQSession.HashPassword: AnsiString;
var
  HashPwd: AnsiString;
  MD5Digest: TMD5Digest;
  MD5Context: TMD5Context;
const
  AIM_MD5_STRING: AnsiString = 'AOL Instant Messenger (SM)';
begin
  HashPwd := MD5Pass(Pwd);
  FillChar(MD5Digest, SizeOf(TMD5Digest), 0);
  MD5Init(MD5Context);
  MD5UpdateBuffer(MD5Context, PAnsiChar(FAuthKey), Length(FAuthKey));
  MD5UpdateBuffer(MD5Context, PAnsiChar(HashPwd), Length(HashPwd));
  MD5UpdateBuffer(MD5Context, PAnsiChar(AIM_MD5_STRING), Length(AIM_MD5_STRING));
  MD5Final(MD5Digest, MD5Context);
  SetLength(Result, Length(MD5Digest));
  StrPLCopy(@Result[1], PAnsiChar(@MD5Digest), Length(MD5Digest))
end;

Все ответы на вопросы есть в коде... :side:

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 9 мес. назад #10 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ
Спасибо за информацию. Буду двигаться дальше. :)
Медленно, но верно.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад #11 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ
Появились вопросы, на которые в коде ответа не увидел(может плохо смотрел :)):
1) Всегда ли длина cookie 256 символов?
2) Что такое 00 4C 00 00 в конце SNAC(17,02)?
3) Что за TLV 00 8E 00 01 00 сразу после Flap header'а и Snac'а в SNAC(17,03)?
4) SNAC(01, 03) содержит информацию о том какие сервисы поддерживает сервер? Если да, то что за сервисы соответствуют кодам: 0x00022, 0x0024, 0x0025?
5) Какой смысл в SNAC(01,15), содежащий адреса различных страниц на aol.com?
6) Как приходят c сервера SNAC(01,15) и SNAC(01,18), а также другие пакеты если их более одного? Одновременно или по очереди?
7) Если начинается потоковый обмен пакетами, то в какой момент(после приема/отправки какого пакета) он стартует?

Извиняюсь, если некоторые вопросы не очень умны.
Информацию по OSCAR, частично, черпаю тут: iserverd.khstu.ru/oscar

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад - 13 года 8 мес. назад #12 от cy6
cy6 ответил в теме RE: Авторизация ICQ
usbdrag писал(а):

Появились вопросы, на которые в коде ответа не увидел(может плохо смотрел :)):
1) Всегда ли длина cookie 256 символов?
2) Что такое 00 4C 00 00 в конце SNAC(17,02)?
3) Что за TLV 00 8E 00 01 00 сразу после Flap header'а и Snac'а в SNAC(17,03)?
4) SNAC(01, 03) содержит информацию о том какие сервисы поддерживает сервер? Если да, то что за сервисы соответствуют кодам: 0x00022, 0x0024, 0x0025?
5) Какой смысл в SNAC(01,15), содежащий адреса различных страниц на aol.com?
6) Как приходят c сервера SNAC(01,15) и SNAC(01,18), а также другие пакеты если их более одного? Одновременно или по очереди?
7) Если начинается потоковый обмен пакетами, то в какой момент(после приема/отправки какого пакета) он стартует?

Извиняюсь, если некоторые вопросы не очень умны.
Информацию по OSCAR, частично, черпаю тут: iserverd.khstu.ru/oscar

Наконец то, хоть кто то открыл обсуждение протокола Oscar столь серьезно. :)
До некоторого времени тут была доступна информация по протоколу от первоисточника. Сегодня не без огорчения увидела, что его закрыли незарегистрированным пользователям. :dry: Хотя, что то подобное ожидалось, и копия валяется у меня где то на дисках.

1) Кукис в этой версии протокола всегда ровно 256 байт.
2) Из самого свежего кода миранды:
packDWord(&packet, 0x004C0000); // empty TLV(0x4C): unknown
3) Это признак, является ли сервер SSL, параметры которого далее в пакете, если не ошибаюсь.
4) Да. Поскольку протокол проприетарный, все основные знания получены снифингом и реверсом кода. Хорошо если две трети его используется в альтернативных клиентах. Мне и самой интересны подробности этих сервисов. :silly:
5) Для альтернативных клиентов никакого, а для оригинального ICQ скорее всего значительные, если взглянуть на ссылки внимательнее.
6) Немного не поняла вашего вопроса. TCP протокол, это потоковый протокол, поток байт от "подключения" до "разрыва". Как этот поток делится на физические пакеты низкого уровня, знать для нашей задачи необязательно. Достаточно склеивать все данные, НЕ предполагая, что какие то куски дойдут именно так, как мы их сами делили на порции. Пакет Oscar состоит из заголовка и данных, независимо от длины. Обычно, пакеты сервера приходят не просто так, а в ответ на пакеты (запросы) от клиента. Либо пакеты от сервера приходят как "подписка", опять же с запроса клиента в процессе стадии инициализации. К последним относятся пакеты обновления rates (обычно после отправки сообщения), а также извещения о изменениях статуса buddy, например.
7) С момента подключения на порт логин сервера. Сервер сразу же отправляет FLAP "Hello".
Последнее редактирование: 13 года 8 мес. назад пользователем cy6.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад #13 от Пушкожук
Пушкожук ответил в теме RE: Авторизация ICQ
cy6 писал(а):

1) Кукис в этой версии протокола всегда ровно 256 байт.

А можно спросить, откуда такая информация? :)

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад - 13 года 8 мес. назад #14 от cy6
cy6 ответил в теме RE: Авторизация ICQ
Пушкожук писал(а):

cy6 писал(а):

1) Кукис в этой версии протокола всегда ровно 256 байт.

А можно спросить, откуда такая информация? :)

Из пакетов. :) За пару лет интереса к протоколу, другого значения не встречалось.

Но для сохранения в программе, у нас всегда есть длина TLV(0x0006). Думаю, что делать длину в виде константы, при таком раскладе, ни один разумный программер не станет. :P

Вот кстати, кажется перевод официальной документации по протоколу с AOL - oscar.asechka.ru/ .
Википедия рулит как всегда, см. основную статью Oscar .
Последнее редактирование: 13 года 8 мес. назад пользователем cy6.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад #15 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ

packDWord(&packet, 0x004C0000); // empty TLV(0x4C): unknown

Это я видел, потому и решил спросить - может кто знает.

До некоторого времени тут была доступна информация по протоколу от первоисточника. Сегодня не без огорчения увидела, что его закрыли незарегистрированным пользователям. Хотя, что то подобное ожидалось, и копия валяется у меня где то на дисках.

Можно получить как-нибудь? На почту, например? Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript.

6) Немного не поняла вашего вопроса. TCP протокол, это потоковый протокол, поток байт от "подключения" до "разрыва". Как этот поток делится на физические пакеты низкого уровня, знать для нашей задачи необязательно. Достаточно склеивать все данные, НЕ предполагая, что какие то куски дойдут именно так, как мы их сами делили на порции. Пакет Oscar состоит из заголовка и данных, независимо от длины. Обычно, пакеты сервера приходят не просто так, а в ответ на пакеты (запросы) от клиента. Либо пакеты от сервера приходят как "подписка", опять же с запроса клиента в процессе стадии инициализации. К последним относятся пакеты обновления rates (обычно после отправки сообщения), а также извещения о изменениях статуса buddy, например.

То есть, что вызывать поочередно функцию recv(), что пускать поток под это дело - все равно?
//вариант 1 один за одним
recv();
recv();
recv();
//вариант 2 поток
void *ReciveThread()
{
while(1)
{
recv();
Sleep(1);
}
}

Но для сохранения в программе, у нас всегда есть длина TLV(0x0006). Думаю, что делать длину в виде константы, при таком раскладе, ни один разумный программер не станет.

Пора браться за ум. :)

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад - 11 года 2 мес. назад #16 от cy6
cy6 ответил в теме RE: Авторизация ICQ
usbdrag писал(а):

До некоторого времени тут была доступна информация по протоколу от первоисточника. Сегодня не без огорчения увидела, что его закрыли незарегистрированным пользователям. Хотя, что то подобное ожидалось, и копия валяется у меня где то на дисках.

Можно получить как-нибудь? На почту, например? Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript.


Выложила на свою страницу c6lab.org/oscar/OSCAR_Protocol.rar

То есть, что вызывать поочередно функцию recv(), что пускать поток под это дело - все равно?

//вариант 1 один за одним
recv();
recv();
recv();
//вариант 2 поток
void *ReciveThread()
{
while(1)
{
recv();
Sleep(1);
}
}

И снова не поняла. :side:

Как написать сетевой ввод вывод - дело личного вкуса.
Будете ли использовать блокирующий ввод/вывод, проверки select, отдельные thread, а возможно даже события Windows и т.д. В миранде сделан серверный thread, в котором организован прием данных и отправка на обработку. В процессе обработки принятых данных генерируются данные для отправки.

Смысл в том, что все байты полученные recv() должны складываться в некий буфер накопитель, который выполняет роль склеивателя фрагментов. Обработчик пакетов оскар должен проверять этот буфер, на наличие в нем целого FLAP, извлекать и обрабатывать его.

Сам протокол, это не только формат пакетов (FLAP и вложенные SNAC, TLV), но и некая последовательность запросов и ожидаемые варианты ответов. Разговор двух программ между собой. :)
Последнее редактирование: 11 года 2 мес. назад пользователем cy6.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад #17 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ
Спасибо. Архив с описанием подобрал.

А по поводу приема-передачи, наверное, неверно подбираю слова. Постараюсь изложить по другому.
Пример пакетного лога RnQ:
25.06.2010 16:58:07.267> (01,17) CLIENT size:0060 ref:00000001 i'm icq
2A 02 67 65 00 36 00 01 00 17 00 00 00 00 00 01 *.ge.6..........
00 22 00 01 00 01 00 04 00 02 00 01 00 03 00 01 ."..............
00 04 00 01 00 06 00 01 00 09 00 01 00 0B 00 01 ................
00 13 00 04 00 15 00 01 00 0A 00 01 ............

25.06.2010 16:58:07.267> (01,15) SERVER size:0517 ref:C978CC16 Нет данных
2A 02 FA CE 01 FF 00 01 00 15 00 00 C9 78 CC 16 *.ъО.я......ЙxМ.
00 03 00 24 68 74 74 70 3A 2F 2F 61 70 69 2E 6F ...$http://api.o
73 63 61 72 2E 61 6F 6C 2E 63 6F 6D 2F 6C 69 66 scar.aol.com/lif
65 73 74 72 65 61 6D 2F 00 04 00 2A 68 74 74 70 estream/...*http
3A 2F 2F 6F 2E 61 6F 6C 63 64 6E 2E 63 6F 6D 2F ://o.aolcdn.com/
6C 69 66 65 73 74 72 65 61 6D 2F 63 6C 69 65 6E lifestream/clien
74 2F 66 75 6C 6C 00 05 00 28 68 74 74 70 3A 2F t/full...(http:/
2F 6F 2E 61 6F 6C 63 64 6E 2E 63 6F 6D 2F 6C 69 /o.aolcdn.com/li
66 65 73 74 72 65 61 6D 2F 63 6C 69 65 6E 74 2F festream/client/
6D 65 00 07 00 25 68 74 74 70 3A 2F 2F 6F 2E 61 me...%http://o.a
6F 6C 63 64 6E 2E 63 6F 6D 2F 6C 69 66 65 73 74 olcdn.com/lifest
72 65 61 6D 2F 70 68 6F 74 6F 2F 00 08 00 27 68 ream/photo/...'h
74 74 70 3A 2F 2F 6C 69 66 65 73 74 72 65 61 6D ttp://lifestream
2E 61 6F 6C 2E 63 6F 6D 2F 70 68 6F 74 6F 2F 75 .aol.com/photo/u
70 6C 6F 61 64 3F 00 09 00 19 68 74 74 70 3A 2F pload?....http:/
2F 61 70 69 2E 6F 73 63 61 72 2E 61 6F 6C 2E 63 /api.oscar.aol.c
6F 6D 2F 00 0A 00 22 68 74 74 70 3A 2F 2F 6C 69 om/..."http://li
66 65 73 74 72 65 61 6D 2E 61 6F 6C 2E 63 6F 6D festream.aol.com
2F 73 65 74 74 69 6E 67 73 00 0B 00 21 68 74 74 /settings...!htt
70 3A 2F 2F 6C 69 66 65 73 74 72 65 61 6D 2E 61 p://lifestream.a
6F 6C 2E 63 6F 6D 2F 73 74 72 65 61 6D 2F 00 0C ol.com/stream/..
00 1D 68 74 74 70 73 3A 2F 2F 6D 79 2E 73 63 72 ..my.scr
65 65 6E 6E 61 6D 65 2E 61 6F 6C 2E 63 6F 6D 00 eenname.aol.com.
0D 00 24 68 74 74 70 3A 2F 2F 61 62 61 70 69 2E ..$http://abapi.
61 62 77 65 62 2E 61 6F 6C 2E 63 6F 6D 2F 41 42 abweb.aol.com/AB
57 65 62 41 70 69 2F 00 0E 00 1D 68 74 74 70 73 WebApi/....https
3A 2F 2F 64 62 72 2E 73 65 72 76 69 63 65 73 2E ://dbr.services.
61 6F 6C 2E 63 6F 6D 2F 00 0F 00 1A 68 74 74 70 aol.com/....http
3A 2F 2F 6C 69 66 65 73 74 72 65 61 6D 2E 61 6F ://lifestream.ao
6C 2E 63 6F 6D 2F 00 10 00 2B 68 74 74 70 3A 2F l.com/...+http:/
2F 6C 69 66 65 73 74 72 65 61 6D 2E 61 6F 6C 2E /lifestream.aol.
63 6F 6D 2F 70 68 6F 74 6F 2F 6C 69 66 65 73 74 com/photo/lifest
72 65 61 6D 2F ream/

25.06.2010 16:58:07.423> (01,18) SERVER size:0076 ref:C978CD5C you're icq
2A 02 FA CF 00 46 00 01 00 18 00 00 C9 78 CD 5C *.ъП.F......ЙxН\
00 01 00 04 00 02 00 01 00 03 00 01 00 04 00 01 ................
00 06 00 01 00 08 00 01 00 09 00 01 00 0A 00 01 ................
00 0B 00 01 00 0C 00 01 00 13 00 05 00 15 00 02 ................
00 22 00 01 00 24 00 01 00 25 00 01 ."...$...%..

В коде я, например, буду принимать данные, вызывая два раза recv(). Получу ли я так же как выше сначала SNAC(01,15), потом SNAC(01,18)? Или может прийти и наоборот?

Или вот пример:
25.06.2010 16:58:08.079> (02,03) SERVER size:0046 ref:00000001 location rights
2A 02 FA D3 00 28 00 02 00 03 00 00 00 00 00 01 *.ъУ.(..........
00 01 00 02 10 00 00 02 00 02 00 12 00 05 00 02 ................
00 80 00 03 00 02 00 0A 00 04 00 02 10 00 .Ђ............

25.06.2010 16:58:08.079> (03,03) SERVER size:0040 ref:00000001 buddy rights
2A 02 FA D4 00 22 00 03 00 03 00 00 00 00 00 01 *.ъФ."..........
00 02 00 02 0B B8 00 01 00 02 03 E8 00 03 00 02 .....ё.....и....
02 00 00 04 00 02 00 A0 ....... 

25.06.2010 16:58:08.079> (04,05) SERVER size:0032 ref:00000001 im rights
2A 02 FA D5 00 1A 00 04 00 05 00 00 00 00 00 01 *.ъХ............
00 04 00 00 00 03 02 00 03 84 03 E7 00 00 03 E8 .........„.з...и

25.06.2010 16:58:08.126> (09,03) SERVER size:0028 ref:00000001 bos rights
2A 02 FA D6 00 16 00 09 00 03 00 00 00 00 00 01 *.ъЦ............
00 02 00 02 03 E8 00 01 00 02 03 E8 .....и.....и

Сервер посылает в данном случае один за одним уже 4 пакета. Идет ли стогая последовательность отправляемых пакетов сервером(SNAC(02,03),SNAC(03,03),SNAC(04,05),SNAC(09,03) ) или нет? Приму я их так же как в логе?
Если нет строгой последовательности приема, то мне надо анализировать какой SNAC пришел и исходя из этого думать что делать дальше.

И если уж, упомянул SNAC(01,15) и SNAC(01,18), то что сервер пытается мне сказать установив значение номера запроса(Request ID) в SNAC-заголовке не равным 0x00000001?
По ссылке что, вы дали ранее oscar.asechka.ru/ сказано следующее:
"Номер запроса использует для объединения запросов и ответов и так же может использоваться для определения направления трафика." :blink:

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад - 13 года 8 мес. назад #18 от cy6
cy6 ответил в теме RE: Авторизация ICQ
usbdrag писал(а):

А по поводу приема-передачи, наверное, неверно подбираю слова. Постараюсь изложить по другому.
...
В коде я, например, буду принимать данные, вызывая два раза recv(). Получу ли я так же как выше сначала SNAC(01,15), потом SNAC(01,18)? Или может прийти и наоборот?
...
Сервер посылает в данном случае один за одним уже 4 пакета. Идет ли стогая последовательность отправляемых пакетов сервером(SNAC(02,03),SNAC(03,03),SNAC(04,05),SNAC(09,03) ) или нет? Приму я их так же как в логе?
Если нет строгой последовательности приема, то мне надо анализировать какой SNAC пришел и исходя из этого думать что делать дальше.

Функция из WinSock recv() вызывается столько раз, сколько нужно (обычно пока не происходит разрыв соединения по чьей либо инициативе). Принятые байты нас интересуют только в контексте принятых целых FLAP.

Так как мы обсуждаем протокол, то в передаче пакетов есть совершенно логичная последовательность. Но некоторые пакеты могут просто придти, потому что сервер так решил. :silly: И, как я уже говорила, есть пакеты, которые являются ответами на пакеты клиента.

запрос-ответ
SNAC(02,02)-SNAC(02,03)
SNAC(03,02)-SNAC(03,03)
SNAC(04,04)-SNAC(04,05)
SNAC(09,02)-SNAC(09,03)

Каждый запрос является неделимой логической единицей, на которые сервер формирует такие же неделимые логические единицы - ответы, причем обычно в порядке поступления запросов.

И если уж, упомянул SNAC(01,15) и SNAC(01,18), то что сервер пытается мне сказать установив значение номера запроса(Request ID) в SNAC-заголовке не равным 0x00000001?
По ссылке что, вы дали ранее oscar.asechka.ru/ сказано следующее:
"Номер запроса использует для объединения запросов и ответов и так же может использоваться для определения направления трафика." :blink:

Айди запроса используется во всех SNAC, как ссылка на запрос.
То есть, ответ на некий запрос будет с тем же айди, что был в запросе.
Последнее редактирование: 13 года 8 мес. назад пользователем cy6.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
13 года 8 мес. назад - 13 года 8 мес. назад #19 от usbdrag
usbdrag ответил в теме RE: Авторизация ICQ
Родилось еще несколько вопросов.
1) Иногда бывает сервер в ответ на запрос присылает 0 байт! Пробую снова - все в норме. Такое бывает?
2) SNAC(01, 15) от сервера ни разу не получил. Это нормально?
3) Нужно ли сохранять информацию полученную из этих пакетов:
SNAC(01, 03), SNAC(01, 18)?
3.1) SNAC(01, 03) - сервер говорит какие сервисы держит.
SNAC(01, 18) - сервер снова говорит какие сервисы держит и добавляет их версии.
Может что-то не понимаю, но где смысл? Почему сразу нельзя послать сервисы с их версиями?
4) Всегда ли в SNAC(02, 03) TLV идут не по порядку их номеров?
Пример из лога:
25.06.2010 16:58:08.079> (02,03) SERVER size:0046 ref:00000001 location rights
2A 02 FA D3 00 28 00 02 00 03 00 00 00 00 00 01 *.ъУ.(..........
00 01 00 02 10 00 00 02 00 02 00 12 00 05 00 02 ................
00 80 00 03 00 02 00 0A 00 04 00 02 10 00 .Ђ............
5) Какие UUID мне понадобятся для самого минимального функционала (SNAC(02,04))?
Приведенного ниже будет достаточно?
BYTE UUID_SupportICQ[16] = {0x09, 0x46,0x13, 0x4D, 0x4C, 0x7F, 0x11, 0xD1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00};
6)В SNAC(03, 02), достаточно одного флага INITIAL_DEPARTS в TLV(05), который говорит серверу о том, что мы принимаем сообщения об уходе/приходе друзей?
Пример из лога:
25.06.2010 16:58:07.751> (03,02) CLIENT size:0022 ref:00000001 ask buddy rights
2A 02 67 69 00 10 00 03 00 02 00 00 00 00 00 01 *.gi............
00 05 00 02 00 03 ......
7) Вы говорили, что сервер может прислать что-нибудь "Не по плану"("Но некоторые пакеты могут просто придти, потому что сервер так решил"). Такая "посылка" во время процедуры авторизации прийти может?
Спрашиваю по следующей причине. Все этапы авторизации хотелось пройти, последовательно т.е. запрос-ответ, запрос-ответ.
8) SNAC(01, 07) - Лимит обращений. Цитата с сайта oscar.asechka.ru/#OSERVICE: "Фактические параметры для формул не известны :blink: , так как они могут время от времени изменяться и различны в зависимости от текущего уровня предупреждения и других вещей. Клиент может отправлять около одного IM-сообщения каждые две секунды, не будучи ограниченным лимитами".
То есть не используя никаких формул, ставлю ограничение - не более одного сообщения в 2 секунды? Интересно...
Как мне потом эту информацию использовать, если "Фактические параметры для формул не известны..." и нужно ли?

Никак не дойду до конца авторизации.

Добавил позже:
9) SNAC(13,03). Есть его нормальное описание где-нибудь?
В нем TLV(4), TLVs(2,3,5,6,7), TLVs(8,B,C,D,E)
Последнее редактирование: 13 года 8 мес. назад пользователем usbdrag.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
12 года 2 мес. назад #20 от noober
noober ответил в теме RE: Авторизация ICQ
если ветка ещё живая.хелпните плиз.
короче написал на с++ авторизацию на icq.com но почему то всё время ответ приходит с кодом 0005 неверен логин или пароль.
использовал авторизацию через hmac_md5(я правильно понимаю ведь по этому алгоритму хешируется пароль?).
всё перепроверил уже много раз хеш считает правильно пароль правильно посылает вроде то что нужно...
возможно я пароль захешировал не потому алгоритму?

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Модераторы: dekRapid D
Время создания страницы: 0.519 секунд
Работает на Kunena форум