ESP8266-based WiFi serial modem emulator ROM
1/*
2 * WiFiPPP
3 * Copyright (c) 2021 joshua stein <jcs@jcs.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include "SocksClient.h"
19#include "wifippp.h"
20
21enum {
22 STATE_DEAD = 0,
23 STATE_INIT,
24 STATE_METHOD,
25 STATE_REQUEST,
26 STATE_CONNECT,
27 STATE_PROXY,
28};
29
30unsigned int tls_ports[] = {
31 443, /* https */
32 993, /* imaps */
33 995, /* pop3s */
34};
35
36/* https://datatracker.ietf.org/doc/html/rfc1928 */
37
38#define VERSION_SOCKS5 0x05
39
40#define METHOD_MIN_LENGTH 3
41#define METHOD_AUTH_NONE 0x0
42#define METHOD_AUTH_BAD 0xFF
43
44#define REQUEST_MIN_LENGTH 9
45#define REQUEST_COMMAND_CONNECT 0x1
46#define REQUEST_ATYP_IP 0x1
47#define REQUEST_ATYP_HOSTNAME 0x3
48#define REQUEST_ATYP_IP6 0x4
49
50#define REPLY_SUCCESS 0x0
51#define REPLY_FAIL 0x1
52#define REPLY_EPERM 0x02
53#define REPLY_NET_UNREACHABLE 0x03
54#define REPLY_HOST_UNREACHABLE 0x04
55#define REPLY_CONN_REFUSED 0x05
56#define REPLY_TTL_EXPIRED 0x06
57#define REPLY_BAD_COMMAND 0x07
58#define REPLY_BAD_ADDRESS 0x08
59
60#define REMOTE_CLIENT (tls() ? remote_client_tls : remote_client)
61
62static unsigned long last_buffer_check = 0;
63
64SocksClient::~SocksClient()
65{
66 local_client.stop();
67 REMOTE_CLIENT.stop();
68}
69
70SocksClient::SocksClient(int _slot, WiFiClient _client)
71 : slot(_slot), local_client(_client)
72{
73 state = STATE_INIT;
74
75 memset(local_buf, 0, sizeof(local_buf));
76 memset(remote_buf, 0, sizeof(remote_buf));
77 local_buf_len = 0;
78 remote_buf_len = 0;
79 remote_port = 0;
80 ip4_addr_set_zero(&remote_ip);
81 _tls = false;
82
83#ifdef SOCKS_TRACE
84 syslog.logf(LOG_DEBUG, "[%d] in socks client init with ip %s", slot,
85 local_client.remoteIP().toString().c_str());
86#endif
87}
88
89bool
90SocksClient::done()
91{
92 return (state == STATE_DEAD);
93}
94
95void
96SocksClient::finish()
97{
98 state = STATE_DEAD;
99}
100
101void
102SocksClient::process()
103{
104 switch (state) {
105 case STATE_DEAD:
106 return;
107 case STATE_INIT:
108 case STATE_METHOD:
109 case STATE_REQUEST:
110 if (local_client.available() &&
111 sizeof(local_buf) - local_buf_len > 0)
112 local_buf_len += local_client.read(local_buf +
113 local_buf_len, sizeof(local_buf) - local_buf_len);
114 break;
115 default:
116 /* proxy() will do its own buffering */
117 break;
118 }
119
120 switch (state) {
121 case STATE_INIT:
122 if (local_buf_len >= METHOD_MIN_LENGTH) {
123 state = STATE_METHOD;
124 break;
125 }
126 break;
127 case STATE_METHOD:
128 verify_method();
129 break;
130 case STATE_REQUEST:
131 handle_request();
132 break;
133 case STATE_CONNECT:
134 connect();
135 break;
136 case STATE_PROXY:
137 proxy();
138 break;
139 }
140}
141
142void
143SocksClient::fail_close(char code)
144{
145 unsigned char msg[] = {
146 VERSION_SOCKS5,
147 code,
148 0,
149 REQUEST_ATYP_IP,
150 0, 0, 0, 0,
151 0, 0,
152 };
153
154 local_client.write(msg, sizeof(msg));
155
156 finish();
157}
158
159bool
160SocksClient::verify_version()
161{
162 if (local_buf[0] != VERSION_SOCKS5) {
163 syslog.logf(LOG_ERR, "[%d] unsupported version 0x%x", slot,
164 local_buf[0]);
165 fail_close(REPLY_FAIL);
166 return false;
167 }
168
169 return true;
170}
171
172bool
173SocksClient::verify_state(int _state)
174{
175 if (state != _state) {
176 syslog.logf(LOG_ERR, "[%d] in state %d but expected %d", slot,
177 state, _state);
178 state = STATE_DEAD;
179 return false;
180 }
181
182 return true;
183}
184
185void
186SocksClient::verify_method()
187{
188 int i;
189
190 if (!verify_state(STATE_METHOD))
191 return;
192
193 if (local_buf_len < METHOD_MIN_LENGTH)
194 return;
195
196 if (!verify_version())
197 return;
198
199 /* buf[1] is NMETHODS, find one we like */
200 for (i = 0; i < (unsigned char)local_buf[1]; i++) {
201 if (local_buf[2 + i] == METHOD_AUTH_NONE) {
202 /* send back method selection */
203 unsigned char msg[] = {
204 VERSION_SOCKS5,
205 METHOD_AUTH_NONE,
206 };
207
208 local_client.write(msg, sizeof(msg));
209 state = STATE_REQUEST;
210 local_buf_len = 0;
211 return;
212 }
213 }
214
215 syslog.logf(LOG_ERR, "[%d] no supported auth methods", slot);
216
217 unsigned char msg[] = {
218 VERSION_SOCKS5,
219 METHOD_AUTH_BAD,
220 };
221 local_client.write(msg, sizeof(msg));
222 fail_close(REPLY_FAIL);
223}
224
225void
226SocksClient::handle_request()
227{
228 if (!verify_state(STATE_REQUEST))
229 return;
230
231 if (local_buf_len < METHOD_MIN_LENGTH)
232 return;
233
234 if (!verify_version())
235 return;
236
237 if (local_buf[1] != REQUEST_COMMAND_CONNECT) {
238 syslog.logf(LOG_ERR, "[%d] unsupported request command 0x%x",
239 slot, local_buf[1]);
240 fail_close(REPLY_BAD_COMMAND);
241 return;
242 }
243
244 /* local_buf[2] is reserved */
245
246 switch (local_buf[3]) {
247 case REQUEST_ATYP_IP:
248 if (local_buf_len < 4 + 4 + 2)
249 return;
250
251 IP4_ADDR(&remote_ip, local_buf[4], local_buf[5], local_buf[6],
252 local_buf[7]);
253 remote_port = (uint16_t)((local_buf[8] & 0xff) << 8) |
254 (local_buf[9] & 0xff);
255
256#ifdef SOCKS_TRACE
257 syslog.logf(LOG_DEBUG, "[%d] CONNECT request to IP %s:%d",
258 slot, ipaddr_ntoa(&remote_ip), remote_port);
259#endif
260
261 break;
262 case REQUEST_ATYP_HOSTNAME: {
263 IPAddress resip;
264 unsigned char hostlen;
265
266 if (local_buf_len < 4 + 2)
267 return;
268
269 hostlen = local_buf[4];
270 if (local_buf_len < (unsigned char)(4 + hostlen + 2))
271 return;
272
273 remote_hostname = (unsigned char *)malloc(hostlen + 1);
274 if (!remote_hostname) {
275 syslog.logf(LOG_ERR, "[%d] malloc(%d) failure",
276 slot, hostlen + 1);
277 fail_close(REPLY_FAIL);
278 return;
279 }
280
281 memcpy(remote_hostname, local_buf + 5, hostlen);
282 remote_hostname[hostlen] = '\0';
283
284 /* network order */
285 remote_port = (uint16_t)((local_buf[5 + hostlen] & 0xff) << 8) |
286 (local_buf[5 + hostlen + 1] & 0xff);
287
288 if (WiFi.hostByName((const char *)remote_hostname,
289 resip) != 1) {
290 syslog.logf(LOG_ERR, "[%d] CONNECT request to "
291 "hostname %s:%d, couldn't resolve name",
292 slot, remote_hostname, remote_port);
293 fail_close(REPLY_BAD_ADDRESS);
294 return;
295 }
296
297 ip4_addr_set_u32(&remote_ip, resip.v4());
298
299#ifdef SOCKS_TRACE
300 syslog.logf(LOG_DEBUG, "[%d] CONNECT request to hostname "
301 "%s:%d, resolved to IP %s", slot, remote_hostname,
302 remote_port, ipaddr_ntoa(&remote_ip));
303#endif
304 break;
305 }
306 case REQUEST_ATYP_IP6:
307 syslog.logf(LOG_ERR, "[%d] ipv6 not supported", slot);
308 fail_close(REPLY_BAD_ADDRESS);
309 return;
310 default:
311 syslog.logf(LOG_ERR, "[%d] request ATYP 0x%x not supported",
312 slot, local_buf[3]);
313 fail_close(REPLY_BAD_ADDRESS);
314 return;
315 }
316
317 switch (local_buf[1]) {
318 case REQUEST_COMMAND_CONNECT:
319 state = STATE_CONNECT;
320 return;
321 default:
322 syslog.logf(LOG_ERR, "[%d] unsupported command 0x%x",
323 slot, local_buf[1]);
324 fail_close(REPLY_BAD_COMMAND);
325 return;
326 }
327}
328
329void
330SocksClient::connect()
331{
332 bool ret;
333
334 _tls = false;
335
336 if (!verify_state(STATE_CONNECT))
337 return;
338
339 if (remote_port == 0 || ip4_addr_isany_val(remote_ip)) {
340 syslog.logf(LOG_ERR, "[%d] bogus ip/port %s:%d", slot,
341 ipaddr_ntoa(&remote_ip), remote_port);
342 fail_close(REPLY_BAD_ADDRESS);
343 return;
344 }
345
346 for (size_t i = 0; i < sizeof(tls_ports) / sizeof(tls_ports[0]); i++) {
347 if (remote_port == tls_ports[i]) {
348 _tls = true;
349 break;
350 }
351 }
352
353 if (tls()) {
354 remote_client_tls.setInsecure();
355 remote_client_tls.setBufferSizes(1024, 1024);
356#ifdef SOCKS_TRACE
357 syslog.logf(LOG_DEBUG, "[%d] making TLS connection to %s:%d "
358 "with %d free mem", slot, ipaddr_ntoa(&remote_ip),
359 remote_port, ESP.getFreeHeap());
360#endif
361 }
362
363 ret = REMOTE_CLIENT.connect(remote_ip, remote_port);
364 if (!ret) {
365 syslog.logf(LOG_WARNING, "[%d] connection to %s:%d%s failed",
366 slot, ipaddr_ntoa(&remote_ip), remote_port,
367 (tls() ? " (TLS decrypt)" : ""));
368 fail_close(REPLY_CONN_REFUSED);
369 return;
370 }
371
372 unsigned char msg[] = {
373 VERSION_SOCKS5, REPLY_SUCCESS, 0, REQUEST_ATYP_IP,
374 ip4_addr1(&remote_ip), ip4_addr2(&remote_ip),
375 ip4_addr3(&remote_ip), ip4_addr4(&remote_ip),
376 (unsigned char)((remote_port >> 8) & 0xff),
377 (unsigned char)(remote_port & 0xff)
378 };
379
380 local_buf_len = 0;
381 local_client.write(msg, sizeof(msg));
382 state = STATE_PROXY;
383}
384
385void
386SocksClient::proxy()
387{
388 size_t len, wrote;
389
390 if (!verify_state(STATE_PROXY))
391 return;
392
393 /*
394 * Process buffers before checking connection, we may have read some
395 * before the client closed.
396 */
397
398 /* push out buffered data from remote to local client */
399 if (remote_buf_len) {
400 len = remote_buf_len;
401 if (len > 64)
402 len = 64;
403 wrote = local_client.write(remote_buf, len);
404 if (wrote) {
405 memmove(remote_buf, remote_buf + wrote,
406 remote_buf_len - wrote);
407 remote_buf_len -= wrote;
408#ifdef SOCKS_TRACE
409 syslog.logf(LOG_DEBUG, "[%d] wrote %d to local "
410 "(%d left)", slot, wrote, remote_buf_len);
411#endif
412 }
413 }
414
415 /* push out buffered data from local to remote client */
416 if (local_buf_len) {
417 wrote = REMOTE_CLIENT.write(local_buf, local_buf_len);
418 if (wrote) {
419 memmove(local_buf, local_buf + wrote,
420 local_buf_len - wrote);
421 local_buf_len -= wrote;
422#ifdef SOCKS_TRACE
423 syslog.logf(LOG_DEBUG, "[%d] wrote %d to remote "
424 "(%d left)", slot, wrote, local_buf_len);
425#endif
426 }
427 }
428
429 /* buffer new data from local client */
430 if (local_client.available() && local_buf_len < sizeof(local_buf)) {
431 len = local_client.read(local_buf + local_buf_len,
432 sizeof(local_buf) - local_buf_len);
433#ifdef SOCKS_TRACE
434 syslog.logf(LOG_DEBUG, "[%d] read %d from local (now %d):",
435 slot, len, local_buf_len + len);
436 syslog_buf((const char *)local_buf + local_buf_len, len);
437#endif
438 local_buf_len += len;
439 }
440
441 /* and then read in new data from remote if we have room */
442 if (REMOTE_CLIENT.available() &&
443 (remote_buf_len < sizeof(remote_buf))) {
444 len = REMOTE_CLIENT.read(remote_buf + remote_buf_len,
445 sizeof(remote_buf) - remote_buf_len);
446#ifdef SOCKS_TRACE
447 syslog.logf(LOG_DEBUG, "[%d] read %d from remote (now %d):",
448 slot, len, remote_buf_len + len);
449 syslog_buf((const char *)remote_buf + remote_buf_len, len);
450#endif
451 remote_buf_len += len;
452 }
453
454#ifdef SOCKS_TRACE
455 if (millis() - last_buffer_check > (3 * 1000)) {
456 syslog.logf(LOG_DEBUG, "[%d] local:%d remote:%d free:%d", slot,
457 local_buf_len, remote_buf_len, ESP.getFreeHeap());
458 last_buffer_check = millis();
459 }
460#endif
461
462 if (!local_client.connected()) {
463#ifdef SOCKS_TRACE
464 syslog.logf(LOG_DEBUG, "[%d] local client closed", slot);
465#endif
466 REMOTE_CLIENT.stop();
467 finish();
468 return;
469 }
470
471 if (!REMOTE_CLIENT.connected()) {
472#ifdef SOCKS_TRACE
473 syslog.logf(LOG_DEBUG, "[%d] remote client closed", slot);
474#endif
475 local_client.stop();
476 REMOTE_CLIENT.stop();
477 finish();
478 return;
479 }
480}