[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Password expiration patch



On Wed, 13 Sep 2006, [ISO-8859-1] Love Hörnquist Åstrand wrote:

> Hi Eric,
>
>> I have been working on a patch to allow for "pluggable" modules to be 
>> loaded into kadmin (much the same way the password quality checks are done) 
>> to allow for variable password lifetimes (rather than just reading a static 
>> value from the config files). The patch still has the same functionality 
>> (use config file value) if the modules are unconfigured, but could be used 
>> to do just about anything (we have a module which queries LDAP for an 
>> attribute, so that faculty/staff/students/systems people have different 
>> expirations).
>> 
>> Would such a patch be accepted into Heimdal, and if so, where should I send 
>> it?
>
> I've not given much thought to how to do stuff like that. Viewed
> from a greater perspecitive there seams to be many things that should
> be configured depending on what "group" the user belonged too.
>
> Please send the patch to the list or a pointer to the patch if its large.
>
> When I've read the patch I can comment on what I think about it.
>
> Love
>
>
>

Patch is attached.

This is against 0.7.2, If there is a CVS head or something you would like 
the patch against, please let me know.

-- 
Eric Sturdivant
University of Maryland
Office of Information Technology
Distributed Computing Services
(301) 405-8473
<sturdiva AT umd DOT edu>
http://www.oit.umd.edu/dcs
--- /tmp/heimdal/heimdal-0.7.2/lib/kadm5/bump_pw_expire.c	2006-09-13 09:05:29.750560808 -0400
+++ lib/kadm5/bump_pw_expire.c	2006-09-13 09:03:06.000001000 -0400
@@ -32,9 +32,227 @@
  */
 
 #include "kadm5_locl.h"
+#include "kadm5-pwlifetime.h"
 
 RCSID("$Id: bump_pw_expire.c,v 1.1 2000/07/24 03:47:54 assar Exp $");
 
+#ifdef HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+
+/* builtin default (read lifetime from config file) */
+static krb5_error_code
+krb5_config_passwd_lifetime (krb5_context context,
+			     krb5_principal principal,
+			     krb5_deltat *lifetime)
+{
+    *lifetime = krb5_config_get_time_default(context,
+					     NULL,
+					     365 * 24 * 60 * 60,
+					     "kadmin",
+					     "password_lifetime",
+					     NULL);
+
+    return 0;
+}
+
+
+
+/*
+ * builtin default function is just the normal 
+ * "query krb5.conf" method.
+ */
+struct kadm5_pw_lifetime_func lifetime_builtin_funcs[] = {
+    { "krb5-config", krb5_config_passwd_lifetime },
+    { NULL }
+};
+struct kadm5_pw_lifetime_library builtin_library = {
+    "builtin",
+    KADM5_PASSWD_LIFETIME_VERSION_V0,
+    "Heimdal builtin",
+    lifetime_builtin_funcs
+};
+
+static struct kadm5_pw_lifetime_library **libraries = NULL;
+static int num_libraries = -1;
+
+
+#ifdef HAVE_DLOPEN
+static krb5_error_code
+add_func (krb5_context context, const char *lifetime_library)
+{
+    void *handle;
+    struct kadm5_pw_lifetime_library *l, **tmp;
+    int i;
+
+    if ( lifetime_library == NULL ) {
+	return EINVAL;
+    }
+
+    handle = dlopen (lifetime_library, RTLD_NOW);
+    if ( handle == NULL ) {
+	krb5_warnx (context, "failed to open '%s'", lifetime_library);
+	return ENOENT;
+    }
+
+    l = dlsym (handle, "kadm5_password_lifetime");
+    if ( l == NULL ) {
+	krb5_warnx (context, 
+		    "didn't find 'kadm5_password_lifetime' symbol "
+		    "in '%s'", lifetime_library);
+	dlclose (handle);
+	return ENOENT;
+    }
+
+    if ( l->version != KADM5_PASSWD_LIFETIME_VERSION_V0 ) {
+	krb5_warnx (context, 
+		    "version of loaded library is %d (expected %d)",
+		    l->version, KADM5_PASSWD_LIFETIME_VERSION_V0);
+	dlclose (handle);
+	return EINVAL;
+    }
+
+    for (i=0; i<num_libraries; i++) {
+	if ( strcmp (l->name, libraries[i]->name) == 0 ) {
+	    break;
+	}
+    }
+
+    if ( i < num_libraries ) {
+	krb5_warnx (context, "password lifetime library '%s' (%s)"
+		    "is already loaded",
+		    l->name, lifetime_library);
+	dlclose (handle);
+
+	/* don't fall back to the default */
+	return 0;
+    }
+
+    tmp = realloc (libraries, (num_libraries + 1) * sizeof (*libraries));
+    if ( tmp == NULL ) {
+	krb5_warnx (context, "out of memory");
+	dlclose (handle);
+
+	/* fallback if we don't have anything */
+	if ( num_libraries ) {
+	    return ENOMEM;
+	} else {
+	    return 0;
+	}
+    }
+
+    libraries = tmp;
+    libraries[num_libraries] = l;
+    num_libraries++;
+
+    return 0;
+}
+#endif /* HAVE_DLOPEN */
+
+
+
+static void
+_kadm5_setup_passwd_lifetime_libraries (krb5_context context)
+{
+#ifdef HAVE_DLOPEN
+
+    char **tmp;
+    krb5_error_code ret;
+
+    /* query the config files to see if they want to use an
+       external library. (If not, we just fall back to the 
+       builtin method. */
+    tmp = krb5_config_get_strings (context, NULL,
+				   "password_lifetime",
+				   "lifetime_libraries",
+				   NULL);
+    if ( tmp == NULL ) {
+	/* fallback to the default. */
+	return ;
+    } 
+
+    while (*tmp) {
+	ret = add_func (context, *tmp);
+	if ( ret ) {
+	    krb5_warn (context, ret, 
+		       "Can't load password lifetime library '%s'", *tmp);
+	    
+	    krb5_config_free_strings (tmp);
+
+	    /* fallback to the default */
+	    return ;
+	}
+	tmp++;
+    }
+#endif /* HAVE_DLOPEN */
+
+    return ;
+}
+					
+
+
+/* 
+ * find the given function (from config file) in the libraries
+ * we have configured. 
+ */
+static struct kadm5_pw_lifetime_func *
+find_func (krb5_context context, const char *name)
+{
+    struct kadm5_pw_lifetime_func *f;
+    char *module = NULL;
+    const char *p, *func;
+    int i;
+
+    /* name is made up of "$module:$function" or "$function" */
+    p = strchr (name, ':');
+    if ( p ) {
+	func = p+1;
+	module = strndup (name, p-name);
+	if ( module == NULL ) {
+	    return NULL;
+	}
+    } else {
+	func = name;
+    }
+
+    /* find it in the loaded modules first. */
+    for (i=0; i<num_libraries; i++) {
+	/* skip if we *did* specify a module, and it's not this one. */
+	if ( module && strcmp (module, libraries[i]->name) != 0 )
+	    continue;
+
+	for (f=libraries[i]->funcs; f->name; f++) {
+	    if ( strcmp (func, f->name) == 0 ) {
+		if ( module ) {
+		    free (module);
+		}
+		return f;
+	    }
+	}
+    }
+
+    /* try the builtin modules */
+    if ( module == NULL || strcmp (module, "builtin") == 0 ) {
+	for (f=builtin_library.funcs; f->name; f++) {
+	    if ( strcmp (func, f->name) == 0 ) {
+		if ( module ) {
+		    free (module);
+		}
+		return f;
+	    }
+	}
+    }
+
+    if ( module ) {
+	free (module);
+    }
+
+    return NULL;
+}
+
+
+
 /*
  * extend password_expiration if it's defined
  */
@@ -43,17 +261,56 @@
 _kadm5_bump_pw_expire(kadm5_server_context *context,
 		      hdb_entry *ent)
 {
+
+    if ( num_libraries == -1 ) {
+	num_libraries = 0;
+	_kadm5_setup_passwd_lifetime_libraries (context->context);
+    }
+
     if (ent->pw_end != NULL) {
-	time_t life;
+	char **l, **lp;
+	krb5_deltat life, least_life=0;
+	int least_set=0;
+	krb5_error_code ret;
+	struct kadm5_pw_lifetime_func *proc;
+
+	/* find what function we should be using. */
+	l = krb5_config_get_strings (context->context, NULL,
+				     "password_lifetime",
+				     "policies",
+				     NULL);
+
+	if ( l == NULL ) {
+	    /* default. */
+	    ret = krb5_config_passwd_lifetime (context->context,
+					       ent->principal,
+					       &life);
+	    if ( !ret ) {
+		*(ent->pw_end) = time(NULL) + life;
+	    }
+	    return 0;
+	}
+
+	/* go through the policies, and find the most restrictive one. */
+	for (lp = l; *lp; lp++) {
+	    proc = find_func (context->context, *lp);
+	    if ( proc == NULL ) {
+		krb5_warnx (context->context, 
+			    "Failed to find password lifetime function '%s'",
+			    *lp);
+		continue;
+	    }
 
-	life = krb5_config_get_time_default(context->context,
-					    NULL,
-					    365 * 24 * 60 * 60,
-					    "kadmin",
-					    "password_lifetime",
-					    NULL);
+	    ret = (proc->func)(context->context, ent->principal, &life);
+	    if ( !ret && (!least_set || life < least_life) ) {
+		least_life = life;
+		least_set = 1;
+	    }
+	}
 
-	*(ent->pw_end) = time(NULL) + life;
+	if ( least_set ) {
+	    *(ent->pw_end) = time(NULL) + least_life;
+	}
     }
     return 0;
 }
--- /tmp/heimdal/heimdal-0.7.2/lib/kadm5/Makefile.am	2006-09-13 09:05:29.763648454 -0400
+++ lib/kadm5/Makefile.am	2006-09-13 09:07:04.000001000 -0400
@@ -20,7 +20,7 @@
 buildkadm5include = $(buildinclude)/kadm5
 
 kadm5include_HEADERS = kadm5_err.h admin.h private.h \
-	kadm5-protos.h kadm5-private.h
+	kadm5-protos.h kadm5-private.h kadm5-pwlifetime.h
 
 install-build-headers:: $(kadm5include_HEADERS)
 	@foo='$(kadm5include_HEADERS)'; \
--- /dev/null	2006-09-13 09:08:07.000000000 -0400
+++ lib/kadm5/kadm5-pwlifetime.h	2006-09-12 11:02:12.000001000 -0400
@@ -0,0 +1,30 @@
+#include <krb5.h>
+
+#ifndef KADM5_PWLIFETIME_H
+#define KADM5_PWLIFETIME_H 1
+
+#define KADM5_PASSWD_LIFETIME_VERSION_V0 0
+
+/*
+ * function provided by the client library which can return a krb5_deltat
+ * to be used as the password lifetime bump for the given principal.
+ */
+typedef krb5_error_code 
+(*kadm5_passwd_lifetime_func)(krb5_context context,
+			      krb5_principal principal,
+			      krb5_deltat *lifetime);
+
+struct kadm5_pw_lifetime_func {
+    char *name;
+    kadm5_passwd_lifetime_func func;
+};
+
+struct kadm5_pw_lifetime_library {
+    char *name;
+    int version;
+    char *vendor;
+    struct kadm5_pw_lifetime_func *funcs;
+};
+
+
+#endif /* KADM5_PWLIFETIME_H */