Simple, Extensible FTP Server in Pure JavaScript
Introduction
This is a simple but very configurable FTP server. Notable features include:
- Abstracts out the
fs
module, so you can use any implementation,
even on a per-user basis. This makes it possible for each user to have
his/her own virtual file system, isolated from that of the system or other
users. - Provides hooks for handling authentication, content modification, etc.
- Supports TLS with explicit AUTH.
Installation
npm install ftpd
Usage
See example code in test.js
FtpServer options:
host (string) - IP Address
host is a string representation of the IP address clients use to connect to the FTP server. It's imperative that this actually reflects the remote IP the clients use to access the server, as this IP will be used in the establishment of PASV data connections. If this IP is not the one clients use to connect, you will see some strange behavior from the client side (hangs).
options (object) - Configuration
See test.js
for a simple example. FtpServer
accepts the following options:
Path Configurations
Both these need to be set - there are no defaults.
-
getInitialCwd
: Gets the initial working directory for the user. Called after user is authenticated.
This path is relative to the root directory. The user may escape their initial cwd.
-
Pattern: function(username, [callback(err, path)])
-
Arguments:
- username (string): the username to get CWD for
- callback (function, optional):
-
Examples:
- Simplest usage, no callback, just return:
getInitialCwd: function(connection) {
return "/" + connection.username;
}
getInitialCwd: function(connection, callback) {
var userDir = '/' + connection.username;
fs.exists(userDir, function(exists) {
if (exists) {
callback(null, userDir);
} else {
fs.mkDir(userDir, function(err) {
callback(err, userDir);
});
}
});
}
- Typical cases where you would want/need the callback involve retrieving configurations from external datasources and suchlike.
-
getRoot
: Gets the root directory for the user. This directory has the path '/' from the point of view of the user.
The user is not able to escape this directory.
-
Pattern: function(connection, [callback(err, rootPath)])
-
Arguments:
- connection (object): the connection for which to get root
- callback (function, optional):
-
Examples:
getRoot: function() {
return process.cwd();
}
getRoot: function(connection, callback) {
var rootPath = process.cwd() + '/' + connection.username;
fs.exists(rootPath, function(exists) {
if (exists) {
callback(null, rootPath);
} else {
fs.mkDir(userDir, function(err) {
if (err) {
callback(null, '/');
} else {
callback(err, rootPath);
}
});
}
});
}
- Typical cases where you would want/need the callback involve retrieving configurations from external datasources and suchlike.
- Additionally, you may want to provide emulation of a path, for instance /users/(username)/ftproot.
File/handling Configurations
useWriteFile
: (default: false)
- If set to
true
, then files which the client uploads are buffered in memory and then written to disk using writeFile
. - If
false
, files are written using writeStream.
useReadFile
: (default: false)
- If set to
true
, then files which the client downloads are slurped using 'readFile'. - If
false
, files are read using readStream.
uploadMaxSlurpSize
: (default: unlimited)
- Determines the maximum file size (in bytes) for which uploads are buffered in memory before being written to disk.
- Has an effect only if
useWriteFile
is set to true
. - If
uploadMaxSlurpSize
is not set, then there is no limit on buffer size.
hideDotFiles
: (default: false)
- Hides files beginning with a dot (UNIX hidden files) on
LIST
commands.
maxStatsAtOnce
: (default: 5)
- The maximum number of concurrent calls to
fs.stat
which will be
made when processing a LIST
request.
filenameSortFunc
: (default: localeCompare
)
filenameSortMap
: (default: function (x) { return x.toUpperCase() }
)
- A function which is applied to each filename before sorting.
- If set to
false
, filenames are unaltered.
dontSortFilenames
: (default: false)
- If this is set, then filenames are not sorted in responses to the
LIST
and NLST
commands.
noWildcards
: (default: false)
- If set to
true
, then LIST
and NLST
treat the characters ?
and *
as literals instead of as wildcards.
Command configuration
allowedCommands
: (default: undefined)
- List of strings, the server will respond to only commands contained in this list, all other commands will result in a 502 unimplemented error.
Connectivity settings
tlsOptions
: (default: undefined)
- If this is set, the server will allow explicit TLS authentication.
- Value should be a dictionary which is suitable as the
options
argument of tls.createServer
.
tlsOnly
: (default: false)
- If this is set to
true
, and tlsOptions
is also set, then the server will not allow logins over non-secure connections.
allowUnauthorizedTls
: ?? I obviously set this to true when tlsOnly is on -someone needs to update this.pasvPortRangeStart
: (default: random?)
- Integer, specifies the lower-bound port (min port) for creating PASV connections
pasvPortRangeEnd
: (default: random?)
- Integer, specifies the upper-bound port (max port) for creating PASV connections
Filesystem Abstraction
Filesystem abstraction makes it possible to
create an FTP server which interacts directly with a database rather than the
actual filesystem.
The server raises a command:pass
event which is given pass
, success
and
failure
arguments. On successful login, success
should be called with a
username argument. It may also optionally be given a second argument, which
should be an object providing an implementation of the API for Node's fs
module.
The following must be implemented:
unlink
readdir
mkdir
open
close
rmdir
rename
stat
→
- specific object properties:
{ mode, isDirectory(), size, mtime }
- if
useWriteFile
option is not set or is false
createWriteStream
: Returns a writable stream, requiring:
- events: 'open', 'error', 'finish'
- functions: 'write', 'end'
- properties: 'bytesWritten'
- if
useWriteFile
option is set to 'true'
- if
useReadFile
option is not set or is false
createReadStream
: Returns a readable stream, requiring:
- events: 'error', 'data', 'end'
- functions: 'destroy'
- if
useReadFile
option is set to 'true'
FtpServer
has listen
and close
methods which behave as expected. It
emits close
and error
events.