UNPKG

eximanager

Version:

Virtual domains/users manager for Exim

538 lines (450 loc) 15.6 kB
'use strict' var fs = require('fs'), q = require('q'), path = require('path'), mkdirp = require('mkdirp'), userid = require('userid'), bcrypt = require('bcrypt'); function FileManager(serviceLocator) { this.sl = serviceLocator; this.sl.set('file-manager', this); }; module.exports = FileManager; FileManager.prototype.checkDir = function (dirname) { var config = this.sl.get('config'), defer = q.defer(); if (fs.existsSync(dirname)) { var stat = fs.statSync(dirname); if (!stat.isDirectory()) { console.error("Error:\tTarget is not a directory: " + dirname); defer.reject(); return promise; } defer.resolve(); return defer.promise; } mkdirp(dirname, { mode: config['dir_mode'] }, function (err) { if (err) { console.error("Error:\tFailed to create directory: " + dirname); defer.reject(err); return; } if (process.getuid() != 0) { defer.resolve(); return; } fs.chown(dirname, userid.uid(config['user']), userid.gid(config['group']), function (err) { if (err) { console.error("Error:\tFailed to change owner to: " + config['user'] + ':' + config['group']); defer.reject(); return; } defer.resolve(); }); }); return defer.promise; }; FileManager.prototype.checkFile = function (filename) { var config = this.sl.get('config'), defer = q.defer(); if (fs.existsSync(filename)) { var stat = fs.statSync(filename); if (!stat.isFile()) { console.error("Error:\tTarget is not a file: " + filename); defer.reject(); return promise; } defer.resolve(); return defer.promise; } this.checkDir(path.dirname(filename)) .then(function () { fs.open(filename, "a", config['file_mode'], function (err, fd) { if (err) { console.error("Error:\tFailed to create file: " + filename); defer.reject(err); return; } fs.closeSync(fd); if (process.getuid() != 0) { defer.resolve(); return; } fs.chown(filename, userid.uid(config['user']), userid.gid(config['group']), function (err) { if (err) { console.error("Error:\tFailed to change owner to: " + config['user'] + ':' + config['group']); defer.reject(); return; } defer.resolve(); }); }); }) .catch(function () { defer.reject(); }); return defer.promise; }; FileManager.prototype.iterateDir = function (dir) { var defer = q.defer(); fs.readdir(dir, function (err, files) { if (err) { console.error("Error:\tFailed to read directory"); defer.reject(err); return; } var result = []; files .sort() .forEach(function (file) { var stats = fs.statSync(dir + '/' + file); result.push({ name: file, stats: stats, }); }); defer.resolve(result); }); return defer.promise; }; FileManager.prototype.countLines = function (filename) { var defer = q.defer(); if (!fs.existsSync(filename)) { defer.resolve(0); return defer.promise; } var count = 0; fs.createReadStream(filename) .on('data', function (chunk) { for (var i = 0; i < chunk.length; ++i) { if (chunk[i] == "\n".charCodeAt(0)) count++; } }) .on('error', function (err) { console.error('Error\tFailed to read file: ' + filename); defer.reject(err); }) .on('end', function() { defer.resolve(count); }); return defer.promise; }; FileManager.prototype.lookup = function (filename, key) { var defer = q.defer(); if (!fs.existsSync(filename)) { defer.resolve(""); return defer.promise; } fs.readFile(filename, 'utf-8', function (err, data) { if (err) { console.error("Error:\tFailed to read file: " + filename); defer.reject(err); return; } var lines = data.split("\n"); for (var i = 0; i < lines.length; i++) { var fields = lines[i].split(":"); if (fields[0].trim() == key) { fields.shift(); defer.resolve(fields.join(":").trim()); return; } } defer.resolve(""); }); return defer.promise; }; FileManager.prototype.copyFile = function (source, target) { var defer = q.defer(); var rd = fs.createReadStream(source); rd.on("error", function (err) { console.error("Error:\tFailed to read file: " + source); defer.reject(err); }); var wr = fs.createWriteStream(target); wr.on("error", function(err) { console.error("Error:\tFailed to write file: " + target); defer.reject(err); }); wr.on("close", function(ex) { defer.resolve(); }); rd.pipe(wr); return defer.promise; }; FileManager.prototype.readSimpleFile = function (filename) { var defer = q.defer(); if (!fs.existsSync(filename)) { defer.resolve([]); return defer.promise; } fs.readFile(filename, 'utf-8', function (err, data) { if (err) { console.error("Error:\tFailed to read file: " + filename); defer.reject(err); return; } var result = []; var lines = data.split("\n"); for (var i = 0; i < lines.length; i++) { if (lines[i].trim() == "") continue; var fields = lines[i].split(":"); for (var j = 0; j < fields.length; j++) fields[j] = fields[j].trim(); result.push(fields); } defer.resolve(result); }); return defer.promise; }; FileManager.prototype.writeSimpleFile = function (filename, key, value) { var defer = q.defer(); this.checkFile(filename) .then(function () { fs.readFile(filename, 'utf-8', function (err, data) { if (err) { console.error("Error:\tFailed to read file: " + filename); defer.reject(err); return; } var result = "", found = false; var lines = data.split("\n"); for (var i = 0; i < lines.length; i++) { if (lines[i].trim() == "") continue; var columns = lines[i].split(':'); var thisKey = columns.shift().trim(); var thisValue = columns.join(':').trim(); if (thisKey == key) { found = true; thisValue = value; } result += thisKey + ':' + thisValue + "\n"; } if (!found) result += key + ':' + value + "\n"; fs.writeFile(filename, result, 'utf-8', function (err) { if (err) { console.error("Error:\tFailed to write file: " + filename); defer.reject(err); return; } defer.resolve(); }); }); }); return defer.promise; }; FileManager.prototype.readPasswordFiles = function (dirname) { var me = this, defer = q.defer(); q.all([ this.readSimpleFile(dirname + '/master.passwd'), this.readSimpleFile(dirname + '/passwd'), ]).then(function (files) { var result = new Array(files[0].length), tasks = []; for (var i = 0; i < files[0].length; i++) { var found = false; for (var j = 0; j < files[1].length; j++) { if (files[0][i][0] == files[1][j][0]) { found = true; break; } } if (!found) console.error("Error:\tUser " + files[0][i][0] + " exists in 'master.passwd' only"); result[i] = [ files[0][i][0], "" ]; (function (index) { var task = q.defer(); tasks.push(task.promise); me.lookup(dirname + '/quota', files[0][index][0]) .then(function (value) { result[index][1] = value; task.resolve(); }) .catch(function (err) { task.reject(err); }); })(i); } for (var i = 0; i < files[1].length; i++) { var found = false; for (var j = 0; j < files[0].length; j++) { if (files[0][i][0] == files[1][j][0]) { found = true; break; } } if (!found) console.error("Error:\tUser " + files[1][i][0] + " exists in 'passwd' only"); } q.all(tasks) .then(function () { defer.resolve(result); }) .catch(function (err) { defer.reject(err); }); }).catch(function (err) { defer.reject(err); }); return defer.promise; }; FileManager.prototype.writePasswordFiles = function (dirname, account, password) { var me = this, config = this.sl.get('config'), defer = q.defer(); var passwordDefer = q.defer(); if (password) { bcrypt.genSalt(10, function (err, salt) { if (err) { console.error('Error:\tFailed to generate salt'); passwordDefer.reject(); defer.reject(err); return; } bcrypt.hash(password, salt, function (err, hash) { if (err) { console.error('Error:\tFailed to calculate hash'); passwordDefer.reject(); defer.reject(err); return; } passwordDefer.resolve(hash); }); }); } else { me.lookup(dirname + '/master.passwd', account) .then(function (prev) { if (prev.trim().length == 0) { console.error("Error:\tPassword switch (-p) must be set for new accounts"); passwordDefer.reject(); defer.reject(); return; } var parts = prev.split(':'); passwordDefer.resolve(parts[0]); }) .catch(function (err) { passwordDefer.reject(err); defer.reject(err); }); } passwordDefer.promise .then(function (password) { var master = [ password, userid.uid(config['user']), userid.gid(config['group']), "", "0", "0", "User &", config['data_dir'] + '/' + path.basename(dirname) + '/' + account, "/sbin/nologin" ]; var passwd = [ "*", userid.uid(config['user']), userid.gid(config['group']), "User &", config['data_dir'] + '/' + path.basename(dirname) + '/' + account, "/sbin/nologin" ]; q.all([ me.writeSimpleFile(dirname + '/master.passwd', account, master.join(':')), me.writeSimpleFile(dirname + '/passwd', account, passwd.join(':')), ]).then(function () { defer.resolve(); }).catch(function (err) { defer.reject(err); }); }) .catch(function (err) { defer.reject(err); }); return defer.promise; }; FileManager.prototype.rmDir = function (dirname) { var me = this, defer = q.defer(); if (!fs.existsSync(dirname)) { defer.resolve(); return defer.promise; } this.iterateDir(dirname) .then(function (files) { var tasks = []; files.forEach(function (item) { var thisFile = dirname + '/' + item.name; if (item.stats.isDirectory()) { tasks.push(me.rmDir(thisFile)); return; } var task = q.defer(); tasks.push(task.promise); fs.unlink(thisFile, function (err) { if (err) { console.error("Error:\tFailed to delete file: " + thisFile); task.reject(err); return; } task.resolve(); }); }); q.all(tasks) .then(function () { fs.rmdir(dirname, function (err) { if (err) { console.error("Error:\tFailed to delete directory: " + dirname); defer.reject(err); return; } defer.resolve(); }); }) .catch(function (err) { defer.reject(err); }); }) .catch(function (err) { defer.reject(err); }); return defer.promise; }; FileManager.prototype.rmKey = function (filename, key) { var defer = q.defer(); this.checkFile(filename) .then(function () { fs.readFile(filename, 'utf-8', function (err, data) { if (err) { console.error("Error:\tFailed to read file: " + filename); defer.reject(err); return; } var result = ""; var lines = data.split("\n"); for (var i = 0; i < lines.length; i++) { if (lines[i].trim() == "") continue; var columns = lines[i].split(':'); var thisKey = columns.shift().trim(); var thisValue = columns.join(':').trim(); if (thisKey == key) continue; result += thisKey + ':' + thisValue + "\n"; } fs.writeFile(filename, result, 'utf-8', function (err) { if (err) { console.error("Error:\tFailed to write file: " + filename); defer.reject(err); return; } defer.resolve(); }); }); }); return defer.promise; };