version 1.294, 2018/02/10 09:25:35 |
version 1.295, 2018/02/23 02:34:33 |
|
|
#include <sys/socket.h> |
#include <sys/socket.h> |
#include <sys/time.h> |
#include <sys/time.h> |
|
|
|
#include <net/if.h> |
#include <netinet/in.h> |
#include <netinet/in.h> |
|
|
#include <ctype.h> |
#include <ctype.h> |
|
|
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
#include <unistd.h> |
#include <unistd.h> |
|
#include <ifaddrs.h> |
|
|
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "ssh.h" |
#include "ssh.h" |
|
|
} |
} |
|
|
/* |
/* |
|
* 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. |
* Creates a (possibly privileged) socket for use as the ssh connection. |
*/ |
*/ |
static int |
static int |
ssh_create_socket(int privileged, struct addrinfo *ai) |
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 addrinfo hints, *res = NULL; |
|
struct ifaddrs *ifaddrs = NULL; |
|
char ntop[NI_MAXHOST]; |
|
|
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
if (sock < 0) { |
if (sock < 0) { |
|
|
fcntl(sock, F_SETFD, FD_CLOEXEC); |
fcntl(sock, F_SETFD, FD_CLOEXEC); |
|
|
/* Bind the socket to an alternative local IP address */ |
/* 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; |
return sock; |
|
|
if (options.bind_address) { |
if (options.bind_address != NULL) { |
memset(&hints, 0, sizeof(hints)); |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = ai->ai_family; |
hints.ai_family = ai->ai_family; |
hints.ai_socktype = ai->ai_socktype; |
hints.ai_socktype = ai->ai_socktype; |
hints.ai_protocol = ai->ai_protocol; |
hints.ai_protocol = ai->ai_protocol; |
hints.ai_flags = AI_PASSIVE; |
hints.ai_flags = AI_PASSIVE; |
gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res); |
if ((r = getaddrinfo(options.bind_address, NULL, |
if (gaierr) { |
&hints, &res)) != 0) { |
error("getaddrinfo: %s: %s", options.bind_address, |
error("getaddrinfo: %s: %s", options.bind_address, |
ssh_gai_strerror(gaierr)); |
ssh_gai_strerror(r)); |
close(sock); |
goto fail; |
return -1; |
|
} |
} |
|
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 |
* If we are running as root and want to connect to a privileged |
* port, bind our own socket to a privileged port. |
* port, bind our own socket to a privileged port. |
*/ |
*/ |
if (privileged) { |
if (privileged) { |
PRIV_START; |
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; |
PRIV_END; |
if (r < 0) { |
if (r < 0) { |
error("bindresvport_sa: af=%d %s", ai->ai_family, |
error("bindresvport_sa %s: %s", ntop, |
strerror(errno)); |
strerror(oerrno)); |
goto fail; |
goto fail; |
} |
} |
} else { |
} else if (bind(sock, (struct sockaddr *)&bindaddr, bindaddrlen) != 0) { |
if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { |
error("bind %s: %s", ntop, strerror(errno)); |
error("bind: %s: %s", options.bind_address, |
goto fail; |
strerror(errno)); |
|
fail: |
|
close(sock); |
|
freeaddrinfo(res); |
|
return -1; |
|
} |
|
} |
} |
|
debug("%s: bound to %s", __func__, ntop); |
|
/* success */ |
|
goto out; |
|
fail: |
|
close(sock); |
|
sock = -1; |
|
out: |
if (res != NULL) |
if (res != NULL) |
freeaddrinfo(res); |
freeaddrinfo(res); |
|
if (ifaddrs != NULL) |
|
freeifaddrs(ifaddrs); |
return sock; |
return sock; |
} |
} |
|
|