a5c4fde106d77695161ba6f8a7d6513b82244564
[exim.git] / src / src / transports / smtp_socks.c
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
28 struct 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
45 typedef 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
54 static void
55 socks_option_defaults(socks_opts * sob)
56 {
57 sob->auth_type = AUTH_NONE;
58 sob->auth_name = US"";
59 sob->auth_pwd = US"";
60 sob->port = SOCKS_PORT;
61 sob->timeout = SOCKS_TIMEOUT;
62 }
63
64 static void
65 socks_option(socks_opts * sob, const uschar * opt)
66 {
67 const uschar * s;
68
69 if (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 }
75 else if (Ustrncmp(opt, "name=", 5) == 0)
76 sob->auth_name = opt + 5;
77 else if (Ustrncmp(opt, "pass=", 5) == 0)
78 sob->auth_pwd = opt + 5;
79 else if (Ustrncmp(opt, "port=", 5) == 0)
80 sob->port = atoi(opt + 5);
81 else if (Ustrncmp(opt, "tmo=", 4) == 0)
82 sob->timeout = atoi(opt + 4);
83 return;
84 }
85
86 static int
87 socks_auth(int fd, int method, socks_opts * sob, time_t tmo)
88 {
89 uschar * s;
90 int len, i, j;
91
92 switch(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
138 Arguments:
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
146 Return value:
147 0 on success; -1 on failure, with errno set
148 */
149
150 int
151 socks_sock_connect(host_item * host, int host_af, int port, uschar * interface,
152 transport_instance * tb, int timeout)
153 {
154 smtp_transport_options_block * ob =
155 (smtp_transport_options_block *)tb->options_block;
156 const uschar * proxy_list;
157 const uschar * proxy_spec;
158 int sep = 0;
159 int fd;
160 time_t tmo;
161 const uschar * state;
162 uschar buf[24];
163
164 if (!timeout) timeout = 24*60*60; /* use 1 day for "indefinite" */
165 tmo = time(NULL) + timeout;
166
167 if (!(proxy_list = expand_string(ob->socks_proxy)))
168 {
169 log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
170 tb->name);
171 return -1;
172 }
173
174 /* Loop over proxy list, trying in order until one works */
175 while ((proxy_spec = string_nextinlist(&proxy_list, &sep, NULL, 0)))
176 {
177 const uschar * proxy_host;
178 int subsep = -' ';
179 host_item proxy;
180 int proxy_af;
181 union sockaddr_46 sin;
182 unsigned size;
183 socks_opts sob;
184 const uschar * option;
185
186 if (!(proxy_host = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
187 {
188 /* paniclog config error */
189 return -1;
190 }
191
192 /*XXX consider global options eg. "hide socks_password = wibble" on the tpt */
193 socks_option_defaults(&sob);
194
195 /* extract any further per-proxy options */
196 while ((option = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
197 socks_option(&sob, option);
198
199 /* bodge up a host struct for the proxy */
200 proxy.address = proxy_host;
201 proxy_af = Ustrchr(proxy_host, ':') ? AF_INET6 : AF_INET;
202
203 if ((fd = smtp_sock_connect(&proxy, proxy_af, sob.port,
204 interface, tb, sob.timeout)) < 0)
205 continue;
206
207 /* Do the socks protocol stuff */
208 /* Send method-selection */
209 state = US"method select";
210 HDEBUG(D_transport|D_acl|D_v) debug_printf(" SOCKS>> 05 01 %02x\n", sob.auth_type);
211 buf[0] = 5; buf[1] = 1; buf[2] = sob.auth_type;
212 if (send(fd, buf, 3, 0) < 0)
213 goto snd_err;
214
215 /* expect method response */
216 if ( !fd_ready(fd, tmo-time(NULL))
217 || read(fd, buf, 2) != 2
218 )
219 goto rcv_err;
220 HDEBUG(D_transport|D_acl|D_v)
221 debug_printf(" SOCKS<< %02x %02x\n", buf[0], buf[1]);
222 if ( buf[0] != 5
223 || socks_auth(fd, buf[1], &sob, tmo) != OK
224 )
225 goto proxy_err;
226
227 (void) ip_addr(&sin, host_af, host->address, port);
228
229 /* send connect (ipver, ipaddr, port) */
230 buf[0] = 5; buf[1] = 1; buf[2] = 0; buf[3] = host_af == AF_INET6 ? 4 : 1;
231 #if HAVE_IPV6
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 #endif
241 {
242 memcpy(buf+4, &sin.v4.sin_addr.s_addr, sizeof(sin.v4.sin_addr.s_addr));
243 memcpy(buf+4+sizeof(sin.v4.sin_addr.s_addr),
244 &sin.v4.sin_port, sizeof(sin.v4.sin_port));
245 size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port);
246 }
247
248 state = US"connect";
249 HDEBUG(D_transport|D_acl|D_v)
250 {
251 int i;
252 debug_printf(" SOCKS>>");
253 for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
254 debug_printf("\n");
255 }
256 if (send(fd, buf, size, 0) < 0)
257 goto snd_err;
258
259 /* expect conn-reply (success, local(ipver, addr, port))
260 of same length as conn-request, or non-success fail code */
261 if ( !fd_ready(fd, tmo-time(NULL))
262 || (size = read(fd, buf, size)) < 2
263 )
264 goto rcv_err;
265 HDEBUG(D_transport|D_acl|D_v)
266 {
267 int i;
268 debug_printf(" SOCKS>>");
269 for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
270 debug_printf("\n");
271 }
272 if ( buf[0] != 5
273 || buf[1] != 0
274 )
275 goto proxy_err;
276
277 /*XXX log proxy outbound addr/port? */
278 HDEBUG(D_transport|D_acl|D_v)
279 debug_printf(" proxy farside local: [%s]:%d\n",
280 host_ntoa(buf[3] == 4 ? AF_INET6 : AF_INET, buf+4, NULL, NULL),
281 ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8)))));
282
283 return fd;
284 }
285
286 HDEBUG(D_transport|D_acl|D_v) debug_printf(" no proxies left\n");
287 return -1;
288
289 snd_err:
290 HDEBUG(D_transport|D_acl|D_v) debug_printf(" proxy snd_err %s: %s\n", state, strerror(errno));
291 return -1;
292
293 proxy_err:
294 {
295 struct socks_err * se =
296 buf[1] > nelem(socks_errs) ? NULL : socks_errs + buf[1];
297 HDEBUG(D_transport|D_acl|D_v)
298 debug_printf(" proxy %s: %s\n", state, se ? se->reason : US"unknown error code received");
299 errno = se ? se->errcode : EPROTO;
300 }
301
302 rcv_err:
303 HDEBUG(D_transport|D_acl|D_v) debug_printf(" proxy rcv_err %s: %s\n", state, strerror(errno));
304 if (!errno) errno = EPROTO;
305 else if (errno == ENOENT) errno = ECONNABORTED;
306 return -1;
307 }
308
309 #endif /* entire file */
310 /* vi: aw ai sw=2
311 */