UNPKG

@twec/sftp-server

Version:

SFTP Server.

230 lines (203 loc) 6.73 kB
/* eslint-disable no-param-reassign */ // Needed for assignment of statresponder parameters // "use strict"; const fs = require('fs'); const debug = require('debug'); const SFTPServer = require('node-sftp-server'); const logger = { debug: debug('sftp-server:debug'), info: debug('sftp-server:info'), error: debug('sftp-server:error'), }; // Use the directory that the server lives in as the root directory const serverRootDirectory = __dirname; const tempDirectory = 'tmp'; // Build the full path on the server function getFullPath(path) { return `${serverRootDirectory}/${path}`; } function tempDir() { return getFullPath(tempDirectory); } function exists(path) { try { fs.lstatSync(path); return true; } catch (err) { return false; } } function deleteFile(path) { try { if (exists(path)) fs.unlinkSync(path); } catch (err) {} } function deleteAllFilesInDirectory(path) { try { if (!exists(path)) return; const files = fs.readdirSync(path); files.forEach(file => { deleteFile(`${path}/${file}`); }); } catch (err) {} } function createTempDir() { try { if (!exists(tempDir())) fs.mkdirSync(tempDir()); return true; } catch (err) { return false; } } function createSFTPServer({ hostKey, port, username, password }) { if (!createTempDir()) { logger.info(`sftp-server unable to create temp directory: ${tempDir()}`); return; } const srv = new SFTPServer({ privateKeyFile: hostKey, temporaryFileDirectory: tempDir(), // debug: true }); srv.listen(port); logger.info(`sftp-server listening on port: ${port}`); srv.on('connect', function(auth, info) { logger.info( `Authentication attempted, client info is: ${JSON.stringify( info, )}, auth method is: ${auth.method}`, ); // Reject the connection if invalid username or password if ( auth.method !== 'password' || auth.username !== username || auth.password !== password ) { logger.info('Connection rejected because of username and/or password'); return auth.reject(['password'], false); } // Try accepting the connection return auth.accept(function(session) { logger.info('Accepted the connection.'); // Server directory listing session.on('readdir', function(path, responder) { const fullPath = getFullPath(path); logger.info(`readdir: ${fullPath}`); let results = []; const dirs = function() { try { if (exists(fullPath)) results = fs.readdirSync(fullPath); } catch (err) {} return results; }.apply(this); let i = 0; responder.on('dir', function() { if (dirs[i]) { responder.file(dirs[i]); i += 1; return i; } return responder.end(); }); return responder.on('end', function() { return logger.info('readdir: do cleanup here.'); }); }); // Read a file on the server session.on('readfile', function(path, writestream) { const fullPath = getFullPath(path); logger.info(`readfile: ${fullPath}`); const readstream = fs.createReadStream(fullPath); return readstream.pipe(writestream); }); // Write a file to the server session.on('writefile', function(path, readstream) { const fullPath = getFullPath(path); logger.info(`writefile: ${fullPath}`); const writestream = fs.createWriteStream(fullPath); readstream.on('end', function() { logger.info('writefile request has ended.'); }); return readstream.pipe(writestream); }); // Delete a file on the server session.on('delete', function(path, callback) { try { const fullPath = getFullPath(path); logger.info(`delete file: ${fullPath}`); if (exists(fullPath)) fs.unlinkSync(fullPath); callback.ok(); } catch (err) { callback.fail(); } }); // Rename a file on the server session.on('rename', function(oldPath, newPath, callback) { try { const oldFullPath = getFullPath(oldPath); const newFullPath = getFullPath(newPath); logger.info(`rename file: ${oldFullPath} to ${newFullPath}`); if (exists(oldFullPath)) fs.renameSync(oldFullPath, newFullPath); callback.ok(); } catch (err) { callback.fail(); } }); // Create a directory on the server session.on('mkdir', function(path, callback) { try { const fullPath = getFullPath(path); logger.info(`mkdir: ${fullPath}`); if (!exists(fullPath)) fs.mkdirSync(fullPath); callback.ok(); } catch (err) { callback.fail(); } }); // Remove a directory on the server session.on('rmdir', function(path, callback) { try { const fullPath = getFullPath(path); logger.info(`rmdir: ${fullPath}`); if (exists(fullPath)) { deleteAllFilesInDirectory(fullPath); fs.rmdirSync(fullPath); } callback.ok(); } catch (err) { callback.fail(); } }); // Get file or directory information session.on('stat', function(path, statkind, statresponder) { const fullPath = getFullPath(path); if (!exists(fullPath)) { statresponder.nofile(); // Tells the statter that no file exists. return; } try { const stats = fs.lstatSync(fullPath); if (stats.isFile()) { statresponder.is_file(); } else { statresponder.is_directory(); } statresponder.permissions = stats.mode; // Octal permissions, like what you'd send to a chmod command statresponder.uid = stats.uid; // User ID that owns the file. statresponder.gid = stats.gid; // Group ID that owns the file. statresponder.size = stats.size; // File size in bytes. statresponder.atime = stats.atime; // Created at (unix style timestamp in seconds-from-epoch). statresponder.mtime = stats.mtime; // Modified at (unix style timestamp in seconds-from-epoch). statresponder.file(); // Tells the statter to actually send the values above down the wire. } catch (err) {} }); }); }); srv.on('error', function() { return logger.info('Encountered an error'); }); srv.on('end', function() { return logger.info('User disconnected'); }); } module.exports.createSFTPServer = createSFTPServer;