Mercurial > pidgin.yaz
view src/protocols/gg/lib/libgadu.c @ 11912:8eb1f4b9210c
[gaim-migrate @ 14203]
I'm bored of testing this now, I'll just fix any problems I find as I come
across them.
howl/bonjour is now split out into its own rpm, as is mono (which I guess
may or may not make the final cut). I cleaned up some perl foo, but may
have broken it in the process.
committer: Tailor Script <tailor@pidgin.im>
author | Stu Tomlinson <stu@nosnilmot.com> |
---|---|
date | Mon, 31 Oct 2005 01:41:45 +0000 |
parents | 3c536224f0d0 |
children | 8724718d387f |
line wrap: on
line source
/* $Id: libgadu.c 13801 2005-09-14 19:10:39Z datallah $ */ /* * (C) Copyright 2001-2003 Wojtek Kaniewski <wojtekka@irc.pl> * Robert J. Woźny <speedy@ziew.org> * Arkadiusz Miśkiewicz <arekm@pld-linux.org> * Tomasz Chiliński <chilek@chilan.com> * * 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 <sys/types.h> #ifndef _WIN32 #include <sys/wait.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #ifdef sun # include <sys/filio.h> #endif #else #include <io.h> #include <fcntl.h> #include <errno.h> #define SHUT_RDWR SD_BOTH #endif #include "libgadu-config.h" #include <errno.h> #ifndef _WIN32 #include <netdb.h> #endif #ifdef __GG_LIBGADU_HAVE_PTHREAD # include <pthread.h> #endif #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #ifdef __GG_LIBGADU_HAVE_OPENSSL # include <openssl/err.h> # include <openssl/rand.h> #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: libgadu.c 13801 2005-09-14 19:10:39Z datallah $"; #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; } #ifndef _WIN32 /* * 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) { 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; } #endif #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; } #elif defined _WIN32 struct gg_resolve_win32thread_data { char *hostname; int fd; }; static DWORD WINAPI gg_resolve_win32thread_thread(LPVOID arg) { struct gg_resolve_win32thread_data *d = arg; struct in_addr a; 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); return 0; } int gg_resolve_win32thread(int *fd, void **resolver, const char *hostname) { struct gg_resolve_win32thread_data *d = NULL; HANDLE h; DWORD dwTId; int pipes[2], new_errno; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve_win32thread(%p, %p, \"%s\");\n", fd, resolver, hostname); if (!resolver || !fd || !hostname) { gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread() invalid arguments\n"); errno = EFAULT; return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); return -1; } if (!(d = malloc(sizeof(*d)))) { gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread() out of memory\n"); new_errno = GetLastError(); goto cleanup; } d->hostname = NULL; if (!(d->hostname = strdup(hostname))) { gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread() out of memory\n"); new_errno = GetLastError(); goto cleanup; } d->fd = pipes[1]; h = CreateThread(NULL, 0, gg_resolve_win32thread_thread, d, 0, &dwTId); if (h == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread() unable to create thread\n"); new_errno = GetLastError(); goto cleanup; } *resolver = h; *fd = pipes[0]; return 0; cleanup: if (d) { free(d->hostname); free(d); } close(pipes[0]); close(pipes[1]); 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) { #ifdef __GG_LIBGADU_HAVE_PTHREAD if (gg_resolve_pthread(&sess->fd, &sess->resolver, hostname)) { #elif defined _WIN32 if (gg_resolve_win32thread(&sess->fd, &sess->resolver, hostname)) { #else if (gg_resolve(&sess->fd, &sess->pid, 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; } #elif defined _WIN32 if (sess->resolver) { HANDLE h = sess->resolver; TerminateThread(h, 0); CloseHandle(h); sess->resolver = NULL; } #else if (sess->pid != -1) 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; } #elif defined _WIN32 if (sess->resolver) { HANDLE h = sess->resolver; TerminateThread(h, 0); CloseHandle(h); sess->resolver = NULL; } #else if (sess->pid != -1) { 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: */