portune 0.1.3__tar.gz → 0.1.5__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.3
3
+ Version: 0.1.5
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Author: Franck Jouvanceau
6
6
  Maintainer: Franck Jouvanceau
@@ -52,13 +52,19 @@ Description-Content-Type: text/markdown
52
52
  License-File: LICENSE
53
53
  Dynamic: license-file
54
54
 
55
+ [![Pypi version](https://img.shields.io/pypi/v/portune.svg)](https://pypi.org/project/portune/)
56
+ ![example](https://github.com/joknarf/portune/actions/workflows/python-publish.yml/badge.svg)
57
+ [![Licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://shields.io/)
58
+ [![](https://pepy.tech/badge/portune)](https://pepy.tech/project/portune)
59
+ [![Python versions](https://img.shields.io/badge/python-3.9+-blue.svg)](https://shields.io/)
60
+
55
61
  # portune
56
62
 
57
63
  Multitreaded port scanner
58
64
 
59
65
  # features
60
66
 
61
- * parallel check of port availability against server/ports
67
+ * parallel check of port availability against servers/ports
62
68
  * console output result / summary
63
69
  * html full report / dns domain summary / vlan timeout summary
64
70
  * mail with html summary / report attachment (mailhost relay)
@@ -0,0 +1,17 @@
1
+ [![Pypi version](https://img.shields.io/pypi/v/portune.svg)](https://pypi.org/project/portune/)
2
+ ![example](https://github.com/joknarf/portune/actions/workflows/python-publish.yml/badge.svg)
3
+ [![Licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://shields.io/)
4
+ [![](https://pepy.tech/badge/portune)](https://pepy.tech/project/portune)
5
+ [![Python versions](https://img.shields.io/badge/python-3.9+-blue.svg)](https://shields.io/)
6
+
7
+ # portune
8
+
9
+ Multitreaded port scanner
10
+
11
+ # features
12
+
13
+ * parallel check of port availability against servers/ports
14
+ * console output result / summary
15
+ * html full report / dns domain summary / vlan timeout summary
16
+ * mail with html summary / report attachment (mailhost relay)
17
+
@@ -16,6 +16,7 @@ from __future__ import annotations
16
16
  # Standard library imports
17
17
  import os
18
18
  import sys
19
+ import platform
19
20
  import argparse
20
21
  import socket
21
22
  import threading
@@ -32,13 +33,16 @@ from typing import List, Dict, Tuple, Any, Optional, Union
32
33
 
33
34
  # Constants and global variables
34
35
  ICON = "data:image/svg+xml,%3Csvg height='200px' width='200px' version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 508 508' xml:space='preserve' fill='%23000000'%3E%3Cg id='SVGRepo_bgCarrier' stroke-width='0'%3E%3C/g%3E%3Cg id='SVGRepo_tracerCarrier' stroke-linecap='round' stroke-linejoin='round'%3E%3C/g%3E%3Cg id='SVGRepo_iconCarrier'%3E%3Ccircle style='fill:%23ffbb06;' cx='254' cy='254' r='254'%3E%3C/circle%3E%3Cg%3E%3Ccircle style='fill:%234c4c4c;' cx='399.6' cy='252' r='55.2'%3E%3C/circle%3E%3Ccircle style='fill:%234c4c4c;' cx='150' cy='356' r='55.2'%3E%3C/circle%3E%3Ccircle style='fill:%234c4c4c;' cx='178.4' cy='127.2' r='55.2'%3E%3C/circle%3E%3C/g%3E%3Cpath style='fill:%23000000;' d='M301.2,282.4l15.6,1.2l6.4-19.2l-13.2-8c0.4-5.6,0-11.2-1.2-16.4l12-10l-9.2-18l-15.2,3.6 c-3.6-4-7.6-8-12.4-10.8l1.2-15.6l-19.2-6.4l-8,13.2c-5.6-0.4-11.2,0-16.4,1.2l-10-12l-18,8.8l3.6,15.2c-4,3.6-8,7.6-10.8,12.4 l-15.6-1.2l-6.4,19.2l13.2,8.4c-0.4,5.6,0,11.2,1.2,16.4l-12,10l9.2,18l15.2-3.6c3.6,4,7.6,8,12.4,10.8l-1.6,15.2l19.2,6.4l8.4-13.2 c5.6,0.4,11.2,0,16.4-1.2l10,12l18-8.8l-3.6-15.2C294.4,291.2,298.4,287.2,301.2,282.4z M242.4,286c-18.8-6.4-28.8-26.4-22.8-45.2 c6.4-18.8,26.8-29.2,45.6-22.8c18.8,6.4,28.8,26.4,22.8,45.2C281.6,282,261.2,292.4,242.4,286z'%3E%3C/path%3E%3Cpath style='fill:%23324A5E;' d='M380.4,304c-20.4,50-69.6,85.2-126.8,85.2c-18.8,0-36.8-4-53.6-10.8c-2.4,5.6-5.6,10.4-9.6,14.8 c19.2,8.8,40.8,13.6,63.2,13.6c65.6,0,122-41.2,144.4-99.2C392,307.2,386,306,380.4,304z M132,157.2c-20.4,26-32.8,59.2-32.8,94.8 c0,22.4,4.8,43.6,13.6,62.8c4.4-4,9.2-7.2,14.8-9.6c-6.8-16.4-10.8-34.4-10.8-53.2c0-30.4,10-58.8,27.2-81.6 C139.2,166.8,135.2,162.4,132,157.2z M253.6,97.6c-9.2,0-18.4,0.8-27.6,2.4c2.8,5.2,5.2,10.8,6.4,16.8c6.8-1.2,14-1.6,21.2-1.6 c57.2,0,106.4,35.2,126.8,85.2c5.6-2,11.2-3.2,17.2-3.2C375.6,138.8,319.6,97.6,253.6,97.6z'%3E%3C/path%3E%3Cg%3E%3Ccircle style='fill:%23FF7058;' cx='399.6' cy='252' r='28.4'%3E%3C/circle%3E%3Ccircle style='fill:%23FF7058;' cx='150' cy='356' r='28.4'%3E%3C/circle%3E%3Ccircle style='fill:%23FF7058;' cx='178.4' cy='127.2' r='28.4'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/svg%3E"
36
+ XL='data:image/svg+xml,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M12.5535 16.5061C12.4114 16.6615 12.2106 16.75 12 16.75C11.7894 16.75 11.5886 16.6615 11.4465 16.5061L7.44648 12.1311C7.16698 11.8254 7.18822 11.351 7.49392 11.0715C7.79963 10.792 8.27402 10.8132 8.55352 11.1189L11.25 14.0682V3C11.25 2.58579 11.5858 2.25 12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V14.0682L15.4465 11.1189C15.726 10.8132 16.2004 10.792 16.5061 11.0715C16.8118 11.351 16.833 11.8254 16.5535 12.1311L12.5535 16.5061Z" fill="%23eee"></path><path d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z" fill="%23eee"></path></g></svg>'
35
37
  CSS="""
36
38
  <style>
37
39
  body {
38
40
  font-family: Arial, sans-serif;
39
41
  overflow: auto;
40
42
  }
41
-
43
+ .hidden {
44
+ display: none;
45
+ }
42
46
  .icon {
43
47
  border-radius: 25px;
44
48
  background-color: #444;
@@ -80,7 +84,7 @@ div {
80
84
  }
81
85
  .desc {
82
86
  text-overflow: ellipsis;
83
- max-width: 300px;
87
+ max-width: 200px;
84
88
  white-space: nowrap;
85
89
  overflow: hidden;
86
90
  }
@@ -134,7 +138,7 @@ th {
134
138
  }
135
139
  .sort-btn {
136
140
  color: #aaa;
137
- font-size: 14px;
141
+ font-size: 10px;
138
142
  user-select: none;
139
143
  display: inline-block;
140
144
  width: 7px;
@@ -216,13 +220,18 @@ th {
216
220
  border-radius: 17px;
217
221
  /* min-width: 17px; */
218
222
  text-align: center;
219
- padding: 0px 4px;
223
+ /* text-indent: 14px;*/
224
+ padding: 0px 18px 0px 4px;
220
225
  margin-left: auto;
221
226
  cursor: pointer;
227
+ background-image: url('{XL}');
228
+ background-repeat: no-repeat;
229
+ background-position: right 4px center;
230
+ background-size: 12px 12px;
222
231
  }
223
232
 
224
233
  </style>
225
- """.replace("{ICON}", ICON)
234
+ """.replace("{ICON}", ICON).replace("{XL}", XL)
226
235
  JS="""
227
236
  <script src=https://unpkg.com/exceljs@4.1.1/dist/exceljs.min.js></script>
228
237
  <script>
@@ -236,18 +245,25 @@ function initTableFilters(table) {
236
245
  // Add sort button first
237
246
  const sortBtn = document.createElement('span');
238
247
  sortBtn.className = 'sort-btn';
239
- sortBtn.innerHTML = '&#11014;'; // Unicode for sort icon
248
+ sortBtn.innerHTML = '▲' //'&#11014;'; // Unicode for sort icon
240
249
  sortBtn.style.cursor = 'pointer';
241
250
  sortBtn.setAttribute('data-sort-order', '');
242
251
  sortBtn.onclick = () => toggleSort(table, index, sortBtn);
243
-
252
+
253
+ const titleSpan = document.createElement('span');
254
+ titleSpan.className = 'th-title';
255
+ titleSpan.innerHTML = header.innerHTML;
256
+
244
257
  // Move existing elements into the content span
245
- while (header.firstChild) {
246
- contentSpan.appendChild(header.firstChild);
247
- }
258
+ //while (header.firstChild) {
259
+ // contentSpan.appendChild(header.firstChild);
260
+ //}
248
261
 
249
262
  // Add sort button at the beginning
250
- contentSpan.insertBefore(sortBtn, contentSpan.firstChild);
263
+ contentSpan.appendChild(sortBtn);
264
+ contentSpan.appendChild(titleSpan);
265
+
266
+ //contentSpan.insertBefore(sortBtn, contentSpan.firstChild);
251
267
 
252
268
  // Add export button and row counter for last column
253
269
  if (index === headers.length - 1) {
@@ -259,6 +275,7 @@ function initTableFilters(table) {
259
275
  contentSpan.appendChild(rowCount);
260
276
  }
261
277
 
278
+ header.innerHTML = '';
262
279
  header.appendChild(contentSpan);
263
280
 
264
281
  // Add filter input
@@ -277,7 +294,7 @@ function initTableFilters(table) {
277
294
  function updateRowCount(table, count) {
278
295
  const rowCount = table.querySelector('.row-count');
279
296
  if (rowCount) {
280
- rowCount.innerHTML = `<span>&#9660;</span> ${count}`; // &#129095;🡇 not working macOS
297
+ rowCount.innerHTML = ` ${count}`; // &#129095;🡇 not working macOS
281
298
  }
282
299
  }
283
300
 
@@ -286,7 +303,7 @@ function toggleSort(table, colIndex, sortBtn) {
286
303
  table.querySelectorAll('.sort-btn').forEach(btn => {
287
304
  if (btn !== sortBtn) {
288
305
  btn.setAttribute('data-sort-order', '');
289
- btn.innerHTML = '&#11014;';
306
+ btn.innerHTML = '▲'; //'&#11014;';
290
307
  }
291
308
  });
292
309
 
@@ -295,12 +312,12 @@ function toggleSort(table, colIndex, sortBtn) {
295
312
  let newOrder = 'asc';
296
313
  if (currentOrder === 'asc') {
297
314
  newOrder = 'desc';
298
- sortBtn.innerHTML = '&#11015;';
315
+ sortBtn.innerHTML = '▼'; //'&#11015;';
299
316
  } else if (currentOrder === 'desc') {
300
317
  newOrder = '';
301
- sortBtn.innerHTML = '&#11014;';
318
+ sortBtn.innerHTML = '▲'; //'&#11014;';
302
319
  } else {
303
- sortBtn.innerHTML = '&#11014;';
320
+ sortBtn.innerHTML = '▲'; //'&#11014;';
304
321
  }
305
322
  sortBtn.setAttribute('data-sort-order', newOrder);
306
323
  sortBtn.setAttribute('data-col-index', colIndex); // Store column index on the button
@@ -308,6 +325,7 @@ function toggleSort(table, colIndex, sortBtn) {
308
325
  }
309
326
 
310
327
  function applyFilters(table) {
328
+ table.classList.add('hidden');
311
329
  const rows = Array.from(table.querySelectorAll('tbody tr'));
312
330
  const filters = Array.from(table.querySelectorAll('.column-filter'))
313
331
  .map(filter => ({
@@ -322,10 +340,10 @@ function applyFilters(table) {
322
340
  // First apply filters
323
341
  const filteredRows = rows.filter(row => {
324
342
  // If no filters are active, show all rows
325
- if (filters.every(f => !f.value)) {
326
- row.style.display = '';
327
- return true;
328
- }
343
+ //if (filters.every(f => !f.value)) {
344
+ // row.classList.remove('hidden');
345
+ // return true;
346
+ //}
329
347
  const cells = row.cells;
330
348
  const shouldShow = !filters.some(filter => {
331
349
  if (!filter.value) return false;
@@ -333,7 +351,11 @@ function applyFilters(table) {
333
351
  if (filter.regexp) return !filter.regexp.test(cellText);
334
352
  return !cellText.toLowerCase().includes(filter.value);
335
353
  });
336
- row.style.display = shouldShow ? '' : 'none';
354
+ if (shouldShow) {
355
+ row.classList.remove('hidden');
356
+ } else {
357
+ row.classList.add('hidden');
358
+ }
337
359
  return shouldShow;
338
360
  });
339
361
 
@@ -371,6 +393,7 @@ function applyFilters(table) {
371
393
  const tbody = table.querySelector('tbody');
372
394
  filteredRows.forEach(row => tbody.appendChild(row));
373
395
  }
396
+ table.classList.remove('hidden');
374
397
  }
375
398
 
376
399
  function processHtmlContent(element) {
@@ -425,7 +448,7 @@ function exportToExcel(table, fileNamePrefix = 'export') {
425
448
 
426
449
  // Get headers and data
427
450
  const headers = Array.from(table.querySelectorAll('thead th'))
428
- .map(th => th.querySelector('.th-content')?.textContent.replace(/[▼].*/, '').replace(/[^\\w\\s]/g, '').trim() || '');
451
+ .map(th => th.querySelector('.th-title')?.textContent.trim() || '');
429
452
 
430
453
  // Get data rows with type information
431
454
  const rows = Array.from(table.querySelectorAll('tbody tr'))
@@ -628,11 +651,11 @@ def parse_input_file(filename: str) -> List[Tuple[str, List[int]]]:
628
651
  host_dict = {}
629
652
  with open(filename, 'r') as f:
630
653
  for line in f:
631
- line = line.strip().lower()
654
+ line = line.strip()
632
655
  if not line or line.startswith('#'):
633
656
  continue
634
657
  words = line.split()
635
- fqdn = words[0]
658
+ fqdn = words[0].lower()
636
659
  ports = words[1] if len(words) > 1 else '22'
637
660
  port_list = [int(p) for p in ports.split(',')]
638
661
  desc = ' '.join(words[2:]).strip() if len(words) > 2 else ''
@@ -662,8 +685,12 @@ def ping_host(ip: str, timeout: float = 2.0) -> bool:
662
685
  """
663
686
  try:
664
687
  # Using -c 1 for count=1, -W timeout for timeout in seconds
688
+ # Windows uses -n 1 for count=1, -w timeout in milliseconds
665
689
  # These are standard Linux ping parameters
666
- command = ['ping', '-c', '1', '-W', str(int(timeout)), ip]
690
+ if platform.system().lower() == "windows":
691
+ command = ["ping", "-n", "1", "-w", str(int(timeout * 1000)), ip]
692
+ else:
693
+ command = ['ping', '-c', '1', '-W', str(int(timeout)), ip]
667
694
  output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout)
668
695
  return output.returncode == 0
669
696
  except Exception:
@@ -1158,7 +1185,8 @@ def generate_html_report(
1158
1185
  parallelism: int,
1159
1186
  noping: bool,
1160
1187
  stats: Dict[str, Any],
1161
- input_file: str = ''
1188
+ input_file: str = '',
1189
+ desc_titles: List[str] = [],
1162
1190
  ) -> None:
1163
1191
  """Generate a complete HTML report of the port scan results.
1164
1192
 
@@ -1203,6 +1231,9 @@ def generate_html_report(
1203
1231
  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')
1204
1232
 
1205
1233
  # Write detailed results table
1234
+ if not desc_titles:
1235
+ _, _, _, _, _, desc = results[0]
1236
+ desc_titles = ["Description" for d in desc.split("|")]
1206
1237
  f.write(f'''
1207
1238
  <div class="table-container" id="result-container">
1208
1239
  <table id="commandTable">
@@ -1214,7 +1245,7 @@ def generate_html_report(
1214
1245
  <th>Port</th>
1215
1246
  <th>Status</th>
1216
1247
  <th>Ping</th>
1217
- <th>Description</th>
1248
+ {"\n".join([f"<th>{d}</th>" for d in desc_titles])}
1218
1249
  </tr>
1219
1250
  </thead>
1220
1251
  <tbody>
@@ -1233,7 +1264,7 @@ def generate_html_report(
1233
1264
  <td style="text-align: right;">{port}</td>
1234
1265
  <td style="text-align: center;"><span class="{status_class} status">{escape(status)}</span></td>
1235
1266
  <td style="text-align: center;"><span class="{ping_class} ping">{ping_status}</span></td>
1236
- <td class="desc">{escape(str(desc))}</td>
1267
+ {"\n".join([f'<td class="desc">{escape(str(d))}</td>' for d in desc.split("|")])}
1237
1268
  </tr>
1238
1269
  ''')
1239
1270
 
@@ -1417,7 +1448,7 @@ def main():
1417
1448
  parser.add_argument('-n', '--noping', action="store_true", help='No ping check')
1418
1449
  parser.add_argument('-s', '--summary', action="store_true", help='Print scan summary information')
1419
1450
  parser.add_argument('-b', '--bits', type=int, default=16, help='VLAN bits for timeout summary (default: 16)')
1420
-
1451
+ parser.add_argument('-d', '--desc_titles', type=str, nargs='*', help='List of custom description titles for hosts (optional)')
1421
1452
  # Email related arguments
1422
1453
  email_group = parser.add_argument_group('Email Options')
1423
1454
  email_group.add_argument('--email-to', help='Comma-separated list of email recipients')
@@ -1475,7 +1506,17 @@ def main():
1475
1506
 
1476
1507
  # Generate report
1477
1508
  results.sort(key=lambda x: (x[0], x[2]))
1478
- generate_html_report(results, args.output, time.localtime(start_time), args.timeout, args.parallelism, args.noping, stats, args.input_file)
1509
+ generate_html_report(
1510
+ results,
1511
+ args.output,
1512
+ time.localtime(start_time),
1513
+ args.timeout,
1514
+ args.parallelism,
1515
+ args.noping,
1516
+ stats,
1517
+ args.input_file,
1518
+ args.desc_titles,
1519
+ )
1479
1520
 
1480
1521
  # Print summary
1481
1522
  # Display detailed results in table format
@@ -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.3'
21
- __version_tuple__ = version_tuple = (0, 1, 3)
20
+ __version__ = version = '0.1.5'
21
+ __version_tuple__ = version_tuple = (0, 1, 5)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portune
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Author: Franck Jouvanceau
6
6
  Maintainer: Franck Jouvanceau
@@ -52,13 +52,19 @@ Description-Content-Type: text/markdown
52
52
  License-File: LICENSE
53
53
  Dynamic: license-file
54
54
 
55
+ [![Pypi version](https://img.shields.io/pypi/v/portune.svg)](https://pypi.org/project/portune/)
56
+ ![example](https://github.com/joknarf/portune/actions/workflows/python-publish.yml/badge.svg)
57
+ [![Licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://shields.io/)
58
+ [![](https://pepy.tech/badge/portune)](https://pepy.tech/project/portune)
59
+ [![Python versions](https://img.shields.io/badge/python-3.9+-blue.svg)](https://shields.io/)
60
+
55
61
  # portune
56
62
 
57
63
  Multitreaded port scanner
58
64
 
59
65
  # features
60
66
 
61
- * parallel check of port availability against server/ports
67
+ * parallel check of port availability against servers/ports
62
68
  * console output result / summary
63
69
  * html full report / dns domain summary / vlan timeout summary
64
70
  * mail with html summary / report attachment (mailhost relay)
portune-0.1.3/README.md DELETED
@@ -1,11 +0,0 @@
1
- # portune
2
-
3
- Multitreaded port scanner
4
-
5
- # features
6
-
7
- * parallel check of port availability against server/ports
8
- * console output result / summary
9
- * html full report / dns domain summary / vlan timeout summary
10
- * mail with html summary / report attachment (mailhost relay)
11
-
File without changes
File without changes
File without changes
File without changes
File without changes