changeset 31427:feb07422513c

merge of '8459331448aff4ed1551775d833a55e5fde5f543' and 'ad42fb62961cca268fce0b985556099fafd6b7ea'
author John Bailey <rekkanoryo@rekkanoryo.org>
date Fri, 25 Mar 2011 00:06:47 +0000
parents 08dcd2d4f0b7 (current diff) a056c1eaa1f0 (diff)
children f9b34192147c
files
diffstat 32 files changed, 5310 insertions(+), 2358 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Mar 24 23:22:29 2011 +0000
+++ b/ChangeLog	Fri Mar 25 00:06:47 2011 +0000
@@ -7,6 +7,10 @@
 
 	Pidgin:
 	* Duplicate code cleanup.  (Gabriel Schulhof) (#10599)
+	* Voice/Video call window adapts correctly to adding or removing streams
+	  on the fly. (Jakub Adam) (half of #13535)
+	* Don't cancel an ongoing call when rejecting the addition of a stream to
+	  the existing call. (Jakub Adam) (#13537)
 
 	libpurple:
 	* media: Allow obtaining active local and remote candidates. (#11830)
@@ -15,16 +19,33 @@
 	* Simple Silence Suppression is optional per-account. (Jakub Adam) (half
 	  of #13180)
 	* Fix purple-url-handler being unable to find an account.
+	* media: Allow adding/removing streams on the fly. (Jakub Adam) (half of
+	  #13535)
 
 	Gadu-Gadu:
 	* Allow showing your status only to buddies. (Mateusz Piękos) (#13358)
+	* Updated internal libgadu to version 1.10.1. (Mateusz Piękos, Krzysztof
+	  Klinikowski) (#13525)
+	* Suppress blank messages that happen when sending inline images. (Tomasz
+	  Wasilczyk) (#13554)
 
 	ICQ:
 	* Fix unsetting your mood when "None" is selected. (#7431)
+	* Ignore Daylight Saving Time when performing calculations related to
+	  birthdays. (dustin) (#13533)
+	* It is now possible to specify multiple encodings on the Advanced tab of
+	  an ICQ account's settings by using a comma-delimited list.  (loentar)
+	  (#13496)
+
+	Plugins:
+	* The Voice/Video Settings plugin now includes the ability to test
+	  microphone settings. (Jakub Adam) (#13182)
 
 	Windows-Specific Changes:
 	* Fix building libpurple with Visual C++ .NET 2005. This was accidentally
 	  broken in 2.7.11. (Florian Quèze)
+	* Build internal libgadu without -mms-bitfields, fixing several
+	  long-standing Gadu-Gadu issues.
 
 version 2.7.11 (03/10/2011):
 	General:
--- a/libpurple/media/backend-fs2.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/media/backend-fs2.c	Fri Mar 25 00:06:47 2011 +0000
@@ -86,6 +86,9 @@
 		PurpleMediaBackend *self, const gchar *sess_id,
 		PurpleMediaCodec *codec);
 
+static void free_stream(PurpleMediaBackendFs2Stream *stream);
+static void free_session(PurpleMediaBackendFs2Session *session);
+
 struct _PurpleMediaBackendFs2Class
 {
 	GObjectClass parent_class;
@@ -110,6 +113,8 @@
 	GstElement *tee;
 	GstElement *volume;
 	GstElement *level;
+	GstElement *fakesink;
+	GstElement *queue;
 
 	GList *local_candidates;
 	GList *remote_candidates;
@@ -300,20 +305,7 @@
 	for (; priv->streams; priv->streams =
 			g_list_delete_link(priv->streams, priv->streams)) {
 		PurpleMediaBackendFs2Stream *stream = priv->streams->data;
-
-		/* Remove the connected_cb timeout */
-		if (stream->connected_cb_id != 0)
-			purple_timeout_remove(stream->connected_cb_id);
-
-		g_free(stream->participant);
-
-		if (stream->local_candidates)
-			fs_candidate_list_destroy(stream->local_candidates);
-
-		if (stream->remote_candidates)
-			fs_candidate_list_destroy(stream->remote_candidates);
-
-		g_free(stream);
+		free_stream(stream);
 	}
 
 	if (priv->sessions) {
@@ -323,8 +315,7 @@
 				g_list_delete_link(sessions, sessions)) {
 			PurpleMediaBackendFs2Session *session =
 					sessions->data;
-			g_free(session->id);
-			g_free(session);
+			free_session(session);
 		}
 
 		g_hash_table_destroy(priv->sessions);
@@ -1138,9 +1129,62 @@
 }
 
 static void
+remove_element(GstElement *element)
+{
+	if (element) {
+		gst_element_set_locked_state(element, TRUE);
+		gst_element_set_state(element, GST_STATE_NULL);
+		gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
+	}
+}
+
+static void
 state_changed_cb(PurpleMedia *media, PurpleMediaState state,
 		gchar *sid, gchar *name, PurpleMediaBackendFs2 *self)
 {
+	if (state == PURPLE_MEDIA_STATE_END) {
+		PurpleMediaBackendFs2Private *priv =
+				PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
+
+		if (sid && name) {
+			PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name);
+			gst_object_unref(stream->stream);
+
+			priv->streams = g_list_remove(priv->streams, stream);
+
+			remove_element(stream->src);
+			remove_element(stream->tee);
+			remove_element(stream->volume);
+			remove_element(stream->level);
+			remove_element(stream->fakesink);
+			remove_element(stream->queue);
+
+			free_stream(stream);
+		} else if (sid && !name) {
+			PurpleMediaBackendFs2Session *session = get_session(self, sid);
+			GstPad *pad;
+
+			g_object_get(session->session, "sink-pad", &pad, NULL);
+			gst_pad_unlink(GST_PAD_PEER(pad), pad);
+			gst_object_unref(pad);
+
+			gst_object_unref(session->session);
+			g_hash_table_remove(priv->sessions, session->id);
+
+			pad = gst_pad_get_peer(session->srcpad);
+			gst_element_remove_pad(GST_ELEMENT_PARENT(pad), pad);
+			gst_object_unref(pad);
+			gst_object_unref(session->srcpad);
+
+			remove_element(session->srcvalve);
+			remove_element(session->tee);
+
+			free_session(session);
+		}
+
+		purple_media_manager_remove_output_windows(
+				purple_media_get_manager(media), media, sid, name);
+	}
 }
 
 static void
@@ -1420,6 +1464,7 @@
 			  ? "success" : "failure");
 	gst_element_set_locked_state(session->src, FALSE);
 	gst_object_unref(session->src);
+	gst_object_unref(sinkpad);
 
 	gst_element_set_state(session->src, GST_STATE_PLAYING);
 
@@ -1536,6 +1581,13 @@
 	return TRUE;
 }
 
+static void
+free_session(PurpleMediaBackendFs2Session *session)
+{
+	g_free(session->id);
+	g_free(session);
+}
+
 static gboolean
 create_participant(PurpleMediaBackendFs2 *self, const gchar *name)
 {
@@ -1603,7 +1655,6 @@
 		GstElement *sink = NULL;
 
 		if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
-			GstElement *queue = NULL;
 			double output_volume = purple_prefs_get_int(
 					"/purple/media/audio/volume/output")/10.0;
 			/*
@@ -1611,7 +1662,7 @@
 			 *  audioconvert ! audioresample ! liveadder !
 			 *   audioresample ! audioconvert ! realsink
 			 */
-			queue = gst_element_factory_make("queue", NULL);
+			stream->queue = gst_element_factory_make("queue", NULL);
 			stream->volume = gst_element_factory_make(
 					"volume", NULL);
 			g_object_set(stream->volume, "volume",
@@ -1625,18 +1676,18 @@
 					PURPLE_MEDIA_RECV_AUDIO, priv->media,
 					stream->session->id,
 					stream->participant);
-			gst_bin_add(GST_BIN(priv->confbin), queue);
+			gst_bin_add(GST_BIN(priv->confbin), stream->queue);
 			gst_bin_add(GST_BIN(priv->confbin), stream->volume);
 			gst_bin_add(GST_BIN(priv->confbin), stream->level);
 			gst_bin_add(GST_BIN(priv->confbin), sink);
 			gst_element_set_state(sink, GST_STATE_PLAYING);
 			gst_element_set_state(stream->level, GST_STATE_PLAYING);
 			gst_element_set_state(stream->volume, GST_STATE_PLAYING);
-			gst_element_set_state(queue, GST_STATE_PLAYING);
+			gst_element_set_state(stream->queue, GST_STATE_PLAYING);
 			gst_element_link(stream->level, sink);
 			gst_element_link(stream->volume, stream->level);
-			gst_element_link(queue, stream->volume);
-			sink = queue;
+			gst_element_link(stream->queue, stream->volume);
+			sink = stream->queue;
 		} else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) {
 			stream->src = gst_element_factory_make(
 					"fsfunnel", NULL);
@@ -1645,6 +1696,7 @@
 			g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
 			gst_bin_add(GST_BIN(priv->confbin), sink);
 			gst_element_set_state(sink, GST_STATE_PLAYING);
+			stream->fakesink = sink;
 		}
 		stream->tee = gst_element_factory_make("tee", NULL);
 		gst_bin_add_many(GST_BIN(priv->confbin),
@@ -1814,6 +1866,24 @@
 	return TRUE;
 }
 
+static void
+free_stream(PurpleMediaBackendFs2Stream *stream)
+{
+	/* Remove the connected_cb timeout */
+	if (stream->connected_cb_id != 0)
+		purple_timeout_remove(stream->connected_cb_id);
+
+	g_free(stream->participant);
+
+	if (stream->local_candidates)
+		fs_candidate_list_destroy(stream->local_candidates);
+
+	if (stream->remote_candidates)
+		fs_candidate_list_destroy(stream->remote_candidates);
+
+	g_free(stream);
+}
+
 static gboolean
 purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
 		const gchar *sess_id, const gchar *who,
--- a/libpurple/protocols/gg/Makefile.am	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/Makefile.am	Fri Mar 25 00:06:47 2011 +0000
@@ -5,18 +5,26 @@
 	lib/COPYING \
 	lib/dcc.c \
 	lib/dcc7.c \
+	lib/debug.c \
+	lib/encoding.c \
+	lib/encoding.h \
 	lib/events.c \
+	lib/handlers.c \
 	lib/http.c \
+	lib/libgadu.h \
 	lib/libgadu.c \
 	lib/libgadu-config.h \
+	lib/libgadu-debug.h \
 	lib/libgadu-internal.h \
-	lib/libgadu.h \
+	lib/message.c \
+	lib/message.h \
 	lib/obsolete.c \
 	lib/protocol.h \
 	lib/pubdir.c \
 	lib/pubdir50.c \
 	lib/resolver.c \
 	lib/resolver.h \
+	lib/session.h \
 	lib/sha1.c
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
@@ -27,18 +35,25 @@
 	lib/compat.h \
 	lib/dcc.c \
 	lib/dcc7.c \
+	lib/debug.c \
+	lib/encoding.c \
+	lib/encoding.h \
 	lib/events.c \
+	lib/handlers.c \
 	lib/http.c \
+	lib/libgadu.h \
 	lib/libgadu.c \
 	lib/libgadu-config.h \
 	lib/libgadu-internal.h \
-	lib/libgadu.h \
+	lib/message.c \
+	lib/message.h \
 	lib/obsolete.c \
 	lib/protocol.h \
 	lib/pubdir.c \
 	lib/pubdir50.c \
 	lib/resolver.c \
 	lib/resolver.h \
+	lib/session.h \
 	lib/sha1.c
 
 INTGG_CFLAGS = -I$(top_srcdir)/libpurple/protocols/gg/lib -DGG_IGNORE_DEPRECATED
--- a/libpurple/protocols/gg/Makefile.mingw	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/Makefile.mingw	Fri Mar 25 00:06:47 2011 +0000
@@ -43,9 +43,13 @@
 	lib/common.c \
 	lib/dcc.c \
 	lib/dcc7.c \
+	lib/debug.c \
+	lib/encoding.c \
 	lib/events.c \
+	lib/handlers.c \
 	lib/http.c \
 	lib/libgadu.c \
+	lib/message.c \
 	lib/obsolete.c \
 	lib/pubdir.c \
 	lib/pubdir50.c \
--- a/libpurple/protocols/gg/gg.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/gg.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1417,6 +1417,12 @@
 	gchar *msg;
 	gchar *tmp;
 
+	if (ev->event.msg.message == NULL)
+	{
+		purple_debug_warning("gg", "ggp_recv_message_handler: NULL as message pointer\n");
+		return;
+	}
+
 	from = g_strdup_printf("%lu", (unsigned long int)ev->event.msg.sender);
 
 	/*
--- a/libpurple/protocols/gg/lib/COPYING	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/COPYING	Fri Mar 25 00:06:47 2011 +0000
@@ -2,7 +2,7 @@
 		       Version 2.1, February 1999
 
  Copyright (C) 1991, 1999 Free Software Foundation, Inc.
-     51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
@@ -485,7 +485,7 @@
 
     You should have received a copy of the GNU Lesser General Public
     License along with this library; if not, write to the Free Software
-    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 Also add information on how to contact you by electronic and paper mail.
 
--- a/libpurple/protocols/gg/lib/common.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/common.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,4 +1,4 @@
-/* $Id: common.c 878 2009-11-16 23:48:19Z wojtekka $ */
+/* $Id: common.c 1037 2010-12-17 22:18:08Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -19,29 +19,6 @@
  *  USA.
  */
 
-/*
- * Funkcje konwersji między UTF-8 i CP1250 są oparte o kod biblioteki iconv.
- * Informacje o prawach autorskich oryginalnego kodu zamieszczono poniżej:
- *
- * Copyright (C) 1999-2001, 2004 Free Software Foundation, Inc.
- * This file is part of the GNU LIBICONV Library.
- *
- * The GNU LIBICONV Library is free software; you can redistribute it
- * and/or modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * The GNU LIBICONV Library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with the GNU LIBICONV Library; see the file COPYING.LIB.
- * If not, write to the Free Software Foundation, Inc., 51 Franklin Street,
- * Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
 /**
  * \file common.c
  *
@@ -66,7 +43,7 @@
 #include <fcntl.h>
 
 #ifndef _WIN32
-# include <netdb.h>
+#  include <netdb.h>
 #endif
 
 #include <stdarg.h>
@@ -75,81 +52,6 @@
 #include <string.h>
 #include <unistd.h>
 
-/**
- * Plik, do którego będą przekazywane informacje odpluskwiania.
- *
- * Funkcja \c gg_debug() i pochodne mogą być przechwytywane przez aplikację
- * korzystającą z biblioteki, by wyświetlić je na żądanie użytkownika lub
- * zapisać do późniejszej analizy. Jeśli nie określono pliku, wybrane
- * informacje będą wysyłane do standardowego wyjścia błędu (\c stderr).
- *
- * \ingroup debug
- */
-FILE *gg_debug_file = NULL;
-
-#ifndef GG_DEBUG_DISABLE
-
-/**
- * \internal Przekazuje informacje odpluskwiania do odpowiedniej funkcji.
- *
- * Jeśli aplikacja ustawiła odpowiednią funkcję obsługi w
- * \c gg_debug_handler_session lub \c gg_debug_handler, jest ona wywoływana.
- * W przeciwnym wypadku wynik jest wysyłany do standardowego wyjścia błędu.
- *
- * \param sess Struktura sesji (może być \c NULL)
- * \param level Poziom informacji
- * \param format Format wiadomości (zgodny z \c printf)
- * \param ap Lista argumentów (zgodna z \c printf)
- */
-void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap)
-{
-	if (gg_debug_handler_session)
-		(*gg_debug_handler_session)(sess, level, format, ap);
-	else if (gg_debug_handler)
-		(*gg_debug_handler)(level, format, ap);
-	else if (gg_debug_level & level)
-		vfprintf(gg_debug_file ? gg_debug_file : stderr, format, ap);
-}
-
-
-/**
- * \internal Przekazuje informację odpluskawiania.
- *
- * \param level Poziom wiadomości
- * \param format Format wiadomości (zgodny z \c printf)
- *
- * \ingroup debug
- */
-void gg_debug(int level, const char *format, ...)
-{
-	va_list ap;
-	int old_errno = errno;
-	va_start(ap, format);
-	gg_debug_common(NULL, level, format, ap);
-	va_end(ap);
-	errno = old_errno;
-}
-
-/**
- * \internal Przekazuje informację odpluskwiania związaną z sesją.
- *
- * \param sess Struktura sesji
- * \param level Poziom wiadomości
- * \param format Format wiadomości (zgodny z \c printf)
- *
- * \ingroup debug
- */
-void gg_debug_session(struct gg_session *sess, int level, const char *format, ...)
-{
-	va_list ap;
-	int old_errno = errno;
-	va_start(ap, format);
-	gg_debug_common(sess, level, format, ap);
-	va_end(ap);
-	errno = old_errno;
-}
-
-#endif
 
 /**
  * \internal Odpowiednik funkcji \c vsprintf alokujący miejsce na wynik.
@@ -167,7 +69,6 @@
 char *gg_vsaprintf(const char *format, va_list ap)
 {
 	int size = 0;
-	const char *start;
 	char *buf = NULL;
 
 #ifdef GG_CONFIG_HAVE_VA_COPY
@@ -182,8 +83,6 @@
 #  endif
 #endif
 
-	start = format;
-
 #ifndef GG_CONFIG_HAVE_C99_VSNPRINTF
 	{
 		int res;
@@ -212,8 +111,6 @@
 	}
 #endif
 
-	format = start;
-
 #ifdef GG_CONFIG_HAVE_VA_COPY
 	vsnprintf(buf, size + 1, format, aq);
 	va_end(aq);
@@ -311,7 +208,7 @@
 
 	for (; length > 1; buf++, length--) {
 		do {
-			if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) {
+			if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR && errno != EAGAIN) {
 				gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno));
 				*buf = 0;
 				return NULL;
@@ -320,7 +217,7 @@
 				*buf = 0;
 				return NULL;
 			}
-		} while (ret == -1 && errno == EINTR);
+		} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
 
 		if (*buf == '\n') {
 			buf++;
@@ -370,10 +267,6 @@
 		return -1;
 	}
 
-#ifdef ASSIGN_SOCKETS_TO_THREADS
-	gg_win32_thread_socket(0, sock);
-#endif
-
 	if (async) {
 #ifdef FIONBIO
 		if (ioctl(sock, FIONBIO, &one) == -1) {
@@ -445,7 +338,8 @@
  */
 char *gg_urlencode(const char *str)
 {
-	char *q, *buf, hex[] = "0123456789abcdef";
+	char *q, *buf;
+	const char hex[] = "0123456789abcdef";
 	const char *p;
 	unsigned int size = 0;
 
@@ -524,80 +418,6 @@
 	return (b < 0 ? -b : b);
 }
 
-#ifdef ASSIGN_SOCKETS_TO_THREADS
-
-typedef struct gg_win32_thread {
-	int id;
-	int socket;
-	struct gg_win32_thread *next;
-} gg_win32_thread;
-
-struct gg_win32_thread *gg_win32_threads = 0;
-
-/**
- * \internal Zwraca deskryptor gniazda, które było ostatnio tworzone dla wątku.
- *
- * Jeśli na win32 przy połączeniach synchronicznych zapamiętamy w jakim
- * wątku uruchomiliśmy funkcję, która się z czymkolwiek łączy, to z osobnego
- * wątku możemy anulować połączenie poprzez \c gg_win32_thread_socket(watek,-1)
- *
- * \param thread_id Identyfikator wątku (jeśli jest równe 0, brany jest
- *                  aktualny wątek, jeśli równe -1, usuwa wpis dotyczący
- *                  danego gniazda sockecie)
- * \param socket Deskryptor gniazda (jeśli równe 0, zwraca deskryptor gniazda
- *               dla podanego wątku, jeśli równe -1, usuwa wpis, jeśli coś
- *               innego, ustawia dla podanego wątku dany numer deskryptora)
- *
- * \return Jeśli socket jest równe 0, zwraca deskryptor gniazda dla podanego
- *         wątku.
- */
-int gg_win32_thread_socket(int thread_id, int socket)
-{
-	char close = (thread_id == -1) || socket == -1;
-	gg_win32_thread *wsk = gg_win32_threads;
-	gg_win32_thread **p_wsk = &gg_win32_threads;
-
-	if (!thread_id)
-		thread_id = GetCurrentThreadId();
-
-	while (wsk) {
-		if ((thread_id == -1 && wsk->socket == socket) || wsk->id == thread_id) {
-			if (close) {
-				/* socket zostaje usuniety */
-				closesocket(wsk->socket);
-				*p_wsk = wsk->next;
-				free(wsk);
-				return 1;
-			} else if (!socket) {
-				/* socket zostaje zwrocony */
-				return wsk->socket;
-			} else {
-				/* socket zostaje ustawiony */
-				wsk->socket = socket;
-				return socket;
-			}
-		}
-		p_wsk = &(wsk->next);
-		wsk = wsk->next;
-	}
-
-	if (close && socket != -1)
-		closesocket(socket);
-	if (close || !socket)
-		return 0;
-
-	/* Dodaje nowy element */
-	wsk = malloc(sizeof(gg_win32_thread));
-	wsk->id = thread_id;
-	wsk->socket = socket;
-	wsk->next = 0;
-	*p_wsk = wsk;
-
-	return socket;
-}
-
-#endif /* ASSIGN_SOCKETS_TO_THREADS */
-
 /**
  * \internal Zestaw znaków kodowania base64.
  */
@@ -732,7 +552,7 @@
  * \return Zaalokowany bufor z tekstem lub NULL, jeśli serwer pośredniczący
  *         nie jest używany lub nie wymaga autoryzacji.
  */
-char *gg_proxy_auth()
+char *gg_proxy_auth(void)
 {
 	char *tmp, *enc, *out;
 	unsigned int tmp_size;
@@ -767,33 +587,73 @@
 /**
  * \internal Tablica pomocnicza do wyznaczania sumy kontrolnej.
  */
-static uint32_t gg_crc32_table[256];
-
-/**
- * \internal Flaga wypełnienia tablicy pomocniczej do wyznaczania sumy
- * kontrolnej.
- */
-static int gg_crc32_initialized = 0;
-
-/**
- * \internal Tworzy tablicę pomocniczą do wyznaczania sumy kontrolnej.
- */
-static void gg_crc32_make_table(void)
+static const uint32_t gg_crc32_table[256] =
 {
-	uint32_t h = 1;
-	unsigned int i, j;
-
-	memset(gg_crc32_table, 0, sizeof(gg_crc32_table));
-
-	for (i = 128; i; i >>= 1) {
-		h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0);
-
-		for (j = 0; j < 256; j += 2 * i)
-			gg_crc32_table[i + j] = gg_crc32_table[j] ^ h;
-	}
-
-	gg_crc32_initialized = 1;
-}
+	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 
+	0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 
+	0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 
+	0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 
+	0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 
+	0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 
+	0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 
+	0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 
+	0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 
+	0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 
+	0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 
+	0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 
+	0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 
+	0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 
+	0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 
+	0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 
+	0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 
+	0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 
+	0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 
+	0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 
+	0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 
+	0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 
+	0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 
+	0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 
+	0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 
+	0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 
+	0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 
+	0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 
+	0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 
+	0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 
+	0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 
+	0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 
+	0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 
+	0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 
+	0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 
+	0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 
+	0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 
+	0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 
+	0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 
+	0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 
+	0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 
+	0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 
+	0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 
+	0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 
+	0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 
+	0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 
+	0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 
+	0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 
+	0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 
+	0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 
+	0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 
+	0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 
+	0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 
+	0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 
+	0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 
+	0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 
+	0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 
+	0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 
+	0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 
+	0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 
+	0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 
+	0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 
+	0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 
+	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
 
 /**
  * Wyznacza sumę kontrolną CRC32.
@@ -807,10 +667,7 @@
  */
 uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len)
 {
-	if (!gg_crc32_initialized)
-		gg_crc32_make_table();
-
-	if (!buf || len < 0)
+	if (buf == NULL || len < 0)
 		return crc;
 
 	crc ^= 0xffffffffL;
@@ -821,187 +678,6 @@
 	return crc ^ 0xffffffffL;
 }
 
-/**
- * \internal Tablica konwersji między CP1250 a UTF-8.
- */
-static const uint16_t table_cp1250[] = {
-	0x20ac, '?',    0x201a,    '?', 0x201e, 0x2026, 0x2020, 0x2021,
-	   '?', 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179,
-	   '?', 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
-	   '?', 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a,
-	0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7,
-	0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b,
-	0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
-	0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c,
-	0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7,
-	0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e,
-	0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7,
-	0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df,
-	0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7,
-	0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f,
-	0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7,
-	0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9,
-};
-
-/**
- * \internal Zamienia tekst kodowany CP1250 na UTF-8.
- *
- * \param b Tekst źródłowy w CP1250.
- *
- * \return Zaalokowany bufor z tekstem w UTF-8.
- */
-char *gg_cp_to_utf8(const char *b)
-{
-	unsigned char *buf = (unsigned char *) b;
-	char *newbuf;
-	int newlen = 0;
-	int i, j;
-
-	for (i = 0; buf[i]; i++) {
-		uint16_t znak = (buf[i] < 0x80) ? buf[i] : table_cp1250[buf[i]-0x80];
-
-		if (znak < 0x80)	newlen += 1;
-		else if (znak < 0x800)	newlen += 2;
-		else			newlen += 3;
-	}
-
-	if (!(newbuf = malloc(newlen+1))) {
-		gg_debug(GG_DEBUG_MISC, "// gg_cp_to_utf8() not enough memory\n");
-		return NULL;
-	}
-
-	for (i = 0, j = 0; buf[i]; i++) {
-		uint16_t znak = (buf[i] < 0x80) ? buf[i] : table_cp1250[buf[i]-0x80];
-		int count;
-
-		if (znak < 0x80)	count = 1;
-		else if (znak < 0x800)	count = 2;
-		else			count = 3;
-
-		switch (count) {
-			case 3: newbuf[j+2] = 0x80 | (znak & 0x3f); znak = znak >> 6; znak |= 0x800;
-			case 2: newbuf[j+1] = 0x80 | (znak & 0x3f); znak = znak >> 6; znak |= 0xc0;
-			case 1: newbuf[j] = znak;
-		}
-		j += count;
-	}
-	newbuf[j] = '\0';
-
-	return newbuf;
-}
-
-/**
- * \internal Dekoduje jeden znak UTF-8.
- *
- * \note Funkcja nie jest kompletną implementacją UTF-8, a wersją uproszczoną
- * do potrzeb kodowania CP1250.
- *
- * \param s Tekst źródłowy.
- * \param n Długość tekstu źródłowego.
- * \param ch Wskaźnik na wynik dekodowania.
- *
- * \return Długość zdekodowanej sekwencji w bajtach lub wartość mniejsza
- * od zera w przypadku błędu.
- */
-static int gg_utf8_helper(unsigned char *s, int n, uint16_t *ch)
-{
-	unsigned char c = s[0];
-
-	if (c < 0x80) {
-		*ch = c;
-		return 1;
-	}
-
-	if (c < 0xc2)
-		return -1;
-
-	if (c < 0xe0) {
-		if (n < 2)
-			return -2;
-		if (!((s[1] ^ 0x80) < 0x40))
-			return -1;
-		*ch = ((uint16_t) (c & 0x1f) << 6) | (uint16_t) (s[1] ^ 0x80);
-		return 2;
-	}
-
-	if (c < 0xf0) {
-		if (n < 3)
-			return -2;
-		if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40 && (c >= 0xe1 || s[1] >= 0xa0)))
-			return -1;
-		*ch = ((uint16_t) (c & 0x0f) << 12) | ((uint16_t) (s[1] ^ 0x80) << 6) | (uint16_t) (s[2] ^ 0x80);
-		return 3;
-	}
-
-	return -1;
-}
-
-/**
- * \internal Zamienia tekst kodowany UTF-8 na CP1250.
- *
- * \param b Tekst źródłowy w UTF-8.
- *
- * \return Zaalokowany bufor z tekstem w CP1250.
- */
-char *gg_utf8_to_cp(const char *b)
-{
-	unsigned char *buf = (unsigned char *) b;
-	char *newbuf;
-	int newlen = 0;
-	int len;
-	int i, j;
-
-	len = strlen(b);
-
-	for (i = 0; i < len; newlen++) {
-		uint16_t discard;
-		int ret;
-
-		ret = gg_utf8_helper(&buf[i], len - i, &discard);
-
-		if (ret > 0)
-			i += ret;
-		else
-			i++;
-	}
-
-	if (!(newbuf = malloc(newlen+1))) {
-		gg_debug(GG_DEBUG_MISC, "// gg_utf8_to_cp() not enough memory\n");
-		return NULL;
-	}
-
-	for (i = 0, j = 0; buf[i]; j++) {
-		uint16_t znak;
-		int ret, k;
-
-		ret = gg_utf8_helper(&buf[i], len - i, &znak);
-
-		if (ret > 0) {
-			i += ret;
-		} else {
-			znak = '?';
-			i++;
-		}
-
-		if (znak < 0x80) {
-			newbuf[j] = znak;
-			continue;
-		}
-
-		newbuf[j] = '?';
-
-		for (k = 0; k < (sizeof(table_cp1250)/sizeof(table_cp1250[0])); k++) {
-			if (table_cp1250[k] == znak) {
-				newbuf[j] = (0x80 | k);
-				break;
-			}
-		}
-	}
-	newbuf[j] = '\0';
-
-	return newbuf;
-}
-
 /*
  * Local variables:
  * c-indentation-style: k&r
--- a/libpurple/protocols/gg/lib/dcc.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/dcc.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,4 +1,4 @@
-/* $Id: dcc.c 711 2009-04-16 00:52:47Z darkjames $ */
+/* $Id: dcc.c 1023 2010-11-16 18:27:35Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2008 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -421,7 +421,7 @@
 		return NULL;
 	}
 
-	if (!port)
+	if (port == 0 || port == -1)
 		port = GG_DEFAULT_DCC_PORT;
 
 	while (!bound) {
--- a/libpurple/protocols/gg/lib/dcc7.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/dcc7.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,9 +1,10 @@
-/* $Id: dcc7.c 711 2009-04-16 00:52:47Z darkjames $ */
+/* $Id: dcc7.c 1037 2010-12-17 22:18:08Z wojtekka $ */
 
 /*
- *  (C) Copyright 2001-2008 Wojtek Kaniewski <wojtekka@irc.pl>
+ *  (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl>
  *                          Tomasz Chiliński <chilek@chilan.com>
  *                          Adam Wysocki <gophi@ekg.chmurka.net>
+ *                          Bartłomiej Zimoń <uzi18@o2.pl>
  *
  *  Thanks to Jakub Zawadzki <darkjames@darkjames.ath.cx>
  *
@@ -29,6 +30,8 @@
  */
 
 #include "libgadu.h"
+#include "libgadu-internal.h"
+#include "libgadu-debug.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -55,9 +58,14 @@
 #include <unistd.h>
 
 #include "compat.h"
+#include "protocol.h"
+#include "resolver.h"
 
-#define gg_debug_dcc(dcc, fmt...) \
-	gg_debug_session((dcc) ? (dcc)->sess : NULL, fmt)
+#define gg_debug_dcc(dcc, level, fmt...) \
+	gg_debug_session(((dcc) != NULL) ? (dcc)->sess : NULL, level, fmt)
+
+#define gg_debug_dump_dcc(dcc, level, buf, len) \
+	gg_debug_dump(((dcc) != NULL) ? (dcc)->sess : NULL, level, buf, len)
 
 /**
  * \internal Dodaje połączenie bezpośrednie do sesji.
@@ -72,7 +80,7 @@
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_add(%p, %p)\n", sess, dcc);
 
 	if (!sess || !dcc || dcc->next) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_remove() invalid parameters\n");
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_add() invalid parameters\n");
 		errno = EINVAL;
 		return -1;
 	}
@@ -97,7 +105,7 @@
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_remove(%p, %p)\n", sess, dcc);
 
-	if (!sess || !dcc) {
+	if (sess == NULL || dcc == NULL) {
 		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_remove() invalid parameters\n");
 		errno = EINVAL;
 		return -1;
@@ -109,9 +117,9 @@
 		return 0;
 	}
 
-	for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) {
+	for (tmp = sess->dcc7_list; tmp != NULL; tmp = tmp->next) {
 		if (tmp->next == dcc) {
-			tmp = dcc->next;
+			tmp->next = dcc->next;
 			dcc->next = NULL;
 			return 0;
 		}
@@ -153,25 +161,53 @@
 }
 
 /**
- * \internal Nawiązuje połączenie bezpośrednie
+ * \internal Rozpoczyna proces pobierania adresu
  *
- * \param sess Struktura sesji
  * \param dcc Struktura połączenia
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
-static int gg_dcc7_connect(struct gg_session *sess, struct gg_dcc7 *dcc)
+static int gg_dcc7_get_relay_addr(struct gg_dcc7 *dcc)
 {
-	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_connect(%p, %p)\n", sess, dcc);
+	gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_get_relay_addr(%p)\n", dcc);
+
+	if (dcc == NULL || dcc->sess == NULL) {
+		gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() invalid parameters\n");
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (dcc->sess->resolver_start(&dcc->fd, &dcc->resolver, GG_RELAY_HOST) == -1) {
+		gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() resolving failed (errno=%d, %s)\n", errno, strerror(errno));
+		return -1;
+	}
+
+	dcc->state = GG_STATE_RESOLVING_RELAY;
+	dcc->check = GG_CHECK_READ;
+	dcc->timeout = GG_DEFAULT_TIMEOUT;
 
-	if (!sess || !dcc) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_connect() invalid parameters\n");
+	return 0;
+}
+
+/**
+ * \internal Nawiązuje połączenie bezpośrednie
+ *
+ * \param dcc Struktura połączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+static int gg_dcc7_connect(struct gg_dcc7 *dcc)
+{
+	gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_connect(%p)\n", dcc);
+
+	if (dcc == NULL) {
+		gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_connect() invalid parameters\n");
 		errno = EINVAL;
 		return -1;
 	}
 
 	if ((dcc->fd = gg_connect(&dcc->remote_addr, dcc->remote_port, 1)) == -1) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_connect() connection failed\n");
+		gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_connect() connection failed\n");
 		return -1;
 	}
 
@@ -260,21 +296,39 @@
 static int gg_dcc7_listen_and_send_info(struct gg_dcc7 *dcc)
 {
 	struct gg_dcc7_info pkt;
+	uint16_t external_port;
+	uint16_t local_port;
 
 	gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_listen_and_send_info(%p)\n", dcc);
 
-	// XXX dać możliwość konfiguracji?
+	if (!dcc->sess->client_port)
+		local_port = dcc->sess->external_port;
+	else
+		local_port = dcc->sess->client_port;
 
-	dcc->local_addr = dcc->sess->client_addr;
+	if (gg_dcc7_listen(dcc, local_port) == -1)
+		return -1;
+	
+	if (!dcc->sess->external_port || dcc->local_port != local_port)
+		external_port = dcc->local_port;
+	else
+		external_port = dcc->sess->external_port;
 
-	if (gg_dcc7_listen(dcc, 0) == -1)
-		return -1;
+	if (!dcc->sess->external_addr || dcc->local_port != local_port)
+		dcc->local_addr = dcc->sess->client_addr;
+	else
+		dcc->local_addr = dcc->sess->external_addr;
+
+	gg_debug_dcc(dcc, GG_DEBUG_MISC, "// dcc7_listen_and_send_info() sending IP address %s and port %d\n", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), external_port);
 
 	memset(&pkt, 0, sizeof(pkt));
 	pkt.uin = gg_fix32(dcc->peer_uin);
 	pkt.type = GG_DCC7_TYPE_P2P;
 	pkt.id = dcc->cid;
-	snprintf((char*) pkt.info, sizeof(pkt.info), "%s %d", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), dcc->local_port);
+	snprintf((char*) pkt.info, sizeof(pkt.info), "%s %d", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), external_port);
+	// TODO: implement hash count
+	// we MUST fill hash to recive from server request for server connection
+	snprintf((char*) pkt.hash, sizeof(pkt.hash), "0");
 
 	return gg_send_packet(dcc->sess, GG_DCC7_INFO, &pkt, sizeof(pkt), NULL);
 }
@@ -583,9 +637,9 @@
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
-int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, const void *payload, int len)
 {
-	struct gg_dcc7_id_reply *p = payload;
+	const struct gg_dcc7_id_reply *p = payload;
 	struct gg_dcc7 *tmp;
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_id(%p, %p, %p, %d)\n", sess, e, payload, len);
@@ -633,9 +687,9 @@
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
-int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, const void *payload, int len)
 {
-	struct gg_dcc7_accept *p = payload;
+	const struct gg_dcc7_accept *p = payload;
 	struct gg_dcc7 *dcc;
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_accept(%p, %p, %p, %d)\n", sess, e, payload, len);
@@ -673,35 +727,92 @@
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
-int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, const void *payload, int len)
 {
-	struct gg_dcc7_info *p = payload;
+	const struct gg_dcc7_info *p = payload;
 	struct gg_dcc7 *dcc;
 	char *tmp;
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_info(%p, %p, %p, %d)\n", sess, e, payload, len);
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "// gg_dcc7_handle_info() received address: %s, hash: %s\n", p->info, p->hash);
 
 	if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) {
 		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown dcc session\n");
 		return 0;
 	}
-
-	if (p->type != GG_DCC7_TYPE_P2P) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unhandled transfer type (%d)\n", p->type);
-		e->type = GG_EVENT_DCC7_ERROR;
-		e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+	
+	if (dcc->state == GG_STATE_CONNECTED) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() state is already connected\n");
 		return 0;
 	}
 
-	if ((dcc->remote_addr = inet_addr(p->info)) == INADDR_NONE) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP address\n");
-		e->type = GG_EVENT_DCC7_ERROR;
-		e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
-		return 0;
-	}
+	switch (p->type)
+	{
+	case GG_DCC7_TYPE_P2P:
+		if ((dcc->remote_addr = inet_addr(p->info)) == INADDR_NONE) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP address\n");
+			e->type = GG_EVENT_DCC7_ERROR;
+			e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+			return 0;
+		}
+
+		if (!(tmp = strchr(p->info, ' ')) || !(dcc->remote_port = atoi(tmp + 1))) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP port\n");
+			e->type = GG_EVENT_DCC7_ERROR;
+			e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+			return 0;
+		}
+
+		if (dcc->state == GG_STATE_WAITING_FOR_INFO) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() wainting for info so send one\n");
+			gg_dcc7_listen_and_send_info(dcc);
+			return 0;
+		}
+
+		break;
+
+	case GG_DCC7_TYPE_SERVER:
+		if (!(tmp = strstr(p->info, "GG"))) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown info packet\n");
+			e->type = GG_EVENT_DCC7_ERROR;
+			e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+			return 0;
+		}
 
-	if (!(tmp = strchr(p->info, ' ')) || !(dcc->remote_port = atoi(tmp + 1))) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP port\n");
+#if defined(HAVE_UINT64_T) && defined(HAVE_STRTOULL)
+		{
+			uint64_t cid;
+
+			cid = strtoull(tmp + 2, NULL, 0);
+
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() info.str=%s, info.id=%llu, sess.id=%llu\n", tmp + 2, cid, *((unsigned long long*) &dcc->cid));
+
+			cid = gg_fix64(cid);
+
+			if (memcmp(&dcc->cid, &cid, sizeof(cid)) != 0) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid session id\n");
+				e->type = GG_EVENT_DCC7_ERROR;
+				e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+				return 0;
+			}
+		}
+#endif
+
+		if (gg_dcc7_get_relay_addr(dcc) == -1) {
+			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unable to retrieve relay address\n");
+			e->type = GG_EVENT_DCC7_ERROR;
+			e->event.dcc7_error = GG_ERROR_DCC7_RELAY;
+			return 0;
+		}
+
+		// XXX wysyłać dopiero jeśli uda się połączyć z serwerem?
+
+		gg_send_packet(dcc->sess, GG_DCC7_INFO, payload, len, NULL);
+
+		break;
+
+	default:
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unhandled transfer type (%d)\n", p->type);
 		e->type = GG_EVENT_DCC7_ERROR;
 		e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
 		return 0;
@@ -710,19 +821,19 @@
 	// jeśli nadal czekamy na połączenie przychodzące, a druga strona nie
 	// daje rady i oferuje namiary na siebie, bierzemy co dają.
 
-	if (dcc->state != GG_STATE_WAITING_FOR_INFO && (dcc->state != GG_STATE_LISTENING || dcc->reverse)) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid state\n");
-		e->type = GG_EVENT_DCC7_ERROR;
-		e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
-		return 0;
-	}
+// 	if (dcc->state != GG_STATE_WAITING_FOR_INFO && (dcc->state != GG_STATE_LISTENING || dcc->reverse)) {
+// 		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid state\n");
+// 		e->type = GG_EVENT_DCC7_ERROR;
+// 		e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE;
+// 		return 0;
+// 	}
 
 	if (dcc->state == GG_STATE_LISTENING) {
 		close(dcc->fd);
 		dcc->fd = -1;
 		dcc->reverse = 1;
 	}
-
+	
 	if (dcc->type == GG_SESSION_DCC7_SEND) {
 		e->type = GG_EVENT_DCC7_ACCEPT;
 		e->event.dcc7_accept.dcc7 = dcc;
@@ -734,7 +845,7 @@
 		e->event.dcc7_pending.dcc7 = dcc;
 	}
 
-	if (gg_dcc7_connect(sess, dcc) == -1) {
+	if (gg_dcc7_connect(dcc) == -1) {
 		if (gg_dcc7_reverse_connect(dcc) == -1) {
 			e->type = GG_EVENT_DCC7_ERROR;
 			e->event.dcc7_error = GG_ERROR_DCC7_NET;
@@ -755,9 +866,9 @@
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
-int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, const void *payload, int len)
 {
-	struct gg_dcc7_reject *p = payload;
+	const struct gg_dcc7_reject *p = payload;
 	struct gg_dcc7 *dcc;
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_reject(%p, %p, %p, %d)\n", sess, e, payload, len);
@@ -793,9 +904,9 @@
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
-int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len)
+int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, const void *payload, int len)
 {
-	struct gg_dcc7_new *p = payload;
+	const struct gg_dcc7_new *p = payload;
 	struct gg_dcc7 *dcc;
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_new(%p, %p, %p, %d)\n", sess, e, payload, len);
@@ -1003,15 +1114,32 @@
 			if (error || (res = getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &error, &error_size)) == -1 || error != 0) {
 				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (%s)\n", (res == -1) ? strerror(errno) : strerror(error));
 
-				if (gg_dcc7_reverse_connect(dcc) != -1) {
-					e->type = GG_EVENT_DCC7_PENDING;
-					e->event.dcc7_pending.dcc7 = dcc;
+				if (dcc->relay) {
+					for (dcc->relay_index++; dcc->relay_index < dcc->relay_count; dcc->relay_index++) {
+						dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr;
+						dcc->remote_port = dcc->relay_list[dcc->relay_index].port;
+
+						if (gg_dcc7_connect(dcc) == 0)
+							break;
+					}
+
+					if (dcc->relay_index >= dcc->relay_count) {
+						gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available");
+						e->type = GG_EVENT_DCC7_ERROR;
+						e->event.dcc_error = GG_ERROR_DCC7_RELAY;
+						return e;
+					}
 				} else {
-					e->type = GG_EVENT_DCC7_ERROR;
-					e->event.dcc_error = GG_ERROR_DCC7_NET;
+					if (gg_dcc7_reverse_connect(dcc) != -1) {
+						e->type = GG_EVENT_DCC7_PENDING;
+						e->event.dcc7_pending.dcc7 = dcc;
+					} else {
+						e->type = GG_EVENT_DCC7_ERROR;
+						e->event.dcc_error = GG_ERROR_DCC7_NET;
+					}
+
+					return e;
 				}
-
-				return e;
 			}
 
 			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connected, sending id\n");
@@ -1026,23 +1154,45 @@
 
 		case GG_STATE_READING_ID:
 		{
-			gg_dcc7_id_t id;
 			int res;
 
 			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_ID\n");
 
-			if ((res = read(dcc->fd, &id, sizeof(id))) != sizeof(id)) {
-				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno));
-				e->type = GG_EVENT_DCC7_ERROR;
-				e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
-				return e;
-			}
+			if (!dcc->relay) {
+				struct gg_dcc7_welcome_p2p welcome, welcome_ok;
+				welcome_ok.id = dcc->cid;
+
+				if ((res = read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) {
+					gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno));
+					e->type = GG_EVENT_DCC7_ERROR;
+					e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
+					return e;
+				}
 
-			if (memcmp(&id, &dcc->cid, sizeof(id))) {
-				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n");
-				e->type = GG_EVENT_DCC7_ERROR;
-				e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
-				return e;
+				if (memcmp(&welcome, &welcome_ok, sizeof(welcome))) {
+					gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n");
+					e->type = GG_EVENT_DCC7_ERROR;
+					e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
+					return e;
+				}
+			} else {
+				struct gg_dcc7_welcome_server welcome, welcome_ok;
+				welcome_ok.magic = GG_DCC7_WELCOME_SERVER;
+				welcome_ok.id = dcc->cid;
+
+				if ((res = read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) {
+					gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno));
+					e->type = GG_EVENT_DCC7_ERROR;
+					e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
+					return e;
+				}
+
+				if (memcmp(&welcome, &welcome_ok, sizeof(welcome)) != 0) {
+					gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n");
+					e->type = GG_EVENT_DCC7_ERROR;
+					e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
+					return e;
+				}
 			}
 
 			if (dcc->incoming) {
@@ -1063,11 +1213,29 @@
 
 			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_SENDING_ID\n");
 
-			if ((res = write(dcc->fd, &dcc->cid, sizeof(dcc->cid))) != sizeof(dcc->cid)) {
-				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)", res, strerror(errno));
-				e->type = GG_EVENT_DCC7_ERROR;
-				e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
-				return e;
+			if (!dcc->relay) {
+				struct gg_dcc7_welcome_p2p welcome;
+
+				welcome.id = dcc->cid;
+
+				if ((res = write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) {
+					gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)\n", res, strerror(errno));
+					e->type = GG_EVENT_DCC7_ERROR;
+					e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
+					return e;
+				}
+			} else {
+				struct gg_dcc7_welcome_server welcome;
+
+				welcome.magic = gg_fix32(GG_DCC7_WELCOME_SERVER);
+				welcome.id = dcc->cid;
+
+				if ((res = write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) {
+					gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)\n", res, strerror(errno));
+					e->type = GG_EVENT_DCC7_ERROR;
+					e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE;
+					return e;
+				}
 			}
 
 			if (dcc->incoming) {
@@ -1092,6 +1260,7 @@
 			if (dcc->offset >= dcc->size) {
 				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() offset >= size, finished\n");
 				e->type = GG_EVENT_DCC7_DONE;
+				e->event.dcc7_done.dcc7 = dcc;
 				return e;
 			}
 
@@ -1124,6 +1293,7 @@
 			if (dcc->offset >= dcc->size) {
 				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n");
 				e->type = GG_EVENT_DCC7_DONE;
+				e->event.dcc7_done.dcc7 = dcc;
 				return e;
 			}
 
@@ -1144,6 +1314,7 @@
 			if (dcc->offset >= dcc->size) {
 				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n");
 				e->type = GG_EVENT_DCC7_DONE;
+				e->event.dcc7_done.dcc7 = dcc;
 				return e;
 			}
 
@@ -1168,6 +1339,7 @@
 			if (dcc->offset >= dcc->size) {
 				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n");
 				e->type = GG_EVENT_DCC7_DONE;
+				e->event.dcc7_done.dcc7 = dcc;
 				return e;
 			}
 
@@ -1178,6 +1350,157 @@
 			return e;
 		}
 
+		case GG_STATE_RESOLVING_RELAY:
+		{
+			struct in_addr addr;
+
+			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_RESOLVING_RELAY\n");
+
+			if (read(dcc->fd, &addr, sizeof(addr)) < sizeof(addr) || addr.s_addr == INADDR_NONE) {
+				int errno_save = errno;
+
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolving failed\n");
+				close(dcc->fd);
+				dcc->fd = -1;
+				dcc->sess->resolver_cleanup(&dcc->resolver, 0);
+				errno = errno_save;
+				e->type = GG_EVENT_DCC7_ERROR;
+				e->event.dcc_error = GG_ERROR_DCC7_RELAY;
+				return e;
+			}
+
+			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), GG_RELAY_PORT);
+
+			if ((dcc->fd = gg_connect(&addr, GG_RELAY_PORT, 1)) == -1) {
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno));
+				e->type = GG_EVENT_DCC7_ERROR;
+				e->event.dcc_error = GG_ERROR_DCC7_RELAY;
+				return e;
+			}
+			
+			dcc->state = GG_STATE_CONNECTING_RELAY;
+			dcc->check = GG_CHECK_WRITE;
+			dcc->timeout = GG_DEFAULT_TIMEOUT;
+
+			return e;
+		}
+
+		case GG_STATE_CONNECTING_RELAY:
+		{
+			int res;
+			unsigned int res_size = sizeof(res);
+			struct gg_dcc7_relay_req pkt;
+
+			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING_RELAY\n");
+			
+			if (getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) != 0 || res != 0) {
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res));
+				e->type = GG_EVENT_DCC7_ERROR;
+				e->event.dcc_error = GG_ERROR_DCC7_RELAY;
+				return e;
+			}
+
+			memset(&pkt, 0, sizeof(pkt));
+			pkt.magic = gg_fix32(GG_DCC7_RELAY_REQUEST);
+			pkt.len = gg_fix32(sizeof(pkt));
+			pkt.id = dcc->cid;
+			pkt.type = gg_fix16(GG_DCC7_RELAY_TYPE_SERVER);
+			pkt.dunno1 = gg_fix16(GG_DCC7_RELAY_DUNNO1);
+
+			gg_debug_dcc(dcc, GG_DEBUG_DUMP, "// gg_dcc7_watch_fd() send pkt(0x%.2x)\n", gg_fix32(pkt.magic));
+			gg_debug_dump_dcc(dcc, GG_DEBUG_DUMP, (const char*) &pkt, sizeof(pkt));
+
+			if ((res = write(dcc->fd, &pkt, sizeof(pkt))) != sizeof(pkt)) {
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() sending failed\n");
+				e->type = GG_EVENT_DCC7_ERROR;
+				e->event.dcc_error = GG_ERROR_DCC7_RELAY;
+				return e;
+			}
+
+			dcc->state = GG_STATE_READING_RELAY;
+			dcc->check = GG_CHECK_READ;
+			dcc->timeout = GG_DEFAULT_TIMEOUT;
+
+			return e;
+		}
+
+		case GG_STATE_READING_RELAY:
+		{
+			char buf[256];
+			struct gg_dcc7_relay_reply *pkt;
+			struct gg_dcc7_relay_reply_server srv;
+			int res;
+			int i;
+
+			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_RELAY\n");
+
+			if ((res = read(dcc->fd, buf, sizeof(buf))) < sizeof(*pkt)) {
+				if (res == 0)
+					errno = ECONNRESET;
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno));
+				e->type = GG_EVENT_DCC7_ERROR;
+				e->event.dcc_error = GG_ERROR_DCC7_RELAY;
+				return e;
+			}
+
+			pkt = (struct gg_dcc7_relay_reply*) buf;
+
+			if (gg_fix32(pkt->magic) != GG_DCC7_RELAY_REPLY || gg_fix32(pkt->rcount) < 1 || gg_fix32(pkt->rcount > 256) || gg_fix32(pkt->len) < sizeof(*pkt) + gg_fix32(pkt->rcount) * sizeof(srv)) {
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_wathc_fd() invalid reply\n");
+				errno = EINVAL;
+				e->type = GG_EVENT_DCC7_ERROR;
+				e->event.dcc_error = GG_ERROR_DCC7_RELAY;
+				return e;
+			}
+
+			gg_debug_dcc(dcc, GG_DEBUG_DUMP, "// gg_dcc7_get_relay() read pkt(0x%.2x)\n", gg_fix32(pkt->magic));
+			gg_debug_dump_dcc(dcc, GG_DEBUG_DUMP, buf, res);
+
+			free(dcc->relay_list);
+
+			dcc->relay_index = 0;
+			dcc->relay_count = gg_fix32(pkt->rcount);
+			dcc->relay_list = malloc(dcc->relay_count * sizeof(gg_dcc7_relay_t));
+
+			if (dcc->relay_list == NULL) {
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory");
+				dcc->relay_count = 0;
+				free(e);
+				return NULL;
+			}
+
+			for (i = 0; i < dcc->relay_count; i++) {
+				struct in_addr addr;
+
+				memcpy(&srv, buf + sizeof(*pkt) + i * sizeof(srv), sizeof(srv));
+				dcc->relay_list[i].addr = srv.addr;
+				dcc->relay_list[i].port = gg_fix16(srv.port);
+				dcc->relay_list[i].family = srv.family;
+
+				addr.s_addr = srv.addr;
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "//    %s %d %d\n", inet_ntoa(addr), gg_fix16(srv.port), srv.family);
+			}
+			
+			dcc->relay = 1;
+
+			for (; dcc->relay_index < dcc->relay_count; dcc->relay_index++) {
+				dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr;
+				dcc->remote_port = dcc->relay_list[dcc->relay_index].port;
+
+				if (gg_dcc7_connect(dcc) == 0)
+					break;
+			}
+
+			if (dcc->relay_index >= dcc->relay_count) {
+				gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available");
+				e->type = GG_EVENT_DCC7_ERROR;
+				e->event.dcc_error = GG_ERROR_DCC7_RELAY;
+				return e;
+			}
+
+			return e;
+		}
+
 		default:
 		{
 			gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_???\n");
@@ -1214,6 +1537,8 @@
 	if (dcc->sess)
 		gg_dcc7_session_remove(dcc->sess, dcc);
 
+	free(dcc->relay_list);
+
 	free(dcc);
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/debug.c	Fri Mar 25 00:06:47 2011 +0000
@@ -0,0 +1,298 @@
+/*
+ *  (C) Copyright 2001-2006 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>
+ *                          Adam Wysocki <gophi@ekg.chmurka.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+/**
+ * \file debug.c
+ *
+ * \brief Funkcje odpluskwiania
+ */
+#include <sys/types.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libgadu.h"
+#include "debug.h"
+
+/**
+ * Poziom rejestracji informacji odpluskwiających. Zmienna jest maską bitową
+ * składającą się ze stałych \c GG_DEBUG_...
+ *
+ * \ingroup debug
+ */
+int gg_debug_level = 0;
+
+/**
+ * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno
+ * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe
+ * \c NULL, informacje są wysyłane do standardowego wyjścia błędu (\c stderr).
+ *
+ * \param level Poziom rejestracji
+ * \param format Format wiadomości (zgodny z \c printf)
+ * \param ap Lista argumentów (zgodna z \c printf)
+ *
+ * \note Funkcja jest przesłaniana przez \c gg_debug_handler_session.
+ *
+ * \ingroup debug
+ */
+void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL;
+
+/**
+ * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno
+ * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe
+ * \c NULL, informacje są wysyłane do standardowego wyjścia błędu.
+ *
+ * \param sess Sesja której dotyczy informacja lub \c NULL
+ * \param level Poziom rejestracji
+ * \param format Format wiadomości (zgodny z \c printf)
+ * \param ap Lista argumentów (zgodna z \c printf)
+ *
+ * \note Funkcja przesłania przez \c gg_debug_handler_session.
+ *
+ * \ingroup debug
+ */
+void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap) = NULL;
+
+/**
+ * Plik, do którego będą przekazywane informacje odpluskwiania.
+ *
+ * Funkcja \c gg_debug() i pochodne mogą być przechwytywane przez aplikację
+ * korzystającą z biblioteki, by wyświetlić je na żądanie użytkownika lub
+ * zapisać do późniejszej analizy. Jeśli nie określono pliku, wybrane
+ * informacje będą wysyłane do standardowego wyjścia błędu (\c stderr).
+ *
+ * \ingroup debug
+ */
+FILE *gg_debug_file = NULL;
+
+#ifndef GG_DEBUG_DISABLE
+
+/**
+ * \internal Przekazuje informacje odpluskwiania do odpowiedniej funkcji.
+ *
+ * Jeśli aplikacja ustawiła odpowiednią funkcję obsługi w
+ * \c gg_debug_handler_session lub \c gg_debug_handler, jest ona wywoływana.
+ * W przeciwnym wypadku wynik jest wysyłany do standardowego wyjścia błędu.
+ *
+ * \param sess Struktura sesji (może być \c NULL)
+ * \param level Poziom informacji
+ * \param format Format wiadomości (zgodny z \c printf)
+ * \param ap Lista argumentów (zgodna z \c printf)
+ */
+void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap)
+{
+	if (gg_debug_handler_session != NULL)
+		(*gg_debug_handler_session)(sess, level, format, ap);
+	else if (gg_debug_handler != NULL)
+		(*gg_debug_handler)(level, format, ap);
+	else if ((gg_debug_level & level) != 0)
+		vfprintf((gg_debug_file) ? gg_debug_file : stderr, format, ap);
+}
+
+
+/**
+ * \internal Przekazuje informację odpluskawiania.
+ *
+ * \param level Poziom wiadomości
+ * \param format Format wiadomości (zgodny z \c printf)
+ *
+ * \ingroup debug
+ */
+void gg_debug(int level, const char *format, ...)
+{
+	va_list ap;
+	int old_errno = errno;
+
+	va_start(ap, format);
+	gg_debug_common(NULL, level, format, ap);
+	va_end(ap);
+	errno = old_errno;
+}
+
+/**
+ * \internal Przekazuje informację odpluskwiania związaną z sesją.
+ *
+ * \param gs Struktura sesji
+ * \param level Poziom wiadomości
+ * \param format Format wiadomości (zgodny z \c printf)
+ *
+ * \ingroup debug
+ */
+void gg_debug_session(struct gg_session *gs, int level, const char *format, ...)
+{
+	va_list ap;
+	int old_errno = errno;
+
+	va_start(ap, format);
+	gg_debug_common(gs, level, format, ap);
+	va_end(ap);
+	errno = old_errno;
+}
+
+/**
+ * \internal Przekazuje zrzut bufora do odpluskwiania.
+ *
+ * \param gs Struktura sesji
+ * \param level Poziom wiadomości
+ * \param buf Bufor danych
+ * \param len Długość bufora danych
+ *
+ * \ingroup debug
+ */
+void gg_debug_dump(struct gg_session *gs, int level, const char *buf, size_t len)
+{
+	char line[80];
+	int i, j;
+
+	for (i = 0; i < len; i += 16) {
+		int ofs;
+
+		sprintf(line, "%.4x: ", i);
+		ofs = 6;
+
+		for (j = 0; j < 16; j++) {
+			if (i + j < len)
+				sprintf(line + ofs, " %02x", (unsigned char) buf[i + j]);
+			else
+				sprintf(line + ofs, "   ");
+
+			ofs += 3;
+		}
+
+		sprintf(line + ofs, "  ");
+		ofs += 2;
+
+		for (j = 0; j < 16; j++) {
+			unsigned char ch;
+
+			if (i + j < len) {
+				ch = buf[i + j];
+
+				if (ch < 32 || ch > 126)
+					ch = '.';
+			} else {
+				ch = ' ';
+			}
+
+			line[ofs++] = ch;
+		}
+
+		line[ofs++] = '\n';
+		line[ofs++] = 0;
+
+		gg_debug_session(gs, level, "%s", line);
+	}
+}
+
+/**
+ * \internal Zwraca ciąg z nazwą podanego stanu sesji.
+ *
+ * \param state Stan sesji.
+ *
+ * \return Ciąg z nazwą stanu
+ *
+ * \ingroup debug
+ */
+const char *gg_debug_state(enum gg_state_t state)
+{
+	switch (state) {
+#define GG_DEBUG_STATE(x) case x: return #x;
+	GG_DEBUG_STATE(GG_STATE_IDLE)
+	GG_DEBUG_STATE(GG_STATE_RESOLVING)
+	GG_DEBUG_STATE(GG_STATE_CONNECTING)
+	GG_DEBUG_STATE(GG_STATE_READING_DATA)
+	GG_DEBUG_STATE(GG_STATE_ERROR)
+	GG_DEBUG_STATE(GG_STATE_CONNECTING_HUB)
+	GG_DEBUG_STATE(GG_STATE_CONNECTING_GG)
+	GG_DEBUG_STATE(GG_STATE_READING_KEY)
+	GG_DEBUG_STATE(GG_STATE_READING_REPLY)
+	GG_DEBUG_STATE(GG_STATE_CONNECTED)
+	GG_DEBUG_STATE(GG_STATE_SENDING_QUERY)
+	GG_DEBUG_STATE(GG_STATE_READING_HEADER)
+	GG_DEBUG_STATE(GG_STATE_PARSING)
+	GG_DEBUG_STATE(GG_STATE_DONE)
+	GG_DEBUG_STATE(GG_STATE_LISTENING)
+	GG_DEBUG_STATE(GG_STATE_READING_UIN_1)
+	GG_DEBUG_STATE(GG_STATE_READING_UIN_2)
+	GG_DEBUG_STATE(GG_STATE_SENDING_ACK)
+	GG_DEBUG_STATE(GG_STATE_READING_ACK)
+	GG_DEBUG_STATE(GG_STATE_READING_REQUEST)
+	GG_DEBUG_STATE(GG_STATE_SENDING_REQUEST)
+	GG_DEBUG_STATE(GG_STATE_SENDING_FILE_INFO)
+	GG_DEBUG_STATE(GG_STATE_READING_PRE_FILE_INFO)
+	GG_DEBUG_STATE(GG_STATE_READING_FILE_INFO)
+	GG_DEBUG_STATE(GG_STATE_SENDING_FILE_ACK)
+	GG_DEBUG_STATE(GG_STATE_READING_FILE_ACK)
+	GG_DEBUG_STATE(GG_STATE_SENDING_FILE_HEADER)
+	GG_DEBUG_STATE(GG_STATE_READING_FILE_HEADER)
+	GG_DEBUG_STATE(GG_STATE_GETTING_FILE)
+	GG_DEBUG_STATE(GG_STATE_SENDING_FILE)
+	GG_DEBUG_STATE(GG_STATE_READING_VOICE_ACK)
+	GG_DEBUG_STATE(GG_STATE_READING_VOICE_HEADER)
+	GG_DEBUG_STATE(GG_STATE_READING_VOICE_SIZE)
+	GG_DEBUG_STATE(GG_STATE_READING_VOICE_DATA)
+	GG_DEBUG_STATE(GG_STATE_SENDING_VOICE_ACK)
+	GG_DEBUG_STATE(GG_STATE_SENDING_VOICE_REQUEST)
+	GG_DEBUG_STATE(GG_STATE_READING_TYPE)
+	GG_DEBUG_STATE(GG_STATE_TLS_NEGOTIATION)
+	GG_DEBUG_STATE(GG_STATE_REQUESTING_ID)
+	GG_DEBUG_STATE(GG_STATE_WAITING_FOR_ACCEPT)
+	GG_DEBUG_STATE(GG_STATE_WAITING_FOR_INFO)
+	GG_DEBUG_STATE(GG_STATE_READING_ID)
+	GG_DEBUG_STATE(GG_STATE_SENDING_ID)
+	GG_DEBUG_STATE(GG_STATE_RESOLVING_GG)
+	GG_DEBUG_STATE(GG_STATE_RESOLVING_RELAY)
+	GG_DEBUG_STATE(GG_STATE_CONNECTING_RELAY)
+	GG_DEBUG_STATE(GG_STATE_READING_RELAY)
+	GG_DEBUG_STATE(GG_STATE_DISCONNECTING)
+
+	// Celowo nie ma default, żeby kompilator wyłapał brakujące stany
+	
+	}
+
+	return NULL;
+}
+
+#else
+
+#undef gg_debug_common
+void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap)
+{
+}
+
+#undef gg_debug
+void gg_debug(int level, const char *format, ...)
+{
+}
+
+#undef gg_debug_session
+void gg_debug_session(struct gg_session *gs, int level, const char *format, ...)
+{
+}
+
+#undef gg_debug_dump
+void gg_debug_dump(struct gg_session *gs, int level, const char *buf, int len)
+{
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/encoding.c	Fri Mar 25 00:06:47 2011 +0000
@@ -0,0 +1,274 @@
+/*
+ *  (C) Copyright 2008-2009 Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *                          Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "libgadu.h"
+
+/**
+ * \file encoding.c
+ *
+ * \brief Funkcje konwersji kodowania tekstu
+ */
+
+/**
+ * \internal Tablica konwersji CP1250 na Unikod.
+ */
+static const uint16_t table_cp1250[] =
+{
+        0x20ac, '?',    0x201a, '?',    0x201e, 0x2026, 0x2020, 0x2021,
+        '?',    0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179,
+        '?',    0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
+        '?',    0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a,
+        0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7,
+        0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b,
+        0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+        0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c,
+        0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7,
+        0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e,
+        0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7,
+        0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df,
+        0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7,
+        0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f,
+        0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7,
+        0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9,
+};
+
+/**
+ * \internal Zamienia tekst kodowany CP1250 na UTF-8.
+ *
+ * \param src Tekst źródłowy w CP1250.
+ * \param src_length Długość ciągu źródłowego (nigdy ujemna).
+ * \param dst_length Długość ciągu docelowego (jeśli -1, nieograniczona).
+ *
+ * \return Zaalokowany bufor z tekstem w UTF-8.
+ */
+static char *gg_encoding_convert_cp1250_utf8(const char *src, int src_length, int dst_length)
+{
+	int i, j, len;
+	char *result = NULL;
+
+	for (i = 0, len = 0; (src[i] != 0) && (i < src_length); i++) {
+		uint16_t uc;
+
+		if ((unsigned char) src[i] < 0x80)
+			uc = (unsigned char) src[i];
+		else
+			uc = table_cp1250[(unsigned char) src[i] - 128];
+
+		if (uc < 0x80)
+			len += 1;
+		else if (uc < 0x800)
+			len += 2;
+		else
+			len += 3;
+	}
+
+	if ((dst_length != -1) && (len > dst_length))
+		len = dst_length;
+
+	result = malloc(len + 1);
+
+	if (result == NULL) 
+		return NULL;
+
+	for (i = 0, j = 0; (src[i] != 0) && (i < src_length) && (j < len); i++) {
+		uint16_t uc;
+
+		if ((unsigned char) src[i] < 0x80)
+			uc = (unsigned char) src[i];
+		else
+			uc = table_cp1250[(unsigned char) src[i] - 128];
+
+		if (uc < 0x80) 
+			result[j++] = uc;
+		else if (uc < 0x800) {
+			if (j + 1 > len)
+				break;
+			result[j++] = 0xc0 | ((uc >> 6) & 0x1f);
+			result[j++] = 0x80 | (uc & 0x3f);
+		} else {
+			if (j + 2 > len)
+				break;
+			result[j++] = 0xe0 | ((uc >> 12) & 0x1f);
+			result[j++] = 0x80 | ((uc >> 6) & 0x3f);
+			result[j++] = 0x80 | (uc & 0x3f);
+		}
+	}
+
+	result[j] = 0;
+
+	return result;
+}
+
+/**
+ * \internal Zamienia tekst kodowany UTF-8 na CP1250.
+ *
+ * \param src Tekst źródłowy w UTF-8.
+ * \param src_length Długość ciągu źródłowego (nigdy ujemna).
+ * \param dst_length Długość ciągu docelowego (jeśli -1, nieograniczona).
+ *
+ * \return Zaalokowany bufor z tekstem w CP1250.
+ */
+static char *gg_encoding_convert_utf8_cp1250(const char *src, int src_length, int dst_length)
+{
+	char *result;
+	int i, j, len, uc_left = 0;
+	uint32_t uc = 0, uc_min = 0;
+
+	for (i = 0, len = 0; (src[i] != 0) && (i < src_length); i++) {
+		if ((src[i] & 0xc0) == 0xc0) {
+			len++;
+		} else if ((src[i] & 0x80) == 0x00) {
+			len++;
+		}
+	}
+
+	if ((dst_length != -1) && (len > dst_length))
+		len = dst_length;
+
+	result = malloc(len + 1);
+
+	if (result == NULL)
+		return NULL;
+
+	for (i = 0, j = 0; (src[i] != 0) && (i < src_length) && (j < len); i++) {
+		if ((unsigned char) src[i] >= 0xf5) {
+			if (uc_left != 0) 
+				result[j++] = '?';
+			/* Restricted sequences */
+			result[j++] = '?';
+			uc_left = 0;
+		} else if ((src[i] & 0xf8) == 0xf0) {
+			if (uc_left != 0) 
+				result[j++] = '?';
+			uc = src[i] & 0x07;
+			uc_left = 3;
+			uc_min = 0x10000;
+		} else if ((src[i] & 0xf0) == 0xe0) {
+			if (uc_left != 0) 
+				result[j++] = '?';
+			uc = src[i] & 0x0f;
+			uc_left = 2;
+			uc_min = 0x800;
+		} else if ((src[i] & 0xe0) == 0xc0) {
+			if (uc_left != 0) 
+				result[j++] = '?';
+			uc = src[i] & 0x1f;
+			uc_left = 1;
+			uc_min = 0x80;
+		} else if ((src[i] & 0xc0) == 0x80) {
+			if (uc_left > 0) {
+				uc <<= 6;
+				uc |= src[i] & 0x3f;
+				uc_left--;
+
+				if (uc_left == 0) {
+					int valid = 0;
+					int k;
+
+					if (uc >= uc_min) {
+						for (k = 0; k < 128; k++) {
+							if (uc == table_cp1250[k]) {
+								result[j++] = k + 128;
+								valid = 1;
+								break;
+							}
+						}
+					}
+
+					if (!valid && uc != 0xfeff)	/* Byte Order Mark */
+						result[j++] = '?';
+				}
+			}
+		} else {
+			if (uc_left != 0) {
+				result[j++] = '?';
+				uc_left = 0;
+			}
+			result[j++] = src[i];
+		}
+	}
+
+	if ((uc_left != 0) && (src[i] == 0))
+		result[j++] = '?';
+
+	result[j] = 0;
+
+	return result;
+}
+
+/**
+ * \internal Zamienia kodowanie tekstu.
+ *
+ * \param src Tekst źródłowy.
+ * \param src_encoding Kodowanie tekstu źródłowego.
+ * \param dst_encoding Kodowanie tekstu docelowego.
+ * \param src_length Długość ciągu źródłowego w bajtach (nigdy ujemna).
+ * \param dst_length Długość ciągu docelowego w bajtach (jeśli -1, nieograniczona).
+ *
+ * \return Zaalokowany bufor z tekstem w kodowaniu docelowym.
+ */
+char *gg_encoding_convert(const char *src, gg_encoding_t src_encoding, gg_encoding_t dst_encoding, int src_length, int dst_length)
+{
+	char *result;
+
+	if (src == NULL) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	// specjalny przypadek obsługiwany ekspresowo
+	if ((dst_encoding == src_encoding) && (dst_length == -1) && (src_length == -1))
+		return strdup(src);
+
+	if (src_length == -1)
+		src_length = strlen(src);
+
+	if (dst_encoding == src_encoding) {
+		int len;
+
+		if (dst_length == -1)
+			len = src_length;
+		else
+			len = (src_length < dst_length) ? src_length : dst_length;
+
+		result = malloc(len + 1);
+
+		if (result == NULL)
+			return NULL;
+
+		strncpy(result, src, len);
+		result[len] = 0;
+
+		return result;
+	}
+
+	if (dst_encoding == GG_ENCODING_CP1250 && src_encoding == GG_ENCODING_UTF8)
+		return gg_encoding_convert_utf8_cp1250(src, src_length, dst_length);
+
+	if (dst_encoding == GG_ENCODING_UTF8 && src_encoding == GG_ENCODING_CP1250)
+		return gg_encoding_convert_cp1250_utf8(src, src_length, dst_length);
+
+	errno = EINVAL;
+	return NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/encoding.h	Fri Mar 25 00:06:47 2011 +0000
@@ -0,0 +1,27 @@
+/*
+ *  (C) Copyright 2008-2009 Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *                          Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  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.
+ */
+
+#ifndef LIBGADU_ENCODING_H
+#define LIBGADU_ENCODING_H
+
+#include "libgadu.h"
+
+char *gg_encoding_convert(const char *src, gg_encoding_t src_encoding, gg_encoding_t dst_encoding, int src_length, int dst_length);
+
+#endif /* LIBGADU_SESSION_H */
--- a/libpurple/protocols/gg/lib/events.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/events.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,4 +1,4 @@
-/* $Id: events.c 855 2009-10-12 21:42:51Z wojtekka $ */
+/* $Id: events.c 1062 2011-03-13 18:10:24Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -29,6 +29,7 @@
 
 #include "libgadu.h"
 #include "libgadu-internal.h"
+#include "libgadu-debug.h"
 
 #include <sys/types.h>
 
@@ -41,6 +42,8 @@
 
 #include "compat.h"
 #include "protocol.h"
+#include "encoding.h"
+#include "session.h"
 
 #include <errno.h>
 #include <stdio.h>
@@ -49,6 +52,10 @@
 #include <time.h>
 #include <unistd.h>
 #include <ctype.h>
+#ifdef GG_CONFIG_HAVE_GNUTLS
+#  include <gnutls/gnutls.h>
+#  include <gnutls/x509.h>
+#endif
 #ifdef GG_CONFIG_HAVE_OPENSSL
 #  include <openssl/err.h>
 #  include <openssl/x509.h>
@@ -73,9 +80,11 @@
 
 	switch (e->type) {
 		case GG_EVENT_MSG:
+		case GG_EVENT_MULTILOGON_MSG:
 			free(e->event.msg.message);
 			free(e->event.msg.formats);
 			free(e->event.msg.recipients);
+			free(e->event.msg.xhtml_message);
 			break;
 
 		case GG_EVENT_NOTIFY:
@@ -129,6 +138,36 @@
 		case GG_EVENT_XML_EVENT:
 			free(e->event.xml_event.data);
 			break;
+
+		case GG_EVENT_USER_DATA:
+		{
+			int i, j;
+
+			for (i = 0; i < e->event.user_data.user_count; i++) {
+				for (j = 0; j < e->event.user_data.users[i].attr_count; j++) {
+					free(e->event.user_data.users[i].attrs[j].key);
+					free(e->event.user_data.users[i].attrs[j].value);
+				}
+
+				free(e->event.user_data.users[i].attrs);
+			}
+
+			free(e->event.user_data.users);
+
+			break;
+		}
+
+		case GG_EVENT_MULTILOGON_INFO:
+		{
+			int i;
+
+			for (i = 0; i < e->event.multilogon_info.count; i++)
+				free(e->event.multilogon_info.sessions[i].name);
+
+			free(e->event.multilogon_info.sessions);
+
+			break;
+		}
 	}
 
 	free(e);
@@ -174,1236 +213,6 @@
 	return 0;
 }
 
-/**
- * \internal Analizuje przychodzący pakiet z obrazkiem.
- *
- * \param e Struktura zdarzenia
- * \param p Bufor z danymi
- * \param len Długość bufora
- * \param sess Struktura sesji
- * \param sender Numer nadawcy
- */
-static void gg_image_queue_parse(struct gg_event *e, char *p, unsigned int len, struct gg_session *sess, uin_t sender)
-{
-	struct gg_msg_image_reply *i = (void*) p;
-	struct gg_image_queue *q, *qq;
-
-	if (!p || !sess || !e) {
-		errno = EFAULT;
-		return;
-	}
-
-	/* znajdź dany obrazek w kolejce danej sesji */
-
-	for (qq = sess->images, q = NULL; qq; qq = qq->next) {
-		if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) {
-			q = qq;
-			break;
-		}
-	}
-
-	if (!q) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32);
-		return;
-	}
-
-	if (p[0] == 0x05) {
-		q->done = 0;
-
-		len -= sizeof(struct gg_msg_image_reply);
-		p += sizeof(struct gg_msg_image_reply);
-
-		if (memchr(p, 0, len) == NULL) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender);
-			return;
-		}
-
-		if (!(q->filename = strdup(p))) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n");
-			return;
-		}
-
-		len -= strlen(p) + 1;
-		p += strlen(p) + 1;
-	} else {
-		len -= sizeof(struct gg_msg_image_reply);
-		p += sizeof(struct gg_msg_image_reply);
-	}
-
-	if (q->done + len > q->size)
-		len = q->size - q->done;
-
-	memcpy(q->image + q->done, p, len);
-	q->done += len;
-
-	/* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */
-
-	if (q->done >= q->size) {
-		e->type = GG_EVENT_IMAGE_REPLY;
-		e->event.image_reply.sender = sender;
-		e->event.image_reply.size = q->size;
-		e->event.image_reply.crc32 = q->crc32;
-		e->event.image_reply.filename = q->filename;
-		e->event.image_reply.image = q->image;
-
-		gg_image_queue_remove(sess, q, 0);
-
-		free(q);
-	}
-}
-
-/**
- * \internal Analizuje informacje rozszerzone wiadomości.
- *
- * \param sess Struktura sesji.
- * \param e Struktura zdarzenia.
- * \param sender Numer nadawcy.
- * \param p Wskaźnik na dane rozszerzone.
- * \param packet_end Wskaźnik na koniec pakietu.
- *
- * \return 0 jeśli się powiodło, -1 jeśli wiadomość obsłużono i wynik ma
- * zostać przekazany aplikacji, -2 jeśli wystąpił błąd ogólny, -3 jeśli
- * wiadomość jest niepoprawna.
- */
-static int gg_handle_recv_msg_options(struct gg_session *sess, struct gg_event *e, uin_t sender, char *p, char *packet_end)
-{
-	while (p < packet_end) {
-		switch (*p) {
-			case 0x01:		/* konferencja */
-			{
-				struct gg_msg_recipients *m = (void*) p;
-				uint32_t i, count;
-
-				p += sizeof(*m);
-
-				if (p > packet_end) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1)\n");
-					goto malformed;
-				}
-
-				count = gg_fix32(m->count);
-
-				if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1.5)\n");
-					goto malformed;
-				}
-
-				if (!(e->event.msg.recipients = (void*) malloc(count * sizeof(uin_t)))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for recipients data\n");
-					goto fail;
-				}
-
-				for (i = 0; i < count; i++, p += sizeof(uint32_t)) {
-					uint32_t u;
-					memcpy(&u, p, sizeof(uint32_t));
-					e->event.msg.recipients[i] = gg_fix32(u);
-				}
-
-				e->event.msg.recipients_count = count;
-
-				break;
-			}
-
-			case 0x02:		/* richtext */
-			{
-				uint16_t len;
-				char *buf;
-
-				if (p + 3 > packet_end) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (2)\n");
-					goto malformed;
-				}
-
-				memcpy(&len, p + 1, sizeof(uint16_t));
-				len = gg_fix16(len);
-
-				if (!(buf = malloc(len))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for richtext data\n");
-					goto fail;
-				}
-
-				p += 3;
-
-				if (p + len > packet_end) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n");
-					free(buf);
-					goto malformed;
-				}
-
-				memcpy(buf, p, len);
-
-				e->event.msg.formats = buf;
-				e->event.msg.formats_length = len;
-
-				p += len;
-
-				break;
-			}
-
-			case 0x04:		/* image_request */
-			{
-				struct gg_msg_image_request *i = (void*) p;
-
-				if (p + sizeof(*i) > packet_end) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n");
-					goto malformed;
-				}
-
-				e->event.image_request.sender = sender;
-				e->event.image_request.size = gg_fix32(i->size);
-				e->event.image_request.crc32 = gg_fix32(i->crc32);
-
-				e->type = GG_EVENT_IMAGE_REQUEST;
-
-				goto handled;
-			}
-
-			case 0x05:		/* image_reply */
-			case 0x06:
-			{
-				struct gg_msg_image_reply *rep = (void*) p;
-
-				if (p + sizeof(struct gg_msg_image_reply) == packet_end) {
-
-					/* pusta odpowiedź - klient po drugiej stronie nie ma żądanego obrazka */
-
-					e->type = GG_EVENT_IMAGE_REPLY;
-					e->event.image_reply.sender = sender;
-					e->event.image_reply.size = 0;
-					e->event.image_reply.crc32 = gg_fix32(rep->crc32);
-					e->event.image_reply.filename = NULL;
-					e->event.image_reply.image = NULL;
-					goto handled;
-
-				} else if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) {
-
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n");
-					goto malformed;
-				}
-
-				rep->size = gg_fix32(rep->size);
-				rep->crc32 = gg_fix32(rep->crc32);
-				gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, sender);
-
-				goto handled;
-			}
-
-			default:
-			{
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p);
-				p = packet_end;
-			}
-		}
-	}
-
-	return 0;
-
-handled:
-	return -1;
-
-fail:
-	return -2;
-
-malformed:
-	return -3;
-}
-
-/**
- * \internal Analizuje przychodzący pakiet z wiadomością.
- *
- * Rozbija pakiet na poszczególne składniki -- tekst, informacje
- * o konferencjach, formatowani itd.
- *
- * \param h Wskaźnik do odebranego pakietu
- * \param e Struktura zdarzenia
- * \param sess Struktura sesji
- *
- * \return 0 jeśli się powiodło, -1 w przypadku błędu
- */
-static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess)
-{
-	struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header));
-	char *p, *packet_end = (char*) r + h->length;
-
-	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e);
-
-	if (!r->seq && !r->msgclass) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n");
-		e->type = GG_EVENT_NONE;
-		return 0;
-	}
-
-	/* znajdź \0 */
-	for (p = (char*) r + sizeof(*r); ; p++) {
-		if (p >= packet_end) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n");
-			goto malformed;
-		}
-
-		if (*p == 0x02 && p == packet_end - 1) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n");
-			break;
-		}
-
-		if (!*p)
-			break;
-	}
-
-	p++;
-
-	switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), p, packet_end)) {
-		case -1:	// handled
-			return 0;
-
-		case -2:	// failed
-			goto fail;
-
-		case -3:	// malformed
-			goto malformed;
-	}
-
-	e->type = GG_EVENT_MSG;
-	e->event.msg.msgclass = gg_fix32(r->msgclass);
-	e->event.msg.sender = gg_fix32(r->sender);
-	e->event.msg.time = gg_fix32(r->time);
-	e->event.msg.seq = gg_fix32(r->seq);
-	e->event.msg.message = (unsigned char*) strdup((char*) r + sizeof(*r));
-
-	return 0;
-
-malformed:
-	e->type = GG_EVENT_NONE;
-	free(e->event.msg.message);
-	free(e->event.msg.recipients);
-	free(e->event.msg.formats);
-
-	return 0;
-
-fail:
-	free(e->event.msg.message);
-	free(e->event.msg.recipients);
-	free(e->event.msg.formats);
-	return -1;
-}
-
-/**
- * \internal Zamienia tekst w formacie HTML na czysty tekst.
- *
- * \param dst Bufor wynikowy (może być \c NULL)
- * \param html Tekst źródłowy
- *
- * \note Dokleja \c \\0 na końcu bufora wynikowego.
- *
- * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL).
- */
-static int gg_convert_from_html(char *dst, const char *html)
-{
-	const char *src, *entity, *tag;
-	int len, in_tag, in_entity;
-
-	len = 0;
-	in_tag = 0;
-	tag = NULL;
-	in_entity = 0;
-	entity = NULL;
-
-	for (src = html; *src != 0; src++) {
-		if (*src == '<') {
-			tag = src;
-			in_tag = 1;
-			continue;
-		}
-
-		if (in_tag && (*src == '>')) {
-			if (strncmp(tag, "<br", 3) == 0) {
-				if (dst != NULL)
-					dst[len] = '\n';
-				len++;
-			}
-			in_tag = 0;
-			continue;
-		}
-
-		if (in_tag)
-			continue;
-
-		if (*src == '&') {
-			in_entity = 1;
-			entity = src;
-			continue;
-		}
-
-		if (in_entity && *src == ';') {
-			in_entity = 0;
-			if (dst != NULL) {
-				if (strncmp(entity, "&lt;", 4) == 0)
-					dst[len] = '<';
-				else if (strncmp(entity, "&gt;", 4) == 0)
-					dst[len] = '>';
-				else if (strncmp(entity, "&quot;", 6) == 0)
-					dst[len] = '"';
-				else if (strncmp(entity, "&apos;", 6) == 0)
-					dst[len] = '\'';
-				else if (strncmp(entity, "&amp;", 5) == 0)
-					dst[len] = '&';
-				else
-					dst[len] = '?';
-			}
-			len++;
-			continue;
-		}
-
-		if (in_entity && !(isalnum(*src) || *src == '#'))
-			in_entity = 0;
-
-		if (in_entity)
-			continue;
-
-		if (dst != NULL)
-			dst[len] = *src;
-
-		len++;
-	}
-
-	if (dst != NULL)
-		dst[len] = 0;
-
-	return len;
-}
-
-/**
- * \internal Analizuje przychodzący pakiet z wiadomością protokołu Gadu-Gadu 8.0.
- *
- * Rozbija pakiet na poszczególne składniki -- tekst, informacje
- * o konferencjach, formatowani itd.
- *
- * \param h Wskaźnik do odebranego pakietu
- * \param e Struktura zdarzenia
- * \param sess Struktura sesji
- *
- * \return 0 jeśli się powiodło, -1 w przypadku błędu
- */
-static int gg_handle_recv_msg80(struct gg_header *h, struct gg_event *e, struct gg_session *sess)
-{
-	char *packet = (char*) h + sizeof(struct gg_header);
-	struct gg_recv_msg80 *r = (struct gg_recv_msg80*) packet;
-	uint32_t offset_plain;
-	uint32_t offset_attr;
-
-	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg80(%p, %p);\n", h, e);
-
-	if (!r->seq && !r->msgclass) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() oops, silently ignoring the bait\n");
-		goto malformed;
-	}
-
-	offset_plain = gg_fix32(r->offset_plain);
-	offset_attr  = gg_fix32(r->offset_attr);
-
-	if (offset_plain < sizeof(struct gg_recv_msg80) || offset_plain >= h->length) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (0)\n");
-		goto malformed;
-	}
-
-	if (offset_attr < sizeof(struct gg_recv_msg80) || offset_attr > h->length) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, attr out of bounds (1)\n");
-		offset_attr = 0;	/* nie parsuj attr. */
-		/* goto ignore; */
-	}
-
-	/* Normalna sytuacja, więc nie podpada pod powyższy warunek. */
-	if (offset_attr == h->length)
-		offset_attr = 0;
-
-	if (memchr(packet + offset_plain, 0, h->length - offset_plain) == NULL) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (2)\n");
-		goto malformed;
-	}
-
-	if (offset_plain > sizeof(struct gg_recv_msg80) && memchr(packet + sizeof(struct gg_recv_msg80), 0, offset_plain - sizeof(struct gg_recv_msg80)) == NULL) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (3)\n");
-		goto malformed;
-	}
-
-	e->type = GG_EVENT_MSG;
-	e->event.msg.msgclass = gg_fix32(r->msgclass);
-	e->event.msg.sender = gg_fix32(r->sender);
-	e->event.msg.time = gg_fix32(r->time);
-	e->event.msg.seq = gg_fix32(r->seq);
-
-	if (sess->encoding == GG_ENCODING_CP1250) {
-		e->event.msg.message = (unsigned char*) strdup(packet + offset_plain);
-	} else {
-		if (offset_plain > sizeof(struct gg_recv_msg80)) {
-			int len;
-
-			len = gg_convert_from_html(NULL, packet + sizeof(struct gg_recv_msg80));
-
-			e->event.msg.message = malloc(len + 1);
-
-			if (e->event.msg.message == NULL)
-				goto fail;
-
-			gg_convert_from_html((char*) e->event.msg.message, packet + sizeof(struct gg_recv_msg80));
-		} else {
-			e->event.msg.message = (unsigned char*) gg_cp_to_utf8(packet + offset_plain);
-		}
-	}
-
-	if (offset_plain > sizeof(struct gg_recv_msg80)) {
-		if (sess->encoding == GG_ENCODING_UTF8)
-			e->event.msg.xhtml_message = strdup(packet + sizeof(struct gg_recv_msg80));
-		else
-			e->event.msg.xhtml_message = gg_utf8_to_cp(packet + sizeof(struct gg_recv_msg80));
-	} else {
-		e->event.msg.xhtml_message = NULL;
-	}
-
-	if (offset_attr != 0) {
-		switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), packet + offset_attr, packet + h->length)) {
-			case -1:	// handled
-				return 0;
-
-			case -2:	// failed
-				goto fail;
-
-			case -3:	// malformed
-				goto malformed;
-		}
-	}
-
-	return 0;
-
-fail:
-	free(e->event.msg.message);
-	free(e->event.msg.xhtml_message);
-	free(e->event.msg.recipients);
-	free(e->event.msg.formats);
-	return -1;
-
-malformed:
-	e->type = GG_EVENT_NONE;
-	free(e->event.msg.message);
-	free(e->event.msg.xhtml_message);
-	free(e->event.msg.recipients);
-	free(e->event.msg.formats);
-	return 0;
-}
-
-/**
- * \internal Odbiera pakiet od serwera.
- *
- * Analizuje pakiet i wypełnia strukturę zdarzenia.
- *
- * \param sess Struktura sesji
- * \param e Struktura zdarzenia
- *
- * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd
- */
-static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e)
-{
-	struct gg_header *h = NULL;
-	char *p;
-
-	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(%p, %p);\n", sess, e);
-
-	if (!sess) {
-		errno = EFAULT;
-		return -1;
-	}
-
-	if (!(h = gg_recv_packet(sess))) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno));
-		goto fail;
-	}
-
-	p = (char*) h + sizeof(struct gg_header);
-
-	switch (h->type) {
-		case GG_RECV_MSG:
-		{
-			if (h->length >= sizeof(struct gg_recv_msg))
-				if (gg_handle_recv_msg(h, e, sess))
-					goto fail;
-
-			break;
-		}
-
-		case GG_RECV_MSG80:
-		{
-			if (h->length >= sizeof(struct gg_recv_msg80))
-				if (gg_handle_recv_msg80(h, e, sess))
-					goto fail;
-
-			break;
-		}
-
-
-		case GG_NOTIFY_REPLY:
-		{
-			struct gg_notify_reply *n = (void*) p;
-			unsigned int count, i;
-			char *tmp;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
-
-			if (h->length < sizeof(*n)) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n");
-				errno = EINVAL;
-				goto fail;
-			}
-
-			if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) {
-				e->type = GG_EVENT_NOTIFY_DESCR;
-
-				if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-					goto fail;
-				}
-				e->event.notify_descr.notify[1].uin = 0;
-				memcpy(e->event.notify_descr.notify, p, sizeof(*n));
-				e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin);
-				e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status);
-				e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port);
-				e->event.notify_descr.notify[0].version = gg_fix32(e->event.notify_descr.notify[0].version);
-
-				count = h->length - sizeof(*n);
-				if (!(tmp = malloc(count + 1))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-					goto fail;
-				}
-				memcpy(tmp, p + sizeof(*n), count);
-				tmp[count] = 0;
-				e->event.notify_descr.descr = tmp;
-
-			} else {
-				e->type = GG_EVENT_NOTIFY;
-
-				if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-					goto fail;
-				}
-
-				memcpy(e->event.notify, p, h->length);
-				count = h->length / sizeof(*n);
-				e->event.notify[count].uin = 0;
-
-				for (i = 0; i < count; i++) {
-					e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin);
-					e->event.notify[i].status = gg_fix32(e->event.notify[i].status);
-					e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port);
-					e->event.notify[i].version = gg_fix32(e->event.notify[i].version);
-				}
-			}
-
-			break;
-		}
-
-		case GG_STATUS:
-		{
-			struct gg_status *s = (void*) p;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
-
-			if (h->length >= sizeof(*s)) {
-				e->type = GG_EVENT_STATUS;
-				memcpy(&e->event.status, p, sizeof(*s));
-				e->event.status.uin = gg_fix32(e->event.status.uin);
-				e->event.status.status = gg_fix32(e->event.status.status);
-				if (h->length > sizeof(*s)) {
-					int len = h->length - sizeof(*s);
-					char *buf = malloc(len + 1);
-					if (buf) {
-						memcpy(buf, p + sizeof(*s), len);
-						buf[len] = 0;
-					}
-					e->event.status.descr = buf;
-				} else
-					e->event.status.descr = NULL;
-			}
-
-			break;
-		}
-
-		case GG_NOTIFY_REPLY77:
-		case GG_NOTIFY_REPLY80BETA:
-		{
-			struct gg_notify_reply77 *n = (void*) p;
-			unsigned int length = h->length, i = 0;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
-
-			e->type = GG_EVENT_NOTIFY60;
-			e->event.notify60 = malloc(sizeof(*e->event.notify60));
-
-			if (!e->event.notify60) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-				goto fail;
-			}
-
-			e->event.notify60[0].uin = 0;
-
-			while (length >= sizeof(struct gg_notify_reply77)) {
-				uin_t uin = gg_fix32(n->uin);
-				char *tmp;
-
-				e->event.notify60[i].uin = uin & 0x00ffffff;
-				e->event.notify60[i].status = n->status;
-				e->event.notify60[i].remote_ip = n->remote_ip;
-				e->event.notify60[i].remote_port = gg_fix16(n->remote_port);
-				e->event.notify60[i].version = n->version;
-				e->event.notify60[i].image_size = n->image_size;
-				e->event.notify60[i].descr = NULL;
-				e->event.notify60[i].time = 0;
-
-				if (uin & 0x40000000)
-					e->event.notify60[i].version |= GG_HAS_AUDIO_MASK;
-				if (uin & 0x20000000)
-					e->event.notify60[i].version |= GG_HAS_AUDIO7_MASK;
-				if (uin & 0x08000000)
-					e->event.notify60[i].version |= GG_ERA_OMNIX_MASK;
-
-				if (GG_S_D(n->status)) {
-					unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply77));
-
-					if (sizeof(struct gg_notify_reply77) + descr_len <= length) {
-						char *descr;
-
-						if (!(descr = malloc(descr_len + 1))) {
-							gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-							goto fail;
-						}
-
-						memcpy(descr, (char*) n + sizeof(struct gg_notify_reply77) + 1, descr_len);
-						descr[descr_len] = 0;
-
-						if (h->type == GG_NOTIFY_REPLY80BETA && sess->encoding != GG_ENCODING_UTF8) {
-							char *cp_descr = gg_utf8_to_cp(descr);
-
-							if (!cp_descr) {
-								gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-								free(descr);
-								goto fail;
-							}
-
-							free(descr);
-							descr = cp_descr;
-						}
-
-						e->event.notify60[i].descr = descr;
-
-						/* XXX czas */
-
-						length -= sizeof(struct gg_notify_reply77) + descr_len + 1;
-						n = (void*) ((char*) n + sizeof(struct gg_notify_reply77) + descr_len + 1);
-					} else {
-						length = 0;
-					}
-
-				} else {
-					length -= sizeof(struct gg_notify_reply77);
-					n = (void*) ((char*) n + sizeof(struct gg_notify_reply77));
-				}
-
-				if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-					free(e->event.notify60);
-					goto fail;
-				}
-
-				e->event.notify60 = (void*) tmp;
-				e->event.notify60[++i].uin = 0;
-			}
-
-			break;
-		}
-
-		case GG_STATUS77:
-		case GG_STATUS80BETA:
-		{
-			struct gg_status77 *s = (void*) p;
-			uint32_t uin;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
-
-			if (h->length < sizeof(*s))
-				break;
-
-			uin = gg_fix32(s->uin);
-
-			e->type = GG_EVENT_STATUS60;
-			e->event.status60.uin = uin & 0x00ffffff;
-			e->event.status60.status = s->status;
-			e->event.status60.remote_ip = s->remote_ip;
-			e->event.status60.remote_port = gg_fix16(s->remote_port);
-			e->event.status60.version = s->version;
-			e->event.status60.image_size = s->image_size;
-			e->event.status60.descr = NULL;
-			e->event.status60.time = 0;
-
-			if (uin & 0x40000000)
-				e->event.status60.version |= GG_HAS_AUDIO_MASK;
-			if (uin & 0x20000000)
-				e->event.status60.version |= GG_HAS_AUDIO7_MASK;
-			if (uin & 0x08000000)
-				e->event.status60.version |= GG_ERA_OMNIX_MASK;
-
-			if (h->length > sizeof(*s)) {
-				int len = h->length - sizeof(*s);
-				char *buf = malloc(len + 1);
-
-				/* XXX, jesli malloc() sie nie uda to robic tak samo jak przy GG_NOTIFY_REPLY* ?
-				 * 	- goto fail; (?)
-				 */
-				if (buf) {
-					memcpy(buf, (char*) p + sizeof(*s), len);
-					buf[len] = 0;
-
-					if (h->type == GG_STATUS80BETA && sess->encoding != GG_ENCODING_UTF8) {
-						char *cp_buf = gg_utf8_to_cp(buf);
-						free(buf);
-						buf = cp_buf;
-					}
-				}
-
-				e->event.status60.descr = buf;
-
-				if (len > 4 && p[h->length - 5] == 0) {
-					uint32_t t;
-					memcpy(&t, p + h->length - 4, sizeof(uint32_t));
-					e->event.status60.time = gg_fix32(t);
-				}
-			}
-
-			break;
-		}
-
-		case GG_NOTIFY_REPLY60:
-		{
-			struct gg_notify_reply60 *n = (void*) p;
-			unsigned int length = h->length, i = 0;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
-
-			e->type = GG_EVENT_NOTIFY60;
-			e->event.notify60 = malloc(sizeof(*e->event.notify60));
-
-			if (!e->event.notify60) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-				goto fail;
-			}
-
-			e->event.notify60[0].uin = 0;
-
-			while (length >= sizeof(struct gg_notify_reply60)) {
-				uin_t uin = gg_fix32(n->uin);
-				char *tmp;
-
-				e->event.notify60[i].uin = uin & 0x00ffffff;
-				e->event.notify60[i].status = n->status;
-				e->event.notify60[i].remote_ip = n->remote_ip;
-				e->event.notify60[i].remote_port = gg_fix16(n->remote_port);
-				e->event.notify60[i].version = n->version;
-				e->event.notify60[i].image_size = n->image_size;
-				e->event.notify60[i].descr = NULL;
-				e->event.notify60[i].time = 0;
-
-				if (uin & 0x40000000)
-					e->event.notify60[i].version |= GG_HAS_AUDIO_MASK;
-				if (uin & 0x08000000)
-					e->event.notify60[i].version |= GG_ERA_OMNIX_MASK;
-
-				if (GG_S_D(n->status)) {
-					unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60));
-
-					if (sizeof(struct gg_notify_reply60) + descr_len <= length) {
-						if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) {
-							gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-							goto fail;
-						}
-
-						memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len);
-						e->event.notify60[i].descr[descr_len] = 0;
-
-						/* XXX czas */
-
-						length -= sizeof(struct gg_notify_reply60) + descr_len + 1;
-						n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1);
-					} else {
-						length = 0;
-					}
-
-				} else {
-					length -= sizeof(struct gg_notify_reply60);
-					n = (void*) ((char*) n + sizeof(struct gg_notify_reply60));
-				}
-
-				if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-					free(e->event.notify60);
-					goto fail;
-				}
-
-				e->event.notify60 = (void*) tmp;
-				e->event.notify60[++i].uin = 0;
-			}
-
-			break;
-		}
-
-		case GG_STATUS60:
-		{
-			struct gg_status60 *s = (void*) p;
-			uint32_t uin;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
-
-			if (h->length < sizeof(*s))
-				break;
-
-			uin = gg_fix32(s->uin);
-
-			e->type = GG_EVENT_STATUS60;
-			e->event.status60.uin = uin & 0x00ffffff;
-			e->event.status60.status = s->status;
-			e->event.status60.remote_ip = s->remote_ip;
-			e->event.status60.remote_port = gg_fix16(s->remote_port);
-			e->event.status60.version = s->version;
-			e->event.status60.image_size = s->image_size;
-			e->event.status60.descr = NULL;
-			e->event.status60.time = 0;
-
-			if (uin & 0x40000000)
-				e->event.status60.version |= GG_HAS_AUDIO_MASK;
-			if (uin & 0x08000000)
-				e->event.status60.version |= GG_ERA_OMNIX_MASK;
-
-			if (h->length > sizeof(*s)) {
-				int len = h->length - sizeof(*s);
-				char *buf = malloc(len + 1);
-
-				if (buf) {
-					memcpy(buf, (char*) p + sizeof(*s), len);
-					buf[len] = 0;
-				}
-
-				e->event.status60.descr = buf;
-
-				if (len > 4 && p[h->length - 5] == 0) {
-					uint32_t t;
-					memcpy(&t, p + h->length - 4, sizeof(uint32_t));
-					e->event.status60.time = gg_fix32(t);
-				}
-			}
-
-			break;
-		}
-
-		case GG_STATUS80:
-		{
-			struct gg_notify_reply80 *s = (void*) p;
-			uint32_t descr_len;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
-
-			if (h->length < sizeof(*s))
-				break;
-
-			e->type = GG_EVENT_STATUS60;
-			e->event.status60.uin		= gg_fix32(s->uin);
-			e->event.status60.status	= gg_fix32(s->status);
-			e->event.status60.remote_ip	= s->remote_ip;
-			e->event.status60.remote_port	= gg_fix16(s->remote_port);
-			e->event.status60.image_size	= s->image_size;
-			e->event.status60.descr		= NULL;
-			e->event.status60.version	= 0x00;	/* not-supported */
-			e->event.status60.time		= 0;	/* not-supported */
-
-			descr_len = gg_fix32(s->descr_len);
-
-			if (descr_len > 0 && h->length-sizeof(*s) >= descr_len) {
-				char *buf = malloc(descr_len + 1);
-
-				if (buf) {
-					memcpy(buf, (char*) p + sizeof(*s), descr_len);
-					buf[descr_len] = 0;
-
-					if (sess->encoding != GG_ENCODING_UTF8) {
-						char *cp_buf = gg_utf8_to_cp(buf);
-						free(buf);
-						buf = cp_buf;
-					}
-				}
-
-				e->event.status60.descr = buf;
-			}
-			break;
-		}
-
-		case GG_NOTIFY_REPLY80:
-		{
-			struct gg_notify_reply80 *n = (void*) p;
-			unsigned int length = h->length, i = 0;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
-
-			e->type = GG_EVENT_NOTIFY60;
-			e->event.notify60 = malloc(sizeof(*e->event.notify60));
-
-			if (!e->event.notify60) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-				goto fail;
-			}
-
-			e->event.notify60[0].uin = 0;
-
-			while (length >= sizeof(struct gg_notify_reply80)) {
-				uint32_t descr_len;
-				char *tmp;
-
-				e->event.notify60[i].uin	= gg_fix32(n->uin);
-				e->event.notify60[i].status	= gg_fix32(n->status);
-				e->event.notify60[i].remote_ip	= n->remote_ip;
-				e->event.notify60[i].remote_port= gg_fix16(n->remote_port);
-				e->event.notify60[i].image_size	= n->image_size;
-				e->event.notify60[i].descr	= NULL;
-				e->event.notify60[i].version	= 0x00;	/* not-supported */
-				e->event.notify60[i].time	= 0;	/* not-supported */
-
-				descr_len = gg_fix32(n->descr_len);
-
-				length -= sizeof(struct gg_notify_reply80);
-				n = (void*) ((char*) n + sizeof(struct gg_notify_reply80));
-
-				if (descr_len) {
-					if (length >= descr_len) {
-						/* XXX, GG_S_D(n->status) */
-						char *descr;
-
-						if (!(descr = malloc(descr_len + 1))) {
-							gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-							goto fail;
-						}
-
-						memcpy(descr, n, descr_len);
-						descr[descr_len] = 0;
-
-						if (sess->encoding != GG_ENCODING_UTF8) {
-							char *cp_descr = gg_utf8_to_cp(descr);
-
-							if (!cp_descr) {
-								gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-								free(descr);
-								goto fail;
-							}
-
-							free(descr);
-							descr = cp_descr;
-						}
-						e->event.notify60[i].descr = descr;
-
-						length -= descr_len;
-						n = (void*) ((char*) n + descr_len);
-					} else
-						length = 0;
-				}
-
-				if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n");
-					free(e->event.notify60);
-					goto fail;
-				}
-
-				e->event.notify60 = (void*) tmp;
-				e->event.notify60[++i].uin = 0;
-			}
-			break;
-		}
-
-		case GG_SEND_MSG_ACK:
-		{
-			struct gg_send_msg_ack *s = (void*) p;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n");
-
-			if (h->length < sizeof(*s))
-				break;
-
-			e->type = GG_EVENT_ACK;
-			e->event.ack.status = gg_fix32(s->status);
-			e->event.ack.recipient = gg_fix32(s->recipient);
-			e->event.ack.seq = gg_fix32(s->seq);
-
-			break;
-		}
-
-		case GG_PONG:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n");
-
-			e->type = GG_EVENT_PONG;
-			sess->last_pong = time(NULL);
-
-			break;
-		}
-
-		case GG_DISCONNECTING:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n");
-			e->type = GG_EVENT_DISCONNECT;
-			break;
-		}
-
-		case GG_DISCONNECT_ACK:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection acknowledge\n");
-			e->type = GG_EVENT_DISCONNECT_ACK;
-			break;
-		}
-
-		case GG_XML_EVENT:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML event\n");
-			e->type = GG_EVENT_XML_EVENT;
-			if (!(e->event.xml_event.data = (char *) malloc(h->length + 1))) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for XML event data\n");
-				goto fail;
-			}
-			memcpy(e->event.xml_event.data, p, h->length);
-			e->event.xml_event.data[h->length] = 0;
-			break;
-		}
-
-		case GG_PUBDIR50_REPLY:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n");
-			if (gg_pubdir50_handle_reply_sess(sess, e, p, h->length) == -1)
-				goto fail;
-			break;
-		}
-
-		case GG_USERLIST_REPLY:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n");
-
-			if (h->length < 1)
-				break;
-
-			/* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko
-			 * gdy otrzymano wszystkie odpowiedzi */
-			if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) {
-				if (--sess->userlist_blocks)
-					break;
-
-				p[0] = GG_USERLIST_PUT_REPLY;
-			}
-
-			if (h->length > 1) {
-				char *tmp;
-				unsigned int len = (sess->userlist_reply) ? strlen(sess->userlist_reply) : 0;
-
-				gg_debug_session(sess, GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len);
-
-				if (!(tmp = realloc(sess->userlist_reply, len + h->length))) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n");
-					free(sess->userlist_reply);
-					sess->userlist_reply = NULL;
-					goto fail;
-				}
-
-				sess->userlist_reply = tmp;
-				sess->userlist_reply[len + h->length - 1] = 0;
-				memcpy(sess->userlist_reply + len, p + 1, h->length - 1);
-			}
-
-			if (p[0] == GG_USERLIST_GET_MORE_REPLY)
-				break;
-
-			e->type = GG_EVENT_USERLIST;
-			e->event.userlist.type = p[0];
-			e->event.userlist.reply = sess->userlist_reply;
-			sess->userlist_reply = NULL;
-
-			break;
-		}
-
-		case GG_DCC7_ID_REPLY:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 id packet\n");
-
-			if (h->length < sizeof(struct gg_dcc7_id_reply))
-				break;
-
-			if (gg_dcc7_handle_id(sess, e, p, h->length) == -1)
-				goto fail;
-
-			break;
-		}
-
-		case GG_DCC7_ACCEPT:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 accept\n");
-
-			if (h->length < sizeof(struct gg_dcc7_accept))
-				break;
-
-			if (gg_dcc7_handle_accept(sess, e, p, h->length) == -1)
-				goto fail;
-
-			break;
-		}
-
-		case GG_DCC7_NEW:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 request\n");
-
-			if (h->length < sizeof(struct gg_dcc7_new))
-				break;
-
-			if (gg_dcc7_handle_new(sess, e, p, h->length) == -1)
-				goto fail;
-
-			break;
-		}
-
-		case GG_DCC7_REJECT:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 reject\n");
-
-			if (h->length < sizeof(struct gg_dcc7_reject))
-				break;
-
-			if (gg_dcc7_handle_reject(sess, e, p, h->length) == -1)
-				goto fail;
-
-			break;
-		}
-
-		case GG_DCC7_INFO:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 info\n");
-
-			if (h->length < sizeof(struct gg_dcc7_info))
-				break;
-
-			if (gg_dcc7_handle_info(sess, e, p, h->length) == -1)
-				goto fail;
-
-			break;
-		}
-
-		default:
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type);
-	}
-
-	free(h);
-	return 0;
-
-fail:
-	free(h);
-	return -1;
-}
-
 /** \endcond */
 
 /**
@@ -1465,6 +274,8 @@
 			memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res);
 			sess->send_left -= res;
 		}
+
+		res = 0;
 	}
 
 	switch (sess->state) {
@@ -1568,15 +379,14 @@
 
 			auth = gg_proxy_auth();
 
-#ifdef GG_CONFIG_HAVE_OPENSSL
-			if (sess->ssl) {
+#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL)
+			if (sess->ssl != NULL) {
 				snprintf(buf, sizeof(buf) - 1,
-					"GET %s/appsvc/appmsg3.asp?fmnumber=%u&version=%s&lastmsg=%d HTTP/1.0\r\n"
+					"GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n"
+					"Connection: close\r\n"
 					"Host: " GG_APPMSG_HOST "\r\n"
-					"User-Agent: " GG_HTTP_USERAGENT "\r\n"
-					"Pragma: no-cache\r\n"
 					"%s"
-					"\r\n", host, sess->uin, client, sess->last_sysmsg, (auth) ? auth : "");
+					"\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : "");
 			} else
 #endif
 			{
@@ -1590,12 +400,6 @@
 			free(auth);
 			free(client);
 
-			/* zwolnij pamięć po wersji klienta. */
-			if (sess->client_version) {
-				free(sess->client_version);
-				sess->client_version = NULL;
-			}
-
 			gg_debug_session(sess, GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf);
 
 			/* zapytanie jest krótkie, więc zawsze zmieści się
@@ -1681,15 +485,10 @@
 			/* analizujemy otrzymane dane. */
 			tmp = buf;
 
-#ifdef GG_CONFIG_HAVE_OPENSSL
-			if (!sess->ssl)
-#endif
-			{
-				while (*tmp && *tmp != ' ')
-					tmp++;
-				while (*tmp && *tmp == ' ')
-					tmp++;
-			}
+			while (*tmp && *tmp != ' ')
+				tmp++;
+			while (*tmp && *tmp == ' ')
+				tmp++;
 			while (*tmp && *tmp != ' ')
 				tmp++;
 			while (*tmp && *tmp == ' ')
@@ -1730,6 +529,67 @@
 
 			sess->port = port;
 
+			/* Jeśli podano nazwę, nie adres serwera... */
+			if (sess->server_addr == INADDR_NONE) {
+				if (sess->resolver_start(&sess->fd, &sess->resolver, host) == -1) {
+					gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno));
+					goto fail_resolving;
+				}
+
+				sess->state = GG_STATE_RESOLVING_GG;
+				sess->check = GG_CHECK_READ;
+				sess->timeout = GG_DEFAULT_TIMEOUT;
+				break;
+			}
+
+			/* łączymy się z właściwym serwerem. */
+			if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno));
+
+				sess->port = GG_HTTPS_PORT;
+
+				/* nie wyszło? próbujemy portu 443. */
+				if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) {
+					/* ostatnia deska ratunku zawiodła?
+					 * w takim razie zwijamy manatki. */
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+					goto fail_connecting;
+				}
+			}
+
+			sess->state = GG_STATE_CONNECTING_GG;
+			sess->check = GG_CHECK_WRITE;
+			sess->timeout = GG_DEFAULT_TIMEOUT;
+			sess->soft_timeout = 1;
+
+			break;
+		}
+
+		case GG_STATE_RESOLVING_GG:
+		{
+			struct in_addr addr;
+			int failed = 0;
+
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING_GG\n");
+
+			if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n");
+				failed = 1;
+				errno2 = errno;
+			}
+
+			close(sess->fd);
+			sess->fd = -1;
+
+			sess->resolver_cleanup(&sess->resolver, 0);
+
+			if (failed) {
+				errno = errno2;
+				goto fail_resolving;
+			}
+
+			sess->server_addr = addr.s_addr;
+
 			/* łączymy się z właściwym serwerem. */
 			if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) {
 				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno));
@@ -1779,7 +639,7 @@
 					errno = ETIMEDOUT;
 #endif
 
-#ifdef GG_CONFIG_HAVE_OPENSSL
+#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL)
 				/* jeśli logujemy się po TLS, nie próbujemy
 				 * się łączyć już z niczym innym w przypadku
 				 * błędu. nie dość, że nie ma sensu, to i
@@ -1858,9 +718,14 @@
 				}
 			}
 
+#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL)
+			if (sess->ssl != NULL) {
+#ifdef GG_CONFIG_HAVE_GNUTLS
+				gnutls_transport_set_ptr(GG_SESSION_GNUTLS(sess), (gnutls_transport_ptr_t) sess->fd);
+#endif
 #ifdef GG_CONFIG_HAVE_OPENSSL
-			if (sess->ssl) {
 				SSL_set_fd(sess->ssl, sess->fd);
+#endif
 
 				sess->state = GG_STATE_TLS_NEGOTIATION;
 				sess->check = GG_CHECK_WRITE;
@@ -1877,6 +742,83 @@
 			break;
 		}
 
+#ifdef GG_CONFIG_HAVE_GNUTLS
+		case GG_STATE_TLS_NEGOTIATION:
+		{
+			int res;
+
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n");
+
+gnutls_handshake_repeat:
+			res = gnutls_handshake(GG_SESSION_GNUTLS(sess));
+
+			if (res == GNUTLS_E_AGAIN) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_AGAIN\n");
+
+				sess->state = GG_STATE_TLS_NEGOTIATION;
+				if (gnutls_record_get_direction(GG_SESSION_GNUTLS(sess)) == 0)
+					sess->check = GG_CHECK_READ;
+				else
+					sess->check = GG_CHECK_WRITE;
+				sess->timeout = GG_DEFAULT_TIMEOUT;
+				break;
+			}
+
+			if (res == GNUTLS_E_INTERRUPTED) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_INTERRUPTED\n");
+				goto gnutls_handshake_repeat;
+			}
+
+			if (res != 0) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake error %d\n", res);
+				e->type = GG_EVENT_CONN_FAILED;
+				e->event.failure = GG_FAILURE_TLS;
+				sess->state = GG_STATE_IDLE;
+				close(sess->fd);
+				sess->fd = -1;
+				break;
+			}
+
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n");
+			gg_debug_session(sess, GG_DEBUG_MISC, "//   cipher: VERS-%s:%s:%s:%s:COMP-%s\n",
+				gnutls_protocol_get_name(gnutls_protocol_get_version(GG_SESSION_GNUTLS(sess))),
+				gnutls_cipher_get_name(gnutls_cipher_get(GG_SESSION_GNUTLS(sess))),
+				gnutls_kx_get_name(gnutls_kx_get(GG_SESSION_GNUTLS(sess))),
+				gnutls_mac_get_name(gnutls_mac_get(GG_SESSION_GNUTLS(sess))),
+				gnutls_compression_get_name(gnutls_compression_get(GG_SESSION_GNUTLS(sess))));
+
+			if (gnutls_certificate_type_get(GG_SESSION_GNUTLS(sess)) == GNUTLS_CRT_X509) {
+				unsigned int peer_count;
+				const gnutls_datum_t *peers;
+				gnutls_x509_crt_t cert;
+
+				if (gnutls_x509_crt_init(&cert) >= 0) {
+					peers = gnutls_certificate_get_peers(GG_SESSION_GNUTLS(sess), &peer_count);
+
+					if (peers != NULL) {
+						char buf[256];
+						size_t size;
+
+						if (gnutls_x509_crt_import(cert, &peers[0], GNUTLS_X509_FMT_DER) >= 0) {
+							size = sizeof(buf);
+							gnutls_x509_crt_get_dn(cert, buf, &size);
+							gg_debug_session(sess, GG_DEBUG_MISC, "//   cert subject: %s\n", buf);
+							size = sizeof(buf);
+							gnutls_x509_crt_get_issuer_dn(cert, buf, &size);
+							gg_debug_session(sess, GG_DEBUG_MISC, "//   cert issuer: %s\n", buf);
+						}
+					}
+				}
+			}
+
+			sess->state = GG_STATE_READING_KEY;
+			sess->check = GG_CHECK_READ;
+			sess->timeout = GG_DEFAULT_TIMEOUT;
+
+			break;
+		}
+#endif
+
 #ifdef GG_CONFIG_HAVE_OPENSSL
 		case GG_STATE_TLS_NEGOTIATION:
 		{
@@ -1916,7 +858,7 @@
 
 					break;
 				} else {
-					char buf[1024];
+					char buf[256];
 
 					ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
 
@@ -1938,7 +880,7 @@
 			if (!peer)
 				gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! unable to get peer certificate!\n");
 			else {
-				char buf[1024];
+				char buf[256];
 
 				X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf));
 				gg_debug_session(sess, GG_DEBUG_MISC, "//   cert subject: %s\n", buf);
@@ -1956,20 +898,17 @@
 #endif
 
 		case GG_STATE_READING_KEY:
+		case GG_STATE_READING_REPLY:
+		case GG_STATE_CONNECTED:
+		case GG_STATE_DISCONNECTING:
 		{
-			struct gg_header *h;
-			struct gg_welcome *w;
-			unsigned char *password = (unsigned char*) sess->password;
-			int ret;
-			uint8_t login_hash[64];
+			struct gg_header *gh;
 
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n");
-
-			memset(login_hash, 0, sizeof(login_hash));
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n");
 
 			/* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie
 			 * się tekstu wrzucanego przez proxy. */
-			if (sess->proxy_addr && sess->proxy_port) {
+			if (sess->state == GG_STATE_READING_KEY && sess->proxy_addr && sess->proxy_port) {
 				char buf[100];
 
 				strcpy(buf, "");
@@ -1991,249 +930,30 @@
 				break;
 			}
 
-			/* czytaj pierwszy pakiet. */
-			if (!(h = gg_recv_packet(sess))) {
-				if (errno == EAGAIN) {
-					sess->check = GG_CHECK_READ;
-					break;
-				}
-
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno));
-
-				e->type = GG_EVENT_CONN_FAILED;
-				e->event.failure = GG_FAILURE_READING;
-				sess->state = GG_STATE_IDLE;
-				errno2 = errno;
-				close(sess->fd);
-				errno = errno2;
-				sess->fd = -1;
-				break;
-			}
-
-			if (h->type != GG_WELCOME) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n");
-				free(h);
-				close(sess->fd);
-				sess->fd = -1;
-				errno = EINVAL;
-				e->type = GG_EVENT_CONN_FAILED;
-				e->event.failure = GG_FAILURE_INVALID;
-				sess->state = GG_STATE_IDLE;
-				break;
-			}
-
-			w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header));
-			w->key = gg_fix32(w->key);
-
-			switch (sess->hash_type) {
-				case GG_LOGIN_HASH_GG32:
-				{
-					unsigned int hash;
-
-					hash = gg_fix32(gg_login_hash(password, w->key));
-					gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> GG32 hash %.8x\n", w->key, hash);
-					memcpy(login_hash, &hash, sizeof(hash));
-
-					break;
-				}
-
-				case GG_LOGIN_HASH_SHA1:
-				{
-					char tmp[41];
-					int i;
-
-					gg_login_hash_sha1((char*) password, w->key, login_hash);
-					for (i = 0; i < 40; i += 2)
-						snprintf(tmp + i, sizeof(tmp) - i, "%02x", login_hash[i / 2]);
-
-					gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> SHA1 hash: %s\n", w->key, tmp);
-
-					break;
-				}
-			}
-
-			free(h);
-			free(sess->password);
-			sess->password = NULL;
-
-			if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) {
-				struct sockaddr_in sin;
-				socklen_t sin_len = sizeof(sin);
-
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n");
-
-				if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr));
-					sess->client_addr = sin.sin_addr.s_addr;
-				} else {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n");
-					sess->client_addr = 0;
-				}
-			} else
-				sess->client_addr = gg_dcc_ip;
-
-			if (sess->protocol_version >= 0x2e) {
-				struct gg_login80 l;
-
-				uint32_t tmp_version_len	= gg_fix32(strlen(GG8_VERSION));
-				uint32_t tmp_descr_len		= gg_fix32((sess->initial_descr) ? strlen(sess->initial_descr) : 0);
-
-				memset(&l, 0, sizeof(l));
-				l.uin           = gg_fix32(sess->uin);
-				memcpy(l.language, GG8_LANG, sizeof(l.language));
-				l.hash_type     = sess->hash_type;
-				memcpy(l.hash, login_hash, sizeof(login_hash));
-				l.status        = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL);
-				l.flags		= gg_fix32(0x00800001);
-				l.features	= gg_fix32(sess->protocol_features);
-				l.image_size    = sess->image_size;
-				l.dunno2        = 0x64;
-
-				gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN80 packet\n");
-				ret = gg_send_packet(sess, GG_LOGIN80,
-						&l, sizeof(l),
-						&tmp_version_len, sizeof(uint32_t), GG8_VERSION, strlen(GG8_VERSION),
-						&tmp_descr_len, sizeof(uint32_t), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0,
-						NULL);
-
-			} else if (sess->protocol_version == 0x2d) {
-				struct gg_login70 l;
-
-				memset(&l, 0, sizeof(l));
-				l.uin		= gg_fix32(sess->uin);
-				l.local_ip	= (sess->external_addr) ? sess->external_addr : sess->client_addr;
-				l.local_port	= gg_fix16((sess->external_port > 1023) ? sess->external_port : gg_dcc_port);
-				l.hash_type	= sess->hash_type;
-				memcpy(l.hash, login_hash, sizeof(login_hash));
-				l.image_size	= sess->image_size;
-				l.dunno2 	= 0x64;
-				l.status	= gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL);
-				l.version	= gg_fix32(sess->protocol_version | sess->protocol_flags);
-
-				gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN80BETA packet\n");
-				ret = gg_send_packet(sess, GG_LOGIN80BETA,
-						&l, sizeof(l),
-						sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0,
-						(sess->initial_descr) ? "\0" : NULL, (sess->initial_descr) ? 1 : 0,
-						NULL);
-			} else {
-				struct gg_login70 l;
-
-				memset(&l, 0, sizeof(l));
-				l.local_ip	= (sess->external_addr) ? sess->external_addr : sess->client_addr;
-				l.uin		= gg_fix32(sess->uin);
-				l.local_port	= gg_fix16((sess->external_port > 1023) ? sess->external_port : gg_dcc_port);
-				l.hash_type	= sess->hash_type;
-				memcpy(l.hash, login_hash, sizeof(login_hash));
-				l.image_size	= sess->image_size;
-				l.dunno2	= 0xbe;
-				l.status	= gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL);
-				l.version	= gg_fix32(sess->protocol_version | sess->protocol_flags);
-
-				gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN70 packet\n");
-				ret = gg_send_packet(sess, GG_LOGIN70,
-						&l, sizeof(l),
-						sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0,
-						NULL);
-			}
-
-			free(sess->initial_descr);
-			sess->initial_descr = NULL;
-
-			if (ret == -1) {
-				gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno));
-				errno2 = errno;
-				close(sess->fd);
-				errno = errno2;
-				sess->fd = -1;
-				e->type = GG_EVENT_CONN_FAILED;
-				e->event.failure = GG_FAILURE_WRITING;
-				sess->state = GG_STATE_IDLE;
-				break;
-			}
-
-			sess->state = GG_STATE_READING_REPLY;
-			sess->check = GG_CHECK_READ;
-
-			break;
-		}
-
-		case GG_STATE_READING_REPLY:
-		{
-			struct gg_header *h;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n");
-
-			if (!(h = gg_recv_packet(sess))) {
-				if (errno == EAGAIN) {
-					sess->check = GG_CHECK_READ;
-					break;
-				}
-
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno));
-				e->type = GG_EVENT_CONN_FAILED;
-				e->event.failure = GG_FAILURE_READING;
-				sess->state = GG_STATE_IDLE;
-				errno2 = errno;
-				close(sess->fd);
-				errno = errno2;
-				sess->fd = -1;
-				break;
-			}
-
-			if (h->type == GG_LOGIN_OK || h->type == GG_NEED_EMAIL || h->type == GG_LOGIN80_OK) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n");
-				e->type = GG_EVENT_CONN_SUCCESS;
-				sess->state = GG_STATE_CONNECTED;
-				sess->check = GG_CHECK_READ;
-				sess->timeout = -1;
-				sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL;
-				free(h);
-				break;
-			}
-
-			if (h->type == GG_LOGIN_FAILED) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login failed\n");
-				e->event.failure = GG_FAILURE_PASSWORD;
-				errno = EACCES;
-			} else if (h->type == GG_DISCONNECTING) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n");
-				e->event.failure = GG_FAILURE_INTRUDER;
-				errno = EACCES;
-			} else {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n");
-				e->event.failure = GG_FAILURE_INVALID;
-				errno = EINVAL;
-			}
-
-			e->type = GG_EVENT_CONN_FAILED;
-			sess->state = GG_STATE_IDLE;
-			errno2 = errno;
-			close(sess->fd);
-			errno = errno2;
-			sess->fd = -1;
-			free(h);
-
-			break;
-		}
-
-		case GG_STATE_CONNECTED:
-		{
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n");
-
 			sess->last_event = time(NULL);
 
-			if ((res = gg_watch_fd_connected(sess, e)) == -1) {
+			gh = gg_recv_packet(sess);
 
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() watch_fd_connected failed (errno=%d, %s)\n", errno, strerror(errno));
-
+			if (gh == NULL) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno));
  				if (errno == EAGAIN) {
 					e->type = GG_EVENT_NONE;
 					res = 0;
-				} else
+				} else {
 					res = -1;
+				}
+
+				goto done;
 			}
 
+			if (gg_session_handle_packet(sess, gh->type, (const char *) gh + sizeof(struct gg_header), gh->length, e) == -1) {
+				free(gh);
+				res = -1;
+				goto done;
+			}
+
+			free(gh);
+
 			sess->check = GG_CHECK_READ;
 
 			break;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/handlers.c	Fri Mar 25 00:06:47 2011 +0000
@@ -0,0 +1,1792 @@
+/*
+ *  (C) Copyright 2001-2011 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>
+ *                          Adam Wysocki <gophi@ekg.chmurka.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+/**
+ * \file handlers.c
+ *
+ * \brief Funkcje obsługi przychodzących pakietów
+ */
+
+#include <sys/types.h>
+
+#ifndef _WIN32
+#  include <sys/socket.h>
+#  include <netinet/in.h>
+#  include <arpa/inet.h>
+#  ifdef sun
+#    include <sys/filio.h>
+#  endif
+#endif
+
+#include "compat.h"
+#include "libgadu.h"
+#include "resolver.h"
+#include "session.h"
+#include "protocol.h"
+#include "encoding.h"
+#include "message.h"
+#include "libgadu-internal.h"
+
+#include <errno.h>
+#ifndef _WIN32
+#  include <netdb.h>
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <time.h>
+#ifdef GG_CONFIG_HAVE_OPENSSL
+#  include <openssl/err.h>
+#  include <openssl/rand.h>
+#endif
+
+/**
+ * \internal Struktura opisująca funkcję obsługi pakietu.
+ */
+typedef struct {
+	/* Typ pakietu */
+	uint32_t type;
+	/* Stan w którym pakiet jest obsługiwany */
+	int state;
+	/* Minimalny rozmiar danych pakietu */
+	int min_length;
+	/* Funkcja obsługująca pakiet. Patrz gg_session_handle_packet(). */
+	int (*handler)(struct gg_session *, uint32_t, const char *, size_t, struct gg_event *);
+} gg_packet_handler_t;
+
+/**
+ * \internal Obsługuje pakiet GG_WELCOME.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_welcome(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_welcome *w;
+	int ret;
+	uint8_t hash_buf[64];
+	uint32_t local_ip;
+
+	if (len < sizeof(struct gg_welcome)) {
+		ge->type = GG_EVENT_CONN_FAILED;
+		ge->event.failure = GG_FAILURE_INVALID;
+		gs->state = GG_STATE_IDLE;
+		close(gs->fd);
+		gs->fd = -1;
+		return 0;
+	}
+
+	w = (struct gg_welcome*) ptr;
+	w->key = gg_fix32(w->key);
+
+	memset(hash_buf, 0, sizeof(hash_buf));
+
+	switch (gs->hash_type) {
+		case GG_LOGIN_HASH_GG32:
+		{
+			uint32_t hash;
+
+			hash = gg_fix32(gg_login_hash((unsigned char*) gs->password, w->key));
+			gg_debug_session(gs, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> GG32 hash %.8x\n", w->key, hash);
+			memcpy(hash_buf, &hash, sizeof(hash));
+
+			break;
+		}
+
+		case GG_LOGIN_HASH_SHA1:
+		{
+#ifndef GG_DEBUG_DISABLE
+			char tmp[41];
+			int i;
+#endif
+
+			gg_login_hash_sha1(gs->password, w->key, hash_buf);
+
+#ifndef GG_DEBUG_DISABLE
+			for (i = 0; i < 40; i += 2)
+				snprintf(tmp + i, sizeof(tmp) - i, "%02x", hash_buf[i / 2]);
+
+			gg_debug_session(gs, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> SHA1 hash: %s\n", w->key, tmp);
+#endif
+
+			break;
+		}
+
+		default:
+			break;
+	}
+
+#if 0
+	if (gs->password != NULL && (gs->flags & (1 << GG_SESSION_FLAG_CLEAR_PASSWORD))) {
+		memset(gs->password, 0, strlen(gs->password));
+		free(gs->password);
+		gs->password = NULL;
+	}
+#endif
+
+	if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) {
+		struct sockaddr_in sin;
+		unsigned int sin_len = sizeof(sin);
+
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n");
+
+		if (!getsockname(gs->fd, (struct sockaddr*) &sin, &sin_len)) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr));
+			local_ip = sin.sin_addr.s_addr;
+		} else {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n");
+			local_ip = 0;
+		}
+	} else
+		local_ip = gg_dcc_ip;
+
+	gs->client_addr = local_ip;
+
+	if (GG_SESSION_IS_PROTOCOL_8_0(gs)) {
+		struct gg_login80 l80;
+		const char *version, *descr;
+		uint32_t version_len, descr_len;
+
+		memset(&l80, 0, sizeof(l80));
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() sending GG_LOGIN80 packet\n");
+		l80.uin = gg_fix32(gs->uin);
+		memcpy(l80.language, GG8_LANG, sizeof(l80.language));
+		l80.hash_type = gs->hash_type;
+		memcpy(l80.hash, hash_buf, sizeof(l80.hash));
+		l80.status = gg_fix32(gs->initial_status ? gs->initial_status : GG_STATUS_AVAIL);
+		l80.flags = gg_fix32(gs->status_flags);
+		l80.features = gg_fix32(gs->protocol_features);
+		l80.image_size = gs->image_size;
+		l80.dunno2 = 0x64;
+
+		version = (gs->client_version != NULL) ? gs->client_version : GG_DEFAULT_CLIENT_VERSION;
+		version_len = gg_fix32(strlen(GG8_VERSION) + strlen(version));
+
+		descr = (gs->initial_descr != NULL) ? gs->initial_descr : "";
+		descr_len = (gs->initial_descr != NULL) ? gg_fix32(strlen(gs->initial_descr)) : 0;
+
+		ret = gg_send_packet(gs,
+				GG_LOGIN80,
+				&l80, sizeof(l80),
+				&version_len, sizeof(version_len),
+				GG8_VERSION, strlen(GG8_VERSION),
+				version, strlen(version),
+				&descr_len, sizeof(descr_len),
+				descr, strlen(descr),
+				NULL);
+	} else {
+		struct gg_login70 l70;
+
+		memset(&l70, 0, sizeof(l70));
+		l70.uin = gg_fix32(gs->uin);
+		l70.hash_type = gs->hash_type;
+		memcpy(l70.hash, hash_buf, sizeof(l70.hash));
+		l70.status = gg_fix32(gs->initial_status ? gs->initial_status : GG_STATUS_AVAIL);
+		l70.version = gg_fix32(gs->protocol_version | gs->protocol_flags);
+		if (gs->external_addr && gs->external_port > 1023) {
+			l70.local_ip = gs->external_addr;
+			l70.local_port = gg_fix16(gs->external_port);
+		} else {
+			l70.local_ip = local_ip;
+			l70.local_port = gg_fix16(gg_dcc_port);
+		}
+
+		l70.image_size = gs->image_size;
+		l70.dunno2 = 0xbe;
+
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() sending GG_LOGIN70 packet\n");
+		ret = gg_send_packet(gs, GG_LOGIN70, &l70, sizeof(l70), gs->initial_descr, (gs->initial_descr) ? strlen(gs->initial_descr) : 0, NULL);
+	}
+
+	if (ret == -1) {
+		int errno_copy;
+
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno));
+		errno_copy = errno;
+		close(gs->fd);
+		errno = errno_copy;
+		gs->fd = -1;
+		ge->type = GG_EVENT_CONN_FAILED;
+		ge->event.failure = GG_FAILURE_WRITING;
+		gs->state = GG_STATE_IDLE;
+		return -1;
+	}
+
+	gs->state = GG_STATE_READING_REPLY;
+	gs->check = GG_CHECK_READ;
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_LOGIN_OK.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_login_ok(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n");
+	ge->type = GG_EVENT_CONN_SUCCESS;
+	gs->state = GG_STATE_CONNECTED;
+	gs->check = GG_CHECK_READ;
+	gs->timeout = -1;
+	gs->status = (gs->initial_status) ? gs->initial_status : GG_STATUS_AVAIL;
+#if 0
+	free(gs->status_descr);
+	gs->status_descr = gs->initial_descr;
+#else
+	free(gs->initial_descr);
+#endif
+	gs->initial_descr = NULL;
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_LOGIN_FAILED.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_login_failed(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	if (type != GG_DISCONNECTING)
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() login failed\n");
+	else
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n");
+	ge->type = GG_EVENT_CONN_FAILED;
+	ge->event.failure = (type != GG_DISCONNECTING) ? GG_FAILURE_PASSWORD : GG_FAILURE_INTRUDER;
+	gs->state = GG_STATE_IDLE;
+	close(gs->fd);
+	gs->fd = -1;
+	errno = EACCES;
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_SEND_MSG_ACK.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_send_msg_ack(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_send_msg_ack *s = (struct gg_send_msg_ack*) ptr;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n");
+
+	ge->type = GG_EVENT_ACK;
+	ge->event.ack.status = gg_fix32(s->status);
+	ge->event.ack.recipient = gg_fix32(s->recipient);
+	ge->event.ack.seq = gg_fix32(s->seq);
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_PONG.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_pong(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n");
+
+	ge->type = GG_EVENT_PONG;
+
+	gs->last_pong = time(NULL);
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_DISCONNECTING.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_disconnecting(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n");
+
+	ge->type = GG_EVENT_DISCONNECT;
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_DISCONNECT_ACK.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_disconnect_ack(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received logoff acknowledge\n");
+
+	ge->type = GG_EVENT_DISCONNECT_ACK;
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiety GG_XML_EVENT i GG_XML_ACTION.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_xml_event(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML event\n");
+
+	ge->type = GG_EVENT_XML_EVENT;
+	ge->event.xml_event.data = malloc(len + 1);
+
+	if (ge->event.xml_event.data == NULL) {
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+		return -1;
+	}
+
+	memcpy(ge->event.xml_event.data, ptr, len);
+	ge->event.xml_event.data[len] = 0;
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_PUBDIR50_REPLY.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_pubdir50_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n");
+
+	return gg_pubdir50_handle_reply_sess(gs, ge, ptr, len);
+}
+
+/**
+ * \internal Obsługuje pakiet GG_USERLIST_REPLY.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_userlist_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	char reply_type;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n");
+
+	reply_type = ptr[0];
+
+	/* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko
+	 * gdy otrzymano wszystkie odpowiedzi */
+	if (reply_type == GG_USERLIST_PUT_REPLY || reply_type == GG_USERLIST_PUT_MORE_REPLY) {
+		if (--gs->userlist_blocks)
+			return 0;
+
+		reply_type = GG_USERLIST_PUT_REPLY;
+	}
+
+	if (len > 1) {
+		unsigned int reply_len = (gs->userlist_reply != NULL) ? strlen(gs->userlist_reply) : 0;
+		char *tmp;
+
+		gg_debug_session(gs, GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", gs->userlist_reply, len);
+
+		tmp = realloc(gs->userlist_reply, reply_len + len);
+
+		if (tmp == NULL) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			return -1;
+		}
+
+		gs->userlist_reply = tmp;
+		memcpy(gs->userlist_reply + reply_len, ptr + 1, len - 1);
+		gs->userlist_reply[reply_len + len - 1] = 0;
+	}
+
+	if (reply_type == GG_USERLIST_GET_MORE_REPLY)
+		return 0;
+
+	ge->type = GG_EVENT_USERLIST;
+	ge->event.userlist.type = reply_type;
+	ge->event.userlist.reply = gs->userlist_reply;
+
+	gs->userlist_reply = NULL;
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_DCC7_ID_REPLY.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_dcc7_id_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 id packet\n");
+
+	return gg_dcc7_handle_id(gs, ge, ptr, len);
+}
+
+/**
+ * \internal Obsługuje pakiet GG_DCC7_ACCEPT.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_dcc7_accept(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 accept\n");
+
+	return gg_dcc7_handle_accept(gs, ge, ptr, len);
+}
+
+/**
+ * \internal Obsługuje pakiet GG_DCC7_NEW.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_dcc7_new(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 request\n");
+
+	return gg_dcc7_handle_new(gs, ge, ptr, len);
+}
+
+/**
+ * \internal Obsługuje pakiet GG_DCC7_REJECT.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_dcc7_reject(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 reject\n");
+
+	return gg_dcc7_handle_reject(gs, ge, ptr, len);
+}
+
+/**
+ * \internal Obsługuje pakiet GG_DCC7_INFO.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_dcc7_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 info\n");
+
+	return gg_dcc7_handle_info(gs, ge, ptr, len);
+}
+
+/**
+ * \internal Analizuje przychodzący pakiet z obrazkiem.
+ *
+ * \param e Struktura zdarzenia
+ * \param p Bufor z danymi
+ * \param len Długość bufora
+ * \param sess Struktura sesji
+ * \param sender Numer nadawcy
+ */
+static void gg_image_queue_parse(struct gg_event *e, const char *p, unsigned int len, struct gg_session *sess, uin_t sender)
+{
+	struct gg_msg_image_reply *i = (void*) p;
+	struct gg_image_queue *q, *qq;
+
+	if (!p || !sess || !e) {
+		errno = EFAULT;
+		return;
+	}
+
+	/* znajdź dany obrazek w kolejce danej sesji */
+
+	for (qq = sess->images, q = NULL; qq; qq = qq->next) {
+		if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) {
+			q = qq;
+			break;
+		}
+	}
+
+	if (!q) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32);
+		return;
+	}
+
+	if (p[0] == GG_MSG_OPTION_IMAGE_REPLY) {
+		q->done = 0;
+
+		len -= sizeof(struct gg_msg_image_reply);
+		p += sizeof(struct gg_msg_image_reply);
+
+		if (memchr(p, 0, len) == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender);
+			return;
+		}
+
+		if (!(q->filename = strdup(p))) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() out of memory\n");
+			return;
+		}
+
+		len -= strlen(p) + 1;
+		p += strlen(p) + 1;
+	} else {
+		len -= sizeof(struct gg_msg_image_reply);
+		p += sizeof(struct gg_msg_image_reply);
+	}
+
+	if (q->done + len > q->size)
+		len = q->size - q->done;
+
+	memcpy(q->image + q->done, p, len);
+	q->done += len;
+
+	/* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */
+
+	if (q->done >= q->size) {
+		e->type = GG_EVENT_IMAGE_REPLY;
+		e->event.image_reply.sender = sender;
+		e->event.image_reply.size = q->size;
+		e->event.image_reply.crc32 = q->crc32;
+		e->event.image_reply.filename = q->filename;
+		e->event.image_reply.image = q->image;
+
+		gg_image_queue_remove(sess, q, 0);
+
+		free(q);
+	}
+}
+
+/**
+ * \internal Analizuje informacje rozszerzone wiadomości.
+ *
+ * \param sess Struktura sesji.
+ * \param e Struktura zdarzenia.
+ * \param sender Numer nadawcy.
+ * \param p Wskaźnik na dane rozszerzone.
+ * \param packet_end Wskaźnik na koniec pakietu.
+ *
+ * \return 0 jeśli się powiodło, -1 jeśli wiadomość obsłużono i wynik ma
+ * zostać przekazany aplikacji, -2 jeśli wystąpił błąd ogólny, -3 jeśli
+ * wiadomość jest niepoprawna.
+ */
+static int gg_handle_recv_msg_options(struct gg_session *sess, struct gg_event *e, uin_t sender, const char *p, const char *packet_end)
+{
+	while (p < packet_end) {
+		switch (*p) {
+			case GG_MSG_OPTION_CONFERENCE:
+			{
+				struct gg_msg_recipients *m = (void*) p;
+				uint32_t i, count;
+
+				p += sizeof(*m);
+
+				if (p > packet_end) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (1)\n");
+					goto malformed;
+				}
+
+				count = gg_fix32(m->count);
+
+				if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (1.5)\n");
+					goto malformed;
+				}
+
+				if (e->event.msg.recipients != NULL) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() e->event.msg.recipients already exist\n");
+					goto malformed;
+				}
+
+				e->event.msg.recipients = malloc(count * sizeof(uin_t));
+
+				if (e->event.msg.recipients == NULL) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() not enough memory for recipients data\n");
+					goto fail;
+				}
+
+				memcpy(e->event.msg.recipients, p, count * sizeof(uin_t));
+				p += count * sizeof(uin_t);
+
+				for (i = 0; i < count; i++)
+					e->event.msg.recipients[i] = gg_fix32(e->event.msg.recipients[i]);
+
+				e->event.msg.recipients_count = count;
+
+				break;
+			}
+
+			case GG_MSG_OPTION_ATTRIBUTES:
+			{
+				uint16_t len;
+				char *buf;
+
+				if (p + 3 > packet_end) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (2)\n");
+					goto malformed;
+				}
+
+				memcpy(&len, p + 1, sizeof(uint16_t));
+				len = gg_fix16(len);
+
+				if (e->event.msg.formats != NULL) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() e->event.msg.formats already exist\n");
+					goto malformed;
+				}
+
+				buf = malloc(len);
+
+				if (buf == NULL) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() not enough memory for richtext data\n");
+					goto fail;
+				}
+
+				p += 3;
+
+				if (p + len > packet_end) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (3)\n");
+					free(buf);
+					goto malformed;
+				}
+
+				memcpy(buf, p, len);
+
+				e->event.msg.formats = buf;
+				e->event.msg.formats_length = len;
+
+				p += len;
+
+				break;
+			}
+
+			case GG_MSG_OPTION_IMAGE_REQUEST:
+			{
+				struct gg_msg_image_request *i = (void*) p;
+
+				if (p + sizeof(*i) > packet_end) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n");
+					goto malformed;
+				}
+
+				if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() mixed options (1)\n");
+					goto malformed;
+				}
+
+				e->event.image_request.sender = sender;
+				e->event.image_request.size = gg_fix32(i->size);
+				e->event.image_request.crc32 = gg_fix32(i->crc32);
+
+				e->type = GG_EVENT_IMAGE_REQUEST;
+
+				goto handled;
+			}
+
+			case GG_MSG_OPTION_IMAGE_REPLY:
+			case GG_MSG_OPTION_IMAGE_REPLY_MORE:
+			{
+				struct gg_msg_image_reply *rep = (void*) p;
+
+				if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) {
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() mixed options (2)\n");
+					goto malformed;
+				}
+
+				if (p + sizeof(struct gg_msg_image_reply) == packet_end) {
+
+					/* pusta odpowiedź - klient po drugiej stronie nie ma żądanego obrazka */
+
+					e->type = GG_EVENT_IMAGE_REPLY;
+					e->event.image_reply.sender = sender;
+					e->event.image_reply.size = 0;
+					e->event.image_reply.crc32 = gg_fix32(rep->crc32);
+					e->event.image_reply.filename = NULL;
+					e->event.image_reply.image = NULL;
+					goto handled;
+
+				} else if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) {
+
+					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n");
+					goto malformed;
+				}
+
+				rep->size = gg_fix32(rep->size);
+				rep->crc32 = gg_fix32(rep->crc32);
+				gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, sender);
+
+				goto handled;
+			}
+
+			default:
+			{
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p);
+				p = packet_end;
+			}
+		}
+	}
+
+	return 0;
+
+handled:
+	return -1;
+
+fail:
+	return -2;
+
+malformed:
+	return -3;
+}
+
+/**
+ * \internal Wysyła potwierdzenie odebrania wiadomości.
+ *
+ * \param gs Struktura sesji
+ *
+ * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd
+ */
+static int gg_session_send_msg_ack(struct gg_session *gs)
+{
+	struct gg_recv_msg_ack pkt;
+
+	gg_debug_session(gs, GG_DEBUG_FUNCTION, "** gg_session_send_msg_ack(%p);\n", gs);
+
+	if ((gs->protocol_features & GG_FEATURE_MSG_ACK) == 0)
+		return 0;
+
+	gs->recv_msg_count++;
+	pkt.count = gg_fix32(gs->recv_msg_count);
+
+	return gg_send_packet(gs, GG_RECV_MSG_ACK, &pkt, sizeof(pkt), NULL);
+}
+
+/**
+ * \internal Obsługuje pakiet GG_RECV_MSG.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_recv_msg(struct gg_session *sess, uint32_t type, const char *packet, size_t length, struct gg_event *e)
+{
+	const struct gg_recv_msg *r = (const struct gg_recv_msg*) packet;
+	const char *payload = packet + sizeof(struct gg_recv_msg);
+	const char *payload_end = packet + length;
+	char *tmp;
+
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %d, %p);\n", packet, length, e);
+
+	if ((r->seq == 0) && (r->msgclass == 0)) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n");
+		goto malformed;
+	}
+
+	// jednobajtowa wiadomość o treści \x02 to żądanie połączenia DCC
+	if (*payload == GG_MSG_CALLBACK && payload == payload_end - 1) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n");
+		length = 1;
+	} else {
+		const char *options;
+
+		options = memchr(payload, 0, (size_t) (payload_end - payload));
+
+		if (options == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n");
+			goto malformed;
+		}
+
+		length = (size_t) (options - payload);
+
+		switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), options + 1, payload_end)) {
+			case -1:	// handled
+				gg_session_send_msg_ack(sess);
+				return 0;
+
+			case -2:	// failed
+				goto fail;
+
+			case -3:	// malformed
+				goto malformed;
+		}
+	}
+
+	e->type = GG_EVENT_MSG;
+	e->event.msg.msgclass = gg_fix32(r->msgclass);
+	e->event.msg.sender = gg_fix32(r->sender);
+	e->event.msg.time = gg_fix32(r->time);
+	e->event.msg.seq = gg_fix32(r->seq);
+
+	tmp = gg_encoding_convert(payload, GG_ENCODING_CP1250, sess->encoding, length, -1);
+	if (tmp == NULL)
+		goto fail;
+	e->event.msg.message = (unsigned char*) tmp;
+
+	gg_session_send_msg_ack(sess);
+	return 0;
+
+fail:
+	free(e->event.msg.message);
+	free(e->event.msg.recipients);
+	free(e->event.msg.formats);
+	return -1;
+
+malformed:
+	e->type = GG_EVENT_NONE;
+	free(e->event.msg.message);
+	free(e->event.msg.xhtml_message);
+	free(e->event.msg.recipients);
+	free(e->event.msg.formats);
+	gg_session_send_msg_ack(sess);
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_RECV_MSG80.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_recv_msg_80(struct gg_session *sess, uint32_t type, const char *packet, size_t length, struct gg_event *e)
+{
+	const struct gg_recv_msg80 *r = (const struct gg_recv_msg80*) packet;
+	uint32_t offset_plain;
+	uint32_t offset_attr;
+
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg80(%p, %d, %p);\n", packet, length, e);
+
+	if (r->seq == 0 && r->msgclass == 0) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() oops, silently ignoring the bait\n");
+		goto malformed;
+	}
+
+	offset_plain = gg_fix32(r->offset_plain);
+	offset_attr = gg_fix32(r->offset_attr);
+
+	if (offset_plain < sizeof(struct gg_recv_msg80) || offset_plain >= length) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (0)\n");
+		goto malformed;
+	}
+
+	if (offset_attr < sizeof(struct gg_recv_msg80) || offset_attr > length) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, attr out of bounds (1)\n");
+		offset_attr = 0;	/* nie parsuj attr. */
+	}
+
+	/* Normalna sytuacja, więc nie podpada pod powyższy warunek. */
+	if (offset_attr == length)
+		offset_attr = 0;
+
+	if (memchr(packet + offset_plain, 0, length - offset_plain) == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (2)\n");
+		goto malformed;
+	}
+
+	if (offset_plain > sizeof(struct gg_recv_msg80) && memchr(packet + sizeof(struct gg_recv_msg80), 0, offset_plain - sizeof(struct gg_recv_msg80)) == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (3)\n");
+		goto malformed;
+	}
+
+	e->type = (type != GG_RECV_OWN_MSG) ? GG_EVENT_MSG : GG_EVENT_MULTILOGON_MSG;
+	e->event.msg.msgclass = gg_fix32(r->msgclass);
+	e->event.msg.sender = gg_fix32(r->sender);
+	e->event.msg.time = gg_fix32(r->time);
+	e->event.msg.seq = gg_fix32(r->seq);
+
+	if (offset_attr != 0) {
+		switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), packet + offset_attr, packet + length)) {
+			case -1:	// handled
+				gg_session_send_msg_ack(sess);
+				return 0;
+
+			case -2:	// failed
+				goto fail;
+
+			case -3:	// malformed
+				goto malformed;
+		}
+	}
+
+	if (sess->encoding == GG_ENCODING_CP1250) {
+		e->event.msg.message = (unsigned char*) strdup(packet + offset_plain);
+	} else {
+		if (offset_plain > sizeof(struct gg_recv_msg80)) {
+			int len;
+
+			len = gg_message_html_to_text(NULL, packet + sizeof(struct gg_recv_msg80));
+			e->event.msg.message = malloc(len + 1);
+
+			if (e->event.msg.message == NULL)
+				goto fail;
+
+			gg_message_html_to_text((char*) e->event.msg.message, packet + sizeof(struct gg_recv_msg80));
+		} else {
+			e->event.msg.message = (unsigned char*) gg_encoding_convert(packet + offset_plain, GG_ENCODING_CP1250, sess->encoding, -1, -1);
+		}
+	}
+
+	if (offset_plain > sizeof(struct gg_recv_msg80))
+		e->event.msg.xhtml_message = gg_encoding_convert(packet + sizeof(struct gg_recv_msg80), GG_ENCODING_UTF8, sess->encoding, -1, -1);
+	else
+		e->event.msg.xhtml_message = NULL;
+
+	gg_session_send_msg_ack(sess);
+	return 0;
+
+fail:
+	free(e->event.msg.message);
+	free(e->event.msg.xhtml_message);
+	free(e->event.msg.recipients);
+	free(e->event.msg.formats);
+	return -1;
+
+malformed:
+	e->type = GG_EVENT_NONE;
+	free(e->event.msg.message);
+	free(e->event.msg.xhtml_message);
+	free(e->event.msg.recipients);
+	free(e->event.msg.formats);
+	gg_session_send_msg_ack(sess);
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_STATUS.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_status(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_status *s = (void*) ptr;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
+
+	ge->type = GG_EVENT_STATUS;
+	ge->event.status.uin = gg_fix32(s->uin);
+	ge->event.status.status = gg_fix32(s->status);
+	ge->event.status.descr = NULL;
+
+	if (len > sizeof(*s)) {
+		ge->event.status.descr = gg_encoding_convert(ptr + sizeof(*s), GG_ENCODING_CP1250, gs->encoding, len - sizeof(*s), -1);
+
+		if (ge->event.status.descr == NULL) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiety GG_STATUS60, GG_STATUS77 i GG_STATUS80BETA.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_status_60_77_80beta(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_status60 *s60 = (void*) ptr;
+	struct gg_status77 *s77 = (void*) ptr;
+	size_t struct_len;
+	uint32_t uin;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
+
+	ge->type = GG_EVENT_STATUS60;
+	ge->event.status60.descr = NULL;
+	ge->event.status60.time = 0;
+
+	if (type == GG_STATUS60) {
+		uin = gg_fix32(s60->uin);
+		ge->event.status60.status = s60->status;
+		ge->event.status60.remote_ip = s60->remote_ip;
+		ge->event.status60.remote_port = gg_fix16(s60->remote_port);
+		ge->event.status60.version = s60->version;
+		ge->event.status60.image_size = s60->image_size;
+		struct_len = sizeof(*s60);
+	} else {
+		uin = gg_fix32(s77->uin);
+		ge->event.status60.status = s77->status;
+		ge->event.status60.remote_ip = s77->remote_ip;
+		ge->event.status60.remote_port = gg_fix16(s77->remote_port);
+		ge->event.status60.version = s77->version;
+		ge->event.status60.image_size = s77->image_size;
+		struct_len = sizeof(*s77);
+	}
+
+	ge->event.status60.uin = uin & 0x00ffffff;
+
+	if (uin & 0x40000000)
+		ge->event.status60.version |= GG_HAS_AUDIO_MASK;
+	if (uin & 0x20000000)
+		ge->event.status60.version |= GG_HAS_AUDIO7_MASK;
+	if (uin & 0x08000000)
+		ge->event.status60.version |= GG_ERA_OMNIX_MASK;
+
+	if (len > struct_len) {
+		size_t descr_len;
+
+		descr_len = len - struct_len;
+
+		ge->event.status60.descr = gg_encoding_convert(ptr + struct_len, (type == GG_STATUS80BETA) ? GG_ENCODING_UTF8 : GG_ENCODING_CP1250, gs->encoding, descr_len, -1);
+
+		if (ge->event.status60.descr == NULL) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			return -1;
+		}
+
+		if (descr_len > 4 && ptr[len - 5] == 0) {
+			uint32_t t;
+			memcpy(&t, ptr + len - 4, sizeof(uint32_t));
+			ge->event.status60.time = gg_fix32(t);
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_NOTIFY_REPLY.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_notify_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_notify_reply *n = (void*) ptr;
+	char *descr;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+	if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) {
+		size_t descr_len;
+
+		ge->type = GG_EVENT_NOTIFY_DESCR;
+
+		if (!(ge->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			return -1;
+		}
+		ge->event.notify_descr.notify[1].uin = 0;
+		memcpy(ge->event.notify_descr.notify, ptr, sizeof(*n));
+		ge->event.notify_descr.notify[0].uin = gg_fix32(ge->event.notify_descr.notify[0].uin);
+		ge->event.notify_descr.notify[0].status = gg_fix32(ge->event.notify_descr.notify[0].status);
+		ge->event.notify_descr.notify[0].remote_port = gg_fix16(ge->event.notify_descr.notify[0].remote_port);
+		ge->event.notify_descr.notify[0].version = gg_fix32(ge->event.notify_descr.notify[0].version);
+
+		descr_len = len - sizeof(*n);
+
+		descr = gg_encoding_convert(ptr + sizeof(*n), GG_ENCODING_CP1250, gs->encoding, descr_len, -1);
+
+		if (descr == NULL) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			return -1;
+		}
+
+		ge->event.notify_descr.descr = descr;
+
+	} else {
+		unsigned int i, count;
+
+		ge->type = GG_EVENT_NOTIFY;
+
+		if (!(ge->event.notify = (void*) malloc(len + 2 * sizeof(*n)))) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			return -1;
+		}
+
+		memcpy(ge->event.notify, ptr, len);
+		count = len / sizeof(*n);
+		ge->event.notify[count].uin = 0;
+
+		for (i = 0; i < count; i++) {
+			ge->event.notify[i].uin = gg_fix32(ge->event.notify[i].uin);
+			ge->event.notify[i].status = gg_fix32(ge->event.notify[i].status);
+			ge->event.notify[i].remote_port = gg_fix16(ge->event.notify[i].remote_port);
+			ge->event.notify[i].version = gg_fix32(ge->event.notify[i].version);
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_STATUS80.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_status_80(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_notify_reply80 *n = (void*) ptr;
+	size_t descr_len;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n");
+
+	ge->type = GG_EVENT_STATUS60;
+	ge->event.status60.uin = gg_fix32(n->uin);
+	ge->event.status60.status = gg_fix32(n->status);
+	ge->event.status60.remote_ip = n->remote_ip;
+	ge->event.status60.remote_port = gg_fix16(n->remote_port);
+	ge->event.status60.version = 0;
+	ge->event.status60.image_size = n->image_size;
+	ge->event.status60.descr = NULL;
+	ge->event.status60.time = 0;
+
+	descr_len = gg_fix32(n->descr_len);
+
+	if (descr_len != 0 && sizeof(struct gg_notify_reply80) + descr_len <= len) {
+		ge->event.status60.descr = gg_encoding_convert((char*) n + sizeof(struct gg_notify_reply80), GG_ENCODING_UTF8, gs->encoding, descr_len, -1);
+
+		if (ge->event.status60.descr == NULL) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			return -1;
+		}
+
+		/* XXX czas */
+	}
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_NOTIFY_REPLY80.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_notify_reply_80(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_notify_reply80 *n = (void*) ptr;
+	unsigned int length = len, i = 0;
+
+	// TODO: najpierw przeanalizować strukturę i określić
+	// liczbę rekordów, żeby obyć się bez realloc()
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+	ge->type = GG_EVENT_NOTIFY60;
+	ge->event.notify60 = malloc(sizeof(*ge->event.notify60));
+
+	if (!ge->event.notify60) {
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+		return -1;
+	}
+
+	ge->event.notify60[0].uin = 0;
+
+	while (length >= sizeof(struct gg_notify_reply80)) {
+		uin_t uin = gg_fix32(n->uin);
+		int descr_len;
+		char *tmp;
+
+		ge->event.notify60[i].uin = uin;
+		ge->event.notify60[i].status = gg_fix32(n->status);
+		ge->event.notify60[i].remote_ip = n->remote_ip;
+		ge->event.notify60[i].remote_port = gg_fix16(n->remote_port);
+		ge->event.notify60[i].version = 0;
+		ge->event.notify60[i].image_size = n->image_size;
+		ge->event.notify60[i].descr = NULL;
+		ge->event.notify60[i].time = 0;
+
+		descr_len = gg_fix32(n->descr_len);
+
+		if (descr_len != 0) {
+			if (sizeof(struct gg_notify_reply80) + descr_len <= length) {
+				ge->event.notify60[i].descr = gg_encoding_convert((char*) n + sizeof(struct gg_notify_reply80), GG_ENCODING_UTF8, gs->encoding, descr_len, -1);
+
+				if (ge->event.notify60[i].descr == NULL) {
+					gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+					return -1;
+				}
+
+				/* XXX czas */
+
+				length -= sizeof(struct gg_notify_reply80) + descr_len;
+				n = (void*) ((char*) n + sizeof(struct gg_notify_reply80) + descr_len);
+			} else {
+				length = 0;
+			}
+
+		} else {
+			length -= sizeof(struct gg_notify_reply80);
+			n = (void*) ((char*) n + sizeof(struct gg_notify_reply80));
+		}
+
+		if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			free(ge->event.notify60);
+			return -1;
+		}
+
+		ge->event.notify60 = (void*) tmp;
+		ge->event.notify60[++i].uin = 0;
+	}
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiety GG_NOTIFY_REPLY77 i GG_NOTIFY_REPLY80BETA.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_notify_reply_77_80beta(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_notify_reply77 *n = (void*) ptr;
+	unsigned int length = len, i = 0;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+	ge->type = GG_EVENT_NOTIFY60;
+	ge->event.notify60 = malloc(sizeof(*ge->event.notify60));
+
+	if (ge->event.notify60 == NULL) {
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+		return -1;
+	}
+
+	ge->event.notify60[0].uin = 0;
+
+	while (length >= sizeof(struct gg_notify_reply77)) {
+		uin_t uin = gg_fix32(n->uin);
+		char *tmp;
+
+		ge->event.notify60[i].uin = uin & 0x00ffffff;
+		ge->event.notify60[i].status = n->status;
+		ge->event.notify60[i].remote_ip = n->remote_ip;
+		ge->event.notify60[i].remote_port = gg_fix16(n->remote_port);
+		ge->event.notify60[i].version = n->version;
+		ge->event.notify60[i].image_size = n->image_size;
+		ge->event.notify60[i].descr = NULL;
+		ge->event.notify60[i].time = 0;
+
+		if (uin & 0x40000000)
+			ge->event.notify60[i].version |= GG_HAS_AUDIO_MASK;
+		if (uin & 0x20000000)
+			ge->event.notify60[i].version |= GG_HAS_AUDIO7_MASK;
+		if (uin & 0x08000000)
+			ge->event.notify60[i].version |= GG_ERA_OMNIX_MASK;
+
+		if (GG_S_D(n->status)) {
+			unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply77));
+
+			if (sizeof(struct gg_notify_reply77) + descr_len <= length) {
+				ge->event.notify60[i].descr = gg_encoding_convert((char*) n + sizeof(struct gg_notify_reply77) + 1, (type == GG_NOTIFY_REPLY80BETA) ? GG_ENCODING_UTF8 : GG_ENCODING_CP1250, gs->encoding, descr_len, -1);
+
+				if (ge->event.notify60[i].descr == NULL) {
+					gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+					return -1;
+				}
+
+				/* XXX czas */
+
+				length -= sizeof(struct gg_notify_reply77) + descr_len + 1;
+				n = (void*) ((char*) n + sizeof(struct gg_notify_reply77) + descr_len + 1);
+			} else {
+				length = 0;
+			}
+
+		} else {
+			length -= sizeof(struct gg_notify_reply77);
+			n = (void*) ((char*) n + sizeof(struct gg_notify_reply77));
+		}
+
+		if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			free(ge->event.notify60);
+			return -1;
+		}
+
+		ge->event.notify60 = (void*) tmp;
+		ge->event.notify60[++i].uin = 0;
+	}
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_NOTIFY_REPLY60.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_notify_reply_60(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_notify_reply60 *n = (void*) ptr;
+	unsigned int length = len, i = 0;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n");
+
+	ge->type = GG_EVENT_NOTIFY60;
+	ge->event.notify60 = malloc(sizeof(*ge->event.notify60));
+
+	if (ge->event.notify60 == NULL) {
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+		return -1;
+	}
+
+	ge->event.notify60[0].uin = 0;
+
+	while (length >= sizeof(struct gg_notify_reply60)) {
+		uin_t uin = gg_fix32(n->uin);
+		char *tmp;
+
+		ge->event.notify60[i].uin = uin & 0x00ffffff;
+		ge->event.notify60[i].status = n->status;
+		ge->event.notify60[i].remote_ip = n->remote_ip;
+		ge->event.notify60[i].remote_port = gg_fix16(n->remote_port);
+		ge->event.notify60[i].version = n->version;
+		ge->event.notify60[i].image_size = n->image_size;
+		ge->event.notify60[i].descr = NULL;
+		ge->event.notify60[i].time = 0;
+
+		if (uin & 0x40000000)
+			ge->event.notify60[i].version |= GG_HAS_AUDIO_MASK;
+		if (uin & 0x08000000)
+			ge->event.notify60[i].version |= GG_ERA_OMNIX_MASK;
+
+		if (GG_S_D(n->status)) {
+			unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60));
+
+			if (sizeof(struct gg_notify_reply60) + descr_len <= length) {
+				char *descr;
+
+				descr = gg_encoding_convert((char*) n + sizeof(struct gg_notify_reply60) + 1, GG_ENCODING_CP1250, gs->encoding, descr_len, -1);
+
+				if (descr == NULL) {
+					gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+					return -1;
+				}
+
+				ge->event.notify60[i].descr = descr;
+
+				/* XXX czas */
+
+				length -= sizeof(struct gg_notify_reply60) + descr_len + 1;
+				n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1);
+			} else {
+				length = 0;
+			}
+
+		} else {
+			length -= sizeof(struct gg_notify_reply60);
+			n = (void*) ((char*) n + sizeof(struct gg_notify_reply60));
+		}
+
+		if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n");
+			free(ge->event.notify60);
+			return -1;
+		}
+
+		ge->event.notify60 = (void*) tmp;
+		ge->event.notify60[++i].uin = 0;
+	}
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_USER_DATA.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_user_data(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_user_data d;
+	char *p = (char*) ptr;
+	char *packet_end = (char*) ptr + len;
+	struct gg_event_user_data_user *users;
+	int i, j;
+	int res = 0;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received user data\n");
+
+	ge->event.user_data.user_count = 0;
+	ge->event.user_data.users = NULL;
+
+	if (ptr + sizeof(d) > packet_end)
+		goto malformed;
+
+	memcpy(&d, p, sizeof(d));
+	p += sizeof(d);
+
+	d.type = gg_fix32(d.type);
+	d.user_count = gg_fix32(d.user_count);
+
+	if (d.user_count > 0xffff) {
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (1)\n");
+		goto malformed;
+	}
+
+	if (d.user_count > 0) {
+		users = calloc(d.user_count, sizeof(struct gg_event_user_data_user));
+
+		if (users == NULL) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory (%d*%d)\n", d.user_count, sizeof(struct gg_event_user_data_user));
+			goto fail;
+		}
+	} else {
+		users = NULL;
+	}
+
+	ge->type = GG_EVENT_USER_DATA;
+	ge->event.user_data.type = d.type;
+	ge->event.user_data.user_count = d.user_count;
+	ge->event.user_data.users = users;
+
+	gg_debug_session(gs, GG_DEBUG_DUMP, "type=%d, count=%d\n", d.type, d.user_count);
+
+	for (i = 0; i < d.user_count; i++) {
+		struct gg_user_data_user u;
+		struct gg_event_user_data_attr *attrs;
+
+		if (p + sizeof(u) > packet_end) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (2)\n");
+			goto malformed;
+		}
+
+		memcpy(&u, p, sizeof(u));
+		p += sizeof(u);
+
+		u.uin = gg_fix32(u.uin);
+		u.attr_count = gg_fix32(u.attr_count);
+
+		if (u.attr_count > 0xffff) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (2)\n");
+			goto malformed;
+		}
+
+		if (u.attr_count > 0) {
+			attrs = calloc(u.attr_count, sizeof(struct gg_event_user_data_attr));
+
+			if (attrs == NULL) {
+				gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory (%d*%d)\n", u.attr_count, sizeof(struct gg_event_user_data_attr));
+				goto fail;
+			}
+		} else {
+			attrs = NULL;
+		}
+
+		users[i].uin = u.uin;
+		users[i].attr_count = u.attr_count;
+		users[i].attrs = attrs;
+
+		gg_debug_session(gs, GG_DEBUG_DUMP, "    uin=%d, count=%d\n", u.uin, u.attr_count);
+
+		for (j = 0; j < u.attr_count; j++) {
+			uint32_t key_size;
+			uint32_t attr_type;
+			uint32_t value_size;
+			char *key;
+			char *value;
+
+			if (p + sizeof(key_size) > packet_end) {
+				gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (3)\n");
+				goto malformed;
+			}
+
+			memcpy(&key_size, p, sizeof(key_size));
+			p += sizeof(key_size);
+
+			key_size = gg_fix32(key_size);
+
+			if (key_size > 0xffff || p + key_size > packet_end) {
+				gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (3)\n");
+				goto malformed;
+			}
+
+			key = malloc(key_size + 1);
+
+			if (key == NULL) {
+				gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory (%d)\n", key_size + 1);
+				goto fail;
+			}
+
+			memcpy(key, p, key_size);
+			p += key_size;
+
+			key[key_size] = 0;
+
+			attrs[j].key = key;
+
+			if (p + sizeof(attr_type) + sizeof(value_size) > packet_end) {
+				gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (4)\n");
+				goto malformed;
+			}
+
+			memcpy(&attr_type, p, sizeof(attr_type));
+			p += sizeof(attr_type);
+			memcpy(&value_size, p, sizeof(value_size));
+			p += sizeof(value_size);
+
+			attrs[j].type = gg_fix32(attr_type);
+			value_size = gg_fix32(value_size);
+
+			if (value_size > 0xffff || p + value_size > packet_end) {
+				gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (5)\n");
+				goto malformed;
+			}
+
+			value = malloc(value_size + 1);
+
+			if (value == NULL) {
+				gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory (%d)\n", value_size + 1);
+				goto fail;
+			}
+
+			memcpy(value, p, value_size);
+			p += value_size;
+
+			value[value_size] = 0;
+
+			attrs[j].value = value;
+
+			gg_debug_session(gs, GG_DEBUG_DUMP, "        key=\"%s\", type=%d, value=\"%s\"\n", key, attr_type, value);
+		}
+	}
+
+	return 0;
+
+fail:
+	res = -1;
+
+malformed:
+	ge->type = GG_EVENT_NONE;
+
+	for (i = 0; i < ge->event.user_data.user_count; i++) {
+		for (j = 0; j < ge->event.user_data.users[i].attr_count; j++) {
+			free(ge->event.user_data.users[i].attrs[j].key);
+			free(ge->event.user_data.users[i].attrs[j].value);
+		}
+
+		free(ge->event.user_data.users[i].attrs);
+	}
+
+	free(ge->event.user_data.users);
+
+	return res;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_TYPING_NOTIFICATION.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_typing_notification(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	struct gg_typing_notification *n = (void*) ptr;
+	uin_t uin;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received typing notification\n");
+
+	memcpy(&uin, &n->uin, sizeof(uin_t));
+
+	ge->type = GG_EVENT_TYPING_NOTIFICATION;
+	ge->event.typing_notification.uin = gg_fix32(uin);
+	ge->event.typing_notification.length = gg_fix16(n->length);
+
+	return 0;
+}
+
+/**
+ * \internal Obsługuje pakiet GG_MULTILOGON_INFO.
+ *
+ * Patrz gg_packet_handler_t
+ */
+static int gg_session_handle_multilogon_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	char *packet_end = (char*) ptr + len;
+	struct gg_multilogon_info *info = (struct gg_multilogon_info*) ptr;
+	char *p = (char*) ptr + sizeof(*info);
+	struct gg_multilogon_session *sessions = NULL;
+	size_t count;
+	size_t i;
+	int res = 0;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received multilogon info\n");
+
+	count = gg_fix32(info->count);
+
+	if (count > 0xffff) {
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (1)\n");
+		goto malformed;
+	}
+
+	sessions = calloc(count, sizeof(struct gg_multilogon_session));
+
+	if (sessions == NULL) {
+		gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of memory (%d*%d)\n", count, sizeof(struct gg_multilogon_session));
+		return -1;
+	}
+
+	ge->type = GG_EVENT_MULTILOGON_INFO;
+	ge->event.multilogon_info.count = count;
+	ge->event.multilogon_info.sessions = sessions;
+
+	for (i = 0; i < count; i++) {
+		struct gg_multilogon_info_item item;
+		size_t name_size;
+
+		if (p + sizeof(item) > packet_end) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (2)\n");
+			goto malformed;
+		}
+
+		memcpy(&item, p, sizeof(item));
+
+		sessions[i].id = item.conn_id;
+		sessions[i].remote_addr = item.addr;
+		sessions[i].status_flags = gg_fix32(item.flags);
+		sessions[i].protocol_features = gg_fix32(item.features);
+		sessions[i].logon_time = gg_fix32(item.logon_time);
+
+		p += sizeof(item);
+
+		name_size = gg_fix32(item.name_size);
+
+		if (name_size > 0xffff || p + name_size > packet_end) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (3)\n");
+			goto malformed;
+		}
+
+		sessions[i].name = malloc(name_size + 1);
+
+		if (sessions[i].name == NULL) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of memory (%d)\n", name_size);
+			goto fail;
+		}
+
+		memcpy(sessions[i].name, p, name_size);
+		sessions[i].name[name_size] = 0;
+
+		p += name_size;
+	}
+
+	return 0;
+
+fail:
+	res = -1;
+
+malformed:
+	ge->type = GG_EVENT_NONE;
+
+	for (i = 0; i < ge->event.multilogon_info.count; i++)
+		free(ge->event.multilogon_info.sessions[i].name);
+
+	free(ge->event.multilogon_info.sessions);
+
+	return res;
+}
+
+/**
+ * \internal Tablica obsługiwanych pakietów
+ */
+static const gg_packet_handler_t handlers[] =
+{
+	{ GG_WELCOME, GG_STATE_READING_KEY, 0, gg_session_handle_welcome },
+	{ GG_LOGIN_OK, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok },
+	{ GG_LOGIN80_OK, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok },
+	{ GG_NEED_EMAIL, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok },
+	{ GG_LOGIN_FAILED, GG_STATE_READING_REPLY, 0, gg_session_handle_login_failed },
+	{ GG_LOGIN80_FAILED, GG_STATE_READING_REPLY, 0, gg_session_handle_login_failed },
+	{ GG_SEND_MSG_ACK, GG_STATE_CONNECTED, sizeof(struct gg_send_msg_ack), gg_session_handle_send_msg_ack },
+	{ GG_PONG, GG_STATE_CONNECTED, 0, gg_session_handle_pong },
+	{ GG_DISCONNECTING, GG_STATE_CONNECTED, 0, gg_session_handle_disconnecting },
+	{ GG_DISCONNECT_ACK, GG_STATE_DISCONNECTING, 0, gg_session_handle_disconnect_ack },
+	{ GG_XML_EVENT, GG_STATE_CONNECTED, 0, gg_session_handle_xml_event },
+	{ GG_PUBDIR50_REPLY, GG_STATE_CONNECTED, 0, gg_session_handle_pubdir50_reply },
+	{ GG_USERLIST_REPLY, GG_STATE_CONNECTED, 0, gg_session_handle_userlist_reply },
+	{ GG_DCC7_ID_REPLY, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_id_reply), gg_session_handle_dcc7_id_reply },
+	{ GG_DCC7_ACCEPT, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_accept), gg_session_handle_dcc7_accept },
+	{ GG_DCC7_NEW, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_new), gg_session_handle_dcc7_new },
+	{ GG_DCC7_REJECT, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_reject), gg_session_handle_dcc7_reject },
+	{ GG_DCC7_INFO, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_info), gg_session_handle_dcc7_info },
+	{ GG_RECV_MSG, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg), gg_session_handle_recv_msg },
+	{ GG_RECV_MSG80, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg80), gg_session_handle_recv_msg_80 },
+	{ GG_STATUS, GG_STATE_CONNECTED, sizeof(struct gg_status), gg_session_handle_status },
+	{ GG_STATUS60, GG_STATE_CONNECTED, sizeof(struct gg_status60), gg_session_handle_status_60_77_80beta },
+	{ GG_STATUS77, GG_STATE_CONNECTED, sizeof(struct gg_status77), gg_session_handle_status_60_77_80beta },
+	{ GG_STATUS80BETA, GG_STATE_CONNECTED, sizeof(struct gg_status77), gg_session_handle_status_60_77_80beta },
+	{ GG_STATUS80, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply80), gg_session_handle_status_80 },
+	{ GG_NOTIFY_REPLY, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply), gg_session_handle_notify_reply },
+	{ GG_NOTIFY_REPLY60, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply60), gg_session_handle_notify_reply_60 },
+	{ GG_NOTIFY_REPLY77, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply77), gg_session_handle_notify_reply_77_80beta },
+	{ GG_NOTIFY_REPLY80BETA, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply77), gg_session_handle_notify_reply_77_80beta },
+	{ GG_NOTIFY_REPLY80, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply80), gg_session_handle_notify_reply_80 },
+	{ GG_USER_DATA, GG_STATE_CONNECTED, sizeof(struct gg_user_data), gg_session_handle_user_data },
+	{ GG_TYPING_NOTIFICATION, GG_STATE_CONNECTED, sizeof(struct gg_typing_notification), gg_session_handle_typing_notification },
+	{ GG_MULTILOGON_INFO, GG_STATE_CONNECTED, sizeof(struct gg_multilogon_info), gg_session_handle_multilogon_info },
+	{ GG_XML_ACTION, GG_STATE_CONNECTED, 0, gg_session_handle_xml_event },
+	{ GG_RECV_OWN_MSG, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg80), gg_session_handle_recv_msg_80 },
+};
+
+/**
+ * \internal Analizuje przychodzący pakiet danych.
+ *
+ * \param gs Struktura sesji
+ * \param type Typ pakietu
+ * \param ptr Wskaźnik do bufora pakietu
+ * \param len Długość bufora pakietu
+ * \param[out] ge Struktura zdarzenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_session_handle_packet(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge)
+{
+	int i;
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet(%d, %p, %d)\n", type, ptr, len);
+
+	gs->last_event = time(NULL);
+
+#if 0
+	if ((gs->flags & (1 << GG_SESSION_FLAG_RAW_PACKET)) != 0) {
+		char *tmp;
+
+		tmp = malloc(len);
+
+		if (tmp == NULL) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet() out of memory (%d bytes)\n", len);
+			return -1;
+		}
+
+		memcpy(tmp, ptr, len);
+
+		ge->type = GG_EVENT_RAW_PACKET;
+		ge->event.raw_packet.type = type;
+		ge->event.raw_packet.length = len;
+		ge->event.raw_packet.data = tmp;
+
+		return 0;
+	}
+#endif
+
+	for (i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) {
+		if (handlers[i].type != 0 && handlers[i].type != type)
+			continue;
+
+		if (handlers[i].state != 0 && handlers[i].state != gs->state) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet() packet 0x%02x unexpected in state %d\n", type, gs->state);
+			continue;
+		}
+
+		if (len < handlers[i].min_length) {
+			gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet() packet 0x%02x too short (%d bytes)\n", type, len);
+			continue;
+		}
+
+		return (*handlers[i].handler)(gs, type, ptr, len, ge);
+	}
+
+	gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet() unhandled packet 0x%02x, len %d, state %d\n", type, len, gs->state);
+
+	return 0;
+}
--- a/libpurple/protocols/gg/lib/http.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/http.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,4 +1,4 @@
-/* $Id: http.c 833 2009-10-01 20:48:01Z wojtekka $ */
+/* $Id: http.c 1036 2010-12-15 00:02:28Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -34,7 +34,6 @@
 #  include <arpa/inet.h>
 #endif
 
-#include "compat.h"
 #include "resolver.h"
 
 #include <ctype.h>
@@ -136,21 +135,26 @@
 		h->check = GG_CHECK_READ;
 		h->timeout = GG_DEFAULT_TIMEOUT;
 	} else {
-		struct in_addr addr;
+		struct in_addr *addr_list = NULL;
+		int addr_count;
 
-		if (gg_gethostbyname_real(hostname, &addr, 0) == -1) {
+		if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 0) == -1 || addr_count == 0) {
 			gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n");
 			gg_http_free(h);
+			free(addr_list);
 			errno = ENOENT;
 			return NULL;
 		}
 
-		if ((h->fd = gg_connect(&addr, port, 0)) == -1) {
+		if ((h->fd = gg_connect(&addr_list[0], port, 0)) == -1) {
 			gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno));
 			gg_http_free(h);
+			free(addr_list);
 			return NULL;
 		}
 
+		free(addr_list);
+
 		h->state = GG_STATE_CONNECTING;
 
 		while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) {
@@ -257,7 +261,7 @@
 	}
 
 	if (h->state == GG_STATE_SENDING_QUERY) {
-		ssize_t res;
+		size_t res;
 
 		if ((res = write(h->fd, h->query, strlen(h->query))) < 1) {
 			gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/libgadu-debug.h	Fri Mar 25 00:06:47 2011 +0000
@@ -0,0 +1,27 @@
+/*
+ *  (C) Copyright 2009 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  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.
+ */
+
+#ifndef LIBGADU_DEBUG_H
+#define LIBGADU_DEBUG_H
+
+#include "libgadu.h"
+
+const char *gg_debug_state(enum gg_state_t state);
+void gg_debug_dump(struct gg_session *sess, int level, const char *buf, size_t len);
+
+#endif /* LIBGADU_DEBUG_H */
--- a/libpurple/protocols/gg/lib/libgadu-internal.h	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/libgadu-internal.h	Fri Mar 25 00:06:47 2011 +0000
@@ -23,9 +23,22 @@
 
 #include "libgadu.h"
 
-char *gg_cp_to_utf8(const char *b);
-char *gg_utf8_to_cp(const char *b);
+struct gg_dcc7_relay {
+	uint32_t addr;
+	uint16_t port;
+	uint8_t family;
+};
+
+typedef struct gg_dcc7_relay gg_dcc7_relay_t;
+
 int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length);
-void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap);
+
+int gg_resolve(int *fd, int *pid, const char *hostname);
+int gg_resolve_pthread(int *fd, void **resolver, const char *hostname);
+void gg_resolve_pthread_cleanup(void *resolver, int kill);
+
+#ifdef HAVE_UINT64_T
+uint64_t gg_fix64(uint64_t x);
+#endif
 
 #endif /* LIBGADU_INTERNAL_H */
--- a/libpurple/protocols/gg/lib/libgadu.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,7 +1,7 @@
-/* $Id: libgadu.c 878 2009-11-16 23:48:19Z wojtekka $ */
+/* $Id: libgadu.c 1062 2011-03-13 18:10:24Z wojtekka $ */
 
 /*
- *  (C) Copyright 2001-2009 Wojtek Kaniewski <wojtekka@irc.pl>
+ *  (C) Copyright 2001-2010 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>
@@ -31,6 +31,7 @@
 #include "libgadu.h"
 #include "libgadu-config.h"
 #include "libgadu-internal.h"
+#include "libgadu-debug.h"
 
 #include <sys/types.h>
 
@@ -51,6 +52,8 @@
 #include "compat.h"
 #include "protocol.h"
 #include "resolver.h"
+#include "encoding.h"
+#include "session.h"
 
 #ifndef _WIN32
 #  include <errno.h> /* on Win32 this is included above */
@@ -64,51 +67,15 @@
 #include <signal.h>
 #include <time.h>
 #include <unistd.h>
+#ifdef GG_CONFIG_HAVE_GNUTLS
+#  include <gnutls/gnutls.h>
+#endif
 #ifdef GG_CONFIG_HAVE_OPENSSL
 #  include <openssl/err.h>
 #  include <openssl/rand.h>
 #endif
 
-#define GG_LIBGADU_VERSION "1.9.0"
-
-/**
- * Poziom rejestracji informacji odpluskwiających. Zmienna jest maską bitową
- * składającą się ze stałych \c GG_DEBUG_...
- *
- * \ingroup debug
- */
-int gg_debug_level = 0;
-
-/**
- * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno
- * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe
- * \c NULL, informacje są wysyłane do standardowego wyjścia błędu (\c stderr).
- *
- * \param level Poziom rejestracji
- * \param format Format wiadomości (zgodny z \c printf)
- * \param ap Lista argumentów (zgodna z \c printf)
- *
- * \note Funkcja jest przesłaniana przez \c gg_debug_handler_session.
- *
- * \ingroup debug
- */
-void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL;
-
-/**
- * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno
- * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe
- * \c NULL, informacje są wysyłane do standardowego wyjścia błędu.
- *
- * \param sess Sesja której dotyczy informacja lub \c NULL
- * \param level Poziom rejestracji
- * \param format Format wiadomości (zgodny z \c printf)
- * \param ap Lista argumentów (zgodna z \c printf)
- *
- * \note Funkcja przesłania przez \c gg_debug_handler_session.
- *
- * \ingroup debug
- */
-void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap) = NULL;
+#define GG_LIBGADU_VERSION "1.10.1"
 
 /**
  * Port gniazda nasłuchującego dla połączeń bezpośrednich.
@@ -180,7 +147,7 @@
 #ifdef __GNUC__
 __attribute__ ((unused))
 #endif
-= "$Id: libgadu.c 923 2010-03-09 20:03:29Z wojtekka $";
+= "$Id: libgadu.c 1062 2011-03-13 18:10:24Z wojtekka $";
 #endif
 
 #endif /* DOXYGEN */
@@ -197,6 +164,37 @@
 	return GG_LIBGADU_VERSION;
 }
 
+#ifdef GG_CONFIG_HAVE_UINT64_T
+/**
+ * \internal Zamienia kolejność bajtów w 64-bitowym słowie.
+ *
+ * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach
+ * big-endianowych odwraca kolejność bajtów w słowie.
+ *
+ * \param x Liczba do zamiany
+ *
+ * \return Liczba z odpowiednią kolejnością bajtów
+ *
+ * \ingroup helper
+ */
+uint64_t gg_fix64(uint64_t x)
+{
+#ifndef GG_CONFIG_BIGENDIAN
+	return x;
+#else
+	return (uint64_t)
+		(((x & (uint64_t) 0x00000000000000ffULL) << 56) |
+		((x & (uint64_t) 0x000000000000ff00ULL) << 40) |
+		((x & (uint64_t) 0x0000000000ff0000ULL) << 24) |
+		((x & (uint64_t) 0x00000000ff000000ULL) << 8) |
+		((x & (uint64_t) 0x000000ff00000000ULL) >> 8) |
+		((x & (uint64_t) 0x0000ff0000000000ULL) >> 24) |
+		((x & (uint64_t) 0x00ff000000000000ULL) >> 40) |
+		((x & (uint64_t) 0xff00000000000000ULL) >> 56));
+#endif
+}
+#endif /* GG_CONFIG_HAVE_UINT64_T */
+
 /**
  * \internal Zamienia kolejność bajtów w 32-bitowym słowie.
  *
@@ -280,7 +278,9 @@
 /**
  * \internal Odbiera od serwera dane binarne.
  *
- * Funkcja odbiera dane od serwera zajmując się TLS w razie konieczności.
+ * Funkcja odbiera dane od serwera zajmując się SSL/TLS w razie konieczności.
+ * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi
+ * wywołaniami systemowymi.
  *
  * \param sess Struktura sesji
  * \param buf Bufor na danymi
@@ -290,32 +290,135 @@
  */
 int gg_read(struct gg_session *sess, char *buf, int length)
 {
-	int res;
+#ifdef GG_CONFIG_HAVE_GNUTLS
+	if (sess->ssl != NULL) {
+		for (;;) {
+			int res;
+
+			res = gnutls_record_recv(GG_SESSION_GNUTLS(sess), buf, length);
+
+			if (res < 0) {
+				if (!gnutls_error_is_fatal(res) || res == GNUTLS_E_INTERRUPTED)
+					continue;
+
+				if (res == GNUTLS_E_AGAIN)
+					errno = EAGAIN;
+				else
+					errno = EINVAL;
+
+				return -1;
+			}
+
+			return res;
+		}
+	}
+#endif
 
 #ifdef GG_CONFIG_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;
+	if (sess->ssl != NULL) {
+		for (;;) {
+			int res, err;
+
+			res = SSL_read(sess->ssl, buf, length);
+
+			if (res < 0) {
+				err = SSL_get_error(sess->ssl, res);
+
+				if (err == SSL_ERROR_SYSCALL && errno == EINTR)
+					continue;
+
+				if (err == SSL_ERROR_WANT_READ)
+					errno = EAGAIN;
+				else if (err != SSL_ERROR_SYSCALL)
+					errno = EINVAL;
+
+				return -1;
+			}
+
+			return res;
 		}
-	} else
+	}
 #endif
-		res = read(sess->fd, buf, length);
-
-	return res;
+
+	return read(sess->fd, buf, length);
 }
 
 /**
  * \internal Wysyła do serwera dane binarne.
  *
+ * Funkcja wysyła dane do serwera zajmując się SSL/TLS w razie konieczności.
+ * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi
+ * wywołaniami systemowymi.
+ *
+ * \note Funkcja nie zajmuje się buforowaniem wysyłanych danych (patrz
+ * gg_write()).
+ *
+ * \param sess Struktura sesji
+ * \param buf Bufor z danymi
+ * \param length Długość bufora
+ *
+ * \return To samo co funkcja systemowa \c write
+ */
+static int gg_write_common(struct gg_session *sess, const char *buf, int length)
+{
+#ifdef GG_CONFIG_HAVE_GNUTLS
+	if (sess->ssl != NULL) {
+		for (;;) {
+			int res;
+
+			res = gnutls_record_send(GG_SESSION_GNUTLS(sess), buf, length);
+
+			if (res < 0) {
+				if (!gnutls_error_is_fatal(res) || res == GNUTLS_E_INTERRUPTED)
+					continue;
+
+				if (res == GNUTLS_E_AGAIN)
+					errno = EAGAIN;
+				else
+					errno = EINVAL;
+
+				return -1;
+			}
+
+			return res;
+		}
+	}
+#endif
+
+#ifdef GG_CONFIG_HAVE_OPENSSL
+	if (sess->ssl != NULL) {
+		for (;;) {
+			int res, err;
+
+			res = SSL_write(sess->ssl, buf, length);
+
+			if (res < 0) {
+				err = SSL_get_error(sess->ssl, res);
+
+				if (err == SSL_ERROR_SYSCALL && errno == EINTR)
+					continue;
+
+				if (err == SSL_ERROR_WANT_WRITE)
+					errno = EAGAIN;
+				else if (err != SSL_ERROR_SYSCALL)
+					errno = EINVAL;
+
+				return -1;
+			}
+
+			return res;
+		}
+	}
+#endif
+
+	return write(sess->fd, buf, length);
+}
+
+
+
+/**
+ * \internal Wysyła do serwera dane binarne.
+ *
  * Funkcja wysyła dane do serwera zajmując się TLS w razie konieczności.
  *
  * \param sess Struktura sesji
@@ -328,66 +431,41 @@
 {
 	int res = 0;
 
-#ifdef GG_CONFIG_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;
+	if (!sess->async) {
+		int written = 0;
+
+		while (written < length) {
+			res = gg_write_common(sess, buf + written, length - written);
+
+			if (res == -1)
+				return -1;
+
+			written += res;
+			res = written;
 		}
-	} else
-#endif
-	{
-		if (!sess->async) {
-			int written = 0;
-
-			while (written < length) {
-				res = write(sess->fd, buf + written, length - written);
-
-				if (res == -1) {
-					if (errno != EINTR)
-						break;
-
-					continue;
-				}
-
-				written += res;
-				res = written;
+	} else {
+		res = 0;
+
+		if (sess->send_buf == NULL) {
+			res = gg_write_common(sess, buf, length);
+
+			if (res == -1)
+				return -1;
+		}
+
+		if (res < length) {
+			char *tmp;
+
+			if (!(tmp = realloc(sess->send_buf, sess->send_left + length - res))) {
+				errno = ENOMEM;
+				return -1;
 			}
-		} else {
-			if (!sess->send_buf)
-				res = write(sess->fd, buf, length);
-			else
-				res = 0;
-
-			if (res == -1) {
-				if (errno != EAGAIN)
-					return res;
-
-				res = 0;
-			}
-
-			if (res < length) {
-				char *tmp;
-
-				if (!(tmp = realloc(sess->send_buf, sess->send_left + length - res))) {
-					errno = ENOMEM;
-					return -1;
-				}
-
-				sess->send_buf = tmp;
-
-				memcpy(sess->send_buf + sess->send_left, buf + res, length - res);
-
-				sess->send_left += length - res;
-			}
+
+			sess->send_buf = tmp;
+
+			memcpy(sess->send_buf + sess->send_left, buf + res, length - res);
+
+			sess->send_left += length - res;
 		}
 	}
 
@@ -443,11 +521,6 @@
 			}
 
 			if (ret == -1) {
-				if (errno == EINTR) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() interrupted system call, resuming\n");
-					continue;
-				}
-
 				if (errno == EAGAIN) {
 					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n");
 
@@ -513,10 +586,7 @@
 			offset += ret;
 			size -= ret;
 		} else if (ret == -1) {
-			int errno2 = errno;
-
 			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno));
-			errno = errno2;
 
 			if (errno == EAGAIN) {
 				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size);
@@ -525,23 +595,16 @@
 				sess->recv_done = offset;
 				return NULL;
 			}
-			if (errno != EINTR) {
-				free(buf);
-				return NULL;
-			}
+
+			free(buf);
+			return NULL;
 		}
 	}
 
 	sess->recv_left = 0;
 
-	if ((gg_debug_level & GG_DEBUG_DUMP)) {
-		unsigned int i;
-
-		gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_recv_packet(%.2x)", h.type);
-		for (i = 0; i < sizeof(h) + h.length; i++)
-			gg_debug_session(sess, GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]);
-		gg_debug_session(sess, GG_DEBUG_DUMP, "\n");
-	}
+	gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_recv_packet(type=0x%.2x, length=%d)\n", h.type, h.length);
+	gg_debug_dump(sess, GG_DEBUG_DUMP, buf, sizeof(h) + h.length);
 
 	return buf;
 }
@@ -609,14 +672,8 @@
 	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_session(sess, GG_DEBUG_DUMP, "// gg_send_packet(0x%.2x)", gg_fix32(h->type));
-		for (i = 0; i < tmp_length; ++i)
-			gg_debug_session(sess, GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]);
-		gg_debug_session(sess, GG_DEBUG_DUMP, "\n");
-	}
+	gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_send_packet(type=0x%.2x, length=%d)\n", gg_fix32(h->type), gg_fix32(h->length));
+	gg_debug_dump(sess, GG_DEBUG_DUMP, tmp, tmp_length);
 
 	res = gg_write(sess, tmp, tmp_length);
 
@@ -739,14 +796,22 @@
 	sess->server_addr = p->server_addr;
 	sess->external_port = p->external_port;
 	sess->external_addr = p->external_addr;
-
-	sess->protocol_features = (p->protocol_features & ~(GG_FEATURE_STATUS77 | GG_FEATURE_MSG77));
-
-	if (!(p->protocol_features & GG_FEATURE_STATUS77))
-		sess->protocol_features |= GG_FEATURE_STATUS80;
-
-	if (!(p->protocol_features & GG_FEATURE_MSG77))
-		sess->protocol_features |= GG_FEATURE_MSG80;
+	sess->client_port = p->client_port;
+
+	if (p->protocol_features == 0) {
+		sess->protocol_features = GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION;
+	} else {
+		sess->protocol_features = (p->protocol_features & ~(GG_FEATURE_STATUS77 | GG_FEATURE_MSG77));
+
+		if (!(p->protocol_features & GG_FEATURE_STATUS77))
+			sess->protocol_features |= GG_FEATURE_STATUS80;
+
+		if (!(p->protocol_features & GG_FEATURE_MSG77))
+			sess->protocol_features |= GG_FEATURE_MSG80;
+	}
+
+	if (!(sess->status_flags = p->status_flags))
+		sess->status_flags = GG_STATUS_FLAG_UNKNOWN | GG_STATUS_FLAG_SPAM;
 
 	sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION;
 
@@ -774,8 +839,8 @@
 		else
 			max_length = GG_STATUS_DESCR_MAXSIZE_PRE_8_0;
 
-		if (sess->protocol_version >= 0x2d && p->encoding != GG_ENCODING_UTF8)
-			sess->initial_descr = gg_cp_to_utf8(p->status_descr);
+		if (sess->protocol_version >= 0x2d)
+			sess->initial_descr = gg_encoding_convert(p->status_descr, p->encoding, GG_ENCODING_UTF8, -1, -1);
 		else
 			sess->initial_descr = strdup(p->status_descr);
 
@@ -791,7 +856,25 @@
 	}
 
 	if (p->tls == 1) {
-#ifdef GG_CONFIG_HAVE_OPENSSL
+#ifdef GG_CONFIG_HAVE_GNUTLS
+		gg_session_gnutls_t *tmp;
+
+		tmp = malloc(sizeof(gg_session_gnutls_t));
+
+		if (tmp == NULL) {
+			gg_debug(GG_DEBUG_MISC, "// gg_login() out of memory for GnuTLS session\n");
+			goto fail;
+		}
+
+		sess->ssl = tmp;
+
+		gnutls_global_init();
+		gnutls_certificate_allocate_credentials(&tmp->xcred);
+		gnutls_init(&tmp->session, GNUTLS_CLIENT);
+		gnutls_priority_set_direct(tmp->session, "NORMAL:-VERS-TLS", NULL);
+//		gnutls_priority_set_direct(tmp->session, "NONE:+VERS-SSL3.0:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL", NULL);
+		gnutls_credentials_set(tmp->session, GNUTLS_CRD_CERTIFICATE, tmp->xcred);
+#elif defined(GG_CONFIG_HAVE_OPENSSL)
 		char buf[1024];
 
 		OpenSSL_add_ssl_algorithms();
@@ -810,7 +893,7 @@
 			RAND_seed((void *) &rstruct, sizeof(rstruct));
 		}
 
-		sess->ssl_ctx = SSL_CTX_new(TLSv1_client_method());
+		sess->ssl_ctx = SSL_CTX_new(SSLv3_client_method());
 
 		if (!sess->ssl_ctx) {
 			ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
@@ -850,10 +933,18 @@
 
 		if (!sess->server_addr) {
 			if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) {
-				if (gg_gethostbyname_real(hostname, &addr, 0) == -1) {
+				struct in_addr *addr_list = NULL;
+				int addr_count;
+
+				if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 0) == -1 || addr_count == 0) {
 					gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname);
+					free(addr_list);
 					goto fail;
 				}
+
+				addr = addr_list[0];
+
+				free(addr_list);
 			}
 		} else {
 			addr.s_addr = sess->server_addr;
@@ -976,7 +1067,10 @@
  *
  * \note Jeśli w buforze nadawczym połączenia z serwerem znajdują się jeszcze
  * dane (np. z powodu strat pakietów na łączu), prawdopodobnie zostaną one
- * utracone przy zrywaniu połączenia.
+ * utracone przy zrywaniu połączenia. Aby mieć pewność, że opis statusu
+ * zostanie zachowany, należy ustawić stan \c GG_STATUS_NOT_AVAIL_DESCR
+ * za pomocą funkcji \c gg_change_status_descr() i poczekać na zdarzenie
+ * \c GG_EVENT_DISCONNECT_ACK.
  *
  * \param sess Struktura sesji
  *
@@ -989,11 +1083,13 @@
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess);
 
-	if (GG_S_NA(sess->status))
-		gg_change_status(sess, GG_STATUS_NOT_AVAIL);
+#ifdef GG_CONFIG_HAVE_GNUTLS
+	if (sess->ssl != NULL)
+		gnutls_bye(GG_SESSION_GNUTLS(sess), GNUTLS_SHUT_RDWR);
+#endif
 
 #ifdef GG_CONFIG_HAVE_OPENSSL
-	if (sess->ssl)
+	if (sess->ssl != NULL)
 		SSL_shutdown(sess->ssl);
 #endif
 
@@ -1005,6 +1101,18 @@
 		sess->fd = -1;
 	}
 
+#ifdef GG_CONFIG_HAVE_GNUTLS
+	if (sess->ssl != NULL) {
+		gg_session_gnutls_t *tmp;
+
+		tmp = (gg_session_gnutls_t*) sess->ssl;
+		gnutls_deinit(tmp->session);
+		gnutls_certificate_free_credentials(tmp->xcred);
+		gnutls_global_deinit();
+		free(sess->ssl);
+	}
+#endif
+
 	if (sess->send_buf) {
 		free(sess->send_buf);
 		sess->send_buf = NULL;
@@ -1103,7 +1211,7 @@
 
 	if (sess->protocol_version >= 0x2d) {
 		if (descr != NULL && sess->encoding != GG_ENCODING_UTF8) {
-			new_descr = gg_cp_to_utf8(descr);
+			new_descr = gg_encoding_convert(descr, GG_ENCODING_CP1250, GG_ENCODING_UTF8, -1, -1);
 
 			if (!new_descr)
 				return -1;
@@ -1140,7 +1248,7 @@
 		struct gg_new_status80 p;
 
 		p.status		= gg_fix32(status);
-		p.flags			= gg_fix32(0x00800001);
+		p.flags			= gg_fix32(sess->status_flags);
 		p.description_size	= gg_fix32(descr_len);
 		res = gg_send_packet(sess,
 				packet_type,
@@ -1168,6 +1276,10 @@
 	}
 
 	free(new_descr);
+
+	if (GG_S_NA(status))
+		sess->state = GG_STATE_DISCONNECTING;
+
 	return res;
 }
 
@@ -1228,6 +1340,33 @@
 }
 
 /**
+ * Funkcja zmieniająca flagi statusu.
+ *
+ * \param sess Struktura sesji
+ * \param flags Nowe flagi statusu
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \note Aby zmiany weszły w życie, należy ponownie ustawić status za pomocą
+ * funkcji z rodziny \c gg_change_status().
+ *
+ * \ingroup status
+ */
+int gg_change_status_flags(struct gg_session *sess, int flags)
+{
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_flags(%p, 0x%08x);\n", sess, flags);
+
+	if (sess == NULL) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	sess->status_flags = flags;
+
+	return 0;
+}
+
+/**
  * Wysyła wiadomość do użytkownika.
  *
  * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
@@ -1377,7 +1516,7 @@
 
 			format_idx += 3;
 
-			if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0) {
+			if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0 || (attr == 0 && old_attr != 0)) {
 				if (char_pos != 0) {
 					if ((old_attr & GG_FONT_UNDERLINE) != 0)
 						gg_append(dst, &len, "</u>", 4);
@@ -1388,7 +1527,8 @@
 					if ((old_attr & GG_FONT_BOLD) != 0)
 						gg_append(dst, &len, "</b>", 4);
 
-					gg_append(dst, &len, "</span>", 7);
+					if (src[i] != 0)
+						gg_append(dst, &len, "</span>", 7);
 				}
 
 				if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) {
@@ -1398,9 +1538,11 @@
 					color = (const unsigned char*) "\x00\x00\x00";
 				}
 
-				if (dst != NULL)
-					sprintf(&dst[len], span_fmt, color[0], color[1], color[2]);
-				len += span_len;
+				if (src[i] != 0) {
+					if (dst != NULL)
+						sprintf(&dst[len], span_fmt, color[0], color[1], color[2]);
+					len += span_len;
+				}
 			} else if (char_pos == 0 && src[0] != 0) {
 				if (dst != NULL)
 					sprintf(&dst[len], span_fmt, 0, 0, 0);
@@ -1543,13 +1685,13 @@
 	}
 
 	if (sess->encoding == GG_ENCODING_UTF8) {
-		if (!(cp_msg = gg_utf8_to_cp((const char *) message)))
+		if (!(cp_msg = gg_encoding_convert((const char *) message, GG_ENCODING_UTF8, GG_ENCODING_CP1250, -1, -1)))
 			return -1;
 
 		utf_msg = (char*) message;
 	} else {
 		if (sess->protocol_version >= 0x2d) {
-			if (!(utf_msg = gg_cp_to_utf8((const char *) message)))
+			if (!(utf_msg = gg_encoding_convert((const char *) message, GG_ENCODING_CP1250, GG_ENCODING_UTF8, -1, -1)))
 				return -1;
 		}
 
@@ -2192,6 +2334,47 @@
 	return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL);
 }
 
+/**
+ * Informuje rozmówcę o pisaniu wiadomości.
+ *
+ * \param sess Struktura sesji
+ * \param recipient Numer adresata
+ * \param length Długość wiadomości lub 0 jeśli jest pusta
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup messages
+ */
+int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length){
+	struct gg_typing_notification pkt;
+	uin_t uin;
+
+	pkt.length = gg_fix16(length);
+	uin = gg_fix32(recipient);
+	memcpy(&pkt.uin, &uin, sizeof(uin_t));
+
+	return gg_send_packet(sess, GG_TYPING_NOTIFICATION, &pkt, sizeof(pkt), NULL);
+}
+
+/**
+ * Rozłącza inną sesję multilogowania.
+ *
+ * \param gs Struktura sesji
+ * \param conn_id Sesja do rozłączenia
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup login
+ */
+int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id)
+{
+	struct gg_multilogon_disconnect pkt;
+
+	pkt.conn_id = conn_id;
+
+	return gg_send_packet(gs, GG_MULTILOGON_DISCONNECT, &pkt, sizeof(pkt), NULL);
+}
+
 /* @} */
 
 /*
--- a/libpurple/protocols/gg/lib/libgadu.h	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.h	Fri Mar 25 00:06:47 2011 +0000
@@ -1,4 +1,4 @@
-/* $Id: libgadu.h.in 878 2009-11-16 23:48:19Z wojtekka $ */
+/* $Id: libgadu.h.in 1037 2010-12-17 22:18:08Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2009 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -71,7 +71,10 @@
 /* Defined if this machine supports long long. */
 #undef GG_CONFIG_HAVE_LONG_LONG
 
-/* Defined if libgadu was compiled and linked with TLS support. */
+/* Defined if libgadu was compiled and linked with GnuTLS support. */
+#undef GG_CONFIG_HAVE_GNUTLS
+
+/* Defined if libgadu was compiled and linked with OpenSSL support. */
 #undef GG_CONFIG_HAVE_OPENSSL
 
 /* Defined if uintX_t types are defined in <stdint.h>. */
@@ -148,6 +151,13 @@
 } gg_dcc7_id_t;
 
 /**
+ * Identyfikator sesji multilogowania.
+ */
+typedef struct {
+	uint8_t id[8];
+} gg_multilogon_id_t;
+
+/**
  * Makro deklarujące pola wspólne dla struktur sesji.
  */
 #define gg_common_head(x) \
@@ -173,6 +183,8 @@
 
 struct gg_dcc7;
 
+struct gg_dcc7_relay;
+
 /**
  * Sposób rozwiązywania nazw serwerów.
  */
@@ -280,6 +292,8 @@
 	void (*resolver_cleanup)(void **private_data, int force);	/**< Funkcja zwalniająca zasoby po rozwiązaniu nazwy */
 
 	int protocol_features;	/**< Opcje protokołu */
+	int status_flags;	/**< Flagi statusu */
+	int recv_msg_count;	/**< Liczba odebranych wiadomości */
 };
 
 /**
@@ -387,7 +401,8 @@
 
 #define GG_DCC7_HASH_LEN	20	/**< Maksymalny rozmiar skrótu pliku w połączeniach bezpośrenich */
 #define GG_DCC7_FILENAME_LEN	255	/**< Maksymalny rozmiar nazwy pliku w połączeniach bezpośrednich */
-#define GG_DCC7_INFO_LEN	64	/**< Maksymalny rozmiar informacji o połączeniach bezpośrednich */
+#define GG_DCC7_INFO_LEN	32	/**< Maksymalny rozmiar informacji o połączeniach bezpośrednich */
+#define GG_DCC7_INFO_HASH_LEN	32	/**< Maksymalny rozmiar skrótu ip informacji o połączeniach bezpośrednich */
 
 /**
  * Połączenie bezpośrednie od wersji Gadu-Gadu 7.x.
@@ -429,6 +444,13 @@
 
 	int soft_timeout;	/**< Flaga mówiąca, że po przekroczeniu \c timeout należy wywołać \c gg_dcc7_watch_fd() */
 	int seek;		/**< Flaga mówiąca, że można zmieniać położenie w wysyłanym pliku */
+
+	void *resolver;		/**< Dane prywatne procesu lub wątku rozwiązującego nazwę serwera */
+
+	int relay;		/**< Flaga mówiąca, że laczymy sie przez serwer */
+	int relay_index;	/**< Numer serwera pośredniczącego, do którego się łączymy */
+	int relay_count;	/**< Rozmiar listy serwerów pośredniczących */
+	struct gg_dcc7_relay *relay_list;	/**< Lista serwerów pośredniczących */
 };
 
 /**
@@ -524,7 +546,14 @@
 	GG_STATE_WAITING_FOR_INFO,	/**< Oczekiwanie na informacje o połączeniu bezpośrednim */
 
 	GG_STATE_READING_ID,		/**< Odebranie identyfikatora połączenia bezpośredniego */
-	GG_STATE_SENDING_ID		/**< Wysłano identyfikatora połączenia bezpośredniego */
+	GG_STATE_SENDING_ID,		/**< Wysłano identyfikator połączenia bezpośredniego */
+	GG_STATE_RESOLVING_GG,		/**< Oczekiwanie na rozwiązanie nazwy serwera Gadu-Gadu */
+
+	GG_STATE_RESOLVING_RELAY,	/**< Oczekiwanie na rozwiązanie nazwy serwera pośredniczącego */
+	GG_STATE_CONNECTING_RELAY,	/**< Oczekiwanie na połączenie z serwerem pośredniczącym */
+	GG_STATE_READING_RELAY,		/**< Odbieranie danych */
+
+	GG_STATE_DISCONNECTING,		/**< Oczekiwanie na potwierdzenie rozłączenia */
 };
 
 /**
@@ -576,9 +605,10 @@
 	gg_encoding_t encoding;		/**< Rodzaj kodowania używanego w sesji (domyślnie CP1250) */
 	gg_resolver_t resolver;		/**< Sposób rozwiązywania nazw (patrz \ref build-resolver) */
 	int protocol_features;		/**< Opcje protokołu (flagi GG_FEATURE_*). */
+	int status_flags;		/**< Flagi statusu (flagi GG_STATUS_FLAG_*, patrz \ref status). */
 
 #ifndef DOXYGEN
-	char dummy[2 * sizeof(int)];	/**< \internal Miejsce na kilka kolejnych
+	char dummy[1 * sizeof(int)];	/**< \internal Miejsce na kilka kolejnych
 					  parametrów, żeby wraz z dodawaniem kolejnych
 					  parametrów nie zmieniał się rozmiar struktury */
 #endif
@@ -591,6 +621,7 @@
 int gg_change_status(struct gg_session *sess, int status);
 int gg_change_status_descr(struct gg_session *sess, int status, const char *descr);
 int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time);
+int gg_change_status_flags(struct gg_session *sess, int flags);
 int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message);
 int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen);
 int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message);
@@ -600,6 +631,7 @@
 int gg_userlist_request(struct gg_session *sess, char type, const char *request);
 int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32);
 int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size);
+int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length);
 
 uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len);
 
@@ -615,6 +647,8 @@
 gg_resolver_t gg_global_get_resolver(void);
 int gg_global_set_custom_resolver(int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int));
 
+int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id);
+
 /**
  * Rodzaj zdarzenia.
  *
@@ -623,7 +657,7 @@
 enum gg_event_t {
 	GG_EVENT_NONE = 0,		/**< Nie wydarzyło się nic wartego uwagi */
 	GG_EVENT_MSG,			/**< \brief Otrzymano wiadomość. Przekazuje również wiadomości systemowe od numeru 0. */
-	GG_EVENT_NOTIFY,		/**< \brief Informacja o statusach osób z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */
+	GG_EVENT_NOTIFY,		/**< \brief Informacja o statusach osób z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. Ostatni element tablicy zawiera uin równy 0, a pozostałe pola są niezainicjowane. */
 	GG_EVENT_NOTIFY_DESCR,		/**< \brief Informacja o statusie opisowym osoby z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */
 	GG_EVENT_STATUS,		/**< \brief Zmiana statusu osoby z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */
 	GG_EVENT_ACK,			/**< Potwierdzenie doręczenia wiadomości */
@@ -647,9 +681,9 @@
 	GG_EVENT_PUBDIR50_WRITE,	/**< Zmieniono własne dane w katalogu publicznym */
 
 	GG_EVENT_STATUS60,		/**< Zmiana statusu osoby z listy kontaktów */
-	GG_EVENT_NOTIFY60,		/**< Informacja o statusach osób z listy kontaktów */
+	GG_EVENT_NOTIFY60,		/**< Informacja o statusach osób z listy kontaktów. Ostatni element tablicy zawiera uin równy 0, a pozostałe pola są niezainicjowane.  */
 	GG_EVENT_USERLIST,		/**< Wynik importu lub eksportu listy kontaktów */
-	GG_EVENT_IMAGE_REQUEST,		/**< Żądanie przesłania obrazka z wiadommości */
+	GG_EVENT_IMAGE_REQUEST,		/**< Żądanie przesłania obrazka z wiadomości */
 	GG_EVENT_IMAGE_REPLY,		/**< Przysłano obrazek z wiadomości */
 	GG_EVENT_DCC_ACK,		/**< Potwierdzenie transmisji w połączeniu bezpośrednim (6.x) */
 
@@ -663,6 +697,10 @@
 
 	GG_EVENT_XML_EVENT,		/**< Otrzymano komunikat systemowy (7.7) */
 	GG_EVENT_DISCONNECT_ACK,	/**< \brief Potwierdzenie zakończenia sesji. Informuje o tym, że zmiana stanu na niedostępny z opisem dotarła do serwera i można zakończyć połączenie TCP. */
+	GG_EVENT_TYPING_NOTIFICATION,	/**< Powiadomienie o pisaniu */
+	GG_EVENT_USER_DATA,		/**< Informacja o kontaktach */
+	GG_EVENT_MULTILOGON_MSG,	/**< Wiadomość wysłana z innej sesji multilogowania */
+	GG_EVENT_MULTILOGON_INFO,	/**< Informacja o innych sesjach multilogowania */
 };
 
 #define GG_EVENT_SEARCH50_REPLY GG_EVENT_PUBDIR50_SEARCH_REPLY
@@ -707,7 +745,8 @@
 	GG_ERROR_DCC7_FILE,		/**< Błąd odczytu/zapisu pliku */
 	GG_ERROR_DCC7_EOF,		/**< Przedwczesny koniec pliku */
 	GG_ERROR_DCC7_NET,		/**< Błąd wysyłania/odbierania */
-	GG_ERROR_DCC7_REFUSED 		/**< Połączenie odrzucone */
+	GG_ERROR_DCC7_REFUSED, 		/**< Połączenie odrzucone */
+	GG_ERROR_DCC7_RELAY,		/**< Problem z serwerem pośredniczącym */
 };
 
 /**
@@ -742,10 +781,10 @@
 typedef struct gg_pubdir50_s *gg_pubdir50_t;
 
 /**
- * Opis zdarzenia \c GG_EVENT_MSG.
+ * Opis zdarzeń \c GG_EVENT_MSG i \c GG_EVENT_MULTILOGON_MSG.
  */
 struct gg_event_msg {
-	uin_t sender;		/**< Numer nadawcy */
+	uin_t sender;		/**< Numer nadawcy/odbiorcy */
 	int msgclass;		/**< Klasa wiadomości */
 	time_t time;		/**< Czas nadania */
 	unsigned char *message;	/**< Treść wiadomości */
@@ -795,7 +834,7 @@
  * Opis zdarzenia \c GG_EVENT_NOTIFY_REPLY60.
  */
 struct gg_event_notify60 {
-	uin_t uin;		/**< Numer Gadu-Gadu */
+	uin_t uin;		/**< Numer Gadu-Gadu. W ostatnim elemencie jest równy 0, a pozostałe pola są niezainicjowane. */
 	int status;		/**< Nowy status */
 	uint32_t remote_ip;	/**< Adres IP dla połączeń bezpośrednich */
 	uint16_t remote_port;	/**< Port dla połączeń bezpośrednich */
@@ -862,7 +901,6 @@
  */
 struct gg_event_dcc7_connected {
 	struct gg_dcc7 *dcc7;	/**< Struktura połączenia */
-	// XXX czy coś się przyda?
 };
 
 /**
@@ -891,6 +929,68 @@
 };
 
 /**
+ * Opis zdarzenia \c GG_EVENT_DCC7_DONE.
+ */
+struct gg_event_dcc7_done {
+	struct gg_dcc7 *dcc7;	/**< Struktura połączenia */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_TYPING_NOTIFICATION.
+ */
+struct gg_event_typing_notification {
+	uin_t uin;		/**< Numer rozmówcy */
+	int length;		/**< Długość tekstu */
+};
+
+/**
+ * Atrybut użytkownika.
+ */
+struct gg_event_user_data_attr {
+	int type;		/**< Typ atrybutu */
+	char *key;		/**< Klucz */
+	char *value;		/**< Wartość */
+};
+
+/**
+ * Struktura opisująca kontakt w zdarzeniu GG_EVENT_USER_DATA.
+ */
+struct gg_event_user_data_user {
+	uin_t uin;		/**< Numer kontaktu */
+	size_t attr_count;	/**< Liczba atrybutów */
+	struct gg_event_user_data_attr *attrs;	/**< Lista atrybutów */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_USER_DATA.
+ */
+struct gg_event_user_data {
+	int type;		/**< Rodzaj informacji o kontaktach */
+	size_t user_count;	/**< Liczba kontaktów */
+	struct gg_event_user_data_user *users;	/**< Lista kontaktów */
+};
+
+/**
+ * Struktura opisująca sesję multilogowania.
+ */
+struct gg_multilogon_session {
+	gg_multilogon_id_t id;		/**< Identyfikator sesji */
+	char *name;			/**< Nazwa sesji (podana w \c gg_login_params.client_version) */
+	uint32_t remote_addr;		/**< Adres sesji */
+	int status_flags;		/**< Flagi statusu sesji */
+	int protocol_features;		/**< Opcje protokolu sesji */
+	time_t logon_time;		/**< Czas zalogowania */
+};
+
+/**
+ * Opis zdarzenia \c GG_EVENT_MULTILOGON_INFO.
+ */
+struct gg_event_multilogon_info {
+	int count;		/**< Liczba sesji */
+	struct gg_multilogon_session *sessions;	/** Lista sesji */
+};
+
+/**
  * Unia wszystkich zdarzeń zwracanych przez funkcje \c gg_watch_fd(),
  * \c gg_dcc_watch_fd() i \c gg_dcc7_watch_fd().
  *
@@ -919,6 +1019,11 @@
 	struct gg_event_dcc7_pending dcc7_pending;	/**< Trwa próba połączenia bezpośredniego (\c GG_EVENT_DCC7_PENDING) */
 	struct gg_event_dcc7_reject dcc7_reject;	/**< Odrzucono połączenia bezpośredniego (\c GG_EVENT_DCC7_REJECT) */
 	struct gg_event_dcc7_accept dcc7_accept;	/**< Zaakceptowano połączenie bezpośrednie (\c GG_EVENT_DCC7_ACCEPT) */
+	struct gg_event_dcc7_done dcc7_done;	/**< Zakończono połączenie bezpośrednie (\c GG_EVENT_DCC7_DONE) */
+	struct gg_event_typing_notification typing_notification;	/**< Powiadomienie o pisaniu */
+	struct gg_event_user_data user_data;	/**< Informacje o kontaktach */
+	struct gg_event_msg multilogon_msg;	/**< Inna sesja wysłała wiadomość (\c GG_EVENT_MULTILOGON_MSG) */
+	struct gg_event_multilogon_info multilogon_info;	/**< Informacja o innych sesjach multilogowania (\c GG_EVENT_MULTILOGON_INFO) */
 };
 
 /**
@@ -1318,8 +1423,8 @@
 int gg_send_packet(struct gg_session *sess, int type, ...) GG_DEPRECATED;
 unsigned int gg_login_hash(const unsigned char *password, unsigned int seed) GG_DEPRECATED;
 void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result) GG_DEPRECATED;
-uint32_t gg_fix32(uint32_t x) GG_DEPRECATED;
-uint16_t gg_fix16(uint16_t x) GG_DEPRECATED;
+uint32_t gg_fix32(uint32_t x) GG_DEPRECATED;;
+uint16_t gg_fix16(uint16_t x) GG_DEPRECATED;;
 #define fix16 gg_fix16
 #define fix32 gg_fix32
 char *gg_proxy_auth(void) GG_DEPRECATED;
@@ -1341,11 +1446,11 @@
 	struct gg_image_queue *next;	/**< Kolejny element listy */
 } GG_DEPRECATED;
 
-int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
-int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
-int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
-int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
-int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED;
+int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED;
 
 #define GG_APPMSG_HOST "appmsg.gadu-gadu.pl"
 #define GG_APPMSG_PORT 80
@@ -1355,12 +1460,14 @@
 #define GG_REGISTER_PORT 80
 #define GG_REMIND_HOST "retr.gadu-gadu.pl"
 #define GG_REMIND_PORT 80
+#define GG_RELAY_HOST "relay.gadu-gadu.pl"
+#define GG_RELAY_PORT 80
 
 #define GG_DEFAULT_PORT 8074
 #define GG_HTTPS_PORT 443
 #define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)"
 
-#define GG_DEFAULT_CLIENT_VERSION "8.0.0.7669"
+#define GG_DEFAULT_CLIENT_VERSION "10.1.0.11070"
 #define GG_DEFAULT_PROTOCOL_VERSION 0x2e
 #define GG_DEFAULT_TIMEOUT 30
 #define GG_HAS_AUDIO_MASK 0x40000000
@@ -1370,16 +1477,28 @@
 
 #ifndef DOXYGEN
 
-#define GG_FEATURE_MSG77		0x01
-#define GG_FEATURE_STATUS77		0x02
-#define GG_FEATURE_DND_FFC		0x10
-#define GG_FEATURE_IMAGE_DESCR		0x20
+#define GG_FEATURE_MSG77		0x0001
+#define GG_FEATURE_STATUS77		0x0002
+#define GG_FEATURE_UNKNOWN_4		0x0004
+#define GG_FEATURE_UNKNOWN_8		0x0008
+#define GG_FEATURE_DND_FFC		0x0010
+#define GG_FEATURE_IMAGE_DESCR		0x0020
+#define GG_FEATURE_UNKNOWN_40		0x0040
+#define GG_FEATURE_UNKNOWN_80		0x0080
+#define GG_FEATURE_UNKNOWN_100		0x0100
+#define GG_FEATURE_USER_DATA		0x0200
+#define GG_FEATURE_MSG_ACK		0x0400
+#define GG_FEATURE_UNKNOWN_800		0x0800
+#define GG_FEATURE_UNKNOWN_1000		0x1000
+#define GG_FEATURE_TYPING_NOTIFICATION	0x2000
+#define GG_FEATURE_MULTILOGON		0x4000
 
 /* Poniższe makra zostały zachowane dla zgodności API */
 #define GG_FEATURE_MSG80		0
 #define GG_FEATURE_STATUS80		0
 #define GG_FEATURE_STATUS80BETA		0
-#define GG_FEATURE_ALL			(GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR)
+
+#define GG_FEATURE_ALL			(GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION)
 
 #else
 
@@ -1509,6 +1628,11 @@
 #define GG_STATUS_DESCR_MASK 0x4000
 #define GG_STATUS_FRIENDS_MASK 0x8000
 
+#define GG_STATUS_FLAG_UNKNOWN 0x00000001
+#define GG_STATUS_FLAG_VIDEO 0x00000002
+#define GG_STATUS_FLAG_MOBILE 0x00100000
+#define GG_STATUS_FLAG_SPAM 0x00800000
+
 #else
 
 /**
@@ -1535,6 +1659,18 @@
 	GG_STATUS_FRIENDS_MASK,		/**< Flaga bitowa dostępności tylko dla znajomych */
 };
 
+/**
+ * Rodzaje statusów użytkownika. Mapa bitowa.
+ *
+ * \ingroup status
+ */
+enum {
+	GG_STATUS_FLAG_UNKNOWN,		/**< Przeznaczenie nieznane, ale występuje zawsze */
+	GG_STATUS_FLAG_VIDEO,		/**< Klient obsługuje wideorozmowy */
+	GG_STATUS_FLAG_MOBILE,		/**< Klient mobilny (ikona telefonu komórkowego) */
+	GG_STATUS_FLAG_SPAM,		/**< Klient chce otrzymywać linki od nieznajomych */
+};
+
 #endif	/* DOXYGEN */
 
 /**
@@ -1985,6 +2121,7 @@
 	uint32_t type;			/* sposób połączenia */
 	gg_dcc7_id_t id;		/* identyfikator połączenia */
 	char info[GG_DCC7_INFO_LEN];	/* informacje o połączeniu "ip port" */
+	char hash[GG_DCC7_INFO_HASH_LEN];/* skrót "ip" */
 } GG_PACKED;
 
 #define GG_DCC7_NEW 0x20
@@ -2055,14 +2192,14 @@
 #define GG_DCC7_TIMEOUT_FILE_ACK 300	/* 5 minut */
 #define GG_DCC7_TIMEOUT_VOICE_ACK 300	/* 5 minut */
 
+#ifdef _WIN32
+#pragma pack(pop)
+#endif
+
 #ifdef __cplusplus
 }
 #endif
 
-#ifdef _WIN32
-#pragma pack(pop)
-#endif
-
 #endif /* __GG_LIBGADU_H */
 
 /*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/message.c	Fri Mar 25 00:06:47 2011 +0000
@@ -0,0 +1,657 @@
+/*
+ *  (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License Version
+ *  2.1 as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+/**
+ * \file message.c
+ *
+ * \brief Obsługa wiadomości
+ * 
+ * Plik zawiera funkcje dotyczące obsługi "klasy" gg_message_t, które
+ * w przyszłości zostaną dołączone do API. Obecnie używane są funkcje
+ * konwersji między tekstem z atrybutami i HTML.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "message.h"
+
+#if 0
+
+gg_message_t *gg_message_new(void)
+{
+	gg_message_t *gm;
+
+	gm = malloc(sizeof(gg_message_t));
+
+	if (gm == NULL)
+		return NULL;
+
+	memset(gm, 0, sizeof(gg_message_t));
+
+	gm->msgclass = GG_CLASS_CHAT;
+	gm->seq = (uint32_t) -1;
+
+	return gm;
+}
+
+int gg_message_init(gg_message_t *gm, int msgclass, int seq, uin_t *recipients, size_t recipient_count, char *text, char *html, char *attributes, size_t attributes_length, int auto_convert)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	memset(gm, 0, sizeof(gg_message_t));
+	gm->recipients = recipients;
+	gm->recipient_count = recipient_count;
+	gm->text = text;
+	gm->html = html;
+	gm->attributes = attributes;
+	gm->attributes_length = attributes_length;
+	gm->msgclass = msgclass;
+	gm->seq = seq;
+	gm->auto_convert = auto_convert;
+
+	return 0;
+}
+
+void gg_message_free(gg_message_t *gm)
+{
+	if (gm == NULL) {
+		errno = EINVAL;
+		return;
+	}	
+
+	free(gm->text);
+	free(gm->text_converted);
+	free(gm->html);
+	free(gm->html_converted);
+	free(gm->recipients);
+	free(gm->attributes);
+
+	free(gm);
+}
+
+int gg_message_set_auto_convert(gg_message_t *gm, int auto_convert)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	gm->auto_convert = !!auto_convert;
+
+	if (!gm->auto_convert) {
+		free(gm->text_converted);
+		free(gm->html_converted);
+		gm->text_converted = NULL;
+		gm->html_converted = NULL;
+	}
+
+	return 0;
+}
+
+int gg_message_get_auto_convert(gg_message_t *gm)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	return gm->auto_convert;
+}
+
+int gg_message_set_recipients(gg_message_t *gm, const uin_t *recipients, size_t recipient_count)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	if (recipient_count >= INT_MAX / sizeof(uin_t)) {
+		errno = EINVAL;
+		return -1;
+	}	
+
+	if ((recipients == NULL) || (recipient_count == 0)) {
+		free(gm->recipients);
+		gm->recipients = NULL;
+		gm->recipient_count = 0;
+	} else {
+		uin_t *tmp;
+
+		tmp = realloc(gm->recipients, recipient_count * sizeof(uin_t));
+
+		if (tmp == NULL)
+			return -1;
+
+		memcpy(tmp, recipients, recipient_count * sizeof(uin_t));
+
+		gm->recipients = tmp;
+		gm->recipient_count = recipient_count;
+	}
+	
+	return 0;
+}
+
+int gg_message_set_recipient(gg_message_t *gm, uin_t recipient)
+{
+	return gg_message_set_recipients(gm, &recipient, 1);
+}
+
+int gg_message_get_recipients(gg_message_t *gm, const uin_t **recipients, size_t *recipient_count)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	if (recipients != NULL)
+		*recipients = gm->recipients;
+
+	if (recipient_count != NULL)
+		*recipient_count = gm->recipient_count;
+
+	return 0;
+}
+
+uin_t gg_message_get_recipient(gg_message_t *gm)
+{
+	GG_MESSAGE_CHECK(gm, (uin_t) -1);
+
+	if ((gm->recipients == NULL) || (gm->recipient_count < 1)) {
+		// errno = XXX;
+		return (uin_t) -1;
+	}
+
+	return gm->recipients[0];
+}
+
+int gg_message_set_class(gg_message_t *gm, uint32_t msgclass)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	gm->msgclass = msgclass;
+
+	return 0;
+}
+
+uint32_t gg_message_get_class(gg_message_t *gm)
+{
+	GG_MESSAGE_CHECK(gm, (uint32_t) -1);
+
+	return gm->msgclass;
+}
+
+int gg_message_set_seq(gg_message_t *gm, uint32_t seq)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	gm->seq = seq;
+
+	return 0;
+}
+
+uint32_t gg_message_get_seq(gg_message_t *gm)
+{
+	GG_MESSAGE_CHECK(gm, (uint32_t) -1);
+
+	return gm->seq;
+}
+
+int gg_message_set_text(gg_message_t *gm, const char *text)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	if (text == NULL) {
+		free(gm->text);
+		gm->text = NULL;
+	} else {
+		char *tmp;
+
+		tmp = strdup(text);
+
+		if (tmp == NULL)
+			return -1;
+
+		free(gm->text);
+		gm->text = tmp;
+	}
+
+	free(gm->html_converted);
+	gm->html_converted = NULL;
+
+	return 0;
+}
+
+const char *gg_message_get_text(gg_message_t *gm)
+{
+	GG_MESSAGE_CHECK(gm, NULL);
+
+	if (gm->text_converted != NULL)
+		return gm->text_converted;
+
+	if (gm->text == NULL && gm->html != NULL && gm->auto_convert) {
+		size_t len;
+
+		free(gm->text_converted);
+
+		len = gg_message_html_to_text(NULL, gm->html);
+
+		gm->text_converted = malloc(len + 1);
+
+		if (gm->text_converted == NULL)
+			return NULL;
+
+		gg_message_html_to_text(gm->text_converted, gm->html);
+
+		return gm->text_converted;
+	}
+
+	return gm->text;
+}
+
+int gg_message_set_html(gg_message_t *gm, const char *html)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	if (html == NULL) {
+		free(gm->html);
+		gm->html = NULL;
+	} else {
+		char *tmp;
+
+		tmp = strdup(html);
+
+		if (tmp == NULL)
+			return -1;
+
+		free(gm->html);
+		gm->html = tmp;
+	}
+
+	free(gm->text_converted);
+	gm->text_converted = NULL;
+
+	return 0;
+}
+
+const char *gg_message_get_html(gg_message_t *gm)
+{
+	GG_MESSAGE_CHECK(gm, NULL);
+
+	if (gm->html_converted != NULL)
+		return gm->html_converted;
+
+	if (gm->html == NULL && gm->text != NULL && gm->auto_convert) {
+		size_t len;
+
+		free(gm->html_converted);
+
+		len = gg_message_text_to_html(NULL, gm->text, gm->attributes, gm->attributes_length);
+
+		gm->html_converted = malloc(len + 1);
+
+		if (gm->html_converted == NULL)
+			return NULL;
+
+		gg_message_text_to_html(gm->html_converted, gm->text, gm->attributes, gm->attributes_length);
+
+		return gm->html_converted;
+	}
+
+	return gm->html;
+}
+
+int gg_message_set_attributes(gg_message_t *gm, const char *attributes, size_t length)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	if (length > 0xfffd) {
+		// errno = XXX;
+		return -1;
+	}
+
+	if ((attributes == NULL) || (length == 0)) {
+		free(gm->attributes);
+		gm->attributes = NULL;
+		gm->attributes_length = 0;
+	} else {
+		char *tmp;
+
+		tmp = realloc(gm->attributes, length);
+
+		if (tmp == NULL)
+			return -1;
+
+		gm->attributes = tmp;
+		gm->attributes_length = length;
+	}
+
+	free(gm->html_converted);
+	gm->html_converted = NULL;
+
+	return 0;
+}
+
+int gg_message_get_attributes(gg_message_t *gm, const char **attributes, size_t *attributes_length)
+{
+	GG_MESSAGE_CHECK(gm, -1);
+
+	if (attributes != NULL)
+		*attributes = gm->attributes;
+
+	if (attributes_length != NULL)
+		*attributes_length = gm->attributes_length;
+
+	return 0;
+}
+
+#endif
+
+/**
+ * \internal Dodaje tekst na koniec bufora.
+ * 
+ * \param dst Wskaźnik na bufor roboczy
+ * \param pos Wskaźnik na aktualne położenie w buforze roboczym
+ * \param src Dodawany tekst
+ * \param len Długość dodawanego tekstu
+ */
+static void gg_append(char *dst, int *pos, const void *src, int len)
+{
+	if (dst != NULL)
+		memcpy(&dst[*pos], src, len);
+
+	*pos += len;
+}
+
+/**
+ * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML.
+ *
+ * \param dst Bufor wynikowy (może być \c NULL)
+ * \param src Tekst źródłowy w UTF-8
+ * \param format Atrybuty tekstu źródłowego
+ * \param format_len Długość bloku atrybutów tekstu źródłowego
+ *
+ * \note Wynikowy tekst nie jest idealnym kodem HTML, ponieważ ma jak
+ * dokładniej odzwierciedlać to, co wygenerowałby oryginalny klient.
+ *
+ * \note Dokleja \c \\0 na końcu bufora wynikowego.
+ *
+ * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL).
+ */
+size_t gg_message_text_to_html(char *dst, const char *src, const char *format, size_t format_len)
+{
+	const char span_fmt[] = "<span style=\"color:#%02x%02x%02x; font-family:'MS Shell Dlg 2'; font-size:9pt; \">";
+	const int span_len = 75;
+	const char img_fmt[] = "<img name=\"%02x%02x%02x%02x%02x%02x%02x%02x\">";
+	const int img_len = 29;
+	int char_pos = 0;
+	int format_idx = 0;
+	unsigned char old_attr = 0;
+	const unsigned char *color = (const unsigned char*) "\x00\x00\x00";
+	int len, i;
+	const unsigned char *format_ = (const unsigned char*) format;
+
+	len = 0;
+
+	/* Nie mamy atrybutów dla pierwsze znaku, a tekst nie jest pusty, więc
+	 * tak czy inaczej trzeba otworzyć <span>. */
+
+	if (src[0] != 0 && (format_idx + 3 > format_len || (format_[format_idx] | (format_[format_idx + 1] << 8)) != 0)) {
+		if (dst != NULL)
+			sprintf(&dst[len], span_fmt, 0, 0, 0);
+
+		len += span_len;
+	}
+
+	/* Pętla przechodzi też przez kończące \0, żeby móc dokleić obrazek
+	 * na końcu tekstu. */
+
+	for (i = 0; ; i++) {
+		/* Analizuj atrybuty tak długo jak dotyczą aktualnego znaku. */
+		for (;;) {
+			unsigned char attr;
+			int attr_pos;
+
+			if (format_idx + 3 > format_len)
+				break;
+
+			attr_pos = format_[format_idx] | (format_[format_idx + 1] << 8);
+
+			if (attr_pos != char_pos)
+				break;
+
+			attr = format_[format_idx + 2];
+
+			/* Nie doklejaj atrybutów na końcu, co najwyżej obrazki. */
+
+			if (src[i] == 0)
+				attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR);
+
+			format_idx += 3;
+
+			if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0 || (attr == 0 && old_attr != 0)) {
+				if (char_pos != 0) {
+					if ((old_attr & GG_FONT_UNDERLINE) != 0)
+						gg_append(dst, &len, "</u>", 4);
+
+					if ((old_attr & GG_FONT_ITALIC) != 0)
+						gg_append(dst, &len, "</i>", 4);
+
+					if ((old_attr & GG_FONT_BOLD) != 0)
+						gg_append(dst, &len, "</b>", 4);
+
+					if (src[i] != 0)
+						gg_append(dst, &len, "</span>", 7);
+				}
+
+				if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) {
+					color = &format_[format_idx];
+					format_idx += 3;
+				} else {
+					color = (unsigned char*) "\x00\x00\x00";
+				}
+
+				if (src[i] != 0) {
+					if (dst != NULL)
+						sprintf(&dst[len], span_fmt, color[0], color[1], color[2]);
+					len += span_len;
+				}
+			} else if (char_pos == 0 && src[0] != 0) {
+				if (dst != NULL)
+					sprintf(&dst[len], span_fmt, 0, 0, 0);
+				len += span_len;
+			}
+
+			if ((attr & GG_FONT_BOLD) != 0)
+				gg_append(dst, &len, "<b>", 3);
+
+			if ((attr & GG_FONT_ITALIC) != 0)
+				gg_append(dst, &len, "<i>", 3);
+
+			if ((attr & GG_FONT_UNDERLINE) != 0)
+				gg_append(dst, &len, "<u>", 3);
+
+			if (((attr & GG_FONT_IMAGE) != 0) && (format_idx + 10 <= format_len)) {
+				if (dst != NULL) {
+					sprintf(&dst[len], img_fmt,
+						format_[format_idx + 9],
+						format_[format_idx + 8], 
+						format_[format_idx + 7],
+						format_[format_idx + 6], 
+						format_[format_idx + 5],
+						format_[format_idx + 4],
+						format_[format_idx + 3],
+						format_[format_idx + 2]);
+				}
+
+				len += img_len;
+				format_idx += 10;
+			}
+
+			old_attr = attr;
+		}
+
+		/* Doklej znak zachowując htmlowe escapowanie. */
+
+		switch (src[i]) {
+			case '&':
+				gg_append(dst, &len, "&amp;", 5);
+				break;
+			case '<':
+				gg_append(dst, &len, "&lt;", 4);
+				break;
+			case '>':
+				gg_append(dst, &len, "&gt;", 4);
+				break;
+			case '\'':
+				gg_append(dst, &len, "&apos;", 6);
+				break;
+			case '\"':
+				gg_append(dst, &len, "&quot;", 6);
+				break;
+			case '\n':
+				gg_append(dst, &len, "<br>", 4);
+				break;
+			case '\r':
+			case 0:
+				break;
+			default:
+				if (dst != NULL)
+					dst[len] = src[i];
+				len++;
+		}
+
+		/* Sprawdź, czy bajt nie jest kontynuacją znaku unikodowego. */
+
+		if ((src[i] & 0xc0) != 0xc0)
+			char_pos++;
+
+		if (src[i] == 0)
+			break;
+	}
+
+	/* Zamknij tagi. */
+
+	if ((old_attr & GG_FONT_UNDERLINE) != 0)
+		gg_append(dst, &len, "</u>", 4);
+
+	if ((old_attr & GG_FONT_ITALIC) != 0)
+		gg_append(dst, &len, "</i>", 4);
+
+	if ((old_attr & GG_FONT_BOLD) != 0)
+		gg_append(dst, &len, "</b>", 4);
+
+	if (src[0] != 0)
+		gg_append(dst, &len, "</span>", 7);
+
+	if (dst != NULL)
+		dst[len] = 0;
+
+	return len;
+}
+
+/**
+ * \internal Zamienia tekst w formacie HTML na czysty tekst.
+ *
+ * \param dst Bufor wynikowy (może być \c NULL)
+ * \param html Tekst źródłowy
+ *
+ * \note Dokleja \c \\0 na końcu bufora wynikowego.
+ *
+ * \note Funkcja służy do zachowania kompatybilności przy przesyłaniu
+ * wiadomości HTML do klientów, które tego formatu nie obsługują. Z tego
+ * powodu funkcja nie zachowuje formatowania, a jedynie usuwa tagi i
+ * zamienia podstawowe encje na ich odpowiedniki ASCII.
+ *
+ * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL).
+ */
+size_t gg_message_html_to_text(char *dst, const char *html)
+{
+	const char *src, *entity, *tag;
+	int in_tag, in_entity;
+	size_t len;
+
+	len = 0;
+	in_tag = 0;
+	tag = NULL;
+	in_entity = 0;
+	entity = NULL;
+
+	for (src = html; *src != 0; src++) {
+		if (*src == '<') {
+			tag = src;
+			in_tag = 1;
+			continue;
+		}
+
+		if (in_tag && (*src == '>')) {
+			if (strncmp(tag, "<br", 3) == 0) {
+				if (dst != NULL)
+					dst[len] = '\n';
+				len++;
+			}
+			in_tag = 0;
+			continue;
+		}
+
+		if (in_tag)
+			continue;
+
+		if (*src == '&') {
+			in_entity = 1;
+			entity = src;
+			continue;
+		}
+
+		if (in_entity && *src == ';') {
+			in_entity = 0;
+			if (dst != NULL) {
+				if (strncmp(entity, "&lt;", 4) == 0)
+					dst[len++] = '<';
+				else if (strncmp(entity, "&gt;", 4) == 0)
+					dst[len++] = '>';
+				else if (strncmp(entity, "&quot;", 6) == 0)
+					dst[len++] = '"';
+				else if (strncmp(entity, "&apos;", 6) == 0)
+					dst[len++] = '\'';
+				else if (strncmp(entity, "&amp;", 5) == 0)
+					dst[len++] = '&';
+				else if (strncmp(entity, "&nbsp;", 6) == 0) {
+					dst[len++] = 0xc2;
+					dst[len++] = 0xa0;
+				} else
+					dst[len++] = '?';
+			} else {
+				if (strncmp(entity, "&nbsp;", 6) == 0)
+					len += 2;
+				else
+					len++;
+			}
+
+			continue;
+		}
+
+		if (in_entity && !(isalnum(*src) || *src == '#'))
+			in_entity = 0;
+
+		if (in_entity)
+			continue;
+
+		if (dst != NULL)
+			dst[len] = *src;
+
+		len++;
+	}
+
+	if (dst != NULL)
+		dst[len] = 0;
+	
+	return len;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/message.h	Fri Mar 25 00:06:47 2011 +0000
@@ -0,0 +1,56 @@
+/*
+ *  (C) Copyright 2009 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  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.
+ */
+
+#ifndef LIBGADU_MESSAGE_H
+#define LIBGADU_MESSAGE_H
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include "libgadu.h"
+
+#if 0
+
+struct gg_message {
+	uin_t *recipients;
+	size_t recipient_count;
+	char *text;
+	char *html;
+	char *attributes;
+	size_t attributes_length;
+	uint32_t msgclass;
+	uint32_t seq;
+
+	int auto_convert;
+	char *text_converted;
+	char *html_converted;
+};
+
+#define GG_MESSAGE_CHECK(gm, result) \
+	if ((gm) == NULL) { \
+		errno = EINVAL; \
+		return (result); \
+	}
+
+int gg_message_init(gg_message_t *gm, int msgclass, int seq, uin_t *recipients, size_t recipient_count, char *text, char *xhtml, char *attributes, size_t attributes_length, int auto_convert);
+
+#endif
+
+size_t gg_message_html_to_text(char *dst, const char *html);
+size_t gg_message_text_to_html(char *dst, const char *utf_msg, const char *format, size_t format_len);
+
+#endif /* LIBGADU_MESSAGE_H */
--- a/libpurple/protocols/gg/lib/obsolete.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/obsolete.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,4 +1,4 @@
-/* $Id: obsolete.c 854 2009-10-12 21:06:28Z wojtekka $ */
+/* $Id: obsolete.c 1036 2010-12-15 00:02:28Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2003 Wojtek Kaniewski <wojtekka@irc.pl>
--- a/libpurple/protocols/gg/lib/protocol.h	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/protocol.h	Fri Mar 25 00:06:47 2011 +0000
@@ -1,7 +1,9 @@
 /* $Id$ */
 
 /*
- *  (C) Copyright 2009 Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *  (C) Copyright 2009-2010 Jakub Zawadzki <darkjames@darkjames.ath.cx>
+ *                          Bartłomiej Zimoń <uzi18@o2.pl>
+ *                          Wojtek Kaniewski <wojtekka@irc.pl>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License Version
@@ -39,7 +41,7 @@
 #define GG_FEATURE_STATUS80 		0x05
 
 #define GG8_LANG	"pl"
-#define GG8_VERSION	"Gadu-Gadu Client Build 8.0.0.8731"
+#define GG8_VERSION	"Gadu-Gadu Client Build "
 
 struct gg_login80 {
 	uint32_t uin;			/* mój numerek */
@@ -61,6 +63,22 @@
 
 #define GG_LOGIN80_OK 0x0035
 
+/**
+ * Logowanie powiodło się (pakiet \c GG_LOGIN80_OK)
+ */
+struct gg_login80_ok {
+	uint32_t unknown1;		/* 0x00000001 */
+} GG_PACKED;
+
+/**
+ * Logowanie nie powiodło się (pakiet \c GG_LOGIN80_FAILED)
+ */
+#define GG_LOGIN80_FAILED 0x0043
+
+struct gg_login80_failed {
+	uint32_t unknown1;		/* 0x00000001 */
+} GG_PACKED;
+
 #define GG_NEW_STATUS80BETA 0x0028
 
 #define GG_NEW_STATUS80 0x0038
@@ -83,12 +101,12 @@
 struct gg_notify_reply80 {
 	uint32_t uin;		/* numerek plus flagi w najstarszym bajcie */
 	uint32_t status;	/* status danej osoby */
-	uint32_t flags;		/* flagi (przeznaczenie nieznane) */
+	uint32_t features;	/* opcje protokołu */
 	uint32_t remote_ip;	/* adres IP bezpośrednich połączeń */
 	uint16_t remote_port;	/* port bezpośrednich połączeń */
 	uint8_t image_size;	/* maksymalny rozmiar obrazków w KB */
-	uint8_t unknown2;	/* 0x00 */
-	uint32_t unknown3;	/* 0x00000000 */
+	uint8_t unknown1;	/* 0x00 */
+	uint32_t flags;		/* flagi połączenia */
 	uint32_t descr_len;	/* rozmiar opisu */
 } GG_PACKED;
 
@@ -115,6 +133,65 @@
 
 #define GG_DISCONNECT_ACK 0x000d
 
+#define GG_RECV_MSG_ACK 0x0046
+
+struct gg_recv_msg_ack {
+	uint32_t count;
+} GG_PACKED;
+
+#define GG_USER_DATA 0x0044
+
+struct gg_user_data {
+	uint32_t type;
+	uint32_t user_count;
+} GG_PACKED;
+
+struct gg_user_data_user {
+	uint32_t uin;
+	uint32_t attr_count;
+} GG_PACKED;
+
+#define GG_TYPING_NOTIFICATION 0x0059
+
+struct gg_typing_notification {
+	uint16_t length;
+	uint32_t uin;
+} GG_PACKED;
+
+#define GG_XML_ACTION 0x002c
+
+#define GG_RECV_OWN_MSG 0x005a
+
+#define GG_MULTILOGON_INFO 0x005b
+
+struct gg_multilogon_info {
+	uint32_t count;
+} GG_PACKED;
+
+struct gg_multilogon_info_item {
+	uint32_t addr;
+	uint32_t flags;
+	uint32_t features;
+	uint32_t logon_time;
+	gg_multilogon_id_t conn_id;
+	uint32_t unknown1;
+	uint32_t name_size;
+} GG_PACKED;
+
+#define GG_MULTILOGON_DISCONNECT 0x0062
+
+struct gg_multilogon_disconnect {
+	gg_multilogon_id_t conn_id;
+} GG_PACKED;
+
+#define GG_MSG_CALLBACK 0x02	/**< Żądanie zwrotnego połączenia bezpośredniego */
+
+#define GG_MSG_OPTION_CONFERENCE 0x01
+#define GG_MSG_OPTION_ATTRIBUTES 0x02
+#define GG_MSG_OPTION_IMAGE_REQUEST 0x04
+#define GG_MSG_OPTION_IMAGE_REPLY 0x05
+#define GG_MSG_OPTION_IMAGE_REPLY_MORE 0x06
+
 #define GG_DCC7_VOICE_RETRIES 0x11	/* 17 powtorzen */
 
 #define GG_DCC7_RESERVED1		0xdeadc0de
@@ -158,6 +235,48 @@
 	uint32_t id;			/* id tego co potwierdzamy [0x1 - 0x5] */
 } GG_PACKED;
 
+#define GG_DCC7_RELAY_TYPE_SERVER 0x01	/* adres serwera, na który spytać o proxy */
+#define GG_DCC7_RELAY_TYPE_PROXY 0x08	/* adresy proxy, na które sie łączyć */
+
+#define GG_DCC7_RELAY_DUNNO1 0x02
+
+#define GG_DCC7_RELAY_REQUEST 0x0a
+
+struct gg_dcc7_relay_req {
+	uint32_t magic;			/* 0x0a */
+	uint32_t len;			/* długość całego pakietu */
+	gg_dcc7_id_t id;   		/* identyfikator połączenia */
+	uint16_t type;   		/* typ zapytania */
+	uint16_t dunno1;		/* 0x02 */
+} GG_PACKED;
+
+#define GG_DCC7_RELAY_REPLY_RCOUNT 0x02
+
+#define GG_DCC7_RELAY_REPLY 0x0b
+
+struct gg_dcc7_relay_reply {
+	uint32_t magic;			/* 0x0b */
+	uint32_t len;			/* długość całego pakietu */
+	uint32_t rcount;		/* ilość serwerów */
+} GG_PACKED;
+
+struct gg_dcc7_relay_reply_server {
+	uint32_t addr;		/* adres ip serwera */
+	uint16_t port;		/* port serwera */
+	uint8_t family;		/* rodzina adresów (na końcu?!) AF_INET=2 */
+} GG_PACKED;
+
+#define GG_DCC7_WELCOME_SERVER 0xc0debabe
+
+struct gg_dcc7_welcome_server {
+	uint32_t magic;			/* 0xc0debabe */
+	gg_dcc7_id_t id;		/* identyfikator połączenia */
+} GG_PACKED;
+
+struct gg_dcc7_welcome_p2p {
+	gg_dcc7_id_t id;		/* identyfikator połączenia */
+} GG_PACKED;
+
 #ifdef _WIN32
 #pragma pack(pop)
 #endif
--- a/libpurple/protocols/gg/lib/pubdir50.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/pubdir50.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,5 +1,3 @@
-/* $Id: pubdir50.c 854 2009-10-12 21:06:28Z wojtekka $ */
-
 /*
  *  (C) Copyright 2003 Wojtek Kaniewski <wojtekka@irc.pl>
  *
@@ -22,6 +20,9 @@
  * \file pubdir50.c
  *
  * \brief Obsługa katalogu publicznego od wersji Gadu-Gadu 5.x
+ *
+ * \todo Zoptymalizować konwersję CP1250<->UTF8. Obecnie robiona jest
+ * testowa konwersja, żeby poznać długość tekstu wynikowego.
  */
 
 #include <errno.h>
@@ -31,6 +32,7 @@
 
 #include "libgadu.h"
 #include "libgadu-internal.h"
+#include "encoding.h"
 
 /**
  * Tworzy nowe zapytanie katalogu publicznego.
@@ -224,7 +226,8 @@
 		} else {
 			char *tmp;
 
-			tmp = gg_utf8_to_cp(req->entries[i].field);
+			// XXX \todo zoptymalizować
+			tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1);
 
 			if (tmp == NULL)
 				return -1;
@@ -233,7 +236,8 @@
 
 			free(tmp);
 
-			tmp = gg_utf8_to_cp(req->entries[i].value);
+			// XXX \todo zoptymalizować
+			tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1);
 
 			if (tmp == NULL)
 				return -1;
@@ -271,7 +275,8 @@
 		} else {
 			char *tmp;
 
-			tmp = gg_utf8_to_cp(req->entries[i].field);
+			// XXX \todo zoptymalizować
+			tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1);
 
 			if (tmp == NULL) {
 				free(buf);
@@ -282,7 +287,9 @@
 			p += strlen(tmp) + 1;
 			free(tmp);
 
-			tmp = gg_utf8_to_cp(req->entries[i].value);
+			// XXX \todo zoptymalizować
+			tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1);
+
 
 			if (tmp == NULL) {
 				free(buf);
@@ -416,7 +423,7 @@
 			} else {
 				char *tmp;
 
-				tmp = gg_cp_to_utf8(value);
+				tmp = gg_encoding_convert(value, GG_ENCODING_CP1250, sess->encoding, -1, -1);
 
 				if (tmp == NULL)
 					goto failure;
--- a/libpurple/protocols/gg/lib/resolver.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/resolver.c	Fri Mar 25 00:06:47 2011 +0000
@@ -1,5 +1,3 @@
-/* $Id$ */
-
 /*
  *  (C) Copyright 2001-2009 Wojtek Kaniewski <wojtekka@irc.pl>
  *                          Robert J. Woźny <speedy@ziew.org>
@@ -44,6 +42,7 @@
 #include "libgadu.h"
 #include "resolver.h"
 #include "compat.h"
+#include "session.h"
 
 /** Sposób rozwiązywania nazw serwerów */
 static gg_resolver_t gg_global_resolver_type = GG_RESOLVER_DEFAULT;
@@ -80,15 +79,17 @@
  * \internal Odpowiednik \c gethostbyname zapewniający współbieżność.
  *
  * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli
- * nie, to zwykłej \c gethostbyname.
+ * nie, to zwykłej \c gethostbyname. Wynikiem jest tablica adresów zakończona
+ * wartością INADDR_NONE, którą należy zwolnić po użyciu.
  *
  * \param hostname Nazwa serwera
- * \param addr Wskaźnik na rezultat rozwiązywania nazwy
+ * \param result Wskaźnik na wskaźnik z tablicą adresów zakończoną INADDR_NONE
+ * \param count Wskaźnik na zmienną, do ktorej zapisze się liczbę wyników
  * \param pthread Flaga blokowania unicestwiania wątku podczas alokacji pamięci
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
-int gg_gethostbyname_real(const char *hostname, struct in_addr *addr, int pthread)
+int gg_gethostbyname_real(const char *hostname, struct in_addr **result, int *count, int pthread)
 {
 #ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R
 	char *buf = NULL;
@@ -96,13 +97,18 @@
 	struct hostent he;
 	struct hostent *he_ptr = NULL;
 	size_t buf_len = 1024;
-	int result = -1;
+	int res = -1;
 	int h_errnop;
 	int ret = 0;
 #ifdef GG_CONFIG_HAVE_PTHREAD
 	int old_state;
 #endif
 
+	if (result == NULL) {
+		errno = EINVAL;
+		return -1;
+	}
+
 #ifdef GG_CONFIG_HAVE_PTHREAD
 	pthread_cleanup_push(gg_gethostbyname_cleaner, &buf);
 
@@ -146,9 +152,41 @@
 			}
 		}
 
-		if (ret == 0 && he_ptr != NULL) {
-			memcpy(addr, he_ptr->h_addr, sizeof(struct in_addr));
-			result = 0;
+		if (ret == 0 && he_ptr != NULL && he_ptr->h_addr_list[0] != NULL) {
+			int i;
+
+			/* Policz liczbę adresów */
+
+			for (i = 0; he_ptr->h_addr_list[i] != NULL; i++)
+				;
+
+			/* Zaalokuj */
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+			if (pthread)
+				pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state);
+#endif
+
+			*result = malloc((i + 1) * sizeof(struct in_addr));
+
+#ifdef GG_CONFIG_HAVE_PTHREAD
+			if (pthread)
+				pthread_setcancelstate(old_state, NULL);
+#endif
+
+			if (*result == NULL)
+				return -1;
+
+			/* Kopiuj */
+
+			for (i = 0; he_ptr->h_addr_list[i] != NULL; i++)
+				memcpy(&((*result)[i]), he_ptr->h_addr_list[i], sizeof(struct in_addr));
+
+			(*result)[i].s_addr = INADDR_NONE;
+
+			*count = i;
+
+			res = 0;
 		}
 
 #ifdef GG_CONFIG_HAVE_PTHREAD
@@ -169,26 +207,92 @@
 	pthread_cleanup_pop(1);
 #endif
 
-	return result;
-#else
+	return res;
+#else /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */
 	struct hostent *he;
+	int i;
+
+
+	if (result == NULL || count == NULL) {
+		errno = EINVAL;
+		return -1;
+	}
 
 	he = gethostbyname(hostname);
 
-	if (he == NULL)
+	if (he == NULL || he->h_addr_list[0] == NULL)
 		return -1;
 
-	memcpy(addr, he->h_addr, sizeof(struct in_addr));
+	/* Policz liczbę adresów */
+
+	for (i = 0; he->h_addr_list[i] != NULL; i++)
+		;
+
+	/* Zaalokuj */
+
+	*result = malloc((i + 1) * sizeof(struct in_addr));
+
+	if (*result == NULL)
+		return -1;
+
+	/* Kopiuj */
+
+	for (i = 0; he->h_addr_list[i] != NULL; i++)
+		memcpy(&((*result)[i]), he->h_addr_list[0], sizeof(struct in_addr));
+
+	(*result)[i].s_addr = INADDR_NONE;
+
+	*count = i;
 
 	return 0;
 #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */
 }
 
 /**
+ * \internal Rozwiązuje nazwę i zapisuje wynik do podanego desktyptora.
+ *
+ * \param fd Deskryptor
+ * \param hostname Nazwa serwera
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ */
+int gg_resolver_run(int fd, const char *hostname)
+{
+	struct in_addr addr_ip[2], *addr_list;
+	int addr_count;
+	int res = 0;
+
+	gg_debug(GG_DEBUG_MISC, "// gg_resolver_run(%d, %s)\n", fd, hostname);
+
+	if ((addr_ip[0].s_addr = inet_addr(hostname)) == INADDR_NONE) {
+		if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 1) == -1) {
+			addr_list = addr_ip;
+			/* addr_ip[0] już zawiera INADDR_NONE */
+		}
+	} else {
+		addr_list = addr_ip;
+		addr_ip[1].s_addr = INADDR_NONE;
+		addr_count = 1;
+	}
+
+	gg_debug(GG_DEBUG_MISC, "// gg_resolver_run() count = %d\n", addr_count);
+
+	if (write(fd, addr_list, (addr_count + 1) * sizeof(struct in_addr)) != (addr_count + 1) * sizeof(struct in_addr))
+		res = -1;
+
+	if (addr_list != addr_ip)
+		free(addr_list);
+
+	return res;
+}
+
+/**
  * \internal Odpowiednik \c gethostbyname zapewniający współbieżność.
  *
  * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli
- * nie, to zwykłej \c gethostbyname.
+ * nie, to zwykłej \c gethostbyname. Funkcja służy do zachowania zgodności
+ * ABI i służy do pobierania tylko pierwszego adresu -- pozostałe mogą
+ * zostać zignorowane przez aplikację.
  *
  * \param hostname Nazwa serwera
  *
@@ -196,16 +300,13 @@
  */
 struct in_addr *gg_gethostbyname(const char *hostname)
 {
-	struct in_addr *addr;
+	struct in_addr *result;
+	int count;
 
-	if (!(addr = malloc(sizeof(struct in_addr))))
+	if (gg_gethostbyname_real(hostname, &result, &count, 0) == -1)
 		return NULL;
 
-	if (gg_gethostbyname_real(hostname, addr, 0)) {
-		free(addr);
-		return NULL;
-	}
-	return addr;
+	return result;
 }
 
 /**
@@ -215,8 +316,6 @@
 	int pid;		/*< Identyfikator procesu */
 };
 
-
-
 #ifdef _WIN32
 /**
  *  Deal with the fact that you can't select() on a win32 file fd.
@@ -353,29 +452,28 @@
 static DWORD WINAPI gg_resolve_win32thread_thread(LPVOID arg)
 {
 	struct gg_resolve_win32thread_data *d = arg;
-	struct in_addr a;
+	struct in_addr addr_ip[2], *addr_list;
+	int addr_count;
 
 	gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread_thread() host: %s, fd: %i called\n", d->hostname, d->fd);
 
-	if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) {
+	if ((addr_ip[0].s_addr = inet_addr(d->hostname)) == INADDR_NONE) {
 		/* W przypadku błędu gg_gethostbyname_real() zwróci -1
 					 * i nie zmieni &addr. Tam jest już INADDR_NONE,
 					 * więc nie musimy robić nic więcej. */
-		gg_gethostbyname_real(d->hostname, &a, 0);
+		if (gg_gethostbyname_real(d->hostname, &addr_list, &addr_count, 0) == -1)
+		{
+		    addr_list = addr_ip;
+		}
+	} else {
+		addr_list = addr_ip;
+		addr_ip[1].s_addr = INADDR_NONE;
+		addr_count = 1;
 	}
 
-	// if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) {
-		// struct in_addr *hn;
+	gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread_thread() count = %d\n", addr_count);
 
-		// 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));
+	write(d->fd, addr_list, (addr_count+1) * sizeof(struct in_addr));
 	close(d->fd);
 
 	free(d->hostname);
@@ -383,6 +481,9 @@
 
 	free(d);
 
+    if (addr_list != addr_ip)
+		free(addr_list);
+
 	gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread_thread() done\n");
 
 	return 0;
@@ -509,7 +610,6 @@
 static int gg_resolver_fork_start(int *fd, void **priv_data, const char *hostname)
 {
 	struct gg_resolver_fork_data *data = NULL;
-	struct in_addr addr;
 	int pipes[2], new_errno;
 
 	gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname);
@@ -543,17 +643,10 @@
 	if (data->pid == 0) {
 		close(pipes[0]);
 
-		if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) {
-			/* W przypadku błędu gg_gethostbyname_real() zwróci -1
-                         * i nie zmieni &addr. Tam jest już INADDR_NONE,
-                         * więc nie musimy robić nic więcej. */
-			gg_gethostbyname_real(hostname, &addr, 0);
-		}
-
-		if (write(pipes[1], &addr, sizeof(addr)) != sizeof(addr))
+		if (gg_resolver_run(pipes[1], hostname) == -1)
 			_exit(1);
-
-		_exit(0);
+		else
+			_exit(0);
 	}
 
 	close(pipes[1]);
@@ -660,21 +753,13 @@
 static void *gg_resolver_pthread_thread(void *arg)
 {
 	struct gg_resolver_pthread_data *data = arg;
-	struct in_addr addr;
 
 	pthread_detach(pthread_self());
 
-	if ((addr.s_addr = inet_addr(data->hostname)) == INADDR_NONE) {
-		/* W przypadku błędu gg_gethostbyname_real() zwróci -1
-                 * i nie zmieni &addr. Tam jest już INADDR_NONE,
-                 * więc nie musimy robić nic więcej. */
-		gg_gethostbyname_real(data->hostname, &addr, 1);
-	}
-
-	if (write(data->wfd, &addr, sizeof(addr)) == sizeof(addr))
+	if (gg_resolver_run(data->wfd, data->hostname) == -1)
+		pthread_exit((void*) -1);
+	else
 		pthread_exit(NULL);
-	else
-		pthread_exit((void*) -1);
 
 	return NULL;	/* żeby kompilator nie marudził */
 }
@@ -745,10 +830,10 @@
 	return 0;
 
 cleanup:
-	if (data) {
+	if (data != NULL)
 		free(data->hostname);
-		free(data);
-	}
+
+	free(data);
 
 	close(pipes[0]);
 	close(pipes[1]);
@@ -770,10 +855,7 @@
  */
 int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type)
 {
-	if (gs == NULL) {
-		errno = EINVAL;
-		return -1;
-	}
+	GG_SESSION_CHECK(gs, -1);
 
 	if (type == GG_RESOLVER_DEFAULT) {
 		if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) {
@@ -796,13 +878,13 @@
 
 	switch (type) {
 #ifdef _WIN32
-	case GG_RESOLVER_WIN32:
+		case GG_RESOLVER_WIN32:
 			gs->resolver_type = type;
 			gs->resolver_start = gg_resolve_win32thread;
 			gs->resolver_cleanup = gg_resolve_win32thread_cleanup;
 			return 0;
 #else
-	case GG_RESOLVER_FORK:
+		case GG_RESOLVER_FORK:
 			gs->resolver_type = type;
 			gs->resolver_start = gg_resolver_fork_start;
 			gs->resolver_cleanup = gg_resolver_fork_cleanup;
@@ -832,10 +914,7 @@
  */
 gg_resolver_t gg_session_get_resolver(struct gg_session *gs)
 {
-	if (gs == NULL) {
-		errno = EINVAL;
-		return GG_RESOLVER_INVALID;
-	}
+	GG_SESSION_CHECK(gs, (gg_resolver_t) -1);
 
 	return gs->resolver_type;
 }
@@ -847,11 +926,32 @@
  * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy
  * \param resolver_cleanup Funkcja zwalniająca zasoby
  *
+ * Parametry funkcji rozpoczynającej rozwiązywanie nazwy wyglądają następująco:
+ *  - \c "int *fd" &mdash; wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor potoku
+ *  - \c "void **priv_data" &mdash; wskaźnik na zmienną, gdzie można umieścić wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy
+ *  - \c "const char *name" &mdash; nazwa serwera do rozwiązania
+ *
+ * Parametry funkcji zwalniającej zasoby wyglądają następująco:
+ *  - \c "void **priv_data" &mdash; wskaźnik na zmienną przechowującą wskaźnik do prywatnych danych, należy go ustawić na \c NULL po zakończeniu
+ *  - \c "int force" &mdash; flaga mówiąca o tym, że zasoby są zwalniane przed zakończeniem rozwiązywania nazwy, np. z powodu zamknięcia sesji.
+ *
+ * Własny kod rozwiązywania nazwy powinien stworzyć potok, parę gniazd lub
+ * inny deskryptor pozwalający na co najmniej jednostronną komunikację i
+ * przekazać go w parametrze \c fd. Po zakończeniu rozwiązywania nazwy,
+ * powinien wysłać otrzymany adres IP w postaci sieciowej (big-endian) do
+ * deskryptora. Jeśli rozwiązywanie nazwy się nie powiedzie, należy wysłać
+ * \c INADDR_NONE. Następnie zostanie wywołana funkcja zwalniająca zasoby
+ * z parametrem \c force równym \c 0. Gdyby sesja została zakończona przed
+ * rozwiązaniem nazwy, np. za pomocą funkcji \c gg_logoff(), funkcja
+ * zwalniająca zasoby zostanie wywołana z parametrem \c force równym \c 1.
+ *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
 int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int))
 {
-	if (gs == NULL || resolver_start == NULL || resolver_cleanup == NULL) {
+	GG_SESSION_CHECK(gs, -1);
+
+	if (resolver_start == NULL || resolver_cleanup == NULL) {
 		errno = EINVAL;
 		return -1;
 	}
@@ -899,13 +999,13 @@
 
 	switch (type) {
 #ifdef _WIN32
-	case GG_RESOLVER_WIN32:
+		case GG_RESOLVER_WIN32:
 			gh->resolver_type = type;
 			gh->resolver_start = gg_resolve_win32thread;
 			gh->resolver_cleanup = gg_resolve_win32thread_cleanup;
 			return 0;
 #else
-	case GG_RESOLVER_FORK:
+		case GG_RESOLVER_FORK:
 			gh->resolver_type = type;
 			gh->resolver_start = gg_resolver_fork_start;
 			gh->resolver_cleanup = gg_resolver_fork_cleanup;
@@ -1028,24 +1128,7 @@
  * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy
  * \param resolver_cleanup Funkcja zwalniająca zasoby
  *
- * Parametry funkcji rozpoczynającej rozwiązywanie nazwy wyglądają następująco:
- *  - \c "int *fd" &mdash; wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor potoku
- *  - \c "void **priv_data" &mdash; wskaźnik na zmienną, gdzie można umieścić wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy
- *  - \c "const char *name" &mdash; nazwa serwera do rozwiązania
- *
- * Parametry funkcji zwalniającej zasoby wyglądają następująco:
- *  - \c "void **priv_data" &mdash; wskaźnik na zmienną przechowującą wskaźnik do prywatnych danych, należy go ustawić na \c NULL po zakończeniu
- *  - \c "int force" &mdash; flaga mówiąca o tym, że zasoby są zwalniane przed zakończeniem rozwiązywania nazwy, np. z powodu zamknięcia sesji.
- *
- * Własny kod rozwiązywania nazwy powinien stworzyć potok, parę gniazd lub
- * inny deskryptor pozwalający na co najmniej jednostronną komunikację i
- * przekazać go w parametrze \c fd. Po zakończeniu rozwiązywania nazwy,
- * powinien wysłać otrzymany adres IP w postaci sieciowej (big-endian) do
- * deskryptora. Jeśli rozwiązywanie nazwy się nie powiedzie, należy wysłać
- * \c INADDR_NONE. Następnie zostanie wywołana funkcja zwalniająca zasoby
- * z parametrem \c force równym \c 0. Gdyby sesja została zakończona przed
- * rozwiązaniem nazwy, np. za pomocą funkcji \c gg_logoff(), funkcja
- * zwalniająca zasoby zostanie wywołana z parametrem \c force równym \c 1.
+ * Patrz \ref gg_session_set_custom_resolver.
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
--- a/libpurple/protocols/gg/lib/resolver.h	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/gg/lib/resolver.h	Fri Mar 25 00:06:47 2011 +0000
@@ -1,5 +1,3 @@
-/* $Id$ */
-
 /*
  *  (C) Copyright 2008 Wojtek Kaniewski <wojtekka@irc.pl>
  *
@@ -25,6 +23,6 @@
 #  include <arpa/inet.h>
 #endif
 
-int gg_gethostbyname_real(const char *hostname, struct in_addr *result, int pthread);
+int gg_gethostbyname_real(const char *hostname, struct in_addr **result, int *count, int pthread);
 
 #endif /* LIBGADU_RESOLVER_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/gg/lib/session.h	Fri Mar 25 00:06:47 2011 +0000
@@ -0,0 +1,70 @@
+/*
+ *  (C) Copyright 2008-2010 Wojtek Kaniewski <wojtekka@irc.pl>
+ *
+ *  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.
+ */
+
+#ifndef LIBGADU_SESSION_H
+#define LIBGADU_SESSION_H
+
+#ifdef GG_CONFIG_HAVE_GNUTLS
+#  include <gnutls/gnutls.h>
+#endif
+
+#define GG_SESSION_CHECK(gs, result) \
+	do { \
+		if ((gs) == NULL) { \
+			errno = EINVAL; \
+			return (result); \
+		} \
+	} while (0)
+
+#define GG_SESSION_CHECK_CONNECTED(gs, result) \
+	do { \
+		GG_SESSION_CHECK(gs, result); \
+		\
+		if (!GG_SESSION_IS_CONNECTED(gs)) { \
+			errno = ENOTCONN; \
+			return (result); \
+		} \
+	} while (0)
+
+#define GG_SESSION_IS_PROTOCOL_7_7(gs) ((gs)->protocol_version >= 0x2a)
+#define GG_SESSION_IS_PROTOCOL_8_0(gs) ((gs)->protocol_version >= 0x2d)
+
+#define GG_SESSION_IS_IDLE(gs) ((gs)->state == GG_STATE_IDLE)
+#define GG_SESSION_IS_CONNECTING(gs) ((gs)->state != GG_STATE_IDLE && (gs)->state != GG_STATE_CONNECTED)
+#define GG_SESSION_IS_CONNECTED(gs) ((gs)->state == GG_STATE_CONNECTED)
+
+#ifdef GG_CONFIG_HAVE_GNUTLS
+
+typedef struct {
+	gnutls_session_t session;
+	gnutls_certificate_credentials_t xcred;
+} gg_session_gnutls_t;
+
+#define GG_SESSION_GNUTLS(gs) ((gg_session_gnutls_t*) (gs)->ssl)->session
+
+#endif /* GG_CONFIG_HAVE_GNUTLS */
+
+#ifdef GG_CONFIG_HAVE_OPENSSL
+
+#define GG_SESSION_OPENSSL(gs) ((SSL*) (gs)->ssl)
+
+#endif /* GG_CONFIG_HAVE_OPENSSL */
+
+int gg_session_handle_packet(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge);
+
+#endif /* LIBGADU_SESSION_H */
--- a/libpurple/protocols/oscar/encoding.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/oscar/encoding.c	Fri Mar 25 00:06:47 2011 +0000
@@ -21,6 +21,60 @@
 #include "encoding.h"
 
 static gchar *
+encoding_multi_convert_to_utf8(const gchar *text, gssize textlen, const gchar *encodings, GError **error, gboolean fallback)
+{
+	gchar *utf8 = NULL;
+	const gchar *begin = encodings;
+	const gchar *end = NULL;
+	gchar *curr_encoding = NULL; /* allocated buffer for encoding name */
+	const gchar *curr_encoding_ro = NULL; /* read-only encoding name */
+
+	if (!encodings) {
+		purple_debug_error("oscar", "encodings is NULL");
+		return NULL;
+	}
+
+	for (;;)
+	{
+		/* extract next encoding */
+		end = strchr(begin, ',');
+		if (!end) {
+			curr_encoding_ro = begin;
+		}	else { /* allocate buffer for encoding */
+			curr_encoding = g_strndup(begin, end - begin);
+			if (!curr_encoding) {
+				purple_debug_error("oscar", "Error allocating memory for encoding");
+				break;
+			}
+			curr_encoding_ro = curr_encoding;
+		}
+
+		if (!g_ascii_strcasecmp(curr_encoding_ro, "utf-8") && g_utf8_validate(text, textlen, NULL)) {
+			break;
+		}
+
+		utf8 = g_convert(text, textlen, "UTF-8", curr_encoding_ro, NULL, NULL, NULL);
+
+		if (!end) /* last occurence. do not free curr_encoding: buffer was'nt allocated */
+			break;
+
+		g_free(curr_encoding); /* free allocated buffer for encoding here */
+
+		if (utf8) /* text was successfully converted */
+			break;
+
+		begin = end + 1;
+	}
+
+	if (!utf8 && fallback)
+	{ /* "begin" points to last encoding */
+		utf8 = g_convert_with_fallback(text, textlen, "UTF-8", begin, "?", NULL, NULL, error);
+	}
+
+	return utf8;
+}
+
+static gchar *
 encoding_extract(const char *encoding)
 {
 	char *begin, *end;
@@ -65,7 +119,7 @@
 	}
 
 	if (glib_encoding != NULL) {
-		utf8 = g_convert(text, textlen, "UTF-8", glib_encoding, NULL, NULL, NULL);
+		utf8 = encoding_multi_convert_to_utf8(text, textlen, glib_encoding, NULL, FALSE);
 	}
 
 	/*
@@ -101,7 +155,7 @@
 		charset = purple_account_get_string(account, "encoding", NULL);
 
 	if(charset && *charset)
-		ret = g_convert(msg, -1, "UTF-8", charset, NULL, NULL, NULL);
+		ret = encoding_multi_convert_to_utf8(msg, -1, charset, NULL, FALSE);
 
 	if(!ret)
 		ret = purple_utf8_try_convert(msg);
@@ -119,10 +173,7 @@
 		return NULL;
 
 	if (g_ascii_strcasecmp("UTF-8", charsetstr)) {
-		if (fallback)
-			ret = g_convert_with_fallback(data, datalen, "UTF-8", charsetstr, "?", NULL, NULL, &err);
-		else
-			ret = g_convert(data, datalen, "UTF-8", charsetstr, NULL, NULL, &err);
+		ret = encoding_multi_convert_to_utf8(data, datalen, charsetstr, &err, fallback);
 		if (err != NULL) {
 			purple_debug_warning("oscar", "Conversion from %s failed: %s.\n",
 							   charsetstr, err->message);
--- a/libpurple/protocols/oscar/userinfo.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/libpurple/protocols/oscar/userinfo.c	Fri Mar 25 00:06:47 2011 +0000
@@ -437,6 +437,10 @@
 		tm->tm_mon  = (int)info->birthmonth - 1;
 		tm->tm_year = (int)info->birthyear - 1900;
 
+		/* Ignore dst setting of today to avoid timezone shift between 
+		 * dates in summer and winter time. */
+		tm->tm_isdst = -1;
+
 		/* To be 100% sure that the fields are re-normalized.
 		 * If you're sure strftime() ALWAYS does this EVERYWHERE,
 		 * feel free to remove it.  --rlaager */
--- a/pidgin/gtkdialogs.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/pidgin/gtkdialogs.c	Fri Mar 25 00:06:47 2011 +0000
@@ -101,6 +101,7 @@
 
 /* Order: Alphabetical by Last Name */
 static const struct developer patch_writers[] = {
+	{"Jakub 'haakon' Adam",            NULL,                        NULL},
 	{"Peter 'Fmoo' Ruibal",            NULL,                        NULL},
 	{"Gabriel 'Nix' Schulhof",         NULL,                        NULL},
 	{NULL, NULL, NULL}
--- a/pidgin/gtkmedia.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/pidgin/gtkmedia.c	Fri Mar 25 00:06:47 2011 +0000
@@ -93,7 +93,7 @@
 	GtkWidget *pause;
 
 	GtkWidget *send_progress;
-	GtkWidget *recv_progress;
+	GHashTable *recv_progressbars;
 
 	PidginMediaState state;
 
@@ -102,7 +102,7 @@
 	GtkWidget *recv_widget;
 	GtkWidget *button_widget;
 	GtkWidget *local_video;
-	GtkWidget *remote_video;
+	GHashTable *remote_videos;
 
 	guint timeout_id;
 	PurpleMediaSessionType request_type;
@@ -352,18 +352,110 @@
 
 	g_signal_connect(G_OBJECT(media), "delete-event",
 			G_CALLBACK(pidgin_media_delete_event_cb), media);
+
+	media->priv->recv_progressbars =
+			g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+	media->priv->remote_videos =
+			g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+}
+
+static gchar *
+create_key(const gchar *session_id, const gchar *participant)
+{
+	return g_strdup_printf("%s_%s", session_id, participant);
+}
+
+static void
+pidgin_media_insert_widget(PidginMedia *gtkmedia, GtkWidget *widget,
+		const gchar *session_id, const gchar *participant)
+{
+	gchar *key = create_key(session_id, participant);
+	PurpleMediaSessionType type =
+			purple_media_get_session_type(gtkmedia->priv->media, session_id);
+
+	if (type & PURPLE_MEDIA_AUDIO)
+		g_hash_table_insert(gtkmedia->priv->recv_progressbars, key, widget);
+	else if (type & PURPLE_MEDIA_VIDEO)
+		g_hash_table_insert(gtkmedia->priv->remote_videos, key, widget);
+}
+
+static GtkWidget *
+pidgin_media_get_widget(PidginMedia *gtkmedia,
+		const gchar *session_id, const gchar *participant)
+{
+	GtkWidget *widget = NULL;
+	gchar *key = create_key(session_id, participant);
+	PurpleMediaSessionType type =
+			purple_media_get_session_type(gtkmedia->priv->media, session_id);
+
+	if (type & PURPLE_MEDIA_AUDIO)
+		widget = g_hash_table_lookup(gtkmedia->priv->recv_progressbars, key);
+	else if (type & PURPLE_MEDIA_VIDEO)
+		widget = g_hash_table_lookup(gtkmedia->priv->remote_videos, key);
+
+	g_free(key);
+	return widget;
+}
+
+static void
+pidgin_media_remove_widget(PidginMedia *gtkmedia,
+		const gchar *session_id, const gchar *participant)
+{
+	GtkWidget *widget = pidgin_media_get_widget(gtkmedia, session_id, participant);
+
+	if (widget) {
+		PurpleMediaSessionType type =
+				purple_media_get_session_type(gtkmedia->priv->media, session_id);
+		gchar *key = create_key(session_id, participant);
+		GtkRequisition req;
+
+		if (type & PURPLE_MEDIA_AUDIO) {
+			g_hash_table_remove(gtkmedia->priv->recv_progressbars, key);
+
+			if (g_hash_table_size(gtkmedia->priv->recv_progressbars) == 0 &&
+				gtkmedia->priv->send_progress) {
+
+				gtk_widget_destroy(gtkmedia->priv->send_progress);
+				gtkmedia->priv->send_progress = NULL;
+
+				gtk_widget_destroy(gtkmedia->priv->mute);
+				gtkmedia->priv->mute = NULL;
+			}
+		} else if (type & PURPLE_MEDIA_VIDEO) {
+			g_hash_table_remove(gtkmedia->priv->remote_videos, key);
+
+			if (g_hash_table_size(gtkmedia->priv->remote_videos) == 0 &&
+				gtkmedia->priv->local_video) {
+
+				gtk_widget_destroy(gtkmedia->priv->local_video);
+				gtkmedia->priv->local_video = NULL;
+
+				gtk_widget_destroy(gtkmedia->priv->pause);
+				gtkmedia->priv->pause = NULL;
+			}
+		}
+
+		g_free(key);
+
+		gtk_widget_destroy(widget);
+
+		gtk_widget_size_request(GTK_WIDGET(gtkmedia), &req);
+		gtk_window_resize(GTK_WINDOW(gtkmedia), req.width, req.height);
+	}
 }
 
 static void
 level_message_cb(PurpleMedia *media, gchar *session_id, gchar *participant,
 		double level, PidginMedia *gtkmedia)
 {
-	GtkWidget *progress;
+	GtkWidget *progress = NULL;
 	if (participant == NULL)
 		progress = gtkmedia->priv->send_progress;
 	else
-		progress = gtkmedia->priv->recv_progress;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), level * 5);
+		progress = pidgin_media_get_widget(gtkmedia, session_id, participant);
+
+	if (progress)
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), level * 5);
 }
 
 
@@ -402,6 +494,13 @@
 	if (gtkmedia->priv->timeout_id != 0)
 		g_source_remove(gtkmedia->priv->timeout_id);
 
+	if (gtkmedia->priv->recv_progressbars) {
+		g_hash_table_destroy(gtkmedia->priv->recv_progressbars);
+		g_hash_table_destroy(gtkmedia->priv->remote_videos);
+		gtkmedia->priv->recv_progressbars = NULL;
+		gtkmedia->priv->remote_videos = NULL;
+	}
+
 	G_OBJECT_CLASS(parent_class)->dispose(media);
 }
 
@@ -436,24 +535,30 @@
 realize_cb_cb(PidginMediaRealizeData *data)
 {
 	PidginMediaPrivate *priv = data->gtkmedia->priv;
-	gulong window_id;
+	GdkWindow *window = NULL;
 
-#ifdef _WIN32
 	if (data->participant == NULL)
-		window_id = GDK_WINDOW_HWND(priv->local_video->window);
-	else
-		window_id = GDK_WINDOW_HWND(priv->remote_video->window);
+		window = gtk_widget_get_window(priv->local_video);
+	else {
+		GtkWidget *widget = pidgin_media_get_widget(data->gtkmedia,
+				data->session_id, data->participant);
+		if (widget)
+			window = gtk_widget_get_window(widget);
+	}
+
+	if (window) {
+		gulong window_id;
+#ifdef _WIN32
+		window_id = GDK_WINDOW_HWND(window);
 #elif defined(HAVE_X11)
-	if (data->participant == NULL)
-		window_id = GDK_WINDOW_XWINDOW(priv->local_video->window);
-	else
-		window_id = GDK_WINDOW_XWINDOW(priv->remote_video->window);
+		window_id = GDK_WINDOW_XWINDOW(window);
 #else
-#	error "Unsupported windowing system"
+#		error "Unsupported windowing system"
 #endif
 
-	purple_media_set_output_window(priv->media, data->session_id,
-			data->participant, window_id);
+		purple_media_set_output_window(priv->media, data->session_id,
+				data->participant, window_id);
+	}
 
 	g_free(data->session_id);
 	g_free(data->participant);
@@ -490,8 +595,13 @@
 static void
 pidgin_media_reject_cb(PurpleMedia *media, int index)
 {
-	purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT,
-			NULL, NULL, TRUE);
+	GList *iter = purple_media_get_session_ids(media);
+	for (; iter; iter = g_list_delete_link(iter, iter)) {
+		const gchar *sessionid = iter->data;
+		if (!purple_media_accepted(media, sessionid, NULL))
+			purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT,
+					sessionid, NULL, TRUE);
+	}
 }
 
 static gboolean
@@ -563,9 +673,17 @@
 	purple_media_set_output_volume(media, NULL, NULL, val);
 }
 
+static void
+destroy_parent_widget_cb(GtkWidget *widget, GtkWidget *parent)
+{
+	g_return_if_fail(GTK_IS_WIDGET(parent));
+
+	gtk_widget_destroy(parent);
+}
+
 static GtkWidget *
 pidgin_media_add_audio_widget(PidginMedia *gtkmedia,
-		PurpleMediaSessionType type)
+		PurpleMediaSessionType type, const gchar *sid)
 {
 	GtkWidget *volume_widget, *progress_parent, *volume, *progress;
 	double value;
@@ -619,9 +737,14 @@
 		g_signal_connect (G_OBJECT(volume), "value-changed",
 				G_CALLBACK(pidgin_media_output_volume_changed),
 				gtkmedia->priv->media);
-		gtkmedia->priv->recv_progress = progress;
+
+		pidgin_media_insert_widget(gtkmedia, progress, sid, gtkmedia->priv->screenname);
 	}
 
+	g_signal_connect(G_OBJECT(progress), "destroy",
+			G_CALLBACK(destroy_parent_widget_cb),
+			volume_widget);
+
 	gtk_widget_show_all(volume_widget);
 
 	return volume_widget;
@@ -691,13 +814,17 @@
 				G_CALLBACK(realize_cb), data);
 		gtk_container_add(GTK_CONTAINER(aspect), remote_video);
 		gtk_widget_set_size_request (GTK_WIDGET(remote_video), 320, 240);
+		g_signal_connect(G_OBJECT(remote_video), "destroy",
+				G_CALLBACK(destroy_parent_widget_cb), aspect);
+
 		gtk_widget_show(remote_video);
 		gtk_widget_show(aspect);
 
-		gtkmedia->priv->remote_video = remote_video;
+		pidgin_media_insert_widget(gtkmedia, remote_video,
+				data->session_id, data->participant);
 	}
 
-	if (type & PURPLE_MEDIA_SEND_VIDEO) {
+	if (type & PURPLE_MEDIA_SEND_VIDEO && !gtkmedia->priv->local_video) {
 		PidginMediaRealizeData *data;
 		GtkWidget *aspect;
 		GtkWidget *local_video;
@@ -718,6 +845,8 @@
 				G_CALLBACK(realize_cb), data);
 		gtk_container_add(GTK_CONTAINER(aspect), local_video);
 		gtk_widget_set_size_request (GTK_WIDGET(local_video), 80, 60);
+		g_signal_connect(G_OBJECT(local_video), "destroy",
+				G_CALLBACK(destroy_parent_widget_cb), aspect);
 
 		gtk_widget_show(local_video);
 		gtk_widget_show(aspect);
@@ -736,7 +865,7 @@
 	if (type & PURPLE_MEDIA_RECV_AUDIO) {
 		gtk_box_pack_end(GTK_BOX(recv_widget),
 				pidgin_media_add_audio_widget(gtkmedia,
-				PURPLE_MEDIA_RECV_AUDIO), FALSE, FALSE, 0);
+				PURPLE_MEDIA_RECV_AUDIO, sid), FALSE, FALSE, 0);
 	}
 
 	if (type & PURPLE_MEDIA_SEND_AUDIO) {
@@ -751,7 +880,7 @@
 
 		gtk_box_pack_end(GTK_BOX(recv_widget),
 				pidgin_media_add_audio_widget(gtkmedia,
-				PURPLE_MEDIA_SEND_AUDIO), FALSE, FALSE, 0);
+				PURPLE_MEDIA_SEND_AUDIO, NULL), FALSE, FALSE, 0);
 	}
 
 	if (type & PURPLE_MEDIA_AUDIO &&
@@ -804,8 +933,10 @@
 {
 	purple_debug_info("gtkmedia", "state: %d sid: %s name: %s\n",
 			state, sid ? sid : "(null)", name ? name : "(null)");
-	if (sid == NULL && name == NULL) {
-		if (state == PURPLE_MEDIA_STATE_END) {
+	if (state == PURPLE_MEDIA_STATE_END) {
+		if (sid != NULL && name != NULL) {
+			pidgin_media_remove_widget(gtkmedia, sid, name);
+		} else if (sid == NULL && name == NULL) {
 			pidgin_media_emit_message(gtkmedia,
 					_("The call has been terminated."));
 			gtk_widget_destroy(GTK_WIDGET(gtkmedia));
--- a/pidgin/plugins/vvconfig.c	Thu Mar 24 23:22:29 2011 +0000
+++ b/pidgin/plugins/vvconfig.c	Fri Mar 25 00:06:47 2011 +0000
@@ -505,19 +505,22 @@
 	gtk_widget_destroy(GTK_WIDGET(window));
 }
 
+typedef GtkWidget *(*FrameCreateCb)(PurplePlugin *plugin);
+
 static void
 show_config(PurplePluginAction *action)
 {
 	if (!window) {
+		FrameCreateCb create_frame = action->user_data;
 		GtkWidget *vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 		GtkWidget *hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
-		GtkWidget *config_frame = get_plugin_config_frame(NULL);
+		GtkWidget *config_frame = create_frame(NULL);
 		GtkWidget *close = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
 
 		gtk_container_add(GTK_CONTAINER(vbox), config_frame);
 		gtk_container_add(GTK_CONTAINER(vbox), hbox);
-		window = pidgin_create_window(_("Voice/Video Settings"),
-			PIDGIN_HIG_BORDER, NULL, TRUE);
+		window = pidgin_create_window(action->label,
+			PIDGIN_HIG_BORDER, NULL, FALSE);
 		g_signal_connect(G_OBJECT(window), "destroy",
 			G_CALLBACK(config_destroy), NULL);
 		g_signal_connect(G_OBJECT(close), "clicked",
@@ -531,6 +534,180 @@
 	gtk_window_present(GTK_WINDOW(window));
 }
 
+static GstElement *
+create_pipeline()
+{
+	GstElement *pipeline = gst_pipeline_new("voicetest");
+	GstElement *src = create_audio_src(NULL, NULL, NULL);
+	GstElement *sink = create_audio_sink(NULL, NULL, NULL);
+	GstElement *volume = gst_element_factory_make("volume", "volume");
+	GstElement *level = gst_element_factory_make("level", "level");
+	GstElement *valve = gst_element_factory_make("valve", "valve");
+
+	gst_bin_add_many(GST_BIN(pipeline), src, volume, level, valve, sink, NULL);
+	gst_element_link_many(src, volume, level, valve, sink, NULL);
+
+	gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
+
+	return pipeline;
+}
+
+static void
+on_volume_change_cb(GtkRange *range, GstBin *pipeline)
+{
+	GstElement *volume;
+
+	g_return_if_fail(pipeline != NULL);
+
+	volume = gst_bin_get_by_name(pipeline, "volume");
+	g_object_set(volume, "volume", gtk_range_get_value(range) / 10.0, NULL);
+}
+
+static gdouble
+gst_msg_db_to_percent(GstMessage *msg, gchar *value_name)
+{
+	const GValue *list;
+	const GValue *value;
+	gdouble value_db;
+	gdouble percent;
+
+	list = gst_structure_get_value(
+				gst_message_get_structure(msg), value_name);
+	value = gst_value_list_get_value(list, 0);
+	value_db = g_value_get_double(value);
+	percent = pow(10, value_db / 20);
+	return (percent > 1.0) ? 1.0 : percent;
+}
+
+typedef struct
+{
+	GtkProgressBar *level;
+	GtkRange *threshold;
+} BusCbCtx;
+
+static gboolean
+gst_bus_cb(GstBus *bus, GstMessage *msg, BusCbCtx *ctx)
+{
+	if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
+		gst_structure_has_name(msg->structure, "level")) {
+
+		GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
+		gchar *name = gst_element_get_name(src);
+
+		if (!strcmp(name, "level")) {
+			gdouble percent;
+			gdouble threshold;
+			GstElement *valve;
+
+			percent = gst_msg_db_to_percent(msg, "rms");
+			gtk_progress_bar_set_fraction(ctx->level, percent * 5);
+
+			percent = gst_msg_db_to_percent(msg, "decay");
+			threshold = gtk_range_get_value(ctx->threshold) / 100.0;
+			valve = gst_bin_get_by_name(GST_BIN(GST_ELEMENT_PARENT(src)), "valve");
+			g_object_set(valve, "drop", (percent < threshold), NULL);
+			g_object_set(ctx->level,
+					"text", (percent < threshold) ? _("DROP") : " ", NULL);
+		}
+
+		g_free(name);
+	}
+
+	return TRUE;
+}
+
+static void
+voice_test_frame_destroy_cb(GtkObject *w, GstElement *pipeline)
+{
+	g_return_if_fail(GST_IS_ELEMENT(pipeline));
+
+	gst_element_set_state(pipeline, GST_STATE_NULL);
+	gst_object_unref(pipeline);
+}
+
+static void
+volume_scale_destroy_cb(GtkRange *volume, gpointer nul)
+{
+	purple_prefs_set_int("/purple/media/audio/volume/input",
+			gtk_range_get_value(volume));
+}
+
+static gchar*
+threshold_value_format_cb(GtkScale *scale, gdouble value)
+{
+	return g_strdup_printf ("%.*f%%", gtk_scale_get_digits(scale), value);
+}
+
+static void
+threshold_scale_destroy_cb(GtkRange *threshold, gpointer nul)
+{
+	purple_prefs_set_int("/purple/media/audio/silence_threshold",
+			gtk_range_get_value(threshold));
+}
+
+static GtkWidget *
+get_voice_test_frame(PurplePlugin *plugin)
+{
+	GtkWidget *vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
+	GtkWidget *level = gtk_progress_bar_new();
+	GtkWidget *volume = gtk_hscale_new_with_range(0, 100, 1);
+	GtkWidget *threshold = gtk_hscale_new_with_range(0, 100, 1);
+	GtkWidget *label;
+	GtkTable *table = GTK_TABLE(gtk_table_new(2, 2, FALSE));
+
+	GstElement *pipeline;
+	GstBus *bus;
+	BusCbCtx *ctx;
+
+	g_object_set(vbox, "width-request", 500, NULL);
+
+	gtk_table_set_row_spacings(table, PIDGIN_HIG_BOX_SPACE);
+	gtk_table_set_col_spacings(table, PIDGIN_HIG_BOX_SPACE);
+
+	label = gtk_label_new(_("Volume:"));
+	g_object_set(label, "xalign", 0.0, NULL);
+	gtk_table_attach(table, label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach_defaults(table, volume, 1, 2, 0, 1);
+	label = gtk_label_new(_("Silence threshold:"));
+	g_object_set(label, "xalign", 0.0, "yalign", 1.0, NULL);
+	gtk_table_attach(table, label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+	gtk_table_attach_defaults(table, threshold, 1, 2, 1, 2);
+
+	gtk_container_add(GTK_CONTAINER(vbox), level);
+	gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(table));
+	gtk_widget_show_all(vbox);
+
+	pipeline = create_pipeline();
+	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
+	gst_bus_add_signal_watch(bus);
+	ctx = g_new(BusCbCtx, 1);
+	ctx->level = GTK_PROGRESS_BAR(level);
+	ctx->threshold = GTK_RANGE(threshold);
+	g_signal_connect_data(bus, "message", G_CALLBACK(gst_bus_cb),
+			ctx, (GClosureNotify)g_free, 0);
+	gst_object_unref(bus);
+
+	g_signal_connect(volume, "value-changed",
+			(GCallback)on_volume_change_cb, pipeline);
+
+	gtk_range_set_value(GTK_RANGE(volume),
+			purple_prefs_get_int("/purple/media/audio/volume/input"));
+	gtk_widget_set(volume, "draw-value", FALSE, NULL);
+
+	gtk_range_set_value(GTK_RANGE(threshold),
+			purple_prefs_get_int("/purple/media/audio/silence_threshold"));
+
+	g_signal_connect(vbox, "destroy",
+			G_CALLBACK(voice_test_frame_destroy_cb), pipeline);
+	g_signal_connect(volume, "destroy",
+			G_CALLBACK(volume_scale_destroy_cb), NULL);
+	g_signal_connect(threshold, "format-value",
+			G_CALLBACK(threshold_value_format_cb), NULL);
+	g_signal_connect(threshold, "destroy",
+			G_CALLBACK(threshold_scale_destroy_cb), NULL);
+
+	return vbox;
+}
 
 static GList *
 actions(PurplePlugin *plugin, gpointer context)
@@ -538,8 +715,14 @@
 	GList *l = NULL;
 	PurplePluginAction *act = NULL;
 
-	act = purple_plugin_action_new(_("Voice and Video Settings"),
+	act = purple_plugin_action_new(_("Input and Output Settings"),
 		show_config);
+	act->user_data = get_plugin_config_frame;
+	l = g_list_append(l, act);
+
+	act = purple_plugin_action_new(_("Microphone Test"),
+		show_config);
+	act->user_data = get_voice_test_frame;
 	l = g_list_append(l, act);
 
 	return l;