diff options
author | Greg Hudson <ghudson@mit.edu> | 2010-07-22 03:13:38 +0000 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2010-07-22 03:13:38 +0000 |
commit | 9e1b4bc8d254fd6671a94d2a8a5c84650754fce3 (patch) | |
tree | 81c86ecb849fb18b3ae2cda4f4fe3eb3bcbad871 /src/lib | |
parent | c5e940f6d4d6737bf0ed778a72dbfc6dfb9640dc (diff) | |
download | krb5-9e1b4bc8d254fd6671a94d2a8a5c84650754fce3.zip krb5-9e1b4bc8d254fd6671a94d2a8a5c84650754fce3.tar.gz krb5-9e1b4bc8d254fd6671a94d2a8a5c84650754fce3.tar.bz2 |
Proof of concept code for a candidate plugin framework
git-svn-id: svn://anonsvn.mit.edu/krb5/branches/plugins2@24203 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/kadm5/server_internal.h | 53 | ||||
-rw-r--r-- | src/lib/kadm5/srv/Makefile.in | 16 | ||||
-rw-r--r-- | src/lib/kadm5/srv/libkadm5srv_mit.exports | 3 | ||||
-rw-r--r-- | src/lib/kadm5/srv/pwqual.c | 114 | ||||
-rw-r--r-- | src/lib/kadm5/srv/pwqual_dict.c | 242 | ||||
-rw-r--r-- | src/lib/kadm5/srv/pwqual_policy.c | 81 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_dict.c | 208 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_init.c | 4 | ||||
-rw-r--r-- | src/lib/kadm5/srv/server_misc.c | 187 | ||||
-rw-r--r-- | src/lib/kadm5/srv/svr_principal.c | 6 | ||||
-rw-r--r-- | src/lib/krb5/krb/Makefile.in | 3 | ||||
-rw-r--r-- | src/lib/krb5/krb/init_ctx.c | 2 | ||||
-rw-r--r-- | src/lib/krb5/krb/plugin.c | 382 | ||||
-rw-r--r-- | src/lib/krb5/libkrb5.exports | 4 |
14 files changed, 944 insertions, 361 deletions
diff --git a/src/lib/kadm5/server_internal.h b/src/lib/kadm5/server_internal.h index cc589fa..52f71e9 100644 --- a/src/lib/kadm5/server_internal.h +++ b/src/lib/kadm5/server_internal.h @@ -22,6 +22,7 @@ #include <errno.h> #include <kdb.h> #include <kadm5/admin.h> +#include <krb5/plugin.h> #include "admin_internal.h" /* @@ -33,6 +34,9 @@ */ #define INITIAL_HIST_KVNO 2 +/* A pwqual_handle represents a password quality plugin module. */ +typedef struct pwqual_handle_st *pwqual_handle; + typedef struct _kadm5_server_handle_t { krb5_ui_4 magic_number; krb5_ui_4 struct_version; @@ -42,6 +46,7 @@ typedef struct _kadm5_server_handle_t { kadm5_config_params params; struct _kadm5_server_handle_t *lhandle; char **db_args; + pwqual_handle *qual_handles; } kadm5_server_handle_rec, *kadm5_server_handle_t; #define OSA_ADB_PRINC_VERSION_1 0x12345C01 @@ -65,8 +70,7 @@ typedef struct _osa_princ_ent_t { kadm5_ret_t adb_policy_init(kadm5_server_handle_t handle); kadm5_ret_t adb_policy_close(kadm5_server_handle_t handle); kadm5_ret_t passwd_check(kadm5_server_handle_t handle, - char *pass, int use_policy, - kadm5_policy_ent_t policy, + const char *pass, kadm5_policy_ent_t policy, krb5_principal principal); kadm5_ret_t principal_exists(krb5_principal principal); krb5_error_code kdb_init_master(kadm5_server_handle_t handle, @@ -90,9 +94,8 @@ krb5_error_code kdb_iter_entry(kadm5_server_handle_t handle, void (*iter_fct)(void *, krb5_principal), void *data); -int init_dict(kadm5_config_params *); -int find_word(const char *word); -void destroy_dict(void); +kadm5_ret_t init_pwqual(kadm5_server_handle_t handle); +void destroy_pwqual(kadm5_server_handle_t handle); /* XXX this ought to be in libkrb5.a, but isn't */ kadm5_ret_t krb5_copy_key_data_contents(krb5_context context, @@ -153,4 +156,44 @@ bool_t xdr_osa_princ_ent_rec(XDR *xdrs, osa_princ_ent_t objp); void osa_free_princ_ent(osa_princ_ent_t val); +/*** Password quality plugin consumer interface ***/ + +/* Load the available password quality plugins and store the result into + * *handles. Free the result with k5_pwqual_free_handles. */ +krb5_error_code +k5_pwqual_load(krb5_context context, pwqual_handle **handles); + +/* Release a handle list allocated by k5_pwqual_load. All modules must have + * been closed by the caller. */ +krb5_error_code +k5_pwqual_free_handles(krb5_context context, pwqual_handle *handles); + +/* Initialize a password quality plugin, possibly using the realm's configured + * dictionary filename. */ +krb5_error_code +k5_pwqual_open(krb5_context context, pwqual_handle handle, + const char *dict_file); + +/* Check a password using a password quality plugin. */ +krb5_error_code +k5_pwqual_check(krb5_context context, pwqual_handle handle, + const char *password, kadm5_policy_ent_t policy, + krb5_principal princ); + +/* Release the memory used by a password quality plugin. */ +void +k5_pwqual_close(krb5_context context, pwqual_handle handle); + +/*** Init functions for built-in password quality modules ***/ + +/* The dict module checks passwords against the realm's dictionary. */ +krb5_error_code +pwqual_dict_init(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + +/* The policy module enforces password policy constraints. */ +krb5_error_code +pwqual_policy_init(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + #endif /* __KADM5_SERVER_INTERNAL_H__ */ diff --git a/src/lib/kadm5/srv/Makefile.in b/src/lib/kadm5/srv/Makefile.in index c7e0fac..6000d73 100644 --- a/src/lib/kadm5/srv/Makefile.in +++ b/src/lib/kadm5/srv/Makefile.in @@ -27,36 +27,42 @@ SHLIB_DIRS=-L$(TOPLIBD) SHLIB_RDIRS=$(KRB5_LIBDIR) RELDIR=kadm5/srv -SRCS = $(srcdir)/svr_policy.c \ +SRCS = $(srcdir)/pwqual.c \ + $(srcdir)/pwqual_dict.c \ + $(srcdir)/pwqual_policy.c \ + $(srcdir)/svr_policy.c \ $(srcdir)/svr_principal.c \ $(srcdir)/server_acl.c \ $(srcdir)/server_kdb.c \ $(srcdir)/server_misc.c \ $(srcdir)/server_init.c \ - $(srcdir)/server_dict.c \ $(srcdir)/svr_iters.c \ $(srcdir)/svr_chpass_util.c \ $(srcdir)/adb_xdr.c -OBJS = svr_policy.$(OBJEXT) \ +OBJS = pwqual.$(OBJEXT) \ + pwqual_dict.$(OBJEXT) \ + pwqual_policy.$(OBJECT) \ + svr_policy.$(OBJEXT) \ svr_principal.$(OBJEXT) \ server_acl.$(OBJEXT) \ server_kdb.$(OBJEXT) \ server_misc.$(OBJEXT) \ server_init.$(OBJEXT) \ - server_dict.$(OBJEXT) \ svr_iters.$(OBJEXT) \ svr_chpass_util.$(OBJEXT) \ adb_xdr.$(OBJEXT) STLIBOBJS = \ + pwqual.o \ + pwqual_dict.o \ + pwqual_policy.o \ svr_policy.o \ svr_principal.o \ server_acl.o \ server_kdb.o \ server_misc.o \ server_init.o \ - server_dict.o \ svr_iters.o \ svr_chpass_util.o \ adb_xdr.o diff --git a/src/lib/kadm5/srv/libkadm5srv_mit.exports b/src/lib/kadm5/srv/libkadm5srv_mit.exports index 6da95bd..345957a 100644 --- a/src/lib/kadm5/srv/libkadm5srv_mit.exports +++ b/src/lib/kadm5/srv/libkadm5srv_mit.exports @@ -7,10 +7,7 @@ kadm5int_acl_impose_restrictions kadm5int_acl_init adb_policy_close adb_policy_init -destroy_dict -find_word hist_princ -init_dict kadm5_set_use_password_server kadm5_chpass_principal kadm5_chpass_principal_3 diff --git a/src/lib/kadm5/srv/pwqual.c b/src/lib/kadm5/srv/pwqual.c new file mode 100644 index 0000000..4452376 --- /dev/null +++ b/src/lib/kadm5/srv/pwqual.c @@ -0,0 +1,114 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * lib/kadm5/srv/pwqual.c + * + * Copyright (C) 2010 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * Consumer interface for password quality plugins + */ + +#include "k5-int.h" +#include "server_internal.h" +#include <krb5/pwqual_plugin.h> + +struct pwqual_handle_st { + struct krb5_pwqual_vtable_st vt; + krb5_pwqual_moddata data; +}; + +krb5_error_code +k5_pwqual_load(krb5_context context, pwqual_handle **handles) +{ + krb5_error_code ret; + krb5_plugin_init_fn *modules = NULL, *mod; + size_t count; + pwqual_handle *list = NULL, handle = NULL; + + ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_PWQUAL, &modules); + if (ret != 0) + goto cleanup; + + /* Allocate a large enough list of handles. */ + for (count = 0; modules[count] != NULL; count++); + list = k5alloc((count + 1) * sizeof(*list), &ret); + if (list == NULL) + goto cleanup; + + /* For each module, allocate a handle and initialize its vtable. Skip + * modules which don't successfully initialize. */ + count = 0; + for (mod = modules; *mod != NULL; mod++) { + handle = k5alloc(sizeof(*handle), &ret); + if (handle == NULL) + goto cleanup; + ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&handle->vt); + if (ret == 0) + list[count++] = handle; + else + free(handle); + } + + *handles = list; + list = NULL; + +cleanup: + k5_plugin_free_modules(context, modules); + k5_pwqual_free_handles(context, list); + return ret; +} + +krb5_error_code +k5_pwqual_free_handles(krb5_context context, pwqual_handle *handles) +{ + /* It's the caller's responsibility to close each handle, so all of the + * module data should be freed by now, leaving only the list itself. */ + free(handles); +} + +krb5_error_code +k5_pwqual_open(krb5_context context, pwqual_handle handle, + const char *dict_file) +{ + if (handle->data != NULL) + return EINVAL; + if (handle->vt.open == NULL) + return 0; + return handle->vt.open(context, dict_file, &handle->data); +} + +krb5_error_code +k5_pwqual_check(krb5_context context, pwqual_handle handle, + const char *password, kadm5_policy_ent_t policy, + krb5_principal princ) +{ + return handle->vt.check(context, handle->data, password, policy, princ); +} + +void +k5_pwqual_close(krb5_context context, pwqual_handle handle) +{ + if (handle->vt.close) + handle->vt.close(context, handle->data); + handle->data = NULL; +} diff --git a/src/lib/kadm5/srv/pwqual_dict.c b/src/lib/kadm5/srv/pwqual_dict.c new file mode 100644 index 0000000..60bc5ff --- /dev/null +++ b/src/lib/kadm5/srv/pwqual_dict.c @@ -0,0 +1,242 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved + * + * $Header$ + */ + +#if !defined(lint) && !defined(__CODECENTER__) +static char *rcsid = "$Header$"; +#endif + +#include "k5-platform.h" +#include <krb5/pwqual_plugin.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <kadm5/admin.h> +#include "adm_proto.h" +#include <syslog.h> +#include "server_internal.h" + +typedef struct dict_moddata_st { + char **word_list; /* list of word pointers */ + char *word_block; /* actual word data */ + unsigned int word_count; /* number of words */ +} *dict_moddata; + + +/* + * Function: word_compare + * + * Purpose: compare two words in the dictionary. + * + * Arguments: + * w1 (input) pointer to first word + * w2 (input) pointer to second word + * <return value> result of strcmp + * + * Requires: + * w1 and w2 to point to valid memory + * + */ + +static int +word_compare(const void *s1, const void *s2) +{ + return (strcasecmp(*(const char **)s1, *(const char **)s2)); +} + +/* + * Function: init-dict + * + * Purpose: Initialize in memory word dictionary + * + * Arguments: + * none + * <return value> KADM5_OK on success errno on failure; + * (but success on ENOENT) + * + * Requires: + * If WORDFILE exists, it must contain a list of words, + * one word per-line. + * + * Effects: + * If WORDFILE exists, it is read into memory sorted for future + * use. If it does not exist, it syslogs an error message and returns + * success. + * + * Modifies: + * word_list to point to a chunck of allocated memory containing + * pointers to words + * word_block to contain the dictionary. + * + */ + +static int +init_dict(dict_moddata dict, const char *dict_file) +{ + int fd; + size_t len, i; + char *p, *t; + struct stat sb; + + if (dict_file == NULL) { + krb5_klog_syslog(LOG_INFO, "No dictionary file specified, continuing " + "without one."); + return KADM5_OK; + } + if ((fd = open(dict_file, O_RDONLY)) == -1) { + if (errno == ENOENT) { + krb5_klog_syslog(LOG_ERR, + "WARNING! Cannot find dictionary file %s, " + "continuing without one.", dict_file); + return KADM5_OK; + } else + return errno; + } + set_cloexec_fd(fd); + if (fstat(fd, &sb) == -1) { + close(fd); + return errno; + } + if ((dict->word_block = malloc(sb.st_size + 1)) == NULL) + return ENOMEM; + if (read(fd, dict->word_block, sb.st_size) != sb.st_size) + return errno; + (void) close(fd); + dict->word_block[sb.st_size] = '\0'; + + p = dict->word_block; + len = sb.st_size; + while(len > 0 && (t = memchr(p, '\n', len)) != NULL) { + *t = '\0'; + len -= t - p + 1; + p = t + 1; + dict->word_count++; + } + if ((dict->word_list = malloc(dict->word_count * sizeof(char *))) == NULL) + return ENOMEM; + p = dict->word_block; + for (i = 0; i < dict->word_count; i++) { + dict->word_list[i] = p; + p += strlen(p) + 1; + } + qsort(dict->word_list, dict->word_count, sizeof(char *), word_compare); + return KADM5_OK; +} + +/* + * Function: destroy_dict + * + * Purpose: destroy in-core copy of dictionary. + * + * Arguments: + * none + * <return value> none + * Requires: + * nothing + * Effects: + * frees up memory occupied by word_list and word_block + * sets count back to 0, and resets the pointers to NULL + * + * Modifies: + * word_list, word_block, and word_count. + * + */ + +static void +destroy_dict(dict_moddata dict) +{ + if (dict == NULL) + return; + free(dict->word_list); + free(dict->word_block); + free(dict); + return; +} + +/* Implement the password quality open method by reading in dict_file. */ +static krb5_error_code +dict_open(krb5_context context, const char *dict_file, + krb5_pwqual_moddata *data) +{ + krb5_error_code ret; + dict_moddata dict; + + *data = NULL; + + /* Allocate and initialize a dictionary structure. */ + dict = malloc(sizeof(*dict)); + if (dict == NULL) + return ENOMEM; + dict->word_list = NULL; + dict->word_block = NULL; + dict->word_count = 0; + + /* Fill in the dictionary structure with data from dict_file. */ + ret = init_dict(dict, dict_file); + if (ret != 0) { + destroy_dict(dict); + return ret; + } + + *data = (krb5_pwqual_moddata)dict; + return 0; +} + +/* Implement the password quality check method by checking the password + * against the dictionary, as well as against principal components. */ +static krb5_error_code +dict_check(krb5_context context, krb5_pwqual_moddata data, + const char *password, kadm5_policy_ent_t policy, + krb5_principal princ) +{ + dict_moddata dict = (dict_moddata)data; + int i, n; + char *cp; + + /* Don't check the dictionary for principals with no password policy. */ + if (policy == NULL) + return 0; + + /* Check against words in the dictionary if we successfully loaded one. */ + if (dict->word_list != NULL && + bsearch(&password, dict->word_list, dict->word_count, sizeof(char *), + word_compare) != NULL) + return KADM5_PASS_Q_DICT; + + /* Check against components of the principal. */ + n = krb5_princ_size(handle->context, princ); + cp = krb5_princ_realm(handle->context, princ)->data; + if (strcasecmp(cp, password) == 0) + return KADM5_PASS_Q_DICT; + for (i = 0; i < n; i++) { + cp = krb5_princ_component(handle->context, princ, i)->data; + if (strcasecmp(cp, password) == 0) + return KADM5_PASS_Q_DICT; + } + return 0; +} + +/* Implement the password quality close method. */ +static void +dict_close(krb5_context context, krb5_pwqual_moddata data) +{ + destroy_dict((dict_moddata)data); +} + +krb5_error_code +pwqual_dict_init(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable) +{ + krb5_pwqual_vtable vt; + + if (maj_ver != 1) + return EINVAL; /* XXX create error code */ + vt = (krb5_pwqual_vtable)vtable; + vt->open = dict_open; + vt->check = dict_check; + vt->close = dict_close; + return 0; +} diff --git a/src/lib/kadm5/srv/pwqual_policy.c b/src/lib/kadm5/srv/pwqual_policy.c new file mode 100644 index 0000000..978744d --- /dev/null +++ b/src/lib/kadm5/srv/pwqual_policy.c @@ -0,0 +1,81 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * lib/kadm5/srv/pwqual_policy.c + * + * Copyright (C) 2010 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * Password quality module to enforce password policy + */ + +#include "k5-platform.h" +#include <krb5/pwqual_plugin.h> +#include <kadm5/admin.h> +#include <ctype.h> +#include "server_internal.h" + +/* Implement the password quality check module. */ +static krb5_error_code +policy_check(krb5_context context, krb5_pwqual_moddata data, + const char *password, kadm5_policy_ent_t policy, + krb5_principal princ) +{ + int nupper = 0, nlower = 0, ndigit = 0, npunct = 0, nspec = 0; + const char *s; + unsigned char c; + + if (policy == NULL) + return (*password == '\0') ? KADM5_PASS_Q_TOOSHORT : 0; + + if(strlen(password) < (size_t)policy->pw_min_length) + return KADM5_PASS_Q_TOOSHORT; + s = password; + while ((c = (unsigned char)*s++)) { + if (islower(c)) + nlower = 1; + else if (isupper(c)) + nupper = 1; + else if (isdigit(c)) + ndigit = 1; + else if (ispunct(c)) + npunct = 1; + else + nspec = 1; + } + if ((nupper + nlower + ndigit + npunct + nspec) < policy->pw_min_classes) + return KADM5_PASS_Q_CLASS; + return 0; +} + +krb5_error_code +pwqual_policy_init(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable) +{ + krb5_pwqual_vtable vt; + + if (maj_ver != 1) + return EINVAL; /* XXX create error code */ + vt = (krb5_pwqual_vtable)vtable; + vt->check = policy_check; + return 0; +} diff --git a/src/lib/kadm5/srv/server_dict.c b/src/lib/kadm5/srv/server_dict.c deleted file mode 100644 index 81cc5f9..0000000 --- a/src/lib/kadm5/srv/server_dict.c +++ /dev/null @@ -1,208 +0,0 @@ -/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved - * - * $Header$ - */ - -#if !defined(lint) && !defined(__CODECENTER__) -static char *rcsid = "$Header$"; -#endif - -#include <sys/types.h> -#include <sys/file.h> -#include <fcntl.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <kadm5/admin.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#ifdef HAVE_MEMORY_H -#include <memory.h> -#endif -#include "adm_proto.h" -#include <syslog.h> -#include "server_internal.h" -#include "k5-platform.h" - -static char **word_list = NULL; /* list of word pointers */ -static char *word_block = NULL; /* actual word data */ -static unsigned int word_count = 0; /* number of words */ - - -/* - * Function: word_compare - * - * Purpose: compare two words in the dictionary. - * - * Arguments: - * w1 (input) pointer to first word - * w2 (input) pointer to second word - * <return value> result of strcmp - * - * Requires: - * w1 and w2 to point to valid memory - * - */ - -static int -word_compare(const void *s1, const void *s2) -{ - return (strcasecmp(*(const char **)s1, *(const char **)s2)); -} - -/* - * Function: init-dict - * - * Purpose: Initialize in memory word dictionary - * - * Arguments: - * none - * <return value> KADM5_OK on success errno on failure; - * (but success on ENOENT) - * - * Requires: - * If WORDFILE exists, it must contain a list of words, - * one word per-line. - * - * Effects: - * If WORDFILE exists, it is read into memory sorted for future - * use. If it does not exist, it syslogs an error message and returns - * success. - * - * Modifies: - * word_list to point to a chunck of allocated memory containing - * pointers to words - * word_block to contain the dictionary. - * - */ - -int init_dict(kadm5_config_params *params) -{ - int fd, - len, - i; - char *p, - *t; - struct stat sb; - - if(word_list != NULL && word_block != NULL) - return KADM5_OK; - if (! (params->mask & KADM5_CONFIG_DICT_FILE)) { - krb5_klog_syslog(LOG_INFO, "No dictionary file specified, continuing " - "without one."); - return KADM5_OK; - } - if ((fd = open(params->dict_file, O_RDONLY)) == -1) { - if (errno == ENOENT) { - krb5_klog_syslog(LOG_ERR, - "WARNING! Cannot find dictionary file %s, " - "continuing without one.", params->dict_file); - return KADM5_OK; - } else - return errno; - } - set_cloexec_fd(fd); - if (fstat(fd, &sb) == -1) { - close(fd); - return errno; - } - if ((word_block = (char *) malloc(sb.st_size + 1)) == NULL) - return ENOMEM; - if (read(fd, word_block, sb.st_size) != sb.st_size) - return errno; - (void) close(fd); - word_block[sb.st_size] = '\0'; - - p = word_block; - len = sb.st_size; - while(len > 0 && (t = memchr(p, '\n', len)) != NULL) { - *t = '\0'; - len -= t - p + 1; - p = t + 1; - word_count++; - } - if ((word_list = (char **) malloc(word_count * sizeof(char *))) == NULL) - return ENOMEM; - p = word_block; - for (i = 0; i < word_count; i++) { - word_list[i] = p; - p += strlen(p) + 1; - } - qsort(word_list, word_count, sizeof(char *), word_compare); - return KADM5_OK; -} - -/* - * Function: find_word - * - * Purpose: See if the specified word exists in the in-core dictionary - * - * Arguments: - * word (input) word to search for. - * <return value> WORD_NOT_FOUND if not in dictionary, - * KADM5_OK if if found word - * errno if init needs to be called and returns an - * error - * - * Requires: - * word to be a null terminated string. - * That word_list and word_block besetup - * - * Effects: - * finds word in dictionary. - * Modifies: - * nothing. - * - */ - -int -find_word(const char *word) -{ - char **value; - - if(word_list == NULL || word_block == NULL) - return WORD_NOT_FOUND; - if ((value = (char **) bsearch(&word, word_list, word_count, sizeof(char *), - word_compare)) == NULL) - return WORD_NOT_FOUND; - else - return KADM5_OK; -} - -/* - * Function: destroy_dict - * - * Purpose: destroy in-core copy of dictionary. - * - * Arguments: - * none - * <return value> none - * Requires: - * nothing - * Effects: - * frees up memory occupied by word_list and word_block - * sets count back to 0, and resets the pointers to NULL - * - * Modifies: - * word_list, word_block, and word_count. - * - */ - -void -destroy_dict(void) -{ - if(word_list) { - free(word_list); - word_list = NULL; - } - if(word_block) { - free(word_block); - word_block = NULL; - } - if(word_count) - word_count = 0; - return; -} diff --git a/src/lib/kadm5/srv/server_init.c b/src/lib/kadm5/srv/server_init.c index 557ef0a..9ebc13e 100644 --- a/src/lib/kadm5/srv/server_init.c +++ b/src/lib/kadm5/srv/server_init.c @@ -317,7 +317,7 @@ kadm5_ret_t kadm5_init(krb5_context context, char *client_name, char *pass, return ret; } - ret = init_dict(&handle->params); + ret = init_pwqual(handle); if (ret) { krb5_db_fini(handle->context); krb5_free_principal(handle->context, handle->current_caller); @@ -337,7 +337,7 @@ kadm5_ret_t kadm5_destroy(void *server_handle) CHECK_HANDLE(server_handle); - destroy_dict(); + destroy_pwqual(handle); adb_policy_close(handle); krb5_db_fini(handle->context); diff --git a/src/lib/kadm5/srv/server_misc.c b/src/lib/kadm5/srv/server_misc.c index 1faeb86..38c0607 100644 --- a/src/lib/kadm5/srv/server_misc.c +++ b/src/lib/kadm5/srv/server_misc.c @@ -13,10 +13,6 @@ static char *rcsid = "$Header$"; #include <kdb.h> #include <ctype.h> #include <pwd.h> - -/* for strcasecmp */ -#include <string.h> - #include "server_internal.h" kadm5_ret_t @@ -37,147 +33,68 @@ adb_policy_close(kadm5_server_handle_t handle) return KADM5_OK; } -#ifdef HESIOD -/* stolen from v4sever/kadm_funcs.c */ -static char * -reverse(str) - char *str; -{ - static char newstr[80]; - char *p, *q; - int i; - - i = strlen(str); - if (i >= sizeof(newstr)) - i = sizeof(newstr)-1; - p = str+i-1; - q = newstr; - q[i]='\0'; - for(; i > 0; i--) - *q++ = *p--; - - return(newstr); -} -#endif /* HESIOD */ - -#if 0 -static int -lower(str) - char *str; +kadm5_ret_t +init_pwqual(kadm5_server_handle_t handle) { - register char *cp; - int effect=0; - - for (cp = str; *cp; cp++) { - if (isupper(*cp)) { - *cp = tolower(*cp); - effect++; + krb5_error_code ret; + pwqual_handle *list, *h; + const char *dict_file = NULL; + + ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL, + "dict", pwqual_dict_init); + if (ret != 0) + return ret; + + ret = k5_plugin_register(handle->context, PLUGIN_INTERFACE_PWQUAL, + "policy", pwqual_policy_init); + if (ret != 0) + return ret; + + ret = k5_pwqual_load(handle->context, &list); + if (ret != 0) + return ret; + + if (handle->params.mask & KADM5_CONFIG_DICT_FILE) + dict_file = handle->params.dict_file; + + for (h = list; *h != NULL; h++) { + ret = k5_pwqual_open(handle->context, *h, dict_file); + if (ret != 0) { + /* Close any previously opened modules and error out. */ + for (; h > list; h--) + k5_pwqual_close(handle->context, *(h - 1)); + k5_pwqual_free_handles(handle->context, list); + return ret; } } - return(effect); + + handle->qual_handles = list; + return 0; } -#endif -#ifdef HESIOD -static int -str_check_gecos(gecos, pwstr) - char *gecos; - char *pwstr; +/* Check a password against all available password quality plugin modules. */ +kadm5_ret_t +passwd_check(kadm5_server_handle_t handle, const char *password, + kadm5_policy_ent_t policy, krb5_principal princ) { - char *cp, *ncp, *tcp; - - for (cp = gecos; *cp; ) { - /* Skip past punctuation */ - for (; *cp; cp++) - if (isalnum(*cp)) - break; - /* Skip to the end of the word */ - for (ncp = cp; *ncp; ncp++) - if (!isalnum(*ncp) && *ncp != '\'') - break; - /* Delimit end of word */ - if (*ncp) - *ncp++ = '\0'; - /* Check word to see if it's the password */ - if (*cp) { - if (!strcasecmp(pwstr, cp)) - return 1; - tcp = reverse(cp); - if (!strcasecmp(pwstr, tcp)) - return 1; - cp = ncp; - } else - break; + krb5_error_code ret; + pwqual_handle *h; + + for (h = handle->qual_handles; *h != NULL; h++) { + ret = k5_pwqual_check(handle->context, *h, password, policy, princ); + if (ret != 0) + return ret; } return 0; } -#endif /* HESIOD */ -/* some of this is stolen from gatekeeper ... */ -kadm5_ret_t -passwd_check(kadm5_server_handle_t handle, - char *password, int use_policy, kadm5_policy_ent_t pol, - krb5_principal principal) +void +destroy_pwqual(kadm5_server_handle_t handle) { - int nupper = 0, - nlower = 0, - ndigit = 0, - npunct = 0, - nspec = 0; - char c, *s, *cp; -#ifdef HESIOD - extern struct passwd *hes_getpwnam(); - struct passwd *ent; -#endif + pwqual_handle *h; - if(use_policy) { - if(strlen(password) < pol->pw_min_length) - return KADM5_PASS_Q_TOOSHORT; - s = password; - while ((c = *s++)) { - if (islower((unsigned char) c)) { - nlower = 1; - continue; - } - else if (isupper((unsigned char) c)) { - nupper = 1; - continue; - } else if (isdigit((unsigned char) c)) { - ndigit = 1; - continue; - } else if (ispunct((unsigned char) c)) { - npunct = 1; - continue; - } else { - nspec = 1; - continue; - } - } - if ((nupper + nlower + ndigit + npunct + nspec) < pol->pw_min_classes) - return KADM5_PASS_Q_CLASS; - if((find_word(password) == KADM5_OK)) - return KADM5_PASS_Q_DICT; - else { - int i, n = krb5_princ_size(handle->context, principal); - cp = krb5_princ_realm(handle->context, principal)->data; - if (strcasecmp(cp, password) == 0) - return KADM5_PASS_Q_DICT; - for (i = 0; i < n ; i++) { - cp = krb5_princ_component(handle->context, principal, i)->data; - if (strcasecmp(cp, password) == 0) - return KADM5_PASS_Q_DICT; -#ifdef HESIOD - ent = hes_getpwnam(cp); - if (ent && ent->pw_gecos) - if (str_check_gecos(ent->pw_gecos, password)) - return KADM5_PASS_Q_DICT; /* XXX new error code? */ -#endif - } - return KADM5_OK; - } - } else { - if (strlen(password) < 1) - return KADM5_PASS_Q_TOOSHORT; - } - return KADM5_OK; + for (h = handle->qual_handles; *h != NULL; h++) + k5_pwqual_close(handle->context, *h); + k5_pwqual_free_handles(handle->context, handle->qual_handles); + handle->qual_handles = NULL; } diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c index 6b14d3b..dc16406 100644 --- a/src/lib/kadm5/srv/svr_principal.c +++ b/src/lib/kadm5/srv/svr_principal.c @@ -292,7 +292,7 @@ kadm5_create_principal_3(void *server_handle, have_polent = TRUE; } if (password) { - ret = passwd_check(handle, password, have_polent, &polent, + ret = passwd_check(handle, password, have_polent ? &polent : NULL, entry->principal); if (ret) goto cleanup; @@ -1341,8 +1341,8 @@ kadm5_chpass_principal_3(void *server_handle, have_pol = 1; } - if ((ret = passwd_check(handle, password, adb.aux_attributes & - KADM5_POLICY, &pol, principal))) + if ((ret = passwd_check(handle, password, have_pol ? &pol : NULL, + principal))) goto done; ret = krb5_dbe_find_act_mkey(handle->context, master_keylist, diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index e52200d..0c01386 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -74,6 +74,7 @@ STLIBOBJS= \ pac.o \ pac_sign.o \ parse.o \ + plugin.o \ pr_to_salt.o \ preauth2.o \ gic_opt_set_pa.o \ @@ -173,6 +174,7 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \ $(OUTPRE)pac.$(OBJEXT) \ $(OUTPRE)pac_sign.$(OBJEXT) \ $(OUTPRE)parse.$(OBJEXT) \ + $(OUTPRE)plugin.$(OBJEXT) \ $(OUTPRE)pr_to_salt.$(OBJEXT) \ $(OUTPRE)preauth2.$(OBJEXT) \ $(OUTPRE)gic_opt_set_pa.$(OBJEXT) \ @@ -273,6 +275,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/pac.c \ $(srcdir)/pac_sign.c \ $(srcdir)/parse.c \ + $(srcdir)/plugin.c \ $(srcdir)/pr_to_salt.c \ $(srcdir)/preauth2.c \ $(srcdir)/gic_opt_set_pa.c \ diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c index e7419f5..c5975f1 100644 --- a/src/lib/krb5/krb/init_ctx.c +++ b/src/lib/krb5/krb/init_ctx.c @@ -273,6 +273,8 @@ krb5_free_context(krb5_context ctx) ctx->trace_callback(ctx, NULL, ctx->trace_callback_data); #endif + k5_plugin_free_context(ctx); + ctx->magic = 0; free(ctx); } diff --git a/src/lib/krb5/krb/plugin.c b/src/lib/krb5/krb/plugin.c new file mode 100644 index 0000000..aa9ac89 --- /dev/null +++ b/src/lib/krb5/krb/plugin.c @@ -0,0 +1,382 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * lib/krb5/krb/plugin.c + * + * Copyright (C) 2010 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * Plugin framework functions + */ + +#include "k5-int.h" + +const char *interface_names[PLUGIN_NUM_INTERFACES] = { + "pwqual" +}; + +/* Return the context's interface structure for id, or NULL if invalid. */ +static inline struct plugin_interface * +get_interface(krb5_context context, int id) +{ + if (context == NULL || id < 0 || id >= PLUGIN_NUM_INTERFACES) + return NULL; + return &context->plugins[id]; +} + +/* Release the memory associated with the linked list entry map. */ +static void +free_plugin_mapping(struct plugin_mapping *map) +{ + if (map == NULL) + return; + free(map->modname); + if (map->dyn_handle != NULL) + krb5int_close_plugin(map->dyn_handle); + free(map); +} + +/* + * Register a mapping from modname to module. On success, dyn_handle is + * remembered in the mapping and will be released when the mapping is + * overwritten or the context is destroyed. + */ +static krb5_error_code +register_module(krb5_context context, struct plugin_interface *interface, + const char *modname, krb5_plugin_init_fn module, + struct plugin_file_handle *dyn_handle) +{ + struct plugin_mapping *map, **pmap; + + /* If a mapping already exists for modname, remove it. */ + for (pmap = &interface->modules; *pmap != NULL; pmap = &(*pmap)->next) { + map = *pmap; + if (strcmp(map->modname, modname) == 0) { + *pmap = map->next; + free_plugin_mapping(map); + break; + } + } + + /* Create a new mapping structure. */ + map = malloc(sizeof(*map)); + if (map == NULL) + return ENOMEM; + map->modname = strdup(modname); + if (map->modname == NULL) { + free(map); + return ENOMEM; + } + map->module = module; + map->dyn_handle = dyn_handle; + + /* Chain it into the list. */ + map->next = interface->modules; + interface->modules = map; + return 0; +} + +/* Parse a profile module string of the form "modname:modpath" into its + * component parts. */ +static krb5_error_code +parse_modstr(krb5_context context, const char *modstr, + char **modname, char **modpath) +{ + const char *sep; + char *name = NULL, *path = NULL; + + *modname = NULL; + *modpath = NULL; + + sep = strchr(modstr, ':'); + if (sep == NULL) { + krb5_set_error_message(context, EINVAL, "Invalid module string %s", + modstr); + return EINVAL; /* XXX create specific error code */ + } + + /* Copy the module name. */ + name = malloc(sep - modstr + 1); + if (name == NULL) + return ENOMEM; + memcpy(name, modstr, sep - modstr); + name[sep - modstr] = '\0'; + + /* Copy the module path. */ + path = strdup(sep + 1); + if (path == NULL) { + free(name); + return ENOMEM; + } + + *modname = name; + *modpath = path; + return 0; +} + +/* Open a dynamic object at modpath, look up symname within it, and register + * the resulting init function as modname. */ +static krb5_error_code +open_and_register(krb5_context context, struct plugin_interface *interface, + const char *modname, const char *modpath, + const char *symname) +{ + krb5_error_code ret; + struct plugin_file_handle *handle; + void (*init_fn)(); + + ret = krb5int_open_plugin(modpath, &handle, &context->err); + if (ret != 0) + return ret; + + ret = krb5int_get_plugin_func(handle, symname, &init_fn, &context->err); + if (ret != 0) { + krb5int_close_plugin(handle); + return ret; + } + + ret = register_module(context, interface, modname, + (krb5_plugin_init_fn)init_fn, handle); + if (ret != 0) + krb5int_close_plugin(handle); + return ret; +} + +/* Register the plugins given by the profile strings in modules. */ +static krb5_error_code +register_dyn_modules(krb5_context context, struct plugin_interface *interface, + const char *iname, char **modules) +{ + krb5_error_code ret; + char *modname = NULL, *modpath = NULL, *symname = NULL; + + for (; *modules != NULL; modules++) { + ret = parse_modstr(context, *modules, &modname, &modpath); + if (ret != 0) + return ret; + if (asprintf(&symname, "%s_%s_init", iname, modname) < 0) { + free(modname); + free(modpath); + return ENOMEM; + } + /* XXX should errors here be fatal, or just ignore the module? */ + ret = open_and_register(context, interface, modname, modpath, symname); + free(modname); + free(modpath); + free(symname); + if (ret != 0) + return ret; + } + return 0; +} + +/* Return true if value is found in list. */ +static krb5_boolean +find_in_list(char **list, const char *value) +{ + for (; *list != NULL; list++) { + if (strcmp(*list, value) == 0) + return TRUE; + } + return FALSE; +} + +/* Remove any registered modules whose names are not present in enable. */ +static void +filter_enable(krb5_context context, struct plugin_interface *interface, + char **enable) +{ + struct plugin_mapping *map, **pmap; + + pmap = &interface->modules; + while (*pmap != NULL) { + map = *pmap; + if (!find_in_list(enable, map->modname)) { + *pmap = map->next; + free_plugin_mapping(map); + } else + pmap = &map->next; + } +} + +/* Remove any registered modules whose names are present in disable. */ +static void +filter_disable(krb5_context context, struct plugin_interface *interface, + char **disable) +{ + struct plugin_mapping *map, **pmap; + + pmap = &interface->modules; + while (*pmap != NULL) { + map = *pmap; + if (find_in_list(disable, map->modname)) { + *pmap = map->next; + free_plugin_mapping(map); + } else + pmap = &map->next; + } +} + +/* Ensure that a plugin interface is configured. id is assumed to be valid. */ +static krb5_error_code +configure_interface(krb5_context context, int id) +{ + krb5_error_code ret; + struct plugin_interface *interface = &context->plugins[id]; + const char *iname = interface_names[id]; + char **modules = NULL, **enable = NULL, **disable = NULL; + static const char *path[4]; + + if (interface->configured) + return 0; + + /* Read the configuration variables for this interface. */ + path[0] = KRB5_CONF_PLUGINS; + path[1] = iname; + path[2] = KRB5_CONF_MODULE; + path[3] = NULL; + ret = profile_get_values(context->profile, path, &modules); + if (ret != 0 && ret != PROF_NO_RELATION) + goto cleanup; + path[2] = KRB5_CONF_ENABLE_ONLY; + ret = profile_get_values(context->profile, path, &enable); + if (ret != 0 && ret != PROF_NO_RELATION) + goto cleanup; + path[2] = KRB5_CONF_DISABLE; + ret = profile_get_values(context->profile, path, &disable); + if (ret != 0 && ret != PROF_NO_RELATION) + goto cleanup; + + if (modules != NULL) { + ret = register_dyn_modules(context, interface, iname, modules); + if (ret != 0) + return ret; + } + if (enable != NULL) + filter_enable(context, interface, enable); + if (disable != NULL) + filter_disable(context, interface, disable); + + ret = 0; +cleanup: + profile_free_list(modules); + profile_free_list(enable); + profile_free_list(disable); + return ret; +} + +krb5_error_code +k5_plugin_load(krb5_context context, int interface_id, const char *modname, + krb5_plugin_init_fn *module) +{ + krb5_error_code ret; + struct plugin_interface *interface = get_interface(context, interface_id); + struct plugin_mapping *map; + + if (interface == NULL) + return EINVAL; + ret = configure_interface(context, interface_id); + if (ret != 0) + return ret; + for (map = interface->modules; map != NULL; map = map->next) { + if (strcmp(map->modname, modname) == 0) { + *module = map->module; + return 0; + } + } + return ENOENT; /* XXX Create error code? */ +} + +krb5_error_code +k5_plugin_load_all(krb5_context context, int interface_id, + krb5_plugin_init_fn **modules) +{ + krb5_error_code ret; + struct plugin_interface *interface = get_interface(context, interface_id); + struct plugin_mapping *map; + krb5_plugin_init_fn *list; + size_t count; + + if (interface == NULL) + return EINVAL; + ret = configure_interface(context, interface_id); + if (ret != 0) + return ret; + + /* Count the modules and allocate a list to hold them. */ + count = 0; + for (map = interface->modules; map != NULL; map = map->next) + count++; + list = malloc((count + 1) * sizeof(*list)); + if (list == NULL) + return ENOMEM; + + /* Place each module's init function into list. */ + count = 0; + for (map = interface->modules; map != NULL; map = map->next) + list[count++] = map->module; + list[count] = NULL; + + *modules = list; + return 0; +} + +void +k5_plugin_free_modules(krb5_context context, krb5_plugin_init_fn *modules) +{ + free(modules); +} + +krb5_error_code +k5_plugin_register(krb5_context context, int interface_id, const char *modname, + krb5_plugin_init_fn module) +{ + struct plugin_interface *interface = get_interface(context, interface_id); + + if (interface == NULL) + return EINVAL; + + /* Disallow registering plugins after load. We may need to reconsider + * this, but it simplifies the design. */ + if (interface->configured) + return EINVAL; + + return register_module(context, interface, modname, module, NULL); +} + +void +k5_plugin_free_context(krb5_context context) +{ + int i; + struct plugin_interface *interface; + struct plugin_mapping *map, *next; + + for (i = 0; i < PLUGIN_NUM_INTERFACES; i++) { + interface = &context->plugins[i]; + for (map = interface->modules; map != NULL; map = next) { + next = map->next; + free_plugin_mapping(map); + } + interface->modules = NULL; + interface->configured = FALSE; + } +} diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index 2bd5972..b375c1b 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -104,6 +104,10 @@ initialize_kdb5_error_table initialize_krb5_error_table initialize_kv5m_error_table initialize_prof_error_table +k5_plugin_free_modules +k5_plugin_load +k5_plugin_load_all +k5_plugin_register krb524_convert_creds_kdc krb524_init_ets krb5_425_conv_principal |