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