/* 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: */