samba_kcc: respect kcc.read_only flag on RODC
[samba.git] / python / samba / kcc / __init__.py
index 5918bf29599234b7e2aa0cfb711c90f2c78af8bb..5df3e788227d7a9f577393f2a6d05aff6ed9d203 100644 (file)
@@ -94,7 +94,7 @@ class KCC(object):
     Service can then utilize to replicate naming contexts
 
     :param unix_now: The putative current time in seconds since 1970.
-    :param read_only: Don't write to the database.
+    :param readonly: Don't write to the database.
     :param verify: Check topological invariants for the generated graphs
     :param debug: Write verbosely to stderr.
     "param dot_file_dir: write diagnostic Graphviz files in this directory
@@ -524,7 +524,7 @@ class KCC(object):
         :return: None
         """
         # TODO Figure out how best to handle the RODC case
-        # The RODC is ITSG, but shouldn't act on anyone's behalf.
+        # The RODC is ISTG, but shouldn't act on anyone's behalf.
         if self.my_dsa.is_ro():
             return
 
@@ -541,8 +541,9 @@ class KCC(object):
                 if s_dnstr not in local_dsas:
                     from_dsa = self.get_dsa(s_dnstr)
                     # Samba ONLY: ISTG removes connections to dead DCs
-                    if from_dsa is None and '\\0ADEL' in s_dnstr:
-                        logger.info("DSA appears deleted, removing connection %s" % s_dnstr)
+                    if from_dsa is None or '\\0ADEL' in s_dnstr:
+                        logger.info("DSA appears deleted, removing connection %s"
+                                    % s_dnstr)
                         cn.to_be_deleted = True
                         continue
                     connections_and_dsas.append((cn, dsa, from_dsa))
@@ -909,11 +910,10 @@ class KCC(object):
                                      drsuapi.DRSUAPI_DRS_PER_SYNC |
                                      drsuapi.DRSUAPI_DRS_ADD_REF |
                                      drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
-                                     drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP |
                                      drsuapi.DRSUAPI_DRS_NONGC_RO_REP)
                     if t_repsFrom.replica_flags != replica_flags:
                         t_repsFrom.replica_flags = replica_flags
-                c_rep.commit_repsFrom(self.samdb)
+                c_rep.commit_repsFrom(self.samdb, ro=self.readonly)
             else:
                 if dnstr not in needed_rep_table:
                     delete_reps.add(dnstr)
@@ -927,6 +927,8 @@ class KCC(object):
             for dnstr in delete_reps:
                 del current_rep_table[dnstr]
 
+        # HANDLE REPS-FROM
+        #
         # Now perform the scan of replicas we'll need
         # and compare any current repsFrom against the
         # connections
@@ -1039,6 +1041,74 @@ class KCC(object):
                 # Commit any modified repsFrom to the NC replica
                 n_rep.commit_repsFrom(self.samdb)
 
+        # HANDLE REPS-TO:
+        #
+        # Now perform the scan of replicas we'll need
+        # and compare any current repsTo against the
+        # connections
+
+        # RODC should never push to anybody (should we check this?)
+        if ro:
+            return
+
+        for n_rep in needed_rep_table.values():
+
+            # load any repsTo and fsmo roles as we'll
+            # need them during connection translation
+            n_rep.load_repsTo(self.samdb)
+
+            # Loop thru the existing repsTo tuples (if any)
+            # XXX This is a list and could contain duplicates
+            #     (multiple load_repsTo calls)
+            for t_repsTo in n_rep.rep_repsTo:
+
+                # for each tuple t in n!repsTo, let s be the nTDSDSA
+                # object such that s!objectGUID = t.uuidDsa
+                guidstr = str(t_repsTo.source_dsa_obj_guid)
+                s_dsa = self.get_dsa_by_guidstr(guidstr)
+
+                # Source dsa is gone from config (strange)
+                # so cleanup stale repsTo for unlisted DSA
+                if s_dsa is None:
+                    logger.warning("repsTo source DSA guid (%s) not found" %
+                                   guidstr)
+                    t_repsTo.to_be_deleted = True
+                    continue
+
+                # Find the connection that this repsTo would use. If
+                # there isn't a good one (i.e. non-RODC_TOPOLOGY,
+                # meaning non-FRS), we delete the repsTo.
+                s_dnstr = s_dsa.dsa_dnstr
+                if '\\0ADEL' in s_dnstr:
+                    logger.warning("repsTo source DSA guid (%s) appears deleted" %
+                                   guidstr)
+                    t_repsTo.to_be_deleted = True
+                    continue
+
+                connections = s_dsa.get_connection_by_from_dnstr(self.my_dsa_dnstr)
+                if len(connections) > 0:
+                    # Then this repsTo is tentatively valid
+                    continue
+                else:
+                    # There is no plausible connection for this repsTo
+                    t_repsTo.to_be_deleted = True
+
+            if self.readonly:
+                # Display any to be deleted or modified repsTo
+                text = n_rep.dumpstr_reps_to()
+                if text:
+                    logger.info("REMOVING REPS-TO:\n%s" % text)
+
+                # Peform deletion from our tables but perform
+                # no database modification
+                n_rep.commit_repsTo(self.samdb, ro=True)
+            else:
+                # Commit any modified repsTo to the NC replica
+                n_rep.commit_repsTo(self.samdb)
+
+        # TODO Remove any duplicate repsTo values. This should never happen in
+        # any normal situations.
+
     def merge_failed_links(self, ping=None):
         """Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
 
@@ -1945,7 +2015,7 @@ class KCC(object):
 
         if not needed:
             debug.DEBUG_RED("%s lacks 'should be present' status, "
-                            "aborting construct_intersite_graph!" %
+                            "aborting construct_intrasite_graph!" %
                             nc_x.nc_dnstr)
             return
 
@@ -2237,7 +2307,7 @@ class KCC(object):
                 while candidates and not tnode.has_sufficient_edges():
                     other = random.choice(candidates)
                     DEBUG("trying to add candidate %s" % other.dsa_dnstr)
-                    if not tnode.add_edge_from(other):
+                    if not tnode.add_edge_from(other.dsa_dnstr):
                         debug.DEBUG_RED("could not add %s" % other.dsa_dnstr)
                     candidates.remove(other)
             else: