# sftp-debugger

A command-line script that walks through SFTP connection issues step by step.

## Requirements

- Python 3.10+
- `ssh-keyscan` and `sftp` available on `PATH` (standard on macOS/Linux)

### Optional: paramiko mode

To use `--mode paramiko`, install the `paramiko` library:

```bash
pip install paramiko
```

## Usage

```bash
python sftp_debug.py <user> <host> [port] [options]
```

| Argument | Required | Description |
|---|---|---|
| `user` | yes | SSH/SFTP username |
| `host` | yes | Remote hostname or IP |
| `port` | no | SSH/SFTP port (default: `22`) |
| `--key-base64` | no | Base64-encoded private key (omit to skip the authentication step) |
| `--host-key-algorithms` | no | Comma-separated host key algorithms to request (default: `ssh-ed25519,ssh-rsa`) |
| `--expected-host-key` | no | Expected host public key to verify against (e.g. `"ssh-ed25519 AAAA..."`) |
| `--mode` | no | `openssh` (default) or `paramiko` — see below |
| `--test-batch` | no | After a successful paramiko connection, create a temp directory, verify it appears in `ls`, then delete it. Only valid with `--mode paramiko` |

### Examples

```bash
# Basic — no auth step
python sftp_debug.py myuser example.com

# With private key (base64-encoded)
python sftp_debug.py myuser example.com --key-base64 "<base64-encoded-private-key>"

# Custom port
python sftp_debug.py myuser example.com 2222 --key-base64 "<base64-encoded-private-key>"

# Verify against a known host key instead of ~/.ssh/known_hosts
python sftp_debug.py myuser example.com --key-base64 "<base64-encoded-private-key>" --expected-host-key "ssh-ed25519 AAAA..."

# Override the default host key algorithms
python sftp_debug.py myuser example.com --key-base64 "<base64-encoded-private-key>" --host-key-algorithms ssh-rsa

# paramiko mode
python sftp_debug.py myuser example.com --mode paramiko --key-base64 "<base64-encoded-private-key>"

# paramiko mode with SFTP operations test (mkdir, ls, rmdir)
python sftp_debug.py myuser example.com --mode paramiko --key-base64 "<base64-encoded-private-key>" --test-batch
```

### Providing a private key

Pass the private key as a base64-encoded string via `--key-base64`. The script decodes it, writes it to a temp file with `600` permissions, uses it for authentication, and deletes the file on exit.

## What it does

### openssh mode (default)

**Step 1 — TCP reachability**
Opens a raw TCP connection to `host:port`. Fails immediately if the host is unreachable or the port is closed.

**Step 2 — Host key verification**
Runs `ssh-keyscan` to retrieve the server's public key(s), then verifies them:

- If `--expected-host-key` is provided, the scanned key is compared directly against it. Fails on mismatch.
- Otherwise, the script checks `~/.ssh/known_hosts` using `ssh-keygen -F` (handles hashed entries). If an entry exists and conflicts, the script fails with a message indicating how to remove the stale entry.

Either way, the scanned keys are written to a temporary known_hosts file used by all subsequent steps, so your actual `~/.ssh/known_hosts` is never modified.

**Step 3 — Host key algorithm enumeration**
Runs `sftp -vvv -o PreferredAuthentications=none` to connect without authenticating and parses the verbose output for the list of host key algorithms the server offers. Checks that at least one of the requested algorithms (from `--host-key-algorithms`) is in the server's list; fails with a clear message if there is no overlap. Useful for diagnosing algorithm mismatch errors.

**Step 4 — Authentication**
Runs `sftp -vvv -i <key>` with `BatchMode=yes` and attempts to log in. Distinguishes between an auth rejection (bad key or wrong user) and a connection-level failure.

### paramiko mode (`--mode paramiko`)

Requires `pip install paramiko`. Does not invoke any external binaries.

**Step 1 — TCP reachability** (same as above)

**Step 2 — Host key verification**
Opens a bare `paramiko.Transport` to complete the SSH key exchange and retrieve the server's host key, then verifies it against `--expected-host-key` or `~/.ssh/known_hosts` using the same logic as openssh mode.

**Step 3 — Authentication and SFTP session**
Loads the private key (auto-detects Ed25519, RSA, or ECDSA), connects via `paramiko.SSHClient` with the verified host key pinned, and opens an SFTP subsystem. Prints the remote working directory on success.

**Step 4 — SFTP operations test (`--test-batch`)**
Creates a uniquely-named temporary directory on the server, confirms it appears in a directory listing, then removes it. Runs only when `--test-batch` is passed. The cleanup runs in a `finally` block so the directory is removed even if the listing check fails.
