When trying spooled messages, account for the local-interface in grouping for a conne...
[exim.git] / src / src / transports / smtp_socks.c
CommitLineData
7eb6c37c
JH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
5/* Copyright (c) Jeremy Harris 2015 */
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 EXPERIMENTAL_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
24#define AUTH_NONE 0
25#define AUTH_NAME 2 /* user/password per RFC 1929 */
26#define AUTH_NAME_VER 1
27
28struct socks_err
29 {
30 uschar * reason;
31 int errcode;
32 } socks_errs[] =
33 {
34 {NULL, 0},
35 {US"general SOCKS server failure", EIO},
36 {US"connection not allowed by ruleset", EACCES},
37 {US"Network unreachable", ENETUNREACH},
38 {US"Host unreachable", EHOSTUNREACH},
39 {US"Connection refused", ECONNREFUSED},
40 {US"TTL expired", ECANCELED},
41 {US"Command not supported", EOPNOTSUPP},
42 {US"Address type not supported", EAFNOSUPPORT}
43 };
44
45typedef struct
46 {
47 uschar auth_type; /* RFC 1928 encoding */
48 const uschar * auth_name;
49 const uschar * auth_pwd;
50 short port;
51 unsigned timeout;
52 } socks_opts;
53
54static void
55socks_option_defaults(socks_opts * sob)
56{
57sob->auth_type = AUTH_NONE;
58sob->auth_name = US"";
59sob->auth_pwd = US"";
60sob->port = SOCKS_PORT;
61sob->timeout = SOCKS_TIMEOUT;
62}
63
64static void
65socks_option(socks_opts * sob, const uschar * opt)
66{
67const uschar * s;
68
69if (Ustrncmp(opt, "auth=", 5) == 0)
70 {
71 opt += 5;
72 if (Ustrcmp(opt, "none") == 0) sob->auth_type = AUTH_NONE;
73 else if (Ustrcmp(opt, "name") == 0) sob->auth_type = AUTH_NAME;
74 }
75else if (Ustrncmp(opt, "name=", 5) == 0)
76 sob->auth_name = opt + 5;
77else if (Ustrncmp(opt, "pass=", 5) == 0)
78 sob->auth_pwd = opt + 5;
79else if (Ustrncmp(opt, "port=", 5) == 0)
80 sob->port = atoi(opt + 5);
81else if (Ustrncmp(opt, "tmo=", 4) == 0)
82 sob->timeout = atoi(opt + 4);
83return;
84}
85
86static int
87socks_auth(int fd, int method, socks_opts * sob, time_t tmo)
88{
89uschar * s;
90int len, i, j;
91
92switch(method)
93 {
94 default:
95 log_write(0, LOG_MAIN|LOG_PANIC,
96 "Unrecognised socks auth method %d", method);
97 return FAIL;
98 case AUTH_NONE:
99 return OK;
100 case AUTH_NAME:
101 HDEBUG(D_transport|D_acl|D_v) debug_printf(" socks auth NAME '%s' '%s'\n",
102 sob->auth_name, sob->auth_pwd);
103 i = Ustrlen(sob->auth_name);
104 j = Ustrlen(sob->auth_pwd);
105 s = string_sprintf("%c%c%.255s%c%.255s", AUTH_NAME_VER,
106 i, sob->auth_name, j, sob->auth_pwd);
107 len = i + j + 3;
108 HDEBUG(D_transport|D_acl|D_v)
109 {
110 int i;
111 debug_printf(" SOCKS>>");
112 for (i = 0; i<len; i++) debug_printf(" %02x", s[i]);
113 debug_printf("\n");
114 }
115 if ( send(fd, s, len, 0) < 0
116 || !fd_ready(fd, tmo-time(NULL))
117 || read(fd, s, 2) != 2
118 )
119 return FAIL;
120 HDEBUG(D_transport|D_acl|D_v)
121 debug_printf(" SOCKS<< %02x %02x\n", s[0], s[1]);
122 if (s[0] == AUTH_NAME_VER && s[1] == 0)
123 {
124 HDEBUG(D_transport|D_acl|D_v) debug_printf(" socks auth OK\n");
125 return OK;
126 }
127
128 log_write(0, LOG_MAIN|LOG_PANIC, "socks auth failed");
129 errno = EPROTO;
130 return FAIL;
131 }
132}
133
134
135
136/* Make a connection via a socks proxy
137
138Arguments:
139 host smtp target host
140 host_af address family
141 port remote tcp port number
142 interface local interface
143 tb transport
144 timeout connection timeout (zero for indefinite)
145
146Return value:
147 0 on success; -1 on failure, with errno set
148*/
149
150int
151socks_sock_connect(host_item * host, int host_af, int port, uschar * interface,
152 transport_instance * tb, int timeout)
153
154{
155smtp_transport_options_block * ob =
156 (smtp_transport_options_block *)tb->options_block;
157const uschar * proxy_list;
158const uschar * proxy_spec;
159int sep = 0;
160int fd;
161time_t tmo;
162const uschar * state;
163uschar buf[24];
164
165if (!timeout) timeout = 24*60*60; /* use 1 day for "indefinite" */
166tmo = time(NULL) + timeout;
167
168if (!(proxy_list = expand_string(ob->socks_proxy)))
169 {
170 log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
171 tb->name);
172 return -1;
173 }
174
175/* Loop over proxy list, trying in order until one works */
176while ((proxy_spec = string_nextinlist(&proxy_list, &sep, NULL, 0)))
177 {
178 const uschar * proxy_host;
179 int subsep = -' ';
180 host_item proxy;
181 int proxy_af;
182 union sockaddr_46 sin;
183 unsigned size;
184 socks_opts sob;
185 const uschar * option;
186
187 if (!(proxy_host = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
188 {
189 /* paniclog config error */
190 return -1;
191 }
192
193 /*XXX consider global options eg. "hide socks_password = wibble" on the tpt */
194 socks_option_defaults(&sob);
195
196 /* extract any further per-proxy options */
197 while ((option = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
198 socks_option(&sob, option);
199
200 /* bodge up a host struct for the proxy */
201 proxy.address = proxy_host;
202 proxy_af = Ustrchr(proxy_host, ':') ? AF_INET6 : AF_INET;
203
204 if ((fd = smtp_sock_connect(&proxy, proxy_af, sob.port,
205 interface, tb, sob.timeout)) < 0)
206 continue;
207
208 /* Do the socks protocol stuff */
209 /* Send method-selection */
210 state = US"method select";
211 HDEBUG(D_transport|D_acl|D_v) debug_printf(" SOCKS>> 05 01 %02x\n", sob.auth_type);
212 buf[0] = 5; buf[1] = 1; buf[2] = sob.auth_type;
213 if (send(fd, buf, 3, 0) < 0)
214 goto snd_err;
215
216 /* expect method response */
217 if ( !fd_ready(fd, tmo-time(NULL))
218 || read(fd, buf, 2) != 2
219 )
220 goto rcv_err;
221 HDEBUG(D_transport|D_acl|D_v)
222 debug_printf(" SOCKS<< %02x %02x\n", buf[0], buf[1]);
223 if ( buf[0] != 5
224 || socks_auth(fd, buf[1], &sob, tmo) != OK
225 )
226 goto proxy_err;
227
228 (void) ip_addr(&sin, host_af, host->address, port);
229
230 /* send connect (ipver, ipaddr, port) */
231 buf[0] = 5; buf[1] = 1; buf[2] = 0; buf[3] = host_af == AF_INET6 ? 4 : 1;
232 if (host_af == AF_INET6)
233 {
234 memcpy(buf+4, &sin.v6.sin6_addr, sizeof(sin.v6.sin6_addr));
235 memcpy(buf+4+sizeof(sin.v6.sin6_addr),
236 &sin.v6.sin6_port, sizeof(sin.v6.sin6_port));
237 size = 4+sizeof(sin.v6.sin6_addr)+sizeof(sin.v6.sin6_port);
238 }
239 else
240 {
241 memcpy(buf+4, &sin.v4.sin_addr.s_addr, sizeof(sin.v4.sin_addr.s_addr));
242 memcpy(buf+4+sizeof(sin.v4.sin_addr.s_addr),
243 &sin.v4.sin_port, sizeof(sin.v4.sin_port));
244 size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port);
245 }
246
247 state = US"connect";
248 HDEBUG(D_transport|D_acl|D_v)
249 {
250 int i;
251 debug_printf(" SOCKS>>");
252 for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
253 debug_printf("\n");
254 }
255 if (send(fd, buf, size, 0) < 0)
256 goto snd_err;
257
258 /* expect conn-reply (success, local(ipver, addr, port))
259 of same length as conn-request, or non-success fail code */
260 if ( !fd_ready(fd, tmo-time(NULL))
261 || (size = read(fd, buf, size)) < 2
262 )
263 goto rcv_err;
264 HDEBUG(D_transport|D_acl|D_v)
265 {
266 int i;
267 debug_printf(" SOCKS>>");
268 for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
269 debug_printf("\n");
270 }
271 if ( buf[0] != 5
272 || buf[1] != 0
273 )
274 goto proxy_err;
275
276 /*XXX log proxy outbound addr/port? */
277 HDEBUG(D_transport|D_acl|D_v)
278 debug_printf(" proxy farside local: [%s]:%d\n",
279 host_ntoa(buf[3] == 4 ? AF_INET6 : AF_INET, buf+4, NULL, NULL),
280 ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8)))));
281
282 return fd;
283 }
284
285HDEBUG(D_transport|D_acl|D_v) debug_printf(" no proxies left\n");
286return -1;
287
288snd_err:
289 HDEBUG(D_transport|D_acl|D_v) debug_printf(" proxy snd_err %s: %s\n", state, strerror(errno));
290 return -1;
291
292proxy_err:
293 {
294 struct socks_err * se =
295 buf[1] > nelem(socks_errs) ? NULL : socks_errs + buf[1];
296 HDEBUG(D_transport|D_acl|D_v)
297 debug_printf(" proxy %s: %s\n", state, se ? se->reason : US"unknown error code received");
298 errno = se ? se->errcode : EPROTO;
299 }
300
301rcv_err:
302 HDEBUG(D_transport|D_acl|D_v) debug_printf(" proxy rcv_err %s: %s\n", state, strerror(errno));
303 if (!errno) errno = EPROTO;
304 else if (errno == ENOENT) errno = ECONNABORTED;
305 return -1;
306}
307
308#endif /* entire file */
309/* vi: aw ai sw=2
310*/