From dc7990be865450574c7940c9880567f5d2555b37 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Fri, 15 Apr 2016 00:30:19 +0000 Subject: upstream commit Include directive for ssh_config(5); feedback & ok markus@ Upstream-ID: ae3b76e2e343322b9f74acde6f1e1c5f027d5fff --- ssh_config.5 | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'ssh_config.5') diff --git a/ssh_config.5 b/ssh_config.5 index caf13a62d..880f11049 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -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.228 2016/02/20 23:01:46 sobrado Exp $ -.Dd $Mdocdate: February 20 2016 $ +.\" $OpenBSD: ssh_config.5,v 1.229 2016/04/15 00:30:19 djm Exp $ +.Dd $Mdocdate: April 15 2016 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1019,6 +1019,25 @@ It is recommended that .Cm IgnoreUnknown be listed early in the configuration file as it will not be applied to unknown options that appear before it. +.It Cm Include +Include the specified configuration file(s). +Multiple path names may be specified and each pathname may contain +.Xr glob 3 +wildcards and, for user configurations, shell-like +.Dq ~ +references to user home directories. +Files without absolute paths are assumed to be in +.Pa ~/.ssh +if included in a user configurations file or +.Pa /etc/ssh +if included from the system configuration file. +.Cm Include +directive may appear inside a +.Cm Match +or +.Cm Host +block +to perform conditional inclusion. .It Cm IPQoS Specifies the IPv4 type-of-service or DSCP class for connections. Accepted values are -- cgit v1.2.3 From 6aaabc2b610e44bae473457ad9556ffb43d90ee3 Mon Sep 17 00:00:00 2001 From: "jmc@openbsd.org" Date: Sun, 17 Apr 2016 14:34:46 +0000 Subject: upstream commit tweak previous; Upstream-ID: 46c1bab91c164078edbccd5f7d06b9058edd814f --- ssh_config.5 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'ssh_config.5') diff --git a/ssh_config.5 b/ssh_config.5 index 880f11049..10650e1bc 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -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.229 2016/04/15 00:30:19 djm Exp $ -.Dd $Mdocdate: April 15 2016 $ +.\" $OpenBSD: ssh_config.5,v 1.230 2016/04/17 14:34:46 jmc Exp $ +.Dd $Mdocdate: April 17 2016 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1021,14 +1021,14 @@ be listed early in the configuration file as it will not be applied to unknown options that appear before it. .It Cm Include Include the specified configuration file(s). -Multiple path names may be specified and each pathname may contain +Multiple pathnames may be specified and each pathname may contain .Xr glob 3 wildcards and, for user configurations, shell-like .Dq ~ references to user home directories. Files without absolute paths are assumed to be in .Pa ~/.ssh -if included in a user configurations file or +if included in a user configuration file or .Pa /etc/ssh if included from the system configuration file. .Cm Include -- cgit v1.2.3 From b02ad1ce9105bfa7394ac7590c0729dd52e26a81 Mon Sep 17 00:00:00 2001 From: "markus@openbsd.org" Date: Wed, 4 May 2016 12:21:53 +0000 Subject: upstream commit IdentityAgent for specifying specific agent sockets; ok djm@ Upstream-ID: 3e6a15eb89ea0fd406f108826b7dc7dec4fbfac1 --- readconf.c | 11 +++++++++-- readconf.h | 3 ++- ssh.1 | 5 +++-- ssh.c | 18 +++++++++++++++++- ssh_config.5 | 29 +++++++++++++++++++++++++++-- 5 files changed, 58 insertions(+), 8 deletions(-) (limited to 'ssh_config.5') diff --git a/readconf.c b/readconf.c index b348c9683..26436b3ac 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.252 2016/04/15 00:30:19 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.253 2016/05/04 12:21:53 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -147,7 +147,7 @@ typedef enum { oPasswordAuthentication, oRSAAuthentication, oChallengeResponseAuthentication, oXAuthLocation, oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, - oCertificateFile, oAddKeysToAgent, + oCertificateFile, oAddKeysToAgent, oIdentityAgent, oUser, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, @@ -217,6 +217,7 @@ static struct { { "identitiesonly", oIdentitiesOnly }, { "certificatefile", oCertificateFile }, { "addkeystoagent", oAddKeysToAgent }, + { "identityagent", oIdentityAgent }, { "hostname", oHostName }, { "hostkeyalias", oHostKeyAlias }, { "proxycommand", oProxyCommand }, @@ -1636,6 +1637,10 @@ parse_keytypes: multistate_ptr = multistate_yesnoaskconfirm; goto parse_multistate; + case oIdentityAgent: + charptr = &options->identity_agent; + goto parse_string; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); @@ -1814,6 +1819,7 @@ initialize_options(Options * options) options->local_command = NULL; options->permit_local_command = -1; options->add_keys_to_agent = -1; + options->identity_agent = NULL; options->visual_host_key = -1; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; @@ -2463,6 +2469,7 @@ dump_client_config(Options *o, const char *host) dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms); dump_cfg_string(oHostKeyAlias, o->host_key_alias); dump_cfg_string(oHostbasedKeyTypes, o->hostbased_key_types); + dump_cfg_string(oIdentityAgent, o->identity_agent); dump_cfg_string(oKbdInteractiveDevices, o->kbd_interactive_devices); dump_cfg_string(oKexAlgorithms, o->kex_algorithms ? o->kex_algorithms : KEX_CLIENT_KEX); dump_cfg_string(oLocalCommand, o->local_command); diff --git a/readconf.h b/readconf.h index 5f4451066..f0e498ea2 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.114 2016/04/15 00:30:19 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.115 2016/05/04 12:21:53 markus Exp $ */ /* * Author: Tatu Ylonen @@ -101,6 +101,7 @@ typedef struct { struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES]; int add_keys_to_agent; + char *identity_agent; /* Optional path to ssh-agent socket */ /* Local TCP/IP forward requests. */ int num_local_forwards; diff --git a/ssh.1 b/ssh.1 index 85309ecc4..9ed5a5662 100644 --- a/ssh.1 +++ b/ssh.1 @@ -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.370 2016/04/15 00:30:19 djm Exp $ -.Dd $Mdocdate: April 15 2016 $ +.\" $OpenBSD: ssh.1,v 1.371 2016/05/04 12:21:53 markus Exp $ +.Dd $Mdocdate: May 4 2016 $ .Dt SSH 1 .Os .Sh NAME @@ -501,6 +501,7 @@ For full details of the options listed below, and their possible values, see .It HostKeyAlgorithms .It HostKeyAlias .It HostName +.It IdentityAgent .It IdentityFile .It IdentitiesOnly .It Include diff --git a/ssh.c b/ssh.c index a881ba14c..ea52bbf5d 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.438 2016/04/29 08:07:53 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.439 2016/05/04 12:21:53 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1335,6 +1335,22 @@ main(int ac, char **av) /* load options.identity_files */ load_public_identity_files(); + /* optionally set the SSH_AUTHSOCKET_ENV_NAME varibale */ + if (options.identity_agent) { + if (strcmp(options.identity_agent, "none") == 0) { + unsetenv(SSH_AUTHSOCKET_ENV_NAME); + } else { + p = tilde_expand_filename(options.identity_agent, + original_real_uid); + cp = percent_expand(p, "d", pw->pw_dir, + "u", pw->pw_name, "l", thishost, "h", host, + "r", options.user, (char *)NULL); + setenv(SSH_AUTHSOCKET_ENV_NAME, cp, 1); + free(cp); + free(p); + } + } + /* Expand ~ in known host file names. */ tilde_expand_paths(options.system_hostfiles, options.num_system_hostfiles); diff --git a/ssh_config.5 b/ssh_config.5 index 10650e1bc..be790114a 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -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.230 2016/04/17 14:34:46 jmc Exp $ -.Dd $Mdocdate: April 17 2016 $ +.\" $OpenBSD: ssh_config.5,v 1.231 2016/05/04 12:21:53 markus Exp $ +.Dd $Mdocdate: May 4 2016 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -952,6 +952,31 @@ This option is intended for situations where ssh-agent offers many different identities. The default is .Dq no . +.It Cm IdentityAgent +Specifies the +.Ux Ns -domain +socket used to communicate with the authentication agent. +.Pp +This option overrides the +.Dq SSH_AUTH_SOCK +environment variable and can be used to select a specific agent. +Setting the socket name to +.Dq none +disables the use of an authentication agent. +.Pp +The socket name may use the tilde +syntax to refer to a user's home directory or one of the following +escape characters: +.Ql %d +(local user's home directory), +.Ql %u +(local user name), +.Ql %l +(local host name), +.Ql %h +(remote host name) or +.Ql %r +(remote user name). .It Cm IdentityFile Specifies a file from which the user's DSA, ECDSA, Ed25519 or RSA authentication identity is read. -- cgit v1.2.3 From 1a75d14daf4b60db903e6103cf50e74e0cd0a76b Mon Sep 17 00:00:00 2001 From: "markus@openbsd.org" Date: Wed, 4 May 2016 14:29:58 +0000 Subject: upstream commit allow setting IdentityAgent to SSH_AUTH_SOCK; ok djm@ Upstream-ID: 20c508480d8db3eef18942c0fc39b1fcf25652ac --- readconf.c | 3 ++- ssh.c | 5 +++-- ssh_config.5 | 7 ++++++- sshd_config.5 | 6 +++--- 4 files changed, 14 insertions(+), 7 deletions(-) (limited to 'ssh_config.5') diff --git a/readconf.c b/readconf.c index 26436b3ac..c706fbf46 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.253 2016/05/04 12:21:53 markus Exp $ */ +/* $OpenBSD: readconf.c,v 1.254 2016/05/04 14:29:58 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2034,6 +2034,7 @@ fill_default_options(Options * options) CLEAR_ON_NONE(options->proxy_command); CLEAR_ON_NONE(options->control_path); CLEAR_ON_NONE(options->revoked_host_keys); + /* options->identity_agent distinguishes NULL from 'none' */ /* options->user will be set in the main program if appropriate */ /* options->hostname will be set in the main program if appropriate */ /* options->host_key_alias should not be set by default */ diff --git a/ssh.c b/ssh.c index ea52bbf5d..7a536a0f7 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.439 2016/05/04 12:21:53 markus Exp $ */ +/* $OpenBSD: ssh.c,v 1.440 2016/05/04 14:29:58 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1336,7 +1336,8 @@ main(int ac, char **av) load_public_identity_files(); /* optionally set the SSH_AUTHSOCKET_ENV_NAME varibale */ - if (options.identity_agent) { + if (options.identity_agent && + strcmp(options.identity_agent, SSH_AUTHSOCKET_ENV_NAME) != 0) { if (strcmp(options.identity_agent, "none") == 0) { unsetenv(SSH_AUTHSOCKET_ENV_NAME); } else { diff --git a/ssh_config.5 b/ssh_config.5 index be790114a..45fe89202 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -33,7 +33,7 @@ .\" (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.231 2016/05/04 12:21:53 markus Exp $ +.\" $OpenBSD: ssh_config.5,v 1.232 2016/05/04 14:29:58 markus Exp $ .Dd $Mdocdate: May 4 2016 $ .Dt SSH_CONFIG 5 .Os @@ -963,6 +963,11 @@ environment variable and can be used to select a specific agent. Setting the socket name to .Dq none disables the use of an authentication agent. +If the string +.Dq SSH_AUTH_SOCK +is specified, the location of the socket will be read from the +.Ev SSH_AUTH_SOCK +environment variable. .Pp The socket name may use the tilde syntax to refer to a user's home directory or one of the following diff --git a/sshd_config.5 b/sshd_config.5 index 63807c030..479fa38eb 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -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: sshd_config.5,v 1.222 2016/04/27 13:53:48 jmc Exp $ -.Dd $Mdocdate: April 27 2016 $ +.\" $OpenBSD: sshd_config.5,v 1.223 2016/05/04 14:29:58 markus Exp $ +.Dd $Mdocdate: May 4 2016 $ .Dt SSHD_CONFIG 5 .Os .Sh NAME @@ -738,7 +738,7 @@ to an .It Cm HostKeyAgent Identifies the UNIX-domain socket used to communicate with an agent that has access to the private host keys. -If +If the string .Dq SSH_AUTH_SOCK is specified, the location of the socket will be read from the .Ev SSH_AUTH_SOCK -- cgit v1.2.3 From ed877ef653847d056bb433975d731b7a1132a979 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Fri, 15 Jul 2016 00:24:30 +0000 Subject: upstream commit Add a ProxyJump ssh_config(5) option and corresponding -J ssh(1) command-line flag to allow simplified indirection through a SSH bastion or "jump host". These options construct a proxy command that connects to the specified jump host(s) (more than one may be specified) and uses port-forwarding to establish a connection to the next destination. This codifies the safest way of indirecting connections through SSH servers and makes it easy to use. ok markus@ Upstream-ID: fa899cb8b26d889da8f142eb9774c1ea36b04397 --- misc.c | 63 +++++++++++++++++++++++++++++++++++++++- misc.h | 3 +- readconf.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- readconf.h | 8 ++++- ssh.1 | 24 +++++++++++++-- ssh.c | 77 +++++++++++++++++++++++++++++++++++++++--------- ssh_config.5 | 28 ++++++++++++++++-- 7 files changed, 271 insertions(+), 27 deletions(-) (limited to 'ssh_config.5') diff --git a/misc.c b/misc.c index 9d59ca6b2..9421b4d39 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.104 2016/04/06 06:42:17 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.105 2016/07/15 00:24:30 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. @@ -451,6 +451,67 @@ colon(char *cp) return NULL; } +/* + * Parse a [user@]host[:port] string. + * Caller must free returned user and host. + * Any of the pointer return arguments may be NULL (useful for syntax checking). + * If user was not specified then *userp will be set to NULL. + * If port was not specified then *portp will be -1. + * Returns 0 on success, -1 on failure. + */ +int +parse_user_host_port(const char *s, char **userp, char **hostp, int *portp) +{ + char *sdup, *cp, *tmp; + char *user = NULL, *host = NULL; + int port = -1, ret = -1; + + if (userp != NULL) + *userp = NULL; + if (hostp != NULL) + *hostp = NULL; + if (portp != NULL) + *portp = -1; + + if ((sdup = tmp = strdup(s)) == NULL) + return -1; + /* Extract optional username */ + if ((cp = strchr(tmp, '@')) != NULL) { + *cp = '\0'; + if (*tmp == '\0') + goto out; + if ((user = strdup(tmp)) == NULL) + goto out; + tmp = cp + 1; + } + /* Extract mandatory hostname */ + if ((cp = hpdelim(&tmp)) == NULL || *cp == '\0') + goto out; + host = xstrdup(cleanhostname(cp)); + /* Convert and verify optional port */ + if (tmp != NULL && *tmp != '\0') { + if ((port = a2port(tmp)) <= 0) + goto out; + } + /* Success */ + if (userp != NULL) { + *userp = user; + user = NULL; + } + if (hostp != NULL) { + *hostp = host; + host = NULL; + } + if (portp != NULL) + *portp = port; + ret = 0; + out: + free(sdup); + free(user); + free(host); + return ret; +} + /* function to assist building execv() arguments */ void addargs(arglist *args, char *fmt, ...) diff --git a/misc.h b/misc.h index 01432ba8b..7c76a6a72 100644 --- a/misc.h +++ b/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.56 2016/04/06 06:42:17 djm Exp $ */ +/* $OpenBSD: misc.h,v 1.57 2016/07/15 00:24:30 djm Exp $ */ /* * Author: Tatu Ylonen @@ -49,6 +49,7 @@ char *put_host_port(const char *, u_short); char *hpdelim(char **); char *cleanhostname(char *); char *colon(char *); +int parse_user_host_port(const char *, char **, char **, int *); long convtime(const char *); char *tilde_expand_filename(const char *, uid_t); char *percent_expand(const char *, ...) __attribute__((__sentinel__)); diff --git a/readconf.c b/readconf.c index 9dcc383da..cb2999d82 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.256 2016/06/03 04:09:38 dtucker Exp $ */ +/* $OpenBSD: readconf.c,v 1.257 2016/07/15 00:24:30 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -170,7 +170,7 @@ typedef enum { oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes, - oPubkeyAcceptedKeyTypes, + oPubkeyAcceptedKeyTypes, oProxyJump, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; @@ -295,6 +295,7 @@ static struct { { "hostbasedkeytypes", oHostbasedKeyTypes }, { "pubkeyacceptedkeytypes", oPubkeyAcceptedKeyTypes }, { "ignoreunknown", oIgnoreUnknown }, + { "proxyjump", oProxyJump }, { NULL, oBadOption } }; @@ -1121,6 +1122,9 @@ parse_char_array: case oProxyCommand: charptr = &options->proxy_command; + /* Ignore ProxyCommand if ProxyJump already specified */ + if (options->jump_host != NULL) + charptr = &options->jump_host; /* Skip below */ parse_command: if (s == NULL) fatal("%.200s line %d: Missing argument.", filename, linenum); @@ -1129,6 +1133,18 @@ parse_command: *charptr = xstrdup(s + len); return 0; + case oProxyJump: + if (s == NULL) { + fatal("%.200s line %d: Missing argument.", + filename, linenum); + } + len = strspn(s, WHITESPACE "="); + if (parse_jump(s + len, options, *activep) == -1) { + fatal("%.200s line %d: Invalid ProxyJump \"%s\"", + filename, linenum, s + len); + } + return 0; + case oPort: intptr = &options->port; parse_int: @@ -1789,6 +1805,10 @@ initialize_options(Options * options) options->hostname = NULL; options->host_key_alias = NULL; options->proxy_command = NULL; + options->jump_user = NULL; + options->jump_host = NULL; + options->jump_port = -1; + options->jump_extra = NULL; options->user = NULL; options->escape_char = -1; options->num_system_hostfiles = 0; @@ -2261,6 +2281,44 @@ parse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remo return (0); } +int +parse_jump(const char *s, Options *o, int active) +{ + char *orig, *sdup, *cp; + char *host = NULL, *user = NULL; + int ret = -1, port = -1; + + active &= o->proxy_command == NULL && o->jump_host == NULL; + + orig = sdup = xstrdup(s); + while ((cp = strsep(&sdup, ",")) && cp != NULL) { + if (active) { + /* First argument and configuration is active */ + if (parse_user_host_port(cp, &user, &host, &port) != 0) + goto out; + } else { + /* Subsequent argument or inactive configuration */ + if (parse_user_host_port(cp, NULL, NULL, NULL) != 0) + goto out; + } + active = 0; /* only check syntax for subsequent hosts */ + } + /* success */ + free(orig); + o->jump_user = user; + o->jump_host = host; + o->jump_port = port; + o->proxy_command = xstrdup("none"); + user = host = NULL; + if ((cp = strchr(s, ',')) != NULL && cp[1] != '\0') + o->jump_extra = xstrdup(cp + 1); + ret = 0; + out: + free(user); + free(host); + return ret; +} + /* XXX the following is a near-vebatim copy from servconf.c; refactor */ static const char * fmt_multistate_int(int val, const struct multistate *m) @@ -2412,7 +2470,7 @@ void dump_client_config(Options *o, const char *host) { int i; - char vbuf[5]; + char buf[8]; /* This is normally prepared in ssh_kex2 */ if (kex_assemble_names(KEX_DEFAULT_PK_ALG, &o->hostkeyalgorithms) != 0) @@ -2490,7 +2548,6 @@ dump_client_config(Options *o, const char *host) dump_cfg_string(oMacs, o->macs ? o->macs : KEX_CLIENT_MAC); dump_cfg_string(oPKCS11Provider, o->pkcs11_provider); dump_cfg_string(oPreferredAuthentications, o->preferred_authentications); - dump_cfg_string(oProxyCommand, o->proxy_command); dump_cfg_string(oPubkeyAcceptedKeyTypes, o->pubkey_key_types); dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys); dump_cfg_string(oXAuthLocation, o->xauth_location); @@ -2551,8 +2608,8 @@ dump_client_config(Options *o, const char *host) if (o->escape_char == SSH_ESCAPECHAR_NONE) printf("escapechar none\n"); else { - vis(vbuf, o->escape_char, VIS_WHITE, 0); - printf("escapechar %s\n", vbuf); + vis(buf, o->escape_char, VIS_WHITE, 0); + printf("escapechar %s\n", buf); } /* oIPQoS */ @@ -2566,4 +2623,30 @@ dump_client_config(Options *o, const char *host) /* oStreamLocalBindMask */ printf("streamlocalbindmask 0%o\n", o->fwd_opts.streamlocal_bind_mask); + + /* oProxyCommand / oProxyJump */ + if (o->jump_host == NULL) + dump_cfg_string(oProxyCommand, o->proxy_command); + else { + /* Check for numeric addresses */ + i = strchr(o->jump_host, ':') != NULL || + strspn(o->jump_host, "1234567890.") == strlen(o->jump_host); + snprintf(buf, sizeof(buf), "%d", o->jump_port); + printf("proxyjump %s%s%s%s%s%s%s%s%s\n", + /* optional user */ + o->jump_user == NULL ? "" : o->jump_user, + o->jump_user == NULL ? "" : "@", + /* opening [ if hostname is numeric */ + i ? "[" : "", + /* mandatory hostname */ + o->jump_host, + /* closing ] if hostname is numeric */ + i ? "]" : "", + /* optional port number */ + o->jump_port <= 0 ? "" : ":", + o->jump_port <= 0 ? "" : buf, + /* optional additional jump spec */ + o->jump_extra == NULL ? "" : ",", + o->jump_extra == NULL ? "" : o->jump_extra); + } } diff --git a/readconf.h b/readconf.h index a8b0b9188..cef55f71c 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.116 2016/06/03 03:14:41 dtucker Exp $ */ +/* $OpenBSD: readconf.h,v 1.117 2016/07/15 00:24:30 djm Exp $ */ /* * Author: Tatu Ylonen @@ -163,6 +163,11 @@ typedef struct { char *hostbased_key_types; char *pubkey_key_types; + char *jump_user; + char *jump_host; + int jump_port; + char *jump_extra; + char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ } Options; @@ -198,6 +203,7 @@ int process_config_line(Options *, struct passwd *, const char *, int read_config_file(const char *, struct passwd *, const char *, const char *, Options *, int); int parse_forward(struct Forward *, const char *, int, int); +int parse_jump(const char *, Options *, int); int default_ssh_port(void); int option_clear_or_none(const char *); void dump_client_config(Options *o, const char *host); diff --git a/ssh.1 b/ssh.1 index 32949b056..f3492b4d6 100644 --- a/ssh.1 +++ b/ssh.1 @@ -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.374 2016/06/29 17:14:28 jmc Exp $ -.Dd $Mdocdate: June 29 2016 $ +.\" $OpenBSD: ssh.1,v 1.375 2016/07/15 00:24:30 djm Exp $ +.Dd $Mdocdate: July 15 2016 $ .Dt SSH 1 .Os .Sh NAME @@ -52,6 +52,7 @@ .Op Fl F Ar configfile .Op Fl I Ar pkcs11 .Op Fl i Ar identity_file +.Oo Fl J Ar user Ns @ Oc Ns Ar host Ns Op : Ns Ar port .Op Fl L Ar address .Op Fl l Ar login_name .Op Fl m Ar mac_spec @@ -312,6 +313,24 @@ by appending .Pa -cert.pub to identity filenames. .Pp +.It Fl J Xo +.Sm off +.Oo Ar jump_user @ Oc +.Ar jump_host +.Ns Op : Ns Ar jump_port +.Sm on +.Xc +Connect to the target host by first making a +.Nm +connection to +.Ar jump_host +and then establishing a TCP forward to the ultimate destination from +there. +Multiple jump hops may be specified separated by comma characters. +This is a shortcut to specify a +.Cm ProxyJump +configuration directive. +.Pp .It Fl K Enables GSSAPI-based authentication and forwarding (delegation) of GSSAPI credentials to the server. @@ -523,6 +542,7 @@ For full details of the options listed below, and their possible values, see .It PreferredAuthentications .It Protocol .It ProxyCommand +.It ProxyJump .It ProxyUseFdpass .It PubkeyAcceptedKeyTypes .It PubkeyAuthentication diff --git a/ssh.c b/ssh.c index e7d4fd915..a9b685349 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.442 2016/06/03 04:09:39 dtucker Exp $ */ +/* $OpenBSD: ssh.c,v 1.443 2016/07/15 00:24:30 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -330,7 +330,7 @@ resolve_addr(const char *name, int port, char *caddr, size_t clen) * NB. this function must operate with a options having undefined members. */ static int -check_follow_cname(char **namep, const char *cname) +check_follow_cname(int direct, char **namep, const char *cname) { int i; struct allowed_cname *rule; @@ -342,9 +342,9 @@ check_follow_cname(char **namep, const char *cname) return 0; /* * Don't attempt to canonicalize names that will be interpreted by - * a proxy unless the user specifically requests so. + * a proxy or jump host unless the user specifically requests so. */ - if (!option_clear_or_none(options.proxy_command) && + if (!direct && options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS) return 0; debug3("%s: check \"%s\" CNAME \"%s\"", __func__, *namep, cname); @@ -371,7 +371,7 @@ check_follow_cname(char **namep, const char *cname) static struct addrinfo * resolve_canonicalize(char **hostp, int port) { - int i, ndots; + int i, direct, ndots; char *cp, *fullhost, newname[NI_MAXHOST]; struct addrinfo *addrs; @@ -382,7 +382,9 @@ resolve_canonicalize(char **hostp, int port) * Don't attempt to canonicalize names that will be interpreted by * a proxy unless the user specifically requests so. */ - if (!option_clear_or_none(options.proxy_command) && + direct = option_clear_or_none(options.proxy_command) && + options.jump_host == NULL; + if (!direct && options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS) return NULL; @@ -437,7 +439,7 @@ resolve_canonicalize(char **hostp, int port) /* Remove trailing '.' */ fullhost[strlen(fullhost) - 1] = '\0'; /* Follow CNAME if requested */ - if (!check_follow_cname(&fullhost, newname)) { + if (!check_follow_cname(direct, &fullhost, newname)) { debug("Canonicalized hostname \"%s\" => \"%s\"", *hostp, fullhost); } @@ -510,7 +512,7 @@ int main(int ac, char **av) { struct ssh *ssh = NULL; - int i, r, opt, exit_status, use_syslog, config_test = 0; + int i, r, opt, exit_status, use_syslog, direct, config_test = 0; char *p, *cp, *line, *argv0, buf[PATH_MAX], *host_arg, *logfile; char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; char cname[NI_MAXHOST], uidstr[32], *conn_hash_hex; @@ -603,7 +605,7 @@ main(int ac, char **av) again: while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" - "ACD:E:F:GI:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { + "ACD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { switch (opt) { case '1': options.protocol = SSH_PROTO_1; @@ -728,6 +730,15 @@ main(int ac, char **av) fprintf(stderr, "no support for PKCS#11.\n"); #endif break; + case 'J': + if (options.jump_host != NULL) + fatal("Only a single -J option permitted"); + if (options.proxy_command != NULL) + fatal("Cannot specify -J with ProxyCommand"); + if (parse_jump(optarg, &options, 1) == -1) + fatal("Invalid -J argument"); + options.proxy_command = xstrdup("none"); + break; case 't': if (options.request_tty == REQUEST_TTY_YES) options.request_tty = REQUEST_TTY_FORCE; @@ -739,8 +750,10 @@ main(int ac, char **av) debug_flag = 1; options.log_level = SYSLOG_LEVEL_DEBUG1; } else { - if (options.log_level < SYSLOG_LEVEL_DEBUG3) + if (options.log_level < SYSLOG_LEVEL_DEBUG3) { + debug_flag++; options.log_level++; + } } break; case 'V': @@ -1038,9 +1051,10 @@ main(int ac, char **av) * has specifically requested canonicalisation for this case via * CanonicalizeHostname=always */ - if (addrs == NULL && options.num_permitted_cnames != 0 && - (option_clear_or_none(options.proxy_command) || - options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) { + direct = option_clear_or_none(options.proxy_command) && + options.jump_host == NULL; + if (addrs == NULL && options.num_permitted_cnames != 0 && (direct || + options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) { if ((addrs = resolve_host(host, options.port, option_clear_or_none(options.proxy_command), cname, sizeof(cname))) == NULL) { @@ -1048,7 +1062,7 @@ main(int ac, char **av) if (option_clear_or_none(options.proxy_command)) cleanup_exit(255); /* logged in resolve_host */ } else - check_follow_cname(&host, cname); + check_follow_cname(direct, &host, cname); } /* @@ -1073,6 +1087,41 @@ main(int ac, char **av) /* Fill configuration defaults. */ fill_default_options(&options); + /* + * If ProxyJump option specified, then construct a ProxyCommand now. + */ + if (options.jump_host != NULL) { + char port_s[8]; + + /* Consistency check */ + if (options.proxy_command != NULL) + fatal("inconsistent options: ProxyCommand+ProxyJump"); + /* Never use FD passing for ProxyJump */ + options.proxy_use_fdpass = 0; + snprintf(port_s, sizeof(port_s), "%d", options.jump_port); + xasprintf(&options.proxy_command, + "ssh%s%s%s%s%s%s%s%s%s%.*s -W %%h:%%p %s", + /* Optional "-l user" argument if jump_user set */ + options.jump_user == NULL ? "" : " -l ", + options.jump_user == NULL ? "" : options.jump_user, + /* Optional "-p port" argument if jump_port set */ + options.jump_port <= 0 ? "" : " -p ", + options.jump_port <= 0 ? "" : port_s, + /* Optional additional jump hosts ",..." */ + options.jump_extra == NULL ? "" : " -J ", + options.jump_extra == NULL ? "" : options.jump_extra, + /* Optional "-F" argumment if -F specified */ + config == NULL ? "" : " -F ", + config == NULL ? "" : config, + /* Optional "-v" arguments if -v set */ + debug_flag ? " -" : "", + debug_flag, "vvv", + /* Mandatory hostname */ + options.jump_host); + debug("Setting implicit ProxyCommand from ProxyJump: %s", + options.proxy_command); + } + if (options.port == 0) options.port = default_ssh_port(); channel_set_af(options.address_family); diff --git a/ssh_config.5 b/ssh_config.5 index 45fe89202..860577023 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -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.232 2016/05/04 14:29:58 markus Exp $ -.Dd $Mdocdate: May 4 2016 $ +.\" $OpenBSD: ssh_config.5,v 1.233 2016/07/15 00:24:30 djm Exp $ +.Dd $Mdocdate: July 15 2016 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1358,6 +1358,30 @@ For example, the following directive would connect via an HTTP proxy at .Bd -literal -offset 3n ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p .Ed +.It Cm ProxyJump +Specifies one or more jump proxies as +.Xo +.Sm off +.Oo Ar user @ Oc +.Ar host +.Ns Op : Ns Ar port +.Sm on +.Xc . +Multiple proxies may be separated by comma characters. +Setting this option will cause +.Xr ssh 1 +to connect to the target host by first making a +.Xr ssh 1 +connection to the specified +.Cm ProxyJump +host and then establishing a +a TCP forwarding to the ultimate target from there. +.Pp +Note that this option will compete with the +.Cm ProxyCommand +option - whichever is specified first will prevent later instances of the +other from taking effect. +.Pp .It Cm ProxyUseFdpass Specifies that .Cm ProxyCommand -- cgit v1.2.3 From e4eb7d910976fbfc7ce3e90c95c11b07b483d0d7 Mon Sep 17 00:00:00 2001 From: "jmc@openbsd.org" Date: Sat, 16 Jul 2016 06:57:55 +0000 Subject: upstream commit - add proxyjump to the options list - formatting fixes - update usage() ok djm Upstream-ID: 43d318e14ce677a2eec8f21ef5ba2f9f68a59457 --- scp.1 | 5 +++-- sftp.1 | 5 +++-- ssh.1 | 18 +++++++++--------- ssh.c | 11 ++++++----- ssh_config.5 | 11 +++++------ 5 files changed, 26 insertions(+), 24 deletions(-) (limited to 'ssh_config.5') diff --git a/scp.1 b/scp.1 index 3f5459089..4ae877753 100644 --- a/scp.1 +++ b/scp.1 @@ -8,9 +8,9 @@ .\" .\" Created: Sun May 7 00:14:37 1995 ylo .\" -.\" $OpenBSD: scp.1,v 1.70 2016/06/29 17:14:28 jmc Exp $ +.\" $OpenBSD: scp.1,v 1.71 2016/07/16 06:57:55 jmc Exp $ .\" -.Dd $Mdocdate: June 29 2016 $ +.Dd $Mdocdate: July 16 2016 $ .Dt SCP 1 .Os .Sh NAME @@ -172,6 +172,7 @@ For full details of the options listed below, and their possible values, see .It PreferredAuthentications .It Protocol .It ProxyCommand +.It ProxyJump .It PubkeyAcceptedKeyTypes .It PubkeyAuthentication .It RekeyLimit diff --git a/sftp.1 b/sftp.1 index ab8b186af..fbdd00a1e 100644 --- a/sftp.1 +++ b/sftp.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: sftp.1,v 1.104 2016/06/29 17:14:28 jmc Exp $ +.\" $OpenBSD: sftp.1,v 1.105 2016/07/16 06:57:55 jmc Exp $ .\" .\" Copyright (c) 2001 Damien Miller. All rights reserved. .\" @@ -22,7 +22,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: June 29 2016 $ +.Dd $Mdocdate: July 16 2016 $ .Dt SFTP 1 .Os .Sh NAME @@ -237,6 +237,7 @@ For full details of the options listed below, and their possible values, see .It PreferredAuthentications .It Protocol .It ProxyCommand +.It ProxyJump .It PubkeyAuthentication .It RekeyLimit .It RhostsRSAAuthentication diff --git a/ssh.1 b/ssh.1 index f3492b4d6..4011c65aa 100644 --- a/ssh.1 +++ b/ssh.1 @@ -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.375 2016/07/15 00:24:30 djm Exp $ -.Dd $Mdocdate: July 15 2016 $ +.\" $OpenBSD: ssh.1,v 1.376 2016/07/16 06:57:55 jmc Exp $ +.Dd $Mdocdate: July 16 2016 $ .Dt SSH 1 .Os .Sh NAME @@ -52,7 +52,7 @@ .Op Fl F Ar configfile .Op Fl I Ar pkcs11 .Op Fl i Ar identity_file -.Oo Fl J Ar user Ns @ Oc Ns Ar host Ns Op : Ns Ar port +.Op Fl J Oo Ar user Ns @ Oc Ns Ar host Ns Op : Ns Ar port .Op Fl L Ar address .Op Fl l Ar login_name .Op Fl m Ar mac_spec @@ -315,16 +315,16 @@ to identity filenames. .Pp .It Fl J Xo .Sm off -.Oo Ar jump_user @ Oc -.Ar jump_host -.Ns Op : Ns Ar jump_port +.Op Ar user No @ +.Ar host +.Op : Ar port .Sm on .Xc Connect to the target host by first making a .Nm -connection to -.Ar jump_host -and then establishing a TCP forward to the ultimate destination from +connection to the jump +.Ar host +and then establishing a TCP forwarding to the ultimate destination from there. Multiple jump hops may be specified separated by comma characters. This is a shortcut to specify a diff --git a/ssh.c b/ssh.c index a9b685349..4e4ff610d 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.443 2016/07/15 00:24:30 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.444 2016/07/16 06:57:55 jmc Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -198,10 +198,11 @@ usage(void) fprintf(stderr, "usage: ssh [-1246AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]\n" " [-D [bind_address:]port] [-E log_file] [-e escape_char]\n" -" [-F configfile] [-I pkcs11] [-i identity_file] [-L address]\n" -" [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" -" [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n" -" [-w local_tun[:remote_tun]] [user@]hostname [command]\n" +" [-F configfile] [-I pkcs11] [-i identity_file]\n" +" [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]\n" +" [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]\n" +" [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n" +" [user@]hostname [command]\n" ); exit(255); } diff --git a/ssh_config.5 b/ssh_config.5 index 860577023..56d7798ca 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -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.233 2016/07/15 00:24:30 djm Exp $ -.Dd $Mdocdate: July 15 2016 $ +.\" $OpenBSD: ssh_config.5,v 1.234 2016/07/16 06:57:55 jmc Exp $ +.Dd $Mdocdate: July 16 2016 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1362,9 +1362,9 @@ ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p Specifies one or more jump proxies as .Xo .Sm off -.Oo Ar user @ Oc +.Op Ar user No @ .Ar host -.Ns Op : Ns Ar port +.Op : Ns Ar port .Sm on .Xc . Multiple proxies may be separated by comma characters. @@ -1375,13 +1375,12 @@ to connect to the target host by first making a connection to the specified .Cm ProxyJump host and then establishing a -a TCP forwarding to the ultimate target from there. +TCP forwarding to the ultimate target from there. .Pp Note that this option will compete with the .Cm ProxyCommand option - whichever is specified first will prevent later instances of the other from taking effect. -.Pp .It Cm ProxyUseFdpass Specifies that .Cm ProxyCommand -- cgit v1.2.3 From 286f5a77c3bfec1e8892ca268087ac885ac871bf Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Fri, 22 Jul 2016 03:35:11 +0000 Subject: upstream commit reverse the order in which -J/JumpHost proxies are visited to be more intuitive and document reported by and manpage bits naddy@ Upstream-ID: 3a68fd6a841fd6cf8cedf6552a9607ba99df179a --- readconf.c | 25 ++++++++++++++++--------- ssh_config.5 | 7 ++++--- 2 files changed, 20 insertions(+), 12 deletions(-) (limited to 'ssh_config.5') diff --git a/readconf.c b/readconf.c index 8b5b21907..c177202b1 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.258 2016/07/20 10:45:27 naddy Exp $ */ +/* $OpenBSD: readconf.c,v 1.259 2016/07/22 03:35:11 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2292,7 +2292,12 @@ parse_jump(const char *s, Options *o, int active) orig = sdup = xstrdup(s); first = active; - while ((cp = strsep(&sdup, ",")) && cp != NULL) { + do { + if ((cp = strrchr(sdup, ',')) == NULL) + cp = sdup; /* last */ + else + *cp++ = '\0'; + if (first) { /* First argument and configuration is active */ if (parse_user_host_port(cp, &user, &host, &port) != 0) @@ -2303,7 +2308,7 @@ parse_jump(const char *s, Options *o, int active) goto out; } first = 0; /* only check syntax for subsequent hosts */ - } + } while (cp != sdup); /* success */ if (active) { o->jump_user = user; @@ -2311,8 +2316,10 @@ parse_jump(const char *s, Options *o, int active) o->jump_port = port; o->proxy_command = xstrdup("none"); user = host = NULL; - if ((cp = strchr(s, ',')) != NULL && cp[1] != '\0') - o->jump_extra = xstrdup(cp + 1); + if ((cp = strrchr(s, ',')) != NULL && cp != s) { + o->jump_extra = xstrdup(s); + o->jump_extra[cp - s] = '\0'; + } } ret = 0; out: @@ -2636,6 +2643,9 @@ dump_client_config(Options *o, const char *host) strspn(o->jump_host, "1234567890.") == strlen(o->jump_host); snprintf(buf, sizeof(buf), "%d", o->jump_port); printf("proxyjump %s%s%s%s%s%s%s%s%s\n", + /* optional additional jump spec */ + o->jump_extra == NULL ? "" : o->jump_extra, + o->jump_extra == NULL ? "" : ",", /* optional user */ o->jump_user == NULL ? "" : o->jump_user, o->jump_user == NULL ? "" : "@", @@ -2647,9 +2657,6 @@ dump_client_config(Options *o, const char *host) i ? "]" : "", /* optional port number */ o->jump_port <= 0 ? "" : ":", - o->jump_port <= 0 ? "" : buf, - /* optional additional jump spec */ - o->jump_extra == NULL ? "" : ",", - o->jump_extra == NULL ? "" : o->jump_extra); + o->jump_port <= 0 ? "" : buf); } } diff --git a/ssh_config.5 b/ssh_config.5 index 56d7798ca..fd7af4b62 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -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.234 2016/07/16 06:57:55 jmc Exp $ -.Dd $Mdocdate: July 16 2016 $ +.\" $OpenBSD: ssh_config.5,v 1.235 2016/07/22 03:35:11 djm Exp $ +.Dd $Mdocdate: July 22 2016 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1367,7 +1367,8 @@ Specifies one or more jump proxies as .Op : Ns Ar port .Sm on .Xc . -Multiple proxies may be separated by comma characters. +Multiple proxies may be separated by comma characters and will be visited +left-to-right. Setting this option will cause .Xr ssh 1 to connect to the target host by first making a -- cgit v1.2.3 From f00211e3c6d24d6ea2b64b4b1209f671f6c1d42e Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Fri, 22 Jul 2016 07:00:46 +0000 Subject: upstream commit improve wording; suggested by jmc@ Upstream-ID: 55cb0a24c8e0618b3ceec80998dc82c85db2d2f8 --- ssh_config.5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ssh_config.5') diff --git a/ssh_config.5 b/ssh_config.5 index fd7af4b62..7630e7bcb 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -33,7 +33,7 @@ .\" (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.235 2016/07/22 03:35:11 djm Exp $ +.\" $OpenBSD: ssh_config.5,v 1.236 2016/07/22 07:00:46 djm Exp $ .Dd $Mdocdate: July 22 2016 $ .Dt SSH_CONFIG 5 .Os @@ -1368,7 +1368,7 @@ Specifies one or more jump proxies as .Sm on .Xc . Multiple proxies may be separated by comma characters and will be visited -left-to-right. +sequentially. Setting this option will cause .Xr ssh 1 to connect to the target host by first making a -- cgit v1.2.3 From eecddf8b72fcad83ccca43b1badb03782704f6b7 Mon Sep 17 00:00:00 2001 From: Simon Wilkinson Date: Sun, 9 Feb 2014 16:09:48 +0000 Subject: GSSAPI key exchange support This patch has been rejected upstream: "None of the OpenSSH developers are in favour of adding this, and this situation has not changed for several years. This is not a slight on Simon's patch, which is of fine quality, but just that a) we don't trust GSSAPI implementations that much and b) we don't like adding new KEX since they are pre-auth attack surface. This one is particularly scary, since it requires hooks out to typically root-owned system resources." However, quite a lot of people rely on this in Debian, and it's better to have it merged into the main openssh package rather than having separate -krb5 packages (as we used to have). It seems to have a generally good security history. Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1242 Last-Updated: 2016-08-07 Patch-Name: gssapi.patch --- ChangeLog.gssapi | 113 +++++++++++++++++++ Makefile.in | 3 +- auth-krb5.c | 17 ++- auth.c | 96 +--------------- auth2-gss.c | 48 +++++++- auth2.c | 2 + canohost.c | 93 +++++++++++++++ canohost.h | 3 + clientloop.c | 15 ++- config.h.in | 6 + configure.ac | 24 ++++ gss-genr.c | 275 +++++++++++++++++++++++++++++++++++++++++++- gss-serv-krb5.c | 85 ++++++++++++-- gss-serv.c | 185 +++++++++++++++++++++++++++--- kex.c | 19 ++++ kex.h | 14 +++ kexgssc.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ kexgsss.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++++ monitor.c | 108 +++++++++++++++++- monitor.h | 3 + monitor_wrap.c | 47 +++++++- monitor_wrap.h | 4 +- readconf.c | 42 +++++++ readconf.h | 5 + servconf.c | 28 ++++- servconf.h | 2 + ssh-gss.h | 41 ++++++- ssh_config | 2 + ssh_config.5 | 32 ++++++ sshconnect2.c | 122 +++++++++++++++++++- sshd.c | 110 ++++++++++++++++++ sshd_config | 2 + sshd_config.5 | 10 ++ sshkey.c | 3 +- sshkey.h | 1 + 35 files changed, 2054 insertions(+), 139 deletions(-) create mode 100644 ChangeLog.gssapi create mode 100644 kexgssc.c create mode 100644 kexgsss.c (limited to 'ssh_config.5') diff --git a/ChangeLog.gssapi b/ChangeLog.gssapi new file mode 100644 index 000000000..f117a336a --- /dev/null +++ b/ChangeLog.gssapi @@ -0,0 +1,113 @@ +20110101 + - Finally update for OpenSSH 5.6p1 + - Add GSSAPIServerIdentity option from Jim Basney + +20100308 + - [ Makefile.in, key.c, key.h ] + Updates for OpenSSH 5.4p1 + - [ servconf.c ] + Include GSSAPI options in the sshd -T configuration dump, and flag + some older configuration options as being unsupported. Thanks to Colin + Watson. + - + +20100124 + - [ sshconnect2.c ] + Adapt to deal with additional element in Authmethod structure. Thanks to + Colin Watson + +20090615 + - [ gss-genr.c gss-serv.c kexgssc.c kexgsss.c monitor.c sshconnect2.c + sshd.c ] + Fix issues identified by Greg Hudson following a code review + Check return value of gss_indicate_mechs + Protect GSSAPI calls in monitor, so they can only be used if enabled + Check return values of bignum functions in key exchange + Use BN_clear_free to clear other side's DH value + Make ssh_gssapi_id_kex more robust + Only configure kex table pointers if GSSAPI is enabled + Don't leak mechanism list, or gss mechanism list + Cast data.length before printing + If serverkey isn't provided, use an empty string, rather than NULL + +20090201 + - [ gss-genr.c gss-serv.c kex.h kexgssc.c readconf.c readconf.h ssh-gss.h + ssh_config.5 sshconnet2.c ] + Add support for the GSSAPIClientIdentity option, which allows the user + to specify which GSSAPI identity to use to contact a given server + +20080404 + - [ gss-serv.c ] + Add code to actually implement GSSAPIStrictAcceptCheck, which had somehow + been omitted from a previous version of this patch. Reported by Borislav + Stoichkov + +20070317 + - [ gss-serv-krb5.c ] + Remove C99ism, where new_ccname was being declared in the middle of a + function + +20061220 + - [ servconf.c ] + Make default for GSSAPIStrictAcceptorCheck be Yes, to match previous, and + documented, behaviour. Reported by Dan Watson. + +20060910 + - [ gss-genr.c kexgssc.c kexgsss.c kex.h monitor.c sshconnect2.c sshd.c + ssh-gss.h ] + add support for gss-group14-sha1 key exchange mechanisms + - [ gss-serv.c servconf.c servconf.h sshd_config sshd_config.5 ] + Add GSSAPIStrictAcceptorCheck option to allow the disabling of + acceptor principal checking on multi-homed machines. + + - [ sshd_config ssh_config ] + Add settings for GSSAPIKeyExchange and GSSAPITrustDNS to the sample + configuration files + - [ kexgss.c kegsss.c sshconnect2.c sshd.c ] + Code cleanup. Replace strlen/xmalloc/snprintf sequences with xasprintf() + Limit length of error messages displayed by client + +20060909 + - [ gss-genr.c gss-serv.c ] + move ssh_gssapi_acquire_cred() and ssh_gssapi_server_ctx to be server + only, where they belong + + +20060829 + - [ gss-serv-krb5.c ] + Fix CCAPI credentials cache name when creating KRB5CCNAME environment + variable + +20060828 + - [ gss-genr.c ] + Avoid Heimdal context freeing problem + + +20060818 + - [ gss-genr.c ssh-gss.h sshconnect2.c ] + Make sure that SPENGO is disabled + + +20060421 + - [ gssgenr.c, sshconnect2.c ] + a few type changes (signed versus unsigned, int versus size_t) to + fix compiler errors/warnings + (from jbasney AT ncsa.uiuc.edu) + - [ kexgssc.c, sshconnect2.c ] + fix uninitialized variable warnings + (from jbasney AT ncsa.uiuc.edu) + - [ gssgenr.c ] + pass oid to gss_display_status (helpful when using GSSAPI mechglue) + (from jbasney AT ncsa.uiuc.edu) + + - [ gss-serv-krb5.c ] + #ifdef HAVE_GSSAPI_KRB5 should be #ifdef HAVE_GSSAPI_KRB5_H + (from jbasney AT ncsa.uiuc.edu) + + - [ readconf.c, readconf.h, ssh_config.5, sshconnect2.c + add client-side GssapiKeyExchange option + (from jbasney AT ncsa.uiuc.edu) + - [ sshconnect2.c ] + add support for GssapiTrustDns option for gssapi-with-mic + (from jbasney AT ncsa.uiuc.edu) + diff --git a/Makefile.in b/Makefile.in index 12991cd9f..51817dfe0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -92,6 +92,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ kexdhc.o kexgexc.o kexecdhc.o kexc25519c.o \ kexdhs.o kexgexs.o kexecdhs.o kexc25519s.o \ + kexgssc.o \ platform-pledge.o platform-tracing.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ @@ -105,7 +106,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ auth2-none.o auth2-passwd.o auth2-pubkey.o \ monitor_mm.o monitor.o monitor_wrap.o auth-krb5.o \ - auth2-gss.o gss-serv.o gss-serv-krb5.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \ loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ sftp-server.o sftp-common.o \ sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \ diff --git a/auth-krb5.c b/auth-krb5.c index a5a81ed2e..38e7fee21 100644 --- a/auth-krb5.c +++ b/auth-krb5.c @@ -182,8 +182,13 @@ auth_krb5_password(Authctxt *authctxt, const char *password) len = strlen(authctxt->krb5_ticket_file) + 6; authctxt->krb5_ccname = xmalloc(len); +#ifdef USE_CCAPI + snprintf(authctxt->krb5_ccname, len, "API:%s", + authctxt->krb5_ticket_file); +#else snprintf(authctxt->krb5_ccname, len, "FILE:%s", authctxt->krb5_ticket_file); +#endif #ifdef USE_PAM if (options.use_pam) @@ -240,15 +245,22 @@ krb5_cleanup_proc(Authctxt *authctxt) #ifndef HEIMDAL krb5_error_code ssh_krb5_cc_gen(krb5_context ctx, krb5_ccache *ccache) { - int tmpfd, ret, oerrno; + int ret, oerrno; char ccname[40]; mode_t old_umask; +#ifdef USE_CCAPI + char cctemplate[] = "API:krb5cc_%d"; +#else + char cctemplate[] = "FILE:/tmp/krb5cc_%d_XXXXXXXXXX"; + int tmpfd; +#endif ret = snprintf(ccname, sizeof(ccname), - "FILE:/tmp/krb5cc_%d_XXXXXXXXXX", geteuid()); + cctemplate, geteuid()); if (ret < 0 || (size_t)ret >= sizeof(ccname)) return ENOMEM; +#ifndef USE_CCAPI old_umask = umask(0177); tmpfd = mkstemp(ccname + strlen("FILE:")); oerrno = errno; @@ -265,6 +277,7 @@ ssh_krb5_cc_gen(krb5_context ctx, krb5_ccache *ccache) { return oerrno; } close(tmpfd); +#endif return (krb5_cc_resolve(ctx, ccname, ccache)); } diff --git a/auth.c b/auth.c index 24527dd7c..f56dcc6cf 100644 --- a/auth.c +++ b/auth.c @@ -363,7 +363,8 @@ auth_root_allowed(const char *method) case PERMIT_NO_PASSWD: if (strcmp(method, "publickey") == 0 || strcmp(method, "hostbased") == 0 || - strcmp(method, "gssapi-with-mic") == 0) + strcmp(method, "gssapi-with-mic") == 0 || + strcmp(method, "gssapi-keyex") == 0) return 1; break; case PERMIT_FORCED_ONLY: @@ -785,99 +786,6 @@ fakepw(void) return (&fake); } -/* - * Returns the remote DNS hostname as a string. The returned string must not - * be freed. NB. this will usually trigger a DNS query the first time it is - * called. - * This function does additional checks on the hostname to mitigate some - * attacks on legacy rhosts-style authentication. - * XXX is RhostsRSAAuthentication vulnerable to these? - * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?) - */ - -static char * -remote_hostname(struct ssh *ssh) -{ - struct sockaddr_storage from; - socklen_t fromlen; - struct addrinfo hints, *ai, *aitop; - char name[NI_MAXHOST], ntop2[NI_MAXHOST]; - const char *ntop = ssh_remote_ipaddr(ssh); - - /* Get IP address of client. */ - fromlen = sizeof(from); - memset(&from, 0, sizeof(from)); - if (getpeername(ssh_packet_get_connection_in(ssh), - (struct sockaddr *)&from, &fromlen) < 0) { - debug("getpeername failed: %.100s", strerror(errno)); - return strdup(ntop); - } - - ipv64_normalise_mapped(&from, &fromlen); - if (from.ss_family == AF_INET6) - fromlen = sizeof(struct sockaddr_in6); - - debug3("Trying to reverse map address %.100s.", ntop); - /* Map the IP address to a host name. */ - if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), - NULL, 0, NI_NAMEREQD) != 0) { - /* Host name not found. Use ip address. */ - return strdup(ntop); - } - - /* - * if reverse lookup result looks like a numeric hostname, - * someone is trying to trick us by PTR record like following: - * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5 - */ - memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = SOCK_DGRAM; /*dummy*/ - hints.ai_flags = AI_NUMERICHOST; - if (getaddrinfo(name, NULL, &hints, &ai) == 0) { - logit("Nasty PTR record \"%s\" is set up for %s, ignoring", - name, ntop); - freeaddrinfo(ai); - return strdup(ntop); - } - - /* Names are stored in lowercase. */ - lowercase(name); - - /* - * Map it back to an IP address and check that the given - * address actually is an address of this host. This is - * necessary because anyone with access to a name server can - * define arbitrary names for an IP address. Mapping from - * name to IP address can be trusted better (but can still be - * fooled if the intruder has access to the name server of - * the domain). - */ - memset(&hints, 0, sizeof(hints)); - hints.ai_family = from.ss_family; - hints.ai_socktype = SOCK_STREAM; - if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { - logit("reverse mapping checking getaddrinfo for %.700s " - "[%s] failed.", name, ntop); - return strdup(ntop); - } - /* Look for the address from the list of addresses. */ - for (ai = aitop; ai; ai = ai->ai_next) { - if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, - sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && - (strcmp(ntop, ntop2) == 0)) - break; - } - freeaddrinfo(aitop); - /* If we reached the end of the list, the address was not there. */ - if (ai == NULL) { - /* Address not found for the host name. */ - logit("Address %.100s maps to %.600s, but this does not " - "map back to the address.", ntop, name); - return strdup(ntop); - } - return strdup(name); -} - /* * Return the canonical name of the host in the other side of the current * connection. The host name is cached, so it is efficient to call this diff --git a/auth2-gss.c b/auth2-gss.c index 1ca835773..3b5036dfd 100644 --- a/auth2-gss.c +++ b/auth2-gss.c @@ -1,7 +1,7 @@ /* $OpenBSD: auth2-gss.c,v 1.22 2015/01/19 20:07:45 markus Exp $ */ /* - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -53,6 +53,40 @@ static int input_gssapi_mic(int type, u_int32_t plen, void *ctxt); static int input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt); static int input_gssapi_errtok(int, u_int32_t, void *); +/* + * The 'gssapi_keyex' userauth mechanism. + */ +static int +userauth_gsskeyex(Authctxt *authctxt) +{ + int authenticated = 0; + Buffer b; + gss_buffer_desc mic, gssbuf; + u_int len; + + mic.value = packet_get_string(&len); + mic.length = len; + + packet_check_eom(); + + ssh_gssapi_buildmic(&b, authctxt->user, authctxt->service, + "gssapi-keyex"); + + gssbuf.value = buffer_ptr(&b); + gssbuf.length = buffer_len(&b); + + /* gss_kex_context is NULL with privsep, so we can't check it here */ + if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gss_kex_context, + &gssbuf, &mic)))) + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, + authctxt->pw)); + + buffer_free(&b); + free(mic.value); + + return (authenticated); +} + /* * We only support those mechanisms that we know about (ie ones that we know * how to check local user kuserok and the like) @@ -238,7 +272,8 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt) packet_check_eom(); - authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, + authctxt->pw)); authctxt->postponed = 0; dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); @@ -274,7 +309,8 @@ input_gssapi_mic(int type, u_int32_t plen, void *ctxt) gssbuf.length = buffer_len(&b); if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))) - authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); + authenticated = + PRIVSEP(ssh_gssapi_userok(authctxt->user, authctxt->pw)); else logit("GSSAPI MIC check failed"); @@ -290,6 +326,12 @@ input_gssapi_mic(int type, u_int32_t plen, void *ctxt) return 0; } +Authmethod method_gsskeyex = { + "gssapi-keyex", + userauth_gsskeyex, + &options.gss_authentication +}; + Authmethod method_gssapi = { "gssapi-with-mic", userauth_gssapi, diff --git a/auth2.c b/auth2.c index 9108b8612..ce0d37601 100644 --- a/auth2.c +++ b/auth2.c @@ -70,6 +70,7 @@ extern Authmethod method_passwd; extern Authmethod method_kbdint; extern Authmethod method_hostbased; #ifdef GSSAPI +extern Authmethod method_gsskeyex; extern Authmethod method_gssapi; #endif @@ -77,6 +78,7 @@ Authmethod *authmethods[] = { &method_none, &method_pubkey, #ifdef GSSAPI + &method_gsskeyex, &method_gssapi, #endif &method_passwd, diff --git a/canohost.c b/canohost.c index f71a08568..404731d24 100644 --- a/canohost.c +++ b/canohost.c @@ -35,6 +35,99 @@ #include "canohost.h" #include "misc.h" +/* + * Returns the remote DNS hostname as a string. The returned string must not + * be freed. NB. this will usually trigger a DNS query the first time it is + * called. + * This function does additional checks on the hostname to mitigate some + * attacks on legacy rhosts-style authentication. + * XXX is RhostsRSAAuthentication vulnerable to these? + * XXX Can we remove these checks? (or if not, remove RhostsRSAAuthentication?) + */ + +char * +remote_hostname(struct ssh *ssh) +{ + struct sockaddr_storage from; + socklen_t fromlen; + struct addrinfo hints, *ai, *aitop; + char name[NI_MAXHOST], ntop2[NI_MAXHOST]; + const char *ntop = ssh_remote_ipaddr(ssh); + + /* Get IP address of client. */ + fromlen = sizeof(from); + memset(&from, 0, sizeof(from)); + if (getpeername(ssh_packet_get_connection_in(ssh), + (struct sockaddr *)&from, &fromlen) < 0) { + debug("getpeername failed: %.100s", strerror(errno)); + return strdup(ntop); + } + + ipv64_normalise_mapped(&from, &fromlen); + if (from.ss_family == AF_INET6) + fromlen = sizeof(struct sockaddr_in6); + + debug3("Trying to reverse map address %.100s.", ntop); + /* Map the IP address to a host name. */ + if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), + NULL, 0, NI_NAMEREQD) != 0) { + /* Host name not found. Use ip address. */ + return strdup(ntop); + } + + /* + * if reverse lookup result looks like a numeric hostname, + * someone is trying to trick us by PTR record like following: + * 1.1.1.10.in-addr.arpa. IN PTR 2.3.4.5 + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(name, NULL, &hints, &ai) == 0) { + logit("Nasty PTR record \"%s\" is set up for %s, ignoring", + name, ntop); + freeaddrinfo(ai); + return strdup(ntop); + } + + /* Names are stored in lowercase. */ + lowercase(name); + + /* + * Map it back to an IP address and check that the given + * address actually is an address of this host. This is + * necessary because anyone with access to a name server can + * define arbitrary names for an IP address. Mapping from + * name to IP address can be trusted better (but can still be + * fooled if the intruder has access to the name server of + * the domain). + */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = from.ss_family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { + logit("reverse mapping checking getaddrinfo for %.700s " + "[%s] failed.", name, ntop); + return strdup(ntop); + } + /* Look for the address from the list of addresses. */ + for (ai = aitop; ai; ai = ai->ai_next) { + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, + sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && + (strcmp(ntop, ntop2) == 0)) + break; + } + freeaddrinfo(aitop); + /* If we reached the end of the list, the address was not there. */ + if (ai == NULL) { + /* Address not found for the host name. */ + logit("Address %.100s maps to %.600s, but this does not " + "map back to the address.", ntop, name); + return strdup(ntop); + } + return strdup(name); +} + void ipv64_normalise_mapped(struct sockaddr_storage *addr, socklen_t *len) { diff --git a/canohost.h b/canohost.h index 26d62855a..0cadc9f18 100644 --- a/canohost.h +++ b/canohost.h @@ -15,6 +15,9 @@ #ifndef _CANOHOST_H #define _CANOHOST_H +struct ssh; + +char *remote_hostname(struct ssh *); char *get_peer_ipaddr(int); int get_peer_port(int); char *get_local_ipaddr(int); diff --git a/clientloop.c b/clientloop.c index 2c44f5d19..421241f74 100644 --- a/clientloop.c +++ b/clientloop.c @@ -114,6 +114,10 @@ #include "ssherr.h" #include "hostfile.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + /* import options */ extern Options options; @@ -1666,9 +1670,18 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) break; /* Do channel operations unless rekeying in progress. */ - if (!ssh_packet_is_rekeying(active_state)) + if (!ssh_packet_is_rekeying(active_state)) { channel_after_select(readset, writeset); +#ifdef GSSAPI + if (options.gss_renewal_rekey && + ssh_gssapi_credentials_updated(NULL)) { + debug("credentials updated - forcing rekey"); + need_rekeying = 1; + } +#endif + } + /* Buffer input from the connection. */ client_process_net_input(readset); diff --git a/config.h.in b/config.h.in index 39d018f4c..d7caf9aa4 100644 --- a/config.h.in +++ b/config.h.in @@ -1668,6 +1668,9 @@ /* Use btmp to log bad logins */ #undef USE_BTMP +/* platform uses an in-memory credentials cache */ +#undef USE_CCAPI + /* Use libedit for sftp */ #undef USE_LIBEDIT @@ -1683,6 +1686,9 @@ /* Use PIPES instead of a socketpair() */ #undef USE_PIPES +/* platform has the Security Authorization Session API */ +#undef USE_SECURITY_SESSION_API + /* Define if you have Solaris privileges */ #undef USE_SOLARIS_PRIVS diff --git a/configure.ac b/configure.ac index 373d21b34..894ec3b7b 100644 --- a/configure.ac +++ b/configure.ac @@ -632,6 +632,30 @@ main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) [Use tunnel device compatibility to OpenBSD]) AC_DEFINE([SSH_TUN_PREPEND_AF], [1], [Prepend the address family to IP tunnel traffic]) + AC_MSG_CHECKING([if we have the Security Authorization Session API]) + AC_TRY_COMPILE([#include ], + [SessionCreate(0, 0);], + [ac_cv_use_security_session_api="yes" + AC_DEFINE([USE_SECURITY_SESSION_API], [1], + [platform has the Security Authorization Session API]) + LIBS="$LIBS -framework Security" + AC_MSG_RESULT([yes])], + [ac_cv_use_security_session_api="no" + AC_MSG_RESULT([no])]) + AC_MSG_CHECKING([if we have an in-memory credentials cache]) + AC_TRY_COMPILE( + [#include ], + [cc_context_t c; + (void) cc_initialize (&c, 0, NULL, NULL);], + [AC_DEFINE([USE_CCAPI], [1], + [platform uses an in-memory credentials cache]) + LIBS="$LIBS -framework Security" + AC_MSG_RESULT([yes]) + if test "x$ac_cv_use_security_session_api" = "xno"; then + AC_MSG_ERROR([*** Need a security framework to use the credentials cache API ***]) + fi], + [AC_MSG_RESULT([no])] + ) m4_pattern_allow([AU_IPv]) AC_CHECK_DECL([AU_IPv4], [], AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) diff --git a/gss-genr.c b/gss-genr.c index d617d600a..b4eca3feb 100644 --- a/gss-genr.c +++ b/gss-genr.c @@ -1,7 +1,7 @@ /* $OpenBSD: gss-genr.c,v 1.23 2015/01/20 23:14:00 deraadt Exp $ */ /* - * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -41,12 +41,167 @@ #include "buffer.h" #include "log.h" #include "ssh2.h" +#include "cipher.h" +#include "key.h" +#include "kex.h" +#include #include "ssh-gss.h" extern u_char *session_id2; extern u_int session_id2_len; +typedef struct { + char *encoded; + gss_OID oid; +} ssh_gss_kex_mapping; + +/* + * XXX - It would be nice to find a more elegant way of handling the + * XXX passing of the key exchange context to the userauth routines + */ + +Gssctxt *gss_kex_context = NULL; + +static ssh_gss_kex_mapping *gss_enc2oid = NULL; + +int +ssh_gssapi_oid_table_ok(void) { + return (gss_enc2oid != NULL); +} + +/* + * Return a list of the gss-group1-sha1 mechanisms supported by this program + * + * We test mechanisms to ensure that we can use them, to avoid starting + * a key exchange with a bad mechanism + */ + +char * +ssh_gssapi_client_mechanisms(const char *host, const char *client) { + gss_OID_set gss_supported; + OM_uint32 min_status; + + if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) + return NULL; + + return(ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, + host, client)); +} + +char * +ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, + const char *host, const char *client) { + Buffer buf; + size_t i; + int oidpos, enclen; + char *mechs, *encoded; + u_char digest[EVP_MAX_MD_SIZE]; + char deroid[2]; + const EVP_MD *evp_md = EVP_md5(); + EVP_MD_CTX md; + + if (gss_enc2oid != NULL) { + for (i = 0; gss_enc2oid[i].encoded != NULL; i++) + free(gss_enc2oid[i].encoded); + free(gss_enc2oid); + } + + gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * + (gss_supported->count + 1)); + + buffer_init(&buf); + + oidpos = 0; + for (i = 0; i < gss_supported->count; i++) { + if (gss_supported->elements[i].length < 128 && + (*check)(NULL, &(gss_supported->elements[i]), host, client)) { + + deroid[0] = SSH_GSS_OIDTYPE; + deroid[1] = gss_supported->elements[i].length; + + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, deroid, 2); + EVP_DigestUpdate(&md, + gss_supported->elements[i].elements, + gss_supported->elements[i].length); + EVP_DigestFinal(&md, digest, NULL); + + encoded = xmalloc(EVP_MD_size(evp_md) * 2); + enclen = __b64_ntop(digest, EVP_MD_size(evp_md), + encoded, EVP_MD_size(evp_md) * 2); + + if (oidpos != 0) + buffer_put_char(&buf, ','); + + buffer_append(&buf, KEX_GSS_GEX_SHA1_ID, + sizeof(KEX_GSS_GEX_SHA1_ID) - 1); + buffer_append(&buf, encoded, enclen); + buffer_put_char(&buf, ','); + buffer_append(&buf, KEX_GSS_GRP1_SHA1_ID, + sizeof(KEX_GSS_GRP1_SHA1_ID) - 1); + buffer_append(&buf, encoded, enclen); + buffer_put_char(&buf, ','); + buffer_append(&buf, KEX_GSS_GRP14_SHA1_ID, + sizeof(KEX_GSS_GRP14_SHA1_ID) - 1); + buffer_append(&buf, encoded, enclen); + + gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); + gss_enc2oid[oidpos].encoded = encoded; + oidpos++; + } + } + gss_enc2oid[oidpos].oid = NULL; + gss_enc2oid[oidpos].encoded = NULL; + + buffer_put_char(&buf, '\0'); + + mechs = xmalloc(buffer_len(&buf)); + buffer_get(&buf, mechs, buffer_len(&buf)); + buffer_free(&buf); + + if (strlen(mechs) == 0) { + free(mechs); + mechs = NULL; + } + + return (mechs); +} + +gss_OID +ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { + int i = 0; + + switch (kex_type) { + case KEX_GSS_GRP1_SHA1: + if (strlen(name) < sizeof(KEX_GSS_GRP1_SHA1_ID)) + return GSS_C_NO_OID; + name += sizeof(KEX_GSS_GRP1_SHA1_ID) - 1; + break; + case KEX_GSS_GRP14_SHA1: + if (strlen(name) < sizeof(KEX_GSS_GRP14_SHA1_ID)) + return GSS_C_NO_OID; + name += sizeof(KEX_GSS_GRP14_SHA1_ID) - 1; + break; + case KEX_GSS_GEX_SHA1: + if (strlen(name) < sizeof(KEX_GSS_GEX_SHA1_ID)) + return GSS_C_NO_OID; + name += sizeof(KEX_GSS_GEX_SHA1_ID) - 1; + break; + default: + return GSS_C_NO_OID; + } + + while (gss_enc2oid[i].encoded != NULL && + strcmp(name, gss_enc2oid[i].encoded) != 0) + i++; + + if (gss_enc2oid[i].oid != NULL && ctx != NULL) + ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); + + return gss_enc2oid[i].oid; +} + /* Check that the OID in a data stream matches that in the context */ int ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) @@ -199,7 +354,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok, } ctx->major = gss_init_sec_context(&ctx->minor, - GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid, + ctx->client_creds, &ctx->context, ctx->name, ctx->oid, GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, 0, NULL, recv_tok, NULL, send_tok, flags, NULL); @@ -228,9 +383,43 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host) return (ctx->major); } +OM_uint32 +ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) +{ + gss_buffer_desc gssbuf; + gss_name_t gssname; + OM_uint32 status; + gss_OID_set oidset; + + gssbuf.value = (void *) name; + gssbuf.length = strlen(gssbuf.value); + + gss_create_empty_oid_set(&status, &oidset); + gss_add_oid_set_member(&status, ctx->oid, &oidset); + + ctx->major = gss_import_name(&ctx->minor, &gssbuf, + GSS_C_NT_USER_NAME, &gssname); + + if (!ctx->major) + ctx->major = gss_acquire_cred(&ctx->minor, + gssname, 0, oidset, GSS_C_INITIATE, + &ctx->client_creds, NULL, NULL); + + gss_release_name(&status, &gssname); + gss_release_oid_set(&status, &oidset); + + if (ctx->major) + ssh_gssapi_error(ctx); + + return(ctx->major); +} + OM_uint32 ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) { + if (ctx == NULL) + return -1; + if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, GSS_C_QOP_DEFAULT, buffer, hash))) ssh_gssapi_error(ctx); @@ -238,6 +427,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) return (ctx->major); } +/* Priviledged when used by server */ +OM_uint32 +ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) +{ + if (ctx == NULL) + return -1; + + ctx->major = gss_verify_mic(&ctx->minor, ctx->context, + gssbuf, gssmic, NULL); + + return (ctx->major); +} + void ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, const char *context) @@ -251,11 +453,16 @@ ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, } int -ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) +ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, + const char *client) { gss_buffer_desc token = GSS_C_EMPTY_BUFFER; OM_uint32 major, minor; gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; + Gssctxt *intctx = NULL; + + if (ctx == NULL) + ctx = &intctx; /* RFC 4462 says we MUST NOT do SPNEGO */ if (oid->length == spnego_oid.length && @@ -265,6 +472,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) ssh_gssapi_build_ctx(ctx); ssh_gssapi_set_oid(*ctx, oid); major = ssh_gssapi_import_name(*ctx, host); + + if (!GSS_ERROR(major) && client) + major = ssh_gssapi_client_identity(*ctx, client); + if (!GSS_ERROR(major)) { major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, NULL); @@ -274,10 +485,66 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) GSS_C_NO_BUFFER); } - if (GSS_ERROR(major)) + if (GSS_ERROR(major) || intctx != NULL) ssh_gssapi_delete_ctx(ctx); return (!GSS_ERROR(major)); } +int +ssh_gssapi_credentials_updated(Gssctxt *ctxt) { + static gss_name_t saved_name = GSS_C_NO_NAME; + static OM_uint32 saved_lifetime = 0; + static gss_OID saved_mech = GSS_C_NO_OID; + static gss_name_t name; + static OM_uint32 last_call = 0; + OM_uint32 lifetime, now, major, minor; + int equal; + + now = time(NULL); + + if (ctxt) { + debug("Rekey has happened - updating saved versions"); + + if (saved_name != GSS_C_NO_NAME) + gss_release_name(&minor, &saved_name); + + major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, + &saved_name, &saved_lifetime, NULL, NULL); + + if (!GSS_ERROR(major)) { + saved_mech = ctxt->oid; + saved_lifetime+= now; + } else { + /* Handle the error */ + } + return 0; + } + + if (now - last_call < 10) + return 0; + + last_call = now; + + if (saved_mech == GSS_C_NO_OID) + return 0; + + major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, + &name, &lifetime, NULL, NULL); + if (major == GSS_S_CREDENTIALS_EXPIRED) + return 0; + else if (GSS_ERROR(major)) + return 0; + + major = gss_compare_name(&minor, saved_name, name, &equal); + gss_release_name(&minor, &name); + if (GSS_ERROR(major)) + return 0; + + if (equal && (saved_lifetime < lifetime + now - 10)) + return 1; + + return 0; +} + #endif /* GSSAPI */ diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c index 795992d9f..fd8b37183 100644 --- a/gss-serv-krb5.c +++ b/gss-serv-krb5.c @@ -1,7 +1,7 @@ /* $OpenBSD: gss-serv-krb5.c,v 1.8 2013/07/20 01:55:13 djm Exp $ */ /* - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -121,8 +121,8 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) krb5_error_code problem; krb5_principal princ; OM_uint32 maj_status, min_status; - int len; const char *errmsg; + const char *new_ccname; if (client->creds == NULL) { debug("No credentials stored"); @@ -181,11 +181,16 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) return; } - client->store.filename = xstrdup(krb5_cc_get_name(krb_context, ccache)); + new_ccname = krb5_cc_get_name(krb_context, ccache); + client->store.envvar = "KRB5CCNAME"; - len = strlen(client->store.filename) + 6; - client->store.envval = xmalloc(len); - snprintf(client->store.envval, len, "FILE:%s", client->store.filename); +#ifdef USE_CCAPI + xasprintf(&client->store.envval, "API:%s", new_ccname); + client->store.filename = NULL; +#else + xasprintf(&client->store.envval, "FILE:%s", new_ccname); + client->store.filename = xstrdup(new_ccname); +#endif #ifdef USE_PAM if (options.use_pam) @@ -197,6 +202,71 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) return; } +int +ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store, + ssh_gssapi_client *client) +{ + krb5_ccache ccache = NULL; + krb5_principal principal = NULL; + char *name = NULL; + krb5_error_code problem; + OM_uint32 maj_status, min_status; + + if ((problem = krb5_cc_resolve(krb_context, store->envval, &ccache))) { + logit("krb5_cc_resolve(): %.100s", + krb5_get_err_text(krb_context, problem)); + return 0; + } + + /* Find out who the principal in this cache is */ + if ((problem = krb5_cc_get_principal(krb_context, ccache, + &principal))) { + logit("krb5_cc_get_principal(): %.100s", + krb5_get_err_text(krb_context, problem)); + krb5_cc_close(krb_context, ccache); + return 0; + } + + if ((problem = krb5_unparse_name(krb_context, principal, &name))) { + logit("krb5_unparse_name(): %.100s", + krb5_get_err_text(krb_context, problem)); + krb5_free_principal(krb_context, principal); + krb5_cc_close(krb_context, ccache); + return 0; + } + + + if (strcmp(name,client->exportedname.value)!=0) { + debug("Name in local credentials cache differs. Not storing"); + krb5_free_principal(krb_context, principal); + krb5_cc_close(krb_context, ccache); + krb5_free_unparsed_name(krb_context, name); + return 0; + } + krb5_free_unparsed_name(krb_context, name); + + /* Name matches, so lets get on with it! */ + + if ((problem = krb5_cc_initialize(krb_context, ccache, principal))) { + logit("krb5_cc_initialize(): %.100s", + krb5_get_err_text(krb_context, problem)); + krb5_free_principal(krb_context, principal); + krb5_cc_close(krb_context, ccache); + return 0; + } + + krb5_free_principal(krb_context, principal); + + if ((maj_status = gss_krb5_copy_ccache(&min_status, client->creds, + ccache))) { + logit("gss_krb5_copy_ccache() failed. Sorry!"); + krb5_cc_close(krb_context, ccache); + return 0; + } + + return 1; +} + ssh_gssapi_mech gssapi_kerberos_mech = { "toWM5Slw5Ew8Mqkay+al2g==", "Kerberos", @@ -204,7 +274,8 @@ ssh_gssapi_mech gssapi_kerberos_mech = { NULL, &ssh_gssapi_krb5_userok, NULL, - &ssh_gssapi_krb5_storecreds + &ssh_gssapi_krb5_storecreds, + &ssh_gssapi_krb5_updatecreds }; #endif /* KRB5 */ diff --git a/gss-serv.c b/gss-serv.c index 53993d674..2f6baf70d 100644 --- a/gss-serv.c +++ b/gss-serv.c @@ -1,7 +1,7 @@ /* $OpenBSD: gss-serv.c,v 1.29 2015/05/22 03:50:02 djm Exp $ */ /* - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -45,17 +45,22 @@ #include "session.h" #include "misc.h" #include "servconf.h" +#include "uidswap.h" #include "ssh-gss.h" +#include "monitor_wrap.h" + +extern ServerOptions options; extern ServerOptions options; static ssh_gssapi_client gssapi_client = { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, - GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}}; + GSS_C_NO_CREDENTIAL, GSS_C_NO_NAME, NULL, + {NULL, NULL, NULL, NULL, NULL}, 0, 0}; ssh_gssapi_mech gssapi_null_mech = - { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL}; + { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; #ifdef KRB5 extern ssh_gssapi_mech gssapi_kerberos_mech; @@ -141,6 +146,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid) return (ssh_gssapi_acquire_cred(*ctx)); } +/* Unprivileged */ +char * +ssh_gssapi_server_mechanisms(void) { + gss_OID_set supported; + + ssh_gssapi_supported_oids(&supported); + return (ssh_gssapi_kex_mechs(supported, &ssh_gssapi_server_check_mech, + NULL, NULL)); +} + +/* Unprivileged */ +int +ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data, + const char *dummy) { + Gssctxt *ctx = NULL; + int res; + + res = !GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctx, oid))); + ssh_gssapi_delete_ctx(&ctx); + + return (res); +} + /* Unprivileged */ void ssh_gssapi_supported_oids(gss_OID_set *oidset) @@ -151,7 +179,9 @@ ssh_gssapi_supported_oids(gss_OID_set *oidset) gss_OID_set supported; gss_create_empty_oid_set(&min_status, oidset); - gss_indicate_mechs(&min_status, &supported); + + if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported))) + return; while (supported_mechs[i]->name != NULL) { if (GSS_ERROR(gss_test_oid_set_member(&min_status, @@ -277,8 +307,48 @@ OM_uint32 ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) { int i = 0; + int equal = 0; + gss_name_t new_name = GSS_C_NO_NAME; + gss_buffer_desc ename = GSS_C_EMPTY_BUFFER; + + if (options.gss_store_rekey && client->used && ctx->client_creds) { + if (client->mech->oid.length != ctx->oid->length || + (memcmp(client->mech->oid.elements, + ctx->oid->elements, ctx->oid->length) !=0)) { + debug("Rekeyed credentials have different mechanism"); + return GSS_S_COMPLETE; + } + + if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor, + ctx->client_creds, ctx->oid, &new_name, + NULL, NULL, NULL))) { + ssh_gssapi_error(ctx); + return (ctx->major); + } + + ctx->major = gss_compare_name(&ctx->minor, client->name, + new_name, &equal); + + if (GSS_ERROR(ctx->major)) { + ssh_gssapi_error(ctx); + return (ctx->major); + } + + if (!equal) { + debug("Rekeyed credentials have different name"); + return GSS_S_COMPLETE; + } - gss_buffer_desc ename; + debug("Marking rekeyed credentials for export"); + + gss_release_name(&ctx->minor, &client->name); + gss_release_cred(&ctx->minor, &client->creds); + client->name = new_name; + client->creds = ctx->client_creds; + ctx->client_creds = GSS_C_NO_CREDENTIAL; + client->updated = 1; + return GSS_S_COMPLETE; + } client->mech = NULL; @@ -293,6 +363,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) if (client->mech == NULL) return GSS_S_FAILURE; + if (ctx->client_creds && + (ctx->major = gss_inquire_cred_by_mech(&ctx->minor, + ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) { + ssh_gssapi_error(ctx); + return (ctx->major); + } + if ((ctx->major = gss_display_name(&ctx->minor, ctx->client, &client->displayname, NULL))) { ssh_gssapi_error(ctx); @@ -310,6 +387,8 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) return (ctx->major); } + gss_release_buffer(&ctx->minor, &ename); + /* We can't copy this structure, so we just move the pointer to it */ client->creds = ctx->client_creds; ctx->client_creds = GSS_C_NO_CREDENTIAL; @@ -357,7 +436,7 @@ ssh_gssapi_do_child(char ***envp, u_int *envsizep) /* Privileged */ int -ssh_gssapi_userok(char *user) +ssh_gssapi_userok(char *user, struct passwd *pw) { OM_uint32 lmin; @@ -367,9 +446,11 @@ ssh_gssapi_userok(char *user) return 0; } if (gssapi_client.mech && gssapi_client.mech->userok) - if ((*gssapi_client.mech->userok)(&gssapi_client, user)) + if ((*gssapi_client.mech->userok)(&gssapi_client, user)) { + gssapi_client.used = 1; + gssapi_client.store.owner = pw; return 1; - else { + } else { /* Destroy delegated credentials if userok fails */ gss_release_buffer(&lmin, &gssapi_client.displayname); gss_release_buffer(&lmin, &gssapi_client.exportedname); @@ -383,14 +464,90 @@ ssh_gssapi_userok(char *user) return (0); } -/* Privileged */ -OM_uint32 -ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) +/* These bits are only used for rekeying. The unpriviledged child is running + * as the user, the monitor is root. + * + * In the child, we want to : + * *) Ask the monitor to store our credentials into the store we specify + * *) If it succeeds, maybe do a PAM update + */ + +/* Stuff for PAM */ + +#ifdef USE_PAM +static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg, + struct pam_response **resp, void *data) { - ctx->major = gss_verify_mic(&ctx->minor, ctx->context, - gssbuf, gssmic, NULL); + return (PAM_CONV_ERR); +} +#endif - return (ctx->major); +void +ssh_gssapi_rekey_creds(void) { + int ok; + int ret; +#ifdef USE_PAM + pam_handle_t *pamh = NULL; + struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL}; + char *envstr; +#endif + + if (gssapi_client.store.filename == NULL && + gssapi_client.store.envval == NULL && + gssapi_client.store.envvar == NULL) + return; + + ok = PRIVSEP(ssh_gssapi_update_creds(&gssapi_client.store)); + + if (!ok) + return; + + debug("Rekeyed credentials stored successfully"); + + /* Actually managing to play with the ssh pam stack from here will + * be next to impossible. In any case, we may want different options + * for rekeying. So, use our own :) + */ +#ifdef USE_PAM + if (!use_privsep) { + debug("Not even going to try and do PAM with privsep disabled"); + return; + } + + ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name, + &pamconv, &pamh); + if (ret) + return; + + xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar, + gssapi_client.store.envval); + + ret = pam_putenv(pamh, envstr); + if (!ret) + pam_setcred(pamh, PAM_REINITIALIZE_CRED); + pam_end(pamh, PAM_SUCCESS); +#endif +} + +int +ssh_gssapi_update_creds(ssh_gssapi_ccache *store) { + int ok = 0; + + /* Check we've got credentials to store */ + if (!gssapi_client.updated) + return 0; + + gssapi_client.updated = 0; + + temporarily_use_uid(gssapi_client.store.owner); + if (gssapi_client.mech && gssapi_client.mech->updatecreds) + ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client); + else + debug("No update function for this mechanism"); + + restore_uid(); + + return ok; } #endif diff --git a/kex.c b/kex.c index 50c7a0f9b..c17d652c9 100644 --- a/kex.c +++ b/kex.c @@ -55,6 +55,10 @@ #include "sshbuf.h" #include "digest.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + #if OPENSSL_VERSION_NUMBER >= 0x00907000L # if defined(HAVE_EVP_SHA256) # define evp_ssh_sha256 EVP_sha256 @@ -113,6 +117,14 @@ static const struct kexalg kexalgs[] = { #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ { NULL, -1, -1, -1}, }; +static const struct kexalg kexalg_prefixes[] = { +#ifdef GSSAPI + { KEX_GSS_GEX_SHA1_ID, KEX_GSS_GEX_SHA1, 0, SSH_DIGEST_SHA1 }, + { KEX_GSS_GRP1_SHA1_ID, KEX_GSS_GRP1_SHA1, 0, SSH_DIGEST_SHA1 }, + { KEX_GSS_GRP14_SHA1_ID, KEX_GSS_GRP14_SHA1, 0, SSH_DIGEST_SHA1 }, +#endif + { NULL, -1, -1, -1 }, +}; char * kex_alg_list(char sep) @@ -145,6 +157,10 @@ kex_alg_by_name(const char *name) if (strcmp(k->name, name) == 0) return k; } + for (k = kexalg_prefixes; k->name != NULL; k++) { + if (strncmp(k->name, name, strlen(k->name)) == 0) + return k; + } return NULL; } @@ -587,6 +603,9 @@ kex_free(struct kex *kex) sshbuf_free(kex->peer); sshbuf_free(kex->my); free(kex->session_id); +#ifdef GSSAPI + free(kex->gss_host); +#endif /* GSSAPI */ free(kex->client_version_string); free(kex->server_version_string); free(kex->failed_choice); diff --git a/kex.h b/kex.h index c35195568..8ed459a76 100644 --- a/kex.h +++ b/kex.h @@ -98,6 +98,9 @@ enum kex_exchange { KEX_DH_GEX_SHA256, KEX_ECDH_SHA2, KEX_C25519_SHA256, + KEX_GSS_GRP1_SHA1, + KEX_GSS_GRP14_SHA1, + KEX_GSS_GEX_SHA1, KEX_MAX }; @@ -146,6 +149,12 @@ struct kex { u_int flags; int hash_alg; int ec_nid; +#ifdef GSSAPI + int gss_deleg_creds; + int gss_trust_dns; + char *gss_host; + char *gss_client; +#endif char *client_version_string; char *server_version_string; char *failed_choice; @@ -196,6 +205,11 @@ int kexecdh_server(struct ssh *); int kexc25519_client(struct ssh *); int kexc25519_server(struct ssh *); +#ifdef GSSAPI +int kexgss_client(struct ssh *); +int kexgss_server(struct ssh *); +#endif + int kex_dh_hash(int, const char *, const char *, const u_char *, size_t, const u_char *, size_t, const u_char *, size_t, const BIGNUM *, const BIGNUM *, const BIGNUM *, u_char *, size_t *); diff --git a/kexgssc.c b/kexgssc.c new file mode 100644 index 000000000..10447f2b0 --- /dev/null +++ b/kexgssc.c @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" + +#ifdef GSSAPI + +#include "includes.h" + +#include +#include + +#include + +#include "xmalloc.h" +#include "buffer.h" +#include "ssh2.h" +#include "key.h" +#include "cipher.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "dh.h" +#include "digest.h" + +#include "ssh-gss.h" + +int +kexgss_client(struct ssh *ssh) { + gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; + gss_buffer_desc recv_tok, gssbuf, msg_tok, *token_ptr; + Gssctxt *ctxt; + OM_uint32 maj_status, min_status, ret_flags; + u_int klen, kout, slen = 0, strlen; + DH *dh; + BIGNUM *dh_server_pub = NULL; + BIGNUM *shared_secret = NULL; + BIGNUM *p = NULL; + BIGNUM *g = NULL; + u_char *kbuf; + u_char *serverhostkey = NULL; + u_char *empty = ""; + char *msg; + int type = 0; + int first = 1; + int nbits = 0, min = DH_GRP_MIN, max = DH_GRP_MAX; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t hashlen; + + /* Initialise our GSSAPI world */ + ssh_gssapi_build_ctx(&ctxt); + if (ssh_gssapi_id_kex(ctxt, ssh->kex->name, ssh->kex->kex_type) + == GSS_C_NO_OID) + fatal("Couldn't identify host exchange"); + + if (ssh_gssapi_import_name(ctxt, ssh->kex->gss_host)) + fatal("Couldn't import hostname"); + + if (ssh->kex->gss_client && + ssh_gssapi_client_identity(ctxt, ssh->kex->gss_client)) + fatal("Couldn't acquire client credentials"); + + switch (ssh->kex->kex_type) { + case KEX_GSS_GRP1_SHA1: + dh = dh_new_group1(); + break; + case KEX_GSS_GRP14_SHA1: + dh = dh_new_group14(); + break; + case KEX_GSS_GEX_SHA1: + debug("Doing group exchange\n"); + nbits = dh_estimate(ssh->kex->we_need * 8); + packet_start(SSH2_MSG_KEXGSS_GROUPREQ); + packet_put_int(min); + packet_put_int(nbits); + packet_put_int(max); + + packet_send(); + + packet_read_expect(SSH2_MSG_KEXGSS_GROUP); + + if ((p = BN_new()) == NULL) + fatal("BN_new() failed"); + packet_get_bignum2(p); + if ((g = BN_new()) == NULL) + fatal("BN_new() failed"); + packet_get_bignum2(g); + packet_check_eom(); + + if (BN_num_bits(p) < min || BN_num_bits(p) > max) + fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", + min, BN_num_bits(p), max); + + dh = dh_new_group(g, p); + break; + default: + fatal("%s: Unexpected KEX type %d", __func__, ssh->kex->kex_type); + } + + /* Step 1 - e is dh->pub_key */ + dh_gen_key(dh, ssh->kex->we_need * 8); + + /* This is f, we initialise it now to make life easier */ + dh_server_pub = BN_new(); + if (dh_server_pub == NULL) + fatal("dh_server_pub == NULL"); + + token_ptr = GSS_C_NO_BUFFER; + + do { + debug("Calling gss_init_sec_context"); + + maj_status = ssh_gssapi_init_ctx(ctxt, + ssh->kex->gss_deleg_creds, token_ptr, &send_tok, + &ret_flags); + + if (GSS_ERROR(maj_status)) { + if (send_tok.length != 0) { + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value, + send_tok.length); + } + fatal("gss_init_context failed"); + } + + /* If we've got an old receive buffer get rid of it */ + if (token_ptr != GSS_C_NO_BUFFER) + free(recv_tok.value); + + if (maj_status == GSS_S_COMPLETE) { + /* If mutual state flag is not true, kex fails */ + if (!(ret_flags & GSS_C_MUTUAL_FLAG)) + fatal("Mutual authentication failed"); + + /* If integ avail flag is not true kex fails */ + if (!(ret_flags & GSS_C_INTEG_FLAG)) + fatal("Integrity check failed"); + } + + /* + * If we have data to send, then the last message that we + * received cannot have been a 'complete'. + */ + if (send_tok.length != 0) { + if (first) { + packet_start(SSH2_MSG_KEXGSS_INIT); + packet_put_string(send_tok.value, + send_tok.length); + packet_put_bignum2(dh->pub_key); + first = 0; + } else { + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value, + send_tok.length); + } + packet_send(); + gss_release_buffer(&min_status, &send_tok); + + /* If we've sent them data, they should reply */ + do { + type = packet_read(); + if (type == SSH2_MSG_KEXGSS_HOSTKEY) { + debug("Received KEXGSS_HOSTKEY"); + if (serverhostkey) + fatal("Server host key received more than once"); + serverhostkey = + packet_get_string(&slen); + } + } while (type == SSH2_MSG_KEXGSS_HOSTKEY); + + switch (type) { + case SSH2_MSG_KEXGSS_CONTINUE: + debug("Received GSSAPI_CONTINUE"); + if (maj_status == GSS_S_COMPLETE) + fatal("GSSAPI Continue received from server when complete"); + recv_tok.value = packet_get_string(&strlen); + recv_tok.length = strlen; + break; + case SSH2_MSG_KEXGSS_COMPLETE: + debug("Received GSSAPI_COMPLETE"); + packet_get_bignum2(dh_server_pub); + msg_tok.value = packet_get_string(&strlen); + msg_tok.length = strlen; + + /* Is there a token included? */ + if (packet_get_char()) { + recv_tok.value= + packet_get_string(&strlen); + recv_tok.length = strlen; + /* If we're already complete - protocol error */ + if (maj_status == GSS_S_COMPLETE) + packet_disconnect("Protocol error: received token when complete"); + } else { + /* No token included */ + if (maj_status != GSS_S_COMPLETE) + packet_disconnect("Protocol error: did not receive final token"); + } + break; + case SSH2_MSG_KEXGSS_ERROR: + debug("Received Error"); + maj_status = packet_get_int(); + min_status = packet_get_int(); + msg = packet_get_string(NULL); + (void) packet_get_string_ptr(NULL); + fatal("GSSAPI Error: \n%.400s",msg); + default: + packet_disconnect("Protocol error: didn't expect packet type %d", + type); + } + token_ptr = &recv_tok; + } else { + /* No data, and not complete */ + if (maj_status != GSS_S_COMPLETE) + fatal("Not complete, and no token output"); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + /* + * We _must_ have received a COMPLETE message in reply from the + * server, which will have set dh_server_pub and msg_tok + */ + + if (type != SSH2_MSG_KEXGSS_COMPLETE) + fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it"); + + /* Check f in range [1, p-1] */ + if (!dh_pub_is_valid(dh, dh_server_pub)) + packet_disconnect("bad server public DH value"); + + /* compute K=f^x mod p */ + klen = DH_size(dh); + kbuf = xmalloc(klen); + kout = DH_compute_key(kbuf, dh_server_pub, dh); + if (kout < 0) + fatal("DH_compute_key: failed"); + + shared_secret = BN_new(); + if (shared_secret == NULL) + fatal("kexgss_client: BN_new failed"); + + if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) + fatal("kexdh_client: BN_bin2bn failed"); + + memset(kbuf, 0, klen); + free(kbuf); + + hashlen = sizeof(hash); + switch (ssh->kex->kex_type) { + case KEX_GSS_GRP1_SHA1: + case KEX_GSS_GRP14_SHA1: + kex_dh_hash( + ssh->kex->hash_alg, + ssh->kex->client_version_string, + ssh->kex->server_version_string, + buffer_ptr(ssh->kex->my), buffer_len(ssh->kex->my), + buffer_ptr(ssh->kex->peer), buffer_len(ssh->kex->peer), + (serverhostkey ? serverhostkey : empty), slen, + dh->pub_key, /* e */ + dh_server_pub, /* f */ + shared_secret, /* K */ + hash, &hashlen + ); + break; + case KEX_GSS_GEX_SHA1: + kexgex_hash( + ssh->kex->hash_alg, + ssh->kex->client_version_string, + ssh->kex->server_version_string, + buffer_ptr(ssh->kex->my), buffer_len(ssh->kex->my), + buffer_ptr(ssh->kex->peer), buffer_len(ssh->kex->peer), + (serverhostkey ? serverhostkey : empty), slen, + min, nbits, max, + dh->p, dh->g, + dh->pub_key, + dh_server_pub, + shared_secret, + hash, &hashlen + ); + break; + default: + fatal("%s: Unexpected KEX type %d", __func__, ssh->kex->kex_type); + } + + gssbuf.value = hash; + gssbuf.length = hashlen; + + /* Verify that the hash matches the MIC we just got. */ + if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) + packet_disconnect("Hash's MIC didn't verify"); + + free(msg_tok.value); + + DH_free(dh); + free(serverhostkey); + BN_clear_free(dh_server_pub); + + /* save session id */ + if (ssh->kex->session_id == NULL) { + ssh->kex->session_id_len = hashlen; + ssh->kex->session_id = xmalloc(ssh->kex->session_id_len); + memcpy(ssh->kex->session_id, hash, ssh->kex->session_id_len); + } + + if (ssh->kex->gss_deleg_creds) + ssh_gssapi_credentials_updated(ctxt); + + if (gss_kex_context == NULL) + gss_kex_context = ctxt; + else + ssh_gssapi_delete_ctx(&ctxt); + + kex_derive_keys_bn(ssh, hash, hashlen, shared_secret); + BN_clear_free(shared_secret); + return kex_send_newkeys(ssh); +} + +#endif /* GSSAPI */ diff --git a/kexgsss.c b/kexgsss.c new file mode 100644 index 000000000..38ca082ba --- /dev/null +++ b/kexgsss.c @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" + +#ifdef GSSAPI + +#include + +#include +#include + +#include "xmalloc.h" +#include "buffer.h" +#include "ssh2.h" +#include "key.h" +#include "cipher.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "dh.h" +#include "ssh-gss.h" +#include "monitor_wrap.h" +#include "misc.h" +#include "servconf.h" +#include "digest.h" + +extern ServerOptions options; + +int +kexgss_server(struct ssh *ssh) +{ + OM_uint32 maj_status, min_status; + + /* + * Some GSSAPI implementations use the input value of ret_flags (an + * output variable) as a means of triggering mechanism specific + * features. Initializing it to zero avoids inadvertently + * activating this non-standard behaviour. + */ + + OM_uint32 ret_flags = 0; + gss_buffer_desc gssbuf, recv_tok, msg_tok; + gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; + Gssctxt *ctxt = NULL; + u_int slen, klen, kout; + u_char *kbuf; + DH *dh; + int min = -1, max = -1, nbits = -1; + BIGNUM *shared_secret = NULL; + BIGNUM *dh_client_pub = NULL; + int type = 0; + gss_OID oid; + char *mechs; + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t hashlen; + + /* Initialise GSSAPI */ + + /* If we're rekeying, privsep means that some of the private structures + * in the GSSAPI code are no longer available. This kludges them back + * into life + */ + if (!ssh_gssapi_oid_table_ok()) { + mechs = ssh_gssapi_server_mechanisms(); + free(mechs); + } + + debug2("%s: Identifying %s", __func__, ssh->kex->name); + oid = ssh_gssapi_id_kex(NULL, ssh->kex->name, ssh->kex->kex_type); + if (oid == GSS_C_NO_OID) + fatal("Unknown gssapi mechanism"); + + debug2("%s: Acquiring credentials", __func__); + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid)))) + fatal("Unable to acquire credentials for the server"); + + switch (ssh->kex->kex_type) { + case KEX_GSS_GRP1_SHA1: + dh = dh_new_group1(); + break; + case KEX_GSS_GRP14_SHA1: + dh = dh_new_group14(); + break; + case KEX_GSS_GEX_SHA1: + debug("Doing group exchange"); + packet_read_expect(SSH2_MSG_KEXGSS_GROUPREQ); + min = packet_get_int(); + nbits = packet_get_int(); + max = packet_get_int(); + packet_check_eom(); + if (max < min || nbits < min || max < nbits) + fatal("GSS_GEX, bad parameters: %d !< %d !< %d", + min, nbits, max); + dh = PRIVSEP(choose_dh(MAX(DH_GRP_MIN, min), + nbits, MIN(DH_GRP_MAX, max))); + if (dh == NULL) + packet_disconnect("Protocol error: no matching group found"); + + packet_start(SSH2_MSG_KEXGSS_GROUP); + packet_put_bignum2(dh->p); + packet_put_bignum2(dh->g); + packet_send(); + + packet_write_wait(); + break; + default: + fatal("%s: Unexpected KEX type %d", __func__, ssh->kex->kex_type); + } + + dh_gen_key(dh, ssh->kex->we_need * 8); + + do { + debug("Wait SSH2_MSG_GSSAPI_INIT"); + type = packet_read(); + switch(type) { + case SSH2_MSG_KEXGSS_INIT: + if (dh_client_pub != NULL) + fatal("Received KEXGSS_INIT after initialising"); + recv_tok.value = packet_get_string(&slen); + recv_tok.length = slen; + + if ((dh_client_pub = BN_new()) == NULL) + fatal("dh_client_pub == NULL"); + + packet_get_bignum2(dh_client_pub); + + /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ + break; + case SSH2_MSG_KEXGSS_CONTINUE: + recv_tok.value = packet_get_string(&slen); + recv_tok.length = slen; + break; + default: + packet_disconnect( + "Protocol error: didn't expect packet type %d", + type); + } + + maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok, + &send_tok, &ret_flags)); + + free(recv_tok.value); + + if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) + fatal("Zero length token output when incomplete"); + + if (dh_client_pub == NULL) + fatal("No client public key"); + + if (maj_status & GSS_S_CONTINUE_NEEDED) { + debug("Sending GSSAPI_CONTINUE"); + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value, send_tok.length); + packet_send(); + gss_release_buffer(&min_status, &send_tok); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + if (GSS_ERROR(maj_status)) { + if (send_tok.length > 0) { + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value, send_tok.length); + packet_send(); + } + fatal("accept_ctx died"); + } + + if (!(ret_flags & GSS_C_MUTUAL_FLAG)) + fatal("Mutual Authentication flag wasn't set"); + + if (!(ret_flags & GSS_C_INTEG_FLAG)) + fatal("Integrity flag wasn't set"); + + if (!dh_pub_is_valid(dh, dh_client_pub)) + packet_disconnect("bad client public DH value"); + + klen = DH_size(dh); + kbuf = xmalloc(klen); + kout = DH_compute_key(kbuf, dh_client_pub, dh); + if (kout < 0) + fatal("DH_compute_key: failed"); + + shared_secret = BN_new(); + if (shared_secret == NULL) + fatal("kexgss_server: BN_new failed"); + + if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) + fatal("kexgss_server: BN_bin2bn failed"); + + memset(kbuf, 0, klen); + free(kbuf); + + hashlen = sizeof(hash); + switch (ssh->kex->kex_type) { + case KEX_GSS_GRP1_SHA1: + case KEX_GSS_GRP14_SHA1: + kex_dh_hash( + ssh->kex->hash_alg, + ssh->kex->client_version_string, ssh->kex->server_version_string, + buffer_ptr(ssh->kex->peer), buffer_len(ssh->kex->peer), + buffer_ptr(ssh->kex->my), buffer_len(ssh->kex->my), + NULL, 0, /* Change this if we start sending host keys */ + dh_client_pub, dh->pub_key, shared_secret, + hash, &hashlen + ); + break; + case KEX_GSS_GEX_SHA1: + kexgex_hash( + ssh->kex->hash_alg, + ssh->kex->client_version_string, ssh->kex->server_version_string, + buffer_ptr(ssh->kex->peer), buffer_len(ssh->kex->peer), + buffer_ptr(ssh->kex->my), buffer_len(ssh->kex->my), + NULL, 0, + min, nbits, max, + dh->p, dh->g, + dh_client_pub, + dh->pub_key, + shared_secret, + hash, &hashlen + ); + break; + default: + fatal("%s: Unexpected KEX type %d", __func__, ssh->kex->kex_type); + } + + BN_clear_free(dh_client_pub); + + if (ssh->kex->session_id == NULL) { + ssh->kex->session_id_len = hashlen; + ssh->kex->session_id = xmalloc(ssh->kex->session_id_len); + memcpy(ssh->kex->session_id, hash, ssh->kex->session_id_len); + } + + gssbuf.value = hash; + gssbuf.length = hashlen; + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt,&gssbuf,&msg_tok)))) + fatal("Couldn't get MIC"); + + packet_start(SSH2_MSG_KEXGSS_COMPLETE); + packet_put_bignum2(dh->pub_key); + packet_put_string(msg_tok.value,msg_tok.length); + + if (send_tok.length != 0) { + packet_put_char(1); /* true */ + packet_put_string(send_tok.value, send_tok.length); + } else { + packet_put_char(0); /* false */ + } + packet_send(); + + gss_release_buffer(&min_status, &send_tok); + gss_release_buffer(&min_status, &msg_tok); + + if (gss_kex_context == NULL) + gss_kex_context = ctxt; + else + ssh_gssapi_delete_ctx(&ctxt); + + DH_free(dh); + + kex_derive_keys_bn(ssh, hash, hashlen, shared_secret); + BN_clear_free(shared_secret); + kex_send_newkeys(ssh); + + /* If this was a rekey, then save out any delegated credentials we + * just exchanged. */ + if (options.gss_store_rekey) + ssh_gssapi_rekey_creds(); + return 0; +} +#endif /* GSSAPI */ diff --git a/monitor.c b/monitor.c index cb57bd066..05bb48a8e 100644 --- a/monitor.c +++ b/monitor.c @@ -158,6 +158,8 @@ int mm_answer_gss_setup_ctx(int, Buffer *); int mm_answer_gss_accept_ctx(int, Buffer *); int mm_answer_gss_userok(int, Buffer *); int mm_answer_gss_checkmic(int, Buffer *); +int mm_answer_gss_sign(int, Buffer *); +int mm_answer_gss_updatecreds(int, Buffer *); #endif #ifdef SSH_AUDIT_EVENTS @@ -235,11 +237,18 @@ struct mon_table mon_dispatch_proto20[] = { {MONITOR_REQ_GSSSTEP, MON_ISAUTH, mm_answer_gss_accept_ctx}, {MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok}, {MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic}, + {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign}, #endif {0, 0, NULL} }; struct mon_table mon_dispatch_postauth20[] = { +#ifdef GSSAPI + {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx}, + {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, + {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign}, + {MONITOR_REQ_GSSUPCREDS, 0, mm_answer_gss_updatecreds}, +#endif #ifdef WITH_OPENSSL {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, #endif @@ -354,6 +363,10 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); +#ifdef GSSAPI + /* and for the GSSAPI key exchange */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); +#endif } else { mon_dispatch = mon_dispatch_proto15; @@ -462,6 +475,10 @@ monitor_child_postauth(struct monitor *pmonitor) monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); +#ifdef GSSAPI + /* and for the GSSAPI key exchange */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); +#endif } else { mon_dispatch = mon_dispatch_postauth15; monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); @@ -1876,6 +1893,13 @@ monitor_apply_keystate(struct monitor *pmonitor) # endif #endif /* WITH_OPENSSL */ kex->kex[KEX_C25519_SHA256] = kexc25519_server; +#ifdef GSSAPI + if (options.gss_keyex) { + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; + kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; + kex->kex[KEX_GSS_GEX_SHA1] = kexgss_server; + } +#endif kex->load_host_public_key=&get_hostkey_public_by_type; kex->load_host_private_key=&get_hostkey_private_by_type; kex->host_key_index=&get_hostkey_index; @@ -1975,6 +1999,9 @@ mm_answer_gss_setup_ctx(int sock, Buffer *m) OM_uint32 major; u_int len; + if (!options.gss_authentication && !options.gss_keyex) + fatal("In GSSAPI monitor when GSSAPI is disabled"); + goid.elements = buffer_get_string(m, &len); goid.length = len; @@ -2002,6 +2029,9 @@ mm_answer_gss_accept_ctx(int sock, Buffer *m) OM_uint32 flags = 0; /* GSI needs this */ u_int len; + if (!options.gss_authentication && !options.gss_keyex) + fatal("In GSSAPI monitor when GSSAPI is disabled"); + in.value = buffer_get_string(m, &len); in.length = len; major = ssh_gssapi_accept_ctx(gsscontext, &in, &out, &flags); @@ -2019,6 +2049,7 @@ mm_answer_gss_accept_ctx(int sock, Buffer *m) monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1); } return (0); } @@ -2030,6 +2061,9 @@ mm_answer_gss_checkmic(int sock, Buffer *m) OM_uint32 ret; u_int len; + if (!options.gss_authentication && !options.gss_keyex) + fatal("In GSSAPI monitor when GSSAPI is disabled"); + gssbuf.value = buffer_get_string(m, &len); gssbuf.length = len; mic.value = buffer_get_string(m, &len); @@ -2056,7 +2090,11 @@ mm_answer_gss_userok(int sock, Buffer *m) { int authenticated; - authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); + if (!options.gss_authentication && !options.gss_keyex) + fatal("In GSSAPI monitor when GSSAPI is disabled"); + + authenticated = authctxt->valid && + ssh_gssapi_userok(authctxt->user, authctxt->pw); buffer_clear(m); buffer_put_int(m, authenticated); @@ -2069,5 +2107,73 @@ mm_answer_gss_userok(int sock, Buffer *m) /* Monitor loop will terminate if authenticated */ return (authenticated); } + +int +mm_answer_gss_sign(int socket, Buffer *m) +{ + gss_buffer_desc data; + gss_buffer_desc hash = GSS_C_EMPTY_BUFFER; + OM_uint32 major, minor; + u_int len; + + if (!options.gss_authentication && !options.gss_keyex) + fatal("In GSSAPI monitor when GSSAPI is disabled"); + + data.value = buffer_get_string(m, &len); + data.length = len; + if (data.length != 20) + fatal("%s: data length incorrect: %d", __func__, + (int) data.length); + + /* Save the session ID on the first time around */ + if (session_id2_len == 0) { + session_id2_len = data.length; + session_id2 = xmalloc(session_id2_len); + memcpy(session_id2, data.value, session_id2_len); + } + major = ssh_gssapi_sign(gsscontext, &data, &hash); + + free(data.value); + + buffer_clear(m); + buffer_put_int(m, major); + buffer_put_string(m, hash.value, hash.length); + + mm_request_send(socket, MONITOR_ANS_GSSSIGN, m); + + gss_release_buffer(&minor, &hash); + + /* Turn on getpwnam permissions */ + monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); + + /* And credential updating, for when rekeying */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSUPCREDS, 1); + + return (0); +} + +int +mm_answer_gss_updatecreds(int socket, Buffer *m) { + ssh_gssapi_ccache store; + int ok; + + store.filename = buffer_get_string(m, NULL); + store.envvar = buffer_get_string(m, NULL); + store.envval = buffer_get_string(m, NULL); + + ok = ssh_gssapi_update_creds(&store); + + free(store.filename); + free(store.envvar); + free(store.envval); + + buffer_clear(m); + buffer_put_int(m, ok); + + mm_request_send(socket, MONITOR_ANS_GSSUPCREDS, m); + + return(0); +} + #endif /* GSSAPI */ diff --git a/monitor.h b/monitor.h index 93b8b66dd..bc50ade1f 100644 --- a/monitor.h +++ b/monitor.h @@ -65,6 +65,9 @@ enum monitor_reqtype { MONITOR_REQ_PAM_FREE_CTX = 110, MONITOR_ANS_PAM_FREE_CTX = 111, MONITOR_REQ_AUDIT_EVENT = 112, MONITOR_REQ_AUDIT_COMMAND = 113, + MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151, + MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153, + }; struct mm_master; diff --git a/monitor_wrap.c b/monitor_wrap.c index 99dc13b61..5a9f1b52d 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -1073,7 +1073,7 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) } int -mm_ssh_gssapi_userok(char *user) +mm_ssh_gssapi_userok(char *user, struct passwd *pw) { Buffer m; int authenticated = 0; @@ -1090,5 +1090,50 @@ mm_ssh_gssapi_userok(char *user) debug3("%s: user %sauthenticated",__func__, authenticated ? "" : "not "); return (authenticated); } + +OM_uint32 +mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash) +{ + Buffer m; + OM_uint32 major; + u_int len; + + buffer_init(&m); + buffer_put_string(&m, data->value, data->length); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, &m); + + major = buffer_get_int(&m); + hash->value = buffer_get_string(&m, &len); + hash->length = len; + + buffer_free(&m); + + return(major); +} + +int +mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *store) +{ + Buffer m; + int ok; + + buffer_init(&m); + + buffer_put_cstring(&m, store->filename ? store->filename : ""); + buffer_put_cstring(&m, store->envvar ? store->envvar : ""); + buffer_put_cstring(&m, store->envval ? store->envval : ""); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUPCREDS, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUPCREDS, &m); + + ok = buffer_get_int(&m); + + buffer_free(&m); + + return (ok); +} + #endif /* GSSAPI */ diff --git a/monitor_wrap.h b/monitor_wrap.h index 9fd02b30c..b5414c298 100644 --- a/monitor_wrap.h +++ b/monitor_wrap.h @@ -60,8 +60,10 @@ BIGNUM *mm_auth_rsa_generate_challenge(Key *); OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *); -int mm_ssh_gssapi_userok(char *user); +int mm_ssh_gssapi_userok(char *user, struct passwd *); OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); +OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); +int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *); #endif #ifdef USE_PAM diff --git a/readconf.c b/readconf.c index c177202b1..e019195e7 100644 --- a/readconf.c +++ b/readconf.c @@ -160,6 +160,8 @@ typedef enum { oClearAllForwardings, oNoHostAuthenticationForLocalhost, oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, oAddressFamily, oGssAuthentication, oGssDelegateCreds, + oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey, + oGssServerIdentity, oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oControlPath, oControlMaster, oControlPersist, oHashKnownHosts, @@ -205,10 +207,19 @@ static struct { { "afstokenpassing", oUnsupported }, #if defined(GSSAPI) { "gssapiauthentication", oGssAuthentication }, + { "gssapikeyexchange", oGssKeyEx }, { "gssapidelegatecredentials", oGssDelegateCreds }, + { "gssapitrustdns", oGssTrustDns }, + { "gssapiclientidentity", oGssClientIdentity }, + { "gssapiserveridentity", oGssServerIdentity }, + { "gssapirenewalforcesrekey", oGssRenewalRekey }, #else { "gssapiauthentication", oUnsupported }, + { "gssapikeyexchange", oUnsupported }, { "gssapidelegatecredentials", oUnsupported }, + { "gssapitrustdns", oUnsupported }, + { "gssapiclientidentity", oUnsupported }, + { "gssapirenewalforcesrekey", oUnsupported }, #endif { "fallbacktorsh", oDeprecated }, { "usersh", oDeprecated }, @@ -962,10 +973,30 @@ parse_time: intptr = &options->gss_authentication; goto parse_flag; + case oGssKeyEx: + intptr = &options->gss_keyex; + goto parse_flag; + case oGssDelegateCreds: intptr = &options->gss_deleg_creds; goto parse_flag; + case oGssTrustDns: + intptr = &options->gss_trust_dns; + goto parse_flag; + + case oGssClientIdentity: + charptr = &options->gss_client_identity; + goto parse_string; + + case oGssServerIdentity: + charptr = &options->gss_server_identity; + goto parse_string; + + case oGssRenewalRekey: + intptr = &options->gss_renewal_rekey; + goto parse_flag; + case oBatchMode: intptr = &options->batch_mode; goto parse_flag; @@ -1777,7 +1808,12 @@ initialize_options(Options * options) options->pubkey_authentication = -1; options->challenge_response_authentication = -1; options->gss_authentication = -1; + options->gss_keyex = -1; options->gss_deleg_creds = -1; + options->gss_trust_dns = -1; + options->gss_renewal_rekey = -1; + options->gss_client_identity = NULL; + options->gss_server_identity = NULL; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->kbd_interactive_devices = NULL; @@ -1921,8 +1957,14 @@ fill_default_options(Options * options) options->challenge_response_authentication = 1; if (options->gss_authentication == -1) options->gss_authentication = 0; + if (options->gss_keyex == -1) + options->gss_keyex = 0; if (options->gss_deleg_creds == -1) options->gss_deleg_creds = 0; + if (options->gss_trust_dns == -1) + options->gss_trust_dns = 0; + if (options->gss_renewal_rekey == -1) + options->gss_renewal_rekey = 0; if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) diff --git a/readconf.h b/readconf.h index cef55f71c..fd3d7c75d 100644 --- a/readconf.h +++ b/readconf.h @@ -45,7 +45,12 @@ typedef struct { int challenge_response_authentication; /* Try S/Key or TIS, authentication. */ int gss_authentication; /* Try GSS authentication */ + int gss_keyex; /* Try GSS key exchange */ int gss_deleg_creds; /* Delegate GSS credentials */ + int gss_trust_dns; /* Trust DNS for GSS canonicalization */ + int gss_renewal_rekey; /* Credential renewal forces rekey */ + char *gss_client_identity; /* Principal to initiate GSSAPI with */ + char *gss_server_identity; /* GSSAPI target principal */ int password_authentication; /* Try password * authentication. */ int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ diff --git a/servconf.c b/servconf.c index 873b0d02a..9b0628181 100644 --- a/servconf.c +++ b/servconf.c @@ -117,8 +117,10 @@ initialize_server_options(ServerOptions *options) options->kerberos_ticket_cleanup = -1; options->kerberos_get_afs_token = -1; options->gss_authentication=-1; + options->gss_keyex = -1; options->gss_cleanup_creds = -1; options->gss_strict_acceptor = -1; + options->gss_store_rekey = -1; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->challenge_response_authentication = -1; @@ -287,10 +289,14 @@ fill_default_server_options(ServerOptions *options) options->kerberos_get_afs_token = 0; if (options->gss_authentication == -1) options->gss_authentication = 0; + if (options->gss_keyex == -1) + options->gss_keyex = 0; if (options->gss_cleanup_creds == -1) options->gss_cleanup_creds = 1; if (options->gss_strict_acceptor == -1) - options->gss_strict_acceptor = 0; + options->gss_strict_acceptor = 1; + if (options->gss_store_rekey == -1) + options->gss_store_rekey = 0; if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) @@ -427,6 +433,7 @@ typedef enum { sHostKeyAlgorithms, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, + sGssKeyEx, sGssStoreRekey, sAcceptEnv, sPermitTunnel, sMatch, sPermitOpen, sForceCommand, sChrootDirectory, sUsePrivilegeSeparation, sAllowAgentForwarding, @@ -500,12 +507,20 @@ static struct { #ifdef GSSAPI { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, + { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL }, { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL }, + { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL }, + { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL }, #else { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, + { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL }, { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL }, + { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL }, + { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL }, #endif + { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL }, + { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL }, { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, { "challengeresponseauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, @@ -1251,6 +1266,10 @@ process_server_config_line(ServerOptions *options, char *line, intptr = &options->gss_authentication; goto parse_flag; + case sGssKeyEx: + intptr = &options->gss_keyex; + goto parse_flag; + case sGssCleanupCreds: intptr = &options->gss_cleanup_creds; goto parse_flag; @@ -1259,6 +1278,10 @@ process_server_config_line(ServerOptions *options, char *line, intptr = &options->gss_strict_acceptor; goto parse_flag; + case sGssStoreRekey: + intptr = &options->gss_store_rekey; + goto parse_flag; + case sPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; @@ -2308,7 +2331,10 @@ dump_config(ServerOptions *o) #endif #ifdef GSSAPI dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); + dump_cfg_fmtint(sGssKeyEx, o->gss_keyex); dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); + dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor); + dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); #endif dump_cfg_fmtint(sPasswordAuthentication, o->password_authentication); dump_cfg_fmtint(sKbdInteractiveAuthentication, diff --git a/servconf.h b/servconf.h index f4137af7d..778ba1742 100644 --- a/servconf.h +++ b/servconf.h @@ -118,8 +118,10 @@ typedef struct { int kerberos_get_afs_token; /* If true, try to get AFS token if * authenticated with Kerberos. */ int gss_authentication; /* If true, permit GSSAPI authentication */ + int gss_keyex; /* If true, permit GSSAPI key exchange */ int gss_cleanup_creds; /* If true, destroy cred cache on logout */ int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */ + int gss_store_rekey; int password_authentication; /* If true, permit password * authentication. */ int kbd_interactive_authentication; /* If true, permit */ diff --git a/ssh-gss.h b/ssh-gss.h index a99d7f08b..914701bcf 100644 --- a/ssh-gss.h +++ b/ssh-gss.h @@ -1,6 +1,6 @@ /* $OpenBSD: ssh-gss.h,v 1.11 2014/02/26 20:28:44 djm Exp $ */ /* - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -61,10 +61,22 @@ #define SSH_GSS_OIDTYPE 0x06 +#define SSH2_MSG_KEXGSS_INIT 30 +#define SSH2_MSG_KEXGSS_CONTINUE 31 +#define SSH2_MSG_KEXGSS_COMPLETE 32 +#define SSH2_MSG_KEXGSS_HOSTKEY 33 +#define SSH2_MSG_KEXGSS_ERROR 34 +#define SSH2_MSG_KEXGSS_GROUPREQ 40 +#define SSH2_MSG_KEXGSS_GROUP 41 +#define KEX_GSS_GRP1_SHA1_ID "gss-group1-sha1-" +#define KEX_GSS_GRP14_SHA1_ID "gss-group14-sha1-" +#define KEX_GSS_GEX_SHA1_ID "gss-gex-sha1-" + typedef struct { char *filename; char *envvar; char *envval; + struct passwd *owner; void *data; } ssh_gssapi_ccache; @@ -72,8 +84,11 @@ typedef struct { gss_buffer_desc displayname; gss_buffer_desc exportedname; gss_cred_id_t creds; + gss_name_t name; struct ssh_gssapi_mech_struct *mech; ssh_gssapi_ccache store; + int used; + int updated; } ssh_gssapi_client; typedef struct ssh_gssapi_mech_struct { @@ -84,6 +99,7 @@ typedef struct ssh_gssapi_mech_struct { int (*userok) (ssh_gssapi_client *, char *); int (*localname) (ssh_gssapi_client *, char **); void (*storecreds) (ssh_gssapi_client *); + int (*updatecreds) (ssh_gssapi_ccache *, ssh_gssapi_client *); } ssh_gssapi_mech; typedef struct { @@ -94,10 +110,11 @@ typedef struct { gss_OID oid; /* client */ gss_cred_id_t creds; /* server */ gss_name_t client; /* server */ - gss_cred_id_t client_creds; /* server */ + gss_cred_id_t client_creds; /* both */ } Gssctxt; extern ssh_gssapi_mech *supported_mechs[]; +extern Gssctxt *gss_kex_context; int ssh_gssapi_check_oid(Gssctxt *, void *, size_t); void ssh_gssapi_set_oid_data(Gssctxt *, void *, size_t); @@ -119,16 +136,32 @@ void ssh_gssapi_build_ctx(Gssctxt **); void ssh_gssapi_delete_ctx(Gssctxt **); OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); void ssh_gssapi_buildmic(Buffer *, const char *, const char *, const char *); -int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *); +int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *, const char *); +OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *); +int ssh_gssapi_credentials_updated(Gssctxt *); /* In the server */ +typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *, + const char *); +char *ssh_gssapi_client_mechanisms(const char *, const char *); +char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, const char *, + const char *); +gss_OID ssh_gssapi_id_kex(Gssctxt *, char *, int); +int ssh_gssapi_server_check_mech(Gssctxt **,gss_OID, const char *, + const char *); OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID); -int ssh_gssapi_userok(char *name); +int ssh_gssapi_userok(char *name, struct passwd *); OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); void ssh_gssapi_do_child(char ***, u_int *); void ssh_gssapi_cleanup_creds(void); void ssh_gssapi_storecreds(void); +char *ssh_gssapi_server_mechanisms(void); +int ssh_gssapi_oid_table_ok(void); + +int ssh_gssapi_update_creds(ssh_gssapi_ccache *store); +void ssh_gssapi_rekey_creds(void); + #endif /* GSSAPI */ #endif /* _SSH_GSS_H */ diff --git a/ssh_config b/ssh_config index 90fb63f0b..4e879cd20 100644 --- a/ssh_config +++ b/ssh_config @@ -26,6 +26,8 @@ # HostbasedAuthentication no # GSSAPIAuthentication no # GSSAPIDelegateCredentials no +# GSSAPIKeyExchange no +# GSSAPITrustDNS no # BatchMode no # CheckHostIP yes # AddressFamily any diff --git a/ssh_config.5 b/ssh_config.5 index 7630e7bcb..707d0e1c3 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -826,10 +826,42 @@ The default is Specifies whether user authentication based on GSSAPI is allowed. The default is .Dq no . +.It Cm GSSAPIKeyExchange +Specifies whether key exchange based on GSSAPI may be used. When using +GSSAPI key exchange the server need not have a host key. +The default is +.Dq no . +.It Cm GSSAPIClientIdentity +If set, specifies the GSSAPI client identity that ssh should use when +connecting to the server. The default is unset, which means that the default +identity will be used. +.It Cm GSSAPIServerIdentity +If set, specifies the GSSAPI server identity that ssh should expect when +connecting to the server. The default is unset, which means that the +expected GSSAPI server identity will be determined from the target +hostname. .It Cm GSSAPIDelegateCredentials Forward (delegate) credentials to the server. The default is .Dq no . +.It Cm GSSAPIRenewalForcesRekey +If set to +.Dq yes +then renewal of the client's GSSAPI credentials will force the rekeying of the +ssh connection. With a compatible server, this can delegate the renewed +credentials to a session on the server. +The default is +.Dq no . +.It Cm GSSAPITrustDns +Set to +.Dq yes +to indicate that the DNS is trusted to securely canonicalize +the name of the host being connected to. If +.Dq no , +the hostname entered on the +command line will be passed untouched to the GSSAPI library. +The default is +.Dq no . .It Cm HashKnownHosts Indicates that .Xr ssh 1 diff --git a/sshconnect2.c b/sshconnect2.c index fae8b0f2c..34b9d304e 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -162,6 +162,11 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) struct kex *kex; int r; +#ifdef GSSAPI + char *orig = NULL, *gss = NULL; + char *gss_host = NULL; +#endif + xxx_host = host; xxx_hostaddr = hostaddr; @@ -192,6 +197,36 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) order_hostkeyalgs(host, hostaddr, port)); } +#ifdef GSSAPI + if (options.gss_keyex) { + /* Add the GSSAPI mechanisms currently supported on this + * client to the key exchange algorithm proposal */ + orig = myproposal[PROPOSAL_KEX_ALGS]; + + if (options.gss_server_identity) + gss_host = xstrdup(options.gss_server_identity); + else if (options.gss_trust_dns) + gss_host = remote_hostname(active_state); + else + gss_host = xstrdup(host); + + gss = ssh_gssapi_client_mechanisms(gss_host, + options.gss_client_identity); + if (gss) { + debug("Offering GSSAPI proposal: %s", gss); + xasprintf(&myproposal[PROPOSAL_KEX_ALGS], + "%s,%s", gss, orig); + + /* If we've got GSSAPI algorithms, then we also + * support the 'null' hostkey, as a last resort */ + orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; + xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS], + "%s,null", orig); + free(gss); + } + } +#endif + if (options.rekey_limit || options.rekey_interval) packet_set_rekey_limits((u_int32_t)options.rekey_limit, (time_t)options.rekey_interval); @@ -213,10 +248,26 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) # endif #endif kex->kex[KEX_C25519_SHA256] = kexc25519_client; +#ifdef GSSAPI + if (options.gss_keyex) { + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client; + kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_client; + kex->kex[KEX_GSS_GEX_SHA1] = kexgss_client; + } +#endif kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; kex->verify_host_key=&verify_host_key_callback; +#ifdef GSSAPI + if (options.gss_keyex) { + kex->gss_deleg_creds = options.gss_deleg_creds; + kex->gss_trust_dns = options.gss_trust_dns; + kex->gss_client = options.gss_client_identity; + kex->gss_host = gss_host; + } +#endif + dispatch_run(DISPATCH_BLOCK, &kex->done, active_state); /* remove ext-info from the KEX proposals for rekeying */ @@ -311,6 +362,7 @@ int input_gssapi_token(int type, u_int32_t, void *); int input_gssapi_hash(int type, u_int32_t, void *); int input_gssapi_error(int, u_int32_t, void *); int input_gssapi_errtok(int, u_int32_t, void *); +int userauth_gsskeyex(Authctxt *authctxt); #endif void userauth(Authctxt *, char *); @@ -326,6 +378,11 @@ static char *authmethods_get(void); Authmethod authmethods[] = { #ifdef GSSAPI + {"gssapi-keyex", + userauth_gsskeyex, + NULL, + &options.gss_authentication, + NULL}, {"gssapi-with-mic", userauth_gssapi, NULL, @@ -650,25 +707,40 @@ userauth_gssapi(Authctxt *authctxt) static u_int mech = 0; OM_uint32 min; int ok = 0; + char *gss_host; + + if (options.gss_server_identity) + gss_host = xstrdup(options.gss_server_identity); + else if (options.gss_trust_dns) + gss_host = remote_hostname(active_state); + else + gss_host = xstrdup(authctxt->host); /* Try one GSSAPI method at a time, rather than sending them all at * once. */ if (gss_supported == NULL) - gss_indicate_mechs(&min, &gss_supported); + if (GSS_ERROR(gss_indicate_mechs(&min, &gss_supported))) { + gss_supported = NULL; + free(gss_host); + return 0; + } /* Check to see if the mechanism is usable before we offer it */ while (mech < gss_supported->count && !ok) { /* My DER encoding requires length<128 */ if (gss_supported->elements[mech].length < 128 && ssh_gssapi_check_mechanism(&gssctxt, - &gss_supported->elements[mech], authctxt->host)) { + &gss_supported->elements[mech], gss_host, + options.gss_client_identity)) { ok = 1; /* Mechanism works */ } else { mech++; } } + free(gss_host); + if (!ok) return 0; @@ -759,8 +831,8 @@ input_gssapi_response(int type, u_int32_t plen, void *ctxt) { Authctxt *authctxt = ctxt; Gssctxt *gssctxt; - int oidlen; - char *oidv; + u_int oidlen; + u_char *oidv; if (authctxt == NULL) fatal("input_gssapi_response: no authentication context"); @@ -873,6 +945,48 @@ input_gssapi_error(int type, u_int32_t plen, void *ctxt) free(lang); return 0; } + +int +userauth_gsskeyex(Authctxt *authctxt) +{ + Buffer b; + gss_buffer_desc gssbuf; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER; + OM_uint32 ms; + + static int attempt = 0; + if (attempt++ >= 1) + return (0); + + if (gss_kex_context == NULL) { + debug("No valid Key exchange context"); + return (0); + } + + ssh_gssapi_buildmic(&b, authctxt->server_user, authctxt->service, + "gssapi-keyex"); + + gssbuf.value = buffer_ptr(&b); + gssbuf.length = buffer_len(&b); + + if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) { + buffer_free(&b); + return (0); + } + + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(authctxt->server_user); + packet_put_cstring(authctxt->service); + packet_put_cstring(authctxt->method->name); + packet_put_string(mic.value, mic.length); + packet_send(); + + buffer_free(&b); + gss_release_buffer(&ms, &mic); + + return (1); +} + #endif /* GSSAPI */ int diff --git a/sshd.c b/sshd.c index 799c7711f..ebb88c776 100644 --- a/sshd.c +++ b/sshd.c @@ -125,6 +125,10 @@ #include "version.h" #include "ssherr.h" +#ifdef USE_SECURITY_SESSION_API +#include +#endif + #ifndef O_NOCTTY #define O_NOCTTY 0 #endif @@ -1892,10 +1896,13 @@ main(int ac, char **av) logit("Disabling protocol version 1. Could not load host key"); options.protocol &= ~SSH_PROTO_1; } +#ifndef GSSAPI + /* The GSSAPI key exchange can run without a host key */ if ((options.protocol & SSH_PROTO_2) && !sensitive_data.have_ssh2_key) { logit("Disabling protocol version 2. Could not load host key"); options.protocol &= ~SSH_PROTO_2; } +#endif if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) { logit("sshd: no hostkeys available -- exiting."); exit(1); @@ -2207,6 +2214,60 @@ main(int ac, char **av) remote_ip, remote_port, laddr, ssh_local_port(ssh)); free(laddr); +#ifdef USE_SECURITY_SESSION_API + /* + * Create a new security session for use by the new user login if + * the current session is the root session or we are not launched + * by inetd (eg: debugging mode or server mode). We do not + * necessarily need to create a session if we are launched from + * inetd because Panther xinetd will create a session for us. + * + * The only case where this logic will fail is if there is an + * inetd running in a non-root session which is not creating + * new sessions for us. Then all the users will end up in the + * same session (bad). + * + * When the client exits, the session will be destroyed for us + * automatically. + * + * We must create the session before any credentials are stored + * (including AFS pags, which happens a few lines below). + */ + { + OSStatus err = 0; + SecuritySessionId sid = 0; + SessionAttributeBits sattrs = 0; + + err = SessionGetInfo(callerSecuritySession, &sid, &sattrs); + if (err) + error("SessionGetInfo() failed with error %.8X", + (unsigned) err); + else + debug("Current Session ID is %.8X / Session Attributes are %.8X", + (unsigned) sid, (unsigned) sattrs); + + if (inetd_flag && !(sattrs & sessionIsRoot)) + debug("Running in inetd mode in a non-root session... " + "assuming inetd created the session for us."); + else { + debug("Creating new security session..."); + err = SessionCreate(0, sessionHasTTY | sessionIsRemote); + if (err) + error("SessionCreate() failed with error %.8X", + (unsigned) err); + + err = SessionGetInfo(callerSecuritySession, &sid, + &sattrs); + if (err) + error("SessionGetInfo() failed with error %.8X", + (unsigned) err); + else + debug("New Session ID is %.8X / Session Attributes are %.8X", + (unsigned) sid, (unsigned) sattrs); + } + } +#endif + /* * We don't want to listen forever unless the other side * successfully authenticates itself. So we set up an alarm which is @@ -2631,6 +2692,48 @@ do_ssh2_kex(void) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = compat_pkalg_proposal( list_hostkey_types()); +#ifdef GSSAPI + { + char *orig; + char *gss = NULL; + char *newstr = NULL; + orig = myproposal[PROPOSAL_KEX_ALGS]; + + /* + * If we don't have a host key, then there's no point advertising + * the other key exchange algorithms + */ + + if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0) + orig = NULL; + + if (options.gss_keyex) + gss = ssh_gssapi_server_mechanisms(); + else + gss = NULL; + + if (gss && orig) + xasprintf(&newstr, "%s,%s", gss, orig); + else if (gss) + newstr = gss; + else if (orig) + newstr = orig; + + /* + * If we've got GSSAPI mechanisms, then we've got the 'null' host + * key alg, but we can't tell people about it unless its the only + * host key algorithm we support + */ + if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = "null"; + + if (newstr) + myproposal[PROPOSAL_KEX_ALGS] = newstr; + else + fatal("No supported key exchange algorithms"); + } +#endif + /* start key exchange */ if ((r = kex_setup(active_state, myproposal)) != 0) fatal("kex_setup: %s", ssh_err(r)); @@ -2648,6 +2751,13 @@ do_ssh2_kex(void) # endif #endif kex->kex[KEX_C25519_SHA256] = kexc25519_server; +#ifdef GSSAPI + if (options.gss_keyex) { + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; + kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; + kex->kex[KEX_GSS_GEX_SHA1] = kexgss_server; + } +#endif kex->server = 1; kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; diff --git a/sshd_config b/sshd_config index 75ae8e739..3fe3e017e 100644 --- a/sshd_config +++ b/sshd_config @@ -83,6 +83,8 @@ AuthorizedKeysFile .ssh/authorized_keys # GSSAPI options #GSSAPIAuthentication no #GSSAPICleanupCredentials yes +#GSSAPIStrictAcceptorCheck yes +#GSSAPIKeyExchange no # Set this to 'yes' to enable PAM authentication, account processing, # and session processing. If this is enabled, PAM authentication will diff --git a/sshd_config.5 b/sshd_config.5 index 1bc26ec4d..3b4cba9ae 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -632,6 +632,11 @@ The default is Specifies whether user authentication based on GSSAPI is allowed. The default is .Dq no . +.It Cm GSSAPIKeyExchange +Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange +doesn't rely on ssh keys to verify host identity. +The default is +.Dq no . .It Cm GSSAPICleanupCredentials Specifies whether to automatically destroy the user's credentials cache on logout. @@ -652,6 +657,11 @@ machine's default store. This facility is provided to assist with operation on multi homed machines. The default is .Dq yes . +.It Cm GSSAPIStoreCredentialsOnRekey +Controls whether the user's GSSAPI credentials should be updated following a +successful connection rekeying. This option can be used to accepted renewed +or updated credentials from a compatible client. The default is +.Dq no . .It Cm HostbasedAcceptedKeyTypes Specifies the key types that will be accepted for hostbased authentication as a comma-separated pattern list. diff --git a/sshkey.c b/sshkey.c index c9f04cd67..558bbbef6 100644 --- a/sshkey.c +++ b/sshkey.c @@ -115,6 +115,7 @@ static const struct keytype keytypes[] = { # endif /* OPENSSL_HAS_NISTP521 */ # endif /* OPENSSL_HAS_ECC */ #endif /* WITH_OPENSSL */ + { "null", "null", KEY_NULL, 0, 0, 0 }, { NULL, NULL, -1, -1, 0, 0 } }; @@ -203,7 +204,7 @@ key_alg_list(int certs_only, int plain_only) const struct keytype *kt; for (kt = keytypes; kt->type != -1; kt++) { - if (kt->name == NULL || kt->sigonly) + if (kt->name == NULL || kt->sigonly || kt->type == KEY_NULL) continue; if ((certs_only && !kt->cert) || (plain_only && kt->cert)) continue; diff --git a/sshkey.h b/sshkey.h index 8c3d866bf..e0caa37a7 100644 --- a/sshkey.h +++ b/sshkey.h @@ -62,6 +62,7 @@ enum sshkey_types { KEY_DSA_CERT, KEY_ECDSA_CERT, KEY_ED25519_CERT, + KEY_NULL, KEY_UNSPEC }; -- cgit v1.2.3 From 67c34f23edbcd0f39ebc2aadae9d33950bd1f98a Mon Sep 17 00:00:00 2001 From: Richard Kettlewell Date: Sun, 9 Feb 2014 16:09:52 +0000 Subject: Various keepalive extensions Add compatibility aliases for ProtocolKeepAlives and SetupTimeOut, supported in previous versions of Debian's OpenSSH package but since superseded by ServerAliveInterval. (We're probably stuck with this bit for compatibility.) In batch mode, default ServerAliveInterval to five minutes. Adjust documentation to match and to give some more advice on use of keepalives. Author: Ian Jackson Author: Matthew Vernon Author: Colin Watson Last-Update: 2015-11-29 Patch-Name: keepalive-extensions.patch --- readconf.c | 14 ++++++++++++-- ssh_config.5 | 21 +++++++++++++++++++-- sshd_config.5 | 3 +++ 3 files changed, 34 insertions(+), 4 deletions(-) (limited to 'ssh_config.5') diff --git a/readconf.c b/readconf.c index c0b7822e1..3a6c67ba5 100644 --- a/readconf.c +++ b/readconf.c @@ -173,6 +173,7 @@ typedef enum { oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes, oPubkeyAcceptedKeyTypes, oProxyJump, + oProtocolKeepAlives, oSetupTimeOut, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; @@ -308,6 +309,8 @@ static struct { { "pubkeyacceptedkeytypes", oPubkeyAcceptedKeyTypes }, { "ignoreunknown", oIgnoreUnknown }, { "proxyjump", oProxyJump }, + { "protocolkeepalives", oProtocolKeepAlives }, + { "setuptimeout", oSetupTimeOut }, { NULL, oBadOption } }; @@ -1403,6 +1406,8 @@ parse_keytypes: goto parse_flag; case oServerAliveInterval: + case oProtocolKeepAlives: /* Debian-specific compatibility alias */ + case oSetupTimeOut: /* Debian-specific compatibility alias */ intptr = &options->server_alive_interval; goto parse_time; @@ -2048,8 +2053,13 @@ fill_default_options(Options * options) options->rekey_interval = 0; if (options->verify_host_key_dns == -1) options->verify_host_key_dns = 0; - if (options->server_alive_interval == -1) - options->server_alive_interval = 0; + if (options->server_alive_interval == -1) { + /* in batch mode, default is 5mins */ + if (options->batch_mode == 1) + options->server_alive_interval = 300; + else + options->server_alive_interval = 0; + } if (options->server_alive_count_max == -1) options->server_alive_count_max = 3; if (options->control_master == -1) diff --git a/ssh_config.5 b/ssh_config.5 index 707d0e1c3..efc265ab7 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -268,8 +268,12 @@ The default is If set to .Dq yes , passphrase/password querying will be disabled. +In addition, the +.Cm ServerAliveInterval +option will be set to 300 seconds by default. This option is useful in scripts and other batch jobs where no user -is present to supply the password. +is present to supply the password, +and where it is desirable to detect a broken network swiftly. The argument must be .Dq yes or @@ -1624,7 +1628,14 @@ from the server, will send a message through the encrypted channel to request a response from the server. The default -is 0, indicating that these messages will not be sent to the server. +is 0, indicating that these messages will not be sent to the server, +or 300 if the +.Cm BatchMode +option is set. +.Cm ProtocolKeepAlives +and +.Cm SetupTimeOut +are Debian-specific compatibility aliases for this option. .It Cm StreamLocalBindMask Sets the octal file creation mode mask .Pq umask @@ -1690,6 +1701,12 @@ Specifies whether the system should send TCP keepalive messages to the other side. If they are sent, death of the connection or crash of one of the machines will be properly noticed. +This option only uses TCP keepalives (as opposed to using ssh level +keepalives), so takes a long time to notice when the connection dies. +As such, you probably want +the +.Cm ServerAliveInterval +option as well. However, this means that connections will die if the route is down temporarily, and some people find it annoying. diff --git a/sshd_config.5 b/sshd_config.5 index 3b4cba9ae..e05cdbef5 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -1530,6 +1530,9 @@ This avoids infinitely hanging sessions. .Pp To disable TCP keepalive messages, the value should be set to .Dq no . +.Pp +This option was formerly called +.Cm KeepAlive . .It Cm TrustedUserCAKeys Specifies a file containing public keys of certificate authorities that are trusted to sign user certificates for authentication, or -- cgit v1.2.3 From 563974a78e937c4844e1198b5f6d79b8b2b5c600 Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Sun, 9 Feb 2014 16:09:58 +0000 Subject: Allow harmless group-writability Allow secure files (~/.ssh/config, ~/.ssh/authorized_keys, etc.) to be group-writable, provided that the group in question contains only the file's owner. Rejected upstream for IMO incorrect reasons (e.g. a misunderstanding about the contents of gr->gr_mem). Given that per-user groups and umask 002 are the default setup in Debian (for good reasons - this makes operating in setgid directories with other groups much easier), we need to permit this by default. Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1060 Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=314347 Last-Update: 2013-09-14 Patch-Name: user-group-modes.patch --- auth-rhosts.c | 6 ++---- auth.c | 9 +++----- misc.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- misc.h | 2 ++ platform.c | 16 -------------- readconf.c | 3 +-- ssh.1 | 2 ++ ssh_config.5 | 2 ++ 8 files changed, 80 insertions(+), 29 deletions(-) (limited to 'ssh_config.5') diff --git a/auth-rhosts.c b/auth-rhosts.c index 0ef344712..c17c13cc1 100644 --- a/auth-rhosts.c +++ b/auth-rhosts.c @@ -273,8 +273,7 @@ auth_rhosts2_raw(struct passwd *pw, const char *client_user, const char *hostnam return 0; } if (options.strict_modes && - ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || - (st.st_mode & 022) != 0)) { + !secure_permissions(&st, pw->pw_uid)) { logit("Rhosts authentication refused for %.100s: " "bad ownership or modes for home directory.", pw->pw_name); auth_debug_add("Rhosts authentication refused for %.100s: " @@ -300,8 +299,7 @@ auth_rhosts2_raw(struct passwd *pw, const char *client_user, const char *hostnam * allowing access to their account by anyone. */ if (options.strict_modes && - ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || - (st.st_mode & 022) != 0)) { + !secure_permissions(&st, pw->pw_uid)) { logit("Rhosts authentication refused for %.100s: bad modes for %.200s", pw->pw_name, buf); auth_debug_add("Bad file modes for %.200s", buf); diff --git a/auth.c b/auth.c index f56dcc6cf..3f8b348fc 100644 --- a/auth.c +++ b/auth.c @@ -435,8 +435,7 @@ check_key_in_hostfiles(struct passwd *pw, Key *key, const char *host, user_hostfile = tilde_expand_filename(userfile, pw->pw_uid); if (options.strict_modes && (stat(user_hostfile, &st) == 0) && - ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || - (st.st_mode & 022) != 0)) { + !secure_permissions(&st, pw->pw_uid)) { logit("Authentication refused for %.100s: " "bad owner or modes for %.200s", pw->pw_name, user_hostfile); @@ -498,8 +497,7 @@ auth_secure_path(const char *name, struct stat *stp, const char *pw_dir, snprintf(err, errlen, "%s is not a regular file", buf); return -1; } - if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || - (stp->st_mode & 022) != 0) { + if (!secure_permissions(stp, uid)) { snprintf(err, errlen, "bad ownership or modes for file %s", buf); return -1; @@ -514,8 +512,7 @@ auth_secure_path(const char *name, struct stat *stp, const char *pw_dir, strlcpy(buf, cp, sizeof(buf)); if (stat(buf, &st) < 0 || - (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || - (st.st_mode & 022) != 0) { + !secure_permissions(&st, uid)) { snprintf(err, errlen, "bad ownership or modes for directory %s", buf); return -1; diff --git a/misc.c b/misc.c index 9421b4d39..68efb2b38 100644 --- a/misc.c +++ b/misc.c @@ -51,8 +51,9 @@ #include #ifdef HAVE_PATHS_H # include -#include #endif +#include +#include #ifdef SSH_TUN_OPENBSD #include #endif @@ -61,6 +62,7 @@ #include "misc.h" #include "log.h" #include "ssh.h" +#include "platform.h" /* remove newline at end of string */ char * @@ -708,6 +710,71 @@ read_keyfile_line(FILE *f, const char *filename, char *buf, size_t bufsz, return -1; } +/* + * return 1 if the specified uid is a uid that may own a system directory + * otherwise 0. + */ +int +platform_sys_dir_uid(uid_t uid) +{ + if (uid == 0) + return 1; +#ifdef PLATFORM_SYS_DIR_UID + if (uid == PLATFORM_SYS_DIR_UID) + return 1; +#endif + return 0; +} + +int +secure_permissions(struct stat *st, uid_t uid) +{ + if (!platform_sys_dir_uid(st->st_uid) && st->st_uid != uid) + return 0; + if ((st->st_mode & 002) != 0) + return 0; + if ((st->st_mode & 020) != 0) { + /* If the file is group-writable, the group in question must + * have exactly one member, namely the file's owner. + * (Zero-member groups are typically used by setgid + * binaries, and are unlikely to be suitable.) + */ + struct passwd *pw; + struct group *gr; + int members = 0; + + gr = getgrgid(st->st_gid); + if (!gr) + return 0; + + /* Check primary group memberships. */ + while ((pw = getpwent()) != NULL) { + if (pw->pw_gid == gr->gr_gid) { + ++members; + if (pw->pw_uid != uid) + return 0; + } + } + endpwent(); + + pw = getpwuid(st->st_uid); + if (!pw) + return 0; + + /* Check supplementary group memberships. */ + if (gr->gr_mem[0]) { + ++members; + if (strcmp(pw->pw_name, gr->gr_mem[0]) || + gr->gr_mem[1]) + return 0; + } + + if (!members) + return 0; + } + return 1; +} + int tun_open(int tun, int mode) { diff --git a/misc.h b/misc.h index 7c76a6a72..42cd95e42 100644 --- a/misc.h +++ b/misc.h @@ -139,4 +139,6 @@ char *read_passphrase(const char *, int); int ask_permission(const char *, ...) __attribute__((format(printf, 1, 2))); int read_keyfile_line(FILE *, const char *, char *, size_t, u_long *); +int secure_permissions(struct stat *st, uid_t uid); + #endif /* _MISC_H */ diff --git a/platform.c b/platform.c index 483170680..2ce4dbf81 100644 --- a/platform.c +++ b/platform.c @@ -199,19 +199,3 @@ platform_krb5_get_principal_name(const char *pw_name) return NULL; #endif } - -/* - * return 1 if the specified uid is a uid that may own a system directory - * otherwise 0. - */ -int -platform_sys_dir_uid(uid_t uid) -{ - if (uid == 0) - return 1; -#ifdef PLATFORM_SYS_DIR_UID - if (uid == PLATFORM_SYS_DIR_UID) - return 1; -#endif - return 0; -} diff --git a/readconf.c b/readconf.c index 3a6c67ba5..f6b4c8f1e 100644 --- a/readconf.c +++ b/readconf.c @@ -1753,8 +1753,7 @@ read_config_file_depth(const char *filename, struct passwd *pw, if (fstat(fileno(f), &sb) == -1) fatal("fstat %s: %s", filename, strerror(errno)); - if (((sb.st_uid != 0 && sb.st_uid != getuid()) || - (sb.st_mode & 022) != 0)) + if (!secure_permissions(&sb, getuid())) fatal("Bad owner or permissions on %s", filename); } diff --git a/ssh.1 b/ssh.1 index 4011c65aa..feef81a54 100644 --- a/ssh.1 +++ b/ssh.1 @@ -1484,6 +1484,8 @@ The file format and configuration options are described in .Xr ssh_config 5 . Because of the potential for abuse, this file must have strict permissions: read/write for the user, and not writable by others. +It may be group-writable provided that the group in question contains only +the user. .Pp .It Pa ~/.ssh/environment Contains additional definitions for environment variables; see diff --git a/ssh_config.5 b/ssh_config.5 index efc265ab7..5dd26bcaa 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -1903,6 +1903,8 @@ The format of this file is described above. This file is used by the SSH client. Because of the potential for abuse, this file must have strict permissions: read/write for the user, and not accessible by others. +It may be group-writable provided that the group in question contains only +the user. .It Pa /etc/ssh/ssh_config Systemwide configuration file. This file provides defaults for those -- cgit v1.2.3 From bfea780bba64294541d98efcc26b01392ff64c60 Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Sun, 9 Feb 2014 16:10:11 +0000 Subject: Document that HashKnownHosts may break tab-completion Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1727 Bug-Debian: http://bugs.debian.org/430154 Last-Update: 2013-09-14 Patch-Name: doc-hash-tab-completion.patch --- ssh_config.5 | 3 +++ 1 file changed, 3 insertions(+) (limited to 'ssh_config.5') diff --git a/ssh_config.5 b/ssh_config.5 index 5dd26bcaa..30c97a9b9 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -883,6 +883,9 @@ Note that existing names and addresses in known hosts files will not be converted automatically, but may be manually hashed using .Xr ssh-keygen 1 . +Use of this option may break facilities such as tab-completion that rely +on being able to read unhashed host names from +.Pa ~/.ssh/known_hosts . .It Cm HostbasedAuthentication Specifies whether to try rhosts based authentication with public key authentication. -- cgit v1.2.3 From 4c914ccd85bbf391c4dc61b85e3c178fef465e3f Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Sun, 9 Feb 2014 16:10:18 +0000 Subject: Various Debian-specific configuration changes ssh: Enable ForwardX11Trusted, returning to earlier semantics which cause fewer problems with existing setups (http://bugs.debian.org/237021). ssh: Set 'SendEnv LANG LC_*' by default (http://bugs.debian.org/264024). ssh: Enable HashKnownHosts by default to try to limit the spread of ssh worms. ssh: Enable GSSAPIAuthentication and disable GSSAPIDelegateCredentials by default. Document all of this, along with several sshd defaults set in debian/openssh-server.postinst. Author: Russ Allbery Forwarded: not-needed Last-Update: 2015-12-07 Patch-Name: debian-config.patch --- readconf.c | 2 +- ssh.1 | 21 +++++++++++++++++++++ ssh_config | 7 ++++++- ssh_config.5 | 19 ++++++++++++++++++- sshd_config | 2 +- sshd_config.5 | 25 +++++++++++++++++++++++++ 6 files changed, 72 insertions(+), 4 deletions(-) (limited to 'ssh_config.5') diff --git a/readconf.c b/readconf.c index f6b4c8f1e..5cd51f3e6 100644 --- a/readconf.c +++ b/readconf.c @@ -1928,7 +1928,7 @@ fill_default_options(Options * options) if (options->forward_x11 == -1) options->forward_x11 = 0; if (options->forward_x11_trusted == -1) - options->forward_x11_trusted = 0; + options->forward_x11_trusted = 1; if (options->forward_x11_timeout == -1) options->forward_x11_timeout = 1200; /* diff --git a/ssh.1 b/ssh.1 index 22e56a7b9..6aa57c462 100644 --- a/ssh.1 +++ b/ssh.1 @@ -785,6 +785,16 @@ directive in .Xr ssh_config 5 for more information. .Pp +(Debian-specific: X11 forwarding is not subjected to X11 SECURITY extension +restrictions by default, because too many programs currently crash in this +mode. +Set the +.Cm ForwardX11Trusted +option to +.Dq no +to restore the upstream behaviour. +This may change in future depending on client-side improvements.) +.Pp .It Fl x Disables X11 forwarding. .Pp @@ -793,6 +803,17 @@ Enables trusted X11 forwarding. Trusted X11 forwardings are not subjected to the X11 SECURITY extension controls. .Pp +(Debian-specific: This option does nothing in the default configuration: it +is equivalent to +.Dq Cm ForwardX11Trusted No yes , +which is the default as described above. +Set the +.Cm ForwardX11Trusted +option to +.Dq no +to restore the upstream behaviour. +This may change in future depending on client-side improvements.) +.Pp .It Fl y Send log information using the .Xr syslog 3 diff --git a/ssh_config b/ssh_config index 4e879cd20..5190b06b1 100644 --- a/ssh_config +++ b/ssh_config @@ -17,9 +17,10 @@ # list of available options, their meanings and defaults, please see the # ssh_config(5) man page. -# Host * +Host * # ForwardAgent no # ForwardX11 no +# ForwardX11Trusted yes # RhostsRSAAuthentication no # RSAAuthentication yes # PasswordAuthentication yes @@ -50,3 +51,7 @@ # VisualHostKey no # ProxyCommand ssh -q -W %h:%p gateway.example.com # RekeyLimit 1G 1h + SendEnv LANG LC_* + HashKnownHosts yes + GSSAPIAuthentication yes + GSSAPIDelegateCredentials no diff --git a/ssh_config.5 b/ssh_config.5 index 30c97a9b9..c96725862 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -74,6 +74,22 @@ Since the first obtained value for each parameter is used, more host-specific declarations should be given near the beginning of the file, and general defaults at the end. .Pp +Note that the Debian +.Ic openssh-client +package sets several options as standard in +.Pa /etc/ssh/ssh_config +which are not the default in +.Xr ssh 1 : +.Pp +.Bl -bullet -offset indent -compact +.It +.Cm SendEnv No LANG LC_* +.It +.Cm HashKnownHosts No yes +.It +.Cm GSSAPIAuthentication No yes +.El +.Pp The configuration file has the following format: .Pp Empty lines and lines starting with @@ -799,7 +815,8 @@ token used for the session will be set to expire after 20 minutes. Remote clients will be refused access after this time. .Pp The default is -.Dq no . +.Dq yes +(Debian-specific). .Pp See the X11 SECURITY extension specification for full details on the restrictions imposed on untrusted clients. diff --git a/sshd_config b/sshd_config index 3fe3e017e..ec8ff8f19 100644 --- a/sshd_config +++ b/sshd_config @@ -124,7 +124,7 @@ AuthorizedKeysFile .ssh/authorized_keys #Banner none # override default of no subsystems -Subsystem sftp /usr/libexec/sftp-server +Subsystem sftp /usr/lib/openssh/sftp-server # Example of overriding settings on a per-user basis #Match User anoncvs diff --git a/sshd_config.5 b/sshd_config.5 index b2b349e1b..79f2d611f 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -57,6 +57,31 @@ Arguments may optionally be enclosed in double quotes .Pq \&" in order to represent arguments containing spaces. .Pp +Note that the Debian +.Ic openssh-server +package sets several options as standard in +.Pa /etc/ssh/sshd_config +which are not the default in +.Xr sshd 8 . +The exact list depends on whether the package was installed fresh or +upgraded from various possible previous versions, but includes at least the +following: +.Pp +.Bl -bullet -offset indent -compact +.It +.Cm ChallengeResponseAuthentication No no +.It +.Cm X11Forwarding No yes +.It +.Cm PrintMotd No no +.It +.Cm AcceptEnv No LANG LC_* +.It +.Cm Subsystem No sftp /usr/lib/openssh/sftp-server +.It +.Cm UsePAM No yes +.El +.Pp The possible keywords and their meanings are as follows (note that keywords are case-insensitive and arguments are case-sensitive): -- cgit v1.2.3