comparison libgaim/proxy.c @ 14192:60b1bc8dbf37

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