Feature patch: key name/patterns in authorized_keys2 [e.g., Kerberos principal names] (was Re: Adding 'name' key types)

Nicolas Williams Nicolas.Williams at ubsw.com
Wed Jul 4 04:57:39 EST 2001


Ok, to follow up to myself, here's a much more featureful, complete and
tested patch.

This patch (to OpenSSH 2.9p2) adds:

 - ssh-ext-named	key entry type for authorized_keys2 files
 - ssh-ext-name-pat	key entry type for authorized_keys2 files

 - deny-access		option for authorized_keys2 files

 - SSH_AUTH_EXT_NAME	environment variable added by sshd when
 - SSH_AUTH_EXT_NAME_TYPE	similar


Now you can have entries like this in you authorized_keys2 files:

ssh-ext-named:krb5 someuser at SOMEREALM
deny-access ssh-ext-named:krb5 joe/superroot at SOMEREALM
ssh-ext-name-pat:krb5 */superroot at SOMERALM

Double quotes can be used when key names contain whitespace.

So far I have only modified simon at sxw.org.uk's GSS-API patches for
OpenSSH to support the use of 'krb5' key names in authorized_keys2
files.

I really hope that this feature or a variation thereof will find its way
into OpenSSH. In conjunction with Kerberos (IV or V) it can be extremely
useful:

 - key management is simplified: key management is done at the KDC and
   there is no need to edit authorized_keys2 files all over to revoke
   keys!

 - authorized_keys2 is much more featureful than .klogin and .k5login
   are, regardless of Kerberos implementation source (KTH, Heimdal, MIT,
   SEAM, all implement pretty much the same all-or-nothing
   .klogin/.k5login functionality).

A similar patch of gss-serv.c:ssh_gssapi_gsi_userok() to support the
use of 'gsi' key names in authorized_keys2 would be trivial.

A similar patch to auth-krb4.c:auth_krb4() to support the use of 'krb4'
key names would be trivial, but I could not test such a patch.

A question, in my mind, is whether the krb4/gss:krb5/gss:gsi ssh_*userok()
code should require both, authorized_keys2 check *and* the underlying
mechanism userok() check to pass, or either, or what. My patch to
gss-serv.c:ssh_gssapi_krb5_userok() requires either check to pass.

Below you should find two versions of this patch, one against OpenSSH
2.9p2, the other against 2.9p2 + simon at sxw.org.uk's GSS-API patches.

NOTE: I did not strive too hard to keep to the code style of OpenSSH.
      Point me a the description of the OpenSSH code style and I'll
      modify my patch accordingly.

Files modified:

 - key.h
    - added KEY_NAME key type
    - added KEY_NAME_PAT key type
    - added name, name_len and name_type fields to the Key struct
    - added prototype for key_match()

 - key.c
    - added initialization/finalization of new Key fields to key_new()/key_free()
    - added named/pattern key type support to a variety of functions,
      including key_read() and key_write(), among others
    - added key_match() implementation

 - auth-options.h
    - added void auth_set_key_env(Key *) prototype

 - auth-options.c
    - added auth_set_key_env() implementation
    - modified auth_parse_options() to return (-1) when new deny-access
      option is encountered

 - auth-rsa.c
    - modified auth_parse_options() return value check according to the
      change made to auth_parse_options()

 - auth2.c
    - modified user_key_allowed() to:
       - try key_match() if key_equal() fails
       - check the result of auth_parse_options() for negative, 0, or
         positive values.
    - modified userauth_pubkey() to check for positive return value of
      user_key_allowed()

 - sshd.8
    - added documentation

 - gss-serv.c
    - modified ssh_gssapi_krb5_userok() to build a Key struct and 
      call user_key_allowed()

Nico

-DISCLAIMER: an automatically appended disclaimer may follow. By posting-
-to a public e-mail mailing list I hereby grant permission to distribute-
-and copy this message.-

********************************************************************************

Index: 2_9_p2.1/sshd.8
--- 2_9_p2.1/sshd.8 Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/h/28_sshd.8 1.1 644)
+++ 2_9_p2_w_named_keys.2/sshd.8 Tue, 03 Jul 2001 14:20:28 -0400 willian (OpenSSH/h/28_sshd.8 1.1.1.1 644)
@@ -852,7 +852,8 @@
 .Pa $HOME/.ssh/authorized_keys2
 file lists the DSA and RSA keys that are
 permitted for public key authentication (PubkeyAuthentication)
-in protocol version 2.
+in protocol version 2. It can also list key names or key patterns
+for external authentication systems, such as krb4, krb5, gsi, etc...
 .Pp
 Each line of the file contains one
 key (empty lines and lines starting with a
@@ -873,7 +874,19 @@
 For protocol version 2 the keytype is
 .Dq ssh-dss
 or
-.Dq ssh-rsa .
+.Dq ssh-rsa
+or
+.Dq ssh-ext-named:<keytype>
+or
+.Dq ssh-ext-name-pat:<keytype> .
+.Pp
+Named keys and key name patterns follow the latter two, in double
+quotes if they contain whitespace. Named key types may include:
+.Dq krb4 ,
+.Dq krb5
+and/or
+.Dq gsi ,
+depending on what features are compiled in to OpenSSH.
 .Pp
 Note that lines in this file are usually several hundred bytes long
 (because of the size of the RSA key modulus).
@@ -930,6 +943,10 @@
 Environment variables set this way
 override other default environment values.
 Multiple options of this type are permitted.
+.It Cm deny-access
+This option ends authorized_keys2 processing if the key matches. This
+option is only really useful with named key and named key pattern
+entries.
 .It Cm no-port-forwarding
 Forbids TCP/IP forwarding when this key is used for authentication.
 Any port forward requests by the client will return an error.
Index: 2_9_p2.1/key.h
--- 2_9_p2.1/key.h Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/j/7_key.h 1.1 644)
+++ 2_9_p2_w_named_keys.2/key.h Tue, 03 Jul 2001 13:57:30 -0400 willian (OpenSSH/j/7_key.h 1.1.1.1 644)
@@ -34,7 +34,9 @@
 	KEY_RSA1,
 	KEY_RSA,
 	KEY_DSA,
-	KEY_UNSPEC
+	KEY_UNSPEC,
+	KEY_NAME,
+	KEY_NAME_PAT
 };
 enum fp_type {
 	SSH_FP_SHA1,
@@ -48,12 +50,16 @@
 	int	type;
 	RSA	*rsa;
 	DSA	*dsa;
+	u_char	*name;
+	u_int	name_len;
+	char	*name_type;
 };
 
 Key	*key_new(int type);
 Key	*key_new_private(int type);
 void	key_free(Key *k);
 int	key_equal(Key *a, Key *b);
+int	key_match(Key *a, Key *b);
 char	*key_fingerprint(Key *k, enum fp_type dgst_type, enum fp_rep dgst_rep);
 char	*key_type(Key *k);
 int	key_write(Key *key, FILE *f);
Index: 2_9_p2.1/key.c
--- 2_9_p2.1/key.c Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/j/8_key.c 1.1 644)
+++ 2_9_p2_w_named_keys.2/key.c Tue, 03 Jul 2001 13:57:30 -0400 willian (OpenSSH/j/8_key.c 1.1.1.1 644)
@@ -56,6 +56,9 @@
 	k->type = type;
 	k->dsa = NULL;
 	k->rsa = NULL;
+	k->name = NULL;
+	k->name_len = 0;
+	k->name_type = NULL;
 	switch (k->type) {
 	case KEY_RSA1:
 	case KEY_RSA:
@@ -72,6 +75,8 @@
 		dsa->pub_key = BN_new();
 		k->dsa = dsa;
 		break;
+	case KEY_NAME:
+	case KEY_NAME_PAT:
 	case KEY_UNSPEC:
 		break;
 	default:
@@ -119,6 +124,14 @@
 			DSA_free(k->dsa);
 		k->dsa = NULL;
 		break;
+	case KEY_NAME:
+	case KEY_NAME_PAT:
+		if (k->name != NULL)
+			xfree(k->name);
+		k->name_len = 0;
+		if (k->name_type != NULL)
+			xfree(k->name_type);
+		break;
 	case KEY_UNSPEC:
 		break;
 	default:
@@ -130,8 +143,9 @@
 int
 key_equal(Key *a, Key *b)
 {
-	if (a == NULL || b == NULL || a->type != b->type)
+	if (a == NULL || b == NULL || a->type != b->type) {
 		return 0;
+	}
 	switch (a->type) {
 	case KEY_RSA1:
 	case KEY_RSA:
@@ -146,12 +160,67 @@
 		    BN_cmp(a->dsa->g, b->dsa->g) == 0 &&
 		    BN_cmp(a->dsa->pub_key, b->dsa->pub_key) == 0;
 		break;
+	case KEY_NAME:
+		if ((a->name_type == NULL && b->name_type == NULL) ||
+		    (a->name_type == b->name_type))
+			return (a->name_len == b->name_len) &&
+			    (memcmp(a->name, b->name, a->name_len) == 0);
+		if (a->name_type == NULL || b->name_type == NULL)
+			return 0;
+		if (strcmp(a->name_type, b->name_type) == 0)
+			return (a->name_len == b->name_len) &&
+			    (memcmp(a->name, b->name, a->name_len) == 0);
+		break;
+	case KEY_NAME_PAT:
+		return 0;
+		break;
 	default:
 		fatal("key_equal: bad key type %d", a->type);
 		break;
 	}
 	return 0;
 }
+int
+key_match(Key *a, Key *b)
+{
+	debug3("key_match: trying to match %x and %x", a, b);
+	if (a == NULL || b == NULL)
+		return 0;
+
+	debug3("key_match: trying to match key types %d and %d -- KEY_NAME_PAT == %d", a->type, b->type, KEY_NAME_PAT);
+	/* One key must be a name pattern, the other must be a name */
+	if (!(a->type == KEY_NAME_PAT && b->type == KEY_NAME) &&
+	    !(b->type == KEY_NAME_PAT && a->type == KEY_NAME))
+		return 0;
+	
+	/* Both keys must have name types, or both must not */
+	/* or one key must have '*' as its name type        */
+	if ((a->name_type == NULL && b->name_type != NULL) ||
+	    (b->name_type == NULL && a->name_type != NULL)) {
+
+		debug3("key_match: foo");
+		if (a->name_type != NULL && *(a->name_type) != '*')
+			return 0;
+		if (b->name_type != NULL && *(b->name_type) != '*')
+			return 0;
+	}
+
+	/* Name type "*" matches any name type */
+	/* Otherwise name types must match */
+	if ((a->name_type != NULL && strcmp(a->name_type, b->name_type) != 0) &&
+	    (*(a->name_type) != '*' || *(b->name_type) != '*')) {
+		debug3("key_match: a->name_type == %s", a->name_type ? a->name_type : "");
+		debug3("key_match: b->name_type == %s", b->name_type ? b->name_type : "");
+		return 0;
+	    }
+
+	debug3("key_match: trying to match %s WITH %s", a->name, b->name);
+	if (a->type == KEY_NAME_PAT)
+		return match_pattern(b->name, a->name);
+	else
+		return match_pattern(a->name, b->name);
+}
+
 
 u_char*
 key_fingerprint_raw(Key *k, enum fp_type dgst_type, size_t *dgst_raw_length)
@@ -160,7 +229,7 @@
 	EVP_MD_CTX ctx;
 	u_char *blob = NULL;
 	u_char *retval = NULL;
-	int len = 0;
+	u_int len = 0;
 	int nlen, elen;
 
 	*dgst_raw_length = 0;
@@ -363,11 +432,12 @@
 {
 	Key *k;
 	int success = -1;
-	char *cp, *space;
+	char *cp, *space, *name_type;
 	int len, n, type;
 	u_int bits;
-	u_char *blob;
+	u_char *blob = NULL;
 
+	name_type = NULL;
 	cp = *cpp;
 
 	switch(ret->type) {
@@ -390,6 +460,8 @@
 	case KEY_UNSPEC:
 	case KEY_RSA:
 	case KEY_DSA:
+	case KEY_NAME:
+	case KEY_NAME_PAT:
 		space = strchr(cp, ' ');
 		if (space == NULL) {
 			debug3("key_read: no space");
@@ -397,6 +469,17 @@
 		}
 		*space = '\0';
 		type = key_type_from_name(cp);
+		if ((type == KEY_NAME) || (type == KEY_NAME_PAT)) {
+			char * colon = NULL;
+			
+			colon = strchr(cp, ':');
+
+			debug3("key_read: handling named key or pattern (%d), %s, colon at %x", type, cp, colon);
+			if (colon != NULL && *(++colon) != '\0') {
+				name_type = xstrdup(colon);
+			} else
+				name_type == NULL;
+		}
 		*space = ' ';
 		if (type == KEY_UNSPEC) {
 			debug3("key_read: no key found");
@@ -410,30 +493,80 @@
 		if (ret->type == KEY_UNSPEC) {
 			ret->type = type;
 		} else if (ret->type != type) {
-			/* is a key, but different type */
-			debug3("key_read: type mismatch");
-			return 0;
+			if (! ((ret->type == KEY_NAME) &&
+			       type ==  KEY_NAME_PAT)) {
+				/* is a key, but different type */
+				debug3("key_read: type mismatch");
+				return 0;
+			}
+			ret->type = type;
 		}
-		len = 2*strlen(cp);
-		blob = xmalloc(len);
-		n = uudecode(cp, blob, len);
-		if (n < 0) {
-			error("key_read: uudecode %s failed", cp);
-			return -1;
+		debug3("key_read: here -- ret->type == %d", ret->type);
+		if ((ret->type == KEY_NAME) || (ret->type == KEY_NAME_PAT)) {
+			char *quote, *newline;
+			debug3("key_read: reading named key %s", cp);
+			if (cp == NULL || *cp == '\0')
+				return 0;
+			if (*cp == '"') {
+				quote = strchr(++cp, '"');
+				if (quote == NULL) {
+					debug3("key_read: missing quote");
+					return 0;
+				}
+				*quote = '\0';
+			}
+			newline = strchr(cp, '\n');
+			if (newline != NULL)
+				*newline = '\0';
+			debug3("key_read: reading named key %s", cp);
+			k = key_new(ret->type);
+			k->name = (unsigned char *) xstrdup(cp);
+			k->name_len = strlen(cp);
+			k->name_type = name_type;
+			if (newline !=NULL)
+				*newline = '\n';
+			if (quote !=NULL)
+				*quote = '"';
+			debug3("key_read: read named key %s", k->name_type);
+		} else {
+			len = 2*strlen(cp);
+			blob = xmalloc(len);
+			n = uudecode(cp, blob, len);
+			if (n < 0) {
+				error("key_read: uudecode %s failed", cp);
+				return -1;
+			}
+			debug3("key_read: reading uuencoded key %s", blob);
+			k = key_from_blob(blob, n);
 		}
-		k = key_from_blob(blob, n);
 		if (k == NULL) {
 			error("key_read: key_from_blob %s failed", cp);
 			return -1;
 		}
-		xfree(blob);
+		if (blob != NULL)
+			xfree(blob);
 		if (k->type != type) {
-			error("key_read: type mismatch: encoding error");
-			key_free(k);
-			return -1;
+			if (! ((ret->type == KEY_NAME) &&
+			       type ==  KEY_NAME_PAT)) {
+				error("key_read: type mismatch: encoding error");
+				key_free(k);
+				return -1;
+			}
 		}
 /*XXXX*/
-		if (ret->type == KEY_RSA) {
+		if ((ret->type == KEY_NAME) || (ret->type == KEY_NAME_PAT)) {
+			/*
+			if (ret->name != NULL)
+				xfree(ret->name);
+			 */
+			ret->name = k->name;
+			ret->name_len = k->name_len;
+			ret->name_type = k->name_type;
+			k->name = NULL;
+			k->name_type = NULL;
+			k->name_len = 0;
+			success = 1;
+		} else if (ret->type == KEY_RSA) {
 			if (ret->rsa != NULL)
 				RSA_free(ret->rsa);
 			ret->rsa = k->rsa;
@@ -487,7 +620,7 @@
 		}
 	} else if ((key->type == KEY_DSA && key->dsa != NULL) ||
 	    (key->type == KEY_RSA && key->rsa != NULL)) {
-		int len, n;
+		u_int len, n;
 		u_char *blob, *uu;
 		key_to_blob(key, &blob, &len);
 		uu = xmalloc(2*len);
@@ -498,6 +631,14 @@
 		}
 		xfree(blob);
 		xfree(uu);
+	} else if (key->type == KEY_NAME && key->name != NULL &&
+		   key->name_len) {
+
+		fprintf(f, "%s ", key_ssh_name(key));
+		if (key->name_type != NULL)
+			fprintf(f, ":%s", key->name_type);
+		else
+			fprintf(f, " \"%.*s\"", key->name, key->name_len);
 	}
 	return success;
 }
@@ -514,6 +655,12 @@
 	case KEY_DSA:
 		return "DSA";
 		break;
+	case KEY_NAME:
+		return "Named";
+		break;
+	case KEY_NAME_PAT:
+		return "Name_Pattern";
+		break;
 	}
 	return "unknown";
 }
@@ -527,6 +674,12 @@
 	case KEY_DSA:
 		return "ssh-dss";
 		break;
+	case KEY_NAME:
+		return "ssh-ext-named";
+		break;
+	case KEY_NAME_PAT:
+		return "ssh-ext-name-pat";
+		break;
 	}
 	return "ssh-unknown";
 }
@@ -604,6 +757,16 @@
 		BN_copy(n->rsa->n, k->rsa->n);
 		BN_copy(n->rsa->e, k->rsa->e);
 		break;
+	case KEY_NAME:
+	case KEY_NAME_PAT:
+		n = key_new(k->type);
+		n->name_len = k->name_len;
+		n->name = xmalloc(k->name_len);
+		memcpy(n->name, k->name, n->name_len);
+		if (k->name_type) {
+			n->name_type = xstrdup(k->name_type);
+		}
+		break;
 	default:
 		fatal("key_from_private: unknown type %d", k->type);
 		break;
@@ -624,7 +787,16 @@
 		return KEY_RSA;
 	} else if (strcmp(name, "ssh-dss") == 0){
 		return KEY_DSA;
+	} else if (strcmp(name, "ssh-ext-named") == 0){
+		return KEY_NAME;
+	} else if (strncmp(name, "ssh-ext-named:", strlen("ssh-ext-named:")) == 0){
+		return KEY_NAME;
+	} else if (strcmp(name, "ssh-ext-name-pat") == 0){
+		return KEY_NAME_PAT;
+	} else if (strncmp(name, "ssh-ext-name-pat:", strlen("ssh-ext-name-pat:")) == 0){
+		return KEY_NAME_PAT;
 	}
+
 	debug2("key_type_from_name: unknown key type '%s'", name);
 	return KEY_UNSPEC;
 }
Index: 2_9_p2.1/auth2.c
--- 2_9_p2.1/auth2.c Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/k/6_auth2.c 1.1 644)
+++ 2_9_p2_w_named_keys.2/auth2.c Tue, 03 Jul 2001 13:57:30 -0400 willian (OpenSSH/k/6_auth2.c 1.1.1.1 644)
@@ -491,7 +491,7 @@
 			buffer_dump(&b);
 #endif
 			/* test for correct signature */
-			if (user_key_allowed(authctxt->pw, key) &&
+			if (user_key_allowed(authctxt->pw, key) > 0 &&
 			    key_verify(key, sig, slen, buffer_ptr(&b), buffer_len(&b)) == 1)
 				authenticated = 1;
 			buffer_clear(&b);
@@ -508,7 +508,7 @@
 			 * if a user is not allowed to login. is this an
 			 * issue? -markus
 			 */
-			if (user_key_allowed(authctxt->pw, key)) {
+			if (user_key_allowed(authctxt->pw, key) > 0) {
 				packet_start(SSH2_MSG_USERAUTH_PK_OK);
 				packet_put_string(pkalg, alen);
 				packet_put_string(pkblob, blen);
@@ -768,19 +768,36 @@
 				continue;
 			}
 		}
-		if (key_equal(found, key) &&
-		    auth_parse_options(pw, options, file, linenum) == 1) {
-			found_key = 1;
-			debug("matching key found: file %s, line %ld",
-			    file, linenum);
+		if (key_equal(found, key)) {
+			found_key = auth_parse_options(pw, options, file, linenum);
+			if (found_key == 0)
+				continue;
+			break;
+		}
+		if (key_match(found, key)) {
+			found_key = auth_parse_options(pw, options, file, linenum);
+			if (found_key == 0)
+				continue;
+			/* Special treatment for key name patterns belongs here */
 			break;
 		}
 	}
+
+done:
 	restore_uid();
 	fclose(f);
 	key_free(found);
-	if (!found_key)
+	if (found_key > 0) {
+		debug("matching key found: file %s, line %ld",
+		    file, linenum);
+		auth_set_key_env(key);
+	}
+	if (found_key == 0)
 		debug2("key not found");
+	if (found_key < 0) {
+		debug("user_key_allowed: matching deny key found: "
+		      "file %s, line %ld", file, linenum);
+	}
 	return found_key;
 }
 
Index: 2_9_p2.1/auth-rsa.c
--- 2_9_p2.1/auth-rsa.c Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/k/15_auth-rsa.c 1.1 644)
+++ 2_9_p2_w_named_keys.2/auth-rsa.c Tue, 03 Jul 2001 13:57:30 -0400 willian (OpenSSH/k/15_auth-rsa.c 1.1.1.1 644)
@@ -259,7 +259,7 @@
 		 * If our options do not allow this key to be used,
 		 * do not send challenge.
 		 */
-		if (!auth_parse_options(pw, options, file, linenum))
+		if (auth_parse_options(pw, options, file, linenum) < 1)
 			continue;
 
 		/* Perform the challenge-response dialog for this key. */
Index: 2_9_p2.1/auth-options.h
--- 2_9_p2.1/auth-options.h Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/k/21_auth-optio 1.1 644)
+++ 2_9_p2_w_named_keys.2/auth-options.h Tue, 03 Jul 2001 13:57:30 -0400 willian (OpenSSH/k/21_auth-optio 1.1.1.1 644)
@@ -16,6 +16,8 @@
 #ifndef AUTH_OPTIONS_H
 #define AUTH_OPTIONS_H
 
+#include "key.h"
+
 /* Linked list of custom environment strings */
 struct envstring {
 	struct envstring *next;
@@ -37,6 +39,9 @@
 int
 auth_parse_options(struct passwd *pw, char *options, char *file,
     u_long linenum);
+
+void
+auth_set_key_env(Key *k);
 
 /* reset options flags */
 void	auth_clear_options(void);
Index: 2_9_p2.1/auth-options.c
--- 2_9_p2.1/auth-options.c Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/k/22_auth-optio 1.1 644)
+++ 2_9_p2_w_named_keys.2/auth-options.c Tue, 03 Jul 2001 13:57:30 -0400 willian (OpenSSH/k/22_auth-optio 1.1.1.1 644)
@@ -55,8 +55,43 @@
 	channel_clear_permitted_opens();
 }
 
+void auth_set_key_env(Key *k)
+{
+	struct envstring *new_env;
+	char *s;
+	int len;
+
+	if (k->type != KEY_NAME)
+		return;
+
+	len = strlen("SSH_AUTH_EXT_NAME=");
+	len += k->name_len + 1;
+	s = xmalloc(len);
+	snprintf(s, len, "SSH_AUTH_EXT_NAME=%.*s", k->name_len, k->name);
+	debug3("auth_set_key_env: Adding to the environment: %.*s", len, s);
+	new_env = xmalloc(sizeof(struct envstring));
+	new_env->s = s;
+	new_env->next = custom_environment;
+	custom_environment = new_env;
+
+	if (k->name_type == NULL)
+		return;
+
+	len = strlen("SSH_AUTH_EXT_NAME_TYPE=");
+	len += strlen(k->name_type) + 1;
+	s = xmalloc(len);
+	snprintf(s, len, "SSH_AUTH_EXT_NAME_TYPE=%s", k->name_type);
+
+	new_env = xmalloc(sizeof(struct envstring));
+	new_env->s = s;
+	new_env->next = custom_environment;
+	custom_environment = new_env;
+
+	return;
+}
+
 /*
- * return 1 if access is granted, 0 if not.
+ * return 1 if access is granted, 0 if not, -1 if access explicitly denied
  * side effect: sets key option flags
  */
 int
@@ -72,6 +107,12 @@
 		return 1;
 
 	while (*opts && *opts != ' ' && *opts != '\t') {
+		cp = "deny-access";
+		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
+			log("Authentication successful, but authorization denied");
+			packet_send_debug("Permission denied");
+			return -1;
+		}
 		cp = "no-port-forwarding";
 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
 			packet_send_debug("Port forwarding disabled.");

********************************************************************************

Index: 2_9_p2_w_gss_and_krb5.4/sshd.8
--- 2_9_p2_w_gss_and_krb5.4/sshd.8 Tue, 26 Jun 2001 16:27:13 -0400 willian (OpenSSH/h/28_sshd.8 1.2 644)
+++ 2_9_p2_w_gss_krb5_named_keys.5/sshd.8 Tue, 03 Jul 2001 14:20:01 -0400 willian (OpenSSH/h/28_sshd.8 1.3 644)
@@ -871,7 +871,8 @@
 .Pa $HOME/.ssh/authorized_keys2
 file lists the DSA and RSA keys that are
 permitted for public key authentication (PubkeyAuthentication)
-in protocol version 2.
+in protocol version 2. It can also list key names or key patterns
+for external authentication systems, such as krb4, krb5, gsi, etc...
 .Pp
 Each line of the file contains one
 key (empty lines and lines starting with a
@@ -892,7 +893,19 @@
 For protocol version 2 the keytype is
 .Dq ssh-dss
 or
-.Dq ssh-rsa .
+.Dq ssh-rsa
+or
+.Dq ssh-ext-named:<keytype>
+or
+.Dq ssh-ext-name-pat:<keytype> .
+.Pp
+Named keys and key name patterns follow the latter two, in double
+quotes if they contain whitespace. Named key types may include:
+.Dq krb4 ,
+.Dq krb5
+and/or
+.Dq gsi ,
+depending on what features are compiled in to OpenSSH.
 .Pp
 Note that lines in this file are usually several hundred bytes long
 (because of the size of the RSA key modulus).
@@ -949,6 +962,10 @@
 Environment variables set this way
 override other default environment values.
 Multiple options of this type are permitted.
+.It Cm deny-access
+This option ends authorized_keys2 processing if the key matches. This
+option is only really useful with named key and named key pattern
+entries.
 .It Cm no-port-forwarding
 Forbids TCP/IP forwarding when this key is used for authentication.
 Any port forward requests by the client will return an error.
Index: 2_9_p2_w_gss_and_krb5.4/key.h
--- 2_9_p2_w_gss_and_krb5.4/key.h Tue, 26 Jun 2001 16:27:13 -0400 willian (OpenSSH/j/7_key.h 1.2 644)
+++ 2_9_p2_w_gss_krb5_named_keys.5/key.h Tue, 03 Jul 2001 13:14:57 -0400 willian (OpenSSH/j/7_key.h 1.4 644)
@@ -35,7 +35,9 @@
 	KEY_RSA,
 	KEY_DSA,
 	KEY_NULL,
-	KEY_UNSPEC
+	KEY_UNSPEC,
+	KEY_NAME,
+	KEY_NAME_PAT
 };
 enum fp_type {
 	SSH_FP_SHA1,
@@ -49,12 +51,16 @@
 	int	type;
 	RSA	*rsa;
 	DSA	*dsa;
+	u_char	*name;
+	u_int	name_len;
+	char	*name_type;
 };
 
 Key	*key_new(int type);
 Key	*key_new_private(int type);
 void	key_free(Key *k);
 int	key_equal(Key *a, Key *b);
+int	key_match(Key *a, Key *b);
 char	*key_fingerprint(Key *k, enum fp_type dgst_type, enum fp_rep dgst_rep);
 char	*key_type(Key *k);
 int	key_write(Key *key, FILE *f);
Index: 2_9_p2_w_gss_and_krb5.4/key.c
--- 2_9_p2_w_gss_and_krb5.4/key.c Tue, 26 Jun 2001 16:27:13 -0400 willian (OpenSSH/j/8_key.c 1.2 644)
+++ 2_9_p2_w_gss_krb5_named_keys.5/key.c Tue, 03 Jul 2001 14:23:39 -0400 willian (OpenSSH/j/8_key.c 1.6 644)
@@ -56,6 +56,9 @@
 	k->type = type;
 	k->dsa = NULL;
 	k->rsa = NULL;
+	k->name = NULL;
+	k->name_len = 0;
+	k->name_type = NULL;
 	switch (k->type) {
 	case KEY_RSA1:
 	case KEY_RSA:
@@ -72,6 +75,8 @@
 		dsa->pub_key = BN_new();
 		k->dsa = dsa;
 		break;
+	case KEY_NAME:
+	case KEY_NAME_PAT:
 	case KEY_UNSPEC:
 		break;
 	default:
@@ -119,6 +124,14 @@
 			DSA_free(k->dsa);
 		k->dsa = NULL;
 		break;
+	case KEY_NAME:
+	case KEY_NAME_PAT:
+		if (k->name != NULL)
+			xfree(k->name);
+		k->name_len = 0;
+		if (k->name_type != NULL)
+			xfree(k->name_type);
+		break;
 	case KEY_UNSPEC:
 		break;
 	default:
@@ -130,8 +143,9 @@
 int
 key_equal(Key *a, Key *b)
 {
-	if (a == NULL || b == NULL || a->type != b->type)
+	if (a == NULL || b == NULL || a->type != b->type) {
 		return 0;
+	}
 	switch (a->type) {
 	case KEY_RSA1:
 	case KEY_RSA:
@@ -146,12 +160,67 @@
 		    BN_cmp(a->dsa->g, b->dsa->g) == 0 &&
 		    BN_cmp(a->dsa->pub_key, b->dsa->pub_key) == 0;
 		break;
+	case KEY_NAME:
+		if ((a->name_type == NULL && b->name_type == NULL) ||
+		    (a->name_type == b->name_type))
+			return (a->name_len == b->name_len) &&
+			    (memcmp(a->name, b->name, a->name_len) == 0);
+		if (a->name_type == NULL || b->name_type == NULL)
+			return 0;
+		if (strcmp(a->name_type, b->name_type) == 0)
+			return (a->name_len == b->name_len) &&
+			    (memcmp(a->name, b->name, a->name_len) == 0);
+		break;
+	case KEY_NAME_PAT:
+		return 0;
+		break;
 	default:
 		fatal("key_equal: bad key type %d", a->type);
 		break;
 	}
 	return 0;
 }
+int
+key_match(Key *a, Key *b)
+{
+	debug3("key_match: trying to match %x and %x", a, b);
+	if (a == NULL || b == NULL)
+		return 0;
+
+	debug3("key_match: trying to match key types %d and %d -- KEY_NAME_PAT == %d", a->type, b->type, KEY_NAME_PAT);
+	/* One key must be a name pattern, the other must be a name */
+	if (!(a->type == KEY_NAME_PAT && b->type == KEY_NAME) &&
+	    !(b->type == KEY_NAME_PAT && a->type == KEY_NAME))
+		return 0;
+	
+	/* Both keys must have name types, or both must not */
+	/* or one key must have '*' as its name type        */
+	if ((a->name_type == NULL && b->name_type != NULL) ||
+	    (b->name_type == NULL && a->name_type != NULL)) {
+
+		debug3("key_match: foo");
+		if (a->name_type != NULL && *(a->name_type) != '*')
+			return 0;
+		if (b->name_type != NULL && *(b->name_type) != '*')
+			return 0;
+	}
+
+	/* Name type "*" matches any name type */
+	/* Otherwise name types must match */
+	if ((a->name_type != NULL && strcmp(a->name_type, b->name_type) != 0) &&
+	    (*(a->name_type) != '*' || *(b->name_type) != '*')) {
+		debug3("key_match: a->name_type == %s", a->name_type ? a->name_type : "");
+		debug3("key_match: b->name_type == %s", b->name_type ? b->name_type : "");
+		return 0;
+	    }
+
+	debug3("key_match: trying to match %s WITH %s", a->name, b->name);
+	if (a->type == KEY_NAME_PAT)
+		return match_pattern(b->name, a->name);
+	else
+		return match_pattern(a->name, b->name);
+}
+
 
 u_char*
 key_fingerprint_raw(Key *k, enum fp_type dgst_type, size_t *dgst_raw_length)
@@ -160,7 +229,7 @@
 	EVP_MD_CTX ctx;
 	u_char *blob = NULL;
 	u_char *retval = NULL;
-	int len = 0;
+	u_int len = 0;
 	int nlen, elen;
 
 	*dgst_raw_length = 0;
@@ -363,11 +432,12 @@
 {
 	Key *k;
 	int success = -1;
-	char *cp, *space;
+	char *cp, *space, *name_type;
 	int len, n, type;
 	u_int bits;
-	u_char *blob;
+	u_char *blob = NULL;
 
+	name_type = NULL;
 	cp = *cpp;
 
 	switch(ret->type) {
@@ -390,6 +460,8 @@
 	case KEY_UNSPEC:
 	case KEY_RSA:
 	case KEY_DSA:
+	case KEY_NAME:
+	case KEY_NAME_PAT:
 		space = strchr(cp, ' ');
 		if (space == NULL) {
 			debug3("key_read: no space");
@@ -397,6 +469,17 @@
 		}
 		*space = '\0';
 		type = key_type_from_name(cp);
+		if ((type == KEY_NAME) || (type == KEY_NAME_PAT)) {
+			char * colon = NULL;
+			
+			colon = strchr(cp, ':');
+
+			debug3("key_read: handling named key or pattern (%d), %s, colon at %x", type, cp, colon);
+			if (colon != NULL && *(++colon) != '\0') {
+				name_type = xstrdup(colon);
+			} else
+				name_type == NULL;
+		}
 		*space = ' ';
 		if (type == KEY_UNSPEC) {
 			debug3("key_read: no key found");
@@ -410,30 +493,80 @@
 		if (ret->type == KEY_UNSPEC) {
 			ret->type = type;
 		} else if (ret->type != type) {
-			/* is a key, but different type */
-			debug3("key_read: type mismatch");
-			return 0;
+			if (! ((ret->type == KEY_NAME) &&
+			       type ==  KEY_NAME_PAT)) {
+				/* is a key, but different type */
+				debug3("key_read: type mismatch");
+				return 0;
+			}
+			ret->type = type;
 		}
-		len = 2*strlen(cp);
-		blob = xmalloc(len);
-		n = uudecode(cp, blob, len);
-		if (n < 0) {
-			error("key_read: uudecode %s failed", cp);
-			return -1;
+		debug3("key_read: here -- ret->type == %d", ret->type);
+		if ((ret->type == KEY_NAME) || (ret->type == KEY_NAME_PAT)) {
+			char *quote, *newline;
+			debug3("key_read: reading named key %s", cp);
+			if (cp == NULL || *cp == '\0')
+				return 0;
+			if (*cp == '"') {
+				quote = strchr(++cp, '"');
+				if (quote == NULL) {
+					debug3("key_read: missing quote");
+					return 0;
+				}
+				*quote = '\0';
+			}
+			newline = strchr(cp, '\n');
+			if (newline != NULL)
+				*newline = '\0';
+			debug3("key_read: reading named key %s", cp);
+			k = key_new(ret->type);
+			k->name = (unsigned char *) xstrdup(cp);
+			k->name_len = strlen(cp);
+			k->name_type = name_type;
+			if (newline !=NULL)
+				*newline = '\n';
+			if (quote !=NULL)
+				*quote = '"';
+			debug3("key_read: read named key %s", k->name_type);
+		} else {
+			len = 2*strlen(cp);
+			blob = xmalloc(len);
+			n = uudecode(cp, blob, len);
+			if (n < 0) {
+				error("key_read: uudecode %s failed", cp);
+				return -1;
+			}
+			debug3("key_read: reading uuencoded key %s", blob);
+			k = key_from_blob(blob, n);
 		}
-		k = key_from_blob(blob, n);
 		if (k == NULL) {
 			error("key_read: key_from_blob %s failed", cp);
 			return -1;
 		}
-		xfree(blob);
+		if (blob != NULL)
+			xfree(blob);
 		if (k->type != type) {
-			error("key_read: type mismatch: encoding error");
-			key_free(k);
-			return -1;
+			if (! ((ret->type == KEY_NAME) &&
+			       type ==  KEY_NAME_PAT)) {
+				error("key_read: type mismatch: encoding error");
+				key_free(k);
+				return -1;
+			}
 		}
 /*XXXX*/
-		if (ret->type == KEY_RSA) {
+		if ((ret->type == KEY_NAME) || (ret->type == KEY_NAME_PAT)) {
+			/*
+			if (ret->name != NULL)
+				xfree(ret->name);
+			 */
+			ret->name = k->name;
+			ret->name_len = k->name_len;
+			ret->name_type = k->name_type;
+			k->name = NULL;
+			k->name_type = NULL;
+			k->name_len = 0;
+			success = 1;
+		} else if (ret->type == KEY_RSA) {
 			if (ret->rsa != NULL)
 				RSA_free(ret->rsa);
 			ret->rsa = k->rsa;
@@ -487,7 +620,7 @@
 		}
 	} else if ((key->type == KEY_DSA && key->dsa != NULL) ||
 	    (key->type == KEY_RSA && key->rsa != NULL)) {
-		int len, n;
+		u_int len, n;
 		u_char *blob, *uu;
 		key_to_blob(key, &blob, &len);
 		uu = xmalloc(2*len);
@@ -498,6 +631,14 @@
 		}
 		xfree(blob);
 		xfree(uu);
+	} else if (key->type == KEY_NAME && key->name != NULL &&
+		   key->name_len) {
+
+		fprintf(f, "%s ", key_ssh_name(key));
+		if (key->name_type != NULL)
+			fprintf(f, ":%s", key->name_type);
+		else
+			fprintf(f, " \"%.*s\"", key->name, key->name_len);
 	}
 	return success;
 }
@@ -514,6 +655,12 @@
 	case KEY_DSA:
 		return "DSA";
 		break;
+	case KEY_NAME:
+		return "Named";
+		break;
+	case KEY_NAME_PAT:
+		return "Name_Pattern";
+		break;
 	}
 	return "unknown";
 }
@@ -527,6 +674,12 @@
 	case KEY_DSA:
 		return "ssh-dss";
 		break;
+	case KEY_NAME:
+		return "ssh-ext-named";
+		break;
+	case KEY_NAME_PAT:
+		return "ssh-ext-name-pat";
+		break;
 	}
 	return "ssh-unknown";
 }
@@ -604,6 +757,16 @@
 		BN_copy(n->rsa->n, k->rsa->n);
 		BN_copy(n->rsa->e, k->rsa->e);
 		break;
+	case KEY_NAME:
+	case KEY_NAME_PAT:
+		n = key_new(k->type);
+		n->name_len = k->name_len;
+		n->name = xmalloc(k->name_len);
+		memcpy(n->name, k->name, n->name_len);
+		if (k->name_type) {
+			n->name_type = xstrdup(k->name_type);
+		}
+		break;
 	default:
 		fatal("key_from_private: unknown type %d", k->type);
 		break;
@@ -624,6 +787,14 @@
 		return KEY_RSA;
 	} else if (strcmp(name, "ssh-dss") == 0){
 		return KEY_DSA;
+	} else if (strcmp(name, "ssh-ext-named") == 0){
+		return KEY_NAME;
+	} else if (strncmp(name, "ssh-ext-named:", strlen("ssh-ext-named:")) == 0){
+		return KEY_NAME;
+	} else if (strcmp(name, "ssh-ext-name-pat") == 0){
+		return KEY_NAME_PAT;
+	} else if (strncmp(name, "ssh-ext-name-pat:", strlen("ssh-ext-name-pat:")) == 0){
+		return KEY_NAME_PAT;
 	} else if (strcmp(name, "null") == 0){
 		return KEY_NULL;
 	}
Index: 2_9_p2_w_gss_and_krb5.4/auth2.c
--- 2_9_p2_w_gss_and_krb5.4/auth2.c Tue, 26 Jun 2001 16:27:13 -0400 willian (OpenSSH/k/6_auth2.c 1.2 644)
+++ 2_9_p2_w_gss_krb5_named_keys.5/auth2.c Tue, 03 Jul 2001 13:14:57 -0400 willian (OpenSSH/k/6_auth2.c 1.3 644)
@@ -514,7 +514,7 @@
 			buffer_dump(&b);
 #endif
 			/* test for correct signature */
-			if (user_key_allowed(authctxt->pw, key) &&
+			if (user_key_allowed(authctxt->pw, key) > 0 &&
 			    key_verify(key, sig, slen, buffer_ptr(&b), buffer_len(&b)) == 1)
 				authenticated = 1;
 			buffer_clear(&b);
@@ -531,7 +531,7 @@
 			 * if a user is not allowed to login. is this an
 			 * issue? -markus
 			 */
-			if (user_key_allowed(authctxt->pw, key)) {
+			if (user_key_allowed(authctxt->pw, key) > 0) {
 				packet_start(SSH2_MSG_USERAUTH_PK_OK);
 				packet_put_string(pkalg, alen);
 				packet_put_string(pkblob, blen);
@@ -791,19 +791,36 @@
 				continue;
 			}
 		}
-		if (key_equal(found, key) &&
-		    auth_parse_options(pw, options, file, linenum) == 1) {
-			found_key = 1;
-			debug("matching key found: file %s, line %ld",
-			    file, linenum);
+		if (key_equal(found, key)) {
+			found_key = auth_parse_options(pw, options, file, linenum);
+			if (found_key == 0)
+				continue;
+			break;
+		}
+		if (key_match(found, key)) {
+			found_key = auth_parse_options(pw, options, file, linenum);
+			if (found_key == 0)
+				continue;
+			/* Special treatment for key name patterns belongs here */
 			break;
 		}
 	}
+
+done:
 	restore_uid();
 	fclose(f);
 	key_free(found);
-	if (!found_key)
+	if (found_key > 0) {
+		debug("matching key found: file %s, line %ld",
+		    file, linenum);
+		auth_set_key_env(key);
+	}
+	if (found_key == 0)
 		debug2("key not found");
+	if (found_key < 0) {
+		debug("user_key_allowed: matching deny key found: "
+		      "file %s, line %ld", file, linenum);
+	}
 	return found_key;
 }
 
Index: 2_9_p2_w_gss_and_krb5.4/auth-rsa.c
--- 2_9_p2_w_gss_and_krb5.4/auth-rsa.c Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/k/15_auth-rsa.c 1.1 644)
+++ 2_9_p2_w_gss_krb5_named_keys.5/auth-rsa.c Tue, 03 Jul 2001 13:14:57 -0400 willian (OpenSSH/k/15_auth-rsa.c 1.2 644)
@@ -259,7 +259,7 @@
 		 * If our options do not allow this key to be used,
 		 * do not send challenge.
 		 */
-		if (!auth_parse_options(pw, options, file, linenum))
+		if (auth_parse_options(pw, options, file, linenum) < 1)
 			continue;
 
 		/* Perform the challenge-response dialog for this key. */
Index: 2_9_p2_w_gss_and_krb5.4/auth-options.h
--- 2_9_p2_w_gss_and_krb5.4/auth-options.h Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/k/21_auth-optio 1.1 644)
+++ 2_9_p2_w_gss_krb5_named_keys.5/auth-options.h Tue, 03 Jul 2001 13:14:57 -0400 willian (OpenSSH/k/21_auth-optio 1.2 644)
@@ -16,6 +16,8 @@
 #ifndef AUTH_OPTIONS_H
 #define AUTH_OPTIONS_H
 
+#include "key.h"
+
 /* Linked list of custom environment strings */
 struct envstring {
 	struct envstring *next;
@@ -37,6 +39,9 @@
 int
 auth_parse_options(struct passwd *pw, char *options, char *file,
     u_long linenum);
+
+void
+auth_set_key_env(Key *k);
 
 /* reset options flags */
 void	auth_clear_options(void);
Index: 2_9_p2_w_gss_and_krb5.4/auth-options.c
--- 2_9_p2_w_gss_and_krb5.4/auth-options.c Thu, 03 May 2001 16:12:13 -0400 jd (OpenSSH/k/22_auth-optio 1.1 644)
+++ 2_9_p2_w_gss_krb5_named_keys.5/auth-options.c Tue, 03 Jul 2001 13:14:57 -0400 willian (OpenSSH/k/22_auth-optio 1.2 644)
@@ -55,8 +55,43 @@
 	channel_clear_permitted_opens();
 }
 
+void auth_set_key_env(Key *k)
+{
+	struct envstring *new_env;
+	char *s;
+	int len;
+
+	if (k->type != KEY_NAME)
+		return;
+
+	len = strlen("SSH_AUTH_EXT_NAME=");
+	len += k->name_len + 1;
+	s = xmalloc(len);
+	snprintf(s, len, "SSH_AUTH_EXT_NAME=%.*s", k->name_len, k->name);
+	debug3("auth_set_key_env: Adding to the environment: %.*s", len, s);
+	new_env = xmalloc(sizeof(struct envstring));
+	new_env->s = s;
+	new_env->next = custom_environment;
+	custom_environment = new_env;
+
+	if (k->name_type == NULL)
+		return;
+
+	len = strlen("SSH_AUTH_EXT_NAME_TYPE=");
+	len += strlen(k->name_type) + 1;
+	s = xmalloc(len);
+	snprintf(s, len, "SSH_AUTH_EXT_NAME_TYPE=%s", k->name_type);
+
+	new_env = xmalloc(sizeof(struct envstring));
+	new_env->s = s;
+	new_env->next = custom_environment;
+	custom_environment = new_env;
+
+	return;
+}
+
 /*
- * return 1 if access is granted, 0 if not.
+ * return 1 if access is granted, 0 if not, -1 if access explicitly denied
  * side effect: sets key option flags
  */
 int
@@ -72,6 +107,12 @@
 		return 1;
 
 	while (*opts && *opts != ' ' && *opts != '\t') {
+		cp = "deny-access";
+		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
+			log("Authentication successful, but authorization denied");
+			packet_send_debug("Permission denied");
+			return -1;
+		}
 		cp = "no-port-forwarding";
 		if (strncasecmp(opts, cp, strlen(cp)) == 0) {
 			packet_send_debug("Port forwarding disabled.");
Index: 2_9_p2_w_gss_and_krb5.4/gss-serv.c
--- 2_9_p2_w_gss_and_krb5.4/gss-serv.c Tue, 26 Jun 2001 16:27:13 -0400 willian (OpenSSH/l/25_gss-serv.c 1.1 644)
+++ 2_9_p2_w_gss_krb5_named_keys.5/gss-serv.c Tue, 03 Jul 2001 13:14:57 -0400 willian (OpenSSH/l/25_gss-serv.c 1.4 644)
@@ -127,24 +127,38 @@
 int
 ssh_gssapi_krb5_userok(char *name) {
 	krb5_principal princ;
-	int retval;
+	int retval, retval2;
+	Key k;
 
 	if (ssh_gssapi_krb5_init() == 0)
 		return 0;
 		
+	k.type = KEY_NAME;
+	k.name = gssapi_client_name.value;
+	k.name_len = strlen(gssapi_client_name.value);
+	k.name_type = "krb5";
+
+	debug3("ssh_gssapi_krb5_userok:");
+	debug3("ssh_gssapi_krb5_userok: %s", k.name_type);
+
 	if ((retval=krb5_parse_name(krb_context, gssapi_client_name.value, 
 				    &princ))) {
 		log("krb5_parse_name(): %.100s", 
 			krb5_get_err_text(krb_context,retval));
 		return 0;
 	}
+
+	retval2 = user_key_allowed(getpwnam(name), &k);
+	if (retval2 < 0)
+		return 0;
+
 	if (krb5_kuserok(krb_context, princ, name))
 		retval = 1;
 	else
 		retval = 0;
 	
 	krb5_free_principal(krb_context, princ);
-	return retval;
+	return retval | retval2;
 }
 	
 /* Make sure that this is called _after_ we've setuid to the user */

Visit our website at http://www.ubswarburg.com

This message contains confidential information and is intended only 
for the individual named.  If you are not the named addressee you 
should not disseminate, distribute or copy this e-mail.  Please 
notify the sender immediately by e-mail if you have received this 
e-mail by mistake and delete this e-mail from your system.

E-mail transmission cannot be guaranteed to be secure or error-free 
as information could be intercepted, corrupted, lost, destroyed, 
arrive late or incomplete, or contain viruses.  The sender therefore 
does not accept liability for any errors or omissions in the contents 
of this message which arise as a result of e-mail transmission.  If 
verification is required please request a hard-copy version.  This 
message is provided for informational purposes and should not be 
construed as a solicitation or offer to buy or sell any securities or 
related financial instruments.




More information about the openssh-unix-dev mailing list