add a "match localnetwork" predicate.
authordjm <djm@openbsd.org>
Mon, 17 Jul 2023 04:04:36 +0000 (04:04 +0000)
committerdjm <djm@openbsd.org>
Mon, 17 Jul 2023 04:04:36 +0000 (04:04 +0000)
This allows matching on the addresses of available network interfaces
and may be used to vary the effective client configuration based on
network location (e.g. to use a ProxyJump when not on a particular
network).

ok markus@

usr.bin/ssh/readconf.c
usr.bin/ssh/ssh_config.5

index de70683..9895524 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.377 2023/06/21 05:10:26 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.378 2023/07/17 04:04:36 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -18,6 +18,7 @@
 #include <sys/wait.h>
 #include <sys/un.h>
 
+#include <net/if.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 
@@ -25,6 +26,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <glob.h>
+#include <ifaddrs.h>
 #include <netdb.h>
 #include <paths.h>
 #include <pwd.h>
@@ -562,6 +564,60 @@ execute_in_shell(const char *cmd)
        return WEXITSTATUS(status);
 }
 
+/*
+ * Check whether a local network interface address appears in CIDR pattern-
+ * list 'addrlist'. Returns 1 if matched or 0 otherwise.
+ */
+static int
+check_match_ifaddrs(const char *addrlist)
+{
+       struct ifaddrs *ifa, *ifaddrs = NULL;
+       int r, found = 0;
+       char addr[NI_MAXHOST];
+       socklen_t salen;
+
+       if (getifaddrs(&ifaddrs) != 0) {
+               error("match localnetwork: getifaddrs failed: %s",
+                   strerror(errno));
+               return 0;
+       }
+       for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+               if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL ||
+                   (ifa->ifa_flags & IFF_UP) == 0)
+                       continue;
+               switch (ifa->ifa_addr->sa_family) {
+               case AF_INET:
+                       salen = sizeof(struct sockaddr_in);
+                       break;
+               case AF_INET6:
+                       salen = sizeof(struct sockaddr_in6);
+                       break;
+               case AF_LINK:
+                       /* ignore */
+                       continue;
+               default:
+                       debug2_f("interface %s: unsupported address family %d",
+                           ifa->ifa_name, ifa->ifa_addr->sa_family);
+                       continue;
+               }
+               if ((r = getnameinfo(ifa->ifa_addr, salen, addr, sizeof(addr),
+                   NULL, 0, NI_NUMERICHOST)) != 0) {
+                       debug2_f("interface %s getnameinfo failed: %s",
+                           ifa->ifa_name, gai_strerror(r));
+                       continue;
+               }
+               debug3_f("interface %s addr %s", ifa->ifa_name, addr);
+               if (addr_match_cidr_list(addr, addrlist) == 1) {
+                       debug3_f("matched interface %s: address %s in %s",
+                           ifa->ifa_name, addr, addrlist);
+                       found = 1;
+                       break;
+               }
+       }
+       freeifaddrs(ifaddrs);
+       return found;
+}
+
 /*
  * Parse and execute a Match directive.
  */
@@ -666,6 +722,15 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw,
                        r = match_pattern_list(pw->pw_name, arg, 0) == 1;
                        if (r == (negate ? 1 : 0))
                                this_result = result = 0;
+               } else if (strcasecmp(attrib, "localnetwork") == 0) {
+                       if (addr_match_cidr_list(NULL, arg) == -1) {
+                               /* Error already printed */
+                               result = -1;
+                               goto out;
+                       }
+                       r = check_match_ifaddrs(arg) == 1;
+                       if (r == (negate ? 1 : 0))
+                               this_result = result = 0;
                } else if (strcasecmp(attrib, "exec") == 0) {
                        char *conn_hash_hex, *keyalias;
 
@@ -719,9 +784,11 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw,
                        result = -1;
                        goto out;
                }
-               debug3("%.200s line %d: %smatched '%s \"%.100s\"' ",
-                   filename, linenum, this_result ? "": "not ",
-                   oattrib, criteria);
+               debug3("%.200s line %d: %smatched '%s%s%.100s%s' ",
+                   filename, linenum, this_result ? "": "not ", oattrib,
+                   criteria == NULL ? "" : " \"",
+                   criteria == NULL ? "" : criteria,
+                   criteria == NULL ? "" : "\"");
                free(criteria);
        }
        if (attributes == 0) {
index 972cafe..65ba886 100644 (file)
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.380 2023/03/27 03:56:11 dtucker Exp $
-.Dd $Mdocdate: March 27 2023 $
+.\" $OpenBSD: ssh_config.5,v 1.381 2023/07/17 04:04:36 djm Exp $
+.Dd $Mdocdate: July 17 2023 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -141,6 +141,7 @@ The available criteria keywords are:
 .Cm canonical ,
 .Cm final ,
 .Cm exec ,
+.Cm localnetwork ,
 .Cm host ,
 .Cm originalhost ,
 .Cm user ,
@@ -195,6 +196,17 @@ accept the tokens described in the
 .Sx TOKENS
 section.
 .Pp
+The
+.Cm localnetwork
+keyword matches the addresses of active local network interfaces against the
+supplied list of networks in CIDR format.
+This may be convenient for varying the effective configuration on devices that
+roam between networks.
+Note that network address is not a trustworthy criteria in many
+situations (e.g. when the network is automatically configured using DHCP)
+and so caution should be applied if using it to control security-sensitive
+configuration.
+.Pp
 The other keywords' criteria must be single entries or comma-separated
 lists and may use the wildcard and negation operators described in the
 .Sx PATTERNS