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