Beispiel #1
0
    def test_basic_dot_files(self):
        vertices = tuple('abcdefgh')
        all_edges = tuple(itertools.combinations(vertices, 2))
        line_edges = list(zip(vertices[1:], vertices[:-1]))
        ring_edges = line_edges + [(vertices[0], vertices[-1])]
        no_edges = []
        # even join to even numbers, odd to odd
        disjoint_edges = [(a, b) for a, b in all_edges if
                          ord(a) ^ ord(b) == 0]

        for name, edges in (('all', all_edges),
                            ('line', line_edges),
                            ('ring', ring_edges),
                            ('no', no_edges),
                            ('disjoint', disjoint_edges)):

            for directed, tag in ((True, "directed"),
                                  (False, "undirected")):
                title = "%s %s" % (name, tag)

                g = graph.dot_graph(vertices, edges,
                                    directed=directed,
                                    title=title)
                lines = g.split('\n')
                self.assertHeader(lines, title, directed)
                self.assertVertices(lines[7:], vertices)
                self.assertEdges(lines[len(vertices) + 7:], edges, directed)
Beispiel #2
0
    def test_basic_dot_files(self):
        vertices = tuple('abcdefgh')
        all_edges = tuple(itertools.combinations(vertices, 2))
        line_edges = zip(vertices[1:], vertices[:-1])
        ring_edges = line_edges + [(vertices[0], vertices[-1])]
        no_edges = []
        # even join to even numbers, odd to odd
        disjoint_edges = [(a, b) for a, b in all_edges if ord(a) ^ ord(b) == 0]

        for name, edges in (('all', all_edges), ('line', line_edges),
                            ('ring', ring_edges), ('no', no_edges),
                            ('disjoint', disjoint_edges)):

            for directed, tag in ((True, "directed"), (False, "undirected")):
                title = "%s %s" % (name, tag)

                g = graph.dot_graph(vertices,
                                    edges,
                                    directed=directed,
                                    title=title)
                print(g)
                lines = g.split('\n')
                self.assertHeader(lines, title, directed)
                self.assertVertices(lines[7:], vertices)
                self.assertEdges(lines[len(vertices) + 7:], edges, directed)
Beispiel #3
0
def write_dot_file(basename, edge_list, vertices=None, label=None,
                   dot_file_dir=None, debug=None, **kwargs):
    s = dot_graph(vertices, edge_list, title=label, **kwargs)
    if label:
        # sanitise DN and guid labels
        basename += '_' + label.translate(None, ', ')

    filename = os.path.join(dot_file_dir, "%s.dot" % basename)
    if debug is not None:
        debug("writing graph to %s" % filename)
    f = open(filename, 'w')
    f.write(s)
    f.close()
Beispiel #4
0
def write_dot_file(basename, edge_list, vertices=None, label=None,
                   dot_file_dir=None, debug=None, **kwargs):
    s = dot_graph(vertices, edge_list, title=label, **kwargs)
    if label:
        # sanitise DN and guid labels
        basename += '_' + label.replace(', ', '')

    filename = os.path.join(dot_file_dir, "%s.dot" % basename)
    if debug is not None:
        debug("writing graph to %s" % filename)
    f = open(filename, 'w')
    f.write(s)
    f.close()
Beispiel #5
0
    def run(self, H=None, output=None, shorten_names=False,
            key=True, talk_to_remote=False,
            sambaopts=None, credopts=None, versionopts=None,
            color=None, color_scheme=None,
            utf8=None, format=None, importldif=None,
            xdot=False):

        lp = sambaopts.get_loadparm()
        if importldif is None:
            creds = credopts.get_credentials(lp, fallback_machine=True)
        else:
            creds = None
            H = self.import_ldif_db(importldif, lp)

        local_kcc, dsas = self.get_kcc_and_dsas(H, lp, creds)
        local_dsa_dn = local_kcc.my_dsa_dnstr.split(',', 1)[1]
        vertices = set()
        attested_edges = []
        for dsa_dn in dsas:
            if talk_to_remote:
                res = local_kcc.samdb.search(dsa_dn,
                                             scope=SCOPE_BASE,
                                             attrs=["dNSHostName"])
                dns_name = res[0]["dNSHostName"][0]
                try:
                    samdb = self.get_db("ldap://%s" % dns_name, sambaopts,
                                        credopts)
                except LdbError as e:
                    print("Could not contact ldap://%s (%s)" % (dns_name, e),
                          file=sys.stderr)
                    continue

                ntds_dn = samdb.get_dsServiceName()
                dn = samdb.domain_dn()
            else:
                samdb = self.get_db(H, sambaopts, credopts)
                ntds_dn = 'CN=NTDS Settings,' + dsa_dn
                dn = dsa_dn

            res = samdb.search(ntds_dn,
                               scope=SCOPE_BASE,
                               attrs=["msDS-isRODC"])

            is_rodc = res[0]["msDS-isRODC"][0] == 'TRUE'

            vertices.add((ntds_dn, 'RODC' if is_rodc else ''))
            # XXX we could also look at schedule
            res = samdb.search(dn,
                               scope=SCOPE_SUBTREE,
                               expression="(objectClass=nTDSConnection)",
                               attrs=['fromServer'],
                               # XXX can't be critical for ldif test
                               #controls=["search_options:1:2"],
                               controls=["search_options:0:2"],
            )

            for msg in res:
                msgdn = str(msg.dn)
                dest_dn = msgdn[msgdn.index(',') + 1:]
                attested_edges.append((msg['fromServer'][0],
                                       dest_dn, ntds_dn))

        if importldif and H == self._tmp_fn_to_delete:
            os.remove(H)
            os.rmdir(os.path.dirname(H))

        # now we overlay all the graphs and generate styles accordingly
        edges = {}
        for src, dest, attester in attested_edges:
            k = (src, dest)
            if k in edges:
                e = edges[k]
            else:
                e = NTDSConn(*k)
                edges[k] = e
            e.attest(attester)

        vertices, rodc_status = zip(*sorted(vertices))

        if self.calc_output_format(format, output) == 'distance':
            color_scheme = self.calc_distance_color_scheme(color,
                                                           color_scheme,
                                                           output)
            colours = COLOUR_SETS[color_scheme]
            c_header = colours.get('header', '')
            c_reset = colours.get('reset', '')

            epilog = []
            if 'RODC' in rodc_status:
                epilog.append('No outbound connections are expected from RODCs')

            if not talk_to_remote:
                # If we are not talking to remote servers, we list all
                # the connections.
                graph_edges = edges.keys()
                title = 'NTDS Connections known to %s' % local_dsa_dn

            else:
                # If we are talking to the remotes, there are
                # interesting cases we can discover. What matters most
                # is that the destination (i.e. owner) knowns about
                # the connection, but it would be worth noting if the
                # source doesn't. Another strange situation could be
                # when a DC thinks there is a connection elsewhere,
                # but the computers allegedly involved don't believe
                # it exists.
                #
                # With limited bandwidth in the table, we mark the
                # edges known to the destination, and note the other
                # cases in a list after the diagram.
                graph_edges = []
                source_denies = []
                dest_denies = []
                both_deny = []
                for e, conn in edges.items():
                    if conn.dest_attests:
                        graph_edges.append(e)
                        if not conn.src_attests:
                            source_denies.append(e)
                    elif conn.src_attests:
                        dest_denies.append(e)
                    else:
                        both_deny.append(e)

                title = 'NTDS Connections known to each destination DC'

                if both_deny:
                    epilog.append('The following connections are alleged by '
                                  'DCs other than the source and '
                                  'destination:\n')
                    for e in both_deny:
                        epilog.append('  %s -> %s\n' % e)
                if dest_denies:
                    epilog.append('The following connections are alleged by '
                                  'DCs other than the destination but '
                                  'including the source:\n')
                    for e in dest_denies:
                        epilog.append('  %s -> %s\n' % e)
                if source_denies:
                    epilog.append('The following connections '
                                  '(included in the chart) '
                                  'are not known to the source DC:\n')
                    for e in source_denies:
                        epilog.append('  %s -> %s\n' % e)


            s = distance_matrix(vertices, graph_edges,
                                utf8=utf8,
                                colour=color_scheme,
                                shorten_names=shorten_names,
                                generate_key=key,
                                grouping_function=get_dnstr_site,
                                row_comments=rodc_status)

            epilog = ''.join(epilog)
            if epilog:
                epilog = '\n%sNOTES%s\n%s' % (c_header,
                                              c_reset,
                                              epilog)

            self.write('\n%s\n\n%s\n%s' % (title,
                                           s,
                                           epilog), output)
            return

        dot_edges = []
        edge_colours = []
        edge_styles = []
        edge_labels = []
        n_servers = len(dsas)
        for k, e in sorted(edges.items()):
            dot_edges.append(k)
            if e.observations == n_servers or not talk_to_remote:
                edge_colours.append('#000000')
                edge_styles.append('')
            elif e.dest_attests:
                edge_styles.append('')
                if e.src_attests:
                    edge_colours.append('#0000ff')
                else:
                    edge_colours.append('#cc00ff')
            elif e.src_attests:
                edge_colours.append('#ff0000')
                edge_styles.append('style=dashed')
            else:
                edge_colours.append('#ff0000')
                edge_styles.append('style=dotted')

        key_items = []
        if key:
            key_items.append((False,
                              'color="#000000"',
                              "NTDS Connection"))
            for colour, desc in (('#0000ff', "missing from some DCs"),
                                 ('#cc00ff', "missing from source DC")):
                if colour in edge_colours:
                    key_items.append((False, 'color="%s"' % colour, desc))

            for style, desc in (('style=dashed', "unknown to destination"),
                                ('style=dotted',
                                 "unknown to source and destination")):
                if style in edge_styles:
                    key_items.append((False,
                                      'color="#ff0000; %s"' % style,
                                      desc))

        if talk_to_remote:
            title = 'NTDS Connections'
        else:
            title = 'NTDS Connections known to %s' % local_dsa_dn

        s = dot_graph(sorted(vertices), dot_edges,
                      directed=True,
                      title=title,
                      edge_colors=edge_colours,
                      edge_labels=edge_labels,
                      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)
Beispiel #6
0
    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)