comparison libpurple/proxy.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 0d4890637238
comparison
equal deleted inserted replaced
15373:f79e0f4df793 15374:5fe8042783c1
1 /**
2 * @file proxy.c Proxy API
3 * @ingroup core
4 *
5 * gaim
6 *
7 * Gaim is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27 /* this is a little piece of code to handle proxy connection */
28 /* it is intended to : 1st handle http proxy, using the CONNECT command
29 , 2nd provide an easy way to add socks support
30 , 3rd draw women to it like flies to honey */
31
32 #include "internal.h"
33 #include "cipher.h"
34 #include "debug.h"
35 #include "dnsquery.h"
36 #include "notify.h"
37 #include "ntlm.h"
38 #include "prefs.h"
39 #include "proxy.h"
40 #include "util.h"
41
42 struct _GaimProxyConnectData {
43 void *handle;
44 GaimProxyConnectFunction connect_cb;
45 gpointer data;
46 gchar *host;
47 int port;
48 int fd;
49 guint inpa;
50 GaimProxyInfo *gpi;
51 GaimDnsQueryData *query_data;
52
53 /**
54 * This contains alternating length/char* values. The char*
55 * values need to be freed when removed from the linked list.
56 */
57 GSList *hosts;
58
59 /*
60 * All of the following variables are used when establishing a
61 * connection through a proxy.
62 */
63 guchar *write_buffer;
64 gsize write_buf_len;
65 gsize written_len;
66 GaimInputFunction read_cb;
67 guchar *read_buffer;
68 gsize read_buf_len;
69 gsize read_len;
70 };
71
72 static const char *socks5errors[] = {
73 "succeeded\n",
74 "general SOCKS server failure\n",
75 "connection not allowed by ruleset\n",
76 "Network unreachable\n",
77 "Host unreachable\n",
78 "Connection refused\n",
79 "TTL expired\n",
80 "Command not supported\n",
81 "Address type not supported\n"
82 };
83
84 static GaimProxyInfo *global_proxy_info = NULL;
85
86 static GSList *handles = NULL;
87
88 static void try_connect(GaimProxyConnectData *connect_data);
89
90 /**************************************************************************
91 * Proxy structure API
92 **************************************************************************/
93 GaimProxyInfo *
94 gaim_proxy_info_new(void)
95 {
96 return g_new0(GaimProxyInfo, 1);
97 }
98
99 void
100 gaim_proxy_info_destroy(GaimProxyInfo *info)
101 {
102 g_return_if_fail(info != NULL);
103
104 g_free(info->host);
105 g_free(info->username);
106 g_free(info->password);
107
108 g_free(info);
109 }
110
111 void
112 gaim_proxy_info_set_type(GaimProxyInfo *info, GaimProxyType type)
113 {
114 g_return_if_fail(info != NULL);
115
116 info->type = type;
117 }
118
119 void
120 gaim_proxy_info_set_host(GaimProxyInfo *info, const char *host)
121 {
122 g_return_if_fail(info != NULL);
123
124 g_free(info->host);
125 info->host = g_strdup(host);
126 }
127
128 void
129 gaim_proxy_info_set_port(GaimProxyInfo *info, int port)
130 {
131 g_return_if_fail(info != NULL);
132
133 info->port = port;
134 }
135
136 void
137 gaim_proxy_info_set_username(GaimProxyInfo *info, const char *username)
138 {
139 g_return_if_fail(info != NULL);
140
141 g_free(info->username);
142 info->username = g_strdup(username);
143 }
144
145 void
146 gaim_proxy_info_set_password(GaimProxyInfo *info, const char *password)
147 {
148 g_return_if_fail(info != NULL);
149
150 g_free(info->password);
151 info->password = g_strdup(password);
152 }
153
154 GaimProxyType
155 gaim_proxy_info_get_type(const GaimProxyInfo *info)
156 {
157 g_return_val_if_fail(info != NULL, GAIM_PROXY_NONE);
158
159 return info->type;
160 }
161
162 const char *
163 gaim_proxy_info_get_host(const GaimProxyInfo *info)
164 {
165 g_return_val_if_fail(info != NULL, NULL);
166
167 return info->host;
168 }
169
170 int
171 gaim_proxy_info_get_port(const GaimProxyInfo *info)
172 {
173 g_return_val_if_fail(info != NULL, 0);
174
175 return info->port;
176 }
177
178 const char *
179 gaim_proxy_info_get_username(const GaimProxyInfo *info)
180 {
181 g_return_val_if_fail(info != NULL, NULL);
182
183 return info->username;
184 }
185
186 const char *
187 gaim_proxy_info_get_password(const GaimProxyInfo *info)
188 {
189 g_return_val_if_fail(info != NULL, NULL);
190
191 return info->password;
192 }
193
194 /**************************************************************************
195 * Global Proxy API
196 **************************************************************************/
197 GaimProxyInfo *
198 gaim_global_proxy_get_info(void)
199 {
200 return global_proxy_info;
201 }
202
203 static GaimProxyInfo *
204 gaim_gnome_proxy_get_info(void)
205 {
206 static GaimProxyInfo info = {0, NULL, 0, NULL, NULL};
207 gchar *path;
208 if ((path = g_find_program_in_path("gconftool-2"))) {
209 gchar *tmp;
210
211 g_free(path);
212
213 /* See whether to use a proxy. */
214 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/use_http_proxy", &tmp,
215 NULL, NULL, NULL))
216 return gaim_global_proxy_get_info();
217 if (!strcmp(tmp, "false\n")) {
218 info.type = GAIM_PROXY_NONE;
219 g_free(tmp);
220 return &info;
221 } else if (strcmp(tmp, "true\n")) {
222 g_free(tmp);
223 return gaim_global_proxy_get_info();
224 }
225
226 g_free(tmp);
227 info.type = GAIM_PROXY_HTTP;
228
229 /* Free the old fields */
230 if (info.host) {
231 g_free(info.host);
232 info.host = NULL;
233 }
234 if (info.username) {
235 g_free(info.username);
236 info.username = NULL;
237 }
238 if (info.password) {
239 g_free(info.password);
240 info.password = NULL;
241 }
242
243 /* Get the new ones */
244 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/host", &info.host,
245 NULL, NULL, NULL))
246 return gaim_global_proxy_get_info();
247 g_strchomp(info.host);
248
249 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/authentication_user", &info.username,
250 NULL, NULL, NULL))
251 return gaim_global_proxy_get_info();
252 g_strchomp(info.username);
253
254 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/authentication_password", &info.password,
255 NULL, NULL, NULL))
256 return gaim_global_proxy_get_info();
257 g_strchomp(info.password);
258
259 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/port", &tmp,
260 NULL, NULL, NULL))
261 return gaim_global_proxy_get_info();
262 info.port = atoi(tmp);
263 g_free(tmp);
264
265 return &info;
266 }
267 return gaim_global_proxy_get_info();
268 }
269 /**************************************************************************
270 * Proxy API
271 **************************************************************************/
272
273 /**
274 * Whoever calls this needs to have called
275 * gaim_proxy_connect_data_disconnect() beforehand.
276 */
277 static void
278 gaim_proxy_connect_data_destroy(GaimProxyConnectData *connect_data)
279 {
280 handles = g_slist_remove(handles, connect_data);
281
282 if (connect_data->query_data != NULL)
283 gaim_dnsquery_destroy(connect_data->query_data);
284
285 while (connect_data->hosts != NULL)
286 {
287 /* Discard the length... */
288 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
289 /* Free the address... */
290 g_free(connect_data->hosts->data);
291 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
292 }
293
294 g_free(connect_data->host);
295 g_free(connect_data);
296 }
297
298 /**
299 * Free all information dealing with a connection attempt and
300 * reset the connect_data to prepare for it to try to connect
301 * to another IP address.
302 *
303 * If an error message is passed in, then we know the connection
304 * attempt failed. If the connection attempt failed and
305 * connect_data->hosts is not empty then we try the next IP address.
306 * If the connection attempt failed and we have no more hosts
307 * try try then we call the callback with the given error message,
308 * then destroy the connect_data.
309 *
310 * @param error_message An error message explaining why the connection
311 * failed. This will be passed to the callback function
312 * specified in the call to gaim_proxy_connect(). If the
313 * connection was successful then pass in null.
314 */
315 static void
316 gaim_proxy_connect_data_disconnect(GaimProxyConnectData *connect_data, const gchar *error_message)
317 {
318 if (connect_data->inpa > 0)
319 {
320 gaim_input_remove(connect_data->inpa);
321 connect_data->inpa = 0;
322 }
323
324 if (connect_data->fd >= 0)
325 {
326 close(connect_data->fd);
327 connect_data->fd = -1;
328 }
329
330 g_free(connect_data->write_buffer);
331 connect_data->write_buffer = NULL;
332
333 g_free(connect_data->read_buffer);
334 connect_data->read_buffer = NULL;
335
336 if (error_message != NULL)
337 {
338 gaim_debug_info("proxy", "Connection attempt failed: %s\n",
339 error_message);
340 if (connect_data->hosts != NULL)
341 try_connect(connect_data);
342 else
343 {
344 /* Everything failed! Tell the originator of the request. */
345 connect_data->connect_cb(connect_data->data, -1, error_message);
346 gaim_proxy_connect_data_destroy(connect_data);
347 }
348 }
349 }
350
351 /**
352 * This calls gaim_proxy_connect_data_disconnect(), but it lets you
353 * specify the error_message using a printf()-like syntax.
354 */
355 static void
356 gaim_proxy_connect_data_disconnect_formatted(GaimProxyConnectData *connect_data, const char *format, ...)
357 {
358 va_list args;
359 gchar *tmp;
360
361 va_start(args, format);
362 tmp = g_strdup_vprintf(format, args);
363 va_end(args);
364
365 gaim_proxy_connect_data_disconnect(connect_data, tmp);
366 g_free(tmp);
367 }
368
369 static void
370 gaim_proxy_connect_data_connected(GaimProxyConnectData *connect_data)
371 {
372 connect_data->connect_cb(connect_data->data, connect_data->fd, NULL);
373
374 /*
375 * We've passed the file descriptor to the protocol, so it's no longer
376 * our responsibility, and we should be careful not to free it when
377 * we destroy the connect_data.
378 */
379 connect_data->fd = -1;
380
381 gaim_proxy_connect_data_disconnect(connect_data, NULL);
382 gaim_proxy_connect_data_destroy(connect_data);
383 }
384
385 static void
386 socket_ready_cb(gpointer data, gint source, GaimInputCondition cond)
387 {
388 GaimProxyConnectData *connect_data = data;
389 socklen_t len;
390 int error = 0;
391 int ret;
392
393 gaim_debug_info("proxy", "Connected.\n");
394
395 /*
396 * getsockopt after a non-blocking connect returns -1 if something is
397 * really messed up (bad descriptor, usually). Otherwise, it returns 0 and
398 * error holds what connect would have returned if it blocked until now.
399 * Thus, error == 0 is success, error == EINPROGRESS means "try again",
400 * and anything else is a real error.
401 *
402 * (error == EINPROGRESS can happen after a select because the kernel can
403 * be overly optimistic sometimes. select is just a hint that you might be
404 * able to do something.)
405 */
406 len = sizeof(error);
407 ret = getsockopt(connect_data->fd, SOL_SOCKET, SO_ERROR, &error, &len);
408
409 if (ret == 0 && error == EINPROGRESS)
410 /* No worries - we'll be called again later */
411 /* TODO: Does this ever happen? */
412 return;
413
414 if (ret != 0 || error != 0) {
415 if (ret != 0)
416 error = errno;
417 gaim_proxy_connect_data_disconnect(connect_data, strerror(error));
418 return;
419 }
420
421 gaim_proxy_connect_data_connected(connect_data);
422 }
423
424 static gboolean
425 clean_connect(gpointer data)
426 {
427 gaim_proxy_connect_data_connected(data);
428
429 return FALSE;
430 }
431
432 static void
433 proxy_connect_none(GaimProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
434 {
435 gaim_debug_info("proxy", "Connecting to %s:%d with no proxy\n",
436 connect_data->host, connect_data->port);
437
438 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
439 if (connect_data->fd < 0)
440 {
441 gaim_proxy_connect_data_disconnect_formatted(connect_data,
442 _("Unable to create socket:\n%s"), strerror(errno));
443 return;
444 }
445
446 fcntl(connect_data->fd, F_SETFL, O_NONBLOCK);
447 #ifndef _WIN32
448 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
449 #endif
450
451 if (connect(connect_data->fd, addr, addrlen) != 0)
452 {
453 if ((errno == EINPROGRESS) || (errno == EINTR))
454 {
455 gaim_debug_info("proxy", "Connection in progress\n");
456 connect_data->inpa = gaim_input_add(connect_data->fd,
457 GAIM_INPUT_WRITE, socket_ready_cb, connect_data);
458 }
459 else
460 {
461 gaim_proxy_connect_data_disconnect(connect_data, strerror(errno));
462 }
463 }
464 else
465 {
466 /*
467 * The connection happened IMMEDIATELY... strange, but whatever.
468 */
469 socklen_t len;
470 int error = ETIMEDOUT;
471 int ret;
472
473 gaim_debug_info("proxy", "Connected immediately.\n");
474
475 len = sizeof(error);
476 ret = getsockopt(connect_data->fd, SOL_SOCKET, SO_ERROR, &error, &len);
477 if ((ret != 0) || (error != 0))
478 {
479 if (ret != 0)
480 error = errno;
481 gaim_proxy_connect_data_disconnect(connect_data, strerror(error));
482 return;
483 }
484
485 /*
486 * We want to call the "connected" callback eventually, but we
487 * don't want to call it before we return, just in case.
488 */
489 gaim_timeout_add(10, clean_connect, connect_data);
490 }
491 }
492
493 /**
494 * This is a utility function used by the HTTP, SOCKS4 and SOCKS5
495 * connect functions. It writes data from a buffer to a socket.
496 * When all the data is written it sets up a watcher to read a
497 * response and call a specified function.
498 */
499 static void
500 proxy_do_write(gpointer data, gint source, GaimInputCondition cond)
501 {
502 GaimProxyConnectData *connect_data;
503 const guchar *request;
504 gsize request_len;
505 int ret;
506
507 connect_data = data;
508 request = connect_data->write_buffer + connect_data->written_len;
509 request_len = connect_data->write_buf_len - connect_data->written_len;
510
511 ret = write(connect_data->fd, request, request_len);
512 if (ret <= 0)
513 {
514 if (errno == EAGAIN)
515 /* No worries */
516 return;
517
518 /* Error! */
519 gaim_proxy_connect_data_disconnect(connect_data, strerror(errno));
520 return;
521 }
522 if (ret < request_len) {
523 connect_data->written_len += ret;
524 return;
525 }
526
527 /* We're done writing data! Wait for a response. */
528 g_free(connect_data->write_buffer);
529 connect_data->write_buffer = NULL;
530 gaim_input_remove(connect_data->inpa);
531 connect_data->inpa = gaim_input_add(connect_data->fd,
532 GAIM_INPUT_READ, connect_data->read_cb, connect_data);
533 }
534
535 #define HTTP_GOODSTRING "HTTP/1.0 200"
536 #define HTTP_GOODSTRING2 "HTTP/1.1 200"
537
538 /**
539 * We're using an HTTP proxy for a non-port 80 tunnel. Read the
540 * response to the CONNECT request.
541 */
542 static void
543 http_canread(gpointer data, gint source, GaimInputCondition cond)
544 {
545 int len, headers_len, status = 0;
546 gboolean error;
547 GaimProxyConnectData *connect_data = data;
548 guchar *p;
549 gsize max_read;
550
551 if (connect_data->read_buffer == NULL)
552 {
553 connect_data->read_buf_len = 8192;
554 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
555 connect_data->read_len = 0;
556 }
557
558 p = connect_data->read_buffer + connect_data->read_len;
559 max_read = connect_data->read_buf_len - connect_data->read_len - 1;
560
561 len = read(connect_data->fd, p, max_read);
562
563 if (len == 0)
564 {
565 gaim_proxy_connect_data_disconnect(connect_data,
566 _("Server closed the connection."));
567 return;
568 }
569
570 if (len < 0)
571 {
572 if (errno == EAGAIN)
573 /* No worries */
574 return;
575
576 /* Error! */
577 gaim_proxy_connect_data_disconnect_formatted(connect_data,
578 _("Lost connection with server:\n%s"), strerror(errno));
579 return;
580 }
581
582 connect_data->read_len += len;
583 p[len] = '\0';
584
585 p = (guchar *)g_strstr_len((const gchar *)connect_data->read_buffer,
586 connect_data->read_len, "\r\n\r\n");
587 if (p != NULL) {
588 *p = '\0';
589 headers_len = (p - connect_data->read_buffer) + 4;
590 } else if(len == max_read)
591 headers_len = len;
592 else
593 return;
594
595 error = strncmp((const char *)connect_data->read_buffer, "HTTP/", 5) != 0;
596 if (!error)
597 {
598 int major;
599 p = connect_data->read_buffer + 5;
600 major = strtol((const char *)p, (char **)&p, 10);
601 error = (major == 0) || (*p != '.');
602 if(!error) {
603 int minor;
604 p++;
605 minor = strtol((const char *)p, (char **)&p, 10);
606 error = (*p != ' ');
607 if(!error) {
608 p++;
609 status = strtol((const char *)p, (char **)&p, 10);
610 error = (*p != ' ');
611 }
612 }
613 }
614
615 /* Read the contents */
616 p = (guchar *)g_strrstr((const gchar *)connect_data->read_buffer, "Content-Length: ");
617 if (p != NULL)
618 {
619 gchar *tmp;
620 int len = 0;
621 char tmpc;
622 p += strlen("Content-Length: ");
623 tmp = strchr((const char *)p, '\r');
624 if(tmp)
625 *tmp = '\0';
626 len = atoi((const char *)p);
627 if(tmp)
628 *tmp = '\r';
629
630 /* Compensate for what has already been read */
631 len -= connect_data->read_len - headers_len;
632 /* I'm assuming that we're doing this to prevent the server from
633 complaining / breaking since we don't read the whole page */
634 while (len--) {
635 /* TODO: deal with EAGAIN (and other errors) better */
636 if (read(connect_data->fd, &tmpc, 1) < 0 && errno != EAGAIN)
637 break;
638 }
639 }
640
641 if (error)
642 {
643 gaim_proxy_connect_data_disconnect_formatted(connect_data,
644 _("Unable to parse response from HTTP proxy: %s\n"),
645 connect_data->read_buffer);
646 return;
647 }
648 else if (status != 200)
649 {
650 gaim_debug_error("proxy",
651 "Proxy server replied with:\n%s\n",
652 connect_data->read_buffer);
653
654 if (status == 407 /* Proxy Auth */)
655 {
656 gchar *ntlm;
657 ntlm = g_strrstr((const gchar *)connect_data->read_buffer,
658 "Proxy-Authenticate: NTLM ");
659 if (ntlm != NULL)
660 {
661 /* Check for Type-2 */
662 gchar *tmp = ntlm;
663 guint8 *nonce;
664 gchar *domain = (gchar*)gaim_proxy_info_get_username(connect_data->gpi);
665 gchar *username;
666 gchar *request;
667 gchar *response;
668 username = strchr(domain, '\\');
669 if (username == NULL)
670 {
671 gaim_proxy_connect_data_disconnect_formatted(connect_data,
672 _("HTTP proxy connection error %d"), status);
673 return;
674 }
675 *username = '\0';
676 username++;
677 ntlm += strlen("Proxy-Authenticate: NTLM ");
678 while(*tmp != '\r' && *tmp != '\0') tmp++;
679 *tmp = '\0';
680 nonce = gaim_ntlm_parse_type2(ntlm, NULL);
681 response = gaim_ntlm_gen_type3(username,
682 (gchar*) gaim_proxy_info_get_password(connect_data->gpi),
683 (gchar*) gaim_proxy_info_get_host(connect_data->gpi),
684 domain, nonce, NULL);
685 username--;
686 *username = '\\';
687 request = g_strdup_printf(
688 "CONNECT %s:%d HTTP/1.1\r\n"
689 "Host: %s:%d\r\n"
690 "Proxy-Authorization: NTLM %s\r\n"
691 "Proxy-Connection: Keep-Alive\r\n\r\n",
692 connect_data->host, connect_data->port, connect_data->host,
693 connect_data->port, response);
694 g_free(response);
695
696 g_free(connect_data->read_buffer);
697 connect_data->read_buffer = NULL;
698
699 connect_data->write_buffer = (guchar *)request;
700 connect_data->write_buf_len = strlen(request);
701 connect_data->written_len = 0;
702 connect_data->read_cb = http_canread;
703
704 gaim_input_remove(connect_data->inpa);
705 connect_data->inpa = gaim_input_add(connect_data->fd,
706 GAIM_INPUT_WRITE, proxy_do_write, connect_data);
707 proxy_do_write(connect_data, connect_data->fd, cond);
708 return;
709 } else if((ntlm = g_strrstr((const char *)connect_data->read_buffer, "Proxy-Authenticate: NTLM"))) { /* Empty message */
710 gchar request[2048];
711 gchar *domain = (gchar*) gaim_proxy_info_get_username(connect_data->gpi);
712 gchar *username;
713 int request_len;
714 username = strchr(domain, '\\');
715 if (username == NULL)
716 {
717 gaim_proxy_connect_data_disconnect_formatted(connect_data,
718 _("HTTP proxy connection error %d"), status);
719 return;
720 }
721 *username = '\0';
722
723 request_len = g_snprintf(request, sizeof(request),
724 "CONNECT %s:%d HTTP/1.1\r\n"
725 "Host: %s:%d\r\n",
726 connect_data->host, connect_data->port,
727 connect_data->host, connect_data->port);
728
729 g_return_if_fail(request_len < sizeof(request));
730 request_len += g_snprintf(request + request_len,
731 sizeof(request) - request_len,
732 "Proxy-Authorization: NTLM %s\r\n"
733 "Proxy-Connection: Keep-Alive\r\n\r\n",
734 gaim_ntlm_gen_type1(
735 (gchar*) gaim_proxy_info_get_host(connect_data->gpi),
736 domain));
737 *username = '\\';
738
739 gaim_input_remove(connect_data->inpa);
740 g_free(connect_data->read_buffer);
741 connect_data->read_buffer = NULL;
742
743 connect_data->write_buffer = g_memdup(request, request_len);
744 connect_data->write_buf_len = request_len;
745 connect_data->written_len = 0;
746
747 connect_data->read_cb = http_canread;
748
749 connect_data->inpa = gaim_input_add(connect_data->fd,
750 GAIM_INPUT_WRITE, proxy_do_write, connect_data);
751
752 proxy_do_write(connect_data, connect_data->fd, cond);
753 return;
754 } else {
755 gaim_proxy_connect_data_disconnect_formatted(connect_data,
756 _("HTTP proxy connection error %d"), status);
757 return;
758 }
759 }
760 if (status == 403)
761 {
762 /* Forbidden */
763 gaim_proxy_connect_data_disconnect_formatted(connect_data,
764 _("Access denied: HTTP proxy server forbids port %d tunneling."),
765 connect_data->port);
766 } else {
767 gaim_proxy_connect_data_disconnect_formatted(connect_data,
768 _("HTTP proxy connection error %d"), status);
769 }
770 } else {
771 gaim_input_remove(connect_data->inpa);
772 connect_data->inpa = 0;
773 g_free(connect_data->read_buffer);
774 connect_data->read_buffer = NULL;
775 gaim_debug_info("proxy", "HTTP proxy connection established\n");
776 gaim_proxy_connect_data_connected(connect_data);
777 return;
778 }
779 }
780
781 static void
782 http_canwrite(gpointer data, gint source, GaimInputCondition cond)
783 {
784 GString *request;
785 GaimProxyConnectData *connect_data;
786 socklen_t len;
787 int error = ETIMEDOUT;
788 int ret;
789
790 gaim_debug_info("proxy", "Connected.\n");
791
792 connect_data = data;
793
794 if (connect_data->inpa > 0)
795 {
796 gaim_input_remove(connect_data->inpa);
797 connect_data->inpa = 0;
798 }
799
800 len = sizeof(error);
801 ret = getsockopt(connect_data->fd, SOL_SOCKET, SO_ERROR, &error, &len);
802 if ((ret != 0) || (error != 0))
803 {
804 if (ret != 0)
805 error = errno;
806 gaim_proxy_connect_data_disconnect(connect_data, strerror(error));
807 return;
808 }
809
810 gaim_debug_info("proxy", "Using CONNECT tunneling for %s:%d\n",
811 connect_data->host, connect_data->port);
812
813 request = g_string_sized_new(4096);
814 g_string_append_printf(request,
815 "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n",
816 connect_data->host, connect_data->port,
817 connect_data->host, connect_data->port);
818
819 if (gaim_proxy_info_get_username(connect_data->gpi) != NULL)
820 {
821 char *t1, *t2;
822
823 t1 = g_strdup_printf("%s:%s",
824 gaim_proxy_info_get_username(connect_data->gpi),
825 gaim_proxy_info_get_password(connect_data->gpi) ?
826 gaim_proxy_info_get_password(connect_data->gpi) : "");
827 t2 = gaim_base64_encode((const guchar *)t1, strlen(t1));
828 g_free(t1);
829
830 g_string_append_printf(request,
831 "Proxy-Authorization: Basic %s\r\n"
832 "Proxy-Authorization: NTLM %s\r\n"
833 "Proxy-Connection: Keep-Alive\r\n",
834 t2, gaim_ntlm_gen_type1(
835 gaim_proxy_info_get_host(connect_data->gpi), ""));
836 g_free(t2);
837 }
838
839 g_string_append(request, "\r\n");
840
841 connect_data->write_buf_len = request->len;
842 connect_data->write_buffer = (guchar *)g_string_free(request, FALSE);
843 connect_data->written_len = 0;
844 connect_data->read_cb = http_canread;
845
846 connect_data->inpa = gaim_input_add(connect_data->fd,
847 GAIM_INPUT_WRITE, proxy_do_write, connect_data);
848 proxy_do_write(connect_data, connect_data->fd, cond);
849 }
850
851 static void
852 proxy_connect_http(GaimProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
853 {
854 gaim_debug_info("proxy",
855 "Connecting to %s:%d via %s:%d using HTTP\n",
856 connect_data->host, connect_data->port,
857 (gaim_proxy_info_get_host(connect_data->gpi) ? gaim_proxy_info_get_host(connect_data->gpi) : "(null)"),
858 gaim_proxy_info_get_port(connect_data->gpi));
859
860 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
861 if (connect_data->fd < 0)
862 {
863 gaim_proxy_connect_data_disconnect_formatted(connect_data,
864 _("Unable to create socket:\n%s"), strerror(errno));
865 return;
866 }
867
868 fcntl(connect_data->fd, F_SETFL, O_NONBLOCK);
869 #ifndef _WIN32
870 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
871 #endif
872
873 if (connect(connect_data->fd, addr, addrlen) != 0)
874 {
875 if ((errno == EINPROGRESS) || (errno == EINTR)) {
876 gaim_debug_info("proxy", "Connection in progress\n");
877
878 if (connect_data->port != 80)
879 {
880 /* we need to do CONNECT first */
881 connect_data->inpa = gaim_input_add(connect_data->fd,
882 GAIM_INPUT_WRITE, http_canwrite, connect_data);
883 }
884 else
885 {
886 /*
887 * If we're trying to connect to something running on
888 * port 80 then we assume the traffic using this
889 * connection is going to be HTTP traffic. If it's
890 * not then this will fail (uglily). But it's good
891 * to avoid using the CONNECT method because it's
892 * not always allowed.
893 */
894 gaim_debug_info("proxy", "HTTP proxy connection established\n");
895 gaim_proxy_connect_data_connected(connect_data);
896 }
897 }
898 else
899 {
900 gaim_proxy_connect_data_disconnect(connect_data, strerror(errno));
901 }
902 }
903 else
904 {
905 gaim_debug_info("proxy", "Connected immediately.\n");
906
907 http_canwrite(connect_data, connect_data->fd, GAIM_INPUT_WRITE);
908 }
909 }
910
911 static void
912 s4_canread(gpointer data, gint source, GaimInputCondition cond)
913 {
914 GaimProxyConnectData *connect_data = data;
915 guchar *buf;
916 int len, max_read;
917
918 /* This is really not going to block under normal circumstances, but to
919 * be correct, we deal with the unlikely scenario */
920
921 if (connect_data->read_buffer == NULL) {
922 connect_data->read_buf_len = 12;
923 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
924 connect_data->read_len = 0;
925 }
926
927 buf = connect_data->read_buffer + connect_data->read_len;
928 max_read = connect_data->read_buf_len - connect_data->read_len;
929
930 len = read(connect_data->fd, buf, max_read);
931
932 if ((len < 0 && errno == EAGAIN) || (len > 0 && len + connect_data->read_len < 4))
933 return;
934 else if (len + connect_data->read_len >= 4) {
935 if (connect_data->read_buffer[1] == 90) {
936 gaim_proxy_connect_data_connected(connect_data);
937 return;
938 }
939 }
940
941 gaim_proxy_connect_data_disconnect(connect_data, strerror(errno));
942 }
943
944 static void
945 s4_canwrite(gpointer data, gint source, GaimInputCondition cond)
946 {
947 unsigned char packet[9];
948 struct hostent *hp;
949 GaimProxyConnectData *connect_data = data;
950 socklen_t len;
951 int error = ETIMEDOUT;
952 int ret;
953
954 gaim_debug_info("socks4 proxy", "Connected.\n");
955
956 if (connect_data->inpa > 0)
957 {
958 gaim_input_remove(connect_data->inpa);
959 connect_data->inpa = 0;
960 }
961
962 len = sizeof(error);
963 ret = getsockopt(connect_data->fd, SOL_SOCKET, SO_ERROR, &error, &len);
964 if ((ret != 0) || (error != 0))
965 {
966 if (ret != 0)
967 error = errno;
968 gaim_proxy_connect_data_disconnect(connect_data, strerror(error));
969 return;
970 }
971
972 /*
973 * The socks4 spec doesn't include support for doing host name
974 * lookups by the proxy. Some socks4 servers do this via
975 * extensions to the protocol. Since we don't know if a
976 * server supports this, it would need to be implemented
977 * with an option, or some detection mechanism - in the
978 * meantime, stick with plain old SOCKS4.
979 */
980 /* TODO: Use gaim_dnsquery_a() */
981 hp = gethostbyname(connect_data->host);
982 if (hp == NULL) {
983 gaim_proxy_connect_data_disconnect_formatted(connect_data,
984 _("Error resolving %s"), connect_data->host);
985 return;
986 }
987
988 packet[0] = 4;
989 packet[1] = 1;
990 packet[2] = connect_data->port >> 8;
991 packet[3] = connect_data->port & 0xff;
992 packet[4] = (unsigned char)(hp->h_addr_list[0])[0];
993 packet[5] = (unsigned char)(hp->h_addr_list[0])[1];
994 packet[6] = (unsigned char)(hp->h_addr_list[0])[2];
995 packet[7] = (unsigned char)(hp->h_addr_list[0])[3];
996 packet[8] = 0;
997
998 connect_data->write_buffer = g_memdup(packet, sizeof(packet));
999 connect_data->write_buf_len = sizeof(packet);
1000 connect_data->written_len = 0;
1001 connect_data->read_cb = s4_canread;
1002
1003 connect_data->inpa = gaim_input_add(connect_data->fd, GAIM_INPUT_WRITE, proxy_do_write, connect_data);
1004
1005 proxy_do_write(connect_data, connect_data->fd, cond);
1006 }
1007
1008 static void
1009 proxy_connect_socks4(GaimProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
1010 {
1011 gaim_debug_info("proxy",
1012 "Connecting to %s:%d via %s:%d using SOCKS4\n",
1013 connect_data->host, connect_data->port,
1014 gaim_proxy_info_get_host(connect_data->gpi),
1015 gaim_proxy_info_get_port(connect_data->gpi));
1016
1017 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
1018 if (connect_data->fd < 0)
1019 {
1020 gaim_proxy_connect_data_disconnect_formatted(connect_data,
1021 _("Unable to create socket:\n%s"), strerror(errno));
1022 return;
1023 }
1024
1025 fcntl(connect_data->fd, F_SETFL, O_NONBLOCK);
1026 #ifndef _WIN32
1027 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
1028 #endif
1029
1030 if (connect(connect_data->fd, addr, addrlen) != 0)
1031 {
1032 if ((errno == EINPROGRESS) || (errno == EINTR))
1033 {
1034 gaim_debug_info("proxy", "Connection in progress.\n");
1035 connect_data->inpa = gaim_input_add(connect_data->fd,
1036 GAIM_INPUT_WRITE, s4_canwrite, connect_data);
1037 }
1038 else
1039 {
1040 gaim_proxy_connect_data_disconnect(connect_data, strerror(errno));
1041 }
1042 }
1043 else
1044 {
1045 gaim_debug_info("proxy", "Connected immediately.\n");
1046
1047 s4_canwrite(connect_data, connect_data->fd, GAIM_INPUT_WRITE);
1048 }
1049 }
1050
1051 static void
1052 s5_canread_again(gpointer data, gint source, GaimInputCondition cond)
1053 {
1054 guchar *dest, *buf;
1055 GaimProxyConnectData *connect_data = data;
1056 int len;
1057
1058 if (connect_data->read_buffer == NULL) {
1059 connect_data->read_buf_len = 512;
1060 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1061 connect_data->read_len = 0;
1062 }
1063
1064 dest = connect_data->read_buffer + connect_data->read_len;
1065 buf = connect_data->read_buffer;
1066
1067 gaim_debug_info("socks5 proxy", "Able to read again.\n");
1068
1069 len = read(connect_data->fd, dest, (connect_data->read_buf_len - connect_data->read_len));
1070
1071 if (len == 0)
1072 {
1073 gaim_proxy_connect_data_disconnect(connect_data,
1074 _("Server closed the connection."));
1075 return;
1076 }
1077
1078 if (len < 0)
1079 {
1080 if (errno == EAGAIN)
1081 /* No worries */
1082 return;
1083
1084 /* Error! */
1085 gaim_proxy_connect_data_disconnect_formatted(connect_data,
1086 _("Lost connection with server:\n%s"), strerror(errno));
1087 return;
1088 }
1089
1090 connect_data->read_len += len;
1091
1092 if(connect_data->read_len < 4)
1093 return;
1094
1095 if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
1096 if ((buf[0] == 0x05) && (buf[1] < 0x09)) {
1097 gaim_debug_error("socks5 proxy", socks5errors[buf[1]]);
1098 gaim_proxy_connect_data_disconnect(connect_data,
1099 socks5errors[buf[1]]);
1100 } else {
1101 gaim_debug_error("socks5 proxy", "Bad data.\n");
1102 gaim_proxy_connect_data_disconnect(connect_data,
1103 _("Received invalid data on connection with server."));
1104 }
1105 return;
1106 }
1107
1108 /* Skip past BND.ADDR */
1109 switch(buf[3]) {
1110 case 0x01: /* the address is a version-4 IP address, with a length of 4 octets */
1111 if(connect_data->read_len < 4 + 4)
1112 return;
1113 buf += 4 + 4;
1114 break;
1115 case 0x03: /* the address field contains a fully-qualified domain name. The first
1116 octet of the address field contains the number of octets of name that
1117 follow, there is no terminating NUL octet. */
1118 if(connect_data->read_len < 4 + 1)
1119 return;
1120 buf += 4 + 1;
1121 if(connect_data->read_len < 4 + 1 + buf[0])
1122 return;
1123 buf += buf[0];
1124 break;
1125 case 0x04: /* the address is a version-6 IP address, with a length of 16 octets */
1126 if(connect_data->read_len < 4 + 16)
1127 return;
1128 buf += 4 + 16;
1129 break;
1130 }
1131
1132 if(connect_data->read_len < (buf - connect_data->read_buffer) + 2)
1133 return;
1134
1135 /* Skip past BND.PORT */
1136 buf += 2;
1137
1138 gaim_proxy_connect_data_connected(connect_data);
1139 }
1140
1141 static void
1142 s5_sendconnect(gpointer data, int source)
1143 {
1144 GaimProxyConnectData *connect_data = data;
1145 int hlen = strlen(connect_data->host);
1146 connect_data->write_buf_len = 5 + hlen + 2;
1147 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1148 connect_data->written_len = 0;
1149
1150 connect_data->write_buffer[0] = 0x05;
1151 connect_data->write_buffer[1] = 0x01; /* CONNECT */
1152 connect_data->write_buffer[2] = 0x00; /* reserved */
1153 connect_data->write_buffer[3] = 0x03; /* address type -- host name */
1154 connect_data->write_buffer[4] = hlen;
1155 memcpy(connect_data->write_buffer + 5, connect_data->host, hlen);
1156 connect_data->write_buffer[5 + hlen] = connect_data->port >> 8;
1157 connect_data->write_buffer[5 + hlen + 1] = connect_data->port & 0xff;
1158
1159 connect_data->read_cb = s5_canread_again;
1160
1161 connect_data->inpa = gaim_input_add(connect_data->fd, GAIM_INPUT_WRITE, proxy_do_write, connect_data);
1162 proxy_do_write(connect_data, connect_data->fd, GAIM_INPUT_WRITE);
1163 }
1164
1165 static void
1166 s5_readauth(gpointer data, gint source, GaimInputCondition cond)
1167 {
1168 GaimProxyConnectData *connect_data = data;
1169 int len;
1170
1171 if (connect_data->read_buffer == NULL) {
1172 connect_data->read_buf_len = 2;
1173 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1174 connect_data->read_len = 0;
1175 }
1176
1177 gaim_debug_info("socks5 proxy", "Got auth response.\n");
1178
1179 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1180 connect_data->read_buf_len - connect_data->read_len);
1181
1182 if (len == 0)
1183 {
1184 gaim_proxy_connect_data_disconnect(connect_data,
1185 _("Server closed the connection."));
1186 return;
1187 }
1188
1189 if (len < 0)
1190 {
1191 if (errno == EAGAIN)
1192 /* No worries */
1193 return;
1194
1195 /* Error! */
1196 gaim_proxy_connect_data_disconnect_formatted(connect_data,
1197 _("Lost connection with server:\n%s"), strerror(errno));
1198 return;
1199 }
1200
1201 connect_data->read_len += len;
1202 if (connect_data->read_len < 2)
1203 return;
1204
1205 gaim_input_remove(connect_data->inpa);
1206 connect_data->inpa = 0;
1207
1208 if ((connect_data->read_buffer[0] != 0x01) || (connect_data->read_buffer[1] != 0x00)) {
1209 gaim_proxy_connect_data_disconnect(connect_data,
1210 _("Received invalid data on connection with server."));
1211 return;
1212 }
1213
1214 g_free(connect_data->read_buffer);
1215 connect_data->read_buffer = NULL;
1216
1217 s5_sendconnect(connect_data, connect_data->fd);
1218 }
1219
1220 static void
1221 hmacmd5_chap(const unsigned char * challenge, int challen, const char * passwd, unsigned char * response)
1222 {
1223 GaimCipher *cipher;
1224 GaimCipherContext *ctx;
1225 int i;
1226 unsigned char Kxoripad[65];
1227 unsigned char Kxoropad[65];
1228 int pwlen;
1229
1230 cipher = gaim_ciphers_find_cipher("md5");
1231 ctx = gaim_cipher_context_new(cipher, NULL);
1232
1233 memset(Kxoripad,0,sizeof(Kxoripad));
1234 memset(Kxoropad,0,sizeof(Kxoropad));
1235
1236 pwlen=strlen(passwd);
1237 if (pwlen>64) {
1238 gaim_cipher_context_append(ctx, (const guchar *)passwd, strlen(passwd));
1239 gaim_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL);
1240 pwlen=16;
1241 } else {
1242 memcpy(Kxoripad, passwd, pwlen);
1243 }
1244 memcpy(Kxoropad,Kxoripad,pwlen);
1245
1246 for (i=0;i<64;i++) {
1247 Kxoripad[i]^=0x36;
1248 Kxoropad[i]^=0x5c;
1249 }
1250
1251 gaim_cipher_context_reset(ctx, NULL);
1252 gaim_cipher_context_append(ctx, Kxoripad, 64);
1253 gaim_cipher_context_append(ctx, challenge, challen);
1254 gaim_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL);
1255
1256 gaim_cipher_context_reset(ctx, NULL);
1257 gaim_cipher_context_append(ctx, Kxoropad, 64);
1258 gaim_cipher_context_append(ctx, Kxoripad, 16);
1259 gaim_cipher_context_digest(ctx, 16, response, NULL);
1260
1261 gaim_cipher_context_destroy(ctx);
1262 }
1263
1264 static void
1265 s5_readchap(gpointer data, gint source, GaimInputCondition cond)
1266 {
1267 guchar *cmdbuf, *buf;
1268 GaimProxyConnectData *connect_data = data;
1269 int len, navas, currentav;
1270
1271 gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Got CHAP response.\n");
1272
1273 if (connect_data->read_buffer == NULL) {
1274 connect_data->read_buf_len = 20;
1275 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1276 connect_data->read_len = 0;
1277 }
1278
1279 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1280 connect_data->read_buf_len - connect_data->read_len);
1281
1282 if (len == 0)
1283 {
1284 gaim_proxy_connect_data_disconnect(connect_data,
1285 _("Server closed the connection."));
1286 return;
1287 }
1288
1289 if (len < 0)
1290 {
1291 if (errno == EAGAIN)
1292 /* No worries */
1293 return;
1294
1295 /* Error! */
1296 gaim_proxy_connect_data_disconnect_formatted(connect_data,
1297 _("Lost connection with server:\n%s"), strerror(errno));
1298 return;
1299 }
1300
1301 connect_data->read_len += len;
1302 if (connect_data->read_len < 2)
1303 return;
1304
1305 cmdbuf = connect_data->read_buffer;
1306
1307 if (*cmdbuf != 0x01) {
1308 gaim_proxy_connect_data_disconnect(connect_data,
1309 _("Received invalid data on connection with server."));
1310 return;
1311 }
1312 cmdbuf++;
1313
1314 navas = *cmdbuf;
1315 cmdbuf++;
1316
1317 for (currentav = 0; currentav < navas; currentav++) {
1318 if (connect_data->read_len - (cmdbuf - connect_data->read_buffer) < 2)
1319 return;
1320 if (connect_data->read_len - (cmdbuf - connect_data->read_buffer) < cmdbuf[1])
1321 return;
1322 buf = cmdbuf + 2;
1323 switch (cmdbuf[0]) {
1324 case 0x00:
1325 /* Did auth work? */
1326 if (buf[0] == 0x00) {
1327 gaim_input_remove(connect_data->inpa);
1328 connect_data->inpa = 0;
1329 g_free(connect_data->read_buffer);
1330 connect_data->read_buffer = NULL;
1331 /* Success */
1332 s5_sendconnect(connect_data, connect_data->fd);
1333 return;
1334 } else {
1335 /* Failure */
1336 gaim_debug_warning("proxy",
1337 "socks5 CHAP authentication "
1338 "failed. Disconnecting...");
1339 gaim_proxy_connect_data_disconnect(connect_data,
1340 _("Authentication failed"));
1341 return;
1342 }
1343 break;
1344 case 0x03:
1345 /* Server wants our credentials */
1346
1347 connect_data->write_buf_len = 16 + 4;
1348 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1349 connect_data->written_len = 0;
1350
1351 hmacmd5_chap(buf, cmdbuf[1],
1352 gaim_proxy_info_get_password(connect_data->gpi),
1353 connect_data->write_buffer + 4);
1354 connect_data->write_buffer[0] = 0x01;
1355 connect_data->write_buffer[1] = 0x01;
1356 connect_data->write_buffer[2] = 0x04;
1357 connect_data->write_buffer[3] = 0x10;
1358
1359 gaim_input_remove(connect_data->inpa);
1360 g_free(connect_data->read_buffer);
1361 connect_data->read_buffer = NULL;
1362
1363 connect_data->read_cb = s5_readchap;
1364
1365 connect_data->inpa = gaim_input_add(connect_data->fd,
1366 GAIM_INPUT_WRITE, proxy_do_write, connect_data);
1367
1368 proxy_do_write(connect_data, connect_data->fd, GAIM_INPUT_WRITE);
1369 break;
1370 case 0x11:
1371 /* Server wants to select an algorithm */
1372 if (buf[0] != 0x85) {
1373 /* Only currently support HMAC-MD5 */
1374 gaim_debug_warning("proxy",
1375 "Server tried to select an "
1376 "algorithm that we did not advertise "
1377 "as supporting. This is a violation "
1378 "of the socks5 CHAP specification. "
1379 "Disconnecting...");
1380 gaim_proxy_connect_data_disconnect(connect_data,
1381 _("Received invalid data on connection with server."));
1382 return;
1383 }
1384 break;
1385 }
1386 cmdbuf = buf + cmdbuf[1];
1387 }
1388 /* Fell through. We ran out of CHAP events to process, but haven't
1389 * succeeded or failed authentication - there may be more to come.
1390 * If this is the case, come straight back here. */
1391 }
1392
1393 static void
1394 s5_canread(gpointer data, gint source, GaimInputCondition cond)
1395 {
1396 GaimProxyConnectData *connect_data = data;
1397 int len;
1398
1399 if (connect_data->read_buffer == NULL) {
1400 connect_data->read_buf_len = 2;
1401 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1402 connect_data->read_len = 0;
1403 }
1404
1405 gaim_debug_info("socks5 proxy", "Able to read.\n");
1406
1407 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1408 connect_data->read_buf_len - connect_data->read_len);
1409
1410 if (len == 0)
1411 {
1412 gaim_proxy_connect_data_disconnect(connect_data,
1413 _("Server closed the connection."));
1414 return;
1415 }
1416
1417 if (len < 0)
1418 {
1419 if (errno == EAGAIN)
1420 /* No worries */
1421 return;
1422
1423 /* Error! */
1424 gaim_proxy_connect_data_disconnect_formatted(connect_data,
1425 _("Lost connection with server:\n%s"), strerror(errno));
1426 return;
1427 }
1428
1429 connect_data->read_len += len;
1430 if (connect_data->read_len < 2)
1431 return;
1432
1433 gaim_input_remove(connect_data->inpa);
1434 connect_data->inpa = 0;
1435
1436 if ((connect_data->read_buffer[0] != 0x05) || (connect_data->read_buffer[1] == 0xff)) {
1437 gaim_proxy_connect_data_disconnect(connect_data,
1438 _("Received invalid data on connection with server."));
1439 return;
1440 }
1441
1442 if (connect_data->read_buffer[1] == 0x02) {
1443 gsize i, j;
1444 const char *u, *p;
1445
1446 u = gaim_proxy_info_get_username(connect_data->gpi);
1447 p = gaim_proxy_info_get_password(connect_data->gpi);
1448
1449 i = (u == NULL) ? 0 : strlen(u);
1450 j = (p == NULL) ? 0 : strlen(p);
1451
1452 connect_data->write_buf_len = 1 + 1 + i + 1 + j;
1453 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1454 connect_data->written_len = 0;
1455
1456 connect_data->write_buffer[0] = 0x01; /* version 1 */
1457 connect_data->write_buffer[1] = i;
1458 if (u != NULL)
1459 memcpy(connect_data->write_buffer + 2, u, i);
1460 connect_data->write_buffer[2 + i] = j;
1461 if (p != NULL)
1462 memcpy(connect_data->write_buffer + 2 + i + 1, p, j);
1463
1464 g_free(connect_data->read_buffer);
1465 connect_data->read_buffer = NULL;
1466
1467 connect_data->read_cb = s5_readauth;
1468
1469 connect_data->inpa = gaim_input_add(connect_data->fd, GAIM_INPUT_WRITE,
1470 proxy_do_write, connect_data);
1471
1472 proxy_do_write(connect_data, connect_data->fd, GAIM_INPUT_WRITE);
1473
1474 return;
1475 } else if (connect_data->read_buffer[1] == 0x03) {
1476 gsize userlen;
1477 userlen = strlen(gaim_proxy_info_get_username(connect_data->gpi));
1478
1479 connect_data->write_buf_len = 7 + userlen;
1480 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1481 connect_data->written_len = 0;
1482
1483 connect_data->write_buffer[0] = 0x01;
1484 connect_data->write_buffer[1] = 0x02;
1485 connect_data->write_buffer[2] = 0x11;
1486 connect_data->write_buffer[3] = 0x01;
1487 connect_data->write_buffer[4] = 0x85;
1488 connect_data->write_buffer[5] = 0x02;
1489 connect_data->write_buffer[6] = userlen;
1490 memcpy(connect_data->write_buffer + 7,
1491 gaim_proxy_info_get_username(connect_data->gpi), userlen);
1492
1493 g_free(connect_data->read_buffer);
1494 connect_data->read_buffer = NULL;
1495
1496 connect_data->read_cb = s5_readchap;
1497
1498 connect_data->inpa = gaim_input_add(connect_data->fd, GAIM_INPUT_WRITE,
1499 proxy_do_write, connect_data);
1500
1501 proxy_do_write(connect_data, connect_data->fd, GAIM_INPUT_WRITE);
1502
1503 return;
1504 } else {
1505 g_free(connect_data->read_buffer);
1506 connect_data->read_buffer = NULL;
1507
1508 s5_sendconnect(connect_data, connect_data->fd);
1509 }
1510 }
1511
1512 static void
1513 s5_canwrite(gpointer data, gint source, GaimInputCondition cond)
1514 {
1515 unsigned char buf[5];
1516 int i;
1517 GaimProxyConnectData *connect_data = data;
1518 socklen_t len;
1519 int error = ETIMEDOUT;
1520 int ret;
1521
1522 gaim_debug_info("socks5 proxy", "Connected.\n");
1523
1524 if (connect_data->inpa > 0)
1525 {
1526 gaim_input_remove(connect_data->inpa);
1527 connect_data->inpa = 0;
1528 }
1529
1530 len = sizeof(error);
1531 ret = getsockopt(connect_data->fd, SOL_SOCKET, SO_ERROR, &error, &len);
1532 if ((ret != 0) || (error != 0))
1533 {
1534 if (ret != 0)
1535 error = errno;
1536 gaim_proxy_connect_data_disconnect(connect_data, strerror(error));
1537 return;
1538 }
1539
1540 i = 0;
1541 buf[0] = 0x05; /* SOCKS version 5 */
1542
1543 if (gaim_proxy_info_get_username(connect_data->gpi) != NULL) {
1544 buf[1] = 0x03; /* three methods */
1545 buf[2] = 0x00; /* no authentication */
1546 buf[3] = 0x03; /* CHAP authentication */
1547 buf[4] = 0x02; /* username/password authentication */
1548 i = 5;
1549 }
1550 else {
1551 buf[1] = 0x01;
1552 buf[2] = 0x00;
1553 i = 3;
1554 }
1555
1556 connect_data->write_buf_len = i;
1557 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1558 memcpy(connect_data->write_buffer, buf, i);
1559
1560 connect_data->read_cb = s5_canread;
1561
1562 connect_data->inpa = gaim_input_add(connect_data->fd, GAIM_INPUT_WRITE, proxy_do_write, connect_data);
1563 proxy_do_write(connect_data, connect_data->fd, GAIM_INPUT_WRITE);
1564 }
1565
1566 static void
1567 proxy_connect_socks5(GaimProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
1568 {
1569 gaim_debug_info("proxy",
1570 "Connecting to %s:%d via %s:%d using SOCKS5\n",
1571 connect_data->host, connect_data->port,
1572 gaim_proxy_info_get_host(connect_data->gpi),
1573 gaim_proxy_info_get_port(connect_data->gpi));
1574
1575 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
1576 if (connect_data->fd < 0)
1577 {
1578 gaim_proxy_connect_data_disconnect_formatted(connect_data,
1579 _("Unable to create socket:\n%s"), strerror(errno));
1580 return;
1581 }
1582
1583 fcntl(connect_data->fd, F_SETFL, O_NONBLOCK);
1584 #ifndef _WIN32
1585 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
1586 #endif
1587
1588 if (connect(connect_data->fd, addr, addrlen) != 0)
1589 {
1590 if ((errno == EINPROGRESS) || (errno == EINTR))
1591 {
1592 gaim_debug_info("socks5 proxy", "Connection in progress\n");
1593 connect_data->inpa = gaim_input_add(connect_data->fd,
1594 GAIM_INPUT_WRITE, s5_canwrite, connect_data);
1595 }
1596 else
1597 {
1598 gaim_proxy_connect_data_disconnect(connect_data, strerror(errno));
1599 }
1600 }
1601 else
1602 {
1603 gaim_debug_info("proxy", "Connected immediately.\n");
1604
1605 s5_canwrite(connect_data, connect_data->fd, GAIM_INPUT_WRITE);
1606 }
1607 }
1608
1609 /**
1610 * This function attempts to connect to the next IP address in the list
1611 * of IP addresses returned to us by gaim_dnsquery_a() and attemps
1612 * to connect to each one. This is called after the hostname is
1613 * resolved, and each time a connection attempt fails (assuming there
1614 * is another IP address to try).
1615 */
1616 static void try_connect(GaimProxyConnectData *connect_data)
1617 {
1618 size_t addrlen;
1619 struct sockaddr *addr;
1620 char ipaddr[INET6_ADDRSTRLEN];
1621
1622 addrlen = GPOINTER_TO_INT(connect_data->hosts->data);
1623 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
1624 addr = connect_data->hosts->data;
1625 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
1626
1627 inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr,
1628 ipaddr, sizeof(ipaddr));
1629 gaim_debug_info("proxy", "Attempting connection to %s\n", ipaddr);
1630
1631 switch (gaim_proxy_info_get_type(connect_data->gpi)) {
1632 case GAIM_PROXY_NONE:
1633 proxy_connect_none(connect_data, addr, addrlen);
1634 break;
1635
1636 case GAIM_PROXY_HTTP:
1637 proxy_connect_http(connect_data, addr, addrlen);
1638 break;
1639
1640 case GAIM_PROXY_SOCKS4:
1641 proxy_connect_socks4(connect_data, addr, addrlen);
1642 break;
1643
1644 case GAIM_PROXY_SOCKS5:
1645 proxy_connect_socks5(connect_data, addr, addrlen);
1646 break;
1647
1648 case GAIM_PROXY_USE_ENVVAR:
1649 proxy_connect_http(connect_data, addr, addrlen);
1650 break;
1651
1652 default:
1653 break;
1654 }
1655
1656 g_free(addr);
1657 }
1658
1659 static void
1660 connection_host_resolved(GSList *hosts, gpointer data,
1661 const char *error_message)
1662 {
1663 GaimProxyConnectData *connect_data;
1664
1665 connect_data = data;
1666 connect_data->query_data = NULL;
1667
1668 if (error_message != NULL)
1669 {
1670 gaim_proxy_connect_data_disconnect(connect_data, error_message);
1671 return;
1672 }
1673
1674 if (hosts == NULL)
1675 {
1676 gaim_proxy_connect_data_disconnect(connect_data, _("Could not resolve host name"));
1677 return;
1678 }
1679
1680 connect_data->hosts = hosts;
1681
1682 try_connect(connect_data);
1683 }
1684
1685 GaimProxyInfo *
1686 gaim_proxy_get_setup(GaimAccount *account)
1687 {
1688 GaimProxyInfo *gpi = NULL;
1689 const gchar *tmp;
1690
1691 /* This is used as a fallback so we don't overwrite the selected proxy type */
1692 static GaimProxyInfo *tmp_none_proxy_info = NULL;
1693 if (!tmp_none_proxy_info) {
1694 tmp_none_proxy_info = gaim_proxy_info_new();
1695 gaim_proxy_info_set_type(tmp_none_proxy_info, GAIM_PROXY_NONE);
1696 }
1697
1698 if (account && gaim_account_get_proxy_info(account) != NULL) {
1699 gpi = gaim_account_get_proxy_info(account);
1700 if (gaim_proxy_info_get_type(gpi) == GAIM_PROXY_USE_GLOBAL)
1701 gpi = NULL;
1702 }
1703 if (gpi == NULL) {
1704 if (gaim_running_gnome())
1705 gpi = gaim_gnome_proxy_get_info();
1706 else
1707 gpi = gaim_global_proxy_get_info();
1708 }
1709
1710 if (gaim_proxy_info_get_type(gpi) == GAIM_PROXY_USE_ENVVAR) {
1711 #ifdef _WIN32
1712 wgaim_check_for_proxy_changes();
1713 #endif
1714 if ((tmp = g_getenv("HTTP_PROXY")) != NULL ||
1715 (tmp = g_getenv("http_proxy")) != NULL ||
1716 (tmp = g_getenv("HTTPPROXY")) != NULL) {
1717 char *proxyhost, *proxyuser, *proxypasswd;
1718 int proxyport;
1719
1720 /* http_proxy-format:
1721 * export http_proxy="http://user:passwd@your.proxy.server:port/"
1722 */
1723 if(gaim_url_parse(tmp, &proxyhost, &proxyport, NULL, &proxyuser, &proxypasswd)) {
1724 gaim_proxy_info_set_host(gpi, proxyhost);
1725 g_free(proxyhost);
1726
1727 gaim_proxy_info_set_username(gpi, proxyuser);
1728 g_free(proxyuser);
1729
1730 gaim_proxy_info_set_password(gpi, proxypasswd);
1731 g_free(proxypasswd);
1732
1733 /* only for backward compatibility */
1734 if (proxyport == 80 &&
1735 ((tmp = g_getenv("HTTP_PROXY_PORT")) != NULL ||
1736 (tmp = g_getenv("http_proxy_port")) != NULL ||
1737 (tmp = g_getenv("HTTPPROXYPORT")) != NULL))
1738 proxyport = atoi(tmp);
1739
1740 gaim_proxy_info_set_port(gpi, proxyport);
1741
1742 /* XXX: Do we want to skip this step if user/password were part of url? */
1743 if ((tmp = g_getenv("HTTP_PROXY_USER")) != NULL ||
1744 (tmp = g_getenv("http_proxy_user")) != NULL ||
1745 (tmp = g_getenv("HTTPPROXYUSER")) != NULL)
1746 gaim_proxy_info_set_username(gpi, tmp);
1747
1748 if ((tmp = g_getenv("HTTP_PROXY_PASS")) != NULL ||
1749 (tmp = g_getenv("http_proxy_pass")) != NULL ||
1750 (tmp = g_getenv("HTTPPROXYPASS")) != NULL)
1751 gaim_proxy_info_set_password(gpi, tmp);
1752
1753 }
1754 } else {
1755 /* no proxy environment variable found, don't use a proxy */
1756 gaim_debug_info("proxy", "No environment settings found, not using a proxy\n");
1757 gpi = tmp_none_proxy_info;
1758 }
1759
1760 }
1761
1762 return gpi;
1763 }
1764
1765 GaimProxyConnectData *
1766 gaim_proxy_connect(void *handle, GaimAccount *account,
1767 const char *host, int port,
1768 GaimProxyConnectFunction connect_cb, gpointer data)
1769 {
1770 const char *connecthost = host;
1771 int connectport = port;
1772 GaimProxyConnectData *connect_data;
1773
1774 g_return_val_if_fail(host != NULL, NULL);
1775 g_return_val_if_fail(port > 0, NULL);
1776 g_return_val_if_fail(connect_cb != NULL, NULL);
1777
1778 connect_data = g_new0(GaimProxyConnectData, 1);
1779 connect_data->fd = -1;
1780 connect_data->handle = handle;
1781 connect_data->connect_cb = connect_cb;
1782 connect_data->data = data;
1783 connect_data->host = g_strdup(host);
1784 connect_data->port = port;
1785 connect_data->gpi = gaim_proxy_get_setup(account);
1786
1787 if ((gaim_proxy_info_get_type(connect_data->gpi) != GAIM_PROXY_NONE) &&
1788 (gaim_proxy_info_get_host(connect_data->gpi) == NULL ||
1789 gaim_proxy_info_get_port(connect_data->gpi) <= 0)) {
1790
1791 gaim_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid."));
1792 gaim_proxy_connect_data_destroy(connect_data);
1793 return NULL;
1794 }
1795
1796 switch (gaim_proxy_info_get_type(connect_data->gpi))
1797 {
1798 case GAIM_PROXY_NONE:
1799 break;
1800
1801 case GAIM_PROXY_HTTP:
1802 case GAIM_PROXY_SOCKS4:
1803 case GAIM_PROXY_SOCKS5:
1804 case GAIM_PROXY_USE_ENVVAR:
1805 connecthost = gaim_proxy_info_get_host(connect_data->gpi);
1806 connectport = gaim_proxy_info_get_port(connect_data->gpi);
1807 break;
1808
1809 default:
1810 gaim_proxy_connect_data_destroy(connect_data);
1811 return NULL;
1812 }
1813
1814 connect_data->query_data = gaim_dnsquery_a(connecthost,
1815 connectport, connection_host_resolved, connect_data);
1816 if (connect_data->query_data == NULL)
1817 {
1818 gaim_proxy_connect_data_destroy(connect_data);
1819 return NULL;
1820 }
1821
1822 handles = g_slist_prepend(handles, connect_data);
1823
1824 return connect_data;
1825 }
1826
1827 /*
1828 * Combine some of this code with gaim_proxy_connect()
1829 */
1830 GaimProxyConnectData *
1831 gaim_proxy_connect_socks5(void *handle, GaimProxyInfo *gpi,
1832 const char *host, int port,
1833 GaimProxyConnectFunction connect_cb,
1834 gpointer data)
1835 {
1836 GaimProxyConnectData *connect_data;
1837
1838 g_return_val_if_fail(host != NULL, NULL);
1839 g_return_val_if_fail(port >= 0, NULL);
1840 g_return_val_if_fail(connect_cb != NULL, NULL);
1841
1842 connect_data = g_new0(GaimProxyConnectData, 1);
1843 connect_data->fd = -1;
1844 connect_data->handle = handle;
1845 connect_data->connect_cb = connect_cb;
1846 connect_data->data = data;
1847 connect_data->host = g_strdup(host);
1848 connect_data->port = port;
1849 connect_data->gpi = gpi;
1850
1851 connect_data->query_data =
1852 gaim_dnsquery_a(gaim_proxy_info_get_host(gpi),
1853 gaim_proxy_info_get_port(gpi),
1854 connection_host_resolved, connect_data);
1855 if (connect_data->query_data == NULL)
1856 {
1857 gaim_proxy_connect_data_destroy(connect_data);
1858 return NULL;
1859 }
1860
1861 handles = g_slist_prepend(handles, connect_data);
1862
1863 return connect_data;
1864 }
1865
1866 void
1867 gaim_proxy_connect_cancel(GaimProxyConnectData *connect_data)
1868 {
1869 gaim_proxy_connect_data_disconnect(connect_data, NULL);
1870 gaim_proxy_connect_data_destroy(connect_data);
1871 }
1872
1873 void
1874 gaim_proxy_connect_cancel_with_handle(void *handle)
1875 {
1876 GSList *l, *l_next;
1877
1878 for (l = handles; l != NULL; l = l_next) {
1879 GaimProxyConnectData *connect_data = l->data;
1880
1881 l_next = l->next;
1882
1883 if (connect_data->handle == handle)
1884 gaim_proxy_connect_cancel(connect_data);
1885 }
1886 }
1887
1888 static void
1889 proxy_pref_cb(const char *name, GaimPrefType type,
1890 gconstpointer value, gpointer data)
1891 {
1892 GaimProxyInfo *info = gaim_global_proxy_get_info();
1893
1894 if (!strcmp(name, "/core/proxy/type")) {
1895 int proxytype;
1896 const char *type = value;
1897
1898 if (!strcmp(type, "none"))
1899 proxytype = GAIM_PROXY_NONE;
1900 else if (!strcmp(type, "http"))
1901 proxytype = GAIM_PROXY_HTTP;
1902 else if (!strcmp(type, "socks4"))
1903 proxytype = GAIM_PROXY_SOCKS4;
1904 else if (!strcmp(type, "socks5"))
1905 proxytype = GAIM_PROXY_SOCKS5;
1906 else if (!strcmp(type, "envvar"))
1907 proxytype = GAIM_PROXY_USE_ENVVAR;
1908 else
1909 proxytype = -1;
1910
1911 gaim_proxy_info_set_type(info, proxytype);
1912 } else if (!strcmp(name, "/core/proxy/host"))
1913 gaim_proxy_info_set_host(info, value);
1914 else if (!strcmp(name, "/core/proxy/port"))
1915 gaim_proxy_info_set_port(info, GPOINTER_TO_INT(value));
1916 else if (!strcmp(name, "/core/proxy/username"))
1917 gaim_proxy_info_set_username(info, value);
1918 else if (!strcmp(name, "/core/proxy/password"))
1919 gaim_proxy_info_set_password(info, value);
1920 }
1921
1922 void *
1923 gaim_proxy_get_handle()
1924 {
1925 static int handle;
1926
1927 return &handle;
1928 }
1929
1930 void
1931 gaim_proxy_init(void)
1932 {
1933 void *handle;
1934
1935 /* Initialize a default proxy info struct. */
1936 global_proxy_info = gaim_proxy_info_new();
1937
1938 /* Proxy */
1939 gaim_prefs_add_none("/core/proxy");
1940 gaim_prefs_add_string("/core/proxy/type", "none");
1941 gaim_prefs_add_string("/core/proxy/host", "");
1942 gaim_prefs_add_int("/core/proxy/port", 0);
1943 gaim_prefs_add_string("/core/proxy/username", "");
1944 gaim_prefs_add_string("/core/proxy/password", "");
1945
1946 /* Setup callbacks for the preferences. */
1947 handle = gaim_proxy_get_handle();
1948 gaim_prefs_connect_callback(handle, "/core/proxy/type", proxy_pref_cb,
1949 NULL);
1950 gaim_prefs_connect_callback(handle, "/core/proxy/host", proxy_pref_cb,
1951 NULL);
1952 gaim_prefs_connect_callback(handle, "/core/proxy/port", proxy_pref_cb,
1953 NULL);
1954 gaim_prefs_connect_callback(handle, "/core/proxy/username",
1955 proxy_pref_cb, NULL);
1956 gaim_prefs_connect_callback(handle, "/core/proxy/password",
1957 proxy_pref_cb, NULL);
1958 }
1959
1960 void
1961 gaim_proxy_uninit(void)
1962 {
1963 while (handles != NULL)
1964 {
1965 gaim_proxy_connect_data_disconnect(handles->data, NULL);
1966 gaim_proxy_connect_data_destroy(handles->data);
1967 }
1968 }