summaryrefslogtreecommitdiff
path: root/sshconnect.c
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 /sshconnect.c
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
Diffstat (limited to 'sshconnect.c')
-rw-r--r--sshconnect.c142
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 */
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