mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-01-27 16:46:42 -06:00
dbf5df4a66
NIST SP 800-63-3B sets a recommendation to have password length upper bound limited in A.2: https://pages.nist.gov/800-63-3/sp800-63b.html#appA Users should be encouraged to make their passwords as lengthy as they want, within reason. Since the size of a hashed password is independent of its length, there is no reason not to permit the use of lengthy passwords (or pass phrases) if the user wishes. Extremely long passwords (perhaps megabytes in length) could conceivably require excessive processing time to hash, so it is reasonable to have some limit. FreeIPA already applied 256 characters limit for non-random passwords set through ipa-getkeytab tool. The limit was not, however, enforced in other places. MIT Kerberos limits the length of the password to 1024 characters in its tools. However, these tools (kpasswd and 'cpw' command of kadmin) do not differentiate between a password larger than 1024 and a password of 1024 characters. As a result, longer passwords are silently cut off. To prevent silent cut off for user passwords, use limit of 1000 characters. Thus, this patch enforces common limit of 1000 characters everywhere: - LDAP-based password changes - LDAP password change control - LDAP ADD and MOD operations on clear-text userPassword - Keytab setting with ipa-getkeytab - Kerberos password setting and changing Fixes: https://pagure.io/freeipa/issue/8268 Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> Signed-off-by: Rob Crittenden <rcritten@redhat.com> Reviewed-by: Simo Sorce <ssorce@redhat.com> Reviewed-By: Simo Sorce <ssorce@redhat.com>
2045 lines
62 KiB
C
2045 lines
62 KiB
C
/** 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, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Additional permission under GPLv3 section 7:
|
|
*
|
|
* In the following paragraph, "GPL" means the GNU General Public
|
|
* License, version 3 or any later version, and "Non-GPL Code" means
|
|
* code that is governed neither by the GPL nor a license
|
|
* compatible with the GPL.
|
|
*
|
|
* You may link the code of this Program with Non-GPL Code and convey
|
|
* linked combinations including the two, provided that such Non-GPL
|
|
* Code only links 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 GPL. Only the copyright holders of this
|
|
* Program may make changes or additions to the list of Approved
|
|
* Interfaces.
|
|
*
|
|
* Authors:
|
|
* Simo Sorce <ssorce@redhat.com>
|
|
*
|
|
* Copyright (C) 2007-2010 Red Hat, Inc.
|
|
* All rights reserved.
|
|
* END COPYRIGHT BLOCK **/
|
|
|
|
#include "ipapwd.h"
|
|
#include "util.h"
|
|
#include "../libotp/otp_config.h"
|
|
#include "ipa_asn1.h"
|
|
|
|
/*
|
|
* Password Modify - LDAP Extended Operation.
|
|
* RFC 3062
|
|
*
|
|
*
|
|
* This plugin implements the "Password Modify - LDAP3"
|
|
* extended operation for LDAP. The plugin function is called by
|
|
* the server if an LDAP client request contains the OID:
|
|
* "1.3.6.1.4.1.4203.1.11.1".
|
|
*
|
|
*/
|
|
|
|
/* ber tags for the PasswdModifyRequestValue sequence */
|
|
#define LDAP_EXTOP_PASSMOD_TAG_USERID 0x80U
|
|
#define LDAP_EXTOP_PASSMOD_TAG_OLDPWD 0x81U
|
|
#define LDAP_EXTOP_PASSMOD_TAG_NEWPWD 0x82U
|
|
|
|
/* ber tags for the PasswdModifyResponseValue sequence */
|
|
#define LDAP_EXTOP_PASSMOD_TAG_GENPWD 0x80U
|
|
|
|
/* OID of the extended operation handled by this plug-in */
|
|
#define EXOP_PASSWD_OID "1.3.6.1.4.1.4203.1.11.1"
|
|
|
|
/* OID to retrieve keytabs */
|
|
#define KEYTAB_SET_OID "2.16.840.1.113730.3.8.10.1"
|
|
#define KEYTAB_RET_OID "2.16.840.1.113730.3.8.10.2"
|
|
|
|
|
|
|
|
/* base DN of IPA realm tree */
|
|
const char *ipa_realm_tree;
|
|
/* dn of Kerberos realm entry */
|
|
const char *ipa_realm_dn;
|
|
const char *ipa_pwd_config_dn;
|
|
const char *ipa_etc_config_dn;
|
|
const char *ipa_changepw_principal_dn;
|
|
|
|
Slapi_PluginDesc ipapwd_plugin_desc = {
|
|
IPAPWD_FEATURE_DESC,
|
|
"FreeIPA project",
|
|
"FreeIPA/1.0",
|
|
IPAPWD_PLUGIN_DESC
|
|
};
|
|
|
|
void *ipapwd_plugin_id;
|
|
static int usetxn = 0;
|
|
|
|
extern struct otp_config *otp_config;
|
|
|
|
void *ipapwd_get_plugin_id(void)
|
|
{
|
|
return ipapwd_plugin_id;
|
|
}
|
|
|
|
static void filter_keys(struct ipapwd_krbcfg *krbcfg,
|
|
struct ipapwd_keyset *kset,
|
|
bool allow_nthash)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < kset->num_keys; i++) {
|
|
for (j = 0; j < krbcfg->num_supp_encsalts; j++) {
|
|
if (kset->keys[i].key_data_type[0] ==
|
|
krbcfg->supp_encsalts[j].ks_enctype) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if requested by the caller, allow arcfour-hmac even
|
|
* if it is missing in the list of supported enctypes. */
|
|
if (allow_nthash &&
|
|
(ENCTYPE_ARCFOUR_HMAC == kset->keys[i].key_data_type[0])) {
|
|
break;
|
|
}
|
|
|
|
if (j == krbcfg->num_supp_encsalts) { /* not valid */
|
|
|
|
/* free key */
|
|
free(kset->keys[i].key_data_contents[0]);
|
|
free(kset->keys[i].key_data_contents[1]);
|
|
|
|
/* move all remaining keys up by one */
|
|
kset->num_keys -= 1;
|
|
|
|
for (j = i; j < kset->num_keys; j++) {
|
|
kset->keys[j] = kset->keys[j + 1];
|
|
}
|
|
|
|
/* new key has been moved to this position, make sure
|
|
* we do not skip it, by neutralizing next increment */
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void filter_enctypes(struct ipapwd_krbcfg *krbcfg,
|
|
krb5_key_salt_tuple *kenctypes,
|
|
int *num_kenctypes,
|
|
bool allow_nthash)
|
|
{
|
|
/* first filter for duplicates */
|
|
for (int i = 0; i + 1 < *num_kenctypes; i++) {
|
|
for (int j = i + 1; j < *num_kenctypes; j++) {
|
|
if (kenctypes[i].ks_enctype == kenctypes[j].ks_enctype) {
|
|
/* duplicate, filter out */
|
|
for (int k = j; k + 1 < *num_kenctypes; k++) {
|
|
kenctypes[k].ks_enctype = kenctypes[k + 1].ks_enctype;
|
|
kenctypes[k].ks_salttype = kenctypes[k + 1].ks_salttype;
|
|
}
|
|
(*num_kenctypes)--;
|
|
j--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* then filter for supported */
|
|
for (int i = 0; i < *num_kenctypes; i++) {
|
|
int j;
|
|
|
|
/* Check if supported */
|
|
for (j = 0; j < krbcfg->num_supp_encsalts; j++) {
|
|
if (kenctypes[i].ks_enctype ==
|
|
krbcfg->supp_encsalts[j].ks_enctype) {
|
|
break;
|
|
}
|
|
}
|
|
/* if requested by the caller, allow arcfour-hmac even
|
|
* if it is missing in the list of supported enctypes. */
|
|
if (allow_nthash &&
|
|
(ENCTYPE_ARCFOUR_HMAC == kenctypes[i].ks_enctype)) {
|
|
break;
|
|
}
|
|
if (j == krbcfg->num_supp_encsalts) {
|
|
/* Unsupported, filter out */
|
|
for (int k = i; k + 1 < *num_kenctypes; k++) {
|
|
kenctypes[k].ks_enctype = kenctypes[k + 1].ks_enctype;
|
|
kenctypes[k].ks_salttype = kenctypes[k + 1].ks_salttype;
|
|
}
|
|
(*num_kenctypes)--;
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int ipapwd_to_ldap_pwpolicy_error(int ipapwderr)
|
|
{
|
|
switch (ipapwderr) {
|
|
case IPAPWD_POLICY_ACCOUNT_EXPIRED:
|
|
return LDAP_PWPOLICY_PWDMODNOTALLOWED;
|
|
case IPAPWD_POLICY_PWD_TOO_YOUNG:
|
|
return LDAP_PWPOLICY_PWDTOOYOUNG;
|
|
case IPAPWD_POLICY_PWD_TOO_SHORT:
|
|
return LDAP_PWPOLICY_PWDTOOSHORT;
|
|
case IPAPWD_POLICY_PWD_IN_HISTORY:
|
|
return LDAP_PWPOLICY_PWDINHISTORY;
|
|
case IPAPWD_POLICY_PWD_COMPLEXITY:
|
|
return LDAP_PWPOLICY_INVALIDPWDSYNTAX;
|
|
}
|
|
/* in case of unhandled error return access denied */
|
|
return LDAP_PWPOLICY_PWDMODNOTALLOWED;
|
|
}
|
|
|
|
|
|
static int ipapwd_chpwop(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
|
|
{
|
|
char *bindDN = NULL;
|
|
char *authmethod = NULL;
|
|
char *dn = NULL;
|
|
char *oldPasswd = NULL;
|
|
char *newPasswd = NULL;
|
|
char *errMesg = NULL;
|
|
int ret=0, rc=0, is_root=0;
|
|
ber_tag_t tag=0;
|
|
ber_len_t len=-1;
|
|
struct berval *extop_value = NULL;
|
|
BerElement *ber = NULL;
|
|
Slapi_Entry *targetEntry=NULL;
|
|
Slapi_Value *objectclass=NULL;
|
|
char *attrlist[] = {"*", "passwordHistory", NULL };
|
|
struct ipapwd_data pwdata;
|
|
int is_krb, is_smb, is_ipant, is_memberof;
|
|
char *principal = NULL;
|
|
Slapi_PBlock *chpwop_pb = NULL;
|
|
Slapi_DN *target_sdn = NULL;
|
|
const char *target_dn = NULL;
|
|
|
|
/* Get the ber value of the extended operation */
|
|
slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
|
|
|
|
if (extop_value == NULL ||
|
|
(extop_value->bv_len == 0 || extop_value->bv_val == NULL)) {
|
|
errMesg = "PasswdModify Request empty.\n";
|
|
rc = LDAP_UNWILLING_TO_PERFORM;
|
|
goto free_and_return;
|
|
}
|
|
|
|
if ((ber = ber_init(extop_value)) == NULL)
|
|
{
|
|
errMesg = "PasswdModify Request decode failed.\n";
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* Format of request to parse
|
|
*
|
|
* PasswdModifyRequestValue ::= SEQUENCE {
|
|
* userIdentity [0] OCTET STRING OPTIONAL
|
|
* oldPasswd [1] OCTET STRING OPTIONAL
|
|
* newPasswd [2] OCTET STRING OPTIONAL }
|
|
*
|
|
* The request value field is optional. If it is
|
|
* provided, at least one field must be filled in.
|
|
*/
|
|
|
|
/* ber parse code */
|
|
if ( ber_scanf( ber, "{") == LBER_ERROR )
|
|
{
|
|
/* The request field wasn't provided. We'll
|
|
* now try to determine the userid and verify
|
|
* knowledge of the old password via other
|
|
* means.
|
|
*/
|
|
goto parse_req_done;
|
|
} else {
|
|
tag = ber_peek_tag( ber, &len);
|
|
}
|
|
|
|
/* identify userID field by tags */
|
|
if (tag == LDAP_EXTOP_PASSMOD_TAG_USERID )
|
|
{
|
|
if (ber_scanf(ber, "a", &dn) == LBER_ERROR) {
|
|
slapi_ch_free_string(&dn);
|
|
errMesg = "ber_scanf failed at userID parse.\n";
|
|
LOG_FATAL("%s", errMesg);
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
tag = ber_peek_tag(ber, &len);
|
|
}
|
|
|
|
/* identify oldPasswd field by tags */
|
|
if (tag == LDAP_EXTOP_PASSMOD_TAG_OLDPWD )
|
|
{
|
|
if (ber_scanf(ber, "a", &oldPasswd) == LBER_ERROR) {
|
|
errMesg = "ber_scanf failed at oldPasswd parse.\n";
|
|
LOG_FATAL("%s", errMesg);
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
tag = ber_peek_tag(ber, &len);
|
|
}
|
|
|
|
/* identify newPasswd field by tags */
|
|
if (tag == LDAP_EXTOP_PASSMOD_TAG_NEWPWD )
|
|
{
|
|
if (ber_scanf(ber, "a", &newPasswd) == LBER_ERROR) {
|
|
errMesg = "ber_scanf failed at newPasswd parse.\n";
|
|
LOG_FATAL("%s", errMesg);
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
}
|
|
|
|
parse_req_done:
|
|
/* Uncomment for debugging, otherwise we don't want to leak the
|
|
* password values into the log... */
|
|
/* LDAPDebug( LDAP_DEBUG_ARGS, "passwd: dn (%s), oldPasswd (%s),
|
|
* newPasswd (%s)\n", dn, oldPasswd, newPasswd); */
|
|
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* A new password was not supplied in the request, and we do not support
|
|
* password generation yet.
|
|
*/
|
|
if (newPasswd == NULL || *newPasswd == '\0') {
|
|
errMesg = "Password generation not implemented.\n";
|
|
rc = LDAP_UNWILLING_TO_PERFORM;
|
|
goto free_and_return;
|
|
}
|
|
|
|
rc = ipapwd_check_max_pwd_len(strlen(newPasswd), &errMesg);
|
|
if (rc) {
|
|
goto free_and_return;
|
|
}
|
|
|
|
if (oldPasswd == NULL || *oldPasswd == '\0') {
|
|
/* If user is authenticated, they already gave their password during
|
|
the bind operation (or used sasl or client cert auth or OS creds) */
|
|
slapi_pblock_get(pb, SLAPI_CONN_AUTHMETHOD, &authmethod);
|
|
if (!authmethod || !strcmp(authmethod, SLAPD_AUTH_NONE)) {
|
|
errMesg = "User must be authenticated to the directory server.\n";
|
|
rc = LDAP_INSUFFICIENT_ACCESS;
|
|
goto free_and_return;
|
|
}
|
|
}
|
|
|
|
/* Determine the target DN for this operation */
|
|
slapi_pblock_get(pb, SLAPI_TARGET_SDN, &target_sdn);
|
|
if (target_sdn != NULL) {
|
|
/* If there is a TARGET_SDN we are consuming it */
|
|
slapi_pblock_set(pb, SLAPI_TARGET_SDN, NULL);
|
|
target_dn = slapi_sdn_get_ndn(target_sdn);
|
|
}
|
|
if (target_dn == NULL || *target_dn == '\0') {
|
|
/* Did they give us a DN ? */
|
|
if (dn == NULL || *dn == '\0') {
|
|
/* Get the DN from the bind identity on this connection */
|
|
dn = slapi_ch_strdup(bindDN);
|
|
LOG_TRACE("Missing userIdentity in request, "
|
|
"using the bind DN instead.\n");
|
|
}
|
|
LOG_TRACE("extop dn %s (from ber)\n", dn ? dn : "<empty>");
|
|
} else {
|
|
/* At this point if SLAPI_TARGET_SDN was set that means
|
|
* that a SLAPI_PLUGIN_PRE_EXTOP_FN plugin sets it
|
|
* So take this one rather that the raw one that is in the ber
|
|
*/
|
|
LOG_TRACE("extop dn %s was translated to %s\n", dn ? dn : "<empty>", target_dn);
|
|
slapi_ch_free_string(&dn);
|
|
dn = slapi_ch_strdup(target_dn);
|
|
}
|
|
slapi_sdn_free(&target_sdn);
|
|
|
|
if (slapi_pblock_set( pb, SLAPI_ORIGINAL_TARGET, dn )) {
|
|
LOG_FATAL("slapi_pblock_set failed!\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
if (usetxn) {
|
|
Slapi_DN *sdn = slapi_sdn_new_dn_byref(dn);
|
|
Slapi_Backend *be = slapi_be_select(sdn);
|
|
slapi_sdn_free(&sdn);
|
|
if (be) {
|
|
chpwop_pb = slapi_pblock_new();
|
|
if (slapi_pblock_set(chpwop_pb, SLAPI_BACKEND, be)) {
|
|
LOG_FATAL("slapi_pblock_set failed!\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
rc = slapi_back_transaction_begin(chpwop_pb);
|
|
if (rc) {
|
|
LOG_FATAL("failed to start transaction\n");
|
|
}
|
|
} else {
|
|
LOG_FATAL("failed to get be backend from %s\n", dn);
|
|
}
|
|
}
|
|
|
|
/* Now we have the DN, look for the entry */
|
|
target_sdn = slapi_sdn_new_dn_byval(dn);
|
|
ret = ipapwd_getEntry(target_sdn, &targetEntry, attrlist);
|
|
slapi_sdn_free(&target_sdn);
|
|
|
|
/* If we can't find the entry, then that's an error */
|
|
if (ret) {
|
|
/* Couldn't find the entry, fail */
|
|
errMesg = "No such Entry exists.\n" ;
|
|
rc = LDAP_NO_SUCH_OBJECT;
|
|
goto free_and_return;
|
|
}
|
|
|
|
if (dn) {
|
|
Slapi_DN *bind_sdn;
|
|
|
|
/* if the user changing the password is self, we must request the
|
|
* old password and verify it matches the current one before
|
|
* proceeding with the password change */
|
|
bind_sdn = slapi_sdn_new_dn_byval(bindDN);
|
|
target_sdn = slapi_sdn_new_dn_byval(dn);
|
|
|
|
rc = (!bind_sdn || !target_sdn) ? LDAP_OPERATIONS_ERROR : 0;
|
|
|
|
/* this one will normalize and compare, so difference in case will be
|
|
* correctly handled */
|
|
ret = slapi_sdn_compare(bind_sdn, target_sdn);
|
|
|
|
slapi_sdn_free(&bind_sdn);
|
|
slapi_sdn_free(&target_sdn);
|
|
|
|
/* rc should always be 0 (else slapi_sdn_new_dn_byval should have sigsev)
|
|
* but if we end in rc==LDAP_OPERATIONS_ERROR be sure to stop here
|
|
* because ret is not significant */
|
|
if (rc != 0) {
|
|
LOG_OOM();
|
|
goto free_and_return;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
Slapi_Value *cpw[2] = { NULL, NULL };
|
|
Slapi_Value *pw;
|
|
char *cur_pw;
|
|
|
|
if (oldPasswd == NULL || *oldPasswd == '\0') {
|
|
LOG_FATAL("Old password was not provided!\n");
|
|
rc = LDAP_INVALID_CREDENTIALS;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* if the user is changing his own password we need to check that
|
|
* oldPasswd matches the current password */
|
|
cur_pw = slapi_entry_attr_get_charptr(targetEntry,
|
|
"userPassword");
|
|
if (!cur_pw) {
|
|
LOG_FATAL("User has no current password?\n");
|
|
rc = LDAP_UNWILLING_TO_PERFORM;
|
|
goto free_and_return;
|
|
}
|
|
|
|
cpw[0] = slapi_value_new_string(cur_pw);
|
|
pw = slapi_value_new_string(oldPasswd);
|
|
if (!cpw[0] || !pw) {
|
|
LOG_OOM();
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
ret = slapi_pw_find_sv(cpw, pw);
|
|
|
|
slapi_value_free(&cpw[0]);
|
|
slapi_value_free(&pw);
|
|
|
|
if (ret != 0) {
|
|
LOG_TRACE("Invalid password!\n");
|
|
rc = LDAP_INVALID_CREDENTIALS;
|
|
goto free_and_return;
|
|
}
|
|
}
|
|
} else {
|
|
LOG_TRACE("Undefined target DN!\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
rc = ipapwd_entry_checks(pb, targetEntry,
|
|
&is_root, &is_krb, &is_smb, &is_ipant,
|
|
&is_memberof,
|
|
SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE);
|
|
if (rc) {
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* When setting the password for host principals do not set kerberos
|
|
* keys */
|
|
objectclass = slapi_value_new_string("ipaHost");
|
|
if ((slapi_entry_attr_has_syntax_value(targetEntry, SLAPI_ATTR_OBJECTCLASS, objectclass)) == 1) {
|
|
is_krb = 0;
|
|
}
|
|
slapi_value_free(&objectclass);
|
|
|
|
/* 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);
|
|
if (slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &is_root)) {
|
|
LOG_FATAL("slapi_pblock_set failed!\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* In order to perform the access control check, we need to select a
|
|
* backend (even though we don't actually need it otherwise).
|
|
*/
|
|
{
|
|
Slapi_Backend *be = NULL;
|
|
|
|
be = slapi_be_select(slapi_entry_get_sdn(targetEntry));
|
|
if (NULL == be) {
|
|
errMesg = "Failed to find backend for target entry";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
if (slapi_pblock_set(pb, SLAPI_BACKEND, be)) {
|
|
LOG_FATAL("slapi_pblock_set failed!\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Now we have the entry which we want to modify
|
|
* They gave us a password (old), check it against the target entry
|
|
* Is the old password valid ?
|
|
*/
|
|
if (oldPasswd && *oldPasswd) {
|
|
/* If user is authenticated, they already gave their password
|
|
* during the bind operation (or used sasl or client cert auth
|
|
* or OS creds) */
|
|
LOG_TRACE("oldPasswd provided, but we will ignore it");
|
|
}
|
|
|
|
memset(&pwdata, 0, sizeof(pwdata));
|
|
pwdata.target = targetEntry;
|
|
pwdata.dn = dn;
|
|
pwdata.password = newPasswd;
|
|
pwdata.timeNow = time(NULL);
|
|
pwdata.changetype = IPA_CHANGETYPE_NORMAL;
|
|
|
|
/*
|
|
* (technically strcasecmp to compare DNs is not absolutely correct,
|
|
* but it should work for the cases we care about here)
|
|
*/
|
|
|
|
/* determine type of password change */
|
|
/* special cases */
|
|
if ((strcasecmp(dn, bindDN) != 0) &&
|
|
(strcasecmp(ipa_changepw_principal_dn, bindDN) != 0)) {
|
|
int i;
|
|
|
|
pwdata.changetype = IPA_CHANGETYPE_ADMIN;
|
|
|
|
for (i = 0; i < krbcfg->num_passsync_mgrs; i++) {
|
|
if (strcasecmp(krbcfg->passsync_mgrs[i], bindDN) == 0) {
|
|
pwdata.changetype = IPA_CHANGETYPE_DSMGR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* check the policy */
|
|
ret = ipapwd_CheckPolicy(&pwdata);
|
|
if (ret) {
|
|
if (ret == IPAPWD_POLICY_ERROR) {
|
|
errMesg = "Internal error";
|
|
rc = ret;
|
|
goto free_and_return;
|
|
}
|
|
/* ipapwd_CheckPolicy happily will try to apply a policy
|
|
* even if it doesn't need to be applied for Directory Manager
|
|
* or passsync managers, filter that error out */
|
|
if (pwdata.changetype != IPA_CHANGETYPE_DSMGR) {
|
|
errMesg = ipapwd_error2string(ret);
|
|
ret = ipapwd_to_ldap_pwpolicy_error(ret);
|
|
slapi_pwpolicy_make_response_control(pb, -1, -1, ret);
|
|
rc = LDAP_CONSTRAINT_VIOLATION;
|
|
goto free_and_return;
|
|
}
|
|
}
|
|
|
|
/* Now we're ready to set the kerberos key material */
|
|
ret = ipapwd_SetPassword(krbcfg, &pwdata, is_krb);
|
|
if (ret != LDAP_SUCCESS) {
|
|
/* Failed to modify the password,
|
|
* e.g. because insufficient access allowed */
|
|
errMesg = "Failed to update password";
|
|
if (ret > 0) {
|
|
rc = ret;
|
|
} else {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
}
|
|
goto free_and_return;
|
|
}
|
|
|
|
LOG_TRACE("<= result: %d\n", rc);
|
|
|
|
if (pwdata.changetype == IPA_CHANGETYPE_NORMAL) {
|
|
principal = slapi_entry_attr_get_charptr(pwdata.target,
|
|
"krbPrincipalName");
|
|
} else {
|
|
principal = slapi_ch_smprintf("root/admin@%s", krbcfg->realm);
|
|
}
|
|
if (principal)
|
|
ipapwd_set_extradata(pwdata.dn, principal, pwdata.timeNow);
|
|
|
|
/* Free anything that we allocated above */
|
|
free_and_return:
|
|
if (usetxn && chpwop_pb) {
|
|
if (rc) { /* fails */
|
|
slapi_back_transaction_abort(chpwop_pb);
|
|
} else {
|
|
slapi_back_transaction_commit(chpwop_pb);
|
|
}
|
|
slapi_pblock_destroy(chpwop_pb);
|
|
}
|
|
slapi_ch_free_string(&oldPasswd);
|
|
slapi_ch_free_string(&newPasswd);
|
|
/* Either this is the same pointer that we allocated and set above,
|
|
* or whoever used it should have freed it and allocated a new
|
|
* value that we need to free here */
|
|
ret = slapi_pblock_get(pb, SLAPI_ORIGINAL_TARGET, &dn);
|
|
if (ret) {
|
|
LOG_TRACE("Failed to get SLAPI_ORIGINAL_TARGET\n");
|
|
}
|
|
slapi_ch_free_string(&dn);
|
|
ret = slapi_pblock_set(pb, SLAPI_ORIGINAL_TARGET, NULL);
|
|
if (ret) {
|
|
LOG_TRACE("Failed to clear SLAPI_ORIGINAL_TARGET\n");
|
|
}
|
|
slapi_ch_free_string(&authmethod);
|
|
slapi_ch_free_string(&principal);
|
|
|
|
if (targetEntry) slapi_entry_free(targetEntry);
|
|
if (ber) ber_free(ber, 1);
|
|
|
|
LOG("%s", errMesg ? errMesg : "success");
|
|
slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
|
|
|
|
return SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
|
|
|
|
}
|
|
|
|
static char *check_service_name(krb5_context krbctx, char *svc)
|
|
{
|
|
krb5_principal krbname = NULL;
|
|
krb5_error_code krberr;
|
|
char *name = NULL;
|
|
|
|
krberr = krb5_parse_name(krbctx, svc, &krbname);
|
|
if (krberr) {
|
|
LOG_FATAL("krb5_parse_name failed\n");
|
|
} else {
|
|
/* invert so that we get the canonical form (add REALM if not present
|
|
* for example) */
|
|
krberr = krb5_unparse_name(krbctx, krbname, &name);
|
|
if (krberr) {
|
|
LOG_FATAL("krb5_unparse_name failed\n");
|
|
}
|
|
}
|
|
|
|
krb5_free_principal(krbctx, krbname);
|
|
return name;
|
|
}
|
|
|
|
static Slapi_Backend *get_realm_backend(void)
|
|
{
|
|
Slapi_Backend *be;
|
|
Slapi_DN *sdn;
|
|
|
|
sdn = slapi_sdn_new_dn_byval(ipa_realm_dn);
|
|
if (!sdn) return NULL;
|
|
be = slapi_be_select(sdn);
|
|
slapi_sdn_free(&sdn);
|
|
return be;
|
|
}
|
|
|
|
static const char *get_realm_base_dn(void)
|
|
{
|
|
const Slapi_DN *bsdn;
|
|
Slapi_Backend *be;
|
|
|
|
/* Find ancestor base DN */
|
|
be = get_realm_backend();
|
|
if (!be) return NULL;
|
|
|
|
bsdn = slapi_be_getsuffix(be, 0);
|
|
if (!bsdn) return NULL;
|
|
|
|
return slapi_sdn_get_dn(bsdn);
|
|
}
|
|
|
|
static Slapi_Entry *get_entry_by_principal(const char *principal)
|
|
{
|
|
const char *bdn;
|
|
char *filter = NULL;
|
|
Slapi_PBlock *pb = NULL;
|
|
char *attrlist[] = { "krbPrincipalKey", "krbLastPwdChange",
|
|
"userPassword", "krbPrincipalName",
|
|
"krbCanonicalName",
|
|
"enrolledBy", NULL };
|
|
Slapi_Entry **es = NULL;
|
|
int res, ret, i;
|
|
Slapi_Entry *entry = NULL;
|
|
|
|
/* Find ancestor base DN */
|
|
bdn = get_realm_base_dn();
|
|
if (!bdn) {
|
|
LOG_TRACE("Search for Base DN failed\n");
|
|
goto free_and_return;
|
|
}
|
|
|
|
filter = slapi_ch_smprintf("(krbPrincipalName=%s)", principal);
|
|
if (!filter) {
|
|
LOG_TRACE("Building filter failed\n");
|
|
goto free_and_return;
|
|
}
|
|
|
|
pb = slapi_pblock_new();
|
|
slapi_search_internal_set_pb(pb, bdn, LDAP_SCOPE_SUBTREE, filter,
|
|
attrlist, 0,
|
|
NULL, /* Controls */ NULL, /* UniqueID */
|
|
ipapwd_plugin_id, 0); /* Flags */
|
|
|
|
/* do search the tree */
|
|
ret = slapi_search_internal_pb(pb);
|
|
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
|
|
if (ret == -1 || res != LDAP_SUCCESS) {
|
|
LOG_TRACE("Search for Principal failed, err (%d)\n", res ? res : ret);
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* get entries */
|
|
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es);
|
|
if (!es) {
|
|
LOG_TRACE("No entries ?!");
|
|
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) {
|
|
LOG_TRACE("Too many entries, or entry no found (%d)", i);
|
|
goto free_and_return;
|
|
}
|
|
entry = slapi_entry_dup(es[0]);
|
|
|
|
free_and_return:
|
|
if (pb) {
|
|
slapi_free_search_results_internal(pb);
|
|
slapi_pblock_destroy(pb);
|
|
}
|
|
if (filter) slapi_ch_free_string(&filter);
|
|
return entry;
|
|
}
|
|
|
|
static bool is_allowed_to_access_attr(Slapi_PBlock *pb, char *bindDN,
|
|
Slapi_Entry *targetEntry,
|
|
const char *attrname,
|
|
struct berval *value,
|
|
int access)
|
|
{
|
|
Slapi_Backend *be;
|
|
int is_root = 0;
|
|
int ret;
|
|
|
|
is_root = slapi_dn_isroot(bindDN);
|
|
if (slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &is_root)) {
|
|
LOG_FATAL("slapi_pblock_set failed!\n");
|
|
return false;
|
|
}
|
|
|
|
/* In order to perform the access control check, we need to select a
|
|
* backend (even though we don't actually need it otherwise).
|
|
*/
|
|
be = get_realm_backend();
|
|
if (!be) {
|
|
LOG_FATAL("Could not fetch REALM backend!");
|
|
return false;
|
|
}
|
|
if (slapi_pblock_set(pb, SLAPI_BACKEND, be)) {
|
|
LOG_FATAL("slapi_pblock_set failed!\n");
|
|
return false;
|
|
}
|
|
|
|
ret = slapi_access_allowed(pb, targetEntry, discard_const(attrname),
|
|
value, access);
|
|
if (ret != LDAP_SUCCESS) {
|
|
LOG_FATAL("slapi_access_allowed does not allow %s to %s%s!\n",
|
|
(access == SLAPI_ACL_WRITE)?"WRITE":"READ",
|
|
attrname, value?"(value specified)":"");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int set_krbLastPwdChange(Slapi_Mods *smods, time_t now)
|
|
{
|
|
char tstr[GENERALIZED_TIME_LENGTH + 1];
|
|
struct tm utctime;
|
|
|
|
/* change Last Password Change field with the current date */
|
|
if (!gmtime_r(&now, &utctime)) {
|
|
LOG_FATAL("failed to retrieve current date (buggy gmtime_r ?)\n");
|
|
return LDAP_OPERATIONS_ERROR;
|
|
}
|
|
strftime(tstr, GENERALIZED_TIME_LENGTH + 1, "%Y%m%d%H%M%SZ", &utctime);
|
|
slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLastPwdChange", tstr);
|
|
return LDAP_SUCCESS;
|
|
}
|
|
|
|
static void remove_user_password(Slapi_Mods *smods,
|
|
Slapi_Entry *targetEntry, char *bindDN)
|
|
{
|
|
Slapi_Value *objectclass = NULL;
|
|
char *krbLastPwdChange = NULL;
|
|
char *enrolledBy = NULL;
|
|
char *pw = NULL;
|
|
int ret;
|
|
|
|
objectclass = slapi_value_new_string("ipaHost");
|
|
pw = slapi_entry_attr_get_charptr(targetEntry, "userPassword");
|
|
ret = slapi_entry_attr_has_syntax_value(targetEntry,
|
|
SLAPI_ATTR_OBJECTCLASS,
|
|
objectclass);
|
|
if (ret == 1) {
|
|
krbLastPwdChange = slapi_entry_attr_get_charptr(targetEntry,
|
|
"krbLastPwdChange");
|
|
enrolledBy = slapi_entry_attr_get_charptr(targetEntry, "enrolledBy");
|
|
if (!enrolledBy) {
|
|
slapi_mods_add_string(smods, LDAP_MOD_ADD, "enrolledBy", bindDN);
|
|
}
|
|
if ((NULL != pw) && (NULL == krbLastPwdChange)) {
|
|
slapi_mods_add_mod_values(smods, LDAP_MOD_DELETE,
|
|
"userPassword", NULL);
|
|
LOG_TRACE("Removing userPassword from host entry\n");
|
|
}
|
|
}
|
|
if (krbLastPwdChange) slapi_ch_free_string(&krbLastPwdChange);
|
|
if (enrolledBy) slapi_ch_free_string(&enrolledBy);
|
|
if (pw) slapi_ch_free_string(&pw);
|
|
if (objectclass) slapi_value_free(&objectclass);
|
|
}
|
|
|
|
static int store_new_keys(Slapi_Entry *target, char *svcname, char *bind_dn,
|
|
Slapi_Value **svals, char **_err_msg)
|
|
{
|
|
int rc = LDAP_OPERATIONS_ERROR;
|
|
char *err_msg = NULL;
|
|
Slapi_Mods *smods = NULL;
|
|
time_t time_now = time(NULL);
|
|
|
|
smods = slapi_mods_new();
|
|
slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
|
|
"krbPrincipalKey", svals);
|
|
rc = set_krbLastPwdChange(smods, time_now);
|
|
if (rc) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("Failed to set krbLastPwdChange");
|
|
err_msg = "Internal error while storing keytab data\n";
|
|
goto done;
|
|
}
|
|
|
|
/* If we are creating a keytab for a host service, attempt to remove
|
|
* the userPassword attribute if it exists
|
|
*/
|
|
remove_user_password(smods, target, bind_dn);
|
|
|
|
/* commit changes */
|
|
rc = ipapwd_apply_mods(slapi_entry_get_dn_const(target), smods);
|
|
if (rc != LDAP_SUCCESS) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("Failed to apply mods");
|
|
err_msg = "Internal error while saving keys\n";
|
|
goto done;
|
|
}
|
|
|
|
rc = ipapwd_set_extradata(slapi_entry_get_dn_const(target),
|
|
svcname, time_now);
|
|
if (rc != LDAP_SUCCESS) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("Failed to set extradata");
|
|
err_msg = "Internal error while saving keytab extradata\n";
|
|
goto done;
|
|
}
|
|
|
|
rc = LDAP_SUCCESS;
|
|
|
|
done:
|
|
if (smods) slapi_mods_free(&smods);
|
|
*_err_msg = err_msg;
|
|
return rc;
|
|
}
|
|
|
|
/* Format of request to parse
|
|
*
|
|
* KeytabSetRequest ::= SEQUENCE {
|
|
* serviceIdentity OCTET STRING
|
|
* keys SEQUENCE OF KrbKey,
|
|
* ...
|
|
* }
|
|
*
|
|
* KrbKey ::= SEQUENCE {
|
|
* key [0] EncryptionKey,
|
|
* salt [1] KrbSalt OPTIONAL,
|
|
* s2kparams [2] OCTET STRING OPTIONAL,
|
|
* ...
|
|
* }
|
|
*
|
|
* EncryptionKey ::= SEQUENCE {
|
|
* keytype [0] Int32,
|
|
* keyvalue [1] OCTET STRING
|
|
* }
|
|
*
|
|
* KrbSalt ::= SEQUENCE {
|
|
* type [0] Int32,
|
|
* salt [1] OCTET STRING OPTIONAL
|
|
* }
|
|
*/
|
|
|
|
#define SKREQ_SALT_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 1)
|
|
#define SKREQ_SALTVALUE_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 1)
|
|
#define SKREQ_S2KPARAMS_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 2)
|
|
|
|
/* The returned krb5_key_data kvno is set to 0 for all keys, the caller,
|
|
* is responsible for fixing it up if necessary before using the data */
|
|
static int decode_setkeytab_request(krb5_context krbctx,
|
|
krb5_keyblock *kmkey, int mkvno,
|
|
struct berval *extop, char **_svcname,
|
|
struct ipapwd_keyset **_kset,
|
|
char **_err_msg) {
|
|
int rc = LDAP_OPERATIONS_ERROR;
|
|
char *err_msg = NULL;
|
|
BerElement *ber = NULL;
|
|
char *svcname = NULL;
|
|
ber_tag_t rtag;
|
|
ber_len_t tlen;
|
|
struct ipapwd_keyset *kset = NULL;
|
|
|
|
ber = ber_init(extop);
|
|
if (ber == NULL) {
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
err_msg = "KeytabSet Request decode failed.\n";
|
|
goto done;
|
|
}
|
|
|
|
/* ber parse code */
|
|
rtag = ber_scanf(ber, "{a{", &svcname);
|
|
if (rtag == LBER_ERROR) {
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
LOG_FATAL("ber_scanf failed to fecth service name\n");
|
|
err_msg = "Invalid payload.\n";
|
|
goto done;
|
|
}
|
|
|
|
kset = calloc(1, sizeof(struct ipapwd_keyset));
|
|
if (!kset) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_OOM();
|
|
err_msg = "Internal error.\n";
|
|
goto done;
|
|
}
|
|
|
|
/* this encoding assumes all keys have the same kvno */
|
|
/* major-vno = 1 and minor-vno = 1 */
|
|
kset->major_vno = 1;
|
|
kset->minor_vno = 1;
|
|
kset->mkvno = mkvno;
|
|
|
|
rtag = ber_peek_tag(ber, &tlen);
|
|
for (int i = 0; rtag == LBER_SEQUENCE; i++) {
|
|
krb5_key_data *newset;
|
|
ber_tag_t ctag;
|
|
ber_int_t type;
|
|
krb5_data plain;
|
|
krb5_enc_data cipher;
|
|
struct berval tval;
|
|
krb5_octet *kdata;
|
|
krb5_int16 le_len;
|
|
size_t klen;
|
|
|
|
newset = realloc(kset->keys, sizeof(krb5_key_data) * (i + 1));
|
|
if (!newset) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_OOM();
|
|
err_msg = "Internal error.\n";
|
|
goto done;
|
|
}
|
|
kset->keys = newset;
|
|
kset->num_keys = i + 1;
|
|
|
|
memset(&kset->keys[i], 0, sizeof(krb5_key_data));
|
|
kset->keys[i].key_data_ver = 1;
|
|
kset->keys[i].key_data_kvno = 0;
|
|
|
|
/* EncryptionKey */
|
|
rtag = ber_scanf(ber, "{t[{t[i]t[o]}]",
|
|
&ctag, &ctag, &type, &ctag, &tval);
|
|
if (rtag == LBER_ERROR) {
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
LOG_FATAL("ber_scanf failed fetching key\n");
|
|
err_msg = "Invalid payload.\n";
|
|
goto done;
|
|
}
|
|
|
|
kset->keys[i].key_data_type[0] = type;
|
|
plain.length = tval.bv_len;
|
|
plain.data = tval.bv_val;
|
|
|
|
rc = krb5_c_encrypt_length(krbctx, kmkey->enctype,
|
|
plain.length, &klen);
|
|
if (rc) {
|
|
ber_memfree(tval.bv_val);
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("krb5_c_encrypt_length failed!\n");
|
|
err_msg = "Internal error.\n";
|
|
goto done;
|
|
}
|
|
kdata = malloc(2 + klen);
|
|
if (!kdata) {
|
|
ber_memfree(tval.bv_val);
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_OOM();
|
|
err_msg = "Internal error.\n";
|
|
goto done;
|
|
}
|
|
le_len = htole16(plain.length);
|
|
memcpy(kdata, &le_len, 2);
|
|
|
|
kset->keys[i].key_data_length[0] = 2 + klen;
|
|
kset->keys[i].key_data_contents[0] = kdata;
|
|
|
|
cipher.ciphertext.length = klen;
|
|
cipher.ciphertext.data = (char *)kdata + 2;
|
|
|
|
rc = krb5_c_encrypt(krbctx, kmkey, 0, 0, &plain, &cipher);
|
|
if (rc) {
|
|
ber_memfree(tval.bv_val);
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("krb5_c_encrypt failed!\n");
|
|
err_msg = "Internal error.\n";
|
|
goto done;
|
|
}
|
|
|
|
ber_memfree(tval.bv_val);
|
|
|
|
rtag = ber_peek_tag(ber, &tlen);
|
|
/* KrbSalt */
|
|
if (rtag == SKREQ_SALT_TAG) {
|
|
rtag = ber_scanf(ber, "t[{t[i]", &ctag, &ctag, &type);
|
|
if (rtag == LBER_ERROR) {
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
LOG_FATAL("ber_scanf failed fetching salt\n");
|
|
err_msg = "Invalid payload.\n";
|
|
goto done;
|
|
}
|
|
|
|
kset->keys[i].key_data_ver = 2; /* we have a salt */
|
|
kset->keys[i].key_data_type[1] = type;
|
|
|
|
rtag = ber_peek_tag(ber, &tlen);
|
|
if (rtag == SKREQ_SALTVALUE_TAG) {
|
|
rtag = ber_scanf(ber, "t[o]}]", &ctag, &tval);
|
|
if (rtag == LBER_ERROR) {
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
LOG_FATAL("ber_scanf failed fetching salt value\n");
|
|
err_msg = "Invalid payload.\n";
|
|
goto done;
|
|
}
|
|
|
|
kset->keys[i].key_data_length[1] = tval.bv_len;
|
|
kset->keys[i].key_data_contents[1] = malloc(tval.bv_len);
|
|
if (!kset->keys[i].key_data_contents[1]) {
|
|
ber_memfree(tval.bv_val);
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_OOM();
|
|
err_msg = "Internal error.\n";
|
|
goto done;
|
|
}
|
|
memcpy(kset->keys[i].key_data_contents[1],
|
|
tval.bv_val, tval.bv_len);
|
|
ber_memfree(tval.bv_val);
|
|
|
|
rtag = ber_peek_tag(ber, &tlen);
|
|
}
|
|
}
|
|
|
|
/* FIXME: s2kparams - NOT implemented yet */
|
|
if (rtag == SKREQ_S2KPARAMS_TAG) {
|
|
rtag = ber_scanf(ber, "t[x]}", &ctag);
|
|
} else {
|
|
rtag = ber_scanf(ber, "}", &ctag);
|
|
}
|
|
if (rtag == LBER_ERROR) {
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
LOG_FATAL("ber_scanf failed to read key data termination\n");
|
|
err_msg = "Invalid payload.\n";
|
|
goto done;
|
|
}
|
|
|
|
rtag = ber_peek_tag(ber, &tlen);
|
|
}
|
|
|
|
rc = LDAP_SUCCESS;
|
|
|
|
done:
|
|
if (rc != LDAP_SUCCESS) {
|
|
if (kset) ipapwd_keyset_free(&kset);
|
|
free(svcname);
|
|
*_err_msg = err_msg;
|
|
} else {
|
|
*_svcname = svcname;
|
|
*_kset = kset;
|
|
}
|
|
if (ber) ber_free(ber, 1);
|
|
return rc;
|
|
}
|
|
|
|
/* Format of response
|
|
*
|
|
* KeytabGetRequest ::= SEQUENCE {
|
|
* new_kvno Int32
|
|
* SEQUENCE OF KeyTypes
|
|
* }
|
|
*
|
|
* * List of accepted enctypes *
|
|
* KeyTypes ::= SEQUENCE {
|
|
* enctype Int32
|
|
* }
|
|
*/
|
|
|
|
static int encode_setkeytab_reply(struct ipapwd_keyset *kset,
|
|
struct berval **_bvp)
|
|
{
|
|
int rc = LDAP_OPERATIONS_ERROR;
|
|
struct berval *bvp = NULL;
|
|
BerElement *ber = NULL;
|
|
|
|
ber = ber_alloc();
|
|
if (!ber) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_OOM();
|
|
goto done;
|
|
}
|
|
|
|
rc = ber_printf(ber, "{i{", (ber_int_t)kset->keys[0].key_data_kvno);
|
|
if (rc == -1) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("Failed to ber_printf the kvno");
|
|
goto done;
|
|
}
|
|
|
|
for (int i = 0; i < kset->num_keys; i++) {
|
|
rc = ber_printf(ber, "{i}", (ber_int_t)kset->keys[i].key_data_type[0]);
|
|
if (rc == -1) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("Failed to ber_printf the enctype");
|
|
goto done;
|
|
}
|
|
}
|
|
rc = ber_printf(ber, "}}");
|
|
if (rc == -1) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("Failed to ber_printf the termination");
|
|
goto done;
|
|
}
|
|
|
|
rc = ber_flatten(ber, &bvp);
|
|
if (rc == -1) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("Failed to ber_flatten the buffer");
|
|
goto done;
|
|
}
|
|
|
|
rc = LDAP_SUCCESS;
|
|
|
|
done:
|
|
if (rc != LDAP_SUCCESS) {
|
|
if (bvp) ber_bvfree(bvp);
|
|
} else {
|
|
*_bvp = bvp;
|
|
}
|
|
if (ber) ber_free(ber, 1);
|
|
return rc;
|
|
}
|
|
|
|
/* Password Modify Extended operation plugin function */
|
|
static int ipapwd_setkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
|
|
{
|
|
char *bindDN = NULL;
|
|
char *serviceName = NULL;
|
|
char *errMesg = NULL;
|
|
struct berval *extop_value = NULL;
|
|
Slapi_Entry *targetEntry=NULL;
|
|
struct berval *bval = NULL;
|
|
Slapi_Value **svals = NULL;
|
|
krb5_context krbctx = NULL;
|
|
krb5_error_code krberr;
|
|
struct ipapwd_keyset *kset = NULL;
|
|
int rc;
|
|
int kvno;
|
|
char *svcname;
|
|
bool allowed_access = false;
|
|
bool is_nthash_allowed = false;
|
|
struct berval *bvp = NULL;
|
|
LDAPControl new_ctrl;
|
|
|
|
krberr = krb5_init_context(&krbctx);
|
|
if (krberr) {
|
|
LOG_FATAL("krb5_init_context failed\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
rc = decode_setkeytab_request(krbctx, krbcfg->kmkey, krbcfg->mkvno,
|
|
extop_value, &serviceName, &kset, &errMesg);
|
|
if (rc) {
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* make sure it is a valid name */
|
|
svcname = check_service_name(krbctx, serviceName);
|
|
if (!svcname) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
slapi_ch_free_string(&serviceName);
|
|
serviceName = svcname;
|
|
|
|
/* check entry before doing any other decoding */
|
|
|
|
/* get Entry by krbPrincipalName */
|
|
targetEntry = get_entry_by_principal(serviceName);
|
|
if (!targetEntry) {
|
|
errMesg = "PrincipalName not found.\n";
|
|
rc = LDAP_NO_SUCH_OBJECT;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* Accesseck strategy:
|
|
* If the user has WRITE access, a new keytab can be set on the 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.
|
|
*/
|
|
allowed_access = is_allowed_to_access_attr(pb, bindDN, targetEntry,
|
|
"krbPrincipalKey", NULL,
|
|
SLAPI_ACL_WRITE);
|
|
if (!allowed_access) {
|
|
LOG_FATAL("Access not allowed to set keytab on [%s]!\n",
|
|
serviceName);
|
|
errMesg = "Insufficient access rights\n";
|
|
rc = LDAP_INSUFFICIENT_ACCESS;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* get next kvno for entry (will be 1 if this is new) and fix keyset */
|
|
kvno = ipapwd_get_cur_kvno(targetEntry) + 1;
|
|
for (int i = 0; i < kset->num_keys; i++) {
|
|
kset->keys[i].key_data_kvno = kvno;
|
|
}
|
|
|
|
/* Only allow generating arcfour-hmac keys for cifs/.. services
|
|
* unless the enctype is allowed by the IPA configuration for use
|
|
* by the all principals */
|
|
is_nthash_allowed = (0 == strncmp("cifs/", serviceName, 5));
|
|
filter_keys(krbcfg, kset, is_nthash_allowed);
|
|
|
|
/* check if we have any left */
|
|
if (kset->num_keys == 0) {
|
|
LOG_FATAL("keyset filtering rejected all proposed keys\n");
|
|
errMesg = "All enctypes provided are unsupported";
|
|
rc = LDAP_UNWILLING_TO_PERFORM;
|
|
goto free_and_return;
|
|
}
|
|
|
|
rc = ber_encode_krb5_key_data(kset->keys, kset->num_keys,
|
|
kset->mkvno, &bval);
|
|
if (rc != 0) {
|
|
LOG_FATAL("encoding krb5_key_data failed\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *));
|
|
if (!svals) {
|
|
LOG_OOM();
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
svals[0] = slapi_value_new_berval(bval);
|
|
if (!svals[0]) {
|
|
LOG_FATAL("Converting berval to Slapi_Value\n");
|
|
goto free_and_return;
|
|
}
|
|
|
|
rc = store_new_keys(targetEntry, serviceName, bindDN, svals, &errMesg);
|
|
if (rc) {
|
|
goto free_and_return;
|
|
}
|
|
|
|
rc = encode_setkeytab_reply(kset, &bvp);
|
|
if (rc) {
|
|
errMesg = "Internal Error.\n";
|
|
goto free_and_return;
|
|
}
|
|
|
|
new_ctrl.ldctl_oid = KEYTAB_RET_OID;
|
|
new_ctrl.ldctl_value = *bvp;
|
|
new_ctrl.ldctl_iscritical = 0;
|
|
rc = slapi_pblock_set(pb, SLAPI_ADD_RESCONTROL, &new_ctrl);
|
|
|
|
/* Free anything that we allocated above */
|
|
free_and_return:
|
|
free(serviceName);
|
|
if (kset) ipapwd_keyset_free(&kset);
|
|
|
|
if (bval) ber_bvfree(bval);
|
|
if (bvp) ber_bvfree(bvp);
|
|
|
|
if (targetEntry) slapi_entry_free(targetEntry);
|
|
|
|
if (svals) {
|
|
for (int i = 0; svals[i]; i++) {
|
|
slapi_value_free(&svals[i]);
|
|
}
|
|
free(svals);
|
|
}
|
|
|
|
if (krbctx) krb5_free_context(krbctx);
|
|
|
|
if (rc == LDAP_SUCCESS)
|
|
errMesg = NULL;
|
|
LOG("%s", errMesg ? errMesg : "success");
|
|
slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
|
|
|
|
return SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
|
|
}
|
|
|
|
/* decode a getkeytab control request using libipaasn1 helpers */
|
|
static int decode_getkeytab_request(struct berval *extop, bool *wantold,
|
|
char **_svcname, char **_password,
|
|
krb5_key_salt_tuple **kenctypes,
|
|
int *num_kenctypes, char **_err_msg)
|
|
{
|
|
int rc = LDAP_OPERATIONS_ERROR;
|
|
char *err_msg = NULL;
|
|
char *svcname = NULL;
|
|
char *password = NULL;
|
|
long *etypes = NULL;
|
|
int numtypes = 0;
|
|
krb5_key_salt_tuple *enctypes = NULL;
|
|
bool newkt;
|
|
bool ret;
|
|
int i;
|
|
|
|
ret = ipaasn1_dec_getkt(extop->bv_val, extop->bv_len, &newkt,
|
|
&svcname, &password, &etypes, &numtypes);
|
|
if (!ret) {
|
|
err_msg = "Failed to decode GetKeytab Control.\n";
|
|
rc = LDAP_PROTOCOL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
if (newkt) {
|
|
if (numtypes) {
|
|
enctypes = malloc(numtypes * sizeof(krb5_key_salt_tuple));
|
|
if (!enctypes) {
|
|
LOG_FATAL("allocation failed\n");
|
|
err_msg = "Internal error\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0; i < numtypes; i++) {
|
|
enctypes[i].ks_enctype = etypes[i];
|
|
enctypes[i].ks_salttype = KRB5_KDB_SALTTYPE_NORMAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = LDAP_SUCCESS;
|
|
|
|
done:
|
|
free(etypes);
|
|
if (rc != LDAP_SUCCESS) {
|
|
free(password);
|
|
free(svcname);
|
|
free(enctypes);
|
|
*_err_msg = err_msg;
|
|
} else {
|
|
*_password = password;
|
|
*_svcname = svcname;
|
|
*wantold = (newkt == false);
|
|
*kenctypes = enctypes;
|
|
*num_kenctypes = numtypes;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int encode_getkeytab_reply(krb5_context krbctx,
|
|
krb5_keyblock *kmkey, int mkvno,
|
|
krb5_key_data *keys, int num_keys,
|
|
struct berval **_bvp)
|
|
{
|
|
int rc = LDAP_OPERATIONS_ERROR;
|
|
struct krb_key_salt ksdata[num_keys];
|
|
struct keys_container ksc = { num_keys, ksdata };
|
|
struct berval *bvp = NULL;
|
|
int kvno;
|
|
bool ret;
|
|
|
|
memset(ksdata, '\0', num_keys * sizeof(struct krb_key_salt));
|
|
|
|
/* uses last key kvno */
|
|
kvno = keys[num_keys-1].key_data_kvno;
|
|
|
|
for (int i = 0; i < num_keys; i++) {
|
|
krb5_enc_data cipher = { 0 };
|
|
krb5_data plain = { 0 };
|
|
krb5_int16 plen;
|
|
|
|
/* retrieve plain key */
|
|
memcpy(&plen, keys[i].key_data_contents[0], 2);
|
|
cipher.ciphertext.data = (char *)keys[i].key_data_contents[0] + 2;
|
|
cipher.ciphertext.length = keys[i].key_data_length[0] - 2;
|
|
cipher.enctype = kmkey->enctype;
|
|
cipher.kvno = mkvno;
|
|
|
|
plain.length = le16toh(plen);
|
|
plain.data = malloc(plain.length);
|
|
if (!plain.data) {
|
|
LOG_FATAL("Failed to allocate plain buffer\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
rc = krb5_c_decrypt(krbctx, kmkey, 0, 0, &cipher, &plain);
|
|
if (rc) {
|
|
LOG_FATAL("Failed to decrypt keys\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ksc.ksdata[i].enctype = keys[i].key_data_type[0];
|
|
ksc.ksdata[i].key.enctype = keys[i].key_data_type[0];
|
|
ksc.ksdata[i].key.contents = (void *)plain.data;
|
|
ksc.ksdata[i].key.length = plain.length;
|
|
|
|
/* if salt available, add it */
|
|
if (keys[i].key_data_length[1] != 0) {
|
|
ksc.ksdata[i].salttype = keys[i].key_data_type[1];
|
|
ksc.ksdata[i].salt.data = (void *)keys[i].key_data_contents[1];
|
|
ksc.ksdata[i].salt.length = keys[i].key_data_length[1];
|
|
}
|
|
}
|
|
|
|
bvp = calloc(1, sizeof(struct berval));
|
|
if (!bvp) goto done;
|
|
|
|
ret = ipaasn1_enc_getktreply(kvno, &ksc,
|
|
(void **)&bvp->bv_val, &bvp->bv_len);
|
|
if (!ret) goto done;
|
|
|
|
rc = LDAP_SUCCESS;
|
|
|
|
done:
|
|
for (int i = 0; i < ksc.nkeys; i ++) {
|
|
free(ksc.ksdata[i].key.contents);
|
|
}
|
|
if (rc != LDAP_SUCCESS) {
|
|
if (bvp) ber_bvfree(bvp);
|
|
} else {
|
|
*_bvp = bvp;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int get_decoded_key_data(char *svcname,
|
|
krb5_key_data **_keys, int *_num_keys,
|
|
int *_mkvno, char **_err_msg)
|
|
{
|
|
int rc = LDAP_OPERATIONS_ERROR;
|
|
char *err_msg = NULL;
|
|
krb5_key_data *keys = NULL;
|
|
int num_keys = 0;
|
|
int mkvno = 0;
|
|
Slapi_Entry *target = NULL;
|
|
Slapi_Attr *attr;
|
|
Slapi_Value *keys_value;
|
|
const struct berval *encoded_keys;
|
|
|
|
target = get_entry_by_principal(svcname);
|
|
if (!target) {
|
|
err_msg = "PrincipalName disappeared while processing.\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
rc = slapi_entry_attr_find(target, "krbPrincipalKey", &attr);
|
|
if (rc) {
|
|
err_msg = "krbPrincipalKey not found\n";
|
|
rc = LDAP_NO_SUCH_ATTRIBUTE;
|
|
goto done;
|
|
}
|
|
rc = slapi_attr_first_value(attr, &keys_value);
|
|
if (rc) {
|
|
err_msg = "Error retrieving krbPrincipalKey\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
encoded_keys = slapi_value_get_berval(keys_value);
|
|
if (!encoded_keys) {
|
|
err_msg = "Error retrieving encoded krbPrincipalKey\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
rc = ber_decode_krb5_key_data(discard_const(encoded_keys),
|
|
&mkvno, &num_keys, &keys);
|
|
if (rc) {
|
|
err_msg = "Error retrieving decoded krbPrincipalKey\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
if (num_keys <= 0) {
|
|
err_msg = "No krbPrincipalKeys available\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
rc = LDAP_SUCCESS;
|
|
|
|
done:
|
|
if (rc != LDAP_SUCCESS) {
|
|
if (keys) ipa_krb5_free_key_data(keys, num_keys);
|
|
*_err_msg = err_msg;
|
|
} else {
|
|
*_mkvno = mkvno;
|
|
*_keys = keys;
|
|
*_num_keys = num_keys;
|
|
}
|
|
if (target) slapi_entry_free(target);
|
|
return rc;
|
|
}
|
|
|
|
#define WRITEKEYS_OP_CHECK "ipaProtectedOperation;write_keys"
|
|
#define READKEYS_OP_CHECK "ipaProtectedOperation;read_keys"
|
|
|
|
/* Password Modify Extended operation plugin function */
|
|
static int ipapwd_getkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
|
|
{
|
|
char *bind_dn = NULL;
|
|
char *err_msg = NULL;
|
|
int rc = 0;
|
|
krb5_context krbctx = NULL;
|
|
krb5_error_code krberr;
|
|
struct berval *extop_value = NULL;
|
|
char *service_name = NULL;
|
|
char *svcname;
|
|
Slapi_Entry *target_entry = NULL;
|
|
bool acl_ok = false;
|
|
char *password = NULL;
|
|
int num_kenctypes = 0;
|
|
krb5_key_salt_tuple *kenctypes = NULL;
|
|
int mkvno = 0;
|
|
int num_keys = 0;
|
|
krb5_key_data *keys = NULL;
|
|
struct ipapwd_data data = { 0 };
|
|
Slapi_Value **svals = NULL;
|
|
struct berval *bvp = NULL;
|
|
LDAPControl new_ctrl;
|
|
bool wantold = false;
|
|
bool is_nthash_allowed = false;
|
|
|
|
/* Get Bind DN */
|
|
slapi_pblock_get(pb, SLAPI_CONN_DN, &bind_dn);
|
|
|
|
/* If the connection is bound anonymously, we must refuse to process
|
|
* this operation. */
|
|
if (bind_dn == NULL || *bind_dn == '\0') {
|
|
/* Refuse the operation because they're bound anonymously */
|
|
err_msg = "Anonymous Binds are not allowed.\n";
|
|
rc = LDAP_INSUFFICIENT_ACCESS;
|
|
goto free_and_return;
|
|
}
|
|
|
|
krberr = krb5_init_context(&krbctx);
|
|
if (krberr) {
|
|
LOG_FATAL("krb5_init_context failed\n");
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* Get the ber value of the extended operation */
|
|
slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
|
|
if (!extop_value) {
|
|
LOG_FATAL("Failed to retrieve extended op value from pblock\n");
|
|
err_msg = "Failed to retrieve extended operation value\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
|
|
rc = decode_getkeytab_request(extop_value, &wantold, &service_name,
|
|
&password, &kenctypes, &num_kenctypes,
|
|
&err_msg);
|
|
if (rc != LDAP_SUCCESS) {
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* make sure it is a valid name */
|
|
svcname = check_service_name(krbctx, service_name);
|
|
if (!svcname) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
goto free_and_return;
|
|
}
|
|
slapi_ch_free_string(&service_name);
|
|
service_name = svcname;
|
|
|
|
/* check entry */
|
|
|
|
/* get Entry by krbPrincipalName */
|
|
target_entry = get_entry_by_principal(service_name);
|
|
if (!target_entry) {
|
|
err_msg = "PrincipalName not found.\n";
|
|
rc = LDAP_NO_SUCH_OBJECT;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* ok access allowed */
|
|
/* do we need to create new keys ? */
|
|
if (wantold) { /* requesting to retrieve existing ones */
|
|
|
|
/* check if we are allowed to *read* keys */
|
|
acl_ok = is_allowed_to_access_attr(pb, bind_dn, target_entry,
|
|
READKEYS_OP_CHECK, NULL,
|
|
SLAPI_ACL_READ);
|
|
if (!acl_ok) {
|
|
LOG_FATAL("Not allowed to retrieve keytab on [%s] as user [%s]!\n",
|
|
service_name, bind_dn);
|
|
err_msg = "Insufficient access rights\n";
|
|
rc = LDAP_INSUFFICIENT_ACCESS;
|
|
goto free_and_return;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (password != NULL) {
|
|
/* if password was passed-in, check its length */
|
|
rc = ipapwd_check_max_pwd_len(strlen(password), &err_msg);
|
|
if (rc) {
|
|
goto free_and_return;
|
|
}
|
|
}
|
|
|
|
/* check if we are allowed to *write* keys */
|
|
acl_ok = is_allowed_to_access_attr(pb, bind_dn, target_entry,
|
|
WRITEKEYS_OP_CHECK, NULL,
|
|
SLAPI_ACL_WRITE);
|
|
if (!acl_ok) {
|
|
LOG_FATAL("Not allowed to set keytab on [%s]!\n",
|
|
service_name);
|
|
err_msg = "Insufficient access rights\n";
|
|
rc = LDAP_INSUFFICIENT_ACCESS;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* Only allow generating arcfour-hmac keys for cifs/.. services
|
|
* unless the enctype is allowed by the IPA configuration for use
|
|
* by the all principals */
|
|
is_nthash_allowed = (0 == strncmp("cifs/", service_name, 5));
|
|
filter_enctypes(krbcfg, kenctypes, &num_kenctypes, is_nthash_allowed);
|
|
|
|
/* check if we have any left */
|
|
if (num_kenctypes == 0 && kenctypes != NULL) {
|
|
LOG_FATAL("keyset filtering rejected all proposed keys\n");
|
|
err_msg = "All enctypes provided are unsupported";
|
|
rc = LDAP_UNWILLING_TO_PERFORM;
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* only target is used, leave everything else NULL,
|
|
* if password is not provided we want to generate a random key */
|
|
data.target = target_entry;
|
|
data.password = password;
|
|
|
|
svals = ipapwd_encrypt_encode_key(krbcfg, &data, service_name,
|
|
kenctypes ? num_kenctypes :
|
|
krbcfg->num_pref_encsalts,
|
|
kenctypes ? kenctypes :
|
|
krbcfg->pref_encsalts,
|
|
&err_msg);
|
|
if (!svals) {
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG_FATAL("encrypt_encode_keys failed!\n");
|
|
err_msg = "Internal error while encrypting keys\n";
|
|
goto free_and_return;
|
|
}
|
|
|
|
rc = store_new_keys(target_entry, service_name, bind_dn, svals,
|
|
&err_msg);
|
|
if (rc != LDAP_SUCCESS) {
|
|
goto free_and_return;
|
|
}
|
|
}
|
|
|
|
rc = get_decoded_key_data(service_name,
|
|
&keys, &num_keys, &mkvno, &err_msg);
|
|
if (rc != LDAP_SUCCESS) {
|
|
goto free_and_return;
|
|
}
|
|
|
|
rc = encode_getkeytab_reply(krbctx, krbcfg->kmkey, mkvno,
|
|
keys, num_keys, &bvp);
|
|
if (rc != LDAP_SUCCESS) {
|
|
err_msg = "Internal Error.\n";
|
|
goto free_and_return;
|
|
}
|
|
|
|
new_ctrl.ldctl_oid = KEYTAB_GET_OID;
|
|
new_ctrl.ldctl_value = *bvp;
|
|
new_ctrl.ldctl_iscritical = 0;
|
|
rc = slapi_pblock_set(pb, SLAPI_ADD_RESCONTROL, &new_ctrl);
|
|
|
|
free_and_return:
|
|
if (rc == LDAP_SUCCESS) err_msg = NULL;
|
|
LOG("%s", err_msg ? err_msg : "success");
|
|
slapi_send_ldap_result(pb, rc, NULL, err_msg, 0, NULL);
|
|
|
|
/* Free anything that we allocated above */
|
|
if (krbctx) krb5_free_context(krbctx);
|
|
free(kenctypes);
|
|
free(service_name);
|
|
free(password);
|
|
if (target_entry) slapi_entry_free(target_entry);
|
|
if (keys) ipa_krb5_free_key_data(keys, num_keys);
|
|
if (svals) {
|
|
for (int i = 0; svals[i]; i++) {
|
|
slapi_value_free(&svals[i]);
|
|
}
|
|
free(svals);
|
|
}
|
|
if (bvp) ber_bvfree(bvp);
|
|
|
|
return SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
|
|
}
|
|
|
|
static int ipapwd_extop(Slapi_PBlock *pb)
|
|
{
|
|
struct ipapwd_krbcfg *krbcfg = NULL;
|
|
char *errMesg = NULL;
|
|
char *oid = NULL;
|
|
int rc, ret;
|
|
|
|
LOG_TRACE("=>\n");
|
|
|
|
rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_CONN_SECURE);
|
|
if (rc) {
|
|
goto free_and_return;
|
|
}
|
|
|
|
/* Before going any further, we'll make sure that the right extended
|
|
* operation plugin has been called: i.e., the OID shipped whithin the
|
|
* extended operation request must match this very plugin's OIDs:
|
|
* EXOP_PASSWD_OID or KEYTAB_SET_OID. */
|
|
if (slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &oid) != 0) {
|
|
errMesg = "Could not get OID value from request.\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
LOG("%s", errMesg);
|
|
goto free_and_return;
|
|
} else {
|
|
LOG("Received extended operation request with OID %s\n", oid);
|
|
}
|
|
|
|
if (strcasecmp(oid, EXOP_PASSWD_OID) == 0) {
|
|
ret = ipapwd_chpwop(pb, krbcfg);
|
|
free_ipapwd_krbcfg(&krbcfg);
|
|
return ret;
|
|
}
|
|
if (strcasecmp(oid, KEYTAB_SET_OID) == 0) {
|
|
ret = ipapwd_setkeytab(pb, krbcfg);
|
|
free_ipapwd_krbcfg(&krbcfg);
|
|
return ret;
|
|
}
|
|
if (strcasecmp(oid, KEYTAB_GET_OID) == 0) {
|
|
ret = ipapwd_getkeytab(pb, krbcfg);
|
|
free_ipapwd_krbcfg(&krbcfg);
|
|
return ret;
|
|
}
|
|
|
|
errMesg = "Request OID does not match supported OIDs.\n";
|
|
rc = LDAP_OPERATIONS_ERROR;
|
|
|
|
free_and_return:
|
|
if (krbcfg) free_ipapwd_krbcfg(&krbcfg);
|
|
|
|
LOG("%s", errMesg);
|
|
slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
|
|
|
|
return SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
|
|
}
|
|
|
|
/* Copied from ipamo_string2filter()
|
|
*
|
|
* ipapwd_string2filter()
|
|
*
|
|
* For some reason slapi_str2filter writes to its input
|
|
* which means you cannot pass in a string constant
|
|
* so this is a fix up function for that
|
|
*/
|
|
Slapi_Filter *ipapwd_string2filter(char *strfilter)
|
|
{
|
|
Slapi_Filter *ret = NULL;
|
|
char *idontbelieveit = slapi_ch_strdup(strfilter);
|
|
|
|
ret = slapi_str2filter(idontbelieveit);
|
|
|
|
slapi_ch_free_string(&idontbelieveit);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Init data structs */
|
|
static int ipapwd_start( Slapi_PBlock *pb )
|
|
{
|
|
krb5_context krbctx = NULL;
|
|
krb5_error_code krberr;
|
|
char *realm = NULL;
|
|
Slapi_DN *config_sdn = NULL;
|
|
Slapi_Entry *config_entry = NULL;
|
|
int ret;
|
|
|
|
krberr = krb5_init_context(&krbctx);
|
|
if (krberr) {
|
|
LOG_FATAL("krb5_init_context failed\n");
|
|
/* Yes, we failed, but it is because /etc/krb5.conf doesn't exist
|
|
* or is misconfigured. Start up in a degraded mode.
|
|
*/
|
|
return LDAP_SUCCESS;
|
|
}
|
|
|
|
if (slapi_pblock_get(pb, SLAPI_TARGET_SDN, &config_sdn) != 0) {
|
|
LOG_FATAL("No config DN?\n");
|
|
ret = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
if (ipapwd_getEntry(config_sdn, &config_entry, NULL) != LDAP_SUCCESS) {
|
|
LOG_FATAL("No config Entry extop?\n");
|
|
ret = LDAP_SUCCESS;
|
|
goto done;
|
|
}
|
|
|
|
ipa_realm_tree = slapi_entry_attr_get_charptr(config_entry,
|
|
"nsslapd-realmtree");
|
|
if (!ipa_realm_tree) {
|
|
LOG_FATAL("Missing partition configuration entry "
|
|
"(nsslapd-realmTree)!\n");
|
|
ret = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ret = krb5_get_default_realm(krbctx, &realm);
|
|
if (ret) {
|
|
LOG_FATAL("Failed to get default realm?!\n");
|
|
ret = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
ipa_realm_dn = slapi_ch_smprintf("cn=%s,cn=kerberos,%s",
|
|
realm, ipa_realm_tree);
|
|
if (!ipa_realm_dn) {
|
|
LOG_OOM();
|
|
ret = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ipa_pwd_config_dn = slapi_ch_strdup(slapi_sdn_get_dn(config_sdn));
|
|
if (!ipa_pwd_config_dn) {
|
|
LOG_OOM();
|
|
ret = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
ipa_changepw_principal_dn = slapi_ch_smprintf("krbprincipalname="
|
|
"kadmin/changepw@%s,%s",
|
|
realm, ipa_realm_dn);
|
|
if (!ipa_changepw_principal_dn) {
|
|
LOG_OOM();
|
|
ret = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ipa_etc_config_dn = slapi_ch_smprintf("cn=ipaConfig,cn=etc,%s",
|
|
ipa_realm_tree);
|
|
if (!ipa_etc_config_dn) {
|
|
LOG_OOM();
|
|
ret = LDAP_OPERATIONS_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ret = LDAP_SUCCESS;
|
|
|
|
/* NOTE: We never call otp_config_fini() from a destructor. This is because
|
|
* it may race with threaded requests at shutdown. This leak should
|
|
* only occur when the DS is exiting, so it isn't a big deal.
|
|
*/
|
|
otp_config = otp_config_init(ipapwd_plugin_id);
|
|
|
|
done:
|
|
free(realm);
|
|
krb5_free_context(krbctx);
|
|
if (config_entry) slapi_entry_free(config_entry);
|
|
return ret;
|
|
}
|
|
|
|
static char *ipapwd_oid_list[] = {
|
|
EXOP_PASSWD_OID,
|
|
KEYTAB_SET_OID,
|
|
KEYTAB_GET_OID,
|
|
NULL
|
|
};
|
|
|
|
|
|
static char *ipapwd_name_list[] = {
|
|
"Password Change Extended Operation",
|
|
"Keytab Retrieval Extended Operation",
|
|
NULL
|
|
};
|
|
|
|
/* Initialization function */
|
|
int ipapwd_init( Slapi_PBlock *pb )
|
|
{
|
|
int ret;
|
|
Slapi_Entry *plugin_entry = NULL;
|
|
|
|
/* get args */
|
|
if ((slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_ENTRY, &plugin_entry) == 0) &&
|
|
plugin_entry) {
|
|
usetxn = slapi_entry_attr_get_bool(plugin_entry,
|
|
"nsslapd-pluginbetxn");
|
|
}
|
|
|
|
/* Get the arguments appended to the plugin extendedop directive. The first argument
|
|
* (after the standard arguments for the directive) should contain the OID of the
|
|
* extended operation. */
|
|
|
|
ret = slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &ipapwd_plugin_id);
|
|
if ((ret != 0) || (NULL == ipapwd_plugin_id)) {
|
|
LOG("Could not get identity or identity was NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
if (ipapwd_ext_init() != 0) {
|
|
LOG("Object Extension Operation failed\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Register the plug-in function as an extended operation
|
|
* plug-in function that handles the operation identified by
|
|
* OID 1.3.6.1.4.1.4203.1.11.1 . Also specify the version of the server
|
|
* plug-in */
|
|
ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)ipapwd_start);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list);
|
|
if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)ipapwd_extop);
|
|
if (ret) {
|
|
LOG("Failed to set plug-in version, function, and OID.\n" );
|
|
return -1;
|
|
}
|
|
|
|
if (usetxn) {
|
|
slapi_register_plugin("betxnpreoperation", 1,
|
|
"ipapwd_pre_init_betxn", ipapwd_pre_init_betxn,
|
|
"IPA pwd pre ops betxn", NULL,
|
|
ipapwd_plugin_id);
|
|
|
|
slapi_register_plugin("betxnpostoperation", 1,
|
|
"ipapwd_post_init_betxn", ipapwd_post_init_betxn,
|
|
"IPA pwd post ops betxn", NULL,
|
|
ipapwd_plugin_id);
|
|
}
|
|
|
|
slapi_register_plugin("preoperation", 1,
|
|
"ipapwd_pre_init", ipapwd_pre_init,
|
|
"IPA pwd pre ops", NULL,
|
|
ipapwd_plugin_id);
|
|
|
|
slapi_register_plugin("postoperation", 1,
|
|
"ipapwd_post_init", ipapwd_post_init,
|
|
"IPA pwd post ops", NULL,
|
|
ipapwd_plugin_id);
|
|
|
|
slapi_register_plugin("internalpostoperation", 1,
|
|
"ipapwd_intpost_init", ipapwd_intpost_init,
|
|
"IPA pwd internal post ops", NULL,
|
|
ipapwd_plugin_id);
|
|
|
|
return 0;
|
|
}
|