Protokół Gadu-Gadu

© Copyright 2001 - 2003 Autorzy


Spis treści

  1. Protokół Gadu-Gadu
    1.1.  Format pakietów
    1.2.  Zanim się połączymy
    1.3.  Logowanie się
    1.4.  Zmiana stanu
    1.5.  Ludzie przychodzą, ludzie odchodzą
    1.6.  Wysyłanie wiadomości
    1.7.  Otrzymywanie wiadomości
    1.8.  Ping, pong
    1.9.  Rozłączenie
    1.10.  Katalog publiczny
  2. Usługi HTTP
    2.1.  Format danych
    2.2.  Rejestracja konta
    2.3.  Usunięcie konta
    2.4.  Zmiana hasła
    2.5.  Umieszczenie listy kontaktów na serwerze
    2.6.  Pobranie listy kontaktów z serwera
    2.7.  Usunięcie listy kontaktów z serwera
    2.8.  Przypomnienie hasła pocztą
  3. Bezpośrednie połączenia
  4. Autorzy

Informacje wstępne

Opis protokołu używanego przez Gadu-Gadu bazuje na doświadczeniach przeprowadzonych przez autorów oraz informacjach nadsyłanych przez użytkowników. Żaden klient Gadu-Gadu nie został skrzywdzony podczas badań. Reverse-engineering opierał się głównie na analizie pakietów przesyłanych między klientem a serwerem.

Najnowsza wersja opisu protokołu znajduje się pod adresem http://dev.null.pl/ekg/docs/protocol.html.


1. Protokół Gadu-Gadu

1.1. Format pakietów

Podobnie jak coraz większa ilość komunikatorów, Gadu-Gadu korzysta z protokołu TCP/IP. Każdy pakiet zawiera na początku dwa stałe pola:

struct gg_header {
	int type;	/* typ pakietu */
	int length;	/* długość reszty pakietu */
};

Wszystkie zmienne liczbowe są zgodne z kolejnością bajtów maszyn Intela, czyli Little-Endian.

Przy opisie struktur, założono, że char ma rozmiar 1 bajtu, short 2 bajtów, int 4 bajtów. Używając innych architektur niż i386 należy zwrócić szczególną uwagę na rozmiar typów zmiennych i kolejność znaków.

Pola, który znaczenie jest nieznane, lub nie do końca jasne, oznaczono przedrostkiem unknown.


1.2. Zanim się połączymy

Żeby wiedzieć, z jakim serwerem mamy się połączyć, należy poudawać przez chwilę przeglądarkę WWW i połączyć się z hostem appmsg.gadu-gadu.pl.

GET /appsvc/appmsg.asp?fmnumber=NUMER HTTP/1.0
Host: appmsg.gadu-gadu.pl
User-Agent: Mozilla/4.7 [en] (Win98; I)
Pragma: no-cache

Oryginalny klient może wysłać jeden z podanych identyfikatorów przeglądarki:

  • Mozilla/4.04 [en] (Win95; I ;Nav)
  • Mozilla/4.7 [en] (Win98; I)
  • Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)
  • Mozilla/4.0 (compatible; MSIE 5.0; Windows NT)
  • Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)
  • Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)

Nowsze wersje (od 4.6.2) korzystają z innego skryptu:

GET /appsvc/appmsg2.asp?fmnumber=NUMER&version=WERSJA&fmt=FORMAT&lastmsg=WIADOMOŚĆ
Host: appmsg.gadu-gadu.pl
User-Agent: PRZEGLĄDARKA
Pragma: no-cache

Gdzie NUMER jest numerem klienta, WERSJA jest wersją klienta postaci ,,A, B, C, D'' (na przykład ,,5, 0, 5, 107'' dla wersji 5.0.5 buildu 107), FORMAT określa czy wiadomość systemowa będzie przesyłana czystym tekstem (brak zmiennej "fmt") czy w HTMLu (jakakolwiek jej wartość), a WIADOMOŚĆ jest numerem ostatnio otrzymanej wiadomości systemowej. Na postawione w ten sposób zapytanie, serwer powinien odpowiedzieć na przykład tak:

HTTP/1.0 200 OK

0 217.17.41.84:8074 217.17.41.84

Pierwsze pole jest numerem wiadomości systemowej, a drugie i trzecie podają nam namiary na właściwy serwer. Jeśli serwer jest niedostępny, zamiast adresu IP jest zwracany tekst ,,notoperating''. Jeżeli połączenie z portem 8074 nie powiedzie się z jakichś powodów, można się łączyć na port 443.

Jeśli pierwsza liczba nie jest równa zero, zaraz po nagłówku znajduje się wiadomość systemowa, lub jeśli linia zaczyna się od znaku ,,@'', adres strony, którą należy otworzyć w przeglądarce.


1.3. Logowanie się

Po połączeniu się portem 8074 lub 443 serwera Gadu-Gadu, dostajemy pakiet typu 0x0001, który na potrzeby tego dokumentu nazwiemy:

#define GG_WELCOME 0x0001

Reszta pakietu zawiera liczbę, na podstawie której liczony jest hash z hasła klienta:

struct gg_welcome {
	int seed;	/* klucz szyfrowania hasła */
};

Kiedy mamy już tą wartość możemy odesłać pakiet logowania

#define GG_LOGIN 0x000c

Musimy podać kilka informacji:

struct gg_login {
	int uin;		/* numer klienta */
	int hash;		/* hash hasła */
	int status;		/* początkowy stan */
	int version;		/* wersja klienta */
	int local_ip;		/* mój adres ip */
	short local_port;	/* port, na którym słucham */
	char description[];	/* opis, nie musi wystąpić */
	int time;		/* czas, nie musi wystąpić */
};

Hash hasła w pierwszych wersjach był liczony w dość prosty sposób, ale niestety z którąś zmianą protokołu wprowadzono nowy algorytm:

int gg_login_hash(char *password, int seed)
{
	unsigned int x, y, z;

	y = seed;

	for (x = 0; *password; password++) {
		x = (x & 0xffffff00) | *password;
		y ^= x;
		y += x;
		x <<= 8;
		y ^= x;
		x <<= 8;
		y -= x;
		x <<= 8;
		y ^= x;

		z = y & 0x1f;
		y = (y << z) | (y >> (32 - z));
	}

	return y;
}

Liczba oznaczająca wersję może być jedną z poniższych:

WartośćWersje klientów
0x1b5.0.5
0x195.0.3
0x185.0.1, 5.0.0, 4.9.3
0x174.9.2
0x164.9.1
0x154.8.9
0x144.8.3, 4.8.1
0x114.6.10, 4.6.1
0x104.5.22, 4.5.21, 4.5.19, 4.5.17, 4.5.15
0x0f4.5.12
0x0b4.0.30, 4.0.29, 4.0.28, 4.0.25

Oczywiście nie są to wszystkie możliwe wersje klientów, lecz te, które udało się złapać na wolności. Najbezpieczniej będzie przedstawiać się jako ta wersja, której własności używamy. Wiadomo, że wersje 4.0 nie obsługiwały trybu ukrytego, tylko dla znajomych itd.

W najnowszej wersji protokołu jest dodatkowa maska na wersję:

#define GG_HAS_AUDIO_MASK 0x40000000

która mówi nam, że dany klient może prowadzić rozmowy głosowe.

Jeśli wszystko się powiedzie, dostaniemy w odpowiedzi pakiet typu:

#define GG_LOGIN_OK 0x0003

o zerowej długości, lub w przypadku błędu pakiet:

#define GG_LOGIN_FAILED 0x0009

Od wersji 4.9.3 (protokół 0x18), możliwe jest również wykorzystanie przekierowania portów. Jeśli chcemy wykorzystać, należy zamiast pakietu GG_LOGIN wysłać następujący:

#define GG_LOGIN_EXT 0x0013

struct gg_login_ext {
	int uin;		/* numer klienta */
	int hash;		/* hash hasła */
	int status;		/* początkowy stan */
	int version;		/* wersja klienta */
	int local_ip;		/* mój adres ip */
	short local_port;	/* port, na którym słucham */
	int external_ip;	/* zewnętrzny adres ip */
	short external_port;	/* zewnętrzny port */
	char description[];	/* opis, nie musi wystąpić */
	int time;		/* czas, nie musi wystąpić */
};

1.4. Zmiana stanu

Gadu-Gadu przewiduje kilka stanów klienta, które zmieniamy pakietem typu:

#define GG_NEW_STATUS 0x0002
	
struct gg_new_status {
	int status;		/* na jaki zmienić? */
	char description[];	/* opis, nie musi wystąpić */
	int time;		/* czas, nie musi wystąpić */
}

Możliwe stany to:

EtykietaWartośćZnaczenie
GG_STATUS_NOT_AVAIL0x0001Niedostępny
GG_STATUS_NOT_AVAIL_DESCR0x0015Niedostępny (z opisem)
GG_STATUS_AVAIL0x0002Dostępny
GG_STATUS_AVAIL_DESCR0x0004Dostępny (z opisem)
GG_STATUS_BUSY0x0003Zajęty
GG_STATUS_BUSY_DESCR0x0005Zajęty (z opisem)
GG_STATUS_INVISIBLE0x0014Niewidoczny
GG_STATUS_INVISIBLE_DESCR0x0016Niewidoczny z opisem
GG_STATUS_BLOCKED0x0006Zablokowany
GG_STATUS_FRIENDS_MASK0x8000Maska bitowa oznaczająca tryb tylko dla przyjaciół

Należy pamiętać, żeby przed rozłączeniem się z serwerem należy zmienić stan na GG_STATUS_NOT_AVAIL lub GG_STATUS_NOT_AVAIL_DESCR. Jeśli ma być widoczny tylko dla przyjaciół, należy dodać GG_STATUS_FRIENDS_MASK do normalnej wartości stanu.

Jeśli wybieramy stan opisowy, należy dołączyć ciąg znaków zakończony zerem oraz ewentualny czas powrotu w postaci ilości sekund od 1 stycznia 1970r (UTC). Maksymalna długość opisu wynosi 70 znaków plus zero plus 4 bajty na godzinę powrotu, co razem daje 75 bajtów.


1.5. Ludzie przychodzą, ludzie odchodzą

Zaraz po zalogowaniu możemy wysłać serwerowi naszą listę kontaktów, żeby dowiedzieć się, czy są w danej chwili dostępni. Pakiet zawiera maksymalnie 409 struktur gg_notify:

#define GG_NOTIFY 0x0010
	
struct gg_notify {
	int uin;	/* numerek danej osoby */
	char type;	/* rodzaj użytkownika */
};

Gdzie pole type przyjmuje następujące wartości:

EtykietaWartośćZnaczenie
GG_USER_OFFLINE0x01Użytkownik, dla którego będziemy niedostępni
GG_USER_NORMAL0x03Zwykły użytkownik dodany do listy kontaktów
GG_USER_BLOCKED0x04Użytkownik, którego wiadomości nie chcemy otrzymywać

Jeśli nie mamy nikogo na liście wysyłamy pakiet:

#define GG_LIST_EMPTY 0x0012

o zerowej długości.

Jeśli ktoś jest, serwer odpowie pakietem zawierającym jedną lub więcej struktur gg_notify_reply:

#define GG_NOTIFY_REPLY 0x000c	/* tak, to samo co GG_LOGIN */
	
struct gg_notify_reply {
	int uin;		/* numerek */
	int status;		/* status danej osoby */
	int remote_ip;		/* adres ip delikwenta */
	short remote_port;	/* port, na którym słucha klient */
	int version;		/* wersja klienta */
	short unknown1;		/* znowu port? */
	char description[];	/* opis, nie musi wystąpić */
	int time;		/* czas, nie musi wystąpić */
};

Większość pól powinna być zrozumiała. remote_port poza zwykłym portem może przyjmować również poniższe wartości:

WartośćZnaczenie
0Klient nie obsługuje bezpośrednich połączeń
1Klient łączy się zza NAT lub innej formy maskarady
2Klient nie ma nas w swojej liście kontaktów

Zdarzają się też inne ,,nietypowe'' wartości, ale ich znaczenie nie jest jeszcze do końca znane.

Jeśli dany klient jest w stanie z podanym opisem, najpierw dostaniemy informację osobnym pakietem o nim, później paczkę struktur gg_notify_reply z ludźmi, którzy nie mają ustawionych opisów.

Możliwe jest również otrzymanie pakietu gg_notify_reply ze swoim własnym numerem, którą najlepiej zignorować.

Żeby dodać kogoś do listy w trakcie pracy, trzeba wysłać niżej opisany pakiet. Jego format jest identyczny jak przy GG_NOTIFY.

#define GG_ADD_NOTIFY 0x000d
	
struct gg_add_notify {
	int uin;	/* numerek */
	char type;	/* rodzaj użytkownika */
};

By usunąć z listy kontaktów, wysyła się podobny pakiet:

#define GG_REMOVE_NOTIFY 0x000e
	
struct gg_remove_notify {
	int uin;	/* numerek */
	char type;	/* rodzaj użytkownika */
};

Jeśli ktoś opuści Gadu-Gadu lub zmieni stan na niedostępny lub niewidoczny, otrzymamy pakiet:

#define GG_STATUS 0x0002
	
struct gg_status {
	int uin;	        /* numerek */
	int status;	        /* nowy stan */
	char description[];	/* opis, nie musi wystąpić */
	int time;		/* czas, nie musi wystąpić */
};

Natomiast jeśli ktoś zmieni status na inny niż niedostępny lub niewidoczny, otrzymamy pakiet gg_notify_reply.


1.6. Wysyłanie wiadomości

Wiadomości wysyła się następującym typem pakietu:

#define GG_SEND_MSG 0x000b

struct gg_send_msg {
	int recipient;		/* numer odbiorcy */
	int seq;		/* numer sekwencyjny */
	int class;		/* klasa wiadomości */
	char message[];		/* treść */
};

Numer sekwencyjny jest wykorzystywany przy potwierdzeniu dostarczenia lub zakolejkowania pakietu. Nie jest wykluczone, że w jakiś sposób odróżnia się różne rozmowy za pomocą części bajtów, ale raczej nie powinno mieć to ma znaczenia. Klasa wiadomości pozwala odróżnić, czy wiadomość ma się pojawić w osobnym okienku czy jako kolejna linijka w okienku rozmowy. Jest to mapa bitowa, więc najlepiej ignorować te bity, których znaczenia nie znamy:

EtykietaWartośćZnaczenie
GG_CLASS_QUEUED0x0001Bit ustawiany wyłącznie przy odbiorze wiadomości, gdy wiadomość została wcześniej zakolejkowania z powodu nieobecności
GG_CLASS_MSG0x0004Wiadomość ma się pojawić w osobnym okienku
GG_CLASS_CHAT0x0008Wiadomość jest częścią toczącej się rozmowy i zostanie wyświetlona w istniejącym okienku
GG_CLASS_CTCP0x0010Wiadomość jest przeznaczona dla klienta Gadu-Gadu i nie powinna być wyświetlona użytkownikowi.
GG_CLASS_ACK0x0020Klient nie życzy sobie potwierdzenia wiadomości.

Długość treści wiadomości nie powinna przekraczać 2000 znaków. Oryginalny klient zezwala na wysłanie do 1989 znaków.

Oryginalny klient wysyłając wiadomość do kilku użytkowników, wysyła po kilka takich samych pakietów z różnymi numerkami odbiorców. Nie ma osobnego pakietu do tego. Natomiast jeśli chodzi o połączenia konferencyjne do pakietu doklejana jest następująca struktura:

struct gg_msg_recipients {
	char flag;		/* == 1 */
	int count;		/* ilość odbiorców */
	int recipients[];	/* tablica odbiorców */
};

Na przykład, by wysłać do dwóch osób, należy wysłać pakiet:

OffsetWartość
nTreść wiadomości
m0x01 (wiadomość konferencyjna)
m + 10x02 (ilość adresatów)
m + 2
m + 3
m + 4
m + 5Numer pierwszego adresata
m + 6
m + 7
m + 8
m + 9Numer drugiego adresata
m + 10
m + 11
m + 12

Od wersji 4.8.1 możliwe jest również dodawanie do wiadomości różnych atrybutów tekstu jak pogrubienie czy kolory. Niezbędne jest dołączenie następującej struktury:

struct gg_msg_richtext {
	char flag;	/* == 2 */
	short length;	/* długość dalszej części */
};

Dalsza część pakietu zawiera odpowiednią ilość struktur o łącznej długości określonej polem length:

struct gg_msg_richtext_format {
	short position;	/* pozycja atrybutu w tekście */
	char font;	/* atrybuty czcionki */
	char rgb[3];	/* kolor czcionki, nie musi wystąpić */
};

Każda z tych struktur określa kawałek tekstu począwszy od znaku określonego przez pole position (liczone od zera) aż do następnego wpisu lub końca tekstu. Pole font jest mapą bitową i kolejne bity mają następujące znaczenie:

EtykietaWartośćZnaczenie
GG_FONT_BOLD0x01Pogrubiony tekst
GG_FONT_ITALIC0x02Kursywa
GG_FONT_UNDERLINE0x04Podkreślenie
GG_FONT_COLOR0x08Kolorowy tekst. Tylko w tym wypadku struktura gg_msg_richtext_format zawiera pole rgb[] będące opisem trzech składowych koloru, kolejno czerwonej, zielonej i niebieskiej.

Dla przykładu, by przesłać tekst ,,ala ma kota'', należy dołączyć do wiadomości następującą sekwencję bajtów:

OffsetWartośćZnaczenie
n0x02Opis atrybutów tekstu...
n + 10x0006...mający 6 bajtów długości
n + 2
n + 30x0004Atrybut zaczyna się od pozycji 4...
n + 4
n + 50x01...i jest to pogrubiony tekst
n + 60x0006Atrybut zaczyna się od pozycji 6...
n + 7
n + 80x00...i jest to zwykły tekst

Serwer po otrzymaniu wiadomości odsyła potwierdzenie, które przy okazji mówi nam, czy wiadomość dotarła do odbiorcy czy została zakolejkowana z powodu nieobecności. Otrzymujemy je w postaci pakietu:

#define GG_SEND_MSG_ACK 0x0005
	
struct gg_send_msg_ack {
	int status;	/* stan wiadomości */
	int recipient;	/* numer odbiorcy */
	int seq;	/* numer sekwencyjny */
};

Numer sekwencyjny i numer adresata są takie same jak podczas wysyłania, a stan wiadomości może być jednym z następujących:

EtykietaWartośćZnaczenie
GG_ACK_DELIVERED0x0002Wiadomość dostarczono
GG_ACK_QUEUED0x0003Wiadomość zakolejkowano
GG_ACK_MBOXFULL0x0004Wiadomości nie dostarczono. Skrzynka odbiorcza na serwerze jest pełna (20 wiadomości maks). Występuje tylko w trybie offline
GG_ACK_NOT_DELIVERED0x0006Wiadomości nie dostarczono. Odpowiedź ta występuje tylko w przypadku wiadomości klasy GG_CLASS_CTCP

1.7. Otrzymywanie wiadomości

Wiadomości serwer przysyła za pomocą pakietu:

#define GG_RECV_MSG 0x000a
	
struct gg_recv_msg {
	int sender;		/* numer nadawcy */
	int seq;		/* numer sekwencyjny */
	int time;		/* czas nadania */
	int class;		/* klasa wiadomości */
	char message[];		/* treść wiadomości */
};

Czas nadania jest zapisany w postaci UTC, jako ilości sekund od 1 stycznie 1970r.

W przypadku pakietów ,,konferencyjnych'' na końcu pakietu doklejona jest struktura identyczna z gg_msg_recipients zawierająca pozostałych rozmówców.


1.8. Ping, pong

Od czasu do czasu klient wysyła pakiet do serwera, by oznajmić, że połączenie jeszcze jest utrzymywane. Jeśli serwer nie dostanie takiego pakietu w przeciągu 5 minut, zrywa połączenie. To, czy klient dostaje odpowiedź zmienia się z wersji na wersję, więc najlepiej nie polegać na tym.

#define GG_PING 0x0008

#define GG_PONG 0x0007

1.9. Rozłączenie

Jeśli serwer zechce nas rozłączyć, wyśle wcześniej pusty pakiet:

#define GG_DISCONNECTING 0x000b

Ma to miejsce, gdy próbowano zbyt wiele razy połączyć się z nieprawidłowym hasłem, lub gdy równocześnie połączy się drugi klient z tym samym numerem (nowe połączenie ma wyższy priorytet).


1.10. Katalog publiczny

Od wersji 5.0.2 zmieniono sposób dostępu do katalogu publicznego -- stał się częścią sesji, zamiast osobnej sesji HTTP. Aby obsługiwać wyszukiwanie osób, odczytywanie własnych informacji lub ich modyfikację należy użyć następującego typu pakietu:

#define GG_PUBDIR50_REQUEST 0x0014
	
struct gg_pubdir50 {
	char type;
	int seq;
	char request[];
};

Pole type oznacza rodzaj zapytania:

#define GG_PUBDIR50_WRITE 0x01
#define GG_PUBDIR50_READ 0x02
#define GG_PUBDIR50_SEARCH 0x03

Pole seq jest numerek sekwencyjnym zapytania, zwracanym również w wyniku. Oryginalny klient tworzy go na podstawie aktualnego czasu. request zawiera parametry zapytania. Ilość jest dowolna. Każdy parametr jest postaci "nazwa\0wartość\0", tzn. nazwa od wartości są oddzielone znakiem o kodzie 0, podobnie jak kolejne parametry od siebie. Możliwe parametry zapytania to:

EtykietaWartośćZnaczenie
GG_PUBDIR50_UINFmNumberNumer szukanej osoby
GG_PUBDIR50_FIRSTNAMEfirstnameImię
GG_PUBDIR50_LASTNAMElastnameNazwisko
GG_PUBDIR50_NICKNAMEnicknamePseudonim
GG_PUBDIR50_BIRTHYEARbirthyearRok urodzenia. Jeśli chcemy szukać osób z danego przedziału, podajemy rok początkowy i końcowy, oddzielone spacją. Na przykład ,,1980 1985''.
GG_PUBDIR50_CITYcityMiejscowość
GG_PUBDIR50_GENDERgenderPłeć. Jeśli szukamy kobiet, ma wartość ,,1'' (stała GG_PUBDIR50_GENDER_FEMALE). Jeśli mężczyzn, ma wartość ,,2'' (stała GG_PUBDIR50_GENDER_MALE).
GG_PUBDIR50_STARTActiveOnlyJeśli szukamy tylko dostępnych osób, ma mieć wartość ,,1'' (stała GG_PUBDIR50_ACTIVE_TRUE).
GG_PUBDIR50_FAMILYNAMEfamilynameNazwisko panieńskie. Ma znaczenie tylko przy ustawianiu własnych danych.
GG_PUBDIR50_FAMILYCITYfamilycityMiejscowość pochodzenia. Ma znaczenie tylko przy ustawianiu własnych danych.
GG_PUBDIR50_STARTfmstartNumer, od którego rozpocząć wyszukiwanie. Ma znaczenie, gdy kontynuujemy wyszukiwanie.

Treść przykładowego zapytania (pomijając pola type i seq) znajduje się poniżej. Szukano dostępnych kobiet o imieniu Ewa z Warszawy. Znaki o kodzie 0 zastąpiono kropkami.

firstname.Ewa.city.Warszawa.gender.1.ActiveOnly.1.

Wynik zapytania zostanie zwrócony za pomocą pakietu:

#define GG_PUBDIR50_REPLY 0x000e
	
struct gg_pubdir50_reply {
	char type;
	int seq;
	char reply[];
};

Pole type poza wartościami takimi jak przy pakiecie typu GG_PUBDIR50_REQUEST może przyjąć jeszcze wartość oznaczającą odpowiedź wyszukiwania:

#define GG_PUBDIR50_SEARCH_REPLY 0x05

Wyniki są zbudowane identycznie jak w przypadku zapytań, z tą różnicą, że kolejne osoby oddzielane pustym polem: "parametr\0wartość\0\0parametr\0wartość\0".

EtykietaWartośćZnaczenie
GG_PUBDIR50_STATUSFmStatusStan szukanej osoby
 nextstartPole występujące w ostatnim wyniku, określające, od jakiego numeru należy rozpocząć wyszukiwanie, by otrzymać kolejną porcję danych. Podaje się go w zapytaniu jako parametr ,,start''.

Przykładowy wynik zawierający dwie znalezione osoby:

FmNumber.12345.FmStatus.1.firstname.Adam.nickname.Janek.birthyear.1979.city.Wzdów
..FmNumber.3141592.FmStatus.5.firstname.Ewa.nickname.Ewcia.birthyear.1982.city.Gd
dańsk..nextstart.0.

Wyszukiwanie nie zwraca nazwisk znalezionych osób.


2. Usługi HTTP

2.1. Format danych

Komunikacja z appmsg.gadu-gadu.pl metodą GET HTTP/1.0 została opisana w poprzednim rozdziale, pozostałe pakiety używają POST dla HTTP/1.0, a w odpowiedzi 1.1. Mają one postać:

POST ŚCIEŻKA HTTP/1.0
Host: HOST
Content-Type: application/x-www-form-urlencoded
User-Agent: AGENT
Content-Length: DŁUGOŚĆ
Pragma: no-cache

DANE

Gdzie AGENT to nazwa przeglądarki (na przykład Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) lub inne, wymienione w rozdziale 1.2), DŁUGOŚĆ to długość bloku DANE w znakach.

Jeśli będzie mowa o wysyłaniu danych do serwera, to chodzi o cały powyższy pakiet, opisane zostaną tylko: HOST, ŚCIEŻKA i DANE. Pakiet jest wysyłany na port 80. Gdy mowa o wysyłaniu pól zapytania, mowa o DANE o wartości:

pole1=wartość1&pole2=wartość2&...

Pamiętaj o zmianie kodowania na CP1250 i zakodowaniu danych do postaci URL (na przykład funkcją typu urlencode).

Odpowiedzi serwera na powyższe zapytania mają mniej więcej postać:

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Mon, 01 Jul 2002 22:30:31 GMT
Connection: Keep-Alive
Content-Length: DŁUGOŚĆ
Content-Type: text/html
Set-Cookie: COOKIE
Cache-control: private

ODPOWIEDŹ

Nagłówki nie są dla nas ważne. Można zauważyć tylko to, że czasami serwer ustawia COOKIE np. ,,ASPSESSIONIDQQGGGLJC=CAEKMBGDJCFBEOKCELEFCNKH; path=/''. Pisząc dalej, że serwer ,,odpowie wartością'' można tylko o polu ODPOWIEDŹ. Kodowanie znaków w odpowiedzi to CP1250.


2.2. Rejestracja konta

Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmregister2.asp

Wysyłamy poleZnaczenie
pwdhasło dla nowego numeru
emailadres e-mail do przypomnienia hasła
qahasło pomocnicze i odpowiedź, oddzielone znakiem tyldy ,,~''
codehash liczony z pól email i pwd. Algorytmu szukaj w źródłach libgadu w lib/common.c

Dostępne standardowo pytania pomocnicze i ich aliasy (podawane zamiast treści pytania) to:

AliasPytanie
1Twoja ulubiona potrawa?
2Imię/nazwisko Twojej sympatii z lat szkolnych?
3Imię/nazwisko osoby, z którą siedziałeś w szkolnej ławie?
4Imię Twojego dziadka?
5Imię Twojej babci?
6Nazwisko panieńskie Twojej matki?
7Tytuł Twojego ulubionego filmu?
8Ulubiony aktor?
9Ulubiony solista/zespół muzyczny?

Przykład:

POST /appsvc/fmregister2.asp HTTP/1.0
Host: register.gadu-gadu.pl
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
Content-Length: 62
Pragma: no-cache
		
pwd=sekret&email=moj@adres.email.pl&qa=5~Maria&code=1104465363

Jeśli wszystko przebiegło poprawnie, serwer odpowie:

reg_success:UIN

Gdzie UIN to nowy numer, który właśnie otrzymaliśmy.


2.3. Usunięcie konta

Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmregister2.asp

Wysyłamy poleZnaczenie
fmnumberusuwany numer
fmpwdhasło
deletewartość ,,1''
pwdlosowa liczba
qapytanie pomocnicza i odpowiedź (patrz punkt 2.2)
codehash liczony z pola pwd

Przykład:

POST /appsvc/fmregister2.asp HTTP/1.0
Host: register.gadu-gadu.pl
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
Content-Length: 74
Pragma: no-cache
		
fmnumber=4969256&fmpwd=haslo&delete=1&pwd=%2D388046464&qa=&code=1483497094

Jeśli wszystko przebiegło poprawnie, serwer odpowie:

reg_success:UIN

Gdzie UIN to numer, który skasowaliśmy.


2.4. Zmiana hasła

Pole nagłówkaWartość
HOSTregister.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmregister2.asp

Wysyłamy poleZnaczenie
fmnumbernumer
fmpwdstare hasło
pwdnowe hasło
qapytanie pomocnicze i odpowiedź, patrz punkt 2.2
codehash liczony z pola pwd

Jeśli wszystko przebiegło poprawnie, serwer odpowie:

reg_success:UIN

2.5. Umieszczenie listy kontaktów na serwerze

Pole nagłówkaWartość
HOSTpubdir.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmcontactsput.asp

Wysyłamy poleZnaczenie
FmNumnumer
Passhasło
Contactsdowolna ilość rekordów z kontaktami rozdzielone znakiem nowej linii ,,\r\n''

Format pojedynczego kontaktu:

imie;nazwisko;pseudo;wyswietlane;telefon;grupa;uin;adres@email;0;;0;

Ostatnie 3 pola zostały dodane w wersji 4.9.3 i są związane z dźwiękami.

Przykład:

POST /appsvc/fmcontactsput.asp HTTP/1.0
Host: pubdir.gadu-gadu.pl
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
Content-Length: 110
Pragma: no-cache

FmNum=1000&Pass=sekret&Contacts=imie;nazwisko;pseudo;wyswietlane;+48123123123;grup
a1;1000;adres@email.pl;0;;0;

Jeśli się udało, serwer odpowie:

put_success:

2.6. Pobranie listy kontaktów z serwera

Pole nagłówkaWartość
HOSTpubdir.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmcontactsget.asp

Wysyłamy poleZnaczenie
FmNumnumer
Passhasło

Przykład:

POST /appsvc/fmcontactsget.asp HTTP/1.0
Host: pubdir.gadu-gadu.pl
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
Content-Length: 22
Pragma: no-cache

FmNum=1000&Pass=sekret

Jeśli się udało, serwer odpowie:

get_results:rekord_1
rekord_2
...
rekord_N

Rekord ma taki sam format jak w 2.5.


2.7. Usunięcie listy kontaktów z serwera

Pole nagłówkaWartość
HOSTpubdir.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmcontactsput.asp

Wysyłamy poleZnaczenie
FmNumnumer
Passhasło
Deletewartość ,,1''

Przykład:

POST /appsvc/fmcontactsput.asp HTTP/1.0
Host: pubdir.gadu-gadu.pl
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)
Content-Length: 31
Pragma: no-cache

FmNum=1000&Pass=sekret&Delete=1

Jeśli się udało, serwer odpowie:

put_success:

2.8. Przypomnienie hasła pocztą

Pole nagłówkaWartość
HOSTretr.gadu-gadu.pl
ŚCIEŻKA/appsvc/fmsendpwd.asp

Wysyłamy poleZnaczenie
useridnumer
codehash liczony z pola userid

Jeśli się udało, serwer odpowie:

pwdsend_success

3. Bezpośrednie połączenia

Author: Walerian Sokołowski (C) Copyright 2002
You a free to change it in every way you want.

----- (0) disclaimer ------------------------------
Opisuję tu protokół przesyłania plików w między klientami G*du-G*du.
Informację czerpałem ze źródeł gglib w którejś z wersji
oraz z doświadczeń przeprowadzanych własnoręcznie. W tym celu nic nie 
desasemblowałem i nie reingenerowałem.

----- 1) zamiast wstępu ------------------------------------------------------
Protokół jest pewną implementacją części powszechnie znanego DCC 
opisanego w którymś z RFC. 

Obowiązują naturalne dla GG ustalenia, a więc sizeof(int) = 4 oraz transmisja
jest intel endian. 

----- 2) nawiązanie połączenia ------------------------------------------------
Klient łącząc się z serwerem GG wysyła swój adres IP i port, na którym
nasłuchuje (patrz opis gg_login). Gdy któryś z kontaktów staje się 
dostępny (właśnie się połączyliśmy lub on właśnie zmienił stan) 
otrzymujemu powiadomienie o tym w paczce typu GG_NOTIFY_REPLY, 
która zawiera jego adresek IP i port, na którym on nasłuchuje (patrz
opis gg_notify_reply). Tak więc w najprostszej sytuacji, gdy nie dzielą
nas żadne firewalle strona wysyłająca ma adres odbiorcy.
 
Jeśli odbiorca znajduje się za firewallem, a nadawca nie, to nadawca
może poprosić odbiorcę o nawiązanie połączenia wysyłając do niego
komunikat typu 0x0010 (GG_CLASS_CTCP) o zawartości 0x02. W taki sposób
na czas nawiązania połączenia nadawca i odbiorca zamieniają się rolami.
I do końca punktu będę ich nazywać wg. pełnionych ról.

A więc nadawca nawiązuje połączenie TCP z adresem odbiorcy i wysyła
swój UIN i UIN odbiorcy:

	struct {
		int uin1; /* mój numerek  */
		int uin2; /* jego numerek */
	};

Odbiorca potwierdzając nawiązanie połączenia z klientem GG wysyła 4 bajty:
 
	struct {
		char [] "UDAG";
	};

Jeśli nadawca ma wysyłać plik, to wysyła 0x0002:
 
	#define GG_DCC_CATCH_FILE 0x0002

Jeśli to odbiorca ma wysyłać, to nadawca wysyła 0x0003:
	
	#define GG_DCC_WANT_FILE 0x0003

Po tym wszystkim uważa się połączenie za nawiązane.

----- 3) transmisja pliku: strona nadawcy -------------------------------------
Nadawca wysyła  po kolei:

	#define GG_DCC_HAVE_FILE     0x0001
	#define GG_DCC_HAVE_FILEINFO 0x0003
	int dunno1; /* 0 */
	int dunno2; /* 0 */
	file_info_struct finfo;

Podejrzewam, że dunno2:dunno1 jest pozycją w pliku, od której nadawca 
chce wysyłać plik, ale nie udało mi się zasymulować sytuacji, w której 
byłyby używane.

	struct file_info_struct {
        	int mode;                  /* dwFileAttributes */
        	int ctime[2];              /* ftCreationTime */
        	int atime[2];              /* ftLastAccessTime */
        	int mtime[2];              /* ftLastWriteTime */
		int size_hdw;              /* górne 4 bajty długości pliku */
		int size_ldw;              /* dolne 4 bajty długości pliku */
		int reserved1;             /* 0 */
		int reserved2;             /* 0 */
		char file_name[276];       /* tablica zaczynająca się od nazwy 
					      pliku, wypełniona zerami */
	};

Dalej nadawca czeka na akceptację odbiorcy, czyli następującą strukturkę:
	
	struct {
		int type;      /* 0x0006 GG_DCC_GIMME_FILE */
		int start;     /* od której pozycji zacząć przesyłanie */
		int dunno;     /* 0 */
	};

Teraz możemy zacząć przesyłanie pliku. Plik przesyłamy w paczkach długości 
ustalonej przez nadawcę. Przed każdą paczką z danymi nadawca wysyła nagłówek
paczki:
	struct {
		int type;       /* 0x0003 GG_DCC_FILEHEADER, jeśli paczka nie 
				   jest ostatnia. 0x0002 GG_DCC_LAST_FILEHEADER
				   wpp. */
		int chunk_size; /* rozmiar paczki */
		int dunno;      /* 0 */
	};

Po wysłaniu ostatniej paczki zamykamy połączenie. Plik został przesłany.

----- 4) transmisja pliku: strona odbiorcy ------------------------------------
Zachowanie odbiorcy jest symetryczne:
1. odbiera kolejno 
	GG_DCC_HAVE_FILE
	GG_DCC_HAVE_FILEINFO
	int dunno1;
	int dunno2;
	file_info_struct finfo;

2. jeśli użytkownik zgodzi się odebrać plik, to wysyłamy struktrę jakiej
odbiorca się spodziewa.

3. otrzymujemy nagłówek paczki i paczkę z danymi zadeklarowanej długości
4. jeśli nagłówek był typu GG_DCC_LAST_FILEHEADER to otrzymaliśmy całość, 
więc zamykamy połączenie. Jeśli nie, to wracamy do kroku 3.

4. Autorzy

Autorami powyższego opisu są:

  • Wojtek Kaniewski (wojtekka%irc.pl): pierwsza wersja opisu, poprawki, utrzymanie wszystkiego w porządku.
  • Robert J. Woźny (speedy%atman.pl): opis nowości w protokole GG 4.6, poprawki.
  • Tomasz Jarzynka (tomee%cpi.pl): badanie timeoutów.
  • Adam Ludwikowski (adam.ludwikowski%wp.pl): wiele poprawek, wersje klientów, rozszerzone wiadomości, powody nieobecności.
  • Marek Kozina (klith%hybrid.art.pl): czas otrzymania wiadomości.
  • Rafał Florek (raf%regionet.regionet.pl): opis połączeń konferencyjnych.
  • Igor Popik (igipop%wsfiz.edu.pl): klasy wiadomości przy odbieraniu zakolejkowanej.
  • Rafał Cyran (ajron%wp.pl): informacje o remote_port, rodzaje potwierdzeń przy ctcp, GG_LOGIN_EXT.
  • Piotr Mach (pm%gadu-gadu.com): ilość kontaktów, pełna skrzynka, pusta lista, maska audio, usługi HTTP, GG_LOGIN_EXT.
  • Adam Czyściak (acc%interia.pl): potwierdzenie wiadomości GG_CLASS_ACK.
  • Kamil Dębski (kdebski%kki.net.pl): czas w stanach opisowych.
  • Paweł Piwowar (alfapawel%go2.pl): format czasu.
  • Tomasz Chiliński (chilek%chilan.com): nowości w 5.0.2.
  • Radosław Nowak (rano%ranosoft.com): uzupełnienie statusu opisowego, wersja 5.0.3.
  • Walerian Sokołowski: opis protokołu bezpośrednich połączeń.

$Id: protocol.html,v 1.34 2003/03/29 23:25:33 wojtekka Exp $