diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | daemons/configure.ac | 1 | ||||
-rw-r--r-- | daemons/ipa-slapi-plugins/Makefile.am | 1 | ||||
-rw-r--r-- | daemons/ipa-slapi-plugins/ipa-enrollment/Makefile.am | 42 | ||||
-rw-r--r-- | daemons/ipa-slapi-plugins/ipa-enrollment/enrollment-conf.ldif | 16 | ||||
-rw-r--r-- | daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c | 457 | ||||
-rw-r--r-- | daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c | 40 | ||||
-rw-r--r-- | install/updates/40-delegation.update | 31 | ||||
-rw-r--r-- | ipa-client/Makefile.am | 15 | ||||
-rw-r--r-- | ipa-client/config.c | 155 | ||||
-rw-r--r-- | ipa-client/configure.ac | 24 | ||||
-rw-r--r-- | ipa-client/ipa-getkeytab.c | 56 | ||||
-rw-r--r-- | ipa-client/ipa-install/ipa-client-install | 1 | ||||
-rw-r--r-- | ipa-client/ipa-join.c | 648 | ||||
-rw-r--r-- | ipa.spec.in | 5 | ||||
-rw-r--r-- | ipalib/plugins/host.py | 63 | ||||
-rw-r--r-- | ipaserver/install/dsinstance.py | 4 | ||||
-rw-r--r-- | ipaserver/plugins/join.py (renamed from ipalib/plugins/join.py) | 53 | ||||
-rw-r--r-- | ipaserver/plugins/ldap2.py | 42 |
19 files changed, 1577 insertions, 81 deletions
@@ -51,7 +51,7 @@ bootstrap-autogen: version-update @echo "Building IPA $(IPA_VERSION)" cd daemons; if [ ! -e Makefile ]; then ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); fi cd install; if [ ! -e Makefile ]; then ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); fi - cd ipa-client; if [ ! -e Makefile ]; then ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); fi + cd ipa-client; if [ ! -e Makefile ]; then ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR) --with-openldap; fi install: all server-install @for subdir in $(SUBDIRS); do \ @@ -110,7 +110,7 @@ archive-cleanup: tarballs: local-archive -mkdir -p dist/sources # tar up clean sources - cd dist/$(TARBALL_PREFIX)/ipa-client; ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); make distclean + cd dist/$(TARBALL_PREFIX)/ipa-client; ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR) --with-openldap; make distclean cd dist/$(TARBALL_PREFIX)/daemons; ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); make distclean cd dist/$(TARBALL_PREFIX)/install; ../autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=$(LIBDIR); make distclean cd dist; tar cfz sources/$(TARBALL) $(TARBALL_PREFIX) diff --git a/daemons/configure.ac b/daemons/configure.ac index e726bd982..7f0fd680f 100644 --- a/daemons/configure.ac +++ b/daemons/configure.ac @@ -260,6 +260,7 @@ AC_CONFIG_FILES([ Makefile ipa-kpasswd/Makefile ipa-slapi-plugins/Makefile + ipa-slapi-plugins/ipa-enrollment/Makefile ipa-slapi-plugins/ipa-memberof/Makefile ipa-slapi-plugins/ipa-pwd-extop/Makefile ipa-slapi-plugins/ipa-winsync/Makefile diff --git a/daemons/ipa-slapi-plugins/Makefile.am b/daemons/ipa-slapi-plugins/Makefile.am index 3b58ccb14..ac2d26879 100644 --- a/daemons/ipa-slapi-plugins/Makefile.am +++ b/daemons/ipa-slapi-plugins/Makefile.am @@ -1,6 +1,7 @@ NULL = SUBDIRS = \ + ipa-enrollment \ ipa-pwd-extop \ ipa-memberof \ ipa-winsync \ diff --git a/daemons/ipa-slapi-plugins/ipa-enrollment/Makefile.am b/daemons/ipa-slapi-plugins/ipa-enrollment/Makefile.am new file mode 100644 index 000000000..8a4ba09c7 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-enrollment/Makefile.am @@ -0,0 +1,42 @@ +NULL = + +INCLUDES = \ + -I. \ + -I$(srcdir) \ + -DPREFIX=\""$(prefix)"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(MOZLDAP_CFLAGS) \ + $(KRB5_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NULL) + +plugindir = $(libdir)/dirsrv/plugins +plugin_LTLIBRARIES = \ + libipa_enrollment_extop.la \ + $(NULL) + +libipa_enrollment_extop_la_SOURCES = \ + ipa_enrollment.c \ + $(NULL) + +libipa_enrollment_extop_la_LDFLAGS = -avoid-version + +libipa_enrollment_extop_la_LIBADD = \ + $(MOZLDAP_LIBS) \ + $(NULL) + +appdir = $(IPA_DATA_DIR) +app_DATA = \ + enrollment-conf.ldif \ + $(NULL) + +EXTRA_DIST = \ + $(app_DATA) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in diff --git a/daemons/ipa-slapi-plugins/ipa-enrollment/enrollment-conf.ldif b/daemons/ipa-slapi-plugins/ipa-enrollment/enrollment-conf.ldif new file mode 100644 index 000000000..1c98277a8 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-enrollment/enrollment-conf.ldif @@ -0,0 +1,16 @@ +dn: cn=ipa_enrollment_extop,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: ipa_enrollment_extop +nsslapd-pluginpath: libipa_enrollment_extop +nsslapd-plugininitfunc: ipaenrollment_init +nsslapd-plugintype: extendedop +nsslapd-pluginenabled: on +nsslapd-pluginid: ipa_enrollment_extop +nsslapd-pluginversion: 1.0 +nsslapd-pluginvendor: RedHat +nsslapd-plugindescription: Enroll hosts into the IPA domain +nsslapd-plugin-depends-on-type: database +nsslapd-realmTree: $SUFFIX diff --git a/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c b/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c new file mode 100644 index 000000000..8a9906aab --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c @@ -0,0 +1,457 @@ +/** BEGIN COPYRIGHT BLOCK + * This Program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; version 2 of the License. + * + * This Program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA. + * + * In addition, as a special exception, Red Hat, Inc. gives You the additional + * right to link the code of this Program with code not covered under the GNU + * General Public License ("Non-GPL Code") and to distribute linked combinations + * including the two, subject to the limitations in this paragraph. Non-GPL Code + * permitted under this exception must only link to the code of this Program + * through those well defined interfaces identified in the file named EXCEPTION + * found in the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline functions from + * the Approved Interfaces without causing the resulting work to be covered by + * the GNU General Public License. Only Red Hat, Inc. may make changes or + * additions to the list of Approved Interfaces. You must obey the GNU General + * Public License in all respects for all of the Program code and other code used + * in conjunction with the Program except the Non-GPL Code covered by this + * exception. If you modify this file, you may extend this exception to your + * version of the file, but you are not obligated to do so. If you do not wish to + * provide this exception without modification, you must delete this exception + * statement from your version and license this file solely under the GPL without + * exception. + * + * + * Copyright (C) 2005 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +/* + * Enroll a host into the IPA domain. + * + */ + +#include <stdio.h> +#include <string.h> +#include <dirsrv/slapi-plugin.h> +#include <krb5.h> + +/* OID of the extended operation handled by this plug-in */ +#define JOIN_OID "2.16.840.1.113730.3.8.3.53" + +Slapi_PluginDesc pdesc = { + "ipa-enrollment", + "IPA Project", + "IPA/2.0", + "IPA Enrollment Extended Operation plugin" +}; + +static char *ipaenrollment_oid_list[] = { + JOIN_OID, + NULL +}; + +static char *ipaenrollment_name_list[] = { + "Enrollment Extended Operation", + NULL +}; + +static void *ipaenrollment_plugin_id; + +static char *realm; +static const char *ipa_realm_dn; + +static int +ipaenrollement_secure(Slapi_PBlock *pb, char **errMesg) +{ + int sasl_ssf, is_ssl; + int rc = LDAP_SUCCESS; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_enrollment", "=> ipaenrollment_secure\n"); + + /* Allow enrollment only for SSL/TLS established connections and + * connections using SASL privacy layers */ + if (slapi_pblock_get(pb, SLAPI_CONN_SASL_SSF, &sasl_ssf) != 0) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Could not get SASL SSF from connection\n"); + *errMesg = "Operation requires a secure connection.\n"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (slapi_pblock_get(pb, SLAPI_CONN_IS_SSL_SESSION, &is_ssl) != 0) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Could not get IS SSL from connection\n"); + *errMesg = "Operation requires a secure connection.\n"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + if ((0 == is_ssl) && (sasl_ssf <= 1)) { + *errMesg = "Operation requires a secure connection.\n"; + rc = LDAP_CONFIDENTIALITY_REQUIRED; + goto done; + } + +done: + slapi_log_error(SLAPI_LOG_TRACE, "ipa_enrollment", "<= ipaenrollment_secure\n"); + return rc; + +} + +/* The extop call passes in the FQDN of the host to enroll. + * We take that and set the krbPrincipalName and add the appropriate + * objectclasses, then return krbPrincipalName. The caller should take + * this and pass it to ipa-getkeytab to generate the keytab. + * + * The password for the entry is removed by ipa-getkeytab. + */ +static int +ipa_join(Slapi_PBlock *pb) +{ + char *bindDN = NULL; + char *errMesg = NULL; + struct berval *extop_value = NULL; + Slapi_PBlock *pbte = NULL; + Slapi_PBlock *pbtm = NULL; + Slapi_Entry *targetEntry=NULL; + Slapi_DN *sdn; + Slapi_Backend *be; + Slapi_Entry **es = NULL; + int rc=0, ret=0, res, i; + int is_root=0; + char *krbLastPwdChange = NULL; + char *fqdn = NULL; + Slapi_Mods *smods; + char *attrlist[] = {"fqdn", "krbPrincipalKey", "krbLastPwdChange", "krbPrincipalName", NULL }; + char * filter; + + int scope = LDAP_SCOPE_SUBTREE; + char *principal; + struct berval retbval; + + /* Get Bind DN */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &bindDN); + + /* If the connection is bound anonymously we must refuse to process + * this operation. + */ + if (bindDN == NULL || *bindDN == '\0') { + /* Refuse the operation because they're bound anonymously */ + errMesg = "Anonymous Binds are not allowed.\n"; + rc = LDAP_INSUFFICIENT_ACCESS; + goto free_and_return; + } + + /* Get the ber value of the extended operation */ + slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value); + + /* We are passed in the FQDN of the host to enroll. Do an internal + * search and pull that entry. + */ + filter = slapi_ch_smprintf("(fqdn=%s)", extop_value->bv_val); + pbte = slapi_pblock_new(); + slapi_search_internal_set_pb(pbte, + ipa_realm_dn, scope, filter, attrlist, 0, + NULL, /* Controls */ + NULL, /* UniqueID */ + ipaenrollment_plugin_id, + 0); /* Flags */ + + /* do search the tree */ + ret = slapi_search_internal_pb(pbte); + slapi_pblock_get(pbte, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (ret == -1 || res != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_TRACE, "ipaenrollment_extop", + "Search for host failed, err (%d)\n", + res?res:ret); + errMesg = "Host not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + + /* get entries */ + slapi_pblock_get(pbte, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es); + if (!es) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "No entries ?!"); + errMesg = "Host not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + + /* count entries */ + for (i = 0; es[i]; i++) /* count */ ; + + /* if there is none or more than one, freak out */ + if (i != 1) { + slapi_log_error(SLAPI_LOG_TRACE, "ipaenrollment_extop", + "Too many entries, or entry no found (%d)", i); + errMesg = "Host not found.\n"; + rc = LDAP_NO_SUCH_OBJECT; + goto free_and_return; + } + targetEntry = es[0]; + + /* Is this host already enrolled? */ + krbLastPwdChange = slapi_entry_attr_get_charptr(targetEntry, "krbLastPwdChange"); + if (NULL != krbLastPwdChange) { + slapi_log_error(SLAPI_LOG_TRACE, "ipaenrollment_extop", + "Host already enrolled"); + errMesg = "Host already enrolled.\n"; + rc = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + + /* First thing to do is to ask access control if the bound identity has + * rights to modify the userpassword attribute on this entry. If not, + * then we fail immediately with insufficient access. This means that + * we don't leak any useful information to the client such as current + * password wrong, etc. + */ + + is_root = slapi_dn_isroot(bindDN); + slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &is_root); + + /* In order to perform the access control check, + * we need to select a backend (even though + * we don't actually need it otherwise). + */ + sdn = slapi_sdn_new_dn_byval(bindDN); + be = slapi_be_select(sdn); + slapi_pblock_set(pb, SLAPI_BACKEND, be); + + /* Access Strategy: + * If the user has WRITE-ONLY access, a new keytab is set on the entry. + */ + + ret = slapi_access_allowed(pb, targetEntry, "krbPrincipalKey", NULL, SLAPI_ACL_WRITE); + if (ret != LDAP_SUCCESS) { + errMesg = "Insufficient access rights\n"; + rc = LDAP_INSUFFICIENT_ACCESS; + goto free_and_return; + } + + /* If a principal is already set return the name */ + principal = slapi_entry_attr_get_charptr(targetEntry, "krbPrincipalName"); + if (NULL != principal) + goto done; + + /* Add the elements needed for enrollment */ + smods = slapi_mods_new(); + fqdn = slapi_entry_attr_get_charptr(targetEntry, "fqdn"); + principal = slapi_ch_smprintf("host/%s@%s", fqdn, realm); + slapi_mods_add_string(smods, LDAP_MOD_ADD, "krbPrincipalName", principal); + slapi_mods_add_string(smods, LDAP_MOD_ADD, "objectClass", "krbPrincipalAux"); + + pbtm = slapi_pblock_new(); + slapi_modify_internal_set_pb (pbtm, slapi_entry_get_dn_const(targetEntry), + slapi_mods_get_ldapmods_byref(smods), + NULL, /* Controls */ + NULL, /* UniqueID */ + ipaenrollment_plugin_id, /* PluginID */ + 0); /* Flags */ + + rc = slapi_modify_internal_pb (pbtm); + if (rc) { + slapi_log_error(SLAPI_LOG_TRACE, "ipaenrollment_extop", + "WARNING: modify error %d on entry '%s'\n", + rc, slapi_entry_get_dn_const(targetEntry)); + } else { + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); + + if (rc != LDAP_SUCCESS){ + slapi_log_error(SLAPI_LOG_TRACE, "ipaenrollment_extop", + "WARNING: modify error %d on entry '%s'\n", + rc, slapi_entry_get_dn_const(targetEntry)); + } else { + slapi_log_error(SLAPI_LOG_TRACE, "ipaenrollment_extop", + "<= apply mods: Successful\n"); + } + } + +done: + /* Return the krbprincipalname */ + retbval.bv_val = principal; + retbval.bv_len = strlen(principal); + + ret = slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, JOIN_OID); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, &retbval); + if (ret) { + errMesg = "Could not set return values"; + slapi_log_error(SLAPI_LOG_PLUGIN, "ipaenrollmenti_extop", "%s\n", + errMesg); + rc = SLAPI_PLUGIN_EXTENDED_SENT_RESULT; + } + + /* Free anything that we allocated above */ +free_and_return: + + if (pbte) { + slapi_free_search_results_internal(pbte); + slapi_pblock_destroy(pbte); + } + if (pbtm) { + slapi_pblock_destroy(pbtm); + } + + if (krbLastPwdChange) slapi_ch_free_string(&krbLastPwdChange); + + slapi_log_error(SLAPI_LOG_PLUGIN, "ipaenrollment_extop", errMesg ? errMesg : "success\n"); + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + + free(principal); + + return SLAPI_PLUGIN_EXTENDED_SENT_RESULT; +} + +/* Extended operation plug-in */ +static int +ipaenrollment_extop(Slapi_PBlock *pb) +{ + char *oid; + char *errMesg = NULL; + int rc, ret; + + slapi_log_error(SLAPI_LOG_TRACE, "ipa_enrollment", "=> ipaenrollment_extop\n"); + + rc = ipaenrollement_secure(pb, &errMesg); + if (rc) { + goto free_and_return; + } + + /* Get the OID and the value included in the request */ + if (slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &oid ) != 0) { + errMesg = "Could not get OID and value from request.\n"; + rc = LDAP_OPERATIONS_ERROR; + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg); + goto free_and_return; + } + + if (strcasecmp(oid, JOIN_OID) == 0) { + ret = ipa_join(pb); + return ret; + } + + errMesg = "Request OID does not match supported OIDs.\n"; + rc = LDAP_OPERATIONS_ERROR; + +free_and_return: + slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_enrollment", errMesg); + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + + return SLAPI_PLUGIN_EXTENDED_SENT_RESULT; +} + +static int +ipaenrollment_start(Slapi_PBlock *pb) +{ + krb5_error_code krberr; + krb5_context krbctx; + char *config_dn = NULL; + char *partition_dn = NULL; + Slapi_Entry *config_entry = NULL; + int ret = LDAP_SUCCESS; + Slapi_DN *sdn; + int rc = 0; + + krberr = krb5_init_context(&krbctx); + if (krberr) { + slapi_log_error(SLAPI_LOG_FATAL, "ipaenrollment_init", + "krb5_init_context failed\n"); + return LDAP_OPERATIONS_ERROR; + } + + ret = krb5_get_default_realm(krbctx, &realm); + if (ret) { + slapi_log_error(SLAPI_LOG_FATAL, "ipaenrollment_init", + "Failed to get default realm?!\n"); + ret = LDAP_OPERATIONS_ERROR; + } + + if (slapi_pblock_get(pb, SLAPI_TARGET_DN, &config_dn) != 0) { + slapi_log_error( SLAPI_LOG_FATAL, "ipaenrollment_start", "No config DN?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + sdn = slapi_sdn_new_dn_byref(config_dn); + if ((rc = slapi_search_internal_get_entry(sdn, NULL, &config_entry, + ipaenrollment_plugin_id)) != LDAP_SUCCESS ){ + slapi_log_error(SLAPI_LOG_TRACE, "ipaenrollment_extop", + "ipaenrollment_start: No such entry-(%s), err (%d)\n", + config_dn, rc); + } + slapi_sdn_free(&sdn); + + partition_dn = slapi_entry_attr_get_charptr(config_entry, "nsslapd-realmtree"); + if (!partition_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Missing partition configuration entry (nsslapd-realmTree)!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ipa_realm_dn = slapi_ch_smprintf("cn=computers,cn=accounts,%s", partition_dn); + slapi_ch_free_string(&partition_dn); + if (!ipa_realm_dn) { + slapi_log_error( SLAPI_LOG_FATAL, "ipapwd_start", "Out of memory ?\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + +done: + if (krbctx) krb5_free_context(krbctx); + if (config_entry) slapi_entry_free(config_entry); + + return ret; +} + +int +ipaenrollment_init(Slapi_PBlock *pb) +{ + int ret; + + /* Get the arguments appended to the plugin extendedop directive + * in the plugin entry. The first argument + * (after the standard arguments for the directive) should + * contain the OID of the extended op. + */ + + ret = slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &ipaenrollment_plugin_id); + if ((ret != 0) || (NULL == ipaenrollment_plugin_id)) { + slapi_log_error(SLAPI_LOG_PLUGIN, + "ipaenrollment_init", "Could not get identity or identity was NULL\n"); + return -1; + } + + slapi_log_error(SLAPI_LOG_PLUGIN, "ipaenrollment_init", + "Registering plug-in for extended op.\n"); + + /* Register the plug-in function as an extended operation + plug-in function. */ + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)ipaenrollment_start); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipaenrollment_oid_list); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipaenrollment_name_list); + if (!ret) slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)ipaenrollment_extop); + + if (ret) { + slapi_log_error(SLAPI_LOG_PLUGIN, "ipaenrollment_init", + "Failed to set plug-in version, function, and OID.\n"); + return -1; + } + + return 0; +} diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c index 24acc8875..744d7dd3a 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c @@ -2088,6 +2088,7 @@ static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg) Slapi_Entry *targetEntry=NULL; struct berval *bval = NULL; Slapi_Value **svals = NULL; + Slapi_Value **evals = NULL; const char *bdn; const Slapi_DN *bsdn; Slapi_DN *sdn; @@ -2095,7 +2096,7 @@ static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg) Slapi_Entry **es = NULL; int scope, res; char *filter; - char *attrlist[] = {"krbPrincipalKey", "krbLastPwdChange", NULL }; + char *attrlist[] = {"krbPrincipalKey", "krbLastPwdChange", "userPassword", "krbPrincipalName", "enrolledBy", NULL }; krb5_context krbctx = NULL; krb5_principal krbname = NULL; krb5_error_code krberr; @@ -2108,6 +2109,8 @@ static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg) struct tm utctime; char timestr[GENERALIZED_TIME_LENGTH+1]; time_t time_now = time(NULL); + char *pw = NULL; + char *krbPrincipalName = NULL; svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); if (!svals) { @@ -2522,6 +2525,31 @@ static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg) slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbPrincipalKey", svals); + /* If we are creating a keytab for a host service attempt to remove + * the userPassword attribute if it exists + */ + pw = slapi_entry_attr_get_charptr(targetEntry, "userPassword"); + krbPrincipalName = slapi_entry_attr_get_charptr(targetEntry, "krbPrincipalName"); + if ((strncmp(krbPrincipalName, "host/", 5) == 0)) { + char * krbLastPwdChange = slapi_entry_attr_get_charptr(targetEntry, "krbLastPwdChange"); + char * enrolledBy = slapi_entry_attr_get_charptr(targetEntry, "enrolledBy"); + if (NULL == enrolledBy) { + evals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); + evals[0] = slapi_value_new_string(bindDN); + slapi_mods_add_mod_values(smods, LDAP_MOD_ADD, "enrolledBy", evals); + } else { + slapi_ch_free_string(&enrolledBy); + } + if ((NULL != pw) && (NULL == krbLastPwdChange)) { + slapi_mods_add_mod_values(smods, LDAP_MOD_DELETE, "userPassword", NULL); + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Removing userPassword from host entry\n"); + slapi_ch_free_string(&pw); + } + slapi_ch_free_string(&krbLastPwdChange); + } + slapi_ch_free_string(&krbPrincipalName); + /* commit changes */ ret = ipapwd_apply_mods(slapi_entry_get_dn_const(targetEntry), smods); @@ -2603,10 +2631,18 @@ free_and_return: } free(svals); } + if (evals) { + for (i = 0; evals[i]; i++) { + slapi_value_free(&evals[i]); + } + free(evals); + } if (krbname) krb5_free_principal(krbctx, krbname); if (krbctx) krb5_free_context(krbctx); + if (rc == LDAP_SUCCESS) + errMesg = NULL; slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop", errMesg ? errMesg : "success"); slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); @@ -2938,6 +2974,8 @@ static int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg, } sdn = slapi_sdn_new_dn_byref(dn); if (!sdn) { + slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", + "Unable to convert dn to sdn %s", dn?dn:"<NULL>"); *errMesg = "Internal Error"; rc = LDAP_OPERATIONS_ERROR; goto done; diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update index 220c489d9..ee7f4db92 100644 --- a/install/updates/40-delegation.update +++ b/install/updates/40-delegation.update @@ -222,8 +222,9 @@ add:aci: '(target = "ldap:///cn=*,cn=computers,cn=accounts,$SUFFIX")(version add:aci: '(target = "ldap:///cn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "Remove Hosts";allow (delete) groupdn = "ldap:///cn=removehosts,cn= taskgroups,cn=accounts,$SUFFIX";)' -add:aci: '(targetattr = "cn || description || locality || location || platform - || os")(target = "ldap:///cn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0; +add:aci: '(targetattr = "cn || description || l || location || + nshardwareplatform || nsosversion") + (target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0; acl "Modify Hosts";allow (write) groupdn = "ldap:///cn=modifyhosts, cn=taskgroups,cn=accounts,$SUFFIX";)' @@ -449,16 +450,36 @@ add:member:'cn=hostadmin,cn=rolegroups,cn=accounts,$SUFFIX' # Add the ACI needed to do host keytab admin dn: $SUFFIX -add:aci: '(targetattr = "krbPrincipalKey")(target = "ldap:///cn=*, - cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "Manage host keytab"; +add:aci: '(targetattr = "krbPrincipalKey || krbLastPwdChange") + (target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX") + (version 3.0;acl "Manage host keytab"; allow (write) groupdn = "ldap:///cn=manage_host_keytab,cn=taskgroups, cn=accounts,$SUFFIX";)' +# Taskgroup for enrolling hosts. Note that this also requires +# manage_host_keytab access +dn: cn=enroll_host,cn=taskgroups,cn=accounts,$SUFFIX +add:objectClass: top +add:objectClass: groupofnames +add:cn: enroll_host +add:description: Enroll a host +add:member:'cn=hostadmin,cn=rolegroups,cn=accounts,$SUFFIX' + +# Add the ACI needed to do host enrollment. When this occurs we +# set the krbPrincipalName, add krbPrincipalAux to objectClass and +# set enrolledBy to whoever ran join. +dn: $SUFFIX +add:aci: '(targetattr = "krbPrincipalName || enrolledBy || objectClass") + (target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX") + (version 3.0;acl "Enroll a host"; + allow (write) groupdn = "ldap:///cn=enroll_host,cn=taskgroups, + cn=accounts,$SUFFIX";)' + # Taskgroup for updating the DNS entries dn: cn=update_dns,cn=taskgroups,cn=accounts,$SUFFIX add:objectClass: top add:objectClass: groupofnames -add:cn: manage_host_keytab +add:cn: update_sn add:description: Updates DNS add:member:'cn=dnsadmin,cn=rolegroups,cn=accounts,$SUFFIX' add:member:'cn=dnsserver,cn=rolegroups,cn=accounts,$SUFFIX' diff --git a/ipa-client/Makefile.am b/ipa-client/Makefile.am index 796a923fa..9a8b5f690 100644 --- a/ipa-client/Makefile.am +++ b/ipa-client/Makefile.am @@ -22,6 +22,7 @@ INCLUDES = \ sbin_PROGRAMS = \ ipa-getkeytab \ + ipa-join \ $(NULL) ipa_getkeytab_SOURCES = \ @@ -36,6 +37,20 @@ ipa_getkeytab_LDADD = \ $(POPT_LIBS) \ $(NULL) +ipa_join_SOURCES = \ + config.c \ + ipa-join.c \ + $(NULL) + +ipa_join_LDADD = \ + $(KRB5_LIBS) \ + $(OPENLDAP_LIBS) \ + $(SASL_LIBS) \ + $(CURL_LIBS) \ + $(XMLRPC_LIBS) \ + $(POPT_LIBS) \ + $(NULL) + SUBDIRS = \ firefox \ ipaclient \ diff --git a/ipa-client/config.c b/ipa-client/config.c new file mode 100644 index 000000000..81cb793db --- /dev/null +++ b/ipa-client/config.c @@ -0,0 +1,155 @@ +/* Authors: Rob Crittenden <rcritten@redhat.com> + * + * Copyright (C) 2009 Red Hat + * see file 'COPYING' for use and warranty information + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2 only + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Simple and INI-style file reader. + * + * usage is: + * char * data = read_config_file("/path/to/something.conf") + * char * entry = get_config_entry(data, "section", "mykey") + * + * caller must free data and entry. + */ + +#define _GNU_SOURCE + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +char * +read_config_file(const char *filename) +{ + int fd; + struct stat st; + char *data, *dest; + size_t left; + + fd = open(filename, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "cannot open configuration file %s\n", filename); + return NULL; + } + + /* stat() the file so we know the size and can pre-allocate the right + * amount of memory. */ + if (fstat(fd, &st) == -1) { + fprintf(stderr, "cannot stat() configuration file %s\n", filename); + return NULL; + } + left = st.st_size; + data = malloc(st.st_size + 1); + dest = data; + while (left != 0) { + ssize_t res; + + res = read(fd, dest, left); + if (res == 0) + break; + if (res < 0) { + fprintf(stderr, "read error\n"); + close(fd); + free(dest); + return NULL; + } + dest += res; + left -= res; + } + close(fd); + *dest = 0; + return data; +} + +char * +get_config_entry(char * in_data, const char *section, const char *key) +{ + char *ptr, *p, *tmp; + char *line; + int in_section = 0; + char * data = strdup(in_data); + + for (line = strtok_r(data, "\n", &ptr); line != NULL; + line = strtok_r(NULL, "\n", &ptr)) { + /* Skip initial whitespace. */ + while (isspace((unsigned char)*line) && (*line != '\0')) + line++; + + /* If it's a comment, bail. */ + if (*line == '#') { + continue; + } + + /* If it's the beginning of a section, process it and clear the key + * and value values. */ + if (*line == '[') { + line++; + p = strchr(line, ']'); + if (p) { + tmp = strndup(line, p - line); + if (in_section) { + /* We exited the matching section without a match */ + free(data); + return NULL; + } + if (strcmp(section, tmp) == 0) { + free(tmp); + in_section = 1; + continue; + } + } + } /* [ */ + + p = strchr(line, '='); + if (p != NULL && in_section) { + /* Trim any trailing whitespace off the key name. */ + while (p != line && isspace((unsigned char)p[-1])) + p--; + + /* Save the key. */ + tmp = strndup(line, p - line); + if (strcmp(key, tmp) != 0) { + free(tmp); + } else { + free(tmp); + + /* Skip over any whitespace after the equal sign. */ + line = strchr(line, '='); + line++; + while (isspace((unsigned char)*line) && (*line != '\0')) + line++; + + /* Trim off any trailing whitespace. */ + p = strchr(line, '\0'); + while (p != line && isspace((unsigned char)p[-1])) + p--; + + /* Save the value. */ + tmp = strndup(line, p - line); + + free(data); + return tmp; + } + } + } + return NULL; +} diff --git a/ipa-client/configure.ac b/ipa-client/configure.ac index cce4e1865..c9d9e435e 100644 --- a/ipa-client/configure.ac +++ b/ipa-client/configure.ac @@ -157,6 +157,30 @@ if test "x$PYTHON" = "x" ; then fi dnl --------------------------------------------------------------------------- +dnl - Check for CURL +dnl --------------------------------------------------------------------------- + +CURL_LIBS= +AC_CHECK_HEADER(curl/curl.h) +AC_CHECK_LIB(curl, curl_easy_init, [CURL_LIBS="-lcurl"]) +if test "x$CURL_LIBS" = "x" ; then + AC_MSG_ERROR([curl not found]) +fi +AC_SUBST(CURL_LIBS) + +dnl --------------------------------------------------------------------------- +dnl - Check for XMLRPC-C +dnl --------------------------------------------------------------------------- + +XMLRPC_LIBS= +AC_CHECK_HEADER(xmlrpc-c/base.h) +AC_CHECK_LIB(xmlrpc_client, xmlrpc_client_init2, [XMLRPC_LIBS="-lxmlrpc -lxmlrpc_client -lxmlrpc_util"]) +if test "x$XMLRPC_LIBS" = "x" ; then + AC_MSG_ERROR([xmlrpc-c not found]) +fi +AC_SUBST(XMLRPC_LIBS) + +dnl --------------------------------------------------------------------------- dnl - Set the data install directory since we don't use pkgdatadir dnl --------------------------------------------------------------------------- diff --git a/ipa-client/ipa-getkeytab.c b/ipa-client/ipa-getkeytab.c index fbeb547a8..1bbb7759e 100644 --- a/ipa-client/ipa-getkeytab.c +++ b/ipa-client/ipa-getkeytab.c @@ -479,6 +479,8 @@ static int ldap_set_keytab(krb5_context krbctx, const char *servername, const char *principal_name, krb5_principal princ, + const char *binddn, + const char *bindpw, struct keys_container *keys) { int version; @@ -513,7 +515,20 @@ static int ldap_set_keytab(krb5_context krbctx, } /* TODO: support referrals ? */ - ld = ldap_init(servername, 389); + if (binddn) { + int ssl = LDAP_OPT_X_TLS_HARD;; + if (ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, "/etc/ipa/ca.crt") != LDAP_OPT_SUCCESS) { + goto error_out; + } + + ld = ldap_init(servername, 636); + if (ldap_set_option(ld, LDAP_OPT_X_TLS, &ssl) != LDAP_OPT_SUCCESS) { + goto error_out; + } + } else { + ld = ldap_init(servername, 389); + } + if(ld == NULL) { fprintf(stderr, "Unable to initialize ldap library!\n"); goto error_out; @@ -526,14 +541,22 @@ static int ldap_set_keytab(krb5_context krbctx, goto error_out; } - ret = ldap_sasl_interactive_bind_s(ld, - NULL, "GSSAPI", - NULL, NULL, - LDAP_SASL_QUIET, - ldap_sasl_interact, princ); - if (ret != LDAP_SUCCESS) { - fprintf(stderr, "SASL Bind failed!\n"); - goto error_out; + if (binddn) { + ret = ldap_bind_s(ld, binddn, bindpw, LDAP_AUTH_SIMPLE); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Simple bind failed\n"); + goto error_out; + } + } else { + ret = ldap_sasl_interactive_bind_s(ld, + NULL, "GSSAPI", + NULL, NULL, + LDAP_SASL_QUIET, + ldap_sasl_interact, princ); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "SASL Bind failed!\n"); + goto error_out; + } } /* find base dn */ @@ -686,6 +709,8 @@ int main(int argc, char *argv[]) static const char *principal = NULL; static const char *keytab = NULL; static const char *enctypes_string = NULL; + static const char *binddn = NULL; + static const char *bindpw = NULL; int quiet = 0; int askpass = 0; int permitted_enctypes = 0; @@ -697,6 +722,8 @@ int main(int argc, char *argv[]) { "enctypes", 'e', POPT_ARG_STRING, &enctypes_string, 0, "Encryption types to request", "Comma separated encryption types list" }, { "permitted-enctypes", 0, POPT_ARG_NONE, &permitted_enctypes, 0, "Show the list of permitted encryption types and exit", "Permitted Encryption Types"}, { "password", 'P', POPT_ARG_NONE, &askpass, 0, "Asks for a non-random password to use for the principal" }, + { "binddn", 'D', POPT_ARG_STRING, &binddn, 0, "LDAP DN", "DN to bind as if not using kerberos" }, + { "bindpw", 'w', POPT_ARG_STRING, &bindpw, 0, "LDAP password", "password to use if not using kerberos" }, { NULL, 0, POPT_ARG_NONE, NULL, 0, NULL, NULL } }; poptContext pc; @@ -751,6 +778,13 @@ int main(int argc, char *argv[]) exit(2); } + if (NULL!=binddn && NULL==bindpw) { + fprintf(stderr, "Bind password required when using a bind DN.\n"); + if (!quiet) + poptPrintUsage(pc, stderr, 0); + exit(10); + } + if (askpass) { password = ask_password(krbctx); if (!password) { @@ -773,6 +807,7 @@ int main(int argc, char *argv[]) exit(4); } + if (NULL == bindpw) { krberr = krb5_cc_default(krbctx, &ccache); if (krberr) { fprintf(stderr, "Kerberos Credential Cache not found\n" @@ -786,6 +821,7 @@ int main(int argc, char *argv[]) "Do you have a valid Credential Cache?\n"); exit(6); } + } krberr = krb5_kt_resolve(krbctx, ktname, &kt); if (krberr) { @@ -800,7 +836,7 @@ int main(int argc, char *argv[]) exit(8); } - kvno = ldap_set_keytab(krbctx, server, principal, uprinc, &keys); + kvno = ldap_set_keytab(krbctx, server, principal, uprinc, binddn, bindpw, &keys); if (!kvno) { exit(9); } diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install index 7701086c0..1966c18c3 100644 --- a/ipa-client/ipa-install/ipa-client-install +++ b/ipa-client/ipa-install/ipa-client-install @@ -225,6 +225,7 @@ def main(): defopts = [{'name':'basedn', 'type':'option', 'value':cli_basedn}, {'name':'realm', 'type':'option', 'value':cli_realm}, {'name':'domain', 'type':'option', 'value':cli_domain}, + {'name':'server', 'type':'option', 'value':cli_server}, {'name':'xmlrpc_uri', 'type':'option', 'value':'https://%s/ipa/xml' % cli_server}] opts.append({'name':'global', 'type':'section', 'value':defopts}) diff --git a/ipa-client/ipa-join.c b/ipa-client/ipa-join.c new file mode 100644 index 000000000..d08d3b578 --- /dev/null +++ b/ipa-client/ipa-join.c @@ -0,0 +1,648 @@ +/* Authors: Rob Crittenden <rcritten@redhat.com> + * + * Copyright (C) 2009 Red Hat + * see file 'COPYING' for use and warranty information + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2 only + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define _GNU_SOURCE +#define LDAP_DEPRECATED 1 + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/utsname.h> +#include <krb5.h> +/* Doesn't work w/mozldap */ +#include <ldap.h> +#include <popt.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/wait.h> + +#include "xmlrpc-c/base.h" +#include "xmlrpc-c/client.h" + +#define NAME "ipa-join" +#define VERSION "1.0" + +#define JOIN_OID "2.16.840.1.113730.3.8.3.53" + +#define CAFILE "/etc/ipa/ca.crt" + +#define IPA_CONFIG "/etc/ipa/default.conf" + +char * read_config_file(const char *filename); +char * get_config_entry(char * data, const char *section, const char *key); + +static int debug = 0; + +/* + * Translate some IPA exceptions into specific errors in this context. + */ +static int +handle_fault(xmlrpc_env * const envP) { + if (envP->fault_occurred) { + switch(envP->fault_code) { + case 2100: /* unable to add new host entry or write objectClass */ + fprintf(stderr, "No permission to join this host to the IPA domain.\n"); + break; + default: + fprintf(stderr, "%s\n", envP->fault_string); + } + return 1; + } + return 0; +} + +/* Get the IPA server from the configuration file. + * The caller is responsible for freeing this value + */ +static char * +getIPAserver(char * data) { + return get_config_entry(data, "global", "server"); +} + +/* Get the IPA realm from the configuration file. + * The caller is responsible for freeing this value + */ +static char * +getIPArealm(char * data) { + return get_config_entry(data, "global", "realm"); +} + +/* Make sure that the keytab is writable before doing anything */ +static int check_perms(const char *keytab) +{ + int ret; + int fd; + + ret = access(keytab, W_OK); + if (ret == -1) { + switch(errno) { + case EACCES: + fprintf(stderr, "No write permissions on keytab file '%s'\n", keytab); + break; + case ENOENT: + /* file doesn't exist, lets touch it and see if writable */ + fd = open(keytab, O_WRONLY | O_CREAT, 0600); + if (fd != -1) { + close(fd); + unlink(keytab); + return 0; + } + fprintf(stderr, "No write permissions on keytab file '%s'\n", keytab); + break; + default: + fprintf(stderr, "access() on %s failed: errno = %d\n", keytab, errno); + break; + } + return 1; + } + + return 0; +} + +/* + * Make an XML-RPC call to methodName. This uses the curl client to make + * a connection over SSL using the CA cert that should have been installed + * by ipa-client-install. + */ +static void +callRPC(xmlrpc_env * const envP, + xmlrpc_server_info * const serverInfoP, + const char * const methodName, + xmlrpc_value * const paramArrayP, + xmlrpc_value ** const resultPP) { + + struct xmlrpc_clientparms clientparms; + struct xmlrpc_curl_xportparms * curlXportParmsP = NULL; + xmlrpc_client * clientP = NULL; + + XMLRPC_ASSERT(xmlrpc_value_type(paramArrayP) == XMLRPC_TYPE_ARRAY); + + curlXportParmsP = malloc(sizeof(*curlXportParmsP)); + + /* Have curl do SSL certificate validation */ + curlXportParmsP->no_ssl_verifypeer = 1; + curlXportParmsP->no_ssl_verifyhost = 1; + curlXportParmsP->cainfo = "/etc/ipa/ca.crt"; + + clientparms.transport = "curl"; + clientparms.transportparmsP = (struct xmlrpc_xportparms *) + curlXportParmsP; + clientparms.transportparm_size = XMLRPC_CXPSIZE(cainfo); + xmlrpc_client_create(envP, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, + &clientparms, XMLRPC_CPSIZE(transportparm_size), + &clientP); + + /* Set up kerberos negotiate authentication in curl. */ + xmlrpc_server_info_set_user(envP, serverInfoP, ":", ""); + xmlrpc_server_info_allow_auth_negotiate(envP, serverInfoP); + + /* Perform the XML-RPC call */ + if (!envP->fault_occurred) { + xmlrpc_client_call2(envP, clientP, serverInfoP, methodName, paramArrayP, resultPP); + } + + /* Cleanup */ + xmlrpc_server_info_free(serverInfoP); + xmlrpc_client_destroy(clientP); + free((void*)clientparms.transportparmsP); +} + +/* The caller is responsible for unbinding the connection if ld is not NULL */ +static LDAP * +connect_ldap(const char *hostname, const char *binddn, const char *bindpw) { + LDAP *ld = NULL; + int ssl = LDAP_OPT_X_TLS_HARD; + int version = LDAP_VERSION3; + int ret; + + if (ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, CAFILE) != LDAP_OPT_SUCCESS) + goto fail; + + ld = (LDAP *)ldap_init(hostname, 636); + if (ldap_set_option(ld, LDAP_OPT_X_TLS, &ssl) != LDAP_OPT_SUCCESS) { + fprintf(stderr, "Unable to enable SSL in LDAP\n"); + goto fail; + } + + ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Unable to set LDAP version\n"); + goto fail; + } + + ret = ldap_bind_s(ld, binddn, bindpw, LDAP_AUTH_SIMPLE); + if (ret != LDAP_SUCCESS) { + if (debug) + fprintf(stderr, "Bind failed\n"); + goto fail; + } + + return ld; + +fail: + ldap_unbind_ext(ld, NULL, NULL); + return NULL; +} + +static int +get_root_dn(const char *ipaserver, char **ldap_base) +{ + LDAP *ld = NULL; + char *root_attrs[] = {"namingContexts", NULL}; + LDAPMessage *entry, *res = NULL; + struct berval **ncvals; + int ret, rval; + + ld = connect_ldap(ipaserver, NULL, NULL); + if (!ld) { + rval = 1; + goto done; + } + + ret = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE, + "objectclass=*", root_attrs, 0, + NULL, NULL, NULL, 0, &res); + + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Search for %s on rootdse failed with error %d", + root_attrs[0], ret); + goto done; + } + + /* for now just use the first result we get */ + entry = ldap_first_entry(ld, res); + ncvals = ldap_get_values_len(ld, entry, root_attrs[0]); + if (!ncvals) { + fprintf(stderr, "No values for %s", root_attrs[0]); + goto done; + } + + *ldap_base = strdup(ncvals[0]->bv_val); + + ldap_value_free_len(ncvals); + +done: + if (res) ldap_msgfree(res); + ldap_unbind_ext(ld, NULL, NULL); + + return rval; +} + +/* Join a host to the current IPA realm. + * + * There are several scenarios for this: + * 1. You are an IPA admin user with fullrights to add hosts and generate + * keytabs. + * 2. You are an IPA admin user with rights to generate keytabs but not + * write hosts. + * 3. You are a regular IPA user with a password that can be used to + * generate the host keytab. + * + * If a password is presented it will be used regardless of the rights of + * the user. + */ + +/* If we only have a bindpw then try to join in a bit of a degraded mode. + * This is going to duplicate some of the server-side code to determine + * the state of the entry. + */ +static int +join_ldap(const char *ipaserver, const char *hostname, const char ** binddn, const char *bindpw, const char ** princ, int quiet) +{ + LDAP *ld; + char *filter = NULL; + int rval = 0; + char *oidresult; + struct berval valrequest; + struct berval *valresult = NULL; + int rc, ret; + LDAPMessage *result, *e; + char *ldap_base = NULL; + char *search_base = NULL; + char * attrs[] = {"krbPrincipalName", NULL}; + struct berval **ncvals; + int has_principal = 0; + + *binddn = NULL; + + get_root_dn(ipaserver, &ldap_base); + + ld = connect_ldap(ipaserver, NULL, NULL); + if (!ld) { + rval = 1; + goto done; + } + /* Search for the entry. */ + asprintf(&filter, "(fqdn=%s)", hostname); + asprintf(&search_base, "cn=computers,cn=accounts,%s", ldap_base); + if (debug) { + fprintf(stderr, "Searching with %s in %s\n", filter, search_base); + } + if ((ret = ldap_search_ext_s(ld, ldap_base, LDAP_SCOPE_SUB, + filter, attrs, 0, NULL, NULL, LDAP_NO_LIMIT, + LDAP_NO_LIMIT, &result)) != LDAP_SUCCESS) { + fprintf(stderr, "ldap_search_ext_s: %s\n", ldap_err2string(ret)); + rval = 1; + goto ldap_done; + } + e = ldap_first_entry(ld, result); + if (!e) { + fprintf(stderr, "Unable to find host '%s'\n", hostname); + rval = 1; + goto ldap_done; + } + if ((*binddn = ldap_get_dn(ld, e)) == NULL) { + fprintf(stderr, "Unable to get binddn for host '%s'\n", hostname); + rval = 1; + goto ldap_done; + } + ncvals = ldap_get_values_len(ld, e, attrs[0]); + if (ncvals != NULL) { + /* This host is probably already registered. The krbprincipalname + * is not set on password protected entries, but lets try to bind + * anyway. + */ + has_principal = 1; + if (debug) + fprintf(stderr, "Host already has principal, trying bind anyway\n"); + } + + ldap_value_free_len(ncvals); + ldap_msgfree(result); + ldap_unbind_ext(ld, NULL, NULL); + + /* Now rebind as the host */ + ld = connect_ldap(ipaserver, *binddn, bindpw); + if (!ld) { + if (has_principal) + fprintf(stderr, "Host is already joined.\n"); + else + fprintf(stderr, "Incorrect password.\n"); + rval = 1; + goto done; + } + + valrequest.bv_val = (char *)hostname; + valrequest.bv_len = strlen(hostname); + + if ((rc = ldap_extended_operation_s(ld, JOIN_OID, &valrequest, NULL, NULL, &oidresult, &valresult)) != LDAP_SUCCESS) { + fprintf(stderr, "principal not found in host entry\n"); + if (debug) ldap_perror(ld, "ldap_extended_operation_s"); + rval = 12; + goto ldap_done; + } + + /* Get the value from the result returned by the server. */ + *princ = strdup(valresult->bv_val); + +ldap_done: + + free(filter); + free(search_base); + free(ldap_base); + ldap_unbind_ext(ld, NULL, NULL); + +done: + if (valresult) ber_bvfree(valresult); + return rval; +} + +static int +join_krb5(const char *ipaserver, const char *hostname, const char **hostdn, const char **princ, int quiet) { + xmlrpc_env env; + xmlrpc_value * paramArrayP = NULL; + xmlrpc_value * paramP = NULL; + xmlrpc_value * optionsP = NULL; + xmlrpc_value * resultP = NULL; + xmlrpc_value * structP = NULL; + xmlrpc_server_info * serverInfoP = NULL; + struct utsname uinfo; + xmlrpc_value *princP = NULL; + xmlrpc_value *krblastpwdchangeP = NULL; + xmlrpc_value *hostdnP = NULL; + const char *krblastpwdchange = NULL; + char * url = NULL; + int rval = 0; + + /* Start up our XML-RPC client library. */ + xmlrpc_client_init(XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION); + + uname(&uinfo); + + xmlrpc_env_init(&env); + +#if 1 + asprintf(&url, "https://%s:443/ipa/xml", ipaserver); +#else + asprintf(&url, "http://%s:8888/", ipaserver); +#endif + serverInfoP = xmlrpc_server_info_new(&env, url); + + paramArrayP = xmlrpc_array_new(&env); + + if (hostname == NULL) + paramP = xmlrpc_string_new(&env, uinfo.nodename); + else + paramP = xmlrpc_string_new(&env, hostname); +#ifdef REALM + if (!quiet) + printf("Joining %s to IPA realm %s\n", uinfo.nodename, iparealm); +#endif + xmlrpc_array_append_item(&env, paramArrayP, paramP); + xmlrpc_DECREF(paramP); + + optionsP = xmlrpc_build_value(&env, "{s:s,s:s}", + "nsosversion", uinfo.release, + "nshardwareplatform", uinfo.machine); + xmlrpc_array_append_item(&env, paramArrayP, optionsP); + xmlrpc_DECREF(optionsP); + + callRPC(&env, serverInfoP, "join", paramArrayP, &resultP); + if (handle_fault(&env)) { + rval = 1; + goto cleanup_xmlrpc; + } + + /* Return value is the form of an array. The first value is the + * DN, the second a struct of attribute values + */ + xmlrpc_array_read_item(&env, resultP, 0, &hostdnP); + xmlrpc_read_string(&env, hostdnP, &*hostdn); + xmlrpc_DECREF(hostdnP); + xmlrpc_array_read_item(&env, resultP, 1, &structP); + + xmlrpc_struct_find_value(&env, structP, "krbprincipalname", &princP); + if (princP) { + xmlrpc_value * singleprincP = NULL; + + /* FIXME: all values are returned as lists currently. Once this is + * fixed we can read the string directly. + */ + xmlrpc_array_read_item(&env, princP, 0, &singleprincP); + xmlrpc_read_string(&env, singleprincP, &*princ); + xmlrpc_DECREF(princP); + xmlrpc_DECREF(singleprincP); + } else { + fprintf(stderr, "principal not found in XML-RPC response\n"); + rval = 12; + goto cleanup; + } + xmlrpc_struct_find_value(&env, structP, "krblastpwdchange", &krblastpwdchangeP); + if (krblastpwdchangeP) { + xmlrpc_value * singleprincP = NULL; + + /* FIXME: all values are returned as lists currently. Once this is + * fixed we can read the string directly. + */ + xmlrpc_array_read_item(&env, krblastpwdchangeP, 0, &singleprincP); + xmlrpc_read_string(&env, singleprincP, &krblastpwdchange); + xmlrpc_DECREF(krblastpwdchangeP); + fprintf(stderr, "Host is already joined.\n"); + rval = 13; + goto cleanup; + } + +cleanup: + if (paramArrayP) xmlrpc_DECREF(paramArrayP); + if (resultP) xmlrpc_DECREF(resultP); + +cleanup_xmlrpc: + free(url); +// free((char *)princ); +// free((char *)hostdn); + free((char *)krblastpwdchange); + xmlrpc_env_clean(&env); + xmlrpc_client_cleanup(); + + return rval; +} + +static int +join(const char *hostname, const char *bindpw, const char *keytab, int quiet) +{ + int rval; + pid_t childpid = 0; + int status = 0; + char *ipaserver = NULL; + char *iparealm = NULL; + char * conf_data = NULL; + const char * princ = NULL; + const char * hostdn = NULL; + struct utsname uinfo; + + krb5_context krbctx = NULL; + krb5_ccache ccache = NULL; + krb5_principal uprinc = NULL; + krb5_error_code krberr; + + conf_data = read_config_file(IPA_CONFIG); + if ((ipaserver = getIPAserver(conf_data)) == NULL) { + fprintf(stderr, "Unable to determine IPA server from %s\n", IPA_CONFIG); + exit(1); + } +#if 1 + if ((iparealm = getIPArealm(conf_data)) == NULL) { + fprintf(stderr, "Unable to determine IPA realm from %s\n", IPA_CONFIG); + exit(1); + } +#endif + free(conf_data); + + if (NULL == hostname) { + uname(&uinfo); + hostname = strdup(uinfo.nodename); + } + + if (bindpw) + rval = join_ldap(ipaserver, hostname, &hostdn, bindpw, &princ, quiet); + else { + krberr = krb5_init_context(&krbctx); + if (krberr) { + fprintf(stderr, "Unable to join host: Kerberos context initialization failed\n"); + rval = 1; + goto cleanup; + } + krberr = krb5_cc_default(krbctx, &ccache); + if (krberr) { + fprintf(stderr, "Unable to join host: Kerberos Credential Cache not found\n"); + rval = 5; + goto cleanup; + } + + krberr = krb5_cc_get_principal(krbctx, ccache, &uprinc); + if (krberr) { + fprintf(stderr, "Unable to join host: Kerberos User Principal not found and host password not provided.\n"); + rval = 6; + goto cleanup; + } + rval = join_krb5(ipaserver, hostname, &hostdn, &princ, quiet); + } + + if (rval) goto cleanup; + + /* Fork off and let ipa-getkeytab generate the keytab for us */ + childpid = fork(); + + if (childpid < 0) { + fprintf(stderr, "fork() failed\n"); + rval = 1; + goto cleanup; + } + + if (childpid == 0) { + char *argv[12]; + char *path = "/usr/sbin/ipa-getkeytab"; + int arg = 0; + int err; + + argv[arg++] = path; + argv[arg++] = "-s"; + argv[arg++] = ipaserver; + argv[arg++] = "-p"; + argv[arg++] = (char *)princ; + argv[arg++] = "-k"; + argv[arg++] = (char *)keytab; + if (bindpw) { + argv[arg++] = "-D"; + argv[arg++] = (char *)hostdn; + argv[arg++] = "-w"; + argv[arg++] = (char *)bindpw; + } + argv[arg++] = NULL; + err = execv(path, argv); + if (err == -1) { + switch(errno) { + case ENOENT: + fprintf(stderr, "ipa-getkeytab not found\n"); + break; + case EACCES: + fprintf(stderr, "ipa-getkeytab has bad permissions?\n"); + break; + default: + fprintf(stderr, "executing ipa-getkeytab failed, errno %d\n", errno); + break; + } + } + } else { + wait(&status); + } + + if WIFEXITED(status) { + rval = WEXITSTATUS(status); + if (rval != 0) { + fprintf(stderr, "child exited with %d\n", rval); + } + } + +cleanup: + free((char *)princ); + if (bindpw) + ldap_memfree((void *)hostdn); + else + free((char *)hostdn); + free((char *)ipaserver); + free((char *)iparealm); + if (uprinc) krb5_free_principal(krbctx, uprinc); + if (ccache) krb5_cc_close(krbctx, ccache); + if (krbctx) krb5_free_context(krbctx); + + return rval; +} + +int +main(int argc, char **argv) { + static const char *hostname = NULL; + static const char *keytab = NULL; + static const char *bindpw = NULL; + int quiet = 0; + struct poptOption options[] = { + { "debug", 'd', POPT_ARG_NONE, &debug, 0, "Print the raw XML-RPC output", "XML-RPC debugging Output"}, + { "quiet", 'q', POPT_ARG_NONE, &quiet, 0, "Print as little as possible", "Output only on errors"}, + { "hostname", 'h', POPT_ARG_STRING, &hostname, 0, "Use this hostname instead of the node name", "Host Name" }, + { "keytab", 'k', POPT_ARG_STRING, &keytab, 0, "File were to store the keytab information", "Keytab File Name" }, + { "bindpw", 'w', POPT_ARG_STRING, &bindpw, 0, "LDAP password", "password to use if not using kerberos" }, + { NULL, 0, POPT_ARG_NONE, NULL, 0, NULL, NULL } + }; + poptContext pc; + int ret; + + pc = poptGetContext("ipa-join", argc, (const char **)argv, options, 0); + ret = poptGetNextOpt(pc); + if (ret != -1) { + if (!quiet) { + poptPrintUsage(pc, stderr, 0); + } + exit(1); + } + poptFreeContext(pc); + if (debug) + setenv("XMLRPC_TRACE_XML", "1", 1); + + if (!keytab) + keytab = "/etc/krb5.keytab"; + + ret = check_perms(keytab); + if (ret == 0) + ret = join(hostname, bindpw, keytab, quiet); + + exit(ret); +} diff --git a/ipa.spec.in b/ipa.spec.in index cd38b05a8..713a4c5d1 100644 --- a/ipa.spec.in +++ b/ipa.spec.in @@ -207,7 +207,7 @@ export CPPFLAGS="$CPPFLAGS %{optflags}" make version-update touch daemons/NEWS daemons/README daemons/AUTHORS daemons/ChangeLog touch install/NEWS install/README install/AUTHORS install/ChangeLog -cd ipa-client; ../autogen.sh --prefix=%{_usr} --sysconfdir=%{_sysconfdir} --localstatedir=%{_localstatedir} --libdir=%{_libdir} --mandir=%{_mandir}; cd .. +cd ipa-client; ../autogen.sh --prefix=%{_usr} --sysconfdir=%{_sysconfdir} --localstatedir=%{_localstatedir} --libdir=%{_libdir} --mandir=%{_mandir} --with-openldap; cd .. cd daemons; ../autogen.sh --prefix=%{_usr} --sysconfdir=%{_sysconfdir} --localstatedir=%{_localstatedir} --libdir=%{_libdir} --mandir=%{_mandir}; cd .. cd install; ../autogen.sh --prefix=%{_usr} --sysconfdir=%{_sysconfdir} --localstatedir=%{_localstatedir} --libdir=%{_libdir} --mandir=%{_mandir}; cd .. @@ -225,6 +225,7 @@ make install DESTDIR=%{buildroot} # Remove .la files from libtool - we don't want to package # these files rm %{buildroot}/%{plugin_dir}/libipa_pwd_extop.la +rm %{buildroot}/%{plugin_dir}/libipa_enrollment_extop.la rm %{buildroot}/%{plugin_dir}/libipa-memberof-plugin.la rm %{buildroot}/%{plugin_dir}/libipa_winsync.la @@ -355,6 +356,7 @@ fi %dir %{_usr}/share/ipa/updates/ %{_usr}/share/ipa/updates/* %attr(755,root,root) %{plugin_dir}/libipa_pwd_extop.so +%attr(755,root,root) %{plugin_dir}/libipa_enrollment_extop.so %attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so %attr(755,root,root) %{plugin_dir}/libipa_winsync.so %dir %{_localstatedir}/lib/ipa @@ -382,6 +384,7 @@ fi %doc LICENSE README %{_sbindir}/ipa-client-install %{_sbindir}/ipa-getkeytab +%{_sbindir}/ipa-join %dir %{_usr}/share/ipa %dir %{_usr}/share/ipa/ipaclient %dir %{_localstatedir}/lib/ipa-client diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py index 809ec319b..bf720abbc 100644 --- a/ipalib/plugins/host.py +++ b/ipalib/plugins/host.py @@ -21,13 +21,9 @@ Hosts/Machines (Identity) """ -import platform -import os -import sys - from ipalib import api, crud, errors, util from ipalib import Object -from ipalib import Str, Flag +from ipalib import Str, Flag, List from ipalib.plugins.service import split_principal from ipalib import uuid @@ -59,25 +55,6 @@ def validate_host(ugettext, fqdn): return 'Fully-qualified hostname required' return None -def determine_os(): - """ - Return OS name (e.g. redhat 10 Cambridge). - """ - (sysname, nodename, release, version, machine) = os.uname() - if sys.platform == 'linux2': - # something like 'fedora 9 Sulpher' - return unicode(' '.join(platform.dist())) - else: - # on Solaris this will be: 'SunOS 5.10' - return unicode(sysname + ' ' + release) - -def determine_platform(): - """ - Return platform name (e.g. i686). - """ - (sysname, nodename, release, version, machine) = os.uname() - return unicode(machine) - class host(Object): """ @@ -106,14 +83,10 @@ class host(Object): Str('nshardwareplatform?', cli_name='platform', doc='Hardware platform of the host (e.g. Lenovo T61)', - default=determine_platform(), - autofill=True, ), Str('nsosversion?', cli_name='os', doc='Operating System and version of the host (e.g. Fedora 9)', - default=determine_os(), - autofill=True, ), Str('userpassword?', cli_name='password', @@ -157,13 +130,6 @@ class host_add(crud.Create): # FIXME: do a DNS lookup to ensure host exists - current = util.get_current_principal() - if not current: - raise errors.NotFound(reason='Unable to determine current user') - entry_attrs['enrolledby'] = ldap.find_entry_by_attr( - 'krbprincipalname', current, 'posixAccount' - )[0] - # FIXME: add this attribute to cn=ipaconfig # config = ldap.get_ipa_config()[1] # kw['objectclass'] = config.get('ipahostobjectclasses') @@ -242,6 +208,15 @@ class host_mod(crud.Update): """ Modify host. """ + + takes_options = ( + Str('krbprincipalname?', + cli_name='principalname', + doc='Kerberos principal name for this host', + attribute=True + ), + ) + def execute(self, hostname, **kw): """ Execute the host-mod operation. @@ -261,6 +236,14 @@ class host_mod(crud.Update): entry_attrs = self.args_options_2_entry(**kw) + # Once a principal name is set it cannot be changed + if 'krbprincipalname' in entry_attrs: + (d, e) = api.Command['host_show'](hostname, all=True) + if 'krbprincipalname' in e: + raise errors.ACIError(info='Principal name already set, it is unchangeable.') + entry_attrs['objectclass'] = e['objectclass'] + entry_attrs['objectclass'].append('krbprincipalaux') + try: ldap.update_entry(dn, entry_attrs) except errors.EmptyModlist: @@ -349,8 +332,12 @@ class host_show(crud.Retrieve): """ takes_options = ( Flag('all', + cli_short_name='a', doc='Retrieve all attributes' ), + List('attrs?', + doc='comma-separated list of attributes to display' + ), ) def execute(self, hostname, **kw): @@ -371,7 +358,10 @@ class host_show(crud.Retrieve): if kw['all']: attrs_list = ['*'] else: - attrs_list = _default_attributes + if 'attrs' in kw: + attrs_list = kw['attrs'] + else: + attrs_list = _default_attributes return ldap.get_entry(dn, attrs_list) @@ -383,4 +373,3 @@ class host_show(crud.Retrieve): textui.print_entry(entry_attrs) api.register(host_show) - diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index eb0356289..ea9f26da2 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -172,6 +172,7 @@ class DsInstance(service.Service): self.step("enabling memberof plugin", self.__add_memberof_module) self.step("enabling referential integrity plugin", self.__add_referint_module) self.step("enabling winsync plugin", self.__add_winsync_module) + self.step("enabling IPA enrollment plugin", self.__add_enrollment_module) self.step("enabling ldapi", self.__enable_ldapi) self.step("configuring uniqueness plugin", self.__set_unique_attrs) self.step("creating indices", self.__create_indices) @@ -316,6 +317,9 @@ class DsInstance(service.Service): def __add_winsync_module(self): self._ldap_mod("ipa-winsync-conf.ldif") + def __add_enrollment_module(self): + self._ldap_mod("enrollment-conf.ldif", self.sub_dict) + def __enable_ssl(self): dirname = config_dirname(self.serverid) dsdb = certs.CertDB(dirname) diff --git a/ipalib/plugins/join.py b/ipaserver/plugins/join.py index ab029e43c..b63000d89 100644 --- a/ipalib/plugins/join.py +++ b/ipaserver/plugins/join.py @@ -67,6 +67,14 @@ class join(Command): create_default=lambda **kw: get_realm(), autofill=True, ), + Str('nshardwareplatform?', + cli_name='platform', + doc='Hardware platform of the host (e.g. Lenovo T61)', + ), + Str('nsosversion?', + cli_name='os', + doc='Operating System and version of the host (e.g. Fedora 9)', + ), ) def execute(self, hostname, **kw): @@ -79,37 +87,34 @@ class join(Command): :param kw: Keyword arguments for the other attributes. """ assert 'cn' not in kw + ldap = self.api.Backend.ldap2 + host = None try: - host = api.Command['host_show'](hostname) + # First see if the host exists + kw = {'fqdn': hostname, 'all': True} + (dn, attrs_list) = api.Command['host_show'](**kw) + + # If no principal name is set yet we need to try to add + # one. + if 'krbprincipalname' not in attrs_list: + service = "host/%s@%s" % (hostname, api.env.realm) + (d, a) = api.Command['host_mod'](hostname, krbprincipalname=service) + + # It exists, can we write the password attributes? + allowed = ldap.can_write(dn, 'krblastpwdchange') + if not allowed: + raise errors.ACIError(info="Insufficient 'write' privilege to the 'krbLastPwdChange' attribute of entry '%s'." % dn) + + kw = {'fqdn': hostname, 'all': True} + (dn, attrs_list) = api.Command['host_show'](**kw) except errors.NotFound: - pass - else: - raise errors.DuplicateEntry + (dn, attrs_list) = api.Command['host_add'](hostname) - return api.Command['host_add'](hostname) + return (dn, attrs_list) def output_for_cli(self, textui, result, args, **options): textui.print_plain("Welcome to the %s realm" % options['realm']) textui.print_plain("Your keytab is in %s" % result.get('keytab')) - def run(self, *args, **options): - """ - Dispatch to forward() and execute() to do work locally and on the - server. - """ - if self.env.in_server: - return self.execute(*args, **options) - - # This forward will call the server-side portion of join - result = self.forward(*args, **options) - - self._get_keytab(result['krbprincipalname']) - result['keytab'] = '/etc/krb5.keytab' - return result - - def _get_keytab(self, principal, stdin=None): - args = ["/usr/sbin/ipa-getkeytab", "-s", self.env.host, "-p", principal,"-k", "/etc/krb5.keytab"] - return ipautil.run(args, stdin) - api.register(join) diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index c854dac28..0deded937 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -31,15 +31,18 @@ import os import socket import string +import krbV import ldap as _ldap import ldap.filter as _ldap_filter import ldap.sasl as _ldap_sasl +from ldap.controls import LDAPControl # for backward compatibility from ldap.functions import explode_dn from ipalib import api, errors from ipalib.crud import CrudBackend from ipalib.encoder import Encoder, encode_args, decode_retval +from ipalib.request import context # attribute syntax to python type mapping, 'SYNTAX OID': type # everything not in this dict is considered human readable unicode @@ -140,7 +143,11 @@ _schema = _load_schema(api.env.ldap_uri) def _get_syntax(attr, value): schema = api.Backend.ldap2._schema - return schema.get_obj(_ldap.schema.AttributeType, attr).syntax + obj = schema.get_obj(_ldap.schema.AttributeType, attr) + if obj is not None: + return obj.syntax + else: + return None # ldap backend class @@ -215,6 +222,9 @@ class ldap2(CrudBackend, Encoder): if ccache is not None: os.environ['KRB5CCNAME'] = ccache conn.sasl_interactive_bind_s('', _sasl_auth) + principal = krbV.CCache(name=ccache, + context=krbV.default_context()).principal().name + setattr(context, "principal", principal) else: # no kerberos ccache, use simple bind conn.simple_bind_s(bind_dn, bind_pw) @@ -486,6 +496,36 @@ class ldap2(CrudBackend, Encoder): return copy.deepcopy(self._schema) @encode_args(1, 2) + def get_effective_rights(self, dn, entry_attrs): + """Returns the rights the currently bound user has for the given DN. + + Returns 2 attributes, the attributeLevelRights for the given list of + attributes and the entryLevelRights for the entry itself. + """ + principal = getattr(context, 'principal') + (binddn, attrs) = self.find_entry_by_attr("krbprincipalname", principal, "posixAccount") + sctrl = [LDAPControl("1.3.6.1.4.1.42.2.27.9.5.2", True, "dn: " + binddn.encode('UTF-8'))] + self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, sctrl) + (dn, attrs) = self.get_entry(dn, entry_attrs) + # remove the control so subsequent operations don't include GER + self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, []) + return (dn, attrs) + + @encode_args(1, 2) + def can_write(self, dn, attr): + """Returns True/False if the currently bound user has write permissions + on the attribute. This only operates on a single attribute at a time. + """ + (dn, attrs) = self.get_effective_rights(dn, [attr]) + if 'attributelevelrights' in attrs: + attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8') + (attr, rights) = attr_rights.split(':') + if 'w' in rights: + return True + + return False + + @encode_args(1, 2) def update_entry_rdn(self, dn, new_rdn, del_old=True): """ Update entry's relative distinguished name. |