portune 0.1.2__py3-none-any.whl → 0.1.4__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 portune might be problematic. Click here for more details.

portune/portune.py CHANGED
@@ -38,7 +38,9 @@ body {
38
38
  font-family: Arial, sans-serif;
39
39
  overflow: auto;
40
40
  }
41
-
41
+ .hidden {
42
+ display: none;
43
+ }
42
44
  .icon {
43
45
  border-radius: 25px;
44
46
  background-color: #444;
@@ -78,7 +80,16 @@ div {
78
80
  text-align: right;
79
81
  padding-right: 8px;
80
82
  }
81
-
83
+ .desc {
84
+ text-overflow: ellipsis;
85
+ max-width: 300px;
86
+ white-space: nowrap;
87
+ overflow: hidden;
88
+ }
89
+ .desc:hover {
90
+ overflow: visible;
91
+ white-space: normal;
92
+ }
82
93
  .table-container {
83
94
  height: 90%;
84
95
  overflow-y: auto;
@@ -299,6 +310,7 @@ function toggleSort(table, colIndex, sortBtn) {
299
310
  }
300
311
 
301
312
  function applyFilters(table) {
313
+ table.classList.add('hidden');
302
314
  const rows = Array.from(table.querySelectorAll('tbody tr'));
303
315
  const filters = Array.from(table.querySelectorAll('.column-filter'))
304
316
  .map(filter => ({
@@ -313,10 +325,10 @@ function applyFilters(table) {
313
325
  // First apply filters
314
326
  const filteredRows = rows.filter(row => {
315
327
  // If no filters are active, show all rows
316
- if (filters.every(f => !f.value)) {
317
- row.style.display = '';
318
- return true;
319
- }
328
+ //if (filters.every(f => !f.value)) {
329
+ // row.classList.remove('hidden');
330
+ // return true;
331
+ //}
320
332
  const cells = row.cells;
321
333
  const shouldShow = !filters.some(filter => {
322
334
  if (!filter.value) return false;
@@ -324,7 +336,11 @@ function applyFilters(table) {
324
336
  if (filter.regexp) return !filter.regexp.test(cellText);
325
337
  return !cellText.toLowerCase().includes(filter.value);
326
338
  });
327
- row.style.display = shouldShow ? '' : 'none';
339
+ if (shouldShow) {
340
+ row.classList.remove('hidden');
341
+ } else {
342
+ row.classList.add('hidden');
343
+ }
328
344
  return shouldShow;
329
345
  });
330
346
 
@@ -362,6 +378,7 @@ function applyFilters(table) {
362
378
  const tbody = table.querySelector('tbody');
363
379
  filteredRows.forEach(row => tbody.appendChild(row));
364
380
  }
381
+ table.classList.remove('hidden');
365
382
  }
366
383
 
367
384
  function processHtmlContent(element) {
@@ -600,6 +617,7 @@ def parse_input_file(filename: str) -> List[Tuple[str, List[int]]]:
600
617
  A list of tuples, each containing:
601
618
  - hostname (str): The target hostname or IP address
602
619
  - ports (List[int]): List of ports to scan for that host
620
+ - desc (str): Optional description for the host
603
621
 
604
622
  Examples:
605
623
  Input file format:
@@ -615,19 +633,26 @@ def parse_input_file(filename: str) -> List[Tuple[str, List[int]]]:
615
633
  ('host3', [8080])
616
634
  ]
617
635
  """
618
- hosts = []
636
+ host_dict = {}
619
637
  with open(filename, 'r') as f:
620
638
  for line in f:
621
- line = line.strip().lower()
639
+ line = line.strip()
622
640
  if not line or line.startswith('#'):
623
641
  continue
624
- try:
625
- host, ports = line.split()
626
- port_list = [int(p) for p in ports.split(',')]
627
- hosts.append((host, port_list))
628
- except Exception:
629
- hosts.append((line, [22])) # Default port 22 if parsing fails
630
- continue
642
+ words = line.split()
643
+ fqdn = words[0].lower()
644
+ ports = words[1] if len(words) > 1 else '22'
645
+ port_list = [int(p) for p in ports.split(',')]
646
+ desc = ' '.join(words[2:]).strip() if len(words) > 2 else ''
647
+ if fqdn in host_dict:
648
+ existing_ports, existing_desc = host_dict[fqdn]
649
+ host_dict[fqdn] = (list(set(existing_ports + port_list)), existing_desc or desc)
650
+ else:
651
+ host_dict[fqdn] = (port_list, desc)
652
+ hosts = []
653
+ for fqdn in host_dict:
654
+ ports, desc = host_dict[fqdn]
655
+ hosts.append((fqdn, sorted(ports), desc))
631
656
  return hosts
632
657
 
633
658
  def ping_host(ip: str, timeout: float = 2.0) -> bool:
@@ -698,7 +723,7 @@ def resolve_and_ping_host(hostname: str, timeout: float = 2.0, noping: bool = Fa
698
723
  except Exception:
699
724
  return hostname, {'ip': 'N/A', 'ping': False}
700
725
 
701
- def ping_hosts(hosts: List[Tuple[str, List[int]]],
726
+ def ping_hosts(hosts: List[Tuple[str, List[int], str]],
702
727
  timeout: float = 2.0,
703
728
  parallelism: int = 10,
704
729
  noping: bool = False) -> Dict[str, Dict[str, Union[str, bool]]]:
@@ -739,7 +764,7 @@ def ping_hosts(hosts: List[Tuple[str, List[int]]],
739
764
  with ThreadPoolExecutor(max_workers=parallelism) as executor:
740
765
  future_to_host = {
741
766
  executor.submit(resolve_and_ping_host, hostname, timeout, noping): hostname
742
- for hostname, _ in hosts
767
+ for hostname, _, _ in hosts
743
768
  }
744
769
 
745
770
  # Add callback to update progress bar when each future completes
@@ -760,7 +785,8 @@ def ping_hosts(hosts: List[Tuple[str, List[int]]],
760
785
 
761
786
  def check_port(hostname: str,
762
787
  port: int,
763
- host_info: Dict[str, Union[str, bool]],
788
+ host_info: Dict[str, Union[str, bool]],
789
+ desc: str,
764
790
  timeout: float = 2.0) -> Tuple[str, str, int, str, bool]:
765
791
  """Check if a specific TCP port is accessible on a host.
766
792
 
@@ -774,6 +800,7 @@ def check_port(hostname: str,
774
800
  - 'ip': IP address or 'N/A' if resolution failed
775
801
  - 'ping': Boolean indicating ping status
776
802
  - 'hostname': Optional resolved hostname from reverse DNS
803
+ desc: Optional description for the host
777
804
  timeout: Maximum time to wait for connection in seconds
778
805
 
779
806
  Returns:
@@ -783,6 +810,7 @@ def check_port(hostname: str,
783
810
  - port: The port number that was checked
784
811
  - status: Connection status (CONNECTED/TIMEOUT/REFUSED/UNREACHABLE)
785
812
  - ping: Boolean indicating if the host responded to ping
813
+ - desc: Optional description for the host
786
814
 
787
815
  Status meanings:
788
816
  CONNECTED: Successfully established TCP connection
@@ -792,7 +820,7 @@ def check_port(hostname: str,
792
820
  RESOLVE_FAIL: Could not resolve hostname to IP
793
821
  """
794
822
  if host_info['ip'] == 'N/A':
795
- return (hostname, host_info['ip'], port, 'RESOLVE_FAIL', host_info['ping'])
823
+ return (hostname, host_info['ip'], port, 'RESOLVE_FAIL', host_info['ping'], desc)
796
824
 
797
825
  # Use resolved hostname if available
798
826
  display_hostname = host_info.get('hostname', hostname)
@@ -802,16 +830,16 @@ def check_port(hostname: str,
802
830
  try:
803
831
  s.connect((host_info['ip'], port))
804
832
  s.close()
805
- return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'])
833
+ return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'], desc)
806
834
  except ConnectionAbortedError:
807
- return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'])
835
+ return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'], desc)
808
836
  except (TimeoutError, socket.timeout):
809
- return (display_hostname, host_info['ip'], port, 'TIMEOUT', host_info['ping'])
837
+ return (display_hostname, host_info['ip'], port, 'TIMEOUT', host_info['ping'], desc)
810
838
  except ConnectionRefusedError:
811
- return (display_hostname, host_info['ip'], port, 'REFUSED', host_info['ping'])
839
+ return (display_hostname, host_info['ip'], port, 'REFUSED', host_info['ping'], desc)
812
840
  except Exception:
813
841
  # Handle other network errors (filtered, network unreachable, etc)
814
- return (display_hostname, host_info['ip'], port, 'UNREACHABLE', host_info['ping'])
842
+ return (display_hostname, host_info['ip'], port, 'UNREACHABLE', host_info['ping'], desc)
815
843
 
816
844
  def send_email_report(
817
845
  output_file: str,
@@ -979,7 +1007,7 @@ def compute_stats(
979
1007
 
980
1008
 
981
1009
  # Collect VLAN statistics for timeouts
982
- for hostname, ip, port, status, _ in results:
1010
+ for hostname, ip, port, status, _, _ in results:
983
1011
  if ip != 'N/A':
984
1012
  try:
985
1013
  vlan = get_vlan_base(ip, bits)
@@ -993,7 +1021,7 @@ def compute_stats(
993
1021
  # Group results by hostname for host statistics
994
1022
  host_stats = defaultdict(lambda: {'statuses': [], 'ping': False})
995
1023
  for result in results:
996
- hostname, _, _, status, ping = result
1024
+ hostname, _, _, status, ping, _ = result
997
1025
  host_stats[hostname]['statuses'].append(status)
998
1026
  host_stats[hostname]['ping'] |= ping
999
1027
 
@@ -1020,7 +1048,7 @@ def compute_stats(
1020
1048
  })
1021
1049
  })
1022
1050
 
1023
- for hostname, ip, port, status, _ in results:
1051
+ for hostname, ip, port, status, _, _ in results:
1024
1052
  # For IP addresses, use VLAN/16 as domain
1025
1053
  try:
1026
1054
  socket.inet_aton(hostname)
@@ -1138,7 +1166,8 @@ def generate_html_report(
1138
1166
  parallelism: int,
1139
1167
  noping: bool,
1140
1168
  stats: Dict[str, Any],
1141
- input_file: str = ''
1169
+ input_file: str = '',
1170
+ desc_titles: List[str] = [],
1142
1171
  ) -> None:
1143
1172
  """Generate a complete HTML report of the port scan results.
1144
1173
 
@@ -1183,6 +1212,9 @@ def generate_html_report(
1183
1212
  f.write(f'<h3 class="icon">Port Accessibility Report from {HOSTNAME} ({MY_IP}) to {os.path.basename(input_file)} - {time.strftime("%Y-%m-%d %H:%M:%S", scan_time)}</h3>\n')
1184
1213
 
1185
1214
  # Write detailed results table
1215
+ if not desc_titles:
1216
+ _, _, _, _, _, desc = results[0]
1217
+ desc_titles = ["Description" for d in desc.split("|")]
1186
1218
  f.write(f'''
1187
1219
  <div class="table-container" id="result-container">
1188
1220
  <table id="commandTable">
@@ -1194,13 +1226,14 @@ def generate_html_report(
1194
1226
  <th>Port</th>
1195
1227
  <th>Status</th>
1196
1228
  <th>Ping</th>
1229
+ {"\n".join([f"<th>{d}</th>" for d in desc_titles])}
1197
1230
  </tr>
1198
1231
  </thead>
1199
1232
  <tbody>
1200
1233
  ''')
1201
1234
 
1202
1235
  # Add result rows
1203
- for hostname, ip, port, status, ping in results:
1236
+ for hostname, ip, port, status, ping, desc in results:
1204
1237
  ping_status = 'UP' if ping else 'N/A' if noping else 'DOWN'
1205
1238
  ping_class = 'green' if ping else 'blue' if noping else 'red'
1206
1239
  status_class = 'green' if status == 'CONNECTED' else 'blue' if status == 'REFUSED' else 'red'
@@ -1212,6 +1245,7 @@ def generate_html_report(
1212
1245
  <td style="text-align: right;">{port}</td>
1213
1246
  <td style="text-align: center;"><span class="{status_class} status">{escape(status)}</span></td>
1214
1247
  <td style="text-align: center;"><span class="{ping_class} ping">{ping_status}</span></td>
1248
+ {"\n".join([f'<td class="desc">{escape(str(d))}</td>' for d in desc.split("|")])}
1215
1249
  </tr>
1216
1250
  ''')
1217
1251
 
@@ -1374,7 +1408,7 @@ def format_table_output(
1374
1408
  table.append(separator)
1375
1409
 
1376
1410
  # Add data rows
1377
- for hostname, ip, port, status, ping in results:
1411
+ for hostname, ip, port, status, ping, desc in results:
1378
1412
  ping_status = 'UP' if ping else 'N/A' if noping else 'DOWN'
1379
1413
  table.append(row_format.format(
1380
1414
  str(hostname), widths['Hostname'],
@@ -1395,7 +1429,7 @@ def main():
1395
1429
  parser.add_argument('-n', '--noping', action="store_true", help='No ping check')
1396
1430
  parser.add_argument('-s', '--summary', action="store_true", help='Print scan summary information')
1397
1431
  parser.add_argument('-b', '--bits', type=int, default=16, help='VLAN bits for timeout summary (default: 16)')
1398
-
1432
+ parser.add_argument('-d', '--desc_titles', type=str, nargs='*', help='List of custom description titles for hosts (optional)')
1399
1433
  # Email related arguments
1400
1434
  email_group = parser.add_argument_group('Email Options')
1401
1435
  email_group.add_argument('--email-to', help='Comma-separated list of email recipients')
@@ -1420,14 +1454,14 @@ def main():
1420
1454
  host_info = ping_hosts(hosts, args.timeout, args.parallelism, args.noping)
1421
1455
 
1422
1456
  # Calculate total tasks and initialize progress bar
1423
- total_tasks = sum(len(ports) for _, ports in hosts)
1457
+ total_tasks = sum(len(ports) for _, ports, _ in hosts)
1424
1458
  print(f"Preparing to scan {len(hosts)} hosts with {total_tasks} total ports...", file=sys.stderr)
1425
1459
 
1426
1460
  # Prepare tasks with pre-resolved data
1427
1461
  tasks = []
1428
- for hostname, ports in hosts:
1462
+ for hostname, ports, desc in hosts:
1429
1463
  for port in ports:
1430
- tasks.append((hostname, port, host_info[hostname]))
1464
+ tasks.append((hostname, port, host_info[hostname], desc))
1431
1465
 
1432
1466
  results = []
1433
1467
  lock = threading.Lock()
@@ -1436,8 +1470,8 @@ def main():
1436
1470
  progress_bar = ProgressBar(total_tasks, prefix='Scanning')
1437
1471
 
1438
1472
  with ThreadPoolExecutor(max_workers=args.parallelism) as executor:
1439
- future_to_task = {executor.submit(check_port, hostname, port, info, args.timeout): (hostname, port, info)
1440
- for hostname, port, info in tasks}
1473
+ future_to_task = {executor.submit(check_port, hostname, port, info, desc, args.timeout): (hostname, port, info)
1474
+ for hostname, port, info, desc in tasks}
1441
1475
 
1442
1476
  for future in as_completed(future_to_task):
1443
1477
  res = future.result()
@@ -1453,7 +1487,17 @@ def main():
1453
1487
 
1454
1488
  # Generate report
1455
1489
  results.sort(key=lambda x: (x[0], x[2]))
1456
- generate_html_report(results, args.output, time.localtime(start_time), args.timeout, args.parallelism, args.noping, stats, args.input_file)
1490
+ generate_html_report(
1491
+ results,
1492
+ args.output,
1493
+ time.localtime(start_time),
1494
+ args.timeout,
1495
+ args.parallelism,
1496
+ args.noping,
1497
+ stats,
1498
+ args.input_file,
1499
+ args.desc_titles,
1500
+ )
1457
1501
 
1458
1502
  # Print summary
1459
1503
  # Display detailed results in table format
portune/version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.2'
21
- __version_tuple__ = version_tuple = (0, 1, 2)
20
+ __version__ = version = '0.1.4'
21
+ __version_tuple__ = version_tuple = (0, 1, 4)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portune
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Author: Franck Jouvanceau
6
6
  Maintainer: Franck Jouvanceau
@@ -58,7 +58,7 @@ Multitreaded port scanner
58
58
 
59
59
  # features
60
60
 
61
- * parallel check of port availability against server/ports
61
+ * parallel check of port availability against servers/ports
62
62
  * console output result / summary
63
63
  * html full report / dns domain summary / vlan timeout summary
64
64
  * mail with html summary / report attachment (mailhost relay)
@@ -0,0 +1,9 @@
1
+ portune/__init__.py,sha256=RfXuNfHBqfRt_z4IukwN1a0oeCXahuMOO8_eBt4T8NM,58
2
+ portune/portune.py,sha256=Ar5GLB8hCTV9C1NbFKRKoK2nlyRReiDz5uMK5yXmb28,63495
3
+ portune/version.py,sha256=hcPkC9vIGgfrKK6ft7ysLT7iOCjpFmCBmyKLmXiaZ1g,511
4
+ portune-0.1.4.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
5
+ portune-0.1.4.dist-info/METADATA,sha256=qtTM2_95cVJjfAELw9v7QP7hQijJUotJEGnC2SGq2hQ,2838
6
+ portune-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ portune-0.1.4.dist-info/entry_points.txt,sha256=6j7jAf5fOZrLPbVIs3z92R1tT891WyY9YxQ6OVnPKG0,80
8
+ portune-0.1.4.dist-info/top_level.txt,sha256=CITDikHhRKAsSOGmNJzD-xSp6D5iBhSr9ZS1qy8-SL0,8
9
+ portune-0.1.4.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- portune/__init__.py,sha256=RfXuNfHBqfRt_z4IukwN1a0oeCXahuMOO8_eBt4T8NM,58
2
- portune/portune.py,sha256=ODPpO7E9-pXoFRxKavyn22_b9dhVjxUp-aIxKhnxamY,61908
3
- portune/version.py,sha256=bSmADqydH8nBu-J4lG8UVuR7hnU_zcwhnSav2oQ0W0A,511
4
- portune-0.1.2.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
5
- portune-0.1.2.dist-info/METADATA,sha256=qEkffZiY8znnWihHuYtJlzf7t4bRPFTPvCcajLqmYBs,2837
6
- portune-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- portune-0.1.2.dist-info/entry_points.txt,sha256=6j7jAf5fOZrLPbVIs3z92R1tT891WyY9YxQ6OVnPKG0,80
8
- portune-0.1.2.dist-info/top_level.txt,sha256=CITDikHhRKAsSOGmNJzD-xSp6D5iBhSr9ZS1qy8-SL0,8
9
- portune-0.1.2.dist-info/RECORD,,