lanscape 1.2.8a2__py3-none-any.whl → 1.2.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lanscape might be problematic. Click here for more details.

@@ -1,6 +1,7 @@
1
1
  from pathlib import Path
2
- import importlib.util
2
+ import json
3
3
  import sys
4
+ import re
4
5
 
5
6
  class ResourceManager:
6
7
  """
@@ -17,6 +18,16 @@ class ResourceManager:
17
18
  with open(self.asset_dir / asset_name, 'r') as f:
18
19
  return f.read()
19
20
 
21
+ def get_json(self, asset_name: str):
22
+ return json.loads(self.get(asset_name))
23
+
24
+ def get_jsonc(self, asset_name: str):
25
+ " Get JSON content with comments removed "
26
+ content = self.get(asset_name)
27
+ cleaned_content = re.sub(r'//.*', '', content)
28
+ return json.loads(cleaned_content)
29
+
30
+
20
31
  def update(self, asset_name: str, content: str):
21
32
  with open(self.asset_dir / asset_name, 'w') as f:
22
33
  f.write(content)
@@ -28,7 +28,7 @@ def parse_ip_input(ip_input):
28
28
 
29
29
  # If no CIDR or range, assume a single IP
30
30
  else:
31
- ip_ranges.append(ipaddress.IPv4Address(entry,strict=False))
31
+ ip_ranges.append(ipaddress.IPv4Address(entry))
32
32
  if len(ip_ranges) > MAX_IPS_ALLOWED:
33
33
  raise SubnetTooLargeError(ip_input)
34
34
  return ip_ranges
@@ -1,5 +1,4 @@
1
1
  import re
2
- import json
3
2
  import logging
4
3
  import platform
5
4
  import subprocess
@@ -7,7 +6,7 @@ from typing import List
7
6
 
8
7
  from .app_scope import ResourceManager
9
8
 
10
- DB = json.loads(ResourceManager('mac_addresses').get('mac_db.json'))
9
+ DB = ResourceManager('mac_addresses').get_json('mac_db.json')
11
10
 
12
11
  log = logging.getLogger('MacLookup')
13
12
 
@@ -8,9 +8,10 @@ import ipaddress
8
8
  import traceback
9
9
  import subprocess
10
10
  from time import sleep
11
- from typing import List
11
+ from typing import List, Dict
12
12
  from scapy.all import ARP, Ether, srp
13
13
 
14
+ from .service_scan import scan_service
14
15
  from .mac_lookup import lookup_mac, get_macs
15
16
  from .ip_parser import get_address_count, MAX_IPS_ALLOWED
16
17
 
@@ -73,6 +74,7 @@ class Device(IPAlive):
73
74
  self.manufacturer: str = None
74
75
  self.ports: List[int] = []
75
76
  self.stage: str = 'found'
77
+ self.services: Dict[str,List[int]] = {}
76
78
  self.log = logging.getLogger('Device')
77
79
 
78
80
  def get_metadata(self):
@@ -100,6 +102,12 @@ class Device(IPAlive):
100
102
  return True
101
103
  return False
102
104
 
105
+ def scan_service(self,port:int):
106
+ service = scan_service(self.ip,port)
107
+ service_ports = self.services.get(service,[])
108
+ service_ports.append(port)
109
+ self.services[service] = service_ports
110
+
103
111
  def get_mac(self):
104
112
  if not self.macs:
105
113
  self.macs = self._get_mac_addresses()
@@ -0,0 +1,51 @@
1
+ import asyncio
2
+ import logging
3
+ import traceback
4
+ from .app_scope import ResourceManager
5
+
6
+ log = logging.getLogger('ServiceScan')
7
+ SERVICES = ResourceManager('services').get_jsonc('definitions.jsonc')
8
+
9
+ # skip printer ports because they cause blank pages to be printed
10
+ PRINTER_PORTS = [9100, 631]
11
+
12
+ def scan_service(ip: str, port: int, timeout=10) -> str:
13
+ """
14
+ Synchronous function that attempts to identify the service running on a given port.
15
+ """
16
+
17
+ async def _async_scan_service(ip: str, port: int, timeout) -> str:
18
+ if port in PRINTER_PORTS:
19
+ return "Printer"
20
+
21
+ try:
22
+ # Add a timeout to prevent hanging
23
+ reader, writer = await asyncio.wait_for(asyncio.open_connection(ip, port), timeout=5)
24
+
25
+ # Send a probe appropriate for common services
26
+ probe = "GET / HTTP/1.1\r\nHost: {}\r\n\r\n".format(ip).encode("utf-8")
27
+ writer.write(probe)
28
+ await writer.drain()
29
+
30
+ # Receive the response with a timeout
31
+ response = await asyncio.wait_for(reader.read(1024), timeout=timeout)
32
+ writer.close()
33
+ await writer.wait_closed()
34
+
35
+ # Analyze the response to identify the service
36
+ response_str = response.decode("utf-8", errors="ignore")
37
+ for service, hints in SERVICES.items():
38
+ if any(hint.lower() in response_str.lower() for hint in hints):
39
+ return service
40
+ except asyncio.TimeoutError:
41
+ log.warning(f"Timeout scanning {ip}:{port}")
42
+ except Exception as e:
43
+ log.error(f"Error scanning {ip}:{port}: {str(e)}")
44
+ log.debug(traceback.format_exc())
45
+ return "Unknown"
46
+
47
+ # Use asyncio.run to execute the asynchronous logic synchronously
48
+ return asyncio.run(_async_scan_service(ip, port,timeout=timeout))
49
+
50
+
51
+
@@ -27,7 +27,37 @@ TCNT_DEVICE_ISALIVE = 256
27
27
  class ScanConfig:
28
28
  subnet: str
29
29
  port_list: str
30
- parallelism: float = 1.0
30
+ t_multiplier: float = 1.0
31
+ t_cnt_port_scan: int = 10
32
+ t_cnt_port_test: int = 128
33
+ t_cnt_isalive: int = 256
34
+
35
+ task_scan_ports: bool = True
36
+ # below wont run if above false
37
+ task_scan_port_services: bool = False # disabling until more stable
38
+
39
+ def t_cnt(self, id: str) -> int:
40
+ return int(int(getattr(self, f't_cnt_{id}')) * float(self.t_multiplier))
41
+
42
+ @staticmethod
43
+ def from_dict(data: dict) -> 'ScanConfig':
44
+ return ScanConfig(
45
+ subnet = data['subnet'],
46
+ port_list = data['port_list'],
47
+ t_multiplier = data.get('parallelism',1.0),
48
+ t_cnt_port_scan = data.get('t_cnt_port_scan',10),
49
+ t_cnt_port_test = data.get('t_cnt_port_test',128),
50
+ t_cnt_isalive = data.get('t_cnt_isalive',256),
51
+ task_scan_ports = data.get('task_scan_ports',True),
52
+ task_scan_port_services = data.get('task_scan_port_services',True)
53
+ )
54
+
55
+ def get_ports(self) -> List[int]:
56
+ return PortManager().get_port_list(self.port_list).keys()
57
+
58
+ def parse_subnet(self) -> List[ipaddress.IPv4Network]:
59
+ return parse_ip_input(self.subnet)
60
+
31
61
 
32
62
 
33
63
 
@@ -36,12 +66,13 @@ class SubnetScanner:
36
66
  self,
37
67
  config: ScanConfig
38
68
  ):
39
- self.subnet = parse_ip_input(config.subnet)
40
- self.port_list = config.port_list
41
- self.ports: list = PortManager().get_port_list(config.port_list).keys()
69
+ self.cfg = config
70
+ self.subnet = config.parse_subnet()
71
+ self.ports: List[int] = config.get_ports()
42
72
  self.running = False
43
- self.parallelism: float = float(config.parallelism)
44
73
  self.subnet_str = config.subnet
74
+
75
+
45
76
  self.job_stats = JobStats()
46
77
  self.uid = str(uuid.uuid4())
47
78
  self.results = ScannerResults(self)
@@ -58,7 +89,7 @@ class SubnetScanner:
58
89
  """
59
90
  self._set_stage('scanning devices')
60
91
  self.running = True
61
- with ThreadPoolExecutor(max_workers=self._t_cnt(TCNT_DEVICE_ISALIVE)) as executor:
92
+ with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('isalive')) as executor:
62
93
  futures = {executor.submit(self._get_host_details, str(ip)): str(ip) for ip in self.subnet}
63
94
  for future in futures:
64
95
  ip = futures[future]
@@ -73,7 +104,8 @@ class SubnetScanner:
73
104
 
74
105
 
75
106
  self._set_stage('testing ports')
76
- self._scan_network_ports()
107
+ if self.cfg.task_scan_ports:
108
+ self._scan_network_ports()
77
109
  self.running = False
78
110
  self._set_stage('complete')
79
111
 
@@ -104,7 +136,7 @@ class SubnetScanner:
104
136
  remaining_isalive_sec = (self.results.devices_total - self.results.devices_scanned) * avg_host_detail_sec
105
137
  total_isalive_sec = self.results.devices_total * avg_host_detail_sec
106
138
 
107
- isalive_multiplier = self._t_cnt(TCNT_DEVICE_ISALIVE)
139
+ isalive_multiplier = self.cfg.t_cnt('isalive')
108
140
 
109
141
  # --- Port scanning calculations ---
110
142
  device_ports_scanned = self.job_stats.finished.get('_test_port', 0)
@@ -116,7 +148,7 @@ class SubnetScanner:
116
148
  remaining_port_test_sec = device_ports_unscanned * avg_port_test_sec
117
149
  total_port_test_sec = est_subnet_devices * len(self.ports) * avg_port_test_sec
118
150
 
119
- port_test_multiplier = self._t_cnt(TCNT_PORT_SCANS) * self._t_cnt(TCNT_PORT_TEST)
151
+ port_test_multiplier = self.cfg.t_cnt('port_scan') * self.cfg.t_cnt('port_test')
120
152
 
121
153
  # --- Overall progress ---
122
154
  est_total_time = (total_isalive_sec / isalive_multiplier) + (total_port_test_sec / port_test_multiplier)
@@ -126,17 +158,23 @@ class SubnetScanner:
126
158
 
127
159
 
128
160
 
129
- def debug_active_scan(self):
161
+ def debug_active_scan(self,sleep_sec=1):
130
162
  """
131
163
  Run this after running scan_subnet_threaded
132
164
  to see the progress of the scan
133
165
  """
134
166
  while self.running:
167
+ percent = self.calc_percent_complete()
168
+ t_elapsed = time() - self.results.start_time
169
+ t_remain = int((100-percent) * (t_elapsed / percent)) if percent else '∞'
170
+ buffer = f'{self.uid} - {self.subnet_str}\n'
171
+ buffer += f'Elapsed: {int(t_elapsed)} sec - Remain: {t_remain} sec\n'
172
+ buffer += f'Scanned: {self.results.devices_scanned}/{self.results.devices_total}'
173
+ buffer += f' - {percent}%\n'
174
+ buffer += str(self.job_stats)
135
175
  os.system('cls' if os.name == 'nt' else 'clear')
136
- print(f'{self.uid} - {self.subnet_str}')
137
- print(f"Scanned: {self.results.devices_scanned}/{self.results.devices_total} - {self.calc_percent_complete()}%")
138
- print(self.job_stats)
139
- sleep(1)
176
+ print(buffer)
177
+ sleep(sleep_sec)
140
178
 
141
179
  @terminator
142
180
  @job_tracker
@@ -157,7 +195,7 @@ class SubnetScanner:
157
195
 
158
196
  @terminator
159
197
  def _scan_network_ports(self):
160
- with ThreadPoolExecutor(max_workers=self._t_cnt(TCNT_PORT_SCANS)) as executor:
198
+ with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('port_scan')) as executor:
161
199
  futures = {executor.submit(self._scan_ports, device): device for device in self.results.devices}
162
200
  for future in futures:
163
201
  future.result()
@@ -167,7 +205,7 @@ class SubnetScanner:
167
205
  def _scan_ports(self, device: Device):
168
206
  self.log.debug(f'[{device.ip}] Initiating port scan')
169
207
  device.stage = 'scanning'
170
- with ThreadPoolExecutor(max_workers=self._t_cnt(TCNT_PORT_TEST)) as executor:
208
+ with ThreadPoolExecutor(max_workers=self.cfg.t_cnt('port_test')) as executor:
171
209
  futures = {executor.submit(self._test_port, device, int(port)): port for port in self.ports}
172
210
  for future in futures:
173
211
  future.result()
@@ -179,9 +217,13 @@ class SubnetScanner:
179
217
  def _test_port(self,host: Device, port: int):
180
218
  """
181
219
  Test if a port is open on a given host.
220
+ If port open, determine service.
182
221
  Device class handles tracking open ports.
183
222
  """
184
- return host.test_port(port)
223
+ is_alive = host.test_port(port)
224
+ if is_alive and self.cfg.task_scan_port_services:
225
+ host.scan_service(port)
226
+ return is_alive
185
227
 
186
228
 
187
229
  @terminator
@@ -192,13 +234,6 @@ class SubnetScanner:
192
234
  """
193
235
  return host.is_alive(host.ip)
194
236
 
195
- def _t_cnt(self, base_threads: int) -> int:
196
- """
197
- Calculate the number of threads to use based on the base number
198
- of threads and the parallelism factor.
199
- """
200
- return int(base_threads * self.parallelism)
201
-
202
237
  def _set_stage(self,stage):
203
238
  self.log.debug(f'[{self.uid}] Moving to Stage: {stage}')
204
239
  self.results.stage = stage
@@ -208,9 +243,8 @@ class SubnetScanner:
208
243
  class ScannerResults:
209
244
  def __init__(self,scan: SubnetScanner):
210
245
  self.scan = scan
211
- self.port_list: str = scan.port_list
246
+ self.port_list: str = scan.cfg.port_list
212
247
  self.subnet: str = scan.subnet_str
213
- self.parallelism: float = scan.parallelism
214
248
  self.uid = scan.uid
215
249
 
216
250
  self.devices_total: int = len(list(scan.subnet))
@@ -249,6 +283,7 @@ class ScannerResults:
249
283
  out = vars(self).copy()
250
284
  out.pop('scan')
251
285
  out.pop('log')
286
+ out['cfg'] = vars(self.scan.cfg)
252
287
 
253
288
  devices: List[Device] = out.pop('devices')
254
289
  sortedDevices = sorted(devices, key=lambda obj: ipaddress.IPv4Address(obj.ip))
@@ -262,7 +297,7 @@ class ScannerResults:
262
297
  def __str__(self):
263
298
  # Prepare data for tabulate
264
299
  data = [
265
- [device.ip, device.hostname, device.mac_addr, ", ".join(map(str, device.ports))]
300
+ [device.ip, device.hostname, device.get_mac(), ", ".join(map(str, device.ports))]
266
301
  for device in self.devices
267
302
  ]
268
303
 
@@ -0,0 +1,456 @@
1
+ {
2
+ // ----------------------- Web Services -----------------------
3
+ "HTTP": [
4
+ "HTTP",
5
+ "Apache",
6
+ "Nginx",
7
+ "IIS",
8
+ "lighttpd",
9
+ "Caddy",
10
+ "OpenResty",
11
+ "GWS", // Google Web Server
12
+ "LiteSpeed",
13
+ "Gunicorn", // Often in X-Powered-By
14
+ "OpenLiteSpeed",
15
+ "Cloudflare"
16
+ ],
17
+ "HTTPS": [
18
+ "HTTPS",
19
+ "SSL",
20
+ "TLS"
21
+ // Possibly you'd see "443" in a banner or SNI reference,
22
+ // but typically "SSL" or "TLS" coverage is sufficient
23
+ ],
24
+
25
+ // ------------------ Remote Shell / Admin --------------------
26
+ "SSH": [
27
+ "SSH",
28
+ "OpenSSH",
29
+ "Dropbear",
30
+ "libssh"
31
+ ],
32
+ "Telnet": [
33
+ "Telnet",
34
+ "BusyBox on telnetd"
35
+ ],
36
+
37
+ // ------------------ File Transfer Protocols -----------------
38
+ "FTP": [
39
+ "FTP",
40
+ "FileZilla",
41
+ "ProFTPD",
42
+ "vsftpd",
43
+ "Pure-FTPd",
44
+ "WS_FTP",
45
+ "Microsoft ftpd"
46
+ ],
47
+ "SFTP": [
48
+ "SFTP"
49
+ // SFTP typically is an SSH subsystem, so you'd often see an SSH banner anyway
50
+ ],
51
+ "TFTP": [
52
+ "TFTP"
53
+ ],
54
+
55
+ // -------------------- Email / Messaging ----------------------
56
+ "SMTP": [
57
+ "SMTP",
58
+ "Postfix",
59
+ "Exim",
60
+ "Sendmail",
61
+ "Qmail",
62
+ "Exchange"
63
+ ],
64
+ "SMTPS": [
65
+ "SMTPS",
66
+ "465 secure",
67
+ "587 secure"
68
+ // Some servers might mention "TLS wrapper" or similar
69
+ ],
70
+ "POP3": [
71
+ "POP3",
72
+ "Dovecot",
73
+ "Courier",
74
+ "POP3 (Internet Mail)"
75
+ ],
76
+ "POP3S": [
77
+ "POP3S",
78
+ "SSL POP3",
79
+ "Dovecot",
80
+ "Courier"
81
+ ],
82
+ "IMAP": [
83
+ "IMAP",
84
+ "Dovecot",
85
+ "Courier",
86
+ "Cyrus",
87
+ "IMAP4"
88
+ ],
89
+ "IMAPS": [
90
+ "IMAPS",
91
+ "SSL IMAP",
92
+ "Dovecot",
93
+ "Courier",
94
+ "Cyrus"
95
+ ],
96
+
97
+ // ---------------- Domain / Directory Services ---------------
98
+ "DNS": [
99
+ "DNS",
100
+ "Bind",
101
+ "DNSMasq",
102
+ "PowerDNS",
103
+ "Unbound",
104
+ "Microsoft DNS",
105
+ "Knot DNS"
106
+ ],
107
+ "LDAP": [
108
+ "LDAP"
109
+ ],
110
+ "Active Directory": [
111
+ "Active Directory",
112
+ "Kerberos",
113
+ "MSAD",
114
+ "LDAP for AD",
115
+ "LDAP Service (AD)"
116
+ ],
117
+
118
+ // -------------------- Database Services ----------------------
119
+ "MySQL": [
120
+ "MySQL",
121
+ "MariaDB",
122
+ "Percona"
123
+ ],
124
+ "PostgreSQL": [
125
+ "PostgreSQL",
126
+ "Postgres"
127
+ ],
128
+ "Microsoft SQL Server": [
129
+ "MSSQL",
130
+ "Microsoft SQL Server",
131
+ "TDS",
132
+ "SQL Server",
133
+ "MS-SQL"
134
+ ],
135
+ "Oracle Database": [
136
+ "Oracle",
137
+ "TNS",
138
+ "Oracle Net"
139
+ ],
140
+ "MongoDB": [
141
+ "MongoDB"
142
+ ],
143
+ "Redis": [
144
+ "Redis"
145
+ ],
146
+ "Cassandra": [
147
+ "Cassandra"
148
+ ],
149
+ "Memcached": [
150
+ "Memcached"
151
+ ],
152
+
153
+ // ----------------- Caching / Indexing / Logging -------------
154
+ "Elasticsearch": [
155
+ "Elasticsearch"
156
+ ],
157
+ "Logstash": [
158
+ "Logstash"
159
+ ],
160
+
161
+ // --------------- Message Brokers & Streaming ----------------
162
+ "Kafka": [
163
+ "Kafka"
164
+ ],
165
+ "RabbitMQ": [
166
+ "RabbitMQ",
167
+ "AMQP"
168
+ ],
169
+ "ActiveMQ": [
170
+ "ActiveMQ"
171
+ ],
172
+ "Mosquitto": [
173
+ "Mosquitto",
174
+ "MQTT"
175
+ ],
176
+
177
+ // -------------------- Microsoft Services ---------------------
178
+ "RDP": [
179
+ "RDP",
180
+ "Terminal Services",
181
+ "Remote Desktop",
182
+ "MSTerminal"
183
+ ],
184
+ "SMB": [
185
+ "SMB",
186
+ "NetBIOS",
187
+ "Samba"
188
+ ],
189
+ "WMI": [
190
+ "WMI"
191
+ ],
192
+ "WinRM": [
193
+ "WinRM"
194
+ ],
195
+
196
+ // ---------------- Collaboration / Chat -----------------------
197
+ "IRC": [
198
+ "IRC"
199
+ ],
200
+ "XMPP": [
201
+ "XMPP",
202
+ "Jabber"
203
+ ],
204
+ "Matrix": [
205
+ "Matrix"
206
+ ],
207
+
208
+ // ------------- Virtualization / Container Services ----------
209
+ "Docker API": [
210
+ "Docker"
211
+ ],
212
+ "Kubernetes API": [
213
+ "Kubernetes",
214
+ "K8s",
215
+ "kube-apiserver"
216
+ ],
217
+ "VMware ESXi": [
218
+ "VMware ESXi",
219
+ "ESX"
220
+ ],
221
+ "Proxmox": [
222
+ "Proxmox"
223
+ ],
224
+
225
+ // ----------------- Remote Desktop / Control -----------------
226
+ "VNC": [
227
+ "VNC",
228
+ "RFB"
229
+ ],
230
+ "TeamViewer": [
231
+ "TeamViewer"
232
+ ],
233
+ "AnyDesk": [
234
+ "AnyDesk"
235
+ ],
236
+
237
+ // --------------------- Voice over IP ------------------------
238
+ "SIP": [
239
+ "SIP",
240
+ "Asterisk",
241
+ "FreeSWITCH"
242
+ ],
243
+ "H.323": [
244
+ "H.323"
245
+ ],
246
+
247
+ // ----------- Time, Network Config, and Infrastructure -------
248
+ "NTP": [
249
+ "NTP"
250
+ ],
251
+ "DHCP": [
252
+ "DHCP"
253
+ ],
254
+
255
+ // ------------------ VPN / Tunneling / Security --------------
256
+ "OpenVPN": [
257
+ "OpenVPN"
258
+ ],
259
+ "IPSec": [
260
+ "IPSec",
261
+ "IKE"
262
+ ],
263
+ "WireGuard": [
264
+ "WireGuard"
265
+ ],
266
+
267
+ // ---------------------- Other Protocols ----------------------
268
+ "SNMP": [
269
+ "SNMP"
270
+ ],
271
+ "SNMP Trap": [
272
+ "Trap",
273
+ "SNMPTRAP"
274
+ ],
275
+ "Syslog": [
276
+ "Syslog"
277
+ ],
278
+
279
+ // -------------------- Cloud Services / APIs -----------------
280
+ "AWS S3": [
281
+ "AmazonS3",
282
+ "S3"
283
+ ],
284
+ "AWS RDS": [
285
+ "AWS RDS",
286
+ "amazon-rds"
287
+ ],
288
+ "Azure DevOps": [
289
+ "Azure DevOps",
290
+ "VSTS"
291
+ ],
292
+ "GCP Services": [
293
+ "GCP",
294
+ "googleapis.com"
295
+ ],
296
+
297
+ // -------------------- Repo / Code Hosting --------------------
298
+ "Git": [
299
+ "git-daemon"
300
+ ],
301
+ "SVN": [
302
+ "SVN",
303
+ "Subversion"
304
+ ],
305
+
306
+ // -------------- HPC / Cluster Management / HPC --------------
307
+ "Slurm": [
308
+ "Slurm"
309
+ ],
310
+
311
+ // --------------------- Misc / Exotic ------------------------
312
+ "Tor": [
313
+ "Tor",
314
+ "obfs"
315
+ ],
316
+ "ZeroTier": [
317
+ "ZeroTier"
318
+ ],
319
+ "Mumble": [
320
+ "Mumble",
321
+ "Murmur"
322
+ ],
323
+ "Spice": [
324
+ "SPICE protocol"
325
+ ],
326
+ "FusionDirectory": [
327
+ "FusionDirectory"
328
+ ],
329
+ "Jenkins": [
330
+ "Jenkins"
331
+ ],
332
+ "GitLab": [
333
+ "GitLab"
334
+ ],
335
+ "GitHub": [
336
+ "GitHub",
337
+ "github.com"
338
+ ],
339
+ "Bitbucket": [
340
+ "Bitbucket"
341
+ ],
342
+
343
+ // ------------------ Legacy or Older Protocols ---------------
344
+ "Gopher": [
345
+ "Gopher"
346
+ ],
347
+ "Finger": [
348
+ "Finger"
349
+ ],
350
+ "Ident": [
351
+ "Ident"
352
+ ],
353
+ "Rlogin": [
354
+ "rlogin"
355
+ ],
356
+ "Rsync": [
357
+ "rsync"
358
+ ],
359
+ "UUCP": [
360
+ "UUCP"
361
+ ],
362
+ "AFS": [
363
+ "AFS",
364
+ "OpenAFS"
365
+ ],
366
+ "NIS": [
367
+ "NIS",
368
+ "YPServ"
369
+ ],
370
+ "Telnet SSL": [
371
+ "telnet-ssl"
372
+ ],
373
+
374
+ // ----------------- Big Data / Hadoop Ecosystem --------------
375
+ "Hadoop": [
376
+ "Hadoop",
377
+ "MapReduce",
378
+ "YARN"
379
+ ],
380
+ "HDFS": [
381
+ "HDFS"
382
+ ],
383
+ "Hive": [
384
+ "Hive",
385
+ "HiveServer2"
386
+ ],
387
+ "HBase": [
388
+ "HBase",
389
+ "HMaster"
390
+ ],
391
+ "Zookeeper": [
392
+ "Zookeeper",
393
+ "ZooKeeper"
394
+ ],
395
+ "Spark": [
396
+ "Spark"
397
+ ],
398
+
399
+ // ------------ Web Frameworks / Application Servers ----------
400
+ "Tomcat": [
401
+ "Apache Tomcat",
402
+ "Apache-Coyote"
403
+ ],
404
+ "JBoss": [
405
+ "JBoss",
406
+ "JBoss EAP"
407
+ ],
408
+ "GlassFish": [
409
+ "GlassFish"
410
+ ],
411
+ "WebLogic": [
412
+ "WebLogic"
413
+ ],
414
+ "WebSphere": [
415
+ "WebSphere"
416
+ ],
417
+ "Jetty": [
418
+ "Jetty"
419
+ ],
420
+ // Gunicorn and uWSGI also appear under HTTP if they surface in the banner.
421
+
422
+ // ------- Additional Container / Orchestration Tools ---------
423
+ "Docker Swarm": [
424
+ "Swarm"
425
+ ],
426
+
427
+ // ---------- Reverse Proxies / Load Balancers / Gateways -----
428
+ "HAProxy": [
429
+ "HAProxy"
430
+ ],
431
+ "Traefik": [
432
+ "Traefik"
433
+ ],
434
+ "Envoy": [
435
+ "Envoy"
436
+ ],
437
+
438
+ // ------------------ CI/CD and DevOps Tools ------------------
439
+ "Bamboo": [
440
+ "Bamboo"
441
+ ],
442
+ "TeamCity": [
443
+ "TeamCity"
444
+ ],
445
+ "Drone CI": [
446
+ "Drone"
447
+ ],
448
+
449
+ // ------------------- Just for completeness ------------------
450
+ "COBOL-based service": [
451
+ "Mainframe",
452
+ "AS/400",
453
+ // Banner might mention "Enterprise COBOL" or "z/OS"
454
+ "z/OS"
455
+ ]
456
+ }
@@ -11,7 +11,7 @@ class LibraryTestCase(unittest.TestCase):
11
11
  self.assertIsNotNone(subnet)
12
12
  cfg = ScanConfig(
13
13
  subnet = right_size_subnet(subnet),
14
- parallelism=1.0,
14
+ t_multiplier=1.0,
15
15
  port_list='small'
16
16
  )
17
17
  scan = sm.new_scan(cfg)
lanscape/ui/app.py CHANGED
@@ -106,8 +106,6 @@ def start_webserver(args: RuntimeArgs) -> int:
106
106
 
107
107
  app.run(**run_args)
108
108
 
109
-
110
-
111
109
  if __name__ == "__main__":
112
110
  start_webserver(True)
113
111
 
@@ -35,6 +35,8 @@ def get_scan(scan_id):
35
35
  @api_bp.route('/api/scan/<scan_id>/summary',methods=['GET'])
36
36
  def get_scan_summary(scan_id):
37
37
  scan = scan_manager.get_scan(scan_id)
38
+ if not scan:
39
+ return jsonify({'error':'scan not found'}), 404
38
40
  return jsonify({
39
41
  'running': scan.running,
40
42
  'percent_complete': scan.calc_percent_complete(),
@@ -61,5 +63,5 @@ def get_scan_config():
61
63
  return ScanConfig(
62
64
  subnet = data['subnet'],
63
65
  port_list= data['port_list'],
64
- parallelism=data.get('parallelism',1.0)
66
+ t_multiplier=data.get('parallelism',1.0)
65
67
  )
@@ -22,7 +22,7 @@ def index():
22
22
  scan = scanner.results.export()
23
23
  subnet = scan['subnet']
24
24
  port_list = scan['port_list']
25
- parallelism = scan['parallelism']
25
+ parallelism = scan['cfg']['t_multiplier']
26
26
  else:
27
27
  log.debug(f'Redirecting, scan {scan_id} doesnt exist in memory')
28
28
  return redirect('/')
lanscape/ui/main.py CHANGED
@@ -13,6 +13,7 @@ configure_logging(args.loglevel, args.logfile)
13
13
 
14
14
  from ..libraries.version_manager import get_installed_version, is_update_available
15
15
  from .app import start_webserver
16
+ import socket
16
17
 
17
18
 
18
19
  log = logging.getLogger('core')
@@ -27,9 +28,12 @@ def main():
27
28
  if not IS_FLASK_RELOAD:
28
29
  log.info(f'LANscape v{get_installed_version()}')
29
30
  try_check_update()
30
- else:
31
+
32
+ else:
31
33
  log.info('Flask reloaded app.')
32
34
 
35
+ args.port = get_valid_port(args.port)
36
+
33
37
 
34
38
  try:
35
39
 
@@ -63,9 +67,8 @@ def open_browser(url: str,wait=2):
63
67
  time.sleep(wait)
64
68
  webbrowser.open(url, new=2)
65
69
  except:
66
- srv_url = f"0.0.0.0:{url.split(':')[1]}"
67
70
  log.debug(traceback.format_exc())
68
- log.info(f'unable to open web browser, server running on {srv_url}')
71
+ log.info(f'Unable to open web browser, server running on {url}')
69
72
 
70
73
  threading.Thread(target=do_open).start()
71
74
 
@@ -74,13 +77,21 @@ def no_gui(args: RuntimeArgs):
74
77
  # if it was, dont open the browser again
75
78
  if not IS_FLASK_RELOAD:
76
79
  open_browser(f'http://127.0.0.1:{args.port}')
77
-
78
- log.info(f'Server started: http://127.0.0.1:{args.port}')
80
+ log.info(f'Flask started: http://127.0.0.1:{args.port}')
79
81
 
80
82
  start_webserver(
81
83
  args
82
84
  )
83
85
 
86
+ def get_valid_port(port: int):
87
+ """
88
+ Get the first available port starting from the specified port
89
+ """
90
+ while True:
91
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
92
+ if s.connect_ex(('localhost', port)) != 0:
93
+ return port
94
+ port += 1
84
95
 
85
96
  if __name__ == "__main__":
86
97
  main()
@@ -164,7 +164,6 @@ function pollScanSummary(id) {
164
164
  if (summary.running || summary.stage == 'terminating') {
165
165
  progress.css('height','2px');
166
166
  progress.css('width',`${summary.percent_complete}vw`);
167
- // TODO: move overview here??
168
167
  setTimeout(() => {pollScanSummary(id)},500);
169
168
  } else {
170
169
  progress.css('width','100vw');
@@ -179,6 +178,11 @@ function pollScanSummary(id) {
179
178
  },1000);
180
179
  }
181
180
  updateOverviewUI(summary);
181
+ }).fail(function(req) {
182
+ if (req === 404) {
183
+ console.log('Scan not found, redirecting to home');
184
+ window.location.href = '/';
185
+ }
182
186
  });
183
187
  }
184
188
 
@@ -11,7 +11,7 @@
11
11
  <div class="version">
12
12
  v{{app_version}}
13
13
  {% if is_local %}
14
- <span class="text-info">(Local Run)</span>
14
+ <span class="text-info">(Local)</span>
15
15
  {% elif update_available %}
16
16
  <span>(Update Available)</span>
17
17
  {% endif %}
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: lanscape
3
- Version: 1.2.8a2
3
+ Version: 1.2.9
4
4
  Summary: A python based local network scanner
5
5
  Author-email: Michael Dennis <michael@dipduo.com>
6
6
  Project-URL: Homepage, https://github.com/mdennis281/py-lanscape
@@ -32,17 +32,15 @@ python -m lanscape
32
32
 
33
33
  ## Flags
34
34
  - `--port <port number>` port of the flask app (default: 5001)
35
- - `--nogui` run in web mode (default: false)
36
35
  - `--reloader` essentially flask debug mode- good for local development (default: false)
37
36
  - `--logfile` save log output to lanscape.log
38
37
  - `--loglevel <level>` set the logger's log level (default: INFO)
39
- - `--headless` similar to nogui but doesnt try to open a browser (default: false)
40
38
 
41
39
 
42
40
  Examples:
43
41
  ```shell
44
42
  python -m lanscape --reloader
45
- python -m lanscape --nogui --port 5002
43
+ python -m lanscape --port 5002
46
44
  python -m lanscape --logfile --loglevel DEBUG
47
45
  ```
48
46
 
@@ -57,14 +55,6 @@ can sometimes require admin-level permissions to retrieve accurate results.
57
55
  This is a missing dependency related to the ARP lookup. This is handled in the code, but you would get marginally faster/better results with this installed: [npcap download](https://npcap.com/#download)
58
56
 
59
57
 
60
- ### Unable to start webview client. Try --nogui (Linux)
61
- Linux and QT (GUI package) dont seem to play well with each other very well. If you really want the gui (`python -m lanscape --nogui` is almost as good) I had success on ubuntu desktop by running these:
62
- ```sh
63
- sudo apt install libcairo2-dev libxt-dev libgirepository1.0-dev
64
- pip install pycairo PyGObject qtpy PyQt5 PyQtWebEngine
65
- ```
66
-
67
-
68
58
  ### Something else
69
59
  Feel free to submit a github issue detailing your experience.
70
60
 
@@ -1,15 +1,16 @@
1
1
  lanscape/__init__.py,sha256=_8FoHQKR0s9B_stjs5e5CnytwbSK1JgNvE2kZBDrWbw,180
2
2
  lanscape/__main__.py,sha256=Im2Qc9AIScEvjRik_X4x63n0Rie67-myQbuIEU7I-Ac,129
3
- lanscape/libraries/app_scope.py,sha256=FiTKrnttqIfciYcXqnB0uv2mVdGaNg_PtmW1vW9facE,2102
3
+ lanscape/libraries/app_scope.py,sha256=oPRrYIXOn914gF1DTVCDcy1d97hjReFlAxJNBjseBIo,2447
4
4
  lanscape/libraries/decorators.py,sha256=Birxor-ae-CQRlBVOyB5DggHnuMQR98IYZ1FVHdGZzE,2448
5
5
  lanscape/libraries/errors.py,sha256=zvfqvJbIUsUWsEmw6XmUWXZsktbVachGIkYOdo1nR8s,447
6
- lanscape/libraries/ip_parser.py,sha256=LIjpNkF-ovlTsBxnuTJEWYYy06VdGuuS_lEIc6_PZnQ,2239
6
+ lanscape/libraries/ip_parser.py,sha256=ElXz3LU5CUYWqKOHEyrj5Y4Iv6OBtoSlbCcxhCsibfQ,2226
7
7
  lanscape/libraries/logger.py,sha256=hGbnj1cTGZT5elwdXUBVyLg_E2Pnocs2IfWumUkHj7M,1385
8
- lanscape/libraries/mac_lookup.py,sha256=jejM0NHLbxB9G2hfge2qt0qcNgnSlZERbiTP-RLUUPA,2141
9
- lanscape/libraries/net_tools.py,sha256=zWWvKon46i3ImhaGg2IpCV-Cx01-jFBBZCIHHHyFmqg,10112
8
+ lanscape/libraries/mac_lookup.py,sha256=-dRV0ygtjjh3JkgL3GTi_5-w7pcZ1oj4XVH4chjsmRs,2121
9
+ lanscape/libraries/net_tools.py,sha256=dUuLsxS_ypi9joZjlkn0dfzxwtT__85gOUkBCXvKBXM,10436
10
10
  lanscape/libraries/port_manager.py,sha256=fNext3FNfGnGYRZK9RhTEwQ2K0e0YmmMlhK4zVAvoCw,1977
11
11
  lanscape/libraries/runtime_args.py,sha256=JJTzWgQ-0aRh7Ce5efIwBmHZv1LfWjFagtNUNlLN7Uo,1660
12
- lanscape/libraries/subnet_scan.py,sha256=2BW69tbntqEujiqsdlQruLHfoAwnFrv6OLMhvQWauYM,11191
12
+ lanscape/libraries/service_scan.py,sha256=jLU84ZoJnqSQbE30Zly2lm2zHrCGutNXjla1sEvp1hE,1949
13
+ lanscape/libraries/subnet_scan.py,sha256=qgyiOcnZt56gRS-Dn52Y4YL3KuqNEQ-BadrxgX3_AII,12542
13
14
  lanscape/libraries/version_manager.py,sha256=wdB5DPW8IjHJfgvT4mUKz7ndEV922WgVEqbwd6jfun4,1599
14
15
  lanscape/resources/mac_addresses/convert_csv.py,sha256=w3Heed5z2mHYDEZNBep3_hNg4dbrp_N6J54MGxnrq4s,721
15
16
  lanscape/resources/mac_addresses/mac_db.json,sha256=ygtFSwNwJzDlg6hmAujdgCyzUjxt9Di75J8SO4xYIs8,2187804
@@ -18,20 +19,21 @@ lanscape/resources/ports/full.json,sha256=Abfbi-b5yZF4jR5NS6CT6QpIDfx4Vk04gIC1fK
18
19
  lanscape/resources/ports/large.json,sha256=jK4gkzlPT74uv4y0TvFjsaOaUcX7Cy8Zxe2bh5FYcc0,144496
19
20
  lanscape/resources/ports/medium.json,sha256=3yHAca0_pEWXK4k1wmda1eNVM6ftzcpKn5VspVwmkRs,3667
20
21
  lanscape/resources/ports/small.json,sha256=Mj3zGVG1F2eqZx2YkrLpTL8STeLcqB8_65IR67MS1Tg,397
22
+ lanscape/resources/services/definitions.jsonc,sha256=71w9Q7r4RoBYiIMkzzO2KdEJXaSIchNccYQueqAhD4E,8842
21
23
  lanscape/tests/__init__.py,sha256=xYPeceOF-ppTS0wnq7CkVYQMwupmeSHxbYLbGj_imZ0,113
22
24
  lanscape/tests/_helpers.py,sha256=wXJfUwzL3Fq4XBsC3dValCbXsf0U8FisuM_yo1de4QQ,371
23
25
  lanscape/tests/test_api.py,sha256=fIVIA6O9ssPRjofTHLS6z7XPNTkvv2rl2jDaxVCjFGU,5669
24
26
  lanscape/tests/test_env.py,sha256=ivFhCcemJ9vbe0_KtUkbqDY4r9nsDB8rVLUVjV-sNj8,673
25
- lanscape/tests/test_library.py,sha256=u-UBx76Dyh2xLhiCNxnzv7lIsWoUGKUtbPqkVUg7xwE,1552
26
- lanscape/ui/app.py,sha256=HAH6IlJWJD3Mkwj1Z5h1hvk3U09ekwvg66Gfasphv54,3194
27
- lanscape/ui/main.py,sha256=0JOGmYOSMwYPc66Q5c9fVHN92KZCzgPjLNWtwJgMlWM,2373
27
+ lanscape/tests/test_library.py,sha256=OPcTsUoR5IureSNDbePxid2BG98mfNNIJmCIY0BVz3w,1553
28
+ lanscape/ui/app.py,sha256=hGmzoyH4YW2WmQsqZ6v-9r5DYzWi2KYP6_toZSxQqFg,3186
29
+ lanscape/ui/main.py,sha256=HMQythH0fijX1Mt9fGQBcqmY37dQQ19ROC99JbCNAvs,2701
28
30
  lanscape/ui/blueprints/__init__.py,sha256=agvgPOSVbrxddaw6EY64ZZr1CQi1Qzwcs1t0lZMv5oY,206
29
31
  lanscape/ui/blueprints/api/__init__.py,sha256=t0QOq3vHFWmlZm_3YFPQbQzCn1a_a5cmRchtIxwy4eY,103
30
32
  lanscape/ui/blueprints/api/port.py,sha256=2UA38umzXE8pMitx1E-_wJHyL1dYYbtM6Kg5zVtfj6A,1019
31
- lanscape/ui/blueprints/api/scan.py,sha256=qMObcXk22Eg1T5b4JhdMJMXUhz_IZts5Bl6CupXUxiM,2061
33
+ lanscape/ui/blueprints/api/scan.py,sha256=ch4AcZ2nJLAXj0skHo3dMibpAIg4A_BSa2pZ0U782Wo,2137
32
34
  lanscape/ui/blueprints/api/tools.py,sha256=CD0NDSX8kN6_lpl0jEw-ULLsDx7pKODCMFQiaK4GCzM,1153
33
35
  lanscape/ui/blueprints/web/__init__.py,sha256=-WRjENG8D99NfaiSDk9uAa8OX6XJq9Zmq1ck29ARL-w,92
34
- lanscape/ui/blueprints/web/routes.py,sha256=9S-xDBnUR-8Wf-B0fipBNFeTz-xiHhHy0WmhW9qWqZk,2166
36
+ lanscape/ui/blueprints/web/routes.py,sha256=Cd_XpDCSt02_K1hoJoegacA_eMhmqZdz0R0QMsZ2K0s,2174
35
37
  lanscape/ui/static/lanscape.webmanifest,sha256=0aauJk_Bybd0B2iwzJfvPcs7AX43kVHs0dtpV6_jSWk,459
36
38
  lanscape/ui/static/css/style.css,sha256=LbQ4O98uttwY2Msxv9XnniOTzZApGsqkFg8PCoe1ZcQ,15801
37
39
  lanscape/ui/static/img/ico/android-chrome-192x192.png,sha256=DxM2E9GjpKX-hSaSmAoW0GxLJ2fdXKJ-WOgoxYlDybw,24130
@@ -43,12 +45,12 @@ lanscape/ui/static/img/ico/favicon.ico,sha256=2fKhUCOFv7vH_WwGPhemSHoJeIXQl0-iq8
43
45
  lanscape/ui/static/img/ico/site.webmanifest,sha256=ep4Hzh9zhmiZF2At3Fp1dQrYQuYF_3ZPZxc1KcGBvwQ,263
44
46
  lanscape/ui/static/js/core.js,sha256=y-f8iQPIetllUY0lSCwnGbPCk5fTJbbU6Pxm3rw1EBU,1111
45
47
  lanscape/ui/static/js/layout-sizing.js,sha256=23UuKdEmRChg6fyqj3DRvcsNfMoa6MRt6dkaT0k7_UY,841
46
- lanscape/ui/static/js/main.js,sha256=oR1cXcOvl7zzEYw7VXLJ8UL2q-WFRiU7i04WT1-HbCA,6452
48
+ lanscape/ui/static/js/main.js,sha256=qKy_dukRbn5t9qBiL1TNS3wyiH-F2sE6WtSSRjOprKw,6583
47
49
  lanscape/ui/static/js/quietReload.js,sha256=_mHzpUsGL4Lm1hNsr3VYSOGVcgGA2y1-eZHacssTXGs,724
48
50
  lanscape/ui/static/js/shutdown-server.js,sha256=WkO7_SNSHM_6kReUoCoExIdCf7Sl7IPiSiNxpbI-r0s,469
49
51
  lanscape/ui/static/js/subnet-info.js,sha256=aytt0LkBx4FVq36TxiMEw3aM7XQLHg_ng1U2WDwZVF4,577
50
52
  lanscape/ui/static/js/subnet-selector.js,sha256=OG01pDaSOPLq3Ial0aO0CqPcob9tPZA1MZKGmQG0W7Q,366
51
- lanscape/ui/templates/base.html,sha256=QvYk84UCIAYQNCvjf_P1RudsEtc_zbz1ekpd13pGpqk,1502
53
+ lanscape/ui/templates/base.html,sha256=P5xnMlvDXYkYSXdSZUWaRfhsszNuZPP7A56hemBrAFs,1498
52
54
  lanscape/ui/templates/error.html,sha256=zXFO0zPIfQORWq1ZMiSZ8G7FjfhVVr-aaYC0HeBl4Rs,1068
53
55
  lanscape/ui/templates/info.html,sha256=oCC59keGEfgUB4WaCozaeZEfNb8Nr7y61DmkRBMqs18,2461
54
56
  lanscape/ui/templates/main.html,sha256=M12xJSN6Ga565vIPhdCiqcr1tYgDrqzuQTeuXtk-8yo,3759
@@ -61,8 +63,8 @@ lanscape/ui/templates/scan/ip-table-row.html,sha256=ptY24rxJRaA4PEEQRDncaq6Q0ql5
61
63
  lanscape/ui/templates/scan/ip-table.html,sha256=ds__UP9JiTKf5IxCmTMzw--eN_yg1Pvn3Nj1KvQxeZg,940
62
64
  lanscape/ui/templates/scan/overview.html,sha256=Q0gmkVI-xNLZ-zWA9qm4H14cv_eF_bQs1KYPyAaoHLY,1141
63
65
  lanscape/ui/templates/scan/scan-error.html,sha256=Q4eZM5ThrxnFaWOSTUpK8hA2ksHwhxOBTaVUCLALhyA,1032
64
- lanscape-1.2.8a2.dist-info/LICENSE,sha256=cCO-NbS01Ilwc6djHjZ7LIgPFRkRmWdr0fH2ysXKioA,1090
65
- lanscape-1.2.8a2.dist-info/METADATA,sha256=9HwuA83d2UhrgFn9Mtlqt_f3X23pGIVckHltTV7PdQ0,2583
66
- lanscape-1.2.8a2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
67
- lanscape-1.2.8a2.dist-info/top_level.txt,sha256=E9D4sjPz_6H7c85Ycy_pOS2xuv1Wm-ilKhxEprln2ps,9
68
- lanscape-1.2.8a2.dist-info/RECORD,,
66
+ lanscape-1.2.9.dist-info/LICENSE,sha256=cCO-NbS01Ilwc6djHjZ7LIgPFRkRmWdr0fH2ysXKioA,1090
67
+ lanscape-1.2.9.dist-info/METADATA,sha256=CyWSrnjlzocLsOiO07Noy1KPPYT7MupxE6inTpdvLeI,2044
68
+ lanscape-1.2.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
69
+ lanscape-1.2.9.dist-info/top_level.txt,sha256=E9D4sjPz_6H7c85Ycy_pOS2xuv1Wm-ilKhxEprln2ps,9
70
+ lanscape-1.2.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5