- 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@
This commit is contained in:
Damien Miller 2010-08-03 16:04:46 +10:00
parent c4bb91c79c
commit e11e1ea5d4
6 changed files with 223 additions and 31 deletions

View File

@ -16,6 +16,14 @@
bz#1797: fix swapped args in upload_dir_internal(), breaking recursive bz#1797: fix swapped args in upload_dir_internal(), breaking recursive
upload depth checks and causing verbose printing of transfers to always upload depth checks and causing verbose printing of transfers to always
be turned on; patch from imorgan AT nas.nasa.gov 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 20100819
- (dtucker) [contrib/ssh-copy-ud.1] Bug #1786: update ssh-copy-id.1 with more - (dtucker) [contrib/ssh-copy-ud.1] Bug #1786: update ssh-copy-id.1 with more

View File

@ -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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * 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. */ /* Flag indicating whether the user's terminal is in non-blocking mode. */
static int in_non_blocking_mode = 0; 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. */ /* Common data for the client loop code. */
volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */
static int escape_char1; /* Escape character. (proto1 only) */ 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; 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" #define SSH_X11_PROTO "MIT-MAGIC-COOKIE-1"
void void
client_x11_get_proto(const char *display, const char *xauth_path, 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) int *maxfdp, u_int *nallocp, int rekeying)
{ {
struct timeval tv, *tvp; struct timeval tv, *tvp;
int timeout_secs;
int ret; int ret;
/* Add any selections by the channel mechanism. */ /* 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 * Wait for something to happen. This will suspend the process until
* some selected descriptor can be read, written, or has some other * 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; tvp = NULL;
else { else {
tv.tv_sec = options.server_alive_interval; tv.tv_sec = timeout_secs;
tv.tv_usec = 0; tv.tv_usec = 0;
tvp = &tv; tvp = &tv;
} }
ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp); ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp);
if (ret < 0) { if (ret < 0) {
char buf[100]; 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)) if (FD_ISSET(connection_out, writeset))
packet_write_poll(); 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) if (readset)
xfree(readset); xfree(readset);

View File

@ -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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -128,7 +128,8 @@ typedef enum {
oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
oAddressFamily, oGssAuthentication, oGssDelegateCreds, oAddressFamily, oGssAuthentication, oGssDelegateCreds,
oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, oSendEnv, oControlPath, oControlMaster, oControlPersist,
oHashKnownHosts,
oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication, oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication,
oDeprecated, oUnsupported oDeprecated, oUnsupported
@ -225,6 +226,7 @@ static struct {
{ "sendenv", oSendEnv }, { "sendenv", oSendEnv },
{ "controlpath", oControlPath }, { "controlpath", oControlPath },
{ "controlmaster", oControlMaster }, { "controlmaster", oControlMaster },
{ "controlpersist", oControlPersist },
{ "hashknownhosts", oHashKnownHosts }, { "hashknownhosts", oHashKnownHosts },
{ "tunnel", oTunnel }, { "tunnel", oTunnel },
{ "tunneldevice", oTunnelDevice }, { "tunneldevice", oTunnelDevice },
@ -882,6 +884,30 @@ parse_int:
*intptr = value; *intptr = value;
break; 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: case oHashKnownHosts:
intptr = &options->hash_known_hosts; intptr = &options->hash_known_hosts;
goto parse_flag; goto parse_flag;
@ -1083,6 +1109,8 @@ initialize_options(Options * options)
options->num_send_env = 0; options->num_send_env = 0;
options->control_path = NULL; options->control_path = NULL;
options->control_master = -1; options->control_master = -1;
options->control_persist = -1;
options->control_persist_timeout = 0;
options->hash_known_hosts = -1; options->hash_known_hosts = -1;
options->tun_open = -1; options->tun_open = -1;
options->tun_local = -1; options->tun_local = -1;
@ -1218,6 +1246,10 @@ fill_default_options(Options * options)
options->server_alive_count_max = 3; options->server_alive_count_max = 3;
if (options->control_master == -1) if (options->control_master == -1)
options->control_master = 0; options->control_master = 0;
if (options->control_persist == -1) {
options->control_persist = 0;
options->control_persist_timeout = 0;
}
if (options->hash_known_hosts == -1) if (options->hash_known_hosts == -1)
options->hash_known_hosts = 0; options->hash_known_hosts = 0;
if (options->tun_open == -1) if (options->tun_open == -1)

View File

@ -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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -114,6 +114,8 @@ typedef struct {
char *control_path; char *control_path;
int control_master; int control_master;
int control_persist; /* ControlPersist flag */
int control_persist_timeout; /* ControlPersist timeout (seconds) */
int hash_known_hosts; int hash_known_hosts;

117
ssh.c
View File

@ -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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -127,6 +127,15 @@ int no_shell_flag = 0;
*/ */
int stdin_null_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 * 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 * so that the passphrase can be entered manually, and then ssh goes to the
@ -877,6 +886,50 @@ main(int ac, char **av)
return exit_status; 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 */ /* Callback for remote forward global requests */
static void static void
ssh_confirm_remote_forward(int type, u_int32_t seq, void *ctxt) 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) { if (++remote_forward_confirms_received == options.num_remote_forwards) {
debug("All remote forwarding requests processed"); debug("All remote forwarding requests processed");
if (fork_after_authentication_flag) { if (fork_after_authentication_flag)
fork_after_authentication_flag = 0; fork_postauth();
if (daemon(1, 1) < 0)
fatal("daemon() failed: %.200s",
strerror(errno));
}
} }
} }
@ -1153,12 +1202,13 @@ ssh_session(void)
* If requested and we are not interested in replies to remote * If requested and we are not interested in replies to remote
* forwarding requests, then let ssh continue in the background. * forwarding requests, then let ssh continue in the background.
*/ */
if (fork_after_authentication_flag && if (fork_after_authentication_flag) {
(!options.exit_on_forward_failure || if (options.exit_on_forward_failure &&
options.num_remote_forwards == 0)) { options.num_remote_forwards > 0) {
fork_after_authentication_flag = 0; debug("deferring postauth fork until remote forward "
if (daemon(1, 1) < 0) "confirmation received");
fatal("daemon() failed: %.200s", strerror(errno)); } else
fork_postauth();
} }
/* /*
@ -1281,6 +1331,31 @@ ssh_session2(void)
/* XXX should be pre-session */ /* XXX should be pre-session */
ssh_init_forwarding(); 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)) if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN))
id = ssh_session2_open(); id = ssh_session2_open();
@ -1299,19 +1374,17 @@ ssh_session2(void)
options.permit_local_command) options.permit_local_command)
ssh_local_cmd(options.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 * If requested and we are not interested in replies to remote
* forwarding requests, then let ssh continue in the background. * forwarding requests, then let ssh continue in the background.
*/ */
if (fork_after_authentication_flag && if (fork_after_authentication_flag) {
(!options.exit_on_forward_failure || if (options.exit_on_forward_failure &&
options.num_remote_forwards == 0)) { options.num_remote_forwards > 0) {
fork_after_authentication_flag = 0; debug("deferring postauth fork until remote forward "
if (daemon(1, 1) < 0) "confirmation received");
fatal("daemon() failed: %.200s", strerror(errno)); } else
fork_postauth();
} }
if (options.use_roaming) if (options.use_roaming)

View File

@ -34,8 +34,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" 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 $ .\" $OpenBSD: ssh_config.5,v 1.137 2010/07/19 09:15:12 djm Exp $
.Dd $Mdocdate: July 12 2010 $ .Dd $Mdocdate: July 19 2010 $
.Dt SSH_CONFIG 5 .Dt SSH_CONFIG 5
.Os .Os
.Sh NAME .Sh NAME
@ -319,6 +319,28 @@ It is recommended that any
used for opportunistic connection sharing include used for opportunistic connection sharing include
at least %h, %p, and %r. at least %h, %p, and %r.
This ensures that shared connections are uniquely identified. 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 .It Cm DynamicForward
Specifies that a TCP port on the local machine be forwarded Specifies that a TCP port on the local machine be forwarded
over the secure channel, and the application over the secure channel, and the application