Commit | Line | Data |
---|---|---|
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 | ||
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 | { | |
155 | smtp_transport_options_block * ob = | |
156 | (smtp_transport_options_block *)tb->options_block; | |
157 | const uschar * proxy_list; | |
158 | const uschar * proxy_spec; | |
159 | int sep = 0; | |
160 | int fd; | |
161 | time_t tmo; | |
162 | const uschar * state; | |
163 | uschar buf[24]; | |
164 | ||
165 | if (!timeout) timeout = 24*60*60; /* use 1 day for "indefinite" */ | |
166 | tmo = time(NULL) + timeout; | |
167 | ||
168 | if (!(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 */ | |
176 | while ((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 | ||
285 | HDEBUG(D_transport|D_acl|D_v) debug_printf(" no proxies left\n"); | |
286 | return -1; | |
287 | ||
288 | snd_err: | |
289 | HDEBUG(D_transport|D_acl|D_v) debug_printf(" proxy snd_err %s: %s\n", state, strerror(errno)); | |
290 | return -1; | |
291 | ||
292 | proxy_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 | ||
301 | rcv_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 | */ |