diff options
Diffstat (limited to 'sshconnect.c')
-rw-r--r-- | sshconnect.c | 142 |
1 files changed, 121 insertions, 21 deletions
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 | */ | ||
280 | static int | ||
281 | check_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 | */ |
276 | static int | 339 | static int |
277 | ssh_create_socket(int privileged, struct addrinfo *ai) | 340 | ssh_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; | ||
424 | fail: | ||
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 | ||