ioFTPD is a high-performance, Windows-only FTP server originally developed by iniCom Networks, Inc. It is licensed under the GNU General Public License v2+. The project is effectively abandoned upstream; this repository represents a fork/continuation effort.
- Project Overview
- Architecture Summary
- Module Index
- Build Instructions
- Dependencies
- Companion Tools
- Configuration
- Long-Path Support
- Known Issues
- Modernization Roadmap
ioFTPD is a Win32 FTP daemon with:
- RFC 959 FTP protocol support plus modern extensions (FTPS/AUTH TLS, MLSD, XCRC, CPSV, SSCN, REST STREAM)
- I/O Completion Port (IOCP) based async I/O for high connection concurrency (up to 16,384 clients,
MAX_CLIENTS) - Tcl scripting engine for server-side event hooks (login, logout, upload, download, etc.)
- Virtual File System (VFS) with configurable mount points
- Per-user and per-group quotas, ratio tracking, and transfer statistics across 25 sections (
MAX_SECTIONS) - Bandwidth throttling per network device and per user
- Windows service integration
- TLS/FTPS via an embedded OpenSSL integration
- Flat-file user/group database
The server runs as a standard Win32 GUI application (hidden window) or as a Windows service.
WinMain / ServiceMain
└── DoSetup() # Path, config, exception handler, uptime init
└── InitializeDaemon() # Sequential module init (Init_Table)
└── CommonMain()
├── QueueJob(ServerStart) # Fires OnServerStart Tcl events
├── ProcessMessages() # Win32 message loop (blocks until shutdown)
└── DaemonDeInitialize() # Sequential module teardown
| Module | Source | Purpose |
|---|---|---|
| Memory | Memory.c | Custom bucket allocator, shared-ref allocator |
| Time | IoTime.c | Time utilities |
| IoProc | IoProcs.c | I/O procedure dispatch |
| Timer | Timer.c | Async timer system |
| Config | ConfigReader.c | INI file parser |
| File | File.c | File I/O helpers |
| LogSystem | LogSystem.c | Logging subsystem |
| Thread | Threads.c | IOCP thread pool + job queue |
| Windows | InternalMessageHandler.c | Hidden Win32 window + message loop |
| Debug | IoDebug.c | Exception handler, symbol server |
| Client | IoProcs.c/Client.c | Client slot management |
| DataCopy | DataCopy.c | File-to-socket transmit (TransmitFile) |
| Security | OpenSSL.c | TLS context management |
| Socket | Socket.c | Winsock, IOCP, bandwidth scheduler |
| Message | Message.c | FTP response message formatting |
| Help | Help.c | HELP file loading |
| MountFile | File.c | VFS mount-point loading |
| DirectoryCache | DirectoryCache.c | Directory entry caching |
| Group | GroupNew.c | Group database |
| User | UserNew.c | User database |
| Identify | Identify.c | Ident/host resolution |
| Event | Execute.c | Tcl event runner |
| TransmitPackage | DataCopy.c | Protocol-level send/recv packages |
| FTP | FtpServer.c | FTP settings, new-client setup |
| Services | services.c | Service/device binding and accept loop |
| Scheduler | Scheduler.c | Periodic task scheduler |
Main thread ─ Win32 message loop (ProcessMessages)
IOCP threads ─ Multiple (IoThreadEx) dequeuing from hCompletionPort
Worker threads ─ Multiple (WorkerThread) running job queue items
Socket scheduler ─ Single dedicated thread (SocketSchedulerThread) for bandwidth control
All async network I/O goes through Windows IOCP (hCompletionPort). Completion callbacks post jobs into the job queue (QueueJob), which worker threads pick up. Three priority levels exist: JOB_PRIORITY_HIGH, JOB_PRIORITY_NORMAL, JOB_PRIORITY_LOW.
AcceptEx (overlapped)
└── Service_AcceptClient()
└── FTP_New_Client() # Allocate FTP_USER, bind IOCP, send 220 banner
└── FTP_AcceptInput() # Queue receive job with idle timeout
└── FTP_ReceiveLine() # On data received
└── FTP_Command() # Dispatch to command handler
└── FTP_Close_Connection() # On QUIT or error
The command dispatch table (FtpCommand[] in FtpBaseCommands.c) maps command strings to handler functions. Commands are categorized as LOGIN_CMD, OTHER_CMD, or XFER_CMD.
Historically the code used WSAAsyncSelect (Windows message-based). The current codebase replaces this with WSAEventSelect + RegisterWaitForSingleObject threadpool callbacks, which post WM_ASYNC_CALLBACK messages to the hidden window (InternalMessageHandler.c) to preserve the original dispatch path.
The memory subsystem (Memory.c) provides:
FragmentAllocate/FragmentFree– A 128-bucket (MEMORY_BUCKETS) free-list allocator over a private heap (HeapCreate). Bucket sizes start at 8 bytes, increment by 8, 16, 24, … (quadratic growth). Allocations store their bucket index in the header word immediately before the returned pointer.AllocateShared/_AllocateShared– Reference-counted shared allocations. A canary value (0xDEADBEAF) is stored in the header.FreeSharedusesInterlockedDecrementon the refcount.- When compiled with
USE_MALLOC, all custom allocation is bypassed in favour of the CRT heap (used in Purify config for memory analysis).
| Pattern | Mechanism | Location |
|---|---|---|
| Shared/Exclusive r-w lock | LOCKOBJECT (Event + Semaphore) |
Locking.c |
| Spin lock | InterlockedExchange busy-wait |
Throughout (Socket, Memory, FTP) |
| Per-socket lock | CRITICAL_SECTION csLock |
IOSOCKET struct |
| Job queue lock | CRITICAL_SECTION csJobQueue |
Threads.c |
| Debug memory lock | CRITICAL_SECTION csMemoryDebug |
Memory.c |
| File | Purpose |
|---|---|
| Array.c | Generic dynamic array |
| Buffer.c | String/response buffer with format support |
| Change.c | SITE CHANGE admin command |
| Command.c | FTP command logic (login, upload, download, delete, mkdir, rename) |
| Compare.c | Path/string comparison helpers |
| ConfigReader.c | INI-file parser (Config_Get, Config_Get_Int, Config_Get_Bool) |
| Crc32.c | CRC32 computation and combination |
| DataCopy.c | Async file transfer using TransmitFile |
| DataOffset.c | Transfer resume/restart offset handling |
| DirectoryCache.c | In-memory directory cache |
| Execute.c | Tcl event execution (RunEvent) |
| File.c | File I/O, VFS mount-point management |
| FtpBaseCommands.c | Core FTP commands (AUTH, USER, PASS, PASV, PORT, STOR, RETR, …) |
| FtpDataChannel.c | Data channel establishment and transfer management |
| FtpServer.c | FTP_Init, FTP_New_Client, upload/download completion, FTP settings |
| FtpSiteCommands.c | SITE subcommands (WHO, KICK, KILL, BAN, …) |
| GroupFileModule.c | Group database file I/O |
| GroupNew.c | Group lookup, creation, deletion |
| Help.c | HELP file display |
| IdDatabase.c | UID/GID allocation and name-to-id database |
| Identify.c | Ident protocol and hostname resolution |
| InternalMessageHandler.c | Hidden Win32 window, message dispatch, crash guard, restart heartbeat |
| IoDebug.c | Exception handler, stack trace, symbol server, version info |
| IoProcs.c | IOCP callback dispatch, client job queues |
| IoString.c | String utilities (aswprintf, FormatString, etc.) |
| IoTime.c | Tick count helpers, time difference utilities |
| Locking.c | LOCKOBJECT shared/exclusive lock implementation |
| LogSystem.c | Log queue, file rotation, log formatting |
| LongPath.c | Long-path support: OS detection, \\?\ wrappers for all file APIs, IoCanonicalizePath |
| Main.c | Entry point, init table, daemon lifecycle, service support |
| Memory.c | Custom allocator, shared-ref allocator, debug memory tracking |
| Message.c | Message file display (MessageFile_Show) |
| MessageHandler.c | Message variable substitution engine |
| MessageObjects.c | Message object type dispatch |
| MessageVariables.c | Variable resolvers (%[USER], %[SPEED], etc.) |
| NewList.c | LIST, NLIST, MLSD, STAT directory listing |
| OpenSSL.c | TLS context creation/management, certificate generation |
| Permission.c | ACL and permission checking |
| PWD.c | Virtual path resolution |
| RemoteAdmin.c | Remote administration channel |
| RowParser.c | Tabular data parser for configuration |
| Scheduler.c | Periodic task scheduler |
| services.c | Service/device configuration, AcceptEx setup |
| sha1.c | SHA-1 hash implementation |
| Socket.c | Winsock2/IOCP socket layer, bandwidth scheduling, WSAEventSelect integration |
| SocketAPI.c | High-level socket API helpers |
| Stats.c | Server statistics collection |
| Tcl.c | Tcl interpreter lifecycle, command registration |
| Threads.c | Thread pool, job queue, CRC32 table |
| TickCountHelper.c | 64-bit tick count without 49.7-day rollover |
| Timer.c | Async timer creation/cancellation |
| UserFileModule.c | User database file I/O |
| UserNew.c | User lookup, creation, deletion |
| Who.c | SITE WHO / online data display |
| WinErrors.c | FormatError helper |
Key structural types:
| Header | Key Types |
|---|---|
| Client.h | CLIENT, CLIENTSLOT |
| ConnectionInfo.h | CONNECTION_INFO |
| ControlConnection.h | COMMAND, FTP_USER control channel |
| DataConnection.h | DATACHANNEL |
| Ftp.h | FTP_USER, FTP_SETTINGS, FTPCOMMAND |
| GroupFile.h | GROUPFILE |
| IdDataBase.h | ID database types |
| IoService.h | IOSERVICE, IODEVICE, BANDWIDTH |
| IoSocket.h (iosocket.h) | IOSOCKET, SETSOCKETOVERLAPPED |
| IoOverlapped.h | IOOVERLAPPED, SETSOCKETOVERLAPPED |
| LockObject.h | LOCKOBJECT |
| LongPath.h | Long-path API declarations (IoIsNtfsPathTooLongError, IoCreateFile, IoMoveFileEx, etc.) |
| ServerLimits.h | All compile-time constants |
| Threads.h | JOB, THREADDATA, THEME_FIELD |
| UserFile.h | USERFILE, USERFILE_OLD |
| VirtualPath.h | VIRTUALPATH |
| Component | Required Version | Notes |
|---|---|---|
| Visual Studio | 2022 (v143 toolset) | MSVC C compiler |
| Windows SDK | 10.0.19041.0 or later | Minimum target: Vista (0x0600) |
| OpenSSL | 3.6.1 (Release) | See DEV-SETUP.md for build steps |
| Tcl | 9.0.2 | See DEV-SETUP.md for build steps; no source patches required |
Warning: The Release and Debug build configurations reference library paths that may not exist on your machine. Update
AdditionalIncludeDirectoriesandAdditionalLibraryDirectoriesinioFTPD-v7.vcxprojbefore building.
| Configuration | Output | Notes |
|---|---|---|
| Release|x64 | system\x64\ioFTPD.exe |
Primary release build. Optimized x64, links x64 libssl.lib + libcrypto.lib, tcl90.lib |
| Release|Win32 | system\ioFTPD.exe |
Win32 maintenance build; companion tools (ioFTPD-Watch, ServiceInstaller) build Win32 only |
| Debug|Win32 | system\ioFTPD-debug.exe |
No optimization, tcl90.lib, OpenSSL |
| Purify|Win32 | system\ioFTPD.exe |
Debug + EnableFastChecks, tcl90.lib, libssl.lib/libcrypto.lib, memory analysis |
All configurations link against tcl90.lib and OpenSSL 3.x (libssl.lib / libcrypto.lib). x64 library paths are under C:\Dev\Libs\VS2022\x64\; Win32 paths are under C:\Dev\Libs\VS2022\x86\.
- Open
ioFTPD-v7.slnin Visual Studio 2022. - Update include/library paths in the project properties to match your local library locations.
- Ensure OpenSSL DLLs (
libssl-3.dll/libcrypto-3.dll) and the Tcl DLL (tcl90.dll) are in thesystem\output directory. - Select the desired configuration and build.
Every build automatically embeds the current git state via a pre-build PowerShell script (scripts/generate_version.ps1). The resulting version string has the format:
7.10.1.72-0ad5fdd (clean build)
7.10.1.72-0ad5fdd-dirty (uncommitted changes present)
The build number (e.g. 72) is the monotonically increasing git rev-list --count HEAD value. The 7-character hash provides an exact source reference for crash reports. The version appears in the startup log, SITE IOVERSION, crash-log header, and io version Tcl command.
If
include/GitVersion.his missing (e.g. after a clean checkout), run the script once manually before building:powershell -NoProfile -ExecutionPolicy Bypass -File scripts\generate_version.ps1
ioFTPD v8.0.0 requires two Visual C++ Redistributable packages on the target machine:
| Package | Required by | Download |
|---|---|---|
| Visual C++ 2015-2022 x64 | ioFTPD.exe, tcl90.dll, OpenSSL DLLs |
https://aka.ms/vs/17/release/vc_redist.x64.exe |
| Visual C++ 2015-2022 x86 | ioFTPD-Watch.exe (32-bit watchdog) |
https://aka.ms/vs/17/release/vc_redist.x86.exe |
Both packages are included in the PREREQUISITES\ folder of the release archive.
At runtime, system\ must contain:
ioFTPD.exe(or the appropriate build variant)- OpenSSL DLLs (
libssl-3-x64.dll,libcrypto-3-x64.dll) - Tcl DLL (
tcl90.dll) ioFTPD.ini(configuration file)- VFS mount-point files
| Library | Version | Status | CVEs / Notes |
|---|---|---|---|
| OpenSSL | 3.6.1 | Active (LTS) | All known CVEs addressed; TLS 1.3 + ECDHE supported |
| Tcl | 9.0.2 | Active | No source patches required; official unmodified build |
| Winsock2 | Windows SDK | Active | Used for all networking |
| PDH (Performance Data Helper) | Windows SDK | Active | Used for system uptime detection |
| shlwapi | Windows SDK | Active | Path utilities |
| version.lib | Windows SDK | Active | GetFileVersionInfo |
| crypt32 | Windows SDK | Active | Certificate store |
ioFTPD v7.8+ uses Tcl 9.0, which can be built from the official unmodified source.
The handle-inheritance issues that required patching Tcl 8.5's TclpCreateProcess
and TcpAccept are resolved natively in Tcl 9.0:
tclWinSock.c:TcpAccept()already callsSetHandleInformation(HANDLE_FLAG_INHERIT, 0)on newly accepted sockets.tclWinPipe.c: Pipe handles are created withbInheritHandle = FALSEin theSECURITY_ATTRIBUTESstruct passed toCreateProcess.
ioFTPD still maintains its own AcquireHandleLock()/ReleaseHandleLock() critical section for its own CreateProcess calls (Execute.c) and socket-accept paths (Socket.c, FtpDataChannel.c), but these no longer need to reach into Tcl.
See Tcl.txt for build steps.
| Tool | Project | Purpose |
|---|---|---|
ioFTPD-Watch |
ioFTPD-Watch/ |
Watchdog process; monitors ioFTPD and restarts it on crash |
IoKnock |
IoKnock/ |
Port-knocking client GUI for firewall bypass |
ServiceInstaller |
ServiceInstaller/ |
Installs/uninstalls ioFTPD as a Windows service |
VersionAppend |
VersionAppend/ |
Build tool; appends version info to the executable |
The server reads ioFTPD.ini (or a path specified on the command line) at startup. Key INI sections:
| Section | Notable Keys |
|---|---|
[FTP] |
Idle_TimeOut, Login_TimeOut, Login_Attempts, Transfer_Buffer, Socket_Send_Buffer, Idle_Exempt, Banned_User_Flag, Quiet_Login_Flag, Long_Path_Support, Network_Mounts_File, Network_Check_Interval, Network_Max_Retry_Interval |
[Events] |
OnServerStart, OnServerStop, OnFtpLogOut, OnFtpUpload, OnFtpDownload |
[Network] |
Log_OpenSSL_Transfer_Errors, Scheduler_Update_Speed |
[VFS_PreLoad] |
DELAY |
[Services] |
FTP service definitions (port, device binding, TLS settings) |
Configuration is live-reloadable for many settings via SITE CONFIG RELOAD or equivalent events.
From v8.1.0, ioFTPD automatically monitors and reconnects every UNC share
(\\server\share\...) referenced in .vfs files. No configuration is required for
health monitoring — it is always on for UNC paths.
An optional credential file (etc\netmounts.cfg) provides usernames and passwords for
shares that require credentials different from the Windows account ioFTPD runs as.
Important: Entries in
netmounts.cfgare matched as literal strings (case-insensitive). The UNC path must be written identically in bothnetmounts.cfgand your.vfsfile. Using an IP address in one file and a hostname in the other, or a short hostname in one and a FQDN in the other, will result in credentials silently not being applied — there is no error logged.
See docs/network-mounts.md for the full feature guide including configuration options, health monitoring behaviour, SMB compatibility, and troubleshooting.
ioFTPD supports file system paths longer than the legacy MAX_PATH (260-character) Windows limit on
Windows 10 build 14393+ / Server 2016+, subject to the requirements below.
All three must be present:
- Windows 10 build 14393 (v1607) or Server 2016 or later.
- Registry opt-in —
HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled = 1. - Manifest —
ioFTPD.exeembeds<longPathAware>true</longPathAware>(built in at compile time viaioFTPD.additional.manifest).
Control via ioFTPD.ini:
[FTP]
Long_Path_Support = Auto ; Auto (default), On, or OffWhen Auto, ioFTPD probes the OS version and registry at startup and logs the result.
| FTP Command / Feature | Local NTFS | Mapped Drive (Z:\) | UNC (\\server\share) | Notes |
|---|---|---|---|---|
| LIST / MLSD (directory listing) | ✔ | ✔ | ✔ (until SMB limit) | SMB often fails earlier than NTFS |
| CWD / PWD | ✔ | ✔ | ✔ | UNC normalisation may fail at extreme depth |
| STOR / APPE (upload) | ✔ | ✔ | ⚠ May fail early | SMB rejects long normalised paths |
| RETR (download) | ✔ | ✔ | ⚠ Same as STOR | |
| DELE (file delete) | ✔ | ✔ | ✔ (until SMB limit) | |
| RMD (directory delete) | ✔ | ✔ | ✔ (until SMB limit) | .ioFTPD handling fully long-path aware |
| RNFR / RNTO (rename/move) | ✔ | ✔ | ⚠ Destination path often fails first | .ioFTPD creation may trigger SMB limit |
| SIZE / MDTM / MLST | ✔ | ✔ | ✔ (until SMB limit) | |
| DirectoryCache recursion | ✔ | ✔ | ⚠ Deep UNC paths may fail | |
| Reparse points (junctions) | ✔ | ✔ | ❌ Not supported | SMB cannot expose reparse metadata |
| Maximum path length | ~32 767 UTF-16 chars | ~32 767 UTF-16 chars | ⚠ Typically 1–4 k (SMB server-dependent) | SMB is the limiting factor |
| Per-component limit | 255 chars | 255 chars | ⚠ 255 or less (some servers: 240 or 200) |
- Local NTFS is the most robust backend for long paths.
- Mapped drives behave identically to local NTFS — Windows resolves the UNC path before ioFTPD sees it.
- UNC / SMB paths are the weakest: SMB servers impose their own normalisation limits, often failing long before NTFS would. SMB normalisation commonly returns
ERROR_FILE_NOT_FOUND(2) orERROR_PATH_NOT_FOUND(3) for paths that exceed its limit — ioFTPD classifies these correctly and returns550 Path too long for NTFS.to the FTP client. - Error reporting — when any file operation fails due to path length, the FTP client receives
550 <path>: Path too long for NTFS.regardless of which underlying error code Windows produced.
- OpenSSL upgraded to 3.6.1 (resolved — TLS 1.3, ECDHE, modern cipher suites; see OpenSSL.txt).
- Tcl upgraded to 9.0.2 (resolved — official unmodified build; no source patches required).
32-bit onlyResolved in v8.0.0 — a native AMD64 build (Release|x64) ships alongside the Win32 build. The x64 build has no virtual-address ceiling and enables high-entropy ASLR, DEP, and/DYNAMICBASE. The Win32 build continues as a maintenance target.ASLR is explicitly disabledResolved for x64 in v8.0.0 —DYNAMICBASE+HIGHENTROPYVA+NXCOMPATare all enabled on the x64 config. The Win32 release build retains the original settings for binary compatibility with the existing 32-bit deployment base.DEP/NX is explicitly disabledResolved for x64 in v8.0.0 — see item 4.- Passwords hashed with SHA-1 (no salt). SHA-1 is broken for password storage; modern alternatives require bcrypt, scrypt, or Argon2.
- IPv4 only. The codebase is hardcoded to
AF_INET/sockaddr_in. IPv6 is not supported. - Win32 message window dependency. The async I/O dispatch relies on a hidden Win32 message window, a design pattern unsuitable for modern server applications.
GetVersionExis deprecated since Windows 8.1. Behaviour may be incorrect on Windows 10/11/Server 2025.
The following changes are recommended in rough priority order:
Upgrade OpenSSL to 3.xDone – OpenSSL 3.6.1 with TLS 1.3 and ECDHE support (seeOpenSSL.txt).- Replace SHA-1 password hashing – Migrate to
bcrypt/Argon2with per-user salts. Provide a migration path for existing password files. - Enable ASLR and DEP – Set
RandomizedBaseAddress=trueandDataExecutionPrevention=truein all configurations. Upgrade TclDone – Tcl 9.0.2, official unmodified build, no source patches required.- Harden
DummyEncode/DummyDecode– Replace with a standard authenticated encryption scheme (AES-GCM).
Port to 64-bitDone in v8.0.0 – Native AMD64 build ships. All pointer-truncation issues identified in the audit have been resolved.DC_MESSAGE_WIRE/ONLINEDATA_WIREprovide a cross-architecture IPC wire format. External IPC clients must be rebuilt against the v8.0 headers.- Enable Buffer Security Check (
/GS) – Currently disabled in Debug config; enable across all configurations. - Replace spin-lock busy-waits – Replace
while (InterlockedExchange(&lock, TRUE)) SwitchToThread()with properCRITICAL_SECTIONorSRWLockusage. - Fix
DummyEncodeunsigned underflow – Guard the loop with an early-out ifdwIn < 4. - Replace
GetVersionEx– UseVerifyVersionInfoorRtlGetVersion(viaversionhelpers.h).
- IPv6 support – Generalize socket layer to
AF_UNSPEC,sockaddr_storage, andgetaddrinfo/getnameinfo. - Replace Win32 message window – Move async event dispatch to a dedicated I/O thread or use
PostQueuedCompletionStatusexclusively. - Replace INI-file user database – Consider SQLite or a structured binary format with proper locking semantics.
- Remove commented-out dead code –
Unused-code.txt, commentedBindSocketToDevice,UnbindSocket, deadlock detection. - Replace
wsprintfwith safe alternatives – UseStringCbPrintforsnprintf_s.