libpurple/proxy.c
author Bjoern Voigt <bjoern@cs.tu-berlin.de>
Wed, 27 Feb 2013 12:00:44 +0100
branchrelease-2.x.y
changeset 50853d1efbca 50853d1efbca
parent 5f9d676cefdb
child 83f9edc4c976
child dd8ee564e065
permissions -rw-r--r--
German translation update
     1 /**
     2  * @file proxy.c Proxy API
     3  * @ingroup core
     4  */
     5 
     6 /* purple
     7  *
     8  * Purple is the legal property of its developers, whose names are too numerous
     9  * to list here.  Please refer to the COPYRIGHT file distributed with this
    10  * source distribution.
    11  *
    12  * This program is free software; you can redistribute it and/or modify
    13  * it under the terms of the GNU General Public License as published by
    14  * the Free Software Foundation; either version 2 of the License, or
    15  * (at your option) any later version.
    16  *
    17  * This program is distributed in the hope that it will be useful,
    18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    20  * GNU General Public License for more details.
    21  *
    22  * You should have received a copy of the GNU General Public License
    23  * along with this program; if not, write to the Free Software
    24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
    25  *
    26  */
    27 
    28 /* this is a little piece of code to handle proxy connection */
    29 /* it is intended to : 1st handle http proxy, using the CONNECT command
    30  , 2nd provide an easy way to add socks support
    31  , 3rd draw women to it like flies to honey */
    32 #define _PURPLE_PROXY_C_
    33 
    34 #include "internal.h"
    35 #include "cipher.h"
    36 #include "debug.h"
    37 #include "dnsquery.h"
    38 #include "notify.h"
    39 #include "ntlm.h"
    40 #include "prefs.h"
    41 #include "proxy.h"
    42 #include "util.h"
    43 
    44 struct _PurpleProxyConnectData {
    45 	void *handle;
    46 	PurpleProxyConnectFunction connect_cb;
    47 	gpointer data;
    48 	gchar *host;
    49 	int port;
    50 	int fd;
    51 	int socket_type;
    52 	guint inpa;
    53 	PurpleProxyInfo *gpi;
    54 	PurpleDnsQueryData *query_data;
    55 
    56 	/**
    57 	 * This contains alternating length/char* values.  The char*
    58 	 * values need to be freed when removed from the linked list.
    59 	 */
    60 	GSList *hosts;
    61 
    62 	PurpleProxyConnectData *child;
    63 
    64 	/*
    65 	 * All of the following variables are used when establishing a
    66 	 * connection through a proxy.
    67 	 */
    68 	guchar *write_buffer;
    69 	gsize write_buf_len;
    70 	gsize written_len;
    71 	PurpleInputFunction read_cb;
    72 	guchar *read_buffer;
    73 	gsize read_buf_len;
    74 	gsize read_len;
    75 	PurpleAccount *account;
    76 };
    77 
    78 static const char * const socks5errors[] = {
    79 	"succeeded\n",
    80 	"general SOCKS server failure\n",
    81 	"connection not allowed by ruleset\n",
    82 	"Network unreachable\n",
    83 	"Host unreachable\n",
    84 	"Connection refused\n",
    85 	"TTL expired\n",
    86 	"Command not supported\n",
    87 	"Address type not supported\n"
    88 };
    89 
    90 static PurpleProxyInfo *global_proxy_info = NULL;
    91 
    92 static GSList *handles = NULL;
    93 
    94 static void try_connect(PurpleProxyConnectData *connect_data);
    95 
    96 /*
    97  * TODO: Eventually (GObjectification) this bad boy will be removed, because it is
    98  *       a gross fix for a crashy problem.
    99  */
   100 #define PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data) g_slist_find(handles, connect_data)
   101 
   102 /**************************************************************************
   103  * Proxy structure API
   104  **************************************************************************/
   105 PurpleProxyInfo *
   106 purple_proxy_info_new(void)
   107 {
   108 	return g_new0(PurpleProxyInfo, 1);
   109 }
   110 
   111 void
   112 purple_proxy_info_destroy(PurpleProxyInfo *info)
   113 {
   114 	g_return_if_fail(info != NULL);
   115 
   116 	g_free(info->host);
   117 	g_free(info->username);
   118 	g_free(info->password);
   119 
   120 	g_free(info);
   121 }
   122 
   123 void
   124 purple_proxy_info_set_type(PurpleProxyInfo *info, PurpleProxyType type)
   125 {
   126 	g_return_if_fail(info != NULL);
   127 
   128 	info->type = type;
   129 }
   130 
   131 void
   132 purple_proxy_info_set_host(PurpleProxyInfo *info, const char *host)
   133 {
   134 	g_return_if_fail(info != NULL);
   135 
   136 	g_free(info->host);
   137 	info->host = g_strdup(host);
   138 }
   139 
   140 void
   141 purple_proxy_info_set_port(PurpleProxyInfo *info, int port)
   142 {
   143 	g_return_if_fail(info != NULL);
   144 
   145 	info->port = port;
   146 }
   147 
   148 void
   149 purple_proxy_info_set_username(PurpleProxyInfo *info, const char *username)
   150 {
   151 	g_return_if_fail(info != NULL);
   152 
   153 	g_free(info->username);
   154 	info->username = g_strdup(username);
   155 }
   156 
   157 void
   158 purple_proxy_info_set_password(PurpleProxyInfo *info, const char *password)
   159 {
   160 	g_return_if_fail(info != NULL);
   161 
   162 	g_free(info->password);
   163 	info->password = g_strdup(password);
   164 }
   165 
   166 PurpleProxyType
   167 purple_proxy_info_get_type(const PurpleProxyInfo *info)
   168 {
   169 	g_return_val_if_fail(info != NULL, PURPLE_PROXY_NONE);
   170 
   171 	return info->type;
   172 }
   173 
   174 const char *
   175 purple_proxy_info_get_host(const PurpleProxyInfo *info)
   176 {
   177 	g_return_val_if_fail(info != NULL, NULL);
   178 
   179 	return info->host;
   180 }
   181 
   182 int
   183 purple_proxy_info_get_port(const PurpleProxyInfo *info)
   184 {
   185 	g_return_val_if_fail(info != NULL, 0);
   186 
   187 	return info->port;
   188 }
   189 
   190 const char *
   191 purple_proxy_info_get_username(const PurpleProxyInfo *info)
   192 {
   193 	g_return_val_if_fail(info != NULL, NULL);
   194 
   195 	return info->username;
   196 }
   197 
   198 const char *
   199 purple_proxy_info_get_password(const PurpleProxyInfo *info)
   200 {
   201 	g_return_val_if_fail(info != NULL, NULL);
   202 
   203 	return info->password;
   204 }
   205 
   206 /**************************************************************************
   207  * Global Proxy API
   208  **************************************************************************/
   209 PurpleProxyInfo *
   210 purple_global_proxy_get_info(void)
   211 {
   212 	return global_proxy_info;
   213 }
   214 
   215 void
   216 purple_global_proxy_set_info(PurpleProxyInfo *info)
   217 {
   218 	g_return_if_fail(info != NULL);
   219 
   220 	purple_proxy_info_destroy(global_proxy_info);
   221 
   222 	global_proxy_info = info;
   223 }
   224 
   225 
   226 /* index in gproxycmds below, keep them in sync */
   227 #define GNOME_PROXY_MODE 0
   228 #define GNOME_PROXY_USE_SAME_PROXY 1
   229 #define GNOME_PROXY_SOCKS_HOST 2
   230 #define GNOME_PROXY_SOCKS_PORT 3
   231 #define GNOME_PROXY_HTTP_HOST 4
   232 #define GNOME_PROXY_HTTP_PORT 5
   233 #define GNOME_PROXY_HTTP_USER 6
   234 #define GNOME_PROXY_HTTP_PASS 7
   235 #define GNOME2_CMDS 0
   236 #define GNOME3_CMDS 1
   237 
   238 /* detect proxy settings for gnome2/gnome3 */
   239 static const char* gproxycmds[][2] = {
   240 	{ "gconftool-2 -g /system/proxy/mode" , "gsettings get org.gnome.system.proxy mode" },
   241 	{ "gconftool-2 -g /system/http_proxy/use_same_proxy", "gsettings get org.gnome.system.proxy use-same-proxy" },
   242 	{ "gconftool-2 -g /system/proxy/socks_host", "gsettings get org.gnome.system.proxy.socks host" },
   243 	{ "gconftool-2 -g /system/proxy/socks_port", "gsettings get org.gnome.system.proxy.socks port" },
   244 	{ "gconftool-2 -g /system/http_proxy/host", "gsettings get org.gnome.system.proxy.http host" },
   245 	{ "gconftool-2 -g /system/http_proxy/port", "gsettings get org.gnome.system.proxy.http port"},
   246 	{ "gconftool-2 -g /system/http_proxy/authentication_user", "gsettings get org.gnome.system.proxy.http authentication-user" },
   247 	{ "gconftool-2 -g /system/http_proxy/authentication_password", "gsettings get org.gnome.system.proxy.http authentication-password" },
   248 };
   249 
   250 /**
   251  * This is a utility function used to retrieve proxy parameter values from
   252  * GNOME 2/3 environment.
   253  *
   254  * @param parameter	One of the GNOME_PROXY_x constants defined above
   255  * @param gnome_version GNOME2_CMDS or GNOME3_CMDS
   256  *
   257  * @return The value of requested proxy parameter
   258  */
   259 static char *
   260 purple_gnome_proxy_get_parameter(guint8 parameter, guint8 gnome_version)
   261 {
   262 	gchar *param, *err;
   263 	size_t param_len;
   264 
   265 	if (parameter > GNOME_PROXY_HTTP_PASS)
   266 		return NULL;
   267 	if (gnome_version > GNOME3_CMDS)
   268 		return NULL;
   269 
   270 	if (!g_spawn_command_line_sync(gproxycmds[parameter][gnome_version],
   271 			&param, &err, NULL, NULL))
   272 		return NULL;
   273 	g_free(err);
   274 
   275 	g_strstrip(param);
   276 	if (param[0] == '\'' || param[0] == '\"') {
   277 		param_len = strlen(param);
   278 		memmove(param, param + 1, param_len); /* copy last \0 too */
   279 		--param_len;
   280 		if (param_len > 0 && (param[param_len - 1] == '\'' || param[param_len - 1] == '\"'))
   281 			param[param_len - 1] = '\0';
   282 		g_strstrip(param);
   283 	}
   284 
   285 	return param;
   286 }
   287 
   288 static PurpleProxyInfo *
   289 purple_gnome_proxy_get_info(void)
   290 {
   291 	static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
   292 	gboolean use_same_proxy = FALSE;
   293 	gchar *tmp;
   294 	guint8 gnome_version = GNOME3_CMDS;
   295 
   296 	tmp = g_find_program_in_path("gsettings");
   297 	if (tmp == NULL) {
   298 		tmp = g_find_program_in_path("gconftool-2");
   299 		gnome_version = GNOME2_CMDS;
   300 	}
   301 	if (tmp == NULL)
   302 		return purple_global_proxy_get_info();
   303 
   304 	g_free(tmp);
   305 
   306 	/* Check whether to use a proxy. */
   307 	tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_MODE, gnome_version);
   308 	if (!tmp)
   309 		return purple_global_proxy_get_info();
   310 
   311 	if (purple_strequal(tmp, "none")) {
   312 		info.type = PURPLE_PROXY_NONE;
   313 		g_free(tmp);
   314 		return &info;
   315 	}
   316 
   317 	if (!purple_strequal(tmp, "manual")) {
   318 		/* Unknown setting.  Fallback to using our global proxy settings. */
   319 		g_free(tmp);
   320 		return purple_global_proxy_get_info();
   321 	}
   322 
   323 	g_free(tmp);
   324 
   325 	/* Free the old fields */
   326 	if (info.host) {
   327 		g_free(info.host);
   328 		info.host = NULL;
   329 	}
   330 	if (info.username) {
   331 		g_free(info.username);
   332 		info.username = NULL;
   333 	}
   334 	if (info.password) {
   335 		g_free(info.password);
   336 		info.password = NULL;
   337 	}
   338 
   339 	tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_USE_SAME_PROXY, gnome_version);
   340 	if (!tmp)
   341 		return purple_global_proxy_get_info();
   342 
   343 	if (purple_strequal(tmp, "true"))
   344 		use_same_proxy = TRUE;
   345 
   346 	g_free(tmp);
   347 
   348 	if (!use_same_proxy) {
   349 		info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_HOST, gnome_version);
   350 		if (!info.host)
   351 			return purple_global_proxy_get_info();
   352 	}
   353 
   354 	if (!use_same_proxy && (info.host != NULL) && (*info.host != '\0')) {
   355 		info.type = PURPLE_PROXY_SOCKS5;
   356 		tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_PORT, gnome_version);
   357 		if (!tmp) {
   358 			g_free(info.host);
   359 			info.host = NULL;
   360 			return purple_global_proxy_get_info();
   361 		}
   362 		info.port = atoi(tmp);
   363 		g_free(tmp);
   364 	} else {
   365 		g_free(info.host);
   366 		info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_HOST, gnome_version);
   367 		if (!info.host)
   368 			return purple_global_proxy_get_info();
   369 
   370 		/* If we get this far then we know we're using an HTTP proxy */
   371 		info.type = PURPLE_PROXY_HTTP;
   372 
   373 		if (*info.host == '\0')
   374 		{
   375 			purple_debug_info("proxy", "Gnome proxy settings are set to "
   376 					"'manual' but no suitable proxy server is specified.  Using "
   377 					"Pidgin's proxy settings instead.\n");
   378 			g_free(info.host);
   379 			info.host = NULL;
   380 			return purple_global_proxy_get_info();
   381 		}
   382 
   383 		info.username = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_USER, gnome_version);
   384 		if (!info.username)
   385 		{
   386 			g_free(info.host);
   387 			info.host = NULL;
   388 			return purple_global_proxy_get_info();
   389 		}
   390 
   391 		info.password = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PASS, gnome_version);
   392 		if (!info.password)
   393 		{
   394 			g_free(info.host);
   395 			info.host = NULL;
   396 			g_free(info.username);
   397 			info.username = NULL;
   398 			return purple_global_proxy_get_info();
   399 		}
   400 
   401 		tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PORT, gnome_version);
   402 		if (!tmp)
   403 		{
   404 			g_free(info.host);
   405 			info.host = NULL;
   406 			g_free(info.username);
   407 			info.username = NULL;
   408 			g_free(info.password);
   409 			info.password = NULL;
   410 			return purple_global_proxy_get_info();
   411 		}
   412 		info.port = atoi(tmp);
   413 		g_free(tmp);
   414 	}
   415 
   416 	return &info;
   417 }
   418 
   419 #ifdef _WIN32
   420 
   421 typedef BOOL (CALLBACK* LPFNWINHTTPGETIEPROXYCONFIG)(/*IN OUT*/ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* pProxyConfig);
   422 
   423 /* This modifies "host" in-place evilly */
   424 static void
   425 _proxy_fill_hostinfo(PurpleProxyInfo *info, char *host, int default_port)
   426 {
   427 	int port = default_port;
   428 	char *d;
   429 
   430 	d = g_strrstr(host, ":");
   431 	if (d) {
   432 		*d = '\0';
   433 
   434 		d++;
   435 		if (*d)
   436 			sscanf(d, "%d", &port);
   437 
   438 		if (port == 0)
   439 			port = default_port;
   440 	}
   441 
   442 	purple_proxy_info_set_host(info, host);
   443 	purple_proxy_info_set_port(info, port);
   444 }
   445 
   446 static PurpleProxyInfo *
   447 purple_win32_proxy_get_info(void)
   448 {
   449 	static LPFNWINHTTPGETIEPROXYCONFIG MyWinHttpGetIEProxyConfig = NULL;
   450 	static gboolean loaded = FALSE;
   451 	static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
   452 
   453 	WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_proxy_config;
   454 
   455 	if (!loaded) {
   456 		loaded = TRUE;
   457 		MyWinHttpGetIEProxyConfig = (LPFNWINHTTPGETIEPROXYCONFIG)
   458 			wpurple_find_and_loadproc("winhttp.dll", "WinHttpGetIEProxyConfigForCurrentUser");
   459 		if (!MyWinHttpGetIEProxyConfig)
   460 			purple_debug_warning("proxy", "Unable to read Windows Proxy Settings.\n");
   461 	}
   462 
   463 	if (!MyWinHttpGetIEProxyConfig)
   464 		return NULL;
   465 
   466 	ZeroMemory(&ie_proxy_config, sizeof(ie_proxy_config));
   467 	if (!MyWinHttpGetIEProxyConfig(&ie_proxy_config)) {
   468 		purple_debug_error("proxy", "Error reading Windows Proxy Settings(%lu).\n", GetLastError());
   469 		return NULL;
   470 	}
   471 
   472 	/* We can't do much if it is autodetect*/
   473 	if (ie_proxy_config.fAutoDetect) {
   474 		purple_debug_error("proxy", "Windows Proxy Settings set to autodetect (not supported).\n");
   475 
   476 		/* TODO: For 3.0.0 we'll revisit this (maybe)*/
   477 
   478 		return NULL;
   479 
   480 	} else if (ie_proxy_config.lpszProxy) {
   481 		gchar *proxy_list = g_utf16_to_utf8(ie_proxy_config.lpszProxy, -1,
   482 									 NULL, NULL, NULL);
   483 
   484 		/* We can't do anything about the bypass list, as we don't have the url */
   485 		/* TODO: For 3.0.0 we'll revisit this*/
   486 
   487 		/* There are proxy settings for several protocols */
   488 		if (proxy_list && *proxy_list) {
   489 			char *specific = NULL, *tmp;
   490 
   491 			/* If there is only a global proxy, which  means "HTTP" */
   492 			if (!strchr(proxy_list, ';') || (specific = g_strstr_len(proxy_list, -1, "http=")) != NULL) {
   493 
   494 				if (specific) {
   495 					specific += strlen("http=");
   496 					tmp = strchr(specific, ';');
   497 					if (tmp)
   498 						*tmp = '\0';
   499 					/* specific now points the proxy server (and port) */
   500 				} else
   501 					specific = proxy_list;
   502 
   503 				purple_proxy_info_set_type(&info, PURPLE_PROXY_HTTP);
   504 				_proxy_fill_hostinfo(&info, specific, 80);
   505 				/* TODO: is there a way to set the username/password? */
   506 				purple_proxy_info_set_username(&info, NULL);
   507 				purple_proxy_info_set_password(&info, NULL);
   508 
   509 				purple_debug_info("proxy", "Windows Proxy Settings: HTTP proxy: '%s:%d'.\n",
   510 								  purple_proxy_info_get_host(&info),
   511 								  purple_proxy_info_get_port(&info));
   512 
   513 			} else if ((specific = g_strstr_len(proxy_list, -1, "socks=")) != NULL) {
   514 
   515 				specific += strlen("socks=");
   516 				tmp = strchr(specific, ';');
   517 				if (tmp)
   518 					*tmp = '\0';
   519 				/* specific now points the proxy server (and port) */
   520 
   521 				purple_proxy_info_set_type(&info, PURPLE_PROXY_SOCKS5);
   522 				_proxy_fill_hostinfo(&info, specific, 1080);
   523 				/* TODO: is there a way to set the username/password? */
   524 				purple_proxy_info_set_username(&info, NULL);
   525 				purple_proxy_info_set_password(&info, NULL);
   526 
   527 				purple_debug_info("proxy", "Windows Proxy Settings: SOCKS5 proxy: '%s:%d'.\n",
   528 								  purple_proxy_info_get_host(&info),
   529 								  purple_proxy_info_get_port(&info));
   530 
   531 			} else {
   532 
   533 				purple_debug_info("proxy", "Windows Proxy Settings: No supported proxy specified.\n");
   534 
   535 				purple_proxy_info_set_type(&info, PURPLE_PROXY_NONE);
   536 
   537 			}
   538 		}
   539 
   540 		/* TODO: Fix API to be able look at proxy bypass settings */
   541 
   542 		g_free(proxy_list);
   543 	} else {
   544 		purple_debug_info("proxy", "No Windows proxy set.\n");
   545 		purple_proxy_info_set_type(&info, PURPLE_PROXY_NONE);
   546 	}
   547 
   548 	if (ie_proxy_config.lpszAutoConfigUrl)
   549 		GlobalFree(ie_proxy_config.lpszAutoConfigUrl);
   550 	if (ie_proxy_config.lpszProxy)
   551 		GlobalFree(ie_proxy_config.lpszProxy);
   552 	if (ie_proxy_config.lpszProxyBypass)
   553 		GlobalFree(ie_proxy_config.lpszProxyBypass);
   554 
   555 	return &info;
   556 }
   557 #endif
   558 
   559 
   560 /**************************************************************************
   561  * Proxy API
   562  **************************************************************************/
   563 
   564 /**
   565  * Whoever calls this needs to have called
   566  * purple_proxy_connect_data_disconnect() beforehand.
   567  */
   568 static void
   569 purple_proxy_connect_data_destroy(PurpleProxyConnectData *connect_data)
   570 {
   571 	handles = g_slist_remove(handles, connect_data);
   572 
   573 	if (connect_data->query_data != NULL)
   574 		purple_dnsquery_destroy(connect_data->query_data);
   575 
   576 	while (connect_data->hosts != NULL)
   577 	{
   578 		/* Discard the length... */
   579 		connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
   580 		/* Free the address... */
   581 		g_free(connect_data->hosts->data);
   582 		connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
   583 	}
   584 
   585 	g_free(connect_data->host);
   586 	g_free(connect_data);
   587 }
   588 
   589 /**
   590  * Free all information dealing with a connection attempt and
   591  * reset the connect_data to prepare for it to try to connect
   592  * to another IP address.
   593  *
   594  * If an error message is passed in, then we know the connection
   595  * attempt failed.  If the connection attempt failed and
   596  * connect_data->hosts is not empty then we try the next IP address.
   597  * If the connection attempt failed and we have no more hosts
   598  * try try then we call the callback with the given error message,
   599  * then destroy the connect_data.
   600  *
   601  * @param error_message An error message explaining why the connection
   602  *        failed.  This will be passed to the callback function
   603  *        specified in the call to purple_proxy_connect().  If the
   604  *        connection was successful then pass in null.
   605  */
   606 static void
   607 purple_proxy_connect_data_disconnect(PurpleProxyConnectData *connect_data, const gchar *error_message)
   608 {
   609 	if (connect_data->child != NULL)
   610 	{
   611 		purple_proxy_connect_cancel(connect_data->child);
   612 		connect_data->child = NULL;
   613 	}
   614 
   615 	if (connect_data->inpa > 0)
   616 	{
   617 		purple_input_remove(connect_data->inpa);
   618 		connect_data->inpa = 0;
   619 	}
   620 
   621 	if (connect_data->fd >= 0)
   622 	{
   623 		close(connect_data->fd);
   624 		connect_data->fd = -1;
   625 	}
   626 
   627 	g_free(connect_data->write_buffer);
   628 	connect_data->write_buffer = NULL;
   629 
   630 	g_free(connect_data->read_buffer);
   631 	connect_data->read_buffer = NULL;
   632 
   633 	if (error_message != NULL)
   634 	{
   635 		purple_debug_error("proxy", "Connection attempt failed: %s\n",
   636 				error_message);
   637 		if (connect_data->hosts != NULL)
   638 			try_connect(connect_data);
   639 		else
   640 		{
   641 			/* Everything failed!  Tell the originator of the request. */
   642 			connect_data->connect_cb(connect_data->data, -1, error_message);
   643 			purple_proxy_connect_data_destroy(connect_data);
   644 		}
   645 	}
   646 }
   647 
   648 /**
   649  * This calls purple_proxy_connect_data_disconnect(), but it lets you
   650  * specify the error_message using a printf()-like syntax.
   651  */
   652 static void
   653 purple_proxy_connect_data_disconnect_formatted(PurpleProxyConnectData *connect_data, const char *format, ...)
   654 {
   655 	va_list args;
   656 	gchar *tmp;
   657 
   658 	va_start(args, format);
   659 	tmp = g_strdup_vprintf(format, args);
   660 	va_end(args);
   661 
   662 	purple_proxy_connect_data_disconnect(connect_data, tmp);
   663 	g_free(tmp);
   664 }
   665 
   666 static void
   667 purple_proxy_connect_data_connected(PurpleProxyConnectData *connect_data)
   668 {
   669 	purple_debug_info("proxy", "Connected to %s:%d.\n",
   670 	                  connect_data->host, connect_data->port);
   671 
   672 	connect_data->connect_cb(connect_data->data, connect_data->fd, NULL);
   673 
   674 	/*
   675 	 * We've passed the file descriptor to the protocol, so it's no longer
   676 	 * our responsibility, and we should be careful not to free it when
   677 	 * we destroy the connect_data.
   678 	 */
   679 	connect_data->fd = -1;
   680 
   681 	purple_proxy_connect_data_disconnect(connect_data, NULL);
   682 	purple_proxy_connect_data_destroy(connect_data);
   683 }
   684 
   685 static void
   686 socket_ready_cb(gpointer data, gint source, PurpleInputCondition cond)
   687 {
   688 	PurpleProxyConnectData *connect_data = data;
   689 	int error = 0;
   690 	int ret;
   691 
   692 	/* If the socket-connected message had already been triggered when connect_data
   693  	 * was destroyed via purple_proxy_connect_cancel(), we may get here with a freed connect_data.
   694  	 */
   695 	if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data))
   696 		return;
   697 
   698 	purple_debug_info("proxy", "Connecting to %s:%d.\n",
   699 					connect_data->host, connect_data->port);
   700 
   701 	/*
   702 	 * purple_input_get_error after a non-blocking connect returns -1 if something is
   703 	 * really messed up (bad descriptor, usually). Otherwise, it returns 0 and
   704 	 * error holds what connect would have returned if it blocked until now.
   705 	 * Thus, error == 0 is success, error == EINPROGRESS means "try again",
   706 	 * and anything else is a real error.
   707 	 *
   708 	 * (error == EINPROGRESS can happen after a select because the kernel can
   709 	 * be overly optimistic sometimes. select is just a hint that you might be
   710 	 * able to do something.)
   711 	 */
   712 	ret = purple_input_get_error(connect_data->fd, &error);
   713 
   714 	if (ret == 0 && error == EINPROGRESS) {
   715 		/* No worries - we'll be called again later */
   716 		/* TODO: Does this ever happen? */
   717 		purple_debug_info("proxy", "(ret == 0 && error == EINPROGRESS)\n");
   718 		return;
   719 	}
   720 
   721 	if (ret != 0 || error != 0) {
   722 		if (ret != 0)
   723 			error = errno;
   724 		purple_debug_error("proxy", "Error connecting to %s:%d (%s).\n",
   725 						connect_data->host, connect_data->port, g_strerror(error));
   726 
   727 		purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
   728 		return;
   729 	}
   730 
   731 	purple_proxy_connect_data_connected(connect_data);
   732 }
   733 
   734 static gboolean
   735 clean_connect(gpointer data)
   736 {
   737 	purple_proxy_connect_data_connected(data);
   738 
   739 	return FALSE;
   740 }
   741 
   742 static void
   743 proxy_connect_udp_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
   744 {
   745 	int flags;
   746 
   747 	purple_debug_info("proxy", "UDP Connecting to %s:%d with no proxy\n",
   748 			connect_data->host, connect_data->port);
   749 
   750 	connect_data->fd = socket(addr->sa_family, SOCK_DGRAM, 0);
   751 	if (connect_data->fd < 0)
   752 	{
   753 		purple_proxy_connect_data_disconnect_formatted(connect_data,
   754 				_("Unable to create socket: %s"), g_strerror(errno));
   755 		return;
   756 	}
   757 
   758 	flags = fcntl(connect_data->fd, F_GETFL);
   759 	fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
   760 #ifndef _WIN32
   761 	fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
   762 #endif
   763 
   764 	if (connect(connect_data->fd, addr, addrlen) != 0)
   765 	{
   766 		if ((errno == EINPROGRESS) || (errno == EINTR))
   767 		{
   768 			purple_debug_info("proxy", "UDP Connection in progress\n");
   769 			connect_data->inpa = purple_input_add(connect_data->fd,
   770 					PURPLE_INPUT_WRITE, socket_ready_cb, connect_data);
   771 		}
   772 		else
   773 		{
   774 			purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
   775 		}
   776 	}
   777 	else
   778 	{
   779 		/*
   780 		 * The connection happened IMMEDIATELY... strange, but whatever.
   781 		 */
   782 		int error = ETIMEDOUT;
   783 		int ret;
   784 
   785 		purple_debug_info("proxy", "UDP Connected immediately.\n");
   786 
   787 		ret = purple_input_get_error(connect_data->fd, &error);
   788 		if ((ret != 0) || (error != 0))
   789 		{
   790 			if (ret != 0)
   791 				error = errno;
   792 			purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
   793 			return;
   794 		}
   795 
   796 		/*
   797 		 * We want to call the "connected" callback eventually, but we
   798 		 * don't want to call it before we return, just in case.
   799 		 */
   800 		purple_timeout_add(10, clean_connect, connect_data);
   801 	}
   802 }
   803 
   804 static void
   805 proxy_connect_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
   806 {
   807 	int flags;
   808 
   809 	purple_debug_info("proxy", "Connecting to %s:%d with no proxy\n",
   810 			connect_data->host, connect_data->port);
   811 
   812 	connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
   813 	if (connect_data->fd < 0)
   814 	{
   815 		purple_proxy_connect_data_disconnect_formatted(connect_data,
   816 				_("Unable to create socket: %s"), g_strerror(errno));
   817 		return;
   818 	}
   819 
   820 	flags = fcntl(connect_data->fd, F_GETFL);
   821 	fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
   822 #ifndef _WIN32
   823 	fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
   824 #endif
   825 
   826 	if (connect(connect_data->fd, addr, addrlen) != 0)
   827 	{
   828 		if ((errno == EINPROGRESS) || (errno == EINTR))
   829 		{
   830 			purple_debug_info("proxy", "Connection in progress\n");
   831 			connect_data->inpa = purple_input_add(connect_data->fd,
   832 					PURPLE_INPUT_WRITE, socket_ready_cb, connect_data);
   833 		}
   834 		else
   835 		{
   836 			purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
   837 		}
   838 	}
   839 	else
   840 	{
   841 		/*
   842 		 * The connection happened IMMEDIATELY... strange, but whatever.
   843 		 */
   844 		int error = ETIMEDOUT;
   845 		int ret;
   846 
   847 		purple_debug_info("proxy", "Connected immediately.\n");
   848 
   849 		ret = purple_input_get_error(connect_data->fd, &error);
   850 		if ((ret != 0) || (error != 0))
   851 		{
   852 			if (ret != 0)
   853 				error = errno;
   854 			purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
   855 			return;
   856 		}
   857 
   858 		/*
   859 		 * We want to call the "connected" callback eventually, but we
   860 		 * don't want to call it before we return, just in case.
   861 		 */
   862 		purple_timeout_add(10, clean_connect, connect_data);
   863 	}
   864 }
   865 
   866 /**
   867  * This is a utility function used by the HTTP, SOCKS4 and SOCKS5
   868  * connect functions.  It writes data from a buffer to a socket.
   869  * When all the data is written it sets up a watcher to read a
   870  * response and call a specified function.
   871  */
   872 static void
   873 proxy_do_write(gpointer data, gint source, PurpleInputCondition cond)
   874 {
   875 	PurpleProxyConnectData *connect_data;
   876 	const guchar *request;
   877 	gsize request_len;
   878 	int ret;
   879 
   880 	connect_data = data;
   881 	request = connect_data->write_buffer + connect_data->written_len;
   882 	request_len = connect_data->write_buf_len - connect_data->written_len;
   883 
   884 	ret = write(connect_data->fd, request, request_len);
   885 	if (ret <= 0)
   886 	{
   887 		if (errno == EAGAIN)
   888 			/* No worries */
   889 			return;
   890 
   891 		/* Error! */
   892 		purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
   893 		return;
   894 	}
   895 	if (ret < request_len) {
   896 		connect_data->written_len += ret;
   897 		return;
   898 	}
   899 
   900 	/* We're done writing data!  Wait for a response. */
   901 	g_free(connect_data->write_buffer);
   902 	connect_data->write_buffer = NULL;
   903 	purple_input_remove(connect_data->inpa);
   904 	connect_data->inpa = purple_input_add(connect_data->fd,
   905 			PURPLE_INPUT_READ, connect_data->read_cb, connect_data);
   906 }
   907 
   908 #define HTTP_GOODSTRING "HTTP/1.0 200"
   909 #define HTTP_GOODSTRING2 "HTTP/1.1 200"
   910 
   911 /**
   912  * We're using an HTTP proxy for a non-port 80 tunnel.  Read the
   913  * response to the CONNECT request.
   914  */
   915 static void
   916 http_canread(gpointer data, gint source, PurpleInputCondition cond)
   917 {
   918 	int len, headers_len, status = 0;
   919 	gboolean error;
   920 	PurpleProxyConnectData *connect_data = data;
   921 	char *p;
   922 	gsize max_read;
   923 
   924 	if (connect_data->read_buffer == NULL) {
   925 		connect_data->read_buf_len = 8192;
   926 		connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
   927 		connect_data->read_len = 0;
   928 	}
   929 
   930 	p = (char *)connect_data->read_buffer + connect_data->read_len;
   931 	max_read = connect_data->read_buf_len - connect_data->read_len - 1;
   932 
   933 	len = read(connect_data->fd, p, max_read);
   934 
   935 	if (len == 0) {
   936 		purple_proxy_connect_data_disconnect(connect_data,
   937 				_("Server closed the connection"));
   938 		return;
   939 	}
   940 
   941 	if (len < 0) {
   942 		if (errno == EAGAIN)
   943 			/* No worries */
   944 			return;
   945 
   946 		/* Error! */
   947 		purple_proxy_connect_data_disconnect_formatted(connect_data,
   948 				_("Lost connection with server: %s"), g_strerror(errno));
   949 		return;
   950 	}
   951 
   952 	connect_data->read_len += len;
   953 	p[len] = '\0';
   954 
   955 	p = g_strstr_len((const gchar *)connect_data->read_buffer,
   956 			connect_data->read_len, "\r\n\r\n");
   957 	if (p != NULL) {
   958 		*p = '\0';
   959 		headers_len = (p - (char *)connect_data->read_buffer) + 4;
   960 	} else if(len == max_read)
   961 		headers_len = len;
   962 	else
   963 		return;
   964 
   965 	error = strncmp((const char *)connect_data->read_buffer, "HTTP/", 5) != 0;
   966 	if (!error) {
   967 		int major;
   968 		p = (char *)connect_data->read_buffer + 5;
   969 		major = strtol(p, &p, 10);
   970 		error = (major == 0) || (*p != '.');
   971 		if(!error) {
   972 			int minor;
   973 			p++;
   974 			minor = strtol(p, &p, 10);
   975 			error = (*p != ' ');
   976 			if(!error) {
   977 				p++;
   978 				status = strtol(p, &p, 10);
   979 				error = (*p != ' ');
   980 			}
   981 		}
   982 	}
   983 
   984 	/* Read the contents */
   985 	p = g_strrstr((const gchar *)connect_data->read_buffer, "Content-Length: ");
   986 	if (p != NULL) {
   987 		gchar *tmp;
   988 		int len = 0;
   989 		char tmpc;
   990 		p += strlen("Content-Length: ");
   991 		tmp = strchr(p, '\r');
   992 		if(tmp)
   993 			*tmp = '\0';
   994 		len = atoi(p);
   995 		if(tmp)
   996 			*tmp = '\r';
   997 
   998 		/* Compensate for what has already been read */
   999 		len -= connect_data->read_len - headers_len;
  1000 		/* I'm assuming that we're doing this to prevent the server from
  1001 		   complaining / breaking since we don't read the whole page */
  1002 		while (len--) {
  1003 			/* TODO: deal with EAGAIN (and other errors) better */
  1004 			if (read(connect_data->fd, &tmpc, 1) < 0 && errno != EAGAIN)
  1005 				break;
  1006 		}
  1007 	}
  1008 
  1009 	if (error) {
  1010 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  1011 				_("Unable to parse response from HTTP proxy: %s"),
  1012 				connect_data->read_buffer);
  1013 		return;
  1014 	}
  1015 	else if (status != 200) {
  1016 		purple_debug_error("proxy",
  1017 				"Proxy server replied with:\n%s\n",
  1018 				connect_data->read_buffer);
  1019 
  1020 		if (status == 407 /* Proxy Auth */) {
  1021 			const char *header;
  1022 			gchar *request;
  1023 
  1024 			header = g_strrstr((const gchar *)connect_data->read_buffer,
  1025 					"Proxy-Authenticate: NTLM");
  1026 			if (header != NULL) {
  1027 				const char *header_end = header + strlen("Proxy-Authenticate: NTLM");
  1028 				const char *domain = purple_proxy_info_get_username(connect_data->gpi);
  1029 				char *username = NULL, hostname[256];
  1030 				gchar *response;
  1031 				int ret;
  1032 
  1033 				ret = gethostname(hostname, sizeof(hostname));
  1034 				hostname[sizeof(hostname) - 1] = '\0';
  1035 				if (ret < 0 || hostname[0] == '\0') {
  1036 					purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?");
  1037 					g_strlcpy(hostname, "localhost", sizeof(hostname));
  1038 				}
  1039 
  1040 				if (domain != NULL)
  1041 					username = (char*) strchr(domain, '\\');
  1042 				if (username == NULL) {
  1043 					purple_proxy_connect_data_disconnect_formatted(connect_data,
  1044 							_("HTTP proxy connection error %d"), status);
  1045 					return;
  1046 				}
  1047 				*username = '\0';
  1048 
  1049 				/* Is there a message? */
  1050 				if (*header_end == ' ') {
  1051 					/* Check for Type-2 */
  1052 					char *tmp = (char*) header;
  1053 					guint8 *nonce;
  1054 
  1055 					header_end++;
  1056 					username++;
  1057 					while(*tmp != '\r' && *tmp != '\0') tmp++;
  1058 					*tmp = '\0';
  1059 					nonce = purple_ntlm_parse_type2(header_end, NULL);
  1060 					response = purple_ntlm_gen_type3(username,
  1061 						(gchar*) purple_proxy_info_get_password(connect_data->gpi),
  1062 						hostname,
  1063 						domain, nonce, NULL);
  1064 					username--;
  1065 				} else /* Empty message */
  1066 					response = purple_ntlm_gen_type1(hostname, domain);
  1067 
  1068 				*username = '\\';
  1069 
  1070 				request = g_strdup_printf(
  1071 					"CONNECT %s:%d HTTP/1.1\r\n"
  1072 					"Host: %s:%d\r\n"
  1073 					"Proxy-Authorization: NTLM %s\r\n"
  1074 					"Proxy-Connection: Keep-Alive\r\n\r\n",
  1075 					connect_data->host, connect_data->port,
  1076 					connect_data->host, connect_data->port,
  1077 					response);
  1078 
  1079 				g_free(response);
  1080 
  1081 			} else if (g_strrstr((const char *)connect_data->read_buffer, "Proxy-Authenticate: Basic") != NULL) {
  1082 				gchar *t1, *t2;
  1083 				const char *username, *password;
  1084 
  1085 				username = purple_proxy_info_get_username(connect_data->gpi);
  1086 				password = purple_proxy_info_get_password(connect_data->gpi);
  1087 
  1088 				t1 = g_strdup_printf("%s:%s",
  1089 									 username ? username : "",
  1090 									 password ? password : "");
  1091 				t2 = purple_base64_encode((guchar *)t1, strlen(t1));
  1092 				g_free(t1);
  1093 
  1094 				request = g_strdup_printf(
  1095 					"CONNECT %s:%d HTTP/1.1\r\n"
  1096 					"Host: %s:%d\r\n"
  1097 					"Proxy-Authorization: Basic %s\r\n",
  1098 					connect_data->host, connect_data->port,
  1099 					connect_data->host, connect_data->port,
  1100 					t2);
  1101 
  1102 				g_free(t2);
  1103 
  1104 			} else {
  1105 				purple_proxy_connect_data_disconnect_formatted(connect_data,
  1106 						_("HTTP proxy connection error %d"), status);
  1107 				return;
  1108 			}
  1109 
  1110 			purple_input_remove(connect_data->inpa);
  1111 			g_free(connect_data->read_buffer);
  1112 			connect_data->read_buffer = NULL;
  1113 
  1114 			connect_data->write_buffer = (guchar *)request;
  1115 			connect_data->write_buf_len = strlen(request);
  1116 			connect_data->written_len = 0;
  1117 
  1118 			connect_data->read_cb = http_canread;
  1119 
  1120 			connect_data->inpa = purple_input_add(connect_data->fd,
  1121 				PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
  1122 
  1123 			proxy_do_write(connect_data, connect_data->fd, cond);
  1124 
  1125 			return;
  1126 		}
  1127 
  1128 		if (status == 403) {
  1129 			/* Forbidden */
  1130 			purple_proxy_connect_data_disconnect_formatted(connect_data,
  1131 					_("Access denied: HTTP proxy server forbids port %d tunneling"),
  1132 					connect_data->port);
  1133 		} else {
  1134 			purple_proxy_connect_data_disconnect_formatted(connect_data,
  1135 					_("HTTP proxy connection error %d"), status);
  1136 		}
  1137 	} else {
  1138 		purple_input_remove(connect_data->inpa);
  1139 		connect_data->inpa = 0;
  1140 		g_free(connect_data->read_buffer);
  1141 		connect_data->read_buffer = NULL;
  1142 		purple_debug_info("proxy", "HTTP proxy connection established\n");
  1143 		purple_proxy_connect_data_connected(connect_data);
  1144 		return;
  1145 	}
  1146 }
  1147 
  1148 static void
  1149 http_start_connect_tunneling(PurpleProxyConnectData *connect_data) {
  1150 	GString *request;
  1151 	int ret;
  1152 
  1153 	purple_debug_info("proxy", "Using CONNECT tunneling for %s:%d\n",
  1154 		connect_data->host, connect_data->port);
  1155 
  1156 	request = g_string_sized_new(4096);
  1157 	g_string_append_printf(request,
  1158 			"CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n",
  1159 			connect_data->host, connect_data->port,
  1160 			connect_data->host, connect_data->port);
  1161 
  1162 	if (purple_proxy_info_get_username(connect_data->gpi) != NULL)
  1163 	{
  1164 		char *t1, *t2, *ntlm_type1;
  1165 		char hostname[256];
  1166 
  1167 		ret = gethostname(hostname, sizeof(hostname));
  1168 		hostname[sizeof(hostname) - 1] = '\0';
  1169 		if (ret < 0 || hostname[0] == '\0') {
  1170 			purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?");
  1171 			g_strlcpy(hostname, "localhost", sizeof(hostname));
  1172 		}
  1173 
  1174 		t1 = g_strdup_printf("%s:%s",
  1175 			purple_proxy_info_get_username(connect_data->gpi),
  1176 			purple_proxy_info_get_password(connect_data->gpi) ?
  1177 				purple_proxy_info_get_password(connect_data->gpi) : "");
  1178 		t2 = purple_base64_encode((const guchar *)t1, strlen(t1));
  1179 		g_free(t1);
  1180 
  1181 		ntlm_type1 = purple_ntlm_gen_type1(hostname, "");
  1182 
  1183 		g_string_append_printf(request,
  1184 			"Proxy-Authorization: Basic %s\r\n"
  1185 			"Proxy-Authorization: NTLM %s\r\n"
  1186 			"Proxy-Connection: Keep-Alive\r\n",
  1187 			t2, ntlm_type1);
  1188 		g_free(ntlm_type1);
  1189 		g_free(t2);
  1190 	}
  1191 
  1192 	g_string_append(request, "\r\n");
  1193 
  1194 	connect_data->write_buf_len = request->len;
  1195 	connect_data->write_buffer = (guchar *)g_string_free(request, FALSE);
  1196 	connect_data->written_len = 0;
  1197 	connect_data->read_cb = http_canread;
  1198 
  1199 	connect_data->inpa = purple_input_add(connect_data->fd,
  1200 			PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
  1201 	proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  1202 }
  1203 
  1204 static void
  1205 http_canwrite(gpointer data, gint source, PurpleInputCondition cond) {
  1206 	PurpleProxyConnectData *connect_data = data;
  1207 	int ret, error = ETIMEDOUT;
  1208 
  1209 	purple_debug_info("proxy", "Connected to %s:%d.\n",
  1210 		connect_data->host, connect_data->port);
  1211 
  1212 	if (connect_data->inpa > 0)	{
  1213 		purple_input_remove(connect_data->inpa);
  1214 		connect_data->inpa = 0;
  1215 	}
  1216 
  1217 	ret = purple_input_get_error(connect_data->fd, &error);
  1218 	if (ret != 0 || error != 0) {
  1219 		if (ret != 0)
  1220 			error = errno;
  1221 		purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
  1222 		return;
  1223 	}
  1224 
  1225 	if (connect_data->port == 80) {
  1226 		/*
  1227 		 * If we're trying to connect to something running on
  1228 		 * port 80 then we assume the traffic using this
  1229 		 * connection is going to be HTTP traffic.  If it's
  1230 		 * not then this will fail (uglily).  But it's good
  1231 		 * to avoid using the CONNECT method because it's
  1232 		 * not always allowed.
  1233 		 */
  1234 		purple_debug_info("proxy", "HTTP proxy connection established\n");
  1235 		purple_proxy_connect_data_connected(connect_data);
  1236 	} else {
  1237 		http_start_connect_tunneling(connect_data);
  1238 	}
  1239 
  1240 }
  1241 
  1242 static void
  1243 proxy_connect_http(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
  1244 {
  1245 	int flags;
  1246 
  1247 	purple_debug_info("proxy",
  1248 			   "Connecting to %s:%d via %s:%d using HTTP\n",
  1249 			   connect_data->host, connect_data->port,
  1250 			   (purple_proxy_info_get_host(connect_data->gpi) ? purple_proxy_info_get_host(connect_data->gpi) : "(null)"),
  1251 			   purple_proxy_info_get_port(connect_data->gpi));
  1252 
  1253 	connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
  1254 	if (connect_data->fd < 0)
  1255 	{
  1256 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  1257 				_("Unable to create socket: %s"), g_strerror(errno));
  1258 		return;
  1259 	}
  1260 
  1261 	flags = fcntl(connect_data->fd, F_GETFL);
  1262 	fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
  1263 #ifndef _WIN32
  1264 	fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
  1265 #endif
  1266 
  1267 	if (connect(connect_data->fd, addr, addrlen) != 0) {
  1268 		if (errno == EINPROGRESS || errno == EINTR) {
  1269 			purple_debug_info("proxy", "Connection in progress\n");
  1270 
  1271 			connect_data->inpa = purple_input_add(connect_data->fd,
  1272 					PURPLE_INPUT_WRITE, http_canwrite, connect_data);
  1273 		} else
  1274 			purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
  1275 	} else {
  1276 		purple_debug_info("proxy", "Connected immediately.\n");
  1277 
  1278 		http_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  1279 	}
  1280 }
  1281 
  1282 static void
  1283 s4_canread(gpointer data, gint source, PurpleInputCondition cond)
  1284 {
  1285 	PurpleProxyConnectData *connect_data = data;
  1286 	guchar *buf;
  1287 	int len, max_read;
  1288 
  1289 	/* This is really not going to block under normal circumstances, but to
  1290 	 * be correct, we deal with the unlikely scenario */
  1291 
  1292 	if (connect_data->read_buffer == NULL) {
  1293 		connect_data->read_buf_len = 12;
  1294 		connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
  1295 		connect_data->read_len = 0;
  1296 	}
  1297 
  1298 	buf = connect_data->read_buffer + connect_data->read_len;
  1299 	max_read = connect_data->read_buf_len - connect_data->read_len;
  1300 
  1301 	len = read(connect_data->fd, buf, max_read);
  1302 
  1303 	if ((len < 0 && errno == EAGAIN) || (len > 0 && len + connect_data->read_len < 4))
  1304 		return;
  1305 	else if (len + connect_data->read_len >= 4) {
  1306 		if (connect_data->read_buffer[1] == 90) {
  1307 			purple_proxy_connect_data_connected(connect_data);
  1308 			return;
  1309 		}
  1310 	}
  1311 
  1312 	purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
  1313 }
  1314 
  1315 static void
  1316 s4_host_resolved(GSList *hosts, gpointer data, const char *error_message)
  1317 {
  1318 	PurpleProxyConnectData *connect_data = data;
  1319 	unsigned char packet[9];
  1320 	struct sockaddr *addr;
  1321 
  1322 	connect_data->query_data = NULL;
  1323 
  1324 	if (error_message != NULL) {
  1325 		purple_proxy_connect_data_disconnect(connect_data, error_message);
  1326 		return;
  1327 	}
  1328 
  1329 	if (hosts == NULL) {
  1330 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  1331 				_("Error resolving %s"), connect_data->host);
  1332 		return;
  1333 	}
  1334 
  1335 	/* Discard the length... */
  1336 	hosts = g_slist_delete_link(hosts, hosts);
  1337 	addr = hosts->data;
  1338 	hosts = g_slist_delete_link(hosts, hosts);
  1339 
  1340 	packet[0] = 0x04;
  1341 	packet[1] = 0x01;
  1342 	packet[2] = connect_data->port >> 8;
  1343 	packet[3] = connect_data->port & 0xff;
  1344 	memcpy(packet + 4, &((struct sockaddr_in *)addr)->sin_addr.s_addr, 4);
  1345 	packet[8] = 0x00;
  1346 
  1347 	g_free(addr);
  1348 
  1349 	/* We could try the other hosts, but hopefully that shouldn't be necessary */
  1350 	while (hosts != NULL) {
  1351 		/* Discard the length... */
  1352 		hosts = g_slist_delete_link(hosts, hosts);
  1353 		/* Free the address... */
  1354 		g_free(hosts->data);
  1355 		hosts = g_slist_delete_link(hosts, hosts);
  1356 	}
  1357 
  1358 	connect_data->write_buffer = g_memdup(packet, sizeof(packet));
  1359 	connect_data->write_buf_len = sizeof(packet);
  1360 	connect_data->written_len = 0;
  1361 	connect_data->read_cb = s4_canread;
  1362 
  1363 	connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
  1364 
  1365 	proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  1366 }
  1367 
  1368 static void
  1369 s4_canwrite(gpointer data, gint source, PurpleInputCondition cond)
  1370 {
  1371 	PurpleProxyConnectData *connect_data = data;
  1372 	int error = ETIMEDOUT;
  1373 	int ret;
  1374 
  1375 	purple_debug_info("socks4 proxy", "Connected.\n");
  1376 
  1377 	if (connect_data->inpa > 0) {
  1378 		purple_input_remove(connect_data->inpa);
  1379 		connect_data->inpa = 0;
  1380 	}
  1381 
  1382 	ret = purple_input_get_error(connect_data->fd, &error);
  1383 	if ((ret != 0) || (error != 0)) {
  1384 		if (ret != 0)
  1385 			error = errno;
  1386 		purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
  1387 		return;
  1388 	}
  1389 
  1390 	/*
  1391 	 * The socks4 spec doesn't include support for doing host name lookups by
  1392 	 * the proxy.  Many socks4 servers do this via the "socks4a" extension to
  1393 	 * the protocol.  There doesn't appear to be a way to detect if a server
  1394 	 * supports this, so we require that the user set a global option.
  1395 	 */
  1396 	if (purple_prefs_get_bool("/purple/proxy/socks4_remotedns")) {
  1397 		unsigned char packet[9];
  1398 		int len;
  1399 
  1400 		purple_debug_info("socks4 proxy", "Attempting to use remote DNS.\n");
  1401 
  1402 		packet[0] = 0x04;
  1403 		packet[1] = 0x01;
  1404 		packet[2] = connect_data->port >> 8;
  1405 		packet[3] = connect_data->port & 0xff;
  1406 		packet[4] = 0x00;
  1407 		packet[5] = 0x00;
  1408 		packet[6] = 0x00;
  1409 		packet[7] = 0x01;
  1410 		packet[8] = 0x00;
  1411 
  1412 		len = sizeof(packet) + strlen(connect_data->host) + 1;
  1413 
  1414 		connect_data->write_buffer = g_malloc0(len);
  1415 		memcpy(connect_data->write_buffer, packet, sizeof(packet));
  1416 		memcpy(connect_data->write_buffer + sizeof(packet), connect_data->host, strlen(connect_data->host));
  1417 		connect_data->write_buf_len = len;
  1418 		connect_data->written_len = 0;
  1419 		connect_data->read_cb = s4_canread;
  1420 
  1421 		connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
  1422 
  1423 		proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  1424 	} else {
  1425 		connect_data->query_data = purple_dnsquery_a_account(
  1426 				connect_data->account, connect_data->host,
  1427 				connect_data->port, s4_host_resolved, connect_data);
  1428 
  1429 		if (connect_data->query_data == NULL) {
  1430 			purple_debug_error("proxy", "dns query failed unexpectedly.\n");
  1431 			purple_proxy_connect_data_destroy(connect_data);
  1432 		}
  1433 	}
  1434 }
  1435 
  1436 static void
  1437 proxy_connect_socks4(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
  1438 {
  1439 	int flags;
  1440 
  1441 	purple_debug_info("proxy",
  1442 			   "Connecting to %s:%d via %s:%d using SOCKS4\n",
  1443 			   connect_data->host, connect_data->port,
  1444 			   purple_proxy_info_get_host(connect_data->gpi),
  1445 			   purple_proxy_info_get_port(connect_data->gpi));
  1446 
  1447 	connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
  1448 	if (connect_data->fd < 0)
  1449 	{
  1450 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  1451 				_("Unable to create socket: %s"), g_strerror(errno));
  1452 		return;
  1453 	}
  1454 
  1455 	flags = fcntl(connect_data->fd, F_GETFL);
  1456 	fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
  1457 #ifndef _WIN32
  1458 	fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
  1459 #endif
  1460 
  1461 	if (connect(connect_data->fd, addr, addrlen) != 0)
  1462 	{
  1463 		if ((errno == EINPROGRESS) || (errno == EINTR))
  1464 		{
  1465 			purple_debug_info("proxy", "Connection in progress.\n");
  1466 			connect_data->inpa = purple_input_add(connect_data->fd,
  1467 					PURPLE_INPUT_WRITE, s4_canwrite, connect_data);
  1468 		}
  1469 		else
  1470 		{
  1471 			purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
  1472 		}
  1473 	}
  1474 	else
  1475 	{
  1476 		purple_debug_info("proxy", "Connected immediately.\n");
  1477 
  1478 		s4_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  1479 	}
  1480 }
  1481 
  1482 static gboolean
  1483 s5_ensure_buffer_length(PurpleProxyConnectData *connect_data, int len)
  1484 {
  1485 	if(connect_data->read_len < len) {
  1486 		if(connect_data->read_buf_len < len) {
  1487 			/* it's not just that we haven't read enough, it's that we haven't tried to read enough yet */
  1488 			purple_debug_info("s5", "reallocing from %" G_GSIZE_FORMAT
  1489 					" to %d\n", connect_data->read_buf_len, len);
  1490 			connect_data->read_buf_len = len;
  1491 			connect_data->read_buffer = g_realloc(connect_data->read_buffer, connect_data->read_buf_len);
  1492 		}
  1493 		return FALSE;
  1494 	}
  1495 
  1496 	return TRUE;
  1497 }
  1498 
  1499 static void
  1500 s5_canread_again(gpointer data, gint source, PurpleInputCondition cond)
  1501 {
  1502 	guchar *dest, *buf;
  1503 	PurpleProxyConnectData *connect_data = data;
  1504 	int len;
  1505 
  1506 	if (connect_data->read_buffer == NULL) {
  1507 		connect_data->read_buf_len = 5;
  1508 		connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
  1509 		connect_data->read_len = 0;
  1510 	}
  1511 
  1512 	dest = connect_data->read_buffer + connect_data->read_len;
  1513 	buf = connect_data->read_buffer;
  1514 
  1515 	len = read(connect_data->fd, dest, (connect_data->read_buf_len - connect_data->read_len));
  1516 
  1517 	if (len == 0)
  1518 	{
  1519 		purple_proxy_connect_data_disconnect(connect_data,
  1520 				_("Server closed the connection"));
  1521 		return;
  1522 	}
  1523 
  1524 	if (len < 0)
  1525 	{
  1526 		if (errno == EAGAIN)
  1527 			/* No worries */
  1528 			return;
  1529 
  1530 		/* Error! */
  1531 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  1532 				_("Lost connection with server: %s"), g_strerror(errno));
  1533 		return;
  1534 	}
  1535 
  1536 	connect_data->read_len += len;
  1537 
  1538 	if(connect_data->read_len < 4)
  1539 		return;
  1540 
  1541 	if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
  1542 		if ((buf[0] == 0x05) && (buf[1] < 0x09)) {
  1543 			purple_debug_error("socks5 proxy", "%s", socks5errors[buf[1]]);
  1544 			purple_proxy_connect_data_disconnect(connect_data,
  1545 					socks5errors[buf[1]]);
  1546 		} else {
  1547 			purple_debug_error("socks5 proxy", "Bad data.\n");
  1548 			purple_proxy_connect_data_disconnect(connect_data,
  1549 					_("Received invalid data on connection with server"));
  1550 		}
  1551 		return;
  1552 	}
  1553 
  1554 	/* Skip past BND.ADDR */
  1555 	switch(buf[3]) {
  1556 		case 0x01: /* the address is a version-4 IP address, with a length of 4 octets */
  1557 			if(!s5_ensure_buffer_length(connect_data, 4 + 4))
  1558 				return;
  1559 			buf += 4 + 4;
  1560 			break;
  1561 		case 0x03: /* the address field contains a fully-qualified domain name.  The first
  1562 					  octet of the address field contains the number of octets of name that
  1563 					  follow, there is no terminating NUL octet. */
  1564 			if(!s5_ensure_buffer_length(connect_data, 4 + 1))
  1565 				return;
  1566 			buf += 4;
  1567 			if(!s5_ensure_buffer_length(connect_data, 4 + 1 + buf[0]))
  1568 				return;
  1569 			buf += buf[0] + 1;
  1570 			break;
  1571 		case 0x04: /* the address is a version-6 IP address, with a length of 16 octets */
  1572 			if(!s5_ensure_buffer_length(connect_data, 4 + 16))
  1573 				return;
  1574 			buf += 4 + 16;
  1575 			break;
  1576 		default:
  1577 			purple_debug_error("socks5 proxy", "Invalid ATYP received (0x%X)\n", buf[3]);
  1578 			purple_proxy_connect_data_disconnect(connect_data,
  1579 					_("Received invalid data on connection with server"));
  1580 			return;
  1581 	}
  1582 
  1583 	/* Skip past BND.PORT */
  1584 	if(!s5_ensure_buffer_length(connect_data, (buf - connect_data->read_buffer) + 2))
  1585 		return;
  1586 
  1587 	purple_proxy_connect_data_connected(connect_data);
  1588 }
  1589 
  1590 static void
  1591 s5_sendconnect(gpointer data, int source)
  1592 {
  1593 	PurpleProxyConnectData *connect_data = data;
  1594 	size_t hlen = strlen(connect_data->host);
  1595 	connect_data->write_buf_len = 5 + hlen + 2;
  1596 	connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
  1597 	connect_data->written_len = 0;
  1598 
  1599 	connect_data->write_buffer[0] = 0x05;
  1600 	connect_data->write_buffer[1] = 0x01;		/* CONNECT */
  1601 	connect_data->write_buffer[2] = 0x00;		/* reserved */
  1602 	connect_data->write_buffer[3] = 0x03;		/* address type -- host name */
  1603 	connect_data->write_buffer[4] = hlen;
  1604 	memcpy(connect_data->write_buffer + 5, connect_data->host, hlen);
  1605 	connect_data->write_buffer[5 + hlen] = connect_data->port >> 8;
  1606 	connect_data->write_buffer[5 + hlen + 1] = connect_data->port & 0xff;
  1607 
  1608 	connect_data->read_cb = s5_canread_again;
  1609 
  1610 	connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
  1611 	proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  1612 }
  1613 
  1614 static void
  1615 s5_readauth(gpointer data, gint source, PurpleInputCondition cond)
  1616 {
  1617 	PurpleProxyConnectData *connect_data = data;
  1618 	int len;
  1619 
  1620 	if (connect_data->read_buffer == NULL) {
  1621 		connect_data->read_buf_len = 2;
  1622 		connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
  1623 		connect_data->read_len = 0;
  1624 	}
  1625 
  1626 	purple_debug_info("socks5 proxy", "Got auth response.\n");
  1627 
  1628 	len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
  1629 		connect_data->read_buf_len - connect_data->read_len);
  1630 
  1631 	if (len == 0)
  1632 	{
  1633 		purple_proxy_connect_data_disconnect(connect_data,
  1634 				_("Server closed the connection"));
  1635 		return;
  1636 	}
  1637 
  1638 	if (len < 0)
  1639 	{
  1640 		if (errno == EAGAIN)
  1641 			/* No worries */
  1642 			return;
  1643 
  1644 		/* Error! */
  1645 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  1646 				_("Lost connection with server: %s"), g_strerror(errno));
  1647 		return;
  1648 	}
  1649 
  1650 	connect_data->read_len += len;
  1651 	if (connect_data->read_len < 2)
  1652 		return;
  1653 
  1654 	purple_input_remove(connect_data->inpa);
  1655 	connect_data->inpa = 0;
  1656 
  1657 	if ((connect_data->read_buffer[0] != 0x01) || (connect_data->read_buffer[1] != 0x00)) {
  1658 		purple_proxy_connect_data_disconnect(connect_data,
  1659 				_("Received invalid data on connection with server"));
  1660 		return;
  1661 	}
  1662 
  1663 	g_free(connect_data->read_buffer);
  1664 	connect_data->read_buffer = NULL;
  1665 
  1666 	s5_sendconnect(connect_data, connect_data->fd);
  1667 }
  1668 
  1669 static void
  1670 hmacmd5_chap(const unsigned char * challenge, int challen, const char * passwd, unsigned char * response)
  1671 {
  1672 	PurpleCipher *cipher;
  1673 	PurpleCipherContext *ctx;
  1674 	int i;
  1675 	unsigned char Kxoripad[65];
  1676 	unsigned char Kxoropad[65];
  1677 	size_t pwlen;
  1678 
  1679 	cipher = purple_ciphers_find_cipher("md5");
  1680 	ctx = purple_cipher_context_new(cipher, NULL);
  1681 
  1682 	memset(Kxoripad,0,sizeof(Kxoripad));
  1683 	memset(Kxoropad,0,sizeof(Kxoropad));
  1684 
  1685 	pwlen=strlen(passwd);
  1686 	if (pwlen>64) {
  1687 		purple_cipher_context_append(ctx, (const guchar *)passwd, strlen(passwd));
  1688 		purple_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL);
  1689 		pwlen=16;
  1690 	} else {
  1691 		memcpy(Kxoripad, passwd, pwlen);
  1692 	}
  1693 	memcpy(Kxoropad,Kxoripad,pwlen);
  1694 
  1695 	for (i=0;i<64;i++) {
  1696 		Kxoripad[i]^=0x36;
  1697 		Kxoropad[i]^=0x5c;
  1698 	}
  1699 
  1700 	purple_cipher_context_reset(ctx, NULL);
  1701 	purple_cipher_context_append(ctx, Kxoripad, 64);
  1702 	purple_cipher_context_append(ctx, challenge, challen);
  1703 	purple_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL);
  1704 
  1705 	purple_cipher_context_reset(ctx, NULL);
  1706 	purple_cipher_context_append(ctx, Kxoropad, 64);
  1707 	purple_cipher_context_append(ctx, Kxoripad, 16);
  1708 	purple_cipher_context_digest(ctx, 16, response, NULL);
  1709 
  1710 	purple_cipher_context_destroy(ctx);
  1711 }
  1712 
  1713 static void
  1714 s5_readchap(gpointer data, gint source, PurpleInputCondition cond);
  1715 
  1716 /*
  1717  * Return how many bytes we processed
  1718  * -1 means we've shouldn't keep reading from the buffer
  1719  */
  1720 static gssize
  1721 s5_parse_chap_msg(PurpleProxyConnectData *connect_data)
  1722 {
  1723 	guchar *buf, *cmdbuf = connect_data->read_buffer;
  1724 	int len, navas, currentav;
  1725 
  1726 	purple_debug_misc("socks5 proxy", "Reading CHAP message: %x\n", *cmdbuf);
  1727 
  1728 	if (*cmdbuf != 0x01) {
  1729 		purple_proxy_connect_data_disconnect(connect_data,
  1730 				_("Received invalid data on connection with server"));
  1731 		return -1;
  1732 	}
  1733 	cmdbuf++;
  1734 
  1735 	navas = *cmdbuf;
  1736 
  1737 	purple_debug_misc("socks5 proxy", "Expecting %d attribute(s).\n", navas);
  1738 
  1739 	cmdbuf++;
  1740 
  1741 	for (currentav = 0; currentav < navas; currentav++) {
  1742 
  1743 		len = connect_data->read_len - (cmdbuf - connect_data->read_buffer);
  1744 		/* We don't have enough data to even know how long the next attribute is,
  1745 		 * or we don't have the full length of the next attribute. */
  1746 		if (len < 2 || len < (cmdbuf[1] + 2)) {
  1747 			/* Clear out the attributes that have been read - decrease the attribute count */
  1748 			connect_data->read_buffer[1] = navas - currentav;
  1749 			/* Move the unprocessed data into the first attribute position */
  1750 			memmove((connect_data->read_buffer + 2), cmdbuf, len);
  1751 			/* Decrease the read count accordingly */
  1752 			connect_data->read_len = len + 2;
  1753 
  1754 			purple_debug_info("socks5 proxy", "Need more data to retrieve attribute %d.\n", currentav);
  1755 
  1756 			return -1;
  1757 		}
  1758 
  1759 		buf = cmdbuf + 2;
  1760 
  1761 		if (cmdbuf[1] == 0) {
  1762 			purple_debug_error("socks5 proxy", "Attribute %x Value length of 0; ignoring.\n", cmdbuf[0]);
  1763 			cmdbuf = buf;
  1764 			continue;
  1765 		}
  1766 
  1767 		switch (cmdbuf[0]) {
  1768 			case 0x00:
  1769 				purple_debug_info("socks5 proxy", "Received STATUS of %x\n", buf[0]);
  1770 				/* Did auth work? */
  1771 				if (buf[0] == 0x00) {
  1772 					purple_input_remove(connect_data->inpa);
  1773 					connect_data->inpa = 0;
  1774 					g_free(connect_data->read_buffer);
  1775 					connect_data->read_buffer = NULL;
  1776 					/* Success */
  1777 					s5_sendconnect(connect_data, connect_data->fd);
  1778 				} else {
  1779 					/* Failure */
  1780 					purple_debug_warning("proxy",
  1781 						"socks5 CHAP authentication "
  1782 						"failed.  Disconnecting...");
  1783 					purple_proxy_connect_data_disconnect(connect_data,
  1784 							_("Authentication failed"));
  1785 				}
  1786 				return -1;
  1787 			case 0x01:
  1788 				/* We've already validated that cmdbuf[1] is sane. */
  1789 				purple_debug_info("socks5 proxy", "Received TEXT-MESSAGE of '%.*s'\n", (int) cmdbuf[1], buf);
  1790 				break;
  1791 			case 0x03:
  1792 				purple_debug_info("socks5 proxy", "Received CHALLENGE\n");
  1793 				/* Server wants our credentials */
  1794 
  1795 				connect_data->write_buf_len = 16 + 4;
  1796 				connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
  1797 				connect_data->written_len = 0;
  1798 
  1799 				hmacmd5_chap(buf, cmdbuf[1],
  1800 					purple_proxy_info_get_password(connect_data->gpi),
  1801 					connect_data->write_buffer + 4);
  1802 				/* TODO: What about USER-IDENTITY? */
  1803 				connect_data->write_buffer[0] = 0x01;
  1804 				connect_data->write_buffer[1] = 0x01;
  1805 				connect_data->write_buffer[2] = 0x04;
  1806 				connect_data->write_buffer[3] = 0x10;
  1807 
  1808 				purple_input_remove(connect_data->inpa);
  1809 				g_free(connect_data->read_buffer);
  1810 				connect_data->read_buffer = NULL;
  1811 
  1812 				connect_data->read_cb = s5_readchap;
  1813 
  1814 				connect_data->inpa = purple_input_add(connect_data->fd,
  1815 					PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
  1816 
  1817 				proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  1818 				return -1;
  1819 			case 0x11:
  1820 				purple_debug_info("socks5 proxy", "Received ALGORIGTHMS of %x\n", buf[0]);
  1821 				/* Server wants to select an algorithm */
  1822 				if (buf[0] != 0x85) {
  1823 					/* Only currently support HMAC-MD5 */
  1824 					purple_debug_warning("proxy",
  1825 						"Server tried to select an "
  1826 						"algorithm that we did not advertise "
  1827 						"as supporting.  This is a violation "
  1828 						"of the socks5 CHAP specification.  "
  1829 						"Disconnecting...");
  1830 					purple_proxy_connect_data_disconnect(connect_data,
  1831 							_("Received invalid data on connection with server"));
  1832 					return -1;
  1833 				}
  1834 				break;
  1835 			default:
  1836 				purple_debug_info("socks5 proxy", "Received unused command %x, length=%d\n", cmdbuf[0], cmdbuf[1]);
  1837 		}
  1838 		cmdbuf = buf + cmdbuf[1];
  1839 	}
  1840 
  1841 	return (cmdbuf - connect_data->read_buffer);
  1842 }
  1843 
  1844 static void
  1845 s5_readchap(gpointer data, gint source, PurpleInputCondition cond)
  1846 {
  1847 	gssize msg_ret;
  1848 	PurpleProxyConnectData *connect_data = data;
  1849 	int len;
  1850 
  1851 	purple_debug(PURPLE_DEBUG_INFO, "socks5 proxy", "Got CHAP response.\n");
  1852 
  1853 	if (connect_data->read_buffer == NULL) {
  1854 		/* A big enough butfer to read the message header (2 bytes) and at least one complete attribute and value (1 + 1 + 255). */
  1855 		connect_data->read_buf_len = 259;
  1856 		connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
  1857 		connect_data->read_len = 0;
  1858 	}
  1859 
  1860 	if (connect_data->read_buf_len - connect_data->read_len == 0) {
  1861 		/*If the stuff below is right, this shouldn't be possible. */
  1862 		purple_debug_error("socks5 proxy", "This is about to suck because the read buffer is full (shouldn't happen).\n");
  1863 	}
  1864 
  1865 	len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
  1866 		connect_data->read_buf_len - connect_data->read_len);
  1867 
  1868 	if (len == 0) {
  1869 		purple_proxy_connect_data_disconnect(connect_data,
  1870 				_("Server closed the connection"));
  1871 		return;
  1872 	}
  1873 
  1874 	if (len < 0) {
  1875 		if (errno == EAGAIN)
  1876 			/* No worries */
  1877 			return;
  1878 
  1879 		/* Error! */
  1880 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  1881 				_("Lost connection with server: %s"), g_strerror(errno));
  1882 		return;
  1883 	}
  1884 
  1885 	connect_data->read_len += len;
  1886 
  1887 	/* We may have read more than one message into the buffer, we need to make sure to process them all */
  1888 	while (1) {
  1889 
  1890 		/* We need more to be able to read this message */
  1891 		if (connect_data->read_len < 2)
  1892 			return;
  1893 
  1894 		msg_ret = s5_parse_chap_msg(connect_data);
  1895 
  1896 		if (msg_ret < 0)
  1897 			return;
  1898 
  1899 		/* See if we have another message already in the buffer */
  1900 		if ((len = connect_data->read_len - msg_ret) > 0) {
  1901 
  1902 			/* Move on to the next message */
  1903 			memmove(connect_data->read_buffer, connect_data->read_buffer + msg_ret, len);
  1904 			/* Decrease the read count accordingly */
  1905 			connect_data->read_len = len;
  1906 
  1907 			/* Try to read the message that connect_data->read_buffer now points to */
  1908 			continue;
  1909 		}
  1910 
  1911 		break;
  1912 	}
  1913 
  1914 	/* Fell through.  We ran out of CHAP events to process, but haven't
  1915 	 * succeeded or failed authentication - there may be more to come.
  1916 	 * If this is the case, come straight back here. */
  1917 
  1918 	purple_debug_info("socks5 proxy", "Waiting for another message from which to read CHAP info.\n");
  1919 
  1920 	/* We've processed all the available attributes, so get ready for a whole new message */
  1921  	g_free(connect_data->read_buffer);
  1922 	connect_data->read_buffer = NULL;
  1923 }
  1924 
  1925 static void
  1926 s5_canread(gpointer data, gint source, PurpleInputCondition cond)
  1927 {
  1928 	PurpleProxyConnectData *connect_data = data;
  1929 	int len;
  1930 
  1931 	if (connect_data->read_buffer == NULL) {
  1932 		connect_data->read_buf_len = 2;
  1933 		connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
  1934 		connect_data->read_len = 0;
  1935 	}
  1936 
  1937 	purple_debug_info("socks5 proxy", "Able to read.\n");
  1938 
  1939 	len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
  1940 		connect_data->read_buf_len - connect_data->read_len);
  1941 
  1942 	if (len == 0)
  1943 	{
  1944 		purple_proxy_connect_data_disconnect(connect_data,
  1945 				_("Server closed the connection"));
  1946 		return;
  1947 	}
  1948 
  1949 	if (len < 0)
  1950 	{
  1951 		if (errno == EAGAIN)
  1952 			/* No worries */
  1953 			return;
  1954 
  1955 		/* Error! */
  1956 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  1957 				_("Lost connection with server: %s"), g_strerror(errno));
  1958 		return;
  1959 	}
  1960 
  1961 	connect_data->read_len += len;
  1962 	if (connect_data->read_len < 2)
  1963 		return;
  1964 
  1965 	purple_input_remove(connect_data->inpa);
  1966 	connect_data->inpa = 0;
  1967 
  1968 	if ((connect_data->read_buffer[0] != 0x05) || (connect_data->read_buffer[1] == 0xff)) {
  1969 		purple_proxy_connect_data_disconnect(connect_data,
  1970 				_("Received invalid data on connection with server"));
  1971 		return;
  1972 	}
  1973 
  1974 	if (connect_data->read_buffer[1] == 0x02) {
  1975 		size_t i, j;
  1976 		const char *u, *p;
  1977 
  1978 		u = purple_proxy_info_get_username(connect_data->gpi);
  1979 		p = purple_proxy_info_get_password(connect_data->gpi);
  1980 
  1981 		i = (u == NULL) ? 0 : strlen(u);
  1982 		j = (p == NULL) ? 0 : strlen(p);
  1983 
  1984 		connect_data->write_buf_len = 1 + 1 + i + 1 + j;
  1985 		connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
  1986 		connect_data->written_len = 0;
  1987 
  1988 		connect_data->write_buffer[0] = 0x01;	/* version 1 */
  1989 		connect_data->write_buffer[1] = i;
  1990 		if (u != NULL)
  1991 			memcpy(connect_data->write_buffer + 2, u, i);
  1992 		connect_data->write_buffer[2 + i] = j;
  1993 		if (p != NULL)
  1994 			memcpy(connect_data->write_buffer + 2 + i + 1, p, j);
  1995 
  1996 		g_free(connect_data->read_buffer);
  1997 		connect_data->read_buffer = NULL;
  1998 
  1999 		connect_data->read_cb = s5_readauth;
  2000 
  2001 		connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE,
  2002 			proxy_do_write, connect_data);
  2003 
  2004 		proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  2005 
  2006 		return;
  2007 	} else if (connect_data->read_buffer[1] == 0x03) {
  2008 		size_t userlen;
  2009 		userlen = strlen(purple_proxy_info_get_username(connect_data->gpi));
  2010 
  2011 		connect_data->write_buf_len = 7 + userlen;
  2012 		connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
  2013 		connect_data->written_len = 0;
  2014 
  2015 		connect_data->write_buffer[0] = 0x01;
  2016 		connect_data->write_buffer[1] = 0x02;
  2017 		connect_data->write_buffer[2] = 0x11;
  2018 		connect_data->write_buffer[3] = 0x01;
  2019 		connect_data->write_buffer[4] = 0x85;
  2020 		connect_data->write_buffer[5] = 0x02;
  2021 		connect_data->write_buffer[6] = userlen;
  2022 		memcpy(connect_data->write_buffer + 7,
  2023 			purple_proxy_info_get_username(connect_data->gpi), userlen);
  2024 
  2025 		g_free(connect_data->read_buffer);
  2026 		connect_data->read_buffer = NULL;
  2027 
  2028 		connect_data->read_cb = s5_readchap;
  2029 
  2030 		connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE,
  2031 			proxy_do_write, connect_data);
  2032 
  2033 		proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  2034 
  2035 		return;
  2036 	} else {
  2037 		g_free(connect_data->read_buffer);
  2038 		connect_data->read_buffer = NULL;
  2039 
  2040 		s5_sendconnect(connect_data, connect_data->fd);
  2041 	}
  2042 }
  2043 
  2044 static void
  2045 s5_canwrite(gpointer data, gint source, PurpleInputCondition cond)
  2046 {
  2047 	unsigned char buf[5];
  2048 	int i;
  2049 	PurpleProxyConnectData *connect_data = data;
  2050 	int error = ETIMEDOUT;
  2051 	int ret;
  2052 
  2053 	purple_debug_info("socks5 proxy", "Connected.\n");
  2054 
  2055 	if (connect_data->inpa > 0)
  2056 	{
  2057 		purple_input_remove(connect_data->inpa);
  2058 		connect_data->inpa = 0;
  2059 	}
  2060 
  2061 	ret = purple_input_get_error(connect_data->fd, &error);
  2062 	if ((ret != 0) || (error != 0))
  2063 	{
  2064 		if (ret != 0)
  2065 			error = errno;
  2066 		purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
  2067 		return;
  2068 	}
  2069 
  2070 	i = 0;
  2071 	buf[0] = 0x05;		/* SOCKS version 5 */
  2072 
  2073 	if (purple_proxy_info_get_username(connect_data->gpi) != NULL) {
  2074 		buf[1] = 0x03;	/* three methods */
  2075 		buf[2] = 0x00;	/* no authentication */
  2076 		buf[3] = 0x03;	/* CHAP authentication */
  2077 		buf[4] = 0x02;	/* username/password authentication */
  2078 		i = 5;
  2079 	}
  2080 	else {
  2081 		buf[1] = 0x01;
  2082 		buf[2] = 0x00;
  2083 		i = 3;
  2084 	}
  2085 
  2086 	connect_data->write_buf_len = i;
  2087 	connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
  2088 	memcpy(connect_data->write_buffer, buf, i);
  2089 
  2090 	connect_data->read_cb = s5_canread;
  2091 
  2092 	connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
  2093 	proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  2094 }
  2095 
  2096 static void
  2097 proxy_connect_socks5(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
  2098 {
  2099 	int flags;
  2100 
  2101 	purple_debug_info("proxy",
  2102 			   "Connecting to %s:%d via %s:%d using SOCKS5\n",
  2103 			   connect_data->host, connect_data->port,
  2104 			   purple_proxy_info_get_host(connect_data->gpi),
  2105 			   purple_proxy_info_get_port(connect_data->gpi));
  2106 
  2107 	connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
  2108 	if (connect_data->fd < 0)
  2109 	{
  2110 		purple_proxy_connect_data_disconnect_formatted(connect_data,
  2111 				_("Unable to create socket: %s"), g_strerror(errno));
  2112 		return;
  2113 	}
  2114 
  2115 	flags = fcntl(connect_data->fd, F_GETFL);
  2116 	fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
  2117 #ifndef _WIN32
  2118 	fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
  2119 #endif
  2120 
  2121 	if (connect(connect_data->fd, addr, addrlen) != 0)
  2122 	{
  2123 		if ((errno == EINPROGRESS) || (errno == EINTR))
  2124 		{
  2125 			purple_debug_info("socks5 proxy", "Connection in progress\n");
  2126 			connect_data->inpa = purple_input_add(connect_data->fd,
  2127 					PURPLE_INPUT_WRITE, s5_canwrite, connect_data);
  2128 		}
  2129 		else
  2130 		{
  2131 			purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
  2132 		}
  2133 	}
  2134 	else
  2135 	{
  2136 		purple_debug_info("proxy", "Connected immediately.\n");
  2137 
  2138 		s5_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  2139 	}
  2140 }
  2141 
  2142 /**
  2143  * This function attempts to connect to the next IP address in the list
  2144  * of IP addresses returned to us by purple_dnsquery_a() and attemps
  2145  * to connect to each one.  This is called after the hostname is
  2146  * resolved, and each time a connection attempt fails (assuming there
  2147  * is another IP address to try).
  2148  */
  2149 #ifndef INET6_ADDRSTRLEN
  2150 #define INET6_ADDRSTRLEN 46
  2151 #endif
  2152 
  2153 static void try_connect(PurpleProxyConnectData *connect_data)
  2154 {
  2155 	socklen_t addrlen;
  2156 	struct sockaddr *addr;
  2157 	char ipaddr[INET6_ADDRSTRLEN];
  2158 
  2159 	addrlen = GPOINTER_TO_INT(connect_data->hosts->data);
  2160 	connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
  2161 	addr = connect_data->hosts->data;
  2162 	connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
  2163 #ifdef HAVE_INET_NTOP
  2164 	if (addr->sa_family == AF_INET)
  2165 		inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr,
  2166 				ipaddr, sizeof(ipaddr));
  2167 	else if (addr->sa_family == AF_INET6)
  2168 		inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr,
  2169 				ipaddr, sizeof(ipaddr));
  2170 #else
  2171 	memcpy(ipaddr, inet_ntoa(((struct sockaddr_in *)addr)->sin_addr),
  2172 			sizeof(ipaddr));
  2173 #endif
  2174 	purple_debug_info("proxy", "Attempting connection to %s\n", ipaddr);
  2175 
  2176 	if (connect_data->socket_type == SOCK_DGRAM) {
  2177 		proxy_connect_udp_none(connect_data, addr, addrlen);
  2178 		g_free(addr);
  2179 		return;
  2180 	}
  2181 
  2182 	switch (purple_proxy_info_get_type(connect_data->gpi)) {
  2183 		case PURPLE_PROXY_NONE:
  2184 			proxy_connect_none(connect_data, addr, addrlen);
  2185 			break;
  2186 
  2187 		case PURPLE_PROXY_HTTP:
  2188 			proxy_connect_http(connect_data, addr, addrlen);
  2189 			break;
  2190 
  2191 		case PURPLE_PROXY_SOCKS4:
  2192 			proxy_connect_socks4(connect_data, addr, addrlen);
  2193 			break;
  2194 
  2195 		case PURPLE_PROXY_SOCKS5:
  2196 		case PURPLE_PROXY_TOR:
  2197 			proxy_connect_socks5(connect_data, addr, addrlen);
  2198 			break;
  2199 
  2200 		case PURPLE_PROXY_USE_ENVVAR:
  2201 			proxy_connect_http(connect_data, addr, addrlen);
  2202 			break;
  2203 
  2204 		default:
  2205 			break;
  2206 	}
  2207 
  2208 	g_free(addr);
  2209 }
  2210 
  2211 static void
  2212 connection_host_resolved(GSList *hosts, gpointer data,
  2213 						 const char *error_message)
  2214 {
  2215 	PurpleProxyConnectData *connect_data;
  2216 
  2217 	connect_data = data;
  2218 	connect_data->query_data = NULL;
  2219 
  2220 	if (error_message != NULL)
  2221 	{
  2222 		purple_proxy_connect_data_disconnect(connect_data, error_message);
  2223 		return;
  2224 	}
  2225 
  2226 	if (hosts == NULL)
  2227 	{
  2228 		purple_proxy_connect_data_disconnect(connect_data, _("Unable to resolve hostname"));
  2229 		return;
  2230 	}
  2231 
  2232 	connect_data->hosts = hosts;
  2233 
  2234 	try_connect(connect_data);
  2235 }
  2236 
  2237 PurpleProxyInfo *
  2238 purple_proxy_get_setup(PurpleAccount *account)
  2239 {
  2240 	PurpleProxyInfo *gpi = NULL;
  2241 	const gchar *tmp;
  2242 
  2243 	/* This is used as a fallback so we don't overwrite the selected proxy type */
  2244 	static PurpleProxyInfo *tmp_none_proxy_info = NULL;
  2245 	if (!tmp_none_proxy_info) {
  2246 		tmp_none_proxy_info = purple_proxy_info_new();
  2247 		purple_proxy_info_set_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
  2248 	}
  2249 
  2250 	if (account && purple_account_get_proxy_info(account) != NULL) {
  2251 		gpi = purple_account_get_proxy_info(account);
  2252 		if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_GLOBAL)
  2253 			gpi = NULL;
  2254 	}
  2255 	if (gpi == NULL) {
  2256 		if (purple_running_gnome())
  2257 			gpi = purple_gnome_proxy_get_info();
  2258 		else
  2259 			gpi = purple_global_proxy_get_info();
  2260 	}
  2261 
  2262 	if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR) {
  2263 		if ((tmp = g_getenv("HTTP_PROXY")) != NULL ||
  2264 			(tmp = g_getenv("http_proxy")) != NULL ||
  2265 			(tmp = g_getenv("HTTPPROXY")) != NULL) {
  2266 			char *proxyhost, *proxyuser, *proxypasswd;
  2267 			int proxyport;
  2268 
  2269 			/* http_proxy-format:
  2270 			 * export http_proxy="http://user:passwd@your.proxy.server:port/"
  2271 			 */
  2272 			if(purple_url_parse(tmp, &proxyhost, &proxyport, NULL, &proxyuser, &proxypasswd)) {
  2273 				purple_proxy_info_set_host(gpi, proxyhost);
  2274 				g_free(proxyhost);
  2275 
  2276 				purple_proxy_info_set_username(gpi, proxyuser);
  2277 				g_free(proxyuser);
  2278 
  2279 				purple_proxy_info_set_password(gpi, proxypasswd);
  2280 				g_free(proxypasswd);
  2281 
  2282 				/* only for backward compatibility */
  2283 				if (proxyport == 80 &&
  2284 				    ((tmp = g_getenv("HTTP_PROXY_PORT")) != NULL ||
  2285 				     (tmp = g_getenv("http_proxy_port")) != NULL ||
  2286 				     (tmp = g_getenv("HTTPPROXYPORT")) != NULL))
  2287 					proxyport = atoi(tmp);
  2288 
  2289 				purple_proxy_info_set_port(gpi, proxyport);
  2290 
  2291 				/* XXX: Do we want to skip this step if user/password were part of url? */
  2292 				if ((tmp = g_getenv("HTTP_PROXY_USER")) != NULL ||
  2293 					(tmp = g_getenv("http_proxy_user")) != NULL ||
  2294 					(tmp = g_getenv("HTTPPROXYUSER")) != NULL)
  2295 					purple_proxy_info_set_username(gpi, tmp);
  2296 
  2297 				if ((tmp = g_getenv("HTTP_PROXY_PASS")) != NULL ||
  2298 					(tmp = g_getenv("http_proxy_pass")) != NULL ||
  2299 					(tmp = g_getenv("HTTPPROXYPASS")) != NULL)
  2300 					purple_proxy_info_set_password(gpi, tmp);
  2301 
  2302 			}
  2303 		} else {
  2304 #ifdef _WIN32
  2305 			PurpleProxyInfo *wgpi;
  2306 			if ((wgpi = purple_win32_proxy_get_info()) != NULL)
  2307 				return wgpi;
  2308 #endif
  2309 			/* no proxy environment variable found, don't use a proxy */
  2310 			purple_debug_info("proxy", "No environment settings found, not using a proxy\n");
  2311 			gpi = tmp_none_proxy_info;
  2312 		}
  2313 
  2314 	}
  2315 
  2316 	return gpi;
  2317 }
  2318 
  2319 PurpleProxyConnectData *
  2320 purple_proxy_connect(void *handle, PurpleAccount *account,
  2321 				   const char *host, int port,
  2322 				   PurpleProxyConnectFunction connect_cb, gpointer data)
  2323 {
  2324 	const char *connecthost = host;
  2325 	int connectport = port;
  2326 	PurpleProxyConnectData *connect_data;
  2327 
  2328 	g_return_val_if_fail(host       != NULL, NULL);
  2329 	g_return_val_if_fail(port       >  0,    NULL);
  2330 	g_return_val_if_fail(connect_cb != NULL, NULL);
  2331 
  2332 	connect_data = g_new0(PurpleProxyConnectData, 1);
  2333 	connect_data->fd = -1;
  2334 	connect_data->socket_type = SOCK_STREAM;
  2335 	connect_data->handle = handle;
  2336 	connect_data->connect_cb = connect_cb;
  2337 	connect_data->data = data;
  2338 	connect_data->host = g_strdup(host);
  2339 	connect_data->port = port;
  2340 	connect_data->gpi = purple_proxy_get_setup(account);
  2341 	connect_data->account = account;
  2342 
  2343 	if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) &&
  2344 		(purple_proxy_info_get_host(connect_data->gpi) == NULL ||
  2345 		 purple_proxy_info_get_port(connect_data->gpi) <= 0)) {
  2346 
  2347 		purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid."));
  2348 		purple_proxy_connect_data_destroy(connect_data);
  2349 		return NULL;
  2350 	}
  2351 
  2352 	switch (purple_proxy_info_get_type(connect_data->gpi))
  2353 	{
  2354 		case PURPLE_PROXY_NONE:
  2355 			break;
  2356 
  2357 		case PURPLE_PROXY_HTTP:
  2358 		case PURPLE_PROXY_SOCKS4:
  2359 		case PURPLE_PROXY_SOCKS5:
  2360 		case PURPLE_PROXY_TOR:
  2361 		case PURPLE_PROXY_USE_ENVVAR:
  2362 			connecthost = purple_proxy_info_get_host(connect_data->gpi);
  2363 			connectport = purple_proxy_info_get_port(connect_data->gpi);
  2364 			break;
  2365 
  2366 		default:
  2367 			purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n",
  2368 							   purple_proxy_info_get_type(connect_data->gpi));
  2369 			purple_proxy_connect_data_destroy(connect_data);
  2370 			return NULL;
  2371 	}
  2372 
  2373 	connect_data->query_data = purple_dnsquery_a_account(account, connecthost,
  2374 			connectport, connection_host_resolved, connect_data);
  2375 	if (connect_data->query_data == NULL)
  2376 	{
  2377 		purple_debug_error("proxy", "dns query failed unexpectedly.\n");
  2378 		purple_proxy_connect_data_destroy(connect_data);
  2379 		return NULL;
  2380 	}
  2381 
  2382 	handles = g_slist_prepend(handles, connect_data);
  2383 
  2384 	return connect_data;
  2385 }
  2386 
  2387 PurpleProxyConnectData *
  2388 purple_proxy_connect_udp(void *handle, PurpleAccount *account,
  2389 				   const char *host, int port,
  2390 				   PurpleProxyConnectFunction connect_cb, gpointer data)
  2391 {
  2392 	const char *connecthost = host;
  2393 	int connectport = port;
  2394 	PurpleProxyConnectData *connect_data;
  2395 
  2396 	g_return_val_if_fail(host       != NULL, NULL);
  2397 	g_return_val_if_fail(port       >  0,    NULL);
  2398 	g_return_val_if_fail(connect_cb != NULL, NULL);
  2399 
  2400 	connect_data = g_new0(PurpleProxyConnectData, 1);
  2401 	connect_data->fd = -1;
  2402 	connect_data->socket_type = SOCK_DGRAM;
  2403 	connect_data->handle = handle;
  2404 	connect_data->connect_cb = connect_cb;
  2405 	connect_data->data = data;
  2406 	connect_data->host = g_strdup(host);
  2407 	connect_data->port = port;
  2408 	connect_data->gpi = purple_proxy_get_setup(account);
  2409 	connect_data->account = account;
  2410 
  2411 	if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) &&
  2412 		(purple_proxy_info_get_host(connect_data->gpi) == NULL ||
  2413 		 purple_proxy_info_get_port(connect_data->gpi) <= 0)) {
  2414 
  2415 		purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid."));
  2416 		purple_proxy_connect_data_destroy(connect_data);
  2417 		return NULL;
  2418 	}
  2419 
  2420 	switch (purple_proxy_info_get_type(connect_data->gpi))
  2421 	{
  2422 		case PURPLE_PROXY_NONE:
  2423 			break;
  2424 
  2425 		case PURPLE_PROXY_HTTP:
  2426 		case PURPLE_PROXY_SOCKS4:
  2427 		case PURPLE_PROXY_SOCKS5:
  2428 		case PURPLE_PROXY_TOR:
  2429 		case PURPLE_PROXY_USE_ENVVAR:
  2430 			purple_debug_info("proxy", "Ignoring Proxy type (%d) for UDP.\n",
  2431 			                  purple_proxy_info_get_type(connect_data->gpi));
  2432 			break;
  2433 
  2434 		default:
  2435 			purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n",
  2436 			                   purple_proxy_info_get_type(connect_data->gpi));
  2437 			purple_proxy_connect_data_destroy(connect_data);
  2438 			return NULL;
  2439 	}
  2440 
  2441 	connect_data->query_data = purple_dnsquery_a_account(account, connecthost,
  2442 			connectport, connection_host_resolved, connect_data);
  2443 	if (connect_data->query_data == NULL)
  2444 	{
  2445 		purple_proxy_connect_data_destroy(connect_data);
  2446 		return NULL;
  2447 	}
  2448 
  2449 	handles = g_slist_prepend(handles, connect_data);
  2450 
  2451 	return connect_data;
  2452 }
  2453 
  2454 PurpleProxyConnectData *
  2455 purple_proxy_connect_socks5(void *handle, PurpleProxyInfo *gpi,
  2456 						  const char *host, int port,
  2457 						  PurpleProxyConnectFunction connect_cb,
  2458 						  gpointer data)
  2459 {
  2460 	return purple_proxy_connect_socks5_account(handle, NULL, gpi,
  2461 						  host, port, connect_cb, data);
  2462 }
  2463 
  2464 
  2465 /* This is called when we connect to the SOCKS5 proxy server (through any
  2466  * relevant account proxy)
  2467  */
  2468 static void socks5_connected_to_proxy(gpointer data, gint source,
  2469 		const gchar *error_message) {
  2470 	/* This is the PurpleProxyConnectData for the overall SOCKS5 connection */
  2471 	PurpleProxyConnectData *connect_data = data;
  2472 
  2473 	purple_debug_error("proxy", "Connect Data is %p\n", connect_data);
  2474 
  2475 	/* Check that the overall SOCKS5 connection wasn't cancelled while we were
  2476 	 * connecting to it (we don't have a way of associating the process of
  2477 	 * connecting to the SOCKS5 server to the overall PurpleProxyConnectData)
  2478 	 */
  2479 	if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data)) {
  2480 		purple_debug_error("proxy", "Data had gone out of scope :(\n");
  2481 		return;
  2482 	}
  2483 
  2484 	/* Break the link between the two PurpleProxyConnectDatas  */
  2485 	connect_data->child = NULL;
  2486 
  2487 	if (error_message != NULL) {
  2488 		purple_debug_error("proxy", "Unable to connect to SOCKS5 host.\n");
  2489 		connect_data->connect_cb(connect_data->data, source, error_message);
  2490 		return;
  2491 	}
  2492 
  2493 	purple_debug_info("proxy", "Initiating SOCKS5 negotiation.\n");
  2494 
  2495 	purple_debug_info("proxy",
  2496 			   "Connecting to %s:%d via %s:%d using SOCKS5\n",
  2497 			   connect_data->host, connect_data->port,
  2498 			   purple_proxy_info_get_host(connect_data->gpi),
  2499 			   purple_proxy_info_get_port(connect_data->gpi));
  2500 
  2501 	connect_data->fd = source;
  2502 
  2503 	s5_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
  2504 }
  2505 
  2506 /*
  2507  * Combine some of this code with purple_proxy_connect()
  2508  */
  2509 PurpleProxyConnectData *
  2510 purple_proxy_connect_socks5_account(void *handle, PurpleAccount *account,
  2511 						  PurpleProxyInfo *gpi,
  2512 						  const char *host, int port,
  2513 						  PurpleProxyConnectFunction connect_cb,
  2514 						  gpointer data)
  2515 {
  2516 	PurpleProxyConnectData *connect_data;
  2517 	PurpleProxyConnectData *account_proxy_conn_data;
  2518 
  2519 	g_return_val_if_fail(host       != NULL, NULL);
  2520 	g_return_val_if_fail(port       >= 0,    NULL);
  2521 	g_return_val_if_fail(connect_cb != NULL, NULL);
  2522 
  2523 	connect_data = g_new0(PurpleProxyConnectData, 1);
  2524 	connect_data->fd = -1;
  2525 	connect_data->socket_type = SOCK_STREAM;
  2526 	connect_data->handle = handle;
  2527 	connect_data->connect_cb = connect_cb;
  2528 	connect_data->data = data;
  2529 	connect_data->host = g_strdup(host);
  2530 	connect_data->port = port;
  2531 	connect_data->gpi = gpi;
  2532 	connect_data->account = account;
  2533 
  2534 	/* If there is an account proxy, use it to connect to the desired SOCKS5
  2535 	 * proxy.
  2536 	 */
  2537 	account_proxy_conn_data = purple_proxy_connect(connect_data->handle,
  2538 				connect_data->account,
  2539 				purple_proxy_info_get_host(connect_data->gpi),
  2540 				purple_proxy_info_get_port(connect_data->gpi),
  2541 				socks5_connected_to_proxy, connect_data);
  2542 
  2543 	if (account_proxy_conn_data == NULL) {
  2544 		purple_debug_error("proxy", "Unable to initiate connection to account proxy.\n");
  2545 		purple_proxy_connect_data_destroy(connect_data);
  2546 		return NULL;
  2547 	}
  2548 
  2549 	connect_data->child = account_proxy_conn_data;
  2550 
  2551 	handles = g_slist_prepend(handles, connect_data);
  2552 
  2553 	return connect_data;
  2554 }
  2555 
  2556 void
  2557 purple_proxy_connect_cancel(PurpleProxyConnectData *connect_data)
  2558 {
  2559 	g_return_if_fail(connect_data != NULL);
  2560 
  2561 	purple_proxy_connect_data_disconnect(connect_data, NULL);
  2562 	purple_proxy_connect_data_destroy(connect_data);
  2563 }
  2564 
  2565 void
  2566 purple_proxy_connect_cancel_with_handle(void *handle)
  2567 {
  2568 	GSList *l, *l_next;
  2569 
  2570 	for (l = handles; l != NULL; l = l_next) {
  2571 		PurpleProxyConnectData *connect_data = l->data;
  2572 
  2573 		l_next = l->next;
  2574 
  2575 		if (connect_data->handle == handle)
  2576 			purple_proxy_connect_cancel(connect_data);
  2577 	}
  2578 }
  2579 
  2580 static void
  2581 proxy_pref_cb(const char *name, PurplePrefType type,
  2582 			  gconstpointer value, gpointer data)
  2583 {
  2584 	PurpleProxyInfo *info = purple_global_proxy_get_info();
  2585 
  2586 	if (purple_strequal(name, "/purple/proxy/type")) {
  2587 		int proxytype;
  2588 		const char *type = value;
  2589 
  2590 		if (purple_strequal(type, "none"))
  2591 			proxytype = PURPLE_PROXY_NONE;
  2592 		else if (purple_strequal(type, "http"))
  2593 			proxytype = PURPLE_PROXY_HTTP;
  2594 		else if (purple_strequal(type, "socks4"))
  2595 			proxytype = PURPLE_PROXY_SOCKS4;
  2596 		else if (purple_strequal(type, "socks5"))
  2597 			proxytype = PURPLE_PROXY_SOCKS5;
  2598 		else if (purple_strequal(type, "tor"))
  2599 			proxytype = PURPLE_PROXY_TOR;
  2600 		else if (purple_strequal(type, "envvar"))
  2601 			proxytype = PURPLE_PROXY_USE_ENVVAR;
  2602 		else
  2603 			proxytype = -1;
  2604 
  2605 		purple_proxy_info_set_type(info, proxytype);
  2606 	} else if (purple_strequal(name, "/purple/proxy/host"))
  2607 		purple_proxy_info_set_host(info, value);
  2608 	else if (purple_strequal(name, "/purple/proxy/port"))
  2609 		purple_proxy_info_set_port(info, GPOINTER_TO_INT(value));
  2610 	else if (purple_strequal(name, "/purple/proxy/username"))
  2611 		purple_proxy_info_set_username(info, value);
  2612 	else if (purple_strequal(name, "/purple/proxy/password"))
  2613 		purple_proxy_info_set_password(info, value);
  2614 }
  2615 
  2616 void *
  2617 purple_proxy_get_handle()
  2618 {
  2619 	static int handle;
  2620 
  2621 	return &handle;
  2622 }
  2623 
  2624 void
  2625 purple_proxy_init(void)
  2626 {
  2627 	void *handle;
  2628 
  2629 	/* Initialize a default proxy info struct. */
  2630 	global_proxy_info = purple_proxy_info_new();
  2631 
  2632 	/* Proxy */
  2633 	purple_prefs_add_none("/purple/proxy");
  2634 	purple_prefs_add_string("/purple/proxy/type", "none");
  2635 	purple_prefs_add_string("/purple/proxy/host", "");
  2636 	purple_prefs_add_int("/purple/proxy/port", 0);
  2637 	purple_prefs_add_string("/purple/proxy/username", "");
  2638 	purple_prefs_add_string("/purple/proxy/password", "");
  2639 	purple_prefs_add_bool("/purple/proxy/socks4_remotedns", FALSE);
  2640 
  2641 	/* Setup callbacks for the preferences. */
  2642 	handle = purple_proxy_get_handle();
  2643 	purple_prefs_connect_callback(handle, "/purple/proxy/type", proxy_pref_cb,
  2644 		NULL);
  2645 	purple_prefs_connect_callback(handle, "/purple/proxy/host", proxy_pref_cb,
  2646 		NULL);
  2647 	purple_prefs_connect_callback(handle, "/purple/proxy/port", proxy_pref_cb,
  2648 		NULL);
  2649 	purple_prefs_connect_callback(handle, "/purple/proxy/username",
  2650 		proxy_pref_cb, NULL);
  2651 	purple_prefs_connect_callback(handle, "/purple/proxy/password",
  2652 		proxy_pref_cb, NULL);
  2653 
  2654 	/* Load the initial proxy settings */
  2655 	purple_prefs_trigger_callback("/purple/proxy/type");
  2656 	purple_prefs_trigger_callback("/purple/proxy/host");
  2657 	purple_prefs_trigger_callback("/purple/proxy/port");
  2658 	purple_prefs_trigger_callback("/purple/proxy/username");
  2659 	purple_prefs_trigger_callback("/purple/proxy/password");
  2660 }
  2661 
  2662 void
  2663 purple_proxy_uninit(void)
  2664 {
  2665 	while (handles != NULL)
  2666 	{
  2667 		purple_proxy_connect_data_disconnect(handles->data, NULL);
  2668 		purple_proxy_connect_data_destroy(handles->data);
  2669 	}
  2670 
  2671 	purple_prefs_disconnect_by_handle(purple_proxy_get_handle());
  2672 
  2673 	purple_proxy_info_destroy(global_proxy_info);
  2674 	global_proxy_info = NULL;
  2675 }