Implement k5login_directory and k5login_authoritative options
authorGreg Hudson <ghudson@mit.edu>
Fri, 1 Oct 2010 15:56:30 +0000 (15:56 +0000)
committerGreg Hudson <ghudson@mit.edu>
Fri, 1 Oct 2010 15:56:30 +0000 (15:56 +0000)
Add and document two new options for controlling k5login behavior.

ticket: 6792

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24402 dc483132-0cff-0310-8789-dd5450dbe970

doc/admin.texinfo
src/config-files/krb5.conf.M
src/include/k5-int.h
src/lib/krb5/os/kuserok.c

index 8603b93ae396346e46246fd1867dbfcde67c54f3..2a811de96b46556fcd0b979f33ec5ee6fab7ac68 100644 (file)
@@ -468,6 +468,20 @@ Sets the maximum allowable amount of clockskew in seconds that the
 library  will tolerate before assuming that a Kerberos message is
 invalid.  The default value is @value{DefaultClockskew}.
 
+@itemx k5login_authoritative
+If the value of this relation is true (the default), principals must
+be listed in a local user's k5login file to be granted login access,
+if a k5login file exists.  If the value of this relation is false, a
+principal may still be granted login access through other mechanisms
+even if a k5login file exists but does not list the principal.
+
+@itemx k5login_directory
+If set, the library will look for a local user's k5login file within the
+named directory, with a filename corresponding to the local username.
+If not set, the library will look for k5login files in the user's home
+directory, with the filename @code{.k5login}.  For security reasons,
+k5login files must be owned by the local user or by root.
+
 @itemx kdc_timesync
 If this is set to 1 (for true), then client machines will compute the
 difference between their time and the time returned by the KDC in the
index 2995aa2bef73af2e04f5d34c3a3fe304e418f874..e658e8997ffaa814fd65e6f18d3d6a8276afe25e 100644 (file)
@@ -155,6 +155,20 @@ This relation sets the maximum allowable amount of clockskew in seconds
 that the library will tolerate before assuming that a Kerberos message
 is invalid.  The default value is 300 seconds, or five minutes.
 
+.IP k5login_authoritative
+If the value of this relation is true (the default), principals must
+be listed in a local user's k5login file to be granted login access,
+if a k5login file exists.  If the value of this relation is false, a
+principal may still be granted login access through other mechanisms
+even if a k5login file exists but does not list the principal.
+
+.IP k5login_directory
+If set, the library will look for a local user's k5login file within
+the named directory, with a filename corresponding to the local
+username.  If not set, the library will look for k5login files in the
+user's home directory, with the filename .k5login.  For security
+reasons, k5login files must be owned by the local user or by root.
+
 .IP kdc_timesync 
 If the value of this relation is non-zero (the default), the library
 will compute the difference between the system clock and the time
index 750f989c9b177bc271b23eea95a6c3506c57f1d1..f2a037c2304fab973f8f61bb586a441b906f0848 100644 (file)
@@ -222,6 +222,8 @@ typedef INT64_TYPE krb5_int64;
 #define KRB5_CONF_IPROP_PORT                  "iprop_port"
 #define KRB5_CONF_IPROP_SLAVE_POLL            "iprop_slave_poll"
 #define KRB5_CONF_IPROP_LOGFILE               "iprop_logfile"
+#define KRB5_CONF_K5LOGIN_AUTHORITATIVE       "k5login_authoritative"
+#define KRB5_CONF_K5LOGIN_DIRECTORY           "k5login_directory"
 #define KRB5_CONF_KADMIND_PORT                "kadmind_port"
 #define KRB5_CONF_KRB524_SERVER               "krb524_server"
 #define KRB5_CONF_KDC                         "kdc"
index 1bc7505da6395060582ac53417502cb4bb298474..985bb141212687b03a36e5cd5a46550be567944f 100644 (file)
 #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 */