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