Commit 50c9870a authored by Russ Allbery's avatar Russ Allbery

Add support for ad_base_instance

Add a new string krb5.conf option, ad_base_instance, which, if set,
changes the way that password synchronization is handled.  When this
option is set, the password for the principal formed by appending that
instance to a base principal is propagated to Active Directory as the
password for the base principal.  So, for instance, if this is set to
the string "windows", the password of the principal "user/windows" is
propagated to Active Directory as the password for the principal
"user" and password changes for the principal "user" are ignored.
This special behavior only happens if "user/windows" exists in the
local Kerberos KDC database; if not, password propagation for the
principal "user" happens normally, just as if this option weren't set.
This allows the Active Directory principal to be treated as an
instance rather than a main account for specific users without
affecting behavior for other users.

No regressions, but currently untested otherwise.
parent 4563cb8b
......@@ -16,9 +16,9 @@ EXTRA_DIST = .gitignore LICENSE autogen patches/README \
AM_CPPFLAGS = $(KRB5_CPPFLAGS)
noinst_LTLIBRARIES = portable/libportable.la util/libutil.la
portable_libportable_la_SOURCES = portable/dummy.c portable/krb5-extra.c \
portable/krb5.h portable/macros.h portable/stdbool.h \
portable/system.h
portable_libportable_la_SOURCES = portable/dummy.c portable/kadmin.h \
portable/krb5-extra.c portable/krb5.h portable/macros.h \
portable/stdbool.h portable/system.h
portable_libportable_la_LDFLAGS = $(KRB5_LDFLAGS)
portable_libportable_la_LIBADD = $(LTLIBOBJS) $(KRB5_LIBS)
util_libutil_la_SOURCES = util/macros.h util/messages-krb5.c \
......@@ -32,21 +32,23 @@ moduledir = $(libdir)/krb5/plugins/kadm5_hook
# Rules for building the krb5-sync plugin.
module_LTLIBRARIES = plugin/krb5_sync.la
plugin_krb5_sync_la_SOURCES = plugin/ad.c plugin/api.c plugin/error.c \
plugin/internal.h plugin/heimdal.c plugin/mit.c plugin/queue.c
plugin_krb5_sync_la_CPPFLAGS = $(LDAP_CPPFLAGS) $(AM_CPPFLAGS)
plugin_krb5_sync_la_LDFLAGS = -module -avoid-version $(LDAP_LDFLAGS) \
$(KRB5_LDFLAGS)
plugin_krb5_sync_la_LIBADD = portable/libportable.la $(LDAP_LIBS) \
$(KRB5_LIBS)
plugin_krb5_sync_la_SOURCES = plugin/ad.c plugin/api.c plugin/error.c \
plugin/internal.h plugin/heimdal.c plugin/instance.c plugin/mit.c \
plugin/queue.c
plugin_krb5_sync_la_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) \
$(AM_CPPFLAGS)
plugin_krb5_sync_la_LDFLAGS = -module -avoid-version $(KADM5SRV_LDFLAGS) \
$(LDAP_LDFLAGS) $(KRB5_LDFLAGS)
plugin_krb5_sync_la_LIBADD = portable/libportable.la $(KADM5SRV_LIBS) \
$(LDAP_LIBS) $(KRB5_LIBS)
# Rules for building the krb5-sync utility.
sbin_PROGRAMS = tools/krb5-sync
tools_krb5_sync_SOURCES = tools/krb5-sync.c $(plugin_krb5_sync_la_SOURCES)
tools_krb5_sync_CPPFLAGS = $(LDAP_CPPFLAGS) $(AM_CPPFLAGS)
tools_krb5_sync_LDFLAGS = $(LDAP_LDFLAGS) $(KRB5_LDFLAGS)
tools_krb5_sync_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) $(AM_CPPFLAGS)
tools_krb5_sync_LDFLAGS = $(KADM5SRV_LDFLAGS) $(LDAP_LDFLAGS) $(KRB5_LDFLAGS)
tools_krb5_sync_LDADD = portable/libportable.la util/libutil.la \
$(LDAP_LIBS) $(KRB5_LIBS)
$(KADM5SRV_LIBS) $(LDAP_LIBS) $(KRB5_LIBS)
# Rules for the krb5-sync-backend script.
dist_sbin_SCRIPTS = tools/krb5-sync-backend
......@@ -106,16 +108,20 @@ tests_plugin_mit_t_LDADD = tests/tap/libtap.a portable/libportable.la \
$(DL_LIBS)
tests_plugin_queue_only_t_SOURCES = tests/plugin/queue-only-t.c \
$(plugin_krb5_sync_la_SOURCES)
tests_plugin_queue_only_t_CPPFLAGS = $(LDAP_CPPFLAGS) $(AM_CPPFLAGS)
tests_plugin_queue_only_t_LDFLAGS = $(LDAP_LDFLAGS) $(KRB5_LDFLAGS)
tests_plugin_queue_only_t_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) \
$(AM_CPPFLAGS)
tests_plugin_queue_only_t_LDFLAGS = $(KADM5SRV_LDFLAGS) $(LDAP_LDFLAGS) \
$(KRB5_LDFLAGS)
tests_plugin_queue_only_t_LDADD = tests/tap/libtap.a portable/libportable.la \
$(LDAP_LIBS) $(KRB5_LIBS)
$(KADM5SRV_LIBS) $(LDAP_LIBS) $(KRB5_LIBS)
tests_plugin_queuing_t_SOURCES = tests/plugin/queuing-t.c \
$(plugin_krb5_sync_la_SOURCES)
tests_plugin_queuing_t_CPPFLAGS = $(LDAP_CPPFLAGS) $(AM_CPPFLAGS)
tests_plugin_queuing_t_LDFLAGS = $(LDAP_LDFLAGS) $(KRB5_LDFLAGS)
tests_plugin_queuing_t_CPPFLAGS = $(KADM5SRV_CPPFLAGS) $(LDAP_CPPFLAGS) \
$(AM_CPPFLAGS)
tests_plugin_queuing_t_LDFLAGS = $(KADM5SRV_LDFLAGS) $(LDAP_LDFLAGS) \
$(KRB5_LDFLAGS)
tests_plugin_queuing_t_LDADD = tests/tap/libtap.a portable/libportable.la \
$(LDAP_LIBS) $(KRB5_LIBS)
$(KADM5SRV_LIBS) $(LDAP_LIBS) $(KRB5_LIBS)
tests_portable_asprintf_t_SOURCES = tests/portable/asprintf-t.c \
tests/portable/asprintf.c
tests_portable_asprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la
......
......@@ -2,6 +2,21 @@
krb5-sync 2.4 (unreleased)
Add a new string krb5.conf option, ad_base_instance, which, if set,
changes the way that password synchronization is handled. When this
option is set, the password for the principal formed by appending that
instance to a base principal is propagated to Active Directory as the
password for the base principal. So, for instance, if this is set to
the string "windows", the password of the principal "user/windows" is
propagated to Active Directory as the password for the principal
"user" and password changes for the principal "user" are ignored.
This special behavior only happens if "user/windows" exists in the
local Kerberos KDC database; if not, password propagation for the
principal "user" happens normally, just as if this option weren't set.
This allows the Active Directory principal to be treated as an
instance rather than a main account for specific users without
affecting behavior for other users.
Add a new boolean krb5.conf option, ad_queue_only, which, if set to
true, forces all changes to be queued even if there are no conflicting
changes already queued. The changes can then be processed later with
......
......@@ -214,15 +214,16 @@ CONFIGURATION
example:
krb5-sync = {
ad_keytab = /etc/krb5kdc/ad-keytab
ad_principal = service/sync@WINDOWS.EXAMPLE.COM
ad_realm = WINDOWS.EXAMPLE.COM
ad_admin_server = dc1.windows.example.com
ad_ldap_base = ou=People
ad_instances = root ipass
ad_queue_only = false
queue_dir = /var/spool/krb5-sync
ad_keytab = /etc/krb5kdc/ad-keytab
ad_principal = service/sync@WINDOWS.EXAMPLE.COM
ad_realm = WINDOWS.EXAMPLE.COM
ad_admin_server = dc1.windows.example.com
ad_ldap_base = ou=People
ad_instances = root ipass
ad_base_instance = windows
ad_queue_only = false
queue_dir = /var/spool/krb5-sync
}
The ad_keytab option specifies the location of a srvtab or keytab for
......@@ -243,6 +244,19 @@ CONFIGURATION
command-line utility will be acted on, even if they have non-empty
instances.
If ad_base_instance is set, then any password change for a
single-component principal (such as user@EXAMPLE.COM) will be handled
somewhat specially. First, the instance set in ad_base_instance will be
added and a check against the local Kerberos database will be done to
see if that instance (in this case, user/windows@EXAMPLE.COM) exists.
If it doesn't, the password change is processed as normal. If it does,
the password change will be ignored. Instead, if the password for
user/windows@EXAMPLE.COM is changed, that will be propagated as the
password for the main account in Active Directory (in this case,
user@WINDOWS.EXAMPLE.COM). This allows the Active Directory principal
to be linked to a separate instance, rather than the main account, in
the MIT or Heimdal Kerberos realm for particular users.
If ad_realm is not set, the plugin will not attempt to push changes to
Active Directory, so you can deactivate this plugin while still loading
it by removing that part of the configuration.
......
......@@ -41,6 +41,11 @@ AC_CHECK_FUNCS([krb5_appdefault_string], [],
AC_LIBOBJ([krb5-profile])])
RRA_LIB_KRB5_RESTORE
RRA_LIB_KADM5SRV
RRA_LIB_KADM5SRV_SWITCH
AC_CHECK_FUNCS([kadm5_init_with_skey_ctx])
RRA_LIB_KADM5SRV_RESTORE
RRA_LIB_LDAP
dnl Only used for the test suite.
......
dnl Find the compiler and linker flags for the kadmin server library.
dnl
dnl Finds the compiler and linker flags for linking with the kadmin server
dnl library. Provides the --with-kadm-server, --with-kadm-server-include, and
dnl --with-kadm-server-lib configure option to specify a non-standard path to
dnl the library. Uses krb5-config where available unless reduced dependencies
dnl is requested or --with-kadm-server-include or --with-kadm-server-lib are
dnl given.
dnl
dnl Provides the macros RRA_LIB_KADM5SRV and RRA_LIB_KADM5SRV_OPTIONAL and
dnl sets the substitution variables KADM5SRV_CPPFLAGS, KADM5SRV_LDFLAGS, and
dnl KADM5SRV_LIBS. Also provides RRA_LIB_KADM5SRV_SWITCH to set CPPFLAGS,
dnl LDFLAGS, and LIBS to include the kadmin client libraries, saving the
dnl ecurrent values, and RRA_LIB_KADM5SRV_RESTORE to restore those settings
dnl to before the last RRA_LIB_KADM5SRV_SWITCH. Defines HAVE_KADM5SRV and
dnl sets rra_use_KADM5SRV to true if the library is found.
dnl
dnl Depends on the RRA_LIB helper routines.
dnl
dnl The canonical version of this file is maintained in the rra-c-util
dnl package, available at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
dnl
dnl Written by Russ Allbery <eagle@eyrie.org>
dnl Copyright 2005, 2006, 2007, 2008, 2009, 2011, 2013
dnl The Board of Trustees of the Leland Stanford Junior University
dnl
dnl This file is free software; the authors give unlimited permission to copy
dnl and/or distribute it, with or without modifications, as long as this
dnl notice is preserved.
dnl Save the current CPPFLAGS, LDFLAGS, and LIBS settings and switch to
dnl versions that include the kadmin client flags. Used as a wrapper, with
dnl RRA_LIB_KADM5SRV_RESTORE, around tests.
AC_DEFUN([RRA_LIB_KADM5SRV_SWITCH], [RRA_LIB_HELPER_SWITCH([KADM5SRV])])
dnl Restore CPPFLAGS, LDFLAGS, and LIBS to their previous values (before
dnl RRA_LIB_KADM5SRV_SWITCH was called).
AC_DEFUN([RRA_LIB_KADM5SRV_RESTORE], [RRA_LIB_HELPER_RESTORE([KADM5SRV])])
dnl Set KADM5SRV_CPPFLAGS and KADM5SRV_LDFLAGS based on rra_KADM5SRV_root,
dnl rra_KADM5SRV_libdir, and rra_KADM5SRV_includedir.
AC_DEFUN([_RRA_LIB_KADM5SRV_PATHS], [RRA_LIB_HELPER_PATHS([KADM5SRV])])
dnl Does the appropriate library checks for reduced-dependency kadmin client
dnl linkage. The single argument, if "true", says to fail if the kadmin
dnl client library could not be found.
AC_DEFUN([_RRA_LIB_KADM5SRV_REDUCED],
[RRA_LIB_KADM5SRV_SWITCH
AC_CHECK_LIB([kadm5srv], [kadm5_init_with_password],
[KADM5SRV_LIBS=-lkadm5srv],
[AS_IF([test x"$1" = xtrue],
[AC_MSG_ERROR([cannot find usable kadmin server library])])])
RRA_LIB_KADM5SRV_RESTORE])
dnl Sanity-check the results of krb5-config and be sure we can really link a
dnl GSS-API program. If not, fall back on the manual check.
AC_DEFUN([_RRA_LIB_KADM5SRV_CHECK],
[RRA_LIB_HELPER_CHECK([$1], [KADM5SRV], [kadm5_init_with_password],
[kadmin server])])
dnl Determine GSS-API compiler and linker flags from krb5-config.
AC_DEFUN([_RRA_LIB_KADM5SRV_CONFIG],
[RRA_KRB5_CONFIG([${rra_KADM5SRV_root}], [kadm-server], [KADM5SRV],
[_RRA_LIB_KADM5SRV_CHECK([$1])],
[_RRA_LIB_KADM5SRV_PATHS
_RRA_LIB_KADM5SRV_REDUCED([$1])])])
dnl The core of the library checking, shared between RRA_LIB_KADM5SRV and
dnl RRA_LIB_KADM5SRV_OPTIONAL. The single argument, if "true", says to fail
dnl if the kadmin client library could not be found.
AC_DEFUN([_RRA_LIB_KADM5SRV_INTERNAL],
[AC_REQUIRE([RRA_ENABLE_REDUCED_DEPENDS])
AS_IF([test x"$rra_reduced_depends" = xtrue],
[_RRA_LIB_KADM5SRV_PATHS
_RRA_LIB_KADM5SRV_REDUCED([$1])],
[AS_IF([test x"$rra_KADM5SRV_includedir" = x \
&& test x"$rra_KADM5SRV_libdir" = x],
[_RRA_LIB_KADM5SRV_CONFIG([$1])],
[_RRA_LIB_KADM5SRV_PATHS
_RRA_LIB_KADM5SRV_REDUCED([$1])])])])
dnl The main macro for packages with mandatory kadmin client support.
AC_DEFUN([RRA_LIB_KADM5SRV],
[RRA_LIB_HELPER_VAR_INIT([KADM5SRV])
RRA_LIB_HELPER_WITH([kadm-server], [kadmin server], [KADM5SRV])
_RRA_LIB_KADM5SRV_INTERNAL([true])
rra_use_KADM5SRV=true
AC_DEFINE([HAVE_KADM5SRV], 1, [Define to enable kadmin server features.])])
dnl The main macro for packages with optional kadmin client support.
AC_DEFUN([RRA_LIB_KADM5SRV_OPTIONAL],
[RRA_LIB_HELPER_VAR_INIT([KADM5SRV])
RRA_LIB_HELPER_WITH_OPTIONAL([kadm-server], [kadmin server], [KADM5SRV])
AS_IF([test x"$rra_use_KADM5SRV" != xfalse],
[AS_IF([test x"$rra_use_KADM5SRV" = xtrue],
[_RRA_LIB_KADM5SRV_INTERNAL([true])],
[_RRA_LIB_KADM5SRV_INTERNAL([false])])])
AS_IF([test x"$KADM5SRV_LIBS" != x],
[rra_use_KADM5SRV=true
AC_DEFINE([HAVE_KADM5SRV], 1,
[Define to enable kadmin server features.])])])
dnl Helper functions to manage compiler variables.
dnl
dnl These are a wide variety of helper macros to make it easier to construct
dnl standard macros to probe for a library and to set library-specific
dnl CPPFLAGS, LDFLAGS, and LIBS shell substitution variables. Most of them
dnl take as one of the arguments the prefix string to use for variables, which
dnl is usually something like "KRB5" or "GSSAPI".
dnl
dnl Depends on RRA_SET_LDFLAGS.
dnl
dnl The canonical version of this file is maintained in the rra-c-util
dnl package, available at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
dnl
dnl Written by Russ Allbery <eagle@eyrie.org>
dnl Copyright 2011, 2013
dnl The Board of Trustees of the Leland Stanford Junior University
dnl
dnl This file is free software; the authors give unlimited permission to copy
dnl and/or distribute it, with or without modifications, as long as this
dnl notice is preserved.
dnl Add the library flags to the default compiler flags and then remove them.
dnl
dnl To use these macros, pass the prefix string used for the variables as the
dnl only argument. For example, to use these for a library with KRB5 as a
dnl prefix, one would use:
dnl
dnl AC_DEFUN([RRA_LIB_KRB5_SWITCH], [RRA_LIB_HELPER_SWITCH([KRB5])])
dnl AC_DEFUN([RRA_LIB_KRB5_RESTORE], [RRA_LIB_HELPER_RESTORE([KRB5])])
dnl
dnl Then, wrap checks for library features with RRA_LIB_KRB5_SWITCH and
dnl RRA_LIB_KRB5_RESTORE.
AC_DEFUN([RRA_LIB_HELPER_SWITCH],
[rra_$1[]_save_CPPFLAGS="$CPPFLAGS"
rra_$1[]_save_LDFLAGS="$LDFLAGS"
rra_$1[]_save_LIBS="$LIBS"
CPPFLAGS="$$1[]_CPPFLAGS $CPPFLAGS"
LDFLAGS="$$1[]_LDFLAGS $LDFLAGS"
LIBS="$$1[]_LIBS $LIBS"])
AC_DEFUN([RRA_LIB_HELPER_RESTORE],
[CPPFLAGS="$rra_$1[]_save_CPPFLAGS"
LDFLAGS="$rra_$1[]_save_LDFLAGS"
LIBS="$rra_$1[]_save_LIBS"])
dnl Given _root, _libdir, and _includedir variables set for a library (set by
dnl RRA_LIB_HELPER_WITH*), set the LDFLAGS and CPPFLAGS variables for that
dnl library accordingly. Takes the variable prefix as the only argument.
AC_DEFUN([RRA_LIB_HELPER_PATHS],
[AS_IF([test x"$rra_$1[]_libdir" != x],
[$1[]_LDFLAGS="-L$rra_$1[]_libdir"],
[AS_IF([test x"$rra_$1[]_root" != x],
[RRA_SET_LDFLAGS([$1][_LDFLAGS], [${rra_$1[]_root}])])])
AS_IF([test x"$rra_$1[]_includedir" != x],
[$1[]_CPPFLAGS="-I$rra_$1[]_includedir"],
[AS_IF([test x"$rra_$1[]_root" != x],
[AS_IF([test x"$rra_$1[]_root" != x/usr],
[$1[]_CPPFLAGS="-I${rra_$1[]_root}/include"])])])])
dnl Check whether a library works. This is used as a sanity check on the
dnl results of *-config shell scripts. Takes four arguments; the first, if
dnl "true", says that a working library is mandatory and errors out if it
dnl doesn't. The second is the variable prefix. The third is a function to
dnl look for that should be in the libraries. The fourth is the
dnl human-readable name of the library for error messages.
AC_DEFUN([RRA_LIB_HELPER_CHECK],
[RRA_LIB_HELPER_SWITCH([$2])
AC_CHECK_FUNC([$3], [],
[AS_IF([test x"$1" = xtrue],
[AC_MSG_FAILURE([unable to link with $4 library])])
$2[]_CPPFLAGS=
$2[]_LDFLAGS=
$2[]_LIBS=])
RRA_LIB_HELPER_RESTORE([$2])])
dnl Initialize the variables used by a library probe and set the appropriate
dnl ones as substitution variables. Takes the library variable prefix as its
dnl only argument.
AC_DEFUN([RRA_LIB_HELPER_VAR_INIT],
[rra_$1[]_root=
rra_$1[]_libdir=
rra_$1[]_includedir=
rra_use_$1=
$1[]_CPPFLAGS=
$1[]_LDFLAGS=
$1[]_LIBS=
AC_SUBST([$1][_CPPFLAGS])
AC_SUBST([$1][_LDFLAGS])
AC_SUBST([$1][_LIBS])])
dnl Handles --with options for a non-optional library. First argument is the
dnl base for the switch names. Second argument is the short description.
dnl Third argument is the variable prefix. The variables set are used by
dnl RRA_LIB_HELPER_PATHS.
AC_DEFUN([RRA_LIB_HELPER_WITH],
[AC_ARG_WITH([$1],
[AS_HELP_STRING([--with-][$1][=DIR],
[Location of $2 headers and libraries])],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_$3[]_root="$withval"])])
AC_ARG_WITH([$1][-include],
[AS_HELP_STRING([--with-][$1][-include=DIR],
[Location of $2 headers])],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_$3[]_includedir="$withval"])])
AC_ARG_WITH([$1][-lib],
[AS_HELP_STRING([--with-][$1][-lib=DIR],
[Location of $2 libraries])],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_$3[]_libdir="$withval"])])])
dnl Handles --with options for an optional library, so --with-<library> can
dnl cause the checks to be skipped entirely or become mandatory. Sets an
dnl rra_use_PREFIX variable to true or false if the library is explicitly
dnl enabled or disabled.
dnl
dnl First argument is the base for the switch names. Second argument is the
dnl short description. Third argument is the variable prefix.
dnl
dnl The variables set are used by RRA_LIB_HELPER_PATHS.
AC_DEFUN([RRA_LIB_HELPER_WITH_OPTIONAL],
[AC_ARG_WITH([$1],
[AS_HELP_STRING([--with-][$1][@<:@=DIR@:>@],
[Location of $2 headers and libraries])],
[AS_IF([test x"$withval" = xno],
[rra_use_$3=false],
[AS_IF([test x"$withval" != yes], [rra_$3[]_root="$withval"])
rra_use_$3=true])])
AC_ARG_WITH([$1][-include],
[AS_HELP_STRING([--with-][$1][-include=DIR],
[Location of $2 headers])],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_$3[]_includedir="$withval"])])
AC_ARG_WITH([$1][-lib],
[AS_HELP_STRING([--with-][$1][-lib=DIR],
[Location of $2 libraries])],
[AS_IF([test x"$withval" != xyes && test x"$withval" != xno],
[rra_$3[]_libdir="$withval"])])])
......@@ -7,7 +7,7 @@
* Written by Russ Allbery <rra@stanford.edu>
* Based on code developed by Derrick Brashear and Ken Hornstein of Sine
* Nomine Associates, on behalf of Stanford University.
* Copyright 2006, 2007, 2010, 2012
* Copyright 2006, 2007, 2010, 2012, 2013
* The Board of Trustees of the Leland Stanford Junior University
*
* See LICENSE for licensing terms.
......@@ -133,20 +133,49 @@ get_creds(struct plugin_config *config, krb5_context ctx, krb5_ccache *cc,
/*
* Given the krb5_principal from kadmind, convert it to the corresponding
* principal in Active Directory. Returns 0 on success and a Kerberos error
* code on failure. Currently, all this does is change the realm.
* principal in Active Directory. This may involve removing ad_base_instance
* and always involves changing the realm. Returns 0 on success and a
* Kerberos error code on failure.
*/
static krb5_error_code
get_ad_principal(krb5_context ctx, const char *realm,
krb5_const_principal principal,
krb5_principal *ad_principal)
get_ad_principal(krb5_context ctx, struct plugin_config *config,
krb5_const_principal principal, krb5_principal *ad_principal)
{
krb5_error_code ret;
int ncomp;
ret = krb5_copy_principal(ctx, principal, ad_principal);
if (ret != 0)
return ret;
krb5_principal_set_realm(ctx, *ad_principal, realm);
/*
* Set ad_principal to NULL to start. We fall back on copy and realm
* setting if we don't have to build it, and use whether it's NULL as a
* flag.
*/
*ad_principal = NULL;
/* Get the number of components. */
ncomp = krb5_principal_get_num_comp(ctx, principal);
/* See if this is an ad_base_instance principal that needs a rewrite. */
if (config->ad_base_instance != NULL && ncomp == 2) {
const char *base, *instance;
instance = krb5_principal_get_comp_string(ctx, principal, 1);
if (strcmp(instance, config->ad_base_instance) == 0) {
base = krb5_principal_get_comp_string(ctx, principal, 0);
ret = krb5_build_principal(ctx, ad_principal,
strlen(config->ad_realm),
config->ad_realm, base, (char *) 0);
if (ret != 0)
return ret;
}
}
/* Otherwise, copy the principal and set the realm. */
if (*ad_principal == NULL) {
ret = krb5_copy_principal(ctx, principal, ad_principal);
if (ret != 0)
return ret;
krb5_principal_set_realm(ctx, *ad_principal, config->ad_realm);
}
return 0;
}
......@@ -180,7 +209,7 @@ pwupdate_ad_change(struct plugin_config *config, krb5_context ctx,
return 1;
/* Get the corresponding Active Directory principal. */
ret = get_ad_principal(ctx, config->ad_realm, principal, &ad_principal);
ret = get_ad_principal(ctx, config, principal, &ad_principal);
if (ret != 0) {
pwupdate_set_error(errstr, errstrlen, ctx, ret,
"unable to get AD principal");
......@@ -342,7 +371,7 @@ pwupdate_ad_status(struct plugin_config *config, krb5_context ctx,
* the AD principal and then query Active Directory via LDAP to get back
* the CN for the user to construct the full DN.
*/
ret = get_ad_principal(ctx, config->ad_realm, principal, &ad_principal);
ret = get_ad_principal(ctx, config, principal, &ad_principal);
if (ret != 0) {
pwupdate_set_error(errstr, errstrlen, ctx, ret,
"unable to get AD principal");
......
......@@ -62,7 +62,7 @@ config_boolean(krb5_context ctx, const char *opt, bool *result)
* Heimdal version takes a krb5_boolean *, so hope that Heimdal always
* defines krb5_boolean to int or this will require more portability work.
*/
krb5_appdefault_boolean(ctx, "krb5-strength", NULL, opt, *result, &tmp);
krb5_appdefault_boolean(ctx, "krb5-sync", NULL, opt, *result, &tmp);
*result = tmp;
}
......@@ -86,6 +86,7 @@ pwupdate_init(krb5_context ctx, void **data)
config_string(ctx, "ad_realm", &config->ad_realm);
config_string(ctx, "ad_admin_server", &config->ad_admin_server);
config_string(ctx, "ad_ldap_base", &config->ad_ldap_base);
config_string(ctx, "ad_base_instance", &config->ad_base_instance);
config_string(ctx, "ad_instances", &config->ad_instances);
config_boolean(ctx, "ad_queue_only", &config->ad_queue_only);
config_string(ctx, "queue_dir", &config->queue_dir);
......@@ -111,6 +112,8 @@ pwupdate_close(void *data)
free(config->ad_realm);
if (config->ad_admin_server != NULL)
free(config->ad_admin_server);
if (config->ad_base_instance != NULL)
free(config->ad_base_instance);
if (config->queue_dir != NULL)
free(config->queue_dir);
free(config);
......@@ -178,31 +181,62 @@ instance_allowed(const char *allowed, const char *instance)
/*
* Check the principal for which we're changing a password. If it contains a
* non-null instance, we don't want to propagate the change; we only want to
* change passwords for regular users. Returns true if we should proceed,
* false otherwise. If we shouldn't proceed, logs a debug-level message to
* syslog.
* Check the principal for which we're changing a password or the enable
* status. Takes a flag, which is true for a password change and false for
* other types of changes, since password changes use ad_base_instance.
*
* If it contains a non-null instance, we don't want to propagate the change;
* we only want to change passwords for regular users.
*
* If it is a single-part principal name, ad_base_instance is set, and the
* equivalent principal with that instance also exists, we don't propagate
* this change because the instance's password is propagated as the base
* account in Active Directory instead.
*
* Returns true if we should proceed, false otherwise. If we shouldn't
* proceed, logs a debug-level message to syslog.
*/
static int
principal_allowed(struct plugin_config *config, krb5_context ctx,
krb5_principal principal, int ad)
krb5_principal principal, int pwchange)
{
if (krb5_principal_get_num_comp(ctx, principal) > 1) {
char *display;
krb5_error_code ret;
char *display;
krb5_error_code code;
int ncomp, okay;
/* Get the number of components. */
ncomp = krb5_principal_get_num_comp(ctx, principal);
/*
* If the principal is single-part, check against ad_base_instance.
* Otherwise, if the principal is multi-part, check the instance.
*/
if (pwchange && ncomp == 1 && config->ad_base_instance != NULL) {
okay = !pwupdate_instance_exists(principal, config->ad_base_instance);
if (!okay) {
code = krb5_unparse_name(ctx, principal, &display);
if (code != 0)
display = NULL;
syslog(LOG_DEBUG, "account synchronization skipping principal"
" \"%s\" for Active Directory because %s instance exists",
display != NULL ? display : "???",
config->ad_base_instance);
if (display != NULL)
krb5_free_unparsed_name(ctx, display);
}
return okay;
} else if (ncomp > 1) {
const char *instance;
instance = krb5_principal_get_comp_string(ctx, principal, 1);
if (ad && instance_allowed(config->ad_instances, instance))
if (instance_allowed(config->ad_instances, instance))
return 1;
ret = krb5_unparse_name(ctx, principal, &display);
if (ret != 0)
code = krb5_unparse_name(ctx, principal, &display);
if (code != 0)
display = NULL;
syslog(LOG_DEBUG, "account synchronization skipping principal \"%s\""
" with non-null instance for %s",
display != NULL ? display : "???",
ad ? "Active Directory" : "AFS");
" with non-null instance for Active Directory",
display != NULL ? display : "???");
if (display != NULL)
krb5_free_unparsed_name(ctx, display);
return 0;
......@@ -308,7 +342,7 @@ pwupdate_postcommit_status(void *data, krb5_principal principal, int enabled,
return 0;
if (!create_context(&ctx, errstr, errstrlen))
return 1;
if (!principal_allowed(config, ctx, principal, 1))
if (!principal_allowed(config, ctx, principal, 0))
return 0;
if (pwupdate_queue_conflict(config, ctx, principal, "ad", "enable"))
goto queue;
......
/*
* Get data about instances in the Kerberos KDC database.
*
* The functions in this file use the Kerberos kadm5srv library API to look up
* information about instances of a principal in the local Kerberos KDC
* database.
*
* Written by Russ Allbery <eagle@eyrie.org>
* Copyright 2013
* The Board of Trustees of the Leland Stanford Junior University
*
* See LICENSE for licensing terms.
*/
#include <config.h>
#include <portable/kadmin.h>
#include <portable/krb5.h>
#include <portable/system.h>
#include <plugin/internal.h>
#include <util/macros.h>
/*
* Given a principal and an instance, return true if the principal is a
* one-part name and the principal formed by adding the instance as a second
* part is found in the local Kerberos database. Returns false if it is not
* or on any other error.
*/
int
pwupdate_instance_exists(krb5_principal base, const char *instance)
{
krb5_context ctx;
krb5_principal princ = NULL;
krb5_error_code code;
const char *realm;
kadm5_config_params params;
void *handle = NULL;
int mask;
kadm5_principal_ent_rec ent;
/* Get a Kerberos context. Eventually, this will be passed in. */
code = krb5_init_context(&ctx);
if (code != 0)
return 0;
/* Principals must have exactly one component. */
if (krb5_principal_get_num_comp(ctx, base) != 1)
return 0;
/* Form a new principal from the old principal plus the instance. */
realm = krb5_principal_get_realm(ctx, base);
if (realm == NULL)
return 0;
code = krb5_build_principal(ctx, &princ, strlen(realm), realm,
krb5_principal_get_comp_string(ctx, base, 0),
instance, (char *) 0);
if (code != 0)
goto fail;
/* Open the local KDB and look up this new principal. */
memset(&params, 0, sizeof(params));
params.realm = (char *) realm;
params.mask = KADM5_CONFIG_REALM;
code = kadm5_init_with_skey_ctx(ctx, (char *) "kadmin/admin", NULL, NULL,
&params, KADM5_STRUCT_VERSION,
KADM5_API_VERSION_2, &handle);
if (code != 0)
goto fail;
mask = KADM5_ATTRIBUTES | KADM5_PW_EXPIRATION;
code = kadm5_get_principal(handle, princ, &ent, mask);
if (code == 0)
kadm5_free_principal_ent(handle, &ent);
kadm5_destroy(handle);
krb5_free_principal(ctx, princ);
princ = NULL;
krb5_free_context(ctx);
return (code == 0);
fail:
krb5_free_principal(ctx, princ);
krb5_free_context(ctx);
return 0;
}
......@@ -31,6 +31,7 @@ struct plugin_config {
char *ad_realm;
char *ad_admin_server;
char *ad_ldap_base;
char *ad_base_instance;
char *ad_instances;
bool ad_queue_only;
char *queue_dir;
......@@ -58,6 +59,9 @@ int pwupdate_ad_status(struct plugin_config *config, krb5_context ctx,
krb5_principal principal, int enabled, char *errstr,
int errstrlen);
/* Instance lookups. */
int pwupdate_instance_exists(krb5_principal principal, const char *instance);
/* Queuing. */
int pwupdate_queue_conflict(struct plugin_config *config, krb5_context ctx,
krb5_principal principal, const char *domain,
......
/*
* Portability wrapper around kadm5/admin.h.
*
* This header adjusts for differences between the MIT and Heimdal kadmin
* client libraries so that the code can be written to a consistent API
* (favoring the Heimdal API as the exposed one).
*
* The canonical version of this file is maintained in the rra-c-util package,
* which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
*
* Written by Russ Allbery <eagle@eyrie.org>
*
* The authors hereby relinquish any claim to any copyright that they may have
* in this work, whether granted under contract or by operation of law or
* international treaty, and hereby commit to the public, at large, that they
* shall not, at any time in the future, seek to enforce any copyright in this
* work against any person or entity, or prevent any person or entity from
* copying, publishing, distributing or creating derivative works of this
* work.
*/
#ifndef PORTABLE_KADMIN_H
#define PORTABLE_KADMIN_H 1
#include <config.h>
#include <kadm5/admin.h>
#ifdef HAVE_KADM5_KADM5_ERR_H
# include <kadm5/kadm5_err.h>
#else
# include <kadm5/kadm_err.h>
#endif
/*
* MIT as of 1.10 supports version 3. Heimdal as of 1.5 has a maximum version
* of 2. Define a KADM5_API_VERSION symbol that holds the maximum version.
* (Heimdal does this for us, so we only have to do that with MIT, but be
* general just in case.)
*/
#ifndef KADM5_API_VERSION
# ifdef KADM5_API_VERSION_3
# define KADM5_API_VERSION KADM5_API_VERSION_3
# else
# define KADM5_API_VERSION KADM5_API_VERSION_2
# endif
#endif
/* Heimdal doesn't define KADM5_PASS_Q_GENERIC. */
#ifndef KADM5_PASS_Q_GENERIC
# define KADM5_PASS_Q_GENERIC KADM5_PASS_Q_DICT
#endif
/*
* Heimdal provides _ctx functions that take an existing context. MIT always
* requires the context be passed in. Code should use the _ctx variant, and
* the below will fix it up if built against MIT.
*
* MIT also doesn't have a const prototype for the server argument, so cast it
* so that we can use the KADM5_ADMIN_SERVICE define.
*/
#ifndef HAVE_KADM5_INIT_WITH_SKEY_CTX
# define kadm5_init_with_skey_ctx(c, u, k, s, p, sv, av, h) \
kadm5_init_with_skey((c), (u), (k), (char *) (s), (p), (sv), (av), NULL, \
(h))
#endif