Permalink
Browse files

Merge pull request #4 from milesplit/master

NLST support and cleanups
  • Loading branch information...
alanszlosek committed Apr 14, 2012
2 parents 3a939a3 + 5c61a86 commit a5438da895390b963b2ebc8db388c80f4c245b35
Showing with 211 additions and 61 deletions.
  1. +4 −2 README.markdown
  2. +3 −31 dummyfs.js
  3. +81 −28 ftpd.js
  4. +123 −0 glob.js
View
@@ -14,8 +14,7 @@ I assume you'll want to customize:
For my specific needs (at work) we needed custom user authentication, to sandbox all the file operations, and to run special code when a file is uploaded.
-Thanks,
- Alan
+Thanks, Alan
Status
----
@@ -51,6 +50,9 @@ Then implement the following event callbacks with logic you need performed:
* command:user - Same as command:pass above, but first parameter will be the username that was sent from the client.
* command:pass - Sends three params. The first is the password. The second is a callback to be called if you determine the password is correct ... pass the username as the first parameter to this callback. Call the second if incorrect.
+Also, don't run node as root just so you can get access to the FTP port. We run our node FTP server as an unprivileged user and perform port-forwarding with iptables. The following should work for you as well:
+
+> iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 21 -j REDIRECT --to-port 10000
04 September 2011
----
View
@@ -1,42 +1,14 @@
-var sys = require("sys");
+var path = require("path");
function dummyfs(root) {
if (!root) root = "/"; // default to actual root ... probably should default to process owner's home instead
this.dir = root;
}
-// this is deprecated in future versions of node, in favor of: util.inherits(...)
-sys.inherits(dummyfs, process.EventEmitter);
exports.dummyfs = dummyfs;
dummyfs.prototype.chdir = function(dir) {
- if(dir.charAt(dir.length-1) != "/") dir += "/";
- // this still might not be smart enough
- if(dir.charAt(0) != "/") {
- if(dir.substr(0,2) == "..") {
- x = dir.split("/");
- for(i=0; i<x.length; i++) {
- if(x[i] == "..") {
- part = this.dir.split("/");
- part.splice(part.length -2, 1);
- ret = part.join("/");
- if(ret.charAt(ret.length-1) != "/") ret += "/";
- this.dir = ret;
- } else {
- this.dir += x[i];
- }
- }
- } else {
- if(dir.substr(0,2) == "./") {
- this.dir += dir.substr(2,dir.length);
- } else {
- this.dir += dir;
- }
- }
- } else {
- this.dir = dir;
- }
- if(this.dir.charAt(this.dir.length-1) != "/") this.dir += "/";
- return(this.dir);
+ this.dir = path.resolve(this.dir, dir);
+ return(this.dir);
}
dummyfs.prototype.cwd = function() {
View
109 ftpd.js
@@ -1,8 +1,9 @@
var net = require("net");
-var sys = require("sys");
+var util = require("util");
var fs = require("fs");
-var path = require("path");
var dummyfs = require("./dummyfs");
+var PathModule = require("path");
+var glob = require('./glob');
require('./date-format');
/*
@@ -218,8 +219,8 @@ function createServer(host, sandbox) {
case "DELE":
// Delete file.
if (!authenticated()) break;
- var filename = fixPath(socket.fs, commandArg);
- fs.unlink(socket.sandbox + filename, function(err){
+ var filename = PathModule.resolve(socket.fs.cwd(), commandArg);
+ fs.unlink( PathModule.join(socket.sandbox, filename), function(err){
if (err) {
logIf(0, "Error deleting file: "+filename+", "+err, socket);
// write error to socket
@@ -280,20 +281,23 @@ function createServer(host, sandbox) {
socket.write("226 Transfer OK\r\n");
pasvconn.end();
};
+ var failure = function() {
+ pasvconn.end();
+ };
+ var path = PathModule.join(socket.sandbox, socket.fs.cwd());
if (pasvconn.readable) pasvconn.resume();
logIf(3, "Sending file list", socket);
- fs.readdir(socket.sandbox + socket.fs.cwd(), function(err, files) {
- var path = socket.sandbox + socket.fs.cwd();
+ fs.readdir(path, function(err, files) {
if (err) {
logIf(0, "While sending file list, reading directory: " + err, socket);
- pasvconn.write("", success);
+ pasvconn.write("", failure);
} else {
// Wait until acknowledged!
socket.write("150 Here comes the directory listing\r\n", function() {
logIf(3, "Directory has " + files.length + " files", socket);
for (var i = 0; i < files.length; i++) {
var file = files[ i ];
- var s = fs.statSync(path + file);
+ var s = fs.statSync( PathModule.join(path, file) );
var line = s.isDirectory() ? 'd' : '-';
if (i > 0) pasvconn.write("\r\n");
line += (0400 & s.mode) ? 'r' : '-';
@@ -338,8 +342,8 @@ function createServer(host, sandbox) {
case "MKD":
// Make directory.
if (!authenticated()) break;
- var filename = fixPath(socket.fs, commandArg);
- fs.mkdir(socket.sandbox + filename, 0755, function(err){
+ var filename = PathModule.resolve(socket.fs.cwd(), commandArg);
+ fs.mkdir( PathModule.join(socket.sandbox, filename), 0755, function(err){
if(err) {
logIf(0, "Error making directory " + filename, socket);
// write error to socket
@@ -361,7 +365,57 @@ function createServer(host, sandbox) {
break;
case "NLST":
// Returns a list of file names in a specified directory.
- socket.write("202 Not supported\r\n");
+ if (!authenticated()) break;
+
+ /*
+ Normally the server responds with a mark using code 150. It then stops accepting new connections, attempts to send the contents of the directory over the data connection, and closes the data connection. Finally it
+
+ accepts the LIST or NLST request with code 226 if the entire directory was successfully transmitted;
+ rejects the LIST or NLST request with code 425 if no TCP connection was established;
+ rejects the LIST or NLST request with code 426 if the TCP connection was established but then broken by the client or by network failure; or
+ rejects the LIST or NLST request with code 451 if the server had trouble reading the directory from disk.
+
+ The server may reject the LIST or NLST request (with code 450 or 550) without first responding with a mark. In this case the server does not touch the data connection.
+ */
+
+ whenDataWritable( function(pasvconn) {
+ // This will be called once data has ACTUALLY written out ... socket.write() is async!
+ var success = function() {
+ socket.write("226 Transfer OK\r\n");
+ pasvconn.end();
+ };
+ var failure = function() {
+ pasvconn.end();
+ };
+ // Use temporary filesystem path maker since a path might be sent with NLST
+ var temp = '';
+ if (commandArg) {
+ // Remove double slashes or "up directory"
+ commandArg = commandArg.replace(/\/{2,}|\.{2}/g, '');
+ if (commandArg.substr(0, 1) == '/') {
+ temp = PathModule.join(socket.sandbox, commandArg);
+ } else {
+ temp = PathModule.join(socket.sandbox, socket.fs.cwd(), commandArg);
+ }
+ } else temp = PathModule.join(socket.sandbox, socket.fs.cwd());
+ if (pasvconn.readable) pasvconn.resume();
+ logIf(3, "Sending file list", socket);
+
+ glob.glob(temp, function(err, files) {
+ //fs.readdir(socket.sandbox + temp.cwd(), function(err, files) {
+ if (err) {
+ logIf(0, "During NLST, error globbing files: " + err, socket);
+ socket.write("451 Read error\r\n");
+ pasvconn.write("", failure);
+ return;
+ }
+ // Wait until acknowledged!
+ socket.write("150 Here comes the directory listing\r\n", function() {
+ logIf(3, "Directory has " + files.length + " files", socket);
+ pasvconn.write( files.map(PathModule.basename).join("\015\012") + "\015\012", success);
+ });
+ });
+ });
break;
case "NOOP":
// No operation (dummy packet; used mostly on keepalives).
@@ -379,7 +433,7 @@ function createServer(host, sandbox) {
function(username) { // implementor should call this on successful password check
socket.write("230 Logged on\r\n");
socket.username = username;
- socket.sandbox = server.baseSandbox + '/' + username;
+ socket.sandbox = PathModule.join(server.baseSandbox, username);
},
function() { // call second callback if password incorrect
socket.write("530 Invalid password\r\n");
@@ -498,13 +552,13 @@ function createServer(host, sandbox) {
whenDataWritable( function(pasvconn) {
pasvconn.setEncoding(socket.mode);
- var filename = fixPath(socket.fs, commandArg);
+ var filename = PathModule.resolve('/', commandArg);
if(filename != socket.filename)
{
socket.totsize = 0;
socket.filename = filename;
}
- fs.open(socket.sandbox + socket.filename, "r", function (err, fd) {
+ fs.open( PathModule.join(socket.sandbox, socket.filename), "r", function (err, fd) {
console.trace("DATA file " + socket.filename + " opened");
socket.write("150 Opening " + socket.mode.toUpperCase() + " mode data connection\r\n");
function readChunk() {
@@ -541,8 +595,8 @@ function createServer(host, sandbox) {
case "RMD":
// Remove a directory.
if (!authenticated()) break;
- var filename = fixPath(socket.fs, commandArg);
- fs.rmdir(socket.sandbox + filename, function(err){
+ var filename = PathModule.resolve(socket.fs.cwd(), commandArg);
+ fs.rmdir( PathModule.join(socket.sandbox, filename), function(err){
if(err) {
logIf(0, "Error removing directory "+filename, socket);
socket.write("550 Delete operation failed\r\n");
@@ -553,18 +607,18 @@ function createServer(host, sandbox) {
case "RNFR":
// Rename from.
if (!authenticated()) break;
- socket.filefrom = fixPath(socket.fs, commandArg);
- logIf(3, "Rename from "+socket.filefrom, socket);
- path.exists(socket.sandbox + socket.filefrom, function(exists) {
+ socket.filefrom = PathModule.resolve(socket.fs.cwd(), commandArg);
+ logIf(3, "Rename from " + socket.filefrom, socket);
+ path.exists( PathModule.join(socket.sandbox, socket.filefrom), function(exists) {
if (exists) socket.write("350 File exists, ready for destination name\r\n");
else socket.write("350 Command failed, file does not exist\r\n");
});
break;
case "RNTO":
// Rename to.
if (!authenticated()) break;
- var fileto = fixPath(socket.fs, commandArg);
- fs.rename(socket.sandbox + socket.filefrom, socket.sandbox + fileto, function(err){
+ var fileto = PathModule.resolve(socket.fs.cwd(), commandArg);
+ fs.rename( PathModule.join(socket.sandbox, socket.filefrom), PathModule.join(socket.sandbox, fileto), function(err){
if(err) {
logIf(3, "Error renaming file from "+socket.filefrom+" to "+fileto, socket);
socket.write("550 Rename failed\r\n");
@@ -579,8 +633,8 @@ function createServer(host, sandbox) {
case "SIZE":
// Return the size of a file. (RFC 3659)
if (!authenticated()) break;
- var filename = socket.fs.cwd() + commandArg;
- fs.stat(socket.sandbox + filename, function (err, s) {
+ var filename = PathModule.resolve(socket.fs.cwd(), commandArg);
+ fs.stat( PathModule.join(socket.sandbox, filename), function (err, s) {
if(err) {
logIf(0, "Error getting size of file: "+filename, socket);
socket.write("450 Failed to get size of file\r\n");
@@ -615,9 +669,8 @@ function createServer(host, sandbox) {
if (!authenticated()) break;
whenDataWritable( function(dataSocket) {
// dataSocket comes to us paused, so we have a chance to create the file before accepting data
- filename = fixPath(socket.fs, commandArg);
-
- fs.open(socket.sandbox + filename, 'w', 0644, function(err, fd) {
+ filename = PathModule.resolve(socket.fs.cwd(), commandArg);
+ fs.open( PathModule.join(socket.sandbox, filename), 'w', 0644, function(err, fd) {
if(err) {
logIf(0, 'Error opening/creating file: ' + filename, socket);
socket.write("553 Could not create file\r\n");
@@ -636,7 +689,7 @@ function createServer(host, sandbox) {
var writeCallback = function(err, written) {
var buf;
if (err) {
- logIf(0, "Error writing " + socket.sandbox + filename + ": " + err, socket);
+ logIf(0, "Error writing " + PathModule.join(socket.sandbox, filename) + ": " + err, socket);
return;
}
writtenToFile += written;
@@ -720,5 +773,5 @@ function createServer(host, sandbox) {
return server;
}
-sys.inherits(createServer, process.EventEmitter);
+util.inherits(createServer, process.EventEmitter);
exports.createServer = createServer;
Oops, something went wrong.

0 comments on commit a5438da

Please sign in to comment.