summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--daemons/configure.ac1
-rw-r--r--daemons/ipa-slapi-plugins/Makefile.am1
-rw-r--r--daemons/ipa-slapi-plugins/ipa-enrollment/Makefile.am42
-rw-r--r--daemons/ipa-slapi-plugins/ipa-enrollment/enrollment-conf.ldif16
-rw-r--r--daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c457
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c40
-rw-r--r--install/updates/40-delegation.update31
-rw-r--r--ipa-client/Makefile.am15
-rw-r--r--ipa-client/config.c155
-rw-r--r--ipa-client/configure.ac24
-rw-r--r--ipa-client/ipa-getkeytab.c56
-rw-r--r--ipa-client/ipa-install/ipa-client-install1
-rw-r--r--ipa-client/ipa-join.c648
-rw-r--r--ipa.spec.in5
-rw-r--r--ipalib/plugins/host.py63
-rw-r--r--ipaserver/install/dsinstance.py4
-rw-r--r--ipaserver/plugins/join.py (renamed from ipalib/plugins/join.py)53
-rw-r--r--ipaserver/plugins/ldap2.py42
19 files changed, 1577 insertions, 81 deletions
diff --git a/Makefile b/Makefile
index 0e4a3e034..d2c26f360 100644
--- a/Makefile
+++ b/Makefile
@@ -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.