dirsrvtests/tests/tickets/ticket47462_test.py | 389 ++++++++++++-------------- ldap/servers/slapd/daemon.c | 209 ++++--------- ldap/servers/slapd/pw.c | 4 ldap/servers/slapd/task.c | 346 +++++++++++++++++++++++ 4 files changed, 592 insertions(+), 356 deletions(-)
New commits: commit fd96d1bcfc854047e8796c07e44b134d560c495d Author: Mark Reynolds mreynolds@redhat.com Date: Tue Jun 7 10:02:42 2016 -0400
Ticket 48862 - At startup DES to AES password conversion causes timeout in start script
Bug Description: At server start all the backends are searches for entries that contain DES password attributes as defined in the plugin. These are typically unindexed searches, and if there is a very large backend this can cause the server startup to timeout.
Fix Description: At startup only check "cn=config" for entries with DES password attributes. A new "conversion" task has been created that can be run after startup to search all backends(if a suffix is not specified), or specific backends.
dn: cn=convertPasswords, cn=des2aes,cn=tasks,cn=config objectclass: top objectclass: extensibleObject suffix: dc=example,dc=com suffix: dc=other,dc=suffix
Another bug was discovered in pw_rever_encode() in pw.c where a "for" loop counter was accidentially reused by a second "for" loop. This could lead to an infinite loop/hang.
Updated the CI test to perform the conversion task.
https://fedorahosted.org/389/ticket/48862
Reviewed by: nhosoi(Thanks!)
(cherry picked from commit 11f55f3dd2a2c44ddf7b5be54273401add13b1bc)
diff --git a/dirsrvtests/tests/tickets/ticket47462_test.py b/dirsrvtests/tests/tickets/ticket47462_test.py index a909b37..2ac1478 100644 --- a/dirsrvtests/tests/tickets/ticket47462_test.py +++ b/dirsrvtests/tests/tickets/ticket47462_test.py @@ -1,19 +1,20 @@ -import os +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2015 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- +# import sys import time import ldap import logging -import socket -import time -import logging import pytest -import re from lib389 import DirSrv, Entry, tools from lib389.tools import DirSrvTools from lib389._constants import * from lib389.properties import * -from constants import * -from lib389._constants import *
logging.getLogger(__name__).setLevel(logging.DEBUG) log = logging.getLogger(__name__) @@ -31,6 +32,7 @@ AGMT_DN = '' USER_DN = 'cn=test_user,' + DEFAULT_SUFFIX USER1_DN = 'cn=test_user1,' + DEFAULT_SUFFIX TEST_REPL_DN = 'cn=test repl,' + DEFAULT_SUFFIX +DES2AES_TASK_DN = 'cn=convert,cn=des2aes,cn=tasks,cn=config'
class TopologyMaster1Master2(object): @@ -47,27 +49,6 @@ def topology(request): ''' This fixture is used to create a replicated topology for the 'module'. The replicated topology is MASTER1 <-> Master2. - At the beginning, It may exists a master2 instance and/or a master2 instance. - It may also exists a backup for the master1 and/or the master2. - - Principle: - If master1 instance exists: - restart it - If master2 instance exists: - restart it - If backup of master1 AND backup of master2 exists: - create or rebind to master1 - create or rebind to master2 - - restore master1 from backup - restore master2 from backup - else: - Cleanup everything - remove instances - remove backups - Create instances - Initialize replication - Create backups ''' global installation1_prefix global installation2_prefix @@ -96,134 +77,69 @@ def topology(request): args_master = args_instance.copy() master2.allocate(args_master)
- # Get the status of the backups - backup_master1 = master1.checkBackupFS() - backup_master2 = master2.checkBackupFS() - # Get the status of the instance and restart it if it exists instance_master1 = master1.exists() - if instance_master1: - master1.stop(timeout=10) - master1.start(timeout=10) - instance_master2 = master2.exists() - if instance_master2: - master2.stop(timeout=10) - master2.start(timeout=10) - - if backup_master1 and backup_master2: - # The backups exist, assuming they are correct - # we just re-init the instances with them - if not instance_master1: - master1.create() - # Used to retrieve configuration information (dbdir, confdir...) - master1.open() - - if not instance_master2: - master2.create() - # Used to retrieve configuration information (dbdir, confdir...) - master2.open() - - # restore master1 from backup - master1.stop(timeout=10) - master1.restoreFS(backup_master1) - master1.start(timeout=10) - - # restore master2 from backup - master2.stop(timeout=10) - master2.restoreFS(backup_master2) - master2.start(timeout=10) - else: - # We should be here only in two conditions - # - This is the first time a test involve master-consumer - # so we need to create everything - # - Something weird happened (instance/backup destroyed) - # so we discard everything and recreate all - - # Remove all the backups. So even if we have a specific backup file - # (e.g backup_master) we clear all backups that an instance my have created - if backup_master1: - master1.clearBackupFS() - if backup_master2: - master2.clearBackupFS() - - # Remove all the instances - if instance_master1: - master1.delete() - if instance_master2: - master2.delete() - - # Create the instances - master1.create() - master1.open() - master2.create() - master2.open()
- # - # Now prepare the Master-Consumer topology - # - # First Enable replication - master1.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_1) - master2.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_2) - - # Initialize the supplier->consumer - - properties = {RA_NAME: r'meTo_$host:$port', - RA_BINDDN: defaultProperties[REPLICATION_BIND_DN], - RA_BINDPW: defaultProperties[REPLICATION_BIND_PW], - RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD], - RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]} - AGMT_DN = master1.agreement.create(suffix=SUFFIX, host=master2.host, port=master2.port, properties=properties) - master1.agreement - if not AGMT_DN: - log.fatal("Fail to create a replica agreement") - sys.exit(1) - - log.debug("%s created" % AGMT_DN) - - properties = {RA_NAME: r'meTo_$host:$port', - RA_BINDDN: defaultProperties[REPLICATION_BIND_DN], - RA_BINDPW: defaultProperties[REPLICATION_BIND_PW], - RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD], - RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]} - master2.agreement.create(suffix=DEFAULT_SUFFIX, host=master1.host, port=master1.port, properties=properties) - - master1.agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2) - master1.waitForReplInit(AGMT_DN) - - # Check replication is working fine - master1.add_s(Entry((TEST_REPL_DN, {'objectclass': "top person".split(), - 'sn': 'test_repl', - 'cn': 'test_repl'}))) - loop = 0 - while loop <= 10: - try: - ent = master2.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE, "(objectclass=*)") - break - except ldap.NO_SUCH_OBJECT: - time.sleep(1) - loop += 1 - if not ent: - log.fatal('Replication is not working!') - assert False + # Remove all the instances + if instance_master1: + master1.delete() + if instance_master2: + master2.delete()
- # Time to create the backups - master1.stop(timeout=10) - master1.backupfile = master1.backupFS() - master1.start(timeout=10) + # Create the instances + master1.create() + master1.open() + master2.create() + master2.open()
- master2.stop(timeout=10) - master2.backupfile = master2.backupFS() - master2.start(timeout=10) + # + # Now prepare the Master-Consumer topology + # + # First Enable replication + master1.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_1) + master2.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_2) + + # Initialize the supplier->consumer + + properties = {RA_NAME: r'meTo_$host:$port', + RA_BINDDN: defaultProperties[REPLICATION_BIND_DN], + RA_BINDPW: defaultProperties[REPLICATION_BIND_PW], + RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD], + RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]} + AGMT_DN = master1.agreement.create(suffix=SUFFIX, host=master2.host, port=master2.port, properties=properties) + master1.agreement + if not AGMT_DN: + log.fatal("Fail to create a replica agreement") + sys.exit(1) + + log.debug("%s created" % AGMT_DN) + + properties = {RA_NAME: r'meTo_$host:$port', + RA_BINDDN: defaultProperties[REPLICATION_BIND_DN], + RA_BINDPW: defaultProperties[REPLICATION_BIND_PW], + RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD], + RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]} + master2.agreement.create(suffix=DEFAULT_SUFFIX, host=master1.host, port=master1.port, properties=properties) + + master1.agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2) + master1.waitForReplInit(AGMT_DN) + + # Check replication is working fine + if master1.testReplication(DEFAULT_SUFFIX, master2): + log.info('Replication is working.') + else: + log.fatal('Replication is not working.') + assert False
# clear the tmp directory master1.clearTmpDir(__file__)
- # - # Here we have two instances master and consumer - # with replication working. Either coming from a backup recovery - # or from a fresh (re)init - # Time to return the topology + def fin(): + master1.delete() + master2.delete() + request.addfinalizer(fin) + return TopologyMaster1Master2(master1, master2)
@@ -234,31 +150,39 @@ def test_ticket47462(topology): """
# - # First set config as if it's an older version. Set DES to use libdes-plugin, - # MMR to depend on DES, delete the existing AES plugin, and set a DES password - # for the replication agreement. - # - + # First set config as if it's an older version. Set DES to use + # libdes-plugin, MMR to depend on DES, delete the existing AES plugin, + # and set a DES password for the replication agreement. # # Add an extra attribute to the DES plugin args # try: topology.master1.modify_s(DES_PLUGIN, - [(ldap.MOD_REPLACE, 'nsslapd-pluginPath', 'libdes-plugin'), - (ldap.MOD_ADD, 'nsslapd-pluginarg2', 'description')]) + [(ldap.MOD_REPLACE, 'nsslapd-pluginEnabled', 'on')]) + except ldap.LDAPError as e: + log.fatal('Failed to enable DES plugin, error: ' + + e.message['desc']) + assert False
- except ldap.LDAPError, e: - log.fatal('Failed to reset DES plugin, error: ' + e.message['desc']) + try: + topology.master1.modify_s(DES_PLUGIN, + [(ldap.MOD_ADD, 'nsslapd-pluginarg2', 'description')]) + except ldap.LDAPError as e: + log.fatal('Failed to reset DES plugin, error: ' + + e.message['desc']) assert False
try: topology.master1.modify_s(MMR_PLUGIN, - [(ldap.MOD_DELETE, 'nsslapd-plugin-depends-on-named', 'AES')]) + [(ldap.MOD_DELETE, + 'nsslapd-plugin-depends-on-named', + 'AES')])
except ldap.NO_SUCH_ATTRIBUTE: pass - except ldap.LDAPError, e: - log.fatal('Failed to reset DES plugin, error: ' + e.message['desc']) + except ldap.LDAPError as e: + log.fatal('Failed to reset MMR plugin, error: ' + + e.message['desc']) assert False
# @@ -268,8 +192,9 @@ def test_ticket47462(topology): topology.master1.delete_s(AES_PLUGIN) except ldap.NO_SUCH_OBJECT: pass - except ldap.LDAPError, e: - log.fatal('Failed to delete AES plugin, error: ' + e.message['desc']) + except ldap.LDAPError as e: + log.fatal('Failed to delete AES plugin, error: ' + + e.message['desc']) assert False
# restart the server so we must use DES plugin @@ -279,20 +204,23 @@ def test_ticket47462(topology): # Get the agmt dn, and set the password # try: - entry = topology.master1.search_s('cn=config', ldap.SCOPE_SUBTREE, 'objectclass=nsDS5ReplicationAgreement') + entry = topology.master1.search_s('cn=config', ldap.SCOPE_SUBTREE, + 'objectclass=nsDS5ReplicationAgreement') if entry: agmt_dn = entry[0].dn log.info('Found agmt dn (%s)' % agmt_dn) else: log.fatal('No replication agreements!') assert False - except ldap.LDAPError, e: - log.fatal('Failed to search for replica credentials: ' + e.message['desc']) + except ldap.LDAPError as e: + log.fatal('Failed to search for replica credentials: ' + + e.message['desc']) assert False
try: properties = {RA_BINDPW: "password"} - topology.master1.agreement.setProperties(None, agmt_dn, None, properties) + topology.master1.agreement.setProperties(None, agmt_dn, None, + properties) log.info('Successfully modified replication agreement') except ValueError: log.error('Failed to update replica agreement: ' + AGMT_DN) @@ -305,12 +233,14 @@ def test_ticket47462(topology): topology.master1.add_s(Entry((USER1_DN, {'objectclass': "top person".split(), 'sn': 'sn', + 'description': 'DES value to convert', 'cn': 'test_user'}))) loop = 0 ent = None while loop <= 10: try: - ent = topology.master2.getEntry(USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)") + ent = topology.master2.getEntry(USER1_DN, ldap.SCOPE_BASE, + "(objectclass=*)") break except ldap.NO_SUCH_OBJECT: time.sleep(1) @@ -320,11 +250,20 @@ def test_ticket47462(topology): assert False else: log.info('Replication test passed') - except ldap.LDAPError, e: + except ldap.LDAPError as e: log.fatal('Failed to add test user: ' + e.message['desc']) assert False
# + # Add a backend (that has no entries) + # + try: + topology.master1.backend.create("o=empty", {BACKEND_NAME: "empty"}) + except ldap.LDAPError as e: + log.fatal('Failed to create extra/empty backend: ' + e.message['desc']) + assert False + + # # Run the upgrade... # topology.master1.upgrade('online') @@ -335,7 +274,8 @@ def test_ticket47462(topology): # Check that the restart converted existing DES credentials # try: - entry = topology.master1.search_s('cn=config', ldap.SCOPE_SUBTREE, 'nsDS5ReplicaCredentials=*') + entry = topology.master1.search_s('cn=config', ldap.SCOPE_SUBTREE, + 'nsDS5ReplicaCredentials=*') if entry: val = entry[0].getValue('nsDS5ReplicaCredentials') if val.startswith('{AES-'): @@ -344,26 +284,30 @@ def test_ticket47462(topology): log.fatal('Failed to convert credentials from DES to AES!') assert False else: - log.fatal('Failed to find any entries with nsDS5ReplicaCredentials ') + log.fatal('Failed to find entries with nsDS5ReplicaCredentials') assert False - except ldap.LDAPError, e: - log.fatal('Failed to search for replica credentials: ' + e.message['desc']) + except ldap.LDAPError as e: + log.fatal('Failed to search for replica credentials: ' + + e.message['desc']) assert False
# - # Check that the AES plugin exists, and has all the attributes listed in DES plugin. - # The attributes might not be in the expected order so check all the attributes. + # Check that the AES plugin exists, and has all the attributes listed in + # DES plugin. The attributes might not be in the expected order so check + # all the attributes. # try: - entry = topology.master1.search_s(AES_PLUGIN, ldap.SCOPE_BASE, 'objectclass=*') + entry = topology.master1.search_s(AES_PLUGIN, ldap.SCOPE_BASE, + 'objectclass=*') if not entry[0].hasValue('nsslapd-pluginarg0', 'description') and \ not entry[0].hasValue('nsslapd-pluginarg1', 'description') and \ not entry[0].hasValue('nsslapd-pluginarg2', 'description'): - log.fatal('The AES plugin did not have the DES attribute copied over correctly') + log.fatal('The AES plugin did not have the DES attribute copied ' + + 'over correctly') assert False else: log.info('The AES plugin was correctly setup') - except ldap.LDAPError, e: + except ldap.LDAPError as e: log.fatal('Failed to find AES plugin: ' + e.message['desc']) assert False
@@ -371,13 +315,14 @@ def test_ticket47462(topology): # Check that the MMR plugin was updated # try: - entry = topology.master1.search_s(MMR_PLUGIN, ldap.SCOPE_BASE, 'objectclass=*') + entry = topology.master1.search_s(MMR_PLUGIN, ldap.SCOPE_BASE, + 'objectclass=*') if not entry[0].hasValue('nsslapd-plugin-depends-on-named', 'AES'): log.fatal('The MMR Plugin was not correctly updated') assert False else: log.info('The MMR plugin was correctly updated') - except ldap.LDAPError, e: + except ldap.LDAPError as e: log.fatal('Failed to find AES plugin: ' + e.message['desc']) assert False
@@ -385,13 +330,14 @@ def test_ticket47462(topology): # Check that the DES plugin was correctly updated # try: - entry = topology.master1.search_s(DES_PLUGIN, ldap.SCOPE_BASE, 'objectclass=*') + entry = topology.master1.search_s(DES_PLUGIN, ldap.SCOPE_BASE, + 'objectclass=*') if not entry[0].hasValue('nsslapd-pluginPath', 'libpbe-plugin'): log.fatal('The DES Plugin was not correctly updated') assert False else: log.info('The DES plugin was correctly updated') - except ldap.LDAPError, e: + except ldap.LDAPError as e: log.fatal('Failed to find AES plugin: ' + e.message['desc']) assert False
@@ -407,7 +353,8 @@ def test_ticket47462(topology): ent = None while loop <= 10: try: - ent = topology.master2.getEntry(USER_DN, ldap.SCOPE_BASE, "(objectclass=*)") + ent = topology.master2.getEntry(USER_DN, ldap.SCOPE_BASE, + "(objectclass=*)") break except ldap.NO_SUCH_OBJECT: time.sleep(1) @@ -417,36 +364,70 @@ def test_ticket47462(topology): assert False else: log.info('Replication test passed') - except ldap.LDAPError, e: + except ldap.LDAPError as e: log.fatal('Failed to add test user: ' + e.message['desc']) assert False
+ # Check the entry + log.info('Entry before running task...') + try: + entry = topology.master1.search_s(USER1_DN, + ldap.SCOPE_BASE, + 'objectclass=*') + if entry: + print(str(entry)) + else: + log.fatal('Failed to find entries') + assert False + except ldap.LDAPError as e: + log.fatal('Failed to search for entries: ' + + e.message['desc']) + assert False + # - # If we got here the test passed + # Test the DES2AES Task on USER1_DN # - log.info('Test PASSED') - - -def test_ticket47462_final(topology): - topology.master1.stop(timeout=10) - topology.master2.stop(timeout=10) - + try: + topology.master1.add_s(Entry((DES2AES_TASK_DN, + {'objectclass': ['top', + 'extensibleObject'], + 'suffix': DEFAULT_SUFFIX, + 'cn': 'convert'}))) + except ldap.LDAPError as e: + log.fatal('Failed to add task entry: ' + e.message['desc']) + assert False
-def run_isolated(): - ''' - run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..) - To run isolated without py.test, you need to - - edit this file and comment '@pytest.fixture' line before 'topology' function. - - set the installation prefix - - run this program - ''' - global installation1_prefix - global installation2_prefix - installation1_prefix = None - installation2_prefix = None + # Wait for task + task_entry = Entry(DES2AES_TASK_DN) + (done, exitCode) = topology.master1.tasks.checkTask(task_entry, True) + if exitCode: + log.fatal("Error: des2aes task exited with %d" % (exitCode)) + assert False
- topo = topology(True) - test_ticket47462(topo) + # Check the entry + try: + entry = topology.master1.search_s(USER1_DN, + ldap.SCOPE_BASE, + 'objectclass=*') + if entry: + val = entry[0].getValue('description') + print(str(entry[0])) + if val.startswith('{AES-'): + log.info('Task: DES credentials have been converted to AES') + else: + log.fatal('Task: Failed to convert credentials from DES to ' + + 'AES! (%s)' % (val)) + assert False + else: + log.fatal('Failed to find entries') + assert False + except ldap.LDAPError as e: + log.fatal('Failed to search for entries: ' + + e.message['desc']) + assert False
if __name__ == '__main__': - run_isolated() + # Run isolated + # -s for DEBUG mode + CURRENT_FILE = os.path.realpath(__file__) + pytest.main("-s %s" % CURRENT_FILE) diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c index 11f51bc..8876110 100644 --- a/ldap/servers/slapd/daemon.c +++ b/ldap/servers/slapd/daemon.c @@ -950,13 +950,12 @@ convert_pbe_des_to_aes() Slapi_Entry **entries = NULL; struct slapdplugin *plugin = NULL; char **attrs = NULL; - char **backends = NULL; char *val = NULL; - int converted_des = 0; + int converted_des_passwd = 0; int result = -1; int have_aes = 0; int have_des = 0; - int i = 0, ii = 0, be_idx = 0; + int i = 0, ii = 0;
/* * Check that AES plugin is enabled, and grab all the unique @@ -990,91 +989,56 @@ convert_pbe_des_to_aes()
if(have_aes && have_des){ /* - * Build a list of all the backend dn's + * Find any entries in cn=config that contain DES passwords and convert + * them to AES */ - Slapi_Backend *be = NULL; - struct suffixlist *list; - char *cookie = NULL; - - LDAPDebug(LDAP_DEBUG_ANY, "convert_pbe_des_to_aes: " - "Converting DES passwords to AES...\n",0,0,0); - - be = slapi_get_first_backend(&cookie); - while (be){ - int suffix_idx = 0; - int count = slapi_counter_get_value(be->be_suffixcounter); - - list = be->be_suffixlist; - for (suffix_idx = 0; list && suffix_idx < count; suffix_idx++) { - char *suffix = (char *)slapi_sdn_get_ndn(list->be_suffix); - if(charray_inlist(backends, suffix) || strlen(suffix) == 0){ - list = list->next; - continue; - } - charray_add(&backends, slapi_ch_strdup(suffix)); - list = list->next; - } - be = slapi_get_next_backend (cookie); - } - slapi_ch_free ((void **)&cookie); + slapi_log_error(SLAPI_LOG_HOUSE, "convert_pbe_des_to_aes", + "Converting DES passwords to AES...\n");
- /* - * Search for the password attributes - */ for (i = 0; attrs && attrs[i]; i++){ char *filter = PR_smprintf("%s=*", attrs[i]); - /* - * Loop over all the backends looking for the password attribute - */ - for(be_idx = 0; backends && backends[be_idx]; be_idx++){ - pb = slapi_pblock_new(); - slapi_search_internal_set_pb(pb, backends[be_idx], - LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL, - (void *)plugin_get_default_component_id(), - SLAPI_OP_FLAG_IGNORE_UNINDEXED); - slapi_search_internal_pb(pb); - slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result); - if (LDAP_SUCCESS != result) { - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " - "failed to search for password on (%s) error (%d)\n", - backends[be_idx], result, 0); - goto done; - } - slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); - for (ii = 0; entries && entries[ii]; ii++){ - if((val = slapi_entry_attr_get_charptr(entries[ii], attrs[i]))){ - if(strlen(val) >= 5 && strncmp(val,"{DES}", 5) == 0){ - /* - * We have a DES encoded password, convert it AES - */ - Slapi_PBlock *mod_pb = NULL; - Slapi_Value *sval = NULL; - LDAPMod mod_replace; - LDAPMod *mods[2]; - char *replace_val[2]; - char *passwd = NULL; - - /* decode the DES password */ - if(pw_rever_decode(val, &passwd, attrs[i]) == -1){ - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " - "failed to decode existing DES password for (%s)\n", - slapi_entry_get_dn(entries[ii]), 0, 0); - converted_des = 0; - goto done; - }
- /* encode the password */ + pb = slapi_pblock_new(); + slapi_search_internal_set_pb(pb, "cn=config", + LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL, + (void *)plugin_get_default_component_id(), + SLAPI_OP_FLAG_IGNORE_UNINDEXED); + slapi_search_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + for (ii = 0; entries && entries[ii]; ii++){ + if((val = slapi_entry_attr_get_charptr(entries[ii], attrs[i]))){ + if(strlen(val) >= 5 && strncmp(val,"{DES}", 5) == 0){ + /* + * We have a DES encoded password, convert it to AES + */ + Slapi_PBlock *mod_pb = NULL; + Slapi_Value *sval = NULL; + LDAPMod mod_replace; + LDAPMod *mods[2]; + char *replace_val[2]; + char *passwd = NULL; + int rc = 0; + + /* decode the DES password */ + if(pw_rever_decode(val, &passwd, attrs[i]) == -1){ + slapi_log_error(SLAPI_LOG_FATAL ,"convert_pbe_des_to_aes", + "Failed to decode existing DES password for (%s)\n", + slapi_entry_get_dn(entries[ii])); + rc = -1; + } + + /* encode the password */ + if (rc == 0){ sval = slapi_value_new_string(passwd); if(pw_rever_encode(&sval, attrs[i]) == -1){ - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " + slapi_log_error(SLAPI_LOG_FATAL, "convert_pbe_des_to_aes", "failed to encode AES password for (%s)\n", - slapi_entry_get_dn(entries[ii]), 0, 0); - slapi_ch_free_string(&passwd); - slapi_value_free(&sval); - converted_des = 0; - goto done; + slapi_entry_get_dn(entries[ii])); + rc = -1; } + }
+ if (rc == 0){ /* replace the attribute in the entry */ replace_val[0] = (char *)slapi_value_get_string(sval); replace_val[1] = NULL; @@ -1091,88 +1055,35 @@ convert_pbe_des_to_aes()
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result); if (LDAP_SUCCESS != result) { - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " - "failed to convert password for (%s) error (%d)\n", - slapi_entry_get_dn(entries[ii]), result, 0); - converted_des = -1; + slapi_log_error(SLAPI_LOG_FATAL, "convert_pbe_des_to_aes" + "Failed to convert password for (%s) error (%d)\n", + slapi_entry_get_dn(entries[ii]), result); } else { - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " - "successfully converted password for (%s)\n", - slapi_entry_get_dn(entries[ii]), result, 0); - converted_des = 1; - - } - slapi_ch_free_string(&passwd); - slapi_value_free(&sval); - slapi_pblock_destroy(mod_pb); - if(result){ - goto done; + slapi_log_error(SLAPI_LOG_HOUSE, "convert_pbe_des_to_aes", + "Successfully converted password for (%s)\n", + slapi_entry_get_dn(entries[ii])); + converted_des_passwd = 1; } } - slapi_ch_free_string(&val); + slapi_ch_free_string(&passwd); + slapi_value_free(&sval); + slapi_pblock_destroy(mod_pb); } + slapi_ch_free_string(&val); } - slapi_free_search_results_internal(pb); - slapi_pblock_destroy(pb); - pb = NULL; } + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + pb = NULL; slapi_ch_free_string(&filter); } - } - -done: - charray_free(attrs); - charray_free(backends); - slapi_free_search_results_internal(pb); - slapi_pblock_destroy(pb); - - if (have_aes && have_des){ - /* - * If a conversion attempt did not fail, disable DES plugin - */ - if(converted_des != -1){ - /* - * Disable the DES plugin - this also prevents potentially expensive - * searches at every server startup. - */ - LDAPMod mod_replace; - LDAPMod *mods[2]; - char *replace_val[2]; - char *des_dn = "cn=DES,cn=Password Storage Schemes,cn=plugins,cn=config"; - - replace_val[0] = "off"; - replace_val[1] = NULL; - mod_replace.mod_op = LDAP_MOD_REPLACE; - mod_replace.mod_type = "nsslapd-pluginEnabled"; - mod_replace.mod_values = replace_val; - mods[0] = &mod_replace; - mods[1] = 0;
- pb = slapi_pblock_new(); - slapi_modify_internal_set_pb(pb, des_dn, mods, 0, 0, - (void *)plugin_get_default_component_id(), 0); - slapi_modify_internal_pb(pb); - slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result); - if (LDAP_SUCCESS != result) { - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " - "Failed to disable DES plugin (%s), error (%d)\n", - des_dn, result, 0); - } else { - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " - "Successfully disabled DES plugin (%s)\n", - des_dn, 0, 0); - } - slapi_pblock_destroy(pb); - } - if(converted_des == 1){ - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " - "Finished - all DES passwords have been converted to AES.\n", - 0, 0, 0); - } else if (converted_des == 0){ - LDAPDebug(LDAP_DEBUG_ANY, "convert_pbe_des_to_aes: " - "Finished - no DES passwords to convert.\n",0,0,0); + if (!converted_des_passwd){ + slapi_log_error(SLAPI_LOG_HOUSE, "convert_pbe_des_to_aes", + "No DES passwords found to convert.\n"); } } + charray_free(attrs); }
void slapd_daemon( daemon_ports_t *ports ) diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c index 538d915..f16ac98 100644 --- a/ldap/servers/slapd/pw.c +++ b/ldap/servers/slapd/pw.c @@ -552,10 +552,10 @@ pw_rever_encode(Slapi_Value **vals, char * attr_name) for ( p = get_plugin_list(PLUGIN_LIST_REVER_PWD_STORAGE_SCHEME); p != NULL; p = p->plg_next ) { char *L_attr = NULL; - int i = 0; + int i = 0, ii = 0;
/* Get the appropriate encoding function */ - for ( L_attr = p->plg_argv[i]; i<p->plg_argc; L_attr = p->plg_argv[++i] ) + for ( L_attr = p->plg_argv[ii]; ii<p->plg_argc; L_attr = p->plg_argv[++ii] ) { if (slapi_attr_types_equivalent(L_attr, attr_name)) { diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c index 19a52a3..38f83e6 100644 --- a/ldap/servers/slapd/task.c +++ b/ldap/servers/slapd/task.c @@ -82,6 +82,8 @@ static int shutting_down = 0; #define TASK_TOMBSTONE_FIXUP_BACKEND "backend" #define TASK_TOMBSTONE_FIXUP_SUFFIX "suffix" #define TASK_TOMBSTONE_FIXUP_STRIPCSN "stripcsn" +#define TASK_DES2AES "des2aes task" +
#define LOG_BUFFER 256 /* if the cumul. log gets larger than this, it's truncated: */ @@ -112,8 +114,10 @@ static const char *fetch_attr(Slapi_Entry *e, const char *attrname, const char *default_val); static Slapi_Entry *get_internal_entry(Slapi_PBlock *pb, char *dn); static void modify_internal_entry(char *dn, LDAPMod **mods); - static void fixup_tombstone_task_destructor(Slapi_Task *task); +static void task_des2aes_thread(void *arg); +static void des2aes_task_destructor(Slapi_Task *task); +
/*********************************** * Public Functions @@ -2459,6 +2463,345 @@ fixup_tombstone_task_destructor(Slapi_Task *task) "fixup_tombstone_task_destructor <--\n" ); }
+/* + * des2aes Task + * + * Convert any DES passwords to AES + * + * dn: cn=convertPasswords, cn=des2aes,cn=tasks,cn=config + * objectclass: top + * objectclass: extensibleObject + * suffix: dc=example,dc=com (If empty all backends are checked) + * suffix: dc=other,dc=suffix + */ +struct task_des2aes_data +{ + char **suffixes; + Slapi_Task *task; +}; + +static int +task_des2aes(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter, + int *returncode, char *returntext, void *arg) +{ + struct task_des2aes_data *task_data = NULL; + PRThread *thread = NULL; + Slapi_Task *task = NULL; + char **suffix = NULL; + char **bases = NULL; + int rc = SLAPI_DSE_CALLBACK_OK; + + /* Get the suffixes */ + if((suffix = slapi_entry_attr_get_charray(e, "suffix"))){ + int i; + for (i = 0; suffix && suffix[i]; i++){ + /* Make sure "suffix" is NUL terminated string */ + char *dn = slapi_create_dn_string("%s", suffix[i]); + + if(dn){ + if(slapi_dn_syntax_check(pb, dn, 1)){ + /* invalid suffix name */ + PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, + "Invalid DN syntax (%s) specified for "suffix"\n", + suffix[i]); + *returncode = LDAP_INVALID_DN_SYNTAX; + slapi_ch_free_string(&dn); + rc = SLAPI_DSE_CALLBACK_ERROR; + goto error; + } else { + slapi_ch_array_add(&bases, dn); + } + } else{ + PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, + "Invalid DN (%s) specified for "suffix"\n", suffix[i]); + *returncode = LDAP_INVALID_DN_SYNTAX; + rc = SLAPI_DSE_CALLBACK_ERROR; + goto error; + } + } + } + + /* Build the task data and fire off a thread to perform the conversion */ + task = slapi_new_task(slapi_entry_get_ndn(e)); + + /* register our destructor for cleaning up our private data */ + slapi_task_set_destructor_fn(task, des2aes_task_destructor); + task_data = (struct task_des2aes_data *)slapi_ch_calloc(1, sizeof(struct task_des2aes_data)); + task_data->suffixes = bases; + task_data->task = task; + + /* Start the conversion thread */ + thread = PR_CreateThread(PR_USER_THREAD, task_des2aes_thread, + (void *)task_data, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE); + if (thread == NULL) { + PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, + "unable to create des2aes thread!\n"); + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "unable to create des2aes thread!\n"); + *returncode = LDAP_OPERATIONS_ERROR; + slapi_task_finish(task, *returncode); + rc = SLAPI_DSE_CALLBACK_ERROR; + } + +error: + if (rc == SLAPI_DSE_CALLBACK_ERROR){ + slapi_ch_array_free(bases); + slapi_ch_array_free(suffix); + slapi_ch_free((void **)&task_data); + } + return rc; +} + +static void +task_des2aes_thread(void *arg) +{ + struct task_des2aes_data *task_data = arg; + Slapi_PBlock *pb = NULL; + Slapi_Entry **entries = NULL; + Slapi_Task *task = task_data->task; + struct slapdplugin *plugin = NULL; + char **attrs = NULL; + char **backends = NULL; + char *val = NULL; + int converted_des_passwd = 0; + int result = -1; + int have_aes = 0; + int have_des = 0; + int i = 0, ii = 0, be_idx = 0; + int rc = 0; + + /* + * Check that AES plugin is enabled, and grab all the unique + * password attributes. + */ + for ( plugin = get_plugin_list(PLUGIN_LIST_REVER_PWD_STORAGE_SCHEME); + plugin != NULL; + plugin = plugin->plg_next ) + { + char *plugin_arg = NULL; + + if(plugin->plg_started && strcasecmp(plugin->plg_name, "AES") == 0){ + /* We have the AES plugin, and its enabled */ + have_aes = 1; + } + if(plugin->plg_started && strcasecmp(plugin->plg_name, "DES") == 0){ + /* We have the DES plugin, and its enabled */ + have_des = 1; + } + /* Gather all the unique password attributes from all the PBE plugins */ + for ( i = 0, plugin_arg = plugin->plg_argv[i]; + i < plugin->plg_argc; + plugin_arg = plugin->plg_argv[++i] ) + { + if(charray_inlist(attrs, plugin_arg)){ + continue; + } + charray_add(&attrs, slapi_ch_strdup(plugin_arg)); + } + } + + if(have_aes && have_des){ + if(task_data->suffixes == NULL){ + /* + * Build a list of all the backend dn's + */ + Slapi_Backend *be = NULL; + struct suffixlist *list; + char *cookie = NULL; + + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "Checking for DES passwords to convert to AES...\n"); + slapi_task_log_notice(task, + "Checking for DES passwords to convert to AES...\n"); + + be = slapi_get_first_backend(&cookie); + while (be){ + int suffix_idx = 0; + int count = slapi_counter_get_value(be->be_suffixcounter); + + list = be->be_suffixlist; + for (suffix_idx = 0; list && suffix_idx < count; suffix_idx++) { + char *suffix = (char *)slapi_sdn_get_ndn(list->be_suffix); + if(charray_inlist(backends, suffix) || strlen(suffix) == 0){ + list = list->next; + continue; + } + charray_add(&backends, slapi_ch_strdup(suffix)); + list = list->next; + } + be = slapi_get_next_backend (cookie); + } + slapi_ch_free ((void **)&cookie); + } else { + backends = task_data->suffixes; + } + + /* + * Search for the password attributes + */ + for (i = 0; attrs && attrs[i]; i++){ + char *filter = PR_smprintf("%s=*", attrs[i]); + /* + * Loop over all the backends looking for the password attribute + */ + for(be_idx = 0; backends && backends[be_idx]; be_idx++){ + pb = slapi_pblock_new(); + slapi_search_internal_set_pb(pb, backends[be_idx], + LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, NULL, + (void *)plugin_get_default_component_id(), + SLAPI_OP_FLAG_IGNORE_UNINDEXED); + slapi_search_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + if (LDAP_SUCCESS != result) { + slapi_log_error(SLAPI_LOG_FATAL, "convert_pbe_des_to_aes: ", + "Failed to search for password attribute (%s) error (%d), skipping suffix (%s)\n", + attrs[i], result, backends[be_idx]); + slapi_task_log_notice(task, + "Failed to search for password attribute (%s) error (%d), skipping suffix (%s)\n", + attrs[i], result, backends[be_idx]); + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + pb = NULL; + continue; + } + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + for (ii = 0; entries && entries[ii]; ii++){ + if((val = slapi_entry_attr_get_charptr(entries[ii], attrs[i]))){ + if(strlen(val) >= 5 && strncmp(val,"{DES}", 5) == 0){ + /* + * We have a DES encoded password, convert it AES + */ + Slapi_PBlock *mod_pb = NULL; + Slapi_Value *sval = NULL; + LDAPMod mod_replace; + LDAPMod *mods[2]; + char *replace_val[2]; + char *passwd = NULL; + + /* Decode the DES password */ + if(pw_rever_decode(val, &passwd, attrs[i]) == -1){ + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "Failed to decode existing DES password for (%s)\n", + slapi_entry_get_dn(entries[ii])); + slapi_task_log_notice(task, + "Failed to decode existing DES password for (%s)\n", + slapi_entry_get_dn(entries[ii])); + rc = 1; + goto done; + } + + /* Encode the password */ + sval = slapi_value_new_string(passwd); + if(pw_rever_encode(&sval, attrs[i]) == -1){ + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "failed to encode AES password for (%s)\n", + slapi_entry_get_dn(entries[ii])); + slapi_task_log_notice(task, + "failed to encode AES password for (%s)\n", + slapi_entry_get_dn(entries[ii])); + slapi_ch_free_string(&passwd); + slapi_value_free(&sval); + rc = 1; + goto done; + } + + /* Replace the attribute in the entry */ + replace_val[0] = (char *)slapi_value_get_string(sval); + replace_val[1] = NULL; + mod_replace.mod_op = LDAP_MOD_REPLACE; + mod_replace.mod_type = attrs[i]; + mod_replace.mod_values = replace_val; + mods[0] = &mod_replace; + mods[1] = 0; + + mod_pb = slapi_pblock_new(); + slapi_modify_internal_set_pb(mod_pb, slapi_entry_get_dn(entries[ii]), + mods, 0, 0, (void *)plugin_get_default_component_id(), 0); + slapi_modify_internal_pb(mod_pb); + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + if (LDAP_SUCCESS != result) { + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "Failed to convert password for (%s) error (%d)\n", + slapi_entry_get_dn(entries[ii]), result); + slapi_task_log_notice(task, + "Failed to convert password for (%s) error (%d)\n", + slapi_entry_get_dn(entries[ii]), result); + rc = 1; + } else { + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "Successfully converted password for (%s)\n", + slapi_entry_get_dn(entries[ii])); + slapi_task_log_notice(task, + "Successfully converted password for (%s)\n", + slapi_entry_get_dn(entries[ii])); + converted_des_passwd = 1; + } + slapi_ch_free_string(&passwd); + slapi_value_free(&sval); + slapi_pblock_destroy(mod_pb); + } + slapi_ch_free_string(&val); + } + } + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + pb = NULL; + } + slapi_ch_free_string(&filter); + } + if (!converted_des_passwd){ + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "No DES passwords found to convert.\n"); + slapi_task_log_notice(task, "No DES passwords found to convert.\n"); + } + } else { + /* No AES/DES */ + if (!have_des){ + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "DES plugin not enabled\n"); + slapi_task_log_notice(task, "DES plugin not enabled\n"); + } + if (!have_aes){ + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "AES plugin not enabled\n"); + slapi_task_log_notice(task, "AES plugin not enabled\n"); + } + slapi_log_error(SLAPI_LOG_FATAL, TASK_DES2AES, + "Unable to convert passwords\n"); + slapi_task_log_notice(task, "Unable to convert passwords\n"); + rc = 1; + } + +done: + charray_free(attrs); + charray_free(backends); + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + slapi_task_finish(task, rc); +} + +static void +des2aes_task_destructor(Slapi_Task *task) +{ + slapi_log_error(SLAPI_LOG_TRACE, TASK_DES2AES, + "des2aes_task_destructor -->\n" ); + if (task) { + struct task_des2aes_data *task_data = (struct task_des2aes_data *)slapi_task_get_data(task); + while (slapi_task_get_refcount(task) > 0) { + /* Yield to wait for the task to finish. */ + DS_Sleep (PR_MillisecondsToInterval(100)); + } + if (task_data) { + slapi_ch_array_free(task_data->suffixes); + slapi_ch_free((void **)&task_data); + } + } + slapi_log_error(SLAPI_LOG_TRACE, TASK_DES2AES, + "des2aes_task_destructor <--\n" ); +} + /* cleanup old tasks that may still be in the DSE from a previous session * (this can happen if the server crashes [no matter how unlikely we like * to think that is].) @@ -2540,6 +2883,7 @@ void task_init(void) slapi_task_register_handler("upgradedb", task_upgradedb_add); slapi_task_register_handler("sysconfig reload", task_sysconfig_reload_add); slapi_task_register_handler("fixup tombstones", task_fixup_tombstones_add); + slapi_task_register_handler("des2aes", task_des2aes); }
/* called when the server is shutting down -- abort all existing tasks */