summaryrefslogtreecommitdiff
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 f4fb5f05f..b43074ec9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,14 @@
16 bz#1797: fix swapped args in upload_dir_internal(), breaking recursive 16 bz#1797: fix swapped args in upload_dir_internal(), breaking recursive
17 upload depth checks and causing verbose printing of transfers to always 17 upload depth checks and causing verbose printing of transfers to always
18 be turned on; patch from imorgan AT nas.nasa.gov 18 be turned on; patch from imorgan AT nas.nasa.gov
19 - djm@cvs.openbsd.org 2010/07/19 09:15:12
20 [clientloop.c readconf.c readconf.h ssh.c ssh_config.5]
21 add a "ControlPersist" option that automatically starts a background
22 ssh(1) multiplex master when connecting. This connection can stay alive
23 indefinitely, or can be set to automatically close after a user-specified
24 duration of inactivity. bz#1330 - patch by dwmw2 AT infradead.org, but
25 further hacked on by wmertens AT cisco.com, apb AT cequrux.com,
26 martin-mindrot-bugzilla AT earth.li and myself; "looks ok" markus@
19 27
2020100819 2820100819
21 - (dtucker) [contrib/ssh-copy-ud.1] Bug #1786: update ssh-copy-id.1 with more 29 - (dtucker) [contrib/ssh-copy-ud.1] Bug #1786: update ssh-copy-id.1 with more
diff --git a/clientloop.c b/clientloop.c
index 5608bcc2e..de7979366 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: clientloop.c,v 1.221 2010/06/25 23:15:36 djm Exp $ */ 1/* $OpenBSD: clientloop.c,v 1.222 2010/07/19 09:15:12 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -145,6 +145,9 @@ static volatile sig_atomic_t received_signal = 0;
145/* Flag indicating whether the user's terminal is in non-blocking mode. */ 145/* Flag indicating whether the user's terminal is in non-blocking mode. */
146static int in_non_blocking_mode = 0; 146static int in_non_blocking_mode = 0;
147 147
148/* Time when backgrounded control master using ControlPersist should exit */
149static time_t control_persist_exit_time = 0;
150
148/* Common data for the client loop code. */ 151/* Common data for the client loop code. */
149volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ 152volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */
150static int escape_char1; /* Escape character. (proto1 only) */ 153static int escape_char1; /* Escape character. (proto1 only) */
@@ -252,6 +255,34 @@ get_current_time(void)
252 return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0; 255 return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0;
253} 256}
254 257
258/*
259 * Sets control_persist_exit_time to the absolute time when the
260 * backgrounded control master should exit due to expiry of the
261 * ControlPersist timeout. Sets it to 0 if we are not a backgrounded
262 * control master process, or if there is no ControlPersist timeout.
263 */
264static void
265set_control_persist_exit_time(void)
266{
267 if (muxserver_sock == -1 || !options.control_persist
268 || options.control_persist_timeout == 0)
269 /* not using a ControlPersist timeout */
270 control_persist_exit_time = 0;
271 else if (channel_still_open()) {
272 /* some client connections are still open */
273 if (control_persist_exit_time > 0)
274 debug2("%s: cancel scheduled exit", __func__);
275 control_persist_exit_time = 0;
276 } else if (control_persist_exit_time <= 0) {
277 /* a client connection has recently closed */
278 control_persist_exit_time = time(NULL) +
279 (time_t)options.control_persist_timeout;
280 debug2("%s: schedule exit in %d seconds", __func__,
281 options.control_persist_timeout);
282 }
283 /* else we are already counting down to the timeout */
284}
285
255#define SSH_X11_PROTO "MIT-MAGIC-COOKIE-1" 286#define SSH_X11_PROTO "MIT-MAGIC-COOKIE-1"
256void 287void
257client_x11_get_proto(const char *display, const char *xauth_path, 288client_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,
533 int *maxfdp, u_int *nallocp, int rekeying) 564 int *maxfdp, u_int *nallocp, int rekeying)
534{ 565{
535 struct timeval tv, *tvp; 566 struct timeval tv, *tvp;
567 int timeout_secs;
536 int ret; 568 int ret;
537 569
538 /* Add any selections by the channel mechanism. */ 570 /* Add any selections by the channel mechanism. */
@@ -576,16 +608,27 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
576 /* 608 /*
577 * Wait for something to happen. This will suspend the process until 609 * Wait for something to happen. This will suspend the process until
578 * some selected descriptor can be read, written, or has some other 610 * some selected descriptor can be read, written, or has some other
579 * event pending. 611 * event pending, or a timeout expires.
580 */ 612 */
581 613
582 if (options.server_alive_interval == 0 || !compat20) 614 timeout_secs = INT_MAX; /* we use INT_MAX to mean no timeout */
615 if (options.server_alive_interval > 0 && compat20)
616 timeout_secs = options.server_alive_interval;
617 set_control_persist_exit_time();
618 if (control_persist_exit_time > 0) {
619 timeout_secs = MIN(timeout_secs,
620 control_persist_exit_time - time(NULL));
621 if (timeout_secs < 0)
622 timeout_secs = 0;
623 }
624 if (timeout_secs == INT_MAX)
583 tvp = NULL; 625 tvp = NULL;
584 else { 626 else {
585 tv.tv_sec = options.server_alive_interval; 627 tv.tv_sec = timeout_secs;
586 tv.tv_usec = 0; 628 tv.tv_usec = 0;
587 tvp = &tv; 629 tvp = &tv;
588 } 630 }
631
589 ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp); 632 ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp);
590 if (ret < 0) { 633 if (ret < 0) {
591 char buf[100]; 634 char buf[100];
@@ -1478,6 +1521,18 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
1478 */ 1521 */
1479 if (FD_ISSET(connection_out, writeset)) 1522 if (FD_ISSET(connection_out, writeset))
1480 packet_write_poll(); 1523 packet_write_poll();
1524
1525 /*
1526 * If we are a backgrounded control master, and the
1527 * timeout has expired without any active client
1528 * connections, then quit.
1529 */
1530 if (control_persist_exit_time > 0) {
1531 if (time(NULL) >= control_persist_exit_time) {
1532 debug("ControlPersist timeout expired");
1533 break;
1534 }
1535 }
1481 } 1536 }
1482 if (readset) 1537 if (readset)
1483 xfree(readset); 1538 xfree(readset);
diff --git a/readconf.c b/readconf.c
index da48ae7da..0296590e2 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: readconf.c,v 1.186 2010/06/25 23:15:36 djm Exp $ */ 1/* $OpenBSD: readconf.c,v 1.187 2010/07/19 09:15:12 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -128,7 +128,8 @@ typedef enum {
128 oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, 128 oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
129 oAddressFamily, oGssAuthentication, oGssDelegateCreds, 129 oAddressFamily, oGssAuthentication, oGssDelegateCreds,
130 oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, 130 oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
131 oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, 131 oSendEnv, oControlPath, oControlMaster, oControlPersist,
132 oHashKnownHosts,
132 oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, 133 oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
133 oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication, 134 oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication,
134 oDeprecated, oUnsupported 135 oDeprecated, oUnsupported
@@ -225,6 +226,7 @@ static struct {
225 { "sendenv", oSendEnv }, 226 { "sendenv", oSendEnv },
226 { "controlpath", oControlPath }, 227 { "controlpath", oControlPath },
227 { "controlmaster", oControlMaster }, 228 { "controlmaster", oControlMaster },
229 { "controlpersist", oControlPersist },
228 { "hashknownhosts", oHashKnownHosts }, 230 { "hashknownhosts", oHashKnownHosts },
229 { "tunnel", oTunnel }, 231 { "tunnel", oTunnel },
230 { "tunneldevice", oTunnelDevice }, 232 { "tunneldevice", oTunnelDevice },
@@ -882,6 +884,30 @@ parse_int:
882 *intptr = value; 884 *intptr = value;
883 break; 885 break;
884 886
887 case oControlPersist:
888 /* no/false/yes/true, or a time spec */
889 intptr = &options->control_persist;
890 arg = strdelim(&s);
891 if (!arg || *arg == '\0')
892 fatal("%.200s line %d: Missing ControlPersist"
893 " argument.", filename, linenum);
894 value = 0;
895 value2 = 0; /* timeout */
896 if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0)
897 value = 0;
898 else if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0)
899 value = 1;
900 else if ((value2 = convtime(arg)) >= 0)
901 value = 1;
902 else
903 fatal("%.200s line %d: Bad ControlPersist argument.",
904 filename, linenum);
905 if (*activep && *intptr == -1) {
906 *intptr = value;
907 options->control_persist_timeout = value2;
908 }
909 break;
910
885 case oHashKnownHosts: 911 case oHashKnownHosts:
886 intptr = &options->hash_known_hosts; 912 intptr = &options->hash_known_hosts;
887 goto parse_flag; 913 goto parse_flag;
@@ -1083,6 +1109,8 @@ initialize_options(Options * options)
1083 options->num_send_env = 0; 1109 options->num_send_env = 0;
1084 options->control_path = NULL; 1110 options->control_path = NULL;
1085 options->control_master = -1; 1111 options->control_master = -1;
1112 options->control_persist = -1;
1113 options->control_persist_timeout = 0;
1086 options->hash_known_hosts = -1; 1114 options->hash_known_hosts = -1;
1087 options->tun_open = -1; 1115 options->tun_open = -1;
1088 options->tun_local = -1; 1116 options->tun_local = -1;
@@ -1218,6 +1246,10 @@ fill_default_options(Options * options)
1218 options->server_alive_count_max = 3; 1246 options->server_alive_count_max = 3;
1219 if (options->control_master == -1) 1247 if (options->control_master == -1)
1220 options->control_master = 0; 1248 options->control_master = 0;
1249 if (options->control_persist == -1) {
1250 options->control_persist = 0;
1251 options->control_persist_timeout = 0;
1252 }
1221 if (options->hash_known_hosts == -1) 1253 if (options->hash_known_hosts == -1)
1222 options->hash_known_hosts = 0; 1254 options->hash_known_hosts = 0;
1223 if (options->tun_open == -1) 1255 if (options->tun_open == -1)
diff --git a/readconf.h b/readconf.h
index 66acafdef..95d104674 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
1/* $OpenBSD: readconf.h,v 1.85 2010/06/25 23:15:36 djm Exp $ */ 1/* $OpenBSD: readconf.h,v 1.86 2010/07/19 09:15:12 djm Exp $ */
2 2
3/* 3/*
4 * Author: Tatu Ylonen <ylo@cs.hut.fi> 4 * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -114,6 +114,8 @@ typedef struct {
114 114
115 char *control_path; 115 char *control_path;
116 int control_master; 116 int control_master;
117 int control_persist; /* ControlPersist flag */
118 int control_persist_timeout; /* ControlPersist timeout (seconds) */
117 119
118 int hash_known_hosts; 120 int hash_known_hosts;
119 121
diff --git a/ssh.c b/ssh.c
index 61fe10df0..249be2db8 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssh.c,v 1.343 2010/07/12 22:41:13 djm Exp $ */ 1/* $OpenBSD: ssh.c,v 1.344 2010/07/19 09:15:12 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -128,6 +128,15 @@ int no_shell_flag = 0;
128int stdin_null_flag = 0; 128int stdin_null_flag = 0;
129 129
130/* 130/*
131 * Flag indicating that the current process should be backgrounded and
132 * a new slave launched in the foreground for ControlPersist.
133 */
134int need_controlpersist_detach = 0;
135
136/* Copies of flags for ControlPersist foreground slave */
137int ostdin_null_flag, ono_shell_flag, ono_tty_flag, otty_flag;
138
139/*
131 * Flag indicating that ssh should fork after authentication. This is useful 140 * Flag indicating that ssh should fork after authentication. This is useful
132 * so that the passphrase can be entered manually, and then ssh goes to the 141 * so that the passphrase can be entered manually, and then ssh goes to the
133 * background. 142 * background.
@@ -877,6 +886,50 @@ main(int ac, char **av)
877 return exit_status; 886 return exit_status;
878} 887}
879 888
889static void
890control_persist_detach(void)
891{
892 pid_t pid;
893
894 debug("%s: backgrounding master process", __func__);
895
896 /*
897 * master (current process) into the background, and make the
898 * foreground process a client of the backgrounded master.
899 */
900 switch ((pid = fork())) {
901 case -1:
902 fatal("%s: fork: %s", __func__, strerror(errno));
903 case 0:
904 /* Child: master process continues mainloop */
905 break;
906 default:
907 /* Parent: set up mux slave to connect to backgrounded master */
908 debug2("%s: background process is %ld", __func__, (long)pid);
909 stdin_null_flag = ostdin_null_flag;
910 no_shell_flag = ono_shell_flag;
911 no_tty_flag = ono_tty_flag;
912 tty_flag = otty_flag;
913 close(muxserver_sock);
914 muxserver_sock = -1;
915 muxclient(options.control_path);
916 /* muxclient() doesn't return on success. */
917 fatal("Failed to connect to new control master");
918 }
919}
920
921/* Do fork() after authentication. Used by "ssh -f" */
922static void
923fork_postauth(void)
924{
925 if (need_controlpersist_detach)
926 control_persist_detach();
927 debug("forking to background");
928 fork_after_authentication_flag = 0;
929 if (daemon(1, 1) < 0)
930 fatal("daemon() failed: %.200s", strerror(errno));
931}
932
880/* Callback for remote forward global requests */ 933/* Callback for remote forward global requests */
881static void 934static void
882ssh_confirm_remote_forward(int type, u_int32_t seq, void *ctxt) 935ssh_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)
904 } 957 }
905 if (++remote_forward_confirms_received == options.num_remote_forwards) { 958 if (++remote_forward_confirms_received == options.num_remote_forwards) {
906 debug("All remote forwarding requests processed"); 959 debug("All remote forwarding requests processed");
907 if (fork_after_authentication_flag) { 960 if (fork_after_authentication_flag)
908 fork_after_authentication_flag = 0; 961 fork_postauth();
909 if (daemon(1, 1) < 0)
910 fatal("daemon() failed: %.200s",
911 strerror(errno));
912 }
913 } 962 }
914} 963}
915 964
@@ -1153,12 +1202,13 @@ ssh_session(void)
1153 * If requested and we are not interested in replies to remote 1202 * If requested and we are not interested in replies to remote
1154 * forwarding requests, then let ssh continue in the background. 1203 * forwarding requests, then let ssh continue in the background.
1155 */ 1204 */
1156 if (fork_after_authentication_flag && 1205 if (fork_after_authentication_flag) {
1157 (!options.exit_on_forward_failure || 1206 if (options.exit_on_forward_failure &&
1158 options.num_remote_forwards == 0)) { 1207 options.num_remote_forwards > 0) {
1159 fork_after_authentication_flag = 0; 1208 debug("deferring postauth fork until remote forward "
1160 if (daemon(1, 1) < 0) 1209 "confirmation received");
1161 fatal("daemon() failed: %.200s", strerror(errno)); 1210 } else
1211 fork_postauth();
1162 } 1212 }
1163 1213
1164 /* 1214 /*
@@ -1281,6 +1331,31 @@ ssh_session2(void)
1281 /* XXX should be pre-session */ 1331 /* XXX should be pre-session */
1282 ssh_init_forwarding(); 1332 ssh_init_forwarding();
1283 1333
1334 /* Start listening for multiplex clients */
1335 muxserver_listen();
1336
1337 /*
1338 * If we are in control persist mode, then prepare to background
1339 * ourselves and have a foreground client attach as a control
1340 * slave. NB. we must save copies of the flags that we override for
1341 * the backgrounding, since we defer attachment of the slave until
1342 * after the connection is fully established (in particular,
1343 * async rfwd replies have been received for ExitOnForwardFailure).
1344 */
1345 if (options.control_persist && muxserver_sock != -1) {
1346 ostdin_null_flag = stdin_null_flag;
1347 ono_shell_flag = no_shell_flag;
1348 ono_tty_flag = no_tty_flag;
1349 otty_flag = tty_flag;
1350 stdin_null_flag = 1;
1351 no_shell_flag = 1;
1352 no_tty_flag = 1;
1353 tty_flag = 0;
1354 if (!fork_after_authentication_flag)
1355 need_controlpersist_detach = 1;
1356 fork_after_authentication_flag = 1;
1357 }
1358
1284 if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN)) 1359 if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN))
1285 id = ssh_session2_open(); 1360 id = ssh_session2_open();
1286 1361
@@ -1299,19 +1374,17 @@ ssh_session2(void)
1299 options.permit_local_command) 1374 options.permit_local_command)
1300 ssh_local_cmd(options.local_command); 1375 ssh_local_cmd(options.local_command);
1301 1376
1302 /* Start listening for multiplex clients */
1303 muxserver_listen();
1304
1305 /* 1377 /*
1306 * If requested and we are not interested in replies to remote 1378 * If requested and we are not interested in replies to remote
1307 * forwarding requests, then let ssh continue in the background. 1379 * forwarding requests, then let ssh continue in the background.
1308 */ 1380 */
1309 if (fork_after_authentication_flag && 1381 if (fork_after_authentication_flag) {
1310 (!options.exit_on_forward_failure || 1382 if (options.exit_on_forward_failure &&
1311 options.num_remote_forwards == 0)) { 1383 options.num_remote_forwards > 0) {
1312 fork_after_authentication_flag = 0; 1384 debug("deferring postauth fork until remote forward "
1313 if (daemon(1, 1) < 0) 1385 "confirmation received");
1314 fatal("daemon() failed: %.200s", strerror(errno)); 1386 } else
1387 fork_postauth();
1315 } 1388 }
1316 1389
1317 if (options.use_roaming) 1390 if (options.use_roaming)
diff --git a/ssh_config.5 b/ssh_config.5
index e7bb21ebb..04df8184c 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -34,8 +34,8 @@
34.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36.\" 36.\"
37.\" $OpenBSD: ssh_config.5,v 1.136 2010/07/12 22:41:13 djm Exp $ 37.\" $OpenBSD: ssh_config.5,v 1.137 2010/07/19 09:15:12 djm Exp $
38.Dd $Mdocdate: July 12 2010 $ 38.Dd $Mdocdate: July 19 2010 $
39.Dt SSH_CONFIG 5 39.Dt SSH_CONFIG 5
40.Os 40.Os
41.Sh NAME 41.Sh NAME
@@ -319,6 +319,28 @@ It is recommended that any
319used for opportunistic connection sharing include 319used for opportunistic connection sharing include
320at least %h, %p, and %r. 320at least %h, %p, and %r.
321This ensures that shared connections are uniquely identified. 321This ensures that shared connections are uniquely identified.
322.It Cm ControlPersist
323When used in conjunction with
324.Cm ControlMaster ,
325specifies that the master connection should remain open
326in the background (waiting for future client connections)
327after the initial client connection has been closed.
328If set to
329.Dq no ,
330then the master connection will not be placed into the background,
331and will close as soon as the initial client connection is closed.
332If set to
333.Dq yes ,
334then the master connection will remain in the background indefinitely
335(until killed or closed via a mechanism such as the
336.Xr ssh 1
337.Dq Fl O No exit
338option).
339If set to a time in seconds, or a time in any of the formats documented in
340.Xr sshd_config 5 ,
341then the backgrounded master connection will automatically terminate
342after it has remained idle (with no client connections) for the
343specified time.
322.It Cm DynamicForward 344.It Cm DynamicForward
323Specifies that a TCP port on the local machine be forwarded 345Specifies that a TCP port on the local machine be forwarded
324over the secure channel, and the application 346over the secure channel, and the application