Не для кого не секрет, что «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