1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4
5 /* Copyright (c) Jeremy Harris 2015 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
7
8 /* SOCKS version 5 proxy, client-mode */
9
10 #include "../exim.h"
11 #include "smtp.h"
12
13 #ifdef SUPPORT_SOCKS /* entire file */
14
15 #ifndef nelem
16 # define nelem(arr) (sizeof(arr)/sizeof(*arr))
17 #endif
18
19
20 /* Defaults */
21 #define SOCKS_PORT 1080
22 #define SOCKS_TIMEOUT 5
23 #define SOCKS_WEIGHT 1
24 #define SOCKS_PRIORITY 1
25
26 #define AUTH_NONE 0
27 #define AUTH_NAME 2 /* user/password per RFC 1929 */
28 #define AUTH_NAME_VER 1
29
30 struct socks_err
31 {
32 uschar * reason;
33 int errcode;
34 } socks_errs[] =
35 {
36 {NULL, 0},
37 {US"general SOCKS server failure", EIO},
38 {US"connection not allowed by ruleset", EACCES},
39 {US"Network unreachable", ENETUNREACH},
40 {US"Host unreachable", EHOSTUNREACH},
41 {US"Connection refused", ECONNREFUSED},
42 {US"TTL expired", ECANCELED},
43 {US"Command not supported", EOPNOTSUPP},
44 {US"Address type not supported", EAFNOSUPPORT}
45 };
46
47 typedef struct
48 {
49 const uschar * proxy_host;
50 uschar auth_type; /* RFC 1928 encoding */
51 const uschar * auth_name;
52 const uschar * auth_pwd;
53 short port;
54 BOOL is_failed;
55 unsigned timeout;
56 unsigned weight;
57 unsigned priority;
58 } socks_opts;
59
60 static void
socks_option_defaults(socks_opts * sob)61 socks_option_defaults(socks_opts * sob)
62 {
63 sob->proxy_host = NULL;
64 sob->auth_type = AUTH_NONE;
65 sob->auth_name = US"";
66 sob->auth_pwd = US"";
67 sob->is_failed = FALSE;
68 sob->port = SOCKS_PORT;
69 sob->timeout = SOCKS_TIMEOUT;
70 sob->weight = SOCKS_WEIGHT;
71 sob->priority = SOCKS_PRIORITY;
72 }
73
74 static void
socks_option(socks_opts * sob,const uschar * opt)75 socks_option(socks_opts * sob, const uschar * opt)
76 {
77 if (Ustrncmp(opt, "auth=", 5) == 0)
78 {
79 opt += 5;
80 if (Ustrcmp(opt, "none") == 0) sob->auth_type = AUTH_NONE;
81 else if (Ustrcmp(opt, "name") == 0) sob->auth_type = AUTH_NAME;
82 }
83 else if (Ustrncmp(opt, "name=", 5) == 0)
84 sob->auth_name = opt + 5;
85 else if (Ustrncmp(opt, "pass=", 5) == 0)
86 sob->auth_pwd = opt + 5;
87 else if (Ustrncmp(opt, "port=", 5) == 0)
88 sob->port = atoi(CCS opt + 5);
89 else if (Ustrncmp(opt, "tmo=", 4) == 0)
90 sob->timeout = atoi(CCS opt + 4);
91 else if (Ustrncmp(opt, "pri=", 4) == 0)
92 sob->priority = atoi(CCS opt + 4);
93 else if (Ustrncmp(opt, "weight=", 7) == 0)
94 sob->weight = atoi(CCS opt + 7);
95 return;
96 }
97
98 static int
socks_auth(int fd,int method,socks_opts * sob,time_t tmo)99 socks_auth(int fd, int method, socks_opts * sob, time_t tmo)
100 {
101 uschar * s;
102 int len, i, j;
103
104 switch(method)
105 {
106 default:
107 log_write(0, LOG_MAIN|LOG_PANIC,
108 "Unrecognised socks auth method %d", method);
109 return FAIL;
110 case AUTH_NONE:
111 return OK;
112 case AUTH_NAME:
113 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" socks auth NAME '%s' '%s'\n",
114 sob->auth_name, sob->auth_pwd);
115 i = Ustrlen(sob->auth_name);
116 j = Ustrlen(sob->auth_pwd);
117 s = string_sprintf("%c%c%.255s%c%.255s", AUTH_NAME_VER,
118 i, sob->auth_name, j, sob->auth_pwd);
119 len = i + j + 3;
120 HDEBUG(D_transport|D_acl|D_v)
121 {
122 debug_printf_indent(" SOCKS>>");
123 for (int i = 0; i<len; i++) debug_printf(" %02x", s[i]);
124 debug_printf("\n");
125 }
126 if (send(fd, s, len, 0) < 0)
127 return FAIL;
128 #ifdef TCP_QUICKACK
129 (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
130 #endif
131 if (!fd_ready(fd, tmo) || read(fd, s, 2) != 2)
132 return FAIL;
133 HDEBUG(D_transport|D_acl|D_v)
134 debug_printf_indent(" SOCKS<< %02x %02x\n", s[0], s[1]);
135 if (s[0] == AUTH_NAME_VER && s[1] == 0)
136 {
137 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" socks auth OK\n");
138 return OK;
139 }
140
141 log_write(0, LOG_MAIN|LOG_PANIC, "socks auth failed");
142 errno = EPROTO;
143 return FAIL;
144 }
145 }
146
147
148
149 /* Find a suitable proxy to use from the list.
150 Possible common code with spamd_get_server() ?
151
152 Return: index into proxy spec array, or -1
153 */
154
155 static int
socks_get_proxy(socks_opts * proxies,unsigned nproxies)156 socks_get_proxy(socks_opts * proxies, unsigned nproxies)
157 {
158 unsigned int i;
159 socks_opts * sd;
160 socks_opts * lim = &proxies[nproxies];
161 long rnd, weights;
162 unsigned pri;
163
164 if (nproxies == 1) /* shortcut, if we have only 1 server */
165 return (proxies[0].is_failed ? -1 : 0);
166
167 /* scan for highest pri */
168 for (pri = 0, sd = proxies; sd < lim; sd++)
169 if (!sd->is_failed && sd->priority > pri)
170 pri = sd->priority;
171
172 /* get sum of weights at this pri */
173 for (weights = 0, sd = proxies; sd < lim; sd++)
174 if (!sd->is_failed && sd->priority == pri)
175 weights += sd->weight;
176 if (weights == 0) /* all servers failed */
177 return -1;
178
179 for (rnd = random_number(weights), i = 0; i < nproxies; i++)
180 {
181 sd = &proxies[i];
182 if (!sd->is_failed && sd->priority == pri)
183 if ((rnd -= sd->weight) < 0)
184 return i;
185 }
186
187 log_write(0, LOG_MAIN|LOG_PANIC,
188 "%s unknown error (memory/cpu corruption?)", __FUNCTION__);
189 return -1;
190 }
191
192
193
194 /* Make a connection via a socks proxy
195
196 Arguments:
197 host smtp target host
198 host_af address family
199 port remote tcp port number
200 interface local interface
201 tb transport
202 timeout connection timeout (zero for indefinite)
203
204 Return value:
205 0 on success; -1 on failure, with errno set
206 */
207
208 int
socks_sock_connect(host_item * host,int host_af,int port,uschar * interface,transport_instance * tb,int timeout)209 socks_sock_connect(host_item * host, int host_af, int port, uschar * interface,
210 transport_instance * tb, int timeout)
211 {
212 smtp_transport_options_block * ob =
213 (smtp_transport_options_block *)tb->options_block;
214 const uschar * proxy_list;
215 const uschar * proxy_spec;
216 int sep = 0;
217 int fd;
218 time_t tmo;
219 const uschar * state;
220 uschar buf[24];
221 socks_opts proxies[32]; /* max #proxies handled */
222 unsigned nproxies;
223 socks_opts * sob = NULL;
224 unsigned size;
225 blob early_data;
226
227 if (!timeout) timeout = 24*60*60; /* use 1 day for "indefinite" */
228 tmo = time(NULL) + timeout;
229
230 if (!(proxy_list = expand_string(ob->socks_proxy)))
231 {
232 log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
233 tb->name);
234 return -1;
235 }
236
237 /* Read proxy list */
238
239 for (nproxies = 0;
240 nproxies < nelem(proxies)
241 && (proxy_spec = string_nextinlist(&proxy_list, &sep, NULL, 0));
242 nproxies++)
243 {
244 int subsep = -' ';
245 const uschar * option;
246
247 socks_option_defaults(sob = &proxies[nproxies]);
248
249 if (!(sob->proxy_host = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
250 {
251 /* paniclog config error */
252 return -1;
253 }
254
255 /*XXX consider global options eg. "hide socks_password = wibble" on the tpt */
256 /* extract any further per-proxy options */
257 while ((option = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
258 socks_option(sob, option);
259 }
260 if (!sob) return -1;
261
262 /* Set up the socks protocol method-selection message,
263 for sending on connection */
264
265 state = US"method select";
266 buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
267 early_data.data = buf;
268 early_data.len = 3;
269
270 /* Try proxies until a connection succeeds */
271
272 for(;;)
273 {
274 int idx;
275 host_item proxy;
276 int proxy_af;
277
278 if ((idx = socks_get_proxy(proxies, nproxies)) < 0)
279 {
280 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" no proxies left\n");
281 errno = EBUSY;
282 return -1;
283 }
284 sob = &proxies[idx];
285
286 /* bodge up a host struct for the proxy */
287 proxy.address = proxy.name = sob->proxy_host;
288 proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
289
290 /*XXX we trust that the method-select command is idempotent */
291 if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port,
292 interface, tb, sob->timeout, &early_data)) >= 0)
293 {
294 proxy_local_address = string_copy(proxy.address);
295 proxy_local_port = sob->port;
296 break;
297 }
298
299 log_write(0, LOG_MAIN, "%s: %s", __FUNCTION__, strerror(errno));
300 sob->is_failed = TRUE;
301 }
302
303 /* Do the socks protocol stuff */
304
305 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SOCKS>> 05 01 %02x\n", sob->auth_type);
306
307 /* expect method response */
308
309 #ifdef TCP_QUICKACK
310 (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
311 #endif
312
313 if ( !fd_ready(fd, tmo)
314 || read(fd, buf, 2) != 2
315 )
316 goto rcv_err;
317 HDEBUG(D_transport|D_acl|D_v)
318 debug_printf_indent(" SOCKS<< %02x %02x\n", buf[0], buf[1]);
319 if ( buf[0] != 5
320 || socks_auth(fd, buf[1], sob, tmo) != OK
321 )
322 goto proxy_err;
323
324 {
325 union sockaddr_46 sin;
326 (void) ip_addr(&sin, host_af, host->address, port);
327
328 /* send connect (ipver, ipaddr, port) */
329
330 buf[0] = 5; buf[1] = 1; buf[2] = 0; buf[3] = host_af == AF_INET6 ? 4 : 1;
331 #if HAVE_IPV6
332 if (host_af == AF_INET6)
333 {
334 memcpy(buf+4, &sin.v6.sin6_addr, sizeof(sin.v6.sin6_addr));
335 memcpy(buf+4+sizeof(sin.v6.sin6_addr),
336 &sin.v6.sin6_port, sizeof(sin.v6.sin6_port));
337 size = 4+sizeof(sin.v6.sin6_addr)+sizeof(sin.v6.sin6_port);
338 }
339 else
340 #endif
341 {
342 memcpy(buf+4, &sin.v4.sin_addr.s_addr, sizeof(sin.v4.sin_addr.s_addr));
343 memcpy(buf+4+sizeof(sin.v4.sin_addr.s_addr),
344 &sin.v4.sin_port, sizeof(sin.v4.sin_port));
345 size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port);
346 }
347 }
348
349 state = US"connect";
350 HDEBUG(D_transport|D_acl|D_v)
351 {
352 debug_printf_indent(" SOCKS>>");
353 for (int i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
354 debug_printf("\n");
355 }
356 if (send(fd, buf, size, 0) < 0)
357 goto snd_err;
358
359 /* expect conn-reply (success, local(ipver, addr, port))
360 of same length as conn-request, or non-success fail code */
361
362 if ( !fd_ready(fd, tmo)
363 || (size = read(fd, buf, size)) < 2
364 )
365 goto rcv_err;
366 HDEBUG(D_transport|D_acl|D_v)
367 {
368 debug_printf_indent(" SOCKS>>");
369 for (int i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
370 debug_printf("\n");
371 }
372 if ( buf[0] != 5
373 || buf[1] != 0
374 )
375 goto proxy_err;
376
377 proxy_external_address = string_copy(
378 host_ntoa(buf[3] == 4 ? AF_INET6 : AF_INET, buf+4, NULL, NULL));
379 proxy_external_port = ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8))));
380 proxy_session = TRUE;
381
382 HDEBUG(D_transport|D_acl|D_v)
383 debug_printf_indent(" proxy farside: [%s]:%d\n", proxy_external_address, proxy_external_port);
384
385 return fd;
386
387 snd_err:
388 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" proxy snd_err %s: %s\n", state, strerror(errno));
389 return -1;
390
391 proxy_err:
392 {
393 struct socks_err * se =
394 buf[1] > nelem(socks_errs) ? NULL : socks_errs + buf[1];
395 HDEBUG(D_transport|D_acl|D_v)
396 debug_printf_indent(" proxy %s: %s\n", state, se ? se->reason : US"unknown error code received");
397 errno = se ? se->errcode : EPROTO;
398 }
399
400 rcv_err:
401 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" proxy rcv_err %s: %s\n", state, strerror(errno));
402 if (!errno) errno = EPROTO;
403 else if (errno == ENOENT) errno = ECONNABORTED;
404 return -1;
405 }
406
407 #endif /* entire file */
408 /* vi: aw ai sw=2
409 */
410