1 |
#include <stdio.h> |
2 |
#include <stdlib.h> |
3 |
#include <string.h> |
4 |
#include <errno.h> |
5 |
#include <netinet/in.h> |
6 |
#include "my_socket.h" |
7 |
#include "sockbuf.h" |
8 |
|
9 |
typedef struct { |
10 |
char *host; |
11 |
int port; |
12 |
char *username; |
13 |
char *password; |
14 |
int status; |
15 |
int our_idx, their_idx; |
16 |
int sock; |
17 |
} proxy_info_t; |
18 |
|
19 |
static int socks5_on_read(void *client_data, int idx, char *data, int len); |
20 |
static int socks5_on_eof(void *client_data, int idx, int err, const char *errmsg); |
21 |
static int socks5_on_connect(void *client_data, int idx, const char *peer_ip, int peer_port); |
22 |
|
23 |
static sockbuf_handler_t socks5_events = { |
24 |
"socks5 proxy", |
25 |
socks5_on_connect, socks5_on_eof, NULL, |
26 |
socks5_on_read, NULL |
27 |
}; |
28 |
|
29 |
static void socks5_free(proxy_info_t *info) |
30 |
{ |
31 |
free(info->host); |
32 |
free(info->username); |
33 |
free(info->password); |
34 |
sockbuf_delete(info->our_idx); |
35 |
free(info); |
36 |
} |
37 |
|
38 |
/* When there's an error on our idx, we shut down and notify the other idx. */ |
39 |
/* All errors are non-recoverable. */ |
40 |
static void socks5_err(proxy_info_t *info, int err, const char *errmsg) |
41 |
{ |
42 |
errno = err; |
43 |
sockbuf_on_eof(info->their_idx, SOCKBUF_LEVEL_INTERNAL, err, errmsg); |
44 |
socks5_free(info); |
45 |
} |
46 |
|
47 |
/* This sends the connection request once we've authenticated. It calculates |
48 |
what address type we're dealing with (ipv4, ipv6, domain name) and |
49 |
sends the necessary CONNECT request. */ |
50 |
static void send_connect_request(proxy_info_t *info) |
51 |
{ |
52 |
char buf[512]; |
53 |
int len; |
54 |
unsigned short port; |
55 |
struct sockaddr_in addr; |
56 |
struct sockaddr_in6 addr6; |
57 |
|
58 |
/* VER CMD RESERVED */ |
59 |
buf[0] = 5; buf[1] = 1; buf[2] = 0; |
60 |
|
61 |
/* Try a regular ipv4 address first. */ |
62 |
if (inet_pton(AF_INET, info->host, &addr) > 0) { |
63 |
buf[3] = 1; |
64 |
memcpy(buf+4, &addr.sin_addr, 4); |
65 |
len = 8; |
66 |
} |
67 |
else if (inet_pton(AF_INET6, info->host, &addr6) > 0) { |
68 |
buf[3] = 4; |
69 |
memcpy(buf+4, &addr6.sin6_addr, 16); |
70 |
len = 20; |
71 |
} |
72 |
else { |
73 |
buf[3] = 3; |
74 |
len = strlen(info->host) % 255; |
75 |
buf[4] = len; |
76 |
memcpy(buf+5, info->host, len); |
77 |
len += 5; |
78 |
} |
79 |
|
80 |
port = htons(info->port); |
81 |
memcpy(buf+len, &port, 2); |
82 |
len += 2; |
83 |
|
84 |
sockbuf_write(info->our_idx, buf, len); |
85 |
} |
86 |
|
87 |
/* This parses the server's auth method reply. Basically, when we connect |
88 |
we send the server a list of authentication methods we support. Then |
89 |
the server picks one and sends it back. This procedure identifies |
90 |
which one was chosen, and then sends the proper login sequence. */ |
91 |
static void socks5_auth_method(char *data, int len, proxy_info_t *info) |
92 |
{ |
93 |
char buf[520]; |
94 |
|
95 |
/* If it's a bad reply, abort. */ |
96 |
if (len < 2) { |
97 |
socks5_err(info, ECONNABORTED, "Invalid reply from SOCKS5 server"); |
98 |
return; |
99 |
} |
100 |
|
101 |
if (data[0] != 5) { |
102 |
socks5_err(info, ECONNABORTED, "SOCKS5 server replied with SOCKS4 protocol"); |
103 |
return; |
104 |
} |
105 |
|
106 |
if (data[1] == 0) { |
107 |
/* No auth required. */ |
108 |
send_connect_request(info); |
109 |
info->status = 3; |
110 |
} |
111 |
else if (data[1] == 2) { |
112 |
/* User/password authentication */ |
113 |
int ulen, plen; |
114 |
char buf[520]; |
115 |
|
116 |
/* Username and password can be 255 max. */ |
117 |
ulen = strlen(info->username) % 255; |
118 |
plen = strlen(info->password) % 255; |
119 |
|
120 |
buf[0] = 1; |
121 |
buf[1] = ulen; |
122 |
memcpy(buf+2, info->username, ulen); |
123 |
buf[2+ulen] = plen; |
124 |
memcpy(buf+2+ulen+1, info->password, plen); |
125 |
sockbuf_write(info->our_idx, buf, 1+1+ulen+1+plen); |
126 |
info->status = 2; |
127 |
} |
128 |
else { |
129 |
/* We can't authenticate with this server, boo. */ |
130 |
socks5_err(info, ECONNABORTED, "SOCKS5 server doesn't accept our methods of authentication"); |
131 |
} |
132 |
return; |
133 |
} |
134 |
|
135 |
/* After we send our login information, the server responds with a status code |
136 |
to say whether it succeeded or not. */ |
137 |
static void socks5_auth_reply(char *data, int len, proxy_info_t *info) |
138 |
{ |
139 |
/* Abort if it's an invalid reply. */ |
140 |
if (len < 2) { |
141 |
socks5_err(info, ECONNABORTED, "Invalid reply from SOCKS5 server"); |
142 |
return; |
143 |
} |
144 |
|
145 |
if (data[1] != 0) { |
146 |
/* Authentication failed! */ |
147 |
socks5_err(info, ECONNREFUSED, "SOCKS5 authentication failed"); |
148 |
} |
149 |
else { |
150 |
/* Send the connection request. */ |
151 |
send_connect_request(info); |
152 |
info->status = 3; |
153 |
} |
154 |
} |
155 |
|
156 |
/* After we send our CONNECT command, the server tries to make the connection. |
157 |
When it makes the connection, or fails for some reason, it sends back |
158 |
a status code, which we parse here. If the code is successful, then |
159 |
we issue a CONNECT event on the original idx. */ |
160 |
static void socks5_connect_reply(char *data, int len, proxy_info_t *info) |
161 |
{ |
162 |
/* Abort if it's an invalid reply or the connection failed. */ |
163 |
/* It's actually supposed to be more than 2 bytes, but we only care |
164 |
about the 2nd field (status). */ |
165 |
|
166 |
/* Here are the reply field definitions (from rfc1928) |
167 |
0 - success |
168 |
1 - general SOCKS server failure |
169 |
2 - connection not allowed by ruleset |
170 |
3 - network unreachable |
171 |
4 - host unreachable |
172 |
5 - connection refused |
173 |
6 - ttl expired |
174 |
7 - command not supported |
175 |
8 - address type not supported |
176 |
*/ |
177 |
if (len < 2 || data[1] != 0) { |
178 |
char *errmsg; |
179 |
|
180 |
if (len < 2) errmsg = "Invalid reply from SOCKS5 server"; |
181 |
else switch (data[1]) { |
182 |
case 1: errmsg = "SOCKS5 general server failure"; break; |
183 |
case 2: errmsg = "SOCKS5 connection now allowed by ruleset"; break; |
184 |
case 3: errmsg = "SOCKS5 network unreachable"; break; |
185 |
case 4: errmsg = "SOCKS5 host unreachable"; break; |
186 |
case 5: errmsg = "SOCKS5 connection refused"; break; |
187 |
case 6: errmsg = "SOCKS5 TTL expired"; break; |
188 |
case 7: errmsg = "SOCKS5 command not supported"; break; |
189 |
case 8: errmsg = "SOCKS5 address type not supported"; break; |
190 |
default: errmsg = "SOCKS5 unknown error"; |
191 |
} |
192 |
socks5_err(info, ECONNABORTED, errmsg); |
193 |
return; |
194 |
} |
195 |
|
196 |
/* We're connected! Simulate a CONNECT event for the other idx. */ |
197 |
sockbuf_set_sock(info->our_idx, -1, 0); |
198 |
sockbuf_set_sock(info->their_idx, info->sock, 0); |
199 |
sockbuf_on_connect(info->their_idx, SOCKBUF_LEVEL_INTERNAL, info->host, info->port); |
200 |
socks5_free(info); |
201 |
return; |
202 |
} |
203 |
|
204 |
/* When we get data from the server, this procedure redirects it to the |
205 |
appropriate handler, based on the status field of the idx. */ |
206 |
static int socks5_on_read(void *client_data, int idx, char *data, int len) |
207 |
{ |
208 |
proxy_info_t *info = client_data; |
209 |
|
210 |
printf("read from socks5\n"); |
211 |
switch (info->status) { |
212 |
case 1: |
213 |
socks5_auth_method(data, len, info); |
214 |
break; |
215 |
case 2: |
216 |
socks5_auth_reply(data, len, info); |
217 |
break; |
218 |
case 3: |
219 |
socks5_connect_reply(data, len, info); |
220 |
break; |
221 |
} |
222 |
return(0); |
223 |
} |
224 |
|
225 |
/* When there's an error or eof on the server's idx, we issue an error on |
226 |
the original idx. */ |
227 |
static int socks5_on_eof(void *client_data, int idx, int err, const char *errmsg) |
228 |
{ |
229 |
proxy_info_t *info = client_data; |
230 |
if (!err) err = ECONNREFUSED; |
231 |
if (!errmsg) errmsg = "Unexpected EOF from SOCKS5 server"; |
232 |
socks5_err(info, err, errmsg); |
233 |
return(0); |
234 |
} |
235 |
|
236 |
/* When we establish a connection to the server, we have to send it a list of |
237 |
authentication methods we support. Then it chooses one and sends back |
238 |
a request to use that method. Right now we support "none" (duh) and |
239 |
"user/pass". */ |
240 |
static int socks5_on_connect(void *client_data, int idx, const char *peer_ip, int peer_port) |
241 |
{ |
242 |
proxy_info_t *info = client_data; |
243 |
char buf[] = "\005\002\002\000"; |
244 |
|
245 |
printf("socks5 connected\n"); |
246 |
/* Send the accepted auth methods (user/pass and none). */ |
247 |
sockbuf_write(idx, buf, 4); |
248 |
info->status = 1; |
249 |
return(0); |
250 |
} |
251 |
|
252 |
/* To establish a SOCKS5 connection, we create 2 idx's: one for the caller, and |
253 |
one that we use to connect to the server. */ |
254 |
int socks5_connect(int idx, char *proxy_host, int proxy_port, char *username, char *password, char *dest_host, int dest_port) |
255 |
{ |
256 |
int sock; |
257 |
proxy_info_t *info; |
258 |
|
259 |
sock = socket_create(proxy_host, proxy_port, NULL, 0, SOCKET_CLIENT|SOCKET_TCP|SOCKET_NONBLOCK); |
260 |
if (sock < 0) return(-1); |
261 |
|
262 |
info = (proxy_info_t *)malloc(sizeof(*info)); |
263 |
info->host = strdup(dest_host); |
264 |
info->port = dest_port; |
265 |
if (!username) username = ""; |
266 |
if (!password) password = ""; |
267 |
info->username = strdup(username); |
268 |
info->password = strdup(password); |
269 |
info->status = 0; |
270 |
|
271 |
info->our_idx = sockbuf_new(); |
272 |
sockbuf_set_sock(info->our_idx, sock, SOCKBUF_CLIENT); |
273 |
if (idx >= 0) info->their_idx = idx; |
274 |
else info->their_idx = sockbuf_new(); |
275 |
|
276 |
info->sock = sock; |
277 |
|
278 |
sockbuf_set_handler(info->our_idx, &socks5_events, info); |
279 |
|
280 |
return(info->their_idx); |
281 |
} |