#define FILE_OWNER_OK(UID) ((UID) == 0)
#endif
+enum result { ACCEPT, REJECT, PASS };
+
/*
- * Given a Kerberos principal "principal", and a local username "luser",
- * determine whether user is authorized to login according to the
- * authorization file ("~luser/.k5login" by default). Returns TRUE
- * if authorized, FALSE if not authorized.
- *
- * If there is no account for "luser" on the local machine, returns
- * FALSE. If there is no authorization file, and the given Kerberos
- * name "server" translates to the same name as "luser" (using
- * krb5_aname_to_lname()), returns TRUE. Otherwise, if the authorization file
- * can't be accessed, returns FALSE. Otherwise, the file is read for
- * a matching principal name, instance, and realm. If one is found,
- * returns TRUE, if none is found, returns FALSE.
- *
- * The file entries are in the format produced by krb5_unparse_name(),
- * one entry per line.
- *
+ * Find the k5login filename for luser, either in the user's homedir or in a
+ * configured directory under the username.
*/
+static krb5_error_code
+get_k5login_filename(krb5_context context, const char *luser,
+ const char *homedir, char **filename_out)
+{
+ krb5_error_code ret;
+ char *dir, *filename;
-krb5_boolean KRB5_CALLCONV
-krb5_kuserok(krb5_context context, krb5_principal principal, const char *luser)
+ *filename_out = NULL;
+ ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
+ KRB5_CONF_K5LOGIN_DIRECTORY, NULL, NULL, &dir);
+ if (ret != 0)
+ return ret;
+
+ if (dir == NULL) {
+ /* Look in the user's homedir. */
+ if (asprintf(&filename, "%s/.k5login", homedir) < 0)
+ return ENOMEM;
+ } else {
+ /* Look in the configured directory. */
+ if (asprintf(&filename, "%s/%s", dir, luser) < 0)
+ ret = ENOMEM;
+ profile_release_string(dir);
+ if (ret)
+ return ret;
+ }
+ *filename_out = filename;
+ return 0;
+}
+
+/*
+ * Determine whether principal is authorized to log in as luser according to
+ * the user's k5login file. Return ACCEPT if the k5login file authorizes the
+ * principal, PASS if the k5login file does not exist, or REJECT if the k5login
+ * file exists but does not authorize the principal. If k5login files are
+ * configured to be non-authoritative, pass instead of rejecting.
+ */
+static enum result
+k5login_ok(krb5_context context, krb5_principal principal, const char *luser)
{
+ int authoritative = TRUE;
+ enum result result = REJECT;
+ char *filename = NULL, *princname = NULL;
+ char gobble, *newline, linebuf[BUFSIZ], pwbuf[BUFSIZ];
struct stat sbuf;
- struct passwd *pwd;
- char pbuf[MAXPATHLEN];
- krb5_boolean isok = FALSE;
- FILE *fp;
- char kuser[MAX_USERNAME];
- char *princname;
- char linebuf[BUFSIZ];
- char *newline;
- int gobble;
- char pwbuf[BUFSIZ];
- struct passwd pwx;
- int result;
-
- /* no account => no access */
+ struct passwd pwx, *pwd;
+ FILE *fp = NULL;
+
+ if (profile_get_boolean(context->profile, KRB5_CONF_LIBDEFAULTS,
+ KRB5_CONF_K5LOGIN_AUTHORITATIVE, NULL, TRUE,
+ &authoritative) != 0)
+ goto cleanup;
+
+ /* Get the local user's homedir and uid. */
if (k5_getpwnam_r(luser, &pwx, pwbuf, sizeof(pwbuf), &pwd) != 0)
- return(FALSE);
- result = snprintf(pbuf, sizeof(pbuf), "%s/.k5login", pwd->pw_dir);
- if (SNPRINTF_OVERFLOW(result, sizeof(pbuf)))
- return(FALSE);
-
- if (access(pbuf, F_OK)) { /* not accessible */
- /*
- * if he's trying to log in as himself, and there is no .k5login file,
- * let him. To find out, call
- * krb5_aname_to_localname to convert the principal to a name
- * which we can string compare.
- */
- if (!(krb5_aname_to_localname(context, principal,
- sizeof(kuser), kuser))
- && (strcmp(kuser, luser) == 0)) {
- return(TRUE);
- }
- }
- if (krb5_unparse_name(context, principal, &princname))
- return(FALSE); /* no hope of matching */
+ goto cleanup;
+
+ if (get_k5login_filename(context, luser, pwd->pw_dir, &filename) != 0)
+ goto cleanup;
- /* open ~/.k5login */
- if ((fp = fopen(pbuf, "r")) == NULL) {
- free(princname);
- return(FALSE);
+ if (access(filename, F_OK) != 0) {
+ result = PASS;
+ goto cleanup;
}
+
+ if (krb5_unparse_name(context, principal, &princname) != 0)
+ goto cleanup;
+
+ fp = fopen(filename, "r");
+ if (fp == NULL)
+ goto cleanup;
set_cloexec_file(fp);
- /*
- * For security reasons, the .k5login file must be owned either by
- * the user himself, or by root. Otherwise, don't grant access.
- */
- if (fstat(fileno(fp), &sbuf)) {
- fclose(fp);
- free(princname);
- return(FALSE);
- }
- if (sbuf.st_uid != pwd->pw_uid && !FILE_OWNER_OK(sbuf.st_uid)) {
- fclose(fp);
- free(princname);
- return(FALSE);
- }
- /* check each line */
- while (!isok && (fgets(linebuf, BUFSIZ, fp) != NULL)) {
- /* null-terminate the input string */
- linebuf[BUFSIZ-1] = '\0';
- newline = NULL;
- /* nuke the newline if it exists */
- if ((newline = strchr(linebuf, '\n')))
+ /* For security reasons, the .k5login file must be owned either by
+ * the user or by root. */
+ if (fstat(fileno(fp), &sbuf))
+ goto cleanup;
+ if (sbuf.st_uid != pwd->pw_uid && !FILE_OWNER_OK(sbuf.st_uid))
+ goto cleanup;
+
+ /* Check each line. */
+ while (result != ACCEPT && (fgets(linebuf, sizeof(linebuf), fp) != NULL)) {
+ newline = strrchr(linebuf, '\n');
+ if (newline != NULL)
*newline = '\0';
- if (!strcmp(linebuf, princname)) {
- isok = TRUE;
- continue;
- }
- /* clean up the rest of the line if necessary */
- if (!newline)
+ if (strcmp(linebuf, princname) == 0)
+ result = ACCEPT;
+ /* Clean up the rest of the line if necessary. */
+ if (newline == NULL)
while (((gobble = getc(fp)) != EOF) && gobble != '\n');
}
+
+cleanup:
free(princname);
- fclose(fp);
- return(isok);
+ free(filename);
+ if (fp != NULL)
+ fclose(fp);
+ /* If k5login files are non-authoritative, never reject. */
+ return (!authoritative && result == REJECT) ? PASS : result;
+}
+
+/*
+ * Determine whether principal is authorized to log in as luser according to
+ * aname-to-localname translation. Return ACCEPT if principal translates to
+ * luser or PASS if it does not.
+ */
+static enum result
+an2ln_ok(krb5_context context, krb5_principal principal, const char *luser)
+{
+ krb5_error_code ret;
+ char kuser[MAX_USERNAME];
+
+ ret = krb5_aname_to_localname(context, principal, sizeof(kuser), kuser);
+ if (ret != 0)
+ return PASS;
+ return (strcmp(kuser, luser) == 0) ? ACCEPT : PASS;
+}
+
+krb5_boolean KRB5_CALLCONV
+krb5_kuserok(krb5_context context, krb5_principal principal, const char *luser)
+{
+ enum result result;
+
+ result = k5login_ok(context, principal, luser);
+ if (result == PASS)
+ result = an2ln_ok(context, principal, luser);
+ return (result == ACCEPT) ? TRUE : FALSE;
}
#else /* _WIN32 */