From ac2e3026bbee1367e4cda34765d1106099be3287 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Fri, 23 Feb 2018 02:34:33 +0000 Subject: 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 --- sshconnect.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 121 insertions(+), 21 deletions(-) (limited to 'sshconnect.c') diff --git a/sshconnect.c b/sshconnect.c index 6eff397d6..d9618bcf7 100644 --- a/sshconnect.c +++ b/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 * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -23,6 +23,7 @@ # include #endif +#include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include "xmalloc.h" #include "key.h" @@ -270,14 +272,79 @@ ssh_kill_proxy_command(void) kill(proxy_command_pid, SIGHUP); } +/* + * 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) { @@ -287,48 +354,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; } -- cgit v1.2.3