portune 0.1.1__tar.gz → 0.1.3__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portune
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Author: Franck Jouvanceau
6
6
  Maintainer: Franck Jouvanceau
@@ -78,7 +78,16 @@ div {
78
78
  text-align: right;
79
79
  padding-right: 8px;
80
80
  }
81
-
81
+ .desc {
82
+ text-overflow: ellipsis;
83
+ max-width: 300px;
84
+ white-space: nowrap;
85
+ overflow: hidden;
86
+ }
87
+ .desc:hover {
88
+ overflow: visible;
89
+ white-space: normal;
90
+ }
82
91
  .table-container {
83
92
  height: 90%;
84
93
  overflow-y: auto;
@@ -600,6 +609,7 @@ def parse_input_file(filename: str) -> List[Tuple[str, List[int]]]:
600
609
  A list of tuples, each containing:
601
610
  - hostname (str): The target hostname or IP address
602
611
  - ports (List[int]): List of ports to scan for that host
612
+ - desc (str): Optional description for the host
603
613
 
604
614
  Examples:
605
615
  Input file format:
@@ -615,19 +625,26 @@ def parse_input_file(filename: str) -> List[Tuple[str, List[int]]]:
615
625
  ('host3', [8080])
616
626
  ]
617
627
  """
618
- hosts = []
628
+ host_dict = {}
619
629
  with open(filename, 'r') as f:
620
630
  for line in f:
621
631
  line = line.strip().lower()
622
632
  if not line or line.startswith('#'):
623
633
  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
634
+ words = line.split()
635
+ fqdn = words[0]
636
+ ports = words[1] if len(words) > 1 else '22'
637
+ port_list = [int(p) for p in ports.split(',')]
638
+ desc = ' '.join(words[2:]).strip() if len(words) > 2 else ''
639
+ if fqdn in host_dict:
640
+ existing_ports, existing_desc = host_dict[fqdn]
641
+ host_dict[fqdn] = (list(set(existing_ports + port_list)), existing_desc or desc)
642
+ else:
643
+ host_dict[fqdn] = (port_list, desc)
644
+ hosts = []
645
+ for fqdn in host_dict:
646
+ ports, desc = host_dict[fqdn]
647
+ hosts.append((fqdn, sorted(ports), desc))
631
648
  return hosts
632
649
 
633
650
  def ping_host(ip: str, timeout: float = 2.0) -> bool:
@@ -698,7 +715,7 @@ def resolve_and_ping_host(hostname: str, timeout: float = 2.0, noping: bool = Fa
698
715
  except Exception:
699
716
  return hostname, {'ip': 'N/A', 'ping': False}
700
717
 
701
- def ping_hosts(hosts: List[Tuple[str, List[int]]],
718
+ def ping_hosts(hosts: List[Tuple[str, List[int], str]],
702
719
  timeout: float = 2.0,
703
720
  parallelism: int = 10,
704
721
  noping: bool = False) -> Dict[str, Dict[str, Union[str, bool]]]:
@@ -739,7 +756,7 @@ def ping_hosts(hosts: List[Tuple[str, List[int]]],
739
756
  with ThreadPoolExecutor(max_workers=parallelism) as executor:
740
757
  future_to_host = {
741
758
  executor.submit(resolve_and_ping_host, hostname, timeout, noping): hostname
742
- for hostname, _ in hosts
759
+ for hostname, _, _ in hosts
743
760
  }
744
761
 
745
762
  # Add callback to update progress bar when each future completes
@@ -760,7 +777,8 @@ def ping_hosts(hosts: List[Tuple[str, List[int]]],
760
777
 
761
778
  def check_port(hostname: str,
762
779
  port: int,
763
- host_info: Dict[str, Union[str, bool]],
780
+ host_info: Dict[str, Union[str, bool]],
781
+ desc: str,
764
782
  timeout: float = 2.0) -> Tuple[str, str, int, str, bool]:
765
783
  """Check if a specific TCP port is accessible on a host.
766
784
 
@@ -774,6 +792,7 @@ def check_port(hostname: str,
774
792
  - 'ip': IP address or 'N/A' if resolution failed
775
793
  - 'ping': Boolean indicating ping status
776
794
  - 'hostname': Optional resolved hostname from reverse DNS
795
+ desc: Optional description for the host
777
796
  timeout: Maximum time to wait for connection in seconds
778
797
 
779
798
  Returns:
@@ -783,6 +802,7 @@ def check_port(hostname: str,
783
802
  - port: The port number that was checked
784
803
  - status: Connection status (CONNECTED/TIMEOUT/REFUSED/UNREACHABLE)
785
804
  - ping: Boolean indicating if the host responded to ping
805
+ - desc: Optional description for the host
786
806
 
787
807
  Status meanings:
788
808
  CONNECTED: Successfully established TCP connection
@@ -792,7 +812,7 @@ def check_port(hostname: str,
792
812
  RESOLVE_FAIL: Could not resolve hostname to IP
793
813
  """
794
814
  if host_info['ip'] == 'N/A':
795
- return (hostname, host_info['ip'], port, 'RESOLVE_FAIL', host_info['ping'])
815
+ return (hostname, host_info['ip'], port, 'RESOLVE_FAIL', host_info['ping'], desc)
796
816
 
797
817
  # Use resolved hostname if available
798
818
  display_hostname = host_info.get('hostname', hostname)
@@ -802,16 +822,16 @@ def check_port(hostname: str,
802
822
  try:
803
823
  s.connect((host_info['ip'], port))
804
824
  s.close()
805
- return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'])
825
+ return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'], desc)
806
826
  except ConnectionAbortedError:
807
- return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'])
827
+ return (display_hostname, host_info['ip'], port, 'CONNECTED', host_info['ping'], desc)
808
828
  except (TimeoutError, socket.timeout):
809
- return (display_hostname, host_info['ip'], port, 'TIMEOUT', host_info['ping'])
829
+ return (display_hostname, host_info['ip'], port, 'TIMEOUT', host_info['ping'], desc)
810
830
  except ConnectionRefusedError:
811
- return (display_hostname, host_info['ip'], port, 'REFUSED', host_info['ping'])
831
+ return (display_hostname, host_info['ip'], port, 'REFUSED', host_info['ping'], desc)
812
832
  except Exception:
813
833
  # Handle other network errors (filtered, network unreachable, etc)
814
- return (display_hostname, host_info['ip'], port, 'UNREACHABLE', host_info['ping'])
834
+ return (display_hostname, host_info['ip'], port, 'UNREACHABLE', host_info['ping'], desc)
815
835
 
816
836
  def send_email_report(
817
837
  output_file: str,
@@ -906,6 +926,29 @@ def format_percent(value: int, total: int) -> Tuple[str, str]:
906
926
  return "0/0", "0.0%"
907
927
  return f"{value}/{total}", f"{(value/total*100):.1f}%"
908
928
 
929
+ def get_vlan_base(ip: str, bits: int) -> str:
930
+ """Calculate VLAN base address with end padding 0."""
931
+ if ip == 'N/A':
932
+ return 'N/A'
933
+ try:
934
+ octets = ip.split('.')
935
+ if len(octets) != 4:
936
+ return 'invalid'
937
+
938
+ # Convert IP to 32-bit integer
939
+ ip_int = sum(int(octet) << (24 - 8 * i) for i, octet in enumerate(octets))
940
+
941
+ # Apply mask
942
+ mask = ((1 << bits) - 1) << (32 - bits)
943
+ masked_ip = ip_int & mask
944
+
945
+ # Convert back to dotted notation
946
+ result_octets = [(masked_ip >> (24 - 8 * i)) & 255 for i in range(4)]
947
+ return '.'.join(map(str, result_octets))
948
+ except:
949
+ return 'N/A'
950
+
951
+
909
952
  def compute_stats(
910
953
  results: List[Tuple[str, str, int, str, bool]],
911
954
  start_time: float,
@@ -954,28 +997,9 @@ def compute_stats(
954
997
  }
955
998
 
956
999
 
957
- def get_vlan_base(ip: str, bits: int) -> str:
958
- """Calculate VLAN base address with end padding 0."""
959
- try:
960
- octets = ip.split('.')
961
- if len(octets) != 4:
962
- return 'invalid'
963
-
964
- # Convert IP to 32-bit integer
965
- ip_int = sum(int(octet) << (24 - 8 * i) for i, octet in enumerate(octets))
966
-
967
- # Apply mask
968
- mask = ((1 << bits) - 1) << (32 - bits)
969
- masked_ip = ip_int & mask
970
-
971
- # Convert back to dotted notation
972
- result_octets = [(masked_ip >> (24 - 8 * i)) & 255 for i in range(4)]
973
- return '.'.join(map(str, result_octets))
974
- except:
975
- return 'invalid'
976
1000
 
977
1001
  # Collect VLAN statistics for timeouts
978
- for hostname, ip, port, status, _ in results:
1002
+ for hostname, ip, port, status, _, _ in results:
979
1003
  if ip != 'N/A':
980
1004
  try:
981
1005
  vlan = get_vlan_base(ip, bits)
@@ -989,7 +1013,7 @@ def compute_stats(
989
1013
  # Group results by hostname for host statistics
990
1014
  host_stats = defaultdict(lambda: {'statuses': [], 'ping': False})
991
1015
  for result in results:
992
- hostname, _, _, status, ping = result
1016
+ hostname, _, _, status, ping, _ = result
993
1017
  host_stats[hostname]['statuses'].append(status)
994
1018
  host_stats[hostname]['ping'] |= ping
995
1019
 
@@ -1016,7 +1040,7 @@ def compute_stats(
1016
1040
  })
1017
1041
  })
1018
1042
 
1019
- for hostname, ip, port, status, _ in results:
1043
+ for hostname, ip, port, status, _, _ in results:
1020
1044
  # For IP addresses, use VLAN/16 as domain
1021
1045
  try:
1022
1046
  socket.inet_aton(hostname)
@@ -1179,23 +1203,25 @@ def generate_html_report(
1179
1203
  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')
1180
1204
 
1181
1205
  # Write detailed results table
1182
- f.write('''
1206
+ f.write(f'''
1183
1207
  <div class="table-container" id="result-container">
1184
1208
  <table id="commandTable">
1185
1209
  <thead>
1186
1210
  <tr>
1187
1211
  <th>Hostname</th>
1188
1212
  <th>IP</th>
1213
+ <th>VLAN/{stats['vlan_bits']}</th>
1189
1214
  <th>Port</th>
1190
1215
  <th>Status</th>
1191
1216
  <th>Ping</th>
1217
+ <th>Description</th>
1192
1218
  </tr>
1193
1219
  </thead>
1194
1220
  <tbody>
1195
1221
  ''')
1196
1222
 
1197
1223
  # Add result rows
1198
- for hostname, ip, port, status, ping in results:
1224
+ for hostname, ip, port, status, ping, desc in results:
1199
1225
  ping_status = 'UP' if ping else 'N/A' if noping else 'DOWN'
1200
1226
  ping_class = 'green' if ping else 'blue' if noping else 'red'
1201
1227
  status_class = 'green' if status == 'CONNECTED' else 'blue' if status == 'REFUSED' else 'red'
@@ -1203,9 +1229,11 @@ def generate_html_report(
1203
1229
  <tr>
1204
1230
  <td>{escape(str(hostname))}</td>
1205
1231
  <td>{escape(str(ip))}</td>
1232
+ <td>{str(get_vlan_base(ip, stats['vlan_bits']))}</td>
1206
1233
  <td style="text-align: right;">{port}</td>
1207
1234
  <td style="text-align: center;"><span class="{status_class} status">{escape(status)}</span></td>
1208
1235
  <td style="text-align: center;"><span class="{ping_class} ping">{ping_status}</span></td>
1236
+ <td class="desc">{escape(str(desc))}</td>
1209
1237
  </tr>
1210
1238
  ''')
1211
1239
 
@@ -1368,7 +1396,7 @@ def format_table_output(
1368
1396
  table.append(separator)
1369
1397
 
1370
1398
  # Add data rows
1371
- for hostname, ip, port, status, ping in results:
1399
+ for hostname, ip, port, status, ping, desc in results:
1372
1400
  ping_status = 'UP' if ping else 'N/A' if noping else 'DOWN'
1373
1401
  table.append(row_format.format(
1374
1402
  str(hostname), widths['Hostname'],
@@ -1414,14 +1442,14 @@ def main():
1414
1442
  host_info = ping_hosts(hosts, args.timeout, args.parallelism, args.noping)
1415
1443
 
1416
1444
  # Calculate total tasks and initialize progress bar
1417
- total_tasks = sum(len(ports) for _, ports in hosts)
1445
+ total_tasks = sum(len(ports) for _, ports, _ in hosts)
1418
1446
  print(f"Preparing to scan {len(hosts)} hosts with {total_tasks} total ports...", file=sys.stderr)
1419
1447
 
1420
1448
  # Prepare tasks with pre-resolved data
1421
1449
  tasks = []
1422
- for hostname, ports in hosts:
1450
+ for hostname, ports, desc in hosts:
1423
1451
  for port in ports:
1424
- tasks.append((hostname, port, host_info[hostname]))
1452
+ tasks.append((hostname, port, host_info[hostname], desc))
1425
1453
 
1426
1454
  results = []
1427
1455
  lock = threading.Lock()
@@ -1430,8 +1458,8 @@ def main():
1430
1458
  progress_bar = ProgressBar(total_tasks, prefix='Scanning')
1431
1459
 
1432
1460
  with ThreadPoolExecutor(max_workers=args.parallelism) as executor:
1433
- future_to_task = {executor.submit(check_port, hostname, port, info, args.timeout): (hostname, port, info)
1434
- for hostname, port, info in tasks}
1461
+ future_to_task = {executor.submit(check_port, hostname, port, info, desc, args.timeout): (hostname, port, info)
1462
+ for hostname, port, info, desc in tasks}
1435
1463
 
1436
1464
  for future in as_completed(future_to_task):
1437
1465
  res = future.result()
@@ -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.1'
21
- __version_tuple__ = version_tuple = (0, 1, 1)
20
+ __version__ = version = '0.1.3'
21
+ __version_tuple__ = version_tuple = (0, 1, 3)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portune
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Author: Franck Jouvanceau
6
6
  Maintainer: Franck Jouvanceau
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes