QwkSync.FTP 1.0.0

dotnet add package QwkSync.FTP --version 1.0.0
                    
NuGet\Install-Package QwkSync.FTP -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="QwkSync.FTP" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="QwkSync.FTP" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="QwkSync.FTP" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add QwkSync.FTP --version 1.0.0
                    
#r "nuget: QwkSync.FTP, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package QwkSync.FTP@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=QwkSync.FTP&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=QwkSync.FTP&version=1.0.0
                    
Install as a Cake Tool

QWKSync.FTP

FTP/FTPS transport extension for QWKSync.NET.

Overview

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.

Installation

dotnet add package QwkSync.Ftp

Quick Start

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

Configuration Settings

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)

Path Resolution Examples

Example A — Simple FTP, everything in one directory

Endpoint: ftp://bbs.example.com
ftp.remoteRoot: (unset)
RemoteInboxPath: (unset)
RemoteOutboxPath: (unset)

Result:

  • RemoteRoot: (FTP login directory)
  • Inbox: (FTP login directory)
  • Outbox: (FTP login directory)

Example B — FTP with QWK subfolder

Endpoint: ftp://bbs.example.com/qwk
ftp.remoteRoot: (unset)
RemoteInboxPath: (unset)
RemoteOutboxPath: (unset)

Result:

  • RemoteRoot: /qwk
  • Inbox: /qwk
  • Outbox: /qwk

Example C — FTP with explicit layout

Endpoint: ftp://bbs.example.com
ftp.remoteRoot: /bbsdata
RemoteInboxPath: in
RemoteOutboxPath: out

Result:

  • RemoteRoot: /bbsdata
  • Inbox: /bbsdata/in
  • Outbox: /bbsdata/out

Credentials

The FTP transport uses Credentials.UsernamePassword(user, passwordProvider) from the profile.

For anonymous FTP, either:

  • Omit credentials (uses anonymous with a placeholder email)
  • Set username to anonymous and provide an email-style password

Connection Modes

Active vs Passive Mode

  • Active mode (default): The FTP server initiates the data connection back to the client. This is the traditional FTP mode and works well with many BBS systems.
  • Passive mode: The client initiates both control and data connections to the server. Better for clients behind firewalls or NAT.

To use passive mode, set ftp.passive=true in settings or use --passive flag in the demo tool.

Automatic Mode Fallback

The transport automatically handles connection mode failures by cycling through available modes:

  1. When starting in passive mode: PASV → EPSV → PORT (active)
  2. When starting in active mode: PORT → PASV

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.

Directory Listing Fallback

The transport also handles servers that advertise MLSD (machine-readable listing) support but have incomplete implementations:

  1. Attempts MLSD first (modern, structured format)
  2. Falls back to LIST if MLSD returns empty or fails (legacy format)

This fallback is automatic and invisible to callers.

BBS Server Compatibility

Synchronet BBS

Synchronet BBS servers may require special handling due to their FTP implementation:

  • Passive mode ports may be blocked: The server's passive data ports (returned in PASV/EPSV responses) may not be accessible. The transport will automatically fall back to active mode.
  • MLSD may return incomplete results: Despite advertising MLSD support, some Synchronet installations return empty listings. The transport falls back to LIST automatically.
  • NAT configuration: Synchronet servers behind NAT may return internal IP addresses in PASV responses. FluentFTP handles this, but if passive ports are blocked, active mode is required.

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.

Security Notes

TLS/FTPS

When ftp.tls=true:

  • Supports both explicit TLS (AUTH TLS after connection) and implicit TLS (port 990)
  • Set ftp.tls.mode to explicit (default) or implicit
  • Supports TLS 1.2 and TLS 1.3
  • By default, validates server certificates strictly

Certificate Validation

  • strict (default): Validates certificate chain and hostname. Recommended for production.
  • acceptAny: Accepts any certificate including self-signed. Use only in controlled environments.

Advanced TLS Configuration

TLS Protocol Versions

Control which TLS protocol versions are enabled:

  • Default: TLS 1.2 and 1.3 (recommended)
  • Legacy support: Can enable TLS 1.0 or 1.1 for older BBS systems
  • Setting: ftp.tls.protocols=tls12,tls13

Note: 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)
  • Setting: ftp.tls.dataEncryption=both

Certificate Pinning

Pin specific certificates by SHA256 thumbprint for enhanced security:

  • Provides protection against compromised Certificate Authorities
  • Only certificates matching the specified thumbprints will be accepted
  • Multiple thumbprints can be specified (comma-separated)
  • Setting: 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

Proxy Support

FTP connections can be routed through proxy servers. Supported proxy types:

  • HTTP/HTTPS: Standard HTTP proxies using CONNECT method
  • SOCKS4/4a: Basic SOCKS proxy protocol
  • SOCKS5: Modern SOCKS with authentication support

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

Demo Tool

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

Limitations

  • Only forward-slash path separator is supported.
  • Active mode requires incoming connections to be allowed through your firewall/router.

Forward-slash Path Separator

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.

Active Mode Network Requirements

When using active mode (PORT), the FTP server connects back to your machine on a dynamically allocated port. This requires:

  • Your firewall to allow incoming connections on high ports (typically 1024+)
  • Your router to support FTP connection tracking, or manual port forwarding

If active mode fails, the transport will attempt passive modes. If all modes fail, check your network configuration or contact the server administrator.

Dependencies

  • FluentFTP (MIT Licence) - FTP client library

Licence

MIT Licence. See LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 532 1/27/2026

Initial release.