Enrollment for a host in an IPA domain

This will create a host service principal and may create a host entry (for
admins).  A keytab will be generated, by default in /etc/krb5.keytab
If no kerberos credentails are available then enrollment over LDAPS is used
if a password is provided.

This change requires that openldap be used as our C LDAP client. It is much
easier to do SSL using openldap than mozldap (no certdb required). Otherwise
we'd have to write a slew of extra code to create a temporary cert database,
import the CA cert, ...
This commit is contained in:
Rob Crittenden
2009-09-14 17:04:08 -04:00
committed by Jason Gerard DeRose
parent 4f4d57cd30
commit d0587cbdd5
19 changed files with 1578 additions and 82 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -1,6 +1,7 @@
NULL =
SUBDIRS = \
ipa-enrollment \
ipa-pwd-extop \
ipa-memberof \
ipa-winsync \

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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'

View File

@@ -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 \

155
ipa-client/config.c Normal file
View File

@@ -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;
}

View File

@@ -156,6 +156,30 @@ if test "x$PYTHON" = "x" ; then
AC_MSG_ERROR([Python not found])
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 ---------------------------------------------------------------------------

View File

@@ -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);
}

View File

@@ -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})

648
ipa-client/ipa-join.c Normal file
View File

@@ -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);
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
except errors.NotFound:
pass
else:
raise errors.DuplicateEntry
# First see if the host exists
kw = {'fqdn': hostname, 'all': True}
(dn, attrs_list) = api.Command['host_show'](**kw)
return api.Command['host_add'](hostname)
# 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:
(dn, attrs_list) = 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)

View File

@@ -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)
@@ -485,6 +495,36 @@ class ldap2(CrudBackend, Encoder):
"""Returns a copy of the current LDAP schema."""
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):
"""