diff libpurple/protocols/gg/lib/libgadu.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 32c366eeeb99
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,2062 @@
+/* $Id: libgadu.c 16856 2006-08-19 01:13:25Z evands $ */
+
+/*
+ *  (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 16856 2006-08-19 01:13:25Z evands $";
+#endif 
+
+#ifdef _WIN32
+/**
+ *  Deal with the fact that you can't select() on a win32 file fd.
+ *  This makes it practically impossible to tie into gaim's event loop.
+ *
+ *  -This is thanks to Tor Lillqvist.
+ *  XXX - Move this to where the rest of the the win32 compatiblity stuff goes when we push the changes back to libgadu.
+ */
+static int
+socket_pipe (int *fds)
+{
+	SOCKET temp, socket1 = -1, socket2 = -1;
+	struct sockaddr_in saddr;
+	int len;
+	u_long arg;
+	fd_set read_set, write_set;
+	struct timeval tv;
+
+	temp = socket(AF_INET, SOCK_STREAM, 0);
+
+	if (temp == INVALID_SOCKET) {
+		goto out0;
+	}
+
+	arg = 1;
+	if (ioctlsocket(temp, FIONBIO, &arg) == SOCKET_ERROR) {
+		goto out0;
+	}
+
+	memset(&saddr, 0, sizeof(saddr));
+	saddr.sin_family = AF_INET;
+	saddr.sin_port = 0;
+	saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+	if (bind(temp, (struct sockaddr *)&saddr, sizeof (saddr))) {
+		goto out0;
+	}
+
+	if (listen(temp, 1) == SOCKET_ERROR) {
+		goto out0;
+	}
+
+	len = sizeof(saddr);
+	if (getsockname(temp, (struct sockaddr *)&saddr, &len)) {
+		goto out0;
+	}
+
+	socket1 = socket(AF_INET, SOCK_STREAM, 0);
+
+	if (socket1 == INVALID_SOCKET) {
+		goto out0;
+	}
+
+	arg = 1;
+	if (ioctlsocket(socket1, FIONBIO, &arg) == SOCKET_ERROR) {
+		goto out1;
+	}
+
+	if (connect(socket1, (struct sockaddr  *)&saddr, len) != SOCKET_ERROR ||
+			WSAGetLastError() != WSAEWOULDBLOCK) {
+		goto out1;
+	}
+
+	FD_ZERO(&read_set);
+	FD_SET(temp, &read_set);
+
+	tv.tv_sec = 0;
+	tv.tv_usec = 0;
+
+	if (select(0, &read_set, NULL, NULL, NULL) == SOCKET_ERROR) {
+		goto out1;
+	}
+
+	if (!FD_ISSET(temp, &read_set)) {
+		goto out1;
+	}
+
+	socket2 = accept(temp, (struct sockaddr *) &saddr, &len);
+	if (socket2 == INVALID_SOCKET) {
+		goto out1;
+	}
+
+	FD_ZERO(&write_set);
+	FD_SET(socket1, &write_set);
+
+	tv.tv_sec = 0;
+	tv.tv_usec = 0;
+
+	if (select(0, NULL, &write_set, NULL, NULL) == SOCKET_ERROR) {
+		goto out2;
+	}
+
+	if (!FD_ISSET(socket1, &write_set)) {
+		goto out2;
+	}
+
+	arg = 0;
+	if (ioctlsocket(socket1, FIONBIO, &arg) == SOCKET_ERROR) {
+		goto out2;
+	}
+
+	arg = 0;
+	if (ioctlsocket(socket2, FIONBIO, &arg) == SOCKET_ERROR) {
+		goto out2;
+	}
+
+	fds[0] = socket1;
+	fds[1] = socket2;
+
+	closesocket (temp);
+
+	return 0;
+
+out2:
+	closesocket (socket2);
+out1:
+	closesocket (socket1);
+out0:
+	closesocket (temp);
+	errno = EIO;            /* XXX */
+
+	return -1;
+}
+#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 (socket_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, 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;
+	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)) {
+		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 unsigned 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) {
+		size_t 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_t)size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : (size_t)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((const char *)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((const char *)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:
+ */