mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
committed by
Jason Gerard DeRose
parent
4f4d57cd30
commit
d0587cbdd5
4
Makefile
4
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)
|
||||
|
@@ -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
|
||||
|
@@ -1,6 +1,7 @@
|
||||
NULL =
|
||||
|
||||
SUBDIRS = \
|
||||
ipa-enrollment \
|
||||
ipa-pwd-extop \
|
||||
ipa-memberof \
|
||||
ipa-winsync \
|
||||
|
42
daemons/ipa-slapi-plugins/ipa-enrollment/Makefile.am
Normal file
42
daemons/ipa-slapi-plugins/ipa-enrollment/Makefile.am
Normal 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
|
@@ -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
|
457
daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c
Normal file
457
daemons/ipa-slapi-plugins/ipa-enrollment/ipa_enrollment.c
Normal 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;
|
||||
}
|
@@ -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;
|
||||
|
@@ -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'
|
||||
|
@@ -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
155
ipa-client/config.c
Normal 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;
|
||||
}
|
@@ -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 ---------------------------------------------------------------------------
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
648
ipa-client/ipa-join.c
Normal 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);
|
||||
}
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
@@ -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):
|
||||
"""
|
||||
|
Reference in New Issue
Block a user