summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2010-08-03 16:04:46 +1000
committerDamien Miller <djm@mindrot.org>2010-08-03 16:04:46 +1000
commite11e1ea5d475ee8be0038d64aa3e47c776295ac2 (patch)
tree88fa00ef41babbec7cb33e68f400e3a1ff787230
parentc4bb91c79c0a05d2bbf2ac68b7be8421fb4957bf (diff)
- djm@cvs.openbsd.org 2010/07/19 09:15:12
[clientloop.c readconf.c readconf.h ssh.c ssh_config.5] add a "ControlPersist" option that automatically starts a background ssh(1) multiplex master when connecting. This connection can stay alive indefinitely, or can be set to automatically close after a user-specified duration of inactivity. bz#1330 - patch by dwmw2 AT infradead.org, but further hacked on by wmertens AT cisco.com, apb AT cequrux.com, martin-mindrot-bugzilla AT earth.li and myself; "looks ok" markus@
-rw-r--r--ChangeLog8
-rw-r--r--clientloop.c63
-rw-r--r--readconf.c36
-rw-r--r--readconf.h4
-rw-r--r--ssh.c117
-rw-r--r--ssh_config.526
6 files changed, 223 insertions, 31 deletions
diff --git a/ChangeLog b/ChangeLog
index f4fb5f05..b43074ec 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,14 @@
bz#1797: fix swapped args in upload_dir_internal(), breaking recursive
upload depth checks and causing verbose printing of transfers to always
be turned on; patch from imorgan AT nas.nasa.gov
+ - djm@cvs.openbsd.org 2010/07/19 09:15:12
+ [clientloop.c readconf.c readconf.h ssh.c ssh_config.5]
+ add a "ControlPersist" option that automatically starts a background
+ ssh(1) multiplex master when connecting. This connection can stay alive
+ indefinitely, or can be set to automatically close after a user-specified
+ duration of inactivity. bz#1330 - patch by dwmw2 AT infradead.org, but
+ further hacked on by wmertens AT cisco.com, apb AT cequrux.com,
+ martin-mindrot-bugzilla AT earth.li and myself; "looks ok" markus@
20100819
- (dtucker) [contrib/ssh-copy-ud.1] Bug #1786: update ssh-copy-id.1 with more
diff --git a/clientloop.c b/clientloop.c
index 5608bcc2..de797936 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.221 2010/06/25 23:15:36 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.222 2010/07/19 09:15:12 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -145,6 +145,9 @@ static volatile sig_atomic_t received_signal = 0;
/* Flag indicating whether the user's terminal is in non-blocking mode. */
static int in_non_blocking_mode = 0;
+/* Time when backgrounded control master using ControlPersist should exit */
+static time_t control_persist_exit_time = 0;
+
/* Common data for the client loop code. */
volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */
static int escape_char1; /* Escape character. (proto1 only) */
@@ -252,6 +255,34 @@ get_current_time(void)
return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0;
}
+/*
+ * Sets control_persist_exit_time to the absolute time when the
+ * backgrounded control master should exit due to expiry of the
+ * ControlPersist timeout. Sets it to 0 if we are not a backgrounded
+ * control master process, or if there is no ControlPersist timeout.
+ */
+static void
+set_control_persist_exit_time(void)
+{
+ if (muxserver_sock == -1 || !options.control_persist
+ || options.control_persist_timeout == 0)
+ /* not using a ControlPersist timeout */
+ control_persist_exit_time = 0;
+ else if (channel_still_open()) {
+ /* some client connections are still open */
+ if (control_persist_exit_time > 0)
+ debug2("%s: cancel scheduled exit", __func__);
+ control_persist_exit_time = 0;
+ } else if (control_persist_exit_time <= 0) {
+ /* a client connection has recently closed */
+ control_persist_exit_time = time(NULL) +
+ (time_t)options.control_persist_timeout;
+ debug2("%s: schedule exit in %d seconds", __func__,
+ options.control_persist_timeout);
+ }
+ /* else we are already counting down to the timeout */
+}
+
#define SSH_X11_PROTO "MIT-MAGIC-COOKIE-1"
void
client_x11_get_proto(const char *display, const char *xauth_path,
@@ -533,6 +564,7 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
int *maxfdp, u_int *nallocp, int rekeying)
{
struct timeval tv, *tvp;
+ int timeout_secs;
int ret;
/* Add any selections by the channel mechanism. */
@@ -576,16 +608,27 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
/*
* Wait for something to happen. This will suspend the process until
* some selected descriptor can be read, written, or has some other
- * event pending.
+ * event pending, or a timeout expires.
*/
- if (options.server_alive_interval == 0 || !compat20)
+ timeout_secs = INT_MAX; /* we use INT_MAX to mean no timeout */
+ if (options.server_alive_interval > 0 && compat20)
+ timeout_secs = options.server_alive_interval;
+ set_control_persist_exit_time();
+ if (control_persist_exit_time > 0) {
+ timeout_secs = MIN(timeout_secs,
+ control_persist_exit_time - time(NULL));
+ if (timeout_secs < 0)
+ timeout_secs = 0;
+ }
+ if (timeout_secs == INT_MAX)
tvp = NULL;
else {
- tv.tv_sec = options.server_alive_interval;
+ tv.tv_sec = timeout_secs;
tv.tv_usec = 0;
tvp = &tv;
}
+
ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp);
if (ret < 0) {
char buf[100];
@@ -1478,6 +1521,18 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
*/
if (FD_ISSET(connection_out, writeset))
packet_write_poll();
+
+ /*
+ * If we are a backgrounded control master, and the
+ * timeout has expired without any active client
+ * connections, then quit.
+ */
+ if (control_persist_exit_time > 0) {
+ if (time(NULL) >= control_persist_exit_time) {
+ debug("ControlPersist timeout expired");
+ break;
+ }
+ }
}
if (readset)
xfree(readset);
diff --git a/readconf.c b/readconf.c
index da48ae7d..0296590e 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.186 2010/06/25 23:15:36 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.187 2010/07/19 09:15:12 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -128,7 +128,8 @@ typedef enum {
oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
oAddressFamily, oGssAuthentication, oGssDelegateCreds,
oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
- oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
+ oSendEnv, oControlPath, oControlMaster, oControlPersist,
+ oHashKnownHosts,
oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication,
oDeprecated, oUnsupported
@@ -225,6 +226,7 @@ static struct {
{ "sendenv", oSendEnv },
{ "controlpath", oControlPath },
{ "controlmaster", oControlMaster },
+ { "controlpersist", oControlPersist },
{ "hashknownhosts", oHashKnownHosts },
{ "tunnel", oTunnel },
{ "tunneldevice", oTunnelDevice },
@@ -882,6 +884,30 @@ parse_int:
*intptr = value;
break;
+ case oControlPersist:
+ /* no/false/yes/true, or a time spec */
+ intptr = &options->control_persist;
+ arg = strdelim(&s);
+ if (!arg || *arg == '\0')
+ fatal("%.200s line %d: Missing ControlPersist"
+ " argument.", filename, linenum);
+ value = 0;
+ value2 = 0; /* timeout */
+ if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0)
+ value = 0;
+ else if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0)
+ value = 1;
+ else if ((value2 = convtime(arg)) >= 0)
+ value = 1;
+ else
+ fatal("%.200s line %d: Bad ControlPersist argument.",
+ filename, linenum);
+ if (*activep && *intptr == -1) {
+ *intptr = value;
+ options->control_persist_timeout = value2;
+ }
+ break;
+
case oHashKnownHosts:
intptr = &options->hash_known_hosts;
goto parse_flag;
@@ -1083,6 +1109,8 @@ initialize_options(Options * options)
options->num_send_env = 0;
options->control_path = NULL;
options->control_master = -1;
+ options->control_persist = -1;
+ options->control_persist_timeout = 0;
options->hash_known_hosts = -1;
options->tun_open = -1;
options->tun_local = -1;
@@ -1218,6 +1246,10 @@ fill_default_options(Options * options)
options->server_alive_count_max = 3;
if (options->control_master == -1)
options->control_master = 0;
+ if (options->control_persist == -1) {
+ options->control_persist = 0;
+ options->control_persist_timeout = 0;
+ }
if (options->hash_known_hosts == -1)
options->hash_known_hosts = 0;
if (options->tun_open == -1)
diff --git a/readconf.h b/readconf.h
index 66acafde..95d10467 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.85 2010/06/25 23:15:36 djm Exp $ */
+/* $OpenBSD: readconf.h,v 1.86 2010/07/19 09:15:12 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -114,6 +114,8 @@ typedef struct {
char *control_path;
int control_master;
+ int control_persist; /* ControlPersist flag */
+ int control_persist_timeout; /* ControlPersist timeout (seconds) */
int hash_known_hosts;
diff --git a/ssh.c b/ssh.c
index 61fe10df..249be2db 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.343 2010/07/12 22:41:13 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.344 2010/07/19 09:15:12 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -128,6 +128,15 @@ int no_shell_flag = 0;
int stdin_null_flag = 0;
/*
+ * Flag indicating that the current process should be backgrounded and
+ * a new slave launched in the foreground for ControlPersist.
+ */
+int need_controlpersist_detach = 0;
+
+/* Copies of flags for ControlPersist foreground slave */
+int ostdin_null_flag, ono_shell_flag, ono_tty_flag, otty_flag;
+
+/*
* Flag indicating that ssh should fork after authentication. This is useful
* so that the passphrase can be entered manually, and then ssh goes to the
* background.
@@ -877,6 +886,50 @@ main(int ac, char **av)
return exit_status;
}
+static void
+control_persist_detach(void)
+{
+ pid_t pid;
+
+ debug("%s: backgrounding master process", __func__);
+
+ /*
+ * master (current process) into the background, and make the
+ * foreground process a client of the backgrounded master.
+ */
+ switch ((pid = fork())) {
+ case -1:
+ fatal("%s: fork: %s", __func__, strerror(errno));
+ case 0:
+ /* Child: master process continues mainloop */
+ break;
+ default:
+ /* Parent: set up mux slave to connect to backgrounded master */
+ debug2("%s: background process is %ld", __func__, (long)pid);
+ stdin_null_flag = ostdin_null_flag;
+ no_shell_flag = ono_shell_flag;
+ no_tty_flag = ono_tty_flag;
+ tty_flag = otty_flag;
+ close(muxserver_sock);
+ muxserver_sock = -1;
+ muxclient(options.control_path);
+ /* muxclient() doesn't return on success. */
+ fatal("Failed to connect to new control master");
+ }
+}
+
+/* Do fork() after authentication. Used by "ssh -f" */
+static void
+fork_postauth(void)
+{
+ if (need_controlpersist_detach)
+ control_persist_detach();
+ debug("forking to background");
+ fork_after_authentication_flag = 0;
+ if (daemon(1, 1) < 0)
+ fatal("daemon() failed: %.200s", strerror(errno));
+}
+
/* Callback for remote forward global requests */
static void
ssh_confirm_remote_forward(int type, u_int32_t seq, void *ctxt)
@@ -904,12 +957,8 @@ ssh_confirm_remote_forward(int type, u_int32_t seq, void *ctxt)
}
if (++remote_forward_confirms_received == options.num_remote_forwards) {
debug("All remote forwarding requests processed");
- if (fork_after_authentication_flag) {
- fork_after_authentication_flag = 0;
- if (daemon(1, 1) < 0)
- fatal("daemon() failed: %.200s",
- strerror(errno));
- }
+ if (fork_after_authentication_flag)
+ fork_postauth();
}
}
@@ -1153,12 +1202,13 @@ ssh_session(void)
* If requested and we are not interested in replies to remote
* forwarding requests, then let ssh continue in the background.
*/
- if (fork_after_authentication_flag &&
- (!options.exit_on_forward_failure ||
- options.num_remote_forwards == 0)) {
- fork_after_authentication_flag = 0;
- if (daemon(1, 1) < 0)
- fatal("daemon() failed: %.200s", strerror(errno));
+ if (fork_after_authentication_flag) {
+ if (options.exit_on_forward_failure &&
+ options.num_remote_forwards > 0) {
+ debug("deferring postauth fork until remote forward "
+ "confirmation received");
+ } else
+ fork_postauth();
}
/*
@@ -1281,6 +1331,31 @@ ssh_session2(void)
/* XXX should be pre-session */
ssh_init_forwarding();
+ /* Start listening for multiplex clients */
+ muxserver_listen();
+
+ /*
+ * If we are in control persist mode, then prepare to background
+ * ourselves and have a foreground client attach as a control
+ * slave. NB. we must save copies of the flags that we override for
+ * the backgrounding, since we defer attachment of the slave until
+ * after the connection is fully established (in particular,
+ * async rfwd replies have been received for ExitOnForwardFailure).
+ */
+ if (options.control_persist && muxserver_sock != -1) {
+ ostdin_null_flag = stdin_null_flag;
+ ono_shell_flag = no_shell_flag;
+ ono_tty_flag = no_tty_flag;
+ otty_flag = tty_flag;
+ stdin_null_flag = 1;
+ no_shell_flag = 1;
+ no_tty_flag = 1;
+ tty_flag = 0;
+ if (!fork_after_authentication_flag)
+ need_controlpersist_detach = 1;
+ fork_after_authentication_flag = 1;
+ }
+
if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN))
id = ssh_session2_open();
@@ -1299,19 +1374,17 @@ ssh_session2(void)
options.permit_local_command)
ssh_local_cmd(options.local_command);
- /* Start listening for multiplex clients */
- muxserver_listen();
-
/*
* If requested and we are not interested in replies to remote
* forwarding requests, then let ssh continue in the background.
*/
- if (fork_after_authentication_flag &&
- (!options.exit_on_forward_failure ||
- options.num_remote_forwards == 0)) {
- fork_after_authentication_flag = 0;
- if (daemon(1, 1) < 0)
- fatal("daemon() failed: %.200s", strerror(errno));
+ if (fork_after_authentication_flag) {
+ if (options.exit_on_forward_failure &&
+ options.num_remote_forwards > 0) {
+ debug("deferring postauth fork until remote forward "
+ "confirmation received");
+ } else
+ fork_postauth();
}
if (options.use_roaming)
diff --git a/ssh_config.5 b/ssh_config.5
index e7bb21eb..04df8184 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -34,8 +34,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.136 2010/07/12 22:41:13 djm Exp $
-.Dd $Mdocdate: July 12 2010 $
+.\" $OpenBSD: ssh_config.5,v 1.137 2010/07/19 09:15:12 djm Exp $
+.Dd $Mdocdate: July 19 2010 $
.Dt SSH_CONFIG 5
.Os
.Sh NAME
@@ -319,6 +319,28 @@ It is recommended that any
used for opportunistic connection sharing include
at least %h, %p, and %r.
This ensures that shared connections are uniquely identified.
+.It Cm ControlPersist
+When used in conjunction with
+.Cm ControlMaster ,
+specifies that the master connection should remain open
+in the background (waiting for future client connections)
+after the initial client connection has been closed.
+If set to
+.Dq no ,
+then the master connection will not be placed into the background,
+and will close as soon as the initial client connection is closed.
+If set to
+.Dq yes ,
+then the master connection will remain in the background indefinitely
+(until killed or closed via a mechanism such as the
+.Xr ssh 1
+.Dq Fl O No exit
+option).
+If set to a time in seconds, or a time in any of the formats documented in
+.Xr sshd_config 5 ,
+then the backgrounded master connection will automatically terminate
+after it has remained idle (with no client connections) for the
+specified time.
.It Cm DynamicForward
Specifies that a TCP port on the local machine be forwarded
over the secure channel, and the application