Fix non-ipv6 socks compile
[exim.git] / src / src / transports / smtp_socks.c
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 HAVE_IPV6
233   if (host_af == AF_INET6)
234     {
235     memcpy(buf+4, &sin.v6.sin6_addr,       sizeof(sin.v6.sin6_addr));
236     memcpy(buf+4+sizeof(sin.v6.sin6_addr),
237       &sin.v6.sin6_port, sizeof(sin.v6.sin6_port));
238     size = 4+sizeof(sin.v6.sin6_addr)+sizeof(sin.v6.sin6_port);
239     }
240   else
241 #endif
242     {
243     memcpy(buf+4, &sin.v4.sin_addr.s_addr, sizeof(sin.v4.sin_addr.s_addr));
244     memcpy(buf+4+sizeof(sin.v4.sin_addr.s_addr),
245       &sin.v4.sin_port, sizeof(sin.v4.sin_port));
246     size = 4+sizeof(sin.v4.sin_addr.s_addr)+sizeof(sin.v4.sin_port);
247     }
248
249   state = US"connect";
250   HDEBUG(D_transport|D_acl|D_v)
251     {
252     int i;
253     debug_printf("  SOCKS>>");
254     for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
255     debug_printf("\n");
256     }
257   if (send(fd, buf, size, 0) < 0)
258     goto snd_err;
259
260   /* expect conn-reply (success, local(ipver, addr, port))
261   of same length as conn-request, or non-success fail code */
262   if (  !fd_ready(fd, tmo-time(NULL))
263      || (size = read(fd, buf, size)) < 2
264      )
265     goto rcv_err;
266   HDEBUG(D_transport|D_acl|D_v)
267     {
268     int i;
269     debug_printf("  SOCKS>>");
270     for (i = 0; i<size; i++) debug_printf(" %02x", buf[i]);
271     debug_printf("\n");
272     }
273   if (  buf[0] != 5
274      || buf[1] != 0
275      )
276     goto proxy_err;
277
278   /*XXX log proxy outbound addr/port? */
279   HDEBUG(D_transport|D_acl|D_v)
280     debug_printf("  proxy farside local: [%s]:%d\n",
281       host_ntoa(buf[3] == 4 ? AF_INET6 : AF_INET, buf+4, NULL, NULL),
282       ntohs(*((uint16_t *)(buf + (buf[3] == 4 ? 20 : 8)))));
283
284   return fd;
285   }
286
287 HDEBUG(D_transport|D_acl|D_v) debug_printf("  no proxies left\n");
288 return -1;
289
290 snd_err:
291   HDEBUG(D_transport|D_acl|D_v) debug_printf("  proxy snd_err %s: %s\n", state, strerror(errno));
292   return -1;
293
294 proxy_err:
295   {
296   struct socks_err * se =
297     buf[1] > nelem(socks_errs) ? NULL : socks_errs + buf[1];
298   HDEBUG(D_transport|D_acl|D_v)
299     debug_printf("  proxy %s: %s\n", state, se ? se->reason : US"unknown error code received");
300   errno = se ? se->errcode : EPROTO;
301   }
302
303 rcv_err:
304   HDEBUG(D_transport|D_acl|D_v) debug_printf("  proxy rcv_err %s: %s\n", state, strerror(errno));
305   if (!errno) errno = EPROTO;
306   else if (errno == ENOENT) errno = ECONNABORTED;
307   return -1;
308 }
309
310 #endif  /* entire file */
311 /* vi: aw ai sw=2
312 */