Pull to refresh

Изучаем протокол MMP (Mail.ru агент) и пишем альтернативный клиент

Reading time4 min
Views25K
Не для кого не секрет, что «Mail.ru Агент» стал довольно популярным IM проектом. Здесь вам и поддержка ICQ, XMPP, голосовых звонков и даже отправка SMS, только вот компания Mail.ru совсем забыла о разработчиках.
Официальная документация протокола обмена данными Mail.ru Агент описывает версию протокола 1.7 реализованную в 2008 году. На данный момент сервер использует протокол версии 1.24.

Немного теории


На первый взгляд в написании сетевого клиента нет ничего сложного, но в сетевом программировании есть множество «подводных камней». Без понимания деталей работы TCP/IP практически невозможно написать эффективное и стабильное приложение.

Целостность передаваемых данных

Как известно TCP – потоковый протокол, и хотя данные передаются в IP-пакетах, размер пакета напрямую не связан с количеством данных переданных TCP. Поэтому нельзя с уверенностью сказать что при вызове recv мы получим заданное количество байт.
Для получения данных заданной длинны я использую такую функцию,
#define SEND 0
#define RECV 1
int (__stdcall *tcp_func)(SOCKET s,char* buf,int len,int flags);
// функция гарантирует прием/отправку данных заданной длинны len
int tcp_rs(unsigned char type,SOCKET s, void *buf, int len, int flags) {
	int total = 0;
	int n;
	*(void* *)&tcp_func=(type==SEND)?&send:&recv;
	while(total < len) {
		n = tcp_func(s, (char *)buf+total, len-total, flags);
		if(n>0) { total += n; }
		else if(n == 0) { 
			closesocket(s);
			return 0;
		}
		else {
			n=WSAGetLastError();
			closesocket(s);
			return (!n+1);
		}
	}
	return total;
}

которая в случае успеха возвращает количество принятых/переданных байт равных len, 0 в случае, если соединение было разорвано либо закрыто и (минус) номер ошибки, в случае неудачи вызова функции send/recv.

Сбои в сети

Так же необходимо помнить о том, что TCP не выполняет опрос соединения. В случае с блокирующими сокетами при крахе сервера (разрыва соединения, сбоя) ждать ответа мы будем «вечно», программа попросту «зависнет».
Одним из способов определения разрыва соединения является – таймер контроля работоспособности (keep-alive).
#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4)
#pragma pack(push,1) // отключаем выравнивание
typedef struct tcp_keepalive {
	DWORD  onoff;
	DWORD  keepalivetime;
	DWORD  keepaliveinterval;
} tcp_keepalive;
#pragma pack(pop)

//пример изменения интервалов времени посылки keep-alive
struct tcp_keepalive alive;
DWORD  dwSize;

alive.onoff = 1;
alive.keepalivetime = 5000;
alive.keepaliveinterval = 1000;

WSAIoctl(my_sock, SIO_KEEPALIVE_VALS, &alive, sizeof(alive),NULL, 0, &dwSize, NULL, NULL);

В нашем случае, если соединение будет не активно в течении 5 секунд, будет послано служебное сообщение, если на него не будет ответа, соединение закроется.

О протоколе


MMP бинарный асинхронный протокол. Бинарный означает, что данные передаются в виде пакетов определенной структуры:

// заколовок пакета
typedef struct mrim_packet_header_t
{
    unsigned int      magic;		// Magic
    unsigned int      proto;		// Версия протокола
    unsigned int      seq;		// Sequence
    unsigned int      msg;		// Тип пакета
    unsigned int      dlen; 		// Длина данных
    unsigned int	from;		// Адрес отправителя
    unsigned int	fromport;	// Порт отправителя
    unsigned char	reserved[16];	// Зарезервировано
}
mrim_packet_header_t;

// структура описываемая в документации, похожа на MFC, Delphi строки 
typedef struct LPS {
	unsigned int len;
	unsigned char *str;
} LPS;

Асинхронность здесь характеризуется тем, что сервер поддерживая постоянное соединение с различными интервалами времени шлет клиенту пакеты данных, получив которые клиент может (а в некоторых случаях должен) отреагировать и отправить серверу ответ.

Инициализирует соединение клиент, перед этим необходимо получить адрес «свободного» MMP сервера в текстовом формате ip:port, просто подключившись по адресу mrim.mail.ru. Официальный клиент версии 5.9 для подключения использует следующие порты: 2024, 80, 5190, 1863, 25, 110, 443.
Как сказано в официальной документации, после подключения по рекомендуемому адресу клиент должен послать пакет MRIM_CS_HELLO, дождаться MRIM_CS_HELLO_ACK, после чего отправить пакет авторизации, тут то и начинается самое интересное.

На самом деле

Начиная с версии 1.22 (Mail.ru агент 5.7) изменился метод авторизации. Теперь для авторизации необходимо послать пакет 0x1078 (MRIM_CS_LOGIN3) с параметрами

LPS ## login ## email авторизующегося пользователя
LPS ## md5 password ## пароль зашифрованный в md5
FFFFFFFF
и 1391 байт идентифицирующих клиента Mail.ru

На данный момент (версия протокола 1.24) протокол поддерживает обязательное шифрование. После получения пакета MRIM_CS_HELLO_ACK клиент посылает пакет 0x1086 и получает ответ 0x1087, после чего идет инициализация SSL соединения.
Но пока нам никто не запрещает использовать более ранние версии протокола.

Важной особенностью работы клиента является то, что свои запросы клиент может посылать только после получения от сервера пакета MRIM_CS_CONTACT_LIST2, который в свою очередь посылается после успешной авторизации.

Проекты


Весь код MMP клиента занял бы много места, поэтому я предлагаю Вам скачать и изучить его самостоятельно. В архиве MMPclient_sample.25.04.2011.rar, находятся исходники на языке Си и проект Visual Studio.
UPD: исходник на github

Для изучения протокола был написан небольшой SOCKS 5 сервер. Он позволяет в удобном виде отследить цепочку сообщений клиента и сервера. Исходники сервера и проект можете скачать здесь.

А так же:
  • Mail.ru Get Key — программа для получения ключа web-авторизации, который можно использовать для входа в ящик, по md5 хешу пароля
  • Mail.ru SMS sender — программа для отправки SMS сообщений
  • Bruteforce MMP — подбор паролей сервиса Mail.ru

Ссылки


Все версии Mail.ru Агента
Официальная документация по протоколу

Самоучитель игры на WINSOCK
Определение разрыва TCP-соединения
Эффективное программирование TCP/IP
Tags:
Hubs:
Total votes 52: ↑42 and ↓10+32
Comments48

Articles