SOCKS: fix function prototype
[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)
7eb6c37c
JH
153{
154smtp_transport_options_block * ob =
155 (smtp_transport_options_block *)tb->options_block;
156const uschar * proxy_list;
157const uschar * proxy_spec;
158int sep = 0;
159int fd;
160time_t tmo;
161const uschar * state;
162uschar buf[24];
163
164if (!timeout) timeout = 24*60*60; /* use 1 day for "indefinite" */
165tmo = time(NULL) + timeout;
166
167if (!(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 */
175while ((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;
4c115efc 231#if HAVE_IPV6
7eb6c37c
JH
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
4c115efc 240#endif
7eb6c37c
JH
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
286HDEBUG(D_transport|D_acl|D_v) debug_printf(" no proxies left\n");
287return -1;
288
289snd_err:
290 HDEBUG(D_transport|D_acl|D_v) debug_printf(" proxy snd_err %s: %s\n", state, strerror(errno));
291 return -1;
292
293proxy_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
302rcv_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*/