8d4beb39ae1d4801c41d75b2ac619d495448eb3c
[exim.git] / src / src / transports / smtp_socks.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2021 - 2024 */
6 /* Copyright (c) Jeremy Harris 2015 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 /* SOCKS version 5 proxy, client-mode */
11
12 #include "../exim.h"
13 #include "smtp.h"
14
15 #ifdef SUPPORT_SOCKS /* entire file */
16
17 #ifndef nelem
18 # define nelem(arr) (sizeof(arr)/sizeof(*arr))
19 #endif
20
21
22 /* Defaults */
23 #define SOCKS_PORT      1080
24 #define SOCKS_TIMEOUT   5
25 #define SOCKS_WEIGHT    1
26 #define SOCKS_PRIORITY  1
27
28 #define AUTH_NONE       0
29 #define AUTH_NAME       2               /* user/password per RFC 1929 */
30 #define AUTH_NAME_VER   1
31
32 struct socks_err
33   {
34   uschar *      reason;
35   int           errcode;
36   } socks_errs[] =
37   {
38     {NULL, 0},
39     {US"general SOCKS server failure",          EIO},
40     {US"connection not allowed by ruleset",     EACCES},
41     {US"Network unreachable",                   ENETUNREACH},
42     {US"Host unreachable",                      EHOSTUNREACH},
43     {US"Connection refused",                    ECONNREFUSED},
44     {US"TTL expired",                           ECANCELED},
45     {US"Command not supported",                 EOPNOTSUPP},
46     {US"Address type not supported",            EAFNOSUPPORT}
47   };
48
49 typedef struct
50   {
51   const uschar *        proxy_host;
52   uschar                auth_type;      /* RFC 1928 encoding */
53   const uschar *        auth_name;
54   const uschar *        auth_pwd;
55   short                 port;
56   BOOL                  is_failed;
57   unsigned              timeout;
58   unsigned              weight;
59   unsigned              priority;
60   } socks_opts;
61
62 static void
63 socks_option_defaults(socks_opts * sob)
64 {
65 sob->proxy_host = NULL;
66 sob->auth_type =  AUTH_NONE;
67 sob->auth_name =  US"";
68 sob->auth_pwd =   US"";
69 sob->is_failed =  FALSE;
70 sob->port =       SOCKS_PORT;
71 sob->timeout =    SOCKS_TIMEOUT;
72 sob->weight =     SOCKS_WEIGHT;
73 sob->priority =   SOCKS_PRIORITY;
74 }
75
76 static void
77 socks_option(socks_opts * sob, const uschar * opt)
78 {
79 if (Ustrncmp(opt, "auth=", 5) == 0)
80   {
81   opt += 5;
82   if (Ustrcmp(opt, "none") == 0)        sob->auth_type = AUTH_NONE;
83   else if (Ustrcmp(opt, "name") == 0)   sob->auth_type = AUTH_NAME;
84   }
85 else if (Ustrncmp(opt, "name=", 5) == 0)
86   sob->auth_name = opt + 5;
87 else if (Ustrncmp(opt, "pass=", 5) == 0)
88   sob->auth_pwd = opt + 5;
89 else if (Ustrncmp(opt, "port=", 5) == 0)
90   sob->port = atoi(CCS opt + 5);
91 else if (Ustrncmp(opt, "tmo=", 4) == 0)
92   sob->timeout = atoi(CCS opt + 4);
93 else if (Ustrncmp(opt, "pri=", 4) == 0)
94   sob->priority = atoi(CCS opt + 4);
95 else if (Ustrncmp(opt, "weight=", 7) == 0)
96   sob->weight = atoi(CCS opt + 7);
97 return;
98 }
99
100 static int
101 socks_auth(int fd, int method, socks_opts * sob, time_t tmo)
102 {
103 uschar * s;
104 int len, i, j;
105
106 switch(method)
107   {
108   default:
109     log_write(0, LOG_MAIN|LOG_PANIC,
110       "Unrecognised socks auth method %d", method);
111     return FAIL;
112   case AUTH_NONE:
113     return OK;
114   case AUTH_NAME:
115     HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  socks auth NAME '%s' '%s'\n",
116       sob->auth_name, sob->auth_pwd);
117     i = Ustrlen(sob->auth_name);
118     j = Ustrlen(sob->auth_pwd);
119     s = string_sprintf("%c%c%.255s%c%.255s", AUTH_NAME_VER,
120       i, sob->auth_name, j, sob->auth_pwd);
121     len = i + j + 3;
122     HDEBUG(D_transport|D_acl|D_v)
123       {
124       debug_printf_indent("  SOCKS>>");
125       for (int i = 0; i<len; i++) debug_printf(" %02x", s[i]);
126       debug_printf("\n");
127       }
128     if (send(fd, s, len, 0) < 0)
129       return FAIL;
130 #ifdef TCP_QUICKACK
131     (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
132 #endif
133     if (!fd_ready(fd, tmo) || read(fd, s, 2) != 2)
134       return FAIL;
135     HDEBUG(D_transport|D_acl|D_v)
136       debug_printf_indent("  SOCKS<< %02x %02x\n", s[0], s[1]);
137     if (s[0] == AUTH_NAME_VER && s[1] == 0)
138       {
139       HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  socks auth OK\n");
140       return OK;
141       }
142
143     log_write(0, LOG_MAIN|LOG_PANIC, "socks auth failed");
144     errno = EPROTO;
145     return FAIL;
146   }
147 }
148
149
150
151 /* Find a suitable proxy to use from the list.
152 Possible common code with spamd_get_server() ?
153
154 Return: index into proxy spec array, or -1
155 */
156
157 static int
158 socks_get_proxy(socks_opts * proxies, unsigned nproxies)
159 {
160 unsigned int i;
161 socks_opts * sd;
162 socks_opts * lim = &proxies[nproxies];
163 long rnd, weights;
164 unsigned pri;
165
166 if (nproxies == 1)              /* shortcut, if we have only 1 server */
167   return (proxies[0].is_failed ? -1 : 0);
168
169 /* scan for highest pri */
170 for (pri = 0, sd = proxies; sd < lim; sd++)
171   if (!sd->is_failed && sd->priority > pri)
172     pri = sd->priority;
173
174 /* get sum of weights at this pri */
175 for (weights = 0, sd = proxies; sd < lim; sd++)
176   if (!sd->is_failed && sd->priority == pri)
177     weights += sd->weight;
178 if (weights == 0)       /* all servers failed */
179   return -1;
180
181 for (rnd = random_number(weights), i = 0; i < nproxies; i++)
182   {
183   sd = &proxies[i];
184   if (!sd->is_failed && sd->priority == pri)
185     if ((rnd -= sd->weight) < 0)
186       return i;
187   }
188
189 log_write(0, LOG_MAIN|LOG_PANIC,
190   "%s unknown error (memory/cpu corruption?)", __FUNCTION__);
191 return -1;
192 }
193
194
195
196 /* Make a connection via a socks proxy
197
198 Arguments:
199  sc             details for making connection: host, af, interface, transport
200  early_data     data to send down the smtp channel (once proxied)
201
202 Return value:
203  0 on success; -1 on failure, with errno set
204 */
205
206 int
207 socks_sock_connect(smtp_connect_args * sc, const blob * early_data)
208 {
209 transport_instance * tb = sc->tblock;
210 smtp_transport_options_block * ob = tb->drinst.options_block;
211 int timeout = ob->connect_timeout;
212 const uschar * proxy_list = ob->socks_proxy,    /* already expanded */
213               * proxy_spec;
214 int sep = 0;
215 int fd;
216 time_t tmo;
217 const uschar * state;
218 uschar buf[24];
219 socks_opts proxies[32];                 /* max #proxies handled */
220 unsigned nproxies;
221 socks_opts * sob = NULL;
222 unsigned size;
223 blob proxy_early_data;
224
225 if (!timeout) timeout = 24*60*60;       /* use 1 day for "indefinite" */
226 tmo = time(NULL) + timeout;
227
228 GET_OPTION("socks_proxy");
229 if (!(proxy_list = expand_string(ob->socks_proxy)))
230   {
231   log_write(0, LOG_MAIN|LOG_PANIC, "Bad expansion for socks_proxy in %s",
232     tb->drinst.name);
233   return -1;
234   }
235
236 /* Read proxy list */
237
238 for (nproxies = 0;
239         nproxies < nelem(proxies)
240      && (proxy_spec = string_nextinlist(&proxy_list, &sep, NULL, 0));
241      nproxies++)
242   {
243   int subsep = -' ';
244   const uschar * option;
245
246   socks_option_defaults(sob = &proxies[nproxies]);
247
248   if (!(sob->proxy_host = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
249     {
250     /* paniclog config error */
251     return -1;
252     }
253
254   /*XXX consider global options eg. "hide socks_password = wibble" on the tpt */
255   /* extract any further per-proxy options */
256   while ((option = string_nextinlist(&proxy_spec, &subsep, NULL, 0)))
257     socks_option(sob, option);
258   }
259 if (!sob) return -1;
260
261 /* Set up the socks protocol method-selection message,
262 for sending on connection */
263
264 state = US"method select";
265 buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
266 proxy_early_data.data = buf;
267 proxy_early_data.len = 3;
268
269 /* Try proxies until a connection succeeds */
270
271 for(;;)
272   {
273   int idx;
274   host_item proxy;
275   smtp_connect_args proxy_sc = {.sock = -1};
276
277   if ((idx = socks_get_proxy(proxies, nproxies)) < 0)
278     {
279     HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  no proxies left\n");
280     errno = EBUSY;
281     return -1;
282     }
283   sob = &proxies[idx];
284
285   /* bodge up a host struct for the proxy */
286   proxy.address = proxy.name = sob->proxy_host;
287   proxy.port = sob->port;
288
289   proxy_sc.tblock = tb;
290   proxy_sc.ob = ob;
291   proxy_sc.host = &proxy;
292   proxy_sc.host_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
293   proxy_sc.interface = sc->interface;
294
295   /*XXX we trust that the method-select command is idempotent */
296   if ((fd = smtp_sock_connect(&proxy_sc, sob->timeout, &proxy_early_data)) >= 0)
297     {
298     proxy_local_address = string_copy(proxy.address);
299     proxy_local_port = sob->port;
300     break;
301     }
302
303   log_write(0, LOG_MAIN, "%s: %s", __FUNCTION__, strerror(errno));
304   sob->is_failed = TRUE;
305   }
306
307 /* Do the socks protocol stuff */
308
309 HDEBUG(D_transport|D_acl|D_v)
310   debug_printf_indent("  SOCKS>> 05 01 %02x\n", sob->auth_type);
311
312 /* expect method response */
313
314 #ifdef TCP_QUICKACK
315 (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
316 #endif
317
318 if (  !fd_ready(fd, tmo)
319    || read(fd, buf, 2) != 2
320    )
321   goto rcv_err;
322 HDEBUG(D_transport|D_acl|D_v)
323   debug_printf_indent("  SOCKS<< %02x %02x\n", buf[0], buf[1]);
324 if (  buf[0] != 5
325    || socks_auth(fd, buf[1], sob, tmo) != OK
326    )
327   goto proxy_err;
328
329  {
330   int host_af = sc->host_af;
331   host_item * host = sc->host;
332   union sockaddr_46 sin;
333   (void) ip_addr(&sin, host_af, host->address, host->port);
334
335   /* send connect (ipver, ipaddr, port) */
336
337   buf[0] = 5; buf[1] = 1; buf[2] = 0; buf[3] = host_af == AF_INET6 ? 4 : 1;
338   #if HAVE_IPV6
339   if (host_af == AF_INET6)
340     {
341     memcpy(buf+4, &sin.v6.sin6_addr,       sizeof(sin.v6.sin6_addr));
342     memcpy(buf+4+sizeof(sin.v6.sin6_addr),
343       &sin.v6.sin6_port, sizeof(sin.v6.sin6_port));
344     size = 4+sizeof(sin.v6.sin6_addr)+sizeof(sin.v6.sin6_port);
345     }
346   else
347   #endif
348     {
349     memcpy(buf+4, &sin.v4.sin_addr.s_addr, sizeof(sin.v4.sin_addr.s_addr));
350     memcpy(buf+4+sizeof(sin.v4.sin_addr.s_addr),
351       &sin.v4.sin_port, sizeof(sin.v4.sin_port));
352     size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port);
353     }
354  }
355
356 state = US"connect";
357 HDEBUG(D_transport|D_acl|D_v)
358   {
359   debug_printf_indent("  SOCKS>>");
360   for (int i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
361   debug_printf("\n");
362   }
363 if (send(fd, buf, size, 0) < 0)
364   goto snd_err;
365
366 /* expect conn-reply (success, local(ipver, addr, port))
367 of same length as conn-request, or non-success fail code */
368
369 if (  !fd_ready(fd, tmo)
370    || (size = read(fd, buf, size)) < 2
371    )
372   goto rcv_err;
373 HDEBUG(D_transport|D_acl|D_v)
374   {
375   debug_printf_indent("  SOCKS>>");
376   for (int i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
377   debug_printf("\n");
378   }
379 if (  buf[0] != 5
380    || buf[1] != 0
381    )
382   goto proxy_err;
383
384 proxy_external_address = string_copy(
385   host_ntoa(buf[3] == 4 ? AF_INET6 : AF_INET, buf+4, NULL, NULL));
386 proxy_external_port = ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8))));
387 proxy_session = TRUE;
388
389 HDEBUG(D_transport|D_acl|D_v)
390   debug_printf_indent("  proxy farside: [%s]:%d\n", proxy_external_address, proxy_external_port);
391
392 if (early_data && early_data->data && early_data->len)
393   if (send(fd, early_data->data, early_data->len, 0) < 0)
394     {
395     int save_errno = errno;
396     HDEBUG(D_transport|D_acl|D_v)
397       {
398       debug_printf_indent("failed: %s", CUstrerror(save_errno));
399       if (save_errno == ETIMEDOUT)
400         debug_printf(" (timeout=%s)", readconf_printtime(ob->connect_timeout));
401       debug_printf("\n");
402       }
403     (void)close(fd);
404     fd= -1;
405     errno = save_errno;
406     }
407
408 return fd;
409
410 snd_err:
411   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  proxy snd_err %s: %s\n", state, strerror(errno));
412   return -1;
413
414 proxy_err:
415   {
416   struct socks_err * se =
417     buf[1] > nelem(socks_errs) ? NULL : socks_errs + buf[1];
418   HDEBUG(D_transport|D_acl|D_v)
419     debug_printf_indent("  proxy %s: %s\n", state, se ? se->reason : US"unknown error code received");
420   errno = se ? se->errcode : EPROTO;
421   }
422
423 rcv_err:
424   HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  proxy rcv_err %s: %s\n", state, strerror(errno));
425   if (!errno) errno = EPROTO;
426   else if (errno == ENOENT) errno = ECONNABORTED;
427   return -1;
428 }
429
430 #endif  /* entire file */
431 /* vi: aw ai sw=2
432 */