* @file proxy.c Proxy API * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA /* this is a little piece of code to handle proxy connection */ /* it is intended to : 1st handle http proxy, using the CONNECT command , 2nd provide an easy way to add socks support , 3rd draw women to it like flies to honey */ struct _PurpleProxyConnectData { PurpleProxyConnectFunction connect_cb; PurpleDnsQueryData *query_data; * This contains alternating length/char* values. The char* * values need to be freed when removed from the linked list. PurpleProxyConnectData *child; * All of the following variables are used when establishing a * connection through a proxy. PurpleInputFunction read_cb; static const char * const socks5errors[] = { "general SOCKS server failure\n", "connection not allowed by ruleset\n", "Command not supported\n", "Address type not supported\n" static PurpleProxyInfo *global_proxy_info = NULL; static GSList *handles = NULL; static void try_connect(PurpleProxyConnectData *connect_data); * TODO: Eventually (GObjectification) this bad boy will be removed, because it is * a gross fix for a crashy problem. #define PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data) g_slist_find(handles, connect_data) /************************************************************************** **************************************************************************/ purple_proxy_info_new(void) return g_new0(PurpleProxyInfo, 1); purple_proxy_info_destroy(PurpleProxyInfo *info) g_return_if_fail(info != NULL); purple_proxy_info_set_type(PurpleProxyInfo *info, PurpleProxyType type) g_return_if_fail(info != NULL); purple_proxy_info_set_host(PurpleProxyInfo *info, const char *host) g_return_if_fail(info != NULL); info->host = g_strdup(host); purple_proxy_info_set_port(PurpleProxyInfo *info, int port) g_return_if_fail(info != NULL); purple_proxy_info_set_username(PurpleProxyInfo *info, const char *username) g_return_if_fail(info != NULL); info->username = g_strdup(username); purple_proxy_info_set_password(PurpleProxyInfo *info, const char *password) g_return_if_fail(info != NULL); info->password = g_strdup(password); purple_proxy_info_get_type(const PurpleProxyInfo *info) g_return_val_if_fail(info != NULL, PURPLE_PROXY_NONE); purple_proxy_info_get_host(const PurpleProxyInfo *info) g_return_val_if_fail(info != NULL, NULL); purple_proxy_info_get_port(const PurpleProxyInfo *info) g_return_val_if_fail(info != NULL, 0); purple_proxy_info_get_username(const PurpleProxyInfo *info) g_return_val_if_fail(info != NULL, NULL); purple_proxy_info_get_password(const PurpleProxyInfo *info) g_return_val_if_fail(info != NULL, NULL); /************************************************************************** **************************************************************************/ purple_global_proxy_get_info(void) return global_proxy_info; purple_global_proxy_set_info(PurpleProxyInfo *info) g_return_if_fail(info != NULL); purple_proxy_info_destroy(global_proxy_info); global_proxy_info = info; /* index in gproxycmds below, keep them in sync */ #define GNOME_PROXY_MODE 0 #define GNOME_PROXY_USE_SAME_PROXY 1 #define GNOME_PROXY_SOCKS_HOST 2 #define GNOME_PROXY_SOCKS_PORT 3 #define GNOME_PROXY_HTTP_HOST 4 #define GNOME_PROXY_HTTP_PORT 5 #define GNOME_PROXY_HTTP_USER 6 #define GNOME_PROXY_HTTP_PASS 7 /* detect proxy settings for gnome2/gnome3 */ static const char* gproxycmds[][2] = { { "gconftool-2 -g /system/proxy/mode" , "gsettings get org.gnome.system.proxy mode" }, { "gconftool-2 -g /system/http_proxy/use_same_proxy", "gsettings get org.gnome.system.proxy use-same-proxy" }, { "gconftool-2 -g /system/proxy/socks_host", "gsettings get org.gnome.system.proxy.socks host" }, { "gconftool-2 -g /system/proxy/socks_port", "gsettings get org.gnome.system.proxy.socks port" }, { "gconftool-2 -g /system/http_proxy/host", "gsettings get org.gnome.system.proxy.http host" }, { "gconftool-2 -g /system/http_proxy/port", "gsettings get org.gnome.system.proxy.http port"}, { "gconftool-2 -g /system/http_proxy/authentication_user", "gsettings get org.gnome.system.proxy.http authentication-user" }, { "gconftool-2 -g /system/http_proxy/authentication_password", "gsettings get org.gnome.system.proxy.http authentication-password" }, * This is a utility function used to retrieve proxy parameter values from * @param parameter One of the GNOME_PROXY_x constants defined above * @param gnome_version GNOME2_CMDS or GNOME3_CMDS * @return The value of requested proxy parameter purple_gnome_proxy_get_parameter(guint8 parameter, guint8 gnome_version) if (parameter > GNOME_PROXY_HTTP_PASS) if (gnome_version > GNOME3_CMDS) if (!g_spawn_command_line_sync(gproxycmds[parameter][gnome_version], ¶m, &err, NULL, NULL)) if (param[0] == '\'' || param[0] == '\"') { param_len = strlen(param); memmove(param, param + 1, param_len); /* copy last \0 too */ if (param_len > 0 && (param[param_len - 1] == '\'' || param[param_len - 1] == '\"')) param[param_len - 1] = '\0'; purple_gnome_proxy_get_info(void) static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL}; gboolean use_same_proxy = FALSE; guint8 gnome_version = GNOME3_CMDS; tmp = g_find_program_in_path("gsettings"); tmp = g_find_program_in_path("gconftool-2"); gnome_version = GNOME2_CMDS; return purple_global_proxy_get_info(); /* Check whether to use a proxy. */ tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_MODE, gnome_version); return purple_global_proxy_get_info(); if (purple_strequal(tmp, "none")) { info.type = PURPLE_PROXY_NONE; if (!purple_strequal(tmp, "manual")) { /* Unknown setting. Fallback to using our global proxy settings. */ return purple_global_proxy_get_info(); /* Free the old fields */ tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_USE_SAME_PROXY, gnome_version); return purple_global_proxy_get_info(); if (purple_strequal(tmp, "true")) info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_HOST, gnome_version); return purple_global_proxy_get_info(); if (!use_same_proxy && (info.host != NULL) && (*info.host != '\0')) { info.type = PURPLE_PROXY_SOCKS5; tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_PORT, gnome_version); return purple_global_proxy_get_info(); info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_HOST, gnome_version); return purple_global_proxy_get_info(); /* If we get this far then we know we're using an HTTP proxy */ info.type = PURPLE_PROXY_HTTP; purple_debug_info("proxy", "Gnome proxy settings are set to " "'manual' but no suitable proxy server is specified. Using " "Pidgin's proxy settings instead.\n"); return purple_global_proxy_get_info(); info.username = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_USER, gnome_version); return purple_global_proxy_get_info(); info.password = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PASS, gnome_version); return purple_global_proxy_get_info(); tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PORT, gnome_version); return purple_global_proxy_get_info(); typedef BOOL (CALLBACK* LPFNWINHTTPGETIEPROXYCONFIG)(/*IN OUT*/ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* pProxyConfig); /* This modifies "host" in-place evilly */ _proxy_fill_hostinfo(PurpleProxyInfo *info, char *host, int default_port) d = g_strrstr(host, ":"); purple_proxy_info_set_host(info, host); purple_proxy_info_set_port(info, port); purple_win32_proxy_get_info(void) static LPFNWINHTTPGETIEPROXYCONFIG MyWinHttpGetIEProxyConfig = NULL; static gboolean loaded = FALSE; static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL}; WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_proxy_config; MyWinHttpGetIEProxyConfig = (LPFNWINHTTPGETIEPROXYCONFIG) wpurple_find_and_loadproc("winhttp.dll", "WinHttpGetIEProxyConfigForCurrentUser"); if (!MyWinHttpGetIEProxyConfig) purple_debug_warning("proxy", "Unable to read Windows Proxy Settings.\n"); if (!MyWinHttpGetIEProxyConfig) ZeroMemory(&ie_proxy_config, sizeof(ie_proxy_config)); if (!MyWinHttpGetIEProxyConfig(&ie_proxy_config)) { purple_debug_error("proxy", "Error reading Windows Proxy Settings(%lu).\n", GetLastError()); /* We can't do much if it is autodetect*/ if (ie_proxy_config.fAutoDetect) { purple_debug_error("proxy", "Windows Proxy Settings set to autodetect (not supported).\n"); /* TODO: For 3.0.0 we'll revisit this (maybe)*/ } else if (ie_proxy_config.lpszProxy) { gchar *proxy_list = g_utf16_to_utf8(ie_proxy_config.lpszProxy, -1, /* We can't do anything about the bypass list, as we don't have the url */ /* TODO: For 3.0.0 we'll revisit this*/ /* There are proxy settings for several protocols */ if (proxy_list && *proxy_list) { char *specific = NULL, *tmp; /* If there is only a global proxy, which means "HTTP" */ if (!strchr(proxy_list, ';') || (specific = g_strstr_len(proxy_list, -1, "http=")) != NULL) { specific += strlen("http="); tmp = strchr(specific, ';'); /* specific now points the proxy server (and port) */ purple_proxy_info_set_type(&info, PURPLE_PROXY_HTTP); _proxy_fill_hostinfo(&info, specific, 80); /* TODO: is there a way to set the username/password? */ purple_proxy_info_set_username(&info, NULL); purple_proxy_info_set_password(&info, NULL); purple_debug_info("proxy", "Windows Proxy Settings: HTTP proxy: '%s:%d'.\n", purple_proxy_info_get_host(&info), purple_proxy_info_get_port(&info)); } else if ((specific = g_strstr_len(proxy_list, -1, "socks=")) != NULL) { specific += strlen("socks="); tmp = strchr(specific, ';'); /* specific now points the proxy server (and port) */ purple_proxy_info_set_type(&info, PURPLE_PROXY_SOCKS5); _proxy_fill_hostinfo(&info, specific, 1080); /* TODO: is there a way to set the username/password? */ purple_proxy_info_set_username(&info, NULL); purple_proxy_info_set_password(&info, NULL); purple_debug_info("proxy", "Windows Proxy Settings: SOCKS5 proxy: '%s:%d'.\n", purple_proxy_info_get_host(&info), purple_proxy_info_get_port(&info)); purple_debug_info("proxy", "Windows Proxy Settings: No supported proxy specified.\n"); purple_proxy_info_set_type(&info, PURPLE_PROXY_NONE); /* TODO: Fix API to be able look at proxy bypass settings */ purple_debug_info("proxy", "No Windows proxy set.\n"); purple_proxy_info_set_type(&info, PURPLE_PROXY_NONE); if (ie_proxy_config.lpszAutoConfigUrl) GlobalFree(ie_proxy_config.lpszAutoConfigUrl); if (ie_proxy_config.lpszProxy) GlobalFree(ie_proxy_config.lpszProxy); if (ie_proxy_config.lpszProxyBypass) GlobalFree(ie_proxy_config.lpszProxyBypass); /************************************************************************** **************************************************************************/ * Whoever calls this needs to have called * purple_proxy_connect_data_disconnect() beforehand. purple_proxy_connect_data_destroy(PurpleProxyConnectData *connect_data) handles = g_slist_remove(handles, connect_data); if (connect_data->query_data != NULL) purple_dnsquery_destroy(connect_data->query_data); while (connect_data->hosts != NULL) /* Discard the length... */ connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data); /* Free the address... */ g_free(connect_data->hosts->data); connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data); g_free(connect_data->host); * Free all information dealing with a connection attempt and * reset the connect_data to prepare for it to try to connect * If an error message is passed in, then we know the connection * attempt failed. If the connection attempt failed and * connect_data->hosts is not empty then we try the next IP address. * If the connection attempt failed and we have no more hosts * try try then we call the callback with the given error message, * then destroy the connect_data. * @param error_message An error message explaining why the connection * failed. This will be passed to the callback function * specified in the call to purple_proxy_connect(). If the * connection was successful then pass in null. purple_proxy_connect_data_disconnect(PurpleProxyConnectData *connect_data, const gchar *error_message) if (connect_data->child != NULL) purple_proxy_connect_cancel(connect_data->child); connect_data->child = NULL; if (connect_data->inpa > 0) purple_input_remove(connect_data->inpa); if (connect_data->fd >= 0) g_free(connect_data->write_buffer); connect_data->write_buffer = NULL; g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; if (error_message != NULL) purple_debug_error("proxy", "Connection attempt failed: %s\n", if (connect_data->hosts != NULL) try_connect(connect_data); /* Everything failed! Tell the originator of the request. */ connect_data->connect_cb(connect_data->data, -1, error_message); purple_proxy_connect_data_destroy(connect_data); * This calls purple_proxy_connect_data_disconnect(), but it lets you * specify the error_message using a printf()-like syntax. purple_proxy_connect_data_disconnect_formatted(PurpleProxyConnectData *connect_data, const char *format, ...) tmp = g_strdup_vprintf(format, args); purple_proxy_connect_data_disconnect(connect_data, tmp); purple_proxy_connect_data_connected(PurpleProxyConnectData *connect_data) purple_debug_info("proxy", "Connected to %s:%d.\n", connect_data->host, connect_data->port); connect_data->connect_cb(connect_data->data, connect_data->fd, NULL); * We've passed the file descriptor to the protocol, so it's no longer * our responsibility, and we should be careful not to free it when * we destroy the connect_data. purple_proxy_connect_data_disconnect(connect_data, NULL); purple_proxy_connect_data_destroy(connect_data); socket_ready_cb(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data = data; /* If the socket-connected message had already been triggered when connect_data * was destroyed via purple_proxy_connect_cancel(), we may get here with a freed connect_data. if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data)) purple_debug_info("proxy", "Connecting to %s:%d.\n", connect_data->host, connect_data->port); * purple_input_get_error after a non-blocking connect returns -1 if something is * really messed up (bad descriptor, usually). Otherwise, it returns 0 and * error holds what connect would have returned if it blocked until now. * Thus, error == 0 is success, error == EINPROGRESS means "try again", * and anything else is a real error. * (error == EINPROGRESS can happen after a select because the kernel can * be overly optimistic sometimes. select is just a hint that you might be ret = purple_input_get_error(connect_data->fd, &error); if (ret == 0 && error == EINPROGRESS) { /* No worries - we'll be called again later */ /* TODO: Does this ever happen? */ purple_debug_info("proxy", "(ret == 0 && error == EINPROGRESS)\n"); if (ret != 0 || error != 0) { purple_debug_error("proxy", "Error connecting to %s:%d (%s).\n", connect_data->host, connect_data->port, g_strerror(error)); purple_proxy_connect_data_disconnect(connect_data, g_strerror(error)); purple_proxy_connect_data_connected(connect_data); clean_connect(gpointer data) purple_proxy_connect_data_connected(data); proxy_connect_udp_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen) purple_debug_info("proxy", "UDP Connecting to %s:%d with no proxy\n", connect_data->host, connect_data->port); connect_data->fd = socket(addr->sa_family, SOCK_DGRAM, 0); if (connect_data->fd < 0) purple_proxy_connect_data_disconnect_formatted(connect_data, _("Unable to create socket: %s"), g_strerror(errno)); flags = fcntl(connect_data->fd, F_GETFL); fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK); fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC); if (connect(connect_data->fd, addr, addrlen) != 0) if ((errno == EINPROGRESS) || (errno == EINTR)) purple_debug_info("proxy", "UDP Connection in progress\n"); connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, socket_ready_cb, connect_data); purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno)); * The connection happened IMMEDIATELY... strange, but whatever. purple_debug_info("proxy", "UDP Connected immediately.\n"); ret = purple_input_get_error(connect_data->fd, &error); if ((ret != 0) || (error != 0)) purple_proxy_connect_data_disconnect(connect_data, g_strerror(error)); * We want to call the "connected" callback eventually, but we * don't want to call it before we return, just in case. purple_timeout_add(10, clean_connect, connect_data); proxy_connect_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen) purple_debug_info("proxy", "Connecting to %s:%d with no proxy\n", connect_data->host, connect_data->port); connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0); if (connect_data->fd < 0) purple_proxy_connect_data_disconnect_formatted(connect_data, _("Unable to create socket: %s"), g_strerror(errno)); flags = fcntl(connect_data->fd, F_GETFL); fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK); fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC); if (connect(connect_data->fd, addr, addrlen) != 0) if ((errno == EINPROGRESS) || (errno == EINTR)) purple_debug_info("proxy", "Connection in progress\n"); connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, socket_ready_cb, connect_data); purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno)); * The connection happened IMMEDIATELY... strange, but whatever. purple_debug_info("proxy", "Connected immediately.\n"); ret = purple_input_get_error(connect_data->fd, &error); if ((ret != 0) || (error != 0)) purple_proxy_connect_data_disconnect(connect_data, g_strerror(error)); * We want to call the "connected" callback eventually, but we * don't want to call it before we return, just in case. purple_timeout_add(10, clean_connect, connect_data); * This is a utility function used by the HTTP, SOCKS4 and SOCKS5 * connect functions. It writes data from a buffer to a socket. * When all the data is written it sets up a watcher to read a * response and call a specified function. proxy_do_write(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data; request = connect_data->write_buffer + connect_data->written_len; request_len = connect_data->write_buf_len - connect_data->written_len; ret = write(connect_data->fd, request, request_len); purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno)); connect_data->written_len += ret; /* We're done writing data! Wait for a response. */ g_free(connect_data->write_buffer); connect_data->write_buffer = NULL; purple_input_remove(connect_data->inpa); connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_READ, connect_data->read_cb, connect_data); #define HTTP_GOODSTRING "HTTP/1.0 200" #define HTTP_GOODSTRING2 "HTTP/1.1 200" * We're using an HTTP proxy for a non-port 80 tunnel. Read the * response to the CONNECT request. http_canread(gpointer data, gint source, PurpleInputCondition cond) int len, headers_len, status = 0; PurpleProxyConnectData *connect_data = data; if (connect_data->read_buffer == NULL) { connect_data->read_buf_len = 8192; connect_data->read_buffer = g_malloc(connect_data->read_buf_len); connect_data->read_len = 0; p = (char *)connect_data->read_buffer + connect_data->read_len; max_read = connect_data->read_buf_len - connect_data->read_len - 1; len = read(connect_data->fd, p, max_read); purple_proxy_connect_data_disconnect(connect_data, _("Server closed the connection")); purple_proxy_connect_data_disconnect_formatted(connect_data, _("Lost connection with server: %s"), g_strerror(errno)); connect_data->read_len += len; p = g_strstr_len((const gchar *)connect_data->read_buffer, connect_data->read_len, "\r\n\r\n"); headers_len = (p - (char *)connect_data->read_buffer) + 4; } else if(len == max_read) error = strncmp((const char *)connect_data->read_buffer, "HTTP/", 5) != 0; p = (char *)connect_data->read_buffer + 5; major = strtol(p, &p, 10); error = (major == 0) || (*p != '.'); minor = strtol(p, &p, 10); status = strtol(p, &p, 10); p = g_strrstr((const gchar *)connect_data->read_buffer, "Content-Length: "); p += strlen("Content-Length: "); if (sscanf(p, "%" G_GSIZE_FORMAT, &content_len) != 1) { /* Couldn't read content length */ purple_debug_error("proxy", "Couldn't read content length value " purple_proxy_connect_data_disconnect_formatted(connect_data, _("Unable to parse response from HTTP proxy: %s"), connect_data->read_buffer); /* Compensate for what has already been read */ content_len -= connect_data->read_len - headers_len; /* I'm assuming that we're doing this to prevent the server from complaining / breaking since we don't read the whole page */ /* TODO: deal with EAGAIN (and other errors) better */ /* TODO: Reading 1 byte at a time is horrible and stupid. */ if (read(connect_data->fd, &tmpc, 1) < 0 && errno != EAGAIN) purple_proxy_connect_data_disconnect_formatted(connect_data, _("Unable to parse response from HTTP proxy: %s"), connect_data->read_buffer); else if (status != 200) { purple_debug_error("proxy", "Proxy server replied with:\n%s\n", connect_data->read_buffer); if (status == 407 /* Proxy Auth */) { header = g_strrstr((const gchar *)connect_data->read_buffer, "Proxy-Authenticate: NTLM"); const char *header_end = header + strlen("Proxy-Authenticate: NTLM"); const char *domain = purple_proxy_info_get_username(connect_data->gpi); char *username = NULL, hostname[256]; ret = gethostname(hostname, sizeof(hostname)); hostname[sizeof(hostname) - 1] = '\0'; if (ret < 0 || hostname[0] == '\0') { purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?"); g_strlcpy(hostname, "localhost", sizeof(hostname)); username = (char*) strchr(domain, '\\'); purple_proxy_connect_data_disconnect_formatted(connect_data, _("HTTP proxy connection error %d"), status); /* Is there a message? */ if (*header_end == ' ') { char *tmp = (char*) header; while(*tmp != '\r' && *tmp != '\0') tmp++; nonce = purple_ntlm_parse_type2(header_end, NULL); response = purple_ntlm_gen_type3(username, (gchar*) purple_proxy_info_get_password(connect_data->gpi), } else /* Empty message */ response = purple_ntlm_gen_type1(hostname, domain); request = g_strdup_printf( "CONNECT %s:%d HTTP/1.1\r\n" "Proxy-Authorization: NTLM %s\r\n" "Proxy-Connection: Keep-Alive\r\n\r\n", connect_data->host, connect_data->port, connect_data->host, connect_data->port, } else if (g_strrstr((const char *)connect_data->read_buffer, "Proxy-Authenticate: Basic") != NULL) { const char *username, *password; username = purple_proxy_info_get_username(connect_data->gpi); password = purple_proxy_info_get_password(connect_data->gpi); t1 = g_strdup_printf("%s:%s", username ? username : "", password ? password : ""); t2 = purple_base64_encode((guchar *)t1, strlen(t1)); request = g_strdup_printf( "CONNECT %s:%d HTTP/1.1\r\n" "Proxy-Authorization: Basic %s\r\n", connect_data->host, connect_data->port, connect_data->host, connect_data->port, purple_proxy_connect_data_disconnect_formatted(connect_data, _("HTTP proxy connection error %d"), status); purple_input_remove(connect_data->inpa); g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; connect_data->write_buffer = (guchar *)request; connect_data->write_buf_len = strlen(request); connect_data->written_len = 0; connect_data->read_cb = http_canread; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, cond); purple_proxy_connect_data_disconnect_formatted(connect_data, _("Access denied: HTTP proxy server forbids port %d tunneling"), purple_proxy_connect_data_disconnect_formatted(connect_data, _("HTTP proxy connection error %d"), status); purple_input_remove(connect_data->inpa); g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; purple_debug_info("proxy", "HTTP proxy connection established\n"); purple_proxy_connect_data_connected(connect_data); http_start_connect_tunneling(PurpleProxyConnectData *connect_data) { purple_debug_info("proxy", "Using CONNECT tunneling for %s:%d\n", connect_data->host, connect_data->port); request = g_string_sized_new(4096); g_string_append_printf(request, "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n", connect_data->host, connect_data->port, connect_data->host, connect_data->port); if (purple_proxy_info_get_username(connect_data->gpi) != NULL) char *t1, *t2, *ntlm_type1; ret = gethostname(hostname, sizeof(hostname)); hostname[sizeof(hostname) - 1] = '\0'; if (ret < 0 || hostname[0] == '\0') { purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?"); g_strlcpy(hostname, "localhost", sizeof(hostname)); t1 = g_strdup_printf("%s:%s", purple_proxy_info_get_username(connect_data->gpi), purple_proxy_info_get_password(connect_data->gpi) ? purple_proxy_info_get_password(connect_data->gpi) : ""); t2 = purple_base64_encode((const guchar *)t1, strlen(t1)); ntlm_type1 = purple_ntlm_gen_type1(hostname, ""); g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n" "Proxy-Authorization: NTLM %s\r\n" "Proxy-Connection: Keep-Alive\r\n", g_string_append(request, "\r\n"); connect_data->write_buf_len = request->len; connect_data->write_buffer = (guchar *)g_string_free(request, FALSE); connect_data->written_len = 0; connect_data->read_cb = http_canread; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); http_canwrite(gpointer data, gint source, PurpleInputCondition cond) { PurpleProxyConnectData *connect_data = data; int ret, error = ETIMEDOUT; purple_debug_info("proxy", "Connected to %s:%d.\n", connect_data->host, connect_data->port); if (connect_data->inpa > 0) { purple_input_remove(connect_data->inpa); ret = purple_input_get_error(connect_data->fd, &error); if (ret != 0 || error != 0) { purple_proxy_connect_data_disconnect(connect_data, g_strerror(error)); if (connect_data->port == 80) { * If we're trying to connect to something running on * port 80 then we assume the traffic using this * connection is going to be HTTP traffic. If it's * not then this will fail (uglily). But it's good * to avoid using the CONNECT method because it's purple_debug_info("proxy", "HTTP proxy connection established\n"); purple_proxy_connect_data_connected(connect_data); http_start_connect_tunneling(connect_data); proxy_connect_http(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen) purple_debug_info("proxy", "Connecting to %s:%d via %s:%d using HTTP\n", connect_data->host, connect_data->port, (purple_proxy_info_get_host(connect_data->gpi) ? purple_proxy_info_get_host(connect_data->gpi) : "(null)"), purple_proxy_info_get_port(connect_data->gpi)); connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0); if (connect_data->fd < 0) purple_proxy_connect_data_disconnect_formatted(connect_data, _("Unable to create socket: %s"), g_strerror(errno)); flags = fcntl(connect_data->fd, F_GETFL); fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK); fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC); if (connect(connect_data->fd, addr, addrlen) != 0) { if (errno == EINPROGRESS || errno == EINTR) { purple_debug_info("proxy", "Connection in progress\n"); connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, http_canwrite, connect_data); purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno)); purple_debug_info("proxy", "Connected immediately.\n"); http_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); s4_canread(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data = data; /* This is really not going to block under normal circumstances, but to * be correct, we deal with the unlikely scenario */ if (connect_data->read_buffer == NULL) { connect_data->read_buf_len = 12; connect_data->read_buffer = g_malloc(connect_data->read_buf_len); connect_data->read_len = 0; buf = connect_data->read_buffer + connect_data->read_len; max_read = connect_data->read_buf_len - connect_data->read_len; len = read(connect_data->fd, buf, max_read); if ((len < 0 && errno == EAGAIN) || (len > 0 && len + connect_data->read_len < 4)) else if (len + connect_data->read_len >= 4) { if (connect_data->read_buffer[1] == 90) { purple_proxy_connect_data_connected(connect_data); purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno)); s4_host_resolved(GSList *hosts, gpointer data, const char *error_message) PurpleProxyConnectData *connect_data = data; connect_data->query_data = NULL; if (error_message != NULL) { purple_proxy_connect_data_disconnect(connect_data, error_message); purple_proxy_connect_data_disconnect_formatted(connect_data, _("Error resolving %s"), connect_data->host); /* Discard the length... */ hosts = g_slist_delete_link(hosts, hosts); hosts = g_slist_delete_link(hosts, hosts); packet[2] = connect_data->port >> 8; packet[3] = connect_data->port & 0xff; memcpy(packet + 4, &((struct sockaddr_in *)addr)->sin_addr.s_addr, 4); /* We could try the other hosts, but hopefully that shouldn't be necessary */ /* Discard the length... */ hosts = g_slist_delete_link(hosts, hosts); /* Free the address... */ hosts = g_slist_delete_link(hosts, hosts); connect_data->write_buffer = g_memdup(packet, sizeof(packet)); connect_data->write_buf_len = sizeof(packet); connect_data->written_len = 0; connect_data->read_cb = s4_canread; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); s4_canwrite(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data = data; purple_debug_info("socks4 proxy", "Connected.\n"); if (connect_data->inpa > 0) { purple_input_remove(connect_data->inpa); ret = purple_input_get_error(connect_data->fd, &error); if ((ret != 0) || (error != 0)) { purple_proxy_connect_data_disconnect(connect_data, g_strerror(error)); * The socks4 spec doesn't include support for doing host name lookups by * the proxy. Many socks4 servers do this via the "socks4a" extension to * the protocol. There doesn't appear to be a way to detect if a server * supports this, so we require that the user set a global option. if (purple_prefs_get_bool("/purple/proxy/socks4_remotedns")) { purple_debug_info("socks4 proxy", "Attempting to use remote DNS.\n"); packet[2] = connect_data->port >> 8; packet[3] = connect_data->port & 0xff; len = sizeof(packet) + strlen(connect_data->host) + 1; connect_data->write_buffer = g_malloc0(len); memcpy(connect_data->write_buffer, packet, sizeof(packet)); memcpy(connect_data->write_buffer + sizeof(packet), connect_data->host, strlen(connect_data->host)); connect_data->write_buf_len = len; connect_data->written_len = 0; connect_data->read_cb = s4_canread; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); connect_data->query_data = purple_dnsquery_a_account( connect_data->account, connect_data->host, connect_data->port, s4_host_resolved, connect_data); if (connect_data->query_data == NULL) { purple_debug_error("proxy", "dns query failed unexpectedly.\n"); purple_proxy_connect_data_destroy(connect_data); proxy_connect_socks4(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen) purple_debug_info("proxy", "Connecting to %s:%d via %s:%d using SOCKS4\n", connect_data->host, connect_data->port, purple_proxy_info_get_host(connect_data->gpi), purple_proxy_info_get_port(connect_data->gpi)); connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0); if (connect_data->fd < 0) purple_proxy_connect_data_disconnect_formatted(connect_data, _("Unable to create socket: %s"), g_strerror(errno)); flags = fcntl(connect_data->fd, F_GETFL); fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK); fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC); if (connect(connect_data->fd, addr, addrlen) != 0) if ((errno == EINPROGRESS) || (errno == EINTR)) purple_debug_info("proxy", "Connection in progress.\n"); connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, s4_canwrite, connect_data); purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno)); purple_debug_info("proxy", "Connected immediately.\n"); s4_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); s5_ensure_buffer_length(PurpleProxyConnectData *connect_data, int len) if(connect_data->read_len < len) { if(connect_data->read_buf_len < len) { /* it's not just that we haven't read enough, it's that we haven't tried to read enough yet */ purple_debug_info("s5", "reallocing from %" G_GSIZE_FORMAT " to %d\n", connect_data->read_buf_len, len); connect_data->read_buf_len = len; connect_data->read_buffer = g_realloc(connect_data->read_buffer, connect_data->read_buf_len); s5_canread_again(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data = data; if (connect_data->read_buffer == NULL) { connect_data->read_buf_len = 5; connect_data->read_buffer = g_malloc(connect_data->read_buf_len); connect_data->read_len = 0; dest = connect_data->read_buffer + connect_data->read_len; buf = connect_data->read_buffer; len = read(connect_data->fd, dest, (connect_data->read_buf_len - connect_data->read_len)); purple_proxy_connect_data_disconnect(connect_data, _("Server closed the connection")); purple_proxy_connect_data_disconnect_formatted(connect_data, _("Lost connection with server: %s"), g_strerror(errno)); connect_data->read_len += len; if(connect_data->read_len < 4) if ((buf[0] != 0x05) || (buf[1] != 0x00)) { if ((buf[0] == 0x05) && (buf[1] < 0x09)) { purple_debug_error("socks5 proxy", "%s", socks5errors[buf[1]]); purple_proxy_connect_data_disconnect(connect_data, purple_debug_error("socks5 proxy", "Bad data.\n"); purple_proxy_connect_data_disconnect(connect_data, _("Received invalid data on connection with server")); case 0x01: /* the address is a version-4 IP address, with a length of 4 octets */ if(!s5_ensure_buffer_length(connect_data, 4 + 4)) case 0x03: /* the address field contains a fully-qualified domain name. The first octet of the address field contains the number of octets of name that follow, there is no terminating NUL octet. */ if(!s5_ensure_buffer_length(connect_data, 4 + 1)) if(!s5_ensure_buffer_length(connect_data, 4 + 1 + buf[0])) case 0x04: /* the address is a version-6 IP address, with a length of 16 octets */ if(!s5_ensure_buffer_length(connect_data, 4 + 16)) purple_debug_error("socks5 proxy", "Invalid ATYP received (0x%X)\n", buf[3]); purple_proxy_connect_data_disconnect(connect_data, _("Received invalid data on connection with server")); if(!s5_ensure_buffer_length(connect_data, (buf - connect_data->read_buffer) + 2)) purple_proxy_connect_data_connected(connect_data); s5_sendconnect(gpointer data, int source) PurpleProxyConnectData *connect_data = data; size_t hlen = strlen(connect_data->host); connect_data->write_buf_len = 5 + hlen + 2; connect_data->write_buffer = g_malloc(connect_data->write_buf_len); connect_data->written_len = 0; connect_data->write_buffer[0] = 0x05; connect_data->write_buffer[1] = 0x01; /* CONNECT */ connect_data->write_buffer[2] = 0x00; /* reserved */ connect_data->write_buffer[3] = 0x03; /* address type -- host name */ connect_data->write_buffer[4] = hlen; memcpy(connect_data->write_buffer + 5, connect_data->host, hlen); connect_data->write_buffer[5 + hlen] = connect_data->port >> 8; connect_data->write_buffer[5 + hlen + 1] = connect_data->port & 0xff; connect_data->read_cb = s5_canread_again; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); s5_readauth(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data = data; if (connect_data->read_buffer == NULL) { connect_data->read_buf_len = 2; connect_data->read_buffer = g_malloc(connect_data->read_buf_len); connect_data->read_len = 0; purple_debug_info("socks5 proxy", "Got auth response.\n"); len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len, connect_data->read_buf_len - connect_data->read_len); purple_proxy_connect_data_disconnect(connect_data, _("Server closed the connection")); purple_proxy_connect_data_disconnect_formatted(connect_data, _("Lost connection with server: %s"), g_strerror(errno)); connect_data->read_len += len; if (connect_data->read_len < 2) purple_input_remove(connect_data->inpa); if ((connect_data->read_buffer[0] != 0x01) || (connect_data->read_buffer[1] != 0x00)) { purple_proxy_connect_data_disconnect(connect_data, _("Received invalid data on connection with server")); g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; s5_sendconnect(connect_data, connect_data->fd); hmacmd5_chap(const unsigned char * challenge, int challen, const char * passwd, unsigned char * response) PurpleCipherContext *ctx; unsigned char Kxoripad[65]; unsigned char Kxoropad[65]; cipher = purple_ciphers_find_cipher("md5"); ctx = purple_cipher_context_new(cipher, NULL); memset(Kxoripad,0,sizeof(Kxoripad)); memset(Kxoropad,0,sizeof(Kxoropad)); purple_cipher_context_append(ctx, (const guchar *)passwd, strlen(passwd)); purple_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL); memcpy(Kxoripad, passwd, pwlen); memcpy(Kxoropad,Kxoripad,pwlen); purple_cipher_context_reset(ctx, NULL); purple_cipher_context_append(ctx, Kxoripad, 64); purple_cipher_context_append(ctx, challenge, challen); purple_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL); purple_cipher_context_reset(ctx, NULL); purple_cipher_context_append(ctx, Kxoropad, 64); purple_cipher_context_append(ctx, Kxoripad, 16); purple_cipher_context_digest(ctx, 16, response, NULL); purple_cipher_context_destroy(ctx); s5_readchap(gpointer data, gint source, PurpleInputCondition cond); * Return how many bytes we processed * -1 means we've shouldn't keep reading from the buffer s5_parse_chap_msg(PurpleProxyConnectData *connect_data) guchar *buf, *cmdbuf = connect_data->read_buffer; int len, navas, currentav; purple_debug_misc("socks5 proxy", "Reading CHAP message: %x\n", *cmdbuf); purple_proxy_connect_data_disconnect(connect_data, _("Received invalid data on connection with server")); purple_debug_misc("socks5 proxy", "Expecting %d attribute(s).\n", navas); for (currentav = 0; currentav < navas; currentav++) { len = connect_data->read_len - (cmdbuf - connect_data->read_buffer); /* We don't have enough data to even know how long the next attribute is, * or we don't have the full length of the next attribute. */ if (len < 2 || len < (cmdbuf[1] + 2)) { /* Clear out the attributes that have been read - decrease the attribute count */ connect_data->read_buffer[1] = navas - currentav; /* Move the unprocessed data into the first attribute position */ memmove((connect_data->read_buffer + 2), cmdbuf, len); /* Decrease the read count accordingly */ connect_data->read_len = len + 2; purple_debug_info("socks5 proxy", "Need more data to retrieve attribute %d.\n", currentav); purple_debug_error("socks5 proxy", "Attribute %x Value length of 0; ignoring.\n", cmdbuf[0]); purple_debug_info("socks5 proxy", "Received STATUS of %x\n", buf[0]); purple_input_remove(connect_data->inpa); g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; s5_sendconnect(connect_data, connect_data->fd); purple_debug_warning("proxy", "socks5 CHAP authentication " "failed. Disconnecting..."); purple_proxy_connect_data_disconnect(connect_data, _("Authentication failed")); /* We've already validated that cmdbuf[1] is sane. */ purple_debug_info("socks5 proxy", "Received TEXT-MESSAGE of '%.*s'\n", (int) cmdbuf[1], buf); purple_debug_info("socks5 proxy", "Received CHALLENGE\n"); /* Server wants our credentials */ connect_data->write_buf_len = 16 + 4; connect_data->write_buffer = g_malloc(connect_data->write_buf_len); connect_data->written_len = 0; hmacmd5_chap(buf, cmdbuf[1], purple_proxy_info_get_password(connect_data->gpi), connect_data->write_buffer + 4); /* TODO: What about USER-IDENTITY? */ connect_data->write_buffer[0] = 0x01; connect_data->write_buffer[1] = 0x01; connect_data->write_buffer[2] = 0x04; connect_data->write_buffer[3] = 0x10; purple_input_remove(connect_data->inpa); g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; connect_data->read_cb = s5_readchap; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); purple_debug_info("socks5 proxy", "Received ALGORIGTHMS of %x\n", buf[0]); /* Server wants to select an algorithm */ /* Only currently support HMAC-MD5 */ purple_debug_warning("proxy", "Server tried to select an " "algorithm that we did not advertise " "as supporting. This is a violation " "of the socks5 CHAP specification. " purple_proxy_connect_data_disconnect(connect_data, _("Received invalid data on connection with server")); purple_debug_info("socks5 proxy", "Received unused command %x, length=%d\n", cmdbuf[0], cmdbuf[1]); cmdbuf = buf + cmdbuf[1]; return (cmdbuf - connect_data->read_buffer); s5_readchap(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data = data; purple_debug(PURPLE_DEBUG_INFO, "socks5 proxy", "Got CHAP response.\n"); if (connect_data->read_buffer == NULL) { /* A big enough butfer to read the message header (2 bytes) and at least one complete attribute and value (1 + 1 + 255). */ connect_data->read_buf_len = 259; connect_data->read_buffer = g_malloc(connect_data->read_buf_len); connect_data->read_len = 0; if (connect_data->read_buf_len - connect_data->read_len == 0) { /*If the stuff below is right, this shouldn't be possible. */ purple_debug_error("socks5 proxy", "This is about to suck because the read buffer is full (shouldn't happen).\n"); len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len, connect_data->read_buf_len - connect_data->read_len); purple_proxy_connect_data_disconnect(connect_data, _("Server closed the connection")); purple_proxy_connect_data_disconnect_formatted(connect_data, _("Lost connection with server: %s"), g_strerror(errno)); connect_data->read_len += len; /* We may have read more than one message into the buffer, we need to make sure to process them all */ /* We need more to be able to read this message */ if (connect_data->read_len < 2) msg_ret = s5_parse_chap_msg(connect_data); /* See if we have another message already in the buffer */ if ((len = connect_data->read_len - msg_ret) > 0) { /* Move on to the next message */ memmove(connect_data->read_buffer, connect_data->read_buffer + msg_ret, len); /* Decrease the read count accordingly */ connect_data->read_len = len; /* Try to read the message that connect_data->read_buffer now points to */ /* Fell through. We ran out of CHAP events to process, but haven't * succeeded or failed authentication - there may be more to come. * If this is the case, come straight back here. */ purple_debug_info("socks5 proxy", "Waiting for another message from which to read CHAP info.\n"); /* We've processed all the available attributes, so get ready for a whole new message */ g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; s5_canread(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data = data; if (connect_data->read_buffer == NULL) { connect_data->read_buf_len = 2; connect_data->read_buffer = g_malloc(connect_data->read_buf_len); connect_data->read_len = 0; purple_debug_info("socks5 proxy", "Able to read.\n"); len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len, connect_data->read_buf_len - connect_data->read_len); purple_proxy_connect_data_disconnect(connect_data, _("Server closed the connection")); purple_proxy_connect_data_disconnect_formatted(connect_data, _("Lost connection with server: %s"), g_strerror(errno)); connect_data->read_len += len; if (connect_data->read_len < 2) purple_input_remove(connect_data->inpa); if ((connect_data->read_buffer[0] != 0x05) || (connect_data->read_buffer[1] == 0xff)) { purple_proxy_connect_data_disconnect(connect_data, _("Received invalid data on connection with server")); if (connect_data->read_buffer[1] == 0x02) { u = purple_proxy_info_get_username(connect_data->gpi); p = purple_proxy_info_get_password(connect_data->gpi); i = (u == NULL) ? 0 : strlen(u); j = (p == NULL) ? 0 : strlen(p); connect_data->write_buf_len = 1 + 1 + i + 1 + j; connect_data->write_buffer = g_malloc(connect_data->write_buf_len); connect_data->written_len = 0; connect_data->write_buffer[0] = 0x01; /* version 1 */ connect_data->write_buffer[1] = i; memcpy(connect_data->write_buffer + 2, u, i); connect_data->write_buffer[2 + i] = j; memcpy(connect_data->write_buffer + 2 + i + 1, p, j); g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; connect_data->read_cb = s5_readauth; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); } else if (connect_data->read_buffer[1] == 0x03) { userlen = strlen(purple_proxy_info_get_username(connect_data->gpi)); connect_data->write_buf_len = 7 + userlen; connect_data->write_buffer = g_malloc(connect_data->write_buf_len); connect_data->written_len = 0; connect_data->write_buffer[0] = 0x01; connect_data->write_buffer[1] = 0x02; connect_data->write_buffer[2] = 0x11; connect_data->write_buffer[3] = 0x01; connect_data->write_buffer[4] = 0x85; connect_data->write_buffer[5] = 0x02; connect_data->write_buffer[6] = userlen; memcpy(connect_data->write_buffer + 7, purple_proxy_info_get_username(connect_data->gpi), userlen); g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; connect_data->read_cb = s5_readchap; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); g_free(connect_data->read_buffer); connect_data->read_buffer = NULL; s5_sendconnect(connect_data, connect_data->fd); s5_canwrite(gpointer data, gint source, PurpleInputCondition cond) PurpleProxyConnectData *connect_data = data; purple_debug_info("socks5 proxy", "Connected.\n"); if (connect_data->inpa > 0) purple_input_remove(connect_data->inpa); ret = purple_input_get_error(connect_data->fd, &error); if ((ret != 0) || (error != 0)) purple_proxy_connect_data_disconnect(connect_data, g_strerror(error)); buf[0] = 0x05; /* SOCKS version 5 */ if (purple_proxy_info_get_username(connect_data->gpi) != NULL) { buf[1] = 0x03; /* three methods */ buf[2] = 0x00; /* no authentication */ buf[3] = 0x03; /* CHAP authentication */ buf[4] = 0x02; /* username/password authentication */ connect_data->write_buf_len = i; connect_data->write_buffer = g_malloc(connect_data->write_buf_len); memcpy(connect_data->write_buffer, buf, i); connect_data->read_cb = s5_canread; connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data); proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); proxy_connect_socks5(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen) purple_debug_info("proxy", "Connecting to %s:%d via %s:%d using SOCKS5\n", connect_data->host, connect_data->port, purple_proxy_info_get_host(connect_data->gpi), purple_proxy_info_get_port(connect_data->gpi)); connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0); if (connect_data->fd < 0) purple_proxy_connect_data_disconnect_formatted(connect_data, _("Unable to create socket: %s"), g_strerror(errno)); flags = fcntl(connect_data->fd, F_GETFL); fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK); fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC); if (connect(connect_data->fd, addr, addrlen) != 0) if ((errno == EINPROGRESS) || (errno == EINTR)) purple_debug_info("socks5 proxy", "Connection in progress\n"); connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, s5_canwrite, connect_data); purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno)); purple_debug_info("proxy", "Connected immediately.\n"); s5_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); * This function attempts to connect to the next IP address in the list * of IP addresses returned to us by purple_dnsquery_a() and attemps * to connect to each one. This is called after the hostname is * resolved, and each time a connection attempt fails (assuming there * is another IP address to try). #define INET6_ADDRSTRLEN 46 static void try_connect(PurpleProxyConnectData *connect_data) char ipaddr[INET6_ADDRSTRLEN]; addrlen = GPOINTER_TO_INT(connect_data->hosts->data); connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data); addr = connect_data->hosts->data; connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data); if (addr->sa_family == AF_INET) inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr, else if (addr->sa_family == AF_INET6) inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr, memcpy(ipaddr, inet_ntoa(((struct sockaddr_in *)addr)->sin_addr), purple_debug_info("proxy", "Attempting connection to %s\n", ipaddr); if (connect_data->socket_type == SOCK_DGRAM) { proxy_connect_udp_none(connect_data, addr, addrlen); switch (purple_proxy_info_get_type(connect_data->gpi)) { proxy_connect_none(connect_data, addr, addrlen); proxy_connect_http(connect_data, addr, addrlen); case PURPLE_PROXY_SOCKS4: proxy_connect_socks4(connect_data, addr, addrlen); case PURPLE_PROXY_SOCKS5: proxy_connect_socks5(connect_data, addr, addrlen); case PURPLE_PROXY_USE_ENVVAR: proxy_connect_http(connect_data, addr, addrlen); connection_host_resolved(GSList *hosts, gpointer data, const char *error_message) PurpleProxyConnectData *connect_data; connect_data->query_data = NULL; if (error_message != NULL) purple_proxy_connect_data_disconnect(connect_data, error_message); purple_proxy_connect_data_disconnect(connect_data, _("Unable to resolve hostname")); connect_data->hosts = hosts; try_connect(connect_data); purple_proxy_get_setup(PurpleAccount *account) PurpleProxyInfo *gpi = NULL; /* This is used as a fallback so we don't overwrite the selected proxy type */ static PurpleProxyInfo *tmp_none_proxy_info = NULL; if (!tmp_none_proxy_info) { tmp_none_proxy_info = purple_proxy_info_new(); purple_proxy_info_set_type(tmp_none_proxy_info, PURPLE_PROXY_NONE); if (account && purple_account_get_proxy_info(account) != NULL) { gpi = purple_account_get_proxy_info(account); if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_GLOBAL) if (purple_running_gnome()) gpi = purple_gnome_proxy_get_info(); gpi = purple_global_proxy_get_info(); if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR) { if ((tmp = g_getenv("HTTP_PROXY")) != NULL || (tmp = g_getenv("http_proxy")) != NULL || (tmp = g_getenv("HTTPPROXY")) != NULL) { char *proxyhost, *proxyuser, *proxypasswd; * export http_proxy="http://user:passwd@your.proxy.server:port/" if(purple_url_parse(tmp, &proxyhost, &proxyport, NULL, &proxyuser, &proxypasswd)) { purple_proxy_info_set_host(gpi, proxyhost); purple_proxy_info_set_username(gpi, proxyuser); purple_proxy_info_set_password(gpi, proxypasswd); /* only for backward compatibility */ ((tmp = g_getenv("HTTP_PROXY_PORT")) != NULL || (tmp = g_getenv("http_proxy_port")) != NULL || (tmp = g_getenv("HTTPPROXYPORT")) != NULL)) purple_proxy_info_set_port(gpi, proxyport); /* XXX: Do we want to skip this step if user/password were part of url? */ if ((tmp = g_getenv("HTTP_PROXY_USER")) != NULL || (tmp = g_getenv("http_proxy_user")) != NULL || (tmp = g_getenv("HTTPPROXYUSER")) != NULL) purple_proxy_info_set_username(gpi, tmp); if ((tmp = g_getenv("HTTP_PROXY_PASS")) != NULL || (tmp = g_getenv("http_proxy_pass")) != NULL || (tmp = g_getenv("HTTPPROXYPASS")) != NULL) purple_proxy_info_set_password(gpi, tmp); if ((wgpi = purple_win32_proxy_get_info()) != NULL) /* no proxy environment variable found, don't use a proxy */ purple_debug_info("proxy", "No environment settings found, not using a proxy\n"); gpi = tmp_none_proxy_info; purple_proxy_connect(void *handle, PurpleAccount *account, const char *host, int port, PurpleProxyConnectFunction connect_cb, gpointer data) const char *connecthost = host; PurpleProxyConnectData *connect_data; g_return_val_if_fail(host != NULL, NULL); g_return_val_if_fail(port > 0, NULL); g_return_val_if_fail(connect_cb != NULL, NULL); connect_data = g_new0(PurpleProxyConnectData, 1); connect_data->socket_type = SOCK_STREAM; connect_data->handle = handle; connect_data->connect_cb = connect_cb; connect_data->data = data; connect_data->host = g_strdup(host); connect_data->port = port; connect_data->gpi = purple_proxy_get_setup(account); connect_data->account = account; if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) && (purple_proxy_info_get_host(connect_data->gpi) == NULL || purple_proxy_info_get_port(connect_data->gpi) <= 0)) { purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid.")); purple_proxy_connect_data_destroy(connect_data); switch (purple_proxy_info_get_type(connect_data->gpi)) case PURPLE_PROXY_SOCKS4: case PURPLE_PROXY_SOCKS5: case PURPLE_PROXY_USE_ENVVAR: connecthost = purple_proxy_info_get_host(connect_data->gpi); connectport = purple_proxy_info_get_port(connect_data->gpi); purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n", purple_proxy_info_get_type(connect_data->gpi)); purple_proxy_connect_data_destroy(connect_data); connect_data->query_data = purple_dnsquery_a_account(account, connecthost, connectport, connection_host_resolved, connect_data); if (connect_data->query_data == NULL) purple_debug_error("proxy", "dns query failed unexpectedly.\n"); purple_proxy_connect_data_destroy(connect_data); handles = g_slist_prepend(handles, connect_data); purple_proxy_connect_udp(void *handle, PurpleAccount *account, const char *host, int port, PurpleProxyConnectFunction connect_cb, gpointer data) const char *connecthost = host; PurpleProxyConnectData *connect_data; g_return_val_if_fail(host != NULL, NULL); g_return_val_if_fail(port > 0, NULL); g_return_val_if_fail(connect_cb != NULL, NULL); connect_data = g_new0(PurpleProxyConnectData, 1); connect_data->socket_type = SOCK_DGRAM; connect_data->handle = handle; connect_data->connect_cb = connect_cb; connect_data->data = data; connect_data->host = g_strdup(host); connect_data->port = port; connect_data->gpi = purple_proxy_get_setup(account); connect_data->account = account; if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) && (purple_proxy_info_get_host(connect_data->gpi) == NULL || purple_proxy_info_get_port(connect_data->gpi) <= 0)) { purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid.")); purple_proxy_connect_data_destroy(connect_data); switch (purple_proxy_info_get_type(connect_data->gpi)) case PURPLE_PROXY_SOCKS4: case PURPLE_PROXY_SOCKS5: case PURPLE_PROXY_USE_ENVVAR: purple_debug_info("proxy", "Ignoring Proxy type (%d) for UDP.\n", purple_proxy_info_get_type(connect_data->gpi)); purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n", purple_proxy_info_get_type(connect_data->gpi)); purple_proxy_connect_data_destroy(connect_data); connect_data->query_data = purple_dnsquery_a_account(account, connecthost, connectport, connection_host_resolved, connect_data); if (connect_data->query_data == NULL) purple_proxy_connect_data_destroy(connect_data); handles = g_slist_prepend(handles, connect_data); purple_proxy_connect_socks5(void *handle, PurpleProxyInfo *gpi, const char *host, int port, PurpleProxyConnectFunction connect_cb, return purple_proxy_connect_socks5_account(handle, NULL, gpi, host, port, connect_cb, data); /* This is called when we connect to the SOCKS5 proxy server (through any * relevant account proxy) static void socks5_connected_to_proxy(gpointer data, gint source, const gchar *error_message) { /* This is the PurpleProxyConnectData for the overall SOCKS5 connection */ PurpleProxyConnectData *connect_data = data; purple_debug_error("proxy", "Connect Data is %p\n", connect_data); /* Check that the overall SOCKS5 connection wasn't cancelled while we were * connecting to it (we don't have a way of associating the process of * connecting to the SOCKS5 server to the overall PurpleProxyConnectData) if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data)) { purple_debug_error("proxy", "Data had gone out of scope :(\n"); /* Break the link between the two PurpleProxyConnectDatas */ connect_data->child = NULL; if (error_message != NULL) { purple_debug_error("proxy", "Unable to connect to SOCKS5 host.\n"); connect_data->connect_cb(connect_data->data, source, error_message); purple_debug_info("proxy", "Initiating SOCKS5 negotiation.\n"); purple_debug_info("proxy", "Connecting to %s:%d via %s:%d using SOCKS5\n", connect_data->host, connect_data->port, purple_proxy_info_get_host(connect_data->gpi), purple_proxy_info_get_port(connect_data->gpi)); connect_data->fd = source; s5_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE); * Combine some of this code with purple_proxy_connect() purple_proxy_connect_socks5_account(void *handle, PurpleAccount *account, const char *host, int port, PurpleProxyConnectFunction connect_cb, PurpleProxyConnectData *connect_data; PurpleProxyConnectData *account_proxy_conn_data; g_return_val_if_fail(host != NULL, NULL); g_return_val_if_fail(port >= 0, NULL); g_return_val_if_fail(connect_cb != NULL, NULL); connect_data = g_new0(PurpleProxyConnectData, 1); connect_data->socket_type = SOCK_STREAM; connect_data->handle = handle; connect_data->connect_cb = connect_cb; connect_data->data = data; connect_data->host = g_strdup(host); connect_data->port = port; connect_data->account = account; /* If there is an account proxy, use it to connect to the desired SOCKS5 account_proxy_conn_data = purple_proxy_connect(connect_data->handle, purple_proxy_info_get_host(connect_data->gpi), purple_proxy_info_get_port(connect_data->gpi), socks5_connected_to_proxy, connect_data); if (account_proxy_conn_data == NULL) { purple_debug_error("proxy", "Unable to initiate connection to account proxy.\n"); purple_proxy_connect_data_destroy(connect_data); connect_data->child = account_proxy_conn_data; handles = g_slist_prepend(handles, connect_data); purple_proxy_connect_cancel(PurpleProxyConnectData *connect_data) g_return_if_fail(connect_data != NULL); purple_proxy_connect_data_disconnect(connect_data, NULL); purple_proxy_connect_data_destroy(connect_data); purple_proxy_connect_cancel_with_handle(void *handle) for (l = handles; l != NULL; l = l_next) { PurpleProxyConnectData *connect_data = l->data; if (connect_data->handle == handle) purple_proxy_connect_cancel(connect_data); proxy_pref_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data) PurpleProxyInfo *info = purple_global_proxy_get_info(); if (purple_strequal(name, "/purple/proxy/type")) { const char *type = value; if (purple_strequal(type, "none")) proxytype = PURPLE_PROXY_NONE; else if (purple_strequal(type, "http")) proxytype = PURPLE_PROXY_HTTP; else if (purple_strequal(type, "socks4")) proxytype = PURPLE_PROXY_SOCKS4; else if (purple_strequal(type, "socks5")) proxytype = PURPLE_PROXY_SOCKS5; else if (purple_strequal(type, "tor")) proxytype = PURPLE_PROXY_TOR; else if (purple_strequal(type, "envvar")) proxytype = PURPLE_PROXY_USE_ENVVAR; purple_proxy_info_set_type(info, proxytype); } else if (purple_strequal(name, "/purple/proxy/host")) purple_proxy_info_set_host(info, value); else if (purple_strequal(name, "/purple/proxy/port")) purple_proxy_info_set_port(info, GPOINTER_TO_INT(value)); else if (purple_strequal(name, "/purple/proxy/username")) purple_proxy_info_set_username(info, value); else if (purple_strequal(name, "/purple/proxy/password")) purple_proxy_info_set_password(info, value); purple_proxy_get_handle() /* Initialize a default proxy info struct. */ global_proxy_info = purple_proxy_info_new(); purple_prefs_add_none("/purple/proxy"); purple_prefs_add_string("/purple/proxy/type", "none"); purple_prefs_add_string("/purple/proxy/host", ""); purple_prefs_add_int("/purple/proxy/port", 0); purple_prefs_add_string("/purple/proxy/username", ""); purple_prefs_add_string("/purple/proxy/password", ""); purple_prefs_add_bool("/purple/proxy/socks4_remotedns", FALSE); /* Setup callbacks for the preferences. */ handle = purple_proxy_get_handle(); purple_prefs_connect_callback(handle, "/purple/proxy/type", proxy_pref_cb, purple_prefs_connect_callback(handle, "/purple/proxy/host", proxy_pref_cb, purple_prefs_connect_callback(handle, "/purple/proxy/port", proxy_pref_cb, purple_prefs_connect_callback(handle, "/purple/proxy/username", purple_prefs_connect_callback(handle, "/purple/proxy/password", /* Load the initial proxy settings */ purple_prefs_trigger_callback("/purple/proxy/type"); purple_prefs_trigger_callback("/purple/proxy/host"); purple_prefs_trigger_callback("/purple/proxy/port"); purple_prefs_trigger_callback("/purple/proxy/username"); purple_prefs_trigger_callback("/purple/proxy/password"); purple_proxy_uninit(void) purple_proxy_connect_data_disconnect(handles->data, NULL); purple_proxy_connect_data_destroy(handles->data); purple_prefs_disconnect_by_handle(purple_proxy_get_handle()); purple_proxy_info_destroy(global_proxy_info); global_proxy_info = NULL;