Petr Šabata 81d24c
diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c
Petr Šabata 81d24c
index 413b845..54dd383 100644
Petr Šabata 81d24c
--- a/gss-serv-krb5.c
Petr Šabata 81d24c
+++ b/gss-serv-krb5.c
Petr Šabata 81d24c
@@ -32,7 +32,9 @@
Petr Šabata 81d24c
 #include <sys/types.h>
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 #include <stdarg.h>
Petr Šabata 81d24c
+#include <stdio.h>
Petr Šabata 81d24c
 #include <string.h>
Petr Šabata 81d24c
+#include <unistd.h>
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 #include "xmalloc.h"
Petr Šabata 81d24c
 #include "sshkey.h"
Petr Šabata 81d24c
@@ -45,6 +47,7 @@
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 #include "ssh-gss.h"
Petr Šabata 81d24c
 
Petr Šabata 81d24c
+extern Authctxt *the_authctxt;
Petr Šabata 81d24c
 extern ServerOptions options;
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 #ifdef HEIMDAL
Petr Šabata 81d24c
@@ -56,6 +59,13 @@ extern ServerOptions options;
Petr Šabata 81d24c
 # include <gssapi/gssapi_krb5.h>
Petr Šabata 81d24c
 #endif
Petr Šabata 81d24c
 
Petr Šabata 81d24c
+/* all commands are allowed by default */
Petr Šabata 81d24c
+char **k5users_allowed_cmds = NULL;
Petr Šabata 81d24c
+
Petr Šabata 81d24c
+static int ssh_gssapi_k5login_exists();
Petr Šabata 81d24c
+static int ssh_gssapi_krb5_cmdok(krb5_principal, const char *, const char *,
Petr Šabata 81d24c
+    int);
Petr Šabata 81d24c
+
Petr Šabata 81d24c
 static krb5_context krb_context = NULL;
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 /* Initialise the krb5 library, for the stuff that GSSAPI won't do */
Petr Šabata 81d24c
@@ -88,6 +98,7 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
Petr Šabata 81d24c
 	krb5_principal princ;
Petr Šabata 81d24c
 	int retval;
Petr Šabata 81d24c
 	const char *errmsg;
Petr Šabata 81d24c
+	int k5login_exists;
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 	if (ssh_gssapi_krb5_init() == 0)
Petr Šabata 81d24c
 		return 0;
Petr Šabata 81d24c
@@ -99,10 +110,22 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
Petr Šabata 81d24c
 		krb5_free_error_message(krb_context, errmsg);
Petr Šabata 81d24c
 		return 0;
Petr Šabata 81d24c
 	}
Petr Šabata 81d24c
-	if (krb5_kuserok(krb_context, princ, name)) {
Petr Šabata 81d24c
+	/* krb5_kuserok() returns 1 if .k5login DNE and this is self-login.
Petr Šabata 81d24c
+	 * We have to make sure to check .k5users in that case. */
Petr Šabata 81d24c
+	k5login_exists = ssh_gssapi_k5login_exists();
Petr Šabata 81d24c
+	/* NOTE: .k5login and .k5users must opened as root, not the user,
Petr Šabata 81d24c
+	 * because if they are on a krb5-protected filesystem, user credentials
Petr Šabata 81d24c
+	 * to access these files aren't available yet. */
Petr Šabata 81d24c
+	if (krb5_kuserok(krb_context, princ, name) && k5login_exists) {
Petr Šabata 81d24c
 		retval = 1;
Petr Šabata 81d24c
 		logit("Authorized to %s, krb5 principal %s (krb5_kuserok)",
Petr Šabata 81d24c
 		    name, (char *)client->displayname.value);
Petr Šabata 81d24c
+	} else if (ssh_gssapi_krb5_cmdok(princ, client->exportedname.value,
Petr Šabata 81d24c
+		name, k5login_exists)) {
Petr Šabata 81d24c
+		retval = 1;
Petr Šabata 81d24c
+		logit("Authorized to %s, krb5 principal %s "
Petr Šabata 81d24c
+		    "(ssh_gssapi_krb5_cmdok)",
Petr Šabata 81d24c
+		    name, (char *)client->displayname.value);
Petr Šabata 81d24c
 	} else
Petr Šabata 81d24c
 		retval = 0;
Petr Šabata 81d24c
 
Petr Šabata 81d24c
@@ -110,6 +133,137 @@ ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name)
Petr Šabata 81d24c
 	return retval;
Petr Šabata 81d24c
 }
Petr Šabata 81d24c
 
Petr Šabata 81d24c
+/* Test for existence of .k5login.
Petr Šabata 81d24c
+ * We need this as part of our .k5users check, because krb5_kuserok()
Petr Šabata 81d24c
+ * returns success if .k5login DNE and user is logging in as himself.
Petr Šabata 81d24c
+ * With .k5login absent and .k5users present, we don't want absence
Petr Šabata 81d24c
+ * of .k5login to authorize self-login.  (absence of both is required)
Petr Šabata 81d24c
+ * Returns 1 if .k5login is available, 0 otherwise.
Petr Šabata 81d24c
+ */
Petr Šabata 81d24c
+static int
Petr Šabata 81d24c
+ssh_gssapi_k5login_exists()
Petr Šabata 81d24c
+{
Petr Šabata 81d24c
+	char file[MAXPATHLEN];
Petr Šabata 81d24c
+	struct passwd *pw = the_authctxt->pw;
Petr Šabata 81d24c
+
Petr Šabata 81d24c
+	snprintf(file, sizeof(file), "%s/.k5login", pw->pw_dir);
Petr Šabata 81d24c
+	return access(file, F_OK) == 0;
Petr Šabata 81d24c
+}
Petr Šabata 81d24c
+
Petr Šabata 81d24c
+/* check .k5users for login or command authorization
Petr Šabata 81d24c
+ * Returns 1 if principal is authorized, 0 otherwise.
Petr Šabata 81d24c
+ * If principal is authorized, (global) k5users_allowed_cmds may be populated.
Petr Šabata 81d24c
+ */
Petr Šabata 81d24c
+static int
Petr Šabata 81d24c
+ssh_gssapi_krb5_cmdok(krb5_principal principal, const char *name,
Petr Šabata 81d24c
+    const char *luser, int k5login_exists)
Petr Šabata 81d24c
+{
Petr Šabata 81d24c
+	FILE *fp;
Petr Šabata 81d24c
+	char file[MAXPATHLEN];
Petr Šabata 81d24c
+	char *line = NULL;
Petr Šabata 81d24c
+	char kuser[65]; /* match krb5_kuserok() */
Petr Šabata 81d24c
+	struct stat st;
Petr Šabata 81d24c
+	struct passwd *pw = the_authctxt->pw;
Petr Šabata 81d24c
+	int found_principal = 0;
Petr Šabata 81d24c
+	int ncommands = 0, allcommands = 0;
Petr Šabata 81d24c
+	u_long linenum = 0;
Petr Šabata 81d24c
+	size_t linesize = 0;
Petr Šabata 81d24c
+
Petr Šabata 81d24c
+	snprintf(file, sizeof(file), "%s/.k5users", pw->pw_dir);
Petr Šabata 81d24c
+	/* If both .k5login and .k5users DNE, self-login is ok. */
Petr Šabata 81d24c
+	if (!k5login_exists && (access(file, F_OK) == -1)) {
Petr Šabata 81d24c
+		return (krb5_aname_to_localname(krb_context, principal,
Petr Šabata 81d24c
+		    sizeof(kuser), kuser) == 0) &&
Petr Šabata 81d24c
+		    (strcmp(kuser, luser) == 0);
Petr Šabata 81d24c
+	}
Petr Šabata 81d24c
+	if ((fp = fopen(file, "r")) == NULL) {
Petr Šabata 81d24c
+		int saved_errno = errno;
Petr Šabata 81d24c
+		/* 2nd access check to ease debugging if file perms are wrong.
Petr Šabata 81d24c
+		 * But we don't want to report this if .k5users simply DNE. */
Petr Šabata 81d24c
+		if (access(file, F_OK) == 0) {
Petr Šabata 81d24c
+			logit("User %s fopen %s failed: %s",
Petr Šabata 81d24c
+			    pw->pw_name, file, strerror(saved_errno));
Petr Šabata 81d24c
+		}
Petr Šabata 81d24c
+		return 0;
Petr Šabata 81d24c
+	}
Petr Šabata 81d24c
+	/* .k5users must be owned either by the user or by root */
Petr Šabata 81d24c
+	if (fstat(fileno(fp), &st) == -1) {
Petr Šabata 81d24c
+		/* can happen, but very wierd error so report it */
Petr Šabata 81d24c
+		logit("User %s fstat %s failed: %s",
Petr Šabata 81d24c
+		    pw->pw_name, file, strerror(errno));
Petr Šabata 81d24c
+		fclose(fp);
Petr Šabata 81d24c
+		return 0;
Petr Šabata 81d24c
+	}
Petr Šabata 81d24c
+	if (!(st.st_uid == pw->pw_uid || st.st_uid == 0)) {
Petr Šabata 81d24c
+		logit("User %s %s is not owned by root or user",
Petr Šabata 81d24c
+		    pw->pw_name, file);
Petr Šabata 81d24c
+		fclose(fp);
Petr Šabata 81d24c
+		return 0;
Petr Šabata 81d24c
+	}
Petr Šabata 81d24c
+	/* .k5users must be a regular file.  krb5_kuserok() doesn't do this
Petr Šabata 81d24c
+	  * check, but we don't want to be deficient if they add a check. */
Petr Šabata 81d24c
+	if (!S_ISREG(st.st_mode)) {
Petr Šabata 81d24c
+		logit("User %s %s is not a regular file", pw->pw_name, file);
Petr Šabata 81d24c
+		fclose(fp);
Petr Šabata 81d24c
+		return 0;
Petr Šabata 81d24c
+	}
Petr Šabata 81d24c
+	/* file exists; initialize k5users_allowed_cmds (to none!) */
Petr Šabata 81d24c
+	k5users_allowed_cmds = xcalloc(++ncommands,
Petr Šabata 81d24c
+	    sizeof(*k5users_allowed_cmds));
Petr Šabata 81d24c
+
Petr Šabata 81d24c
+	/* Check each line.  ksu allows unlimited length lines. */
Petr Šabata 81d24c
+	while (!allcommands && getline(&line, &linesize, fp) != -1) {
Petr Šabata 81d24c
+		linenum++;
Petr Šabata 81d24c
+		char *token;
Petr Šabata 81d24c
+
Petr Šabata 81d24c
+		/* we parse just like ksu, even though we could do better */
Petr Šabata 81d24c
+		if ((token = strtok(line, " \t\n")) == NULL)
Petr Šabata 81d24c
+			continue;
Petr Šabata 81d24c
+		if (strcmp(name, token) == 0) {
Petr Šabata 81d24c
+			/* we matched on client principal */
Petr Šabata 81d24c
+			found_principal = 1;
Petr Šabata 81d24c
+			if ((token = strtok(NULL, " \t\n")) == NULL) {
Petr Šabata 81d24c
+				/* only shell is allowed */
Petr Šabata 81d24c
+				k5users_allowed_cmds[ncommands-1] =
Petr Šabata 81d24c
+				    xstrdup(pw->pw_shell);
Petr Šabata 81d24c
+				k5users_allowed_cmds =
Petr Šabata 81d24c
+				    xreallocarray(k5users_allowed_cmds, ++ncommands,
Petr Šabata 81d24c
+					sizeof(*k5users_allowed_cmds));
Petr Šabata 81d24c
+				break;
Petr Šabata 81d24c
+			}
Petr Šabata 81d24c
+			/* process the allowed commands */
Petr Šabata 81d24c
+			while (token) {
Petr Šabata 81d24c
+				if (strcmp(token, "*") == 0) {
Petr Šabata 81d24c
+					allcommands = 1;
Petr Šabata 81d24c
+					break;
Petr Šabata 81d24c
+				}
Petr Šabata 81d24c
+				k5users_allowed_cmds[ncommands-1] =
Petr Šabata 81d24c
+				    xstrdup(token);
Petr Šabata 81d24c
+				k5users_allowed_cmds =
Petr Šabata 81d24c
+				    xreallocarray(k5users_allowed_cmds, ++ncommands,
Petr Šabata 81d24c
+					sizeof(*k5users_allowed_cmds));
Petr Šabata 81d24c
+				token = strtok(NULL, " \t\n");
Petr Šabata 81d24c
+			}
Petr Šabata 81d24c
+		}
Petr Šabata 81d24c
+       }
Petr Šabata 81d24c
+	free(line);
Petr Šabata 81d24c
+	if (k5users_allowed_cmds) {
Petr Šabata 81d24c
+		/* terminate vector */
Petr Šabata 81d24c
+		k5users_allowed_cmds[ncommands-1] = NULL;
Petr Šabata 81d24c
+		/* if all commands are allowed, free vector */
Petr Šabata 81d24c
+		if (allcommands) {
Petr Šabata 81d24c
+			int i;
Petr Šabata 81d24c
+			for (i = 0; i < ncommands; i++) {
Petr Šabata 81d24c
+				free(k5users_allowed_cmds[i]);
Petr Šabata 81d24c
+			}
Petr Šabata 81d24c
+			free(k5users_allowed_cmds);
Petr Šabata 81d24c
+			k5users_allowed_cmds = NULL;
Petr Šabata 81d24c
+		}
Petr Šabata 81d24c
+	}
Petr Šabata 81d24c
+	fclose(fp);
Petr Šabata 81d24c
+	return found_principal;
Petr Šabata 81d24c
+}
Petr Šabata 81d24c
+ 
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 /* This writes out any forwarded credentials from the structure populated
Petr Šabata 81d24c
  * during userauth. Called after we have setuid to the user */
Petr Šabata 81d24c
diff --git a/session.c b/session.c
Petr Šabata 81d24c
index 28659ec..9c94d8e 100644
Petr Šabata 81d24c
--- a/session.c
Petr Šabata 81d24c
+++ b/session.c
Petr Šabata 81d24c
@@ -789,6 +789,29 @@ do_exec(Session *s, const char *command)
Petr Šabata 81d24c
 		command = auth_opts->force_command;
Petr Šabata 81d24c
 		forced = "(key-option)";
Petr Šabata 81d24c
 	}
Petr Šabata 81d24c
+#ifdef GSSAPI
Petr Šabata 81d24c
+#ifdef KRB5 /* k5users_allowed_cmds only available w/ GSSAPI+KRB5 */
Petr Šabata 81d24c
+	else if (k5users_allowed_cmds) {
Petr Šabata 81d24c
+		const char *match = command;
Petr Šabata 81d24c
+		int allowed = 0, i = 0;
Petr Šabata 81d24c
+
Petr Šabata 81d24c
+		if (!match)
Petr Šabata 81d24c
+			match = s->pw->pw_shell;
Petr Šabata 81d24c
+		while (k5users_allowed_cmds[i]) {
Petr Šabata 81d24c
+			if (strcmp(match, k5users_allowed_cmds[i++]) == 0) {
Petr Šabata 81d24c
+				debug("Allowed command '%.900s'", match);
Petr Šabata 81d24c
+				allowed = 1;
Petr Šabata 81d24c
+				break;
Petr Šabata 81d24c
+			}
Petr Šabata 81d24c
+		}
Petr Šabata 81d24c
+		if (!allowed) {
Petr Šabata 81d24c
+			debug("command '%.900s' not allowed", match);
Petr Šabata 81d24c
+			return 1;
Petr Šabata 81d24c
+		}
Petr Šabata 81d24c
+	}
Petr Šabata 81d24c
+#endif
Petr Šabata 81d24c
+#endif
Petr Šabata 81d24c
+
Petr Šabata 81d24c
 	s->forced = 0;
Petr Šabata 81d24c
 	if (forced != NULL) {
Petr Šabata 81d24c
 		s->forced = 1;
Petr Šabata 81d24c
diff --git a/ssh-gss.h b/ssh-gss.h
Petr Šabata 81d24c
index 0374c88..509109a 100644
Petr Šabata 81d24c
--- a/ssh-gss.h
Petr Šabata 81d24c
+++ b/ssh-gss.h
Petr Šabata 81d24c
@@ -49,6 +49,10 @@
Petr Šabata 81d24c
 #  endif /* !HAVE_DECL_GSS_C_NT_... */
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 # endif /* !HEIMDAL */
Petr Šabata 81d24c
+
Petr Šabata 81d24c
+/* .k5users support */
Petr Šabata 81d24c
+extern char **k5users_allowed_cmds;
Petr Šabata 81d24c
+
Petr Šabata 81d24c
 #endif /* KRB5 */
Petr Šabata 81d24c
 
Petr Šabata 81d24c
 /* draft-ietf-secsh-gsskeyex-06 */
Petr Šabata 81d24c
diff --git a/sshd.8 b/sshd.8
Petr Šabata 81d24c
index adcaaf9..824163b 100644
Petr Šabata 81d24c
--- a/sshd.8
Petr Šabata 81d24c
+++ b/sshd.8
Petr Šabata 81d24c
@@ -324,6 +324,7 @@ Finally, the server and the client enter an authentication dialog.
Petr Šabata 81d24c
 The client tries to authenticate itself using
Petr Šabata 81d24c
 host-based authentication,
Petr Šabata 81d24c
 public key authentication,
Petr Šabata 81d24c
+GSSAPI authentication,
Petr Šabata 81d24c
 challenge-response authentication,
Petr Šabata 81d24c
 or password authentication.
Petr Šabata 81d24c
 .Pp
Petr Šabata 81d24c
@@ -800,6 +801,12 @@ This file is used in exactly the same way as
Petr Šabata 81d24c
 but allows host-based authentication without permitting login with
Petr Šabata 81d24c
 rlogin/rsh.
Petr Šabata 81d24c
 .Pp
Petr Šabata 81d24c
+.It Pa ~/.k5login
Petr Šabata 81d24c
+.It Pa ~/.k5users
Petr Šabata 81d24c
+These files enforce GSSAPI/Kerberos authentication access control.
Petr Šabata 81d24c
+Further details are described in
Petr Šabata 81d24c
+.Xr ksu 1 .
Petr Šabata 81d24c
+.Pp
Petr Šabata 81d24c
 .It Pa ~/.ssh/
Petr Šabata 81d24c
 This directory is the default location for all user-specific configuration
Petr Šabata 81d24c
 and authentication information.