From c0ad918939c40779e463b71c41ab106e7ee890e2 Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Jun 07 2016 18:11:05 +0000 Subject: 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 f290647..2ac1478 100644 --- a/dirsrvtests/tests/tickets/ticket47462_test.py +++ b/dirsrvtests/tests/tickets/ticket47462_test.py @@ -32,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): @@ -134,6 +135,11 @@ def topology(request): # clear the tmp directory master1.clearTmpDir(__file__) + def fin(): + master1.delete() + master2.delete() + request.addfinalizer(fin) + return TopologyMaster1Master2(master1, master2) @@ -144,11 +150,9 @@ 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 # @@ -156,24 +160,29 @@ def test_ticket47462(topology): topology.master1.modify_s(DES_PLUGIN, [(ldap.MOD_REPLACE, 'nsslapd-pluginEnabled', 'on')]) except ldap.LDAPError as e: - log.fatal('Failed to enable DES plugin, error: ' + e.message['desc']) + log.fatal('Failed to enable DES plugin, error: ' + + e.message['desc']) assert False 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']) + 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 as e: - log.fatal('Failed to reset MMR plugin, error: ' + e.message['desc']) + log.fatal('Failed to reset MMR plugin, error: ' + + e.message['desc']) assert False # @@ -184,7 +193,8 @@ def test_ticket47462(topology): except ldap.NO_SUCH_OBJECT: pass except ldap.LDAPError as e: - log.fatal('Failed to delete AES plugin, error: ' + e.message['desc']) + log.fatal('Failed to delete AES plugin, error: ' + + e.message['desc']) assert False # restart the server so we must use DES plugin @@ -194,7 +204,8 @@ 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) @@ -202,12 +213,14 @@ def test_ticket47462(topology): log.fatal('No replication agreements!') assert False except ldap.LDAPError as e: - log.fatal('Failed to search for replica credentials: ' + e.message['desc']) + 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) @@ -220,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) @@ -259,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-'): @@ -268,22 +284,26 @@ 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 as e: - log.fatal('Failed to search for replica credentials: ' + e.message['desc']) + 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') @@ -295,7 +315,8 @@ 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 @@ -309,7 +330,8 @@ 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 @@ -331,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) @@ -345,30 +368,66 @@ def test_ticket47462(topology): 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 -def test_ticket47462_final(topology): - topology.master1.delete() - topology.master2.delete() - log.info('Testcase PASSED') - - -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 + # + # Test the DES2AES Task on USER1_DN + # + 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 - topo = topology(True) - test_ticket47462(topo) - test_ticket47462_final(topo) + # 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 + # 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 d702129..29562ae 100644 --- a/ldap/servers/slapd/daemon.c +++ b/ldap/servers/slapd/daemon.c @@ -692,14 +692,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_passwd = 0; - int disable_des = 1; 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 @@ -733,94 +731,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: " - "Checking for DES passwords to convert 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) { - slapi_log_error(SLAPI_LOG_TRACE, "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_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){ - 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); - disable_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); - disable_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; @@ -837,83 +797,34 @@ 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: " + 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, 0); - disable_des = 0; + slapi_entry_get_dn(entries[ii]), result); } else { - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " + slapi_log_error(SLAPI_LOG_HOUSE, "convert_pbe_des_to_aes", "Successfully converted password for (%s)\n", - slapi_entry_get_dn(entries[ii]), result, 0); + 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_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); } if (!converted_des_passwd){ - slapi_log_error(SLAPI_LOG_FATAL, "convert_pbe_des_to_aes", + slapi_log_error(SLAPI_LOG_HOUSE, "convert_pbe_des_to_aes", "No DES passwords found to convert.\n"); } } - -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 then we can disable the DES plugin - */ - if(converted_des_passwd && disable_des){ - /* - * 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); - LDAPDebug(LDAP_DEBUG_ANY,"convert_pbe_des_to_aes: " - "All DES passwords have been converted to AES.\n", - 0, 0, 0); - } - } } #ifdef ENABLE_NUNC_STANS diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c index 09a0a6b..93e9078 100644 --- a/ldap/servers/slapd/pw.c +++ b/ldap/servers/slapd/pw.c @@ -516,10 +516,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]; iplg_argc; L_attr = p->plg_argv[++i] ) + for ( L_attr = p->plg_argv[ii]; iiplg_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 db3c222..405f0bf 100644 --- a/ldap/servers/slapd/task.c +++ b/ldap/servers/slapd/task.c @@ -53,6 +53,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: */ @@ -83,8 +85,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 @@ -2425,6 +2429,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].) @@ -2506,6 +2849,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 */