diff options
author | Damien Miller <djm@cvs.openbsd.org> | 2018-02-23 02:34:34 +0000 |
---|---|---|
committer | Damien Miller <djm@cvs.openbsd.org> | 2018-02-23 02:34:34 +0000 |
commit | 9e9c08bc2b557993a3ea6391b315cd7541c6b38f (patch) | |
tree | 74a9a61b69752e8a2b8735577ecf04bd7b97108b /usr.bin/ssh/sshconnect.c | |
parent | 936e9657c96fd39aee58cfa643359b686d9b8418 (diff) |
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@
Diffstat (limited to 'usr.bin/ssh/sshconnect.c')
-rw-r--r-- | usr.bin/ssh/sshconnect.c | 142 |
1 files changed, 121 insertions, 21 deletions
diff --git a/usr.bin/ssh/sshconnect.c b/usr.bin/ssh/sshconnect.c index ede82c06e91..2ea0d007ee2 100644 --- a/usr.bin/ssh/sshconnect.c +++ b/usr.bin/ssh/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.294 2018/02/10 09:25:35 djm Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.295 2018/02/23 02:34:33 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -19,6 +19,7 @@ #include <sys/socket.h> #include <sys/time.h> +#include <net/if.h> #include <netinet/in.h> #include <ctype.h> @@ -33,6 +34,7 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <ifaddrs.h> #include "xmalloc.h" #include "ssh.h" @@ -259,13 +261,78 @@ ssh_kill_proxy_command(void) } /* + * Search a interface address list (returned from getifaddrs(3)) for an + * address that matches the desired address family on the specifed interface. + * Returns 0 and fills in *resultp and *rlenp on success. Returns -1 on failure. + */ +static int +check_ifaddrs(const char *ifname, int af, const struct ifaddrs *ifaddrs, + struct sockaddr_storage *resultp, socklen_t *rlenp) +{ + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa; + struct in6_addr *v6addr; + const struct ifaddrs *ifa; + int allow_local; + + /* + * Prefer addresses that are not loopback or linklocal, but use them + * if nothing else matches. + */ + for (allow_local = 0; allow_local < 2; allow_local++) { + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL || + (ifa->ifa_flags & IFF_UP) == 0 || + ifa->ifa_addr->sa_family != af || + strcmp(ifa->ifa_name, options.bind_interface) != 0) + continue; + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + sa = (struct sockaddr_in *)ifa->ifa_addr; + if (!allow_local && sa->sin_addr.s_addr == + htonl(INADDR_LOOPBACK)) + continue; + if (*rlenp < sizeof(struct sockaddr_in)) { + error("%s: v4 addr doesn't fit", + __func__); + return -1; + } + *rlenp = sizeof(struct sockaddr_in); + memcpy(resultp, sa, *rlenp); + return 0; + case AF_INET6: + sa6 = (struct sockaddr_in6 *)ifa->ifa_addr; + v6addr = &sa6->sin6_addr; + if (!allow_local && + (IN6_IS_ADDR_LINKLOCAL(v6addr) || + IN6_IS_ADDR_LOOPBACK(v6addr))) + continue; + if (*rlenp < sizeof(struct sockaddr_in6)) { + error("%s: v6 addr doesn't fit", + __func__); + return -1; + } + *rlenp = sizeof(struct sockaddr_in6); + memcpy(resultp, sa6, *rlenp); + return 0; + } + } + } + return -1; +} + +/* * Creates a (possibly privileged) socket for use as the ssh connection. */ static int ssh_create_socket(int privileged, struct addrinfo *ai) { - int sock, r, gaierr; + int sock, r, oerrno; + struct sockaddr_storage bindaddr; + socklen_t bindaddrlen = 0; struct addrinfo hints, *res = NULL; + struct ifaddrs *ifaddrs = NULL; + char ntop[NI_MAXHOST]; sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) { @@ -275,48 +342,81 @@ ssh_create_socket(int privileged, struct addrinfo *ai) fcntl(sock, F_SETFD, FD_CLOEXEC); /* Bind the socket to an alternative local IP address */ - if (options.bind_address == NULL && !privileged) + if (options.bind_address == NULL && options.bind_interface == NULL && + !privileged) return sock; - if (options.bind_address) { + if (options.bind_address != NULL) { memset(&hints, 0, sizeof(hints)); hints.ai_family = ai->ai_family; hints.ai_socktype = ai->ai_socktype; hints.ai_protocol = ai->ai_protocol; hints.ai_flags = AI_PASSIVE; - gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res); - if (gaierr) { + if ((r = getaddrinfo(options.bind_address, NULL, + &hints, &res)) != 0) { error("getaddrinfo: %s: %s", options.bind_address, - ssh_gai_strerror(gaierr)); - close(sock); - return -1; + ssh_gai_strerror(r)); + goto fail; + } + if (res == NULL) + error("getaddrinfo: no addrs"); + goto fail; + if (res->ai_addrlen > sizeof(bindaddr)) { + error("%s: addr doesn't fit", __func__); + goto fail; + } + memcpy(&bindaddr, res->ai_addr, res->ai_addrlen); + bindaddrlen = res->ai_addrlen; + } else if (options.bind_interface != NULL) { + if ((r = getifaddrs(&ifaddrs)) != 0) { + error("getifaddrs: %s: %s", options.bind_interface, + strerror(errno)); + goto fail; + } + bindaddrlen = sizeof(bindaddr); + if (check_ifaddrs(options.bind_interface, ai->ai_family, + ifaddrs, &bindaddr, &bindaddrlen) != 0) { + logit("getifaddrs: %s: no suitable addresses", + options.bind_interface); + goto fail; } } + if ((r = getnameinfo((struct sockaddr *)&bindaddr, bindaddrlen, + ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST)) != 0) { + error("%s: getnameinfo failed: %s", __func__, + ssh_gai_strerror(r)); + goto fail; + } /* * If we are running as root and want to connect to a privileged * port, bind our own socket to a privileged port. */ if (privileged) { PRIV_START; - r = bindresvport_sa(sock, res ? res->ai_addr : NULL); + r = bindresvport_sa(sock, + bindaddrlen == 0 ? NULL : (struct sockaddr *)&bindaddr); + oerrno = errno; PRIV_END; if (r < 0) { - error("bindresvport_sa: af=%d %s", ai->ai_family, - strerror(errno)); + error("bindresvport_sa %s: %s", ntop, + strerror(oerrno)); goto fail; } - } else { - if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { - error("bind: %s: %s", options.bind_address, - strerror(errno)); - fail: - close(sock); - freeaddrinfo(res); - return -1; - } + } else if (bind(sock, (struct sockaddr *)&bindaddr, bindaddrlen) != 0) { + error("bind %s: %s", ntop, strerror(errno)); + goto fail; } + debug("%s: bound to %s", __func__, ntop); + /* success */ + goto out; +fail: + close(sock); + sock = -1; + out: if (res != NULL) freeaddrinfo(res); + if (ifaddrs != NULL) + freeifaddrs(ifaddrs); return sock; } |