mazip 0.1.4__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.
@@ -0,0 +1,3 @@
1
+ include README.md TEST_REPORT.md example.json tested-environment.txt
2
+ recursive-include tests *.py
3
+ include install.sh
mazip-0.1.4/PKG-INFO ADDED
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: mazip
3
+ Version: 0.1.4
4
+ Summary: Interactive local network information and verified SSH connections
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: paramiko<6,>=4
8
+ Requires-Dist: psutil<8,>=5.9
9
+ Provides-Extra: test
10
+ Requires-Dist: pytest<10,>=8; extra == "test"
11
+
12
+ # mazip 0.1.4
13
+
14
+ Interactive local network information and verified SSH connections. Requires Python 3.10 or newer. Not published to PyPI.
15
+
16
+ ## Install on Kali / Ubuntu
17
+
18
+ Open a terminal in the extracted MeziLink directory:
19
+
20
+ ```bash
21
+ sudo apt install pipx
22
+ bash install.sh
23
+ ```
24
+
25
+ Open a NEW terminal and run `mazip` from any directory. No environment activation is required. pipx manages dependencies in its own isolated environment; it does not install into the system Python. You may move or remove the extracted project after installation.
26
+
27
+ The installer uses the bundled wheel, exposes the command with pipx, and runs `pipx ensurepath`. It removes only the old `alias mazip=".../.venv/bin/mazip"` format from .zshrc/.bashrc, saving a backup first. Other custom aliases are left unchanged.
28
+
29
+ To install manually:
30
+
31
+ ```bash
32
+ pipx install --force ./dist/mazip-0.1.4-py3-none-any.whl
33
+ pipx ensurepath
34
+ ```
35
+
36
+ Remove any old mazip alias before using the pipx command. Uninstall with `pipx uninstall mazip`.
37
+
38
+ ## Install on Windows
39
+
40
+ ```powershell
41
+ py -m venv .venv
42
+ .venv\Scripts\python.exe -m pip install .
43
+ .venv\Scripts\mazip.exe
44
+ ```
45
+
46
+ ## Use
47
+
48
+ - Option 1 displays local IPv4/IPv6 addresses, local username, SSH client availability, identifiable SSH server bind addresses/ports, and visible TCP listeners/UDP bindings. SSH listeners are identified by process name, not by an assumed port. Limited permissions are reported; firewall reachability and SSH account access are not verified.
49
+ - Option 2 asks for destination OS, IP, port, then TCP-only testing or SSH authentication.
50
+ - After SSH authentication, c opens a persistent interactive terminal; x runs a single noninteractive command. Ctrl+] closes the interactive shell channel and returns to the menu. Ctrl+C is forwarded to interrupt the remote command. Typing exit also returns to the menu. cd and shell variables persist within that shell, but not after closing and reopening it.
51
+ - Option 0 exits or disconnects.
52
+
53
+ ```bash
54
+ mazip --local
55
+ mazip -S -vv
56
+ mazip -S -vv -f example.json
57
+ python -m mezilink
58
+ ```
59
+
60
+ `-S` opens the destination menu. `-v` shows connection stages; `-vv` adds cipher and server metadata. `-f` loads JSON settings without passwords or commands.
61
+
62
+ ## Connection security
63
+
64
+ The destination must already run an SSH service and accept your credentials. No installation, firewall changes or remote scanning are performed.
65
+
66
+ Existing known_hosts entries are checked. For an unknown server, independently compare the displayed SHA256 fingerprint with the destination console or administrator, then enter the full fingerprint. Merely copying the displayed value does not establish identity. This verification applies to the current connection only.
67
+
68
+ A verified `fingerprint` in the JSON settings becomes the independent trust pin and replaces known_hosts verification. Changed or mismatched identities are rejected. Passwords and private-key passphrases use hidden input and are not saved. Automatic SSH-agent and key discovery are disabled. Python does not guarantee secure erasure of secrets from memory.
69
+
70
+ ## Platform and execution limits
71
+
72
+ Windows profiles require OpenSSH Server. Linux requires an SSH service. Android requires an existing SSH environment such as Termux. Selecting a profile does not detect or install a remote OS.
73
+
74
+ Preset commands and option x use separate noninteractive sessions, with 1 MiB output and 30-second waiting limits. Option c requests a remote PTY and preserves shell state, allows interactive input, and has no command duration/output cap. Linux terminal mode supports Ctrl+C, terminal resizing and raw keyboard input. The Windows client keyboard path is implemented but not tested; full-screen rendering depends on the local terminal and remote SSH server. The remote server must permit PTY and shell requests. A real local terminal is required for c. Closing a channel or timing out does not guarantee that the remote process stops. Single-command output has terminal control characters removed; interactive shell output retains terminal sequences for terminal behavior; its language depends on the destination.
75
+
76
+ Local addresses do not reveal a public NAT IP. Port listings may be incomplete due to OS permissions. A UDP binding does not prove remote reachability. When interface enumeration is unavailable, an explicit warning accompanies limited hostname-resolved addresses; interface status is unknown.
77
+
78
+ ## Tests
79
+
80
+ ```bash
81
+ python -m pip install '.[test]'
82
+ python -m pytest -q
83
+ ```
84
+
85
+ See TEST_REPORT.md for the original functional validation and limitations. Version 0.1.2 removes the iOS profile. All application text is English; Python source contains no comments or docstrings. The internal Python module remains `mezilink`; the distribution and executable are `mazip`.
86
+
87
+ Reference: https://docs.paramiko.org/en/stable/api/client.html
mazip-0.1.4/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # mazip 0.1.4
2
+
3
+ Interactive local network information and verified SSH connections. Requires Python 3.10 or newer. Not published to PyPI.
4
+
5
+ ## Install on Kali / Ubuntu
6
+
7
+ Open a terminal in the extracted MeziLink directory:
8
+
9
+ ```bash
10
+ sudo apt install pipx
11
+ bash install.sh
12
+ ```
13
+
14
+ Open a NEW terminal and run `mazip` from any directory. No environment activation is required. pipx manages dependencies in its own isolated environment; it does not install into the system Python. You may move or remove the extracted project after installation.
15
+
16
+ The installer uses the bundled wheel, exposes the command with pipx, and runs `pipx ensurepath`. It removes only the old `alias mazip=".../.venv/bin/mazip"` format from .zshrc/.bashrc, saving a backup first. Other custom aliases are left unchanged.
17
+
18
+ To install manually:
19
+
20
+ ```bash
21
+ pipx install --force ./dist/mazip-0.1.4-py3-none-any.whl
22
+ pipx ensurepath
23
+ ```
24
+
25
+ Remove any old mazip alias before using the pipx command. Uninstall with `pipx uninstall mazip`.
26
+
27
+ ## Install on Windows
28
+
29
+ ```powershell
30
+ py -m venv .venv
31
+ .venv\Scripts\python.exe -m pip install .
32
+ .venv\Scripts\mazip.exe
33
+ ```
34
+
35
+ ## Use
36
+
37
+ - Option 1 displays local IPv4/IPv6 addresses, local username, SSH client availability, identifiable SSH server bind addresses/ports, and visible TCP listeners/UDP bindings. SSH listeners are identified by process name, not by an assumed port. Limited permissions are reported; firewall reachability and SSH account access are not verified.
38
+ - Option 2 asks for destination OS, IP, port, then TCP-only testing or SSH authentication.
39
+ - After SSH authentication, c opens a persistent interactive terminal; x runs a single noninteractive command. Ctrl+] closes the interactive shell channel and returns to the menu. Ctrl+C is forwarded to interrupt the remote command. Typing exit also returns to the menu. cd and shell variables persist within that shell, but not after closing and reopening it.
40
+ - Option 0 exits or disconnects.
41
+
42
+ ```bash
43
+ mazip --local
44
+ mazip -S -vv
45
+ mazip -S -vv -f example.json
46
+ python -m mezilink
47
+ ```
48
+
49
+ `-S` opens the destination menu. `-v` shows connection stages; `-vv` adds cipher and server metadata. `-f` loads JSON settings without passwords or commands.
50
+
51
+ ## Connection security
52
+
53
+ The destination must already run an SSH service and accept your credentials. No installation, firewall changes or remote scanning are performed.
54
+
55
+ Existing known_hosts entries are checked. For an unknown server, independently compare the displayed SHA256 fingerprint with the destination console or administrator, then enter the full fingerprint. Merely copying the displayed value does not establish identity. This verification applies to the current connection only.
56
+
57
+ A verified `fingerprint` in the JSON settings becomes the independent trust pin and replaces known_hosts verification. Changed or mismatched identities are rejected. Passwords and private-key passphrases use hidden input and are not saved. Automatic SSH-agent and key discovery are disabled. Python does not guarantee secure erasure of secrets from memory.
58
+
59
+ ## Platform and execution limits
60
+
61
+ Windows profiles require OpenSSH Server. Linux requires an SSH service. Android requires an existing SSH environment such as Termux. Selecting a profile does not detect or install a remote OS.
62
+
63
+ Preset commands and option x use separate noninteractive sessions, with 1 MiB output and 30-second waiting limits. Option c requests a remote PTY and preserves shell state, allows interactive input, and has no command duration/output cap. Linux terminal mode supports Ctrl+C, terminal resizing and raw keyboard input. The Windows client keyboard path is implemented but not tested; full-screen rendering depends on the local terminal and remote SSH server. The remote server must permit PTY and shell requests. A real local terminal is required for c. Closing a channel or timing out does not guarantee that the remote process stops. Single-command output has terminal control characters removed; interactive shell output retains terminal sequences for terminal behavior; its language depends on the destination.
64
+
65
+ Local addresses do not reveal a public NAT IP. Port listings may be incomplete due to OS permissions. A UDP binding does not prove remote reachability. When interface enumeration is unavailable, an explicit warning accompanies limited hostname-resolved addresses; interface status is unknown.
66
+
67
+ ## Tests
68
+
69
+ ```bash
70
+ python -m pip install '.[test]'
71
+ python -m pytest -q
72
+ ```
73
+
74
+ See TEST_REPORT.md for the original functional validation and limitations. Version 0.1.2 removes the iOS profile. All application text is English; Python source contains no comments or docstrings. The internal Python module remains `mezilink`; the distribution and executable are `mazip`.
75
+
76
+ Reference: https://docs.paramiko.org/en/stable/api/client.html
@@ -0,0 +1,56 @@
1
+ # Validation report — 2026-10-17 UTC
2
+
3
+ Environment: Linux x86_64, Python 3.12; Paramiko 5.0.0, psutil 7.2.2.
4
+
5
+ Automated suite: **38 passed**. Tests use real TCP sockets and real SSH transport,
6
+ key exchange, host identity checks, authentication and command channels on loopback.
7
+ The SSH lab server is implemented with Paramiko; it is not a production OpenSSH server.
8
+ The echo success case runs a real local subprocess on that test server.
9
+
10
+ Covered:
11
+ - IPv4 and IPv6 SSH connection and successful remote command result.
12
+ - Password authentication and rejection of an incorrect password.
13
+ - Encrypted private-key authentication.
14
+ - Pinned fingerprint success/mismatch; rejection of unverified unknown servers.
15
+ - Verified fingerprint callback; known_hosts success and changed-key rejection.
16
+ - TCP open and closed port behavior; IP/port validation.
17
+ - Command timeout, output cap, connection loss, stdout/stderr draining and nonzero exit.
18
+ - Invalid configuration, interactive local menu and end-to-end SSH menu with scripted inputs.
19
+ - Terminal control-character filtering, EOF cancellation.
20
+ - Interface permission fallback with a warning; normal per-interface handling with fixture data.
21
+
22
+ Environment limitation: native network-interface enumeration returned PermissionError.
23
+ The real local-information run therefore used explicitly labeled hostname resolution,
24
+ including IPv4 and IPv6 loopback. Full interface enumeration was verified using fixture
25
+ inputs, not a real unrestricted host. Local port enumeration ran successfully.
26
+
27
+ Not tested on physical Windows, Red Hat, Android or iOS devices. OS menu entries select
28
+ command profiles and do not certify those platforms. No internet targets were scanned.
29
+ No claim of zero bugs or universal platform compatibility is made.
30
+
31
+ Package checks: wheel/source build, wheel installation and CLI smoke checks are
32
+ performed as part of this delivery. The full automated tests are included for reruns.
33
+
34
+ API reference used: https://docs.paramiko.org/en/stable/api/client.html
35
+
36
+ ## Version 0.1.3 validation
37
+
38
+ 41 tests passed on Linux/Python 3.12. New tests cover SSH listener identification
39
+ by process name on nonstandard ports, inaccessible process metadata, permission
40
+ denial, and no visible SSH server. These discovery branches use controlled fixtures;
41
+ SSH transport/authentication tests still use real loopback sockets.
42
+ The installer passes bash syntax validation. The bundled wheel is installed with
43
+ pipx in a separate test directory and its exposed command is smoke-tested without
44
+ activating an environment. Windows and mobile device limitations above still apply.
45
+
46
+ ## Version 0.1.4 validation
47
+
48
+ 45 tests passed on Linux/Python 3.12. Real SSH + local/remote PTY integration
49
+ runs an actual /bin/sh process and verifies consecutive commands, persistent cd
50
+ and shell variables, Ctrl+C interrupting sleep, exit and Ctrl+] returning,
51
+ opening a second shell over the same authenticated SSH connection, and restoring
52
+ local terminal settings. Input escape stripping and non-terminal rejection are
53
+ also tested. Windows client console behavior and physical mobile targets remain
54
+ untested. Interactive terminal output is passed through without control-character
55
+ filtering to support real terminal behavior. No timeout/output cap is imposed on
56
+ interactive sessions; single-command limits remain unchanged.
@@ -0,0 +1,7 @@
1
+ {
2
+ "os": "2",
3
+ "host": "127.0.0.1",
4
+ "port": 22,
5
+ "username": "your-user",
6
+ "timeout": 8
7
+ }
mazip-0.1.4/install.sh ADDED
@@ -0,0 +1,26 @@
1
+ set -eu
2
+ installer_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
3
+ if ! command -v pipx >/dev/null 2>&1; then
4
+ printf '%s\n' 'pipx is required. On Kali/Ubuntu: sudo apt install pipx' 'Then run: bash install.sh'
5
+ exit 1
6
+ fi
7
+ pipx install --force "$installer_dir/dist/mazip-0.1.4-py3-none-any.whl"
8
+ pipx ensurepath
9
+ python3 - <<'PY'
10
+ from pathlib import Path
11
+ import re
12
+ import shutil
13
+ import time
14
+ for name in ('.zshrc', '.bashrc'):
15
+ path = Path.home() / name
16
+ if not path.is_file():
17
+ continue
18
+ text = path.read_text()
19
+ cleaned = re.sub(r'^alias mazip="[^"\n]*/\.venv/bin/mazip"\s*$', '', text, flags=re.MULTILINE)
20
+ if cleaned != text:
21
+ backup = path.with_name(path.name + '.mazip-backup-' + str(time.time_ns()))
22
+ shutil.copy2(path, backup)
23
+ path.write_text(cleaned)
24
+ print('Removed old project alias from ' + str(path) + '; backup: ' + str(backup))
25
+ PY
26
+ printf '%s\n' 'Installed. Open a new terminal, then run: mazip' 'No environment activation is needed. pipx manages isolated dependencies.'
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: mazip
3
+ Version: 0.1.4
4
+ Summary: Interactive local network information and verified SSH connections
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: paramiko<6,>=4
8
+ Requires-Dist: psutil<8,>=5.9
9
+ Provides-Extra: test
10
+ Requires-Dist: pytest<10,>=8; extra == "test"
11
+
12
+ # mazip 0.1.4
13
+
14
+ Interactive local network information and verified SSH connections. Requires Python 3.10 or newer. Not published to PyPI.
15
+
16
+ ## Install on Kali / Ubuntu
17
+
18
+ Open a terminal in the extracted MeziLink directory:
19
+
20
+ ```bash
21
+ sudo apt install pipx
22
+ bash install.sh
23
+ ```
24
+
25
+ Open a NEW terminal and run `mazip` from any directory. No environment activation is required. pipx manages dependencies in its own isolated environment; it does not install into the system Python. You may move or remove the extracted project after installation.
26
+
27
+ The installer uses the bundled wheel, exposes the command with pipx, and runs `pipx ensurepath`. It removes only the old `alias mazip=".../.venv/bin/mazip"` format from .zshrc/.bashrc, saving a backup first. Other custom aliases are left unchanged.
28
+
29
+ To install manually:
30
+
31
+ ```bash
32
+ pipx install --force ./dist/mazip-0.1.4-py3-none-any.whl
33
+ pipx ensurepath
34
+ ```
35
+
36
+ Remove any old mazip alias before using the pipx command. Uninstall with `pipx uninstall mazip`.
37
+
38
+ ## Install on Windows
39
+
40
+ ```powershell
41
+ py -m venv .venv
42
+ .venv\Scripts\python.exe -m pip install .
43
+ .venv\Scripts\mazip.exe
44
+ ```
45
+
46
+ ## Use
47
+
48
+ - Option 1 displays local IPv4/IPv6 addresses, local username, SSH client availability, identifiable SSH server bind addresses/ports, and visible TCP listeners/UDP bindings. SSH listeners are identified by process name, not by an assumed port. Limited permissions are reported; firewall reachability and SSH account access are not verified.
49
+ - Option 2 asks for destination OS, IP, port, then TCP-only testing or SSH authentication.
50
+ - After SSH authentication, c opens a persistent interactive terminal; x runs a single noninteractive command. Ctrl+] closes the interactive shell channel and returns to the menu. Ctrl+C is forwarded to interrupt the remote command. Typing exit also returns to the menu. cd and shell variables persist within that shell, but not after closing and reopening it.
51
+ - Option 0 exits or disconnects.
52
+
53
+ ```bash
54
+ mazip --local
55
+ mazip -S -vv
56
+ mazip -S -vv -f example.json
57
+ python -m mezilink
58
+ ```
59
+
60
+ `-S` opens the destination menu. `-v` shows connection stages; `-vv` adds cipher and server metadata. `-f` loads JSON settings without passwords or commands.
61
+
62
+ ## Connection security
63
+
64
+ The destination must already run an SSH service and accept your credentials. No installation, firewall changes or remote scanning are performed.
65
+
66
+ Existing known_hosts entries are checked. For an unknown server, independently compare the displayed SHA256 fingerprint with the destination console or administrator, then enter the full fingerprint. Merely copying the displayed value does not establish identity. This verification applies to the current connection only.
67
+
68
+ A verified `fingerprint` in the JSON settings becomes the independent trust pin and replaces known_hosts verification. Changed or mismatched identities are rejected. Passwords and private-key passphrases use hidden input and are not saved. Automatic SSH-agent and key discovery are disabled. Python does not guarantee secure erasure of secrets from memory.
69
+
70
+ ## Platform and execution limits
71
+
72
+ Windows profiles require OpenSSH Server. Linux requires an SSH service. Android requires an existing SSH environment such as Termux. Selecting a profile does not detect or install a remote OS.
73
+
74
+ Preset commands and option x use separate noninteractive sessions, with 1 MiB output and 30-second waiting limits. Option c requests a remote PTY and preserves shell state, allows interactive input, and has no command duration/output cap. Linux terminal mode supports Ctrl+C, terminal resizing and raw keyboard input. The Windows client keyboard path is implemented but not tested; full-screen rendering depends on the local terminal and remote SSH server. The remote server must permit PTY and shell requests. A real local terminal is required for c. Closing a channel or timing out does not guarantee that the remote process stops. Single-command output has terminal control characters removed; interactive shell output retains terminal sequences for terminal behavior; its language depends on the destination.
75
+
76
+ Local addresses do not reveal a public NAT IP. Port listings may be incomplete due to OS permissions. A UDP binding does not prove remote reachability. When interface enumeration is unavailable, an explicit warning accompanies limited hostname-resolved addresses; interface status is unknown.
77
+
78
+ ## Tests
79
+
80
+ ```bash
81
+ python -m pip install '.[test]'
82
+ python -m pytest -q
83
+ ```
84
+
85
+ See TEST_REPORT.md for the original functional validation and limitations. Version 0.1.2 removes the iOS profile. All application text is English; Python source contains no comments or docstrings. The internal Python module remains `mezilink`; the distribution and executable are `mazip`.
86
+
87
+ Reference: https://docs.paramiko.org/en/stable/api/client.html
@@ -0,0 +1,20 @@
1
+ MANIFEST.in
2
+ README.md
3
+ TEST_REPORT.md
4
+ example.json
5
+ install.sh
6
+ pyproject.toml
7
+ tested-environment.txt
8
+ mazip.egg-info/PKG-INFO
9
+ mazip.egg-info/SOURCES.txt
10
+ mazip.egg-info/dependency_links.txt
11
+ mazip.egg-info/entry_points.txt
12
+ mazip.egg-info/requires.txt
13
+ mazip.egg-info/top_level.txt
14
+ mezilink/__init__.py
15
+ mezilink/__main__.py
16
+ mezilink/cli.py
17
+ mezilink/core.py
18
+ mezilink/terminal.py
19
+ tests/test_mezilink.py
20
+ tests/test_terminal.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mazip = mezilink.cli:main
@@ -0,0 +1,5 @@
1
+ paramiko<6,>=4
2
+ psutil<8,>=5.9
3
+
4
+ [test]
5
+ pytest<10,>=8
@@ -0,0 +1 @@
1
+ mezilink
@@ -0,0 +1 @@
1
+ __version__ = "0.1.4"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,190 @@
1
+ import argparse
2
+ import getpass
3
+ import platform
4
+ import re
5
+ import sys
6
+ import warnings
7
+
8
+ import paramiko
9
+ import psutil
10
+
11
+ from . import __version__
12
+ from .terminal import interactive_shell
13
+ from .core import PROFILES, connect, load_config, local_ports, network_info, run_command, tcp_probe, validate_target, ssh_info
14
+
15
+
16
+ def safe(text):
17
+
18
+ return re.sub(r'[\x00-\x08\x0b-\x1f\x7f-\x9f]', '', str(text))
19
+
20
+
21
+ def show_local():
22
+ print('\n=== Local device ===')
23
+ print(safe(platform.platform()))
24
+ with warnings.catch_warnings(record=True) as notices:
25
+ warnings.simplefilter('always', RuntimeWarning)
26
+ rows = network_info()
27
+ for notice in notices:
28
+ print('Note: ' + safe(notice.message))
29
+ for row in rows:
30
+ print(safe(f"{row['interface']} | {'UNKNOWN' if row['up'] is None else ('UP' if row['up'] else 'DOWN')} | {row['family']} | {row['address']}"))
31
+ print('Public NAT IP is not queried. Hostname fallback, when used, is not a complete interface list.')
32
+ info = ssh_info()
33
+ print('\n=== Local SSH ===')
34
+ print(safe(f"Local username: {info['username']} (SSH account access is not verified)"))
35
+ print(safe(f"SSH client: {info['client'] or 'Not found on PATH'}"))
36
+ if info['listeners']:
37
+ print('SSH server listeners identified by process name:')
38
+ for host, port, process in info['listeners']:
39
+ print(safe(f' {process} | Bind address: {host} | SSH port: {port}'))
40
+ print('0.0.0.0 / :: are wildcard binds; use a matching device IP above.')
41
+ print('127.0.0.1 / ::1 are local-only. Firewall and login access are not verified.')
42
+ else:
43
+ print('SSH server: No identifiable SSH listener found. Port: Unknown.')
44
+ if info['limited']:
45
+ print('SSH detection is incomplete: some socket/process information is inaccessible.')
46
+ print('An installed SSH client does not mean an SSH server is running.')
47
+ print('Port 22 is a common default, not a detected active service.')
48
+ print('\nLocal TCP listeners / bound UDP ports (visible to your account):')
49
+ try:
50
+ for host, port, protocol in local_ports():
51
+ print(safe(f' {protocol} [{host}]:{port}'))
52
+ except (psutil.Error, OSError):
53
+ print('OS permission denied port listing; IP information is still available.')
54
+ print('Port list may be incomplete without elevated permissions. No remote scanning.')
55
+
56
+
57
+ def choose(prompt, choices):
58
+ while True:
59
+ answer = input(prompt).strip()
60
+ if answer in choices:
61
+ return answer
62
+ print('Invalid choice.')
63
+
64
+
65
+ def value(config, key, prompt, default=None):
66
+ if key in config:
67
+ return config[key]
68
+ answer = input(prompt).strip()
69
+ return answer or default
70
+
71
+
72
+ def verify_server(host, actual):
73
+ print(safe(f'New SSH server: {host}\nFingerprint: {actual}'))
74
+ print('Verify this fingerprint independently using the destination console or administrator, then enter it in full. Enter = cancel.')
75
+ return input('Verified SHA256 fingerprint: ').strip() == actual
76
+
77
+
78
+ def remote(config, verbose=0):
79
+ print('\n=== Destination OS ===')
80
+ for key, (name, _) in PROFILES.items():
81
+ print(f'{key}. {name}')
82
+ os_id = config.get('os') or choose('OS: ', PROFILES)
83
+ name, commands = PROFILES[os_id]
84
+ print(f'Profile: {name}. Destination must already provide SSH.')
85
+ host = value(config, 'host', 'Destination IPv4 / IPv6: ')
86
+ port = value(config, 'port', 'SSH port [22]: ', '22')
87
+ host, port = validate_target(host, port)
88
+ timeout = config.get('timeout', 8)
89
+ mode = choose('1. SSH login 2. TCP test only 0. Back: ', {'0', '1', '2'})
90
+ if mode == '0':
91
+ return
92
+ if mode == '2':
93
+ tcp_probe(host, port, timeout)
94
+ print('TCP connected. This does not confirm SSH availability or authentication.')
95
+ return
96
+ username = value(config, 'username', 'SSH username: ', '')
97
+ key_file = config.get('key_file')
98
+ auth = '2' if key_file else choose('1. Password 2. Private key: ', {'1', '2'})
99
+ password = passphrase = None
100
+ if auth == '1':
101
+ password = getpass.getpass('SSH password (hidden): ')
102
+ else:
103
+ key_file = key_file or input('Private key path: ').strip()
104
+ if not key_file:
105
+ raise ValueError('Private key path is required.')
106
+ passphrase = getpass.getpass('Key passphrase (Enter if unencrypted): ') or None
107
+ if verbose:
108
+ print('Connecting with host verification; credentials and commands are not logged.')
109
+ client = connect(host, port, username, password=password, key_file=key_file,
110
+ passphrase=passphrase, known_hosts=config.get('known_hosts'),
111
+ expected=config.get('fingerprint'), verify=verify_server, timeout=timeout)
112
+ password = passphrase = None
113
+ try:
114
+ print('SSH authenticated successfully.')
115
+ if verbose > 1:
116
+ transport = client.get_transport()
117
+ print(safe(f'Cipher: {transport.remote_cipher}; server: {transport.remote_version}'))
118
+ while True:
119
+ for index, (label, _) in enumerate(commands, 1):
120
+ print(f'{index}. {label}')
121
+ print('c. Interactive terminal\nx. Single command\n0. Disconnect')
122
+ selection = choose('Action: ', {'0', 'c', 'x'} | {str(i) for i in range(1, len(commands) + 1)})
123
+ if selection == '0':
124
+ break
125
+ if selection == 'c':
126
+ try:
127
+ interactive_shell(client)
128
+ except (OSError, EOFError, RuntimeError, paramiko.SSHException) as exc:
129
+ print('Shell ended: ' + explain(exc))
130
+ transport = client.get_transport()
131
+ if transport is None or not transport.is_active():
132
+ print('SSH connection lost.')
133
+ break
134
+ continue
135
+ command = input('Remote command: ').strip() if selection == 'x' else commands[int(selection) - 1][1]
136
+ if not command:
137
+ continue
138
+ print(safe(f'Running on {name}: {command}'))
139
+ code, stdout, stderr = run_command(client, command)
140
+ print(safe(stdout), end='')
141
+ print(safe(stderr), end='', file=sys.stderr)
142
+ print(f'\nExit status: {code}')
143
+ finally:
144
+ client.close()
145
+ print('Disconnected.')
146
+
147
+
148
+ def explain(exc):
149
+ if isinstance(exc, paramiko.BadHostKeyException):
150
+ return 'Host key mismatch. Server identity changed; connection blocked.'
151
+ if isinstance(exc, paramiko.AuthenticationException):
152
+ return 'Authentication failed. Username, password or key was rejected.'
153
+ if isinstance(exc, ConnectionRefusedError):
154
+ return 'Connection refused. Check the destination service and port.'
155
+ if isinstance(exc, TimeoutError):
156
+ return 'Timed out. A remote command may still be running.'
157
+ return safe(str(exc))
158
+
159
+
160
+ def main(argv=None):
161
+ parser = argparse.ArgumentParser(description='mazip: local network information and verified SSH.')
162
+ parser.add_argument('-S', '--ssh', action='store_true', help='Open destination menu directly')
163
+ parser.add_argument('-v', action='count', default=0, help='Status details; -vv adds connection metadata')
164
+ parser.add_argument('-f', '--config', help='JSON settings file; no passwords or commands')
165
+ parser.add_argument('--local', action='store_true', help='Show local network information and exit')
166
+ parser.add_argument('--version', action='version', version=__version__)
167
+ args = parser.parse_args(argv)
168
+ try:
169
+ config = load_config(args.config) if args.config else {}
170
+ if args.local:
171
+ show_local()
172
+ return 0
173
+ if args.ssh:
174
+ remote(config, args.v)
175
+ return 0
176
+ print('mazip | Network & SSH')
177
+ while True:
178
+ choice = choose('\n1. Local device IPs & ports\n2. Connect to destination\n0. Exit\n> ', {'0', '1', '2'})
179
+ if choice == '0':
180
+ return 0
181
+ try:
182
+ show_local() if choice == '1' else remote(config, args.v)
183
+ except (OSError, ValueError, RuntimeError, paramiko.SSHException, psutil.Error) as exc:
184
+ print('Operation failed: ' + explain(exc), file=sys.stderr)
185
+ except (EOFError, KeyboardInterrupt):
186
+ print('\nCancelled.')
187
+ return 130
188
+ except (OSError, ValueError, RuntimeError, paramiko.SSHException, psutil.Error) as exc:
189
+ print('Operation failed: ' + explain(exc), file=sys.stderr)
190
+ return 1