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 */
--
389-commits mailing list
389-commits@lists.fedoraproject.org
https://lists.fedoraproject.org/admin/lists/389-commits@lists.fedoraproject.org
No comments:
Post a Comment