diff libpurple/protocols/gg/lib/dcc7.c @ 31860:93b08d43f684

matekm and kkszysiu collaborated on this patch to update our internal libgadu to version 1.10.1.
author John Bailey <rekkanoryo@rekkanoryo.org>
date Thu, 24 Mar 2011 20:53:13 +0000
parents a8cc50c2279f
children 3a90a59ddea2
line wrap: on
line diff
--- a/libpurple/protocols/gg/lib/dcc7.c	Thu Mar 24 15:18:14 2011 +0000
+++ b/libpurple/protocols/gg/lib/dcc7.c	Thu Mar 24 20:53:13 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);
 }