upstream: Add a StdinNull directive to ssh_config(5) that allows

the config file to do the same thing as -n does on the ssh(1) commandline.
Patch from Volker Diels-Grabsch via GHPR231; ok dtucker

OpenBSD-Commit-ID: 66ddf3f15c76796d4dcd22ff464aed1edd62468e
This commit is contained in:
djm@openbsd.org 2021-07-23 04:00:59 +00:00 committed by Damien Miller
parent e3957e21ff
commit e0c5088f1c
8 changed files with 138 additions and 60 deletions

View File

@ -1,4 +1,4 @@
/* $OpenBSD: clientloop.c,v 1.367 2021/07/16 09:00:23 djm Exp $ */
/* $OpenBSD: clientloop.c,v 1.368 2021/07/23 04:00:59 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -115,9 +115,6 @@
/* import options */
extern Options options;
/* Flag indicating that stdin should be redirected from /dev/null. */
extern int stdin_null_flag;
/* Flag indicating that ssh should daemonise after authentication is complete */
extern int fork_after_authentication_flag;

7
mux.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: mux.c,v 1.90 2021/07/13 23:48:36 djm Exp $ */
/* $OpenBSD: mux.c,v 1.91 2021/07/23 04:00:59 djm Exp $ */
/*
* Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org>
*
@ -71,7 +71,6 @@
/* from ssh.c */
extern int tty_flag;
extern Options options;
extern int stdin_null_flag;
extern char *host;
extern struct sshbuf *command;
extern volatile sig_atomic_t quit_pending;
@ -1879,7 +1878,7 @@ mux_client_request_session(int fd)
ssh_signal(SIGPIPE, SIG_IGN);
if (stdin_null_flag && stdfd_devnull(1, 0, 0) == -1)
if (options.stdin_null && stdfd_devnull(1, 0, 0) == -1)
fatal_f("stdfd_devnull failed");
if ((term = lookup_env_in_list("TERM", options.setenv,
@ -2102,7 +2101,7 @@ mux_client_request_stdio_fwd(int fd)
ssh_signal(SIGPIPE, SIG_IGN);
if (stdin_null_flag && stdfd_devnull(1, 0, 0) == -1)
if (options.stdin_null && stdfd_devnull(1, 0, 0) == -1)
fatal_f("stdfd_devnull failed");
if ((m = sshbuf_new()) == NULL)

View File

@ -1,4 +1,4 @@
/* $OpenBSD: readconf.c,v 1.359 2021/07/13 23:48:36 djm Exp $ */
/* $OpenBSD: readconf.c,v 1.360 2021/07/23 04:00:59 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -167,7 +167,7 @@ typedef enum {
oTunnel, oTunnelDevice,
oLocalCommand, oPermitLocalCommand, oRemoteCommand,
oVisualHostKey,
oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType,
oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType, oStdinNull,
oIgnoreUnknown, oProxyUseFdpass,
oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
@ -299,6 +299,7 @@ static struct {
{ "ipqos", oIPQoS },
{ "requesttty", oRequestTTY },
{ "sessiontype", oSessionType },
{ "stdinnull", oStdinNull },
{ "proxyusefdpass", oProxyUseFdpass },
{ "canonicaldomains", oCanonicalDomains },
{ "canonicalizefallbacklocal", oCanonicalizeFallbackLocal },
@ -1954,6 +1955,10 @@ parse_pubkey_algos:
multistate_ptr = multistate_sessiontype;
goto parse_multistate;
case oStdinNull:
intptr = &options->stdin_null;
goto parse_flag;
case oIgnoreUnknown:
charptr = &options->ignored_unknown;
goto parse_string;
@ -2377,6 +2382,7 @@ initialize_options(Options * options)
options->ip_qos_bulk = -1;
options->request_tty = -1;
options->session_type = -1;
options->stdin_null = -1;
options->proxy_use_fdpass = -1;
options->ignored_unknown = NULL;
options->num_canonical_domains = 0;
@ -2565,6 +2571,8 @@ fill_default_options(Options * options)
options->request_tty = REQUEST_TTY_AUTO;
if (options->session_type == -1)
options->session_type = SESSION_TYPE_DEFAULT;
if (options->stdin_null == -1)
options->stdin_null = 0;
if (options->proxy_use_fdpass == -1)
options->proxy_use_fdpass = 0;
if (options->canonicalize_max_dots == -1)
@ -3243,6 +3251,7 @@ dump_client_config(Options *o, const char *host)
dump_cfg_fmtint(oPubkeyAuthentication, o->pubkey_authentication);
dump_cfg_fmtint(oRequestTTY, o->request_tty);
dump_cfg_fmtint(oSessionType, o->session_type);
dump_cfg_fmtint(oStdinNull, o->stdin_null);
dump_cfg_fmtint(oStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink);
dump_cfg_fmtint(oStrictHostKeyChecking, o->strict_host_key_checking);
dump_cfg_fmtint(oTCPKeepAlive, o->tcp_keep_alive);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: readconf.h,v 1.142 2021/07/13 23:48:36 djm Exp $ */
/* $OpenBSD: readconf.h,v 1.143 2021/07/23 04:00:59 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -147,6 +147,7 @@ typedef struct {
int request_tty;
int session_type;
int stdin_null;
int proxy_use_fdpass;

10
ssh.1
View File

@ -33,8 +33,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.1,v 1.422 2021/07/13 23:48:36 djm Exp $
.Dd $Mdocdate: July 13 2021 $
.\" $OpenBSD: ssh.1,v 1.423 2021/07/23 04:00:59 djm Exp $
.Dd $Mdocdate: July 23 2021 $
.Dt SSH 1
.Os
.Sh NAME
@ -451,6 +451,11 @@ program will be put in the background.
needs to ask for a password or passphrase; see also the
.Fl f
option.)
Refer to the description of
.Cm StdinNull
in
.Xr ssh_config 5
for details.
.Pp
.It Fl O Ar ctl_cmd
Control an active connection multiplexing master process.
@ -553,6 +558,7 @@ For full details of the options listed below, and their possible values, see
.It ServerAliveCountMax
.It SessionType
.It SetEnv
.It StdinNull
.It StreamLocalBindMask
.It StreamLocalBindUnlink
.It StrictHostKeyChecking

22
ssh.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssh.c,v 1.562 2021/07/17 00:38:11 djm Exp $ */
/* $OpenBSD: ssh.c,v 1.563 2021/07/23 04:00:59 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -126,12 +126,6 @@ int debug_flag = 0;
/* Flag indicating whether a tty should be requested */
int tty_flag = 0;
/*
* Flag indicating that nothing should be read from stdin. This can be set
* on the command line.
*/
int stdin_null_flag = 0;
/*
* Flag indicating that the current process should be backgrounded and
* a new mux-client launched in the foreground for ControlPersist.
@ -723,11 +717,11 @@ main(int ac, char **av)
options.address_family = AF_INET6;
break;
case 'n':
stdin_null_flag = 1;
options.stdin_null = 1;
break;
case 'f':
fork_after_authentication_flag = 1;
stdin_null_flag = 1;
options.stdin_null = 1;
break;
case 'x':
options.forward_x11 = 0;
@ -1357,7 +1351,7 @@ main(int ac, char **av)
(muxclient_command && muxclient_command != SSHMUX_COMMAND_PROXY))
tty_flag = 0;
/* Do not allocate a tty if stdin is not a tty. */
if ((!isatty(fileno(stdin)) || stdin_null_flag) &&
if ((!isatty(fileno(stdin)) || options.stdin_null) &&
options.request_tty != REQUEST_TTY_FORCE) {
if (tty_flag)
logit("Pseudo-terminal will not be allocated because "
@ -1734,7 +1728,7 @@ control_persist_detach(void)
default:
/* Parent: set up mux client to connect to backgrounded master */
debug2_f("background process is %ld", (long)pid);
stdin_null_flag = ostdin_null_flag;
options.stdin_null = ostdin_null_flag;
options.request_tty = orequest_tty;
tty_flag = otty_flag;
options.session_type = osession_type;
@ -2075,7 +2069,7 @@ ssh_session2_open(struct ssh *ssh)
Channel *c;
int window, packetmax, in, out, err;
if (stdin_null_flag) {
if (options.stdin_null) {
in = open(_PATH_DEVNULL, O_RDONLY);
} else {
in = dup(STDIN_FILENO);
@ -2144,11 +2138,11 @@ ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
* async rfwd replies have been received for ExitOnForwardFailure).
*/
if (options.control_persist && muxserver_sock != -1) {
ostdin_null_flag = stdin_null_flag;
ostdin_null_flag = options.stdin_null;
osession_type = options.session_type;
orequest_tty = options.request_tty;
otty_flag = tty_flag;
stdin_null_flag = 1;
options.stdin_null = 1;
options.session_type = SESSION_TYPE_NONE;
tty_flag = 0;
if (!fork_after_authentication_flag &&

View File

@ -33,8 +33,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.357 2021/07/14 06:46:38 jmc Exp $
.Dd $Mdocdate: July 14 2021 $
.\" $OpenBSD: ssh_config.5,v 1.358 2021/07/23 04:00:59 djm Exp $
.Dd $Mdocdate: July 23 2021 $
.Dt SSH_CONFIG 5
.Os
.Sh NAME
@ -1675,6 +1675,22 @@ Similarly to
with the exception of the
.Ev TERM
variable, the server must be prepared to accept the environment variable.
.It Cm StdinNull
Redirects stdin from
.Pa /dev/null
(actually, prevents reading from stdin).
Either this or the equivalent
.Fl n
option must be used when
.Nm ssh
is run in the background.
The argument to this keyword must be
.Cm yes
(same as the
.Fl n
option) or
.Cm no
(the default).
.It Cm StreamLocalBindMask
Sets the octal file creation mode mask
.Pq umask

118
sshsig.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshsig.c,v 1.20 2021/01/31 10:50:10 dtucker Exp $ */
/* $OpenBSD: sshsig.c,v 1.21 2021/07/23 04:00:59 djm Exp $ */
/*
* Copyright (c) 2019 Google LLC
*
@ -616,6 +616,7 @@ sshsig_verify_fd(struct sshbuf *signature, int fd,
struct sshsigopt {
int ca;
char *namespaces;
uint64_t valid_after, valid_before;
};
struct sshsigopt *
@ -624,6 +625,7 @@ sshsigopt_parse(const char *opts, const char *path, u_long linenum,
{
struct sshsigopt *ret;
int r;
char *opt;
const char *errstr = NULL;
if ((ret = calloc(1, sizeof(*ret))) == NULL)
@ -643,6 +645,34 @@ sshsigopt_parse(const char *opts, const char *path, u_long linenum,
ret->namespaces = opt_dequote(&opts, &errstr);
if (ret->namespaces == NULL)
goto fail;
} else if (opt_match(&opts, "valid-after")) {
if (ret->valid_after != 0) {
errstr = "multiple \"valid-after\" clauses";
goto fail;
}
if ((opt = opt_dequote(&opts, &errstr)) == NULL)
goto fail;
if (parse_absolute_time(opt, &ret->valid_after) != 0 ||
ret->valid_after == 0) {
free(opt);
errstr = "invalid \"valid-after\" time";
goto fail;
}
free(opt);
} else if (opt_match(&opts, "valid-before")) {
if (ret->valid_before != 0) {
errstr = "multiple \"valid-before\" clauses";
goto fail;
}
if ((opt = opt_dequote(&opts, &errstr)) == NULL)
goto fail;
if (parse_absolute_time(opt, &ret->valid_before) != 0 ||
ret->valid_before == 0) {
free(opt);
errstr = "invalid \"valid-before\" time";
goto fail;
}
free(opt);
}
/*
* Skip the comma, and move to the next option
@ -661,6 +691,12 @@ sshsigopt_parse(const char *opts, const char *path, u_long linenum,
goto fail;
}
}
/* final consistency check */
if (ret->valid_after != 0 && ret->valid_before != 0 &&
ret->valid_before <= ret->valid_after) {
errstr = "\"valid-before\" time is before \"valid-after\"";
goto fail;
}
/* success */
return ret;
fail:
@ -779,12 +815,13 @@ parse_principals_key_and_options(const char *path, u_long linenum, char *line,
static int
check_allowed_keys_line(const char *path, u_long linenum, char *line,
const struct sshkey *sign_key, const char *principal,
const char *sig_namespace)
const char *sig_namespace, uint64_t verify_time)
{
struct sshkey *found_key = NULL;
int r, found = 0;
int r, success = 0;
const char *reason = NULL;
struct sshsigopt *sigopts = NULL;
char tvalid[64], tverify[64];
/* Parse the line */
if ((r = parse_principals_key_and_options(path, linenum, line,
@ -793,6 +830,24 @@ check_allowed_keys_line(const char *path, u_long linenum, char *line,
goto done;
}
if (!sigopts->ca && sshkey_equal(found_key, sign_key)) {
/* Exact match of key */
debug("%s:%lu: matched key", path, linenum);
} else if (sigopts->ca && sshkey_is_cert(sign_key) &&
sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
/* Match of certificate's CA key */
if ((r = sshkey_cert_check_authority(sign_key, 0, 1, 0,
verify_time, principal, &reason)) != 0) {
error("%s:%lu: certificate not authorized: %s",
path, linenum, reason);
goto done;
}
debug("%s:%lu: matched certificate CA key", path, linenum);
} else {
/* Didn't match key */
goto done;
}
/* Check whether options preclude the use of this key */
if (sigopts->namespaces != NULL &&
match_pattern_list(sig_namespace, sigopts->namespaces, 0) != 1) {
@ -801,36 +856,37 @@ check_allowed_keys_line(const char *path, u_long linenum, char *line,
goto done;
}
if (!sigopts->ca && sshkey_equal(found_key, sign_key)) {
/* Exact match of key */
debug("%s:%lu: matched key and principal", path, linenum);
/* success */
found = 1;
} else if (sigopts->ca && sshkey_is_cert(sign_key) &&
sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
/* Match of certificate's CA key */
if ((r = sshkey_cert_check_authority(sign_key, 0, 1, 0,
principal, &reason)) != 0) {
error("%s:%lu: certificate not authorized: %s",
path, linenum, reason);
goto done;
}
debug("%s:%lu: matched certificate CA key", path, linenum);
/* success */
found = 1;
} else {
/* Principal matched but key didn't */
/* check key time validity */
format_absolute_time((uint64_t)verify_time, tverify, sizeof(tverify));
if (sigopts->valid_after != 0 &&
(uint64_t)verify_time < sigopts->valid_after) {
format_absolute_time(sigopts->valid_after,
tvalid, sizeof(tvalid));
error("%s:%lu: key is not yet valid: "
"verify time %s < valid-after %s", path, linenum,
tverify, tvalid);
goto done;
}
if (sigopts->valid_before != 0 &&
(uint64_t)verify_time > sigopts->valid_before) {
format_absolute_time(sigopts->valid_before,
tvalid, sizeof(tvalid));
error("%s:%lu: key has expired: "
"verify time %s > valid-before %s", path, linenum,
tverify, tvalid);
goto done;
}
success = 1;
done:
sshkey_free(found_key);
sshsigopt_free(sigopts);
return found ? 0 : SSH_ERR_KEY_NOT_FOUND;
return success ? 0 : SSH_ERR_KEY_NOT_FOUND;
}
int
sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
const char *principal, const char *sig_namespace)
const char *principal, const char *sig_namespace, uint64_t verify_time)
{
FILE *f = NULL;
char *line = NULL;
@ -850,7 +906,7 @@ sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
while (getline(&line, &linesize, f) != -1) {
linenum++;
r = check_allowed_keys_line(path, linenum, line, sign_key,
principal, sig_namespace);
principal, sig_namespace, verify_time);
free(line);
line = NULL;
linesize = 0;
@ -871,7 +927,7 @@ sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
static int
cert_filter_principals(const char *path, u_long linenum,
char **principalsp, const struct sshkey *cert)
char **principalsp, const struct sshkey *cert, uint64_t verify_time)
{
char *cp, *oprincipals, *principals;
const char *reason;
@ -894,7 +950,7 @@ cert_filter_principals(const char *path, u_long linenum,
}
/* Check against principals list in certificate */
if ((r = sshkey_cert_check_authority(cert, 0, 1, 0,
cp, &reason)) != 0) {
verify_time, cp, &reason)) != 0) {
debug("%s:%lu: principal \"%s\" not authorized: %s",
path, linenum, cp, reason);
continue;
@ -925,7 +981,7 @@ cert_filter_principals(const char *path, u_long linenum,
static int
get_matching_principals_from_line(const char *path, u_long linenum, char *line,
const struct sshkey *sign_key, char **principalsp)
const struct sshkey *sign_key, uint64_t verify_time, char **principalsp)
{
struct sshkey *found_key = NULL;
char *principals = NULL;
@ -951,7 +1007,7 @@ get_matching_principals_from_line(const char *path, u_long linenum, char *line,
sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
/* Remove principals listed in file but not allowed by cert */
if ((r = cert_filter_principals(path, linenum,
&principals, sign_key)) != 0) {
&principals, sign_key, verify_time)) != 0) {
/* error already displayed */
debug_r(r, "%s:%lu: cert_filter_principals",
path, linenum);
@ -977,7 +1033,7 @@ get_matching_principals_from_line(const char *path, u_long linenum, char *line,
int
sshsig_find_principals(const char *path, const struct sshkey *sign_key,
char **principals)
uint64_t verify_time, char **principals)
{
FILE *f = NULL;
char *line = NULL;
@ -996,7 +1052,7 @@ sshsig_find_principals(const char *path, const struct sshkey *sign_key,
while (getline(&line, &linesize, f) != -1) {
linenum++;
r = get_matching_principals_from_line(path, linenum, line,
sign_key, principals);
sign_key, verify_time, principals);
free(line);
line = NULL;
linesize = 0;