summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2018-02-23 02:34:33 +0000
committerDamien Miller <djm@mindrot.org>2018-02-23 13:37:49 +1100
commitac2e3026bbee1367e4cda34765d1106099be3287 (patch)
tree83d0a8e3b1edcc01b087feb6ea98d67ec8607179
parentfcdb9d777839a3fa034b3bc3067ba8c1f6886679 (diff)
upstream: Add BindInterface ssh_config directive and -B
command-line argument to ssh(1) that directs it to bind its outgoing connection to the address of the specified network interface. BindInterface prefers to use addresses that aren't loopback or link- local, but will fall back to those if no other addresses of the required family are available on that interface. Based on patch by Mike Manning in bz#2820, ok dtucker@ OpenBSD-Commit-ID: c5064d285c2851f773dd736a2c342aa384fbf713
-rw-r--r--readconf.c11
-rw-r--r--readconf.h3
-rw-r--r--ssh.111
-rw-r--r--ssh.c21
-rw-r--r--ssh_config.511
-rw-r--r--sshconnect.c142
6 files changed, 162 insertions, 37 deletions
diff --git a/readconf.c b/readconf.c
index 10b57bd45..56bff850a 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: readconf.c,v 1.281 2017/12/05 23:59:47 dtucker Exp $ */ 1/* $OpenBSD: readconf.c,v 1.282 2018/02/23 02:34:33 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -156,7 +156,7 @@ typedef enum {
156 oPubkeyAuthentication, 156 oPubkeyAuthentication,
157 oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, 157 oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
158 oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, 158 oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
159 oHostKeyAlgorithms, oBindAddress, oPKCS11Provider, 159 oHostKeyAlgorithms, oBindAddress, oBindInterface, oPKCS11Provider,
160 oClearAllForwardings, oNoHostAuthenticationForLocalhost, 160 oClearAllForwardings, oNoHostAuthenticationForLocalhost,
161 oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, 161 oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
162 oAddressFamily, oGssAuthentication, oGssDelegateCreds, 162 oAddressFamily, oGssAuthentication, oGssDelegateCreds,
@@ -266,6 +266,7 @@ static struct {
266 { "preferredauthentications", oPreferredAuthentications }, 266 { "preferredauthentications", oPreferredAuthentications },
267 { "hostkeyalgorithms", oHostKeyAlgorithms }, 267 { "hostkeyalgorithms", oHostKeyAlgorithms },
268 { "bindaddress", oBindAddress }, 268 { "bindaddress", oBindAddress },
269 { "bindinterface", oBindInterface },
269 { "clearallforwardings", oClearAllForwardings }, 270 { "clearallforwardings", oClearAllForwardings },
270 { "enablesshkeysign", oEnableSSHKeysign }, 271 { "enablesshkeysign", oEnableSSHKeysign },
271 { "verifyhostkeydns", oVerifyHostKeyDNS }, 272 { "verifyhostkeydns", oVerifyHostKeyDNS },
@@ -1099,6 +1100,10 @@ parse_char_array:
1099 charptr = &options->bind_address; 1100 charptr = &options->bind_address;
1100 goto parse_string; 1101 goto parse_string;
1101 1102
1103 case oBindInterface:
1104 charptr = &options->bind_interface;
1105 goto parse_string;
1106
1102 case oPKCS11Provider: 1107 case oPKCS11Provider:
1103 charptr = &options->pkcs11_provider; 1108 charptr = &options->pkcs11_provider;
1104 goto parse_string; 1109 goto parse_string;
@@ -1800,6 +1805,7 @@ initialize_options(Options * options)
1800 options->log_level = SYSLOG_LEVEL_NOT_SET; 1805 options->log_level = SYSLOG_LEVEL_NOT_SET;
1801 options->preferred_authentications = NULL; 1806 options->preferred_authentications = NULL;
1802 options->bind_address = NULL; 1807 options->bind_address = NULL;
1808 options->bind_interface = NULL;
1803 options->pkcs11_provider = NULL; 1809 options->pkcs11_provider = NULL;
1804 options->enable_ssh_keysign = - 1; 1810 options->enable_ssh_keysign = - 1;
1805 options->no_host_authentication_for_localhost = - 1; 1811 options->no_host_authentication_for_localhost = - 1;
@@ -2509,6 +2515,7 @@ dump_client_config(Options *o, const char *host)
2509 2515
2510 /* String options */ 2516 /* String options */
2511 dump_cfg_string(oBindAddress, o->bind_address); 2517 dump_cfg_string(oBindAddress, o->bind_address);
2518 dump_cfg_string(oBindInterface, o->bind_interface);
2512 dump_cfg_string(oCiphers, o->ciphers ? o->ciphers : KEX_CLIENT_ENCRYPT); 2519 dump_cfg_string(oCiphers, o->ciphers ? o->ciphers : KEX_CLIENT_ENCRYPT);
2513 dump_cfg_string(oControlPath, o->control_path); 2520 dump_cfg_string(oControlPath, o->control_path);
2514 dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms); 2521 dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms);
diff --git a/readconf.h b/readconf.h
index 34aad83cf..f4d9e2b26 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
1/* $OpenBSD: readconf.h,v 1.124 2017/10/21 23:06:24 millert Exp $ */ 1/* $OpenBSD: readconf.h,v 1.125 2018/02/23 02:34:33 djm Exp $ */
2 2
3/* 3/*
4 * Author: Tatu Ylonen <ylo@cs.hut.fi> 4 * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -81,6 +81,7 @@ typedef struct {
81 char *user_hostfiles[SSH_MAX_HOSTS_FILES]; 81 char *user_hostfiles[SSH_MAX_HOSTS_FILES];
82 char *preferred_authentications; 82 char *preferred_authentications;
83 char *bind_address; /* local socket address for connection to sshd */ 83 char *bind_address; /* local socket address for connection to sshd */
84 char *bind_interface; /* local interface for bind address */
84 char *pkcs11_provider; /* PKCS#11 provider */ 85 char *pkcs11_provider; /* PKCS#11 provider */
85 int verify_host_key_dns; /* Verify host key using DNS */ 86 int verify_host_key_dns; /* Verify host key using DNS */
86 87
diff --git a/ssh.1 b/ssh.1
index 9de2a11bd..d754b3a41 100644
--- a/ssh.1
+++ b/ssh.1
@@ -33,8 +33,8 @@
33.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 33.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35.\" 35.\"
36.\" $OpenBSD: ssh.1,v 1.389 2017/11/03 02:29:17 djm Exp $ 36.\" $OpenBSD: ssh.1,v 1.390 2018/02/23 02:34:33 djm Exp $
37.Dd $Mdocdate: November 3 2017 $ 37.Dd $Mdocdate: February 23 2018 $
38.Dt SSH 1 38.Dt SSH 1
39.Os 39.Os
40.Sh NAME 40.Sh NAME
@@ -43,6 +43,7 @@
43.Sh SYNOPSIS 43.Sh SYNOPSIS
44.Nm ssh 44.Nm ssh
45.Op Fl 46AaCfGgKkMNnqsTtVvXxYy 45.Op Fl 46AaCfGgKkMNnqsTtVvXxYy
46.Op Fl B Ar bind_interface
46.Op Fl b Ar bind_address 47.Op Fl b Ar bind_address
47.Op Fl c Ar cipher_spec 48.Op Fl c Ar cipher_spec
48.Op Fl D Oo Ar bind_address : Oc Ns Ar port 49.Op Fl D Oo Ar bind_address : Oc Ns Ar port
@@ -124,6 +125,12 @@ authenticate using the identities loaded into the agent.
124.It Fl a 125.It Fl a
125Disables forwarding of the authentication agent connection. 126Disables forwarding of the authentication agent connection.
126.Pp 127.Pp
128.It Fl B Ar interface
129Bind to the address of
130.Ar interface
131before attempting to connect to the destination host.
132This is only useful on systems with more than one address.
133.Pp
127.It Fl b Ar bind_address 134.It Fl b Ar bind_address
128Use 135Use
129.Ar bind_address 136.Ar bind_address
diff --git a/ssh.c b/ssh.c
index 2d8f6506b..fa57290be 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssh.c,v 1.473 2018/02/13 03:36:56 djm Exp $ */ 1/* $OpenBSD: ssh.c,v 1.474 2018/02/23 02:34:33 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -201,13 +201,13 @@ static void
201usage(void) 201usage(void)
202{ 202{
203 fprintf(stderr, 203 fprintf(stderr,
204"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]\n" 204"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]\n"
205" [-D [bind_address:]port] [-E log_file] [-e escape_char]\n" 205" [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]\n"
206" [-F configfile] [-I pkcs11] [-i identity_file]\n" 206" [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]\n"
207" [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]\n" 207" [-i identity_file] [-J [user@]host[:port]] [-L address]\n"
208" [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]\n" 208" [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n"
209" [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n" 209" [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n"
210" destination [command]\n" 210" [-w local_tun[:remote_tun]] destination [command]\n"
211 ); 211 );
212 exit(255); 212 exit(255);
213} 213}
@@ -663,7 +663,7 @@ main(int ac, char **av)
663 663
664 again: 664 again:
665 while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" 665 while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx"
666 "ACD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { 666 "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) {
667 switch (opt) { 667 switch (opt) {
668 case '1': 668 case '1':
669 fatal("SSH protocol v.1 is no longer supported"); 669 fatal("SSH protocol v.1 is no longer supported");
@@ -973,6 +973,9 @@ main(int ac, char **av)
973 case 'b': 973 case 'b':
974 options.bind_address = optarg; 974 options.bind_address = optarg;
975 break; 975 break;
976 case 'B':
977 options.bind_interface = optarg;
978 break;
976 case 'F': 979 case 'F':
977 config = optarg; 980 config = optarg;
978 break; 981 break;
diff --git a/ssh_config.5 b/ssh_config.5
index a128e4f0e..bdf41371c 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,8 +33,8 @@
33.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 33.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35.\" 35.\"
36.\" $OpenBSD: ssh_config.5,v 1.266 2018/02/16 02:40:45 djm Exp $ 36.\" $OpenBSD: ssh_config.5,v 1.267 2018/02/23 02:34:33 djm Exp $
37.Dd $Mdocdate: February 16 2018 $ 37.Dd $Mdocdate: February 23 2018 $
38.Dt SSH_CONFIG 5 38.Dt SSH_CONFIG 5
39.Os 39.Os
40.Sh NAME 40.Sh NAME
@@ -254,6 +254,13 @@ The argument must be
254or 254or
255.Cm no 255.Cm no
256(the default). 256(the default).
257.It Cm BindInterface
258Use the address of the specified interface on the local machine as the
259source address of the connection.
260Note that this option does not work if
261.Cm UsePrivilegedPort
262is set to
263.Cm yes .
257.It Cm BindAddress 264.It Cm BindAddress
258Use the specified address on the local machine as the source address of 265Use the specified address on the local machine as the source address of
259the connection. 266the connection.
diff --git a/sshconnect.c b/sshconnect.c
index 6eff397d6..d9618bcf7 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: sshconnect.c,v 1.294 2018/02/10 09:25:35 djm Exp $ */ 1/* $OpenBSD: sshconnect.c,v 1.295 2018/02/23 02:34:33 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -23,6 +23,7 @@
23# include <sys/time.h> 23# include <sys/time.h>
24#endif 24#endif
25 25
26#include <net/if.h>
26#include <netinet/in.h> 27#include <netinet/in.h>
27#include <arpa/inet.h> 28#include <arpa/inet.h>
28 29
@@ -43,6 +44,7 @@
43#include <stdlib.h> 44#include <stdlib.h>
44#include <string.h> 45#include <string.h>
45#include <unistd.h> 46#include <unistd.h>
47#include <ifaddrs.h>
46 48
47#include "xmalloc.h" 49#include "xmalloc.h"
48#include "key.h" 50#include "key.h"
@@ -271,13 +273,78 @@ ssh_kill_proxy_command(void)
271} 273}
272 274
273/* 275/*
276 * Search a interface address list (returned from getifaddrs(3)) for an
277 * address that matches the desired address family on the specifed interface.
278 * Returns 0 and fills in *resultp and *rlenp on success. Returns -1 on failure.
279 */
280static int
281check_ifaddrs(const char *ifname, int af, const struct ifaddrs *ifaddrs,
282 struct sockaddr_storage *resultp, socklen_t *rlenp)
283{
284 struct sockaddr_in6 *sa6;
285 struct sockaddr_in *sa;
286 struct in6_addr *v6addr;
287 const struct ifaddrs *ifa;
288 int allow_local;
289
290 /*
291 * Prefer addresses that are not loopback or linklocal, but use them
292 * if nothing else matches.
293 */
294 for (allow_local = 0; allow_local < 2; allow_local++) {
295 for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
296 if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL ||
297 (ifa->ifa_flags & IFF_UP) == 0 ||
298 ifa->ifa_addr->sa_family != af ||
299 strcmp(ifa->ifa_name, options.bind_interface) != 0)
300 continue;
301 switch (ifa->ifa_addr->sa_family) {
302 case AF_INET:
303 sa = (struct sockaddr_in *)ifa->ifa_addr;
304 if (!allow_local && sa->sin_addr.s_addr ==
305 htonl(INADDR_LOOPBACK))
306 continue;
307 if (*rlenp < sizeof(struct sockaddr_in)) {
308 error("%s: v4 addr doesn't fit",
309 __func__);
310 return -1;
311 }
312 *rlenp = sizeof(struct sockaddr_in);
313 memcpy(resultp, sa, *rlenp);
314 return 0;
315 case AF_INET6:
316 sa6 = (struct sockaddr_in6 *)ifa->ifa_addr;
317 v6addr = &sa6->sin6_addr;
318 if (!allow_local &&
319 (IN6_IS_ADDR_LINKLOCAL(v6addr) ||
320 IN6_IS_ADDR_LOOPBACK(v6addr)))
321 continue;
322 if (*rlenp < sizeof(struct sockaddr_in6)) {
323 error("%s: v6 addr doesn't fit",
324 __func__);
325 return -1;
326 }
327 *rlenp = sizeof(struct sockaddr_in6);
328 memcpy(resultp, sa6, *rlenp);
329 return 0;
330 }
331 }
332 }
333 return -1;
334}
335
336/*
274 * Creates a (possibly privileged) socket for use as the ssh connection. 337 * Creates a (possibly privileged) socket for use as the ssh connection.
275 */ 338 */
276static int 339static int
277ssh_create_socket(int privileged, struct addrinfo *ai) 340ssh_create_socket(int privileged, struct addrinfo *ai)
278{ 341{
279 int sock, r, gaierr; 342 int sock, r, oerrno;
343 struct sockaddr_storage bindaddr;
344 socklen_t bindaddrlen = 0;
280 struct addrinfo hints, *res = NULL; 345 struct addrinfo hints, *res = NULL;
346 struct ifaddrs *ifaddrs = NULL;
347 char ntop[NI_MAXHOST];
281 348
282 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 349 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
283 if (sock < 0) { 350 if (sock < 0) {
@@ -287,48 +354,81 @@ ssh_create_socket(int privileged, struct addrinfo *ai)
287 fcntl(sock, F_SETFD, FD_CLOEXEC); 354 fcntl(sock, F_SETFD, FD_CLOEXEC);
288 355
289 /* Bind the socket to an alternative local IP address */ 356 /* Bind the socket to an alternative local IP address */
290 if (options.bind_address == NULL && !privileged) 357 if (options.bind_address == NULL && options.bind_interface == NULL &&
358 !privileged)
291 return sock; 359 return sock;
292 360
293 if (options.bind_address) { 361 if (options.bind_address != NULL) {
294 memset(&hints, 0, sizeof(hints)); 362 memset(&hints, 0, sizeof(hints));
295 hints.ai_family = ai->ai_family; 363 hints.ai_family = ai->ai_family;
296 hints.ai_socktype = ai->ai_socktype; 364 hints.ai_socktype = ai->ai_socktype;
297 hints.ai_protocol = ai->ai_protocol; 365 hints.ai_protocol = ai->ai_protocol;
298 hints.ai_flags = AI_PASSIVE; 366 hints.ai_flags = AI_PASSIVE;
299 gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res); 367 if ((r = getaddrinfo(options.bind_address, NULL,
300 if (gaierr) { 368 &hints, &res)) != 0) {
301 error("getaddrinfo: %s: %s", options.bind_address, 369 error("getaddrinfo: %s: %s", options.bind_address,
302 ssh_gai_strerror(gaierr)); 370 ssh_gai_strerror(r));
303 close(sock); 371 goto fail;
304 return -1; 372 }
373 if (res == NULL)
374 error("getaddrinfo: no addrs");
375 goto fail;
376 if (res->ai_addrlen > sizeof(bindaddr)) {
377 error("%s: addr doesn't fit", __func__);
378 goto fail;
379 }
380 memcpy(&bindaddr, res->ai_addr, res->ai_addrlen);
381 bindaddrlen = res->ai_addrlen;
382 } else if (options.bind_interface != NULL) {
383 if ((r = getifaddrs(&ifaddrs)) != 0) {
384 error("getifaddrs: %s: %s", options.bind_interface,
385 strerror(errno));
386 goto fail;
387 }
388 bindaddrlen = sizeof(bindaddr);
389 if (check_ifaddrs(options.bind_interface, ai->ai_family,
390 ifaddrs, &bindaddr, &bindaddrlen) != 0) {
391 logit("getifaddrs: %s: no suitable addresses",
392 options.bind_interface);
393 goto fail;
305 } 394 }
306 } 395 }
396 if ((r = getnameinfo((struct sockaddr *)&bindaddr, bindaddrlen,
397 ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST)) != 0) {
398 error("%s: getnameinfo failed: %s", __func__,
399 ssh_gai_strerror(r));
400 goto fail;
401 }
307 /* 402 /*
308 * If we are running as root and want to connect to a privileged 403 * If we are running as root and want to connect to a privileged
309 * port, bind our own socket to a privileged port. 404 * port, bind our own socket to a privileged port.
310 */ 405 */
311 if (privileged) { 406 if (privileged) {
312 PRIV_START; 407 PRIV_START;
313 r = bindresvport_sa(sock, res ? res->ai_addr : NULL); 408 r = bindresvport_sa(sock,
409 bindaddrlen == 0 ? NULL : (struct sockaddr *)&bindaddr);
410 oerrno = errno;
314 PRIV_END; 411 PRIV_END;
315 if (r < 0) { 412 if (r < 0) {
316 error("bindresvport_sa: af=%d %s", ai->ai_family, 413 error("bindresvport_sa %s: %s", ntop,
317 strerror(errno)); 414 strerror(oerrno));
318 goto fail; 415 goto fail;
319 } 416 }
320 } else { 417 } else if (bind(sock, (struct sockaddr *)&bindaddr, bindaddrlen) != 0) {
321 if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { 418 error("bind %s: %s", ntop, strerror(errno));
322 error("bind: %s: %s", options.bind_address, 419 goto fail;
323 strerror(errno));
324 fail:
325 close(sock);
326 freeaddrinfo(res);
327 return -1;
328 }
329 } 420 }
421 debug("%s: bound to %s", __func__, ntop);
422 /* success */
423 goto out;
424fail:
425 close(sock);
426 sock = -1;
427 out:
330 if (res != NULL) 428 if (res != NULL)
331 freeaddrinfo(res); 429 freeaddrinfo(res);
430 if (ifaddrs != NULL)
431 freeifaddrs(ifaddrs);
332 return sock; 432 return sock;
333} 433}
334 434