- 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
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

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>
* 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);

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>
* 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)

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>
@ -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;

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>
* 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;
/*
* 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
@ -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)

View File

@ -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