You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

234 lines
5.4 KiB

'use strict';
var path = require('path')
, Stream = require('stream').Stream
, split = require('split2')
, util = require('util')
, defaultPort = 5432
, isWin = (process.platform === 'win32')
, warnStream = process.stderr
;
var S_IRWXG = 56 // 00070(8)
, S_IRWXO = 7 // 00007(8)
, S_IFMT = 61440 // 00170000(8)
, S_IFREG = 32768 // 0100000(8)
;
function isRegFile(mode) {
return ((mode & S_IFMT) == S_IFREG);
}
var fieldNames = [ 'host', 'port', 'database', 'user', 'password' ];
var nrOfFields = fieldNames.length;
var passKey = fieldNames[ nrOfFields -1 ];
function warn() {
var isWritable = (
warnStream instanceof Stream &&
true === warnStream.writable
);
if (isWritable) {
var args = Array.prototype.slice.call(arguments).concat("\n");
warnStream.write( util.format.apply(util, args) );
}
}
Object.defineProperty(module.exports, 'isWin', {
get : function() {
return isWin;
} ,
set : function(val) {
isWin = val;
}
});
module.exports.warnTo = function(stream) {
var old = warnStream;
warnStream = stream;
return old;
};
module.exports.getFileName = function(rawEnv){
var env = rawEnv || process.env;
var file = env.PGPASSFILE || (
isWin ?
path.join( env.APPDATA || './' , 'postgresql', 'pgpass.conf' ) :
path.join( env.HOME || './', '.pgpass' )
);
return file;
};
module.exports.usePgPass = function(stats, fname) {
if (Object.prototype.hasOwnProperty.call(process.env, 'PGPASSWORD')) {
return false;
}
if (isWin) {
return true;
}
fname = fname || '<unkn>';
if (! isRegFile(stats.mode)) {
warn('WARNING: password file "%s" is not a plain file', fname);
return false;
}
if (stats.mode & (S_IRWXG | S_IRWXO)) {
/* If password file is insecure, alert the user and ignore it. */
warn('WARNING: password file "%s" has group or world access; permissions should be u=rw (0600) or less', fname);
return false;
}
return true;
};
var matcher = module.exports.match = function(connInfo, entry) {
return fieldNames.slice(0, -1).reduce(function(prev, field, idx){
if (idx == 1) {
// the port
if ( Number( connInfo[field] || defaultPort ) === Number( entry[field] ) ) {
return prev && true;
}
}
return prev && (
entry[field] === '*' ||
entry[field] === connInfo[field]
);
}, true);
};
module.exports.getPassword = function(connInfo, stream, cb) {
var pass;
var lineStream = stream.pipe(split());
function onLine(line) {
var entry = parseLine(line);
if (entry && isValidEntry(entry) && matcher(connInfo, entry)) {
pass = entry[passKey];
lineStream.end(); // -> calls onEnd(), but pass is set now
}
}
var onEnd = function() {
stream.destroy();
cb(pass);
};
var onErr = function(err) {
stream.destroy();
warn('WARNING: error on reading file: %s', err);
cb(undefined);
};
stream.on('error', onErr);
lineStream
.on('data', onLine)
.on('end', onEnd)
.on('error', onErr)
;
};
var parseLine = module.exports.parseLine = function(line) {
if (line.length < 11 || line.match(/^\s+#/)) {
return null;
}
var curChar = '';
var prevChar = '';
var fieldIdx = 0;
var startIdx = 0;
var endIdx = 0;
var obj = {};
var isLastField = false;
var addToObj = function(idx, i0, i1) {
var field = line.substring(i0, i1);
if (! Object.hasOwnProperty.call(process.env, 'PGPASS_NO_DEESCAPE')) {
field = field.replace(/\\([:\\])/g, '$1');
}
obj[ fieldNames[idx] ] = field;
};
for (var i = 0 ; i < line.length-1 ; i += 1) {
curChar = line.charAt(i+1);
prevChar = line.charAt(i);
isLastField = (fieldIdx == nrOfFields-1);
if (isLastField) {
addToObj(fieldIdx, startIdx);
break;
}
if (i >= 0 && curChar == ':' && prevChar !== '\\') {
addToObj(fieldIdx, startIdx, i+1);
startIdx = i+2;
fieldIdx += 1;
}
}
obj = ( Object.keys(obj).length === nrOfFields ) ? obj : null;
return obj;
};
var isValidEntry = module.exports.isValidEntry = function(entry){
var rules = {
// host
0 : function(x){
return x.length > 0;
} ,
// port
1 : function(x){
if (x === '*') {
return true;
}
x = Number(x);
return (
isFinite(x) &&
x > 0 &&
x < 9007199254740992 &&
Math.floor(x) === x
);
} ,
// database
2 : function(x){
return x.length > 0;
} ,
// username
3 : function(x){
return x.length > 0;
} ,
// password
4 : function(x){
return x.length > 0;
}
};
for (var idx = 0 ; idx < fieldNames.length ; idx += 1) {
var rule = rules[idx];
var value = entry[ fieldNames[idx] ] || '';
var res = rule(value);
if (!res) {
return false;
}
}
return true;
};