From 48540940b6c28bb4378abfeb500ec45a625b37b6 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Tue, 15 May 2012 10:38:20 +0000 Subject: initial commit git-svn-id: http://svn.miranda-ng.org/main/trunk@2 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/Gadu-Gadu/libgadu/libgadu.c | 2353 +++++++++++++++++++++++++++++++++ 1 file changed, 2353 insertions(+) create mode 100644 protocols/Gadu-Gadu/libgadu/libgadu.c (limited to 'protocols/Gadu-Gadu/libgadu/libgadu.c') diff --git a/protocols/Gadu-Gadu/libgadu/libgadu.c b/protocols/Gadu-Gadu/libgadu/libgadu.c new file mode 100644 index 0000000000..f06f8a5b84 --- /dev/null +++ b/protocols/Gadu-Gadu/libgadu/libgadu.c @@ -0,0 +1,2353 @@ +/* coding: UTF-8 */ +/* $Id: libgadu.c 13762 2011-08-09 12:35:16Z dezred $ */ + +/* + * (C) Copyright 2001-2010 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * Tomasz Chiliński + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file libgadu.c + * + * \brief Główny moduł biblioteki + */ + +#ifndef _WIN64 +#define _USE_32BIT_TIME_T +#endif + +#include +#ifdef _WIN32 +#include "win32.h" +#else +#include +#include +#include +#ifdef sun +# include +#endif +#endif /* _WIN32 */ + +#include "compat.h" +#include "libgadu.h" +#include "protocol.h" +#include "resolver.h" +#include "internal.h" + +#include +#ifndef _WIN32 +#include +#endif /* _WIN32 */ +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif /* _WIN32 */ +#if !defined(GG_CONFIG_MIRANDA) && defined(GG_CONFIG_HAVE_OPENSSL) +# include +# include +#endif + +/** + * Poziom rejestracji informacji odpluskwiających. Zmienna jest maską bitową + * składającą się ze stałych \c GG_DEBUG_... + * + * \ingroup debug + */ +int gg_debug_level = 0; + +/** + * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno + * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe + * \c NULL, informacje są wysyłane do standardowego wyjścia błędu (\c stderr). + * + * \param level Poziom rejestracji + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + * + * \note Funkcja jest przesłaniana przez \c gg_debug_handler_session. + * + * \ingroup debug + */ +void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; + +/** + * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno + * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe + * \c NULL, informacje są wysyłane do standardowego wyjścia błędu. + * + * \param sess Sesja której dotyczy informacja lub \c NULL + * \param level Poziom rejestracji + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + * + * \note Funkcja przesłania przez \c gg_debug_handler_session. + * + * \ingroup debug + */ +void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap) = NULL; + +/** + * Port gniazda nasłuchującego dla połączeń bezpośrednich. + * + * \ingroup ip + */ +int gg_dcc_port = 0; + +/** + * Adres IP gniazda nasłuchującego dla połączeń bezpośrednich. + * + * \ingroup ip + */ +unsigned long gg_dcc_ip = 0; + +/** + * Adres lokalnego interfejsu IP, z którego wywoływane są wszystkie połączenia. + * + * \ingroup ip + */ +unsigned long gg_local_ip = 0; + +/** + * Flaga włączenia połączeń przez serwer pośredniczący. + * + * \ingroup proxy + */ +int gg_proxy_enabled = 0; + +/** + * Adres serwera pośredniczącego. + * + * \ingroup proxy + */ +char *gg_proxy_host = NULL; + +/** + * Port serwera pośredniczącego. + * + * \ingroup proxy + */ +int gg_proxy_port = 0; + +/** + * Flaga używania serwera pośredniczącego jedynie dla usług HTTP. + * + * \ingroup proxy + */ +int gg_proxy_http_only = 0; + +/** + * Nazwa użytkownika do autoryzacji serwera pośredniczącego. + * + * \ingroup proxy + */ +char *gg_proxy_username = NULL; + +/** + * Hasło użytkownika do autoryzacji serwera pośredniczącego. + * + * \ingroup proxy + */ +char *gg_proxy_password = NULL; + +#ifndef DOXYGEN + +#ifndef lint +static char rcsid[] +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif += "$Id: libgadu.c 13762 2011-08-09 12:35:16Z dezred $"; +#endif + +#endif /* DOXYGEN */ + +/** + * Zwraca wersję biblioteki. + * + * \return Wskaźnik na statyczny bufor z wersją biblioteki. + * + * \ingroup version + */ +const char *gg_libgadu_version() +{ + return GG_LIBGADU_VERSION; +} + +#ifdef GG_CONFIG_HAVE_UINT64_T +/** + * \internal Zamienia kolejność bajtów w 64-bitowym słowie. + * + * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach + * big-endianowych odwraca kolejność bajtów w słowie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiednią kolejnością bajtów + * + * \ingroup helper + */ +uint64_t gg_fix64(uint64_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint64_t) + (((x & (uint64_t) 0x00000000000000ffULL) << 56) | + ((x & (uint64_t) 0x000000000000ff00ULL) << 40) | + ((x & (uint64_t) 0x0000000000ff0000ULL) << 24) | + ((x & (uint64_t) 0x00000000ff000000ULL) << 8) | + ((x & (uint64_t) 0x000000ff00000000ULL) >> 8) | + ((x & (uint64_t) 0x0000ff0000000000ULL) >> 24) | + ((x & (uint64_t) 0x00ff000000000000ULL) >> 40) | + ((x & (uint64_t) 0xff00000000000000ULL) >> 56)); +#endif +} +#endif /* GG_CONFIG_HAVE_UINT64_T */ + +/** + * \internal Zamienia kolejność bajtów w 32-bitowym słowie. + * + * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach + * big-endianowych odwraca kolejność bajtów w słowie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiednią kolejnością bajtów + * + * \ingroup helper + */ +uint32_t gg_fix32(uint32_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint32_t) + (((x & (uint32_t) 0x000000ffU) << 24) | + ((x & (uint32_t) 0x0000ff00U) << 8) | + ((x & (uint32_t) 0x00ff0000U) >> 8) | + ((x & (uint32_t) 0xff000000U) >> 24)); +#endif +} + +/** + * \internal Zamienia kolejność bajtów w 16-bitowym słowie. + * + * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach + * big-endianowych zamienia kolejność bajtów w słowie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiednią kolejnością bajtów + * + * \ingroup helper + */ +uint16_t gg_fix16(uint16_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint16_t) + (((x & (uint16_t) 0x00ffU) << 8) | + ((x & (uint16_t) 0xff00U) >> 8)); +#endif +} + +/** + * \internal Liczy skrót z hasła i ziarna. + * + * \param password Hasło + * \param seed Ziarno podane przez serwer + * + * \return Wartość skrótu + */ +unsigned int gg_login_hash(const unsigned char *password, unsigned 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; +} + +/** + * \internal Odbiera od serwera dane binarne. + * + * Funkcja odbiera dane od serwera zajmując się SSL/TLS w razie konieczności. + * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi + * wywołaniami systemowymi. + * + * \param sess Struktura sesji + * \param buf Bufor na danymi + * \param length Długość bufora + * + * \return To samo co funkcja systemowa \c read + */ +int gg_read(struct gg_session *sess, char *buf, int length) +{ +#ifdef GG_CONFIG_MIRANDA + if (sess->ssl != NULL) + return si.read(sess->ssl, buf, length, 0); +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) { + for (;;) { + int res, err; + + res = SSL_read(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_SYSCALL && errno == EINTR) + continue; + + if (err == SSL_ERROR_WANT_READ) + errno = EAGAIN; + else if (err != SSL_ERROR_SYSCALL) + errno = EINVAL; + + return -1; + } + + return res; + } + } +#endif + + return gg_sock_read(sess->fd, buf, length); +} + +/** + * \internal Wysyła do serwera dane binarne. + * + * Funkcja wysyła dane do serwera zajmując się SSL/TLS w razie konieczności. + * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi + * wywołaniami systemowymi. + * + * \note Funkcja nie zajmuje się buforowaniem wysyłanych danych (patrz + * gg_write()). + * + * \param sess Struktura sesji + * \param buf Bufor z danymi + * \param length Długość bufora + * + * \return To samo co funkcja systemowa \c write + */ +static int gg_write_common(struct gg_session *sess, const char *buf, int length) +{ +#ifdef GG_CONFIG_MIRANDA + if (sess->ssl != NULL) + return si.write(sess->ssl, buf, length); +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) { + for (;;) { + int res, err; + + res = SSL_write(sess->ssl, (void *)buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_SYSCALL && errno == EINTR) + continue; + + if (err == SSL_ERROR_WANT_WRITE) + errno = EAGAIN; + else if (err != SSL_ERROR_SYSCALL) + errno = EINVAL; + + return -1; + } + + return res; + } + } +#endif + + return gg_sock_write(sess->fd, buf, length); +} + + + +/** + * \internal Wysyła do serwera dane binarne. + * + * Funkcja wysyła dane do serwera zajmując się TLS w razie konieczności. + * + * \param sess Struktura sesji + * \param buf Bufor z danymi + * \param length Długość bufora + * + * \return To samo co funkcja systemowa \c write + */ +int gg_write(struct gg_session *sess, const char *buf, int length) +{ + int res = 0; + + if (!sess->async) { + int written = 0; + + while (written < length) { + res = gg_write_common(sess, buf + written, length - written); + + if (res == -1) + return -1; + + written += res; + res = written; + } + } else { + res = 0; + + if (sess->send_buf == NULL) { + res = gg_write_common(sess, buf, length); + + if (res == -1) + return -1; + } + + if (res < length) { + char *tmp; + + if (!(tmp = realloc(sess->send_buf, sess->send_left + length - res))) { + errno = ENOMEM; + return -1; + } + + sess->send_buf = tmp; + + memcpy(sess->send_buf + sess->send_left, buf + res, length - res); + + sess->send_left += length - res; + } + } + + return res; +} + +/** + * \internal Odbiera pakiet od serwera. + * + * Funkcja odczytuje nagłówek pakietu, a następnie jego zawartość i zwraca + * w zaalokowanym buforze. + * + * Przy połączeniach asynchronicznych, funkcja może nie być w stanie + * skompletować całego pakietu -- w takim przypadku zwróci -1, a kodem błędu + * będzie \c EAGAIN. + * + * \param sess Struktura sesji + * + * \return Wskaźnik do zaalokowanego bufora + */ +void *gg_recv_packet(struct gg_session *sess) +{ + struct gg_header h; + char *buf = NULL; + int ret = 0; + unsigned int offset, size = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (sess->recv_left < 1) { + if (sess->header_buf) { + memcpy(&h, sess->header_buf, sess->header_done); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done); + free(sess->header_buf); + sess->header_buf = NULL; + } else + sess->header_done = 0; + + while (sess->header_done < sizeof(h)) { + ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, &h + sess->header_done, sizeof(h) - sess->header_done, ret); + + if (!ret) { + errno = ECONNRESET; + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n"); + return NULL; + } + + if (ret == -1) { + if (errno == EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n"); + + if (!(sess->header_buf = malloc(sess->header_done))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n"); + return NULL; + } + + memcpy(sess->header_buf, &h, sess->header_done); + + errno = EAGAIN; + + return NULL; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno)); + + return NULL; + } + + sess->header_done += ret; + + } + + h.type = gg_fix32(h.type); + h.length = gg_fix32(h.length); + } else + memcpy(&h, sess->recv_buf, sizeof(h)); + + /* jakieś sensowne limity na rozmiar pakietu */ + if (h.length > 65535) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length); + errno = ERANGE; + return NULL; + } + + if (sess->recv_left > 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n"); + size = sess->recv_left; + offset = sess->recv_done; + buf = sess->recv_buf; + } else { + if (!(buf = malloc(sizeof(h) + h.length + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n"); + return NULL; + } + + memcpy(buf, &h, sizeof(h)); + + offset = 0; + size = h.length; + } + + while (size > 0) { + ret = gg_read(sess, buf + sizeof(h) + offset, size); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret); + if (!ret) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n"); + errno = ECONNRESET; + return NULL; + } + if (ret > -1 && ret <= (int)size) { + offset += ret; + size -= ret; + } else if (ret == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno)); + + if (errno == EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size); + sess->recv_buf = buf; + sess->recv_left = size; + sess->recv_done = offset; + return NULL; + } + + free(buf); + return NULL; + } + } + + sess->recv_left = 0; + + gg_debug_dump_session(sess, buf, sizeof(h) + h.length, "// gg_recv_packet(0x%.2x)", h.type); + + return buf; +} + +/** + * \internal Wysyła pakiet do serwera. + * + * Funkcja konstruuje pakiet do wysłania z dowolnej liczby fragmentów. Jeśli + * rozmiar pakietu jest za duży, by móc go wysłać za jednym razem, pozostała + * część zostanie zakolejkowana i wysłana, gdy będzie to możliwe. + * + * \param sess Struktura sesji + * \param type Rodzaj pakietu + * \param ... Lista kolejnych części pakietu (wskaźnik na bufor i długość + * typu \c int) zakończona \c NULL + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_send_packet(struct gg_session *sess, int type, ...) +{ + struct gg_header *h; + char *tmp; + unsigned int tmp_length; + void *payload; + unsigned int payload_length; + va_list ap; + int res; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...);\n", sess, type); + + tmp_length = sizeof(struct gg_header); + + if (!(tmp = malloc(tmp_length))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n"); + return -1; + } + + va_start(ap, type); + + payload = va_arg(ap, void *); + + while (payload) { + char *tmp2; + + payload_length = va_arg(ap, unsigned int); + + if (!(tmp2 = realloc(tmp, tmp_length + payload_length))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n"); + free(tmp); + va_end(ap); + return -1; + } + + tmp = tmp2; + + memcpy(tmp + tmp_length, payload, payload_length); + tmp_length += payload_length; + + payload = va_arg(ap, void *); + } + + va_end(ap); + + h = (struct gg_header*) tmp; + h->type = gg_fix32(type); + h->length = gg_fix32(tmp_length - sizeof(struct gg_header)); + + gg_debug_dump_session(sess, tmp, tmp_length, "// gg_send_packet(0x%.2x)", gg_fix32(h->type)); + + res = gg_write(sess, tmp, tmp_length); + + free(tmp); + + if (res == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno)); + return -1; + } + + if (sess->async) + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() partial write(), %d sent, %d left, %d total left\n", res, tmp_length - res, sess->send_left); + + if (sess->send_buf) + sess->check |= GG_CHECK_WRITE; + + return 0; +} + +/** + * \internal Funkcja zwrotna sesji. + * + * Pole \c callback struktury \c gg_session zawiera wskaźnik do tej funkcji. + * Wywołuje ona \c gg_watch_fd i zachowuje wynik w polu \c event. + * + * \note Korzystanie z tej funkcjonalności nie jest już zalecane. + * + * \param sess Struktura sesji + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_session_callback(struct gg_session *sess) +{ + if (!sess) { + errno = EFAULT; + return -1; + } + + return ((sess->event = gg_watch_fd(sess)) != NULL) ? 0 : -1; +} + +/** + * Łączy się z serwerem Gadu-Gadu. + * + * Przy połączeniu synchronicznym funkcja zakończy działanie po nawiązaniu + * połączenia lub gdy wystąpi błąd. Po udanym połączeniu należy wywoływać + * funkcję \c gg_watch_fd(), która odbiera informacje od serwera i zwraca + * informacje o zdarzeniach. + * + * Przy połączeniu asynchronicznym funkcja rozpocznie procedurę połączenia + * i zwróci zaalokowaną strukturę. Pole \c fd struktury \c gg_session zawiera + * deskryptor, który należy obserwować funkcją \c select, \c poll lub za + * pomocą mechanizmów użytej pętli zdarzeń (Glib, Qt itp.). Pole \c check + * jest maską bitową mówiącą, czy biblioteka chce być informowana o możliwości + * odczytu danych (\c GG_CHECK_READ) czy zapisu danych (\c GG_CHECK_WRITE). + * Po zaobserwowaniu zmian na deskryptorze należy wywołać funkcję + * \c gg_watch_fd(). Podczas korzystania z połączeń asynchronicznych, w trakcie + * połączenia może zostać stworzony dodatkowy proces rozwiązujący nazwę + * serwera -- z tego powodu program musi poprawnie obsłużyć sygnał SIGCHLD. + * + * \note Po nawiązaniu połączenia z serwerem należy wysłać listę kontaktów + * za pomocą funkcji \c gg_notify() lub \c gg_notify_ex(). + * + * \param p Struktura opisująca parametry połączenia. Wymagane pola: uin, + * password, async. + * + * \return Wskaźnik do zaalokowanej struktury sesji \c gg_session lub NULL + * w przypadku błędu. + * + * \ingroup login + */ +#ifdef GG_CONFIG_MIRANDA +struct gg_session *gg_login(const struct gg_login_params *p, SOCKET *gg_sock, int *gg_failno) +#else +struct gg_session *gg_login(const struct gg_login_params *p) +#endif +{ + struct gg_session *sess = NULL; + char *hostname; + int port; + + if (!p) { + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p); + errno = EFAULT; + return NULL; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async); + + if (!(sess = malloc(sizeof(struct gg_session)))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n"); + goto fail; + } + + memset(sess, 0, sizeof(struct gg_session)); + + if (!p->password || !p->uin) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n"); + errno = EFAULT; + goto fail; + } + + if (!(sess->password = strdup(p->password))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for password\n"); + goto fail; + } + + if (p->hash_type < 0 || p->hash_type > GG_LOGIN_HASH_SHA1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. unknown hash type (%d)\n", p->hash_type); + errno = EFAULT; + goto fail; + } + + sess->uin = p->uin; + sess->state = GG_STATE_RESOLVING; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->async = p->async; + sess->type = GG_SESSION_GG; + sess->initial_status = p->status; + sess->callback = gg_session_callback; + sess->destroy = gg_free_session; + sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT); + sess->server_addr = p->server_addr; + sess->external_port = p->external_port; + sess->external_addr = p->external_addr; + sess->client_port = p->client_port; + + if (p->protocol_features == 0) { + sess->protocol_features = GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION; + } else { + sess->protocol_features = (p->protocol_features & ~(GG_FEATURE_STATUS77 | GG_FEATURE_MSG77)); + + if (!(p->protocol_features & GG_FEATURE_STATUS77)) + sess->protocol_features |= GG_FEATURE_STATUS80; + + if (!(p->protocol_features & GG_FEATURE_MSG77)) + sess->protocol_features |= GG_FEATURE_MSG80; + } + + if (!(sess->status_flags = p->status_flags)) + sess->status_flags = GG_STATUS_FLAG_UNKNOWN | GG_STATUS_FLAG_SPAM; + + sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION; + + if (p->era_omnix) + sess->protocol_flags |= GG_ERA_OMNIX_MASK; + if (p->has_audio) + sess->protocol_flags |= GG_HAS_AUDIO_MASK; + sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL; + sess->last_sysmsg = p->last_sysmsg; + sess->image_size = p->image_size; + sess->pid = -1; + sess->encoding = p->encoding; + + if (gg_session_set_resolver(sess, p->resolver) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. unsupported resolver type (%d)\n", p->resolver); + errno = EFAULT; + goto fail; + } + + if (p->status_descr) { + int max_length; + + if (sess->protocol_version >= 0x2d) + max_length = GG_STATUS_DESCR_MAXSIZE; + else + max_length = GG_STATUS_DESCR_MAXSIZE_PRE_8_0; + + if (sess->protocol_version >= 0x2d && p->encoding != GG_ENCODING_UTF8) + sess->initial_descr = gg_cp_to_utf8(p->status_descr); + else + sess->initial_descr = strdup(p->status_descr); + + if (!sess->initial_descr) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n"); + goto fail; + } + + // XXX pamiętać, żeby nie ciąć w środku znaku utf-8 + + if ((signed)strlen(sess->initial_descr) > max_length) + sess->initial_descr[max_length] = 0; + } + +#ifdef GG_CONFIG_MIRANDA + sess->tls = p->tls; +#endif + if (p->tls == 1) { +#ifdef GG_CONFIG_HAVE_OPENSSL + char buf[1024]; + + OpenSSL_add_ssl_algorithms(); + + if (!RAND_status()) { + char rdata[1024]; + struct { + time_t time; + void *ptr; + } rstruct; + + time(&rstruct.time); + rstruct.ptr = (void *) &rstruct; + + RAND_seed((void *) rdata, sizeof(rdata)); + RAND_seed((void *) &rstruct, sizeof(rstruct)); + } + + sess->ssl_ctx = SSL_CTX_new(SSLv3_client_method()); + + if (!sess->ssl_ctx) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf); + goto fail; + } + + SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL); + + sess->ssl = SSL_new(sess->ssl_ctx); + + if (!sess->ssl) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf); + goto fail; + } +#elif !defined(GG_CONFIG_MIRANDA) + gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n"); +#endif + } + + if (gg_proxy_enabled) { + hostname = gg_proxy_host; + sess->proxy_port = port = gg_proxy_port; + } else { + hostname = GG_APPMSG_HOST; + port = GG_APPMSG_PORT; + } + + if (p->hash_type) + sess->hash_type = p->hash_type; + else + sess->hash_type = GG_LOGIN_HASH_SHA1; + + if (!p->async) { + struct in_addr addr; + + if (!sess->server_addr) { + if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { + if (gg_gethostbyname_real(hostname, &addr, 0) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname); +#ifdef GG_CONFIG_MIRANDA + errno = EACCES; + *gg_failno = GG_FAILURE_RESOLVING; +#endif + goto fail; + } + } + } else { + addr.s_addr = sess->server_addr; + port = sess->port; + } + + sess->hub_addr = addr.s_addr; + + if (gg_proxy_enabled) + sess->proxy_addr = addr.s_addr; + +#ifdef GG_CONFIG_MIRANDA + if ((sess->fd = gg_connect_internal(&addr, port, 0, gg_sock)) == -1) { +#else + if ((sess->fd = gg_connect(&addr, port, 0)) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + + /* nie wyszło? próbujemy portu 443. */ + if (sess->server_addr) { + sess->port = GG_HTTPS_PORT; + +#ifdef GG_CONFIG_MIRANDA + if ((sess->fd = gg_connect_internal(&addr, GG_HTTPS_PORT, 0, gg_sock)) == -1) { +#else + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, 0)) == -1) { +#endif + /* ostatnia deska ratunku zawiodła? + * w takim razie zwijamy manatki. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + goto fail; + } + } + + if (sess->server_addr) + sess->state = GG_STATE_CONNECTING_GG; + else + sess->state = GG_STATE_CONNECTING_HUB; + + while (sess->state != GG_STATE_CONNECTED) { + struct gg_event *e; + + if (!(e = gg_watch_fd(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n"); + goto fail; + } + + if (e->type == GG_EVENT_CONN_FAILED) { + errno = EACCES; +#ifdef GG_CONFIG_MIRANDA + *gg_failno = e->event.failure; +#endif + gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n"); + gg_event_free(e); + goto fail; + } + + gg_event_free(e); + } + + return sess; + } + + if (!sess->server_addr || gg_proxy_enabled) { + if (sess->resolver_start(&sess->fd, &sess->resolver, hostname) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->soft_timeout = 1; + } + + return sess; + +fail: + gg_free_session(sess); + + return NULL; +} + +/** + * Wysyła do serwera pakiet utrzymania połączenia. + * + * Klient powinien regularnie co minutę wysyłać pakiet utrzymania połączenia, + * inaczej serwer uzna, że klient stracił łączność z siecią i zerwie + * połączenie. + * + * \param sess Struktura sesji + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup login + */ +int gg_ping(struct gg_session *sess) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_ping(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + return gg_send_packet(sess, GG_PING, NULL); +} + +/** + * Kończy połączenie z serwerem. + * + * Funkcja nie zwalnia zasobów, więc po jej wywołaniu należy użyć + * \c gg_free_session(). Jeśli chce się ustawić opis niedostępności, należy + * wcześniej wywołać funkcję \c gg_change_status_descr() lub + * \c gg_change_status_descr_time(). + * + * \note Jeśli w buforze nadawczym połączenia z serwerem znajdują się jeszcze + * dane (np. z powodu strat pakietów na łączu), prawdopodobnie zostaną one + * utracone przy zrywaniu połączenia. Aby mieć pewność, że opis statusu + * zostanie zachowany, należy ustawić stan \c GG_STATUS_NOT_AVAIL_DESCR + * za pomocą funkcji \c gg_change_status_descr() i poczekać na zdarzenie + * \c GG_EVENT_DISCONNECT_ACK. + * + * \param sess Struktura sesji + * + * \ingroup login + */ +void gg_logoff(struct gg_session *sess) +{ + if (!sess) + return; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess); + +#ifdef GG_CONFIG_MIRANDA + if (sess->ssl != NULL) + si.shutdown(sess->ssl); +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) + SSL_shutdown(sess->ssl); +#endif + + sess->resolver_cleanup(&sess->resolver, 1); + + if (sess->fd != -1) { + shutdown(sess->fd, SHUT_RDWR); + gg_sock_close(sess->fd); + sess->fd = -1; + } + + if (sess->send_buf) { + free(sess->send_buf); + sess->send_buf = NULL; + sess->send_left = 0; + } +} + +/** + * Zwalnia zasoby używane przez połączenie z serwerem. Funkcję należy wywołać + * po zamknięciu połączenia z serwerem, by nie doprowadzić do wycieku zasobów + * systemowych. + * + * \param sess Struktura sesji + * + * \ingroup login + */ +void gg_free_session(struct gg_session *sess) +{ + struct gg_dcc7 *dcc; + + if (!sess) + return; + + /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */ + + free(sess->password); + free(sess->initial_descr); + free(sess->client_version); + free(sess->header_buf); + +#ifdef GG_CONFIG_MIRANDA + if (sess->ssl != NULL) + si.sfree(sess->ssl); +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) + SSL_free(sess->ssl); + + if (sess->ssl_ctx) + SSL_CTX_free(sess->ssl_ctx); +#endif + + sess->resolver_cleanup(&sess->resolver, 1); + + if (sess->fd != -1) + gg_sock_close(sess->fd); + + while (sess->images) + gg_image_queue_remove(sess, sess->images, 1); + + free(sess->send_buf); + + for (dcc = sess->dcc7_list; dcc; dcc = dcc->next) + dcc->sess = NULL; + + free(sess); +} + +#ifndef DOXYGEN + +/** + * \internal Funkcja wysyłająca pakiet zmiany statusu użytkownika. + * + * \param sess Struktura sesji + * \param status Nowy status użytkownika + * \param descr Opis statusu użytkownika (lub \c NULL) + * \param time Czas powrotu w postaci uniksowego znacznika czasu (lub 0) + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup status + */ +static int gg_change_status_common(struct gg_session *sess, int status, const char *descr, int time) +{ + char *new_descr = NULL; + uint32_t new_time; + int descr_len = 0; + int descr_len_max; + int packet_type; + int append_null = 0; + int res; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + /* XXX, obcinać stany których stary protokół niezna (czyt. dnd->aw; ffc->av) */ + + /* dodaj flagę obsługi połączeń głosowych zgodną z GG 7.x */ + if ((sess->protocol_version >= 0x2a) && (sess->protocol_version < 0x2d /* ? */ ) && (sess->protocol_flags & GG_HAS_AUDIO_MASK) && !GG_S_I(status)) + status |= GG_STATUS_VOICE_MASK; + + sess->status = status; + + if (sess->protocol_version >= 0x2d) { + if (descr != NULL && sess->encoding != GG_ENCODING_UTF8) { + new_descr = gg_cp_to_utf8(descr); + + if (!new_descr) + return -1; + } + + if (sess->protocol_version >= 0x2e) + packet_type = GG_NEW_STATUS80; + else /* sess->protocol_version == 0x2d */ + packet_type = GG_NEW_STATUS80BETA; + descr_len_max = GG_STATUS_DESCR_MAXSIZE; + append_null = 1; + + } else { + packet_type = GG_NEW_STATUS; + descr_len_max = GG_STATUS_DESCR_MAXSIZE_PRE_8_0; + + if (time != 0) + append_null = 1; + } + + if (descr) { + descr_len = (int)strlen((new_descr) ? new_descr : descr); + + if (descr_len > descr_len_max) + descr_len = descr_len_max; + + // XXX pamiętać o tym, żeby nie ucinać w środku znaku utf-8 + } + + if (time) + new_time = gg_fix32(time); + + if (packet_type == GG_NEW_STATUS80) { + struct gg_new_status80 p; + + p.status = gg_fix32(status); + p.flags = gg_fix32(sess->status_flags); + p.description_size = gg_fix32(descr_len); + res = gg_send_packet(sess, + packet_type, + &p, + sizeof(p), + (new_descr) ? new_descr : descr, + descr_len, + NULL); + + } else { + struct gg_new_status p; + + p.status = gg_fix32(status); + res = gg_send_packet(sess, + packet_type, + &p, + sizeof(p), + (new_descr) ? new_descr : descr, + descr_len, + (append_null) ? "\0" : NULL, + (append_null) ? 1 : 0, + (time) ? &new_time : NULL, + (time) ? sizeof(new_time) : 0, + NULL); + } + + free(new_descr); + return res; +} + +#endif /* DOXYGEN */ + +/** + * Zmienia status użytkownika. + * + * \param sess Struktura sesji + * \param status Nowy status użytkownika + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup status + */ +int gg_change_status(struct gg_session *sess, int status) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status); + + return gg_change_status_common(sess, status, NULL, 0); +} + +/** + * Zmienia status użytkownika na status opisowy. + * + * \param sess Struktura sesji + * \param status Nowy status użytkownika + * \param descr Opis statusu użytkownika + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup status + */ +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr); + + return gg_change_status_common(sess, status, descr, 0); +} + +/** + * Zmienia status użytkownika na status opisowy z podanym czasem powrotu. + * + * \param sess Struktura sesji + * \param status Nowy status użytkownika + * \param descr Opis statusu użytkownika + * \param time Czas powrotu w postaci uniksowego znacznika czasu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup status + */ +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time); + + return gg_change_status_common(sess, status, descr, time); +} + +/** + * Funkcja zmieniająca flagi statusu. + * + * \param sess Struktura sesji + * \param flags Nowe flagi statusu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \note Aby zmiany weszły w życie, należy ponownie ustawić status za pomocą + * funkcji z rodziny \c gg_change_status(). + * + * \ingroup status + */ +int gg_change_status_flags(struct gg_session *sess, int flags) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_flags(%p, 0x%08x);\n", sess, flags); + + if (sess == NULL) { + errno = EFAULT; + return -1; + } + + sess->status_flags = flags; + + return 0; +} + +/** + * Wysyła wiadomość do użytkownika. + * + * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipient Numer adresata + * \param message Treść wiadomości + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message); + + return gg_send_message_confer_richtext(sess, msgclass, 1, &recipient, message, NULL, 0); +} + +/** + * Wysyła wiadomość formatowaną. + * + * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipient Numer adresata + * \param message Treść wiadomości + * \param format Informacje o formatowaniu + * \param formatlen Długość informacji o formatowaniu + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen); + + return gg_send_message_confer_richtext(sess, msgclass, 1, &recipient, message, format, formatlen); +} + +/** + * Wysyła wiadomość w ramach konferencji. + * + * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipients_count Liczba adresatów + * \param recipients Wskaźnik do tablicy z numerami adresatów + * \param message Treść wiadomości + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message); + + return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0); +} + +/** + * \internal Dodaje tekst na koniec bufora. + * + * \param dst Wskaźnik na bufor roboczy + * \param pos Wskaźnik na aktualne położenie w buforze roboczym + * \param src Dodawany tekst + * \param len Długość dodawanego tekstu + */ +static void gg_append(char *dst, int *pos, const void *src, int len) +{ + if (dst != NULL) + memcpy(&dst[*pos], src, len); + + *pos += len; +} + +/** + * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML. + * + * \param dst Bufor wynikowy (może być \c NULL) + * \param src Tekst źródłowy w UTF-8 + * \param format Atrybuty tekstu źródłowego + * \param format_len Długość bloku atrybutów tekstu źródłowego + * + * \note Wynikowy tekst nie jest idealnym kodem HTML, ponieważ ma jak + * dokładniej odzwierciedlać to, co wygenerowałby oryginalny klient. + * + * \note Dokleja \c \\0 na końcu bufora wynikowego. + * + * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). + */ +static int gg_convert_to_html(char *dst, const char *src, const unsigned char *format, int format_len) +{ + const char span_fmt[] = ""; + const int span_len = 75; + const char img_fmt[] = ""; + const int img_len = 29; + int char_pos = 0; + int format_idx = 0; + unsigned char old_attr = 0; + const unsigned char *color = (const unsigned char*) "\x00\x00\x00"; + int len, i; + const unsigned char *format_ = (const unsigned char*) format; + + len = 0; + + /* Nie mamy atrybutów dla pierwsze znaku, a tekst nie jest pusty, więc + * tak czy inaczej trzeba otworzyć . */ + + if (src[0] != 0 && (format_idx + 3 > format_len || (format_[format_idx] | (format_[format_idx + 1] << 8)) != 0)) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, 0, 0, 0); + + len += span_len; + } + + /* Pętla przechodzi też przez kończące \0, żeby móc dokleić obrazek + * na końcu tekstu. */ + + for (i = 0; ; i++) { + /* Analizuj atrybuty tak długo jak dotyczą aktualnego znaku. */ + for (;;) { + unsigned char attr; + int attr_pos; + + if (format_idx + 3 > format_len) + break; + + attr_pos = format_[format_idx] | (format_[format_idx + 1] << 8); + + if (attr_pos != char_pos) + break; + + attr = format_[format_idx + 2]; + + /* Nie doklejaj atrybutów na końcu, co najwyżej obrazki. */ + + if (src[i] == 0) + attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR); + + format_idx += 3; + + if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0 || (attr == 0 && old_attr != 0)) { + if (char_pos != 0) { + if ((old_attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "", 4); + + if ((old_attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "", 4); + + if ((old_attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "", 4); + + if (src[i] != 0) + gg_append(dst, &len, "", 7); + } + + if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) { + color = &format_[format_idx]; + format_idx += 3; + } else { + color = (unsigned char*) "\x00\x00\x00"; + } + + if (src[i] != 0) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, color[0], color[1], color[2]); + len += span_len; + } + } else if (char_pos == 0 && src[0] != 0) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, 0, 0, 0); + len += span_len; + } + + if ((attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "", 3); + + if ((attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "", 3); + + if ((attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "", 3); + + if (((attr & GG_FONT_IMAGE) != 0) && (format_idx + 10 <= format_len)) { + if (dst != NULL) { + sprintf(&dst[len], img_fmt, + format_[format_idx + 9], + format_[format_idx + 8], + format_[format_idx + 7], + format_[format_idx + 6], + format_[format_idx + 5], + format_[format_idx + 4], + format_[format_idx + 3], + format_[format_idx + 2]); + } + + len += img_len; + format_idx += 10; + } + + old_attr = attr; + } + + /* Doklej znak zachowując htmlowe escapowanie. */ + + switch (src[i]) { + case '&': + gg_append(dst, &len, "&", 5); + break; + case '<': + gg_append(dst, &len, "<", 4); + break; + case '>': + gg_append(dst, &len, ">", 4); + break; + case '\'': + gg_append(dst, &len, "'", 6); + break; + case '\"': + gg_append(dst, &len, """, 6); + break; + case '\n': + gg_append(dst, &len, "
", 4); + break; + case '\r': + case 0: + break; + default: + if (dst != NULL) + dst[len] = src[i]; + len++; + } + + /* Sprawdź, czy bajt nie jest kontynuacją znaku unikodowego. */ + + if ((src[i] & 0xc0) != 0xc0) + char_pos++; + + if (src[i] == 0) + break; + } + + /* Zamknij tagi. */ + + if ((old_attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "
", 4); + + if ((old_attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "
", 4); + + if ((old_attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "
", 4); + + if (src[0] != 0) + gg_append(dst, &len, "
", 7); + + if (dst != NULL) + dst[len] = 0; + + return len; +} + +/** + * Wysyła wiadomość formatowaną w ramach konferencji. + * + * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipients_count Liczba adresatów + * \param recipients Wskaźnik do tablicy z numerami adresatów + * \param message Treść wiadomości + * \param format Informacje o formatowaniu + * \param formatlen Długość informacji o formatowaniu + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + struct gg_send_msg80 s80; + struct gg_msg_recipients r; + char *cp_msg = NULL; + char *utf_msg = NULL; + char *html_msg = NULL; + int seq_no; + int i, j, k; + uin_t *recps; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (message == NULL || recipients_count <= 0 || recipients_count > 0xffff || (recipients_count != 1 && recipients == NULL)) { + errno = EINVAL; + return -1; + } + + if (sess->encoding == GG_ENCODING_UTF8) { + if (!(cp_msg = gg_utf8_to_cp((const char *) message))) + return -1; + + utf_msg = (char*) message; + } else { + if (sess->protocol_version >= 0x2d) { + if (!(utf_msg = gg_cp_to_utf8((const char *) message))) + return -1; + } + + cp_msg = (char*) message; + } + + if (sess->protocol_version < 0x2d) { + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + seq_no = sess->seq; + sess->seq += (rand() % 0x300) + 0x300; + + s.msgclass = gg_fix32(msgclass); + s.seq = gg_fix32(seq_no); + } else { + int len; + + // Drobne odchylenie od protokołu. Jeśli wysyłamy kilka + // wiadomości w ciągu jednej sekundy, zwiększamy poprzednią + // wartość, żeby każda wiadomość miała unikalny numer. + + seq_no = (int)time(NULL); + + if (seq_no <= sess->seq) + seq_no = sess->seq + 1; + + sess->seq = seq_no; + + if (format == NULL || formatlen < 3) { + format = (unsigned char*) "\x02\x06\x00\x00\x00\x08\x00\x00\x00"; + formatlen = 9; + } + + len = gg_convert_to_html(NULL, utf_msg, format + 3, formatlen - 3); + + html_msg = malloc(len + 1); + + if (html_msg == NULL) { + seq_no = -1; + goto cleanup; + } + + gg_convert_to_html(html_msg, utf_msg, format + 3, formatlen - 3); + + s80.seq = gg_fix32(seq_no); + s80.msgclass = gg_fix32(msgclass); + s80.offset_plain = gg_fix32(sizeof(s80) + (uint32_t)strlen(html_msg) + 1); + s80.offset_attr = gg_fix32(sizeof(s80) + (uint32_t)strlen(html_msg) + 1 + (uint32_t)strlen(cp_msg) + 1); + } + + if (recipients_count > 1) { + r.flag = 0x01; + r.count = gg_fix32(recipients_count - 1); + + recps = malloc(sizeof(uin_t) * recipients_count); + + if (!recps) { + seq_no = -1; + goto cleanup; + } + + for (i = 0; i < recipients_count; i++) { + for (j = 0, k = 0; j < recipients_count; j++) { + if (recipients[j] != recipients[i]) { + recps[k] = gg_fix32(recipients[j]); + k++; + } + } + + if (sess->protocol_version < 0x2d) { + s.recipient = gg_fix32(recipients[i]); + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) + seq_no = -1; + } else { + s80.recipient = gg_fix32(recipients[i]); + + if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), html_msg, strlen(html_msg) + 1, cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) + seq_no = -1; + } + } + + free(recps); + } else { + if (sess->protocol_version < 0x2d) { + s.recipient = gg_fix32(recipients[0]); + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1) + seq_no = -1; + } else { + s80.recipient = gg_fix32(recipients[0]); + + if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), html_msg, strlen(html_msg) + 1, cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1) + seq_no = -1; + } + } + +cleanup: + if (cp_msg != (char*) message) + free(cp_msg); + + if (utf_msg != (char*) message) + free(utf_msg); + + free(html_msg); + + return seq_no; +} + +/** + * Wysyła wiadomość binarną przeznaczoną dla klienta. + * + * Wiadomości między klientami przesyła się np. w celu wywołania zwrotnego + * połączenia bezpośredniego. Funkcja zwraca losowy numer sekwencyjny, + * który można zignorować albo wykorzystać do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomości + * \param recipient Numer adresata + * \param message Treść wiadomości + * \param message_len Długość wiadomości + * + * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu. + * + * \ingroup messages + */ +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len) +{ + struct gg_send_msg s; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_ctcp(%p, %d, %u, ...);\n", sess, msgclass, recipient); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(msgclass); + + return gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, message_len, NULL); +} + +/** + * Wysyła żądanie obrazka o podanych parametrach. + * + * Wiadomości obrazkowe nie zawierają samych obrazków, a tylko ich rozmiary + * i sumy kontrolne. Odbiorca najpierw szuka obrazków w swojej pamięci + * podręcznej i dopiero gdy ich nie znajdzie, wysyła żądanie do nadawcy. + * Wynik zostanie przekazany zdarzeniem \c GG_EVENT_IMAGE_REPLY. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param size Rozmiar obrazka w bajtach + * \param crc32 Suma kontrola obrazka + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup messages + */ +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32) +{ + struct gg_send_msg s; + struct gg_msg_image_request r; + char dummy = 0; + int res; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_image_request(%p, %d, %u, 0x%.4x);\n", sess, recipient, size, crc32); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + r.flag = 0x04; + r.size = gg_fix32(size); + r.crc32 = gg_fix32(crc32); + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), &dummy, 1, &r, sizeof(r), NULL); + + if (!res) { + struct gg_image_queue *q = malloc(sizeof(*q)); + char *buf; + + if (!q) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n"); + return -1; + } + + buf = malloc(size); + if (size && !buf) + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_request() not enough memory for image\n"); + free(q); + return -1; + } + + memset(q, 0, sizeof(*q)); + + q->sender = recipient; + q->size = size; + q->crc32 = crc32; + q->image = buf; + + if (!sess->images) + sess->images = q; + else { + struct gg_image_queue *qq; + + for (qq = sess->images; qq->next; qq = qq->next) + ; + + qq->next = q; + } + } + + return res; +} + +/** + * Wysyła żądany obrazek. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param filename Nazwa pliku + * \param image Bufor z obrazkiem + * \param size Rozmiar obrazka + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup messages + */ +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size) +{ + struct gg_msg_image_reply *r; + struct gg_send_msg s; + const char *tmp; + char buf[1910]; + int res = -1; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_image_reply(%p, %d, \"%s\", %p, %d);\n", sess, recipient, filename, image, size); + + if (!sess || !filename || !image) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + /* wytnij ścieżki, zostaw tylko nazwę pliku */ + while ((tmp = strrchr(filename, '/')) || (tmp = strrchr(filename, '\\'))) + filename = tmp + 1; + + if (strlen(filename) < 1 || strlen(filename) > 1024) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + buf[0] = 0; + r = (void*) &buf[1]; + + r->flag = 0x05; + r->size = gg_fix32(size); + r->crc32 = gg_fix32(gg_crc32(0, (unsigned char*) image, size)); + + while (size > 0) { + int buflen, chunklen; + + /* \0 + struct gg_msg_image_reply */ + buflen = sizeof(struct gg_msg_image_reply) + 1; + + /* w pierwszym kawałku jest nazwa pliku */ + if (r->flag == 0x05) { + strcpy(buf + buflen, filename); + buflen += (int)strlen(filename) + 1; + } + + chunklen = (size >= (int)sizeof(buf) - buflen) ? ((int)sizeof(buf) - buflen) : size; + + memcpy(buf + buflen, image, chunklen); + size -= chunklen; + image += chunklen; + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), buf, buflen + chunklen, NULL); + + if (res == -1) + break; + + r->flag = 0x06; + } + + return res; +} + +/** + * Wysyła do serwera listę kontaktów. + * + * Funkcja informuje serwer o liście kontaktów, których statusy będą + * obserwowane lub kontaktów, które bedą blokowane. Dla każdego z \c count + * kontaktów tablica \c userlist zawiera numer, a tablica \c types rodzaj + * kontaktu (\c GG_USER_NORMAL, \c GG_USER_OFFLINE, \c GG_USER_BLOCKED). + * + * Listę kontaktów należy \b zawsze wysyłać po połączeniu, nawet jeśli + * jest pusta. + * + * \param sess Struktura sesji + * \param userlist Wskaźnik do tablicy numerów kontaktów + * \param types Wskaźnik do tablicy rodzajów kontaktów + * \param count Liczba kontaktów + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count) +{ + struct gg_notify *n; + uin_t *u; + char *t; + int i, res = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify_ex(%p, %p, %p, %d);\n", sess, userlist, types, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = *t; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + free(n); + res = -1; + break; + } + + count -= part_count; + userlist += part_count; + types += part_count; + + free(n); + } + + return res; +} + +/** + * Wysyła do serwera listę kontaktów. + * + * Funkcja jest odpowiednikiem \c gg_notify_ex(), gdzie wszystkie kontakty + * są rodzaju \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param userlist Wskaźnik do tablicy numerów kontaktów + * \param count Liczba kontaktów + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_notify(struct gg_session *sess, uin_t *userlist, int count) +{ + struct gg_notify *n; + uin_t *u; + int i, res = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, i = 0; i < part_count; u++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = GG_USER_NORMAL; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + res = -1; + free(n); + break; + } + + free(n); + + userlist += part_count; + count -= part_count; + } + + return res; +} + +/** + * Dodaje kontakt. + * + * Dodaje do listy kontaktów dany numer w trakcie połączenia. Aby zmienić + * rodzaj kontaktu (np. z normalnego na zablokowany), należy najpierw usunąć + * poprzedni rodzaj, ponieważ serwer operuje na maskach bitowych. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * \param type Rodzaj kontaktu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL); +} + +/** + * Dodaje kontakt. + * + * Funkcja jest odpowiednikiem \c gg_add_notify_ex(), gdzie rodzaj wszystkich + * kontaktów to \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_add_notify(struct gg_session *sess, uin_t uin) +{ + return gg_add_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/** + * Usuwa kontakt. + * + * Usuwa z listy kontaktów dany numer w trakcie połączenia. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * \param type Rodzaj kontaktu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL); +} + +/** + * Usuwa kontakt. + * + * Funkcja jest odpowiednikiem \c gg_add_notify_ex(), gdzie rodzaj wszystkich + * kontaktów to \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup contacts + */ +int gg_remove_notify(struct gg_session *sess, uin_t uin) +{ + return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/** + * Wysyła do serwera zapytanie dotyczące listy kontaktów. + * + * Funkcja służy do importu lub eksportu listy kontaktów do serwera. + * W odróżnieniu od funkcji \c gg_notify(), ta lista kontaktów jest przez + * serwer jedynie przechowywana i nie ma wpływu na połączenie. Format + * listy kontaktów jest ignorowany przez serwer, ale ze względu na + * kompatybilność z innymi klientami, należy przechowywać dane w tym samym + * formacie co oryginalny klient Gadu-Gadu. + * + * Program nie musi się przejmować fragmentacją listy kontaktów wynikającą + * z protokołu -- wysyła i odbiera kompletną listę. + * + * \param sess Struktura sesji + * \param type Rodzaj zapytania + * \param request Treść zapytania (może być równe NULL) + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup importexport + */ +int gg_userlist_request(struct gg_session *sess, char type, const char *request) +{ + int len; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!request) { + sess->userlist_blocks = 1; + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), NULL); + } + + len = (int)strlen(request); + + sess->userlist_blocks = 0; + + while (len > 2047) { + sess->userlist_blocks++; + + if (gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, 2047, NULL) == -1) + return -1; + + if (type == GG_USERLIST_PUT) + type = GG_USERLIST_PUT_MORE; + + request += 2047; + len -= 2047; + } + + sess->userlist_blocks++; + + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL); +} + +/** + * Informuje rozmówcę o pisaniu wiadomości. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param length Długość wiadomości lub 0 jeśli jest pusta + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup messages + */ +int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length){ + struct gg_typing_notification pkt; + uin_t uin; + + pkt.length = gg_fix16((uint16_t)length); + uin = gg_fix32(recipient); + memcpy(&pkt.uin, &uin, sizeof(uin_t)); + + return gg_send_packet(sess, GG_TYPING_NOTIFICATION, &pkt, sizeof(pkt), NULL); +} + +/** + * Rozłącza inną sesję multilogowania. + * + * \param gs Struktura sesji + * \param conn_id Sesja do rozłączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup login + */ +int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id) +{ + struct gg_multilogon_disconnect pkt; + + pkt.conn_id = conn_id; + + return gg_send_packet(gs, GG_MULTILOGON_DISCONNECT, &pkt, sizeof(pkt), NULL); +} + +/* @} */ + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ -- cgit v1.2.3