|
@@ -1,116 +1,192 @@
|
|
|
|
|
|
import json
|
|
|
import sys
|
|
|
+from enum import Enum
|
|
|
|
|
|
+DEBUG = False
|
|
|
+full_output = False
|
|
|
+hosts = []
|
|
|
+
|
|
|
+
|
|
|
class Port:
|
|
|
- port = 0
|
|
|
+ port_number = 0
|
|
|
state = ""
|
|
|
- alias = ""
|
|
|
- version = ""
|
|
|
protocol = ""
|
|
|
+ owner = ""
|
|
|
+ service = ""
|
|
|
+ sunRPCinfo = ""
|
|
|
+ version = ""
|
|
|
|
|
|
def __str__(self):
|
|
|
- return f"Port {self.port} - Version: {self.version}"
|
|
|
-
|
|
|
- def todict(self):
|
|
|
+ return f"Port {self.port_number} - Version: {self.version}"
|
|
|
+
|
|
|
+ def todict(self, full=False):
|
|
|
+ if full:
|
|
|
+ return {
|
|
|
+ "port":self.port_number,
|
|
|
+ "state":self.state,
|
|
|
+ "protocol":self.protocol,
|
|
|
+ "owner":self.owner,
|
|
|
+ "service":self.service,
|
|
|
+ "RPCInfo":self.sunRPCinfo,
|
|
|
+ "version":self.version,
|
|
|
+ }
|
|
|
return {
|
|
|
- "port":self.port,
|
|
|
- "alias":self.alias,
|
|
|
- "version":self.version,
|
|
|
+ "port":self.port_number,
|
|
|
"protocol":self.protocol,
|
|
|
+ "service":self.service,
|
|
|
+ "version":self.version,
|
|
|
}
|
|
|
|
|
|
|
|
|
+
|
|
|
+class Status(Enum):
|
|
|
+ Up = 1
|
|
|
+ Down = 2
|
|
|
+ Unknown = 3
|
|
|
+
|
|
|
+ def from_string(status):
|
|
|
+ if status.lower() == "up":
|
|
|
+ return Status.Up
|
|
|
+ if status.lower() == "down":
|
|
|
+ return Status.Down
|
|
|
+ if status.lower() == "unknown":
|
|
|
+ return Status.Unknown
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
class Host:
|
|
|
ip = ""
|
|
|
+ status = None
|
|
|
hostname = ""
|
|
|
ports = []
|
|
|
|
|
|
- def __str__(self):
|
|
|
- return f"{self.ip} [{self.hostname}]"
|
|
|
-
|
|
|
- def todict(self):
|
|
|
+ def todict(self, full):
|
|
|
return {
|
|
|
"ip":self.ip,
|
|
|
"hostname":self.hostname,
|
|
|
- "ports":[port.todict() for port in self.ports]
|
|
|
+ "ports":[port.todict(full) for port in self.ports],
|
|
|
+ "status":str(self.status.value)
|
|
|
}
|
|
|
|
|
|
|
|
|
def open_gnmap(filename):
|
|
|
with open(filename, "r") as gnmap:
|
|
|
+
|
|
|
lines = gnmap.read().strip().split("\n")
|
|
|
lines = [l for l in lines if not l.startswith("#") and l.startswith("Host:")]
|
|
|
+
|
|
|
return lines
|
|
|
|
|
|
|
|
|
-
|
|
|
-def parse_ports(portline):
|
|
|
- portlist = portline.split("/,")
|
|
|
- outports = []
|
|
|
+
|
|
|
+def parse_line(line):
|
|
|
+ global hosts
|
|
|
+
|
|
|
+
|
|
|
+ fields = line.split("\t")
|
|
|
+
|
|
|
+
|
|
|
+ out_host = parse_fields(fields)
|
|
|
+ merge_hosts(out_host)
|
|
|
+
|
|
|
+
|
|
|
+def parse_host_field(field):
|
|
|
+
|
|
|
+ host = Host()
|
|
|
+ subfields = field.split(" (")
|
|
|
+ host.ip = subfields[0]
|
|
|
+ host.hostname =subfields[1].replace(")", "")
|
|
|
+ if DEBUG: print(f"DEBUG: parse_host_field::ip={host.ip};hostname={host.hostname}")
|
|
|
+ return host
|
|
|
+
|
|
|
+def parse_ports_field(host, field):
|
|
|
+ portlist = field.split("/,")
|
|
|
+
|
|
|
+
|
|
|
for p in portlist:
|
|
|
port = Port()
|
|
|
portparts = p.split("/")
|
|
|
- port.port = portparts[0]
|
|
|
+ port.port_number = portparts[0]
|
|
|
port.state = portparts[1]
|
|
|
port.protocol = portparts[2]
|
|
|
- port.alias = portparts[4]
|
|
|
+ port.owner = portparts[3]
|
|
|
+ port.service = portparts[4]
|
|
|
+ port.sunRPCinfo = portparts[5]
|
|
|
port.version = portparts[6]
|
|
|
-
|
|
|
-
|
|
|
- outports.append(port)
|
|
|
- return outports
|
|
|
+ host.ports.append(port)
|
|
|
+ return host
|
|
|
|
|
|
+def parse_status_field(host, field):
|
|
|
+ host.status = Status.from_string(field)
|
|
|
+ return host
|
|
|
|
|
|
-
|
|
|
-all_hosts = []
|
|
|
-unique = []
|
|
|
-def parse_line(line):
|
|
|
|
|
|
- host = Host()
|
|
|
- line = line.strip().replace(" ", ";").replace("\t", ";").replace(" ", "")
|
|
|
+
|
|
|
+field_parser = {
|
|
|
+ "Host": parse_host_field,
|
|
|
+ "Ports": parse_ports_field,
|
|
|
+ "Status": parse_status_field,
|
|
|
+}
|
|
|
|
|
|
-
|
|
|
- parts = line.split(";")
|
|
|
|
|
|
-
|
|
|
- if parts[3] == "Status:":
|
|
|
- return
|
|
|
+
|
|
|
+def not_implemented(host, field_type):
|
|
|
+ return host
|
|
|
+
|
|
|
+
|
|
|
+def merge_hosts(host):
|
|
|
+ global hosts
|
|
|
+
|
|
|
+ for ih in hosts:
|
|
|
+ if ih.ip == host.ip:
|
|
|
+ ih.ports = host.ports
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ hosts.append(host)
|
|
|
|
|
|
-
|
|
|
- ip = parts[1]
|
|
|
- if ip in unique:
|
|
|
- return
|
|
|
+
|
|
|
+def parse_fields(fields):
|
|
|
|
|
|
-
|
|
|
- hostname = parts[2] if parts[2] != "()" else ""
|
|
|
- hostname = hostname.replace("(", "").replace(")", "")
|
|
|
+
|
|
|
+ host = Host()
|
|
|
|
|
|
- ports = "".join(parts[4:])
|
|
|
- portlist = parse_ports(ports)
|
|
|
+
|
|
|
+ for field in fields:
|
|
|
+ field_type, field_value = field.split(": ")
|
|
|
|
|
|
-
|
|
|
- host.ip = ip
|
|
|
- host.hostname = hostname
|
|
|
- host.ports = portlist
|
|
|
- unique.append(ip)
|
|
|
+ if DEBUG: print(f"DEBUG: field_type={field_type}")
|
|
|
+ if DEBUG: print(f"DEBUG: field_content={field_value}")
|
|
|
+
|
|
|
+ if field_type == "Host":
|
|
|
+ host = parse_host_field(field_value)
|
|
|
+ else:
|
|
|
+ host = field_parser.get(field_type, not_implemented)(host, field_value)
|
|
|
|
|
|
-
|
|
|
return host
|
|
|
|
|
|
|
|
|
+def hosts_to_json(full_output):
|
|
|
+ global hosts
|
|
|
+ out = []
|
|
|
+ for host in hosts:
|
|
|
+ out.append(host.todict(full_output))
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
|
|
|
if len(sys.argv) == 1:
|
|
|
print(f"usage: {sys.argv[0]} <scan.gnmap>")
|
|
|
exit(1)
|
|
|
+elif len(sys.argv) == 3 and sys.argv[2] == "--full":
|
|
|
+ full_output = True
|
|
|
|
|
|
-
|
|
|
+
|
|
|
lines = open_gnmap(sys.argv[1])
|
|
|
|
|
|
|
|
|
for line in lines:
|
|
|
- outhost = parse_line(line)
|
|
|
- if outhost != None:
|
|
|
- all_hosts.append(outhost.todict())
|
|
|
+ parse_line(line)
|
|
|
+
|
|
|
|
|
|
-print(json.dumps(all_hosts))
|
|
|
+print(json.dumps(hosts_to_json(full_output)))
|