# vim: set fileencoding=utf-8 : # ***********************IMPORTANT NMAP LICENSE TERMS************************ # * # * The Nmap Security Scanner is (C) 1996-2024 Nmap Software LLC ("The Nmap # * Project"). Nmap is also a registered trademark of the Nmap Project. # * # * This program is distributed under the terms of the Nmap Public Source # * License (NPSL). The exact license text applying to a particular Nmap # * release or source code control revision is contained in the LICENSE # * file distributed with that version of Nmap or source code control # * revision. More Nmap copyright/legal information is available from # * https://nmap.org/book/man-legal.html, and further information on the # * NPSL license itself can be found at https://nmap.org/npsl/ . This # * header summarizes some key points from the Nmap license, but is no # * substitute for the actual license text. # * # * Nmap is generally free for end users to download and use themselves, # * including commercial use. It is available from https://nmap.org. # * # * The Nmap license generally prohibits companies from using and # * redistributing Nmap in commercial products, but we sell a special Nmap # * OEM Edition with a more permissive license and special features for # * this purpose. See https://nmap.org/oem/ # * # * If you have received a written Nmap license agreement or contract # * stating terms other than these (such as an Nmap OEM license), you may # * choose to use and redistribute Nmap under those terms instead. # * # * The official Nmap Windows builds include the Npcap software # * (https://npcap.com) for packet capture and transmission. It is under # * separate license terms which forbid redistribution without special # * permission. So the official Nmap Windows builds may not be redistributed # * without special permission (such as an Nmap OEM license). # * # * Source is provided to this software because we believe users have a # * right to know exactly what a program is going to do before they run it. # * This also allows you to audit the software for security holes. # * # * Source code also allows you to port Nmap to new platforms, fix bugs, and # * add new features. You are highly encouraged to submit your changes as a # * Github PR or by email to the dev@nmap.org mailing list for possible # * incorporation into the main distribution. Unless you specify otherwise, it # * is understood that you are offering us very broad rights to use your # * submissions as described in the Nmap Public Source License Contributor # * Agreement. This is important because we fund the project by selling licenses # * with various terms, and also because the inability to relicense code has # * caused devastating problems for other Free Software projects (such as KDE # * and NASM). # * # * The free version of Nmap is distributed in the hope that it will be # * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties, # * indemnification and commercial support are all available through the # * Npcap OEM program--see https://nmap.org/oem/ # * # ***************************************************************************/ from radialnet.core.Graph import Graph from radialnet.gui.RadialNet import NetNode import math COLORS = [(0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (1.0, 0.0, 0.0)] BASE_RADIUS = 5.5 NONE_RADIUS = 4.5 def set_node_info(node, host): """ """ node.set_host(host) radius = BASE_RADIUS + 2 * math.log( node.get_info("number_of_open_ports") + 1) node.set_draw_info({"color": COLORS[node.get_info("vulnerability_score")], "radius": radius}) class TracerouteHostInfo(object): """This is a minimal implementation of HostInfo, sufficient to represent the information in an intermediate traceroute hop.""" def __init__(self): self.ip = None self.ipv6 = None self.mac = None self.hostname = None self.ports = [] self.extraports = [] self.osmatches = [] def get_hostname(self): return self.hostname def get_best_osmatch(self): if not self.osmatches: return None def osmatch_key(osmatch): try: return -float(osmatch["accuracy"]) except ValueError: return 0 return sorted(self.osmatches, key=osmatch_key)[0] hostnames = property(lambda self: self.hostname and [self.hostname] or []) def find_hop_by_ttl(hops, ttl): assert ttl >= 0, "ttl must be non-negative" if ttl == 0: # Same machine (i.e. localhost) return {"ipaddr": "127.0.0.1/8"} for h in hops: if ttl == int(h["ttl"]): return h return None def make_graph_from_hosts(hosts): #hosts = parser.get_root().search_children('host', deep=True) graph = Graph() nodes = list() node_cache = {} ancestor_node_cache = {} descendant_node_cache = {} # Setting initial reference host main_node = NetNode() nodes.append(main_node) localhost = TracerouteHostInfo() localhost.ip = {"addr": "127.0.0.1/8", "type": "ipv4"} localhost.hostname = "localhost" main_node.set_host(localhost) main_node.set_draw_info( {"valid": True, "color": (0, 0, 0), "radius": NONE_RADIUS}) #Save endpoints for attaching scanned hosts to endpoints = {} # For each host in hosts just mount the graph for host in hosts: trace = host.trace endpoints[host] = nodes[0] hops = trace.get("hops") # If host has traceroute information mount graph if hops is not None and len(hops) > 0: prev_node = nodes[0] hops = trace.get("hops", []) ttls = [int(hop["ttl"]) for hop in hops] # Getting nodes of host by ttl for ttl in range(1, max(ttls) + 1): if ttl in ttls: hop = find_hop_by_ttl(hops, ttl) node = node_cache.get(hop["ipaddr"]) if node is None: node = NetNode() nodes.append(node) hop_host = TracerouteHostInfo() hop_host.ip = { "addr": hop["ipaddr"], "type": "", "vendor": "" } node.set_draw_info({"valid": True}) node.set_draw_info({"color": (1, 1, 1), "radius": NONE_RADIUS}) if hop["host"] != "": hop_host.hostname = hop["host"] node.set_host(hop_host) node_cache[node.get_info("ip")] = node rtt = hop["rtt"] if rtt != "--": graph.set_connection(node, prev_node, float(rtt)) else: graph.set_connection(node, prev_node) else: # Add an "anonymous" node only if there isn't already a # node equivalent to it (i.e. at same distance from the # previous "real" node) pre_hop = None pre_hop_distance = 0 for i in range(1, ttl + 1): pre_hop = find_hop_by_ttl(hops, ttl - i) if pre_hop is not None: pre_hop_distance = i break post_hop = None post_hop_distance = 0 for i in range(1, max(ttls) - ttl): post_hop = find_hop_by_ttl(hops, ttl + i) if post_hop is not None: post_hop_distance = i break assert pre_hop is not None, \ "pre_hop should have become localhost if nothing else" # noqa ancestor_key = (pre_hop["ipaddr"], pre_hop_distance) descendant_key = None if post_hop is not None: descendant_key = \ (post_hop["ipaddr"], post_hop_distance) if ancestor_key in ancestor_node_cache: node = ancestor_node_cache[ancestor_key] elif (descendant_key is not None and descendant_key in descendant_node_cache): node = descendant_node_cache[descendant_key] graph.set_connection(node, prev_node) else: node = NetNode() nodes.append(node) node.set_draw_info({"valid": False}) node.set_draw_info( {"color": (1, 1, 1), "radius": NONE_RADIUS}) graph.set_connection(node, prev_node) ancestor_node_cache[ancestor_key] = node if descendant_key is not None: descendant_node_cache[descendant_key] = node prev_node = node endpoints[host] = node # For each fully scanned host for host in hosts: ip = host.ip if ip is None: ip = host.ipv6 node = node_cache.get(ip["addr"]) if node is None: node = NetNode() nodes.append(node) node.set_draw_info({"no_route": True}) graph.set_connection(node, endpoints[host]) node.set_draw_info({"valid": True}) node.set_draw_info({"scanned": True}) set_node_info(node, host) node_cache[node.get_info("ip")] = node graph.set_nodes(nodes) graph.set_main_node(main_node) return graph def make_graph_from_nmap_parser(parser): return make_graph_from_hosts( parser.get_root().search_children('host', deep=True))