upstream commit

Add a ProxyJump ssh_config(5) option and corresponding -J
ssh(1) command-line flag to allow simplified indirection through a SSH
bastion or "jump host".

These options construct a proxy command that connects to the
specified jump host(s) (more than one may be specified) and uses
port-forwarding to establish a connection to the next destination.

This codifies the safest way of indirecting connections through SSH
servers and makes it easy to use.

ok markus@

Upstream-ID: fa899cb8b26d889da8f142eb9774c1ea36b04397
diff --git a/readconf.c b/readconf.c
index 9dcc383..cb2999d 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.256 2016/06/03 04:09:38 dtucker Exp $ */
+/* $OpenBSD: readconf.c,v 1.257 2016/07/15 00:24:30 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -170,7 +170,7 @@
 	oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
 	oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
 	oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes,
-	oPubkeyAcceptedKeyTypes,
+	oPubkeyAcceptedKeyTypes, oProxyJump,
 	oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
@@ -295,6 +295,7 @@
 	{ "hostbasedkeytypes", oHostbasedKeyTypes },
 	{ "pubkeyacceptedkeytypes", oPubkeyAcceptedKeyTypes },
 	{ "ignoreunknown", oIgnoreUnknown },
+	{ "proxyjump", oProxyJump },
 
 	{ NULL, oBadOption }
 };
@@ -1121,6 +1122,9 @@
 
 	case oProxyCommand:
 		charptr = &options->proxy_command;
+		/* Ignore ProxyCommand if ProxyJump already specified */
+		if (options->jump_host != NULL)
+			charptr = &options->jump_host; /* Skip below */
 parse_command:
 		if (s == NULL)
 			fatal("%.200s line %d: Missing argument.", filename, linenum);
@@ -1129,6 +1133,18 @@
 			*charptr = xstrdup(s + len);
 		return 0;
 
+	case oProxyJump:
+		if (s == NULL) {
+			fatal("%.200s line %d: Missing argument.",
+			    filename, linenum);
+		}
+		len = strspn(s, WHITESPACE "=");
+		if (parse_jump(s + len, options, *activep) == -1) {
+			fatal("%.200s line %d: Invalid ProxyJump \"%s\"",
+			    filename, linenum, s + len);
+		}
+		return 0;
+
 	case oPort:
 		intptr = &options->port;
 parse_int:
@@ -1789,6 +1805,10 @@
 	options->hostname = NULL;
 	options->host_key_alias = NULL;
 	options->proxy_command = NULL;
+	options->jump_user = NULL;
+	options->jump_host = NULL;
+	options->jump_port = -1;
+	options->jump_extra = NULL;
 	options->user = NULL;
 	options->escape_char = -1;
 	options->num_system_hostfiles = 0;
@@ -2261,6 +2281,44 @@
 	return (0);
 }
 
+int
+parse_jump(const char *s, Options *o, int active)
+{
+	char *orig, *sdup, *cp;
+	char *host = NULL, *user = NULL;
+	int ret = -1, port = -1;
+
+	active &= o->proxy_command == NULL && o->jump_host == NULL;
+
+	orig = sdup = xstrdup(s);
+	while ((cp = strsep(&sdup, ",")) && cp != NULL) {
+		if (active) {
+			/* First argument and configuration is active */
+			if (parse_user_host_port(cp, &user, &host, &port) != 0)
+				goto out;
+		} else {
+			/* Subsequent argument or inactive configuration */
+			if (parse_user_host_port(cp, NULL, NULL, NULL) != 0)
+				goto out;
+		}
+		active = 0; /* only check syntax for subsequent hosts */
+	}
+	/* success */
+	free(orig);
+	o->jump_user = user;
+	o->jump_host = host;
+	o->jump_port = port;
+	o->proxy_command = xstrdup("none");
+	user = host = NULL;
+	if ((cp = strchr(s, ',')) != NULL && cp[1] != '\0')
+		o->jump_extra = xstrdup(cp + 1);
+	ret = 0;
+ out:
+	free(user);
+	free(host);
+	return ret;
+}
+
 /* XXX the following is a near-vebatim copy from servconf.c; refactor */
 static const char *
 fmt_multistate_int(int val, const struct multistate *m)
@@ -2412,7 +2470,7 @@
 dump_client_config(Options *o, const char *host)
 {
 	int i;
-	char vbuf[5];
+	char buf[8];
 
 	/* This is normally prepared in ssh_kex2 */
 	if (kex_assemble_names(KEX_DEFAULT_PK_ALG, &o->hostkeyalgorithms) != 0)
@@ -2490,7 +2548,6 @@
 	dump_cfg_string(oMacs, o->macs ? o->macs : KEX_CLIENT_MAC);
 	dump_cfg_string(oPKCS11Provider, o->pkcs11_provider);
 	dump_cfg_string(oPreferredAuthentications, o->preferred_authentications);
-	dump_cfg_string(oProxyCommand, o->proxy_command);
 	dump_cfg_string(oPubkeyAcceptedKeyTypes, o->pubkey_key_types);
 	dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys);
 	dump_cfg_string(oXAuthLocation, o->xauth_location);
@@ -2551,8 +2608,8 @@
 	if (o->escape_char == SSH_ESCAPECHAR_NONE)
 		printf("escapechar none\n");
 	else {
-		vis(vbuf, o->escape_char, VIS_WHITE, 0);
-		printf("escapechar %s\n", vbuf);
+		vis(buf, o->escape_char, VIS_WHITE, 0);
+		printf("escapechar %s\n", buf);
 	}
 
 	/* oIPQoS */
@@ -2566,4 +2623,30 @@
 	/* oStreamLocalBindMask */
 	printf("streamlocalbindmask 0%o\n",
 	    o->fwd_opts.streamlocal_bind_mask);
+
+	/* oProxyCommand / oProxyJump */
+	if (o->jump_host == NULL)
+		dump_cfg_string(oProxyCommand, o->proxy_command);
+	else {
+		/* Check for numeric addresses */
+		i = strchr(o->jump_host, ':') != NULL ||
+		    strspn(o->jump_host, "1234567890.") == strlen(o->jump_host);
+		snprintf(buf, sizeof(buf), "%d", o->jump_port);
+		printf("proxyjump %s%s%s%s%s%s%s%s%s\n",
+		    /* optional user */
+		    o->jump_user == NULL ? "" : o->jump_user,
+		    o->jump_user == NULL ? "" : "@",
+		    /* opening [ if hostname is numeric */
+		    i ? "[" : "",
+		    /* mandatory hostname */
+		    o->jump_host,
+		    /* closing ] if hostname is numeric */
+		    i ? "]" : "",
+		    /* optional port number */
+		    o->jump_port <= 0 ? "" : ":",
+		    o->jump_port <= 0 ? "" : buf,
+		    /* optional additional jump spec */
+		    o->jump_extra == NULL ? "" : ",",
+		    o->jump_extra == NULL ? "" : o->jump_extra);
+	}
 }