Lightweight FTP/FTPS transport extension for QWKSync.NET. Provides file transfer capabilities over FTP and FTPS protocols using FluentFTP.
$ dotnet add package QwkSync.FTPFTP/FTPS transport extension for QWKSync.NET.
QWKSync.FTP provides lightweight FTP and FTPS (explicit and implicit TLS) file transfer capabilities for QWKSync.NET. It uses the MIT-licenced FluentFTP library for all FTP operations.
dotnet add package QwkSync.Ftp
using QwkSync;
using QwkSync.Ftp;
// Create a profile with credentials in settings
QwkSyncProfile profile = new QwkSyncProfile
{
Endpoint = new Uri("ftp://bbs.example.com"),
TransportId = "ftp",
Settings = new Dictionary<string, string>
{
{ "ftp.username", "myuser" },
{ "ftp.password", "mypassword" },
{ "ftp.tls", "true" }
}
};
// Create a plan
QwkSyncPlan plan = new QwkSyncPlan
{
LocalInboxDirectory = "/path/to/local/inbox",
LocalOutboxDirectory = "/path/to/local/outbox"
};
// Create client and register the FTP transport
QwkSyncClient client = new QwkSyncClient();
client.RegisterTransportFactory(new FtpTransportFactory());
// Run synchronisation
QwkSyncResult result = await client.SyncAsync(profile, plan, CancellationToken.None);
All settings are optional and have sensible defaults.
| Setting | Description | Default |
|---|---|---|
ftp.username | FTP username | (anonymous) |
ftp.password | FTP password | (qwksync@localhost) |
ftp.port | FTP port number | 21 |
ftp.tls | Enable FTPS. Values: true/false | false |
ftp.tls.mode | FTPS mode. Values: explicit, implicit | explicit |
ftp.tls.certValidation | Certificate validation. Values: strict, acceptAny | strict |
ftp.tls.protocols | TLS protocols (comma-separated). Values: tls10, tls11, tls12, tls13 | tls12,tls13 |
ftp.tls.dataEncryption | Data connection encryption. Values: both, controlOnly, none | both |
ftp.tls.certThumbprints | SHA256 certificate thumbprints for pinning (comma-separated) | (none) |
ftp.passive | Use passive mode. Values: true/false | false (active mode) |
ftp.connectTimeoutMs | Connect timeout in milliseconds | 15000 |
ftp.readWriteTimeoutMs | Read/write timeout in milliseconds | 60000 |
ftp.keepAlive | Keep control connection alive. Values: true/false | true |
ftp.userAgent | Client name string | QWKSync.NET |
ftp.remoteRoot | Remote root directory | (server login directory) |
ftp.pathSeparator | Path separator. Values: / only | / |
ftp.verbose | Enable verbose FTP protocol logging. Values: true/false | false |
ftp.proxy.type | Proxy type. Values: none, http, socks4, socks5 | none |
ftp.proxy.host | Proxy server hostname or IP address | (none) |
ftp.proxy.port | Proxy server port number | (varies by type) |
ftp.proxy.username | Proxy authentication username | (none) |
ftp.proxy.password | Proxy authentication password | (none) |
Endpoint: ftp://bbs.example.com
ftp.remoteRoot: (unset)
RemoteInboxPath: (unset)
RemoteOutboxPath: (unset)
Result:
Endpoint: ftp://bbs.example.com/qwk
ftp.remoteRoot: (unset)
RemoteInboxPath: (unset)
RemoteOutboxPath: (unset)
Result:
/qwk/qwk/qwkEndpoint: ftp://bbs.example.com
ftp.remoteRoot: /bbsdata
RemoteInboxPath: in
RemoteOutboxPath: out
Result:
/bbsdata/bbsdata/in/bbsdata/outThe FTP transport uses Credentials.UsernamePassword(user, passwordProvider) from the profile.
For anonymous FTP, either:
anonymous with a placeholder email)anonymous and provide an email-style passwordTo use passive mode, set ftp.passive=true in settings or use --passive flag in the demo tool.
The transport automatically handles connection mode failures by cycling through available modes:
This ensures maximum compatibility with servers that have blocked passive ports or network configurations that prevent active mode connections. The working mode is remembered for the session to avoid repeated fallback attempts.
The transport also handles servers that advertise MLSD (machine-readable listing) support but have incomplete implementations:
This fallback is automatic and invisible to callers.
Synchronet BBS servers may require special handling due to their FTP implementation:
For Synchronet servers, try connecting with --passive first (which will auto-fallback to active if needed), or use active mode directly by omitting the --passive flag.
Example for Synchronet BBS:
dotnet run -- --host bbs.example.net --username myuser --password mypass \
--passive --verbose --list-only
If you see "Connection refused" errors for passive mode, the transport will automatically switch to active mode.
When ftp.tls=true:
ftp.tls.mode to explicit (default) or implicitstrict (default): Validates certificate chain and hostname. Recommended for production.acceptAny: Accepts any certificate including self-signed. Use only in controlled environments.TLS Protocol Versions
Control which TLS protocol versions are enabled:
ftp.tls.protocols=tls12,tls13Note: TLS 1.0 and 1.1 are deprecated due to known security vulnerabilities and should only be enabled when connecting to legacy systems that don't support modern TLS versions. Always use TLS 1.2 or 1.3 when possible.
Data Connection Encryption
Choose whether to encrypt data connections:
both (default): Encrypts both control and data connections (most secure)controlOnly: Only encrypts credentials and commands; data transfers in clear text (better performance)ftp.tls.dataEncryption=bothCertificate Pinning
Pin specific certificates by SHA256 thumbprint for enhanced security:
ftp.tls.certThumbprints=ABC123...,DEF456...Example with advanced TLS:
# Legacy BBS with TLS 1.0, control-only encryption
dotnet run -- --host oldserver.bbs.com --tls \
--tls-protocols tls10,tls11 \
--tls-data-encryption controlOnly \
--list-only
# Modern server with certificate pinning
dotnet run -- --host secure.example.com --tls \
--tls-cert-thumbprints 1A2B3C4D... \
--list-only
FTP connections can be routed through proxy servers. Supported proxy types:
Configure proxy settings using ftp.proxy.* settings or command-line options:
# Connect through HTTP proxy
dotnet run -- --host bbs.example.com --username user \
--proxy-type http --proxy-host proxy.corp.com --proxy-port 8080 \
--list-only
# Connect through SOCKS5 proxy with authentication
dotnet run -- --host bbs.example.com \
--proxy-type socks5 --proxy-host 127.0.0.1 --proxy-port 1080 \
--proxy-username proxyuser --proxy-password proxypass \
--list-only
The QWKSync.FTP.Demo tool demonstrates the FTP transport:
# List files on anonymous FTP server
dotnet run -- --host ftp.example.com --list-only
# Sync with authenticated FTP server
dotnet run -- --host ftp.example.com --username user --password pass \
--local-inbox ./inbox --local-outbox ./outbox
# Sync with FTPS server (explicit TLS)
dotnet run -- --host secure.example.com --tls --username user \
--local-inbox ./inbox --local-outbox ./outbox
# Sync with implicit FTPS (port 990) with verbose logging
dotnet run -- --host secure.example.com --implicit-tls --username user \
--verbose --local-inbox ./inbox --local-outbox ./outbox
FTP Protocol Standard (RFC 959) uses / as the path separator, regardless of the server's operating system. Even Windows FTP servers use forward slashes in FTP commands.
When using active mode (PORT), the FTP server connects back to your machine on a dynamically allocated port. This requires:
If active mode fails, the transport will attempt passive modes. If all modes fail, check your network configuration or contact the server administrator.
MIT Licence. See LICENSE for details.