Helmut Grohne
2009-Jul-08 22:03 UTC
Feature request: "SetupCommand" invoked before connecting
Hi, (I'm not subscribed to the list, so please CC me on reply.) 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. Solutions: ~~~~~~~~~~ 1) (Ab)using ProxyCommand. This is employed in some howtos on port knocking. It however has the disadvantage that TCPKeepAlive and some timeout options are no longer honoured. 2) Wrapping ssh. While this does not disable other options like above one has to create a second option parser for ssh. Furthermore configuration that belongs to ssh is now located somewhere else (not in .ssh/config). The approach may also fail when third party applications that invoke ssh reset $PATH. 3) Extending ssh itself using a new configuration item "SetupCommand": Sample Implementation: ~~~~~~~~~~~~~~~~~~~~~~ I propose adding a new configuration item "SetupCommand" for the ssh client software. It would accept a string that is treated exactly the same as LocalCommand. As with LocalCommand it should also be ignored when PermitLocalCommand is disabled. Otherwise the command should be executed right before connecting to the server. I created a patch against 5.1p1 and tested it (attached). What do you think about this: 1) Is option 3 the best approach or did I overlook something? 2) Is this useful enough to patch ssh? 3) Can this implementation be used or do we need something better? Thanks in advance Helmut -------------- next part -------------- A non-text attachment was scrubbed... Name: setupcommand.diff Type: text/x-diff Size: 3838 bytes Desc: not available URL: <http://lists.mindrot.org/pipermail/openssh-unix-dev/attachments/20090709/0f9aa1c3/attachment.bin>
Damien Miller
2009-Jul-09 03:54 UTC
Feature request: "SetupCommand" invoked before connecting
On Thu, 9 Jul 2009, Helmut Grohne wrote:> Sample Implementation: > ~~~~~~~~~~~~~~~~~~~~~~ > I propose adding a new configuration item "SetupCommand" for the ssh > client software. It would accept a string that is treated exactly the > same as LocalCommand. As with LocalCommand it should also be ignored > when PermitLocalCommand is disabled. Otherwise the command should be > executed right before connecting to the server.If you need to do work before a ssh session, why not just make a shell script and, optionally, alias ssh to point to your shell script? -d
Bert Wesarg
2009-Jul-09 07:12 UTC
[PATCH] ControlCommand: execute a command if no control socket is available
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)
Helmut Grohne
2009-Jul-13 18:34 UTC
Feature request: "SetupCommand" invoked before connecting
Please CC me in a reply, so I don't have to watch out for replies in mailing list archives. Peter Stuge wrote:> Why not? Note: Rename the original ssh binary and your script is the > new ssh.This does not work with an alias. Adding a wrapper still imposes the disadvantages mentioned before (second option parser, scattered configuration). Yes, it will work (around). All I'm saying is that there is a cleaner solution. Workarounds I've seen: * http://trac.cipherdyne.org/trac/fwknop/browser/fwknop/trunk/patches (would be obsoleted by this patch) * http://marc.info/?l=openssh-unix-dev&m=115303182509343&w=2 (cleaner solution by this patch) Feature also requested: * http://lists.mindrot.org/pipermail/openssh-unix-dev/2006-July/024463.html Helmut
Helmut Grohne
2010-Jul-17 23:38 UTC
Feature request: "SetupCommand" invoked before connecting
Hi, On Thu, Jul 09, 2009 at 12:03:37AM +0200, Helmut Grohne wrote:> (I'm not subscribed to the list, so please CC me on reply.) > > 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 proposed a SetupCommand earlier and got a few responses. Both Daniel Kahn Gillmor and Jameson Rollins were in favour of merging my patch. However the merge did not happen so far. What is keeping you from merging it? Damien Miller and Peter Stuge questioned the usefulness. I explained that, but never got any follow up question. Were my arguments insufficient? Helmut