1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2021 - 2024 */
6 /* Copyright (c) Jeremy Harris 2015 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
10 /* SOCKS version 5 proxy, client-mode */
15 #ifdef SUPPORT_SOCKS /* entire file */
18 # define nelem(arr) (sizeof(arr)/sizeof(*arr))
23 #define SOCKS_PORT 1080
24 #define SOCKS_TIMEOUT 5
25 #define SOCKS_WEIGHT 1
26 #define SOCKS_PRIORITY 1
29 #define AUTH_NAME 2 /* user/password per RFC 1929 */
30 #define AUTH_NAME_VER 1
39 {US"general SOCKS server failure", EIO},
40 {US"connection not allowed by ruleset", EACCES},
41 {US"Network unreachable", ENETUNREACH},
42 {US"Host unreachable", EHOSTUNREACH},
43 {US"Connection refused", ECONNREFUSED},
44 {US"TTL expired", ECANCELED},
45 {US"Command not supported", EOPNOTSUPP},
46 {US"Address type not supported", EAFNOSUPPORT}
51 const uschar * proxy_host;
52 uschar auth_type; /* RFC 1928 encoding */
53 const uschar * auth_name;
54 const uschar * auth_pwd;
63 socks_option_defaults(socks_opts * sob)
65 sob->proxy_host = NULL;
66 sob->auth_type = AUTH_NONE;
67 sob->auth_name = US"";
69 sob->is_failed = FALSE;
70 sob->port = SOCKS_PORT;
71 sob->timeout = SOCKS_TIMEOUT;
72 sob->weight = SOCKS_WEIGHT;
73 sob->priority = SOCKS_PRIORITY;
77 socks_option(socks_opts * sob, const uschar * opt)
79 if (Ustrncmp(opt, "auth=", 5) == 0)
82 if (Ustrcmp(opt, "none") == 0) sob->auth_type = AUTH_NONE;
83 else if (Ustrcmp(opt, "name") == 0) sob->auth_type = AUTH_NAME;
85 else if (Ustrncmp(opt, "name=", 5) == 0)
86 sob->auth_name = opt + 5;
87 else if (Ustrncmp(opt, "pass=", 5) == 0)
88 sob->auth_pwd = opt + 5;
89 else if (Ustrncmp(opt, "port=", 5) == 0)
90 sob->port = atoi(CCS opt + 5);
91 else if (Ustrncmp(opt, "tmo=", 4) == 0)
92 sob->timeout = atoi(CCS opt + 4);
93 else if (Ustrncmp(opt, "pri=", 4) == 0)
94 sob->priority = atoi(CCS opt + 4);
95 else if (Ustrncmp(opt, "weight=", 7) == 0)
96 sob->weight = atoi(CCS opt + 7);
101 socks_auth(int fd, int method, socks_opts * sob, time_t tmo)
109 log_write(0, LOG_MAIN|LOG_PANIC,
110 "Unrecognised socks auth method %d", method);
115 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" socks auth NAME '%s' '%s'\n",
116 sob->auth_name, sob->auth_pwd);
117 i = Ustrlen(sob->auth_name);
118 j = Ustrlen(sob->auth_pwd);
119 s = string_sprintf("%c%c%.255s%c%.255s", AUTH_NAME_VER,
120 i, sob->auth_name, j, sob->auth_pwd);
122 HDEBUG(D_transport|D_acl|D_v)
124 debug_printf_indent(" SOCKS>>");
125 for (int i = 0; i<len; i++) debug_printf(" %02x", s[i]);
128 if (send(fd, s, len, 0) < 0)
131 (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
133 if (!fd_ready(fd, tmo) || read(fd, s, 2) != 2)
135 HDEBUG(D_transport|D_acl|D_v)
136 debug_printf_indent(" SOCKS<< %02x %02x\n", s[0], s[1]);
137 if (s[0] == AUTH_NAME_VER && s[1] == 0)
139 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" socks auth OK\n");
143 log_write(0, LOG_MAIN|LOG_PANIC, "socks auth failed");
151 /* Find a suitable proxy to use from the list.
152 Possible common code with spamd_get_server() ?
154 Return: index into proxy spec array, or -1
158 socks_get_proxy(socks_opts * proxies, unsigned nproxies)
162 socks_opts * lim = &proxies[nproxies];
166 if (nproxies == 1) /* shortcut, if we have only 1 server */
167 return (proxies[0].is_failed ? -1 : 0);
169 /* scan for highest pri */
170 for (pri = 0, sd = proxies; sd < lim; sd++)
171 if (!sd->is_failed && sd->priority > pri)
174 /* get sum of weights at this pri */
175 for (weights = 0, sd = proxies; sd < lim; sd++)
176 if (!sd->is_failed && sd->priority == pri)
177 weights += sd->weight;
178 if (weights == 0) /* all servers failed */
181 for (rnd = random_number(weights), i = 0; i < nproxies; i++)
184 if (!sd->is_failed && sd->priority == pri)
185 if ((rnd -= sd->weight) < 0)
189 log_write(0, LOG_MAIN|LOG_PANIC,
190 "%s unknown error (memory/cpu corruption?)", __FUNCTION__);
196 /* Make a connection via a socks proxy
199 sc details for making connection: host, af, interface, transport
200 early_data data to send down the smtp channel (once proxied)
203 0 on success; -1 on failure, with errno set
207 socks_sock_connect(smtp_connect_args * sc, const blob * early_data)
209 transport_instance * tb = sc->tblock;
210 smtp_transport_options_block * ob = tb->drinst.options_block;
211 int timeout = ob->connect_timeout;
212 const uschar * proxy_list = ob->socks_proxy, /* already expanded */
217 const uschar * state;
219 socks_opts proxies[32]; /* max #proxies handled */
221 socks_opts * sob = NULL;
223 blob proxy_early_data;
225 if (!timeout) timeout = 24*60*60; /* use 1 day for "indefinite" */
226 tmo = time(NULL) + timeout;
228 GET_OPTION("socks_proxy");
229 if (!(proxy_list = expand_string(ob->socks_proxy)))
231 log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
236 /* Read proxy list */
239 nproxies < nelem(proxies)
240 && (proxy_spec = string_nextinlist(&proxy_list, &sep, NULL, 0));
244 const uschar * option;
246 socks_option_defaults(sob = &proxies[nproxies]);
248 if (!(sob->proxy_host = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
250 /* paniclog config error */
254 /*XXX consider global options eg. "hide socks_password = wibble" on the tpt */
255 /* extract any further per-proxy options */
256 while ((option = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
257 socks_option(sob, option);
261 /* Set up the socks protocol method-selection message,
262 for sending on connection */
264 state = US"method select";
265 buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
266 proxy_early_data.data = buf;
267 proxy_early_data.len = 3;
269 /* Try proxies until a connection succeeds */
275 smtp_connect_args proxy_sc = {.sock = -1};
277 if ((idx = socks_get_proxy(proxies, nproxies)) < 0)
279 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" no proxies left\n");
285 /* bodge up a host struct for the proxy */
286 proxy.address = proxy.name = sob->proxy_host;
287 proxy.port = sob->port;
289 proxy_sc.tblock = tb;
291 proxy_sc.host = &proxy;
292 proxy_sc.host_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
293 proxy_sc.interface = sc->interface;
295 /*XXX we trust that the method-select command is idempotent */
296 if ((fd = smtp_sock_connect(&proxy_sc, sob->timeout, &proxy_early_data)) >= 0)
298 proxy_local_address = string_copy(proxy.address);
299 proxy_local_port = sob->port;
303 log_write(0, LOG_MAIN, "%s: %s", __FUNCTION__, strerror(errno));
304 sob->is_failed = TRUE;
307 /* Do the socks protocol stuff */
309 HDEBUG(D_transport|D_acl|D_v)
310 debug_printf_indent(" SOCKS>> 05 01 %02x\n", sob->auth_type);
312 /* expect method response */
315 (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
318 if ( !fd_ready(fd, tmo)
319 || read(fd, buf, 2) != 2
322 HDEBUG(D_transport|D_acl|D_v)
323 debug_printf_indent(" SOCKS<< %02x %02x\n", buf[0], buf[1]);
325 || socks_auth(fd, buf[1], sob, tmo) != OK
330 int host_af = sc->host_af;
331 host_item * host = sc->host;
332 union sockaddr_46 sin;
333 (void) ip_addr(&sin, host_af, host->address, host->port);
335 /* send connect (ipver, ipaddr, port) */
337 buf[0] = 5; buf[1] = 1; buf[2] = 0; buf[3] = host_af == AF_INET6 ? 4 : 1;
339 if (host_af == AF_INET6)
341 memcpy(buf+4, &sin.v6.sin6_addr, sizeof(sin.v6.sin6_addr));
342 memcpy(buf+4+sizeof(sin.v6.sin6_addr),
343 &sin.v6.sin6_port, sizeof(sin.v6.sin6_port));
344 size = 4+sizeof(sin.v6.sin6_addr)+sizeof(sin.v6.sin6_port);
349 memcpy(buf+4, &sin.v4.sin_addr.s_addr, sizeof(sin.v4.sin_addr.s_addr));
350 memcpy(buf+4+sizeof(sin.v4.sin_addr.s_addr),
351 &sin.v4.sin_port, sizeof(sin.v4.sin_port));
352 size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port);
357 HDEBUG(D_transport|D_acl|D_v)
359 debug_printf_indent(" SOCKS>>");
360 for (int i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
363 if (send(fd, buf, size, 0) < 0)
366 /* expect conn-reply (success, local(ipver, addr, port))
367 of same length as conn-request, or non-success fail code */
369 if ( !fd_ready(fd, tmo)
370 || (size = read(fd, buf, size)) < 2
373 HDEBUG(D_transport|D_acl|D_v)
375 debug_printf_indent(" SOCKS>>");
376 for (int i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
384 proxy_external_address = string_copy(
385 host_ntoa(buf[3] == 4 ? AF_INET6 : AF_INET, buf+4, NULL, NULL));
386 proxy_external_port = ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8))));
387 proxy_session = TRUE;
389 HDEBUG(D_transport|D_acl|D_v)
390 debug_printf_indent(" proxy farside: [%s]:%d\n", proxy_external_address, proxy_external_port);
392 if (early_data && early_data->data && early_data->len)
393 if (send(fd, early_data->data, early_data->len, 0) < 0)
395 int save_errno = errno;
396 HDEBUG(D_transport|D_acl|D_v)
398 debug_printf_indent("failed: %s", CUstrerror(save_errno));
399 if (save_errno == ETIMEDOUT)
400 debug_printf(" (timeout=%s)", readconf_printtime(ob->connect_timeout));
411 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" proxy snd_err %s: %s\n", state, strerror(errno));
416 struct socks_err * se =
417 buf[1] > nelem(socks_errs) ? NULL : socks_errs + buf[1];
418 HDEBUG(D_transport|D_acl|D_v)
419 debug_printf_indent(" proxy %s: %s\n", state, se ? se->reason : US"unknown error code received");
420 errno = se ? se->errcode : EPROTO;
424 HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" proxy rcv_err %s: %s\n", state, strerror(errno));
425 if (!errno) errno = EPROTO;
426 else if (errno == ENOENT) errno = ECONNABORTED;
430 #endif /* entire file */