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