From 8d31a9d396f5bea88def4db395ad12dca2ac2e9f Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Sun, 25 Oct 2009 16:55:12 +0000 Subject: Account lockout Merge Luke's users/lhoward/lockout2 branch to trunk. Implements account lockout policies for preauth-using principals using existing principal metadata fields and new policy fields. The kadmin API version is bumped from 2 to 3 to compatibly extend the policy_ent_rec structure. ticket: 6577 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@23038 dc483132-0cff-0310-8789-dd5450dbe970 --- src/plugins/kdb/db2/Makefile.in | 12 +- src/plugins/kdb/db2/db2_exp.c | 10 +- src/plugins/kdb/db2/kdb_db2.c | 241 ++++++++++++++++++--- src/plugins/kdb/db2/kdb_db2.h | 22 +- src/plugins/kdb/db2/kdb_ext.c | 99 +++++++++ src/plugins/kdb/db2/lockout.c | 196 +++++++++++++++++ src/plugins/kdb/db2/pol_xdr.c | 26 ++- src/plugins/kdb/db2/policy_db.h | 1 + src/plugins/kdb/ldap/ldap_exp.c | 6 +- src/plugins/kdb/ldap/libkdb_ldap/Makefile.in | 8 +- src/plugins/kdb/ldap/libkdb_ldap/kdb_ext.c | 99 +++++++++ src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.c | 84 +++++-- src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h | 23 ++ src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c | 7 + src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif | 38 +++- src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema | 30 ++- src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h | 9 + src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c | 60 ++++- src/plugins/kdb/ldap/libkdb_ldap/ldap_pwd_policy.c | 24 +- .../kdb/ldap/libkdb_ldap/ldap_service_rights.c | 12 + .../kdb/ldap/libkdb_ldap/libkdb_ldap.exports | 1 + src/plugins/kdb/ldap/libkdb_ldap/lockout.c | 192 ++++++++++++++++ 22 files changed, 1122 insertions(+), 78 deletions(-) create mode 100644 src/plugins/kdb/db2/kdb_ext.c create mode 100644 src/plugins/kdb/db2/lockout.c create mode 100644 src/plugins/kdb/ldap/libkdb_ldap/kdb_ext.c create mode 100644 src/plugins/kdb/ldap/libkdb_ldap/lockout.c (limited to 'src/plugins') diff --git a/src/plugins/kdb/db2/Makefile.in b/src/plugins/kdb/db2/Makefile.in index dd3045c168..b2532a4fd0 100644 --- a/src/plugins/kdb/db2/Makefile.in +++ b/src/plugins/kdb/db2/Makefile.in @@ -25,7 +25,7 @@ DB_DEPS-redirect = $(BUILDTOP)/include/db.h DB_LIB = @DB_LIB@ KDB5_DB_LIB = @KDB5_DB_LIB@ DB_DEPLIB = $(DB_DEPLIB-@DB_VERSION@) -DB_DEPLIB-k5 = $(TOPLIBD)/libdb$(DEPLIBEXT) +DB_DEPLIB-k5 = $(TOPLIBD)/libdb$(DEPLIBEXT) $(KADMSRV_DEPLIBS) DB_DEPLIB-sys = LIBBASE=db2 @@ -39,7 +39,7 @@ SHLIB_EXPDEPS = \ $(GSSRPC_DEPLIBS) \ $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ $(TOPLIBD)/libkrb5$(SHLIBEXT) -SHLIB_EXPLIBS= $(GSSRPC_LIBS) -lkrb5 -lcom_err -lk5crypto $(KDB5_DB_LIB) $(SUPPORT_LIB) $(LIBS) @DB_EXTRA_LIBS@ +SHLIB_EXPLIBS= $(GSSRPC_LIBS) -lkrb5 -lcom_err -lk5crypto $(KDB5_DB_LIB) $(KADMSRV_LIBS) $(SUPPORT_LIB) $(LIBS) @DB_EXTRA_LIBS@ SHLIB_DIRS=-L$(TOPLIBD) SHLIB_RDIRS=$(KRB5_LIBDIR) @@ -56,8 +56,10 @@ SRCS= \ $(srcdir)/adb_openclose.c \ $(srcdir)/adb_policy.c \ $(srcdir)/kdb_db2.c \ + $(srcdir)/kdb_ext.c \ $(srcdir)/pol_xdr.c \ - $(srcdir)/db2_exp.c + $(srcdir)/db2_exp.c \ + $(srcdir)/lockout.c STOBJLISTS=OBJS.ST $(DBOBJLISTS) STLIBOBJS= \ @@ -65,8 +67,10 @@ STLIBOBJS= \ adb_openclose.o \ adb_policy.o \ kdb_db2.o \ + kdb_ext.o \ pol_xdr.o \ - db2_exp.o + db2_exp.o \ + lockout.o all-unix:: all-liblinks install-unix:: install-libs diff --git a/src/plugins/kdb/db2/db2_exp.c b/src/plugins/kdb/db2/db2_exp.c index 85864ac6fe..2e1f3b5198 100644 --- a/src/plugins/kdb/db2/db2_exp.c +++ b/src/plugins/kdb/db2/db2_exp.c @@ -197,6 +197,13 @@ WRAP_K (krb5_db2_promote_db, ( krb5_context kcontext, char *conf_section, char **db_args ), (kcontext, conf_section, db_args)); +WRAP_K (krb5_db2_invoke, + (krb5_context kcontext, + unsigned int method, + const krb5_data *request, + krb5_data *response), + (kcontext, method, request, response)); + static krb5_error_code hack_init () { @@ -256,5 +263,6 @@ kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_db2, kdb_function_table) = { /* get_master_key_list */ wrap_krb5_db2_db_get_mkey_list, /* blah blah blah */ 0,0,0,0,0,0,0,0, /* promote_db */ wrap_krb5_db2_promote_db, - 0,0,0, + 0, 0, + /* invoke */ wrap_krb5_db2_invoke }; diff --git a/src/plugins/kdb/db2/kdb_db2.c b/src/plugins/kdb/db2/kdb_db2.c index a947f2b037..b987039d15 100644 --- a/src/plugins/kdb/db2/kdb_db2.c +++ b/src/plugins/kdb/db2/kdb_db2.c @@ -1,7 +1,7 @@ /* * lib/kdb/kdb_db2.c * - * Copyright 1997,2006,2007,2008 by the Massachusetts Institute of Technology. + * Copyright 1997,2006,2007-2009 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may @@ -68,8 +68,6 @@ #define KDB_DB2_DATABASE_NAME "database_name" -#include "kdb_db2.h" - static char *gen_dbsuffix(char *, char *); static krb5_error_code krb5_db2_db_start_update(krb5_context); @@ -774,7 +772,7 @@ destroy_file_suffix(char *dbname, char *suffix) char *filename; struct stat statb; int nb, fd; - unsigned int j; + int j; off_t pos; char buf[BUFSIZ]; char zbuf[BUFSIZ]; @@ -1315,6 +1313,9 @@ krb5_db2_open(krb5_context kcontext, else if (!opt && !strcmp(val, "temporary") ) { tempdb = 1; } + else if (!opt && !strcmp(val, "merge_nra")) { + ; + } /* ignore hash argument. Might have been passed from create */ else if (!opt || strcmp(opt, "hash")) { krb5_set_error_message(kcontext, EINVAL, @@ -1394,8 +1395,9 @@ krb5_db2_create(krb5_context kcontext, char *conf_section, char **db_args) } else if (!opt && !strcmp(val, "temporary")) { tempdb = 1; - } - else if (opt && !strcmp(opt, "hash")) { + } else if (!opt && !strcmp(val, "merge_nra")) { + ; + } else if (opt && !strcmp(opt, "hash")) { flags = KRB5_KDB_CREATE_HASH; } else { krb5_set_error_message(kcontext, EINVAL, @@ -1563,7 +1565,7 @@ krb5_db2_db_set_option(krb5_context kcontext, int option, void *value) krb5_db2_context *db_ctx; kdb5_dal_handle *dal_handle; - if (!k5db2_inited(kcontext)) + if (!k5db2_inited(kcontext)) return KRB5_KDB_DBNOTINITED; dal_handle = kcontext->dal_handle; @@ -1679,6 +1681,8 @@ krb5_db2_promote_db(krb5_context kcontext, char *conf_section, char **db_args) krb5_error_code status = 0; char *db_name = NULL; char *temp_db_name = NULL; + char **db_argp; + int merge_nra = 0; krb5_clear_error_message (kcontext); @@ -1699,7 +1703,14 @@ krb5_db2_promote_db(krb5_context kcontext, char *conf_section, char **db_args) goto clean_n_exit; } - status = krb5_db2_db_rename (kcontext, temp_db_name, db_name); + for (db_argp = db_args; *db_argp; db_argp++) { + if (!strcmp(*db_argp, "merge_nra")) { + merge_nra++; + break; + } + } + + status = krb5_db2_db_rename (kcontext, temp_db_name, db_name, merge_nra); if (status) goto clean_n_exit; @@ -1711,6 +1722,154 @@ clean_n_exit: return status; } +/* + * Merge non-replicated attributes from src into dst, setting + * changed to non-zero if dst was changed. + * + * Non-replicated attributes are: last_success, last_failed, + * fail_auth_count, and any negative TL data values. + */ +static krb5_error_code +krb5_db2_merge_principal(krb5_context kcontext, + krb5_db_entry *src, + krb5_db_entry *dst, + int *changed) +{ + *changed = 0; + + if (dst->last_success != src->last_success) { + dst->last_success = src->last_success; + (*changed)++; + } + + if (dst->last_failed != src->last_failed) { + dst->last_failed = src->last_failed; + (*changed)++; + } + + if (dst->fail_auth_count != src->fail_auth_count) { + dst->fail_auth_count = src->fail_auth_count; + (*changed)++; + } + + return 0; +} + +struct nra_context { + krb5_context kcontext; + krb5_db2_context *db_context; +}; + +/* + * Iteration callback merges non-replicated attributes from + * old database. + */ +static krb5_error_code +krb5_db2_merge_nra_iterator(krb5_pointer ptr, krb5_db_entry *entry) +{ + struct nra_context *nra = (struct nra_context *)ptr; + kdb5_dal_handle *dal_handle = nra->kcontext->dal_handle; + krb5_error_code retval; + int n_entries = 0, changed; + krb5_db_entry s_entry; + krb5_boolean more; + krb5_db2_context *dst_db; + + memset(&s_entry, 0, sizeof(s_entry)); + + dst_db = dal_handle->db_context; + dal_handle->db_context = nra->db_context; + + /* look up the new principal in the old DB */ + retval = krb5_db2_db_get_principal(nra->kcontext, + entry->princ, + &s_entry, + &n_entries, + &more); + if (retval != 0 || n_entries == 0) { + /* principal may be newly created, so ignore */ + dal_handle->db_context = dst_db; + return 0; + } + + /* merge non-replicated attributes from the old entry in */ + krb5_db2_merge_principal(nra->kcontext, &s_entry, entry, &changed); + + dal_handle->db_context = dst_db; + + /* if necessary, commit the modified new entry to the new DB */ + if (changed) { + retval = krb5_db2_db_put_principal(nra->kcontext, + entry, + &n_entries, + NULL); + } else { + retval = 0; + } + + return retval; +} + +/* + * Merge non-replicated attributes (that is, lockout-related + * attributes and negative TL data types) from the old database + * into the new one. + * + * Note: src_db is locked on success. + */ +static krb5_error_code +krb5_db2_begin_nra_merge(krb5_context kcontext, + krb5_db2_context *src_db, + krb5_db2_context *dst_db) +{ + krb5_error_code retval; + kdb5_dal_handle *dal_handle = kcontext->dal_handle; + struct nra_context nra; + + nra.kcontext = kcontext; + nra.db_context = dst_db; + + assert(dal_handle->db_context == dst_db); + dal_handle->db_context = src_db; + + retval = krb5_db2_db_lock(kcontext, KRB5_LOCKMODE_EXCLUSIVE); + if (retval) { + dal_handle->db_context = dst_db; + return retval; + } + + retval = krb5_db2_db_iterate_ext(kcontext, + krb5_db2_merge_nra_iterator, + &nra, + 0, + 0); + if (retval != 0) + (void) krb5_db2_db_unlock(kcontext); + + dal_handle->db_context = dst_db; + + return retval; +} + +/* + * Finish merge of non-replicated attributes by unlocking + * src_db. + */ +static krb5_error_code +krb5_db2_end_nra_merge(krb5_context kcontext, + krb5_db2_context *src_db, + krb5_db2_context *dst_db) +{ + krb5_error_code retval; + kdb5_dal_handle *dal_handle = kcontext->dal_handle; + + dal_handle->db_context = src_db; + retval = krb5_db2_db_unlock(kcontext); + dal_handle->db_context = dst_db; + + return retval; +} + /* Retrieved from pre-DAL code base. */ /* * "Atomically" rename the database in a way that locks out read @@ -1723,12 +1882,12 @@ clean_n_exit: * have to go through the same stuff that we went through up in db_destroy. */ krb5_error_code -krb5_db2_db_rename(context, from, to) +krb5_db2_db_rename(context, from, to, merge_nra) krb5_context context; char *from; char *to; + int merge_nra; { - DB *db; char *fromok; krb5_error_code retval; krb5_db2_context *s_context, *db_ctx; @@ -1745,13 +1904,10 @@ krb5_db2_db_rename(context, from, to) * files must exist because krb5_db2_db_lock, called below, * will fail otherwise. */ - db = k5db2_dbopen(db_ctx, to, O_RDWR|O_CREAT, 0600, 0); - if (db == NULL) { - retval = errno; + retval = krb5_db2_db_create(context, to, 0); + if (retval != 0 && retval != EEXIST) goto errout; - } - else - (*db->close)(db); + /* * Set the database to the target, so that other processes sharing * the target will stop their activity, and notice the new database. @@ -1764,25 +1920,6 @@ krb5_db2_db_rename(context, from, to) if (retval) goto errout; - { - /* Ugly brute force hack. - - Should be going through nice friendly helper routines for - this, but it's a mess of jumbled so-called interfaces right - now. */ - char policy[2048], new_policy[2048]; - assert (strlen(db_ctx->db_name) < 2000); - snprintf(policy, sizeof(policy), "%s.kadm5", db_ctx->db_name); - snprintf(new_policy, sizeof(new_policy), - "%s~.kadm5", db_ctx->db_name); - if (0 != rename(new_policy, policy)) { - retval = errno; - goto errout; - } - strlcat(new_policy, ".lock",sizeof(new_policy)); - (void) unlink(new_policy); - } - db_ctx->db_lf_name = gen_dbsuffix(db_ctx->db_name, KDB2_LOCK_EXT); if (db_ctx->db_lf_name == NULL) { retval = ENOMEM; @@ -1813,6 +1950,11 @@ krb5_db2_db_rename(context, from, to) if ((retval = krb5_db2_db_start_update(context))) goto errfromok; + if (merge_nra) { + if ((retval = krb5_db2_begin_nra_merge(context, s_context, db_ctx))) + goto errfromok; + } + if (rename(from, to)) { retval = errno; goto errfromok; @@ -1821,7 +1963,35 @@ krb5_db2_db_rename(context, from, to) retval = errno; goto errfromok; } + + if (merge_nra) { + krb5_db2_end_nra_merge(context, s_context, db_ctx); + } + retval = krb5_db2_db_end_update(context); + if (retval) + goto errfromok; + + { + /* XXX moved so that NRA merge works */ + /* Ugly brute force hack. + + Should be going through nice friendly helper routines for + this, but it's a mess of jumbled so-called interfaces right + now. */ + char policy[2048], new_policy[2048]; + assert (strlen(db_ctx->db_name) < 2000); + snprintf(policy, sizeof(policy), "%s.kadm5", db_ctx->db_name); + snprintf(new_policy, sizeof(new_policy), + "%s~.kadm5", db_ctx->db_name); + if (0 != rename(new_policy, policy)) { + retval = errno; + goto errfromok; + } + strlcat(new_policy, ".lock",sizeof(new_policy)); + (void) unlink(new_policy); + } + errfromok: free_dbsuffix(fromok); errout: @@ -1839,3 +2009,4 @@ errout: return retval; } + diff --git a/src/plugins/kdb/db2/kdb_db2.h b/src/plugins/kdb/db2/kdb_db2.h index 640c4d62d3..cef7b648a8 100644 --- a/src/plugins/kdb/db2/kdb_db2.h +++ b/src/plugins/kdb/db2/kdb_db2.h @@ -71,7 +71,8 @@ krb5_error_code krb5_db2_db_destroy krb5_error_code krb5_db2_db_rename (krb5_context, char *, - char * ); + char *, + int ); krb5_error_code krb5_db2_db_get_principal (krb5_context, krb5_const_principal, @@ -219,4 +220,23 @@ void krb5_db2_free_policy( krb5_context kcontext, /* Thread-safety wrapper slapped on top of original implementation. */ extern k5_mutex_t *krb5_db2_mutex; +/* lockout */ +krb5_error_code +krb5_db2_lockout_check_policy(krb5_context context, + krb5_db_entry *entry, + krb5_timestamp stamp); + +krb5_error_code +krb5_db2_lockout_audit(krb5_context context, + krb5_db_entry *entry, + krb5_timestamp stamp, + krb5_error_code status); + +/* methods */ +krb5_error_code +krb5_db2_invoke(krb5_context context, + unsigned int method, + const krb5_data *req, + krb5_data *rep); + #endif /* KRB5_KDB_DB2_H */ diff --git a/src/plugins/kdb/db2/kdb_ext.c b/src/plugins/kdb/db2/kdb_ext.c new file mode 100644 index 0000000000..9d73966b4d --- /dev/null +++ b/src/plugins/kdb/db2/kdb_ext.c @@ -0,0 +1,99 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* + * plugins/kdb/db2/kdb_ext.c + * + * Copyright (C) 2009 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. + * + * + * + */ + +#include "k5-int.h" +#include "kdb.h" +#include +#include +#include +#include "kdb_db2.h" + +static krb5_error_code +krb5_db2_check_policy_as(krb5_context context, + unsigned int method, + const krb5_data *request, + krb5_data *response) +{ + const kdb_check_policy_as_req *req; + kdb_check_policy_as_rep *rep; + krb5_error_code code; + + req = (const kdb_check_policy_as_req *)request->data; + rep = (kdb_check_policy_as_rep *)response->data; + + rep->status = NULL; + + code = krb5_db2_lockout_check_policy(context, req->client, + req->kdc_time); + if (code == KRB5KDC_ERR_CLIENT_REVOKED) + rep->status = "LOCKED_OUT"; + + return code; +} + +static krb5_error_code +krb5_db2_audit_as(krb5_context context, + unsigned int method, + const krb5_data *request, + krb5_data *response) +{ + const kdb_audit_as_req *req; + krb5_error_code code; + + req = (const kdb_audit_as_req *)request->data; + + code = krb5_db2_lockout_audit(context, req->client, + req->authtime, req->error_code); + + return code; +} + +krb5_error_code +krb5_db2_invoke(krb5_context context, + unsigned int method, + const krb5_data *req, + krb5_data *rep) +{ + krb5_error_code code = KRB5_KDB_DBTYPE_NOSUP; + + switch (method) { + case KRB5_KDB_METHOD_CHECK_POLICY_AS: + code = krb5_db2_check_policy_as(context, method, req, rep); + break; + case KRB5_KDB_METHOD_AUDIT_AS: + code = krb5_db2_audit_as(context, method, req, rep); + break; + default: + break; + } + + return code; +} + diff --git a/src/plugins/kdb/db2/lockout.c b/src/plugins/kdb/db2/lockout.c new file mode 100644 index 0000000000..1e6602dcca --- /dev/null +++ b/src/plugins/kdb/db2/lockout.c @@ -0,0 +1,196 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* + * plugins/kdb/db2/lockout.c + * + * Copyright (C) 2009 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. + * + * + * + */ + +#include "k5-int.h" +#include "kdb.h" +#include +#include +#include +#include "kdb_db2.h" + +/* + * Helper routines for databases that wish to use the default + * principal lockout functionality. + */ + +static krb5_error_code +lookup_lockout_policy(krb5_context context, + krb5_db_entry *entry, + krb5_kvno *pw_max_fail, + krb5_deltat *pw_failcnt_interval, + krb5_deltat *pw_lockout_duration) +{ + krb5_tl_data tl_data; + krb5_error_code code; + osa_princ_ent_rec adb; + XDR xdrs; + + *pw_max_fail = 0; + *pw_failcnt_interval = 0; + *pw_lockout_duration = 0; + + tl_data.tl_data_type = KRB5_TL_KADM_DATA; + + code = krb5_dbe_lookup_tl_data(context, entry, &tl_data); + if (code != 0 || tl_data.tl_data_length == 0) + return code; + + memset(&adb, 0, sizeof(adb)); + xdrmem_create(&xdrs, (char *)tl_data.tl_data_contents, + tl_data.tl_data_length, XDR_DECODE); + if (!xdr_osa_princ_ent_rec(&xdrs, &adb)) { + xdr_destroy(&xdrs); + return KADM5_XDR_FAILURE; + } + + if (adb.policy != NULL) { + osa_policy_ent_t policy = NULL; + int count = 0; + + code = krb5_db2_get_policy(context, adb.policy, + &policy, &count); + if (code == 0 && count == 1) { + *pw_max_fail = policy->pw_max_fail; + *pw_failcnt_interval = policy->pw_failcnt_interval; + *pw_lockout_duration = policy->pw_lockout_duration; + } + if (policy != NULL) + krb5_db2_free_policy(context, policy); + } + + xdr_destroy(&xdrs); + + xdrmem_create(&xdrs, NULL, 0, XDR_FREE); + xdr_osa_princ_ent_rec(&xdrs, &adb); + xdr_destroy(&xdrs); + + return 0; +} + +/* draft-behera-ldap-password-policy-10.txt 7.1 */ +static krb5_boolean +locked_check_p(krb5_context context, + krb5_timestamp stamp, + krb5_kvno max_fail, + krb5_timestamp lockout_duration, + krb5_db_entry *entry) +{ + if (max_fail == 0 || entry->fail_auth_count < max_fail) + return FALSE; + + if (lockout_duration == 0) + return TRUE; /* principal permanently locked */ + + return (stamp < entry->last_failed + lockout_duration); +} + +krb5_error_code +krb5_db2_lockout_check_policy(krb5_context context, + krb5_db_entry *entry, + krb5_timestamp stamp) +{ + krb5_error_code code; + krb5_kvno max_fail = 0; + krb5_deltat failcnt_interval = 0; + krb5_deltat lockout_duration = 0; + + code = lookup_lockout_policy(context, entry, &max_fail, + &failcnt_interval, + &lockout_duration); + if (code != 0) + return code; + + if (locked_check_p(context, stamp, max_fail, lockout_duration, entry)) + return KRB5KDC_ERR_CLIENT_REVOKED; + + return 0; +} + +krb5_error_code +krb5_db2_lockout_audit(krb5_context context, + krb5_db_entry *entry, + krb5_timestamp stamp, + krb5_error_code status) +{ + krb5_error_code code; + krb5_kvno max_fail = 0; + krb5_deltat failcnt_interval = 0; + krb5_deltat lockout_duration = 0; + int nentries = 1; + + switch (status) { + case 0: + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + break; +#if 0 + case KRB5KDC_ERR_CLIENT_REVOKED: + break; +#endif + default: + return 0; + } + + code = lookup_lockout_policy(context, entry, &max_fail, + &failcnt_interval, + &lockout_duration); + if (code != 0) + return code; + + assert (!locked_check_p(context, stamp, max_fail, lockout_duration, entry)); + + if (status == 0 && (entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH)) { + /* + * Only mark the authentication as successful if the entry + * required preauthentication, otherwise we have no idea. + */ + entry->fail_auth_count = 0; + entry->last_success = stamp; + } else if (status == KRB5KDC_ERR_PREAUTH_FAILED || + status == KRB5KRB_AP_ERR_BAD_INTEGRITY) { + if (failcnt_interval != 0 && + stamp > entry->last_failed + failcnt_interval) { + /* Reset fail_auth_count after failcnt_interval */ + entry->fail_auth_count = 0; + } + + entry->last_failed = stamp; + entry->fail_auth_count++; + } else + return 0; /* nothing to do */ + + code = krb5_db2_db_put_principal(context, entry, + &nentries, NULL); + if (code != 0) + return code; + + return 0; +} + diff --git a/src/plugins/kdb/db2/pol_xdr.c b/src/plugins/kdb/db2/pol_xdr.c index 82e29b8046..31856fbd2a 100644 --- a/src/plugins/kdb/db2/pol_xdr.c +++ b/src/plugins/kdb/db2/pol_xdr.c @@ -51,14 +51,27 @@ bool_t xdr_nullstring(XDR *xdrs, char **objp) return FALSE; } +static int +osa_policy_min_vers(osa_policy_ent_t objp) +{ + int vers; + + if (objp->pw_max_fail || + objp->pw_failcnt_interval || + objp->pw_lockout_duration) + vers = OSA_ADB_POLICY_VERSION_2; + else + vers = OSA_ADB_POLICY_VERSION_1; + return vers; +} bool_t xdr_osa_policy_ent_rec(XDR *xdrs, osa_policy_ent_t objp) { switch (xdrs->x_op) { case XDR_ENCODE: - objp->version = OSA_ADB_POLICY_VERSION_1; + objp->version = osa_policy_min_vers(objp); /* fall through */ case XDR_FREE: if (!xdr_int(xdrs, &objp->version)) @@ -67,7 +80,8 @@ xdr_osa_policy_ent_rec(XDR *xdrs, osa_policy_ent_t objp) case XDR_DECODE: if (!xdr_int(xdrs, &objp->version)) return FALSE; - if (objp->version != OSA_ADB_POLICY_VERSION_1) + if (objp->version != OSA_ADB_POLICY_VERSION_1 && + objp->version != OSA_ADB_POLICY_VERSION_2) return FALSE; break; } @@ -86,5 +100,13 @@ xdr_osa_policy_ent_rec(XDR *xdrs, osa_policy_ent_t objp) return (FALSE); if (!xdr_u_int32(xdrs, &objp->policy_refcnt)) return (FALSE); + if (objp->version > OSA_ADB_POLICY_VERSION_1) { + if (!xdr_u_int32(xdrs, &objp->pw_max_fail)) + return (FALSE); + if (!xdr_u_int32(xdrs, &objp->pw_failcnt_interval)) + return (FALSE); + if (!xdr_u_int32(xdrs, &objp->pw_lockout_duration)) + return (FALSE); + } return (TRUE); } diff --git a/src/plugins/kdb/db2/policy_db.h b/src/plugins/kdb/db2/policy_db.h index 11fece3df2..d841d7376f 100644 --- a/src/plugins/kdb/db2/policy_db.h +++ b/src/plugins/kdb/db2/policy_db.h @@ -39,6 +39,7 @@ typedef long osa_adb_ret_t; #define OSA_ADB_POLICY_VERSION_MASK 0x12345D00 #define OSA_ADB_POLICY_VERSION_1 0x12345D01 +#define OSA_ADB_POLICY_VERSION_2 0x12345D02 diff --git a/src/plugins/kdb/ldap/ldap_exp.c b/src/plugins/kdb/ldap/ldap_exp.c index 18a89fd619..742e3ce507 100644 --- a/src/plugins/kdb/ldap/ldap_exp.c +++ b/src/plugins/kdb/ldap/ldap_exp.c @@ -87,6 +87,10 @@ kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_ldap, kdb_function_table) = { /* fetch_master_key_list */ NULL, /* store_master_key_list */ NULL, /* Search enc type */ NULL, - /* Change pwd */ NULL + /* Change pwd */ NULL, + /* promote_db */ NULL, + /* dbekd_decrypt_key_data */ NULL, + /* dbekd_encrypt_key_data */ NULL, + /* db_invoke */ krb5_ldap_invoke, }; diff --git a/src/plugins/kdb/ldap/libkdb_ldap/Makefile.in b/src/plugins/kdb/ldap/libkdb_ldap/Makefile.in index 8479fb6fcb..4306da102d 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/Makefile.in +++ b/src/plugins/kdb/ldap/libkdb_ldap/Makefile.in @@ -54,8 +54,10 @@ SRCS= $(srcdir)/kdb_ldap.c \ $(srcdir)/princ_xdr.c \ $(srcdir)/ldap_fetch_mkey.c \ $(srcdir)/ldap_service_stash.c \ + $(srcdir)/kdb_ext.c \ $(srcdir)/kdb_xdr.c \ - $(srcdir)/ldap_err.c + $(srcdir)/ldap_err.c \ + $(srcdir)/lockout.c \ STOBJLISTS=OBJS.ST STLIBOBJS= kdb_ldap.o \ @@ -74,8 +76,10 @@ STLIBOBJS= kdb_ldap.o \ princ_xdr.o \ ldap_fetch_mkey.o \ ldap_service_stash.o \ + kdb_ext.o \ kdb_xdr.o \ - ldap_err.o + ldap_err.o \ + lockout.o all-unix:: all-liblinks install-unix:: install-libs diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ext.c b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ext.c new file mode 100644 index 0000000000..0009b59d68 --- /dev/null +++ b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ext.c @@ -0,0 +1,99 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* + * plugins/kdb/ldap/kdb_ext.c + * + * Copyright (C) 2009 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. + * + * + * + */ + +#include "k5-int.h" +#include "kdb.h" +#include +#include +#include +#include "kdb_ldap.h" + +static krb5_error_code +krb5_ldap_check_policy_as(krb5_context context, + unsigned int method, + const krb5_data *request, + krb5_data *response) +{ + const kdb_check_policy_as_req *req; + kdb_check_policy_as_rep *rep; + krb5_error_code code; + + req = (const kdb_check_policy_as_req *)request->data; + rep = (kdb_check_policy_as_rep *)response->data; + + rep->status = NULL; + + code = krb5_ldap_lockout_check_policy(context, req->client, + req->kdc_time); + if (code == KRB5KDC_ERR_CLIENT_REVOKED) + rep->status = "LOCKED_OUT"; + + return code; +} + +static krb5_error_code +krb5_ldap_audit_as(krb5_context context, + unsigned int method, + const krb5_data *request, + krb5_data *response) +{ + const kdb_audit_as_req *req; + krb5_error_code code; + + req = (const kdb_audit_as_req *)request->data; + + code = krb5_ldap_lockout_audit(context, req->client, + req->authtime, req->error_code); + + return code; +} + +krb5_error_code +krb5_ldap_invoke(krb5_context context, + unsigned int method, + const krb5_data *req, + krb5_data *rep) +{ + krb5_error_code code = KRB5_KDB_DBTYPE_NOSUP; + + switch (method) { + case KRB5_KDB_METHOD_CHECK_POLICY_AS: + code = krb5_ldap_check_policy_as(context, method, req, rep); + break; + case KRB5_KDB_METHOD_AUDIT_AS: + code = krb5_ldap_audit_as(context, method, req, rep); + break; + default: + break; + } + + return code; +} + diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.c b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.c index 4b8fcb2b5d..0d86801d64 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.c @@ -162,69 +162,68 @@ cleanup: } -/* Function to check if a LDAP server supports the SASL external mechanism - *Return values: - * 0 => supports - * 1 => does not support - * 2 => don't know +/* + * Interrogate the root DSE (zero length DN) for an attribute + * value assertion. */ -#define ERR_MSG1 "Unable to check if SASL EXTERNAL mechanism is supported by LDAP server. Proceeding anyway ..." -#define ERR_MSG2 "SASL EXTERNAL mechanism not supported by LDAP server. Can't perform certificate-based bind." - -int -has_sasl_external_mech(context, ldap_server) +static int +has_rootdse_ava(context, ldap_server, attribute, value) krb5_context context; char *ldap_server; + char *attribute; + char *value; { int i=0, flag=0, ret=0, retval=0; - char *attrs[]={"supportedSASLMechanisms", NULL}, **values=NULL; + char *attrs[2], **values=NULL; LDAP *ld=NULL; LDAPMessage *msg=NULL, *res=NULL; + struct berval cred; + + attrs[0] = attribute; + attrs[1] = NULL; retval = ldap_initialize(&ld, ldap_server); if (retval != LDAP_SUCCESS) { - krb5_set_error_message(context, 2, "%s", ERR_MSG1); ret = 2; /* Don't know */ goto cleanup; } + cred.bv_val = ""; + cred.bv_len = 0; + /* Anonymous bind */ - retval = ldap_sasl_bind_s(ld, NULL, NULL, NULL, NULL, NULL, NULL); + retval = ldap_sasl_bind_s(ld, "", NULL, &cred, NULL, NULL, NULL); if (retval != LDAP_SUCCESS) { - krb5_set_error_message(context, 2, "%s", ERR_MSG1); ret = 2; /* Don't know */ goto cleanup; } retval = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE, NULL, attrs, 0, NULL, NULL, NULL, 0, &res); if (retval != LDAP_SUCCESS) { - krb5_set_error_message(context, 2, "%s", ERR_MSG1); ret = 2; /* Don't know */ goto cleanup; } msg = ldap_first_message(ld, res); if (msg == NULL) { - krb5_set_error_message(context, 2, "%s", ERR_MSG1); ret = 2; /* Don't know */ goto cleanup; } - values = ldap_get_values(ld, msg, "supportedSASLMechanisms"); + values = ldap_get_values(ld, msg, attribute); if (values == NULL) { - krb5_set_error_message(context, 1, "%s", ERR_MSG2); ret = 1; /* Not supported */ goto cleanup; } for (i = 0; values[i] != NULL; i++) { - if (strcmp(values[i], "EXTERNAL")) - continue; - flag = 1; + if (strcmp(values[i], value) == 0) { + flag = 1; + break; + } } if (flag != 1) { - krb5_set_error_message(context, 1, "%s", ERR_MSG2); ret = 1; /* Not supported */ goto cleanup; } @@ -243,6 +242,47 @@ cleanup: return ret; } +#define ERR_MSG1 "Unable to check if SASL EXTERNAL mechanism is supported by LDAP server. Proceeding anyway ..." +#define ERR_MSG2 "SASL EXTERNAL mechanism not supported by LDAP server. Can't perform certificate-based bind." + +/* Function to check if a LDAP server supports the SASL external mechanism + *Return values: + * 0 => supports + * 1 => does not support + * 2 => don't know + */ +int +has_sasl_external_mech(context, ldap_server) + krb5_context context; + char *ldap_server; +{ + int ret; + + ret = has_rootdse_ava(context, ldap_server, + "supportedSASLMechanisms", "EXTERNAL"); + switch (ret) { + case 1: /* not supported */ + krb5_set_error_message(context, 1, "%s", ERR_MSG2); + break; + case 2: /* don't know */ + krb5_set_error_message(context, 1, "%s", ERR_MSG1); + break; + default: + break; + } + + return ret; +} + +int +has_modify_increment(context, ldap_server) + krb5_context context; + char *ldap_server; +{ + return has_rootdse_ava(context, ldap_server, + "supportedFeatures", "1.3.6.1.1.14"); +} + void * krb5_ldap_alloc(krb5_context context, void *ptr, size_t size) { return realloc(ptr, size); diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h index 802ab0fc36..d14b48bf97 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h +++ b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.h @@ -197,6 +197,7 @@ struct _krb5_ldap_server_info { #ifdef HAVE_EDIRECTORY char *root_certificate_file; #endif + int modify_increment; struct _krb5_ldap_server_info *next; }; @@ -291,6 +292,9 @@ krb5_ldap_read_startup_information(krb5_context ); int has_sasl_external_mech(krb5_context, char *); +int +has_modify_increment(krb5_context, char *); + krb5_error_code krb5_ldap_free_server_context_params(krb5_ldap_context *ldap_context); @@ -328,4 +332,23 @@ int ldap_unbind_ext_s(LDAP *, LDAPControl **, LDAPControl **); #endif +/* lockout.c */ +krb5_error_code +krb5_ldap_lockout_check_policy(krb5_context context, + krb5_db_entry *entry, + krb5_timestamp stamp); + +krb5_error_code +krb5_ldap_lockout_audit(krb5_context context, + krb5_db_entry *entry, + krb5_timestamp stamp, + krb5_error_code status); + +/* kdb_ext.c */ +krb5_error_code +krb5_ldap_invoke(krb5_context context, + unsigned int method, + const krb5_data *req, + krb5_data *rep); + #endif diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c index fdc5d10c77..db1f76a69e 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c @@ -229,6 +229,13 @@ krb5_ldap_db_init(krb5_context context, krb5_ldap_context *ldap_context) krb5_clear_error_message(context); +#ifdef LDAP_MOD_INCREMENT + server_info->modify_increment = + (has_modify_increment(context, server_info->server_name) == 0); +#else + server_info->modify_increment = 0; +#endif /* LDAP_MOD_INCREMENT */ + for (conns=0; conns < ldap_context->max_server_conns; ++conns) { if ((st=krb5_ldap_initialize(ldap_context, server_info)) != 0) break; diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif b/src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif index 0bbdcf8783..fd226b13d3 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif +++ b/src/plugins/kdb/ldap/libkdb_ldap/kerberos.ldif @@ -337,6 +337,42 @@ attributetypes: ( 2.16.840.1.113719.1.301.4.34.1 SINGLE-VALUE) +##### Number of consecutive pre-authentication failures before lockout + +dn: cn=schema +changetype: modify +add: attributetypes +attributetypes: ( 1.3.6.1.4.1.5322.21.2.1 + NAME 'krbPwdMaxFailure' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE) + + +##### Period after which bad preauthentication count will be reset + +dn: cn=schema +changetype: modify +add: attributetypes +attributetypes: ( 1.3.6.1.4.1.5322.21.2.2 + NAME 'krbPwdFailureCountInterval' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE) + + +##### Period in which lockout is enforced + +dn: cn=schema +changetype: modify +add: attributetypes +attributetypes: ( 1.3.6.1.4.1.5322.21.2.3 + NAME 'krbPwdLockoutDuration' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE) + + ##### FDN pointing to a Kerberos Password Policy object dn: cn=schema @@ -760,7 +796,7 @@ objectClasses: ( 2.16.840.1.113719.1.301.6.14.1 NAME 'krbPwdPolicy' SUP top MUST ( cn ) - MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffChars $ krbPwdMinLength $ krbPwdHistoryLength ) ) + MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffChars $ krbPwdMinLength $ krbPwdHistoryLength $ krbPwdMaxFailure $ krbPwdFailureCountInterval $ krbPwdLockoutDuration ) ) ##### The krbTicketPolicyAux holds Kerberos ticket policy attributes. diff --git a/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema b/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema index 9352cf1e46..9525e60d62 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema +++ b/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema @@ -270,6 +270,33 @@ attributetype ( 2.16.840.1.113719.1.301.4.34.1 SINGLE-VALUE) +##### Number of consecutive pre-authentication failures before lockout + +attributetype ( 1.3.6.1.4.1.5322.21.2.1 + NAME 'krbPwdMaxFailure' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE) + + +##### Period after which bad preauthentication count will be reset + +attributetype ( 1.3.6.1.4.1.5322.21.2.2 + NAME 'krbPwdFailureCountInterval' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE) + + +##### Period in which lockout is enforced + +attributetype ( 1.3.6.1.4.1.5322.21.2.3 + NAME 'krbPwdLockoutDuration' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE) + + ##### FDN pointing to a Kerberos Password Policy object attributetype ( 2.16.840.1.113719.1.301.4.36.1 @@ -506,7 +533,6 @@ attributetype ( 2.16.840.1.113719.1.301.4.53.1 EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12) - ######################################################################## ######################################################################## # Object Class Definitions # @@ -616,7 +642,7 @@ objectclass ( 2.16.840.1.113719.1.301.6.14.1 NAME 'krbPwdPolicy' SUP top MUST ( cn ) - MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffChars $ krbPwdMinLength $ krbPwdHistoryLength ) ) + MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffChars $ krbPwdMinLength $ krbPwdHistoryLength $ krbPwdMaxFailure $ krbPwdFailureCountInterval $ krbPwdLockoutDuration ) ) ##### The krbTicketPolicyAux holds Kerberos ticket policy attributes. diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h index 502e71ccd5..abc27f1148 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h @@ -81,6 +81,15 @@ #define KDB_LAST_SUCCESS_ATTR 0x000800 #define KDB_LAST_FAILED_ATTR 0x001000 #define KDB_FAIL_AUTH_COUNT_ATTR 0x002000 +#define KDB_LOCKED_TIME_ATTR 0x004000 + +/* + * This is a private contract between krb5_ldap_lockout_audit() + * and krb5_ldap_put_principal(). If present, it means that the + * krbPwdMaxFailure attribute should be incremented by one. + */ +#define KADM5_FAIL_AUTH_COUNT_INCREMENT 0x080000 /* KADM5_CPW_FUNCTION */ + extern struct timeval timeout; extern char *policyclass[]; diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c index 03c3da48d7..f81d399041 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c @@ -863,7 +863,7 @@ krb5_ldap_put_principal(context, entries, nentries, db_args) establish_links = TRUE; } - if ((entries->last_success)!=0) { + if (entries->mask & KADM5_LAST_SUCCESS) { memset(strval, 0, sizeof(strval)); if ((strval[0]=getstringtime(entries->last_success)) == NULL) goto cleanup; @@ -874,7 +874,7 @@ krb5_ldap_put_principal(context, entries, nentries, db_args) free (strval[0]); } - if (entries->last_failed!=0) { + if (entries->mask & KADM5_LAST_FAILED) { memset(strval, 0, sizeof(strval)); if ((strval[0]=getstringtime(entries->last_failed)) == NULL) goto cleanup; @@ -885,9 +885,58 @@ krb5_ldap_put_principal(context, entries, nentries, db_args) free(strval[0]); } - if (entries->fail_auth_count!=0) { - if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", LDAP_MOD_REPLACE, entries->fail_auth_count)) !=0) + if (entries->mask & KADM5_FAIL_AUTH_COUNT) { + krb5_kvno fail_auth_count; + + fail_auth_count = entries->fail_auth_count; + if (entries->mask & KADM5_FAIL_AUTH_COUNT_INCREMENT) + fail_auth_count++; + + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_REPLACE, + fail_auth_count); + if (st != 0) goto cleanup; + } else if (entries->mask & KADM5_FAIL_AUTH_COUNT_INCREMENT) { + /* + * If the client library and server supports RFC 4525, + * then use it to increment by one the value of the + * krbLoginFailedCount attribute. Otherwise, assert the + * (provided) old value by deleting it before adding. + */ +#ifdef LDAP_MOD_INCREMENT + if (ldap_server_handle->server_info->modify_increment) { + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_INCREMENT, 1); + if (st != 0) + goto cleanup; + } else +#endif /* LDAP_MOD_INCREMENT */ + if (entries->fail_auth_count == 0) { + /* + * Unfortunately we have no way of distinguishing between + * an absent and a zero-valued attribute by the time we are + * called here. So, although this creates a race condition, + * it appears impossible to assert the old value as that + * would fail were the attribute absent. + */ + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_REPLACE, 1); + if (st != 0) + goto cleanup; + } else { + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_DELETE, + entries->fail_auth_count); + if (st != 0) + goto cleanup; + + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_ADD, + entries->fail_auth_count + 1); + if (st != 0) + goto cleanup; + } } if (entries->mask & KADM5_MAX_LIFE) { @@ -1180,6 +1229,9 @@ krb5_ldap_put_principal(context, entries, nentries, db_args) krb5_set_error_message(context, st, "%s", errbuf); goto cleanup; } + + if (entries->mask & KADM5_FAIL_AUTH_COUNT_INCREMENT) + entries->fail_auth_count++; } } diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_pwd_policy.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_pwd_policy.c index 94d461b29f..ed63e0812e 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_pwd_policy.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_pwd_policy.c @@ -39,7 +39,9 @@ static char *password_policy_attributes[] = { "cn", "krbmaxpwdlife", "krbminpwdlife", "krbpwdmindiffchars", "krbpwdminlength", - "krbpwdhistorylength", NULL }; + "krbpwdhistorylength", "krbpwdmaxfailure", + "krbpwdfailurecountinterval", + "krbpwdlockoutduration", NULL }; /* * Function to create password policy object. @@ -97,7 +99,13 @@ krb5_ldap_create_password_policy (context, policy) || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdminlength", LDAP_MOD_ADD, (signed) policy->pw_min_length)) != 0) || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdhistorylength", LDAP_MOD_ADD, - (signed) policy->pw_history_num)) != 0)) + (signed) policy->pw_history_num)) != 0) + || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdmaxfailure", LDAP_MOD_ADD, + (signed) policy->pw_max_fail)) != 0) + || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdfailurecountinterval", LDAP_MOD_ADD, + (signed) policy->pw_failcnt_interval)) != 0) + || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdlockoutduration", LDAP_MOD_ADD, + (signed) policy->pw_lockout_duration)) != 0)) goto cleanup; /* password policy object creation */ @@ -157,7 +165,13 @@ krb5_ldap_put_password_policy (context, policy) || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdminlength", LDAP_MOD_REPLACE, (signed) policy->pw_min_length)) != 0) || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdhistorylength", LDAP_MOD_REPLACE, - (signed) policy->pw_history_num)) != 0)) + (signed) policy->pw_history_num)) != 0) + || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdmaxfailure", LDAP_MOD_REPLACE, + (signed) policy->pw_max_fail)) != 0) + || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdfailurecountinterval", LDAP_MOD_REPLACE, + (signed) policy->pw_failcnt_interval)) != 0) + || ((st=krb5_add_int_mem_ldap_mod(&mods, "krbpwdlockoutduration", LDAP_MOD_REPLACE, + (signed) policy->pw_lockout_duration)) != 0)) goto cleanup; /* modify the password policy object. */ @@ -199,6 +213,10 @@ populate_policy(krb5_context context, krb5_ldap_get_value(ld, ent, "krbpwdminlength", &(pol_entry->pw_min_length)); krb5_ldap_get_value(ld, ent, "krbpwdhistorylength", &(pol_entry->pw_history_num)); + krb5_ldap_get_value(ld, ent, "krbpwdmaxfailure", &(pol_entry->pw_max_fail)); + krb5_ldap_get_value(ld, ent, "krbpwdfailurecountinterval", &(pol_entry->pw_failcnt_interval)); + krb5_ldap_get_value(ld, ent, "krbpwdlockoutduration", &(pol_entry->pw_lockout_duration)); + /* Get the reference count */ pol_dn = ldap_get_dn(ld, ent); st = krb5_ldap_get_reference_count (context, pol_dn, "krbPwdPolicyReference", diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c index 23bb3dbeb2..d670f59fb7 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_service_rights.c @@ -87,6 +87,9 @@ static char *adminrights_subtree[][2]={ {"2#subtree#","#krbLastFailedAuth"}, {"2#subtree#","#krbLoginFailedCount"}, {"2#subtree#","#krbLastSuccessfulAuth"}, + {"6#subtree#","#krbPwdMaxFailure"}, + {"6#subtree#","#krbPwdFailureCountInterval"}, + {"6#subtree#","#krbPwdLockoutDuration"}, { "","" } }; @@ -116,6 +119,9 @@ static char *pwdrights_subtree[][2] = { {"2#subtree#","#krbLastFailedAuth"}, {"2#subtree#","#krbLoginFailedCount"}, {"2#subtree#","#krbLastSuccessfulAuth"}, + {"2#subtree#","#krbPwdMaxFailure"}, + {"2#subtree#","#krbPwdFailureCountInterval"}, + {"2#subtree#","#krbPwdLockoutDuration"}, { "", "" } }; @@ -187,6 +193,9 @@ static char *adminrights_realmcontainer[][2]={ {"2#subtree#","#krbLastFailedAuth"}, {"2#subtree#","#krbLoginFailedCount"}, {"2#subtree#","#krbLastSuccessfulAuth"}, + {"6#subtree#","#krbPwdMaxFailure"}, + {"6#subtree#","#krbPwdFailureCountInterval"}, + {"6#subtree#","#krbPwdLockoutDuration"}, { "","" } }; @@ -225,6 +234,9 @@ static char *pwdrights_realmcontainer[][2]={ {"2#subtree#","#krbLastFailedAuth"}, {"2#subtree#","#krbLoginFailedCount"}, {"2#subtree#","#krbLastSuccessfulAuth"}, + {"2#subtree#","#krbPwdMaxFailure"}, + {"2#subtree#","#krbPwdFailureCountInterval"}, + {"2#subtree#","#krbPwdLockoutDuration"}, { "", "" } }; diff --git a/src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports b/src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports index a0d94f6740..1ec9a39070 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports +++ b/src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports @@ -51,3 +51,4 @@ krb5_ldap_release_errcode_string krb5_ldap_create krb5_ldap_set_mkey_list krb5_ldap_get_mkey_list +krb5_ldap_invoke diff --git a/src/plugins/kdb/ldap/libkdb_ldap/lockout.c b/src/plugins/kdb/ldap/libkdb_ldap/lockout.c new file mode 100644 index 0000000000..6b2d49e82b --- /dev/null +++ b/src/plugins/kdb/ldap/libkdb_ldap/lockout.c @@ -0,0 +1,192 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* + * plugins/kdb/ldap/lockout.c + * + * Copyright (C) 2009 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. + * + * + * + */ + +#include +#include + +#include +#include +#include + +#include "kdb_ldap.h" +#include "princ_xdr.h" +#include "ldap_principal.h" +#include "ldap_pwd_policy.h" +#include "ldap_tkt_policy.h" + +static krb5_error_code +lookup_lockout_policy(krb5_context context, + krb5_db_entry *entry, + krb5_kvno *pw_max_fail, + krb5_deltat *pw_failcnt_interval, + krb5_deltat *pw_lockout_duration) +{ + krb5_tl_data tl_data; + krb5_error_code code; + osa_princ_ent_rec adb; + XDR xdrs; + + *pw_max_fail = 0; + *pw_failcnt_interval = 0; + *pw_lockout_duration = 0; + + tl_data.tl_data_type = KRB5_TL_KADM_DATA; + + code = krb5_dbe_lookup_tl_data(context, entry, &tl_data); + if (code != 0 || tl_data.tl_data_length == 0) + return code; + + memset(&adb, 0, sizeof(adb)); + + code = krb5_lookup_tl_kadm_data(&tl_data, &adb); + if (code != 0) + return code; + + if (adb.policy != NULL) { + osa_policy_ent_t policy = NULL; + int count = 0; + + code = krb5_ldap_get_password_policy(context, adb.policy, + &policy, &count); + if (code == 0 && count == 1) { + *pw_max_fail = policy->pw_max_fail; + *pw_failcnt_interval = policy->pw_failcnt_interval; + *pw_lockout_duration = policy->pw_lockout_duration; + } + krb5_ldap_free_password_policy(context, policy); + } + + xdrmem_create(&xdrs, NULL, 0, XDR_FREE); + ldap_xdr_osa_princ_ent_rec(&xdrs, &adb); + xdr_destroy(&xdrs); + + return 0; +} + +/* draft-behera-ldap-password-policy-10.txt 7.1 */ +static krb5_boolean +locked_check_p(krb5_context context, + krb5_timestamp stamp, + krb5_kvno max_fail, + krb5_timestamp lockout_duration, + krb5_db_entry *entry) +{ + if (max_fail == 0 || entry->fail_auth_count < max_fail) + return FALSE; + + if (lockout_duration == 0) + return TRUE; /* principal permanently locked */ + + return (stamp < entry->last_failed + lockout_duration); +} + +krb5_error_code +krb5_ldap_lockout_check_policy(krb5_context context, + krb5_db_entry *entry, + krb5_timestamp stamp) +{ + krb5_error_code code; + krb5_kvno max_fail = 0; + krb5_deltat failcnt_interval = 0; + krb5_deltat lockout_duration = 0; + + code = lookup_lockout_policy(context, entry, &max_fail, + &failcnt_interval, + &lockout_duration); + if (code != 0 || failcnt_interval == 0) + return code; + + if (locked_check_p(context, stamp, max_fail, lockout_duration, entry)) + return KRB5KDC_ERR_CLIENT_REVOKED; + + return 0; +} + +krb5_error_code +krb5_ldap_lockout_audit(krb5_context context, + krb5_db_entry *entry, + krb5_timestamp stamp, + krb5_error_code status) +{ + krb5_error_code code; + krb5_kvno max_fail = 0; + krb5_deltat failcnt_interval = 0; + krb5_deltat lockout_duration = 0; + int nentries = 1; + + switch (status) { + case 0: + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + break; + default: + return 0; + } + + code = lookup_lockout_policy(context, entry, &max_fail, + &failcnt_interval, + &lockout_duration); + if (code != 0) + return code; + + entry->mask = 0; + + assert (!locked_check_p(context, stamp, max_fail, lockout_duration, entry)); + + if (status == 0 && (entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH)) { + /* + * Only mark the authentication as successful if the entry + * required preauthentication, otherwise we have no idea. + */ + entry->fail_auth_count = 0; + entry->last_success = stamp; + entry->mask |= KADM5_FAIL_AUTH_COUNT | KADM5_LAST_SUCCESS; + } else if (status == KRB5KDC_ERR_PREAUTH_FAILED || + status == KRB5KRB_AP_ERR_BAD_INTEGRITY) { + if (failcnt_interval != 0 && + stamp > entry->last_failed + failcnt_interval) { + /* Reset fail_auth_count after failcnt_interval */ + entry->fail_auth_count = 0; + entry->mask |= KADM5_FAIL_AUTH_COUNT; + } + + entry->last_failed = stamp; + entry->mask |= KADM5_LAST_FAILED | KADM5_FAIL_AUTH_COUNT_INCREMENT; + } + + if (entry->mask) { + code = krb5_ldap_put_principal(context, entry, + &nentries, NULL); + if (code != 0) + return code; + } + + return 0; +} -- cgit