libpurple/protocols/gg/lib/events.c

changeset 35557
e83a87761544
parent 35369
7e15c82f7d71
child 35620
fb20cfee648a
--- a/libpurple/protocols/gg/lib/events.c	Thu Feb 13 04:27:27 2014 +0100
+++ b/libpurple/protocols/gg/lib/events.c	Thu Feb 13 18:29:10 2014 +0100
@@ -1,4 +1,4 @@
-/* $Id: events.c 1144 2011-07-09 15:43:00Z wojtekka $ */
+/* $Id$ */
 
 /*
  *  (C) Copyright 2001-2006 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -25,25 +25,26 @@
  * \file events.c
  *
  * \brief Obsługa zdarzeń
+ *
+ * \todo Poprawna obsługa gg_proxy_http_only
  */
 
-#include <sys/types.h>
-#include <ctype.h>
+#include "strman.h"
+#include "network.h"
 
-#include "compat.h"
 #include "libgadu.h"
 #include "protocol.h"
 #include "internal.h"
 #include "encoding.h"
 #include "debug.h"
 #include "session.h"
+#include "resolver.h"
+#include "config.h"
 
 #include <errno.h>
-#include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <time.h>
-#include <unistd.h>
 #include <ctype.h>
 #ifdef GG_CONFIG_HAVE_GNUTLS
 #  include <gnutls/gnutls.h>
@@ -52,6 +53,7 @@
 #ifdef GG_CONFIG_HAVE_OPENSSL
 #  include <openssl/err.h>
 #  include <openssl/x509.h>
+#  include <openssl/rand.h>
 #endif
 
 /**
@@ -132,9 +134,14 @@
 			free(e->event.xml_event.data);
 			break;
 
+		case GG_EVENT_JSON_EVENT:
+			free(e->event.json_event.data);
+			free(e->event.json_event.type);
+			break;
+
 		case GG_EVENT_USER_DATA:
 		{
-			int i, j;
+			unsigned 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++) {
@@ -165,6 +172,14 @@
 		case GG_EVENT_USERLIST100_REPLY:
 			free(e->event.userlist100_reply.reply);
 			break;
+
+		case GG_EVENT_IMTOKEN:
+			free(e->event.imtoken.imtoken);
+			break;
+
+		case GG_EVENT_CHAT_INFO:
+			free(e->event.chat_info.participants);
+			break;
 	}
 
 	free(e);
@@ -213,6 +228,1461 @@
 /** \endcond */
 
 /**
+ * \internal Inicjalizuje struktury SSL.
+ *
+ * \param gs Struktura sesji
+ *
+ * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd
+ */
+int gg_session_init_ssl(struct gg_session *gs)
+{
+#ifdef GG_CONFIG_HAVE_GNUTLS
+	gg_session_gnutls_t *tmp;
+
+	tmp = (gg_session_gnutls_t*) gs->ssl;
+
+	if (tmp == NULL) {
+		tmp = malloc(sizeof(gg_session_gnutls_t));
+
+		if (tmp == NULL) {
+			gg_debug(GG_DEBUG_MISC, "// gg_session_connect() out of memory for GnuTLS session\n");
+			return -1;
+		}
+
+		memset(tmp, 0, sizeof(gg_session_gnutls_t));
+
+		gs->ssl = tmp;
+
+		gnutls_global_init();
+		gnutls_certificate_allocate_credentials(&tmp->xcred);
+#ifdef GG_CONFIG_SSL_SYSTEM_TRUST
+#ifdef HAVE_GNUTLS_CERTIFICATE_SET_X509_SYSTEM_TRUST
+		gnutls_certificate_set_x509_system_trust(tmp->xcred);
+#else
+		gnutls_certificate_set_x509_trust_file(tmp->xcred, GG_CONFIG_GNUTLS_SYSTEM_TRUST_STORE, GNUTLS_X509_FMT_PEM);
+#endif
+#endif
+	} else {
+		gnutls_deinit(tmp->session);
+	}
+
+	gnutls_init(&tmp->session, GNUTLS_CLIENT);
+	gnutls_set_default_priority(tmp->session);
+	gnutls_credentials_set(tmp->session, GNUTLS_CRD_CERTIFICATE, tmp->xcred);
+	gnutls_transport_set_ptr(tmp->session, (gnutls_transport_ptr_t) (long) gs->fd);
+#endif
+
+#ifdef GG_CONFIG_HAVE_OPENSSL
+	char buf[1024];
+
+	OpenSSL_add_ssl_algorithms();
+
+	if (!RAND_status()) {
+		char rdata[1024];
+		struct {
+			time_t time;
+			void *ptr;
+		} rstruct;
+
+		time(&rstruct.time);
+		rstruct.ptr = (void *) &rstruct;
+
+		RAND_seed((void *) rdata, sizeof(rdata));
+		RAND_seed((void *) &rstruct, sizeof(rstruct));
+	}
+
+	if (gs->ssl_ctx == NULL) {
+		gs->ssl_ctx = SSL_CTX_new(SSLv3_client_method());
+
+		if (gs->ssl_ctx == NULL) {
+			ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
+			gg_debug(GG_DEBUG_MISC, "// gg_session_connect() SSL_CTX_new() failed: %s\n", buf);
+			return -1;
+		}
+
+		SSL_CTX_set_verify(gs->ssl_ctx, SSL_VERIFY_NONE, NULL);
+#ifdef GG_CONFIG_SSL_SYSTEM_TRUST
+		SSL_CTX_set_default_verify_paths(gs->ssl_ctx);
+#endif
+	}
+
+	if (gs->ssl != NULL)
+		SSL_free(gs->ssl);
+
+	gs->ssl = SSL_new(gs->ssl_ctx);
+
+	if (gs->ssl == NULL) {
+		ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
+		gg_debug(GG_DEBUG_MISC, "// gg_session_connect() SSL_new() failed: %s\n", buf);
+		return -1;
+	}
+
+	SSL_set_fd(gs->ssl, gs->fd);
+#endif
+
+	return 0;
+}
+
+/**
+ * \internal Funkcja próbuje wysłać dane zakolejkowane do wysyłki.
+ *
+ * \param sess Struktura sesji
+ *
+ * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd
+ */
+static int gg_send_queued_data(struct gg_session *sess)
+{
+	int res;
+
+	if (sess->send_buf == NULL || sess->send_left == 0)
+		return 0;
+		
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending %d bytes of queued data\n", sess->send_left);
+
+	res = send(sess->fd, sess->send_buf, sess->send_left, 0);
+
+	if (res == -1) {
+		if (errno == EAGAIN || errno == EINTR) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical send error (errno=%d, %s)\n", errno, strerror(errno));
+
+			return 0;
+		}
+
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() send() failed (errno=%d, %s)\n", errno, strerror(errno));
+
+		return -1;
+	}
+
+	if (res == sess->send_left) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent all queued data\n");
+		free(sess->send_buf);
+		sess->send_buf = NULL;
+		sess->send_left = 0;
+	} else if (res > 0) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent %d bytes of queued data, %d bytes left\n", res, sess->send_left - res);
+
+		memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res);
+		sess->send_left -= res;
+	}
+
+	return 0;
+}
+
+/**
+ * \internal Sprawdza wynik połączenia asynchronicznego.
+ * \param gs Struktura sesji
+ * \param res_ptr Wskaźnik na kod błędu
+ * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd
+ */
+static int gg_async_connect_failed(struct gg_session *gs, int *res_ptr)
+{
+	int res = 0;
+	socklen_t res_size = sizeof(res);
+
+	if (!gs->async)
+		return 0;
+
+	if (gs->timeout == 0) {
+		*res_ptr = ETIMEDOUT;
+		return 1;
+	}
+
+	if (getsockopt(gs->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) == -1) {
+		*res_ptr = errno;
+		return 1;
+	}
+
+	if (res != 0) {
+		*res_ptr = res;
+		return 1;
+	}
+
+	*res_ptr = 0;
+
+	return 0;
+}
+
+typedef enum
+{
+	GG_ACTION_WAIT,
+	GG_ACTION_NEXT,
+	GG_ACTION_FAIL
+} gg_action_t;
+
+typedef gg_action_t (*gg_state_handler_t)(struct gg_session *gs, struct gg_event *ge, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state);
+
+typedef struct
+{
+	enum gg_state_t state;
+	gg_state_handler_t handler;
+	enum gg_state_t next_state;
+	enum gg_state_t alt_state;
+	enum gg_state_t alt2_state;
+} gg_state_transition_t;
+
+/* zwraca:
+ * -1 w przypadku błędu
+ * 0 jeżeli nie ma ustawionego specjalnego managera gniazdek
+ * 1 w przypadku powodzenia
+ */
+static int gg_handle_resolve_custom(struct gg_session *sess, enum gg_state_t next_state)
+{
+	struct gg_session_private *p = sess->private_data;
+	int is_tls = 0;
+	int port;
+
+	if (p->socket_manager_type == GG_SOCKET_MANAGER_TYPE_INTERNAL)
+		return 0;
+
+	if (p->socket_manager.connect_cb == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+			"// gg_handle_resolve_custom() socket_manager.connect "
+			"callback is empty\n");
+		return -1;
+	}
+
+	if (p->socket_handle != NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+			"// gg_handle_resolve_custom() socket_handle is not "
+			"NULL\n");
+		return -1;
+	}
+
+	port = sess->connect_port[sess->connect_index];
+	if (next_state == GG_STATE_SEND_HUB)
+		port = GG_APPMSG_PORT;
+
+	if (sess->ssl_flag != GG_SSL_DISABLED &&
+		next_state == GG_STATE_READING_KEY)
+	{
+		/* XXX: w tej chwili nie ma możliwości łączenia się do HUBa po
+		 * SSL, ale może będzie w przyszłości */
+		is_tls = 1;
+	}
+
+	if (is_tls && p->socket_manager_type == GG_SOCKET_MANAGER_TYPE_TCP) {
+		is_tls = 0;
+		next_state = GG_STATE_TLS_NEGOTIATION;
+	}
+
+	if (port <= 0) {
+		gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+			"// gg_handle_resolve_custom() port <= 0\n");
+		return -1;
+	}
+
+	p->socket_failure = 0;
+	p->socket_next_state = next_state;
+	p->socket_handle = p->socket_manager.connect_cb(
+		p->socket_manager.cb_data, sess->resolver_host, port, is_tls,
+		sess->async, sess);
+
+	if (p->socket_failure != 0) {
+		if (p->socket_handle != NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_WARNING,
+				"// gg_handle_resolve_custom() handle should be"
+				" empty on error\n");
+		}
+		return -1;
+	}
+
+	if (p->socket_handle == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+			"// gg_handle_resolve_custom() returned empty "
+			"handle\n");
+		return -1;
+	}
+
+	return 1;
+}
+
+static gg_action_t gg_handle_resolve_sync(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	struct in_addr addr;
+	int res;
+
+	res = gg_handle_resolve_custom(sess, alt_state);
+	if (res == 1)
+		return GG_ACTION_NEXT;
+	else if (res == -1)
+		return GG_ACTION_FAIL;
+
+	addr.s_addr = inet_addr(sess->resolver_host);
+	
+	if (addr.s_addr == INADDR_NONE) {
+		struct in_addr *addr_list = NULL;
+		unsigned int addr_count;
+
+		if (gg_gethostbyname_real(sess->resolver_host, &addr_list, &addr_count, 0) == -1) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() host %s not found\n", sess->resolver_host);
+			e->event.failure = GG_FAILURE_RESOLVING;
+			free(addr_list);
+			return GG_ACTION_FAIL;
+		}
+
+		sess->resolver_result = addr_list;
+		sess->resolver_count = addr_count;
+		sess->resolver_index = 0;
+	} else {
+		sess->resolver_result = malloc(sizeof(struct in_addr));
+
+		if (sess->resolver_result == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n");
+			return GG_ACTION_FAIL;
+		}
+
+		sess->resolver_result[0].s_addr = addr.s_addr;
+		sess->resolver_count = 1;
+		sess->resolver_index = 0;
+	}
+
+	sess->state = next_state;
+
+	return GG_ACTION_NEXT;
+}
+
+static gg_action_t gg_handle_resolve_async(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	int res;
+
+	res = gg_handle_resolve_custom(sess, alt_state);
+	if (res == 1)
+		return GG_ACTION_WAIT;
+	else if (res == -1)
+		return GG_ACTION_FAIL;
+
+	if (sess->resolver_start(&sess->fd, &sess->resolver, sess->resolver_host) == -1) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed (errno=%d, %s)\n", errno, strerror(errno));
+		e->event.failure = GG_FAILURE_RESOLVING;
+		return GG_ACTION_FAIL;
+	}
+
+	sess->state = next_state;
+	sess->check = GG_CHECK_READ;
+	sess->timeout = GG_DEFAULT_TIMEOUT;
+
+	return GG_ACTION_WAIT;
+}
+
+static gg_action_t gg_handle_resolving(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	char buf[256];
+	int count = -1;
+	int res;
+	unsigned int i;
+
+	res = gg_resolver_recv(sess->fd, buf, sizeof(buf));
+
+	if (res == -1 && (errno == EAGAIN || errno == EINTR)) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical error (errno=%d, %s)\n", errno, strerror(errno));
+		return GG_ACTION_WAIT;
+	}
+
+	sess->resolver_cleanup(&sess->resolver, 0);
+
+	if (res == -1) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() read error (errno=%d, %s)\n", errno, strerror(errno));
+		e->event.failure = GG_FAILURE_RESOLVING;
+		return GG_ACTION_FAIL;
+	}
+
+	if (res > 0) {
+		char *tmp;
+
+		tmp = realloc(sess->recv_buf, sess->recv_done + res);
+
+		if (tmp == NULL)
+			return GG_ACTION_FAIL;
+
+		sess->recv_buf = tmp;
+		memcpy(sess->recv_buf + sess->recv_done, buf, res);
+		sess->recv_done += res;
+	}
+
+	/* Sprawdź, czy mamy listę zakończoną INADDR_NONE */
+
+	for (i = 0; i < sess->recv_done / sizeof(struct in_addr); i++) {
+		if (((struct in_addr*) sess->recv_buf)[i].s_addr == INADDR_NONE) {
+			count = i;
+			break;
+		}
+	}
+
+	/* Nie znaleziono hosta */
+
+	if (count == 0) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() host not found\n");
+		e->event.failure = GG_FAILURE_RESOLVING;
+		return GG_ACTION_FAIL;
+	}
+
+	/* Nie mamy pełnej listy, ale połączenie zerwane */
+
+	if (res == 0 && count == -1) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection broken\n");
+		e->event.failure = GG_FAILURE_RESOLVING;
+		return GG_ACTION_FAIL;
+	}
+
+	/* Nie mamy pełnej listy, normalna sytuacja */
+
+	if (count == -1)
+		return GG_ACTION_WAIT;
+
+#ifndef GG_DEBUG_DISABLE
+	if ((gg_debug_level & GG_DEBUG_DUMP) && (count > 0)) {
+		char *list;
+		size_t len;
+
+		len = 0;
+
+		for (i = 0; i < (unsigned int) count; i++) {
+			if (i > 0)
+				len += 2;
+
+			len += strlen(inet_ntoa(((struct in_addr*) sess->recv_buf)[i]));
+		}
+
+		list = malloc(len + 1);
+
+		if (list == NULL)
+			return GG_ACTION_FAIL;
+
+		list[0] = 0;
+
+		for (i = 0; i < (unsigned int) count; i++) {
+			if (i > 0)
+				strcat(list, ", ");
+
+			strcat(list, inet_ntoa(((struct in_addr*) sess->recv_buf)[i]));
+		}
+
+		gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() resolved: %s\n", list);
+
+		free(list);
+	}
+#endif
+
+	gg_close(sess);
+
+	sess->state = next_state;
+	sess->resolver_result = (struct in_addr*) sess->recv_buf;
+	sess->resolver_count = count;
+	sess->resolver_index = 0;
+	sess->recv_buf = NULL;
+	sess->recv_done = 0;
+	
+	return GG_ACTION_NEXT;
+}
+
+static gg_action_t gg_handle_connect(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	struct in_addr addr;
+	int port;
+
+	if (sess->resolver_index >= sess->resolver_count) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of addresses to connect to\n");
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	addr = sess->resolver_result[sess->resolver_index];
+
+	if (sess->state == GG_STATE_CONNECT_HUB) {
+		port = GG_APPMSG_PORT;
+	} else {
+		sess->proxy_addr = addr.s_addr;
+		port = sess->proxy_port;
+	}
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connecting to %s:%d\n", inet_ntoa(addr), port);
+
+	sess->fd = gg_connect(&addr, port, sess->async);
+
+	if (sess->fd == -1) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+		sess->resolver_index++;
+		return GG_ACTION_NEXT;
+	}
+
+	sess->state = next_state;
+	sess->check = GG_CHECK_WRITE;
+	sess->timeout = GG_DEFAULT_TIMEOUT;
+	sess->soft_timeout = 1;
+
+	return GG_ACTION_WAIT;
+}
+
+static gg_action_t gg_handle_connecting(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	int res;
+
+	sess->soft_timeout = 0;
+
+	if (gg_async_connect_failed(sess, &res)) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res));
+		gg_close(sess);
+		sess->resolver_index++;
+		sess->state = alt_state;
+	} else {
+		/* Z proxy zwykle łączymy się dwa razy, więc nie zwalniamy
+		 * adresów IP po pierwszym połączeniu. */
+		if (sess->state != GG_STATE_CONNECTING_PROXY_HUB) {
+			free(sess->resolver_result);
+			sess->resolver_result = NULL;
+		}
+
+		sess->state = next_state;
+	}
+
+	return GG_ACTION_NEXT;
+}
+
+static gg_action_t gg_handle_connect_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	struct in_addr addr;
+	uint16_t port;
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "resolver_index=%d, connect_index=%d, connect_port={%d,%d}\n", sess->resolver_index, sess->connect_index, sess->connect_port[0], sess->connect_port[1]);
+
+	if ((unsigned int) sess->connect_index >= sizeof(sess->connect_port) / sizeof(sess->connect_port[0]) || sess->connect_port[sess->connect_index] == 0) {
+		sess->connect_index = 0;
+		sess->resolver_index++;
+		if (sess->resolver_index >= sess->resolver_count) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of addresses to connect to\n");
+			e->event.failure = GG_FAILURE_CONNECTING;
+			return GG_ACTION_FAIL;
+		}
+	}
+
+	addr = sess->resolver_result[sess->resolver_index];
+	port = sess->connect_port[sess->connect_index];
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connecting to %s:%d\n", inet_ntoa(addr), port);
+
+	sess->server_addr = addr.s_addr;
+	sess->fd = gg_connect(&addr, port, sess->async);
+
+	if (sess->fd == -1) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
+		sess->connect_index++;
+		return GG_ACTION_NEXT;
+	}
+
+	sess->state = next_state;
+	sess->check = GG_CHECK_WRITE;
+	sess->timeout = GG_DEFAULT_TIMEOUT;
+	sess->soft_timeout = 1;
+
+	return GG_ACTION_WAIT;
+}
+
+static gg_action_t gg_handle_connecting_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	int res;
+
+	sess->soft_timeout = 0;
+
+	/* jeśli wystąpił błąd podczas łączenia się... */
+	if (gg_async_connect_failed(sess, &res)) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res));
+		gg_close(sess);
+		sess->connect_index++;
+		sess->state = alt_state;
+		return GG_ACTION_NEXT;
+	}
+
+	free(sess->resolver_result);
+	sess->resolver_result = NULL;
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected\n");
+
+	if (sess->ssl_flag != GG_SSL_DISABLED) {
+		if (gg_session_init_ssl(sess) == -1) {
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+
+		sess->state = alt2_state;
+		sess->check = GG_CHECK_WRITE;
+		sess->timeout = GG_DEFAULT_TIMEOUT;
+
+		return GG_ACTION_NEXT;
+	} else {
+		sess->state = next_state;
+		sess->check = GG_CHECK_READ;
+		sess->timeout = GG_DEFAULT_TIMEOUT;
+
+		return GG_ACTION_WAIT;
+	}
+}
+
+static gg_action_t gg_handle_send_hub(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	char *req, *client, *auth;
+	const char *host;
+	int res;
+	int proxy;
+	size_t req_len;
+
+	if (sess->client_version != NULL && isdigit(sess->client_version[0]))
+		client = gg_urlencode(sess->client_version);
+	else if (sess->protocol_version <= GG_PROTOCOL_VERSION_100)
+		client = gg_urlencode(GG_DEFAULT_CLIENT_VERSION_100);
+	else /* sess->protocol_version >= GG_PROTOCOL_VERSION_110 */
+		client = gg_urlencode(GG_DEFAULT_CLIENT_VERSION_110);
+
+	if (client == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n");
+		return GG_ACTION_FAIL;
+	}
+
+	if (sess->proxy_addr && sess->proxy_port) {
+		host = "http://" GG_APPMSG_HOST;
+		proxy = 1;
+	} else {
+		host = "";
+		proxy = 0;
+	}
+
+	auth = gg_proxy_auth();
+
+	if (sess->ssl_flag != GG_SSL_DISABLED) {
+		req = gg_saprintf
+			("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"
+			"%s"
+			"\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : "");
+	} else {
+		req = gg_saprintf
+			("GET %s/appsvc/appmsg_ver8.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s HTTP/1.0\r\n"
+			"Host: " GG_APPMSG_HOST "\r\n"
+			"%s"
+			"\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : "");
+	}
+
+	if (req == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n");
+		e->event.failure = GG_FAILURE_PROXY;
+		return GG_ACTION_FAIL;
+	}
+
+	req_len = strlen(req);
+
+	free(auth);
+	free(client);
+
+	gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// sending http query:\n%s", req);
+
+	res = send(sess->fd, req, req_len, 0);
+
+	free(req);
+	
+	if (res == -1 && errno != EINTR && errno != EAGAIN) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n");
+		e->event.failure = (!proxy) ? GG_FAILURE_HUB : GG_FAILURE_PROXY;
+		return GG_ACTION_FAIL;
+	}
+
+	if ((size_t) res < req_len) {
+		sess->state = alt_state;
+		sess->check = GG_CHECK_WRITE;
+		sess->timeout = GG_DEFAULT_TIMEOUT;
+	} else {
+		sess->state = next_state;
+		sess->check = GG_CHECK_READ;
+		sess->timeout = GG_DEFAULT_TIMEOUT;
+	}
+
+	return GG_ACTION_WAIT;
+}
+
+static gg_action_t gg_handle_sending_hub_proxy(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	if (gg_send_queued_data(sess) == -1) {
+		e->event.failure = GG_FAILURE_WRITING;
+		return GG_ACTION_FAIL;
+	}
+
+	if (sess->send_left > 0)
+		return GG_ACTION_WAIT;
+
+	sess->state = next_state;
+	sess->check = GG_CHECK_READ;
+	sess->timeout = GG_DEFAULT_TIMEOUT;
+
+	return GG_ACTION_WAIT;
+}
+
+static gg_action_t gg_handle_reading_hub_proxy(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	char buf[1024], *tmp, host[128];
+	int port = GG_DEFAULT_PORT;
+	int reply;
+	const char *body;
+	struct in_addr addr;
+	int res;
+	char **host_white;
+	char *host_white_default[] = GG_DEFAULT_HOST_WHITE_LIST;
+
+	res = recv(sess->fd, buf, sizeof(buf), 0);
+
+	if (res == -1 && (errno == EAGAIN || errno == EINTR)) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical recv error (errno=%d, %s)\n", errno, strerror(errno));
+		return GG_ACTION_WAIT;
+	}
+
+	if (res == -1) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() recv error (errno=%d, %s)\n", errno, strerror(errno));
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	if (res != 0) {
+		tmp = realloc(sess->recv_buf, sess->recv_done + res + 1);
+
+		if (tmp == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for http reply\n");
+			return GG_ACTION_FAIL;
+		}
+
+		sess->recv_buf = tmp;
+		memcpy(sess->recv_buf + sess->recv_done, buf, res);
+		sess->recv_done += res;
+		sess->recv_buf[sess->recv_done] = 0;
+	}
+
+	if (res == 0 && sess->recv_buf == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection closed\n");
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	if (res != 0)
+		return GG_ACTION_WAIT;
+
+	gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// received http reply:\n%s", sess->recv_buf);
+
+	res = sscanf(sess->recv_buf, "HTTP/1.%*d %3d ", &reply);
+
+	/* sprawdzamy, czy wszystko w porządku. */
+	if (res != 1 || reply != 200) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n");
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	/* szukamy początku treści */
+	body = strstr(sess->recv_buf, "\r\n\r\n");
+	
+	if (body == NULL) {
+		body = strstr(sess->recv_buf, "\n\n");
+
+		if (body == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't find body\n");
+			e->event.failure = GG_FAILURE_CONNECTING;
+			return GG_ACTION_FAIL;
+		} else {
+			body += 2;
+		}
+	} else {
+		body += 4;
+	}
+
+	// 17591 0 91.197.13.71:8074 91.197.13.71
+	res = sscanf(body, "%d %*d %128s", &reply, host);
+
+	if (res != 2) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid hub reply, connection failed\n");
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "reply=%d, host=\"%s\"\n", reply, host);
+
+	/* jeśli pierwsza liczba w linii nie jest równa zeru,
+	 * oznacza to, że mamy wiadomość systemową. */
+	if (reply != 0) {
+		tmp = strchr(body, '\n');
+
+		if (tmp != NULL) {
+			e->type = GG_EVENT_MSG;
+			e->event.msg.msgclass = reply;
+			e->event.msg.sender = 0;
+			e->event.msg.message = (unsigned char*) strdup(tmp + 1);
+
+			if (e->event.msg.message == NULL) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for system message\n");
+				return GG_ACTION_FAIL;
+			}
+		}
+	}
+
+	gg_close(sess);
+
+	tmp = strchr(host, ':');
+
+	if (tmp != NULL) {
+		*tmp = 0;
+		port = atoi(tmp + 1);
+	}
+
+	if (strcmp(host, "notoperating") == 0) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno));
+		e->event.failure = GG_FAILURE_UNAVAILABLE;
+		return GG_ACTION_FAIL;
+	}
+
+	addr.s_addr = inet_addr(host);
+	if (addr.s_addr == INADDR_NONE)
+		addr.s_addr = 0;
+	sess->server_addr = addr.s_addr;
+
+	free(sess->recv_buf);
+	sess->recv_buf = NULL;
+	sess->recv_done = 0;
+
+	if (sess->state != GG_STATE_READING_PROXY_HUB) {
+		if (sess->port == 0) {
+			sess->connect_port[0] = port;
+			sess->connect_port[1] = (port != GG_HTTPS_PORT) ? GG_HTTPS_PORT : 0;
+		} else {
+			sess->connect_port[0] = sess->port;
+			sess->connect_port[1] = 0;
+		}
+	} else {
+		sess->connect_port[0] = (sess->port == 0) ? GG_HTTPS_PORT : sess->port;
+		sess->connect_port[1] = 0;
+	}
+
+	free(sess->connect_host);
+	sess->connect_host = strdup(host);
+
+	if (sess->connect_host == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory\n");
+		return GG_ACTION_FAIL;
+	}
+
+	host_white = sess->private_data->host_white_list;
+	if (!host_white)
+		host_white = host_white_default;
+
+	if (sess->ssl_flag == GG_SSL_REQUIRED && host_white[0] != NULL) {
+		int host_ok = 0;
+		char **it;
+		int host_len;
+
+		host_len = strlen(sess->connect_host);
+
+		for (it = host_white; *it != NULL; it++) {
+			const char *white = *it;
+			int white_len, dom_offset;
+
+			white_len = strlen(white);
+			if (white_len > host_len)
+				continue;
+
+			dom_offset = host_len - white_len;
+			if (strncasecmp(sess->connect_host + dom_offset, white,
+				white_len) != 0)
+			{
+				continue;
+			}
+
+			if (white_len < host_len) {
+				if (sess->connect_host[dom_offset - 1] != '.')
+					continue;
+			}
+
+			host_ok = 1;
+			break;
+		}
+
+		if (!host_ok) {
+			gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+				"// gg_watch_fd() the HUB server returned "
+				"a host that is not trusted (%s)\n",
+				sess->connect_host);
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+	}
+
+	if (sess->state == GG_STATE_READING_HUB)
+		sess->resolver_host = sess->connect_host;
+
+	/* Jeśli łączymy się przez proxy, zacznijmy od początku listy */
+	sess->resolver_index = 0;
+
+	sess->state = (sess->async) ? next_state : alt_state;
+
+	return GG_ACTION_NEXT;
+}
+
+static gg_action_t gg_handle_send_proxy_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	char *req, *auth;
+	size_t req_len;
+	int res;
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state));
+
+	if (sess->connect_index > 1 || sess->connect_port[sess->connect_index] == 0) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of connection candidates\n");
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	auth = gg_proxy_auth();
+
+	req = gg_saprintf("CONNECT %s:%d HTTP/1.0\r\n%s\r\n", sess->connect_host, sess->connect_port[sess->connect_index], (auth) ? auth : "");
+
+	free(auth);
+
+	sess->connect_index++;
+
+	if (req == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory\n");
+		e->event.failure = GG_FAILURE_PROXY;
+		return GG_ACTION_FAIL;
+	}
+
+	req_len = strlen(req);
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n%s", req);
+
+	res = send(sess->fd, req, req_len, 0);
+
+	free(req);
+
+	if (res == -1 && errno != EINTR && errno != EAGAIN) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n");
+		e->event.failure = GG_FAILURE_PROXY;
+		return GG_ACTION_FAIL;
+	}
+
+	if ((size_t) res < req_len) {
+		sess->state = alt_state;
+		sess->check = GG_CHECK_WRITE;
+		sess->timeout = GG_DEFAULT_TIMEOUT;
+	} else {
+		sess->state = next_state;
+		sess->check = GG_CHECK_READ;
+		sess->timeout = GG_DEFAULT_TIMEOUT;
+	}
+
+	return GG_ACTION_WAIT;
+}
+
+static gg_action_t gg_handle_tls_negotiation(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	int valid_hostname = 0;
+
+#ifdef GG_CONFIG_HAVE_GNUTLS
+	unsigned int status;
+	int res;
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n");
+
+	for (;;) {
+		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");
+
+			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;
+			return GG_ACTION_WAIT;
+		}
+
+		if (res == GNUTLS_E_INTERRUPTED) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_INTERRUPTED\n");
+			continue;
+		}
+
+		if (res != GNUTLS_E_SUCCESS) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake error: %d, %s\n", res, gnutls_strerror(res));
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+
+		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);
+
+					if (gnutls_x509_crt_check_hostname(cert, sess->connect_host) != 0)
+						valid_hostname = 1;
+				}
+			}
+
+			gnutls_x509_crt_deinit(cert);
+		}
+	}
+
+	res = gnutls_certificate_verify_peers2(GG_SESSION_GNUTLS(sess), &status);
+
+	if (res != 0 || status != 0) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING!  unable to verify peer certificate: 0x%x, %d, %s\n", status, res, gnutls_strerror(res));
+
+		if (sess->ssl_flag == GG_SSL_REQUIRED) {
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+	} else {
+		gg_debug_session(sess, GG_DEBUG_MISC, "//   verified peer certificate\n");
+	}
+
+
+#elif defined GG_CONFIG_HAVE_OPENSSL
+
+	X509 *peer;
+	int res;
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state));
+
+	res = SSL_connect(GG_SESSION_OPENSSL(sess));
+
+	if (res <= 0) {
+		int err;
+		
+		err = SSL_get_error(GG_SESSION_OPENSSL(sess), res);
+
+		if (res == 0) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n");
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+
+		if (err == SSL_ERROR_WANT_READ) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n");
+
+			sess->check = GG_CHECK_READ;
+			sess->timeout = GG_DEFAULT_TIMEOUT;
+			return GG_ACTION_WAIT;
+		} else if (err == SSL_ERROR_WANT_WRITE) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n");
+
+			sess->check = GG_CHECK_WRITE;
+			sess->timeout = GG_DEFAULT_TIMEOUT;
+			return GG_ACTION_WAIT;
+		} else {
+			char buf[256];
+
+			ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
+
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf);
+
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+	}
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n//   cipher: %s\n", SSL_get_cipher_name(GG_SESSION_OPENSSL(sess)));
+
+	peer = SSL_get_peer_certificate(GG_SESSION_OPENSSL(sess));
+
+	if (peer == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! unable to get peer certificate!\n");
+
+		if (sess->ssl_flag == GG_SSL_REQUIRED) {
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+	} else {
+		char buf[256];
+		long res;
+
+		X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf));
+		gg_debug_session(sess, GG_DEBUG_MISC, "//   cert subject: %s\n", buf);
+
+		X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf));
+		gg_debug_session(sess, GG_DEBUG_MISC, "//   cert issuer: %s\n", buf);
+
+		res = SSL_get_verify_result(GG_SESSION_OPENSSL(sess));
+
+		if (res != X509_V_OK) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING!  unable to verify peer certificate! res=%ld\n", res);
+
+			if (sess->ssl_flag == GG_SSL_REQUIRED) {
+				e->event.failure = GG_FAILURE_TLS;
+				return GG_ACTION_FAIL;
+			}
+		} else {
+			gg_debug_session(sess, GG_DEBUG_MISC, "//   verified peer certificate\n");
+		}
+
+		if (X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, buf, sizeof(buf)) == -1)
+			buf[0] = 0;
+
+		/* Obsługa certyfikatów z wieloznacznikiem */
+		if (strchr(buf, '*') == buf && strchr(buf + 1, '*') == NULL) {
+			char *tmp;
+
+			tmp = strchr(sess->connect_host, '.');
+
+			if (tmp != NULL)
+				valid_hostname = (strcasecmp(tmp, buf + 1) == 0);
+		} else {
+			valid_hostname = (strcasecmp(sess->connect_host, buf) == 0);
+		}
+	}
+
+#else
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() no SSL support\n");
+	e->event.failure = GG_FAILURE_TLS;
+	return GG_ACTION_FAIL;
+
+#endif
+
+	if (!valid_hostname) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING!  unable to verify hostname\n");
+
+		if (sess->ssl_flag == GG_SSL_REQUIRED) {
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+	}
+
+	sess->state = next_state;
+	sess->check = GG_CHECK_READ;
+	sess->timeout = GG_DEFAULT_TIMEOUT;
+
+	return GG_ACTION_WAIT;
+}
+
+static gg_action_t gg_handle_reading_proxy_gg(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	char buf[256];
+	int res;
+	int reply;
+	char *body;
+
+	res = recv(sess->fd, buf, sizeof(buf), 0);
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "recv() = %d\n", res);
+
+	if (res == -1 && (errno == EAGAIN || errno == EINTR)) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical recv error (errno=%d, %s)\n", errno, strerror(errno));
+		return GG_ACTION_WAIT;
+	}
+
+	if (res == -1) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() recv error (errno=%d, %s)\n", errno, strerror(errno));
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	if (res != 0) {
+		char *tmp;
+
+		tmp = realloc(sess->recv_buf, sess->recv_done + res + 1);
+
+		if (tmp == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for http reply\n");
+			return GG_ACTION_FAIL;
+		}
+
+		sess->recv_buf = tmp;
+		memcpy(sess->recv_buf + sess->recv_done, buf, res);
+		sess->recv_done += res;
+		sess->recv_buf[sess->recv_done] = 0;
+	}
+
+	if (res == 0 && sess->recv_buf == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection closed\n");
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	/* szukamy początku treści */
+	body = strstr(sess->recv_buf, "\r\n\r\n");
+	
+	if (body == NULL) {
+		body = strstr(sess->recv_buf, "\n\n");
+
+		if (body == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't find body\n");
+			e->event.failure = GG_FAILURE_CONNECTING;
+			return GG_ACTION_FAIL;
+		} else {
+			body += 2;
+		}
+	} else {
+		body += 4;
+	}
+
+	if (res != 0 && body == NULL)
+		return GG_ACTION_WAIT;
+
+	gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// received proxy reply:\n%s\n", sess->recv_buf);
+
+	res = sscanf(sess->recv_buf, "HTTP/1.%*d %3d ", &reply);
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "res = %d, reply = %d\n", res, reply);
+
+	/* sprawdzamy, czy wszystko w porządku. */
+	if (res != 1 || reply != 200) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n");
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	if (body == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't find body\n");
+		e->event.failure = GG_FAILURE_CONNECTING;
+		return GG_ACTION_FAIL;
+	}
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// found body!\n");
+
+	if (sess->ssl_flag != GG_SSL_DISABLED) {
+		if (gg_session_init_ssl(sess) == -1) {
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+
+		/* Teoretycznie SSL jest inicjowany przez klienta, więc serwer
+		 * nie powinien niczego wysłać. */
+		if (sess->recv_buf + sess->recv_done > body) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() unexpected SSL data\n");
+			e->event.failure = GG_FAILURE_TLS;
+			return GG_ACTION_FAIL;
+		}
+
+		free(sess->recv_buf);
+		sess->recv_buf = NULL;
+		sess->recv_done = 0;
+
+		sess->state = alt_state;
+		sess->check = GG_CHECK_WRITE;
+		sess->timeout = GG_DEFAULT_TIMEOUT;
+
+		return GG_ACTION_WAIT;
+	}
+
+	sess->state = next_state;
+	sess->check = GG_CHECK_READ;
+	sess->timeout = GG_DEFAULT_TIMEOUT;	/* Pierwszy pakiet musi przyjść */
+
+	// Jeśli zbuforowaliśmy za dużo, przeanalizuj
+
+	if (sess->recv_buf + sess->recv_done > body) {
+		sess->recv_done = sess->recv_done - (body - sess->recv_buf);
+		memmove(sess->recv_buf, body, sess->recv_done);
+		sess->state = alt2_state;
+		return GG_ACTION_NEXT;
+	} else {
+		free(sess->recv_buf);
+		sess->recv_buf = NULL;
+		sess->recv_done = 0;
+	}
+
+	return GG_ACTION_WAIT;
+}
+
+static gg_action_t gg_handle_connected(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+#if 0
+	char buf[1024];
+	int res;
+
+	if (gg_send_queued_data(sess) == -1)
+		return GG_ACTION_FAIL;
+
+	res = gg_read(sess, buf, sizeof(buf));
+
+	if (res == -1 && (errno == EAGAIN || errno == EINTR)) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() non-critical read error (errno=%d, %s)\n", errno, strerror(errno));
+		return GG_ACTION_WAIT;
+	}
+	
+	if (res == -1 || res == 0) {
+		if (res == -1)
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() read error (errno=%d, %s)\n", errno, strerror(errno));
+		else
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection closed\n");
+
+		if (sess->state == GG_STATE_DISCONNECTING && res == 0) {
+			e->type = GG_EVENT_DISCONNECT_ACK;
+		} else if (sess->state == GG_STATE_READING_KEY) {
+			e->event.failure = GG_FAILURE_INVALID;
+			return GG_ACTION_FAIL;
+		}
+
+		return GG_ACTION_FAIL;
+	}
+
+	gg_debug_dump(sess, GG_DEBUG_DUMP, buf, res);
+
+	if (gg_session_handle_data(sess, buf, res, e) == -1)
+		return GG_ACTION_FAIL;
+
+	if (sess->send_buf != NULL)
+		sess->check |= GG_CHECK_WRITE;
+
+	return GG_ACTION_WAIT;
+#else
+	struct gg_header *gh;
+
+	if (gg_send_queued_data(sess) == -1)
+		return GG_ACTION_FAIL;
+
+	gh = gg_recv_packet(sess);
+
+	if (gh == NULL) {
+		if (sess->state == GG_STATE_DISCONNECTING) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection broken expectedly\n");
+			e->type = GG_EVENT_DISCONNECT_ACK;
+			return GG_ACTION_WAIT;
+		}
+
+		if (errno != EAGAIN) {
+			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno));
+			return GG_ACTION_FAIL;
+		}
+	} else {
+		if (gg_session_handle_packet(sess, gh->type, (const char *) gh + sizeof(struct gg_header), gh->length, e) == -1) {
+			free(gh);
+			return GG_ACTION_FAIL;
+		}
+
+		free(gh);
+	}
+
+	sess->check = GG_CHECK_READ;
+
+	if (sess->send_buf != NULL)
+		sess->check |= GG_CHECK_WRITE;
+
+	return GG_ACTION_WAIT;
+#endif
+}
+
+static gg_action_t gg_handle_error(struct gg_session *sess, struct gg_event *e, enum gg_state_t next_state, enum gg_state_t alt_state, enum gg_state_t alt2_state)
+{
+	struct gg_session_private *p = sess->private_data;
+
+	gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_handle_error() failure=%d\n", p->socket_failure);
+
+	e->event.failure = p->socket_failure;
+
+	return GG_ACTION_FAIL;
+}
+
+static const gg_state_transition_t handlers[] =
+{
+	{ GG_STATE_RESOLVE_HUB_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_HUB, GG_STATE_SEND_HUB, 0 },
+	{ GG_STATE_RESOLVE_GG_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_GG, GG_STATE_READING_KEY, 0 },
+	{ GG_STATE_RESOLVE_PROXY_HUB_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_PROXY_HUB, GG_STATE_SEND_PROXY_HUB, 0 },
+	{ GG_STATE_RESOLVE_PROXY_GG_SYNC, gg_handle_resolve_sync, GG_STATE_CONNECT_PROXY_GG, GG_STATE_SEND_PROXY_GG, 0 },
+
+	{ GG_STATE_RESOLVE_HUB_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_HUB, GG_STATE_SEND_HUB, 0 },
+	{ GG_STATE_RESOLVE_GG_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_GG, GG_STATE_READING_KEY, 0 },
+	{ GG_STATE_RESOLVE_PROXY_HUB_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_PROXY_HUB, GG_STATE_SEND_PROXY_HUB, 0 },
+	{ GG_STATE_RESOLVE_PROXY_GG_ASYNC, gg_handle_resolve_async, GG_STATE_RESOLVING_PROXY_GG, GG_STATE_SEND_PROXY_GG, 0 },
+
+	{ GG_STATE_RESOLVING_HUB, gg_handle_resolving, GG_STATE_CONNECT_HUB, 0, 0 },
+	{ GG_STATE_RESOLVING_GG, gg_handle_resolving, GG_STATE_CONNECT_GG, 0, 0 },
+	{ GG_STATE_RESOLVING_PROXY_HUB, gg_handle_resolving, GG_STATE_CONNECT_PROXY_HUB, 0, 0 },
+	{ GG_STATE_RESOLVING_PROXY_GG, gg_handle_resolving, GG_STATE_CONNECT_PROXY_GG, 0, 0 },
+
+	{ GG_STATE_CONNECT_HUB, gg_handle_connect, GG_STATE_CONNECTING_HUB, 0, 0 },
+	{ GG_STATE_CONNECT_PROXY_HUB, gg_handle_connect, GG_STATE_CONNECTING_PROXY_HUB, 0, 0 },
+	{ GG_STATE_CONNECT_PROXY_GG, gg_handle_connect, GG_STATE_CONNECTING_PROXY_GG, 0, 0 },
+
+	{ GG_STATE_CONNECT_GG, gg_handle_connect_gg, GG_STATE_CONNECTING_GG, 0, 0 },
+
+	{ GG_STATE_CONNECTING_HUB, gg_handle_connecting, GG_STATE_SEND_HUB, GG_STATE_CONNECT_HUB, 0 },
+	{ GG_STATE_CONNECTING_PROXY_HUB, gg_handle_connecting, GG_STATE_SEND_PROXY_HUB, GG_STATE_CONNECT_PROXY_HUB, 0 },
+	{ GG_STATE_CONNECTING_PROXY_GG, gg_handle_connecting, GG_STATE_SEND_PROXY_GG, GG_STATE_CONNECT_PROXY_GG, 0 },
+
+	{ GG_STATE_CONNECTING_GG, gg_handle_connecting_gg, GG_STATE_READING_KEY, GG_STATE_CONNECT_GG, GG_STATE_TLS_NEGOTIATION },
+
+	{ GG_STATE_SEND_HUB, gg_handle_send_hub, GG_STATE_READING_HUB, GG_STATE_SENDING_HUB, 0 },
+	{ GG_STATE_SEND_PROXY_HUB, gg_handle_send_hub, GG_STATE_READING_PROXY_HUB, GG_STATE_SENDING_PROXY_HUB, 0 },
+
+	{ GG_STATE_SEND_PROXY_GG, gg_handle_send_proxy_gg, GG_STATE_READING_PROXY_GG, GG_STATE_SENDING_PROXY_GG, 0 },
+
+	{ GG_STATE_SENDING_HUB, gg_handle_sending_hub_proxy, GG_STATE_READING_HUB, 0, 0 },
+	{ GG_STATE_SENDING_PROXY_HUB, gg_handle_sending_hub_proxy, GG_STATE_READING_PROXY_HUB, 0, 0 },
+	{ GG_STATE_SENDING_PROXY_GG, gg_handle_sending_hub_proxy, GG_STATE_READING_PROXY_GG, 0, 0 },
+
+	{ GG_STATE_READING_HUB, gg_handle_reading_hub_proxy, GG_STATE_RESOLVE_GG_ASYNC, GG_STATE_RESOLVE_GG_SYNC, 0 },
+	{ GG_STATE_READING_PROXY_HUB, gg_handle_reading_hub_proxy, GG_STATE_CONNECT_PROXY_GG, GG_STATE_CONNECT_PROXY_GG, 0 },
+
+	{ GG_STATE_READING_PROXY_GG, gg_handle_reading_proxy_gg, GG_STATE_READING_KEY, GG_STATE_TLS_NEGOTIATION, GG_STATE_READING_KEY },
+
+	{ GG_STATE_TLS_NEGOTIATION, gg_handle_tls_negotiation, GG_STATE_READING_KEY, 0, 0 },
+
+	{ GG_STATE_READING_KEY, gg_handle_connected, 0, 0, 0 },
+	{ GG_STATE_READING_REPLY, gg_handle_connected, 0, 0, 0 },
+	{ GG_STATE_CONNECTED, gg_handle_connected, 0, 0, 0 },
+	{ GG_STATE_DISCONNECTING, gg_handle_connected, 0, 0, 0 },
+	{ GG_STATE_ERROR, gg_handle_error, 0, 0, 0 },
+};
+
+struct gg_event *gg_eventqueue_add(struct gg_session *sess)
+{
+	struct gg_event *ge;
+	gg_eventqueue_t *queue_el, *it;
+
+	queue_el = gg_new0(sizeof(gg_eventqueue_t));
+	ge = gg_new0(sizeof(struct gg_event));
+
+	if (queue_el == NULL || ge == NULL) {
+		free(queue_el);
+		free(ge);
+		return NULL;
+	}
+
+	ge->type = GG_EVENT_NONE;
+
+	queue_el->event = ge;
+	if (sess->private_data->event_queue == NULL)
+		sess->private_data->event_queue = queue_el;
+	else {
+		it = sess->private_data->event_queue;
+		while (it->next != NULL)
+			it = it->next;
+		it->next = queue_el;
+	}
+
+	return ge;
+}
+
+/**
  * Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze sesji.
  *
  * Funkcja zwraca strukturę zdarzenia \c gg_event. Jeśli rodzaj zdarzenia
@@ -227,787 +1697,99 @@
  */
 struct gg_event *gg_watch_fd(struct gg_session *sess)
 {
-	struct gg_event *e;
-	int res = 0;
-	int port = 0;
-	int errno2 = 0;
+	struct gg_event *ge;
+	struct gg_session_private *priv;
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess);
 
-	if (!sess) {
+	if (sess == NULL) {
 		errno = EFAULT;
 		return NULL;
 	}
 
-	if (!(e = (void*) calloc(1, sizeof(*e)))) {
+	priv = sess->private_data;
+
+	if (priv->event_queue != NULL) {
+		gg_eventqueue_t *next;
+
+		ge = priv->event_queue->event;
+		next = priv->event_queue->next;
+		free(priv->event_queue);
+		priv->event_queue = next;
+
+		if (next == NULL) {
+			sess->check = priv->check_after_queue;
+			sess->fd = priv->fd_after_queue;
+		}
+		return ge;
+	}
+
+	ge = malloc(sizeof(struct gg_event));
+
+	if (ge == NULL) {
 		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n");
 		return NULL;
 	}
 
-	e->type = GG_EVENT_NONE;
-
-	if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED)) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending %d bytes of queued data\n", sess->send_left);
-
-		res = write(sess->fd, sess->send_buf, sess->send_left);
-
-		if (res == -1 && errno != EAGAIN) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno));
-
-			if (sess->state == GG_STATE_READING_REPLY)
-				e->event.failure = GG_FAILURE_CONNECTING;
-
-			goto fail;
-		}
+	memset(ge, 0, sizeof(struct gg_event));
 
-		if (res == sess->send_left) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent all queued data\n");
-			free(sess->send_buf);
-			sess->send_buf = NULL;
-			sess->send_left = 0;
-		} else if (res > 0) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent %d bytes of queued data, %d bytes left\n", res, sess->send_left - res);
-
-			memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res);
-			sess->send_left -= res;
-		}
-
-		res = 0;
-	}
-
-	switch (sess->state) {
-		case GG_STATE_RESOLVING:
-		{
-			struct in_addr addr;
-			int failed = 0;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n");
+	ge->type = GG_EVENT_NONE;
 
-			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_proxy_hub;
-			}
-
-			/* jeśli jesteśmy w resolverze i mamy ustawiony port
-			 * proxy, znaczy, że resolvowaliśmy proxy. zatem
-			 * wpiszmy jego adres. */
-			if (sess->proxy_port)
-				sess->proxy_addr = addr.s_addr;
-
-			/* zapiszmy sobie adres huba i adres serwera (do
-			 * bezpośredniego połączenia, jeśli hub leży)
-			 * z resolvera. */
-			if (sess->proxy_addr && sess->proxy_port)
-				port = sess->proxy_port;
-			else {
-				sess->server_addr = sess->hub_addr = addr.s_addr;
-				port = GG_APPMSG_PORT;
-			}
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port);
-
-			/* łączymy się albo z hubem, albo z proxy, zależnie
-			 * od tego, co resolvowaliśmy. */
-			if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) {
-				/* jeśli w trybie asynchronicznym gg_connect()
-				 * zwróci błąd, nie ma sensu próbować dalej. */
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno));
-				goto fail_proxy_hub;
-			}
+	for (;;) {
+		unsigned int i, found = 0;
+		gg_action_t res;
 
-			/* jeśli podano serwer i łączmy się przez proxy,
-			 * jest to bezpośrednie połączenie, inaczej jest
-			 * do huba. */
-
-			if (sess->proxy_addr && sess->proxy_port && sess->server_addr) {
-				sess->state = GG_STATE_CONNECTING_GG;
-				sess->soft_timeout = 1;
-			} else
-				sess->state = GG_STATE_CONNECTING_HUB;
-
-			sess->check = GG_CHECK_WRITE;
-			sess->timeout = GG_DEFAULT_TIMEOUT;
-
-			break;
-		}
-
-		case GG_STATE_CONNECTING_HUB:
-		{
-			char buf[1024], *client, *auth;
-			int res = 0;
-			socklen_t res_size = sizeof(res);
-			const char *host;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n");
-
-			/* jeśli asynchroniczne, sprawdzamy, czy nie wystąpił
-			 * przypadkiem jakiś błąd. */
-			if (sess->async && (getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to %s failed (errno=%d, %s)\n", (sess->proxy_addr && sess->proxy_port) ? "proxy" : "hub", res, strerror(res));
-				goto fail_proxy_hub;
-			}
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n");
-
-			if (sess->client_version != NULL && isdigit(sess->client_version[0]))
-				client = gg_urlencode(sess->client_version);
-			else
-				client = gg_urlencode(GG_DEFAULT_CLIENT_VERSION);
-
-			if (client == NULL) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n");
-				goto fail;
-			}
+		res = GG_ACTION_FAIL;
 
-			if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port)
-				host = "http://" GG_APPMSG_HOST;
-			else
-				host = "";
-
-			auth = gg_proxy_auth();
-
-#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL)
-			if (sess->ssl != NULL) {
-				snprintf(buf, sizeof(buf) - 1,
-					"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"
-					"%s"
-					"\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : "");
-			} else
-#endif
-			{
-				snprintf(buf, sizeof(buf) - 1,
-					"GET %s/appsvc/appmsg_ver8.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s HTTP/1.0\r\n"
-					"Host: " GG_APPMSG_HOST "\r\n"
-					"%s"
-					"\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : "");
+		for (i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) {
+			if (handlers[i].state == (enum gg_state_t) sess->state) {
+				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() %s\n", gg_debug_state(sess->state));
+				res = (*handlers[i].handler)(sess, ge, handlers[i].next_state, handlers[i].alt_state, handlers[i].alt2_state);
+				found = 1;
+				break;
 			}
-
-			free(auth);
-			free(client);
-
-			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ę
-			 * do bufora gniazda. jeśli write() zwróci mniej,
-			 * stało się coś złego. */
-			if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n");
-				goto fail_proxy_hub;
-			}
-
-			sess->state = GG_STATE_READING_DATA;
-			sess->check = GG_CHECK_READ;
-			sess->timeout = GG_DEFAULT_TIMEOUT;
-
-			break;
 		}
 
-		case GG_STATE_READING_DATA:
-		{
-			char buf[1024], *tmp, *host;
-			int port = GG_DEFAULT_PORT;
-			struct in_addr addr;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n");
-
-			/* czytamy linię z gniazda i obcinamy \r\n. */
-			gg_read_line(sess->fd, buf, sizeof(buf) - 1);
-			gg_chomp(buf);
-			gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf);
-
-			/* sprawdzamy, czy wszystko w porządku. */
-			if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n");
-				goto fail_proxy_hub;
-			}
-
-			/* ignorujemy resztę nagłówka. */
-			while (strcmp(buf, "\r\n") && strcmp(buf, ""))
-				gg_read_line(sess->fd, buf, sizeof(buf) - 1);
-
-			/* czytamy pierwszą linię danych. */
-			if (gg_read_line(sess->fd, buf, sizeof(buf) - 1) == NULL) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() read error\n");
-				goto fail_proxy_hub;
-			}
-			gg_chomp(buf);
-
-			/* jeśli pierwsza liczba w linii nie jest równa zeru,
-			 * oznacza to, że mamy wiadomość systemową. */
-			if (atoi(buf)) {
-				char tmp[1024], *foo, *sysmsg_buf = NULL;
-				int len = 0;
-
-				while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) {
-					if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) {
-						gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n");
-						break;
-					}
-
-					sysmsg_buf = foo;
-
-					if (!len)
-						strcpy(sysmsg_buf, tmp);
-					else
-						strcat(sysmsg_buf, tmp);
-
-					len += strlen(tmp);
-				}
-
-				e->type = GG_EVENT_MSG;
-				e->event.msg.msgclass = atoi(buf);
-				e->event.msg.sender = 0;
-				e->event.msg.message = (unsigned char*) sysmsg_buf;
-			}
-
-			close(sess->fd);
-			sess->fd = -1;
-
-			gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http data (%s)\n", buf);
-
-			/* analizujemy otrzymane dane. */
-			tmp = buf;
-
-			while (*tmp && *tmp != ' ')
-				tmp++;
-			while (*tmp && *tmp == ' ')
-				tmp++;
-			while (*tmp && *tmp != ' ')
-				tmp++;
-			while (*tmp && *tmp == ' ')
-				tmp++;
-			host = tmp;
-			while (*tmp && *tmp != ' ')
-				tmp++;
-			*tmp = 0;
-
-			if ((tmp = strchr(host, ':'))) {
-				*tmp = 0;
-				port = atoi(tmp + 1);
-			}
-
-			if (strcmp(host, "") == 0) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid response\n");
-				e->event.failure = GG_FAILURE_HUB;
-				goto fail;
-			}
-
-			if (!strcmp(host, "notoperating")) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno));
-				e->event.failure = GG_FAILURE_UNAVAILABLE;
-				goto fail;
-			}
-
-			addr.s_addr = inet_addr(host);
-			sess->server_addr = addr.s_addr;
-
-			if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) {
-				/* jeśli mamy proxy, łączymy się z nim. */
-				if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) {
-					/* nie wyszło? trudno. */
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno));
-					e->event.failure = GG_FAILURE_PROXY;
-					goto fail;
-				}
-
-				sess->state = GG_STATE_CONNECTING_GG;
-				sess->check = GG_CHECK_WRITE;
-				sess->timeout = GG_DEFAULT_TIMEOUT;
-				sess->soft_timeout = 1;
-				break;
-			}
-
-			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;
-				}
-
-				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));
-					e->event.failure = GG_FAILURE_CONNECTING;
-					goto fail;
-				}
-			}
-
-			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;
-				e->event.failure = GG_FAILURE_RESOLVING;
-				goto fail;
-			}
-
-			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));
-
-				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));
-					e->event.failure = GG_FAILURE_CONNECTING;
-					goto fail;
-				}
-			}
-
-			sess->state = GG_STATE_CONNECTING_GG;
-			sess->check = GG_CHECK_WRITE;
-			sess->timeout = GG_DEFAULT_TIMEOUT;
-			sess->soft_timeout = 1;
-
-			break;
+		if (!found) {
+			gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_watch_fd() invalid state %s\n", gg_debug_state(sess->state));
+			ge->event.failure = GG_FAILURE_INTERNAL;
 		}
 
-		case GG_STATE_CONNECTING_GG:
-		{
-			int res = 0;
-			socklen_t res_size = sizeof(res);
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n");
-
-			sess->soft_timeout = 0;
-
-			/* jeśli wystąpił błąd podczas łączenia się... */
-			if (sess->async && (sess->timeout == 0 || getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) {
-				/* jeśli nie udało się połączenie z proxy,
-				 * nie mamy czego próbować więcej. */
-				if (sess->proxy_addr && sess->proxy_port) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res));
-					e->event.failure = GG_FAILURE_PROXY;
-					goto fail;
-				}
-
-				close(sess->fd);
-				sess->fd = -1;
-
-#ifdef ETIMEDOUT
-				if (sess->timeout == 0)
-					errno = ETIMEDOUT;
-#endif
-
-#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
-				 * trzeba by się bawić w tworzenie na nowo
-				 * SSL i SSL_CTX. */
-
-				if (sess->ssl) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res));
-					e->event.failure = GG_FAILURE_CONNECTING;
-					goto fail;
-				}
-#endif
-
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res));
-
-				if (sess->port == GG_HTTPS_PORT) {
-					e->event.failure = GG_FAILURE_CONNECTING;
-					goto fail;
-				}
-
-				sess->port = GG_HTTPS_PORT;
+		if (!sess->async && ge->type == GG_EVENT_NONE && res == GG_ACTION_WAIT)
+			res = GG_ACTION_NEXT;
 
-				/* próbujemy na port 443. */
-				if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno));
-					e->event.failure = GG_FAILURE_CONNECTING;
-					goto fail;
+		switch (res) {
+			case GG_ACTION_WAIT:
+				if (priv->event_queue != NULL) {
+					priv->fd_after_queue = sess->fd;
+					priv->check_after_queue = sess->check;
+					/* wymuszamy ponowne wywołanie gg_watch_fd */
+					sess->fd = gg_get_dummy_fd(sess);
+					if (sess->fd < 0)
+						sess->fd = priv->fd_after_queue;
+					sess->check = GG_CHECK_READ | GG_CHECK_WRITE;
 				}
-
-				sess->state = GG_STATE_CONNECTING_GG;
-				sess->check = GG_CHECK_WRITE;
-				sess->timeout = GG_DEFAULT_TIMEOUT;
-				sess->soft_timeout = 1;
-
-				break;
-			}
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected\n");
-
-			if (gg_proxy_http_only)
-				sess->proxy_port = 0;
-
-			/* jeśli mamy proxy, wyślijmy zapytanie. */
-			if (sess->proxy_addr && sess->proxy_port) {
-				char buf[100], *auth = gg_proxy_auth();
-				struct in_addr addr;
+				return ge;
 
-				if (sess->server_addr)
-					addr.s_addr = sess->server_addr;
-				else
-					addr.s_addr = sess->hub_addr;
+			case GG_ACTION_NEXT:
+				continue;
 
-				snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(addr), sess->port);
-
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n//   %s", buf);
+			case GG_ACTION_FAIL:
+				sess->state = GG_STATE_IDLE;
 
-				/* wysyłamy zapytanie. jest ono na tyle krótkie,
-				 * że musi się zmieścić w buforze gniazda. jeśli
-				 * write() zawiedzie, stało się coś złego. */
-				if (write(sess->fd, buf, strlen(buf)) < (signed)strlen(buf)) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
-					free(auth);
-					e->event.failure = GG_FAILURE_PROXY;
-					goto fail;
-				}
+				gg_close(sess);
 
-				if (auth) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "//   %s", auth);
-					if (write(sess->fd, auth, strlen(auth)) < (signed)strlen(auth)) {
-						gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
-						free(auth);
-						e->event.failure = GG_FAILURE_PROXY;
-						goto fail;
-					}
-
-					free(auth);
+				if (ge->event.failure != 0) {
+					ge->type = GG_EVENT_CONN_FAILED;
+				} else {
+					free(ge);
+					ge = NULL;
 				}
 
-				if (write(sess->fd, "\r\n", 2) < 2) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n");
-					e->event.failure = GG_FAILURE_PROXY;
-					goto fail;
-				}
-			}
-
-#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)(long)sess->fd);
-#endif
-#ifdef GG_CONFIG_HAVE_OPENSSL
-				SSL_set_fd(sess->ssl, sess->fd);
-#endif
-
-				sess->state = GG_STATE_TLS_NEGOTIATION;
-				sess->check = GG_CHECK_WRITE;
-				sess->timeout = GG_DEFAULT_TIMEOUT;
-
-				break;
-			}
-#endif
-
-			sess->state = GG_STATE_READING_KEY;
-			sess->check = GG_CHECK_READ;
-			sess->timeout = GG_DEFAULT_TIMEOUT;
-
-			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);
-						}
-					}
-
-					gnutls_x509_crt_deinit(cert);
-				}
-			}
-
-			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:
-		{
-			int res;
-			X509 *peer;
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n");
-
-			if ((res = SSL_connect(sess->ssl)) <= 0) {
-				int err = SSL_get_error(sess->ssl, res);
-
-				if (res == 0) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n");
-
-					e->type = GG_EVENT_CONN_FAILED;
-					e->event.failure = GG_FAILURE_TLS;
-					sess->state = GG_STATE_IDLE;
-					close(sess->fd);
-					sess->fd = -1;
-					break;
-				}
-
-				if (err == SSL_ERROR_WANT_READ) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n");
-
-					sess->state = GG_STATE_TLS_NEGOTIATION;
-					sess->check = GG_CHECK_READ;
-					sess->timeout = GG_DEFAULT_TIMEOUT;
+				return ge;
 
-					break;
-				} else if (err == SSL_ERROR_WANT_WRITE) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n");
-
-					sess->state = GG_STATE_TLS_NEGOTIATION;
-					sess->check = GG_CHECK_WRITE;
-					sess->timeout = GG_DEFAULT_TIMEOUT;
-
-					break;
-				} else {
-					char buf[256];
-
-					ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
-
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf);
-
-					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//   cipher: %s\n", SSL_get_cipher_name(sess->ssl));
-
-			peer = SSL_get_peer_certificate(sess->ssl);
-
-			if (!peer)
-				gg_debug_session(sess, GG_DEBUG_MISC, "//   WARNING! unable to get peer certificate!\n");
-			else {
-				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);
-
-				X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf));
-				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;
+			/* Celowo nie ma default */
 		}
-#endif
-
-		case GG_STATE_READING_KEY:
-		case GG_STATE_READING_REPLY:
-		case GG_STATE_CONNECTED:
-		case GG_STATE_DISCONNECTING:
-		{
-			struct gg_header *gh;
-
-			if (sess->state == GG_STATE_READING_KEY)
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n");
-			else if (sess->state == GG_STATE_READING_REPLY)
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n");
-			else if (sess->state == GG_STATE_CONNECTED)
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n");
-			else if (sess->state == GG_STATE_DISCONNECTING)
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_DISCONNECTING\n");
-
-			/* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie
-			 * się tekstu wrzucanego przez proxy. */
-			if (sess->state == GG_STATE_READING_KEY && sess->proxy_addr && sess->proxy_port) {
-				char buf[100];
-
-				strcpy(buf, "");
-				gg_read_line(sess->fd, buf, sizeof(buf) - 1);
-				gg_chomp(buf);
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n//   %s\n", buf);
-
-				while (strcmp(buf, "")) {
-					gg_read_line(sess->fd, buf, sizeof(buf) - 1);
-					gg_chomp(buf);
-					if (strcmp(buf, ""))
-						gg_debug_session(sess, GG_DEBUG_MISC, "//   %s\n", buf);
-				}
-
-				/* XXX niech czeka jeszcze raz w tej samej
-				 * fazie. głupio, ale działa. */
-				sess->proxy_port = 0;
-
-				break;
-			}
-
-			sess->last_event = time(NULL);
-
-			gh = gg_recv_packet(sess);
-
-			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)
-					goto fail;
-			} else {
-				if (gg_session_handle_packet(sess, gh->type, (const char *) gh + sizeof(struct gg_header), gh->length, e) == -1) {
-					free(gh);
-					goto fail;
-				}
-
-				free(gh);
-			}
-
-			sess->check = GG_CHECK_READ;
-
-			break;
-		}
-	}
-
-	if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED))
-		sess->check |= GG_CHECK_WRITE;
-
-	return e;
-
-fail_proxy_hub:
-	if (sess->proxy_port)
-		e->event.failure = GG_FAILURE_PROXY;
-	else
-		e->event.failure = GG_FAILURE_HUB;
-
-fail:
-	sess->resolver_cleanup(&sess->resolver, 1);
-
-	sess->state = GG_STATE_IDLE;
-
-	if (sess->fd != -1) {
-		int errno2;
-
-		errno2 = errno;
-		close(sess->fd);
-		errno = errno2;
-		sess->fd = -1;
-	}
-
-	if (e->event.failure != 0) {
-		e->type = GG_EVENT_CONN_FAILED;
-		return e;
-	} else {
-		free(e);
-		return NULL;
 	}
 }
 

mercurial