How do I compile this?
Obtain the code from https://github.com/eddylangley/nih-sftp-server Then compile with something like:
gcc -O2 -Wall -Wextra -Werror -std=iso9899:1999 -pedantic-errors nih-sftp-server.c -o sftp-server
How do I install it?
That depends on your SSH daemon (server). Most SSH servers have a config file (typically
/etc/ssh/sshd_config), and as part of that they specify where the executable for the SFTP subsystem lives. Dropbear SSH hard codes the path in
options.h as follows:
#define SFTPSERVER_PATH "/usr/libexec/sftp-server"
So you’ll either want to copy the SFTP server there, or change the configuration to point to somewhere else.
Why? How bad a day were you having when you decided to write your own SFTP server?
Really quite bad. I wanted an SFTP server I could make work with Dropbear SSH. Dropbear doesn’t include one. Tried OpenSSH; swore at making it cross-compile, swore at the dependencies, downloaded the dependencies, swore at cross-compiling the dependencies, swore at the amount of unnecessary things I needed along the way, finally got it to cross-compile to find the executable simply failed with “seg fault”. Suffice to say the words
just configure and make might have caused an appoplectic reaction. Not good. Tried copying a binary from a similar platform; failed due to no libcrypt – which is silly as there are no cryptographic functions in an SFTP server (all the crypto magic is done by the SSH server).
Any positive thoughts?
I’ve always been interested in computational tasks that are simple enough to be coded in a single C file; mainly because this is very easy for people to work with. No headers, no configure step, no Makefile faff, no working out what to rebuild when, definitely no automake; if your C compiler can find the C library and a POSIX include file or two, it’ll compile. I’m also interested in things with a small implementation footprint and things that can work in finite resource. The NIH SFTP server has the smallest memory footprint of anything running on my Linux box; the executable can be as small as 16kB on disc.
SFTP is a simple request-response protocol; the SFTP server responds to packets on standard input by performing file operations and replying on standard out. Each and every input packet produces exactly one output packet; aside from allocating handles no persistent storage is needed bar the actual file system. Handling the input packets one at a time avoids any questions around request synchronisation or re-ordering; files are updated in the order requests arrive. While in theory an infinite number of requests can be outstanding there’s no point the SFTP server buffering more than one packet; this buffering had might as well occur in the SSH server, or better, the underlying operating system, where it could at least exert some back pressure on the SFTP client. All of which means the NIH SFTP server has exactly one input buffer and one output buffer.
Implementations should have limits; otherwise you could exhaust the entire memory of the server with an infinitely long filename – some would consider this a security hole. There are two limits arising directly from the code. Firstly the number of handles is limited to
MAX_HANDLES; a sensibly designed client shouldn’t need more than a handful of handles, and again, the client shouldn’t be able to exhaust the server’s file handle resource. The second limit is the size of the input and output packet buffers (by default the SFTP minimum of 34kB); this largely determines the memory footprint as very little is placed on the stack. The majority of SFTP operations (open, close, mkdir, stat, remove, rename) refer to a single file by name or handle so this limit isn’t reached until the full pathname of something is more than 17000 characters long! The remaining SFTP operations move data (read, write) so get broken into multiple operations in any case; aside from a minor performance effect, the buffer size is irrelevant. Directory listing is a more interesting case.
No malloc, and no fixed size string buffers. It turns out that you don’t need to keep filenames once you’ve given them handles, and if you use the magic of
fdopendir you can entirely avoid catenating filenames onto directory paths. All the names and paths originate from the input packet or the OS, so we don’t have to allocate storage from them. Ok, there’s one malloc hiding in the OS implementation of
realpath(), but if the OS can’t find space for at most one file path you have bigger problems! So no leaks, and just the one string length limit arising from the packet buffer size.
Wherever possible we’re zero-copy; data is read/written directly from/to the packet buffers.
Exceeding the implementation limits is fatal (in the computing sense of the word – no one actually dies). The NIH SFTP server will
assert() if you try and exceed its packet buffers. Disabling
assert() by defining
NDEBUG would be a bad thing to try.
How tested is it?
Not enough! But no-one’s paying me to code it and you get what you pay for. It seems to play nicely with Dropbear SSH and talks to PuTTy SFTP and various other SFTP clients.