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.
- mazip-0.1.4/MANIFEST.in +3 -0
- mazip-0.1.4/PKG-INFO +87 -0
- mazip-0.1.4/README.md +76 -0
- mazip-0.1.4/TEST_REPORT.md +56 -0
- mazip-0.1.4/example.json +7 -0
- mazip-0.1.4/install.sh +26 -0
- mazip-0.1.4/mazip.egg-info/PKG-INFO +87 -0
- mazip-0.1.4/mazip.egg-info/SOURCES.txt +20 -0
- mazip-0.1.4/mazip.egg-info/dependency_links.txt +1 -0
- mazip-0.1.4/mazip.egg-info/entry_points.txt +2 -0
- mazip-0.1.4/mazip.egg-info/requires.txt +5 -0
- mazip-0.1.4/mazip.egg-info/top_level.txt +1 -0
- mazip-0.1.4/mezilink/__init__.py +1 -0
- mazip-0.1.4/mezilink/__main__.py +3 -0
- mazip-0.1.4/mezilink/cli.py +190 -0
- mazip-0.1.4/mezilink/core.py +211 -0
- mazip-0.1.4/mezilink/terminal.py +130 -0
- mazip-0.1.4/pyproject.toml +23 -0
- mazip-0.1.4/setup.cfg +4 -0
- mazip-0.1.4/tested-environment.txt +16 -0
- mazip-0.1.4/tests/test_mezilink.py +290 -0
- mazip-0.1.4/tests/test_terminal.py +182 -0
mazip-0.1.4/MANIFEST.in
ADDED
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.
|
mazip-0.1.4/example.json
ADDED
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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mezilink
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.4"
|
|
@@ -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
|