forked from nmathewson/shim
-
Notifications
You must be signed in to change notification settings - Fork 0
/
conn.c
315 lines (270 loc) · 7.21 KB
/
conn.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#include "netheaders.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/dns.h>
#include "config.h"
#include "conn.h"
#include "util.h"
#include "log.h"
struct conninfo {
enum socks_ver socks;
struct bufferevent *bev;
void *cbarg;
int connecting;
conn_connectcb on_connect;
/* for socks... */
char *host;
int port;
struct sockaddr_storage addr;
int addr_len;
};
static enum socks_ver use_socks = SOCKS_NONE;
static struct sockaddr_storage socks_addr;
static int socks_addr_len = sizeof(socks_addr);
static char *conn_error_string = NULL;
static void
finish_connection(struct conninfo *info, int ok, const char *reason)
{
mem_free(conn_error_string);
conn_error_string = NULL;
if (!ok)
conn_error_string = mem_strdup(reason);
bufferevent_disable(info->bev, EV_READ);
bufferevent_setcb(info->bev, NULL, NULL, NULL, NULL);
info->on_connect(info->bev, ok, info->cbarg);
mem_free(info->host);
mem_free(info);
}
static void
write_socks_request(struct conninfo *info)
{
if (info->socks == SOCKS_4) {
struct sockaddr_in *sin = (struct sockaddr_in*)&info->addr;
if (info->addr.ss_family != AF_INET) {
finish_connection(info, 0,
"SOCKS 4 can't handle ipv6!");
}
/* connection request */
bufferevent_write(info->bev, "\x04\x01", 2);
bufferevent_write(info->bev, &sin->sin_port,
sizeof(sin->sin_port));
bufferevent_write(info->bev, &sin->sin_addr.s_addr,
sizeof(sin->sin_addr.s_addr));
bufferevent_write(info->bev, "xx", 3);
} else {
ev_uint16_t port = htons(info->port);
assert(info->host != NULL);
bufferevent_write(info->bev, "\x04\x01", 2);
bufferevent_write(info->bev, &port, sizeof(port));
bufferevent_write(info->bev, "\x00\x00\x00\xff", 4);
bufferevent_write(info->bev, "xx", 3);
bufferevent_write(info->bev, info->host, strlen(info->host)+1);
}
bufferevent_enable(info->bev, EV_READ);
}
static void
conn_errorcb(struct bufferevent *bev, short what, void *arg)
{
struct conninfo *info = arg;
if (info->connecting) {
info->connecting = 0;
if (what & BEV_EVENT_CONNECTED) {
if (info->socks != SOCKS_NONE)
write_socks_request(info);
else
finish_connection(info, 1, NULL);
} else {
// XXX need better err msg
const char *msg = "Connection failed";
if (info->socks != SOCKS_NONE)
msg = "Connection to proxy server failed";
finish_connection(info, 0, msg);
}
} else {
finish_connection(info, 0, "SOCKS I/O error");
}
}
static const char *
socks4_error_to_string(unsigned char err)
{
switch (err) {
case 0x5b:
return "SOCKS 4: request failed";
case 0x5c:
return "SOCKS 4: client is not running identd";
case 0x5d:
return "SOCKS 4: invalid user ID";
}
return "SOCKS 4: unknown error";
}
static void
conn_readcb(struct bufferevent *bev, void *arg)
{
struct conninfo *info = arg;
struct evbuffer *inbuf = bufferevent_get_input(bev);
unsigned char *data;
unsigned char code;
/* socks4 and socks4a both have an 8 byte response */
if (evbuffer_get_length(inbuf) < 8) {
log_debug("conn: waiting for full socks response");
return;
}
data = evbuffer_pullup(inbuf, 8);
code = data[1];
evbuffer_drain(inbuf, 8);
if (code != 0x5a) {
finish_connection(info, 0, socks4_error_to_string(code));
} else {
finish_connection(info, 1, NULL);
}
}
void socks_resolvecb(int result, struct evutil_addrinfo *ai, void *arg)
{
struct conninfo *info = arg;
if (result) {
char buf[256];
evutil_snprintf(buf, sizeof(buf), "DNS Failure: %s",
evutil_gai_strerror(result));
finish_connection(info, 0, buf);
} else {
log_debug("conn: socks resolve %s",
format_addr(ai->ai_addr));
assert(ai->ai_addrlen <= sizeof(info->addr));
memcpy(&info->addr, ai->ai_addr, ai->ai_addrlen);
info->addr_len = ai->ai_addrlen;
bufferevent_socket_connect(info->bev,
(struct sockaddr*)&socks_addr,
socks_addr_len);
}
if (ai)
evutil_freeaddrinfo(ai);
}
int
conn_connect_bufferevent(struct bufferevent *bev, struct evdns_base *dns,
int family, const char *name, int port,
conn_connectcb conncb, void *arg)
{
struct conninfo *info;
int rv = -1;
info = mem_calloc(1, sizeof(*info));
info->bev = bev;
info->on_connect = conncb;
info->cbarg = arg;
info->connecting = 1;
info->socks = use_socks;
bufferevent_setcb(bev, conn_readcb, NULL, conn_errorcb, info);
if (use_socks != SOCKS_NONE) {
info->host = mem_strdup(name);
info->port = port;
if (use_socks == SOCKS_4a) {
rv = bufferevent_socket_connect(bev,
(struct sockaddr*)&socks_addr,
socks_addr_len);
return rv;
}
#ifndef DISABLE_DIRECT_CONNECTIONS
else {
struct evutil_addrinfo hint;
char portstr[NI_MAXSERV];
evutil_snprintf(portstr, sizeof(portstr), "%d", port);
memset(&hint, 0, sizeof(hint));
hint.ai_family = AF_INET;
hint.ai_protocol = IPPROTO_TCP;
hint.ai_socktype = SOCK_STREAM;
evdns_getaddrinfo(dns, name, portstr, &hint,
socks_resolvecb, info);
return 0;
}
#endif
}
#ifdef DISABLE_DIRECT_CONNECTIONS
{
const char *msg;
msg = "Direct connections disabled, but I have no SOCKS 4a "
"proxy to connect to!";
log_error("conn: %s", msg);
finish_connection(info, 0, msg);
}
#else
rv = bufferevent_socket_connect_hostname(bev, dns, family, name, port);
#endif
return rv;
}
int
conn_set_socks_server(const char *name, int port, enum socks_ver ver)
{
int ret;
int rv = -1;
struct evutil_addrinfo *ai = NULL;
struct evutil_addrinfo hint;
char portstr[NI_MAXSERV];
assert(ver != SOCKS_NONE);
evutil_snprintf(portstr, sizeof(portstr), "%d", port);
memset(&hint, 0, sizeof(hint));
hint.ai_family = AF_UNSPEC;
hint.ai_protocol = IPPROTO_TCP;
hint.ai_socktype = SOCK_STREAM;
hint.ai_flags = EVUTIL_AI_ADDRCONFIG;
ret = evutil_getaddrinfo(name, portstr, &hint, &ai);
if (!ret) {
rv = 0;
memset(&socks_addr, 0, sizeof(socks_addr));
memcpy(&socks_addr, ai->ai_addr, ai->ai_addrlen);
socks_addr_len = ai->ai_addrlen;
use_socks = ver;
log_notice("conn: socks server set to %s",
format_addr((struct sockaddr*)&socks_addr));
} else {
log_error("conn: can't resolve socks server %s: %s",
name, evutil_gai_strerror(ret));
}
if (ai)
evutil_freeaddrinfo(ai);
return rv;
}
const char *
conn_get_connect_error(void)
{
return conn_error_string;
}
#ifdef TEST_CONN
void do_connect(struct bufferevent *bev, int ok, void *arg)
{
if (!ok) {
log_notice("conn: failed: %s", conn_get_connect_error());
} else {
log_notice("conn: conn OK!");
}
bufferevent_free(bev);
}
int main(int argc, char **argv)
{
struct evdns_base *dns;
struct event_base *base;
struct bufferevent *bev;
struct url *socks, *host;
int s4;
base = event_base_new();
dns = evdns_base_new(base, 1);
log_set_file(NULL);
log_set_min_level(LOG_DEBUG);
if (argc >= 3) {
socks = url_tokenize(argv[2]);
s4 = !evutil_ascii_strcasecmp("socks4", socks->scheme);
if (conn_set_socks_server(socks->host, socks->port, s4?
SOCKS_4 : SOCKS_4a) < 0)
return 0;
}
host = url_connect_tokenize(argv[1]);
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
conn_connect_bufferevent(bev, dns, AF_INET, host->host, host->port,
do_connect, NULL);
event_base_dispatch(base);
return 0;
}
#endif