SSH2 client library for PascalAI. Connect to remote servers, execute commands, transfer files via SFTP or SCP, open TCP tunnels through SSH — all using the same protocol as OpenSSH.
Requires the native libssh2 shared library. Ubuntu/Debian: sudo apt install libssh2-1-dev. The library is pre-installed on most CI environments and cloud images.
uses libssh2;
var sess := Connect('server.example.com', 22);
AuthPassword(sess, 'deploy', 'secret');
var r := Exec(sess, 'df -h /');
WriteLn(r.Stdout);
WriteLn('Exit code: ' + IntToStr(r.ExitCode));
Disconnect(sess);
uses libssh2;
var sess := Connect('server.example.com', 22);
AuthPublicKey(sess, 'deploy', '/home/user/.ssh/id_rsa.pub',
'/home/user/.ssh/id_rsa', '');
var sftp := OpenSFTP(sess);
SFTPUpload(sftp, 'C:\builds\app', '/home/deploy/app');
WriteLn('Upload OK');
var files := SFTPListDir(sftp, '/home/deploy');
for var f in files do
WriteLn(f.Name + ' ' + IntToStr(f.Size) + ' bytes');
CloseSFTP(sftp);
Disconnect(sess);
uses libssh2;
{ Connect to a database through SSH bastion }
var sess := Connect('bastion.example.com', 22);
AuthAgent(sess, 'admin');
var ch := OpenTunnel(sess, 'internal-db.lan', 5432);
{ use ChannelRead/ChannelWrite to communicate with PostgreSQL }
ChannelClose(ch);
Disconnect(sess);
A session represents a single TCP connection to an SSH server. All operations require an active, authenticated session.
| Function | Description |
|---|---|
| Connect(Host: String; Port: Integer): TSSHSession | Opens a TCP connection to Host:Port and performs the SSH protocol handshake. Returns a session handle. Raises on connection failure or banner exchange error. |
| Disconnect(Session: TSSHSession) | Sends the SSH disconnect message, closes all open channels, and frees session resources. Always call after use to avoid resource leaks. |
| GetHostKey(Session: TSSHSession): THostKey | Returns the server's host key after handshake. Use THostKey.MD5 or THostKey.SHA256 to verify the fingerprint against a known-hosts entry before authenticating. |
| SetTimeout(Session: TSSHSession; Seconds: Integer) | Sets the read/write timeout for all channel operations on this session. Default is 30 seconds. Pass 0 to disable timeout. |
Authentication must be performed after Connect and before any channel or SFTP operations. Use GetAuthMethods to discover what the server supports.
Password authentication sends credentials over the encrypted channel, but key-based auth (AuthPublicKey, AuthPublicKeyMemory, AuthAgent) is strongly recommended for automated deployments. Keys cannot be brute-forced and can be revoked without changing a password everywhere.
| Function | Description |
|---|---|
| AuthPassword(Session: TSSHSession; Username, Password: String) | Authenticates using a username and password. Simple but not recommended for production automation. Raises ESSHAuthFailed on failure. |
| AuthPublicKey(Session: TSSHSession; Username, PubKeyFile, PrivKeyFile, Passphrase: String) | Authenticates using a public/private key pair read from disk. Passphrase may be empty for unprotected keys. Supports RSA, ECDSA, and Ed25519 key types. |
| AuthPublicKeyMemory(Session: TSSHSession; Username, PubKeyPEM, PrivKeyPEM, Passphrase: String) | Same as AuthPublicKey but reads key material directly from PEM strings rather than files. Useful when keys are stored in environment variables or a secrets manager. |
| AuthAgent(Session: TSSHSession; Username: String) | Authenticates via the local SSH agent (ssh-agent / pageant). The agent signs the challenge without exposing private key material to the process. Preferred for interactive use and CI environments with agent forwarding. |
| GetAuthMethods(Session: TSSHSession; Username: String): List<String> | Queries the server for supported authentication methods for the given username. Returns a list such as ['publickey', 'password', 'keyboard-interactive']. Call before attempting auth to avoid unnecessary failures. |
Execute shell commands on the remote server. Exec is the simplest path for single commands; ExecOpen gives streaming access to stdout/stderr for long-running processes.
| Function | Description |
|---|---|
| Exec(Session: TSSHSession; Command: String): TExecResult | Runs Command on the server and waits for it to complete. Returns a TExecResult with Stdout, Stderr, and ExitCode populated. Blocks until exit or timeout. |
| ExecWithInput(Session: TSSHSession; Command, Stdin: String): TExecResult | Like Exec but writes Stdin to the command's standard input before waiting for completion. Useful for commands that read from stdin (e.g., gpg --import, psql). |
| ExecOpen(Session: TSSHSession; Command: String): TSSHChannel | Opens an exec channel without waiting for the command to finish. Use ChannelRead / ChannelWrite in a loop for streaming I/O. Call ChannelWait to get the exit code. |
| ChannelRead(Ch: TSSHChannel; MaxBytes: Integer): String | Reads up to MaxBytes from the channel's stdout. Returns an empty string at EOF. Non-blocking if the session timeout has expired. |
| ChannelReadStderr(Ch: TSSHChannel; MaxBytes: Integer): String | Like ChannelRead but reads from stderr (SSH stream 1). Mix calls to both to multiplex stdout and stderr from a single channel. |
| ChannelWrite(Ch: TSSHChannel; Data: String) | Writes Data to the channel's stdin. Can be called multiple times before sending EOF. |
| ChannelEOF(Ch: TSSHChannel) | Signals end-of-file on the channel's stdin. The remote command will see EOF on its standard input after all pending data is flushed. |
| ChannelWait(Ch: TSSHChannel): Integer | Waits for the remote command to exit and returns its exit code. Blocks until the channel closes or the session timeout fires. |
| ChannelClose(Ch: TSSHChannel) | Closes the channel and frees its resources. Always call after ExecOpen or OpenTunnel when done, regardless of success or failure. |
SFTP (SSH File Transfer Protocol) is the recommended way to transfer files over SSH. It supports directory listing, stat, rename, delete, and partial reads/writes — unlike SCP.
| Function | Description |
|---|---|
| OpenSFTP(Session: TSSHSession): TSFTPSession | Opens an SFTP subsystem over the existing SSH session. Returns an SFTP handle used for all subsequent file operations. |
| CloseSFTP(SFTP: TSFTPSession) | Closes the SFTP subsystem and frees its resources. The parent SSH session remains open. |
| SFTPUpload(SFTP: TSFTPSession; LocalPath, RemotePath: String) | Uploads a local file to the remote path. Creates or overwrites the remote file. Directory components of RemotePath must already exist. |
| SFTPDownload(SFTP: TSFTPSession; RemotePath, LocalPath: String) | Downloads a remote file to the local path. Creates or overwrites the local file. Raises ESSHFileNotFound if the remote path does not exist. |
| SFTPWriteBytes(SFTP: TSFTPSession; RemotePath: String; Data: Bytes) | Writes raw byte content to a remote file. Overwrites any existing content. Suitable for small payloads; use SFTPUpload for large files. |
| SFTPReadBytes(SFTP: TSFTPSession; RemotePath: String): Bytes | Reads the entire content of a remote file and returns it as a byte array. Raises if the file exceeds available memory; prefer SFTPDownload for large files. |
| SFTPListDir(SFTP: TSFTPSession; RemotePath: String): List<TSFTPFileInfo> | Returns a list of entries in the remote directory. Each entry is a TSFTPFileInfo record with name, size, permissions, and type flags. Does not recurse into subdirectories. |
| SFTPStat(SFTP: TSFTPSession; RemotePath: String): TSFTPFileInfo | Returns metadata for a single remote path. Follows symlinks; use SFTPLStat (not listed) to stat the link itself. |
| SFTPMkDir(SFTP: TSFTPSession; RemotePath: String; Mode: Integer) | Creates a directory at RemotePath with the given Unix permission mode (e.g., $1ED = 0755). Raises if the parent directory does not exist. |
| SFTPDelete(SFTP: TSFTPSession; RemotePath: String) | Deletes a remote file. To remove an empty directory use SFTPRmDir (delegates to the server). Raises if the path does not exist or is a non-empty directory. |
| SFTPRename(SFTP: TSFTPSession; OldPath, NewPath: String) | Atomically renames (moves) a remote file or directory. Both paths must reside on the same remote filesystem. Raises if NewPath already exists. |
SCP is a simpler single-file transfer protocol built directly on SSH channels. It lacks directory listing, rename, and stat operations.
SCP is included for compatibility with older SSH servers and scripts. SFTP is more feature-rich, supports partial transfers, and handles errors more gracefully. Use SFTPUpload / SFTPDownload unless you have a specific reason to use SCP.
| Function | Description |
|---|---|
| SCPUpload(Session: TSSHSession; LocalPath, RemotePath: String) | Sends a single local file to RemotePath on the server using the SCP protocol. Preserves file mode and modification time. Raises on permission denied or disk-full errors. |
| SCPDownload(Session: TSSHSession; RemotePath, LocalPath: String) | Retrieves a single remote file via SCP and saves it to LocalPath. Returns the remote file size in bytes. Raises ESSHFileNotFound if the remote path does not exist. |
Open a TCP tunnel through the SSH connection to reach services on the remote network that are not directly accessible from the client.
Your client connects to the bastion over SSH, then the bastion opens a TCP connection to the internal host. Traffic flows through the encrypted SSH channel — no VPN required. Common targets: databases, internal APIs, container registries.
| Function | Description |
|---|---|
| OpenTunnel(Session: TSSHSession; DestHost: String; DestPort: Integer): TSSHChannel | Requests the server to open a TCP connection to DestHost:DestPort and forwards it over the SSH channel. Use ChannelRead / ChannelWrite to exchange data with the destination service. Call ChannelClose when done. |
uses libssh2;
var sess := Connect('bastion.example.com', 22);
AuthPublicKey(sess, 'jump-user',
'/home/ci/.ssh/id_ed25519.pub',
'/home/ci/.ssh/id_ed25519', '');
{ open tunnel to internal PostgreSQL port }
var ch := OpenTunnel(sess, 'db.internal.lan', 5432);
{ send PostgreSQL startup message, read authentication response, etc. }
ChannelWrite(ch, startupMsg);
var resp := ChannelRead(ch, 4096);
ChannelClose(ch);
Disconnect(sess);
Returned by SFTPListDir and SFTPStat. Contains metadata for a remote filesystem entry.
| Field | Type | Description |
|---|---|---|
| Name | String | Filename without directory component (e.g., app.tar.gz). |
| FullPath | String | Absolute remote path (e.g., /home/deploy/app.tar.gz). |
| Size | Int64 | File size in bytes. Zero for directories and symlinks. |
| IsDir | Boolean | True if the entry is a directory. |
| IsSymlink | Boolean | True if the entry is a symbolic link. SFTPStat follows the link; the original entry still has IsSymlink = True. |
| Permissions | Integer | Unix permissions as a bitmask (e.g., $81A4 = -rw-r--r--). Use bitwise and with $1FF to extract the mode bits. |
| MTime | Integer | Last modification time as a Unix timestamp (seconds since epoch). Convert with UnixToDateTime from datetime-utils. |
uses libssh2;
var sftp := OpenSFTP(sess);
var files := SFTPListDir(sftp, '/var/www/html');
for var f in files do begin
if f.IsDir then
WriteLn('[DIR] ' + f.Name)
else
WriteLn(Format('%-32s %8d bytes mode=%o',
[f.Name, f.Size, f.Permissions and $1FF]));
end;
Connect opens TCP and performs the SSH protocol handshake. Auth proves identity. Use channels/SFTP for work. Disconnect closes everything. Always call Disconnect — even on error paths — to avoid leaving stale server connections.
Call GetHostKey immediately after Connect and before authentication. Compare THostKey.SHA256 against a stored fingerprint to prevent man-in-the-middle attacks. Skip only in isolated test environments.
Password: simple, but credentials travel in-process. PublicKey: private key never leaves disk; the server only sees a signed challenge. Agent: private key stays in the agent process; ideal for CI with agent forwarding or a hardware key.
SFTP is a full-featured subsystem: list, stat, rename, delete, partial read/write, and proper error codes. SCP is a thin shell-based protocol — simpler but fragile with shell prompts or MOTD banners. Use SFTP unless the server lacks it.
uses libssh2;
var sess := Connect('host', 22);
try begin
{ 1. verify host key before trusting the server }
var hk := GetHostKey(sess);
if hk.SHA256 <> KNOWN_FINGERPRINT then
raise 'Host key mismatch — possible MITM attack';
{ 2. authenticate with a key pair }
AuthPublicKey(sess, 'deploy',
'/run/secrets/id_ed25519.pub',
'/run/secrets/id_ed25519', '');
{ 3. do work }
var r := Exec(sess, 'systemctl restart app');
if r.ExitCode <> 0 then
raise 'Restart failed: ' + r.Stderr;
WriteLn('Restarted OK');
end except
WriteLn('SSH error: ' + GetExceptionMessage());
end;
Disconnect(sess);