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