source: lib/proxy.c @ e61f9d1

Last change on this file since e61f9d1 was 4e365ce, checked in by dequis <dx@…>, at 2015-10-26T03:42:15Z

Add proxy_disconnect() to interrupt possibly pending connections

Fixes trac ticket 1198, https://bugs.bitlbee.org/bitlbee/ticket/1198

This function can be used as a safe drop-in replacement to closesocket()

If a proxy connection is pending (connected callback still not called),
it looks up the PHB in a hash table indexed by fd. If it is there, it
closes, frees the phb and avoids further calls to the callback.
If it is not in there, it just does closesocket()

  • Property mode set to 100644
File size: 13.3 KB
Line 
1/*
2 * gaim
3 *
4 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
5 * Copyright (C) 2002-2004, Wilmer van der Gaast, Jelmer Vernooij
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20 *
21 */
22
23#define BITLBEE_CORE
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <sys/types.h>
28#include <sys/socket.h>
29#include <netdb.h>
30#include <netinet/in.h>
31#include <arpa/inet.h>
32#include <unistd.h>
33#include <fcntl.h>
34#include <errno.h>
35#include "nogaim.h"
36#include "proxy.h"
37#include "base64.h"
38
39char proxyhost[128] = "";
40int proxyport = 0;
41int proxytype = PROXY_NONE;
42char proxyuser[128] = "";
43char proxypass[128] = "";
44
45/* Some systems don't know this one. It's not essential, so set it to 0 then. */
46#ifndef AI_NUMERICSERV
47#define AI_NUMERICSERV 0
48#endif
49#ifndef AI_ADDRCONFIG
50#define AI_ADDRCONFIG 0
51#endif
52
53static GHashTable *phb_hash = NULL;
54
55struct PHB {
56        b_event_handler func, proxy_func;
57        gpointer data, proxy_data;
58        char *host;
59        int port;
60        int fd;
61        gint inpa;
62        struct addrinfo *gai, *gai_cur;
63};
64
65typedef int (*proxy_connect_func)(const char *host, unsigned short port_, struct PHB *phb);
66
67static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb);
68
69static gboolean phb_free(struct PHB *phb, gboolean success)
70{
71        g_hash_table_remove(phb_hash, &phb->fd);
72
73        if (!success) {
74                if (phb->fd > 0) {
75                        closesocket(phb->fd);
76                }
77                if (phb->func) {
78                        phb->func(phb->data, -1, B_EV_IO_READ);
79                }
80        }
81        if (phb->gai) {
82                freeaddrinfo(phb->gai);
83        }
84        g_free(phb->host);
85        g_free(phb);
86        return FALSE;
87}
88
89static gboolean proxy_connected(gpointer data, gint source, b_input_condition cond)
90{
91        struct PHB *phb = data;
92        socklen_t len;
93        int error = ETIMEDOUT;
94
95        len = sizeof(error);
96
97        if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0 || error) {
98                if ((phb->gai_cur = phb->gai_cur->ai_next)) {
99                        int new_fd;
100                        b_event_remove(phb->inpa);
101                        if ((new_fd = proxy_connect_none(NULL, 0, phb))) {
102                                b_event_remove(phb->inpa);
103                                closesocket(source);
104                                dup2(new_fd, source);
105                                closesocket(new_fd);
106                                phb->fd = source;
107                                phb->inpa = b_input_add(source, B_EV_IO_WRITE, proxy_connected, phb);
108                                return FALSE;
109                        }
110                }
111                closesocket(source);
112                source = -1;
113                /* socket is dead, but continue to clean up */
114        } else {
115                sock_make_blocking(source);
116        }
117
118        freeaddrinfo(phb->gai);
119        phb->gai = NULL;
120
121        b_event_remove(phb->inpa);
122        phb->inpa = 0;
123
124        if (phb->proxy_func) {
125                phb->proxy_func(phb->proxy_data, source, B_EV_IO_READ);
126        } else {
127                phb->func(phb->data, source, B_EV_IO_READ);
128                phb_free(phb, TRUE);
129        }
130
131        return FALSE;
132}
133
134static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb)
135{
136        struct sockaddr_in me;
137        int fd = -1;
138
139        if (phb->gai_cur == NULL) {
140                int ret;
141                char port[6];
142                struct addrinfo hints;
143
144                g_snprintf(port, sizeof(port), "%d", port_);
145
146                memset(&hints, 0, sizeof(struct addrinfo));
147                hints.ai_family = AF_UNSPEC;
148                hints.ai_socktype = SOCK_STREAM;
149                hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
150
151                if (!(ret = getaddrinfo(host, port, &hints, &phb->gai))) {
152                        phb->gai_cur = phb->gai;
153                } else {
154                        event_debug("gai(): %s\n", gai_strerror(ret));
155                }
156        }
157
158        for (; phb->gai_cur; phb->gai_cur = phb->gai_cur->ai_next) {
159                if ((fd = socket(phb->gai_cur->ai_family, phb->gai_cur->ai_socktype, phb->gai_cur->ai_protocol)) < 0) {
160                        event_debug("socket failed: %d\n", errno);
161                        continue;
162                }
163
164                sock_make_nonblocking(fd);
165
166                if (global.conf->iface_out) {
167                        me.sin_family = AF_INET;
168                        me.sin_port = 0;
169                        me.sin_addr.s_addr = inet_addr(global.conf->iface_out);
170
171                        if (bind(fd, (struct sockaddr *) &me, sizeof(me)) != 0) {
172                                event_debug("bind( %d, \"%s\" ) failure\n", fd, global.conf->iface_out);
173                        }
174                }
175
176                event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port_, fd);
177
178                if (connect(fd, phb->gai_cur->ai_addr, phb->gai_cur->ai_addrlen) < 0 && !sockerr_again()) {
179                        event_debug("connect failed: %s\n", strerror(errno));
180                        closesocket(fd);
181                        fd = -1;
182                        continue;
183                } else {
184                        phb->inpa = b_input_add(fd, B_EV_IO_WRITE, proxy_connected, phb);
185                        phb->fd = fd;
186
187                        break;
188                }
189        }
190
191        if (fd < 0 && host) {
192                phb_free(phb, TRUE);
193        }
194
195        return fd;
196}
197
198
199/* Connecting to HTTP proxies */
200
201#define HTTP_GOODSTRING "HTTP/1.0 200"
202#define HTTP_GOODSTRING2 "HTTP/1.1 200"
203
204static gboolean http_canread(gpointer data, gint source, b_input_condition cond)
205{
206        int nlc = 0;
207        int pos = 0;
208        struct PHB *phb = data;
209        char inputline[8192];
210
211        b_event_remove(phb->inpa);
212
213        while ((pos < sizeof(inputline) - 1) && (nlc != 2) && (read(source, &inputline[pos++], 1) == 1)) {
214                if (inputline[pos - 1] == '\n') {
215                        nlc++;
216                } else if (inputline[pos - 1] != '\r') {
217                        nlc = 0;
218                }
219        }
220        inputline[pos] = '\0';
221
222        if ((memcmp(HTTP_GOODSTRING, inputline, strlen(HTTP_GOODSTRING)) == 0) ||
223            (memcmp(HTTP_GOODSTRING2, inputline, strlen(HTTP_GOODSTRING2)) == 0)) {
224                phb->func(phb->data, source, B_EV_IO_READ);
225                return phb_free(phb, TRUE);
226        }
227
228        return phb_free(phb, FALSE);
229}
230
231static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond)
232{
233        char cmd[384];
234        struct PHB *phb = data;
235        socklen_t len;
236        int error = ETIMEDOUT;
237
238        if (phb->inpa > 0) {
239                b_event_remove(phb->inpa);
240        }
241        len = sizeof(error);
242        if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
243                return phb_free(phb, FALSE);
244        }
245        sock_make_blocking(source);
246
247        g_snprintf(cmd, sizeof(cmd), "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n", phb->host, phb->port,
248                   phb->host, phb->port);
249        if (send(source, cmd, strlen(cmd), 0) < 0) {
250                return phb_free(phb, FALSE);
251        }
252
253        if (strlen(proxyuser) > 0) {
254                char *t1, *t2;
255                t1 = g_strdup_printf("%s:%s", proxyuser, proxypass);
256                t2 = tobase64(t1);
257                g_free(t1);
258                g_snprintf(cmd, sizeof(cmd), "Proxy-Authorization: Basic %s\r\n", t2);
259                g_free(t2);
260                if (send(source, cmd, strlen(cmd), 0) < 0) {
261                        return phb_free(phb, FALSE);
262                }
263        }
264
265        g_snprintf(cmd, sizeof(cmd), "\r\n");
266        if (send(source, cmd, strlen(cmd), 0) < 0) {
267                return phb_free(phb, FALSE);
268        }
269
270        phb->inpa = b_input_add(source, B_EV_IO_READ, http_canread, phb);
271
272        return FALSE;
273}
274
275static int proxy_connect_http(const char *host, unsigned short port, struct PHB *phb)
276{
277        phb->host = g_strdup(host);
278        phb->port = port;
279        phb->proxy_func = http_canwrite;
280        phb->proxy_data = phb;
281
282        return(proxy_connect_none(proxyhost, proxyport, phb));
283}
284
285
286/* Connecting to SOCKS4 proxies */
287
288static gboolean s4_canread(gpointer data, gint source, b_input_condition cond)
289{
290        unsigned char packet[12];
291        struct PHB *phb = data;
292
293        b_event_remove(phb->inpa);
294
295        memset(packet, 0, sizeof(packet));
296        if (read(source, packet, 9) >= 4 && packet[1] == 90) {
297                phb->func(phb->data, source, B_EV_IO_READ);
298                return phb_free(phb, TRUE);
299        }
300
301        return phb_free(phb, FALSE);
302}
303
304static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond)
305{
306        unsigned char packet[12];
307        struct hostent *hp;
308        struct PHB *phb = data;
309        socklen_t len;
310        int error = ETIMEDOUT;
311        gboolean is_socks4a = (proxytype == PROXY_SOCKS4A);
312
313        if (phb->inpa > 0) {
314                b_event_remove(phb->inpa);
315        }
316        len = sizeof(error);
317        if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
318                return phb_free(phb, FALSE);
319        }
320        sock_make_blocking(source);
321
322        if (!is_socks4a && !(hp = gethostbyname(phb->host))) {
323                return phb_free(phb, FALSE);
324        }
325
326        packet[0] = 4;
327        packet[1] = 1;
328        packet[2] = phb->port >> 8;
329        packet[3] = phb->port & 0xff;
330        if (is_socks4a) {
331                packet[4] = 0;
332                packet[5] = 0;
333                packet[6] = 0;
334                packet[7] = 1;
335        } else {
336                packet[4] = (unsigned char) (hp->h_addr_list[0])[0];
337                packet[5] = (unsigned char) (hp->h_addr_list[0])[1];
338                packet[6] = (unsigned char) (hp->h_addr_list[0])[2];
339                packet[7] = (unsigned char) (hp->h_addr_list[0])[3];
340        }
341        packet[8] = 0;
342        if (write(source, packet, 9) != 9) {
343                return phb_free(phb, FALSE);
344        }
345
346        if (is_socks4a) {
347                size_t host_len = strlen(phb->host) + 1; /* include the \0 */
348
349                if (write(source, phb->host, host_len) != host_len) {
350                        return phb_free(phb, FALSE);
351                }
352        }
353
354        phb->inpa = b_input_add(source, B_EV_IO_READ, s4_canread, phb);
355
356        return FALSE;
357}
358
359static int proxy_connect_socks4(const char *host, unsigned short port, struct PHB *phb)
360{
361        phb->host = g_strdup(host);
362        phb->port = port;
363        phb->proxy_func = s4_canwrite;
364        phb->proxy_data = phb;
365
366        return(proxy_connect_none(proxyhost, proxyport, phb));
367}
368
369
370/* Connecting to SOCKS5 proxies */
371
372static gboolean s5_canread_again(gpointer data, gint source, b_input_condition cond)
373{
374        unsigned char buf[512];
375        struct PHB *phb = data;
376
377        b_event_remove(phb->inpa);
378
379        if (read(source, buf, 10) < 10) {
380                return phb_free(phb, FALSE);
381        }
382        if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
383                return phb_free(phb, FALSE);
384        }
385
386        phb->func(phb->data, source, B_EV_IO_READ);
387        return phb_free(phb, TRUE);
388}
389
390static void s5_sendconnect(gpointer data, gint source)
391{
392        unsigned char buf[512];
393        struct PHB *phb = data;
394        int hlen = strlen(phb->host);
395
396        buf[0] = 0x05;
397        buf[1] = 0x01;          /* CONNECT */
398        buf[2] = 0x00;          /* reserved */
399        buf[3] = 0x03;          /* address type -- host name */
400        buf[4] = hlen;
401        memcpy(buf + 5, phb->host, hlen);
402        buf[5 + strlen(phb->host)] = phb->port >> 8;
403        buf[5 + strlen(phb->host) + 1] = phb->port & 0xff;
404
405        if (write(source, buf, (5 + strlen(phb->host) + 2)) < (5 + strlen(phb->host) + 2)) {
406                phb_free(phb, FALSE);
407                return;
408        }
409
410        phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread_again, phb);
411}
412
413static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond)
414{
415        unsigned char buf[512];
416        struct PHB *phb = data;
417
418        b_event_remove(phb->inpa);
419
420        if (read(source, buf, 2) < 2) {
421                return phb_free(phb, FALSE);
422        }
423
424        if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
425                return phb_free(phb, FALSE);
426        }
427
428        s5_sendconnect(phb, source);
429
430        return FALSE;
431}
432
433static gboolean s5_canread(gpointer data, gint source, b_input_condition cond)
434{
435        unsigned char buf[512];
436        struct PHB *phb = data;
437
438        b_event_remove(phb->inpa);
439
440        if (read(source, buf, 2) < 2) {
441                return phb_free(phb, FALSE);
442        }
443
444        if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
445                return phb_free(phb, FALSE);
446        }
447
448        if (buf[1] == 0x02) {
449                unsigned int i = strlen(proxyuser), j = strlen(proxypass);
450                buf[0] = 0x01;  /* version 1 */
451                buf[1] = i;
452                memcpy(buf + 2, proxyuser, i);
453                buf[2 + i] = j;
454                memcpy(buf + 2 + i + 1, proxypass, j);
455                if (write(source, buf, 3 + i + j) < 3 + i + j) {
456                        return phb_free(phb, FALSE);
457                }
458
459                phb->inpa = b_input_add(source, B_EV_IO_READ, s5_readauth, phb);
460        } else {
461                s5_sendconnect(phb, source);
462        }
463
464        return FALSE;
465}
466
467static gboolean s5_canwrite(gpointer data, gint source, b_input_condition cond)
468{
469        unsigned char buf[512];
470        int i;
471        struct PHB *phb = data;
472        socklen_t len;
473        int error = ETIMEDOUT;
474
475        if (phb->inpa > 0) {
476                b_event_remove(phb->inpa);
477        }
478        len = sizeof(error);
479        if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
480                return phb_free(phb, FALSE);
481        }
482        sock_make_blocking(source);
483
484        i = 0;
485        buf[0] = 0x05;          /* SOCKS version 5 */
486        if (proxyuser[0]) {
487                buf[1] = 0x02;  /* two methods */
488                buf[2] = 0x00;  /* no authentication */
489                buf[3] = 0x02;  /* username/password authentication */
490                i = 4;
491        } else {
492                buf[1] = 0x01;
493                buf[2] = 0x00;
494                i = 3;
495        }
496
497        if (write(source, buf, i) < i) {
498                return phb_free(phb, FALSE);
499        }
500
501        phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread, phb);
502
503        return FALSE;
504}
505
506static int proxy_connect_socks5(const char *host, unsigned short port, struct PHB *phb)
507{
508        phb->host = g_strdup(host);
509        phb->port = port;
510        phb->proxy_func = s5_canwrite;
511        phb->proxy_data = phb;
512
513        return(proxy_connect_none(proxyhost, proxyport, phb));
514}
515
516static const proxy_connect_func proxy_connect_funcs_array[] = {
517        proxy_connect_none,   /* PROXY_NONE */
518        proxy_connect_http,   /* PROXY_HTTP */
519        proxy_connect_socks4, /* PROXY_SOCKS4 */
520        proxy_connect_socks5, /* PROXY_SOCKS5 */
521        proxy_connect_socks4, /* PROXY_SOCKS4A */
522};
523
524/* Export functions */
525
526int proxy_connect(const char *host, int port, b_event_handler func, gpointer data)
527{
528        struct PHB *phb;
529        proxy_connect_func fun;
530        int fd;
531
532        if (!phb_hash) {
533                phb_hash = g_hash_table_new(g_int_hash, g_int_equal);
534        }
535
536        if (!host || port <= 0 || !func || strlen(host) > 128) {
537                return -1;
538        }
539
540        phb = g_new0(struct PHB, 1);
541        phb->func = func;
542        phb->data = data;
543
544        if (proxyhost[0] && proxyport > 0 && proxytype >= 0 && proxytype <= G_N_ELEMENTS(proxy_connect_funcs_array)) {
545                fun = proxy_connect_funcs_array[proxytype];
546        } else {
547                fun = proxy_connect_none;
548        }
549
550        fd = fun(host, port, phb);
551
552        if (fd != -1) {
553                g_hash_table_insert(phb_hash, &phb->fd, phb);
554        }
555
556        return fd;
557}
558
559void proxy_disconnect(int fd)
560{
561        struct PHB *phb = g_hash_table_lookup(phb_hash, &fd);
562
563        if (!phb) {
564                /* not in the early part of the connection - just close the fd */
565                closesocket(fd);
566                return;
567        }
568
569        if (phb->inpa) {
570                b_event_remove(phb->inpa);
571                phb->inpa = 0;
572        }
573
574        /* avoid calling the callback, which might result in double-free */
575        phb->func = NULL;
576
577        /* close and free */
578        phb_free(phb, FALSE);
579}
Note: See TracBrowser for help on using the repository browser.