def get_kcc_and_dsas(self, H, lp, creds): """Get a readonly KCC object and the list of DSAs it knows about.""" unix_now = int(time.time()) kcc = KCC(unix_now, readonly=True) kcc.load_samdb(H, lp, creds) dsa_list = kcc.list_dsas() dsas = set(dsa_list) if len(dsas) != len(dsa_list): print("There seem to be duplicate dsas", file=sys.stderr) return kcc, dsas
def run(self, H=None, output=None, shorten_names=False, key=True, talk_to_remote=False, sambaopts=None, credopts=None, versionopts=None, mode='self', partition=None, color=None, color_scheme=None, utf8=None, format=None, xdot=False): # We use the KCC libraries in readonly mode to get the # replication graph. lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) local_kcc, dsas = self.get_kcc_and_dsas(H, lp, creds) unix_now = local_kcc.unix_now # Allow people to say "--partition=DOMAIN" rather than # "--partition=DC=blah,DC=..." short_partitions, long_partitions = get_partition_maps(local_kcc.samdb) if partition is not None: partition = short_partitions.get(partition.upper(), partition) if partition not in long_partitions: raise CommandError("unknown partition %s" % partition) # nc_reps is an autovivifying dictionary of dictionaries of lists. # nc_reps[partition]['current' | 'needed'] is a list of # (dsa dn string, repsFromTo object) pairs. nc_reps = defaultdict(lambda: defaultdict(list)) guid_to_dnstr = {} # We run a new KCC for each DSA even if we aren't talking to # the remote, because after kcc.run (or kcc.list_dsas) the kcc # ends up in a messy state. for dsa_dn in dsas: kcc = KCC(unix_now, readonly=True) if talk_to_remote: res = local_kcc.samdb.search(dsa_dn, scope=SCOPE_BASE, attrs=["dNSHostName"]) dns_name = res[0]["dNSHostName"][0] print("Attempting to contact ldap://%s (%s)" % (dns_name, dsa_dn), file=sys.stderr) try: kcc.load_samdb("ldap://%s" % dns_name, lp, creds) except KCCError as e: print("Could not contact ldap://%s (%s)" % (dns_name, e), file=sys.stderr) continue kcc.run(H, lp, creds) else: kcc.load_samdb(H, lp, creds) kcc.run(H, lp, creds, forced_local_dsa=dsa_dn) dsas_from_here = set(kcc.list_dsas()) if dsas != dsas_from_here: print("found extra DSAs:", file=sys.stderr) for dsa in (dsas_from_here - dsas): print(" %s" % dsa, file=sys.stderr) print("missing DSAs (known locally, not by %s):" % dsa_dn, file=sys.stderr) for dsa in (dsas - dsas_from_here): print(" %s" % dsa, file=sys.stderr) for remote_dn in dsas_from_here: if mode == 'others' and remote_dn == dsa_dn: continue elif mode == 'self' and remote_dn != dsa_dn: continue remote_dsa = kcc.get_dsa('CN=NTDS Settings,' + remote_dn) kcc.translate_ntdsconn(remote_dsa) guid_to_dnstr[str(remote_dsa.dsa_guid)] = remote_dn # get_reps_tables() returns two dictionaries mapping # dns to NCReplica objects c, n = remote_dsa.get_rep_tables() for part, rep in c.items(): if partition is None or part == partition: nc_reps[part]['current'].append((dsa_dn, rep)) for part, rep in n.items(): if partition is None or part == partition: nc_reps[part]['needed'].append((dsa_dn, rep)) all_edges = {'needed': {'to': [], 'from': []}, 'current': {'to': [], 'from': []}} for partname, part in nc_reps.items(): for state, edgelists in all_edges.items(): for dsa_dn, rep in part[state]: short_name = long_partitions.get(partname, partname) for r in rep.rep_repsFrom: edgelists['from'].append( (dsa_dn, guid_to_dnstr[str(r.source_dsa_obj_guid)], short_name)) for r in rep.rep_repsTo: edgelists['to'].append( (guid_to_dnstr[str(r.source_dsa_obj_guid)], dsa_dn, short_name)) # Here we have the set of edges. From now it is a matter of # interpretation and presentation. if self.calc_output_format(format, output) == 'distance': color_scheme = self.calc_distance_color_scheme(color, color_scheme, output) header_strings = { 'from': "RepsFrom objects for %s", 'to': "RepsTo objects for %s", } for state, edgelists in all_edges.items(): for direction, items in edgelists.items(): part_edges = defaultdict(list) for src, dest, part in items: part_edges[part].append((src, dest)) for part, edges in part_edges.items(): s = distance_matrix(None, edges, utf8=utf8, colour=color_scheme, shorten_names=shorten_names, generate_key=key, grouping_function=get_dnstr_site) s = "\n%s\n%s" % (header_strings[direction] % part, s) self.write(s, output) return edge_colours = [] edge_styles = [] dot_edges = [] dot_vertices = set() used_colours = {} key_set = set() for state, edgelist in all_edges.items(): for direction, items in edgelist.items(): for src, dest, part in items: colour = used_colours.setdefault((part), colour_hash((part, direction))) linestyle = 'dotted' if state == 'needed' else 'solid' arrow = 'open' if direction == 'to' else 'empty' dot_vertices.add(src) dot_vertices.add(dest) dot_edges.append((src, dest)) edge_colours.append(colour) style = 'style="%s"; arrowhead=%s' % (linestyle, arrow) edge_styles.append(style) key_set.add((part, 'reps' + direction.title(), colour, style)) key_items = [] if key: for part, direction, colour, linestyle in sorted(key_set): key_items.append((False, 'color="%s"; %s' % (colour, linestyle), "%s %s" % (part, direction))) key_items.append((False, 'style="dotted"; arrowhead="open"', "repsFromTo is needed")) key_items.append((False, 'style="solid"; arrowhead="open"', "repsFromTo currently exists")) s = dot_graph(dot_vertices, dot_edges, directed=True, edge_colors=edge_colours, edge_styles=edge_styles, shorten_names=shorten_names, key_items=key_items) if format == 'xdot': self.call_xdot(s, output) else: self.write(s, output)