summaryrefslogtreecommitdiff
path: root/source4/scripting/python/samba
diff options
context:
space:
mode:
authorDave Craft <wimberosa@gmail.com>2012-01-11 08:11:35 -0600
committerAndrew Tridgell <tridge@samba.org>2012-01-14 07:45:11 +0100
commitab1f896c5152dfd10609ac146eaaecd1bd2d5b70 (patch)
tree1494d3ab9afb06a692171625876c4cb0b144367f /source4/scripting/python/samba
parentaff8dad076f803e6deb2c5b59fa3bc5cb7ca7bd7 (diff)
downloadsamba-ab1f896c5152dfd10609ac146eaaecd1bd2d5b70.tar.gz
samba-ab1f896c5152dfd10609ac146eaaecd1bd2d5b70.tar.bz2
samba-ab1f896c5152dfd10609ac146eaaecd1bd2d5b70.zip
KCC importldif/exportldif and intersite topology
Add options for extracting an LDIF file from a database and reimporting the LDIF into a schema-less database for subsequent topology test/debug. Add intersite topology generation with computation of ISTG and bridgehead servers Signed-off-by: Andrew Tridgell <tridge@samba.org> Autobuild-User: Andrew Tridgell <tridge@samba.org> Autobuild-Date: Sat Jan 14 07:45:11 CET 2012 on sn-devel-104
Diffstat (limited to 'source4/scripting/python/samba')
-rw-r--r--source4/scripting/python/samba/kcc_utils.py1176
1 files changed, 972 insertions, 204 deletions
diff --git a/source4/scripting/python/samba/kcc_utils.py b/source4/scripting/python/samba/kcc_utils.py
index f762f4a2529..93096e96899 100644
--- a/source4/scripting/python/samba/kcc_utils.py
+++ b/source4/scripting/python/samba/kcc_utils.py
@@ -20,15 +20,14 @@
import ldb
import uuid
+import time
-from samba import dsdb
-from samba.dcerpc import (
- drsblobs,
- drsuapi,
- misc,
- )
+from samba import (dsdb, unix2nttime)
+from samba.dcerpc import (drsblobs, \
+ drsuapi, \
+ misc)
from samba.common import dsdb_Dn
-from samba.ndr import (ndr_unpack, ndr_pack)
+from samba.ndr import (ndr_unpack, ndr_pack)
class NCType(object):
@@ -42,47 +41,80 @@ class NamingContext(object):
Subclasses may inherit from this and specialize
"""
- def __init__(self, nc_dnstr, nc_guid=None, nc_sid=None):
+ def __init__(self, nc_dnstr):
"""Instantiate a NamingContext
:param nc_dnstr: NC dn string
- :param nc_guid: NC guid
- :param nc_sid: NC sid
"""
self.nc_dnstr = nc_dnstr
- self.nc_guid = nc_guid
- self.nc_sid = nc_sid
- self.nc_type = NCType.unknown
+ self.nc_guid = None
+ self.nc_sid = None
+ self.nc_type = NCType.unknown
def __str__(self):
'''Debug dump string output of class'''
text = "%s:" % self.__class__.__name__
text = text + "\n\tnc_dnstr=%s" % self.nc_dnstr
text = text + "\n\tnc_guid=%s" % str(self.nc_guid)
- text = text + "\n\tnc_sid=%s" % self.nc_sid
+
+ if self.nc_sid is None:
+ text = text + "\n\tnc_sid=<absent>"
+ else:
+ text = text + "\n\tnc_sid=<present>"
+
text = text + "\n\tnc_type=%s" % self.nc_type
return text
+ def load_nc(self, samdb):
+ attrs = [ "objectGUID",
+ "objectSid" ]
+ try:
+ res = samdb.search(base=self.nc_dnstr,
+ scope=ldb.SCOPE_BASE, attrs=attrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find naming context (%s)" % \
+ (self.nc_dnstr, estr))
+ msg = res[0]
+ if "objectGUID" in msg:
+ self.nc_guid = misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["objectGUID"][0]))
+ if "objectSid" in msg:
+ self.nc_sid = msg["objectSid"][0]
+
+ assert self.nc_guid is not None
+ return
+
def is_schema(self):
'''Return True if NC is schema'''
+ assert self.nc_type != NCType.unknown
return self.nc_type == NCType.schema
def is_domain(self):
'''Return True if NC is domain'''
+ assert self.nc_type != NCType.unknown
return self.nc_type == NCType.domain
def is_application(self):
'''Return True if NC is application'''
+ assert self.nc_type != NCType.unknown
return self.nc_type == NCType.application
def is_config(self):
'''Return True if NC is config'''
+ assert self.nc_type != NCType.unknown
return self.nc_type == NCType.config
def identify_by_basedn(self, samdb):
"""Given an NC object, identify what type is is thru
the samdb basedn strings and NC sid value
"""
+ # Invoke loader to initialize guid and more
+ # importantly sid value (sid is used to identify
+ # domain NCs)
+ if self.nc_guid is None:
+ self.load_nc(samdb)
+
# We check against schema and config because they
# will be the same for all nTDSDSAs in the forest.
# That leaves the domain NCs which can be identified
@@ -118,7 +150,7 @@ class NamingContext(object):
# NCs listed under hasMasterNCs are either
# default domain, schema, or config. We
- # utilize the identify_by_samdb_basedn() to
+ # utilize the identify_by_basedn() to
# identify those
elif attr == "hasMasterNCs":
self.identify_by_basedn(samdb)
@@ -136,14 +168,11 @@ class NCReplica(NamingContext):
class) and it identifies unique attributes of the DSA's replica for a NC.
"""
- def __init__(self, dsa_dnstr, dsa_guid, nc_dnstr,
- nc_guid=None, nc_sid=None):
+ def __init__(self, dsa_dnstr, dsa_guid, nc_dnstr):
"""Instantiate a Naming Context Replica
:param dsa_guid: GUID of DSA where replica appears
:param nc_dnstr: NC dn string
- :param nc_guid: NC guid
- :param nc_sid: NC sid
"""
self.rep_dsa_dnstr = dsa_dnstr
self.rep_dsa_guid = dsa_guid
@@ -152,6 +181,8 @@ class NCReplica(NamingContext):
self.rep_ro = False
self.rep_instantiated_flags = 0
+ self.rep_fsmo_role_owner = None
+
# RepsFromTo tuples
self.rep_repsFrom = []
@@ -163,17 +194,18 @@ class NCReplica(NamingContext):
self.rep_present_criteria_one = False
# Call my super class we inherited from
- NamingContext.__init__(self, nc_dnstr, nc_guid, nc_sid)
+ NamingContext.__init__(self, nc_dnstr)
def __str__(self):
'''Debug dump string output of class'''
text = "%s:" % self.__class__.__name__
- text = text + "\n\tdsa_dnstr=%s" % self.rep_dsa_dnstr
- text = text + "\n\tdsa_guid=%s" % str(self.rep_dsa_guid)
- text = text + "\n\tdefault=%s" % self.rep_default
- text = text + "\n\tro=%s" % self.rep_ro
- text = text + "\n\tpartial=%s" % self.rep_partial
- text = text + "\n\tpresent=%s" % self.is_present()
+ text = text + "\n\tdsa_dnstr=%s" % self.rep_dsa_dnstr
+ text = text + "\n\tdsa_guid=%s" % str(self.rep_dsa_guid)
+ text = text + "\n\tdefault=%s" % self.rep_default
+ text = text + "\n\tro=%s" % self.rep_ro
+ text = text + "\n\tpartial=%s" % self.rep_partial
+ text = text + "\n\tpresent=%s" % self.is_present()
+ text = text + "\n\tfsmo_role_owner=%s" % self.rep_fsmo_role_owner
for rep in self.rep_repsFrom:
text = text + "\n%s" % rep
@@ -283,7 +315,7 @@ class NCReplica(NamingContext):
ndr_unpack(drsblobs.repsFromToBlob, value))
self.rep_repsFrom.append(rep)
- def commit_repsFrom(self, samdb):
+ def commit_repsFrom(self, samdb, ro=False):
"""Commit repsFrom to the database"""
# XXX - This is not truly correct according to the MS-TECH
@@ -298,23 +330,39 @@ class NCReplica(NamingContext):
# older KCC also did
modify = False
newreps = []
+ delreps = []
for repsFrom in self.rep_repsFrom:
# Leave out any to be deleted from
- # replacement list
+ # replacement list. Build a list
+ # of to be deleted reps which we will
+ # remove from rep_repsFrom list below
if repsFrom.to_be_deleted == True:
+ delreps.append(repsFrom)
modify = True
continue
if repsFrom.is_modified():
+ repsFrom.set_unmodified()
modify = True
+ # current (unmodified) elements also get
+ # appended here but no changes will occur
+ # unless something is "to be modified" or
+ # "to be deleted"
newreps.append(ndr_pack(repsFrom.ndr_blob))
+ # Now delete these from our list of rep_repsFrom
+ for repsFrom in delreps:
+ self.rep_repsFrom.remove(repsFrom)
+ delreps = []
+
# Nothing to do if no reps have been modified or
- # need to be deleted. Leave database record "as is"
- if modify == False:
+ # need to be deleted or input option has informed
+ # us to be "readonly" (ro). Leave database
+ # record "as is"
+ if modify == False or ro == True:
return
m = ldb.Message()
@@ -330,15 +378,51 @@ class NCReplica(NamingContext):
raise Exception("Could not set repsFrom for (%s) - (%s)" %
(self.dsa_dnstr, estr))
+ def dumpstr_to_be_deleted(self):
+ text=""
+ for repsFrom in self.rep_repsFrom:
+ if repsFrom.to_be_deleted == True:
+ if text:
+ text = text + "\n%s" % repsFrom
+ else:
+ text = "%s" % repsFrom
+ return text
+
+ def dumpstr_to_be_modified(self):
+ text=""
+ for repsFrom in self.rep_repsFrom:
+ if repsFrom.is_modified() == True:
+ if text:
+ text = text + "\n%s" % repsFrom
+ else:
+ text = "%s" % repsFrom
+ return text
+
def load_fsmo_roles(self, samdb):
- # XXX - to be implemented
+ """Given an NC replica which has been discovered thru the nTDSDSA
+ database object, load the fSMORoleOwner attribute.
+ """
+ try:
+ res = samdb.search(base=self.nc_dnstr, scope=ldb.SCOPE_BASE,
+ attrs=[ "fSMORoleOwner" ])
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find NC for (%s) - (%s)" %
+ (self.nc_dnstr, estr))
+
+ msg = res[0]
+
+ # Possibly no fSMORoleOwner
+ if "fSMORoleOwner" in msg:
+ self.rep_fsmo_role_owner = msg["fSMORoleOwner"]
return
def is_fsmo_role_owner(self, dsa_dnstr):
- # XXX - to be implemented
+ if self.rep_fsmo_role_owner is not None and \
+ self.rep_fsmo_role_owner == dsa_dnstr:
+ return True
return False
-
class DirectoryServiceAgent(object):
def __init__(self, dsa_dnstr):
@@ -352,6 +436,7 @@ class DirectoryServiceAgent(object):
self.dsa_guid = None
self.dsa_ivid = None
self.dsa_is_ro = False
+ self.dsa_is_istg = False
self.dsa_options = 0
self.dsa_behavior = 0
self.default_dnstr = None # default domain dn string for dsa
@@ -365,7 +450,7 @@ class DirectoryServiceAgent(object):
self.needed_rep_table = {}
# NTDSConnections for this dsa. These are current
- # valid connections that are committed or "to be committed"
+ # valid connections that are committed or pending a commit
# in the database. Indexed by DN string of connection
self.connect_table = {}
@@ -382,6 +467,7 @@ class DirectoryServiceAgent(object):
text = text + "\n\tro=%s" % self.is_ro()
text = text + "\n\tgc=%s" % self.is_gc()
+ text = text + "\n\tistg=%s" % self.is_istg()
text = text + "\ncurrent_replica_table:"
text = text + "\n%s" % self.dumpstr_current_replica_table()
@@ -393,7 +479,15 @@ class DirectoryServiceAgent(object):
return text
def get_current_replica(self, nc_dnstr):
- return self.current_rep_table[nc_dnstr]
+ if nc_dnstr in self.current_rep_table.keys():
+ return self.current_rep_table[nc_dnstr]
+ else:
+ return None
+
+ def is_istg(self):
+ '''Returns True if dsa is intersite topology generator for it's site'''
+ # The KCC on an RODC always acts as an ISTG for itself
+ return self.dsa_is_istg or self.dsa_is_ro
def is_ro(self):
'''Returns True if dsa a read only domain controller'''
@@ -415,11 +509,11 @@ class DirectoryServiceAgent(object):
return True
return False
- def should_translate_ntdsconn(self):
+ def is_translate_ntdsconn_disabled(self):
"""Whether this allows NTDSConnection translation in its options."""
if (self.options & dsdb.DS_NTDSDSA_OPT_DISABLE_NTDSCONN_XLATE) != 0:
- return False
- return True
+ return True
+ return False
def get_rep_tables(self):
"""Return DSA current and needed replica tables
@@ -433,12 +527,11 @@ class DirectoryServiceAgent(object):
def load_dsa(self, samdb):
"""Load a DSA from the samdb.
-
+
Prior initialization has given us the DN of the DSA that we are to
load. This method initializes all other attributes, including loading
- the NC replica table for this DSA.
+ the NC replica table for this DSA.
"""
- controls = [ "extended_dn:1:1" ]
attrs = ["objectGUID",
"invocationID",
"options",
@@ -446,7 +539,7 @@ class DirectoryServiceAgent(object):
"msDS-Behavior-Version"]
try:
res = samdb.search(base=self.dsa_dnstr, scope=ldb.SCOPE_BASE,
- attrs=attrs, controls=controls)
+ attrs=attrs)
except ldb.LdbError, (enum, estr):
raise Exception("Unable to find nTDSDSA for (%s) - (%s)" %
@@ -481,17 +574,16 @@ class DirectoryServiceAgent(object):
def load_current_replica_table(self, samdb):
"""Method to load the NC replica's listed for DSA object.
-
+
This method queries the samdb for (hasMasterNCs, msDS-hasMasterNCs,
hasPartialReplicaNCs, msDS-HasDomainNCs, msDS-hasFullReplicaNCs, and
msDS-HasInstantiatedNCs) to determine complete list of NC replicas that
are enumerated for the DSA. Once a NC replica is loaded it is
identified (schema, config, etc) and the other replica attributes
- (partial, ro, etc) are determined.
+ (partial, ro, etc) are determined.
:param samdb: database to query for DSA replica list
"""
- controls = ["extended_dn:1:1"]
ncattrs = [ # not RODC - default, config, schema (old style)
"hasMasterNCs",
# not RODC - default, config, schema, app NCs
@@ -506,7 +598,7 @@ class DirectoryServiceAgent(object):
"msDS-HasInstantiatedNCs" ]
try:
res = samdb.search(base=self.dsa_dnstr, scope=ldb.SCOPE_BASE,
- attrs=ncattrs, controls=controls)
+ attrs=ncattrs)
except ldb.LdbError, (enum, estr):
raise Exception("Unable to find nTDSDSA NCs for (%s) - (%s)" %
@@ -533,23 +625,13 @@ class DirectoryServiceAgent(object):
# listed.
for value in res[0][k]:
# Turn dn into a dsdb_Dn so we can use
- # its methods to parse the extended pieces.
- # Note we don't really need the exact sid value
- # but instead only need to know if its present.
- dsdn = dsdb_Dn(samdb, value)
- guid = dsdn.dn.get_extended_component('GUID')
- sid = dsdn.dn.get_extended_component('SID')
+ # its methods to parse a binary DN
+ dsdn = dsdb_Dn(samdb, value)
flags = dsdn.get_binary_integer()
dnstr = str(dsdn.dn)
- if guid is None:
- raise Exception("Missing GUID for (%s) - (%s: %s)" %
- (self.dsa_dnstr, k, value))
- guid = misc.GUID(guid)
-
- if not dnstr in tmp_table:
- rep = NCReplica(self.dsa_dnstr, self.dsa_guid,
- dnstr, guid, sid)
+ if not dnstr in tmp_table.keys():
+ rep = NCReplica(self.dsa_dnstr, self.dsa_guid, dnstr)
tmp_table[dnstr] = rep
else:
rep = tmp_table[dnstr]
@@ -572,7 +654,7 @@ class DirectoryServiceAgent(object):
def add_needed_replica(self, rep):
"""Method to add a NC replica that "should be present" to the
- needed_rep_table if not already in the table
+ needed_rep_table if not already in the table
"""
if not rep.nc_dnstr in self.needed_rep_table.keys():
self.needed_rep_table[rep.nc_dnstr] = rep
@@ -603,23 +685,45 @@ class DirectoryServiceAgent(object):
connect.load_connection(samdb)
self.connect_table[dnstr] = connect
- def commit_connection_table(self, samdb):
+ def commit_connections(self, samdb, ro=False):
"""Method to commit any uncommitted nTDSConnections
- that are in our table. These would be identified
- connections that are marked to be added or deleted
- :param samdb: database to commit DSA connection list to
+ modifications that are in our table. These would be
+ identified connections that are marked to be added or
+ deleted
+
+ :param samdb: database to commit DSA connection list to
+ :param ro: if (true) then peform internal operations but
+ do not write to the database (readonly)
"""
+ delconn = []
+
for dnstr, connect in self.connect_table.items():
- connect.commit_connection(samdb)
+ if connect.to_be_added:
+ connect.commit_added(samdb, ro)
+
+ if connect.to_be_modified:
+ connect.commit_modified(samdb, ro)
+
+ if connect.to_be_deleted:
+ connect.commit_deleted(samdb, ro)
+ delconn.append(dnstr)
+
+ # Now delete the connection from the table
+ for dnstr in delconn:
+ del self.connect_table[dnstr]
+
+ return
def add_connection(self, dnstr, connect):
+ assert dnstr not in self.connect_table.keys()
self.connect_table[dnstr] = connect
def get_connection_by_from_dnstr(self, from_dnstr):
"""Scan DSA nTDSConnection table and return connection
- with a "fromServer" dn string equivalent to method
- input parameter.
- :param from_dnstr: search for this from server entry
+ with a "fromServer" dn string equivalent to method
+ input parameter.
+
+ :param from_dnstr: search for this from server entry
"""
for dnstr, connect in self.connect_table.items():
if connect.get_from_dnstr() == from_dnstr:
@@ -656,20 +760,71 @@ class DirectoryServiceAgent(object):
text = "%s" % self.connect_table[k]
return text
+ def new_connection(self, options, flags, transport, from_dnstr, sched):
+ """Set up a new connection for the DSA based on input
+ parameters. Connection will be added to the DSA
+ connect_table and will be marked as "to be added" pending
+ a call to commit_connections()
+ """
+ dnstr = "CN=%s," % str(uuid.uuid4()) + self.dsa_dnstr
+
+ connect = NTDSConnection(dnstr)
+ connect.to_be_added = True
+ connect.enabled = True
+ connect.from_dnstr = from_dnstr
+ connect.options = options
+ connect.flags = flags
+
+ if transport is not None:
+ connect.transport_dnstr = transport.dnstr
+
+ if sched is not None:
+ connect.schedule = sched
+ else:
+ # Create schedule. Attribute valuse set according to MS-TECH
+ # intrasite connection creation document
+ connect.schedule = drsblobs.schedule()
+
+ connect.schedule.size = 188
+ connect.schedule.bandwidth = 0
+ connect.schedule.numberOfSchedules = 1
+
+ header = drsblobs.scheduleHeader()
+ header.type = 0
+ header.offset = 20
+
+ connect.schedule.headerArray = [ header ]
+
+ # 168 byte instances of the 0x01 value. The low order 4 bits
+ # of the byte equate to 15 minute intervals within a single hour.
+ # There are 168 bytes because there are 168 hours in a full week
+ # Effectively we are saying to perform replication at the end of
+ # each hour of the week
+ data = drsblobs.scheduleSlots()
+ data.slots = [ 0x01 ] * 168
+
+ connect.schedule.dataArray = [ data ]
+
+ self.add_connection(dnstr, connect);
+ return connect
+
class NTDSConnection(object):
"""Class defines a nTDSConnection found under a DSA
"""
def __init__(self, dnstr):
self.dnstr = dnstr
+ self.guid = None
self.enabled = False
- self.committed = False # new connection needs to be committed
+ self.whenCreated = 0
+ self.to_be_added = False # new connection needs to be added
+ self.to_be_deleted = False # old connection needs to be deleted
+ self.to_be_modified = False
self.options = 0
- self.flags = 0
+ self.system_flags = 0
self.transport_dnstr = None
self.transport_guid = None
self.from_dnstr = None
- self.from_guid = None
self.schedule = None
def __str__(self):
@@ -677,16 +832,21 @@ class NTDSConnection(object):
text = "%s:\n\tdn=%s" % (self.__class__.__name__, self.dnstr)
text = text + "\n\tenabled=%s" % self.enabled
- text = text + "\n\tcommitted=%s" % self.committed
+ text = text + "\n\tto_be_added=%s" % self.to_be_added
+ text = text + "\n\tto_be_deleted=%s" % self.to_be_deleted
+ text = text + "\n\tto_be_modified=%s" % self.to_be_modified
text = text + "\n\toptions=0x%08X" % self.options
- text = text + "\n\tflags=0x%08X" % self.flags
+ text = text + "\n\tsystem_flags=0x%08X" % self.system_flags
+ text = text + "\n\twhenCreated=%d" % self.whenCreated
text = text + "\n\ttransport_dn=%s" % self.transport_dnstr
+ if self.guid is not None:
+ text = text + "\n\tguid=%s" % str(self.guid)
+
if self.transport_guid is not None:
text = text + "\n\ttransport_guid=%s" % str(self.transport_guid)
text = text + "\n\tfrom_dn=%s" % self.from_dnstr
- text = text + "\n\tfrom_guid=%s" % str(self.from_guid)
if self.schedule is not None:
text = text + "\n\tschedule.size=%s" % self.schedule.size
@@ -708,19 +868,20 @@ class NTDSConnection(object):
def load_connection(self, samdb):
"""Given a NTDSConnection object with an prior initialization
- for the object's DN, search for the DN and load attributes
- from the samdb.
+ for the object's DN, search for the DN and load attributes
+ from the samdb.
"""
- controls = ["extended_dn:1:1"]
attrs = [ "options",
"enabledConnection",
"schedule",
+ "whenCreated",
+ "objectGUID",
"transportType",
"fromServer",
"systemFlags" ]
try:
res = samdb.search(base=self.dnstr, scope=ldb.SCOPE_BASE,
- attrs=attrs, controls=controls)
+ attrs=attrs)
except ldb.LdbError, (enum, estr):
raise Exception("Unable to find nTDSConnection for (%s) - (%s)" %
@@ -730,59 +891,105 @@ class NTDSConnection(object):
if "options" in msg:
self.options = int(msg["options"][0])
+
if "enabledConnection" in msg:
if msg["enabledConnection"][0].upper().lstrip().rstrip() == "TRUE":
self.enabled = True
+
if "systemFlags" in msg:
- self.flags = int(msg["systemFlags"][0])
- if "transportType" in msg:
- dsdn = dsdb_Dn(samdb, msg["tranportType"][0])
- guid = dsdn.dn.get_extended_component('GUID')
+ self.system_flags = int(msg["systemFlags"][0])
- assert guid is not None
- self.transport_guid = misc.GUID(guid)
+ if "objectGUID" in msg:
+ self.guid = \
+ misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["objectGUID"][0]))
- self.transport_dnstr = str(dsdn.dn)
- assert self.transport_dnstr is not None
+ if "transportType" in msg:
+ dsdn = dsdb_Dn(samdb, msg["tranportType"][0])
+ self.load_connection_transport(str(dsdn.dn))
if "schedule" in msg:
self.schedule = ndr_unpack(drsblobs.replSchedule, msg["schedule"][0])
+ if "whenCreated" in msg:
+ self.whenCreated = ldb.string_to_time(msg["whenCreated"][0])
+
if "fromServer" in msg:
dsdn = dsdb_Dn(samdb, msg["fromServer"][0])
- guid = dsdn.dn.get_extended_component('GUID')
-
- assert guid is not None
- self.from_guid = misc.GUID(guid)
-
self.from_dnstr = str(dsdn.dn)
assert self.from_dnstr is not None
- # Was loaded from database so connection is currently committed
- self.committed = True
+ def load_connection_transport(self, tdnstr):
+ """Given a NTDSConnection object which enumerates a transport
+ DN, load the transport information for the connection object
+
+ :param tdnstr: transport DN to load
+ """
+ attrs = [ "objectGUID" ]
+ try:
+ res = samdb.search(base=tdnstr,
+ scope=ldb.SCOPE_BASE, attrs=attrs)
- def commit_connection(self, samdb):
- """Given a NTDSConnection object that is not committed in the
- sam database, perform a commit action.
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find transport (%s)" %
+ (tdnstr, estr))
+
+ if "objectGUID" in res[0]:
+ self.transport_dnstr = tdnstr
+ self.transport_guid = \
+ misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["objectGUID"][0]))
+ assert self.transport_dnstr is not None
+ assert self.transport_guid is not None
+ return
+
+ def commit_deleted(self, samdb, ro=False):
+ """Local helper routine for commit_connections() which
+ handles committed connections that are to be deleted from
+ the database database
"""
- # nothing to do
- if self.committed == True:
+ assert self.to_be_deleted
+ self.to_be_deleted = False
+
+ # No database modification requested
+ if ro == True:
+ return
+
+ try:
+ samdb.delete(self.dnstr)
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Could not delete nTDSConnection for (%s) - (%s)" % \
+ (self.dnstr, estr))
+
+ return
+
+ def commit_added(self, samdb, ro=False):
+ """Local helper routine for commit_connections() which
+ handles committed connections that are to be added to the
+ database
+ """
+ assert self.to_be_added
+ self.to_be_added = False
+
+ # No database modification requested
+ if ro == True:
return
# First verify we don't have this entry to ensure nothing
# is programatically amiss
+ found = False
try:
msg = samdb.search(base=self.dnstr, scope=ldb.SCOPE_BASE)
- found = True
+ if len(msg) != 0:
+ found = True
except ldb.LdbError, (enum, estr):
- if enum == ldb.ERR_NO_SUCH_OBJECT:
- found = False
- else:
- raise Exception("Unable to search for (%s) - (%s)" %
+ if enum != ldb.ERR_NO_SUCH_OBJECT:
+ raise Exception("Unable to search for (%s) - (%s)" % \
(self.dnstr, estr))
if found:
- raise Exception("nTDSConnection for (%s) already exists!" % self.dnstr)
+ raise Exception("nTDSConnection for (%s) already exists!" % \
+ self.dnstr)
if self.enabled:
enablestr = "TRUE"
@@ -806,7 +1013,13 @@ class NTDSConnection(object):
m["options"] = \
ldb.MessageElement(str(self.options), ldb.FLAG_MOD_ADD, "options")
m["systemFlags"] = \
- ldb.MessageElement(str(self.flags), ldb.FLAG_MOD_ADD, "systemFlags")
+ ldb.MessageElement(str(self.system_flags), ldb.FLAG_MOD_ADD, \
+ "systemFlags")
+
+ if self.transport_dnstr is not None:
+ m["transportType"] = \
+ ldb.MessageElement(str(self.transport_dnstr), ldb.FLAG_MOD_ADD, \
+ "transportType")
if self.schedule is not None:
m["schedule"] = \
@@ -817,11 +1030,97 @@ class NTDSConnection(object):
except ldb.LdbError, (enum, estr):
raise Exception("Could not add nTDSConnection for (%s) - (%s)" % \
(self.dnstr, estr))
- self.committed = True
+ return
+
+ def commit_modified(self, samdb, ro=False):
+ """Local helper routine for commit_connections() which
+ handles committed connections that are to be modified to the
+ database
+ """
+ assert self.to_be_modified
+ self.to_be_modified = False
+
+ # No database modification requested
+ if ro == True:
+ return
+
+ # First verify we have this entry to ensure nothing
+ # is programatically amiss
+ try:
+ msg = samdb.search(base=self.dnstr, scope=ldb.SCOPE_BASE)
+ found = True
+
+ except ldb.LdbError, (enum, estr):
+ if enum == ldb.ERR_NO_SUCH_OBJECT:
+ found = False
+ else:
+ raise Exception("Unable to search for (%s) - (%s)" % \
+ (self.dnstr, estr))
+ if found == False:
+ raise Exception("nTDSConnection for (%s) doesn't exist!" % \
+ self.dnstr)
+
+ if self.enabled:
+ enablestr = "TRUE"
+ else:
+ enablestr = "FALSE"
+
+ # Prepare a message for modifying the samdb
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, self.dnstr)
+
+ m["enabledConnection"] = \
+ ldb.MessageElement(enablestr, ldb.FLAG_MOD_REPLACE, \
+ "enabledConnection")
+ m["fromServer"] = \
+ ldb.MessageElement(self.from_dnstr, ldb.FLAG_MOD_REPLACE, \
+ "fromServer")
+ m["options"] = \
+ ldb.MessageElement(str(self.options), ldb.FLAG_MOD_REPLACE, \
+ "options")
+ m["systemFlags"] = \
+ ldb.MessageElement(str(self.system_flags), ldb.FLAG_MOD_REPLACE, \
+ "systemFlags")
+
+ if self.transport_dnstr is not None:
+ m["transportType"] = \
+ ldb.MessageElement(str(self.transport_dnstr), \
+ ldb.FLAG_MOD_REPLACE, "transportType")
+ else:
+ m["transportType"] = \
+ ldb.MessageElement([], \
+ ldb.FLAG_MOD_DELETE, "transportType")
+
+ if self.schedule is not None:
+ m["schedule"] = \
+ ldb.MessageElement(ndr_pack(self.schedule), \
+ ldb.FLAG_MOD_REPLACE, "schedule")
+ else:
+ m["schedule"] = \
+ ldb.MessageElement([], \
+ ldb.FLAG_MOD_DELETE, "schedule")
+ try:
+ samdb.modify(m)
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Could not modify nTDSConnection for (%s) - (%s)" % \
+ (self.dnstr, estr))
+ return
+
+ def set_modified(self, truefalse):
+ self.to_be_modified = truefalse
+ return
+
+ def set_added(self, truefalse):
+ self.to_be_added = truefalse
+ return
+
+ def set_deleted(self, truefalse):
+ self.to_be_deleted = truefalse
+ return
def is_schedule_minimum_once_per_week(self):
"""Returns True if our schedule includes at least one
- replication interval within the week. False otherwise
+ replication interval within the week. False otherwise
"""
if self.schedule is None or self.schedule.dataArray[0] is None:
return False
@@ -831,19 +1130,52 @@ class NTDSConnection(object):
return True
return False
+ def is_equivalent_schedule(self, sched):
+ """Returns True if our schedule is equivalent to the input
+ comparison schedule.
+
+ :param shed: schedule to compare to
+ """
+ if self.schedule is not None:
+ if sched is None:
+ return False
+ elif sched is None:
+ return True
+
+ if self.schedule.size != sched.size or \
+ self.schedule.bandwidth != sched.bandwidth or \
+ self.schedule.numberOfSchedules != sched.numberOfSchedules:
+ return False
+
+ for i, header in enumerate(self.schedule.headerArray):
+
+ if self.schedule.headerArray[i].type != sched.headerArray[i].type:
+ return False
+
+ if self.schedule.headerArray[i].offset != \
+ sched.headerArray[i].offset:
+ return False
+
+ for a, b in zip(self.schedule.dataArray[i].slots, \
+ sched.dataArray[i].slots):
+ if a != b:
+ return False
+ return True
+
def convert_schedule_to_repltimes(self):
"""Convert NTDS Connection schedule to replTime schedule.
- NTDS Connection schedule slots are double the size of
- the replTime slots but the top portion of the NTDS
- Connection schedule slot (4 most significant bits in
- uchar) are unused. The 4 least significant bits have
- the same (15 minute interval) bit positions as replTimes.
- We thus pack two elements of the NTDS Connection schedule
- slots into one element of the replTimes slot
- If no schedule appears in NTDS Connection then a default
- of 0x11 is set in each re