diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c index 413b845..54dd383 100644 --- a/gss-serv-krb5.c +++ b/gss-serv-krb5.c @@ -32,7 +32,9 @@ #include #include +#include #include +#include #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 #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 --- a/session.c +++ b/session.c @@ -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 --- 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 adcaaf9..824163b 100644 --- a/sshd.8 +++ b/sshd.8 @@ -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.