source: lib/proxy.c @ 80c2f3c

Last change on this file since 80c2f3c 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
RevLine 
[b7d3cc34]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
[6f10697]19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
[b7d3cc34]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"
[7ed3199]37#include "base64.h"
[b7d3cc34]38
39char proxyhost[128] = "";
40int proxyport = 0;
41int proxytype = PROXY_NONE;
42char proxyuser[128] = "";
43char proxypass[128] = "";
44
[762d96f]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
[389ce9f]48#endif
49#ifndef AI_ADDRCONFIG
50#define AI_ADDRCONFIG 0
[762d96f]51#endif
52
[4e365ce]53static GHashTable *phb_hash = NULL;
54
[b7d3cc34]55struct PHB {
[ba9edaa]56        b_event_handler func, proxy_func;
[b7d3cc34]57        gpointer data, proxy_data;
58        char *host;
59        int port;
60        int fd;
61        gint inpa;
[762d96f]62        struct addrinfo *gai, *gai_cur;
[b7d3cc34]63};
64
[5756890]65typedef int (*proxy_connect_func)(const char *host, unsigned short port_, struct PHB *phb);
66
[762d96f]67static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb);
68
[f710673]69static gboolean phb_free(struct PHB *phb, gboolean success)
[71abe93]70{
[4e365ce]71        g_hash_table_remove(phb_hash, &phb->fd);
72
[f710673]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        }
[71abe93]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)
[b7d3cc34]90{
91        struct PHB *phb = data;
[25c4c78]92        socklen_t len;
[b7d3cc34]93        int error = ETIMEDOUT;
[5ebff60]94
[b7d3cc34]95        len = sizeof(error);
[5ebff60]96
[762d96f]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);
[a010498]105                                closesocket(new_fd);
[4e365ce]106                                phb->fd = source;
[71abe93]107                                phb->inpa = b_input_add(source, B_EV_IO_WRITE, proxy_connected, phb);
[762d96f]108                                return FALSE;
109                        }
110                }
[b7d3cc34]111                closesocket(source);
[71abe93]112                source = -1;
113                /* socket is dead, but continue to clean up */
114        } else {
115                sock_make_blocking(source);
[b7d3cc34]116        }
[71abe93]117
[762d96f]118        freeaddrinfo(phb->gai);
[f710673]119        phb->gai = NULL;
120
[ba9edaa]121        b_event_remove(phb->inpa);
[20c9c21]122        phb->inpa = 0;
[f710673]123
[5ebff60]124        if (phb->proxy_func) {
[e046390]125                phb->proxy_func(phb->proxy_data, source, B_EV_IO_READ);
[5ebff60]126        } else {
[e046390]127                phb->func(phb->data, source, B_EV_IO_READ);
[f710673]128                phb_free(phb, TRUE);
[b7d3cc34]129        }
[5ebff60]130
[ba9edaa]131        return FALSE;
[b7d3cc34]132}
133
[289bd2d]134static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb)
[b7d3cc34]135{
[aefaac3a]136        struct sockaddr_in me;
[b7d3cc34]137        int fd = -1;
[5ebff60]138
139        if (phb->gai_cur == NULL) {
[762d96f]140                int ret;
141                char port[6];
142                struct addrinfo hints;
[5ebff60]143
[762d96f]144                g_snprintf(port, sizeof(port), "%d", port_);
[5ebff60]145
[762d96f]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;
[5ebff60]150
151                if (!(ret = getaddrinfo(host, port, &hints, &phb->gai))) {
[762d96f]152                        phb->gai_cur = phb->gai;
[5ebff60]153                } else {
[762d96f]154                        event_debug("gai(): %s\n", gai_strerror(ret));
[5ebff60]155                }
[762d96f]156        }
[5ebff60]157
158        for (; phb->gai_cur; phb->gai_cur = phb->gai_cur->ai_next) {
[762d96f]159                if ((fd = socket(phb->gai_cur->ai_family, phb->gai_cur->ai_socktype, phb->gai_cur->ai_protocol)) < 0) {
[5ebff60]160                        event_debug("socket failed: %d\n", errno);
[762d96f]161                        continue;
162                }
[289bd2d]163
[762d96f]164                sock_make_nonblocking(fd);
[289bd2d]165
[5ebff60]166                if (global.conf->iface_out) {
[762d96f]167                        me.sin_family = AF_INET;
168                        me.sin_port = 0;
[5ebff60]169                        me.sin_addr.s_addr = inet_addr(global.conf->iface_out);
170
171                        if (bind(fd, (struct sockaddr *) &me, sizeof(me)) != 0) {
[762d96f]172                                event_debug("bind( %d, \"%s\" ) failure\n", fd, global.conf->iface_out);
[5ebff60]173                        }
[762d96f]174                }
[289bd2d]175
[ca8037e]176                event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port_, fd);
[5ebff60]177
[762d96f]178                if (connect(fd, phb->gai_cur->ai_addr, phb->gai_cur->ai_addrlen) < 0 && !sockerr_again()) {
[5ebff60]179                        event_debug("connect failed: %s\n", strerror(errno));
[762d96f]180                        closesocket(fd);
181                        fd = -1;
182                        continue;
183                } else {
[71abe93]184                        phb->inpa = b_input_add(fd, B_EV_IO_WRITE, proxy_connected, phb);
[762d96f]185                        phb->fd = fd;
[5ebff60]186
[762d96f]187                        break;
[289bd2d]188                }
[aefaac3a]189        }
[5ebff60]190
191        if (fd < 0 && host) {
[f710673]192                phb_free(phb, TRUE);
[5ebff60]193        }
[289bd2d]194
195        return fd;
[b7d3cc34]196}
197
198
199/* Connecting to HTTP proxies */
200
[840394e]201#define HTTP_GOODSTRING "HTTP/1.0 200"
202#define HTTP_GOODSTRING2 "HTTP/1.1 200"
[b7d3cc34]203
[ba9edaa]204static gboolean http_canread(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]205{
206        int nlc = 0;
207        int pos = 0;
208        struct PHB *phb = data;
209        char inputline[8192];
210
[ba9edaa]211        b_event_remove(phb->inpa);
[b7d3cc34]212
[5ebff60]213        while ((pos < sizeof(inputline) - 1) && (nlc != 2) && (read(source, &inputline[pos++], 1) == 1)) {
214                if (inputline[pos - 1] == '\n') {
[b7d3cc34]215                        nlc++;
[5ebff60]216                } else if (inputline[pos - 1] != '\r') {
[b7d3cc34]217                        nlc = 0;
[5ebff60]218                }
[b7d3cc34]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)) {
[e046390]224                phb->func(phb->data, source, B_EV_IO_READ);
[f710673]225                return phb_free(phb, TRUE);
[b7d3cc34]226        }
227
[f710673]228        return phb_free(phb, FALSE);
[b7d3cc34]229}
230
[ba9edaa]231static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]232{
233        char cmd[384];
234        struct PHB *phb = data;
[25c4c78]235        socklen_t len;
[b7d3cc34]236        int error = ETIMEDOUT;
[5ebff60]237
238        if (phb->inpa > 0) {
[ba9edaa]239                b_event_remove(phb->inpa);
[5ebff60]240        }
[b7d3cc34]241        len = sizeof(error);
242        if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
[f710673]243                return phb_free(phb, FALSE);
[b7d3cc34]244        }
[701acdd4]245        sock_make_blocking(source);
[b7d3cc34]246
247        g_snprintf(cmd, sizeof(cmd), "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n", phb->host, phb->port,
[5ebff60]248                   phb->host, phb->port);
[b7d3cc34]249        if (send(source, cmd, strlen(cmd), 0) < 0) {
[f710673]250                return phb_free(phb, FALSE);
[b7d3cc34]251        }
252
[f618a4a]253        if (strlen(proxyuser) > 0) {
[b7d3cc34]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) {
[f710673]261                        return phb_free(phb, FALSE);
[b7d3cc34]262                }
263        }
264
265        g_snprintf(cmd, sizeof(cmd), "\r\n");
266        if (send(source, cmd, strlen(cmd), 0) < 0) {
[f710673]267                return phb_free(phb, FALSE);
[b7d3cc34]268        }
269
[e046390]270        phb->inpa = b_input_add(source, B_EV_IO_READ, http_canread, phb);
[5ebff60]271
[ba9edaa]272        return FALSE;
[b7d3cc34]273}
274
[c3ffa45]275static int proxy_connect_http(const char *host, unsigned short port, struct PHB *phb)
[b7d3cc34]276{
277        phb->host = g_strdup(host);
278        phb->port = port;
279        phb->proxy_func = http_canwrite;
280        phb->proxy_data = phb;
[5ebff60]281
282        return(proxy_connect_none(proxyhost, proxyport, phb));
[b7d3cc34]283}
284
285
286/* Connecting to SOCKS4 proxies */
287
[ba9edaa]288static gboolean s4_canread(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]289{
290        unsigned char packet[12];
291        struct PHB *phb = data;
292
[ba9edaa]293        b_event_remove(phb->inpa);
[b7d3cc34]294
295        memset(packet, 0, sizeof(packet));
296        if (read(source, packet, 9) >= 4 && packet[1] == 90) {
[e046390]297                phb->func(phb->data, source, B_EV_IO_READ);
[f710673]298                return phb_free(phb, TRUE);
[b7d3cc34]299        }
300
[f710673]301        return phb_free(phb, FALSE);
[b7d3cc34]302}
303
[ba9edaa]304static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]305{
306        unsigned char packet[12];
307        struct hostent *hp;
308        struct PHB *phb = data;
[25c4c78]309        socklen_t len;
[b7d3cc34]310        int error = ETIMEDOUT;
[12f041d]311        gboolean is_socks4a = (proxytype == PROXY_SOCKS4A);
[5ebff60]312
313        if (phb->inpa > 0) {
[ba9edaa]314                b_event_remove(phb->inpa);
[5ebff60]315        }
[b7d3cc34]316        len = sizeof(error);
317        if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
[f710673]318                return phb_free(phb, FALSE);
[b7d3cc34]319        }
[701acdd4]320        sock_make_blocking(source);
[b7d3cc34]321
[12f041d]322        if (!is_socks4a && !(hp = gethostbyname(phb->host))) {
[f710673]323                return phb_free(phb, FALSE);
[b7d3cc34]324        }
325
326        packet[0] = 4;
327        packet[1] = 1;
328        packet[2] = phb->port >> 8;
329        packet[3] = phb->port & 0xff;
[12f041d]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        }
[b7d3cc34]341        packet[8] = 0;
342        if (write(source, packet, 9) != 9) {
[f710673]343                return phb_free(phb, FALSE);
[b7d3cc34]344        }
345
[12f041d]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) {
[f710673]350                        return phb_free(phb, FALSE);
[12f041d]351                }
352        }
353
[e046390]354        phb->inpa = b_input_add(source, B_EV_IO_READ, s4_canread, phb);
[5ebff60]355
[ba9edaa]356        return FALSE;
[b7d3cc34]357}
358
[c3ffa45]359static int proxy_connect_socks4(const char *host, unsigned short port, struct PHB *phb)
[b7d3cc34]360{
361        phb->host = g_strdup(host);
362        phb->port = port;
363        phb->proxy_func = s4_canwrite;
364        phb->proxy_data = phb;
[5ebff60]365
366        return(proxy_connect_none(proxyhost, proxyport, phb));
[b7d3cc34]367}
368
369
370/* Connecting to SOCKS5 proxies */
371
[ba9edaa]372static gboolean s5_canread_again(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]373{
374        unsigned char buf[512];
375        struct PHB *phb = data;
376
[ba9edaa]377        b_event_remove(phb->inpa);
[b7d3cc34]378
379        if (read(source, buf, 10) < 10) {
[f710673]380                return phb_free(phb, FALSE);
[b7d3cc34]381        }
382        if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
[f710673]383                return phb_free(phb, FALSE);
[b7d3cc34]384        }
385
[e046390]386        phb->func(phb->data, source, B_EV_IO_READ);
[f710673]387        return phb_free(phb, TRUE);
[b7d3cc34]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);
[5ebff60]395
[b7d3cc34]396        buf[0] = 0x05;
[5ebff60]397        buf[1] = 0x01;          /* CONNECT */
398        buf[2] = 0x00;          /* reserved */
399        buf[3] = 0x03;          /* address type -- host name */
[b7d3cc34]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)) {
[f710673]406                phb_free(phb, FALSE);
[b7d3cc34]407                return;
408        }
409
[e046390]410        phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread_again, phb);
[b7d3cc34]411}
412
[ba9edaa]413static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]414{
415        unsigned char buf[512];
416        struct PHB *phb = data;
417
[ba9edaa]418        b_event_remove(phb->inpa);
[b7d3cc34]419
420        if (read(source, buf, 2) < 2) {
[f710673]421                return phb_free(phb, FALSE);
[b7d3cc34]422        }
423
424        if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
[f710673]425                return phb_free(phb, FALSE);
[b7d3cc34]426        }
427
428        s5_sendconnect(phb, source);
[5ebff60]429
[ba9edaa]430        return FALSE;
[b7d3cc34]431}
432
[ba9edaa]433static gboolean s5_canread(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]434{
435        unsigned char buf[512];
436        struct PHB *phb = data;
437
[ba9edaa]438        b_event_remove(phb->inpa);
[b7d3cc34]439
440        if (read(source, buf, 2) < 2) {
[f710673]441                return phb_free(phb, FALSE);
[b7d3cc34]442        }
443
444        if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
[f710673]445                return phb_free(phb, FALSE);
[b7d3cc34]446        }
447
448        if (buf[1] == 0x02) {
449                unsigned int i = strlen(proxyuser), j = strlen(proxypass);
[5ebff60]450                buf[0] = 0x01;  /* version 1 */
[b7d3cc34]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) {
[f710673]456                        return phb_free(phb, FALSE);
[b7d3cc34]457                }
458
[e046390]459                phb->inpa = b_input_add(source, B_EV_IO_READ, s5_readauth, phb);
[b7d3cc34]460        } else {
461                s5_sendconnect(phb, source);
462        }
[5ebff60]463
[ba9edaa]464        return FALSE;
[b7d3cc34]465}
466
[ba9edaa]467static gboolean s5_canwrite(gpointer data, gint source, b_input_condition cond)
[b7d3cc34]468{
469        unsigned char buf[512];
470        int i;
471        struct PHB *phb = data;
[25c4c78]472        socklen_t len;
[b7d3cc34]473        int error = ETIMEDOUT;
[5ebff60]474
475        if (phb->inpa > 0) {
[ba9edaa]476                b_event_remove(phb->inpa);
[5ebff60]477        }
[b7d3cc34]478        len = sizeof(error);
479        if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
[f710673]480                return phb_free(phb, FALSE);
[b7d3cc34]481        }
[701acdd4]482        sock_make_blocking(source);
[b7d3cc34]483
484        i = 0;
[5ebff60]485        buf[0] = 0x05;          /* SOCKS version 5 */
[b7d3cc34]486        if (proxyuser[0]) {
[5ebff60]487                buf[1] = 0x02;  /* two methods */
488                buf[2] = 0x00;  /* no authentication */
489                buf[3] = 0x02;  /* username/password authentication */
[b7d3cc34]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) {
[f710673]498                return phb_free(phb, FALSE);
[b7d3cc34]499        }
500
[e046390]501        phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread, phb);
[5ebff60]502
[ba9edaa]503        return FALSE;
[b7d3cc34]504}
505
[c3ffa45]506static int proxy_connect_socks5(const char *host, unsigned short port, struct PHB *phb)
[b7d3cc34]507{
508        phb->host = g_strdup(host);
509        phb->port = port;
510        phb->proxy_func = s5_canwrite;
511        phb->proxy_data = phb;
[5ebff60]512
513        return(proxy_connect_none(proxyhost, proxyport, phb));
[b7d3cc34]514}
515
[5756890]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};
[b7d3cc34]523
524/* Export functions */
525
[ba9edaa]526int proxy_connect(const char *host, int port, b_event_handler func, gpointer data)
[b7d3cc34]527{
528        struct PHB *phb;
[5756890]529        proxy_connect_func fun;
[4e365ce]530        int fd;
531
532        if (!phb_hash) {
533                phb_hash = g_hash_table_new(g_int_hash, g_int_equal);
534        }
[5ebff60]535
[58a1449]536        if (!host || port <= 0 || !func || strlen(host) > 128) {
[b7d3cc34]537                return -1;
538        }
[5ebff60]539
[b7d3cc34]540        phb = g_new0(struct PHB, 1);
541        phb->func = func;
542        phb->data = data;
[5ebff60]543
[5756890]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;
[5ebff60]548        }
549
[4e365ce]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);
[b7d3cc34]579}
Note: See TracBrowser for help on using the repository browser.