diff --git a/lib/krb5/krb5_kuserok.3 b/lib/krb5/krb5_kuserok.3 index c6f29bc8b..18a6a147a 100644 --- a/lib/krb5/krb5_kuserok.3 +++ b/lib/krb5/krb5_kuserok.3 @@ -1,4 +1,4 @@ -.\" Copyright (c) 2003-2004 Kungliga Tekniska Högskolan +.\" Copyright (c) 2003-2005 Kungliga Tekniska Högskolan .\" (Royal Institute of Technology, Stockholm, Sweden). .\" All rights reserved. .\" @@ -31,12 +31,12 @@ .\" .\" $Id$ .\" -.Dd August 19, 2004 +.Dd May 4, 2005 .Dt KRB5_KUSEROK 3 .Os HEIMDAL .Sh NAME .Nm krb5_kuserok -.Nd "verifies if a principal can log in as a user" +.Nd "checks if a principal is permitted to login as a user" .Sh LIBRARY Kerberos 5 Library (libkrb5, -lkrb5) .Sh SYNOPSIS @@ -67,10 +67,23 @@ The .Pa .k5login file must contain one principal per line, be owned by .Fa user , -and not be writable by group or other. +and not be writable by group or other (but must be readable by +anyone). .Pp Note that if the file exists, no implicit access rights are given to .Fa user Ns @ Ns Aq localrealm . +.Pp +Optionally, a set of files may be put in +.Pa ~/.k5login.d ( Ns +a directory), in which case they will all be checked in the same +manner as +.Pa .k5login . +The files may be called anything, but files starting with a hash +.Dq ( # ) , +or ending with a tilde +.Dq ( ~ ) +are ignored. Subdirectories are not traversed. Note that this +directory may not be checked by other implementations. .Sh RETURN VALUES .Nm returns @@ -78,6 +91,10 @@ returns if access should be granted, .Dv FALSE otherwise. +.Sh HISTORY +The +.Pa ~/.k5login.d +feature appeared in Heimdal 0.7. .Sh SEE ALSO .Xr krb5_get_default_realms 3 , .Xr krb5_verify_user 3 , diff --git a/lib/krb5/kuserok.c b/lib/krb5/kuserok.c index 7d670be8d..bedcf93af 100644 --- a/lib/krb5/kuserok.c +++ b/lib/krb5/kuserok.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997 - 2004 Kungliga Tekniska Högskolan + * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * @@ -36,9 +36,8 @@ RCSID("$Id$"); -/* - * Return TRUE iff `principal' is allowed to login as `luser'. - */ +/* see if principal is mentioned in the filename access file, return + TRUE (in result) if so, FALSE otherwise */ static krb5_error_code check_one_file(krb5_context context, @@ -78,8 +77,9 @@ check_one_file(krb5_context context, while (fgets (buf, sizeof(buf), f) != NULL) { krb5_principal tmp; + char *newline = buf + strcspn(buf, "\n"); - if(buf[strcspn(buf, "\n")] != '\n') { + if(*newline != '\n') { int c; c = fgetc(f); if(c != EOF) { @@ -89,7 +89,7 @@ check_one_file(krb5_context context, continue; } } - buf[strcspn(buf, "\n")] = '\0'; + *newline = '\0'; ret = krb5_parse_name (context, buf, &tmp); if (ret) continue; @@ -104,6 +104,68 @@ check_one_file(krb5_context context, return 0; } +static krb5_error_code +check_directory(krb5_context context, + const char *dirname, + struct passwd *pwd, + krb5_principal principal, + krb5_boolean *result) +{ + DIR *d; + struct dirent *dent; + char buf[BUFSIZ]; + krb5_error_code ret = 0; + struct stat st; + + *result = FALSE; + + if(lstat(buf, &st) < 0) + return errno; + + if (!S_ISDIR(st.st_mode)) + return ENOTDIR; + + if (st.st_uid != pwd->pw_uid && st.st_uid != 0) + return EACCES; + if ((st.st_mode & (S_IWGRP | S_IWOTH)) != 0) + return EACCES; + + if((d = opendir(buf)) == NULL) + return errno; + +#ifdef HAVE_DIRFD + { + int fd; + struct stat st2; + + fd = dirfd(d); + if(fstat(fd, &st2) < 0) { + closedir(d); + return errno; + } + if(st.st_dev != st2.st_dev || st.st_ino != st2.st_ino) { + closedir(d); + return EACCES; + } + } +#endif + + while((dent = readdir(d)) != NULL) { + if(strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0 || + dent->d_name[0] == '#' || /* emacs autosave */ + dent->d_name[strlen(dent->d_name) - 1] == '~') /* emacs backup */ + continue; + snprintf(buf, sizeof(buf), "%s/%s", dirname, dent->d_name); + ret = check_one_file(context, buf, pwd, principal, result); + if(ret == 0 && *result == TRUE) + break; + ret = 0; /* don't propagate errors upstream */ + } + closedir(d); + return ret; +} + static krb5_boolean match_local_principals(krb5_context context, krb5_principal principal, @@ -135,16 +197,23 @@ match_local_principals(krb5_context context, return result; } +/** + * Return TRUE iff `principal' is allowed to login as `luser'. + */ + krb5_boolean KRB5_LIB_FUNCTION krb5_kuserok (krb5_context context, krb5_principal principal, const char *luser) { char *buf; + size_t buflen; struct passwd *pwd; krb5_error_code ret; krb5_boolean result = FALSE; + krb5_boolean found_file = FALSE; + #ifdef HAVE_GETPWNAM_R char pwbuf[2048]; struct passwd pw; @@ -157,43 +226,37 @@ krb5_kuserok (krb5_context context, if (pwd == NULL) return FALSE; - /* check user's ~/.k5login */ - if (asprintf (&buf, "%s/.k5login", pwd->pw_dir) == -1) +#define KLOGIN "/.k5login" + buflen = strlen(pwd->pw_dir) + sizeof(KLOGIN) + 2; /* 2 for .d */ + buf = malloc(buflen); + if(buf == NULL) return FALSE; + /* check user's ~/.k5login */ + strlcpy(buf, pwd->pw_dir, buflen); + strlcat(buf, KLOGIN, buflen); ret = check_one_file(context, buf, pwd, principal, &result); - /* but if it doesn't exist, allow all principals - matching @ */ - if(ret == ENOENT) { + if(ret == 0 && result == TRUE) { free(buf); - return match_local_principals(context, principal, luser); + return TRUE; } -#if notyet - /* on the other hand, if it's a directory, check all files - contained therein */ - if (ret == EISDIR) { - DIR *d = opendir(buf); - struct dirent *dent; - char *buf2; - if(d == NULL) - return FALSE; - while((dent = readdir(d)) != NULL) { - if(strcmp(dent->d_name, ".") == 0 || - strcmp(dent->d_name, "..") == 0) - continue; - if (asprintf(&buf2, "%s/%s", buf, dent->d_name) == -1) - break; - ret = check_one_file(context, buf2, pwd, principal, &result); - free(buf2); - if(ret == 0 && result == TRUE) - break; - } - closedir(d); - } -#endif + if(ret != ENOENT) + found_file = TRUE; + + strlcat(buf, ".d", buflen); + ret = check_directory(context, buf, pwd, principal, &result); free(buf); - if (ret) - return FALSE; - return result; + if(ret == 0 && result == TRUE) + return TRUE; + + if(ret != ENOENT && ret != ENOTDIR) + found_file = TRUE; + + /* finally if no files exist, allow all principals matching + @ */ + if(found_file == FALSE) + return match_local_principals(context, principal, luser); + + return FALSE; }