[PATCH] ControlCommand: execute a command if no control socket is available

Bert Wesarg bert.wesarg at googlemail.com
Thu Jul 9 17:12:01 EST 2009


On Thu, Jul 9, 2009 at 00:03, Helmut Grohne<helmut at subdivi.de> wrote:
> Hi,
>
> I'd like to request adding a feature to OpenSSH:
>
> Task:
> ~~~~~
> It is quite sometime useful to invoke a program prior to connecting to
> an ssh server. The most common use case will probably be port knocking.
> That is a small program sends certain packets to a server and the server
> reacts to this by unlocking the ssh port, which would be blocked
> otherwise to defend against brute force attacks.
I have a similar task:

I don't want my interactive ssh shell session or git/svn sessions to act as
master processes, so that they may hang after I started a second session.
So I would need to start a master process with 'ssh -nNfM' first and than
my interactive session. And I would like to automate this.

To solve this problem I propose and implemented this:

A new ControlMaster mode named "command", which acts like "no", but
if there is no existing control socket it executes the command given by
ControlCommand. It has the substitutions like the LocalCommand and %s
as the path of the control socket, additionally.

The simpliest configuration would be:

ControlMaster  command
ControlCommand "ssh -nNfM -p %p %u@%h"

The patch removes also the redundant check in ssh_local_cmd() for the
permit_local_command option, because all callsites check this in-front of the
call.

Signed-off-by: Bert Wesarg <bert.wesarg at googlemail.com>

---
 mux.c        |   24 +++++++++++++++++++-----
 readconf.c   |   15 +++++++++++++--
 readconf.h   |    3 +++
 ssh.c        |   40 +++++++++++++++++++++++++++++++++++-----
 sshconnect.c |    3 +--
 5 files changed, 71 insertions(+), 14 deletions(-)

diff --git a/mux.c b/mux.c
index 79f8376..f4a1e8a 100644
--- a/mux.c
+++ b/mux.c
@@ -280,14 +280,16 @@ muxserver_accept_control(void)
 	switch (mux_command) {
 	case SSHMUX_COMMAND_OPEN:
 		if (options.control_master == SSHCTL_MASTER_ASK ||
-		    options.control_master == SSHCTL_MASTER_AUTO_ASK)
+		    options.control_master == SSHCTL_MASTER_AUTO_ASK ||
+		    options.control_master == SSHCTL_MASTER_COMMAND_ASK)
 			allowed = ask_permission("Allow shared connection "
 			    "to %s? ", host);
 		/* continue below */
 		break;
 	case SSHMUX_COMMAND_TERMINATE:
 		if (options.control_master == SSHCTL_MASTER_ASK ||
-		    options.control_master == SSHCTL_MASTER_AUTO_ASK)
+		    options.control_master == SSHCTL_MASTER_AUTO_ASK ||
+		    options.control_master == SSHCTL_MASTER_COMMAND_ASK)
 			allowed = ask_permission("Terminate shared connection "
 			    "to %s? ", host);
 		if (allowed)
@@ -518,6 +520,10 @@ muxclient(const char *path)
 		/* FALLTHROUGH */
 	case SSHCTL_MASTER_NO:
 		break;
+	case SSHCTL_MASTER_COMMAND:
+	case SSHCTL_MASTER_COMMAND_ASK:
+		debug("command-mux: Start control command");
+		break;
 	default:
 		return;
 	}
@@ -534,14 +540,22 @@ muxclient(const char *path)
 	if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
 		fatal("%s socket(): %s", __func__, strerror(errno));
 
+retry:
 	if (connect(sock, (struct sockaddr *)&addr, addr_len) == -1) {
 		if (muxclient_command != SSHMUX_COMMAND_OPEN) {
 			fatal("Control socket connect(%.100s): %s", path,
 			    strerror(errno));
 		}
-		if (errno == ENOENT)
-			debug("Control socket \"%.100s\" does not exist", path);
-		else {
+		if (errno == ENOENT) {
+			if (options.control_master != SSHCTL_MASTER_COMMAND ||
+			    options.control_command == NULL) {
+				debug("Control socket \"%.100s\" does not exist", path);
+			} else {
+				debug("Executing control command: %.500s", options.control_command);
+				if (!ssh_local_cmd(options.control_command))
+					goto retry;
+			}
+		} else {
 			error("Control socket connect(%.100s): %s", path,
 			    strerror(errno));
 		}
diff --git a/readconf.c b/readconf.c
index 53fc6c7..4d81a48 100644
--- a/readconf.c
+++ b/readconf.c
@@ -128,8 +128,9 @@ typedef enum {
 	oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
 	oAddressFamily, oGssAuthentication, oGssDelegateCreds,
 	oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
-	oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
-	oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
+	oSendEnv, oControlPath, oControlMaster, oControlCommand,
+	oHashKnownHosts, oTunnel, oTunnelDevice,
+	oLocalCommand, oPermitLocalCommand,
 	oVisualHostKey, oZeroKnowledgePasswordAuthentication,
 	oDeprecated, oUnsupported
 } OpCodes;
@@ -222,6 +223,7 @@ static struct {
 	{ "sendenv", oSendEnv },
 	{ "controlpath", oControlPath },
 	{ "controlmaster", oControlMaster },
+	{ "controlcommand", oControlCommand },
 	{ "hashknownhosts", oHashKnownHosts },
 	{ "tunnel", oTunnel },
 	{ "tunneldevice", oTunnelDevice },
@@ -856,6 +858,10 @@ parse_int:
 			value = SSHCTL_MASTER_ASK;
 		else if (strcmp(arg, "autoask") == 0)
 			value = SSHCTL_MASTER_AUTO_ASK;
+		else if (strcmp(arg, "command") == 0)
+			value = SSHCTL_MASTER_COMMAND;
+		else if (strcmp(arg, "commandask") == 0)
+			value = SSHCTL_MASTER_COMMAND_ASK;
 		else
 			fatal("%.200s line %d: Bad ControlMaster argument.",
 			    filename, linenum);
@@ -863,6 +869,10 @@ parse_int:
 			*intptr = value;
 		break;
 
+	case oControlCommand:
+		charptr = &options->control_command;
+		goto parse_string;
+
 	case oHashKnownHosts:
 		intptr = &options->hash_known_hosts;
 		goto parse_flag;
@@ -1057,6 +1067,7 @@ initialize_options(Options * options)
 	options->num_send_env = 0;
 	options->control_path = NULL;
 	options->control_master = -1;
+	options->control_command = NULL;
 	options->hash_known_hosts = -1;
 	options->tun_open = -1;
 	options->tun_local = -1;
diff --git a/readconf.h b/readconf.h
index 8fb3a85..a3de419 100644
--- a/readconf.h
+++ b/readconf.h
@@ -112,6 +112,7 @@ typedef struct {
 
 	char	*control_path;
 	int	control_master;
+	char	*control_command;
 
 	int	hash_known_hosts;
 
@@ -130,6 +131,8 @@ typedef struct {
 #define SSHCTL_MASTER_AUTO	2
 #define SSHCTL_MASTER_ASK	3
 #define SSHCTL_MASTER_AUTO_ASK	4
+#define SSHCTL_MASTER_COMMAND	5
+#define SSHCTL_MASTER_COMMAND_ASK	6
 
 void     initialize_options(Options *);
 void     fill_default_options(Options *);
diff --git a/ssh.c b/ssh.c
index 9613468..95aa693 100644
--- a/ssh.c
+++ b/ssh.c
@@ -212,6 +212,7 @@ main(int ac, char **av)
 	extern char *optarg;
 	struct servent *sp;
 	Forward fwd;
+	char *host_arg;
 
 	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
 	sanitise_stdfd();
@@ -548,6 +549,9 @@ main(int ac, char **av)
 	if (!host)
 		usage();
 
+	/* safe the hostname given on the command-line */
+	host_arg = host;
+
 	SSLeay_add_all_algorithms();
 	ERR_load_crypto_strings();
 
@@ -623,6 +627,9 @@ main(int ac, char **av)
 		    &options, 0);
 	}
 
+	if (options.hostname != NULL)
+		host = options.hostname;
+
 	/* Fill configuration defaults. */
 	fill_default_options(&options);
 
@@ -652,15 +659,12 @@ main(int ac, char **av)
 		cp = options.local_command;
 		options.local_command = percent_expand(cp, "d", pw->pw_dir,
 		    "h", options.hostname? options.hostname : host,
-                    "l", thishost, "n", host, "r", options.user, "p", buf,
+                    "l", thishost, "n", host_arg, "r", options.user, "p", buf,
                     "u", pw->pw_name, (char *)NULL);
 		debug3("expanded LocalCommand: %s", options.local_command);
 		xfree(cp);
 	}
 
-	if (options.hostname != NULL)
-		host = options.hostname;
-
 	/* force lowercase for hostkey matching */
 	if (options.host_key_alias != NULL) {
 		for (p = options.host_key_alias; *p; p++)
@@ -673,12 +677,12 @@ main(int ac, char **av)
 		xfree(options.proxy_command);
 		options.proxy_command = NULL;
 	}
+
 	if (options.control_path != NULL &&
 	    strcmp(options.control_path, "none") == 0) {
 		xfree(options.control_path);
 		options.control_path = NULL;
 	}
-
 	if (options.control_path != NULL) {
 		char thishost[NI_MAXHOST];
 
@@ -692,6 +696,32 @@ main(int ac, char **av)
 		    "r", options.user, "l", thishost, (char *)NULL);
 		xfree(cp);
 	}
+
+	if (options.control_command != NULL &&
+	    strcmp(options.control_command, "none") == 0) {
+		xfree(options.control_command);
+		options.control_command = NULL;
+	}
+	if (options.control_command != NULL && options.control_path != NULL) {
+		char thishost[NI_MAXHOST];
+
+		if (gethostname(thishost, sizeof(thishost)) == -1)
+			fatal("gethostname: %s", strerror(errno));
+		snprintf(buf, sizeof(buf), "%d", options.port);
+		cp = options.control_command;
+		options.control_command = percent_expand(cp,
+		    "l", thishost,
+		    "h", options.hostname ?: host,
+		    "p", buf,
+		    "r", options.user,
+		    "n", host_arg,
+		    "u", pw->pw_name,
+		    "d", pw->pw_dir,
+		    "s", options.control_path,
+		    (char *)NULL);
+		xfree(cp);
+	}
+
 	if (muxclient_command != 0 && options.control_path == NULL)
 		fatal("No ControlPath specified for \"-O\" command");
 	if (options.control_path != NULL)
diff --git a/sshconnect.c b/sshconnect.c
index 3e57e85..87f4a87 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1157,8 +1157,7 @@ ssh_local_cmd(const char *args)
 	pid_t pid;
 	int status;
 
-	if (!options.permit_local_command ||
-	    args == NULL || !*args)
+	if (args == NULL || !*args)
 		return (1);
 
 	if ((shell = getenv("SHELL")) == NULL)
-- 
tg: (6a49252..) bw/control-command (depends on: origin)


More information about the openssh-unix-dev mailing list