mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-26 00:41:25 -06:00
c19911845d
This creates a new role, replicaadmin, so a non-DM user can do limited management of replication agreements. Note that with cn=config if an unauthorized user performs a search an error is not returned, no entries are returned. This makes it difficult to determine if there are simply no replication agreements or we aren't allowed to see them. Once the ipaldap.py module gets replaced by ldap2 we can use Get Effective Rights to easily tell the difference.
556 lines
22 KiB
Python
556 lines
22 KiB
Python
# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
|
|
#
|
|
# Copyright (C) 2007 Red Hat
|
|
# see file 'COPYING' for use and warranty information
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation; version 2 only
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
|
|
import time, logging
|
|
|
|
import ldap
|
|
from ipaserver.install import dsinstance
|
|
from ipaserver import ipaldap
|
|
from ldap import modlist
|
|
from ipalib import util
|
|
from ipalib import errors
|
|
|
|
DIRMAN_CN = "cn=directory manager"
|
|
CACERT = "/usr/share/ipa/html/ca.crt"
|
|
# the default container used by AD for user entries
|
|
WIN_USER_CONTAINER = "cn=Users"
|
|
# the default container used by IPA for user entries
|
|
IPA_USER_CONTAINER = "cn=users,cn=accounts"
|
|
PORT = 636
|
|
TIMEOUT = 120
|
|
|
|
IPA_REPLICA = 1
|
|
WINSYNC = 2
|
|
|
|
SASL_AUTH = ldap.sasl.sasl({}, 'GSSAPI')
|
|
|
|
class ReplicationManager:
|
|
"""Manage replication agreements between DS servers, and sync
|
|
agreements with Windows servers"""
|
|
def __init__(self, hostname, dirman_passwd):
|
|
self.hostname = hostname
|
|
self.dirman_passwd = dirman_passwd
|
|
|
|
# If we are passed a password we'll use it as the DM password
|
|
# otherwise we'll do a GSSAPI bind.
|
|
self.conn = ipaldap.IPAdmin(hostname, port=PORT, cacert=CACERT)
|
|
if dirman_passwd:
|
|
self.conn.do_simple_bind(bindpw=dirman_passwd)
|
|
else:
|
|
self.conn.sasl_interactive_bind_s('', SASL_AUTH)
|
|
|
|
self.repl_man_passwd = dirman_passwd
|
|
|
|
# these are likely constant, but you could change them
|
|
# at runtime if you really want
|
|
self.repl_man_dn = "cn=replication manager,cn=config"
|
|
self.repl_man_cn = "replication manager"
|
|
self.suffix = ""
|
|
|
|
def _get_replica_id(self, conn, master_conn):
|
|
"""
|
|
Returns the replica ID which is unique for each backend.
|
|
|
|
conn is the connection we are trying to get the replica ID for.
|
|
master_conn is the master we are going to replicate with.
|
|
"""
|
|
# First see if there is already one set
|
|
dn = self.replica_dn()
|
|
try:
|
|
replica = conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0]
|
|
if replica.getValue('nsDS5ReplicaId'):
|
|
return int(replica.getValue('nsDS5ReplicaId'))
|
|
except ldap.NO_SUCH_OBJECT:
|
|
pass
|
|
|
|
# Ok, either the entry doesn't exist or the attribute isn't set
|
|
# so get it from the other master
|
|
retval = -1
|
|
dn = "cn=replication, cn=etc, %s" % self.suffix
|
|
try:
|
|
replica = master_conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0]
|
|
if not replica.getValue('nsDS5ReplicaId'):
|
|
logging.debug("Unable to retrieve nsDS5ReplicaId from remote server")
|
|
raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server")
|
|
except ldap.NO_SUCH_OBJECT:
|
|
logging.debug("Unable to retrieve nsDS5ReplicaId from remote server")
|
|
raise
|
|
|
|
# Now update the value on the master
|
|
retval = int(replica.getValue('nsDS5ReplicaId'))
|
|
mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaId', str(retval + 1))]
|
|
|
|
try:
|
|
master_conn.modify_s(dn, mod)
|
|
except Exception, e:
|
|
logging.debug("Problem updating nsDS5ReplicaID %s" % e)
|
|
raise
|
|
|
|
return retval
|
|
|
|
def find_replication_dns(self, conn):
|
|
"""
|
|
The replication agreements are stored in
|
|
cn="$SUFFIX",cn=mapping tree,cn=config
|
|
|
|
FIXME: Rather than failing with a read error if a user tries
|
|
to read this it simply returns zero entries. We need to use
|
|
GER to determine if we are allowed to read this to return a proper
|
|
response. For now just return "No entries" even if the user may
|
|
not be allowed to see them.
|
|
"""
|
|
filt = "(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement))"
|
|
try:
|
|
ents = conn.search_s("cn=mapping tree,cn=config", ldap.SCOPE_SUBTREE, filt)
|
|
except ldap.NO_SUCH_OBJECT:
|
|
return []
|
|
return [ent.dn for ent in ents]
|
|
|
|
def add_replication_manager(self, conn, passwd=None):
|
|
"""
|
|
Create a pseudo user to use for replication. If no password
|
|
is provided the directory manager password will be used.
|
|
"""
|
|
|
|
if passwd:
|
|
self.repl_man_passwd = passwd
|
|
|
|
ent = ipaldap.Entry(self.repl_man_dn)
|
|
ent.setValues("objectclass", "top", "person")
|
|
ent.setValues("cn", self.repl_man_cn)
|
|
ent.setValues("userpassword", self.repl_man_passwd)
|
|
ent.setValues("sn", "replication manager pseudo user")
|
|
|
|
try:
|
|
conn.add_s(ent)
|
|
except ldap.ALREADY_EXISTS:
|
|
# should we set the password here?
|
|
pass
|
|
|
|
def delete_replication_manager(self, conn, dn="cn=replication manager,cn=config"):
|
|
try:
|
|
conn.delete_s(dn)
|
|
except ldap.NO_SUCH_OBJECT:
|
|
pass
|
|
|
|
def get_replica_type(self, master=True):
|
|
if master:
|
|
return "3"
|
|
else:
|
|
return "2"
|
|
|
|
def replica_dn(self):
|
|
return 'cn=replica, cn="%s", cn=mapping tree, cn=config' % self.suffix
|
|
|
|
def local_replica_config(self, conn, replica_id):
|
|
dn = self.replica_dn()
|
|
|
|
try:
|
|
conn.getEntry(dn, ldap.SCOPE_BASE)
|
|
# replication is already configured
|
|
return
|
|
except errors.NotFound:
|
|
pass
|
|
|
|
replica_type = self.get_replica_type()
|
|
|
|
entry = ipaldap.Entry(dn)
|
|
entry.setValues('objectclass', "top", "nsds5replica", "extensibleobject")
|
|
entry.setValues('cn', "replica")
|
|
entry.setValues('nsds5replicaroot', self.suffix)
|
|
entry.setValues('nsds5replicaid', str(replica_id))
|
|
entry.setValues('nsds5replicatype', replica_type)
|
|
entry.setValues('nsds5flags', "1")
|
|
entry.setValues('nsds5replicabinddn', [self.repl_man_dn])
|
|
entry.setValues('nsds5replicalegacyconsumer', "off")
|
|
|
|
conn.add_s(entry)
|
|
|
|
def setup_changelog(self, conn):
|
|
dn = "cn=changelog5, cn=config"
|
|
dirpath = conn.dbdir + "/cldb"
|
|
entry = ipaldap.Entry(dn)
|
|
entry.setValues('objectclass', "top", "extensibleobject")
|
|
entry.setValues('cn', "changelog5")
|
|
entry.setValues('nsslapd-changelogdir', dirpath)
|
|
try:
|
|
conn.add_s(entry)
|
|
except ldap.ALREADY_EXISTS:
|
|
return
|
|
|
|
def setup_chaining_backend(self, conn):
|
|
chaindn = "cn=chaining database, cn=plugins, cn=config"
|
|
benamebase = "chaindb"
|
|
urls = [self.to_ldap_url(conn)]
|
|
cn = ""
|
|
benum = 1
|
|
done = False
|
|
while not done:
|
|
try:
|
|
cn = benamebase + str(benum) # e.g. localdb1
|
|
dn = "cn=" + cn + ", " + chaindn
|
|
entry = ipaldap.Entry(dn)
|
|
entry.setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance')
|
|
entry.setValues('cn', cn)
|
|
entry.setValues('nsslapd-suffix', self.suffix)
|
|
entry.setValues('nsfarmserverurl', urls)
|
|
entry.setValues('nsmultiplexorbinddn', self.repl_man_dn)
|
|
entry.setValues('nsmultiplexorcredentials', self.repl_man_passwd)
|
|
|
|
self.conn.add_s(entry)
|
|
done = True
|
|
except ldap.ALREADY_EXISTS:
|
|
benum += 1
|
|
except ldap.LDAPError, e:
|
|
print "Could not add backend entry " + dn, e
|
|
raise
|
|
|
|
return cn
|
|
|
|
def to_ldap_url(self, conn):
|
|
return "ldap://%s:%d/" % (conn.host, conn.port)
|
|
|
|
def setup_chaining_farm(self, conn):
|
|
try:
|
|
conn.modify_s(self.suffix, [(ldap.MOD_ADD, 'aci',
|
|
[ "(targetattr = \"*\")(version 3.0; acl \"Proxied authorization for database links\"; allow (proxy) userdn = \"ldap:///%s\";)" % self.repl_man_dn ])])
|
|
except ldap.TYPE_OR_VALUE_EXISTS:
|
|
logging.debug("proxy aci already exists in suffix %s on %s" % (self.suffix, conn.host))
|
|
|
|
def get_mapping_tree_entry(self):
|
|
try:
|
|
entry = self.conn.getEntry("cn=mapping tree,cn=config", ldap.SCOPE_ONELEVEL,
|
|
"(cn=\"%s\")" % (self.suffix))
|
|
except errors.NotFound, e:
|
|
logging.debug("failed to find mappting tree entry for %s" % self.suffix)
|
|
raise e
|
|
|
|
return entry
|
|
|
|
|
|
def enable_chain_on_update(self, bename):
|
|
mtent = self.get_mapping_tree_entry()
|
|
dn = mtent.dn
|
|
|
|
plgent = self.conn.getEntry("cn=Multimaster Replication Plugin,cn=plugins,cn=config",
|
|
ldap.SCOPE_BASE, "(objectclass=*)", ['nsslapd-pluginPath'])
|
|
path = plgent.getValue('nsslapd-pluginPath')
|
|
|
|
mod = [(ldap.MOD_REPLACE, 'nsslapd-state', 'backend'),
|
|
(ldap.MOD_ADD, 'nsslapd-backend', bename),
|
|
(ldap.MOD_ADD, 'nsslapd-distribution-plugin', path),
|
|
(ldap.MOD_ADD, 'nsslapd-distribution-funct', 'repl_chain_on_update')]
|
|
|
|
try:
|
|
self.conn.modify_s(dn, mod)
|
|
except ldap.TYPE_OR_VALUE_EXISTS:
|
|
logging.debug("chainOnUpdate already enabled for %s" % self.suffix)
|
|
|
|
def setup_chain_on_update(self, other_conn):
|
|
chainbe = self.setup_chaining_backend(other_conn)
|
|
self.enable_chain_on_update(chainbe)
|
|
|
|
def add_passsync_user(self, conn, password):
|
|
pass_dn = "uid=passsync,cn=sysaccounts,cn=etc,%s" % self.suffix
|
|
print "The user for the Windows PassSync service is %s" % pass_dn
|
|
try:
|
|
conn.getEntry(pass_dn, ldap.SCOPE_BASE)
|
|
print "Windows PassSync entry exists, not resetting password"
|
|
return
|
|
except errors.NotFound:
|
|
pass
|
|
|
|
# The user doesn't exist, add it
|
|
entry = ipaldap.Entry(pass_dn)
|
|
entry.setValues("objectclass", ["account", "simplesecurityobject"])
|
|
entry.setValues("uid", "passsync")
|
|
entry.setValues("userPassword", password)
|
|
conn.add_s(entry)
|
|
|
|
# Add it to the list of users allowed to bypass password policy
|
|
extop_dn = "cn=ipa_pwd_extop,cn=plugins,cn=config"
|
|
entry = conn.getEntry(extop_dn, ldap.SCOPE_BASE)
|
|
pass_mgrs = entry.getValues('passSyncManagersDNs')
|
|
if not pass_mgrs:
|
|
pass_mgrs = []
|
|
if not isinstance(pass_mgrs, list):
|
|
pass_mgrs = [pass_mgrs]
|
|
pass_mgrs.append(pass_dn)
|
|
mod = [(ldap.MOD_REPLACE, 'passSyncManagersDNs', pass_mgrs)]
|
|
conn.modify_s(extop_dn, mod)
|
|
|
|
# And finally grant it permission to write passwords
|
|
mod = [(ldap.MOD_ADD, 'aci',
|
|
['(targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Windows PassSync service can write passwords"; allow (write) userdn="ldap:///%s";)' % pass_dn])]
|
|
try:
|
|
conn.modify_s(self.suffix, mod)
|
|
except ldap.TYPE_OR_VALUE_EXISTS:
|
|
logging.debug("passsync aci already exists in suffix %s on %s" % (self.suffix, conn.host))
|
|
|
|
def setup_winsync_agmt(self, entry, **kargs):
|
|
entry.setValues("objectclass", "nsDSWindowsReplicationAgreement")
|
|
entry.setValues("nsds7WindowsReplicaSubtree",
|
|
kargs.get("win_subtree",
|
|
WIN_USER_CONTAINER + "," + self.suffix))
|
|
entry.setValues("nsds7DirectoryReplicaSubtree",
|
|
kargs.get("ds_subtree",
|
|
IPA_USER_CONTAINER + "," + self.suffix))
|
|
# for now, just sync users and ignore groups
|
|
entry.setValues("nsds7NewWinUserSyncEnabled", kargs.get('newwinusers', 'true'))
|
|
entry.setValues("nsds7NewWinGroupSyncEnabled", kargs.get('newwingroups', 'false'))
|
|
windomain = ''
|
|
if kargs.has_key('windomain'):
|
|
windomain = kargs['windomain']
|
|
else:
|
|
windomain = '.'.join(ldap.explode_dn(self.suffix, 1))
|
|
entry.setValues("nsds7WindowsDomain", windomain)
|
|
|
|
def agreement_dn(self, hostname, port=PORT):
|
|
cn = "meTo%s%d" % (hostname, port)
|
|
dn = "cn=%s, %s" % (cn, self.replica_dn())
|
|
|
|
return (cn, dn)
|
|
|
|
def setup_agreement(self, a, b, **kargs):
|
|
cn, dn = self.agreement_dn(b.host)
|
|
try:
|
|
a.getEntry(dn, ldap.SCOPE_BASE)
|
|
return
|
|
except errors.NotFound:
|
|
pass
|
|
|
|
iswinsync = kargs.get("winsync", False)
|
|
repl_man_dn = kargs.get("binddn", self.repl_man_dn)
|
|
repl_man_passwd = kargs.get("bindpw", self.repl_man_passwd)
|
|
port = kargs.get("port", PORT)
|
|
|
|
entry = ipaldap.Entry(dn)
|
|
entry.setValues('objectclass', "nsds5replicationagreement")
|
|
entry.setValues('cn', cn)
|
|
entry.setValues('nsds5replicahost', b.host)
|
|
entry.setValues('nsds5replicaport', str(port))
|
|
entry.setValues('nsds5replicatimeout', str(TIMEOUT))
|
|
entry.setValues('nsds5replicabinddn', repl_man_dn)
|
|
entry.setValues('nsds5replicacredentials', repl_man_passwd)
|
|
entry.setValues('nsds5replicabindmethod', 'simple')
|
|
entry.setValues('nsds5replicaroot', self.suffix)
|
|
entry.setValues('nsds5replicaupdateschedule', '0000-2359 0123456')
|
|
entry.setValues('nsds5replicatransportinfo', 'SSL')
|
|
entry.setValues('nsDS5ReplicatedAttributeList', '(objectclass=*) $ EXCLUDE memberOf')
|
|
entry.setValues('description', "me to %s%d" % (b.host, port))
|
|
if iswinsync:
|
|
self.setup_winsync_agmt(entry, **kargs)
|
|
|
|
a.add_s(entry)
|
|
|
|
entry = a.waitForEntry(entry)
|
|
|
|
def delete_agreement(self, hostname):
|
|
cn, dn = self.agreement_dn(hostname)
|
|
return self.conn.deleteEntry(dn)
|
|
|
|
def check_repl_init(self, conn, agmtdn):
|
|
done = False
|
|
hasError = 0
|
|
attrlist = ['cn', 'nsds5BeginReplicaRefresh',
|
|
'nsds5replicaUpdateInProgress',
|
|
'nsds5ReplicaLastInitStatus',
|
|
'nsds5ReplicaLastInitStart',
|
|
'nsds5ReplicaLastInitEnd']
|
|
entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist)
|
|
if not entry:
|
|
print "Error reading status from agreement", agmtdn
|
|
hasError = 1
|
|
else:
|
|
refresh = entry.nsds5BeginReplicaRefresh
|
|
inprogress = entry.nsds5replicaUpdateInProgress
|
|
status = entry.nsds5ReplicaLastInitStatus
|
|
if not refresh: # done - check status
|
|
if not status:
|
|
print "No status yet"
|
|
elif status.find("replica busy") > -1:
|
|
print "[%s] reports: Replica Busy! Status: [%s]" % (conn.host, status)
|
|
done = True
|
|
hasError = 2
|
|
elif status.find("Total update succeeded") > -1:
|
|
print "Update succeeded"
|
|
done = True
|
|
elif inprogress.lower() == 'true':
|
|
print "Update in progress yet not in progress"
|
|
else:
|
|
print "[%s] reports: Update failed! Status: [%s]" % (conn.host, status)
|
|
hasError = 1
|
|
done = True
|
|
else:
|
|
print "Update in progress"
|
|
|
|
return done, hasError
|
|
|
|
def check_repl_update(self, conn, agmtdn):
|
|
done = False
|
|
hasError = 0
|
|
attrlist = ['cn', 'nsds5replicaUpdateInProgress',
|
|
'nsds5ReplicaLastUpdateStatus', 'nsds5ReplicaLastUpdateStart',
|
|
'nsds5ReplicaLastUpdateEnd']
|
|
entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist)
|
|
if not entry:
|
|
print "Error reading status from agreement", agmtdn
|
|
hasError = 1
|
|
else:
|
|
inprogress = entry.nsds5replicaUpdateInProgress
|
|
status = entry.nsds5ReplicaLastUpdateStatus
|
|
start = entry.nsds5ReplicaLastUpdateStart
|
|
end = entry.nsds5ReplicaLastUpdateEnd
|
|
# incremental update is done if inprogress is false and end >= start
|
|
done = inprogress and inprogress.lower() == 'false' and start and end and (start <= end)
|
|
logging.info("Replication Update in progress: %s: status: %s: start: %s: end: %s" %
|
|
(inprogress, status, start, end))
|
|
if not done and status: # check for errors
|
|
# status will usually be a number followed by a string
|
|
# number != 0 means error
|
|
rc, msg = status.split(' ', 1)
|
|
if rc != '0':
|
|
hasError = 1
|
|
done = True
|
|
|
|
return done, hasError
|
|
|
|
def wait_for_repl_init(self, conn, agmtdn):
|
|
done = False
|
|
haserror = 0
|
|
while not done and not haserror:
|
|
time.sleep(1) # give it a few seconds to get going
|
|
done, haserror = self.check_repl_init(conn, agmtdn)
|
|
return haserror
|
|
|
|
def wait_for_repl_update(self, conn, agmtdn, maxtries=600):
|
|
done = False
|
|
haserror = 0
|
|
while not done and not haserror and maxtries > 0:
|
|
time.sleep(1) # give it a few seconds to get going
|
|
done, haserror = self.check_repl_update(conn, agmtdn)
|
|
maxtries -= 1
|
|
if maxtries == 0: # too many tries
|
|
print "Error: timeout: could not determine agreement status: please check your directory server logs for possible errors"
|
|
haserror = 1
|
|
return haserror
|
|
|
|
def start_replication(self, other_conn, conn=None):
|
|
print "Starting replication, please wait until this has completed."
|
|
if conn == None:
|
|
conn = self.conn
|
|
cn, dn = self.agreement_dn(conn.host)
|
|
|
|
mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')]
|
|
other_conn.modify_s(dn, mod)
|
|
|
|
return self.wait_for_repl_init(other_conn, dn)
|
|
|
|
def basic_replication_setup(self, conn, replica_id):
|
|
self.add_replication_manager(conn)
|
|
self.local_replica_config(conn, replica_id)
|
|
self.setup_changelog(conn)
|
|
|
|
def setup_replication(self, other_hostname, realm_name, **kargs):
|
|
"""
|
|
NOTES:
|
|
- the directory manager password needs to be the same on
|
|
both directories. Or use the optional binddn and bindpw
|
|
"""
|
|
iswinsync = kargs.get("winsync", False)
|
|
oth_port = kargs.get("port", PORT)
|
|
oth_cacert = kargs.get("cacert", CACERT)
|
|
oth_binddn = kargs.get("binddn", DIRMAN_CN)
|
|
oth_bindpw = kargs.get("bindpw", self.dirman_passwd)
|
|
# note - there appears to be a bug in python-ldap - it does not
|
|
# allow connections using two different CA certs
|
|
other_conn = ipaldap.IPAdmin(other_hostname, port=oth_port, cacert=oth_cacert)
|
|
try:
|
|
# For now we always require a password to set up new replica
|
|
other_conn.do_simple_bind(binddn=oth_binddn, bindpw=oth_bindpw)
|
|
except Exception, e:
|
|
if iswinsync:
|
|
logging.info("Could not validate connection to remote server %s:%d - continuing" %
|
|
(other_hostname, oth_port))
|
|
logging.info("The error was: %s" % e)
|
|
else:
|
|
raise e
|
|
|
|
self.suffix = ipaldap.IPAdmin.normalizeDN(util.realm_to_suffix(realm_name))
|
|
|
|
if not iswinsync:
|
|
local_id = self._get_replica_id(self.conn, other_conn)
|
|
else:
|
|
# there is no other side to get a replica ID from
|
|
local_id = self._get_replica_id(self.conn, self.conn)
|
|
self.basic_replication_setup(self.conn, local_id)
|
|
|
|
if not iswinsync:
|
|
other_id = self._get_replica_id(other_conn, other_conn)
|
|
self.basic_replication_setup(other_conn, other_id)
|
|
self.setup_agreement(other_conn, self.conn)
|
|
self.setup_agreement(self.conn, other_conn)
|
|
return self.start_replication(other_conn)
|
|
else:
|
|
self.add_passsync_user(self.conn, kargs.get("passsync"))
|
|
self.setup_agreement(self.conn, other_conn, **kargs)
|
|
logging.info("Added new sync agreement, waiting for it to become ready . . .")
|
|
cn, dn = self.agreement_dn(other_hostname)
|
|
self.wait_for_repl_update(self.conn, dn, 30)
|
|
logging.info("Agreement is ready, starting replication . . .")
|
|
return self.start_replication(self.conn, other_conn)
|
|
|
|
def initialize_replication(self, dn, conn):
|
|
mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')]
|
|
try:
|
|
conn.modify_s(dn, mod)
|
|
except ldap.ALREADY_EXISTS:
|
|
return
|
|
|
|
def force_synch(self, dn, schedule, conn):
|
|
newschedule = '2358-2359 0'
|
|
|
|
# On the remote chance of a match. We force a synch to happen right
|
|
# now by changing the schedule to something else and quickly changing
|
|
# it back.
|
|
if newschedule == schedule:
|
|
newschedule = '2358-2359 1'
|
|
logging.info("Changing agreement %s schedule to %s to force synch" %
|
|
(dn, newschedule))
|
|
mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaUpdateSchedule', [ newschedule ])]
|
|
conn.modify_s(dn, mod)
|
|
time.sleep(1)
|
|
logging.info("Changing agreement %s to restore original schedule %s" %
|
|
(dn, schedule))
|
|
mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaUpdateSchedule', [ schedule ])]
|
|
conn.modify_s(dn, mod)
|
|
|
|
def get_agreement_type(self, hostname):
|
|
cn, dn = self.agreement_dn(hostname)
|
|
|
|
entry = self.conn.getEntry(dn, ldap.SCOPE_BASE)
|
|
|
|
objectclass = entry.getValues("objectclass")
|
|
|
|
for o in objectclass:
|
|
if o.lower() == "nsdswindowsreplicationagreement":
|
|
return WINSYNC
|
|
|
|
return IPA_REPLICA
|