libssh2 clib

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.

Version 1.0.0
Type clib
System Requirement libssh2-1-dev
Install (Ubuntu) sudo apt install libssh2-1-dev
ppm install libssh2
CLib package

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.

Quick Start

Remote Command Execution

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);

SFTP Upload & Directory Listing

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);

TCP Tunnel through SSH Bastion

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);

Session API

A session represents a single TCP connection to an SSH server. All operations require an active, authenticated session.

FunctionDescription
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 API

Authentication must be performed after Connect and before any channel or SFTP operations. Use GetAuthMethods to discover what the server supports.

Prefer key-based authentication

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.

FunctionDescription
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.

Remote Execution API

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.

FunctionDescription
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 API

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.

FunctionDescription
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 API

SCP is a simpler single-file transfer protocol built directly on SSH channels. It lacks directory listing, rename, and stat operations.

Prefer SFTP for most use cases

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.

FunctionDescription
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.

Port Forwarding

Open a TCP tunnel through the SSH connection to reach services on the remote network that are not directly accessible from the client.

Use case: accessing internal services through a bastion / jump host

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.

FunctionDescription
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.

Tunnel example: talk to an internal PostgreSQL server

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);

TSFTPFileInfo Record

Returned by SFTPListDir and SFTPStat. Contains metadata for a remote filesystem entry.

FieldTypeDescription
NameStringFilename without directory component (e.g., app.tar.gz).
FullPathStringAbsolute remote path (e.g., /home/deploy/app.tar.gz).
SizeInt64File size in bytes. Zero for directories and symlinks.
IsDirBooleanTrue if the entry is a directory.
IsSymlinkBooleanTrue if the entry is a symbolic link. SFTPStat follows the link; the original entry still has IsSymlink = True.
PermissionsIntegerUnix permissions as a bitmask (e.g., $81A4 = -rw-r--r--). Use bitwise and with $1FF to extract the mode bits.
MTimeIntegerLast modification time as a Unix timestamp (seconds since epoch). Convert with UnixToDateTime from datetime-utils.

Listing example

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;

Key Concepts

Session Workflow

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.

Host Key Verification

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.

Authentication Methods

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 vs SCP

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.

Recommended session pattern

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);