| diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c |
| index 413b845..54dd383 100644 |
| |
| |
| @@ -32,7 +32,9 @@ |
| #include <sys/types.h> |
| |
| #include <stdarg.h> |
| +#include <stdio.h> |
| #include <string.h> |
| +#include <unistd.h> |
| |
| #include "xmalloc.h" |
| #include "sshkey.h" |
| @@ -45,6 +47,7 @@ |
| |
| #include "ssh-gss.h" |
| |
| +extern Authctxt *the_authctxt; |
| extern ServerOptions options; |
| |
| #ifdef HEIMDAL |
| @@ -56,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 */ |
| @@ -88,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; |
| @@ -99,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; |
| |
| @@ -110,6 +133,137 @@ 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 = NULL; |
| + 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 = 0; |
| + size_t linesize = 0; |
| + |
| + 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. */ |
| + while (!allcommands && getline(&line, &linesize, fp) != -1) { |
| + linenum++; |
| + 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"); |
| + } |
| + } |
| + } |
| + free(line); |
| + 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 28659ec..9c94d8e 100644 |
| |
| |
| @@ -789,6 +789,29 @@ do_exec(Session *s, const char *command) |
| command = auth_opts->force_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 |
| + |
| s->forced = 0; |
| if (forced != NULL) { |
| s->forced = 1; |
| diff --git a/ssh-gss.h b/ssh-gss.h |
| index 0374c88..509109a 100644 |
| |
| |
| @@ -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 adcaaf9..824163b 100644 |
| |
| |
| @@ -324,6 +324,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. |