1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) Jeremy Harris 2015 */
6 /* See the file NOTICE for conditions of use and distribution. */
8 /* SOCKS version 5 proxy, client-mode */
13 #ifdef SUPPORT_SOCKS /* entire file */
16 # define nelem(arr) (sizeof(arr)/sizeof(*arr))
21 #define SOCKS_PORT 1080
22 #define SOCKS_TIMEOUT 5
23 #define SOCKS_WEIGHT 1
24 #define SOCKS_PRIORITY 1
27 #define AUTH_NAME 2 /* user/password per RFC 1929 */
28 #define AUTH_NAME_VER 1
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
}
49 const uschar
* proxy_host
;
50 uschar auth_type
; /* RFC 1928 encoding */
51 const uschar
* auth_name
;
52 const uschar
* auth_pwd
;
61 socks_option_defaults(socks_opts
* sob
)
63 sob
->proxy_host
= NULL
;
64 sob
->auth_type
= AUTH_NONE
;
65 sob
->auth_name
= 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
;
75 socks_option(socks_opts
* sob
, const uschar
* opt
)
79 if (Ustrncmp(opt
, "auth=", 5) == 0)
82 if (Ustrcmp(opt
, "none") == 0) sob
->auth_type
= AUTH_NONE
;
83 else if (Ustrcmp(opt
, "name") == 0) sob
->auth_type
= AUTH_NAME
;
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(opt
+ 5);
91 else if (Ustrncmp(opt
, "tmo=", 4) == 0)
92 sob
->timeout
= atoi(opt
+ 4);
93 else if (Ustrncmp(opt
, "pri=", 4) == 0)
94 sob
->priority
= atoi(opt
+ 4);
95 else if (Ustrncmp(opt
, "weight=", 7) == 0)
96 sob
->weight
= atoi(opt
+ 7);
101 socks_auth(int fd
, int method
, socks_opts
* sob
, time_t tmo
)
109 log_write(0, LOG_MAIN
|LOG_PANIC
,
110 "Unrecognised socks auth method %d", method
);
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
);
122 HDEBUG(D_transport
|D_acl
|D_v
)
125 debug_printf_indent(" SOCKS>>");
126 for (i
= 0; i
<len
; i
++) debug_printf(" %02x", s
[i
]);
129 if ( send(fd
, s
, len
, 0) < 0
130 || !fd_ready(fd
, tmo
-time(NULL
))
131 || read(fd
, s
, 2) != 2
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)
138 HDEBUG(D_transport
|D_acl
|D_v
) debug_printf_indent(" socks auth OK\n");
142 log_write(0, LOG_MAIN
|LOG_PANIC
, "socks auth failed");
150 /* Find a suitable proxy to use from the list.
151 Possible common code with spamd_get_server() ?
153 Return: index into proxy spec array, or -1
157 socks_get_proxy(socks_opts
* proxies
, unsigned nproxies
)
161 socks_opts
* lim
= &proxies
[nproxies
];
164 static BOOL srandomed
= FALSE
;
166 if (nproxies
== 1) /* shortcut, if we have only 1 server */
167 return (proxies
[0].is_failed
? -1 : 0);
173 gettimeofday(&tv
, NULL
);
174 srandom((unsigned int)(tv
.tv_usec
/1000));
178 /* scan for highest pri */
179 for (pri
= 0, sd
= proxies
; sd
< lim
; sd
++)
180 if (!sd
->is_failed
&& sd
->priority
> pri
)
183 /* get sum of weights at this pri */
184 for (weights
= 0, sd
= proxies
; sd
< lim
; sd
++)
185 if (!sd
->is_failed
&& sd
->priority
== pri
)
186 weights
+= sd
->weight
;
187 if (weights
== 0) /* all servers failed */
190 for (rnd
= random() % weights
, i
= 0; i
< nproxies
; i
++)
193 if (!sd
->is_failed
&& sd
->priority
== pri
)
194 if ((rnd
-= sd
->weight
) <= 0)
198 log_write(0, LOG_MAIN
|LOG_PANIC
,
199 "%s unknown error (memory/cpu corruption?)", __FUNCTION__
);
205 /* Make a connection via a socks proxy
208 host smtp target host
209 host_af address family
210 port remote tcp port number
211 interface local interface
213 timeout connection timeout (zero for indefinite)
216 0 on success; -1 on failure, with errno set
220 socks_sock_connect(host_item
* host
, int host_af
, int port
, uschar
* interface
,
221 transport_instance
* tb
, int timeout
)
223 smtp_transport_options_block
* ob
=
224 (smtp_transport_options_block
*)tb
->options_block
;
225 const uschar
* proxy_list
;
226 const uschar
* proxy_spec
;
230 const uschar
* state
;
232 socks_opts proxies
[32]; /* max #proxies handled */
238 if (!timeout
) timeout
= 24*60*60; /* use 1 day for "indefinite" */
239 tmo
= time(NULL
) + timeout
;
241 if (!(proxy_list
= expand_string(ob
->socks_proxy
)))
243 log_write(0, LOG_MAIN
|LOG_PANIC
, "Bad expansion for socks_proxy in %s",
248 /* Read proxy list */
251 nproxies
< nelem(proxies
)
252 && (proxy_spec
= string_nextinlist(&proxy_list
, &sep
, NULL
, 0));
256 const uschar
* option
;
258 socks_option_defaults(sob
= &proxies
[nproxies
]);
260 if (!(sob
->proxy_host
= string_nextinlist(&proxy_spec
, &subsep
, NULL
, 0)))
262 /* paniclog config error */
266 /*XXX consider global options eg. "hide socks_password = wibble" on the tpt */
267 /* extract any further per-proxy options */
268 while ((option
= string_nextinlist(&proxy_spec
, &subsep
, NULL
, 0)))
269 socks_option(sob
, option
);
272 /* Set up the socks protocol method-selection message,
273 for sending on connection */
275 state
= US
"method select";
276 buf
[0] = 5; buf
[1] = 1; buf
[2] = sob
->auth_type
;
277 early_data
.data
= buf
;
280 /* Try proxies until a connection succeeds */
288 if ((idx
= socks_get_proxy(proxies
, nproxies
)) < 0)
290 HDEBUG(D_transport
|D_acl
|D_v
) debug_printf_indent(" no proxies left\n");
296 /* bodge up a host struct for the proxy */
297 proxy
.address
= proxy
.name
= sob
->proxy_host
;
298 proxy_af
= Ustrchr(sob
->proxy_host
, ':') ? AF_INET6
: AF_INET
;
300 if ((fd
= smtp_sock_connect(&proxy
, proxy_af
, sob
->port
,
301 interface
, tb
, sob
->timeout
, &early_data
)) >= 0)
303 proxy_local_address
= string_copy(proxy
.address
);
304 proxy_local_port
= sob
->port
;
308 log_write(0, LOG_MAIN
, "%s: %s", __FUNCTION__
, strerror(errno
));
309 sob
->is_failed
= TRUE
;
312 /* Do the socks protocol stuff */
314 HDEBUG(D_transport
|D_acl
|D_v
) debug_printf_indent(" SOCKS>> 05 01 %02x\n", sob
->auth_type
);
316 /* expect method response */
318 if ( !fd_ready(fd
, tmo
-time(NULL
))
319 || read(fd
, buf
, 2) != 2
322 HDEBUG(D_transport
|D_acl
|D_v
)
323 debug_printf_indent(" SOCKS<< %02x %02x\n", buf
[0], buf
[1]);
325 || socks_auth(fd
, buf
[1], sob
, tmo
) != OK
330 union sockaddr_46 sin
;
331 (void) ip_addr(&sin
, host_af
, host
->address
, port
);
333 /* send connect (ipver, ipaddr, port) */
335 buf
[0] = 5; buf
[1] = 1; buf
[2] = 0; buf
[3] = host_af
== AF_INET6
? 4 : 1;
337 if (host_af
== AF_INET6
)
339 memcpy(buf
+4, &sin
.v6
.sin6_addr
, sizeof(sin
.v6
.sin6_addr
));
340 memcpy(buf
+4+sizeof(sin
.v6
.sin6_addr
),
341 &sin
.v6
.sin6_port
, sizeof(sin
.v6
.sin6_port
));
342 size
= 4+sizeof(sin
.v6
.sin6_addr
)+sizeof(sin
.v6
.sin6_port
);
347 memcpy(buf
+4, &sin
.v4
.sin_addr
.s_addr
, sizeof(sin
.v4
.sin_addr
.s_addr
));
348 memcpy(buf
+4+sizeof(sin
.v4
.sin_addr
.s_addr
),
349 &sin
.v4
.sin_port
, sizeof(sin
.v4
.sin_port
));
350 size
= 4+sizeof(sin
.v4
.sin_addr
.s_addr
)+sizeof(sin
.v4
.sin_port
);
355 HDEBUG(D_transport
|D_acl
|D_v
)
358 debug_printf_indent(" SOCKS>>");
359 for (i
= 0; i
<size
; i
++) debug_printf(" %02x", buf
[i
]);
362 if (send(fd
, buf
, size
, 0) < 0)
365 /* expect conn-reply (success, local(ipver, addr, port))
366 of same length as conn-request, or non-success fail code */
368 if ( !fd_ready(fd
, tmo
-time(NULL
))
369 || (size
= read(fd
, buf
, size
)) < 2
372 HDEBUG(D_transport
|D_acl
|D_v
)
375 debug_printf_indent(" SOCKS>>");
376 for (i
= 0; i
<size
; i
++) debug_printf(" %02x", buf
[i
]);
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
;
389 HDEBUG(D_transport
|D_acl
|D_v
)
390 debug_printf_indent(" proxy farside: [%s]:%d\n", proxy_external_address
, proxy_external_port
);
395 HDEBUG(D_transport
|D_acl
|D_v
) debug_printf_indent(" proxy snd_err %s: %s\n", state
, strerror(errno
));
400 struct socks_err
* se
=
401 buf
[1] > nelem(socks_errs
) ? NULL
: socks_errs
+ buf
[1];
402 HDEBUG(D_transport
|D_acl
|D_v
)
403 debug_printf_indent(" proxy %s: %s\n", state
, se
? se
->reason
: US
"unknown error code received");
404 errno
= se
? se
->errcode
: EPROTO
;
408 HDEBUG(D_transport
|D_acl
|D_v
) debug_printf_indent(" proxy rcv_err %s: %s\n", state
, strerror(errno
));
409 if (!errno
) errno
= EPROTO
;
410 else if (errno
== ENOENT
) errno
= ECONNABORTED
;
414 #endif /* entire file */