libgaim/protocols/qq/qq_proxy.c

changeset 14257
6510563c1ee9
parent 14254
77edc7a6191a
child 14672
432897ba0662
--- a/libgaim/protocols/qq/qq_proxy.c	Sat Aug 19 02:26:57 2006 +0000
+++ b/libgaim/protocols/qq/qq_proxy.c	Sat Aug 19 02:32:55 2006 +0000
@@ -43,11 +43,11 @@
 #include "udp_proxy_s5.h"
 #include "utils.h"
 
-/* These functions are used only in development phase
- *
+/* These functions are used only in development phase */
+/*
 static void _qq_show_socket(gchar *desc, gint fd) {
 	struct sockaddr_in sin;
-	gint len = sizeof(sin);
+	socklen_t len = sizeof(sin);
 	getsockname(fd, (struct sockaddr *)&sin, &len);
 	gaim_debug(GAIM_DEBUG_INFO, desc, "%s:%d\n",
             inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
@@ -88,22 +88,24 @@
 	return g_memdup(pwkey_tmp, QQ_KEY_LENGTH);
 }
 
-gint _qq_fill_host(struct sockaddr_in *addr, const gchar *host, guint16 port)
+static gboolean _qq_fill_host(GSList *hosts, struct sockaddr_in *addr, gint *addr_size)
 {
-	if (!inet_aton(host, &(addr->sin_addr))) {
-		struct hostent *hp;
-		if (!(hp = gethostbyname(host))) {
-			return -1;
-		}
-		memset(addr, 0, sizeof(struct sockaddr_in));
-		memcpy(&(addr->sin_addr.s_addr), hp->h_addr, hp->h_length);
-		addr->sin_family = hp->h_addrtype;
-	} else {
-		addr->sin_family = AF_INET;
+	if (!hosts || !hosts->data)
+		return FALSE;
+
+	*addr_size = GPOINTER_TO_INT(hosts->data);
+
+	hosts = g_slist_remove(hosts, hosts->data);
+	memcpy(addr, hosts->data, *addr_size);
+	g_free(hosts->data);
+	hosts = g_slist_remove(hosts, hosts->data);
+	while(hosts) {
+		hosts = g_slist_remove(hosts, hosts->data);
+		g_free(hosts->data);
+		hosts = g_slist_remove(hosts, hosts->data);
 	}
 
-	addr->sin_port = htons(port);
-	return 0;
+	return TRUE;
 }
 
 /* set up any finalizing start-up stuff */
@@ -136,12 +138,16 @@
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
 
 	if (source < 0) {	/* socket returns -1 */
-		gaim_connection_error(gc, _("Unable to connect."));
+		gaim_connection_error(gc, error_message);
 		return;
 	}
 
 	qd = (qq_data *) gc->proto_data;
 
+	/*
+	_qq_show_socket("Got login socket", source);
+	*/
+
 	/* QQ use random seq, to minimize duplicated packets */
 	srandom(time(NULL));
 	qd->send_seq = random() & 0x0000ffff;
@@ -201,6 +207,54 @@
 	qq_buddies_list_free(gc->account, qd);
 }
 
+static void no_one_calls(gpointer data, gint source, GaimInputCondition cond)
+{
+        struct PHB *phb = data;
+	socklen_t len;
+	int error=0, ret;
+
+	gaim_debug_info("proxy", "Connected.\n");
+
+	len = sizeof(error);
+
+	/*
+	* getsockopt after a non-blocking connect returns -1 if something is
+	* really messed up (bad descriptor, usually). Otherwise, it returns 0 and
+	* error holds what connect would have returned if it blocked until now.
+	* Thus, error == 0 is success, error == EINPROGRESS means "try again",
+	* and anything else is a real error.
+	*
+	* (error == EINPROGRESS can happen after a select because the kernel can
+	* be overly optimistic sometimes. select is just a hint that you might be
+	* able to do something.)
+	*/
+	ret = getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len);
+	if (ret == 0 && error == EINPROGRESS)
+		return; /* we'll be called again later */
+	if (ret < 0 || error != 0) {
+		if(ret!=0) 
+			error = errno;
+		close(source);
+		gaim_input_remove(phb->inpa);
+
+		gaim_debug_error("proxy", "getsockopt SO_ERROR check: %s\n", strerror(error));
+
+		phb->func(phb->data, -1, _("Unable to connect"));
+		return;
+	}
+
+	gaim_input_remove(phb->inpa);
+
+	if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+		phb->func(phb->data, source, NULL);
+	}
+
+	g_free(phb->host);
+	g_free(phb);
+}
+
+/* returns -1 if fails, otherwise returns the file handle */
 static gint _qq_proxy_none(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen)
 {
 	gint fd = -1;
@@ -209,7 +263,8 @@
 	fd = socket(PF_INET, SOCK_DGRAM, 0);
 
 	if (fd < 0) {
-		gaim_debug(GAIM_DEBUG_ERROR, "QQ Redirect", "Unable to create socket: %s\n", strerror(errno));
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ Redirect", 
+			"Unable to create socket: %s\n", strerror(errno));
 		return -1;
 	}
 
@@ -238,8 +293,9 @@
 		 */
 		if ((errno == EINPROGRESS) || (errno == EINTR)) {
 			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Connect in asynchronous mode.\n");
+			phb->inpa = gaim_input_add(fd, GAIM_INPUT_WRITE, no_one_calls, phb);
 		} else {
-			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Faiil connection: %d\n", strerror(errno));
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Connection failed: %d\n", strerror(errno));
 			close(fd);
 			return -1;
 		}		/* if errno */
@@ -252,20 +308,80 @@
 	return fd;
 }
 
-/* returns the socket handler, or -1 if there is any error */
+static void _qq_proxy_resolved(GSList *hosts, gpointer data, const char *error_message)
+{
+	struct PHB *phb = (struct PHB *) data;
+	struct sockaddr_in addr;
+	gint addr_size, ret = -1;
+
+	if(_qq_fill_host(hosts, &addr, &addr_size))
+		ret = qq_proxy_socks5(phb, (struct sockaddr *) &addr, addr_size);
+
+	if (ret < 0) {
+		phb->func(phb->data, -1, _("Unable to connect"));
+		g_free(phb->host);
+		g_free(phb);
+	}
+}
+
+static void _qq_server_resolved(GSList *hosts, gpointer data, const char *error_message)
+{
+	struct PHB *phb = (struct PHB *) data;
+	GaimConnection *gc = (GaimConnection *) phb->data;
+	qq_data *qd = (qq_data *) gc->proto_data;
+	struct sockaddr_in addr;
+	gint addr_size, ret = -1;
+
+	if(_qq_fill_host(hosts, &addr, &addr_size)) {
+		switch (gaim_proxy_info_get_type(phb->gpi)) {
+			case GAIM_PROXY_NONE:
+				ret = _qq_proxy_none(phb, (struct sockaddr *) &addr, addr_size);
+				break;
+			case GAIM_PROXY_SOCKS5:
+				ret = 0;
+				if (gaim_proxy_info_get_host(phb->gpi) == NULL || 
+						gaim_proxy_info_get_port(phb->gpi) == 0) {
+					gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
+							"Use of socks5 proxy selected but host or port info doesn't exist.\n");
+					ret = -1;
+				} else {
+					/* as the destination is always QQ server during the session, 
+				 	* we can set dest_sin here, instead of _qq_s5_canread_again */
+					memcpy(&qd->dest_sin, &addr, addr_size);
+					if (gaim_dnsquery_a(gaim_proxy_info_get_host(phb->gpi),
+							gaim_proxy_info_get_port(phb->gpi),
+							_qq_proxy_resolved, phb) == NULL)
+						ret = -1;
+				}
+				break;
+			default:
+				gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
+						"Proxy type %i is unsupported, not using a proxy.\n",
+						gaim_proxy_info_get_type(phb->gpi));
+				ret = _qq_proxy_none(phb, (struct sockaddr *) &addr, addr_size);
+		}
+	}
+
+	if (ret < 0) {
+		phb->func(gc, -1, _("Unable to connect"));
+		g_free(phb->host);
+		g_free(phb);
+	}
+}
+
+/* returns -1 if dns lookup fails, otherwise returns 0 */
 static gint _qq_udp_proxy_connect(GaimAccount *account,
-			   const gchar *server,
-			   guint16 port, void callback(gpointer, gint, const gchar *error_message), GaimConnection *gc)
+			   const gchar *server, guint16 port, 
+			   void callback(gpointer, gint, const gchar *error_message), 
+			   GaimConnection *gc)
 {
-	struct sockaddr_in sin;
+	GaimProxyInfo *info;
 	struct PHB *phb;
-	GaimProxyInfo *info;
-	qq_data *qd;
+	qq_data *qd = (qq_data *) gc->proto_data;
 
-	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
-	qd = (qq_data *) gc->proto_data;
+	g_return_val_if_fail(gc != NULL && qd != NULL, -1);
 
-	info = gaim_account_get_proxy_info(account);
+	info = gaim_proxy_get_setup(account);
 
 	phb = g_new0(struct PHB, 1);
 	phb->host = g_strdup(server);
@@ -274,41 +390,24 @@
 	phb->gpi = info;
 	phb->func = callback;
 	phb->data = gc;
-
-	if (_qq_fill_host(&sin, server, port) < 0) {
-		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
-			   "gethostbyname(\"%s\", %d) failed: %s\n", server, port, hstrerror(h_errno));
-		return -1;
-	}
+	qd->proxy_type = gaim_proxy_info_get_type(phb->gpi);
 
-	if (info == NULL) {
-		qd->proxy_type = GAIM_PROXY_NONE;
-		return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin));
-	}
-
-	qd->proxy_type = info->type;
-	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Choosing proxy type %d\n", info->type);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Choosing proxy type %d\n", 
+			gaim_proxy_info_get_type(phb->gpi));
 
-	switch (info->type) {
-	case GAIM_PROXY_NONE:
-		return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin));
-	case GAIM_PROXY_SOCKS5:
-		/* as the destination is always QQ server during the session, 
-		 * we can set dest_sin here, instead of _qq_s5_canread_again */
-		_qq_fill_host(&qd->dest_sin, phb->host, phb->port);
-		_qq_fill_host(&sin, phb->gpi->host, phb->gpi->port);
-		return qq_proxy_socks5(phb, (struct sockaddr *) &sin, sizeof(sin));
-	default:
-		return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin));
+	if (gaim_dnsquery_a(server, port, _qq_server_resolved, phb) == NULL) {
+		phb->func(gc, -1, _("Unable to connect"));
+		g_free(phb->host);
+		g_free(phb);
+		return -1;
+	} else {
+		return 0;
 	}
-
-	return -1;
 }
 
 /* QQ connection via UDP/TCP. 
- * I use GAIM proxy function to provide TCP proxy support,
- * and qq_udp_proxy.c to add UDP proxy support (thanks henry)
- *  return the socket handle, -1 means fail */
+ * I use Gaim proxy function to provide TCP proxy support,
+ * and qq_udp_proxy.c to add UDP proxy support (thanks henry) */
 static gint _proxy_connect_full (GaimAccount *account, const gchar *host, guint16 port, 
 		GaimProxyConnectFunction func, gpointer data, gboolean use_tcp)
 {
@@ -320,18 +419,16 @@
 	qd->server_ip = g_strdup(host);
 	qd->server_port = port;
 
-	if (use_tcp)
-		/* TCP mode */
+	if(use_tcp)
 		return (gaim_proxy_connect(account, host, port, func, data) == NULL);
 	else
-		/* UDP mode */
 		return _qq_udp_proxy_connect(account, host, port, func, data);
 }
 
 /* establish a generic QQ connection 
- * TCP/UDP, and direct/redirected
- * return the socket handler, or -1 if there is any error */
-gint qq_connect(GaimAccount *account, const gchar *host, guint16 port, gboolean use_tcp, gboolean is_redirect)
+ * TCP/UDP, and direct/redirected */
+gint qq_connect(GaimAccount *account, const gchar *host, guint16 port, 
+		gboolean use_tcp, gboolean is_redirect)
 {
 	GaimConnection *gc;
 
@@ -385,10 +482,19 @@
 		g_memmove(buf + 4, &(qd->dest_sin.sin_addr.s_addr), 4);
 		g_memmove(buf + 8, &(qd->dest_sin.sin_port), 2);
 		g_memmove(buf + 10, data, len);
+		errno = 0;
 		ret = send(qd->fd, buf, len + 10, 0);
 	} else {
+		errno = 0;
 		ret = send(qd->fd, data, len, 0);
 	}
+	if (ret == -1) {
+		gaim_connection_error(qd->gc, _("Socket send error"));
+		return ret;
+	} else if (errno == ECONNREFUSED) {
+		gaim_connection_error(qd->gc, _("Connection refused"));
+		return ret;
+	}
 
 	return ret;
 }

mercurial