Ejemplo n.º 1
0
    def run(self,
            H=None,
            partition=None,
            json=False,
            maximum=False,
            median=False,
            full=False,
            sambaopts=None,
            credopts=None,
            versionopts=None,
            quiet=False,
            verbose=False):

        lp = sambaopts.get_loadparm()
        creds = credopts.get_credentials(lp, fallback_machine=True)
        local_kcc, dsas = get_kcc_and_dsas(H, lp, creds)
        samdb = local_kcc.samdb
        short_partitions, _ = get_partition_maps(samdb)
        if partition:
            if partition in short_partitions:
                part_dn = short_partitions[partition]
                # narrow down to specified partition only
                short_partitions = {partition: part_dn}
            else:
                raise CommandError("unknown partition %s" % partition)

        filters = []
        if maximum:
            filters.append('maximum')
        if median:
            filters.append('median')

        partitions_distances = {}
        partitions_summaries = {}
        for part_name, part_dn in short_partitions.items():
            utdv_edges = get_utdv_edges(local_kcc, dsas, part_dn, lp, creds)
            distances = get_utdv_distances(utdv_edges, dsas)
            summary = get_utdv_summary(distances, filters=filters)
            partitions_distances[part_name] = distances
            partitions_summaries[part_name] = summary

        if full:
            # always print json format
            output = self.format_as_json(partitions_distances)
        else:
            if json:
                output = self.format_as_json(partitions_summaries)
            else:
                output = self.format_as_text(partitions_summaries)

        print(output, file=self.outf)
Ejemplo n.º 2
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=False,
            format=None,
            importldif=None,
            xdot=False,
            partition=None,
            max_digits=3):
        if not talk_to_remote:
            print(
                "this won't work without talking to the remote servers "
                "(use -r)",
                file=self.outf)
            return

        # 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 = get_kcc_and_dsas(H, lp, creds)
        self.samdb = local_kcc.samdb
        partition = get_partition(self.samdb, partition)

        short_partitions, long_partitions = get_partition_maps(self.samdb)
        color_scheme = self.calc_distance_color_scheme(color, color_scheme,
                                                       output)

        for part_name, part_dn in short_partitions.items():
            if partition not in (part_dn, None):
                continue  # we aren't doing this partition

            utdv_edges = get_utdv_edges(local_kcc, dsas, part_dn, lp, creds)

            distances = get_utdv_distances(utdv_edges, dsas)

            max_distance = get_utdv_max_distance(distances)

            digits = min(max_digits, len(str(max_distance)))
            if digits < 1:
                digits = 1
            c_scale = 10**digits

            s = full_matrix(distances,
                            utf8=utf8,
                            colour=color_scheme,
                            shorten_names=shorten_names,
                            generate_key=key,
                            grouping_function=get_dnstr_site,
                            colour_scale=c_scale,
                            digits=digits,
                            ylabel='DC',
                            xlabel='out-of-date-ness')

            self.write('\n%s\n\n%s' % (part_name, s), output)
Ejemplo n.º 3
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 = 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(
                    (str(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_dnstrlist_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)
Ejemplo n.º 4
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 = get_kcc_and_dsas(H, lp, creds)
        unix_now = local_kcc.unix_now

        partition = get_partition(local_kcc.samdb, 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 = str(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': []
            }
        }

        short_partitions, long_partitions = get_partition_maps(local_kcc.samdb)

        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)