From bcb704366cb5e333a626c18c308c7e0448a8e69f Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kopete/protocols/gadu/libgadu/libgadu.c | 1818 +++++++++++++++++++++++++++++++ 1 file changed, 1818 insertions(+) create mode 100644 kopete/protocols/gadu/libgadu/libgadu.c (limited to 'kopete/protocols/gadu/libgadu/libgadu.c') diff --git a/kopete/protocols/gadu/libgadu/libgadu.c b/kopete/protocols/gadu/libgadu/libgadu.c new file mode 100644 index 00000000..47b687f0 --- /dev/null +++ b/kopete/protocols/gadu/libgadu/libgadu.c @@ -0,0 +1,1818 @@ +/* $Id$ */ + +/* + * (C) Copyright 2001-2006 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. + */ + +#include +#include +#include +#include +#include +#ifdef sun +# include +#endif + +#include "libgadu-config.h" + +#include +#include +#ifdef __GG_LIBGADU_HAVE_PTHREAD +# include +#endif +#include +#include +#include +#include +#include +#include +#ifdef __GG_LIBGADU_HAVE_OPENSSL +# include +# include +#endif + +#include "compat.h" +#include "libgadu.h" + +int gg_debug_level = 0; +void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; + +int gg_dcc_port = 0; +unsigned long gg_dcc_ip = 0; + +unsigned long gg_local_ip = 0; +/* + * zmienne opisujące parametry proxy http. + */ +char *gg_proxy_host = NULL; +int gg_proxy_port = 0; +int gg_proxy_enabled = 0; +int gg_proxy_http_only = 0; +char *gg_proxy_username = NULL; +char *gg_proxy_password = NULL; + +#ifndef lint +static char rcsid[] +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif += "$Id$"; +#endif + +/* + * gg_libgadu_version() + * + * zwraca wersję libgadu. + * + * - brak + * + * wersja libgadu. + */ +const char *gg_libgadu_version() +{ + return GG_LIBGADU_VERSION; +} + +/* + * gg_fix32() + * + * zamienia kolejność bajtów w liczbie 32-bitowej tak, by odpowiadała + * kolejności bajtów w protokole GG. ze względu na LE-owość serwera, + * zamienia tylko na maszynach BE-wych. + * + * - x - liczba do zamiany + * + * liczba z odpowiednią kolejnością bajtów. + */ +uint32_t gg_fix32(uint32_t x) +{ +#ifndef __GG_LIBGADU_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 +} + +/* + * gg_fix16() + * + * zamienia kolejność bajtów w liczbie 16-bitowej tak, by odpowiadała + * kolejności bajtów w protokole GG. ze względu na LE-owość serwera, + * zamienia tylko na maszynach BE-wych. + * + * - x - liczba do zamiany + * + * liczba z odpowiednią kolejnością bajtów. + */ +uint16_t gg_fix16(uint16_t x) +{ +#ifndef __GG_LIBGADU_BIGENDIAN + return x; +#else + return (uint16_t) + (((x & (uint16_t) 0x00ffU) << 8) | + ((x & (uint16_t) 0xff00U) >> 8)); +#endif +} + +/* + * gg_login_hash() // funkcja wewnętrzna + * + * liczy hash z hasła i danego seeda. + * + * - password - hasło do hashowania + * - seed - wartość podana przez serwer + * + * hash. + */ +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; +} + +/* + * gg_resolve() // funkcja wewnętrzna + * + * tworzy potok, forkuje się i w drugim procesie zaczyna resolvować + * podanego hosta. zapisuje w sesji deskryptor potoku. jeśli coś tam + * będzie gotowego, znaczy, że można wczytać struct in_addr. jeśli + * nie znajdzie, zwraca INADDR_NONE. + * + * - fd - wskaźnik gdzie wrzucić deskryptor + * - pid - gdzie wrzucić pid procesu potomnego + * - hostname - nazwa hosta do zresolvowania + * + * 0, -1. + */ +int gg_resolve(int *fd, int *pid, const char *hostname) +{ + int pipes[2], res; + struct in_addr a; + int errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve(%p, %p, \"%s\");\n", fd, pid, hostname); + + if (!fd || !pid) { + errno = EFAULT; + return -1; + } + + if (pipe(pipes) == -1) + return -1; + + if ((res = fork()) == -1) { + errno2 = errno; + close(pipes[0]); + close(pipes[1]); + errno = errno2; + return -1; + } + + if (!res) { + close(pipes[0]); + + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(pipes[1], &a, sizeof(a)); + + exit(0); + } + + close(pipes[1]); + + *fd = pipes[0]; + *pid = res; + + return 0; +} + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + +struct gg_resolve_pthread_data { + char *hostname; + int fd; +}; + +static void *gg_resolve_pthread_thread(void *arg) +{ + struct gg_resolve_pthread_data *d = arg; + struct in_addr a; + + pthread_detach(pthread_self()); + + if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(d->hostname))) + a.s_addr = INADDR_NONE; + else { + a.s_addr = hn->s_addr; + free(hn); + } + } + + write(d->fd, &a, sizeof(a)); + close(d->fd); + + free(d->hostname); + d->hostname = NULL; + + free(d); + + pthread_exit(NULL); + + return NULL; /* żeby kompilator nie marudził */ +} + +/* + * gg_resolve_pthread() // funkcja wewnętrzna + * + * tworzy potok, nowy wątek i w nim zaczyna resolvować podanego hosta. + * zapisuje w sesji deskryptor potoku. jeśli coś tam będzie gotowego, + * znaczy, że można wczytać struct in_addr. jeśli nie znajdzie, zwraca + * INADDR_NONE. + * + * - fd - wskaźnik do zmiennej przechowującej desktyptor resolvera + * - resolver - wskaźnik do wskaźnika resolvera + * - hostname - nazwa hosta do zresolvowania + * + * 0, -1. + */ +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname) +{ + struct gg_resolve_pthread_data *d = NULL; + pthread_t *tmp; + int pipes[2], new_errno; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve_pthread(%p, %p, \"%s\");\n", fd, resolver, hostname); + + if (!resolver || !fd || !hostname) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (!(tmp = malloc(sizeof(pthread_t)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory for pthread id\n"); + return -1; + } + + if (pipe(pipes) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); + free(tmp); + return -1; + } + + if (!(d = malloc(sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + d->hostname = NULL; + + if (!(d->hostname = strdup(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + d->fd = pipes[1]; + + if (pthread_create(tmp, NULL, gg_resolve_pthread_thread, d)) { + gg_debug(GG_DEBUG_MISC, "// gg_resolve_phread() unable to create thread\n"); + new_errno = errno; + goto cleanup; + } + + gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() %p\n", tmp); + + *resolver = tmp; + + *fd = pipes[0]; + + return 0; + +cleanup: + if (d) { + free(d->hostname); + free(d); + } + + close(pipes[0]); + close(pipes[1]); + + free(tmp); + + errno = new_errno; + + return -1; +} + +#endif + +/* + * gg_read() // funkcja pomocnicza + * + * czyta z gniazda określoną ilość bajtów. bierze pod uwagę, czy mamy + * połączenie zwykłe czy TLS. + * + * - sess - sesja, + * - buf - bufor, + * - length - ilość bajtów, + * + * takie same wartości jak read(). + */ +int gg_read(struct gg_session *sess, char *buf, int length) +{ + int res; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_read(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_READ) + errno = EAGAIN; + + return -1; + } + } else +#endif + res = read(sess->fd, buf, length); + + return res; +} + +/* + * gg_write() // funkcja pomocnicza + * + * zapisuje do gniazda określoną ilość bajtów. bierze pod uwagę, czy mamy + * połączenie zwykłe czy TLS. + * + * - sess - sesja, + * - buf - bufor, + * - length - ilość bajtów, + * + * takie same wartości jak write(). + */ +int gg_write(struct gg_session *sess, const char *buf, int length) +{ + int res = 0; + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) { + int err; + + res = SSL_write(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_WANT_WRITE) + errno = EAGAIN; + + return -1; + } + } else +#endif + { + int written = 0; + + while (written < length) { + res = write(sess->fd, buf + written, length - written); + + if (res == -1) { + if (errno == EAGAIN) + continue; + else + break; + } else { + written += res; + res = written; + } + } + } + + return res; +} + +/* + * gg_recv_packet() // funkcja wewnętrzna + * + * odbiera jeden pakiet i zwraca wskaźnik do niego. pamięć po nim + * należy zwolnić za pomocą free(). + * + * - sess - opis sesji + * + * w przypadku błędu NULL, kod błędu w errno. należy zwrócić uwagę, że gdy + * połączenie jest nieblokujące, a kod błędu wynosi EAGAIN, nie udało się + * odczytać całego pakietu i nie należy tego traktować jako błąd. + */ +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(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(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(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(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n"); + return NULL; + } + + if (ret == -1) { + if (errno == EINTR) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() interrupted system call, resuming\n"); + continue; + } + + if (errno == EAGAIN) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n"); + + if (!(sess->header_buf = malloc(sess->header_done))) { + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n"); + return NULL; + } + + memcpy(sess->header_buf, &h, sess->header_done); + + return NULL; + } + + gg_debug(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(GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length); + errno = ERANGE; + return NULL; + } + + if (sess->recv_left > 0) { + gg_debug(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(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(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(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n"); + errno = ECONNRESET; + return NULL; + } + if (ret > -1 && ret <= size) { + offset += ret; + size -= ret; + } else if (ret == -1) { + int errno2 = errno; + + gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno = errno2; + + if (errno == EAGAIN) { + gg_debug(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; + } + if (errno != EINTR) { + free(buf); + return NULL; + } + } + } + + sess->recv_left = 0; + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug(GG_DEBUG_DUMP, "// gg_recv_packet(%.2x)", h.type); + for (i = 0; i < sizeof(h) + h.length; i++) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } + + return buf; +} + +/* + * gg_send_packet() // funkcja wewnętrzna + * + * konstruuje pakiet i wysyła go do serwera. + * + * - sock - deskryptor gniazda + * - type - typ pakietu + * - payload_1 - pierwsza część pakietu + * - payload_length_1 - długość pierwszej części + * - payload_2 - druga część pakietu + * - payload_length_2 - długość drugiej części + * - ... - kolejne części pakietu i ich długości + * - NULL - końcowym parametr (konieczny!) + * + * jeśli się powiodło, zwraca 0, w przypadku błędu -1. jeśli errno == ENOMEM, + * zabrakło pamięci. inaczej był błąd przy wysyłaniu pakietu. dla errno == 0 + * nie wysłano całego pakietu. + */ +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(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(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(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)); + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + gg_debug(GG_DEBUG_DUMP, "// gg_send_packet(0x%.2x)", gg_fix32(h->type)); + for (i = 0; i < tmp_length; ++i) + gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]); + gg_debug(GG_DEBUG_DUMP, "\n"); + } + + if ((res = gg_write(sess, tmp, tmp_length)) < tmp_length) { + gg_debug(GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno)); + free(tmp); + return -1; + } + + free(tmp); + return 0; +} + +/* + * gg_session_callback() // funkcja wewnętrzna + * + * wywoływany z gg_session->callback, wykonuje gg_watch_fd() i pakuje + * do gg_session->event jego wynik. + */ +static int gg_session_callback(struct gg_session *s) +{ + if (!s) { + errno = EFAULT; + return -1; + } + + return ((s->event = gg_watch_fd(s)) != NULL) ? 0 : -1; +} + +/* + * gg_login() + * + * rozpoczyna procedurę łączenia się z serwerem. resztę obsługuje się przez + * gg_watch_fd(). + * + * UWAGA! program musi obsłużyć SIGCHLD, jeśli łączy się asynchronicznie, + * żeby poprawnie zamknąć proces resolvera. + * + * - p - struktura opisująca początkowy stan. wymagane pola: uin, + * password + * + * w przypadku błędu NULL, jeśli idzie dobrze (async) albo poszło + * dobrze (sync), zwróci wskaźnik do zaalokowanej struct gg_session. + */ +struct gg_session *gg_login(const struct gg_login_params *p) +{ + 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->status_descr && !(sess->initial_descr = strdup(p->status_descr))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n"); + 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->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION; + if (p->era_omnix) + sess->protocol_version |= GG_ERA_OMNIX_MASK; + if (p->has_audio) + sess->protocol_version |= 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; + + if (p->tls == 1) { +#ifdef __GG_LIBGADU_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(TLSv1_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; + } +#else + 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->async) { + struct in_addr a; + + if (!p->server_addr || !p->server_port) { + if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) { + struct in_addr *hn; + + if (!(hn = gg_gethostbyname(hostname))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname); + goto fail; + } else { + a.s_addr = hn->s_addr; + free(hn); + } + } + } else { + a.s_addr = p->server_addr; + port = p->server_port; + } + + sess->hub_addr = a.s_addr; + + if (gg_proxy_enabled) + sess->proxy_addr = a.s_addr; + + if ((sess->fd = gg_connect(&a, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + if (p->server_addr && p->server_port) + 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; + 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) { +#ifndef __GG_LIBGADU_HAVE_PTHREAD + if (gg_resolve(&sess->fd, &sess->pid, hostname)) { +#else + if (gg_resolve_pthread(&sess->fd, &sess->resolver, hostname)) { +#endif + 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; + } + + return sess; + +fail: + if (sess) { + if (sess->password) + free(sess->password); + if (sess->initial_descr) + free(sess->initial_descr); + free(sess); + } + + return NULL; +} + +/* + * gg_free_session() + * + * próbuje zamknąć połączenia i zwalnia pamięć zajmowaną przez sesję. + * + * - sess - opis sesji + */ +void gg_free_session(struct gg_session *sess) +{ + if (!sess) + return; + + /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */ + + if (sess->password) + free(sess->password); + + if (sess->initial_descr) + free(sess->initial_descr); + + if (sess->client_version) + free(sess->client_version); + + if (sess->header_buf) + free(sess->header_buf); + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + SSL_free(sess->ssl); + + if (sess->ssl_ctx) + SSL_CTX_free(sess->ssl_ctx); +#endif + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + kill(sess->pid, SIGTERM); + waitpid(sess->pid, NULL, WNOHANG); + } +#endif + + if (sess->fd != -1) + close(sess->fd); + + while (sess->images) + gg_image_queue_remove(sess, sess->images, 1); + + free(sess); +} + +/* + * gg_change_status() + * + * zmienia status użytkownika. przydatne do /away i /busy oraz /quit. + * + * - sess - opis sesji + * - status - nowy status użytkownika + * + * 0, -1. + */ +int gg_change_status(struct gg_session *sess, int status) +{ + struct gg_new_status p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), NULL); +} + +/* + * gg_change_status_descr() + * + * zmienia status użytkownika na opisowy. + * + * - sess - opis sesji + * - status - nowy status użytkownika + * - descr - opis statusu + * + * 0, -1. + */ +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr) +{ + struct gg_new_status p; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr); + + if (!sess || !descr) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), NULL); +} + +/* + * gg_change_status_descr_time() + * + * zmienia status użytkownika na opisowy z godziną powrotu. + * + * - sess - opis sesji + * - status - nowy status użytkownika + * - descr - opis statusu + * - time - czas w formacie uniksowym + * + * 0, -1. + */ +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time) +{ + struct gg_new_status p; + uint32_t newtime; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time); + + if (!sess || !descr || !time) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + p.status = gg_fix32(status); + + sess->status = status; + + newtime = gg_fix32(time); + + return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), &newtime, sizeof(newtime), NULL); +} + +/* + * gg_logoff() + * + * wylogowuje użytkownika i zamyka połączenie, ale nie zwalnia pamięci. + * + * - sess - opis sesji + */ +void gg_logoff(struct gg_session *sess) +{ + if (!sess) + return; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess); + + if (GG_S_NA(sess->status & ~GG_STATUS_FRIENDS_MASK)) + gg_change_status(sess, GG_STATUS_NOT_AVAIL); + +#ifdef __GG_LIBGADU_HAVE_OPENSSL + if (sess->ssl) + SSL_shutdown(sess->ssl); +#endif + +#ifdef __GG_LIBGADU_HAVE_PTHREAD + if (sess->resolver) { + pthread_cancel(*((pthread_t*) sess->resolver)); + free(sess->resolver); + sess->resolver = NULL; + } +#else + if (sess->pid != -1) { + kill(sess->pid, SIGTERM); + waitpid(sess->pid, NULL, WNOHANG); + sess->pid = -1; + } +#endif + + if (sess->fd != -1) { + shutdown(sess->fd, SHUT_RDWR); + close(sess->fd); + sess->fd = -1; + } +} + +/* + * gg_image_request() + * + * wysyła żądanie wysłania obrazka o podanych parametrach. + * + * - sess - opis sesji + * - recipient - numer adresata + * - size - rozmiar obrazka + * - crc32 - suma kontrolna obrazka + * + * 0/-1 + */ +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(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(GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n"); + return -1; + } + + buf = malloc(size); + if (size && !buf) + { + gg_debug(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; +} + +/* + * gg_image_reply() + * + * wysyła żądany obrazek. + * + * - sess - opis sesji + * - recipient - numer adresata + * - filename - nazwa pliku + * - image - bufor z obrazkiem + * - size - rozmiar obrazka + * + * 0/-1 + */ +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(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, 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 += strlen(filename) + 1; + } + + chunklen = (size >= sizeof(buf) - buflen) ? (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; +} + +/* + * gg_send_message_ctcp() + * + * wysyła wiadomość do innego użytkownika. zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipient - numer adresata + * - message - treść wiadomości + * - message_len - długość + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +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(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); +} + +/* + * gg_send_message() + * + * wysyła wiadomość do innego użytkownika. zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipient - numer adresata + * - message - treść wiadomości + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message); + + return gg_send_message_richtext(sess, msgclass, recipient, message, NULL, 0); +} + +/* + * gg_send_message_richtext() + * + * wysyła kolorową wiadomość do innego użytkownika. zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipient - numer adresata + * - message - treść wiadomości + * - format - informacje o formatowaniu + * - formatlen - długość informacji o formatowaniu + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!message) { + errno = EFAULT; + return -1; + } + + s.recipient = gg_fix32(recipient); + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, format, formatlen, NULL) == -1) + return -1; + + return gg_fix32(s.seq); +} + +/* + * gg_send_message_confer() + * + * wysyła wiadomość do kilku użytkownikow (konferencja). zwraca losowy numer + * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipients_count - ilość adresatów + * - recipients - numerki adresatów + * - message - treść wiadomości + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message) +{ + gg_debug(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); +} + +/* + * gg_send_message_confer_richtext() + * + * wysyła kolorową wiadomość do kilku użytkownikow (konferencja). zwraca + * losowy numer sekwencyjny, który można zignorować albo wykorzystać do + * potwierdzenia. + * + * - sess - opis sesji + * - msgclass - rodzaj wiadomości + * - recipients_count - ilość adresatów + * - recipients - numerki adresatów + * - message - treść wiadomości + * - format - informacje o formatowaniu + * - formatlen - długość informacji o formatowaniu + * + * numer sekwencyjny wiadomości lub -1 w przypadku błędu. + */ +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_msg_recipients r; + int i, j, k; + uin_t *recps; + + gg_debug(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 || recipients_count <= 0 || recipients_count > 0xffff || !recipients) { + errno = EINVAL; + return -1; + } + + r.flag = 0x01; + r.count = gg_fix32(recipients_count - 1); + + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + s.seq = gg_fix32(sess->seq); + s.msgclass = gg_fix32(msgclass); + + recps = malloc(sizeof(uin_t) * recipients_count); + if (!recps) + return -1; + + for (i = 0; i < recipients_count; i++) { + + s.recipient = gg_fix32(recipients[i]); + + for (j = 0, k = 0; j < recipients_count; j++) + if (recipients[j] != recipients[i]) { + recps[k] = gg_fix32(recipients[j]); + k++; + } + + if (!i) + sess->seq += (rand() % 0x300) + 0x300; + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) { + free(recps); + return -1; + } + } + + free(recps); + + return gg_fix32(s.seq); +} + +/* + * gg_ping() + * + * wysyła do serwera pakiet ping. + * + * - sess - opis sesji + * + * 0, -1. + */ +int gg_ping(struct gg_session *sess) +{ + gg_debug(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); +} + +/* + * gg_notify_ex() + * + * wysyła serwerowi listę kontaktów (wraz z odpowiadającymi im typami userów), + * dzięki czemu wie, czyj stan nas interesuje. + * + * - sess - opis sesji + * - userlist - wskaźnik do tablicy numerów + * - types - wskaźnik do tablicy typów użytkowników + * - count - ilość numerków + * + * 0, -1. + */ +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(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; +} + +/* + * gg_notify() + * + * wysyła serwerowi listę kontaktów, dzięki czemu wie, czyj stan nas + * interesuje. + * + * - sess - opis sesji + * - userlist - wskaźnik do tablicy numerów + * - count - ilość numerków + * + * 0, -1. + */ +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(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; +} + +/* + * gg_add_notify_ex() + * + * dodaje do listy kontaktów dany numer w trakcie połączenia. + * dodawanemu użytkownikowi określamy jego typ (patrz protocol.html) + * + * - sess - opis sesji + * - uin - numer + * - type - typ + * + * 0, -1. + */ +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug(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); +} + +/* + * gg_add_notify() + * + * dodaje do listy kontaktów dany numer w trakcie połączenia. + * + * - sess - opis sesji + * - uin - numer + * + * 0, -1. + */ +int gg_add_notify(struct gg_session *sess, uin_t uin) +{ + return gg_add_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/* + * gg_remove_notify_ex() + * + * usuwa z listy kontaktów w trakcie połączenia. + * usuwanemu użytkownikowi określamy jego typ (patrz protocol.html) + * + * - sess - opis sesji + * - uin - numer + * - type - typ + * + * 0, -1. + */ +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug(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); +} + +/* + * gg_remove_notify() + * + * usuwa z listy kontaktów w trakcie połączenia. + * + * - sess - opis sesji + * - uin - numer + * + * 0, -1. + */ +int gg_remove_notify(struct gg_session *sess, uin_t uin) +{ + return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/* + * gg_userlist_request() + * + * wysyła żądanie/zapytanie listy kontaktów na serwerze. + * + * - sess - opis sesji + * - type - rodzaj zapytania/żądania + * - request - treść zapytania/żądania (może być NULL) + * + * 0, -1 + */ +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 = 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); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ -- cgit v1.2.1